Tiny6410 U-boot移植

前言

最近手头上的活松活了下来,趁着有闲工夫先自己又复习了一下一些基础知识,顺便深入了解了一下一些调试技巧,例如栈回溯的原理。想了想部门正在热火朝天的搞Android和Linux移植工作,又看了看我那吃灰已久的ARM 11开发板,也正好拿出来搞一搞。手头上的ARM 11的开发板是友善之臂的Tiny6410系列,当初读大三买来捣鼓了一学期,系统移植方面的东西初略接触了一点,算是入了个门吧。其实也就是跟着开发板提供的源码,自己在另外一份上修修改改,虽然系统是跑起来了,但是对于一些基础的东西完全是不懂的,这一次就从最开始动手做起。

本文重点不在于知识讲解和移植过程,因为选择的U-boot版本基本上实现了所有的工作,内容也相对较少,更多的是一些步骤的原理性解释。

环境

U-boot版本:U-boot 2010.09

编译器版本:Sourcery CodeBench Lite 2014.05-29

编译系统:Ubuntu 16.04虚拟机

为什么选择这一个U-boot版本?其实也是为了偷懒,因为上面还有ARM 11的支持,不需要自己真正从头开始,毕竟轮子有了还是直接用比较舒服。Tiny6410使用的是三星的S3C6410的芯片,是ARM1176 JZF-S的内核,属于ARM v6架构,详细参数可以参考三星S3C6410X用户手册第一章Overview。

编译器因为我虚拟机里有现成的,就没有再使用友善之臂提供的编译器,不过编译默认的选项-mach=armv5,对编译出来的U-boot没有太大的影响。不过友善提供的编译器,据说是有针对性优化。

正文

准备活动

在开始之前,首先按照U-boot里面给出的SMDK6400的支持,直接先拷贝一下(emmm...好吧我承认我不是从头开始),这里需要注意的是S3C6410基本上是兼容S3C6400的,大部分地方可以不需要修改。修改Makefile,在smdk6400下面直接拷贝修改:

tiny6410_noUSB_config	\
tiny6410_config	:	unconfig
	@mkdir -p $(obj)include $(obj)board/samsung/tiny6410
	@mkdir -p $(obj)nand_spl/board/samsung/tiny6410
	@echo "#define CONFIG_NAND_U_BOOT" > $(obj)include/config.h
	@echo "CONFIG_NAND_U_BOOT = y" >> $(obj)include/config.mk
	@if [ -z "$(findstring tiny6410_noUSB_config,$@)" ]; then			\
		echo "RAM_TEXT = 0x57e00000" >> $(obj)board/samsung/tiny6410/config.tmp;\
	else										\
		echo "RAM_TEXT = 0xc7e00000" >> $(obj)board/samsung/tiny6410/config.tmp;\
	fi
	@$(MKCONFIG) tiny6410 arm arm1176 tiny6410 samsung s3c64xx
	@echo "CONFIG_NAND_U_BOOT = y" >> $(obj)include/config.mk

然后再用相同的手法,在board/samsung和nand_spl/board/samsung按照smdk6400创建tiny6410目录,最后在include/configs下拷贝一份tiny6410.h。

启动流程

这里我并不会详细讲解每一步的启动的过程,只是大概说明一下我们大概需要涉及到添加和修改的地方:

  • arch/arm/cpu/arm1176/u-boot.lds:编译链接文件,ENTRY(_start)声明程序入口为_start

  • arch/arm/cpu/arm1176/start.S & board/samsung/tiny6410/low_init.S:_start-->lowlevel_init-->start_armboot();

  • arch/arm/lib/board.c & board/samsung/tiny6410/tiny6410.c:start_armboot()-->board_init()-->mainloop();

  • common/main.c:mainloop()-->do_bootm()

启动方式

当我们把程序烧录到某个介质中,芯片是如何知道从哪里去加载我们的程序呢?那就要看板子的启动方式了,Tiny6410的板子支持两种启动方式:

1. SD卡启动

2. NAND Flash启动

在板子的右下方由拨码开关决定。但是实际上根据S3C6410的用户手册,是有好几种启动方式的:

上面的这张图来自S3C6410X用户手册System Control章节。图中除了给出了启动方式之外,还指出了芯片的时钟源输入方式。

根据友善之臂提供的Tiny6410-1107-Schmetic.pdf电路原理图,我们可以找到OM[0]的电路:

由于OM[0]前面的R29电阻默认是没有的,所以默认为低电平,即OM[0] = 0,时钟源输入为XXTIpll。

我们从前面的图从左往右看,第一个启动方式的决定项为XSELNAND,然后在原理图上找到它:

由芯片的XSELNAND引脚引出了SELNAND。巧了,刚好又在上一张图里面有这一个,可以看出默认是接的高电平,即SELNAND=1。

