Chinaunix首页 | 论坛 | 博客
  • 博客访问: 477328
  • 博文数量: 100
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 955
  • 用 户 组: 普通用户
  • 注册时间: 2014-11-21 09:30
文章分类

全部博文(100)

文章存档

2017年(1)

2016年(16)

2015年(83)

我的朋友

分类: 嵌入式

2015-06-09 20:43:18

这里补充一下nand flash行列地址的知识:

行地址就是页地址,列地址是指页内偏移地址。spare就是指OOB区域。在一个页内部,数据区域和OOB区域是连续的,如果不需要ECC校验,一般不使用OOB区域。对读写操作来讲,数据和OOB的编程方法上是相同的。
一般说2k页的nand flash,其实省略掉了64字节的OOB。2K+64 ,需要12位二进制来表示,但是对于页内偏移(即列地址),大于2112的地址是无效的,虽然列地址有12位,,因为2048+64=2112。
谁说读4096能读出来,要读OOB,列地址指定2048开始读64个字节就好了。


main函数:

  1. int main()
  2. {    
  3.     int i, ret;
  4.     u8 oob[64];
  5.     u8 buf[2048];
  6.     
  7.     bzero(buf, 2048);
  8.     mdelay(5000);
  9.     printf("erasing...\n");
  10.     mdelay(1000);
  11.     while(1)
  12.     {
  13.         printf("helloword\n");
  14.         mdelay(500);
  15.     }
  16.     /* 1.擦除第0块 */
  17.     nand_erase(0x4000000);
  18.     
  19.     for (i = 0; i < 2048; i++)
  20.         buf[i] = 0xAA;
  21.     mdelay(1000);
  22.     printf("begin write data\n");
  23.     /*
  24.     ** 2.写入1页数据到0地址(正确数据,全部为0xAA)
  25.     ** 同时将计算出的ECC校验码保存到oob中
  26.     */
  27.     nand_select_chip();
  28.     nand_write_page_8bit(buf, 0x4000000, oob);
  29.     mdelay(1000);
  30.     /* 3.擦除第0块 */
  31.     nand_erase(0x4000000);
  32.     mdelay(1000);
  33.     /*
  34.     ** 4.将前8个数据的第0位取反(0xAB),不使用应硬件ECC,将1页数据写入第0页
  35.     ** 同时将上面记录下的ECC写入spare区
  36.     */
  37.     for (i = 0; i < 8; i++)
  38.         buf[i] ^= 0x01;
  39.     
  40.     nand_select_chip();
  41.     nand_write_page(buf, 0x4000000, oob);
  42.     
  43.     bzero(buf, 2048);

  44.     /* 5.使用三星提供的8位硬件ECC校验拷贝函数读取一页数据 */
  45.     //ret = NF8_ReadPage_8ECC(0, buf);
  46.     ret = nand_read_page_8bit(buf, 0x4000000);
  47.     
  48.     nand_deselect_chip();
  49.     
  50.     printf("\nret = %d\n", ret);
  51.     /* 打印第一个数据 */
  52.     for (i = 0; i < 10; i++)
  53.         printf("%X ", buf[i]);
  54.     return 0;
  55. }

