1.Nand flash以page为单位进行读写,以block为单位进行擦除,没页分为main区和spare区,main区用于存放正常的数据,spare区用于存放一些附加信息
2.S3c2440 支持从Nand 启动是因为内部有一个叫做Steppingstone的SRAM buffer,当启动的时候,nand 的前4k的将会代码将被拷贝到steppingstone中执行,注意前4k代码是不会经过ECC校验的,所以必须确保这些代码的准确
3.对nand的操作都是通过使用命令来实现,有的操作只要一个命令就可以完成,而有的需要两个命令才能完成,下面是K9F1G08U0B的命令表:
4.关于TACLS,TWRPH0,TWRH1几个参数的数值问题:
从下面的时序图不难看出这几个参数的意思,在此就不赘述。
图1 (s3c2440)
图2(K9F1G08U0B)
比较上面两个时序图,我们发现,TWRPH0 即为K9F1G08U0B 中的twp, TWRH1 为tCLH
TACLS为tcls – twp,K9F1G08U0B的手册给出了这些参数的最小时间,如图
都是以ns为单位的。Nand flash 控制器使用的是HCLK ,此时为100MHZ。一个周期为10ns。给本例中我们设置:TACLS为1,TWRPH0为2,TWRH1为0;只要注意设置大于上面的最小值就ok了。
6.ECC校验编程:由于使用软件的方法进行ECC校验比较复杂,S3C2440中自带了硬件产生ECC校验,可以通过NFCONT的[5]和[6]位来分别开启硬件ECC产生器,如果是8bit的nand flash接口将产生4个字节的main 区 ECC 校验码和2个字节的spare区 校验码,分别存在NFMECCD和NFSECCD中,当然前提是开启了对应区的ECC发生器。具体可以参考2440的手册P220.下面是编程ECC的过程:
(1)写NFCONT[4]为1初始化ECC 编解码器,写NFCONT[5]为0解锁main区ECC发生器
(2)当读写玩数据时写NFCONT[5]为1来锁定ECC防止被改变
(3)通常用户会将main区产生的ECC校验码写到spare区,这些数据和NFMECC0/1中的数据是相同的。本例中我们约定每一页的spare区的第0个地址到第3个地址存储main区ECC,第4个地址和第5个地址存储spare区ECC
(4)硬件自动产生的ECC会自动保存在NFMECC中,而NFMECCD0/1中的数据需要用户自己写入,当放入数据后,系统会自动比较NFMECC0/1和NFMECCD0/1的内容,这样就实现了ECC码的校验
(5) 最后我们就可以通过读取NFESTAT0/1(因为K9F1G08U0B是8位IO口,因此这里只用到了NFESTAT0)中的低4位来判断读取的数据是否正确,其中第0位和第1位为main区指示错误,第2位和第3位为spare区指示错误
下面是核心代码:
- #include "def.h"
- #include "2440addr.h"
- #include "mynand.h"
- //define command
- #define CMD_READ1 0x00 //页读命令1
- #define CMD_READ2 0x30 //页读命令2
- #define CMD_READID 0x90 //读取ID
- #define CMD_RESET 0xff //复位
- #define CMD_WRITE1 0x80 //页写命令1
- #define CMD_WRITE2 0x10 //页写命令2
- #define CMD_ERASE1 0x60 //块擦除命令1
- #define CMD_ERASE2 0xd0 //块擦除命令2
- #define CMD_STATUS 0x70 //读取状态命令
- #define CMD_RANDOMREAD1 0X05 //随机读取命令1
- #define CMD_RANDOMREAD2 0xe0 //随机读取命令2
- #define CMD_RANDOMWRITE 0x85 //随机写命令
- #define NF_CE_L() {rNFCONT &=~(1<<1);} //使能片选
- #define NF_CE_H() {rNFCONT |=(1<<1);} //关闭片选
- #define NF_MECC_UnLock() {rNFCONT&=~(1<<5);} //解锁main去ECC
- #define NF_MECC_Lock() {rNFCONT|=(1<<5);} //锁定main去ECC
- #define NF_SECC_UnLock() {rNFCONT &= ~(1<<6); } //解锁spare区ECC
- #define NF_SECC_Lock() {rNFCONT |= (1<<6); } //锁定spare区ECC
- #define NF_RSTECC() {rNFCONT |= (1<<4); } //复位ECC
- #define NF_WAITRB() {while(!(rNFSTAT &(1<<0))) ;} //等待nand flash 空闲
- #define NF_CLEAR_RB() {rNFSTAT |= (1<<2); } //清除RnB信号
- #define NF_DETECT_RB() {while(!(rNFSTAT&(1<<2)));}
- #define NF_RDDATA8() ((*(volatile unsigned char*)0x4E000010) )
- #define NF_CMD(cmd) {rNFCMD = (cmd);} //命令
- #define NF_ADDR( addr) {rNFADDR = (addr);} //地址
- #define NF_RDDATA() (rNFDATA) //读取32位数据
- //#define NF_RDDATA8() (rNFDATA) //读取8位数据
- #define NF_WRDATA(data) { rNFDATA = (data);} //写32位数据
- #define NF_WRDATA8(data) { rNFDATA8 = (data);} //写8位数据
- #define TACLS 1
- #define TWRPH0 2
- #define TWRPH1 0
- extern void Delay(int time);
- void nand_init(void)
- {
- rGPACON = rGPACON & (~(0x3f<<17)) |(0x3f<<17) ;
- rNFCONF = (TACLS<<12) |(TWRPH0<<8) |(TWRPH1<<4) |(0<<0) ;
- //非锁定,屏蔽nandflash中断,初始化ECC及锁定main区和spare区ECC,使能nandflash片选及控制器
- rNFCONT = (0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6)|(1<<5)|(1<<4)|(1<<1)|(1<<0);
- }
- //复位nand
- void nand_reset()
- {
- int i;
- NF_CE_L(); //cs
- NF_CLEAR_RB(); //清除RnB信号
- for(i=0;i<10;i++) ;
- NF_CMD(CMD_RESET); //写入复位命令
- NF_DETECT_RB(); //等待RnB信号变高,即不忙
- NF_CE_H(); //关闭nandflash片选
- }
- //读取nand的id号,首先需要写入读ID命令,然后再写入0x00地址,就可以读取到一共五个周期的芯片ID,第一个周期为厂商ID,第二个周期为设备ID,
- //第三个周期至第五个周期包括了一些具体的该芯片信息
- U8 read_id(void)
- {
- int i;
- U8 first, second, third, forth, fifth; //分别读取1---5个周期的数据
- NF_CE_L(); //cs
- //NF_CLEAR_RB(); //清除RnB信号
- // for(i=0;i<10;i++) ;
- NF_CMD(CMD_READID); //读ID命令
- NF_ADDR(0x0); //写0x00地址
- first = NF_RDDATA8() ; //厂商ID: 0Xec
- second = NF_RDDATA8() ; //设备ID,一般从这个参数可以判断出nand的一些参数
- third = NF_RDDATA8() ; //0x00
- forth = NF_RDDATA8() ; //0x95
- fifth = NF_RDDATA8() ; // 0x40
- NF_CE_H(); //关闭nandflash片选
- return second;
- }
- //带硬件ECC校验的读,page_numer为页号,每页2K
- U8 nand_readpage_ecc(U32 page_number, U8 *buffer)
- {
- int i;
- U32 mainecc0, spareecc; //用于存放ecc的临时值
- NF_RSTECC(); //复位ECC
- NF_MECC_UnLock() //解锁主区ECC
- NF_CE_L(); //cs
- NF_CLEAR_RB(); //清除RnB信号
- NF_CMD(CMD_READ1); //页读命令周期1
- //写入地址,首先写入列地址(也即相对每一个页开始的地址),再写入行地址(也即页号)
- NF_ADDR(0x00); //列地址A0~A7,这里直接从每页的开始读可以使用下面被注释的4行代码优化
- NF_ADDR(0x00); //列地址A8~A11
- // page = page_number/4;
- // data_addr = 512 *(page_number%4);
- // NF_ADDR(data_addr&0xff);
- // NF_ADDR((data_addr>>8)&0xff);
- NF_ADDR((page_number) & 0xff); //行地址A12~A19
- NF_ADDR((page_number >> 8) & 0xff); //行地址A20~A27
- NF_CMD(CMD_READ2); //页读命令周期2
- NF_DETECT_RB(); //等待RnB信号变高,即不忙
- for(i=0;i<2048;i++)
- buffer[i] = NF_RDDATA8() ;
- NF_MECC_Lock(); //锁定main区ECC值
- NF_SECC_UnLock(); //解锁spare区ECC
- mainecc0=NF_RDDATA(); //读spare区的前4个地址内容,即第2048~2051地址,这4个字节为main区的ECC
- //把读取到的main区的ECC校验码放入NFMECCD0/1的相应位置内
- rNFMECCD0=((mainecc0&0xff00)<<8)|(mainecc0&0xff);
- rNFMECCD1=((mainecc0&0xff000000)>>8)|((mainecc0&0xff0000)>>16);
- NF_SECC_Lock(); //锁定spare区的ECC值
- spareecc=NF_RDDATA(); //继续读spare区的4个地址内容,即第2052~2055地址,其中前2个字节为spare区的ECC值
- //把读取到的spare区的ECC校验码放入NFSECCD的相应位置内
- rNFSECCD=((spareecc&0xff00)<<8)|(spareecc&0xff);
- NF_CE_H(); //关闭nandflash片选
- //判断所读取到的数据是否正确
- if ((rNFESTAT0&0xf) == 0x0)
- return 0x66; //正确
- else
- return 0x44;
- }
- U8 nand_writepage_ecc(U32 page_number, U8 *buffer)
- {
- int i,stat;
- U32 mecc0, secc; //用于存放ecc的临时值
- char ECCBuf[10];
- i = nand_is_badblock(page_number>>6) ;
- if( i ==0x33)
- return 0x42 ; //坏块
- NF_RSTECC(); //复位ECC
- NF_MECC_UnLock() //解锁主区ECC
- NF_CE_L(); //cs
- NF_CLEAR_RB(); //清除RnB信号
- NF_CMD(CMD_WRITE1); //页读命令周期1
- //写入地址,首先写入列地址(也即相对每一个页开始的地址),再写入行地址(也即页号)
- NF_ADDR(0x00); //列地址A0~A7,这里直接从每页的开始读可以使用下面被注释的4行代码优化
- NF_ADDR(0x00); //列地址A8~A11
- // page = page_number/4;
- // data_addr = 512 *(page_number%4);
- // NF_ADDR(data_addr&0xff);
- // NF_ADDR((data_addr>>8)&0xff);
- NF_ADDR((page_number) & 0xff); //行地址A12~A19
- NF_ADDR((page_number >> 8) & 0xff); //行地址A20~A27
- for(i=0;i<2048;i++)
- NF_WRDATA8(buffer[i]);
- NF_MECC_Lock(); //锁定main区ECC值
- mecc0=rNFMECC0; //读取main区的ECC校验码
- //把ECC校验码由字型转换为字节型,并保存到全局变量数组ECCBuf中
- ECCBuf[0]=(U8)(mecc0&0xff);
- ECCBuf[1]=(U8)((mecc0>>8) & 0xff);
- ECCBuf[2]=(U8)((mecc0>>16) & 0xff);
- ECCBuf[3]=(U8)((mecc0>>24) & 0xff);
- NF_SECC_UnLock(); //解锁spare区ECC
- for(i=0;i<4;i++)
- {
- NF_WRDATA8(ECCBuf[i]);
- }
- NF_SECC_Lock(); //锁定spare区的ECC值
- secc=rNFSECC; //读取spare区的ECC校验码
- //把ECC校验码保存到全局变量数组ECCBuf中
- ECCBuf[4]=(U8)(secc&0xff);
- ECCBuf[5]=(U8)((secc>>8) & 0xff);
- //把spare区的ECC值继续写入到spare区的第2052~2053地址内
- for(i=4;i<6;i++)
- {
- NF_WRDATA8(ECCBuf[i]);
- }
- NF_CMD(CMD_WRITE2); //页读命令周期2
- Delay(100);
- NF_CMD(CMD_STATUS); //读状态命令
- //判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
- do {
- stat = NF_RDDATA8();
- }while(!(stat&0x40));
- NF_CE_H(); //关闭nandflash片选
- //判断所读取到的数据是否正确
- if (stat & 0x1)
- {
- i = rNF_MarkBadBlock(page_number>>6); //标注该页所在的块为坏块
- if (i == 0x21)
- return 0x43 ; //标注坏块失败
- else
- return 0x44; //写操作失败
- }
- else
- return 0x66;
- }
- U8 nand_random_readpage(U32 page_number, U32 add)
- {
- NF_CE_L(); //cs
- NF_CLEAR_RB(); //清除RnB信号
- NF_CMD(CMD_READ1); //页读命令周期1
- //写入地址,首先写入列地址(也即相对每一个页开始的地址),再写入行地址(也即页号)
- NF_ADDR(0x00); //列地址A0~A7,这里直接从每页的开始读可以使用下面被注释的4行代码优化
- NF_ADDR(0x00); //列地址A8~A11
- // page = page_number/4;
- // data_addr = 512 *(page_number%4);
- // NF_ADDR(data_addr&0xff);
- // NF_ADDR((data_addr>>8)&0xff);
- NF_ADDR((page_number) & 0xff); //行地址A12~A19
- NF_ADDR((page_number >> 8) & 0xff); //行地址A20~A27
- NF_CMD(CMD_READ2); //页读命令周期2
- NF_DETECT_RB(); //等待RnB信号变高,即不忙
- NF_CMD(CMD_RANDOMREAD1); //随意读命令周期1
- //页内地址
- NF_ADDR((char)(add&0xff)); //列地址A0~A7
- NF_ADDR((char)((add>>8)&0x0f)); //列地址A8~A11
- NF_CMD(CMD_RANDOMREAD2); //随意读命令周期2
- return NF_RDDATA8(); //读取数据
- }
- U8 nand_random_writepage(U32 page_number, U32 add, U8 data)
- {
- U8 stat;
- NF_CE_L(); //cs
- NF_CLEAR_RB(); //清除RnB信号
- NF_CMD(CMD_WRITE1); //页读命令周期1
- //写入地址,首先写入列地址(也即相对每一个页开始的地址),再写入行地址(也即页号)
- NF_ADDR(0x00); //列地址A0~A7,这里直接从每页的开始读可以使用下面被注释的4行代码优化
- NF_ADDR(0x00); //列地址A8~A11
- // page = page_number/4;
- // data_addr = 512 *(page_number%4);
- // NF_ADDR(data_addr&0xff);
- // NF_ADDR((data_addr>>8)&0xff);
- NF_ADDR((page_number) & 0xff); //行地址A12~A19
- NF_ADDR((page_number >> 8) & 0xff); //行地址A20~A27
- NF_CMD(CMD_RANDOMWRITE); //页读命令周期2
- //页内地址
- NF_ADDR((char)(add&0xff)); //列地址A0~A7
- NF_ADDR((char)((add>>8)&0x0f)); //列地址A8~A11
- NF_WRDATA8(data); //写入数据
- NF_CMD(CMD_WRITE2); //页写命令周期2
- //判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
- do {
- stat = NF_RDDATA8();
- }while(!(stat&0x40));
- NF_CE_H(); //关闭nandflash片选
- //判断所读取到的数据是否正确
- if (stat & 0x1)
- return 0x44;
- else
- return 0x66;
- }
- U8 nand_is_badblock(U32 block)
- {
- return nand_random_readpage(block*64, 2054);
- }
- U8 rNF_MarkBadBlock(U32 block)
- {
- U8 result;
- result = nand_random_writepage(block*64, 2054, 0x33);
- if(result == 0x44)
- return 0x21; //写坏块标注失败
- else
- return 0x60 ;
- }