天天看點

S3C2416裸機開發系列五_Nand驅動以及Nand啟動 S3C2416裸機開發系列五

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

繼續閱讀