读写函数:


  1. /* 写一页数据 */
  2. void nand_write_page(u8 *buf, u32 addr, u8 *oob)
  3. {
  4.     if (addr & (PAGE_SIZE - 1))
  5.     {
  6.         printf("not page align\n");
  7.         return;
  8.     }
  9.     
  10.     nand_cmd(0x80);
  11.     nand_addr(addr);
  12.     nand_wait_ready();
  13.     
  14.     nand_write_buf(buf, PAGE_SIZE);
  15.     
  16.     nand_write_buf(oob, 64);
  17.     
  18.     nand_cmd(0x10);
  19.     nand_wait_ready();
  20. }

  21. /* 写一页数据,同时将Ecc Code写入OOB区 */
  22. void nand_write_page_8bit(u8 *buf, u32 addr, u8 *oob)
  23. {
  24.     if (addr & (PAGE_SIZE - 1))
  25.     {
  26.         printf("not page align\n");
  27.         return;
  28.     }
  29.     
  30.     nand_cmd(0x80);
  31.     nand_addr(addr);
  32.     nand_wait_ready();
  33.     
  34.     int i;
  35.     int eccsize = 512;
  36.     int eccbytes = 13;
  37.     int eccsteps = PAGE_SIZE / eccsize;
  38.     int ecctotal = eccsteps * eccbytes;
  39.     
  40.     for (i = 0; i < 12; i++)
  41.         oob[i] = 0xFF;
  42.     
  43.     /* 每次写eccsize个字节,分eccsteps次写完一页数据 */
  44.     for (i = 0; eccsteps; eccsteps--, i += eccbytes, buf += eccsize)
  45.     {
  46.         nand_init_hwecc_8bit(WRITE_ECC);                    /* 初始化ECC写 */
  47.         nand_write_buf(buf, eccsize);
  48.         nand_calculate_ecc_8bit(oob + i + 12, WRITE_ECC);    /* 计算ECC校验码,  实际上是读取4个ecc寄存器,得到13字节ecc */
  49.     }
  50.     
  51.     nand_write_buf(oob, 64);                    /* 将计算出的ECC写入OOB */
  52.     
  53. #if 1
  54.     printf("--------write Ecc---------\n");
  55.     for (i = 0; i < 64; i++)
  56.     {
  57.         if (i % 8 == 0)
  58.             putchar('\n');
  59.         printf("%X ", oob[i]);
  60.     }
  61.     putchar('\n');
  62. #endif    
  63.     
  64.     nand_cmd(0x10);
  65.     nand_wait_ready();
  66. }

  67. int nand_read_page_8bit(u8 *buf, u32 addr)
  68. {
  69.     int err = 0;
  70.     if (addr & (PAGE_SIZE - 1))
  71.     {
  72.         printf("not page align\n");
  73.         return;
  74.     }
  75.     
  76.     u8 oob[64];
  77.     nand_read_oob(oob, addr, 64);

  78.     int i;
  79. #if 1
  80.     printf("--------read Ecc---------\n");
  81.     for (i = 0; i < 64; i++)
  82.     {
  83.         if (i % 8 == 0)
  84.             putchar('\n');
  85.         printf("%X ", oob[i]);
  86.     }
  87.     putchar('\n');
  88. #endif    
  89.     
  90.     nand_cmd(0);
  91.     nand_addr(addr);
  92.     nand_cmd(0x30);
  93.     nand_wait_ready();
  94.     
  95.     int col;
  96.     int eccsize = 512;
  97.     int eccbytes = 13;
  98.     int eccsteps = PAGE_SIZE / eccsize;
  99.     int ecctotal = eccsteps * eccbytes;
  100.     
  101.     /* 每次写eccsize个字节,分eccsteps次写完一页数据 */
  102.     for (i = 0, col = 0; eccsteps; eccsteps--, i += eccbytes, buf += eccsize, col += eccsize)
  103.     {
  104.         /* 页内偏移 */
  105.         nand_cmd(0x05);
  106.         NFADDR = col & 0xFF;
  107.         NFADDR = (col >> 8) & 0xF;
  108.         nand_cmd(0xE0);
  109.         
  110.         nand_init_hwecc_8bit(READ_ECC);            /* 初始化ECC读 */
  111.         nand_read_buf(buf, eccsize);
  112.         /* 下面两种方式都可以 */
  113. #if 0
  114.         nand_cmd(0x05);
  115.         NFADDR = (2048 + i + 12)& 0xFF;
  116.         NFADDR = ((2048 + i + 12) >> 8) & 0xF;
  117.         nand_cmd(0xE0);
  118.         nand_read_buf(oob, eccbytes);/* 每读512字节数据读取13字节的oob ,需要重新发送地址,不推荐*/
  119. #else
  120.         nand_write_buf(oob + 12 + i, eccbytes);/* 写入13B ecc ,推荐这种方法,只需要在读数据之前预先读取52B ecc检验码 */
  121. #endif
  122.         
  123.         nand_calculate_ecc_8bit(0, READ_ECC);    /* 计算ECC校验码,实际上只需要等待NFECCSTAT标志位 */
  124.         if (nand_correct_data_8bit(buf) < 0)
  125.             err++;
  126.     }
  127.     
  128.     return err;
  129. }

  130. /*
  131. ** 三星提供的固化在iROM里的函数
  132. ** 使用8位硬件ECC读取1页数据
  133. ** 成功返回0,否则返回1
  134. */
  135. int NF8_ReadPage_8ECC(u32 addr, u8 *buf)
  136. {
  137.     if (addr & (PAGE_SIZE - 1))
  138.     {
  139.         printf("not page align\n");
  140.         return;
  141.     }
  142.     
  143.     int page = addr / PAGE_SIZE;
  144.     int ret = NF8_ReadPage_Adv(page / PAGE_PER_BLK, page % PAGE_PER_BLK, buf);
  145.     return ret;
  146. }