然后我们看看第二个条件OM[4:0],由于OM[0]是用来决定时钟源的,这里主要是看OM[4:1],再瞅一眼,正好在刚才OM[0]的旁边,有BOOTSET电路:

好的,默认全部拉高,所以OM[4:1]为1111,这样我们就把犯人锁定在启动方式的下面几项了。最后再看启动方式的最后条件GPN[15:13],对应上面OM[0]图的BOOT_EINT[15-13],这里我就不贴芯片对应的引脚图了,然后得到GPN[15:13]=000。终上所述,板子的默认启动方式为SD/MMC(CH0),即SD卡启动,那么NAND Flash启动呢?这时候我们需要看另外一个文件TinySDK-1312B-Schematic.pdf,由于我的底板是1312版本的,请同学们根据手中实际情况对照。

程序入口

首先我们需要知道程序从哪里开始运行的,而这一步基本上都是固定的,在U-boot的arch目录下根据自己的平台就可以找到一个start.S的文件,但是在上面我写到的启动第一步却是u-boot.lds,这是为什么呢?首先我们需要知道这个lds文件是什么,简单的来说lds就是链接脚本的意思,链接是在程序编译的最后一步,确定了整个程序数据的摆放。而程序又是由不同的段组成的,链接脚本就是用来定义这些段的摆放位置,和指定执行地址。

从ARM 1176的u-boot.lds文件,我们知道程序的入口为_start段,而且里面代码段首先摆放了arch/arm/cpu/arm1176/start.o文件。在这个目录下能编译成start.o的文件,当然是start.S文件。在这个文件中又调用了board/samsung/lowlevel_init.S完成一些板级的初始化任务,什么关狗啊,关中断啊,时钟初始化啊,等等。。。

最后执行到_start_armboot,而该段又被定义为start_armboot,而这个段又定义在board.c文件中。终于看到.c文件了,感觉春天来了,汇编看得脑壳痛,原因还是太菜了。

外围初始化

然后我们接着board.c讲,该文件其实主要任务也是初始化,但是相对于前面的汇编,算是高级的初始化阶段了,在代码最开始的部分,就有一段执行多个初始化函数的部分:

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
    if ((*init_fnc_ptr)() != 0) {
        hang ();
    }
}

而init_sequence就定义在start_armboot()函数上面:

init_fnc_t *init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT)
	arch_cpu_init,		/* basic arch cpu dependent setup */
#endif
	board_init,		/* basic board dependent setup */
#if defined(CONFIG_USE_IRQ)
	interrupt_init,		/* set up exceptions */
#endif
	timer_init,			/* initialize timer */
#ifdef CONFIG_FSL_ESDHC
	get_clocks,
#endif
	env_init,			/* initialize environment */
	init_baudrate,		/* initialze baudrate settings */
	serial_init,		/* serial communications setup */
	console_init_f,		/* stage 1 init of console */
	display_banner,		/* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
	print_cpuinfo,		/* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
	checkboard,		/* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
	init_func_i2c,
#endif
	dram_init,		/* configure available RAM banks */
#if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
	arm_pci_init,
#endif
	display_dram_config,
	NULL,
};

print_cpuinfo函数用于打印CPU时钟信息,定义于arch/arm/cpu/arm1176/speed.c中。

checkboard函数用于打印板级信息,定义于boad/samsung/tiny6410/tiny6410.c中。

borad_init函数用于板级初始化的,也定义在boad/samsung/tiny6410/tiny6410.c文件里。其实这个函数主要功能就是网卡的预初始化和设置机器码,启动参数位置:

int board_init(void)
{
	DECLARE_GLOBAL_DATA_PTR;

	dm9000_pre_init();

	/* NOR-flash in SROM0 */

	/* Enable WAIT */
	SROM_BW_REG |= 4 | 8 | 1;

	gd->bd->bi_arch_number = MACH_TYPE;
	gd->bd->bi_boot_params = PHYS_SDRAM_1 + 0x100;

	return 0;
}

原本的smdk6400这里是cs8900的初始化,这里根据板子实际情况,修改为dm9000的初始化,其实也就把名字改了一下,其他的东西包括定义的值都是没变的:

static void dm9000_pre_init(void)
{
	SROM_BW_REG &= ~(0xf << 4);
	SROM_BW_REG |= (1 << 7) | (1 << 6) | (1 << 4);
	SROM_BC1_REG = ((DM9000_Tacs << 28) + (DM9000_Tcos << 24) +
			(DM9000_Tacc << 16) + (DM9000_Tcoh << 12) +
			(DM9000_Tah << 8) + (DM9000_Tacp << 4) + DM9000_PMC);
}

