S5PV210开发系列七_Nand驱动实现

S5PV210开发系列七

Nand驱动实现

象棋小子    1048272975

Nand flash具有大容量、改写速度快、接口简单等优点,适用于大量数据的存储,为固态大容量存储提供了廉价有效的解决方案。各种电子产品中如手机存储器、sd卡、u盘等均采用Nand flash存储,笔者此处就Nand驱动实现作一个简单的介绍。

1.  Nand flash概述

东芝公司在1989年最先发表Nand flash结构,强调降低每比特的成本,更高的性能,并且像磁盘一样可以通过接口轻松升级。随着Nand技术的发展,根据Nand颗粒存储单元,可以分为SLC(Single Level Cell)、MLC(Multi LevelCell)、TLC(Trinary Level Cell)。SLC为1bit/cell,即1个储存单元存放1bit数据,其特点是成本高、容量小、速度快、寿命长(约10万次擦写)。MLC为2bit/cell,即1个储存单元存放2bit数据,相比SLC来说,其价格一般,容量可以更大,速度一般、寿命一般(约1万次擦写)。TLC为3bit/cell,即1个储存单元存放3bit数据,因此相同容量下其成本最低,容量相比最大,但速度最慢,寿命也最短(约1000次擦写)。目前市面上基于Nand flash的存储器,如sd卡、u盘等大多为MLC型,但对于高容量Nand存储器,如64G以上sd卡等,很有可能为TLC型Nand flash。

2. Nand驱动实现

Nand flash由Block(块)构成,Block的基本单元为Page(页),每个页又分为Data area(数据存储区域)以及Spare area(备用区域)。由于Nand flash在出厂以及在使用过程中会出现坏块,以及会出现位反转的问题,为了数据的可靠性,通常需要采用ecc(Error Correcting Code)算法,一般对于SLC Nand,采用1bit ecc即可,对于MLC Nand,需采用4bit以上ecc,而Spare area即用来保存坏块标记、ecc数据等额外信息。通常Nand flash的读取和编程以页为基础,而擦除却是基于块的。Nand flash编程只能把1编程成0,而不能把0编程成1,因此在页编程时,必须已先擦除。

笔者采用Nand flash为Samsung的K9F4G08U0E,一页有(2048+64)Byte,一个Block有64页,容量大小为(512M+16M)Byte,是一款8位宽的SLC Nand flash。

Nand flash驱动一般应实现Nand初始化、页读、页编程、坏块标记、坏块检查、块擦除这几个接口实现。

2.1. Nand初始化

Nand初始化主要是对Nand引脚功能初始化、根据具体的Nand flash,设置最佳的Nand访问时序。

void Nand_Init(void)

{

    // 配置nand控制引脚

    MP01CON_REG =(MP01CON_REG & ~(0xf<<8)) | (0x3<<8); // NFCS0

    MP01PUD_REG&= ~(3<<4); // pull-up/down disable

    MP03CON_REG =0x22222222;

    MP03PUD_REG =0;

 

    NFCONF_REG =(2<<12)|(2<<8)|(1<<4)|(0<<3)|(0<<2)|(1<<1);

    NFCONT_REG =(1<<23)|(1<<22)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0);

    Nand_Reset();

}

2.2. Nand页读

K9F4G08U0E一页分为2k的data area与64字节的spare area,data area用来存储正常数据,sparearea用来存储附加数据(如ecc数据)。对于flash存储器,是会出现位反转或坏块的问题,写入flash的数据或从flash读出的数据可能是有错的,因此必须采用ecc算法,确保写入的数据与读出的数据是一致的。对于SLC Nand Flash,只需1位ecc即可,每512 byte产生4 byte的ecc数据,最多可纠错1位,spare area 0~1字节存放坏块标记,2~17字节存放该页ecc数据,18~19字节存放spare area产生的ecc数据,剩余spare area存放接口需要写入的其它spare area数据。

int32_t Nand_ReadWithOob(uint32_t page, uint8_t *data,uint32_t data_len, uint8_t *oob, uint32_t oob_len)

