最簡單的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, =0x53000000mov r1, #0str 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=1mov r1, #0x05; // FCLK:HCLK:PCLK=1:4:8str 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, =0x4c000004ldr r1, =S3C2440_MPLL_400MHZstr 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\n" /* 讀出控制寄存器 */ "orr r1, r1, #0xc0000000\n" /* 設定為“asynchronous bus mode” */"mcr p15, 0, r1, c1, c0, 0\n" /* 寫入控制寄存器 */);
/******************************************************************* * 時鐘初始化函數 * 對於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_BASEadr r1, sdram_config /* sdram_config的當前地址 */
add r3, r0, #(13*4)1:ldr r2, [r1], #4//將r1地址中的內容存到r2中,同時r1=r1+4str r2, [r0], #4//將r2中的值存到r0所指定的地址中, 同時r0=r0+4cmp 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 //MRSRB7C代碼:void memsetup(void){volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;/* 這個函數之所以這樣賦值,而不是像前面的實驗(比如mmu實驗)那樣將配置值* 寫在數組中,是因為要產生”位置無關的代碼”,使得這個函數可以在被複製到* SDRAM之前就可以在steppingstone中運行*//* 儲存控制器13個寄存器的值 */p[0] = 0x22011110; //BWSCONp[1] = 0x00000700; //BANKCON0p[2] = 0x00000700; //BANKCON1p[3] = 0x00000700; //BANKCON2p[4] = 0x00000700; //BANKCON3 p[5] = 0x00000700; //BANKCON4p[6] = 0x00000700; //BANKCON5p[7] = 0x00018005; //BANKCON6p[8] = 0x00018005; //BANKCON7/* REFRESH,* HCLK=12MHz: 0x008C07A3,* HCLK=100MHz: 0x008C04F4*/ p[9] = 0x008C04F4;p[10] = 0x000000B1; //BANKSIZEp[11] = 0x00000030; //MRSRB6p[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, r1bl copy_code_to_sdramvoid 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段結束位址}