【nand】基于DJYOS下S3C2440A的nand驱动移植过程

时间:2021-09-27 17:51:53

基于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电路图

【nand】基于DJYOS下S3C2440A的nand驱动移植过程

图1-1 nand电路图

图1-1的左边为 K9F2G08U0B与 TQ2440 的连接图,原理方面就不多介绍,去看

看 datasheet 估计就懂得了,右边的部分是 S3C2440 的 Nand 控制器的配置。配

置引脚 NCON,GPG13,GPG14 和 GPG15 用来设置 Nand Flash 的基本信息,Nand

控制器通过读取配置引脚的状态获取外接的 Nand Flash 的配置信息,图1-2 是这

四个配置引脚的定义:

【nand】基于DJYOS下S3C2440A的nand驱动移植过程

图1-2:Nand flash 引脚配置信息表

由于 K9F2G08U0B 的总线宽度为 8 位,页大小为 2048 字节,需要 5 个寻址命令, 所以NCON、 GPG13 和GPG14应该接高电平, GPG15 应该接低电平。 K9F2G08U0B没有地址或数据总线,只有8个IO口, 这 8 个 IO 口用于传输命令、 地址和数据。

2      nand flash简介

nand的资料很多,细节就不提,这里只提到两点。

 

2.1    K9F2G08U0B的存储阵列

 【nand】基于DJYOS下S3C2440A的nand驱动移植过程

图 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的命令说明

 【nand】基于DJYOS下S3C2440A的nand驱动移植过程

图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;

}