那么为什么我们需要这样设置呢?当然是从芯片手册和原理图来:

根据原理图,我们的PHY芯片也就是DM9000,是接在片选1上的,对应SROM1。然后对SROM1初始化就是前面对SROM_BW_REG这个寄存器的操作:

然后后面的SROM_BC1_REG寄存器用来设置时序:

  • Tacs为地址开始周期,发送片选信号之前的周期;
  • Tcos为片选开始周期,发送读信号nOE之前的周期;
  • Tacc为访问周期,读写信号的持续周期;
  • Tcoh为片选持续周期,nOE为高后,片选信号的持续周期;
  • Tah为地址持续周期,片选信号为高后,地址信号的持续周期;
  • Tacp为页模式访问周期,仅在读操作使用;
  • PMC为正常页模式配置。

由于SROM是挂在AHB总线上的,需要根据HCLK来计算每一个持续周期。这里我并没有用到网口,所以沿用的之前CS8900的参数。

然后我们大部分的修改点还是在配置文件include/configs/tiny6410.h中:

1. 修改网卡配置,将CS8900的配置修改为DM9000;

2. 修改RAM的大小PHYS_SDRAM_1_SIZE,因为板子上是256MB的内存,原先定义的是128MB;

烧录

当选择iROM启动方式后,芯片将会从启动介质中读取前4K的内容开始执行,当不是iROM启动会拷贝介质的前8K数据执行,这里为了统一还是都用4K作为规范。而我编译好的U-boot大概在190K左右,如果只是拷贝前4K数据,肯定是跑不起来的,这怎么办呢?这时候需要用到另外一种启动方式SPL,我们需要先再准备一个小U-boot去实现从介质中拷贝程序到内存这个任务。选择的2010.09的版本,已经有了nand_spl的支持。在编译好了过后,在nand_spl目录下将会生成u-boot-spl.bin文件,这个就是我们需要的小程序啦。

但是光有这个小程序也没有用,我们需要运行的是根目录下的u-boot.bin。在手上只有一张SD卡的情况下,我们可以选择使用友善提供的superboot去烧写的u-boot。但是这个问题又来了,友善提供的superboot烧写程序是固定的三段式的,即U-boot,Kernel和FileSystem。现在我们u-boot分成两个部分了,这可如何是好?不要着急,之前不是说过spl程序就算为了加载后面真正的u-boot的嘛,那我们只需要把真正的u-boot放在它加载的位置不就好了。

在board/samsung/tiny6410/u-boot.lds文件中,定义了加载的代码段有start.o,cpu_init.o和nand_boot.o,而最后的nand_boot.o就是用来搬运NAND Flash数据用的。我们打开同目录下的nand_boot.c文件,直接看到这一行:

/*
 * Load U-Boot image from NAND into RAM
 */
ret = nand_load(&nand_info, CONFIG_SYS_NAND_U_BOOT_OFFS, CONFIG_SYS_NAND_U_BOOT_SIZE, 
        (uchar *)CONFIG_SYS_NAND_U_BOOT_DST);

清楚明了的注释,从NAND加载U-boot镜像到RAM。其中传入的三个宏定义参数定义在include/configs/tiny6410.h文件中:

#define CONFIG_SYS_SDRAM_BASE	0x50000000

#define CONFIG_SYS_NAND_U_BOOT_DST	CONFIG_SYS_PHY_UBOOT_BASE /* NUB load-addr */
#define CONFIG_SYS_NAND_U_BOOT_OFFS	(4 * 1024)   /* Offset to RAM U-Boot image */
#define CONFIG_SYS_NAND_U_BOOT_SIZE	(252 * 1024) /* Size of RAM U-Boot image   */

CONFIG_SYS_NAND_U_BOOT_OFFS:U-boot镜像在NAND Flash中的相对位移地址;

CONFIG_SYS_NAND_U_BOOT_SIZE:U-boot镜像大小;

CONFIG_SYS_NAND_U_BOOT_DST:镜像加载的目标地址。

加载的目标地址又定义为U-boot的物理基地址,然后又是0x50000000,这个值就是我们RAM的起始地址,定义于S3C6410X用户手册的第二章,Memory System Block Diagram。

那么我们只需要把真实的U-boot放到读取的位置就好了。这里有一个办法就是自己将u-boot-spl.bin文件填充到4K大小,然后把u-boot.bin接到后面,合成一个文件,这样使用SD卡只需要烧录这一个软件,就可以让U-boot跑起来:

结尾

花了一天的功夫,简单的把U-boot移植了一下,真的是很简单的那种,实际工作中很多时候其实是要自己从头开始移植的,时钟配置,串口配置,Flash的配置这里我都没有讲到。如果文中提到的地方有误的,欢迎大家指出。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值