NAND Flash控制器S3C2410板的Nand Flash支持由两部分组成:Nand Flash控制器(集成在S3C2410 CPU)和Nand Flash存储芯片(K9F1208U0B)两部分组成当要访问Nand Flash中的数据时,必须通过Nand Flash控制器发送命令才能完成所以Nand Flash相当于S3C2410的一个外设,而不位于它的内存地址区. 为了支持NAND Flash的启动装载,S3C2410A配置了一个叫Steppingstone的内部SRAM缓冲器当系统启动时,NAND Flash存储器的前4KB将被自动加载到Steppingstone中,然后系统自动执行这些载入的启动代码 一般情况下,这4KB的启动代码需要将NAND Flash中的内容复制到SDRAM中使用S3C2410A内部硬件ECC功能可以对NAND Flash的数据进行有效性的检查复制完成后,将在SDRAM中执行主程序NAND Flash控制其具有以下特性: * NAND Flash模式:支持读/擦除/编程NAND Flash存储器 * 自动启动模式:复位后,启动代码被传送到Steppingstone中。
传送完毕后,启动代码在Steppingstone中执行 * 具备硬件ECC(校验码:Error Correction Code)生成模块(硬件生成校验码,通过软件校验) * NAND Flash启动以后,4KB的内部SRAM缓冲器Steppingstone可以作为其他用途使用 * NAND Flash控制器不能通过DMA访问,可以使用LDM/STM指令来代替DMA操作自启动模式的执行步骤如下:(1)完成复位(2)如果自动启动模式使能,NAND Flash存储器的前4KB自动复制到Steppingstone内部缓冲器;(3)Steppingstone映射到nGCS0;(4)CPU在Steppingstone的4KB内部缓冲器中开始执行启动代码注意:在自动启动模式下,不进行ECC检测因此,应确保NAND Flash的前4KB不能有位错误(一般NAND Flash厂家都能确保)NAND Flash模式需要进行以下配置:(1)通过NFCONF寄存器设置NAND Flash配置;(2)将NAND Flash命令写入NFCONF寄存器;(3)将NAND Flash地址写入NFADDR寄存器;(4)通过NFSTAT寄存器检查NAND Flash状态,并读/写数据。
在读操作之前或者编程操作之后应该检查R/nB信号引脚配置D[7:0] 数据/命令/地址的输入/输出口(与数据总线共享)CLE 命令锁存使能(输出)ALE 地址锁存使能(输出)nFCE NAND Flash片选使能(输出)nFRE NAND Flash读使能(输出)nFWE NAND Flash写使能(输出)R/nB NAND Flash就绪/忙(输入)系统启动和NAND Flash所需的配置如下:(1)OM[1:0]=00b:使能NAND Flash控制器为自动启动模式;(2)NAND Flash存储器的页面大小应该为512字节;(3)NCON:NAND Flash存储器寻址步数选择0为3步;1为4步寻址相关寄存器NAND Flash配置寄存器NFCONF 地址0x4E000000NAND Flash命令设置寄存器NFCMD 地址0x4E000004NAND Flash地址设置寄存器NFADDR 地址0x4E000008NAND Flash数据寄存器NFDATA 地址0x4E00000CNAND Flash操作状态寄存器NFSTAT 地址0x4E000010NAND Flash ECC寄存器NFECC 地址0x4E000014下面针对三星的K9F1208U0M为例说明nand flash的读写。
NAND Flash物理组成正如硬盘的盘片被分为磁道,每个磁道又分为若干扇区,一块nand flash也分为若干block,每个block分为如干page一般而言,block、page之间的关系随着芯片的不同而不同,典型的分配是这样的:1block = 32page1page = 512bytes(datafield) + 16bytes(oob)需要注意的是,对于flash的读写都是以一个page开始的,但是在读写之前必须进行flash的擦写,而擦写则是以一个block为单位的按照这种组织方式形成三类地址Column Address:列地址,地址的低8位Page Address:页地址Block Address:块地址8个I/O引脚充当地址、数据、命令的复用端口,所以每次传地址只能传8位,而nand falsh的地址位位26位,因此读写一次nand flash需要传送4次(A[7:0] A[16:9] A[24:17] A[25]一页有528B,在每一页中,最后16个字节(OOB)用于nand flash执行完命令后设置状态用的,剩余512B又分为前半部(1st half Page Register)和后半部(2nd half Page Register)。
可以通过nand flash命令对1st half和2nd half以及OOB进行定位通过nand flash内置的指针指向各自的首地址存储操作特点:1.擦除操作的最小单位是块2.Nand Flash芯片每一位只能从1变为0,而不能从0变为1,所以在对其进行写入操作之前一定要将相应块擦除(擦除就是将相应块的位全部变为13 OOB部分的第六字节(即517字节)标志是否坏块,如果不是坏块该值为FF,否则为坏块4 除OOB第六字节外,通常至少把OOB前3字节存放Nand Flash硬件ECC码NAND Flash寻址方式512byte需要9bit来表示,对于528byte系列的NAND,这512byte被分成1st half Page Register和2nd half Page Register,各自的访问由地址指针命令来选择,A[7:0]就是所谓的column address(列地址),在进行擦除操作时不需要列地址,为什么?因为以块为单位擦除32个page需要5bit来表示,占用A[13:9],即该page在块内的相对地址A8这一位地址被用来设置512byte的1st half page还是2nd half page,0表示1st,1表示2nd。
Block的地址是由A14以上的bit来表示例如64MB(512Mb)的NAND flash(实际中由于存在spare area,故都大于这个值),共4096block,因此,需要12个bit来表示,即A[25:14],如果是128MB(1Gbit) 的528byte/page的NAND Flash,则block address用A[26:14]表示由于地址只能在I/O[7:0]上传递,因此,必须采用移位的方式进行以NAND_ADDR 为例:第1 步是传递column address,就是NAND_ADDR[7:0],不需移位即可传递到I/O[7:0]上,而halfpage pointer即A8 是由操作指令决定的,即指令决定在哪个halfpage 上进行读写,而真正的A8 的值是不需程序员关心的第2 步就是将NAND_ADDR 右移9位,将NAND_ADDR[16:9]传到I/O[7:0]上;第3 步将NAND_ADDR[24:17]放到I/O上;第4步需要将NAND_ADDR[25]放到I/O上;因此,整个地址传递过程需要4 步才能完成,即4-step addressing 如果NAND Flash 的容量是32MB(256Mbit)以下,那么,block adress最高位只到bit24,因此寻址只需要3步。
Nand flash主要的内设命令Nand flash命令执行是通过将命令字送到Nand flash控制寄存器的命令寄存器中来执行的,其命令是分周期执行的,每条命令都有一个或多个执行周期,每个执行周期都有相应的代码表示将要执行的动作功能第一时钟周期第二时钟周期读取数据寄存器Read100h/01h 读取数据寄存器下半区(OOB)Read250h 读取芯片ID90h RESETFFh 写页面(page program)(首先写入00h(A区)/01h(B区)/05h(C区)表示写入区;再写入80h开始编程模式(写入模式),接下来写入地址和数据,最后写入10h表示编程结束80h10h块擦除(block erase)60hD0h读取状态(read status)70h Nand Flash地址的计算Column Address: 列地址Column Address其实就是指定Page上的某个Byte,指定这个Byte其实也就是指定此页的读写起始地址Paage Address:页地址由于页地址总是以512Bytes对齐的,所以它的低9位总是0确定读写操作是在Flash上的哪个页进行的。
当我们得到一个Nand Flash地址srcaddr时候,我们可以这样分解出Column Address和Page Addresscolumnaddr=srcaddr%512 //column addresspageaddr=srcaddr>>9 //page address也可以这么认为,一个Nand Flash地址的A0~A7是它的column_addr,A9~A25是它的Page Address注意地址位A8并没有出现,也就是A8被忽略,在下面你将了解到这是什么原因)以read1命令为例:Read1 命令的操作分为4个Cycle,发送完读命令00h或01h(00h与01h的区别请见下文描述)之后将分4个Cycle发送参数,1st.Cycle是发送Column Address2nd.Cycle ,3rd.Cycle和4th.Cycle则是指定Page Address(每次向地址寄存器发送的数据只能是8位,所以17位的Page Address必须分成3次进行发送Read1的命令里面出现了两个命令选项,分别是00h和01h这里出现了两个读命是否令你意识到什么呢?是的,00h是用于读写1st half的命令,而01h是用于读取2nd half的命令。
现在我可以结合上图给你说明为什么K9F1208U0B的DataField被分为2个half了如上文所提及的,Read1的1st.Cycle是发送Column Address,假设我现在指定的Column Address是0,那么读操作将从此页的第0号Byte开始一直读取到此页的最后一个Byte(包括Spare Field),如果我指定的Column Address是127,情况也与前面一样,但不知道你发现没有,用于传递Column Address的数据线有8条(I/O0~I/O7,对应A0~A7,这也是A8为什么不出现在我们传递的地址位中),也就是说我们能够指定的 Column Address范围为0~255,但不要忘了,1个Page的DataField是由512个Byte组成的,假设现在我要指定读命令从第256个字节处开始读取此页,那将会发生什么情景?我必须把Column Address设置为256,但Column Address最大只能是255,这就造成数据溢出正是因为这个原因我们才把Data Field分为两个半区,当要读取的起始地址(Column Address)在0~255内时我们用00h命令,当读取的起始地址是在256~511时,则使用01h命令.假设现在我要指定从第256个byte开始读取此页,那么我将这样发送命令串column_addr=256;NF_CMD=0x01; ? 从2nd half开始读取NF_ADDR=column_addr&0xff; 1st CycleNF_ADDR=page_address&0xff; 2nd.CycleNF_ADDR=(page_address>>8)&0xff; 3rd.CycleNF_ADDR=(page_address>>16)&0xff; 4th.Cycle其中NF_CMD和NF_ADDR分别是NandFlash的命令寄存器和地址寄存器的地址解引用,我一般这样定义它们,#define rNFCMD (*(volatile unsigned char *)0x4e000004) //NADD Flash command#define rNFADDR (*(volatile unsigned char *)0x4e000008) //NAND Flash address事实上,当NF_CMD=0x01时,地址寄存器中的第8位(A8)将被设置为1(如上文分析,A8位不在我们传递的地址中,这个位其实就是硬件电路根据 01h或是00h这两个命令来置高位或是置低位),这样我们传递column_addr的值256随然由于数据溢出变为1,但A8位已经由于NF_CMD =0x01的关系被置为1了,所以我们传到地址寄存器里的值变成了A0 A1 A2 A3 A4 A5 A6 A7 A80 0 0 0 0 0 0 0 1 & 0xff = 0000 0000这8个位所表示的正好是256,这样读操作将从此页的第256号byte(2nd half的第0号byte)开始读取数据。
现在举一个例子,假设我要从Nand Flash中的第5000字节处开始读取1024个字节到内存的0x30000000处,我们这样调用read函数nf_read(5000, 0x30000000,1024);我们来分析5000这个src_addr.根据 column_addr=src_addr%512; page_address=(src_addr>>9); 我们可得出column_addr=5000%512=392page_address=(5000>>9)=9于是我们可以知道5000这个地址是在第9页的第392个字节处,于是我们的nf_read函数将这样发送命令和参数column_addr=5000%512;>page_address=(5000>>9);NF_CMD=0x01; 从2nd half开始读取NF_ADDR= column_addr &0xff; 1st Cycle A[7:0]NF_ADDR=page_address&0xff; 2nd.Cycle A[16:9]NF_ADDR=(page_address>>8)&0xff; 3rd.Cycle A[24:17]NF_ADDR=(page_address>>16)&0xff; 4th.Cycle A[25]向NandFlash的命令寄存器和地址寄存器发送完以上命令和参数之后,我们就可以从rNFDATA寄存器(NandFlash数据寄存器)读取数据了.我用下面的代码进行数据的读取.for(i=column_addr;i<512;i++){ *buf++=NF_RDDATA();}每当读取完一个Page之后,数据指针会落在下一个Page的0号Column(0号Byte).例如实现一个从某字节处开始读取size大小的数据static int NF_read(unsigned int src_addr,unsigned char *desc_addr,int size){ int i; unsigned int column_addr = src_addr % 512; unsigned int page_address =(src_addr >> 9); unsigned char * buf = desc_addr; while((unsigned int)buf < (unsigned int)(desc_addr)+size) { NF_nFCE_L(); //enable chip if(column_addr > 255) NF_CMD(0x01); else NF_CMD(0x00); NF_ADDR(cloumn_addr & 0xff); //column address A[7:0]; NF_ADDR(page_address & 0xff); //page address A[16:9] NF_ADDR((page_address >> 8) & 0xff); //A[24:17] NF_ADDR((page_address >>16) & 0xff); //A[25]; for(i=0;i<10;i++); NF_WAITRB(); for(i=column_addr;i<512;i++) { *buf++=NF_RDDATA(); } NF_nFCE_H(); column_addr = 0; page_address ++; } return ;}打开s3c2410 的datasheet page 230:我们定义如下寄存器#define rNFCONF (*(volatile unsigned *)0x4e000000) //nand flash configuration#define rNFCMD (*(volatile char *)0x4e000004 //nand flash command#define rNFADDR (*(volatile char *)0x4e000008 //nand flash address#define rNFDATA (*(volatile char *)0x4e00000c //nand flash data#define rNFSTAT (*(volatile unsigned *)0x4e000010 //nand flash opreation status#define rNFECC (*(volatile int *)0x4e000014 //nand flash ecc#define rNFECC0 (*(volatile char *)0x4e000014#define rNFECC1 (*(volatile char *)0x4e000015#define rNFECC2 (*volatile char *)0x4e000016#define NF_CMD(cmd) {rNFCMD=cmd;}#define NF_ADDR(addr) {rNFADDR=addr;}#define NF_nFCEL_L() {rNFCONF &= ~(1<<11);}#define NF_nFCLE_H() {rNFCONF |= (1<<11);}#define NF_RSTECC() {rNFCONF |= (1<<12);} //Initialize ECC#define NF_RDDATA() (rNFDATA)#define NF_WRDATA(data) {rNFDATA=data;}#define NF_WATRB() {while(!(rNFSTAT&(1<<0)));}//读一页数据的程序。
static int NF_RreadPage(int block,int page,char *buffer){ unsigned int blockpage; char *pbuf=buffer; char *oob[16]; unsigned char ecc[3]; page=page&0x1f; blockpage=(block << 5)+page; NF_RSTECC(); //Initialize ECC; NF_nFCE_L(); NF_CMD(0x00); //read command; NF_ADDR(0); //A[7:0] column=0 从第0字节开始读一直读完512B NF_ADDR(blockpage&0xff); //A[16:9]; NF_ADDR((blockpage>>8)&0xff); //A[24:17] NF_ADDR((blockpage>>16)&0xff); //A[25]; for(i=0;i<10;i++); //wait tWB(100ns) NF_WAITRB(); //wait tR(max 12us) for(i=0;i<512;i++) { *pbuf++=NF_RDDATA(); } ecc[0]=rNFECC0; ecc[1]=rNFECC1; ecc[2]=rNFECC2; for(i=0;i<16;i++) { oob=NF_RDDATA(); //read oob; } NF_nFCE_H(); if(ecc[0]==oob[0] && ecc[1] == oob[1] && ecc[2] == oob[2]) //Ecc校验; { print("ECC OK:%x,%x,%x\n",oob[0],oob[1],oob[2]); return 1; }else{ printf("ECC ERROR: read:%x,%x,%x, ECC reg:%x,%x,%x\n",oob[0],oob[1],oob[2],ecc[0],ecc[1],ecc[2]); return 0; }}static int NF_WritePage(unsigned int block,unsigned int page,char *buffer){ int i; unsigned int blockpage=(block<<5)+page; char *pbuf=buffer; oobbuf[16]={0xff}; NF_RSRECC(); NF_nFCE_L(); NF_CMD(0x00); NF_ADDR(blockpage&0xff); NF_ADDR((blockpage>>8)&0xff); NF_ADDR((blockpage>>16)&0xff); for(i=0;i<512;i++) { NF_WRDATA(*pbuf++); } oobbuf[0]=rNFECC0; oobbuf[1]=rNFECC1; oobbuf[2]=rNFECC2; oobbuf[5]=0xff; for(i=0;i<16;i++) { NF_WRDATA(oobbuf); } NF_CMD(0x10); //Write 2nd command; for(i=0;i<10;i++); //tWB=100ns; NF_WAITRB(); NF_CMD(0x70); //read status command; for(i=0;i<3;i++); if(NF_RDDATA()&0x1) //write error { NF_nFCE_H(); NF_MarkBadBlock(block); return 0; }else{ NF_nFCE_H(); return 1; }}static int NF_EraseBlock(U32 block){ U32 blockPage=(block<<5); int i;#if BAD_CHECK//坏块校验 if(NF_IsBadBlock(block)) return 0;#endif NF_nFCE_L();//NF的CE(片选)拉低 NF_CMD(0x60); // Erase one block 1st command NF_ADDR(blockPage&0xff); // 块擦除只针对页 NF_ADDR((blockPage>>8)&0xff); NF_ADDR((blockPage>>16)&0xff); NF_CMD(0xd0); // Erase one blcok 2nd command for(i=0;i<10;i++); //wait tWB(100ns)//?????? NF_WAITRB(); // Wait tBERS max 3ms. NF_CMD(0x70); // Read status command if (NF_RDDATA()&0x1) // Erase error { NF_nFCE_H(); Uart_Printf("[ERASE_ERROR:block#=%d]\n",block); NF_MarkBadBlock(block); return 0; } else { NF_nFCE_H();////NF的CE(片选)拉高 return 1; }}NAND设备存在坏块,为和上层文件系统接口,NAND设备的驱动程序必须给文件系统提供一个可靠的存储空间,这就需要ECC(Error Corection Code)校验,坏块标注、地址映射等一系列的技术手段来达到可靠存储目的。
SSFDC软件规范中,详细定义了如何利用NAND设备每个页中的冗余信息来实现上述功能这个软件规范中,很重要的一个概念就是块的逻辑地址,它将在物理上可能不连续、不可靠的空间分配编号,为他们在逻辑空间上给系统文件提供一个连续可靠的存储空间表3给出了SSFDC规范中逻辑地址的标注方法在系统初始化的时候,驱动程序先将所有的块扫描一遍,读出他们所对应的逻辑地址,并把逻辑地址和虚拟地址的映射表建好系统运行时,驱动程序通过查询映射表,找到需要访问的逻辑地址所对应的物理地址然后进行数据读写 表3 冗余字节定义字节序号内容字节序号内容512用户定义数据520后256BECC校验和513521514522515523块逻辑地址516数据状态524517块状态525前256BECC校验和518块逻辑地址1526519527表4给出了块逻辑地址的存放格式,LA表示逻辑地址,P代表偶校验位逻辑地址只有10bit,代表只有1024bit的寻址空间而SSFDC规范将NAND设备分成了多个zone,每个zone 内有1024块,但这物理上的1024块映射到逻辑空间只有1000块,其他的24块就作为备份使用,当有坏块存在时,就可以以备份块将其替换。
表4 逻辑地址格式D7D6D5D4D3D2D1D0 00010LA9LA8LA7第518 523字节 LA6LA5LA4LA3LA2LA1LA0P第519 524字节 有了以上的软件规范,就可以对NAND设备写出较标准的ECC校验,并可以编写检测坏块、标记坏块、建立物理地址和逻辑地址的映射表的程序了static int NF_IsBadBlock(unsigned int block){ int i; unsigned int blockPage; unsigned char data; blockPage=(block<<5); // For 2'nd cycle I/O[7:5] NF_nFCE_L(); NF_CMD(0x50); // Spare array read command NF_ADDR(517&0xf); // Read the mark of bad block in spare array(M addr=5) NF_ADDR(blockPage&0xff); // The mark of bad block is in 0 page NF_ADDR((blockPage>>8)&0xff); // For block number A[24:17] NF_ADDR((blockPage>>16)&0xff); // For block number A[25] for(i=0;i<10;i++); // wait tWB(100ns) //????? NF_WAITRB(); // Wait tR(max 12us) data=NF_RDDATA(); NF_nFCE_H(); if(data!=0xff) { printf("[block %d has been marked as a bad block(%x)]\n",block,data); return 1; } else { return 0; }}static int NF_MarkBadBlock(U32 block){ int i; unsigned int blockPage=(block<<5); seBuf[0]=0xff; seBuf[1]=0xff; seBuf[2]=0xff; seBuf[5]=0x44; // Bad blcok mark=0 NF_nFCE_L(); NF_CMD(0x50); //read OOB NF_CMD(0x80); // Write 1st command NF_ADDR(0x0); // The mark of bad block is NF_ADDR(blockPage&0xff); // marked 5th spare array NF_ADDR((blockPage>>8)&0xff); // in the 1st page. NF_ADDR((blockPage>>16)&0xff); // for(i=0;i<16;i++) { NF_WRDATA(seBuf); // Write spare array } NF_CMD(0x10); // Write 2nd command for(i=0;i<10;i++); //tWB = 100ns. NF_WAITRB(); // Wait tPROG(200~500us) NF_CMD(0x70); for(i=0;i<3;i++); //twhr=60ns if (NF_RDDATA()&0x1) // Spare arrray write error { NF_nFCE_H(); printf("[Program error is occurred but ignored]\n"); } else { NF_nFCE_H(); } printf("[block #%d is marked as a bad block]\n",block); return 1;}int search_logic_block(void) //建立物理地址到逻辑地址的映射表{ unsigned int block,i,blockPage,logic_no,zone,zone_i; unsigned char oob[16]; for(i=0;i>8)&0xff); // Block & page num. NF_ADDR((blockPage>>16)&0xff); NF_WATIRB(); //等待R/B#信号有效 for(i=0;i<16;i++) se=NF_RDDATA(); // Write spare array NF_WATIRB(); if(oob[5]!=0xff)[q8] //检测是否存在坏块 printk("\n\rphysic block %d is bad block\n\r",block); else if(oob[7]!=se[12][q9] ) printk("block address1:%d!=block address2 %d\n\r",oob[7],oob[12]); else if((oob[6][q10] &0xf8)==0x10) { //计算该block对应的逻辑地址 logic_no=((0x7&oob[6])<<7)+(se[7]>>1)+zone_i*1000; if(lg2ph[logic_no]!=0xffff) //说明有2个block拥有相同的逻辑地址 printk("physical block %d and block %d have the same logic number %d\n",lg2ph[logic_no],block,logic_no); else lg2ph[logic_no]=block; //将该block的逻辑地址关系记入lg2ph表 logic_number++; } else if(oob[7]==0xff) //说明该block尚未编号 {space_block[space_nr]=block; space_nr++; } } } printk("there are totally %d logic blocks\n\r",logic_number); NF_nFCE_H(); return logic_number;}这段代码的主要作用就是产生数组lg2ph[],这个数组的含义就是“块物理地址=lg2ph[逻辑地址]”。
static unsigned short NF_CheckId(void){ int i; unsigned short id; NF_nFCE_L(); NF_CMD(0x90); NF_ADDR(0x0); for(i=0;i<10;i++); //wait tWB(100ns)////????? id=NF_RDDATA()<<8; // Maker code(K9S1208V:0xec) id|=NF_RDDATA(); // Devide code(K9S1208V:0x76) NF_nFCE_H(); return id;}static void NF_Reset(void){ int i; unsigned short id; NF_nFCE_L(); NF_CMD(0xFF); //reset command for(i=0;i<10;i++); //tWB = 100ns. NF_WAITRB(); //wait 200~500us; NF_nFCE_H();}static void NF_Init(void){ rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0); // 1 1 1 1, 1 xxx, r xxx, r xxx // En 512B 4step ECCR nFCE=H tACLS tWRPH0 tWRPH1 NF_Reset();}。