从零开始构建bootloader
项目步骤:
第一阶段:
1、关看门狗;
2、时钟初始化;
3、内存初始化;
4、NandFlash初始化;
5、代码重定位(将flash中的代码复制到指定的内存地址处,也即代码段链接地址);
6、跳转到main函数;
第二阶段:
7、写main函数,在函数中设置要传给内核的参数;
8、跳转到内核入口,启动内核
9、制作链接脚本
第三阶段:
10、编写Makefile文件;
11、编译、下载、运行
1、编写start.S文件,初始化片上硬件
本文件需要完成的目标:
1.关看门狗
2.设置时钟
3.开启指令缓存,初始化SDRAM
4.重定位(把bootloader本身的代码从flash复制到它的链接地址(c函数编写),然后清空bss段(c函数编写))
5.跳转到main函数。
#define CLKDIVN 0X4C000014 /*设置FCLK:HCLK:PCLK的比例*/
#define MPLLCON 0x4C000004 /*设置FCLK频率*/
#define S3C2440_MPLL_200MHZ ((0x5c<<12)|(0x01<<4)|(0x02))
#define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01))
#define WTCON 0x53000000 /*看门狗寄存器*/
#define BWSCON 0X48000000 /*BANK寄存器*/
.text /*设置代码段*/
.global _start /*定义全局变量,要被链接脚本用到*/
_start: /*_start跳转到这里实现硬件初始化*/
/* 1.关看门狗*/
ldr r0, =WTCON
mov r1, #0
str r1, [r0]
/* 2.设置时钟(必须设为异步总线模式) */
ldr r0, =CLKDIVN
mov r1, #5 /* FCLK:HCLK:PCLK=1:4:8 */
str r1, [r0]
mrc p15, 0, r1, c1, c0, 0 /*设置为asynchronous bus mode*/
orr r1, r1, #0xc0000000
mcr p15, 0, r1, c1, c0, 0
ldr r0, =MPLLCON /* 设置时钟频率FCLK为400MHZ */
ldr r1, =S3C2440_MPLL_400MHZ
str r1, [r0]
/* 3.使能ICache,加快指令访问速度;因为目前没有开启MMU,所以还不能开启数据缓存DCache */
mrc p15, 0, r0, c1, c0, 0 /* read control reg */
orr r0, r0, #(1<<12)
mcr p15, 0, r0, c1, c0, 0 /* write it back */
/* 4.初始化SDRAM */
ldr r0, =BWSCON
adr r1, sdram_config /* 使用adr跳转,因为SDRAM未初始化 */
add r3, r0, #(13*4)
1:
ldr r2, [r1], #4
str r2, [r0], #4
cmp r0, r3
bne 1b /*back to 1 when no equal*/
/* 4.重定位 */
ldr sp, =0x34000000 /*因为SDRAM大小为64MB,所以堆栈指针设在最高地址处*/
bl nand_init
mov r0, #0 /* src = r0 */
ldr r1, =_start /* dest = r1,_start为代码段的起始和链接地址0x30000000 */
ldr r2, =__bss_start
sub r2, r2, r1 /* len = r2,代码段长度*/
bl copy_code_to_sdram /* 调用C函数copy_code_to_sdram(src, dest, len)*/
bl clear_bss /* 清除bss段*/
/* 5.执行main */
ldr lr, =halt
ldr pc, =main
mov pc,lr /* 若main函数跳出后,使PC等于lr链接寄存器,避免程序跑飞 */
halt:
b halt /* 死循环,避免跑飞 */
sdram_config:
.long 0x22011110 //BWSCON
.long 0x00000700 //BANKCON0
.long 0x00000700 //BANKCON1
.long 0x00000700 //BANKCON2
.long 0x00000700 //BANKCON3
.long 0x00000700 //BANKCON4
.long 0x00000700 //BANKCON5
.long 0x00018005 //BANKCON6
.long 0x00018005 //BANKCON7
.long 0x008C04F4 // REFRESH
.long 0x000000B1 //BANKSIZE
.long 0x00000030 //MRSRB6
.long 0x00000030 //MRSRB7
关于时钟频率的设置解释:
将CPU频率设为最大值400MHZ(内核启动时间7S变为6S,因为HCLK和PCLK频率没有改变)
然后分频系数FCLK:HCLK:PCLK需要设置为1:4:8。
因为HCLK最高133MHZ,这里需要设置为100MHZ,PCLK最高50MHZ,所以这里需要设置为50HZ,所以得出 CLKDIVN寄存器需要等于0X5即可。
具体为什么等于0x5,可以参考下图:
通过查看数据手册,得知当FCLK取400MHZ时,设置MDIV为0X5C,PDIV为0x1,SDIV为0x1。
关于Cache的设置解释:
通过高速缓存存储器可以加快对内存的数据访问,在CAHE中有ICAHE(指令缓存)和DCAHE(数据缓存):
ICAHE: 指令缓存,用来存放执行这些数据的指令;
DCAHE:用来存放数据,需要开启MMU才能开启DCAHE。
在没开启ICAHE之前,CPU读取SDRAM地址数据时,每次都需要先访问一次地址值,在读数据.
当开了ICAHE后,第一次读取SDRAM地址数据时,ICAHE发现缓存里没有这个地址数据,然后将SDRAM中需要读取的那部分一大块内存数据都复制在缓存中,后面陆续读取数据就不会再访问SDRAM了,直到CPU没有找到地址数据后ICAHE再从SDRAM中重新复制
通过CP15协处理器来开启ICAHE:ICAHE控制位在CP15的寄存器C1中位12(如下图), 然后通过MRS和MSR向该位12置1,开启ICAHE.所以代码如下(放在SDRAM初始化之前):
mrc p15, 0, r0, c1, c0, 0 //将 CP15 的寄存器 C1 的值读到 r0 中
orr r0, r0, #(1<<12) //将r0中位12置1
mcr p15,0, r0,c1,c0,0 //开启ICAHE
2、编写init.c,用于重定位,bss段清除,初始化NandFlash
(1)编写nand_init()函数
准备知识:
我使用的NandFlash型号为K9F2G08U0M,通过查阅芯片手册获知该flash大小=2048块Block=128KPages=256MB=2Gb 。且其构成为:
1个设备=2048(Block)
1块Block=64(Pages)
1页=(2K+64)(Byte) 因为每个地址里都存放了一个字节,所以用Byte表示,其中64B是存放ECC的OOB地址,(ECC:存放判断位反转的校验码)
写过程:
写页
生成校验码ECC
写校验码到OOB页中
读过程:
读出页数据,并计算当前数据的ECC
读出存在OOB页里的原始ECC
比较两个校验码,相同则读成功,不同则出现了位反转,需重新读取。
确定通信时序:
通过图2和图1可以看出:
tCS:等待芯片使能CE的时间, tCS=20nS
tCLS和tALS:等待WE(写信号)结束的时间, tCLS=tALS=15nS
tWP:WE(写信号)维持时间, tWP=15nS
tALH:等待命令写入成功的时间, tALH=5nS
tCLH:等待地址写入成功的时间, tCLH=5nS
通过查看2440芯片手册,nandflash时序图,需要设置TACLS,TWRPH0和TWRPH1
TACLS:属于等待WE(写信号)就绪的时间,对比图2得出TACLS= tCLS- tWP=0nS
TWRPH0:属于WE(写信号)的时间, 对比图2得出TWRPH0= tWP=15nS
TWRPH1:属于等待命令写入成功的时间,对比图2得出TWRPH1=tALH=tCLH=5nS
在NFCONF寄存器中设置这三个参数
TACLS[13:12] :表示Duration(持续时间)=HCLK*TACLS,由于Duration=0nS,所以TACLS=0
TWRPH0 [10:8] :表示Durati