(嵌入式开发)自己写bootloader之编写第一阶段


最简单的bootloader的编写步骤:
1. 初始化硬件:关看门狗、设置时钟、设置SDRAM、初始化NAND FLASH
2. 如果bootloader比较大,要把它重定位到SDRAM
3. 把内核从NAND FLASH读到SDRAM
4. 设置"要传给内核的参数"
5. 跳转执行内核


改进:
1. 提高CPU频率, 200MHZ ==> 400MHZ
2. 启动ICACHE

重定位
分为nor启动和nand启动

int isBootFromNorFlash(void)
{
volatile int *p = (volatile int *)0;
int val;


val = *p;
*p = 0x12345678;
if (*p == 0x12345678)
{       
/* 写成功, 是nand启动 */
//nand启动时,0地址对应内存,内存是可以写的
*p = val;//回复原来的值
return 0;
}
else
{
/* NOR不能像内存一样写 */
return 1;
}
}



TACLS、TWRPH0 、TWRPH1的设置

nand_read
读页数据读到页寄存器
nand结构







bootloader的最终目的是启动内核,而在启动内核前要进行一系列的初始化:
关闭看门狗、改变系统时钟、初始化存储控制器、重定位代码(将更多的代码复制到内存中去)等,
然后将内核从nand flash读到SDRAM中,为内核传递启动参数,跳到相应的地址启动内核。
<pre name="code" class="objc" style="widows: 1;">#define S3C2440_MPLL_200MHZ     ((0x5c<<12)|(0x01<<4)|(0x02))
#define MEM_CTL_BASE    0x48000000

<pre name="code" class="objc" style="widows: 1;">.text                 //指定了后续编译出来的内容放在代码段【可执行】;
.global _start  //告诉编译器后续跟的是一个全局可见的名字【可能是变量,也可以是函数名】;
_start:            /*_start是一个函数的起始地址,也是编译、链接后程序的起始地址。由于程序是通过加载器来加载的,
                       必须要找到 _start名字的函数,因此_start必须定义成全局的,以便存在于编译后的全局符合表中,
                       供其它程序【如加载器】寻找到。*/
