一、需要什么
我们的目的是编写Nand Flash 的裸机代码,可以读写Nand Flash, 那么我们应该从哪开始呢。
以我为例,我板子上的cpu 是时s5pv210, 板子上的nand flash的芯片是1Gbyte 的 NAND FLASH(K9K8G08U0B)。
我们要做的事:
- 仔细阅读K9K8G08U0B的芯片手册,了解基本知识。
- s5pv210 内部已经集成了nand flash的控制器,所以我们只需要阅读s5pv210芯片手册的Nand Flash控制器那部分就可以了。
二、K9K8G08U0B 基本信息
NAND Flash的数据是以bit的方式保存在memory cell,一般来说,一个cell 中只能存储一个bit(即SLC类型的NAND)。这些cell 以8个或者16个为单位,连成bit line,形成所谓的byte(x8)或word(x16),这就是NAND Device的位宽。这些Line会再组成Page,(NAND Flash有多种结构,以NAND Flash是K9F1208为例,下面内容针对三星的K9F1208U0M),每页528Bytes(512byte(Main Area)+16byte(Spare Area)),每32个page形成一个Block(32528B)。具体一片flash上有多少个Block视需要所定。使用的三星k9f1208U0M具有4096个block,故总容量为4096(32*528B)=66MB,但是其中的2MB是用来保存ECC校验码等额外数据的,故实际中可使用的为64MB。
NAND flash以页为单位读写数据,而以块为单位擦除数据。按照这样的组织方式可以形成所谓的三类地址:Column Address(Starting Address of the Register)列地址,地址的低8位;Page Address:页地址; Block Address:块地址;对于NAND Flash来讲,地址和命令只能在I/O[7:0]上传递,数据宽度是8位。
NAND Flash地址的表示:
512byte需要9bit来表示,对于512byte系列的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]表示。而page address就是blcok address | page address in block。NAND Flash的地址表示为:Block Address|Page Address in block|halfpage pointer|Column Address。地址传送顺序是Column Address,Page Address,Block Address。
由于地址只能在I/O[7:0]上传递,因此,必须采用移位的方式进行。例如,对于512Mbit x8的NAND flash,地址范围是0~0x3FF_FFFF,只要是这个范围内的数值表示的地址都是有效的。以发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步。(具体参考芯片手册)
下面,就x16的NAND flash器件稍微进行一下说明。由于一个page的main area的容量为256word,仍相当于512byte。但是,这个时候没有所谓的1st halfpage和2nd halfpage之分了,所以,bit8就变得没有意义了,也就是这个时候,A8完全不用管,地址传递仍然和x8器件相同。除了这一点之外,x16的NAND使用方法和x8的使用方法完全相同。
从芯片手册得知:
1).每页 = 2112Bytes(2048byte(Main Area)+64byte(Spare Area));
2).每块 = 64页;
3).一共8192块;
问:既然得知了该芯片的大小,那么列地址,页内地址,块地址应该分别是多少位呢?为什么?
答:
1).A0~A11表示列地址,因为2的12次方等于4K,但是硬件只有2K,所以发地址的时候,要不A11给舍弃了;
2).A12-A17是页地址,因为2的6次方刚好等于64;
3).A18到A28是块地址;
问:那么地址是怎么发送出去的呢?
答:步骤如下:
第1步是传递column address,就是NAND_ADDR[7:0],不需移位即可传递到I/O[7:0]上;
第2步任然传的是列地址,但是由于每页只有2K,故要对地址做以下操作:
(addr>>8) & 0x7 /* 右移8位,就是为了去掉第八位的地址,因为已经在第一步发出了,与上0x7,是为了只保留A8~A10,即上文所说的舍弃A11 */
第3步就是将NAND_ADDR右移11位,将NAND_ADDR[19:12]传到I/O[7:0]上;
第4步将是将NAND_ADDR右移19位,将NAND_ADDR[27:20]放到I/O上;
第5步将是将NAND_ADDR右移27位,将NAND_ADDR[28]放到I/O上;
三、s5pv210 nand flash 控制器
在这里我们不使用ECC, 所以比较简单,只需要配置几个寄存器就可以了。
NandFlash 配置寄存器:NFCONF
NandFlash 控制寄存器:NFCONT
NandFlash 命令寄存器:NFCMMD
NandFlash 地址寄存器:NFADDR
NandFlash 数据寄存器:NFDATA
NandFlash 状态寄存器:NFSTAT
四、编写代码
- 初始化s5pv210 nand flash 控制器
NFCONF寄存器:
AddrCycle = 1,When page size is 2K or 4K, 1 = 5 address cycle,Mini210S的NAND Flash的页大小为2k,所有是5个地址周期;
PageSize = 0,When MLCFlash is 0, the value of PageSize is as follows: 0 = 2048 Bytes/page,Mini210S使用的是SLC NAND Flash且每页大小为2k;
MLCFlash = 0,在此使用的是SLC NAND Flash;
TWRPH1/TWRPH0/TACLS是关于访问时序的设置,需对照NAND Flash芯片手册设置,这里不再详细解释,分别取TWRPH1=1,TWRPH0=4,TACLS=1;
ECCType0/MsgLength,我们的裸机代码没有使用到ECC,所有不用设置这两个标志。
MODE = 1,使能NAND Flash控制器;
Reg_nCE0 = 1,取消片选,需要操作NAND Flash时再发片选;
Reg_nCE1 = 1, 取消片选,需要操作NAND Flash时再发片选;
InitMECC/InitSECC/SECCLock/MECCLock,我们的裸机代码不涉及ECC,这4个标志位随便设置即可;
RnB_TransMode = 0,Detect rising edge,RnB是NAND Flash的状态探测引脚,我们使用上升沿触发;
EnbRnBINT = 0 ,禁止RnB中断;
EnbIllegalAccINT = 0,禁止Illegal access 中断 ;
EnbMLCDecInt/EnbMLCEncInt为MCL相关,不用设置;
LOCK = 0,我们没有用到Soft Lock,所以禁止Soft Lock;
LockTight = 0,我们没有用到Lock-tight,所有禁止Lock-tight;
MLCEccDirection,MLC相关,可不用设置。
我们还需要配置GPIO来支持Nand Flash, 这里不多讲。只需要查看原理图,配置对应的GPIO口复用就行了。
void nand_init()
{
// SLC / 2048B / 5 address cycle
NFCONF = (0 << 0) | (1 << 1) | (0 << 3) | (0 << 4)
| (TWRPH1 << 4) | (TWRPH0 << 8) | (TACLS << 12);
NFCONT = (1 << 0) | (0x3 << 1) | (0 << 8) | (0 << 9)
| (0 << 10) | (0 << 16) | (0 << 17) | (0x3 << 22);
// 设置
MP0_1CON &= ~(0xF << 8 | 0xF << 12);
MP0_1CON |= (0x3 << 8 | 0x3 << 12);
// 设置引脚0、1、2、3、4分别为 NF_CLE,NF_ALE, NF_FWEn,NF_FREn,NF_RnB[0]
MP0_3CON &= ~(0xFFFFF);
MP0_3CON |= 0x22222;
nand_reset();
}
- nand flash芯片复位
很简单,只要发生命令0xFF就可以了
void nand_reset()
{
nand_select_chip(); // 发片选
nand_send_cmd(NAND_CMD_RESET);
nand_wait_idle(); //
nand_deselect_chip(); // 取消片选
}
- 发命令、读数据、写数据
void nand_send_cmd(unsigned char cmd)
{
NFCMMD = cmd;
}
unsigned char nand_read()
{
return NFDATA;
}
void nand_write(unsigned char c)
{
NFDATA = c;
}
- 读芯片ID
void nand_read_id(nand_info_t *id_info)
{
nand_select_chip(); // 发片选
nand_send_cmd(NAND_CMD_READ_ID); // 发命令
nand_send_addr(0x00); // 发地址
nand_wait_idle(); // 等待就绪
id_info->IDm = nand_read(); // 读数据
id_info->IDd = nand_read(); // 读数据
id_info->ID3rd = nand_read();
id_info->ID4th = nand_read();
id_info->ID5th = nand_read();
nand_deselect_chip(); // 取消片选
}
- 发送地址
首先根据页大小来获取页地址和页内偏移地址,然后通过 5 个周期将地址发送出去,实质就是写NFADDR 寄存器,具体每个周期如何发送,查阅 NAND Flash 芯片手册可知,见下图:
void nand_send_addr(unsigned long addr)
{
int i;
unsigned long row, col;
// 计算出行地址,也就是哪一页
row = addr / NAND_PAGE_SIZE;
// 计算出列地址,也就是页地址
col = addr % NAND_PAGE_SIZE;
// 1st 发送列地址低8位
NFADDR = (col & 0xFF);
for(i = 0; i < 10; i++);
// 2st 发送列地址低9-12位
NFADDR = ((col >> 8) & 0xF);
for(i = 0; i < 10; i++);
// 3st 发送行地址低8位
NFADDR = (row & 0xFF);
for(i = 0; i < 10; i++);
// 4st 发送行地址低9-16位
NFADDR = ((row >> 8) & 0xFF);
for(i = 0; i < 10; i++);
// 5st 发送行地址低17-19位
NFADDR = ((row >> 16) & 0x7);
for(i = 0; i < 10; i++);
}
- 读Nand Flash 状态寄存器
unsigned char nand_read_status()
{
int i;
unsigned char ch;
nand_select_chip(); // 发片选
nand_send_cmd(NAND_CMD_READ_STATUS); // 发命令
for (i = 0; i < 10; i++);
ch = nand_read(); // 读数据
nand_deselect_chip(); // 取消片选
return ch;
}
- 擦除Nand Flash 某个块
int nand_erase_block(unsigned int block)
{
int i;
unsigned long row;
unsigned char st;
// 得到行地址,就是哪个页
row = block * NAND_PAGE_SIZE;
nand_select_chip(); // 发片选
nand_send_cmd(NAND_CMD_ERASE_1st); // 发命令60H
// 分3clcye写地址
NFADDR = (row & 0xFF);
for (i = 0; i < 10; i++);
NFADDR = ((row >> 8) & 0xFF);
for (i = 0; i < 10; i++);
NFADDR = ((row >> 16) & 0x7);
for (i = 0; i < 10; i++);
nand_send_cmd(NAND_CMD_ERASE_2st); // 发命令D0H
nand_wait_idle();
st = nand_read_status(); // 读状态
if (st & 1) {
nand_deselect_chip();
return -1;
}
nand_deselect_chip();
return 0;
}
- 顺序读Nand Flash 数据
// TODO
- 顺序写数据到Nand Falsh
// TODO