{

uint8_t ecc[16];

uint32_t i;

uint8_t *pecc;

uint8_t *pBuffer;

uint32_t MECC, SECC;

uint16_t Col;

uint8_t Status;

   

if (!data && (data_len!=0)) {

    return -1;

}

if (!oob && (oob_len!=0)) {

    return -1;

}

// 保留oob前面20字节作为坏块标记以及数据ecc

if ((data_len>2048) || (oob_len>64-20)) {      

    return -2;

}

   

// ecc data 16字节开始于spare区的第2个字节偏移处

Col = 2048 + 2;

NF_INIT_SECC();

NF_SECC_UNLOCK(); // 解锁spare ECC

   

NF_CE_ENABLE();

NF_CLEAR_RB();

NF_CMD(NAND_CMD_READ0); // page read cycle 1

NF_ADDR(Col & 0xff); // column address

NF_ADDR(Col >> 8); // columu address

NF_ADDR(page & 0xff); // 传输3字节的页地址

NF_ADDR((page>>8) & 0xff);

NF_ADDR((page>>16) & 0xff);

NF_CMD(NAND_CMD_READSTART); // page read cycle 2   

   

NF_WAIT_READY(); // 等待页读完成   

for (i=0; i<16; i++) {

    ecc[i] =NF_READ_BYTE();

}  

 

NF_SECC_LOCK();

SECC = NF_READ_BYTE();

SECC |= NF_READ_BYTE() << 8;   

   

for (i=0; i<oob_len; i++) {

    oob[i] =NF_READ_BYTE();   

}

NFSECCDATA0_REG=((SECC&0xff00)<<8)|(SECC&0xff);

NF_CE_DISABLE();

   

// Read ecc status

Status = NFESTAT0_REG;

if (((Status>>2) & 0x3) == 1) {

    // ecc 1biterror, Correctable             

    ecc[(Status>>21)&0xf]^= (1 << ((Status>>18) & 0x7));  

} else if (((Status>>2) & 0x3) > 1) {

    Debug("ECCuncorrectable error detected\r\n");

    return -3;

}  

if (data_len == 0) {

    return 0;

}

   

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 read cycle 2

NF_WAIT_READY(); // 等待命令完成       

   

// read main area

pecc = ecc;

while (data_len) {

    pBuffer =data;

    NF_INIT_MECC();// main区ECC清空

    NF_MECC_UNLOCK();// main区ECC解锁,开始ECC计算

    for (i=0;i<512; i++) {

        *data++ =NF_READ_BYTE();

        data_len--;

        if(data_len == 0) {

            break;

        }

    }

    NF_MECC_LOCK();// 锁定main ECC

    // SLC: Writeecc to compare

    MECC =(pecc[1] << 16) | (pecc[0] << 0);

    NFMECCDATA0_REG= MECC;

    MECC =(pecc[3] << 16) | (pecc[2] << 0);

    NFMECCDATA1_REG= MECC;

    pecc += 4;

    // Read eccstatus

    Status =NFESTAT0_REG;

    if ((Status& 0x3) == 1) {

        // 1biterror, Correctable

        pBuffer[(Status>>7)&0x7ff]^= (1 << ((Status>>4) & 0x7));

    } else if((Status & 0x3) > 1) {

        Debug("ECCuncorrectable error detected\r\n");

        NF_CE_DISABLE();           

        return-4;

    }  

}

NF_CE_DISABLE();

return 0;

}

2.3. Nand页编程

当要写数据到一个页时,要先确保这个块已经被擦除。数据写完后,为确保写入与读取的数据一致,应同时写入数据的ecc值到spare area约定好的位置。

int32_t Nand_WriteWithOob(uint32_t page, const uint8_t*data, uint32_t data_len, const uint8_t *oob, uint32_t oob_len)

