"); //-->
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????地址0x4E000000
?
NAND Flash命令设置寄存器
NFCMD??????地址0x4E000004
?
NAND Flash地址设置寄存器
NFADDR?????地址0x4E000008
?
NAND Flash数据寄存器
NFDATA?????地址0x4E00000C
?
NAND Flash操作状态寄存器
NFSTAT?????地址0x4E000010
?
NAND Flash ECC寄存器
NFECC???????地址0x4E000014
?
?
?
下面针对三星的K9F1208U0M为例说明nand flash的读写。
?
NAND Flash物理组成
正如硬盘的盘片被分为磁道,每个磁道又分为若干扇区,一块nand flash也分为若干block,每个block分为如干page。一般而言,block、page之间的关系随着芯片的不同而不同,典型的分配是这样的:
1block = 32page
1page = 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,所以在对其进行写入操作之前一定要将相应块擦除(擦除就是将相应块的位全部变为1
3 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控制寄存器的命令寄存器中来执行的,其命令是分周期执行的,每条命令都有一个或多个执行周期,每个执行周期都有相应的代码表示将要执行的动作。
?
功能 |
第一时钟周期 |
第二时钟周期 |
读取数据寄存器 Read1 |
00h/01h |
? |
读取数据寄存器下半区(OOB) Read2 |
50h |
? |
读取芯片ID |
90h |
? |
RESET |
FFh |
? |
写页面(page program) (首先写入00h(A区)/01h(B区)/05h(C区)表示写入区;再写入80h开始编程模式(写入模式),接下来写入地址和数据,最后写入10h表示编程结束。 ? |
80h |
10h |
块擦除(block erase) |
60h |
D0h |
读取状态(read status) |
70h |
? |
?
?
?
?
Nand Flash地址的计算
Column Address: 列地址。Column Address其实就是指定Page上的某个Byte,指定这个Byte其实也就是指定此页的读写起始地址。
Paage Address:页地址。由于页地址总是以512Bytes对齐的,所以它的低9位总是0。确定读写操作是在Flash上的哪个页进行的。
当我们得到一个Nand Flash地址srcaddr时候,我们可以这样分解出Column Address和Page Address
columnaddr=srcaddr%512??//column address
pageaddr=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 Address。2nd.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 Cycle
NF_ADDR=page_address&0xff;??????????????????????2nd.Cycle
NF_ADDR=(page_address>>8)&0xff;?????????????3rd.Cycle
NF_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??A8
0?????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=392
page_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[i]=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[i]);
????}
?
????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 冗余字节定义
字节序号 |
内容 |
字节序号 |
内容 |
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。
|