S3C2416裸機開發系列五
Nand驅動以及Nand啟動
象棋小子 1048272975
在嵌入式設計中,由于Nand Flash具有大容量,擦寫次數高,接口簡單等優點,常用作固化存儲器。S3C2416支援Nand啟動,是以Nand存儲器可以直接儲存固化代碼以及其它的資料。筆者在此簡單的介紹Nand flash驅動的實作以及Nand啟動。
1. Nand驅動實作
筆者采用Nand flash為K9F2G08U0B,一頁有(2048+64)Byte,一個block有64頁,容量大小為(256M+8M)Byte,是一款8位寬的SLC flash。
1.1. Nand子產品頭檔案
S3C2416 Nand控制器需要通過一些指令來通路Nand flash,并且需要實作底層的寄存器通路。在子產品頭檔案中,我們定義Nand通路常用的指令(來自uboot命名)以及一些寄存器操作宏,并引出子產品的接口函數,其内容如下:
#ifndef__NAND_H__
#define__NAND_H__
#ifdef__cplusplus
extern"C" {
#endif
// Nand flash5位元組ID
typedefstruct Nand_ID_Info {
unsigned char Maker;
unsigned char Device;
unsigned char ID_Data3;
unsigned char ID_Data4;
unsigned char ID_Data5;
}Nand_ID_Info;
// 頁讀周期1(指令1)
#defineNAND_CMD_READ0 0
// 頁讀周期1(指令2)
#defineNAND_CMD_READ1 1
// 随機位址讀周期1
#defineNAND_CMD_RNDOUT 5
// 頁寫周期2
#defineNAND_CMD_PAGEPROG 0x10
// OOB區讀指令
#defineNAND_CMD_READOOB 0x50
// 塊擦除指令周期1
#defineNAND_CMD_ERASE1 0x60
// 讀Nand狀态指令
#defineNAND_CMD_STATUS 0x70
// 讀多層狀态指令
#defineNAND_CMD_STATUS_MULTI 0x71
// 頁寫周期1
#defineNAND_CMD_SEQIN 0x80
// 随機位址寫指令
#defineNAND_CMD_RNDIN 0x85
// 讀ID指令
#defineNAND_CMD_READID 0x90
// 塊擦除指令周期2
#define NAND_CMD_ERASE2 0xd0
// Nand複位
#defineNAND_CMD_RESET 0xff
// 頁讀周期2
#defineNAND_CMD_READSTART 0x30
// 随機位址讀周期2
#defineNAND_CMD_RNDOUTSTART 0xE0
#defineNAND_CMD_CACHEDPROG 0x15
// 發送指令
#defineNF_CMD(Data) {rNFCMD = (Data);}
// 寫位址
#defineNF_ADDR(Addr) {rNFADDR = (Addr);}
// 讀字(4位元組)
#defineNF_READ_WORD() (rNFDATA)
// 讀一位元組
#defineNF_READ_BYTE() (rNFDATA8)
// 寫字(4位元組)
#defineNF_WRITE_WORD(Data) {rNFDATA = (Data);}
// 寫一位元組
#defineNF_WRITE_BYTE(Data) {rNFDATA8 = (Data);}
// 使能片選
#defineNF_CE_ENABLE() {rNFCONT &=~(1<<1);}
// 關閉片選
#defineNF_CE_DISABLE() {rNFCONT |=(1<<1);}
// 清空spare區ECC校驗值
#defineNF_INIT_SECC() {rNFCONT |= (1<<4);}
// 清空main區ECC校驗值
#defineNF_INIT_MECC() {rNFCONT |= (1<<5);}
// 解鎖spare區ECC校驗值
#defineNF_SECC_UNLOCK() {rNFCONT &=~(1<<6);}
// 鎖定spare區ECC校驗值
#defineNF_SECC_LOCK() {rNFCONT |= (1<<6);}
// 解鎖main區ECC校驗值
#defineNF_MECC_UNLOCK() {rNFCONT &=~(1<<7);}
// 鎖定main區ECC校驗值
#defineNF_MECC_LOCK() {rNFCONT |= (1<<7);}
// nand傳輸完會置位NFSTAT[4],若開啟中斷,會同時發送中斷請求
#defineNF_WAIT_READY() {while(!(rNFSTAT& (1<<4)));}
// 獲得nand RnB引腳狀态,1為準備好,0為忙
#defineNF_GET_STATE_RB() {(rNFSTAT &(1<<0))}
// 清除nand傳輸完标志
#defineNF_CLEAR_RB() {rNFSTAT |=(1<<4);}
externunsigned char Nand_ReadSkipBad(unsigned int Address,
unsigned char *Buffer, unsignedint Length);
externunsigned char Nand_WriteSkipBad(unsigned int Address,
unsigned char *Buffer, unsignedint Length);
extern voidNand_Init(void);
extern intNand_ReadID(Nand_ID_Info *pInfo);
externunsigned char Nand_EraseBlock(unsigned int Block);
externunsigned char Nand_MarkBadBlock(unsigned int Block);
externunsigned char Nand_IsBadBlock(unsigned int Block);
externunsigned char WriteCodeToNand(void); // 代碼固化進Nand接口函數
#ifdef__cplusplus
}
#endif
#endif
1.2. Nand初始化
對于不同的Nand flash,其時序要求不一樣。為了達到最佳的性能,是要根據晶片手冊設定一下Nand的通路時序參數的。Nand初始化函數如下:
static void Nand_Reset()
{
NF_CE_ENABLE();
NF_CLEAR_RB();
NF_CMD(NAND_CMD_RESET);
NF_WAIT_READY();
NF_CE_DISABLE();
}
void Nand_Init()
{
// 配置nand控制引腳
rGPACON = (rGPACON &~(0x3f<<17)) |(0x3f<<17);
// 配置K9F2G08U0Btiming([email protected])
// TACLS=1, (tALS或tCLS-tWP=0)(ALE或CLE有效後需保持才能發出寫脈沖)
// TWRPH0=2,tWP=12ns(最小寫脈沖寬度)
// TWRPH1=1,tALH或tCLH=5ns(寫脈沖後ALE或CLE需保持有效時間)
// SLC nand 1位檢驗就行,1-bit ECC
rNFCONF =(1<<12)|(2<<8)|(1<<4)|(0<<0);
// 上升沿檢查nand準備好信号線
rNFCONT =(0<<12)|(0<<10)|(0<<9)|(0<<8)
|(0x3<<6)|(0x3<<4)|(0x3<<1)|(1<<0);
Nand_Reset();
}
1.3. Nand ID的讀取
Nand寫入讀ID指令(0x90)後,即可讀取5位元組的ID資訊,ID資訊包含了生産廠商,裝置ID,以及nand頁大小,塊大小,位寬等重要資訊。其函數實作如下:
intNand_ReadID(Nand_ID_Info *pInfo)
{
if (pInfo == (Nand_ID_Info *)0) {
return 1; // 參數錯誤
}
NF_CE_ENABLE();
NF_CLEAR_RB();
NF_CMD(NAND_CMD_READID); // 發送讀ID指令
NF_ADDR(0x0); // 寫0x0位址
pInfo->Maker = NF_READ_BYTE(); //Maker:0xEC
pInfo->Device = NF_READ_BYTE(); //Device:0xDA
pInfo->ID_Data3 = NF_READ_BYTE(); //0x10
pInfo->ID_Data4 = NF_READ_BYTE(); //0x95
pInfo->ID_Data5 = NF_READ_BYTE(); //0x44
NF_CE_DISABLE();
return 0;
}
1.4. Nand頁讀
K9F2G08U0B一頁分為2k的main區與64位元組的spare區,main區用來存儲正常資料,spare區用來存儲附加資料(如ECC檢驗)。對于flash存儲器,是會出現位反轉或壞塊的問題,寫入flash的資料或從flash讀出的資料可能是有錯的,是以必須用ECC進行校驗,確定寫入的資料與讀出的資料是一緻的。對于SLCNand Flash,隻需1位ECC校驗即可,而S3C2416能夠産生硬體ECC,可産生4位元組的main區ECC與2位元組的spare區ECC。我們把4位元組的main區ECC放在spare區0~3偏移位址處,2位元組的sapre區ECC放在spare區4~5偏移位址處。頁讀函數如下:
// Block為Nand塊區号,Page為對應塊中的頁号,Buffer為資料緩存區
static intNand_ReadPage(unsigned int Block, unsigned int Page,
unsigned char*Buffer)
{
unsigned int i;
unsigned int MECC, SECC;
if (Buffer == (unsigned char *)0) {
return 1; // 緩沖區為空,參數錯誤
}
Page &= (64-1); // 64 pagein one block
Page += (Block << 6); //Block轉換為頁數
NF_INIT_MECC(); // main區ECC清空
NF_INIT_SECC(); // spare區ECC清空
NF_MECC_UNLOCK(); // main區ECC解鎖,開始ECC計算
NF_CE_ENABLE(); // 使能片選
NF_CLEAR_RB(); // 清資料傳輸标志
NF_CMD(NAND_CMD_READ0);// page read cycle 1
NF_ADDR(0); // column address
NF_ADDR(0); // columu address
NF_ADDR(Page & 0xff); // 寫入3位元組的頁位址
NF_ADDR((Page>>8) & 0xff);
NF_ADDR((Page>>16) & 0xff);
NF_CMD(NAND_CMD_READSTART); // page readcycle 2
NF_WAIT_READY(); // 等待指令完成
for (i=0; i<2048; i++) { // 讀取main區資料
Buffer[i] = NF_READ_BYTE();
}
NF_MECC_LOCK(); // 鎖定main ECC
NF_SECC_UNLOCK(); // 解鎖spare ECC
MECC = NF_READ_WORD(); // spare區前4位元組為main區ECC
// main區的ECC放入到NFMECCD0/1中相應的位中
rNFMECCD0=((MECC&0xff00)<<8) |(MECC&0xff);
rNFMECCD1=((MECC&0xff000000)>>8) |((MECC&0xff0000)>>16);
NF_SECC_LOCK(); // 鎖定spare ECC
// spare區第5,6這兩位元組為spare區ECC,剩下部分未使用
SECC = NF_READ_WORD();
// spare區的ECC放入到NFMECCD0/1中相應的位中
rNFSECCD=((SECC&0xff00)<<8)|(SECC&0xff);
NF_CE_DISABLE();
// check whether spare/main area bit failerror occurred
if ((rNFECCERR0 & 0xf) == 0) {
return 0; // 資料讀取正确
} else {
return 2; // ECC檢驗不一緻,資料讀取有誤
}
}
1.5. Nand頁寫
當要寫資料到一個Nand block時,要先確定這個塊已經被擦除,并且需要跳過一些壞塊。資料寫完後,為確定寫入與讀取的資料一緻,應同時寫入資料的ECC校驗值到spare區約定好的ECC位置。頁寫函數如下:
// Block為Nand塊區号,Page為對應塊中的頁号,Buffer為資料緩存區
static intNand_WritePage(unsigned int Block, unsigned int Page,
unsigned char*Buffer)
{
unsigned int i;
unsigned char State;
unsigned int MECC, SECC;
if (Buffer == (unsigned char *)0) {
return 1; // 資料緩存參數錯誤
}
if (Nand_IsBadBlock(Block)) {
return 2; // 是壞塊,傳回壞塊錯誤碼
}
Page &= (64-1); // 1 block最大64頁
Page += (Block << 6); // block轉換成頁
NF_INIT_MECC(); // main區ECC清空
NF_INIT_SECC(); // spare區ECC清空
NF_MECC_UNLOCK(); // main區ECC解鎖,開始ECC計算
NF_CE_ENABLE(); // 使能片選
NF_CLEAR_RB(); // 清資料傳輸标志
NF_CMD(NAND_CMD_SEQIN); // page programcycle 1
NF_ADDR(0); // column address
NF_ADDR(0); // columu address
NF_ADDR(Page & 0xff); // 寫入3位元組頁位址
NF_ADDR((Page>>8) & 0xff);
NF_ADDR((Page>>16) & 0xff);
for (i=0; i<2048; i++) { // 寫入2k資料到main區
NF_WRITE_BYTE(Buffer[i]);
}
NF_MECC_LOCK(); // 鎖定main ECC
MECC = rNFMECC0; // 4位元組寫main區資料的ECC
NF_SECC_UNLOCK(); // 解鎖spare ECC
NF_WRITE_BYTE(MECC&0xff);// 寫4位元組main ECC到spare區
NF_WRITE_BYTE((MECC>>8) & 0xff);
NF_WRITE_BYTE((MECC>>16) & 0xff);
NF_WRITE_BYTE((MECC>>24) & 0xff);
NF_SECC_LOCK(); // 鎖定spare ECC
SECC = rNFSECC; // 2位元組的spare寫資料ECC
NF_WRITE_BYTE(SECC & 0xff); // 繼續寫入SECC
NF_WRITE_BYTE((SECC>>8) & 0xff);
NF_CMD(NAND_CMD_PAGEPROG); // page programcycle 2
NF_WAIT_READY(); // 等待寫完
NF_CMD(NAND_CMD_STATUS); // 讀取nand狀态
do {
State = NF_READ_BYTE();
} while(!(State & (1<<6))); // 等待狀态變成Ready
NF_CE_DISABLE();
// 是否寫成功,第0位為0則pass,不然fail
if (State & (1<<0)) {
if (Nand_MarkBadBlock(Block)) { // 标志壞塊
return 3; // 寫不成功并且壞塊标記不成功
} else {
return 4; // 寫不成功壞塊标記成功
}
}
return 0;
}
1.6. Nand壞塊判定
Nand寫時應先判定所在塊是否壞塊,若是,則應跳過寫這一塊,并标記這一塊為壞塊。壞塊标記我們約定用spare區偏移處第六位元組來作标志,壞塊這一位元組标志為非0xff,好塊為0xff。我們讀取block中頁0,spare區第六位元組的值即可判斷這個block是否壞塊,代碼實作如下:
unsigned charNand_IsBadBlock(unsigned int Block)
{
unsigned char Data;
// 每個block第一頁spare區第6位元組為0xff标記為好壞
Nand_RamdomRead(Block, 0, 2054, 1,&Data);
if (Data != 0xff){
return 1; // 壞塊
}
return 0;
}
1.7. Nand随機位址讀
對于标記讀取壞塊中的标志,往往隻需要通路一頁中的幾個位元組,Nand随機位址讀寫即可實作通路一頁中的相應位元組。一頁中随機位址讀函數實作如下:
// Block為Nand塊區号,Page為對應塊中的頁号,Address為頁内随機位址,
// Length為讀取長度,Buffer為資料緩存區
static unsigned char Nand_RamdomRead(unsigned int Block, unsigned intPage,
unsigned intAddress, unsigned short Length,
unsigned char *Buffer)
{
unsigned short i;
if (Address + Length > 2048+64) {
return 1; // 頁内位址及寫入長度不能超出該頁範圍
}
if (Buffer == (void *)0 || Length == 0) {
return 2; // 參數錯誤
}
Page += (Block << 6); // Block轉換為頁數
NF_CE_ENABLE();
NF_CLEAR_RB();
NF_CMD(NAND_CMD_READ0); // page read cycle 1
NF_ADDR(0); // column address
NF_ADDR(0); // columu address
NF_ADDR(Page & 0xff); // 傳輸3位元組的頁位址
NF_ADDR((Page>>8) & 0xff);
NF_ADDR((Page>>16) & 0xff);
NF_CMD(NAND_CMD_READSTART); // page readcycle 2
NF_WAIT_READY(); // 等待頁讀完成
NF_CMD(NAND_CMD_RNDOUT); // ramdom readcycle 1
NF_ADDR(Address & 0xff); // 2 cycle addressin page
NF_ADDR((Address>>8) & 0xf);
NF_CMD(NAND_CMD_RNDOUTSTART); // ramdom readcycle 2
for (i=0; i<Length; i++) {
Buffer[i] = NF_READ_BYTE(); // 讀取Length長度資料
}
NF_CE_DISABLE();
return 0;
}
1.8. Nand随機位址寫
寫入一頁中幾個位元組資料時,需要用到Nand随機位址寫,函數實作如下:
// Block為Nand塊區号,Page為對應塊中的頁号,Address為頁内随機位址,
// Length為寫入長度,Buffer為資料緩存區
static unsigned char Nand_RamdomWrite(unsigned int Block, unsigned intPage,
unsigned intAddress, unsigned short Length,
unsigned char *Buffer)
{
unsigned short i;
unsigned char State;
if (Address + Length > 2048+64) {
return 1; // 頁内位址及寫入長度不能超出該頁範圍
}
if (Buffer == (void *)0 ||Length == 0) {
return 2; // 參數錯誤
}
Page += (Block << 6); //Block轉換為頁數
NF_CE_ENABLE();
NF_CLEAR_RB();
NF_CMD(NAND_CMD_SEQIN); // 頁寫周期1
NF_ADDR(0);// column address
NF_ADDR(0); // columu address
NF_ADDR(Page & 0xff); // 3位元組頁位址
NF_ADDR((Page>>8) & 0xff);
NF_ADDR((Page>>16) & 0xff);
NF_CMD(NAND_CMD_RNDIN); // 頁内随機寫指令
NF_ADDR(Address & 0xff); // 2位元組頁内位址
NF_ADDR((Address>>8) & 0xf);
for (i=0; i<Length; i++) {
NF_WRITE_BYTE(Buffer[i]); // 寫入Length長資料
}
NF_CMD(NAND_CMD_PAGEPROG); // 頁寫周期2
NF_WAIT_READY(); // 等待寫完
NF_CMD(NAND_CMD_STATUS); // 讀取寫結果狀态
do {
State = NF_READ_BYTE();
} while(!(State & (1<<6))); // 等待狀态變成Ready
NF_CE_DISABLE();
if (State & (1<<0)) {
return 3; // ramdom write error
}
return 0;
}
1.9. 壞塊标記
如果檢查出壞塊,需要對相應的塊在約定位置進行壞塊标記,以免再對這個壞塊進行讀寫。其代碼實作如下:
unsigned charNand_MarkBadBlock(unsigned int Block)
{
// 每個block第一頁spare區第6位元組标記非0xff壞塊
unsigned charData = 0x55;
return Nand_RamdomWrite(Block, 0, 2054, 1,&Data);
}
1.10. 塊區擦除
資料寫入塊區前,對應的塊應已擦除好,擦除失敗,則認為是壞塊,并标注。其代碼實作如下:
unsigned charNand_EraseBlock(unsigned int Block)
{
unsigned char State;
NF_CE_ENABLE();
NF_CLEAR_RB();
NF_CMD(NAND_CMD_ERASE1); // erase blockcommand cycle 1
// write 3 cycle block address[A28:A18]
NF_ADDR((Block<<6) & 0xff); //[A19:A18]
NF_ADDR((Block>>2) & 0xff); //[A27:A20]
NF_ADDR((Block>>10) & 0xff); //A28
NF_CMD(NAND_CMD_ERASE2); // erase blockcommand cycle 2
NF_WAIT_READY();
NF_CMD(NAND_CMD_STATUS);
do {
State = NF_READ_BYTE();
} while(!(State & (1<<6))); // 等待狀态變成Ready
NF_CE_DISABLE();
// 是否擦寫成功,第0位為0則pass,不然fail
if (State & (1<<0)) {
if (Nand_MarkBadBlock(Block)) {
return3; // 擦除不成功并且壞塊标記不成功
} else {
return 4; // 擦除不成功壞塊标記成功
}
}
return 0; // 成功擦除
}
1.11. 接口函數寫
對于實際的應用,往往需要對Nand進行一定長度代碼或資料的寫入或讀取,并且判斷出壞塊,則應跳過讀寫。是以,Nand子產品還應實作任意長度代碼、資料的寫入或讀取接口函數功能,接口函數寫實作如下:
// Address為Nand位元組偏移位址,Buffer為資料緩存區
// Length為寫長度
unsigned charNand_WriteSkipBad(unsigned int Address,
unsigned char *Buffer, unsigned intLength)
{
unsigned int BlockIndex, PageIndex;
unsigned int WriteBytes;
unsigned char i;
unsigned char State;
if (Length == 0 || Buffer == (void *)0) {
return 1; // 參數錯誤
}
if ((Address & 0xfff) != 0) {
return 2; // nand位址非頁對齊,讀最小機關為1頁
}
State = 0;
WriteBytes = 0; // 己寫位元組數
PageIndex = (Address >> 11) &0x3f; // 塊中的頁偏移位置
BlockIndex = Address >> 17; // 塊寫位置
while (1) {
if (Nand_EraseBlock(BlockIndex)) {
BlockIndex++; // 壞塊,跳過到下一塊
continue;
}
for (i=PageIndex; i<64; i++) {
if (Nand_WritePage(BlockIndex, i,Buffer)) {// 寫一頁
if (State == 1) {
State=2; // write errortwice
// 再次寫錯誤,認為是壞塊,取消這塊寫的資料
WriteBytes-= 2048 * (i-PageIndex);
// 調整記憶體位置到寫這塊之前
Buffer -= 2048 *(i-PageIndex);
break;
} else {
State = 1;
i -= 1; // 寫出錯,嘗試再次寫該頁
continue;
}
}
WriteBytes += 2048; // 寫取了一頁資料
if (WriteBytes >= Length) {
break; // 資料寫入完退出
}
State = 0;
Buffer += 2048; // 下一頁記憶體存儲位置
}
if (State == 2) { // 兩次寫均出錯,跳過到下一塊
BlockIndex++;
State = 0;
continue;
}
if (WriteBytes >= Length) {
break; // 寫完退出循環
}
PageIndex = 0; // 下一塊從第一頁開始寫
BlockIndex++; // 再寫下一個塊
}
return 0;
}
1.12. 接口函數讀
接口函數讀實作如下:
// Address為Nand位元組偏移位址,Buffer為資料緩存區
// Length為讀取長度
unsigned charNand_ReadSkipBad(unsigned int Address,
unsigned char *Buffer, unsigned intLength)
{
unsigned int BlockIndex, PageIndex;
unsigned int ReadBytes;
unsigned char i;
unsigned char State;
if (Length == 0 || Buffer == (void *)0) {
return 1; // 參數錯誤
}
if ((Address & 0xfff) != 0) {
return 2; // nand位址非頁對齊,讀最小機關為1頁
}
State = 0;
ReadBytes = 0; // 已讀位元組數
PageIndex = (Address >> 11) &0x3f; // 塊中的頁偏移位置
BlockIndex = Address >> 17; // 塊讀位置
while (1) {
if (Nand_IsBadBlock(BlockIndex)) {
BlockIndex++; // 壞塊,跳過讀取下一塊
continue;
}
for (i=PageIndex; i<64; i++) { // 讀取一個block
if (Nand_ReadPage(BlockIndex, i,Buffer)) {
if (State == 1) {
State=2; // read error twice
// 再次讀錯誤,認為是壞塊,取消這塊讀的資料
ReadBytes -=2048 * (i-PageIndex);
// 調整記憶體位置到讀取這塊之前
Buffer -= 2048* (i-PageIndex);
break;
} else {
State = 1;
i -= 1; // 讀出錯,嘗試再次讀該頁
continue;
}
}
ReadBytes += 2048; // 讀取了一頁資料
if (ReadBytes >=Length) {
break; // 讀取資料足夠則退出
}
State = 0;
Buffer += 2048; // 下一頁記憶體存儲位置
}
if (State == 2) { // 兩次讀取均出錯,跳過到下一塊
BlockIndex++;
State = 0;
continue;
}
if (ReadBytes >= Length){
break; // 讀完退出循環
}
PageIndex = 0; // 下一塊從第一頁開始讀
BlockIndex++; // 讀下一個塊
}
return 0;
}
2. Nand啟動
寫好了Nand驅動,即可調用Nand驅動接口函數實作代碼從Nand讀取到RAM,或把代碼從RAM固化進Nand。Nand啟動從Nand搬移代碼到RAM,我們首先要确定代碼運作的RAM基址,以及拷貝的代碼大小,最後調用Nand_ReadSkipBad從Nand 0x0位址偏移處讀取即可。對于代碼需固化到Nand,我們傳傳入連結接器給出的代碼基址和代碼大小參數,最後調用Nand_WriteSkipBad從Nand 0x0位址偏移處寫入即可,我們給出Nand代碼燒錄調用函數如下:
// 從運作代碼的RAM位置一定長度的代碼燒寫進Nand 0x0偏移處
unsigned char WriteCodeToNand()
{
// 在闆級代碼LowLevelInit.s中引出了連結器生成的代碼大小,代碼運作位址資訊
// __CodeAddr__為代碼到拷貝到的RAM位置,連結檔案中設定
// __CodeSize__為二進制代碼編譯生成的大小,連結器最終連結後給出
externunsigned int __CodeAddr__;
extern unsigned int __CodeSize__;
unsigned char State;
Nand_Init(); // Nand時序初始化
State = Nand_WriteSkipBad(0, (unsigned char *)__CodeAddr__,__CodeSize__);
return State;
}
在啟動代碼中,我們需要識别出Nand啟動,并調用Nand_ReadSkipBad接口函數即可把代碼從Nand讀取到代碼到運作記憶體處。在LowLevelInit.s闆級檔案中,我們修改CopyCodeToRAM,判别是Nand啟動還是IROM SD/MMC啟動,并調用相應的裝置代碼拷貝函數即可。我們可以判斷NFCONF(0x4E000000)的最高位是否為1來确定是Nand啟動,為0則認為是IROM SD/MMC啟動。CopyCodeToRAM函數加入Nand啟動後的代碼如下:
; SD/MMCDevice Boot Block Assignment
eFuseBlockSize EQU 1
SdReservedBlockSize EQU 1
BL1BlockSize EQU 16
SdBL1BlockStart EQU SdReservedBlockSize+ \
eFuseBlockSize + BL1BlockSize
globalBlockSizeHide EQU 0x40003FFC
CopyMovitoMem EQU 0x40004008
; Nandcontroller base address
NFCONF EQU 0x4E000000
EXPORT __CodeSize__
EXPORT __CodeAddr__
; 引出代碼搬移函數,以供啟動代碼調用
EXPORT CopyCodeToRAM
; 引入Nand啟動的初始化函數及Nand讀函數
IMPORT Nand_Init
IMPORT Nand_ReadSkipBad
; 引傳入連結接器産生符号,以确定代碼運作位置,編譯生成的大小
IMPORT ||Image$$ER_ROM0$$Base||
IMPORT ||Load$$ER_ROM0$$Length||
IMPORT ||Load$$ER_ROM1$$Length||
IMPORT ||Load$$RW_RAM$$RW$$Length||
; 連結器産生代碼連結運作位置
__CodeAddr__ DCD ||Image$$ER_ROM0$$Base||
; 連結器各個段需儲存的代碼以及需初始代的變量大小
__CodeSize__ DCD ||Load$$ER_ROM0$$Length||+ \
||Load$$ER_ROM1$$Length|| + \
||Load$$RW_RAM$$RW$$Length||
CopyCodeToRAM
STMFD SP!,{LR} ; 儲存傳回位址
; 判斷NFCONF的最高位為1,說明啟動裝置為Nand
LDR R0, =NFCONF
LDR R1,[R0]
AND R1,R1, #0x80000000
CMP R1,#0x80000000
BNE MMC_SD_Boot
Nand_Boot
BL Nand_Init; Nand初始化
MOV R0,#0
LDR R1,__CodeAddr__
LDR R2,__CodeSize__
BL Nand_ReadSkipBad;調用Nand讀函數
MOVS R0, R0 ; 傳回值确定函數成功還是失敗
Nand_Boot_Loop
BNE Nand_Boot_Loop;傳回非0說明拷貝失敗
B AfterCopy
MMC_SD_Boot
; 不需要卡初始化
LDR R3,=0
; 拷貝sd卡代碼到連結執行域記憶體代碼處
LDR R2, __CodeAddr__
; 計算代碼的大小,以block計,不足512位元組的算1個block
; 代碼的大小包括Code RO-data RW-data(代碼需儲存需初始化的RW的初始值)
; 代碼儲存在ROM中,應從加載域得到ROM的大小,而不是執行域,編譯器可能壓縮
; 代碼段儲存在加載域的ROM中
LDR R0, __CodeSize__
LDR R1,=0x1ff
TST R0,R1 ; 是否不足一個block(512Bytes)
BEQ %F0; 代碼恰好block對齊,不用加多一個block
ADD R0,R0, #512
0 LSR R1,R0, #9 ; 得到代碼的block大小
; 計算代碼在SD/MMC卡中的block起啟位址
LDR R4,=SdBL1BlockStart
LDR R0,=globalBlockSizeHide
LDR R0,[R0] ; SD/MMC的總block塊
SUB R0,R4 ; 減去保留塊及BL1大小
CMP R1,#16 ; 代碼不足8k,直接BL1處拷貝
BLS ParameterOK; 代碼少于16個block跳轉
SUB R0,R1 ; 再減去代碼的大小,代碼的block位置
; 調用IROM Movi拷貝函數,僅适用于IROM啟動,卡通路時鐘25M
ParameterOK
LDR R4,=CopyMovitoMem
LDR R4,[R4]
BLX R4
MOVS R0,R0 ; 傳回值确定函數成功還是失敗
MMC_SD_Boot_Loop
BEQ MMC_SD_Boot_Loop; 傳回0說明拷貝失敗
AfterCopy
LDMFD SP!, {PC} ; 函數傳回
對于S3C2416來說,不借助昂貴的燒寫工具,隻有SD卡能直接燒寫進代碼,對于需要把代碼固化進Nand,需先設定代碼從SD卡啟動,然後再通過某一觸發條件(如某一按鍵輸入作為下載下傳鍵)調用WriteCodeToNand函數把代碼燒寫進Nand,以後設定從Nand啟動即可。
3. 附錄
至此,啟動代碼已支援Nand啟動以及sd卡啟動,c代碼運作環境RAM資源可達64MB。
LowLevelInit.s,闆級初始化代碼實作,包括DDR2初始化函數實作,代碼拷貝函數實作,實作Nand啟動以及sd卡啟動。
Nand.h / Nand.c,Nand子產品接口頭檔案以及Nand驅動功能實作。
http://pan.baidu.com/s/1mgqfJWO