{

uint8_t ecc[16];

uint32_t i;

uint8_t *pecc;

uint32_t MECC, SECC;   

uint32_t data_len_temp;

uint16_t Col;

uint8_t State;

   

if (!data && (data_len!=0)) {

    return -1;

}

if (!oob && (oob_len!=0)) {

    return -1;

}

// 保留oob前面20字节作为坏块标记以数据ecc

if ((data_len>2048) || (oob_len>64-20)) {

    return -2;

}

NF_CE_ENABLE(); // 使能片选

NF_CLEAR_RB(); // 清数据传输标志

   

NF_CMD(NAND_CMD_SEQIN); // page program 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);    

pecc = ecc;

data_len_temp = data_len;

// write main area

while (data_len_temp) {

    NF_INIT_MECC();// main区ECC清空

    NF_MECC_UNLOCK();// main区ECC解锁,开始ECC计算

    // Write bufferto main area

    for (i=0;i<512; i++) {

        NF_WRITE_BYTE(*data++);

        data_len_temp--;

        if(data_len_temp == 0) {

            break;

        }

    }      

    NF_MECC_LOCK();// 锁定main ECC    

    MECC =NFMECC0_REG; // 4字节写main区数据的ECC

    *pecc++ =MECC & 0xff;

    *pecc++ =(MECC>>8) & 0xff;

    *pecc++ =(MECC>>16) & 0xff;

    *pecc++ =(MECC>>24) & 0xff;

}

if (data_len < 2048) {

    // 调整到oob区

    Col = 2048;

    NF_CMD(NAND_CMD_RNDIN);// 页内随机写命令

    NF_ADDR(Col& 0xff); // 2字节页内地址

    NF_ADDR((Col>>8)& 0xff);

}

// Reserved for bad block (2 byte) 

NF_WRITE_BYTE(0xff);

NF_WRITE_BYTE(0xff);

   

NF_INIT_SECC();

NF_SECC_UNLOCK(); // 解锁spare ECC

// main ecc 16 byte, start spare area offset 2

for (i=0; i<16; i++) {

    NF_WRITE_BYTE(ecc[i]);

}

NF_SECC_LOCK(); // 锁定spare ECC

SECC = NFSECC_REG; // 2字节的spare写数据ECC

NF_WRITE_BYTE(SECC & 0xff); // 继续写入SECC

NF_WRITE_BYTE((SECC>>8) & 0xff);   

 

for (i=0; i<oob_len; i++) {

    NF_WRITE_BYTE(oob[i]);

}

   

NF_CMD(NAND_CMD_PAGEPROG); // page program cycle 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)) {

    return -3; //写不成功

}

return 0;  

}

 

2.4. Nand坏块检查

坏块标记约定用spare area第0~1字节来作标志,坏块这两字节标志为非0xff,好块为0xff。我们读取block中页0,spare  area第0~1字节的值即可判断这个block是否坏块。

int32_t Nand_IsBadBlock(uint32_t Block)

{

    uint8_toob[2];

    // 每个block第一页spare区0, 1字节非0xff标记为好坏

    Nand_Read(Block,0, 2048, 2, oob);

    if((oob[0]==0xff) && (oob[1]==0xff)) {

        return 0; // 好块

    }

    return 1; // 坏块

}

2.5. 坏块标记

如果页编程不成功或者擦除失败,检查出坏块,需要对相应的块在约定位置进行坏块标记,以免再对这个坏块进行读写。其代码实现如下:

int32_t Nand_MarkBadBlock(uint32_t Block)

{

    // 每个block第一页spare区第0, 1字节标记非0xff坏块

    uint8_t oob[2];

    oob[0] = 0;

    oob[1] = 0;

    returnNand_Write(Block, 0, 2048, 2, oob);

}

2.6. 块区擦除

数据写入块区前,对应的块应已擦除好。

int32_t Nand_EraseBlock(uint32_t Block)

{

    uint8_tState;

    NF_CE_ENABLE();

    NF_CLEAR_RB();

 

    NF_CMD(NAND_CMD_ERASE1);// erase block command cycle 1

    // write 3cycle 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 block command 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)) {

        return-1;

    }  

    return 0; // 成功擦除

}

3. Nand启动

对于OneNand/Nand启动设备,BL0除了检验BL1的检验和之外,还会采用8bit ecc检验BL1代码的正确性,BL1在烧录进Nand设备时,还应生成相应的8/16位ECC数据,写入到spare area的指定位置,不然ecc失败也将无法从Nand设备启动。Nand启动需要8bit ecc处理,8bit ecc参考Bootloader中相应的NandBoot.c这个Nand驱动。

图3-1  Nand ECC检验

4. 附录

Nand.rar,Nand驱动实现。

http://pan.baidu.com/s/1gd6fEIv

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值