基于DJYOS下S3C2440A的nand驱动移植过程
王建忠 2011-9-14
1 开发环境及说明
软件平台:DJYOS 1.0.0
硬件平台:TQ2440开发板(CPU:S3C2440)NAND芯片:K9F2G08B0B(256M)
1.1 说明
DJYOS下的nand驱动,围绕着DJYOS的文件系统djyfs而写接口。S3C2440 内部集成了一个 Nand flash 控制器。S3C2440 的 Nand flash 控制器包含了如下的特性:
一个引导启动单元Nand Flash 存储器接口,支持 8 位或 16 位的每页大小为 256 字,512 字节,1K字和 2K 字节的 Nand flash。
注:这里,主要是为了介绍nand flash在DJYOS环境下移植的介绍。关于NAND的一些细节以及S3C2440的nand控制器,网上这类资料已经非常多了。请大家参考网上的资料。
1.2 Nand电路图
图1-1 nand电路图
图1-1的左边为 K9F2G08U0B与 TQ2440 的连接图,原理方面就不多介绍,去看
看 datasheet 估计就懂得了,右边的部分是 S3C2440 的 Nand 控制器的配置。配
置引脚 NCON,GPG13,GPG14 和 GPG15 用来设置 Nand Flash 的基本信息,Nand
控制器通过读取配置引脚的状态获取外接的 Nand Flash 的配置信息,图1-2 是这
四个配置引脚的定义:
图1-2:Nand flash 引脚配置信息表
由于 K9F2G08U0B 的总线宽度为 8 位,页大小为 2048 字节,需要 5 个寻址命令, 所以NCON、 GPG13 和GPG14应该接高电平, GPG15 应该接低电平。 K9F2G08U0B没有地址或数据总线,只有8个IO口, 这 8 个 IO 口用于传输命令、 地址和数据。
2 nand flash简介
nand的资料很多,细节就不提,这里只提到两点。
2.1 K9F2G08U0B的存储阵列
图 2-1 K9F2G08U0B的存储阵列
由图2-1,我们可以知道:K9F2G08U0B的一页为(2K+64)字节(2K 表示的
是 main 区容量,64 表示的是 spare 区容量),它的一块为 64 页,而整个设备包
括了 2048 个块。这样算下来一共有 2112M 位容量,如果只算 main 区容量则有
256M 字节(即 256M×8 位)。 要实现用 8 个 IO 口来要访问这么大的容量,如图 2-1 所示:K9F2G08U0A 规定了用 5 个周期来实现。第一个周期访问的地址为 A0-A7;第二个周期访问的地址为A8-A11,它作用在 IO0-IO3 上,而此时 IO4-IO7 必须为低电平;第三个周期访问的地址为 A12-A19;第四个周期访问的地址为 A20-A27;第五个周期访问的地址为 A28,它作用在 IO0上,而此时IO1~IO7 必须为低电平。前两个周期传输的是列地址,后三个周期传输的是行地址。通过分析可知,列地址是用于寻址页内空间,行地址用于寻址页,如果要直接访问块,则需要从地址 A18 开始(2^(28-18+1)=2048块)。由于所有的命令、地址和数据全部从8 位 IO 口传输,所以 Nand flash 定义了一个命令集来完成各种操作。有的操作只需要一个命令(即一个周期)即可,而有的操作则需要两个命令(即两个周期)来实现。
2.2 K9F2G08U0B的命令说明
图2-2 K9F2G08U0B命令表
图2-2是K9F2G08U0B芯片操作读写、擦除等操作的命令表。
由于时序都有S3C2440的nand控制器控制。所以,这里的nand驱动。只要好好弄明白K9F2G08U0B这两个要点,就很容易掌握nand驱动。
3 DJYOS下的nand驱动移植
DJYOS下的nand驱动提供的接口,是专门根据djyfs文件系统而写的。并且带有ECC校验、坏块标记。这一章,就详细的介绍,nand在DJYOS下是如何移植。
3.1 数据结构体
struct nand_table { uint16_t vendor_chip_id; u16 oob_size; uint16_t pages_per_block; u32 blocks_sum; u32 block_size; char *chip_name; }; |
这个数据结构体,记录了nand各项参数。最后一个成员chip_name,是相对于DJYOS而言,DJYOS,将会把芯片挂载到资源树上,通过这个名字,在资源数上来识别该芯片。这个数据结构体,不单单保存K9F2G08U0B的各项参数,它将构造成一个表。把常用的nand芯片的各项参数都罗列出来。因为,这份nand驱动要增加其可移植性。要支持好几种芯片。
struct nand_table tg_nand_table[] = { {0x9876,16,32,4096,16384,"Toshiba TH58512FT,1.8v,64Mbytes"}, {0xec36,16,32,4096,16384,"samsung k9f1208,1.8v,64Mbytes"}, {0xec76,16,32,4096,16384,"samsung k9f1208,3.3v,64Mbytes"}, {0xec73,16,32,1024,16384,"samsung k9f2808,3.3v,16Mbytes"}, {0xec33,16,32,1024,16384,"samsung k9f2808,1.8v,16Mbytes"}, {0xecda,64,64,2048,131072,"samsung K9F2G08,3.3v,256Mbytes"} }; |
这表,记录了常用的几个型号的nand芯片。如果里面没有你需要的芯片型号参数,那么就根据你芯片型号的属性,把各项参数添加进去,再增加一个型号参数。
当系统启动,nand被初始化之后。系统就会把这个芯片挂载到资源设备树上。以后要使用该芯片的参数,就直接搜索资源设备树,就可以使用你需要的芯片型号的参数。
3.2 写入地址
K9F2G09U0B是大块,写入地址,需要用到5个周期。有关于nand的存储阵列,其地址周期。请看第2.1章。这里将用代码,如何表达写入地址。
void __write_address_nand(uint32_t addr) { pg_nand_reg->NFADDR = (uint8_t)((addr) & 0xff); pg_nand_reg->NFADDR = (uint8_t)((addr>>8) & 0x7); pg_nand_reg->NFADDR = (uint8_t)((addr>>11) & 0xff); pg_nand_reg->NFADDR = (uint8_t)((addr>>19) & 0xff); pg_nand_reg->NFADDR = (uint8_t)((addr>>27) & 0xff); } |
这里,前面两行是page,后面三行是block。根据A0 ~ A7,A9 ~ A11,A12 ~ A19,A20~A27,确定addr,每写入一个周期右移几位。
3.3 读取芯片ID
Nand芯片的每一个型号,都有固定的芯片ID和制造商ID。用户通过读取ID,确认是什么类型的nand芯片。
下表是K9F2G08U0B读取id的代码。过程如下
1、 激活芯片片选
2、 写入复位命令,芯片复位(记得等待芯片内部操作完成)
3、 写入读取芯片ID。
4、 读取芯片ID
5、 关掉片选
//----读flash 芯片id----------------------------------------------------------- //功能: 读取flash芯片的id //参数: 无 //返回: 芯片id //----------------------------------------------------------------------------- uint16_t __read_chip_id (void) { uint16_t id; ce_active();
__write_command_nand(cn_nand_reset); __wait_ready_nand(); //等待芯片内部操作完成 __write_command_nand(cn_nand_read_id);
pg_nand_reg->NFADDR = 0; // Address. 1cycle
id = pg_nand_reg->NFDATA<<8; id |= pg_nand_reg->NFDATA;
ce_inactive(); return id ; } |
3.4 安装nand芯片(初始化)
module_init_fs_nandflash,这个函数,我只讲nand初始化的部分。因为这个函数,在DJYOS里,不但是初始化nand驱动,而且还用来加载djyfs文件系统。如何加载djyfs,大家可以看教材里文件系统介绍,module_init_fs_nandflash里最后调用的DFFSD_install_chip,就是加载djyfs。除去这一步,nand的初始化过程如下:
1、设置一个定时器,使用cn_int_line_timer1。Nand通过命令操作的时候,会有等待时间。Djyos,使用定时器等待时间。这样,nand在等待的时候。系统可以执行其他的事情。不需要一直在等待nand芯片完成。
2、设置时钟频率
pg_nand_reg->NFCONF=(cn_talcs<<12)|(cn_twrph0<<8)|(cn_twrph1<<4)|(0<<0);
3、设置控制寄存器,对芯片进行设置。
pg_nand_reg->NFCONT =(0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6)
|(1<<5)|(1<<4)|(1<<1)|(1<<0);
4、复位芯片
5、读取芯片ID,通过ID,对tg_nand_table这个表进行识别,将nand_table数据结构体成员填充到tg_samsung_nand相对应的成员里。这就是以后的芯片资源了,在DFFSD_install_chip里,会被挂到flash芯片设备树上。
6、申请一页大小的全局内存pg_sector_buf。这个是为了读写的时候作为缓冲使用。
7、填充tg_samsung_nand结构体剩下的成员。这个结构体是将djyfs和nand桥梁。Djyfs通过tg_samsung_nand的成员可以访问nand。Nand也可以自己使用tg_samsung_nand。
8、DFFSD_install_chip,这个函数就不介绍了。
ptu32_t module_init_fs_nandflash(ptu32_t para) { uint16_t chip_id; char *name;
//初始化timer4作为等候flash内部操作完成的中断,无需ISR timer_set_clk_source(1,0); //主频的1/2分频 //预分频数:设置定时器输入时钟1Mhz timer_set_precale(0,(u32)cn_timer_clk/1000000/2 -1); timer_set_type(1,1); //设置定时器连续工作 int_setto_asyn_signal(cn_int_line_timer1); //设为异步信号 // int_restore_asyn_line(cn_int_line_timer1);//启动中断,
//nand config register // TACLS [14:12] CLE&ALE duration = HCLK*TACLS. // TWRPH0 [10:8] TWRPH0 duration = HCLK*(TWRPH0+1) // TWRPH1 [6:4] TWRPH1 duration = HCLK*(TWRPH1+1) // AdvFlash(R) [3] Advanced NAND, 0:256/512, 1:1024/2048 // PageSize(R) [2] NAND memory page size // when [3]==0, 0:256, 1:512 bytes/page. // when [3]==1, 0:1024, 1:2048 bytes/page. // AddrCycle(R) [1] NAND flash addr size // when [3]==0, 0:3-addr, 1:4-addr. // when [3]==1, 0:4-addr, 1:5-addr. // BusWidth(R/W) [0] NAND bus width. 0:8-bit, 1:16-bit. pg_nand_reg->NFCONF =(cn_talcs<<12)|(cn_twrph0<<8)|(cn_twrph1<<4)|(0<<0);
//nand control register // Lock-tight [13] 0:Disable lock, 1:Enable lock. // Soft Lock [12] 0:Disable lock, 1:Enable lock. // EnablillegalAcINT[10] Illegal access interupt control.0:Disable,1:Enable // EnbRnBINT [9] RnB interrupt. 0:Disable, 1:Enable // RnB_TrandMode[8] RnB transition detection config.0:Low->High,1:High->Low // SpareECCLock [6] 0:Unlock, 1:Lock // MainECCLock [5] 0:Unlock, 1:Lock // InitECC(W) [4] 1:Init ECC decoder/encoder. // Reg_nCE [1] 0:nFCE=0, 1:nFCE=1. // NANDC Enable [0] operating mode. 0:Disable, 1:Enable. pg_nand_reg->NFCONT = (0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6) |(1<<5)|(1<<4)|(1<<1)|(1<<0); __reset_nand(); chip_id = __read_chip_id(); if( __parse_chip(chip_id,&name) == false) return 0; pg_sector_buf = (u8*)m_malloc_gbl(u32g_sector_size+u16g_oob_size,0); if(pg_sector_buf == NULL) { return 0; } tg_samsung_nand.query_block_ready_with_ecc= query_block_ready_ss_with_ecc; tg_samsung_nand.query_block_ready_no_ecc = query_block_ready_nand_no_ecc; tg_samsung_nand.query_ready_with_data = query_ready_with_data_nand; tg_samsung_nand.erase_block = erase_block_nand; tg_samsung_nand.check_block = check_block_nand; tg_samsung_nand.read_data_with_ecc = read_block_ss_with_ecc; tg_samsung_nand.write_data_with_ecc = write_block_ss_with_ecc; tg_samsung_nand.read_data_no_ecc = read_block_ss_no_ecc; tg_samsung_nand.write_data_no_ecc = write_block_ss_no_ecc; tg_samsung_nand.write_PCRB = write_PCRB_nand; tg_samsung_nand.restore_PCRB = restore_PCRB_nand; if(DFFSD_install_chip(&tg_samsung_nand,name,cn_reserve_blocks)) return 1; else { m_free(pg_sector_buf); return 0; } } |
3.5 无ECC校验读写
不带ecc的读写nand,由于S3C2440有nand控制器在控制时序,我们可以不用关心时序。只要按照第2.2章节的命令表操作,就可以了。
3.5.1 读一块(无ecc)
读nand flash,是一页一页的读取。读取一块的话,就必须通过计算一块有多少页,然后循环的读取每一页。读取一块函数过程如下:
1、 判断block是否超出芯片总块,如果超出说明有问题,返回错误。
2、 判断读取的数据大小+偏移量offset总和是否超过一块的大小。如果超过,说明有问题,返回错误。
3、 计算起始扇区号和起始地址偏移量。
4、 计算读取的结束扇区号和结束地址偏移量
5、 以一页一页的调用__read_sector_nand_no_ecc函数开始循环的读取芯片上面的数据。
__read_sector_nand_no_ecc,在下面介绍
u32 read_block_ss_no_ecc(u32 block,u32 offset, u8 *buf,u32 size) { u32 start_sector; //首扇区号 u32 start_offset; //首地址在首扇区的偏移量 u32 end_sector; //结束地址所在扇区 u32 end_offset; //结束地址在扇区中的偏移量 u32 cur_sector; //当前正在读的扇区 u32 read_size; //从当前扇区读取的数据量 u32 completed = 0;
if(block >= tg_samsung_nand.block_sum) return cn_limit_uint32; if((size + offset) > tg_samsung_nand.block_size) return cn_limit_uint32; if(size == 0) return 0;
//起始扇区号 start_sector = offset / u32g_sector_size + u32g_sectors_per_block * block; //起始地址在起始扇区号中的偏移量 start_offset = offset % u32g_sector_size; //结束扇区号 end_sector =(offset + size-1)/u32g_sector_size+u32g_sectors_per_block*block; //结束地址在结束扇区中的偏移量 end_offset = (offset + size -1) % u32g_sector_size; for(cur_sector = start_sector; cur_sector <= end_sector; cur_sector++) { if(cur_sector != end_sector) //当前扇区不是最后一个扇区 read_size = u32g_sector_size - start_offset; else //当前扇区是最后一个扇区 //+1是因为end_offset本身是需要写入的 read_size = end_offset - start_offset +1; __read_sector_nand_no_ecc(cur_sector,start_offset, buf+completed,read_size); completed += read_size; start_offset = 0; //从第二个扇区开始,肯定从0开始读 } return completed; } |
下表是__read_sector_nand_no_ecc的代码。这里,将使用命令。了解好如何使用nand读一页的命令,就很容易明白这个函数。下面是这个函数的大概过程:
1、 通过扇区号和offset,计算实际地址(address=u32g_sector_size*sector +offset)。
2、 激活片选。这步一定要记得,操作命令完毕要记得关闭。
3、 通过__write_command_nand写入命令,通过__write_address_nand写入地址。
读一页的命令是0x00 0x30。不过一定要记得,每写完一个命令要使用__wait_ready_nand函数,等待一小会儿,让nand完成。如果不等待,就会因为nand芯片内部操作未完成,而读取失败。
4、 命令操作完,就开始进行读了。通过循环,不断的读取data[i] = pg_nand_reg->NFDATA。
5、 关闭片选
u32 __read_sector_nand_no_ecc(u32 sector,u32 offset, u8 *data,u32 size) { u32 i; u32 address; address =u32g_sector_size*sector + offset; //计算实际地址 ce_active(); //激活片选 __write_command_nand(cn_nand_page_read); //写入读模式命令 __write_address_nand(address);
__wait_ready_nand( ); //等待芯片内部操作完成 __write_command_nand(cn_nand_startup_read); //wjz code revision 256M的,比64M的,多了个0x30命令。由于这个忘记等待时间了 //data[i],前面一些数据,都是随机数据. __wait_ready_nand( );
for(i=0; i < size; i++) { data[i] = pg_nand_reg->NFDATA; //读取数据 } ce_inactive(); //关闭片选 return cn_all_right_verify; } |
3.5.2 写一块(无ecc)
写nand flash,也是一页一页的写入。所以写入一块的话,就必须通过计算一块有多少页,根据有多少页,循环多少次的写入每一页。写入一块函数过程如下:
1、 判断block是否超出芯片总块,如果超出说明有问题,返回错误。
2、判断读取的数据大小+偏移量offset总和是否超过一块的大小。如果超过,说明有问题,返回错误。
3、计算起始扇区号和起始地址偏移量。
4、计算写入的结束扇区号和结束地址偏移量
5、以一页一页的调用__write_sector_nand_no_ecc函数循环的写入需要写入的数据。
__write_sector_nand_no_ecc,在下面介绍
u32 write_block_ss_no_ecc(u32 block,u32 offset, u8 *buf,u32 size) { u32 start_sector; //首扇区号 u32 start_offset; //首地址在首扇区的偏移量 u32 end_sector; //结束地址所在扇区 u32 end_offset; //结束地址在扇区中的偏移量 u32 cur_sector; //当前正在读的扇区 u32 write_size; //从当前扇区读取的数据量 u32 completed = 0; u32 verify;
if(block >= tg_samsung_nand.block_sum) return cn_limit_uint32; if((size + offset) > tg_samsung_nand.block_size) return cn_limit_uint32;
//起始扇区号 start_sector = offset / u32g_sector_size + u32g_sectors_per_block * block; //起始地址在起始扇区号中的偏移量 start_offset = offset % u32g_sector_size; //结束扇区号 end_sector =(offset + size-1)/u32g_sector_size+u32g_sectors_per_block*block; //结束地址在结束扇区中的偏移量 end_offset = (offset + size -1) % u32g_sector_size; for(cur_sector = start_sector; cur_sector <= end_sector; cur_sector++) { if(cur_sector != end_sector) //当前扇区不是最后一个扇区 write_size = u32g_sector_size - start_offset; else //当前扇区是最后一个扇区 //+1是因为end_offset本身是需要写入的 write_size = end_offset - start_offset +1; verify = __write_sector_nand_no_ecc(cur_sector,start_offset, buf+completed,write_size); if((verify == cn_all_right_verify) || (verify == cn_ecc_right_verify)) { completed += write_size; start_offset = 0; //从第二个扇区开始,肯定从0开始读 }else break; } return completed; } |
下表是__write_sector_nand_no_ecc的代码。这里,将使用命令操作nand的写入。了解好如何使用nand写一页的命令,就很容易明白这个函数。下面是这个函数的大概过程:
1、 通过扇区号和offset,计算实际地址(address=u32g_sector_size*sector +offset)。
2、 激活片选。这步一定要记得,操作命令完毕要记得关闭。
3、 通过__write_command_nand写入命令,通过__write_address_nand写入地址。
写一页的命令是0x80 0x10。不过一定要记得,每写完一个命令要使用__wait_ready_nand函数,等待一小会儿,让nand完成。如果不等待,就会因为nand芯片内部操作未完成,而读取失败。
写入和读取数据的时候,操作命令表顺序还是不一样的。写入要在执行0x80命令,以及写入要写入的开始地址后,就开始写入数据(pg_nand_reg->NFDATA = data[i])。最后写入命令0x10。
4、 关闭片选
u32 __write_sector_nand_no_ecc(u32 sector,u32 offset, u8 *data,u32 size) { u32 i; u32 address; address =u32g_sector_size*sector + offset; //计算实际地址 ce_active(); //激活片选 __write_command_nand(cn_nand_page_program); //启动编程命令 __write_address_nand(address); __wait_ready_nand( ); //等待芯片内部操作完成
for(i=0; i < size; i++) {//逐个把待写入的数据写入到器件的扇区缓冲区 pg_nand_reg->NFDATA = data[i]; } __write_command_nand(cn_nand_startup_write); //启动芯片内部写入过程 __wait_ready_nand_slow(cn_wait_page_write); //等待芯片内部操作完成
if(__read_status_nand() & cn_nand_failure) { ce_inactive(); return cn_ecc_error_verify; } ce_inactive(); return cn_all_right_verify; } |
3.6 ECC校验读写
3.6.1 ECC校验
Nand的ecc算法细节,网上关于ecc的算法资料很多,我这里就不做介绍了。这里介绍如何在nand驱动中使用ecc算法。
Ecc算法,是以256字节为单位使用一次ecc校验,得到3个字节的ecc码。K9F2G08U0B芯片一页有2048字节大小。也就是要K9F2G08U0B芯片的一页,需要24个ecc码。在DJYOS环境下,我们把nand的这24个ecc码,放在每页的oob的第1到25字节处。第0字节,我们存放的是坏块标志。
Tq2440,也有64M的NAND,型号是k9f1208。它的一页是512字节,所以需要6个校验码。k9f1208的oob第5个字节,是存放坏块。所以,把这6个ecc码放在第0到4字节和第6个字节处。
3.6.2 读一块(ecc)
读一块的代码如下,它的实现过程和无ecc读取一块的过程是一样的。只是调用的读取一页的函数不一样。这里调用__read_sector_nand_with_ecc函数。这个读取一块,详细过程,看3.4.1节吧。
u32 read_block_ss_with_ecc(u32 block,u32 offset, u8 *buf,u32 size) { u32 start_sector; //首扇区号 u32 start_offset; //首地址在首扇区的偏移量 u32 end_sector; //结束地址所在扇区 u32 end_offset; //结束地址在扇区中的偏移量 u32 cur_sector; //当前正在读的扇区 u32 read_size; //从当前扇区读取的数据量 u32 completed = 0; u32 verify;
if(block >= tg_samsung_nand.block_sum) return cn_limit_uint32; if((size + offset) > tg_samsung_nand.block_size) return cn_limit_uint32; if(size == 0) return 0;
//起始扇区号 start_sector = offset / u32g_sector_size + u32g_sectors_per_block * block; //起始地址在起始扇区号中的偏移量 start_offset = offset % u32g_sector_size; //结束扇区号 end_sector =(offset + size-1)/u32g_sector_size+u32g_sectors_per_block*block; //结束地址在结束扇区中的偏移量 end_offset = (offset + size -1) % u32g_sector_size; for(cur_sector = start_sector; cur_sector <= end_sector; cur_sector++) { if(cur_sector != end_sector) //当前扇区不是最后一个扇区 read_size = u32g_sector_size - start_offset; else //当前扇区是最后一个扇区 //+1是因为end_offset本身是需要写入的 read_size = end_offset - start_offset +1; verify = __read_sector_nand_with_ecc(cur_sector,start_offset, buf+completed,read_size); if((verify == cn_all_right_verify) || (verify == cn_ecc_right_verify)) { completed += read_size; start_offset = 0; //从第二个扇区开始,肯定从0开始读 }else break; } return completed; } |
__read_sector_nand_with_ecc函数增加了ecc校验部分,其余的和无ECC校验一样。这里介绍ECC如何应用。
在写入读取命令和地址,读出一页的数据之后,就读取oob里的ecc码。然后使用ecc码和校验函数__correct_sector,对刚才已经读出一页的数据进行校验。
u32 __read_sector_nand_with_ecc(u32 sector,u32 offset, u8 *data,u32 size) { u32 i; u32 address,result; u8 *ecc; address =u32g_sector_size*sector; //计算实际地址 ecc = pg_sector_buf+u32g_sector_size; ce_active(); //激活片选 __write_command_nand(cn_nand_page_read); //写入读模式命令 __write_address_nand(address);
__wait_ready_nand( ); //等待芯片内部操作完成 __write_command_nand(cn_nand_startup_read); __wait_ready_nand( ); //等待芯片内部操作完成 for(i=0; i < u32g_sector_size; i++) { pg_sector_buf[i] = pg_nand_reg->NFDATA; //读取数据 }
if(u32g_sector_size > 256) { ecc[0] = pg_nand_reg->NFDATA; //坏块标志,读取并丢弃 for(i=0; i < u32g_sector_size/256*3; i++)//读取校验码,坏块标志后的部分 ecc[i] = pg_nand_reg->NFDATA; } else { for(i = 0; i < 3; i++) //读取校验码,只有3字节 ecc[i] = pg_nand_reg->NFDATA; } ce_inactive(); //关闭片选 result = __correct_sector(pg_sector_buf,ecc); //无论校验结果如何,均执行数据copy,即使错误,也把错误数据告诉用户,让人知道 //错在哪里。 memcpy(data, pg_sector_buf + offset, size); return result; } |
3.6.3 写一块(ecc)
写一块的代码如下,它的实现过程和无ecc读取一块的过程是一样的。只是调用的写入一页的函数不一样。这里调用__write_sector_nand_with_ecc函数。这个写入一块,详细过程,看3.4.1节吧。
u32 write_block_ss_with_ecc(u32 block,u32 offset, u8 *buf,u32 size) { u32 start_sector; //首扇区号 u32 start_offset; //首地址在首扇区的偏移量 u32 end_sector; //结束地址所在扇区 u32 end_offset; //结束地址在扇区中的偏移量 u32 cur_sector; //当前正在写的扇区 u32 write_size; //从当前扇区写入的数据量 u32 completed = 0; u32 verify;
if(block >= tg_samsung_nand.block_sum) return cn_limit_uint32; if((size + offset) > tg_samsung_nand.block_size) return cn_limit_uint32;
//起始扇区号 start_sector = offset / u32g_sector_size + u32g_sectors_per_block * block; //起始地址在起始扇区号中的偏移量 start_offset = offset % u32g_sector_size; //结束扇区号 end_sector =(offset + size-1)/u32g_sector_size+u32g_sectors_per_block*block; //结束地址在结束扇区中的偏移量 end_offset = (offset + size -1) % u32g_sector_size; for(cur_sector = start_sector; cur_sector <= end_sector; cur_sector++) { if(cur_sector != end_sector) //当前扇区不是最后一个扇区 write_size = u32g_sector_size - start_offset; else //当前扇区是最后一个扇区 //+1是因为end_offset本身是需要写入的 write_size = end_offset - start_offset +1; verify = __write_sector_nand_with_ecc(cur_sector,start_offset, buf+completed,write_size); if((verify == cn_all_right_verify) || (verify == cn_ecc_right_verify)) { completed += write_size; start_offset = 0; //从第二个扇区开始,肯定从0开始写 }else break; } return completed; } |
__write_sector_nand_with_ecc函数增加了ecc校验部分,其余的和无ECC校验一样。这里介绍ECC如何应用。
在刚开始,要使用__make_sector_ecc函数,把要写入的一页数据生产ecc码。然后在写入命令0x80和写入要被写入开始地址,把要写入的一页数据写入nand里后,把刚才产生的ECC码,写入到oob里。剩下的,就和无ecc写入一页步骤一样。
u32 __write_sector_nand_with_ecc(u32 sector,u32 offset, u8 *data,u32 size) { u32 i; u32 address; u8 *ecc; if((offset != 0) || (size != u32g_sector_size)) { __read_sector_nand_with_ecc(sector,0,pg_sector_buf,u32g_sector_size); //执行ECC校验,但是不判断校验结果,因为扇区写入前可能是随机数据,校验错 //误并不能说明发生了错误 } ecc = pg_sector_buf+u32g_sector_size; memcpy(pg_sector_buf+offset,data,size); __make_sector_ecc(pg_sector_buf, ecc); //计算ecc代码 ce_active(); __write_command_nand(0x00);
__write_command_nand(cn_nand_page_program); //启动编程命令 address=u32g_sector_size*sector; //写入起始地址 __write_address_nand(address); __wait_ready_nand( ); //等待芯片内部操作完成
for(i=0; i<(u32g_sector_size); i++) {//逐个把待写入的数据写入到器件的扇区缓冲区 pg_nand_reg->NFDATA = pg_sector_buf[i]; // printf("WriteFlash: data = 0x%x\n", data); }
if(u32g_sector_size > 256) { pg_nand_reg->NFDATA = 0xFF; //坏块标志,写0xff相当于保持原值 for(i = 0; i < u32g_sector_size/256*3; i++) //写入校验码,坏块标志前的5字节 pg_nand_reg->NFDATA = ecc[i]; for(;i<64-1;i++)//-1,是第一个写入坏块标志 pg_nand_reg->NFDATA= 0xff; }else { for(i = 0; i < 3; i++) //写入校验码,只有3字节 pg_nand_reg->NFDATA = ecc[i]; } __write_command_nand(cn_nand_startup_write); //启动芯片内部写入过程 __wait_ready_nand_slow(cn_wait_page_write); //等待芯片内部操作完成
if(__read_status_nand() & cn_nand_failure) { ce_inactive(); return cn_ecc_error_verify; } ce_inactive(); return cn_all_right_verify; } |
3.7 标记坏块
Nand flash出厂的时候,厂商只保证第一块是绝对无问题。其他块,在使用中都可能出现问题,我们称其为坏块。K9F2G08U0B对坏块的处理是在一块的第一个扇区oob的第一个字节写入0。正常情况下,都是写入0xff。
下面是标记坏块的代码。
bool_t __mark_invalid_block(u32 block_no) { u32 address;
address = tg_samsung_nand.block_size * block_no + u32g_sector_size+1; ce_active(); __write_command_nand(cn_nand_page_program); __wait_ready_nand( ); //等待芯片内部操作完成 __write_address_nand(address); __wait_ready_nand( ); //等待芯片内部操作完成 pg_nand_reg->NFDATA = 0; __write_command_nand(cn_nand_startup_write); __wait_ready_nand_slow(cn_wait_page_write); //等待芯片内部操作完成 if(__read_status_nand() & cn_nand_failure) { ce_inactive(); return false; } ce_inactive(); return true; } |