关键是nand_init_hwecc_8bit和nand_calculate_ecc_8bit这两个函数,nand每次读写都需要init_hw_ecc,
nand ecc步骤分为4步:init hw ecc->write buf->calc ecc->write oob
1.  init hw ecc
2.  write buf或者read buf
3.  calc ecc
4.  重复1~3
,重复次数为eccstep = pagesize/eccsize= 2048/512=4,每512字节生成13字节ecc。凑齐4份ecc(52B)
5.  write oob(52B)

5pv210实现带ecc方式读函数有2种做法,一种是每读512字节立即读13bytes ECC,这种办法有个缺点就是每页数据(2048B)需要发送4次地址。
另一种做法是先读取52B的ECC数据,再分4次读页数据,每读512B,将之前读到13字节ecc写入,让ecc模块搜索错误位。


计算ecc是硬件完成的,只需要读取4个ecc寄存器就能得到ecc检验码:
/* 读取13 Byte的Ecc Code ,每个寄存器4个字节,需要用4个寄存器存储*/
nfeccprgecc0 = NFECCPRGECC0;
nfeccprgecc1 = NFECCPRGECC1;
nfeccprgecc2 = NFECCPRGECC2;
nfeccprgecc3 = NFECCPRGECC3;
得到ecc之后,需要手动写入OOB区域。

检验数据用nand_correct_data_8bit函数

  1. int nand_correct_data_8bit(u8 *dat)
  2. {
  3.     int ret = 0;
  4.     u32 errNo;
  5.     u32 erl0, erl1, erl2, erl3, erp0, erp1;

  6.     /* Wait until the 8-bit ECC decoding engine is Idle */
  7.     while (NFECCSTAT & (1 << 31));
  8.     
  9.     errNo = NFECCSECSTAT & 0x1F;
  10.     erl0 = NFECCERL0;
  11.     erl1 = NFECCERL1;
  12.     erl2 = NFECCERL2;
  13.     erl3 = NFECCERL3;
  14.     
  15.     erp0 = NFECCERP0;
  16.     erp1 = NFECCERP1;
  17.     
  18.     if (errNo)
  19.         printf("errNo = %d\n", errNo);
  20.     
  21.     switch (errNo)
  22.     {
  23.     case 8:
  24.         dat[(erl3 >> 16) & 0x3FF] ^= (erp1 >> 24) & 0xFF;
  25.     case 7:
  26.         dat[erl3 & 0x3FF] ^= (erp1 >> 16) & 0xFF;
  27.     case 6:
  28.         dat[(erl2 >> 16) & 0x3FF] ^= (erp1 >> 8) & 0xFF;
  29.     case 5:
  30.         dat[erl2 & 0x3FF] ^= erp1 & 0xFF;
  31.     case 4:
  32.         dat[(erl1 >> 16) & 0x3FF] ^= (erp0 >> 24) & 0xFF;
  33.     case 3:
  34.         dat[erl1 & 0x3FF] ^= (erp0 >> 16) & 0xFF;
  35.     case 2:
  36.         dat[(erl0 >> 16) & 0x3FF] ^= (erp0 >> 8) & 0xFF;
  37.     case 1:
  38.         dat[erl0 & 0x3FF] ^= erp0 & 0xFF;
  39.     case 0:
  40.         break;
  41.     default:
  42.         ret = -1;
  43.         printf("ECC uncorrectable error detected\n");
  44.         break;
  45.     }
  46.     
  47.     return ret;
  48. }



