开发板:S3C2440
要写bootloader首先要了解bootloader都做了哪些事
1.关看门狗
2.设置时钟
3.初始化内存
4.重定位代码
5.执行main函数
在main函数中
6.初始化串口
7.读内核到内存
8.设置参数
9.跳转执行
关看门狗
ldr r0, =0x53000000
mov r1, #0
str r1, [r0]
关看门狗的代码很简单,0x53000000为看门狗寄存器的地址,向其中写入0关掉看门狗
设置时钟
ldr r0, =0x4c000014
// mov r1, #0x03; // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1
mov r1, #0x05; // FCLK:HCLK:PCLK=1:4:8
str r1, [r0]
/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
mrc p15, 0, r1, c1, c0, 0 /* 读出控制寄存器 */
orr r1, r1, #0xc0000000 /* 设置为“asynchronous bus mode” */
mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */
/* MPLLCON = S3C2440_MPLL_400MHZ */
ldr r0, =0x4c000004
ldr r1, =S3C2440_MPLL_400MHZ
str r1, [r0]
FCLK用于CPU;HCLK用于高性能模块,如存储控制器;PCLK用于低带宽的周边外设之间连接,如UART;
首先设置分频系数,FCLK:HCLK:PCLK=1:4:8
再设置CPU的总线模式,这是手册上规定的
开始上电时,FCLK等于外部晶振频率,我们要用PLL放大频率
这里设置FCLK的时钟为400MHZ,S3C2440_MPLL_400MHZ 的定义为
#define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01))
这个宏的定义是根据外部晶振的频率以及要设置的FCLK,再通过计算公式得出的,计算公式参考2440手册
初始化内存
ldr r0, =MEM_CTL_BASE
adr r1, sdram_config /* sdram_config的当前地址 */
add r3, r0, #(13*4)
1:
ldr r2, [r1], #4
str r2, [r0], #4
cmp r0, r3
bne 1b
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
这里把寄存器的数值放在一段内存中,然后用一个循环把这些数值写到寄存器中
具体的寄存器代表什么意思看2440手册
重定位代码
ldr sp, =0x34000000
bl nand_init
mov r0, #0
ldr r1, =_start
ldr r2, =__bss_start
sub r2, r2, r1
bl copy_code_to_sdram
bl clear_bss
这段代码做了如下几件事
1.设置堆栈指针
2.初始化nand
3.设置传递给函数copy_code_to_sdram的参数,r0为第一个参数,r1为第二个参数,r3为第三个参数
4.调用copy_code_to_sdram重定位代码
5.清除bss段(假设程序中有很多初始值为零的全局变量,如果一个一个设置很浪费资源,我们可以分配一块内存,然后将这段内存清零就可以了,这段内存就是bss段,bss段不存在文件里)
void nand_init(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);
}
设置NFCONF和NFCONT寄存器,初始化nand
/* 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))
这些是有关nand的寄存器
NFCONF为时序相关
NFCONT为控制寄存器
NFCMMD用于接受命令
NFADDR用于接受地址
NFDATA用于接受数据
NFSTAT为状态寄存器
拷贝从nand的0地址拷贝,所以r0为0
目的为连接地址,也就是_start,所以r1为_start
长度为bss段之前的部分,r2为__bss_start减_start
void 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();
nand_read((unsigned int)src, dest, len);
}
}
判断是nor启动还是nand启动,这里我们认为是nand
void nand_read(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();
}
将数据读到内存,,具体nand的操作可以参考2440手册和nand手册
void clear_bss(void)
{
extern int __bss_start, __bss_end;
int *p = &__bss_start;
for (; p < &__bss_end; p++)
*p = 0;
}
清楚bss段的函数当成语法规则就行了,用的时候复制粘贴
执行main函数
</pre></p><pre name="code" class="cpp"> ldr lr, =halt
ldr pc, =main
halt:
b halt
执行main函数,注意这里不能写成bl main
因为bl是相对跳转,而我们要跳转到重定位后的代码中