在上一节实验搭建好了基于s3c2440CPU的uboot框架,今天实现SDRAM和串口的支持。
在此之前,先来简单说一下uboot的启动过程:
如果选择从NandFlash启动,s3c2440芯片上电后其内部的nandflash控制器会自动把nandflash中前4k的内容拷贝到垫脚石中,然后从垫脚石中运行第一条指令。但是s3c2440的垫脚石只有4k大,所以在这4k中需要对芯片做初始化,对外部器件nandflash和SDRAM初始化,然后把nandflash中全部的uboot代码拷贝到SDRAM中,接着跳转到SDRAM中运行。
如果选择从NorFlash启动,s3c2440会直接在NorFlash上运行指令,在NorFlash中的代码需要初始化芯片、初始化SDRAM,然后把全部uboot代码拷贝到SDRAM中并跳转到SDRAM中运行。
那么问题来了,有两种启动方式我们拿到一个完全的未经修改的uboot应该选择哪种启动呢?听说(因为目前没有工作经验)NorFlash在嵌入式中已经用得少了,现在主流使用的是SD卡启动和NandFlash启动,那么就选择NandFlash启动吧,移植后期也会增加Nor启动和NorFlash的使用支持。
现在来简单介绍一下uboot的主流程:
从上图中的流程可以知道,uboot先对cpu做初始化,然后初始化SDRAM,然后设置gd_t环境变量,然后把内存中的代码复制到内存中新的位置,那么结合NandFlash启动的原理问题就来了----在内存中被拷贝的uboot代码事先可是没有的,所以可以确定的一点是在4k垫脚石中需要把NandFlash中的uboot代码拷贝到内存中的地址A(这个步骤原生uboot主流程中是没有的,需要我们自己加),然后跳到内存中地址A去执行代码,在A处的代码又会计算出一个新的内存地址B,然后把A处的uboot代码拷贝到地址B去(这个步骤称为relocate重定位),接着又跳转到地址B中的uboot去执行。
uboot的流程知道了,但是现在需要来了解一个隐藏的问题,uboot前4k的代码能完成初始化cpu、初始化NandFlash、SDRAM并把NandFlash中的uboot代码拷贝到SDRAM中吗?如果说纯粹的写代码完成这点工作估计1k代码就够了,但是uboot为了解决在重定位后全局变量和函数的寻址问题,在链接器链接的时候使用了-pie选项(使用命令make V=1可以看到)。我对-pie的理解是这样的:使用这个选项后链接器会把全部uboot代码链接为位置无关码,全局变量、函数入口的寻址全部使用相对地址,而这些相对地址的信息会储存在rel.dyn段(该段真是存在于u-boot.bin中)中(更详细的-pie选项说明请看这篇博文:http://blog.csdn.net/skyflying2012/article/details/37660265)。所以就需要保证rel.dyn段也属于uboot的前4k代码中,那么这4k代码能装下rel.dyn段和初始化硬件和搬移代码吗?答案是不能的,为什么呢?你想想,uboot中存储相对地址信息使用的都是.word类型的空间储存,也就是4字节,4k字节能存1k个相对地址,uboot内部对硬件的支持如此丰富,这个版本的uboot使用make smdk2410_defconfig编译出来的uboot.bin有500多k,1k个相对地址估计是不够的。所以现在问题再次呈现,4k垫脚石也许连rel.dyn段都不够装,如何能实现代码搬移呢?
解决上面的问题,针对s3c2440有两种解决方案,一种是修改原生uboot执行的主流程,在垫脚石中复制代码到内存中时直接就复制到靠近内存顶部的位置C,然后一直都在内存C处执行,取消对uboot进行重定位。
另一种就是使用SPL启动。简单的说SPL启动就是uboot会生成一个u-boot-spl.bin文件,这个文件完成初始化CPU、将uboot从NandFlash拷贝到SDRAM的工作,然后跳转到内存中的uboot继续执行。而生成的u-boot-spl.bin只有1k左右。在本次移植过程中会对两种方案都进行演示。
首先使用SPL方式启动:
首先添加SPL支持,make menuconfig进行配置,输入 /SPL 搜索spl的配置选项在什么位置,可以看到如下信息:
Symbol: SPL [=n]
Type : boolean
Prompt: Enable SPL
Location:
(1) -> Boot images
Defined at Kconfig:110
Depends on: SUPPORT_SPL [=n]
Selected by: TEGRA [=n] && <choice> || ARCH_UNIPHIER [=n] && <choice>
|
可以看到开启SPL在Boot images选项下,于是打开Boot images选项却没有看到关于SPL的选项,所以就需要手动的修改Kconfig文件以支持SPL了。
vim Kconfig +110可以看到:
102 menu "Boot images"
103
104 config SUPPORT_SPL
105 bool
106
107 config SUPPORT_TPL
108 bool
109
110 config SPL
111 bool
112 depends on SUPPORT_SPL
113 prompt "Enable SPL"
114 help
115 If you want to build SPL as well as the normal image, say Y.
|
修改成如下情况:
102 menu "Boot images"
103
104 config SUPPORT_SPL
105 bool "SUPPORT_SPL"
106
107 config SUPPORT_TPL
108 bool
|
保存退出,执行命令make menuconfig
Boot images --->
[*]SUPPORT_SPL
[*]Enable SPL
[] Enable SDRAM location for SPL stack(NEW)
退出后执行:
make distclean
make smdk2440_defconfig
make menuconfig 选择SPL
make
编译出错:
arch/arm/lib/built-in.o: In function `clr_gd':
/home/uboot/u-boot-2015.07-rc3/arch/arm/lib/crt0.S:104: undefined reference to `board_init_f'
make[1]: *** [spl/u-boot-spl] Error 1
make: *** [spl/u-boot-spl] Error 2
|
错误原因是未定义board_init_f函数,在u-boot-spl.bin中调用了board_init_f函数,但链接时没有找到board_init_f函数的实现,所以就报错了。其实在u-boot-spl.bin中我们并不需要board_init_f函数。所以需要修改arch\arm\lib\crt0.S文件,修改如下:(在以后的代码修改中,一个文件中只有贴出来的才是修改了的,没贴出来的就是不需要修改的,修改的时候参照行数,并且修改后的代码会用红色字体)
101 str sp, [r9, #GD_MALLOC_BASE]
102 #endif
103
104 #if ! defined(CONFIG_SPL_BUILD)
105 /* mov r0, #0 not needed due to above code */
106 bl board_init_f
107
108 #endif
109
110 #if ! defined(CONFIG_SPL_BUILD)
111
112 /*
113 * Set up intermediate environment (new sp and gd) and call
|
修改保存后再次make编译成功。ls查看当前路径可以看到新增了一个spl文件夹,里面就有生成的u-boot-spl.bin文件。命令ll spl/u-boot-spl* 可以看到现在的u-boot-spl.bin只有400个字节不到的大小。
前面分析中说过,u-boot-spl.bin文件需要完成初始化cpu、初始化NandFlash、SDRAM并把NandFlash中的uboot代码拷贝到内存中去。现在就来修改uboot使spl.bin完成上述工作。
在流程图中可以看到:在start.S中对cpu做初始化,然后进入lowlevel_init.S中执行lowlevel_init函数初始化SDRAM,接着跳转到board_init_f函数配置gd_t结构体。因为s3c2410和s3c2440的寄存器基本相同,在start.S中对cpu的初始化所用到的寄存器都是相同的,所以start.S中对cpu的初始化代码不需要修改,我们要做的是初始化SDRAM、初始化NandFlash并把uboot代码拷贝到内存中。
进入lowlevel_init.S中查看对SDRAM的初始化是否正确。根据以往的知识可以知道对SDRAM初始化时需要用HCLK配置REFRESH寄存器的,而在lowlevel_init.S中108行确实有对REFRESH寄存器计数器的宏定义。但是,在此之前的代码都是在start.S中,并没有对时钟的初始化,而这个版本的uboot对时钟的初始化是由board_f.c中的board_init_f函数调用board\samsung\smdk2440\smdk2440.c中的board_early_init_f函数完成的,那么现在问题就来了,时钟都尚未初始化就在SDRAM的初始化函数中使用60Mhz的HCLK了,明显这是有问题的,所以需要在SDRAM初始化之前把时钟初始化了。代码修改如下:
board\samsung\smdk2440\smdk2440.c中:
58 struct s3c24x0_gpio * const gpio = s3c24x0_get_base_gpio();
59
60 /* to reduce PLL lock time, adjust the LOCKTIME register */
61 //writel(0xFFFFFF, &clk_power->locktime);
62
63 /* configure MPLL */
64 //writel((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV,
65 // &clk_power->mpllcon);
66
67 /* some delay between MPLL and UPLL */
68 //pll_delay(4000);
69
70 /* configure UPLL */
71 writel((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV,
72 &clk_power->upllcon);
|
在start.S中添加对时钟的初始化:
81 # endif
82
83 /* FCLK:HCLK:PCLK = 1:2:4 */
84 /* default FCLK is 120 MHz ! */
85 /* ldr r0, =CLKDIVN
86 * mov r1, #3
87 * str r1, [r0]
88 */
89
90 /* init MPLL */
91 #define CLK_DIVN 0x4c000014
92 #define DIV_VAL ((2<<1) | (1<<0))
93 #define MPLL_CON 0x4c000004
94 #define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01))
95
96 init_clock:
97
98 ldr r0,=CLK_DIVN
99 ldr r1,=DIV_VAL
100 str r1,[r0]
101
102
103 mrc p15,0,r0,c1,c0,0
104 orr r0,r0,#0xc0000000
105 mcr p15,0,r0,c1,c0,0
106
107 ldr r0,=MPLL_CON
108 ldr r1,=S3C2440_MPLL_400MHZ
109 str r1,[r0]
110
111 #endif /* CONFIG_S3C24X0 */
|
回到lowlevel_init.s中对SDRAM的初始化,可以在116行看到 指令:
ldr r1, =CONFIG_SYS_TEXT_BASE,其中的CONFIG_SYS_TEXT_BASE定义的是从NandFlash拷贝uboot到内存中的地址,默认情况下定义的是0x0,在后面我们需要把这个宏的值修改,所以需要把这条指令修改,修改如下:
115 ldr r0, =SMRDATA
116 /*ldr r1, = CONFIG_SYS_TEXT_BASE */
117 ldr r1, =0x0
118 sub r0,r0,r1
|
另外,在lowlevel_init.s文件中还定义了13个对SDRAM相关的寄存器的配置值,这些值经我测试不需要改动也可以支持mini2440的SDRAM芯片。那么现在SDRAM的初始化就完成了。下面是对NandFlash的初始化和代码拷贝:
在board/samsung/smdk2440/目录下新建nand_read_ll.c文件,文件内容为(NandFlash的初始化原理在以后驱动专题的博客中会详细介绍):
/* NAND FLASH 控制器 */
#define NFCONF (*((volatile unsigned long *)0x4E000000))
#define NFCONT (*((volatile unsigned long *)0x4E000004))
#define NFCMMD (*((volatile unsigned char *)0x4E000008))
#define NFADDR (*((volatile unsigned char *)0x4E00000C))
#define NFDATA (*((volatile unsigned char *)0x4E000010))
#define NFSTAT (*((volatile unsigned char *)0x4E000020))
static void nand_read_ll(unsigned int addr, unsigned char *buf, unsigned int len);
static int isBootFromNorFlash(void)
{
volatile int *p = (volatile int *)0;
int val;
val = *p;
*p = 0x12345678;
if(*p == 0x12345678)
{
/* 写成功,是 nand启动 */
*p = val;
return 0;
}
else
{
/* NOR 不能像内存一样写 */
return 1;
}
}
void nand_init_ll(void)
{
#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0
/* 设置时序 */
NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
/* 使能 NAND Flash控制器,初始化 ECC,禁止片选 */
NFCONT = (1<<4)|(1<<1)|(1<<0);
}
int copy_code_to_sdram(unsigned char *src, unsigned char *dest, unsigned int len)
{
int i = 0;
/* 如果是 NOR启动 */
if(isBootFromNorFlash())
{
while (i < len)
{
dest[i] = src[i];
i++;
}
}
else
{
nand_init_ll();
nand_read_ll((unsigned int)src, dest, len);
}
return 0;
}
void clear_bss(void)
{
extern int __bss_start, __bss_end;
int *p = &__bss_start;
for (; p < &__bss_end; p++)
*p = 0;
}
static void nand_select(void)
{
NFCONT &= ~(1<<1);
}
static void nand_deselect(void)
{
NFCONT |= (1<<1);
}
static void nand_cmd(unsigned char cmd)
{
volatile int i;
NFCMMD = cmd;
for (i = 0; i < 10; i++);
}
static void nand_addr(unsigned int addr)
{
unsigned int col = addr % 2048;
unsigned int page = addr / 2048;
volatile int i;
NFADDR = col & 0xff;
for (i = 0; i < 10; i++);
NFADDR = (col >> 8) & 0xff;
for (i = 0; i < 10; i++);
NFADDR = page & 0xff;
for (i = 0; i < 10; i++);
NFADDR = (page >> 8) & 0xff;
for (i = 0; i < 10; i++);
NFADDR = (page >> 16) & 0xff;
for (i = 0; i < 10; i++);
}
static void nand_wait_ready(void)
{
while (!(NFSTAT & 1));
}
static unsigned char nand_data(void)
{
return NFDATA;
}
static void nand_read_ll(unsigned int addr, unsigned char *buf, unsigned int len)
{
int col = addr % 2048;
int i = 0;
/* 1. 选中 */
nand_select();
while (i < len)
{
/* 2. 发出读命令 00h */
nand_cmd(0x00);
/* 3. 发出地址(分 5步发出) */
nand_addr(addr);
/* 4. 发出读命令 30h */
nand_cmd(0x30);
/* 5. 判断状态 */
nand_wait_ready();
/* 6. 读数据 */
for (; (col < 2048) && (i < len); col++)
{
buf[i] = nand_data();
i++;
addr++;
}
col = 0;
}
/* 7. 取消选中 */
nand_deselect();
}
|
添加了.c文件相应的要对board/samsung/smdk2440/Makefile做修改:
obj-y := smdk2440.o
obj-y += lowlevel_init.o
obj-$(CONFIG_SPL_BUILD) += nand_read_ll.o
|
有了实现NandFlash操作的nand_read_ll.c文件后就需要在spl.bin文件中实现NandFlash的初始化和代码拷贝,在srt0.S文件中修改如下:
101 str sp, [r9, #GD_MALLOC_BASE]
102 #endif
103
104 #if defined(CONFIG_SPL_BUILD)
105 /* Read u-boot from Nandflash to SDRAM address $CONFIG_SYS_TEXT_BASE */
106 ldr r0, =CONFIG_UBOOT_NAND_ADDR /*nand_read_ll() 1nd argument*/
107 ldr r1, =CONFIG_SYS_TEXT_BASE /*nand_read_ll() 2st argument*/
108 ldr r2, =CONFIG_UBOOT_LENGTH /*nand_read_ll() 3rd argument*/
109
110 bl copy_code_to_sdram
111
112 ldr pc, =CONFIG_SYS_TEXT_BASE
113
114 #else
115 /* mov r0, #0 not needed due to above code */
116 bl board_init_f
117
118 #endif
119
120 #if ! defined(CONFIG_SPL_BUILD)
介绍一下copy_code_to_sdram函数的参数:
第一个参数:表示从NandFlash中的什么地址开始拷贝
第二个参数:表示拷贝到内存中的何处
第三个参数:表示需要拷贝的代码的长度
三个参数用到了三个宏,其中两个需要在include/configs/smdk2440.h中定义,一个宏需要修改,在smdk2440.h中修改宏后的效果是这样的:
29 #define CONFIG_SYS_TEXT_BASE 0x30008000 /*修改宏*/
181 #define CONFIG_UBOOT_LENGTH 0x100000 /*直接拷贝1M,多点没关系*/
182 #define CONFIG_UBOOT_NAND_ADDR 0x20000 /*start of u-boot.bin in NAND*/
在上面的CONFIG_UBOOT_NAND_ADDR宏定义的为什么不是0x0而是0x2000呢?这是因为以spl方式启动的话需要把u-boot-spl.bin放在NandFlash的0地址,所以u-boot.bin就需要往后一点存储。
|
到此,uboot的spl已经能够把NandFlash中的uboot代码拷贝到内存中去了,现在执行把整个uboot工程重新编译。
make distclean && makesmdk2440_defconfig && make menuconfig 选择SPL
make
编译成功。现在已经生成u-boot.bin和u-boot-spl.bin了,但是在u-boot-spl.bin中已经把cpu初始化了,所以在u-boot.bin中就不需要再对cpu进行初始化了。所以还要把start.S 中添加条件编译,使生成的u-boot.bin中没有对cpu的初始化。
在start.S文件中修改如下(只添加红色的这两行):
50 bne copyex
51 #endif
52
53 #if defined(CONFIG_SPL_BUILD)
54 #ifdef CONFIG_S3C24X0
55 /* turn off the watchdog */
。。。
。。。
119 bl cpu_init_crit
120 #endif
121
122 #endif /* CONFIG_SPL_BUILD */
123
124 bl _main
|
到此为止,u-boot的spl启动方式已经全部修改完成,执行make编译,然后把u-boot.bin和u-boot-spl.bin下载到NandFlash中,从NandFlash启动就能在串口看到乱码信息了,出现乱码说明开发板已经成功进入内存中的u-boot执行了(因为打印串口信息的代码在board_init_f函数中会被调用,在board_init_f函数之前的代码是没有串口打印的)。
下载u-boot.bin和u-boot-spl.bin下载到NandFlash中需要使用一个可用的u-boot来下载,这就要求你原来的板子上(NandFlash或NorFlas中)必须有一个可用的u-boot,在u-boot启动后通过tftp下载,如果没有路由或者交换机使用的话直接用网线连接开发板和电脑网卡口也是可以的,tftp 下载命令如下:
set serverip 192.168.1.100;set ipaddr 192.168.1.99; tftp 30000000 u-boot-spl.bin;nand erase 0 20000;nand write 30000000 0 20000;nand erase 20000 120000;tftp 30000000 u-boot.bin;nand write 30000000 20000 $filesize
这一串命令可以顺序执行,不需要一条一条的敲
|
从NandFlash启动后看到:
如果按照上面的命令把下载两个.bin文件都下载到NandFlash中去了,然而串口任然没有信息,可以选择使用JTag/JLink工具调试,查看u-boot-spl.bin是否把NandFlash中的代码拷贝到指定的CONFIG_SYS_TEXT_BASE地址去了,我没有JTag只有JLink,如果使用JTag的话根据我使用JLink的思路自行百度相关命令查看。使用JLink连接电脑和开发板,打开J-Link Commander软件。依次敲入命令:
r //重启目标板
mem 0x0 30 //查看0x0处连续0x30个字节的数据 因为是NandFlash启动,所
//以0x0地址是在垫脚石中,使用UltraEdit打开u-boot-spl.bin对比0x0处
//的数据和UltraEdit中的数据是否相同
setpc 0x0 //设置PC指针指向0x0
g //运行PC指向的程序
mem 0x30008000 30 //稍等两秒后使用mem查看CONFIG_SYS_TEXT_BASE处的代码
//是不是和UltraEdit打开u-boot.bin的代码相同,相同则代表
//代码拷贝成功
|
如果你JLink/JTag都没有,然而串口始终没有打印信息,那么你可以把我修改好的工程下载下来对比上述工程中修改过的文件,推荐使用文本对比工具Araxis Merge(这是神器)。或者打开芯片手册配置寄存器自己写代码实现串口输出,把内存中的数据输出到串口就可以看到复制是否成功,同样的,还可以使用LED做判断。
功能完善,支持nor和NandFlash启动的u-boot:http://download.csdn.net/detail/doccode/8914859