1. 关闭看门狗
向WTCON寄存器WTCON中写入零
汇编代码:
ldr r0, =0x53000000
mov r1, #0
str r1, [r0]
C代码:(调用C代码之前必须先设置栈,即sp指针,指令mov sp, #4096)
#define WTCON (*(volatile unsigned long *)0x53000000)
void disable_watch_dog(void)
{
WTCON = 0; // 关闭WATCHDOG很简单,往这个寄存器写0即可
}
2. 设置系统时钟
汇编代码:
#define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01))

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_200MHZ */
ldr r0, =0x4c000004
ldr r1, =S3C2440_MPLL_400MHZ
str r1, [r0]

C代码:
void clock_init(void)
{
// LOCKTIME = 0x00ffffff; // 使用默认值即可
CLKDIVN = 0x03; // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1
// 潜入汇编的写法,语法上的要求。
/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
__asm__(
"mrc p15, 0, r1, c1, c0, 0
" /* 读出控制寄存器 */ 
"orr r1, r1, #0xc0000000
" /* 设置为“asynchronous bus mode” */
"mcr p15, 0, r1, c1, c0, 0
" /* 写入控制寄存器 */
);
/*******************************************************************
 *                 时钟初始化函数
 * 对于MPLLCON寄存器,[19:12]为MDIV,[9:4]为PDIV[1:0]为SDIV
 * 计算公式如下:
 * S3C2410 : MPLL(FCLK)=(m*fin)/(p*2^s)
 * S3C2440 : MPLL(FCLK)=(2*m*Fin)/(p*2^s)
 * 其中:m=MDIV+8;    p=PDIV+2; s=SDIV
 * 设置CLKDIVN,令分频比为:FCLK:HCLK:PCLK=1:2:4
 * 由于开发板的输入时钟为12MHz,而且设置MDIV PDIV SDIV分别为
 * S3C2410 : MDIV=0x5C      PDIV=0x04    SDIV=0x00
 * S3C2440 :MDIV=0x12   PDIV=0x01    SDIV=0x02
 * 则有:FCLK=200MHz      HCLK=100MHz   PCLK=50MHz
*******************************************************************/

MPLLCON = S3C2440_MPLL_200MHZ; /* 现在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */

}
//3. 初始化SDRAM
汇编代码:
ldr r0, =MEM_CTL_BASE
adr r1, sdram_config /* sdram_config的当前地址 */
add r3, r0, #(13*4)
1:
ldr r2, [r1], #4//将r1地址中的内容存到r2中,同时r1=r1+4
str r2, [r0], #4//将r2中的值存到r0所指定的地址中, 同时r0=r0+4
cmp r0, r3 // 比较r0和r1的值
bne 1b // bne 表示如果不相同跳转的标号为1的地方,后面跟一个b表示跳转到前面的1标号,如果跳转到后面去将b改为f即可

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
C代码:

void memsetup(void)
{
volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;

/* 这个函数之所以这样赋值,而不是像前面的实验(比如mmu实验)那样将配置值
* 写在数组中,是因为要生成”位置无关的代码”,使得这个函数可以在被复制到
* SDRAM之前就可以在steppingstone中运行
*/
/* 存储控制器13个寄存器的值 */
p[0] = 0x22011110; //BWSCON
p[1] = 0x00000700; //BANKCON0
p[2] = 0x00000700; //BANKCON1
p[3] = 0x00000700; //BANKCON2
p[4] = 0x00000700; //BANKCON3 
p[5] = 0x00000700; //BANKCON4
p[6] = 0x00000700; //BANKCON5
p[7] = 0x00018005; //BANKCON6
p[8] = 0x00018005; //BANKCON7

/* REFRESH,
* HCLK=12MHz: 0x008C07A3,
* HCLK=100MHz: 0x008C04F4
*/ 
p[9] = 0x008C04F4;
p[10] = 0x000000B1; //BANKSIZE
p[11] = 0x00000030; //MRSRB6
p[12] = 0x00000030; //MRSRB7
}
/*
* 初始化SDRAM后,必须重新设置栈,且将sp指针内存的指向最高,因为栈是重高地址向低地址向下增长的,
* 即使用命令ldr sp, =0x34000000 (将0x34000000赋值给sp指针,ldr是一条伪指令,当0x34000000数字很大的时候不能转换为一个立即数的时候,会通过几条汇编指令来完成) 
*/
4. 初始化nand控制器
bl nand_init // 汇编调用C函数

/* 初始化NAND Flash */
void nand_init(void)
{
// 这三个值结合S3C2440手册和nand flash手册设置时序
#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);
}

//5. 重定位代码
// 汇编中调用C函数时,r1传递函数的第一个参数,r2传递函数的第二个参数,r3传递函数的第三个参数
mov r0, #0//从0地址开始复制
ldr r1, =_start // 来自汇编代码的第一行
// .text
// .global _start
// _start:
ldr r2, =__bss_start // __bss_start 来自链接脚本
sub r2, r2, r1

bl copy_code_to_sdram

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);
}
}

void nand_select(void)
{
NFCONT &= ~(1<<1); 
}

void nand_deselect(void)
{
NFCONT |= (1<<1); 
}

void nand_cmd(unsigned char cmd)
{
volatile int i;
NFCMMD = cmd;
for (i = 0; i < 10; i++);
}

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++); 
}

void nand_wait_ready(void)
{
while (!(NFSTAT & 1));
}

unsigned char nand_data(void)
{
return NFDATA;
}

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();
}

链接脚本为:
SECTIONS {
. = 0x33f80000; // 起始链接地址
.text : { *(.text) } // 代码段
. = ALIGN(4); // 四字节对齐

.rodata : {*(.rodata*)} // 只读数据段
. = ALIGN(4);

.data : { *(.data) } // 数据段
. = ALIGN(4);

__bss_start = .; //bss段开始地址
.bss ALIGN(4) : { *(.bss) *(COMMON) }
__bss_end = .; //bss段结束地址
}



原文地址:https://www.cnblogs.com/Zyf2016/p/6337830.html