内容很简单,芯片手册说明了。
参考:

4.3.10 8/12/16-BIT ECC PROGRAMMING GUIDE (DECODING)
1. To use 8/ 12/ 16-bit ECC in software mode, set the MsgLength(NFECCCONF[25:16] to 511(512-byte
message length) and the ECCType to “001/100/101”(enable 8/12/16-bit ECC, respectively). ECC module
generates ECC parity code for 512-byte read data. Therefore, you must reset ECC value by writing the
InitMECC (NFECCCONT[2]) bit as ‘1’, and clear the MainECCLock(NFCONT[7]) bit to ‘0’(Unlock) before read
data.
2. Whenever data is read, the 8/12/16-bit ECC module generates ECC parity code internally.
3. After you complete reading 512-byte (not including spare area data), ensure to read the corresponding parity
codes. ECC module needs parity codes to detect whether error bits have occurred or not. Therefore, you have
to read ECC parity code immediately after reading 512-byte. After ECC parity code is read, the 8/12/16-bit
ECC engine searches for error internally. 8/12/16-bit ECC search engine needs minimum of 155 cycles to find
any errors. DecodeDone(NFECCSTAT[24]) can be used to check whether ECC decoding is completed or not.
4. When DecodeDone (NFECCSTAT[24]) is set (‘1’), ECCError(NFECCSECSTAT[4:0]) indicates whether error
bit exists or not. If any error exists, you can fix it by referencing NFECCERL0~NFECCERL7 and NFECCERP0
~ NFECCERP3 registers.
5. If you have additional main data to read, continue the steps 1 ~ 4.
6. To check spare area data (meta data) error, the sequences are same (steps 1 ~ 4), except setting the
MsgLenght(NFECCCONF[25:16]) to the size that you want.
NOTE: You should set the ECC parity conversion codes to check free page error. For more information, refer to
refer to 4.3.11 .


关键是第4步,只需要以下寄存器:
NFECCERL0~NFECCERL7 and NFECCERP0
~ NFECCERP3
每个ERL指明2个错误bit的位置,每个ERP包含错误4个错误bit的pattern。所以对于8bit ecc只需要4个ERL,2个ERP。
根据errNo = NFECCSECSTAT & 0x1F 得知有多少个bit错误(最多8个);
然后对这些错误位重新赋值,data[erl] ^= erp & 0xFF;   //异或运算就是相同为0,不同为1。

实际上,ecc校验码在oob中的位置,可以由编程人员自己定义,但是如果要使用三星提供的NF8_ReadPage_Adv函数,
就必须按照S5PV210_iROM_ApplicationNote_Preliminary_20091126  的规定从OOB[12]开始。因为nand启动时,是8bit ecc方式读取spl的,所以ecc位置也就必须三星的规定了(从oob[12]开始)。但是u-boot运行起来之后,我们就可以更改ecc的位置了,说这些只是想找出yaffs使用硬件ecc的解决办法,因为yaffs 的tag是占用oob空间的,一般只能使用yaffs自己的ecc检验方法,也就是软件ecc,令人很不爽啊。目前只知道am335平台上,yafffs可以使用1bit硬件ecc。


参考:
这篇是1bit 和4bit的文章:
Uboot分析报告
http://blog.sina.com.cn/s/blog_5f9859ee0101irdy.html
这里是标记坏块的文章:
s3c2440对nandflash的操作-
http://blog.chinaunix.net/uid-26023319-id-1743703.html


阅读(2119) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~