一.預備知識:
1. 首先,U-Boot1.3.4還沒有支持s3c2440,移植仍是用2410的文件稍作修改而成的。
2. 2440和2410的區別:
2440和2410的區別主要是2440的主頻更高,增加了攝像頭接口和AC‘97音頻接口;寄存器方面,除了新增模 塊的寄存器外,移植所要注意的是NAND FlASH控制器的寄存器有較大的變化、芯片的時鐘頻率控制寄存器(芯片PLL的寄存器)有一定的變化。其他寄存器基本是兼容的。
3. 你開發板的boot方式是什么,開發板上電以后是怎么執行的。
一般來說三星的開發板有三種啟動方式:nand、nor、ram。
具體用那一種方式來啟動決定于CPU的0M[0:1]這兩個引腳,具體請參考S3C2440的datasheet
nand:對于2440來說,CPU是不給nand-flash分配地址空間的,nand-flash只相當于CPU的一個外設,S3C2440做了一個從nand-flash啟動的機制。開發板一上電,CPU就自動復制
nand-flash里面的前4K-Bytes內容到S3C2440內部集成的SDRAM,然后把4K內容所在 的RAM映射到S3C2440的0地址,從0地址開始執行。這4K的內容主要負責下面這些工 作:初始化中斷矢量、設定CPU的工作模式為SVC32模式、屏蔽看門狗、屏蔽中斷、 初始化時鐘、把整個u-boot重定向到外部SDRAM、跳到主要的C函數入口。
nor: 早期的時候利用nor-flash啟動的方式比較多,就是把u-boot燒寫到nor-flash里面, 直接把nor-flash映射到S3C2440的0地址,上電從0地址開始執行。
ram: 直接把u-boot放到外部SDRAM上跑,這一般debug時候用到。
4. u-boot程序的入口地址問題
要理解程序的入口地址,自然想到的是連接文件,首先看看開發板相對于某個開發板的連接文件"/board/你的開發板/u-boot.lds",看一個2410的例子:
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
(1) 從ENTRY(_start)可以看出u-boot的入口函數是_start,這個沒錯
(2) 從. = 0x00000000也許可以看出_start的地址是0x00000000,事實并不是這樣的,這里的0x00000000沒效,在連接的時候最終會被TETX_BASE所代替的,具體請參考u-boot根目錄下的config.mk.
(3) 網上很多說法是 _start=TEXT_BASE,我想這種說法也是正確的,但沒有說具體原因。
本人的理解是這樣的,TEXT_BASE表示text段的起始地址,而從
.text :
{
cpu/arm920t/start.o (.text)
*(.text)
}
看,放在text段的第一個文件就是start.c編譯后的內容,而start.c中的第一個函數就是
_start,所以 _start應該是放在text段的起始位置,因此說_start=TEXT_BASE也不為過。
5. 一直不明白的U-BOOT是怎樣從4Ksteppingstone跳到RAM中執行的,現在終于明白了。關鍵在于:
ldr pc, _start_armboot
_start_armboot: .word start_armboot
這兩條語句,ldr pc, _start_armboot指令把_start_armboot這個標簽的地方存放的內容(也即是start_armboot)移到PC寄存器里面,start_armboot是一個函數地址,在編譯的時候給分配了一個絕對地址,所以上面語句實際上是完成了一個絕對地址的跳轉。而我一直不明白的為什么在start.S里面有很多BL,B跳轉語句都沒有跳出4Ksteppingstone,原因是他們都是相對于PC的便宜的跳轉,而不是絕對地址的跳轉。還有要補充一下LDR,MOV,LDR偽指令的區別。
LDR R0,0x12345678 //把地址0x12345678存放的內容放到R0里面
MOV R0,#x //把立即數x放到R0里面,x必須是一個8 bits的數移到偶數次得到的數。
LDR R0,=0x12345678 //把立即數0x12345678放到R0里面
6. 在移植u-boot-1.3.3以上版本的時候要注意:
在u-boot1.3.3及以上版本Makefile有一定的變化,使得對于24x0處理器從nand啟動的遇到問題。也就是網上有人說的:無法運行過lowlevel_init。其實這個問題是由于編譯器將我們自己添加的用于nandboot的子函數nand_read_ll放到了4K之后造成的(到這不理解的話,請仔細看看24x0處理器nandboot原理)。我是在運行失敗后,利用mini2440的4個LED調試發現u-boot根本沒有完成自我拷貝,然后看了uboot根目錄下的System.map文件就可知道原因。
解決辦法其實很簡單:
將__LIBS := $(subst $(obj),,$(LIBS)) $(subst $(obj),,$(LIBBOARD))
改為__LIBS := $(subst $(obj),,$(LIBBOARD)) $(subst $(obj),,$(LIBS))
7. 然后說一下跳轉指令。ARM有兩種跳轉方式。
(1)mov pc <跳轉地址〉
這種向程序計數器PC直接寫跳轉地址,能在4GB連續空間內任意跳轉。
(2)通過B BL BLX BX可以完成在當前指令向前或者向后32MB的地址空間的跳轉(為什么是32MB呢?寄存器是32位的,此時的值是24位有符號數,所以32MB)。
B是最簡單的跳轉指令。要注意的是,跳轉指令的實際值不是絕對地址,而是相對地址——是相對當前PC值的一個偏移量,它的值由匯編器計算得出。
BL非常常用。它在跳轉之前會在寄存器LR(R14)中保存PC的當前內容。BL的經典用法如下:
bl NEXT ; 跳轉到NEXT
……
NEXT
……
mov pc, lr ; 從子程序返回。
二.開始上機移植:(紅色字體為添加的內容,藍色字體為修改的內容,下同)
給自己的開發板取名為qljt2440。
1. 隨便找個目錄解壓u-boot,
$tar –xjvf u-boot-1.3.4.tar.gz2
2. 進入u-boot目錄修改Makefile (你要編譯u-boot那當然少不了配置啦)
$cd u-boot-1.3.4
[uboot@localhost u-boot-1.3.4]$ vim Makefile 修改內容如下:
__LIBS := $(subst $(obj),,$(LIBS)) $(subst $(obj),,$(LIBBOARD))
改為
__LIBS := $(subst $(obj),,$(LIBBOARD)) $(subst $(obj),,$(LIBS))
sbc2410x_config: unconfig @$(MKCONFIG) $(@:_config=) arm arm920t sbc2410x NULL s3c24x0
qljt2440_config : unconfig @$(MKCONFIG) $(@:_config=) arm arm920t qljt2440 qljt s3c24x0
/*
各項的意思如下:
qljt2440_config : 這個名字是將來你配置板子時候用到的名字,參見make qljt2440_config命令。
arm: CPU的架構(ARCH)
arm920t: CPU的類型(CPU),其對應于cpu/arm920t子目錄。
qljt2440: 開發板的型號(BOARD),對應于board/qljt/qljt2440目錄。
qljt: 開發者/或經銷商(vender)。 s3c24x0: 片上系統(SOC)。
*/
4. 在/board子目錄中建立自己的開發板qljt2440目錄
由于我在上一步板子的開發者/或經銷商(vender)中填了 qljt ,所以開發板qljt2440目錄一定要建在/board子目錄中的qljt目錄下 ,否則編譯會出錯。
[uboot@localhost u-boot-1.3.4]$ cd board
[uboot@localhost board]$ mkdir qljt qljt/qljt2440
[uboot@localhost board]$ cp -arf sbc2410x/* qljt/qljt2440/
[uboot@localhost board]$ cd qljt/qljt2440/
[uboot@localhost qljt2440]$ mv sbc2410x.c qljt2440.c
[uboot@localhost qljt2440]$ ls 可以看到下面這些文件:
config.mk flash.c lowlevel_init.s Makefile qljt2440.c u-boot.lds
[uboot@localhost qljt2440]$ vim Makefile
COBJS := qljt2440.o flash.o
5. 在 include/configs/ 中建立開發板所需要的配置頭文件
[uboot@localhost qljt2440]$ cd ../../..
[uboot@localhost u-boot-1.3.4]$ cp include/configs/sbc2410x.h include/configs/qljt2440.h
6. 測試交叉編譯能否成功
(1)配置
[uboot@localhost u-boot-1.3.4]$ make qljt2440_config
Configure for qljt2440 board…
(2)測試編譯
[uboot@localhost u-boot-1.3.4]$ make
詳細信息如下:
編譯信息最后兩行:
arm-linux-objcopy --gap-fill=0xff -O srec u-boot u-boot.srec
arm-linux-objcopy --gap-fill=0xff -O binary u-boot u-boot.bin
到此交叉編譯成功。
三.開始針對自己的開發板移植
1. 修改/cpu/arm920t/start.S
1.1 修改寄存器地址定義
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) || defined(CONFIG_S3C2440)
/* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#else
# define pWTCON 0x53000000 /*該地址用來屏蔽看門狗*/
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses 該地址用來屏蔽中斷*/
# define INTSUBMSK 0x4A00001C /*該地址用來屏蔽子中斷*/
# define CLKDIVN 0x4C000014 /* clock divisor register 該地址用來決定FCLK、HCLK、PCLK的比例*/
#define CLK_CTL_BASE 0x4c000000 /* qljt 從S3C2440A.pdf中可以看出該寄存器是存放Mpll和Upll的P254 */
#if defined(CONFIG_S3C2440)
#define MDIV_405 0x7f << 12 /* qljt 參見P255表,同時要知道本開發板的Fin是12MHz,需要的Fclk(也就
是Mpll)是405MHz*/
#define PSDIV_405 0x21 /* qljt 同上,同時設定PDIV和SDIV的值,PDIV和SDIV參見S3C2440A.pdf*/
#endif
#endif
1.2 修改中斷禁止部分
# if defined(CONFIG_S3C2410)
ldr r1, =0x7ff //根據2410芯片手冊,INTSUBMSK有11位可用,
//vivi也是0x7ff,不知為什么U-Boot一直沒改過來。但是由于芯片復位默認
//所有的終端都是被屏蔽的,所以這個不影響工作
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
# if defined(CONFIG_S3C2440)
ldr r1, =0x7fff //根據2440芯片手冊,INTSUBMSK有15位可用
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
1.3 修改時鐘設置
/*時鐘控制邏輯單元能夠產生s3c2440需要的時鐘信號,包括CPU使用的主頻FCLK,AHB總線使用的HCLK,APB總線設備使用的PCLK,2440里面的兩個鎖相環(PLL),其中一個對應FCLK、HCLK、PCLK,另外一個對應UCLK(48MHz)*/
/*注意:AHP、APB總線的簡介參見“AHB與APB總線.doc” */
/* FCLK:HCLK:PCLK = 1:4:8 */
ldr r0, =CLKDIVN
mov r1, #5
str r1, [r0]
/*這三條協處理器命令確實不知道什么意思,在ATXJGYBC_ql.pdf中搜p15和c1,只知道它們執行以后會把協處理器p15的寄存器c1的最高兩位置1,但c1的最高兩位是沒有意義啊,弄不懂它的真正意思
不過我卻知道這三條語句是從哪里出來的,詳細請參考s3c2440的datasheet和s3c2440datasheet中的R1_nF和R1_iA.doc */
mrc p15, 0, r1, c1, c0, 0 /*read ctrl register qljt*/
orr r1, r1, #0xc0000000 /*Asynchronous qljt 改變總線模式為異步模式網上某位朋友說不知到在哪里看到過
如果FCLK與HCLK不同的話就要選擇這種模式的 */
mcr p15, 0, r1, c1, c0, 0 /*write ctrl register qljt*/
#if defined(CONFIG_S3C2440) // (2440的主頻可達533MHz,但聽說設到533MHz時系統
//很不穩定,不知是不是SDRAM和總線配置的影響,所以現在先設到//405MHz,以后在改進。)
/*now, CPU clock is 405.00 Mhz qljt*/
mov r1, #CLK_CTL_BASE /* qljt*/
mov r2, #MDIV_405 /* mpll_405mhz qljt*/
add r2, r2, #PSDIV_405 /* mpll_405mhz qljt*/
str r2, [r1, #0x04] /* MPLLCON qljt實際上是設置寄存器CLK_CTL_BASE+0x04=0x4c000004的值 */
#endif
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410|| CONFIG_S3C2440 */
1.4 將從Flash啟動改成從NAND Flash啟動。(特別注意:這和2410的程序有不同,不可混用!!!是拷貝vivi的代碼。)
將以下U-Boot的重定向語句段:
@#if ndef CONFIG_AT91RM9200
#if 0
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
#endif /*CONFIG_AT91RM9200 */
然后添加:
/*下載了一個vivi源代碼看了一下,還真的有下面哪一段代碼*/
#ifdef CONFIG_S3C2440_NAND_BOOT @qljt@@@@@@@@@@@@@@@@SSSSSSSSSSSSS
@ reset NAND
/*往下四段內容都是針對S3C2440的關于NAND-FLASH的寄存器的設置,具體有什么作用,看了datasheet,有些明白有些不明白*/
mov r1, #NAND_CTL_BASE
ldr r2, =( (7<<12)|(7<<8)|(7<<4)|(0<<0) )
str r2, [r1, #oNFCONF] /*這些宏是在include/configs/qljt2440.h中被定義的*/
ldr r2, [r1, #oNFCONF] /*還是弄不懂為什么上面一句str以后還要有這句的ldr命令?why?難道是多余的?*/
ldr r2, =( (1<<4)|(0<<1)|(1<<0) ) @ Active low CE Control
str r2, [r1, #oNFCONT]
ldr r2, [r1, #oNFCONT]
ldr r2, =(0x6) @ RnB Clear
str r2, [r1, #oNFSTAT]
ldr r2, [r1, #oNFSTAT]
mov r2, #0xff @ RESET command
strb r2, [r1, #oNFCMD]
/*delay一段時間*/
mov r3, #0 @ wait
nand1:
add r3, r3, #0x1
cmp r3, #0xa
blt nand1
/*等待nand-flash的復位完畢信號*/
nand2:
ldr r2, [r1, #oNFSTAT] @ wait ready
tst r2, #0x4
beq nand2
ldr r2, [r1, #oNFCONT]
orr r2, r2, #0x2 @ Flash Memory Chip Disable /*在這里先Display fansh CE先,在C函數中對falsh進行*/
str r2, [r1, #oNFCONT] /*操作的時候才enable,為什么這樣操作不太清楚*/
/*下面這段用來初始化棧指針sp和幀指針fp,至于它們的定義和作用參考文件夾” 棧指針sp和幀指針fp”里面的內容
記住它們都是與函數調用時候相關的。簡單來講就是子函數被調用以后是通過指針的相對位置來查找調用參數和局部變量的,但是由于sp經常變化,所以需要fp來協助。*/
@ get ready to call C functions (for nand_read())
ldr sp, DW_STACK_START @ setup stack pointer /*sp 是指堆棧指針*/
mov fp, #0 @ no previous frame, so fp=0
@ copy U-Boot to RAM /*vivi里面應該是有一段是針對gpio的程序,也許使用來debug用的信號燈,這里省略了*/
/* TEXT_BASE 是uboot自己的入口地址,在u-boot-1.3.4-board/qljt/qljt2440的config.mk中定義
有趣的是外國人的逆向思維很厲害,它們很靈活地把它放在SDRAM的最后0x80000地方,也就是0x33F80000
*/
ldr r0, =TEXT_BASE /*r0 : 把u-boot復制到ram的那個位置*/
mov r1, #0x0 /*r1 : 從falsh的那個位置開始復制*/
mov r2, #0x20000 /*r2 : 復制多大的內容*/
bl nand_read_ll /*跳到執行uboot復制的程序入口,這個函數從哪里來?也是來自vivi的,沒辦法*/
tst r0, #0x0 /*這里特別注意r0的值是指nand_read_ll 執行完以后的返回值,而不是上面
ldr r0, =TEXT_BASE 的值,初學者往往在這里想不通*/
beq ok_nand_read
bad_nand_read: /*如果讀nand_read失敗的話,那么sorry,重來,或者檢查硬件*/
loop2: b loop2 @ infinite loop
ok_nand_read:
@ verify
/*計算機就是好,很容易就可以檢測我們放在SDRAM中的u-boot是不是flash中的uboot。
本開發板使用的是nand-falsh的啟動方式,板子一上電并不是馬上進入SDRAM執行程序的。是這樣的:板子一上電,S3C2440自動把nand-falsh中從0地址開始的4Kbytes復制到S3C2440集成的某個緩沖區里面(起始地址是0x00),從那里開始執行,那4K程序負責把整個uboot復制到SDRAM,然后才跳到SDRAM開始正真的UBOOT(這個技術是有個專業名字的我忘記了),*/
/*下面這段程序的作用就是用開始執行的4Kbytes程序跟我們復制到SDRAM中的uboot的前4K程序進行比較,從而校驗*/
mov r0, #0
ldr r1, =TEXT_BASE
mov r2, #0x400 @ 4 bytes * 1024 = 4K-bytes
go_next:
ldr r3, [r0], #4
ldr r4, [r1], #4
teq r3, r4
bne notmatch
subs r2, r2, #4
beq stack_setup
bne go_next
notmatch:
loop3: b loop3 @ infinite loop
#endif @ CONFIG_S3C2440_NAND_BOOT @qljt@@@@@@@@@@@@@@@@@@EEEEEEEEE
1.5 在跳到C函數執行前,也就是跳出start.S前,添加幾個LED燈的控制,說明程序跑到這里了,移植的第一階段完成了。
/*本開發板上面有四個LED燈,分別接到CPU 的GPIO_F[4:7]這四個引腳上*/
#if defined(CONFIG_S3C2440)
@ LED1 on u-boot stage 1 is ok!
mov r1, #GPIO_CTL_BASE
add r1, r1, #oGPIO_F
ldr r2,=0x5500
str r2, [r1, #oGPIO_CON]
mov r2, #0xff
str r2, [r1, #oGPIO_UP]
mov r2, #0xdf
str r2, [r1, #oGPIO_DAT]
#endif
1.6 在 “ _start_armboot: .word start_armboot ” 后加入:
#if defined(CONFIG_S3C2440_NAND_BOOT)
.align 2 /*???這里我一直不明白為什么是 .align 2,因為如果按照ARM的規則,意思是按照 2的2次方=4bit的
方式對齊,那么就是半個字節對齊,有可能嗎?*/
DW_STACK_START: .word STACK_BASE+STACK_SIZE-4 /*從這里可以看出該堆棧是從高地址向低地址增長的
注意這里的STACK_BASE和STACK_SIZE還沒定義,在1.1節中定義*/
#endif
2. 修改include/configs/qljt2440.h文件,在結尾處添加如下內容(注意:s3c2410與s3c2440的Nand Flash控制器寄存器不同,不能混用!!):
......
/*
* Nandflash Boot
*/
#define CONFIG_S3C2440_NAND_BOOT 1
#define STACK_BASE 0x33f00000
#define STACK_SIZE 0x8000
/* NAND Flash Controller */
#define NAND_CTL_BASE 0x4E000000
/* Offset */
#define oNFCONF 0x00 /*這些宏是在start.S中被調用的*/
#define oNFCONT 0x04
#define oNFCMD 0x08
#define oNFADDR 0x0c
#define oNFDATA 0x10
#define oNFSTAT 0x20
#define oNFECC 0x2c
/* GPIO */
#define GPIO_CTL_BASE 0x56000000
#define oGPIO_F 0x50
#define oGPIO_CON 0x0 /* R/W, Configures the pins of the port */
#define oGPIO_DAT 0x4 /* R/W, Data register for port */
#define oGPIO_UP 0x8 /* R/W, Pull-up disable register */
#endif /* __CONFIG_H */
3. 在board/qljt/qljt2440加入NAND Flash讀函數文件,拷貝vivi中的nand_read.c文件到此文件夾即可,基本上大陸上移植的都是這樣做的,在此把該文件的內容貼出來,目的是對一些難理解的代碼進行解析:
#include <config.h>
#define __REGb(x) (*(volatile unsigned char *)(x))
#define __REGi(x) (*(volatile unsigned int *)(x))
#define NF_BASE 0x4e000000
#define NFCONF __REGi(NF_BASE + 0x0)
#define NFCONT __REGi(NF_BASE + 0x4)
#define NFCMD __REGb(NF_BASE + 0x8)
#define NFADDR __REGb(NF_BASE + 0xC)
#define NFDATA __REGb(NF_BASE + 0x10)
#define NFSTAT __REGb(NF_BASE + 0x20)
//#define GPDAT __REGi(GPIO_CTL_BASE+oGPIO_F+oGPIO_DAT)
#define NAND_CHIP_ENABLE (NFCONT &= ~(1<<1))
#define NAND_CHIP_DISABLE (NFCONT |= (1<<1))
#define NAND_CLEAR_RB (NFSTAT |= (1<<2))
#define NAND_DETECT_RB { while(! (NFSTAT&(1<<2)) );}
#define BUSY 4
inline void wait_idle(void) {
while(!(NFSTAT & BUSY));
NFSTAT |= BUSY;
}
#define NAND_SECTOR_SIZE 512
#define NAND_BLOCK_MASK (NAND_SECTOR_SIZE - 1)
/* low level nand read function */
/*下面nand_read_ll 的三個參數來自start.S里面調用nand_read_ll 前的r0、r1、r2*/
int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
{
int i, j;
/*下面這個if保證對flash的讀操作是從某一頁的頁頭開始的,從直觀來看是保證start_addr[0:8]位都為0,
為什么呢?因為本flash的一頁的大小位512-bytes,也就是從0x0到0x1ff*/
if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK)) {
return -1; /* invalid alignment */
}
NAND_CHIP_ENABLE;
for(i=start_addr; i < (start_addr + size);) {
/* READ0 */
NAND_CLEAR_RB;
/*到此應該可以明白s3c2440 nandflash 相關寄存器的確切含義了,就是說s3c2440里面已經集成了對nand flash操
作的相關寄存器,只要你的nand flash接線符合s3c2440 datasheet的接法,就可以隨便使用s3c2440 對于nand
flash的相關寄存器,例如如果你想像nand flash寫一個命令,那么只要對命令寄存器寫入你的命令就可以了,s3c2440 可以自動幫你完成所有的時序動作,寫地址也是一樣。反過來說如果沒有了對nand flash的支持,那么我們對nand falsh的操作就會增加好多對I/O口的控制,例如對CLE,ALE的控制。s3c2440已經幫我們完成了這部分工作了*/
NFCMD = 0;
|