南宁设计网站百度一下网页版搜索引擎
S3C2410处理器集成了8位NandFlash控制器。目前市场上常见的8位NandFlash有三星公司的k9f1208、k9f1g08、k9f2g08等。k9f1208、k9f1g08、k9f2g08的数据页大小分别为512Byte、2kByte、2kByte。它们在寻址方式上有一定差异,所以程序代码并不通用。本文以S3C2410处理器和k9f1208系统为例,讲述NandFlash的读写方法。
NandFlash的数据是以bit 的方式保存在memory cell里的,一般来说,一个cell 中只能存储一个bit,这些cell 以8 个或者16 个为单位,连成bit line,形成所谓的byte(x8)/word(x16),这就是NAND Device 的位宽。这些Line 组成Page, page 再组织形成一个Block。k9f1208的相关数据如下:
1block=32page;1page=528byte=512byte(Main Area)+16byte(Spare Area)。
总容量为=4096(block数量)*32(page/block)*512(byte/page)=64Mbyte
NandFlash以页为单位读写数据,而以块为单位擦除数据。按照k9f1208的组织方式可以分四类地址: Column Address、halfpage pointer、Page Address 、Block Address。A[0:25]表示数据在64M空间中的地址。
Column Address表示数据在半页中的地址,大小范围0~255,用A[0:7]表示;
halfpage pointer表示半页在整页中的位置,即在0~255空间还是在256~511空间,用A[8]表示;
Page Address表示页在块中的地址,大小范围0~31,用A[13:9]表示;
Block Address表示块在flash中的位置,大小范围0~4095,A[25:14] 表示;
把nand_flash想象成一本书,每一页纸就是一页数据(每一页书的宽就是flash的位宽,长就是flash的列地址),没几十页组成一个块,所有的块又组成一本书,就这样直观多了。
而页寄存器用来缓存一页数据。
#include <linux/tmd/nand.h>
#define __REGb(x) (*(volatile unsigned char *)(x))
#define __REGw(x) (*(volatile unsigned short *)(x))
#define __REGi(x) (*(volatile unsigned int *)(x))
#define NF_BASE 0x4e000000
#if defined(CONFIG_S3C2440)
#define NFCONF __REGi(NF_BASE + 0x0)
#define NFCONT __REGi(NF_BASE + 0x4)
#define NFCMD __REGb(NF_BASE + 0x8)
#define NFADDR __REGb(NF_BASE + 0xc)
#define NFDATA __REGb(NF_BASE + 0x10)
#define NFDATA16 __REGw(NF_BASE + 0x10)
#define NFSTAT __REGb(NF_BASE + 0x20)
#define NFSTAT_BUSY 1
#define nand_select() (NFCONT &= ~(1 << 1)) //cs低电平选中
#define nand_deselect() (NFCONT |= (1 << 1))
#define nand_clear_RnB() (NFSTAT |= (1 << 2))
#endif
static inline void nand_wait(void)
{
int i;
while(!(NFSTAT & NFSTAT_BUSY))
for(i=0; i<10; i++);
}
struct boot_nand_t {
int page_size;
int block_size;
int bad_block_offset;
};
static int is_bad_block(struct boot_nand_t *nand, unsigned long i)
{
unsigned char data;
unsigned long page_num;
nand_clear_RnB();
if(nand->page_size == 512)
{
NFCMD = NAND_CMD_READOOB;
NFADDR = nand->bad_block_offset & 0xf;
NFADDR = (i >> 9) & 0xff;
NFADDR = (i >> 17) & 0xff;
NFADDR = (i >> 25) & 0xff;
}else if(nand->page_size == 2048)
{
page_num = i >> 11; /* addr / 2048 */
NFCMD = NAND_CMD_READ0;
NFADDR = (nand->bad_block_offset ) & 0xff; // Check "FFh" at the column address 2048
NFADDR = (nand->bad_block_offset >> 8) & 0xff; // of the 1st and 2nd page in the block
NFADDR = (page_num ) & 0xff; // address of block
NFADDR = (page_num >> 8) & 0xff;
NFADDR = (page_num >> 16) & 0xff;
NFCMD = NAND_CMD_READSTART;
}else
{
return -1;
}
nand_wait();
data = (NFDATA & 0xff);
if(data != 0xff)
return 1;
return 0;
}
static int nand_read_page_ll(struct boot_nand_t *nand, unsigned char *buf, unsigned long addr)
{
unsigned short *ptr16 = (unsigned short *)buf;
unsigned int i, page_num;
nand_clear_RnB();
NFCMD = NAND_CMD_READ0;
if(nand->page_size == 512){
NFADDR = addr & 0xff;
NFADDR = (addr >> 9)&0xff;
NFADDR = (addr >> 17)&0xff;
NFADDR = (addr >> 25)&0xff;
}else if (nand->page_size == 2048){
page_num = addr >> 11;
NFADDR = 0; //页内地址 ,为0时读取整页
NFADDR = 0;
NFADDR = page_num & 0Xff; //确定页地址
NFADDR = (page_num >> 8) & 0xff;
NFADDR = (page_num >> 16) & 0xff;
NFCMD = NAND_CMD_READSTART;
}else {
return -1;
}
nand_wait(); //发送命令和地址后,flash ready
#if defined(CONFIG_S3C2410)
for(i = 0; i < nand->page_size; i++){
*buf = (NFDATA & 0xff);
buf++;
}
#elif defined(CONFIG_S3C2440) //循环读出数据
for(i = 0; i < (nand->page_size >> 1); i++){
*ptr16 = NFDATA16;
ptr16++;
}
#endif
return nand->page_size;
}
static unsigned short nand_read_id()
{
unsigned short ret = 0;
NFCMD = NAND_CMD_READID;
NFADDR = 0;
res = NFDATA;
res = (res << 8) | NFDATA;
return res;
}
extern unsigned int dynpart_size[];
int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
{
int i, j;
unsigned short nand_id;
struct boot_nand_t nand;
nand_select();
nand_clear_RnB();
for(i = 0; i < 10; i++)
;
nand_id = nand_read_id();
if(nand_id == 0xec76 || /* Samsung K91208 */
nand_id == 0xad76) { /*Hynix HY27US08121A*/
nand.page_size = 512;
nand.block_size = 16 * 1024;
} else if(nand_id == 0xecf1 ||/* Samsung K9F1G08U0B */
nand_id == 0xecda ||/* Samsung K9F2G08U0B */
nand_id == 0xecd3 ){/* Samsung K9K8G08 */
nand.page_size = 2048;
nand.block_size = 128 * 1024;
nand.bad_block_offset = nand.page_size;
}else{
return -1 ; //hang
}
if((start_addr & (nand.block_size - 1)) || (size & (nand.block_size - 1)))//检查块地址对齐,即地址的低17为只能为0。
return -1;
for(i = start_addr; i < (start_addr + size);){
#ifdef CONFIG_S3C2410_NAND_SKIP_BAD
if(i & (nand.block_size - 1) == 0){ //如果i时块地址对齐就执行,不是块地址对齐就不检查
if(is_bad_block(&nand, i) || is_bad_block(&nand, i+nand.page_size)){//the 1st and 2nd page in the block
i += nand.block_size;
size += nand.block_size;
continue; //跳回去检测,下一个块是否为坏块
}
}
#endif
j = nand_read_page_ll(&nand, buf, i);
i += j;
buf += j;
}
nand_deselect();
return 0;
}