//---------------------------------------------------- //Copyright (C), 2004-2009, lst. //版权所有 (C), 2004-2009, lst. //所属模块: 文件系统芯片驱动 //作者:lst //版本:V1.1.0 //文件描述: 用于三星nand flash的文件系统驱动模块 //其他说明: //修订历史: //2. 日期:20090131 // 作者:lst // 新版本号:v1.1.0 // 修改说明: 添加用定时器中断来等待操作完成的代码。 //1. 日期: 2009-01-04 // 作者: lst // 新版本号: V1.0.0 // 修改说明: 原始版本 //------------------------------------------------------ #include "inc_os.h" #include "nand.h" #include "gpio.h" #include "timer.h" #include <string.h> static struct flash_chip tg_samsung_nand; //芯片 static volatile struct nand_reg *const pg_nand_reg = (void*)0x4e000000; //扇区缓冲区指针,扇区读写函数内部专用,djyffs中,扇区不是所有flash的共性,故不 //出现在struct flash_chip 结构中。动态分配内存而不定义成静态数组,是因为chip作 //为一个资源,是可以删除的,删除时可以回收资源。 static uint8_t *pg_sector_buf; //扇区缓冲区指针 static uint32_t u32g_sectors_per_block; //每块包含的扇区数 static uint32_t u32g_sector_size; //扇区数尺寸 static u16 u16g_oob_size; 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"} }; //----写入命令----------------------------------------------------------------- //功能: 写入芯片写命令字 //参数: val,写命令字, //返回: 无 //----------------------------------------------------------------------------- #define __write_command_nand(cmd) pg_nand_reg->NFCMD = cmd //----写入地址----------------------------------------------------------------- //功能: 写入芯片写命令字,4行分别写入A0 ~ A7,A9 ~ A16,A17 ~ A24,A25 //参数: val,写命令字, //返回: 无 //----------------------------------------------------------------------------- #define __write_address_nand(addr) \ do{ \ pg_nand_reg->NFADDR = (uint8_t)addr; \ pg_nand_reg->NFADDR = (uint8_t)(addr>>9); \ pg_nand_reg->NFADDR = (uint8_t)(addr>>17); \ pg_nand_reg->NFADDR = (uint8_t)(addr>>25); \ }while(0) //----读扇区(含oob)------------------------------------------------------------ //功能: 读一个扇区,含oob中的内容一起读 //参数: sector,被读的扇区号 // data,读出数据的缓冲区 //返回: 无 //----------------------------------------------------------------------------- void __read_sector_and_oob(uint32_t sector,uint8_t *data) { uint32_t i; uint32_t address; address =u32g_sector_size*sector; //计算实际地址 ce_active(); //激活片选 __write_command_nand(cn_nand_select_page0); //写入读模式命令 __write_address_nand(address); __wait_ready_nand( ); //等待芯片内部操作完成 for(i=0; i < 528; i++) { data[i] = pg_nand_reg->NFDATA; //读取数据 } ce_inactive(); //关闭片选 return ; } //----写掉电恢复块------------------------------------------------------------- //功能: 当芯片正在写入数据时掉电或者复位,将使正在写入的块数据丢失,为在这种情况 // 下保护数据,采用的策略是: // 1.如果写入的是新块,则直接写入,因为写入新块失败和写入掉电恢复块失败的 // 后果是一样的。 // 2.如果是修改一块或者从一块后面添加数据,则把数据先写入到掉电恢复块,并 // 在掉电恢复块的适当位置标明掉电恢复块保护的是哪一块数据。 // 3.写入掉电恢复块后,接着把数据写入到目标块中,完成后,擦除掉电恢复块。 // 4.重新启动后,如果检测到掉电恢复块有有效数据,则恢复到目标块 // 本函数的职责:第三种条件下,调用本函数把数据写入掉电恢复块。 //参数: PCRB_block,用于保存掉电恢复数据的块号,芯片的绝对块号 // protected_block,被保护的目标块号,芯片的绝对块号。 //返回: true = 成功写入,false = 写入失败,可能第PCRB_block块是坏块。 //----------------------------------------------------------------------------- bool_t write_PCRB_nand(uint32_t PCRB_block, uint32_t protected_block,uint8_t *buf) { uint32_t cur_sector; //当前正在写的扇区 uint32_t completed = 0; uint32_t verify; uint32_t address; uint32_t loop; if(PCRB_block >= tg_samsung_nand.block_sum) return false; cur_sector = PCRB_block * u32g_sectors_per_block; for(loop = 0; loop < u32g_sectors_per_block; loop++) { verify = __write_sector_nand_with_ecc(cur_sector,0, buf+completed,u32g_sector_size); if((verify == cn_all_right_verify) || (verify == cn_ecc_right_verify)) { completed += u32g_sector_size; }else return false; cur_sector++; } //第一页的最后4字节保存受保护的目标块号。 address =tg_samsung_nand.block_size*PCRB_block + 12; ce_active(); //激活片选 __write_command_nand(cn_nand_select_oob); //操作oob页 __write_command_nand(cn_nand_page_program); //启动编程命令 __write_address_nand(address); __wait_ready_nand( ); //等待芯片内部操作完成 //受保护的目标块号写入flash,注意:没有做ECC校验,以后补上---db pg_nand_reg->NFDATA = (uint8_t)protected_block; pg_nand_reg->NFDATA = (uint8_t)(protected_block>>8); pg_nand_reg->NFDATA = (uint8_t)(protected_block>>16); pg_nand_reg->NFDATA = (uint8_t)(protected_block>>24); __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; } //----从掉电恢复块恢复数据----------------------------------------------------- //功能: 从掉电恢复块恢复数据,如果掉电恢复块里有有效数据,比较其与目标块的数据是 // 否一致,如果不一致则用掉电恢复块的数据覆盖目标块。无论是否需要恢复,最后 // 均擦除掉电恢复块。 //参数: PCRB_block,掉电恢复块块号,芯片的绝对块号 // restored,本指针返回被恢复的目标块 //返回: true = 无需恢复或者正确恢复,false = 发生错误,一般是因为目标块是坏块。 //----------------------------------------------------------------------------- bool_t restore_PCRB_nand(uint32_t PCRB_block,uint32_t *restored) { uint32_t verify; uint32_t address; uint32_t loop; uint32_t protected_block; uint32_t sector_from,sector_to; uint8_t *sector_buf; //扇区缓冲区,动态分配,不在栈中分配 bool_t result = true; if(PCRB_block >= tg_samsung_nand.block_sum) return false; //第一页的最后4字节保存受保护的目标块号。 address =tg_samsung_nand.block_size*PCRB_block + 12; ce_active(); //激活片选 __write_command_nand(cn_nand_select_oob); //操作oob页 __write_address_nand(address); __wait_ready_nand( ); //等待芯片内部操作完成 //受保护的目标块号写入flash,注意:没有做ECC校验,以后补上---db protected_block = pg_nand_reg->NFDATA; protected_block += (uint32_t)(pg_nand_reg->NFDATA)<<8; protected_block += (uint32_t)(pg_nand_reg->NFDATA)<<16; protected_block += (uint32_t)(pg_nand_reg->NFDATA)<<24; *restored = protected_block; ce_inactive(); if(protected_block > tg_samsung_nand.block_sum) { erase_block_nand(PCRB_block); return true; } sector_buf = m_malloc(u32g_sector_size,0); if(sector_buf == NULL) return false; erase_block_nand(protected_block); sector_from = PCRB_block * u32g_sectors_per_block; sector_to = protected_block * u32g_sectors_per_block; for(loop = 0; loop < u32g_sectors_per_block; loop++) { verify = __read_sector_nand_with_ecc( sector_from,0,sector_buf,u32g_sector_size); if((verify != cn_all_right_verify) && (verify != cn_ecc_right_verify)) { result = false; break; } verify = __write_sector_nand_with_ecc( sector_to,0,sector_buf,u32g_sector_size); if((verify != cn_all_right_verify) && (verify != cn_ecc_right_verify)) { result = false; break; } sector_from++; sector_to++; } m_free(sector_buf); erase_block_nand(PCRB_block); return result; } //----等待芯片内部完成操作---------------------------------------------------- //功能: 对芯片执行写操作后,要效用本函数等待操作完成才能进一步操作。 //参数: 无 //返回: true = 正确完成操作,false = 发生错误 //----------------------------------------------------------------------------- bool_t __wait_ready_nand(void) { while((pg_nand_reg->NFSTAT & 1) ==0 ); return true; } //----等待芯片内部慢速操作完成------------------------------------------------- //功能: 对芯片执行写操作后,要调用本函数等待操作完成才能进一步操作。由于一连串的 // 同步操作将浪费很多时间,对于一些很快就绪的操作,宜使用__wait_ready_nand // 函数。 //参数: wait_time,估计等待时间,微秒数 //返回: true = 正确完成操作,false = 发生错误 //----------------------------------------------------------------------------- bool_t __wait_ready_nand_slow(uint16_t wait_time) { timer_set_counter(1,wait_time); //计数值设为wait_time timer_reload(1); //重载定时值 timer_start(1); //启动定时器 int_asyn_signal_sync(cn_int_line_timer1); timer_stop(1); while((pg_nand_reg->NFSTAT & 1) ==0 ); return true; } //----读扇区(无ecc校验)---------------------------------------------------- //功能: 不带ecc校验从一扇区内读取数据,地址不能跨扇区边界 //参数: sector,扇区号 // offset,扇区内偏移地址 // data,保存读取数据的缓冲区 // size,读取的尺寸 //返回: 正确读取 //----------------------------------------------------------------------------- uint32_t __read_sector_nand_no_ecc(uint32_t sector,uint32_t offset, uint8_t *data,uint32_t size) { uint32_t i; uint32_t address; address =u32g_sector_size*sector + offset; //计算实际地址 ce_active(); //激活片选 if(address & 0x100) __write_command_nand(cn_nand_select_page1); //写入读模式命令 else __write_command_nand(cn_nand_select_page0); //写入读模式命令 __write_address_nand(address); __wait_ready_nand( ); //等待芯片内部操作完成 for(i=0; i < size; i++) { data[i] = pg_nand_reg->NFDATA; //读取数据 } ce_inactive(); //关闭片选 return cn_all_right_verify; } //----读块(无ecc校验)---------------------------------------------------- //功能: 不带ecc校验从一块内读取数据,地址不能跨块边界 //参数: sector,扇区号 // offset,块内偏移地址 // data,保存读取数据的缓冲区 // size,读取的尺寸 //返回: 正确读取 //----------------------------------------------------------------------------- uint32_t read_block_ss_no_ecc(uint32_t block,uint32_t offset, uint8_t *buf,uint32_t size) { uint32_t start_sector; //首扇区号 uint32_t start_offset; //首地址在首扇区的偏移量 uint32_t end_sector; //结束地址所在扇区 uint32_t end_offset; //结束地址在扇区中的偏移量 uint32_t cur_sector; //当前正在读的扇区 uint32_t read_size; //从当前扇区读取的数据量 uint32_t 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; } //----ECC检查并改正----------------------------------------------------------- //功能: ecc校验一个扇区 //参数: data,待校验数据的缓冲区 // ecc,校验码 //返回:0=正确,1=被改正,2=错误且不能修正 //----------------------------------------------------------------------------- //---------------------------------------------------------------------------- uint32_t __correct_sector(uint8_t *data,const uint8_t *ecc) { s32 j; uint32_t i,result = cn_all_right_verify; for(i = 0; i < u32g_sector_size/256; i++) { j = ecc_corect_256(data + i*256, ecc + i*3); if(( j == -1) || (j == 2)) return cn_ecc_error_verify; //无法修正则直接返回 if( j == 1) result = cn_ecc_right_verify; } return result; } //----读扇区(带ecc校验)---------------------------------------------------- //功能: 带ecc校验从一扇区内读取数据,地址不能跨扇区边界 //参数: sector,扇区号 // offset,扇区内偏移地址 // data,保存读取数据的缓冲区 // size,读取的尺寸 //返回:0=正确,1=被改正,2=错误且不能修正 //----------------------------------------------------------------------------- uint32_t __read_sector_nand_with_ecc(uint32_t sector,uint32_t offset, uint8_t *data,uint32_t size) { uint32_t i; uint32_t address,result; uint8_t *ecc; address =u32g_sector_size*sector; //计算实际地址 ecc = pg_sector_buf+u32g_sector_size; ce_active(); //激活片选 __write_command_nand(cn_nand_select_page0); //写入读模式命令 __write_address_nand(address); __wait_ready_nand( ); //等待芯片内部操作完成 for(i=0; i < u32g_sector_size; i++) { pg_sector_buf[i] = pg_nand_reg->NFDATA; //读取数据 } if(u32g_sector_size > 256) { for(i = 0; i < 5; i++) //读取校验码,坏块标志前的5字节 ecc[i] = pg_nand_reg->NFDATA; ecc[i] = pg_nand_reg->NFDATA; //坏块标志,读取并丢弃 for(; 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; } //----读块(带ecc校验)---------------------------------------------------- //功能: 带ecc校验从一块内读取数据,地址不能跨块边界 //参数: sector,块号 // offset,块内偏移地址 // data,保存读取数据的缓冲区 // size,读取的尺寸 //返回:实际读取的数据量 //----------------------------------------------------------------------------- uint32_t read_block_ss_with_ecc(uint32_t block,uint32_t offset, uint8_t *buf,uint32_t size) { uint32_t start_sector; //首扇区号 uint32_t start_offset; //首地址在首扇区的偏移量 uint32_t end_sector; //结束地址所在扇区 uint32_t end_offset; //结束地址在扇区中的偏移量 uint32_t cur_sector; //当前正在读的扇区 uint32_t read_size; //从当前扇区读取的数据量 uint32_t completed = 0; uint32_t 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; } //----读芯片状态------------------------------------------------------------- //功能: 读芯片状态 //参数: 无 //返回:芯片状态 //----------------------------------------------------------------------------- uint8_t __read_status_nand(void) { uint8_t chip_status ; __write_command_nand(cn_nand_read_status); chip_status = pg_nand_reg->NFDATA; return chip_status; } //----写一个扇区(no ecc)------------------------------------------------------------- //功能: 写一个扇区,不做ecc校验 //参数: sector,扇区号 // offset,扇区内偏移地址 // data,数据缓冲区 // size,写入的尺寸 //返回:cn_all_right_verify=正确写入,cn_ecc_error_verify=写入错误 //----------------------------------------------------------------------------- uint32_t __write_sector_nand_no_ecc(uint32_t sector,uint32_t offset, uint8_t *data,uint32_t size) { uint32_t i; uint32_t address; address =u32g_sector_size*sector + offset; //计算实际地址 ce_active(); //激活片选 if(address & 0x100) __write_command_nand(cn_nand_select_page1); //写入读模式命令 else __write_command_nand(cn_nand_select_page0); //写入读模式命令 __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]; // printf("WriteFlash: data = 0x%x\n", data); } __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; } //----写块(不带ecc校验)---------------------------------------------------- //功能: 不带ecc校验把缓冲区写入块内,地址不能跨块边界 //参数: sector,块号 // offset,块内偏移地址 // data,保存读取数据的缓冲区 // size,读取的尺寸 //返回:实际写入的数据量,cn_limit_uint32表示出错 //----------------------------------------------------------------------------- uint32_t write_block_ss_no_ecc(uint32_t block,uint32_t offset, uint8_t *buf,uint32_t size) { uint32_t start_sector; //首扇区号 uint32_t start_offset; //首地址在首扇区的偏移量 uint32_t end_sector; //结束地址所在扇区 uint32_t end_offset; //结束地址在扇区中的偏移量 uint32_t cur_sector; //当前正在读的扇区 uint32_t write_size; //从当前扇区读取的数据量 uint32_t completed = 0; uint32_t 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; } //----写一个扇区(with ecc)------------------------------------------------------------- //功能: 写一个扇区,带ecc校验 //参数: sector,扇区号 // offset,扇区内偏移地址 // data,数据缓冲区 // size,写入的尺寸 //返回:cn_all_right_verify=正确写入,cn_ecc_error_verify=写入错误 //----------------------------------------------------------------------------- uint32_t __write_sector_nand_with_ecc(uint32_t sector,uint32_t offset, uint8_t *data,uint32_t size) { uint32_t i; uint32_t address; uint8_t *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) { for(i = 0; i < 5; i++) //写入校验码,坏块标志前的5字节 pg_nand_reg->NFDATA = ecc[i]; pg_nand_reg->NFDATA = 0xff; //坏块标志,写0xff相当于保持原值 for(; i < u32g_sector_size/256*3; i++)//写入校验码,坏块标志后的部分 pg_nand_reg->NFDATA = ecc[i]; }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; } //----写块(带ecc校验)---------------------------------------------------- //功能: 带ecc校验把缓冲区写入块内,地址不能跨块边界 //参数: sector,块号 // offset,块内偏移地址 // data,保存读取数据的缓冲区 // size,读取的尺寸 //返回:实际写入的数据量,cn_limit_uint32表示出错 //----------------------------------------------------------------------------- uint32_t write_block_ss_with_ecc(uint32_t block,uint32_t offset, uint8_t *buf,uint32_t size) { uint32_t start_sector; //首扇区号 uint32_t start_offset; //首地址在首扇区的偏移量 uint32_t end_sector; //结束地址所在扇区 uint32_t end_offset; //结束地址在扇区中的偏移量 uint32_t cur_sector; //当前正在写的扇区 uint32_t write_size; //从当前扇区写入的数据量 uint32_t completed = 0; uint32_t 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; } //----擦除一块------------------------------------------------------------- //功能: 擦除一块 //参数: block_no,目标块号 //返回: true = 成功擦除,false = 坏块 //----------------------------------------------------------------------------- bool_t erase_block_nand(uint32_t block_no) { uint32_t page_no; page_no = block_no * u32g_sectors_per_block; ce_active(); __write_command_nand(cn_nand_block_erase); pg_nand_reg->NFADDR = (uint8_t)page_no; // A9 ~ A16 pg_nand_reg->NFADDR = (uint8_t)(page_no>>8); // A17 ~ A24 pg_nand_reg->NFADDR = (uint8_t)(page_no>>16); // A25 __write_command_nand(cn_nand_startup_erase); __wait_ready_nand_slow(cn_wait_block_erase); //等待芯片内部操作完成 ce_inactive(); return true; // if(readStatus() & cn_nand_failure) // return flWriteFault; } //----擦除整个芯片------------------------------------------------------------- //功能: 擦除整个芯片。 //参数: 无 //返回: true = 成功擦除,false = 坏块 //----------------------------------------------------------------------------- bool_t __erase_all_nand(void) { uint32_t i; for(i=0; i<tg_samsung_nand.block_sum; i++) { erase_block_nand(i); } return true; } //说明: 以下三个查询是否需要擦除的函数,基于flash的特征:无论块是否空,只要是1 // 的位就可以改为0,而flash的寿命由擦除次数决定。 //----查询是否需要擦除(with ecc)----------------------------------------------- //功能: 查询在block的offset处写入buf中的size长度数据前是否需要擦除块。由于擦除 // 既慢又容易磨损flash,故本函数对加快速度和延长flash寿命很有效,在不同情况 // 下,呈现的效果并不一致。 // 1、norflash,多次写入少量数据时非常有效。 // 2、nandflash做高可靠应用,由于存在扇区ecc的问题,需要写入sector边界对齐 // 的数据才有效。 // 3、nandflash做媒体应用,音视频媒体应用可不做ecc,效果与norflash一样。 // 4、故nandflash只判断是否全FF // 对第二种情况,一个改进方法是每次写零碎数据时均新开一个扇区,待扇区用完以 // 后才整体压缩擦除,但太过麻烦,以后再说吧。 //参数: sector,块号 // offset,块内偏移地址 // data,数据缓冲区 // size,数据尺寸 //返回: flase = 需要擦除,true = 已准备好,不需要擦除 //----------------------------------------------------------------------------- bool_t query_block_ready_ss_with_ecc(uint32_t block,uint32_t offset, uint8_t *buf,uint32_t size) { uint32_t start_sector; //首扇区号 uint32_t end_sector; //结束地址所在扇区 uint32_t cur_sector; //当前正在读的扇区 uint32_t address; uint32_t loop; uint8_t data; if(block >= tg_samsung_nand.block_sum) return false; if((size + offset) > tg_samsung_nand.block_size) return false; if(size == 0) return true; if(buf == NULL) return false; //起始扇区号 start_sector = offset / u32g_sector_size + u32g_sectors_per_block * block; //结束扇区号 end_sector =(offset + size-1)/u32g_sector_size+u32g_sectors_per_block*block; address = u32g_sector_size*start_sector; //计算实际地址 for(cur_sector = start_sector; cur_sector <= end_sector; cur_sector++) { ce_active(); //激活片选 __write_command_nand(cn_nand_select_page0); //写入读模式命令 __write_address_nand(address); __wait_ready_nand( ); //等待芯片内部操作完成 for(loop=0; loop < u32g_sector_size+cn_oob_size; loop++) { data = pg_nand_reg->NFDATA; //读取数据 if(data != 0xff) //只要有1字节非oxff,即返回错误 { ce_inactive(); //关闭片选 return false; } } ce_inactive(); //关闭片选 address += u32g_sector_size; } return true; } //----查询是否需要擦除(no ecc)----------------------------------------------- //功能: 同上一个函数,不带ecc //参数: sector,块号 // offset,块内偏移地址 // data,数据缓冲区 // size,数据尺寸 //返回: flase = 需要擦除,true = 已准备好,不需要擦除 //----------------------------------------------------------------------------- bool_t query_block_ready_nand_no_ecc(uint32_t block,uint32_t offset, uint8_t *buf,uint32_t size) { uint32_t start_sector; //首扇区号 uint32_t start_offset; //首地址在首扇区的偏移量 uint32_t end_sector; //结束地址所在扇区 uint32_t end_offset; //结束地址在扇区中的偏移量 uint32_t cur_sector; //当前正在读的扇区 uint32_t read_size; uint32_t address; uint32_t completed = 0; uint32_t loop; uint8_t data; if(block >= tg_samsung_nand.block_sum) return false; if((size + offset) > tg_samsung_nand.block_size) return false; if(size == 0) return true; if(buf == NULL) return false; //起始扇区号 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; address = block * tg_samsung_nand.block_size + offset; 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; if(address & 0x100) __write_command_nand(cn_nand_select_page1); //写入读模式命令 else __write_command_nand(cn_nand_select_page0); //写入读模式命令 __write_address_nand(address); __wait_ready_nand( ); //等待芯片内部操作完成 for(loop=0; loop < read_size; loop++) { data = pg_nand_reg->NFDATA; //读取数据 if((data | buf[completed]) != data) //查看有否0->1的位 { ce_inactive(); //关闭片选 return false; //有0->1的位,返回需要擦除 } completed++; } ce_inactive(); //关闭片选 address += read_size; start_offset = 0; //从第二个扇区开始,肯定从0开始读 } return true; } //----查询是否需要擦除(buf)----------------------------------------------- //功能: flash中的原来的内容在org_data中,如果要改成如new_data中的数据,查看是否 // 需要擦除。 //参数: new_data,新数据缓冲区 // new_data,数据缓冲区 // size,数据尺寸 //返回: flase = 需要擦除,true = 已准备好,不需要擦除 //----------------------------------------------------------------------------- bool_t query_ready_with_data_nand(uint8_t *new_data,uint8_t *org_data, uint32_t size) { uint32_t loop; if(org_data == NULL) return false; if(new_data != NULL) { for(loop = 0; loop < size; loop++) { if((org_data[loop] | new_data[loop]) != org_data[loop]) return false; //如果存在由0改1的数据,返回false } }else { for(loop = 0; loop < size; loop++) { if(org_data[loop] != 0xFF) return false; } } return true; } //坏块标记算法: //1、三星出厂时,坏块OOB的头两页第6个字节被写入非0xFF数据,好块则保证是0xFF。 //2、使用过程中的新增坏块,也在头两页的第6字节写入0. //3、永远不要擦除坏块,否则用于坏块标记的第6字节也被擦除。 //----有效块检查-------------------------------------------------------------- //功能: 检查一个块是否有效块,参考坏块标记算法。 //参数: block_no,块号 //返回: true=好块,false=坏块。 //---------------------------------------------------------------------------- bool_t check_block_nand(uint32_t block_no) { uint16_t j; uint32_t address; uint8_t data; ce_active(); for(j=0; j<2; j++) { address = tg_samsung_nand.block_size * block_no + 0x200 * j + (517&0xf); __write_command_nand(cn_nand_select_oob); __write_address_nand(address); __wait_ready_nand( ); //等待芯片内部操作完成 data = pg_nand_reg->NFDATA; if(data != 0xFF) { ce_inactive(); return false; } } ce_inactive(); return true; } //----标记一个坏块----------------------------------------------------------- //功能:在读、写、擦除等函数中调用,如果操作过程中发现某块是坏块,立即调用本函数 // 把它标记为坏块,即把首扇区的第517字节写0. //参数: block_no,块号 //返回: true=成功标记,false=标记失败。 //---------------------------------------------------------------------------- bool_t __mark_invalid_block(uint32_t block) { uint16_t j; uint32_t address; __write_command_nand(cn_nand_select_oob); for(j=0; j<2; j++) { address = tg_samsung_nand.block_size * block + 0x200 * j + (517&0xf); ce_active(); __write_command_nand(cn_nand_page_program); __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; } //----查flash中好块数量-------------------------------------------------------- //功能: 查flash中好块数量 //参数: 无 //返回: 好块数量 //----------------------------------------------------------------------------- uint32_t __check_all_ss(void) { uint32_t i, j,sum = 0; uint32_t address; uint8_t data; for(i=0; i<tg_samsung_nand.block_sum; i++) { ce_active(); for(j=0; j<2; j++) { address = tg_samsung_nand.block_size * i + 0x200 * j + (517&0xf); __write_command_nand(cn_nand_select_oob); __write_address_nand(address); __wait_ready_nand( ); //等待芯片内部操作完成 data = pg_nand_reg->NFDATA; if(data != 0xFF) { sum++; break; } } } ce_inactive(); return sum; } //----计算扇区ecc码----------------------------------------------------------- //功能: data中包含256字节数据,计算它的ecc码 //参数: data,数据缓冲区 // ecc: 3字节的缓冲区,返回ecc码 //返回: 无 //----------------------------------------------------------------------------- void __make_sector_ecc(const uint8_t *data,uint8_t *ecc) { uint32_t i; for(i = 0; i < u32g_sector_size/256; i++) { ecc_make_256(data + i*256, ecc + i*3); } } //----读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 ; } //----解码芯片id--------------------------------------------------------------- //功能: 根据芯片id识别芯片,识别结果填写到tg_samsung_nand中 //参数: id,待识别的芯片id //返回: true = 正确识别,false = 不能识别 //----------------------------------------------------------------------------- bool_t __parse_chip(uint16_t id,char **name) { uint32_t no,sum; sum = sizeof(tg_nand_table)/sizeof(struct nand_table); for(no = 0; no < sum; no++) { if(id == tg_nand_table[no].vendor_chip_id) { u32g_sectors_per_block = tg_nand_table[no].pages_per_block; tg_samsung_nand.block_sum = tg_nand_table[no].blocks_sum; tg_samsung_nand.block_size = tg_nand_table[no].block_size; tg_samsung_nand.DBX_read_buf_size = tg_nand_table[no].block_size; tg_samsung_nand.DBX_write_buf_size = tg_nand_table[no].block_size; *name = tg_nand_table[no].chip_name; u16g_oob_size = tg_nand_table[no].oob_size; u32g_sector_size =tg_samsung_nand.block_size/u32g_sectors_per_block; return true; } } return false; } //----复位芯片----------------------------------------------------------------- //功能: 复位芯片 //参数: 无 //返回: 无 //----------------------------------------------------------------------------- void __reset_nand(void) { ce_active(); __write_command_nand(cn_nand_reset); __wait_ready_nand_slow(cn_wait_reset); //等待芯片内部操作完成 ce_inactive(); } //----初始化nand芯片----------------------------------------------------------- //功能:初始化nand芯片用于文件系统 //参数: 无 //返回: true= 成功,false=失败 //----------------------------------------------------------------------------- bool_t module_init_fs_nandflash(void) { uint16_t chip_id; char *name; //初始化timer4作为等候flash内部操作完成的中断,无需ISR int_setto_asyn_signal(cn_int_line_timer1); //设为异步信号 timer_set_clk_source(1,0); //主频的1/2分频 //预分频数:设置定时器输入时钟1Mhz timer_set_precale(0,(uint32_t)cn_timer_clk/1000000/2 -1); timer_set_type(1,1); //设置定时器连续工作 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); //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 false; pg_sector_buf = (uint8_t*)m_malloc_gbl(u32g_sector_size+u16g_oob_size,0); if(pg_sector_buf == NULL) { return false; } 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 true; else { m_free(pg_sector_buf); return false; } }


Created by 2beanet Embedded Group : http://www.2beanet.com