Nand flash驱动的编写与移植

1 Nand flash工作原理
    S3C2410板的Nand Flash支持由两部分组成:Nand Flash控制器(集成在S3C2410 CPU)和Nand Flash存储
芯片(K9F1208U0B)两大部分组成。当要访问Nand Flash中的数据时,必须通过Nand Flash控制器发送命
令才能完成。所以, Nand Flash相当于S3C2410的一个外设,而不位于它的内存地址区.
1.1 Nand flash芯片工作原理
     Nand flash芯片型号为Samsung K9F1208U0B,数据存储容量为64MB,采用块页式存储管理。8个I/O
引脚充当数据、地址、命令的复用端口。
1.1.1 芯片内部存储布局及存储操作特点
    一片Nand flash为一个设备(device), 其数据存储分层为:
1设备(Device) = 4096 块(Blocks)
1块(Block) = 32页/行(Pages/rows)   ;页与行是相同的意思,叫法不一样
1块(Page) = 528字节(Bytes) = 数据块大小(512Bytes) + OOB块大小(16Bytes)
    在每一页中,最后16个字节(又称OOB)用于Nand Flash命令执行完后设置状态用,剩余512个字节又
分为前半部分和后半部分。可以通过Nand Flash命令00h/01h/50h分别对前半部、后半部、OOB进行定位通过
Nand Flash内置的指针指向各自的首地址。
存储操作特点:
1. 擦除操作的最小单位是块。
2. Nand Flash芯片每一位(bit)只能从1变为0,而不能从0变为1,所以在对其进行写入操作之前要一定将相应
块擦除(擦除即是将相应块得位全部变为1).
3. OOB部分的第六字节(即517字节)标志是否是坏块,如果不是坏块该值为FF,否则为坏块。
4. 除OOB第六字节外,通常至少把OOB的前3个字节存放Nand Flash硬件ECC码(关于硬件ECC码请参看
Nandflash 控制器一节).
1.1.2 重要芯片引脚功能
    I/O0­I/O7:复用引脚。可以通过它向nand flash芯片输入数据、地址、nand flash命令以及输出数据和操作
状态信息。
    CLE(Command Latch Enable):  命令锁存允许
    ALE(Address Lactch Enable): 地址锁存允许
    ­CE: 芯片选择   
    ­RE: 读允许
    ­WE: 写允许
    ­WP: 在写或擦除期间,提供写保护
    R/­B: 读/忙输出
1.1.3 寻址方式
   Samsung K9F1208U0B Nand Flash 片内寻址采用26位地址形式。从第0位开始分四次通过I/O0-I/O7进行
传送,并进行片内寻址。具体含义如下:
   0-7位:字节在上半部、下半部及OOB内的偏移地址
   8位:值为0代表对一页内前256个字节进行寻址
         值为1代表对一页内后256个字节进行寻址
   9-13位:对页进行寻址   14-25位:对块进行寻址
   当传送地址时,从位0开始
1.1.4 Nand flash主要内设命令详细介绍
Nand Flash命令执行是通过将命令字送到Nand Flash控制器的命令寄存器来执行。
Nand Flash的命令是分周期执行的,每条命令都有一个或多个执行周期,每个执行周期都有相映代码表示该周
期将要执行的动作。
主要命令有:Read 1、Read 2、Read ID、Reset、Page Program、Block Erase、Read Status。
详细介绍如下:
1. Read 1:
功能:表示将要读取Nand flash存储空间中一个页的前半部分,并且将内置指针定位到前半部分的第一个字节。
命令代码:00h
2. Read 2:
功能:表示将要读取Nand flash存储空间中一个页的后半部分,并且将内置指针定位到后半部分的第一个字节。
命令代码:01h
3. Read ID:
功能:读取Nand flash芯片的ID号
命令代码:90h
4. Reset:
功能:重启芯片。
命令代码:FFh
5. Page Program:
功能:对页进行编程命令, 用于写操作。
命令代码:首先写入00h(A区)/01h(B区)/05h(C区), 表示写入那个区; 再写入80h开始编程模式(写入模式),接
下来写入地址和数据; 最后写入10h表示编程结束.
6. Block Erase
功能:块擦除命令。
命令代码:首先写入60h进入擦写模式,然后输入块地址; 接下来写入D0h, 表示擦写结束.
7. Read Status
功能:读取内部状态寄存器值命令。
命令代码:70h
1.2 Nand Flash 控制器工作原理
   对Nand Flash存储芯片进行操作, 必须通过Nand Flash控制器的专用寄存器才能完成。所以,不能对Nand 
Flash进行总线操作。而Nand Flash的写操作也必须块方式进行。对Nand Flash的读操作可以按字节读取。
1.2.1 Nand Flash控制器特性
1. 支持对Nand Flash芯片的读、检验、编程控制
2. 如果支持从Nand Flash启动, 在每次重启后自动将前Nand Flash的前4KB数据搬运到ARM的内部RAM中
3. 支持ECC校验
1.2.2 Nand Flash控制器工作原理
   Nand Flash控制器在其专用寄存器区(SFR)地址空间中映射有属于自己的特殊功能寄存器,就是通过将Nand 
Flash芯片的内设命令写到其特殊功能寄存器中,从而实现对Nand flash芯片读、检验和编程控制的。特殊功能
寄存器有:NFCONF、NFCMD、NFADDR、NFDATA、NFSTAT、NFECC。寄存详细说明见下一节。
1.3 Nand flash 控制器中特殊功能寄存器详细介绍
1. 配置寄存器(NFCONF)功能:用于对Nand Flash控制器的配置状态进行控制。
在地址空间中地址:0x4E000000,其中:
Bit15:Nand Flash控制器使能位,置0代表禁止Nand Flash控制器,置1代表激活Nand Flash控制器;
要想访问Nand Flash芯片上存储空间,必须激活Nand Flash控制器。在复位后该位自动置0,因此在初始化时
必须将该位置为1。
Bit12:初始化ECC位,置1为初始化ECC;置0为不初始化ECC。
Bit11:Nand Flash芯片存储空间使能位,置0代表可以对存储空间进行操作;置1代表禁止对存储空
间进行操作。在复位后,该位自动为1。
Bit10-8:TACLS位。根据此设定CLE&ALE的周期。TACLS的值范围在0-7之间。
Bit6-4、2-0分别为:TWRPH0、TWRPH1位。设定写操作的访问周期。其值在0-7之间。
2. 命令寄存器(NFCMD)
功能:用于存放Nand flash芯片内设的操作命令。
在地址空间中地址:0x4E000004,其中:
Bit0-7:存放具体Nand flash芯片内设的命令值。其余位保留以后用。
3. 地址寄存器(NFADDR)
功能:用于存放用于对Nand flash芯片存储单元寻址的地址值。
在地址空间中地址:0x4E000008,其中:
Bit0-7:用于存放地址值。因为本款Nand flash芯片只有I/O0-7的地址/数据复用引脚且地址是四周
期每次8位送入的,所以这里只用到8位。其余位保留待用。
4. 数据寄存器(NFDATA)
功能:Nand flash芯片所有内设命令执行后都会将其值放到该寄存器中。同时,读出、写入Nand flash
存储空间的值也是放到该寄存器。
在地址空间中地址:0x4E00000C,其中:
Bit0-7:用于存放需要读出和写入的数据。其余位保留代用。
5. 状态寄存器(NFSTAT)
功能:用于检测Nand flash芯片上次对其存储空间的操作是否完成。
在地址空间中地址:0x4E000010,其中:
Bit0:置0表示Nand flash芯片正忙于上次对存储空间的操作;置1表示Nand flash芯片准备好接收新
的对存储空间操作的请求。
6. ECC校验寄存器(NFECC)
功能:ECC校验寄存器
在地址空间中地址:0x4E000014,其中:
        Bit0­Bit7: ECC0
        Bit8­Bit15: ECC1
        Bit16­Bit23: ECC2
1.4 Nand Flash 控制器中的硬件ECC介绍
1.4.1 ECC产生方法
   ECC是用于对存储器之间传送数据正确进行校验的一种算法,分硬件ECC和软件ECC算法两种,在
S3C2410的Nand Flash 控制器中实现了由硬件电路(ECC 生成器)实现的硬件ECC。1.4.2 ECC生成器工作过程
   当写入数据到Nand flash存储空间时, ECC生成器会在写入数据完毕后自动生成ECC码,将其放入到
ECC0-ECC2。当读出数据时Nand Flash 同样会在读数据完毕后,自动生成ECC码将其放到ECC0-ECC2当
中。
1.4.3 ECC的运用
    当写入数据时,可以在每页写完数据后将产生的ECC码放入到OOB指定的位置(Byte 6)去,这样就完成了
ECC码的存储。这样当读出该页数据时,将所需数据以及整个OOB读出,然后将指定位置的ECC码与读出数
据后在ECC0-ECC1的实际产生的ECC码进行对比,如果相等则读出正确,若不相等则读取错误需要进行重
读。
2 在ADS下flash烧写程序
2.1 ADS下flash烧写程序原理及结构
    基本原理:在windows环境下借助ADS仿真器将在SDRAM中的一段存储区域中的数据写到Nand flash存
储空间中。烧写程序在纵向上分三层完成:
    第一层: 主烧写函数(完成将在SDRAM中的一段存储区域中的数据写到Nand flash存储空间中);
    第二层: 为第一层主烧写函数提供支持的对Nand flash进行操作的页读、写,块擦除等函数;
    第三层:为第二层提供具体Nand flash控制器中对特殊功能寄存器进行操作的核心函数,该层也是真正的
将数据能够在SDRAM和Nand flash之间实现传送的函数。
    下面对其三层进行分述:
2.2 第三层实现说明
2.1.1 特殊功能寄存器定义
#define rNFCONF      (*(volatile unsigned int *)0x4e000000) 
#define rNFCMD       (*(volatile unsigned char *)0x4e000004)            
#define rNFADDR     (*(volatile unsigned char *)0x4e000008)            
#define rNFDATA     (*(volatile unsigned char *)0x4e00000c)           
#define rNFSTAT      (*(volatile unsigned int *)0x4e000010)      
#define rNFECC        (*(volatile unsigned int *)0x4e000014)     
#define rNFECC0     (*(volatile unsigned char  *)0x4e000014)
#define rNFECC1     (*(volatile unsigned char *)0x4e000015)
#define rNFECC2     (*(volatile unsigned char *)0x4e000016)
2.1.2 操作的函数实现
1. 发送命令
#define NF_CMD(cmd)       {rNFCMD=cmd;}
2. 写入地址
#define NF_ADDR(addr)       {rNFADDR=addr;}
3. Nand Flash芯片选中
#define NF_nFCE_L()       {rNFCONF&=~(1<<11);}
4. Nand Flash芯片不选中
#define NF_nFCE_H()       {rNFCONF|=(1<<11);}
5. 初始化ECC
#define NF_RSTECC()       {rNFCONF|=(1<<12);}
6. 读数据#define NF_RDDATA()        (rNFDATA)
7. 写数据
#define NF_WRDATA(data) {rNFDATA=data;}
8. 获取Nand Flash芯片状态
#define NF_WAITRB()         {while(!(rNFSTAT&(1<<0)));}
0/假: 表示Nand Flash芯片忙状态
1/真:表示Nand Flash已经准备好
2.3 第二层实现说明
2.3.1 Nand Flash 初始化
void NF_Init(void)
{
    /* 设置Nand Flash配置寄存器, 每一位的取值见1.3节 */
    rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
    /* 复位外部Nand Flash芯片 */  
    NF_Reset();
}
2.3.2 Nand flash复位
static void NF_Reset(void)
{
    int i;
   
    NF_nFCE_L();          /* 片选Nand Flash芯片*/
    NF_CMD(0xFF); /* 复位命令          */
    for(i=0;i<10;i++);     /* 等待tWB = 100ns.     */
    NF_WAITRB();       /* wait 200~500us;          */
    NF_nFCE_H();         /* 取消Nand Flash 选中*/
}
2.3.3 获取Nand flash ID
返回值为Nand flash芯片的ID号
unsigned short  NF_CheckId(void)
{
    int i;
    unsigned short id;
   
    NF_nFCE_L();                /* 片选Nand Flash芯片*/
    NF_CMD(0x90);             /* 发送读ID命令到Nand Flash芯片 */
    NF_ADDR(0x0);             /* 指定地址0x0,芯片手册要求    */
    for(i=0;i<10;i++);             /* 等待tWB = 100ns.             */
    id=NF_RDDATA()<<8;  /* 厂商ID(K9S1208V:0xec)   */
    id|=NF_RDDATA();        /* 设备ID(K9S1208V:0x76)  */
    NF_nFCE_H();                 /* 取消Nand Flash 选中*/
    return id;
}2.3.4 Nand flash写入
以页为单位写入.
参数说明:block  块号
          page   页号
          buffer  指向内存中待写入Nand flash中的数据起始位置
返回值:     0:写错误
         1:写成功
static int NF_WritePage(unsigned int block, unsigned int page, unsigned char *buffer)
{
    int i;
    unsigned int blockPage = (block<<5)+page;
    unsigned char *bufPt = buffer;
    NF_RSTECC();    /* 初始化 ECC              */
    NF_nFCE_L();     /* 片选Nand Flash芯片*/
    NF_CMD(0x0);    /* 从A区开始写      */
    NF_CMD(0x80);  /* 写第一条命令      */
    NF_ADDR(0);     /*  A0~A7 位(Column Address)   */
    NF_ADDR(blockPage&0xff);    /* A9­A16, (Page Address) */
    NF_ADDR((blockPage>>8)&0xff);   /* A17­A24, (Page Address) */
    NF_ADDR((blockPage>>16)&0xff);  /* A25,  (Page Address) */
    for(i=0;i<512;i++)
    {
NF_WRDATA(*bufPt++); /* 写一个页512字节到Nand Flash芯片 */
    }  
   
    /*
    * OOB一共16 Bytes, 每一个字节存放什么由程序员自己定义, 通常,
    * 我们在Byte0­Byte2存ECC检验码. Byte6 存放坏块标志.
    */
    seBuf[0]=rNFECC0;  /* 读取ECC检验码0 */
    seBuf[1]=rNFECC1;  /* 读取ECC检验码1 */
    seBuf[2]=rNFECC2;  /* 读取ECC检验码2 */
    seBuf[5]=0xff;  /* 非坏块标志       */
   
    for(i=0;i<16;i++)
    {
NF_WRDATA(seBuf[i]); /* 写该页的OOB数据块  */
    }  
    NF_CMD(0x10);   /* 结束写命令 */
    /* 等待Nand Flash处于准备状态 */   
    for(i=0;i<10;i++);   
    NF_WAITRB();    
 
    /* 发送读状态命令给Nand Flash  */
    NF_CMD(0x70);
    for(i=0;i<3;i++);   
    if (NF_RDDATA()&0x1)
    {    /*如果写有错, 则标示为坏块    */
     NF_nFCE_H();  /* 取消Nand Flash 选中*/
NF_MarkBadBlock(block);
return 0;
    } else { /* 正常退出 */
     NF_nFCE_H(); /* 取消Nand Flash 选中*/
     return 1;
    }
}
2.3.5 Nand flash读取
参数说明:block:块号
          page:页号
          buffer:指向将要读取到内存中的起始位置
返回值:1:读成功
        0:读失败
static int NF_ReadPage(unsigned int block, unsigned int page, unsigned char *buffer)
{
    int i;
    unsigned int blockPage;
    unsigned char ecc0, ecc1, ecc2;
    unsigned char *bufPt=buffer;
    unsigned char se[16];   
   
    page=page&0x1f;
    blockPage=(block<<5)+page;
    NF_RSTECC();    /* 初始化 ECC              */    
    NF_nFCE_L();      /* 片选Nand Flash芯片*/ 
    NF_CMD(0x00);   /* 从A区开始读      */
    NF_ADDR(0);      /*  A0~A7 位(Column Address)   */
    NF_ADDR(blockPage&0xff);     /* A9­A16, (Page Address) */
    NF_ADDR((blockPage>>8)&0xff);    /* A17­A24, (Page Address) */
    NF_ADDR((blockPage>>16)&0xff);  /* A25,  (Page Address) */
    /* 等待Nand Flash处于再准备状态 */  
    for(i=0;i<10;i++); 
    NF_WAITRB();    /*等待 tR(max 12us) */
    /* 读整个页, 512字节              */
    for(i=0;i<512;i++)
    {
     *bufPt++=NF_RDDATA();
    }
   
    /* 读取ECC码 */
    ecc0=rNFECC0;
    ecc1=rNFECC1;
    ecc2=rNFECC2;
        /* 读取该页的OOB块 */
    for(i=0;i<16;i++)
    {
     se[i]=NF_RDDATA(); 
    }
   
    NF_nFCE_H();    /* 取消Nand Flash 选中*/
    /* 校验ECC码, 并返回 */
    if(ecc0==se[0] && ecc1==se[1] && ecc2==se[2])
  return 1;
    else
      return 0;  
}
2.3.6 Nand flash标记坏块
如果是坏块, 通过写OOB块的Byte6把该块标记为坏块。
参数说明:block块号
返回值:1:ok,成功完成标记。
        0:表示写OOB块正确.
static int NF_MarkBadBlock(unsigned int block)
{
    int i;
    unsigned int blockPage=(block<<5);
 
    seBuf[0]=0xff;
    seBuf[1]=0xff;    
    seBuf[2]=0xff;    
    seBuf[5]=0x44;   /* 设置坏块标记 */
   
    NF_nFCE_L();   /* 片选Nand Flash芯片*/ 
    NF_CMD(0x50);   /* 从C区开始写    */ 
    NF_CMD(0x80);   /* 发送编程命令, 让Nand Flash处理写状态 */
    NF_ADDR(0x0);  /*  A0~A7 位(Column Address)   */
    NF_ADDR(blockPage&0xff);    /* A9­A16, (Page Address) */
    NF_ADDR((blockPage>>8)&0xff);   /* A17­A24, (Page Address) */
    NF_ADDR((blockPage>>16)&0xff);  /* A25,  (Page Address) */
   
    /* 写OOB数据块 */
    for(i=0;i<16;i++)
    {
NF_WRDATA(seBuf[i]);
    }
    NF_CMD(0x10);    /* 结束写命令 */
   
    /* 等待NandFlash准备好 */
    for(i=0;i<10;i++);  /* tWB = 100ns.  */
    NF_WAITRB();     /*读NandFlash的写状态 */  
    NF_CMD(0x70);    
    for(i=0;i<3;i++);  /* twhr=60ns  */
    if (NF_RDDATA()&0x1) 
    {
     NF_nFCE_H(); /* 取消Nand Flash 选中*/
     return 0;
     } else {
     NF_nFCE_H(); /* 取消Nand Flash 选中*/
    }
    return 1;
}
2.3.7 Nand Flash检查坏块
检查指定块是否是坏块.
参数说明:block:块号
返回值:1:指定块是坏块
        0:指定块不是坏块。
static int NF_IsBadBlock(U32 block)
{
    int i;
    unsigned int blockPage;
    U8 data;
       
    blockPage=(block<<5);
    NF_nFCE_L();         /* 片选Nand Flash芯片*/ 
    NF_CMD(0x50);      /* Read OOB数据块   */
    NF_ADDR(517&0xf);  /* A0~A7 位(Column Address)   */
    NF_ADDR(blockPage&0xff); /* A9­A16, (Page Address) */
    NF_ADDR((blockPage>>8)&0xff);    /* A17­A24, (Page Address) */
    NF_ADDR((blockPage>>16)&0xff);  /* A25,  (Page Address) */
    /* 等待NandFlash准备好 */
    for(i=0;i<10;i++); /* wait tWB(100ns)  */
    NF_WAITRB();
    /* 读取读出值 */ 
    data=NF_RDDATA();
    NF_nFCE_H();   /* 取消Nand Flash 选中*/
    /* 如果data不为0xff时, 表示该块是坏块 */
    if(data != 0xff)
     return 1;
    else
     return 0;    
}
2.3.8 擦除指定块中数据
参数说明:block 块号
返回值:0:擦除错误。(若是坏块直接返回0;若擦除出现错误则标记为坏块然后返回0)  1:成功擦除。
static int NF_EraseBlock(unsigned int block)
{
    unsigned int blockPage=(block<<5);
    int i;
    /* 如果该块是坏块, 则返回 */
    if(NF_IsBadBlock(block))
        return 0;
    NF_nFCE_L();      /* 片选Nand Flash芯片*/
    NF_CMD(0x60);   /* 设置擦写模式      */
    NF_ADDR(blockPage&0xff);    /* A9­A16, (Page Address) , 是基于块擦*/
    NF_ADDR((blockPage>>8)&0xff);    /* A17­A24, (Page Address) */
    NF_ADDR((blockPage>>16)&0xff);  /* A25, (Page Address) */
    NF_CMD(0xd0);   /* 发送擦写命令, 开始擦写 */
    /* 等待NandFlash准备好 */
    for(i=0;i<10;i++); /* tWB(100ns) */
    NF_WAITRB(); 
    /* 读取操作状态         */
    NF_CMD(0x70); 
    if (NF_RDDATA()&0x1)
    {
     NF_nFCE_H(); /* 取消Nand Flash 选中*/
NF_MarkBadBlock(block); /* 标记为坏块 */
return 0;
    }  else  {
     NF_nFCE_H(); /* 取消Nand Flash 选中*/
         return 1;
    }
}
2.4  第一层的实现
2.4.1 NandFlash烧写主函数说明
参数说明: block 块号
           srcAddress SDRAM中数据起始地址
           fileSize 要烧写的数据长度
返回值: 无
void K9S1208_Program(unsigned int block, unsigned int srcAddress, unsigned int fileSize)
{
      int  i;
     int  programError=0;
     U32  blockIndex;
     U8  *srcPt,  *saveSrcPt;
     srcPt=(U8 *)srcAddress; /* 文件起始地址 */
      blockIndex = block; /* 块号 */     while(1)
     {
                 saveSrcPt=srcPt;
            /* 如果当前块是坏块,  跳过当前块 */
          if(NF_IsBadBlock(blockIndex))
                 {
                        blockIndex++;   /* 到下一个块 */
                  continue;
         }
            /* 在写之前, 必须先擦除, 如果擦除不成功, 跳过当前块 */
            if(!NF_EraseBlock(blockIndex))
         {
                   blockIndex++;  /* 到下一个块 */
                        continue;
         }
    
            /* 写一个块, 一块有32页 */
         for(i=0;i<32;i++)
         {
                  /* 写入一个页, 如果出错, 停止写当前块 */
                  if(!NF_WritePage(blockIndex,i,srcPt))
                        {
                       programError=1;
                       break;
                  }
                  /* 如果操作正常, 文件的写位置加上1页偏移,到下一页的起始位置 */
     srcPt+=512;
                  /* 如果写地址没有超过文件长度, 继续; 超出则终止写 */
                  if((U32)srcPt>=(srcAddress+fileSize))
   break;
           }
       
        /* 如果写一个块时, 其中某一页写失败, 则把写地址恢复写该块之前, 并跳过当前块 */
            if(programError==1)
            {
                  blockIndex++;
                  srcPt=saveSrcPt;
                  programError=0;
                  continue;
            }
            /* 如果写地址没有超过文件长度, 继续; 超出则终止写 */
            if((U32)srcPt >= (srcAddress+fileSize))
                  break;
            /* 如果正常写成功, 继续写下一个块 */
            blockIndex++;
      }
}3 在U-BOOT对Nand Flash的支持
3.1 U-BOOT对从Nand Flash启动的支持
3.1.1 从Nand Flash启动U-BOOT的基本原理
1. 前4K的问题
    如果S3C2410被配置成从Nand Flash启动(配置由硬件工程师在电路板设置), S3C2410的Nand Flash控制器
有一个特殊的功能, 在S3C2410上电后, Nand Flash控制器会自动的把Nand Flash上的前4K数据搬移到4K内部
RAM中, 并把0x00000000设置内部RAM的起始地址, CPU从内部RAM的0x00000000位置开始启动。这个过
程不需要程序干涉。
    程序员需要完成的工作,是把最核心的启动程序放在Nand Flash的前4K中。
2.  启动程序的安排
     由于Nand Flash控制器从Nand Flash中搬移到内部RAM的代码是有限的,所以, 在启动代码的前4K里,我
们必须完成S3C2410的核心配置以及把启动代码(UBOOT)剩余部分搬到RAM中运行。以UBOOT为例, 前4K
完成的主要工作, 见第四部分的2.2节。
3.1.2 支持Nand Flash启动代码说明
    首先在include/configs/crane2410.h中加入CONFIG_S3C2410_NAND_BOOT, 如下:
#define CONFIG_S3C2410_NAND_BOOT     1
支持从Nand Flash中启动.
1. 执行Nand Flash初始化
下面代码在cpu/arm920t/start.S中
#ifdef CONFIG_S3C2410_NAND_BOOT
copy_myself:
mov r10, lr
ldr sp, DW_STACK_START   @安装栈的起始地址
mov fp, #0                                 @初始化帧指针寄存器
bl nand_reset                             @跳到复位C函数去执行
...
DW_STACK_START:
.word      STACK_BASE+STACK_SIZE­4
2. nand_reset C代码
下面代码被加在/board/crane2410/crane2410.c中
void nand_reset(void)
{
        int i;
/* 设置Nand Flash控制器 */
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
        /* 给Nand Flash芯片发送复位命令 */
        NF_nFCE_L();
        NF_CMD(0xFF);
        for(i=0; i<10; i++);
        NF_WAITRB();        NF_nFCE_H();
}
3.  从Nand Flash中把UBOOT拷贝到RAM
@read U­BOOT from Nand Flash to RAM
ldr   r0, =UBOOT_RAM_BASE   @ 设置第1个参数: UBOOT在RAM中的起始地址
mov   r1, #0x0                                @ 设置第2个参数:Nand Flash的起始地址
mov   r2, #0x20000                        @ 设置第3个参数: UBOOT的长度(128KB)
bl    nand_read_whole                    @ 调用nand_read_whole(), 该函数在board/crane2410/crane2410.c中
tst   r0, #0x0                                   @ 如果函数的返回值为0,表示执行成功.
beq   ok_nand_read                        @ 执行内存比较
4. 从Nand Flash中把数据读入到RAM中
int nand_read_whole(unsigned char *buf, unsigned long start_addr, int size)
{
int i, j;
/* 如果起始地址和长度不是512字节(1页)的倍数, 则返回错误代码 */
if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK)) {
return ­1;
}
/* 激活Nand Flash */
NF_nFCE_L();
for(i=0; i<10; i++);
i = start_addr;
while(i < start_addr + size) {
/* 读A区 */
rNFCMD = 0;
/* 写入读取地址 */
rNFADDR = i & 0xff;
rNFADDR = (i >> 9) & 0xff;
rNFADDR = (i >> 17) & 0xff;
rNFADDR = (i >> 25) & 0xff;
NF_WAITRB();
                /* 读出一页(512字节) */
for(j=0; j < NAND_SECTOR_SIZE; j++, i++) {
*buf = (rNFDATA & 0xff);
buf++;
}
}
/* 停止驱动Nand Flash */
NF_nFCE_H();
return 0;
}5.  校查搬移后的数据
把RAM中的前4K与内部中前4K进行比较, 如果完全相同, 则表示搬移成功.
ok_nand_read:
mov r0, #0x00000000                  @内部RAM的起始地址
ldr r1, =UBOOT_RAM_BASE   @UBOOT在RAM中的起始地址
mov r2, #0x400                            @比较1024次, 每次4字节, 4 bytes * 1024 = 4K­bytes
go_next:     @ 比较1024次, 每次4个字节
ldr   r3, [r0], #4
ldr   r4, [r1], #4
teq   r3, r4
bne  notmatch
subs r2, r2, #4
beq  done_nand_read
bne  go_next
notmatch:
1:b     1b
done_nand_read:
mov pc, r10
3.2 U-BOOT对Nand Flash命令的支持
    在U­BOOT下对Nand Flash的支持主要是在命令行下实现对nand flash的操作。对nand flash实现的命令
为:nand info、nand device、nand read、nand write、nand erease、nand bad。
    用到的主要数据结构有:struct nand_flash_dev、struct nand_chip。前者包括主要的芯片型号、存储容量、
设备ID、I/O总线宽度等信息;后者是具体对nand flash进行操作时用到的信息。
3.2.1 主要数据结构介绍
1. struct nand_flash_dev数据结构
该数据结构在include/linux/mtd/nand.h中定义,在include/linux/mtd/nand_ids.h中赋初值。
struct nand_flash_dev {
char *name;              /* 芯片名称 */
int manufacture_id;          /* 厂商ID     */
int model_id;                    /* 模式ID     */
int chipshift;                     /*  Nand Flash地址位数 */
char page256;                   /* 表明是否时256字节一页。1:是;0:否。*/
char pageadrlen;              /* 完成一次地址传送需要往NFADDR中传送几次。*/
unsigned long erasesize;  /* 一次块擦除可以擦除多少字节 */
int bus16;                          /* 地址线是否是16位,1:是;0:否 */
};
2. struct nand_chip数据结构
该数据结构在include/linux/mtd/nand.h中定义. 该结构体定义出一个Nand Flash设备数组:
struct nand_chip nand_dev_desc[CFG_MAX_NAND_DEVICE];
该数组在nand_probe()中对其进行初始化.
struct nand_chip {
int  page_shift;           /*  Page 地址位数         */
u_char  *data_buf;            /* 本次读出的一页数据    */
u_char  *data_cache;        /* 读出的一页数据        */
int          cache_page;         /* 上次操作的页号        */u_char  ecc_code_buf[6]; /* ECC校验码            */
u_char  reserved[2];
char ChipID;                /* 芯片ID号              */
struct Nand *chips;            /* Nand Flash芯片列表, 表示支持几个芯片为一个设备*/
int chipshift;
char* chips_name;            /* Nand Flash芯片名称     */
unsigned long erasesize;   /* 块擦写的大小           */
unsigned long mfr;           /* 厂商ID                                 */
unsigned long id;              /* 模式ID                                */
char* name;                      /* 设备名称                */
int numchips;                   /* 有几块Nand Flash芯片   */
char page256;                  /* 一页是256字节, 还是512字节 */
char pageadrlen;              /* 页地址的长度           */
unsigned long IO_ADDR;  /* 用于对nand flash进行寻址的地址值存放处 */
unsigned long totlen;    /* Nand Flash总共大小       */
uint oobblock;              /* 一页的大小。本款nand flash为512                 */
uint oobsize;            /* spare array大小。本款nand flash为16            */
uint eccsize;                 /* ECC 大小                  */            
int bus16;                     /* 地址线是否是16位,1:是;0:否        */
};
3.2.2 支持的命令函数说明
1. nand info/nand device
功能:显示当前nand flash芯片信息。
函数调用关系如下(按先后顺序):
static void nand_print(struct nand_chip *nand) ;
2. nand erase
功能:擦除指定块上的数据。
函数调用关系如下(按先后顺序):
int nand_erase(struct nand_chip* nand, size_t ofs, size_t len, int clean);
3. nand bad
功能:显示坏块。
函数调用关系如下(按先后顺序):
static void nand_print_bad(struct nand_chip* nand);
int check_block (struct nand_chip *nand, unsigned long pos);
4. nand read
功能:读取nand flash信息到SDRAM。
函数调用关系如下(按先后顺序):
int nand_rw (struct nand_chip* nand, int cmd,size_t start, size_t len,  size_t * retlen, u_char * buf);
static int nand_read_ecc(struct nand_chip *nand, size_t start, size_t len, 
                      size_t * retlen, u_char *buf, u_char *ecc_code);
static void NanD_ReadBuf (struct nand_chip *nand, u_char * data_buf, int cntr);
READ_NAND(adr);
5. nand write
功能:从SDRAM写数据到nand flash中。
函数调用关系如下(按先后顺序):int nand_rw (struct nand_chip* nand, int cmd,size_t start, size_t len, size_t * retlen, u_char * buf);
static int nand_write_ecc (struct nand_chip* nand, size_t to, size_t len,
                       size_t * retlen, const u_char * buf, u_char * ecc_code);
static int nand_write_page (struct nand_chip *nand, int page, int col, int last, u_char * ecc_code);
WRITE_NAND(d , adr);
3.2.3 U-BOOT支持Nand Flash命令移植说明
1. 设置配置选项
  在CONFIG_COMMANDS中, 打开CFG_CMD_NAND选项.
  #define CONFIG_COMMANDS \
(CONFIG_CMD_DFL  | \
CFG_CMD_CACHE  | \
CFG_CMD_NAND  | \
/*CFG_CMD_EEPROM |*/ \
/*CFG_CMD_I2C  |*/ \
/*CFG_CMD_USB  |*/ \
CFG_CMD_PING     | \
CFG_CMD_REGINFO  | \
CFG_CMD_DATE  | \
CFG_CMD_ELF)
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
#define CFG_NAND_BASE                   0x4E000000    /* Nand Flash控制器在SFR区中起始寄存器地址 */
#define CFG_MAX_NAND_DEVICE   1                      /* 支持的最在Nand Flash数据 */
#define SECTORSIZE                             512                 /* 1页的大小 */
#define NAND_SECTOR_SIZE              SECTORSIZE
#define NAND_BLOCK_MASK            (NAND_SECTOR_SIZE – 1)  /* 页掩码 */
#define ADDR_COLUMN                      1    /* 一个字节的Column地址 */
#define ADDR_PAGE                             3   /* 3字节的页块地址, A9­A25*/
#define ADDR_COLUMN_PAGE         4   /*  总共4字节的页块地址   */
#define NAND_ChipID_UNKNOWN   0x00  /* 未知芯片的ID号 */
#define NAND_MAX_FLOORS            1       
#define NAND_MAX_CHIPS                1
/* Nand Flash命令层底层接口函数 */
#define WRITE_NAND_COMMAND(d, adr) do {rNFCMD = d;} while(0)
#define WRITE_NAND_ADDRESS(d, adr) do {rNFADDR = d;} while(0)
#define WRITE_NAND(d, adr)         do {rNFDATA = d;} while(0)
#define READ_NAND(adr)             (rNFDATA)
#define NAND_WAIT_READY(nand)      {while(!(rNFSTAT&(1<<0)));}
#define NAND_DISABLE_CE(nand) {rNFCONF |= (1<<11);}
#define NAND_ENABLE_CE(nand) {rNFCONF &= ~(1<<11);}
/* 下面一组操作对Nand Flash无效 */
#define NAND_CTL_CLRALE(nandptr)
#define NAND_CTL_SETALE(nandptr)
#define NAND_CTL_CLRCLE(nandptr)
#define NAND_CTL_SETCLE(nandptr)/* 允许Nand Flash写校验 */
#define CONFIG_MTD_NAND_VERIFY_WRITE 1
#endif    /* CONFIG_COMMANDS & CFG_CMD_NAND*/   
2. 加入自己的Nand Flash芯片型号
    在include/linux/mtd/ nand_ids.h中的对如下结构体赋值进行修改:
   static struct nand_flash_dev nand_flash_ids[] = { 
       ......
      {"Samsung K9F1208U0B",    NAND_MFR_SAMSUNG, 0x76, 26, 0, 4, 0x4000, 0},
       .......
   }
   这样对于该款Nand Flash芯片的操作才能正确执行。
3. 编写自己的Nand Flash初始化函数
在board/crane2410/crane2410.c中加入nand_init()函数. 
void nand_init(void)
{
       /* 初始化Nand Flash控制器, 以及Nand Flash 芯片 */
nand_reset();  
       /* 调用nand_probe()来检测芯片类型 */
printf ("%4lu MB\n", nand_probe(CFG_NAND_BASE) >> 20);
}
该函数在启动时被start_armboot()调用.
4 在Linux对Nand Flash的支持
4.1 Linux下Nand Flash调用关系
4.1.1 Nand Flash设备添加时数据结构包含关系
        struct mtd_partition          partition_info[]
    --> struct s3c2410_nand_set       nandset
    --> struct s3c2410_plat form_nand  superlpplatfrom
    --> struct platform_device         s3c_device_nand
 在该数据结构的name字段的初始化值"s3c2410-nand",必须与Nand Flash设备驱动注册时
        struct device_driver结构中的name字段相同,因为platfrom bus是依靠名字来匹配的.
    --> struct platform_device         *smdk2410_devices[]
4.1.2 Nand Flash设备注册时数据结构包含关系
    struct device_driver s3c2410_n and_driver
    -->struct device *dev
       该数据构由系统分配.
    -->struct platform_device *pdev
    -->struct s3c2410_plat form_nand *plat
    -->struct s3c2410_nand_set nset
    -->struct mtd_partition
4.1.3  当发生系统调用时数据结构调用关系
struct mtd_info它的*priv指向chip
    -->struct nand_chip
  它的*priv指向nmtd
    -->struct s3c2410_nand_mtd
它是s3c2410_nand_info的一个字段
    -->s3c2410_nand_info
它被设为Nand Flash设备驱动的私有数据结构,在Nand Flash设备驱动注册时分配空间.
    -->struct device
   
4.2 Linux下Nand Flash驱动主要数据结构说明
4.2.1 s3c2410专有数据结构
1. s3c2410_nand_set
struct s3c2410_nand_set {
        int                     n r_chips;      /* 芯片的数目 */
        int                     n r_partitions; /* 分区的数目 */
        char                    *n ame;         /* 集合名称   */
        int                      nr_map;        /* 可选, 底层逻辑到物理的芯片数目 */
        struct mtd_partition    partitions;    /* 分区列表   */
};
2. s3c2410_platform_and
struct s3c2410_platform_nand {
        /* timing information for controller, all times in nanoseconds */
        int     tacls;  /* 从CLE/ALE有效到 nWE/nOE的时间 */
        int     twrph0; /* nWE/nOE的有效时间 */
        int     twrph1; /* 从释放CLE/ALE到nWE/nOE不活动的时间 */
        int     nr_sets; /* 集合数目 */
        struct s3c2410_nand_set sets; /* 集合列表 */
 /* 根据芯片编号选择有效集合 */
        void (*select_chip)(struct s3c2410_nand_set , int chip);
};
3.  s3c2410_nand_mtd
在drivers/mtd/nand/s3c2410.c中,
struct s3c2410_nand_mtd {
        struct mtd_info                 mt d;    /* MTD 信息 */
        struct nand_chip                ch ip;   /* nand flash 芯片信息 */
        struct s3c2410_nand_set         set;    /* nand flash 集合     */
        struct s3c2410_nand_info        *info;  /* nand flash 信息     */
        int                             scan _res;
};
4. s3c2410_nand_info
struct s3c2410_nand_info {
        /* mtd info */
        struct nand_hw_control          co ntroller; /* 硬件控制器 */
        struct s3c2410_nand_mtd         *mt ds;      /* MTD 设备表 */
        struct s3c2410_platform_nand    platform;   /* Nand 设备的平台 */        /* device info */
        struct device                   *device;    /*  设备指针 */
        struct resource                 *area;      /*  资源指针 */
        struct clk                      *clk;       /* N and Flash 时钟 */
        void __iomem                    *reg s;      /* 寄存器基地址(map后的逻辑地址) */
        int                             mt d_count;  /* MTD的数目 */
        unsigned char                   is_s3c2440;
};
5. struct clk
在arch/arm/mach­s3c2410/clock.h中
struct clk {
        struct list_head      list ;     /* clock 列表结点 */
        struct module        *o wner;    /* 所属模块       */
        struct clk           *paren t;   /* 父结点         */
        const char           *n ame;     /* 名称           */
        int                   id;       /*  编号           */
        atomic_t              used;     /*  使用者计数     */
        unsigned long         rate;     /* 时钟速率       */
        unsigned long         ctrlbit;  /* 控制位         */
        int                 (*en able)(struct clk *, int enable); /* Clock打开方法 */
};
4.2.2 Linux 通用数据结构说明
1. device_driver
include/linux/device.h
struct device_driver {
        const char              * n ame; /* 驱动名称  */
        struct bus_type         * b us;  /* 总线类型  */
        struct completion       unloaded;  /* 卸载事件通知机制 */
        struct kobject          kobj; /* sys中的对象 */
        struct klist            klist _devices; /* 设备列表 */
        struct klist_node       knode_bus; /* 总线结点列表 */
        struct module           * o wner;/* 所有者  */
        /* 设备驱动通用方法 */
        int     (*probe)        (struct device * dev) ; /* 探测设备 */
        int     (*remove)       (struct device * dev) ; /* 移除设备 */
        void    (*shutdown)     (struct device * dev) ; /* 关闭设备 */
        /* 挂起设备         */
        int     (*suspend)      (struct device * dev, pm_messag e_t state, u32 level) ;
        int     (*resume)        (struct device * dev, u32 level) ; /* 恢复 */
};
2. platform_device
include/linux/device.h
struct platform_device {
        const char      * name;  /* 名称 */
        u32             id;      /*  设备编号, -1表示不支持同类多个设备 */
        struct device   dev;     /* 设备 */
        u32             n um_resources; /* 资源数 */
        struct resource * resource; /* 资源列表 */};
3. resource
struct resource {
        const char name;           /* 资源名称 */
        unsigned long start, end;  /* 开始位置和结束位置 */
        unsigned long flags;       /* 资源类型 */
        /* 资源在资源树中的父亲,兄弟和孩子 */
        struct resource *parent, *sibling, *child;
};
4. device
include/linux/device.h
struct device {
        struct klist            klist _children;  /* 在设备列表中的孩子列表 */
        struct klist_node       knode_parent;    /* 兄弟结点 */
        struct klist_node       knode_driver;    /* 驱动结点 */
        struct klist_node       knode_bus;       /* 总线结点 */
        struct device            parent;          /* 父亲     */
        struct kobject kobj;                     /* sys结点 */
        char    bus_id[BUS_ID_SIZE];          
        struct semaphore        sem;    /* 同步驱动的信号量  */
        struct bus_type * bus;          /* 总线类型          */
        struct device_driver *driver;   /*  设备驱动          */
        void            *driver_dat a;   /* 驱动的私有数据    */
        void            *plat form_data; /* 平台指定的数据,为device核心驱动保留 */
        void            *firmw are_data; /* 固件指定的数据,为device核心驱动保留 */
        struct dev_pm_info  power;      /* 设备电源管理信息 */
        u64             *dma_mask;      /* DMA掩码          */
        u64             co herent_dma_mask;
        struct list_head dma_pools;     /* DMA缓冲池        */
        struct dma_coherent_mem *dma_mem; /* 连续DMA内存的起始位置 */
        void    (*release) (struct device * dev) ;  /* 释放设置方法 */
};
5. nand_hw_control
include/linux/mtd/nand.h
struct nand_hw_control {
        spinlock_t       lock;      /* 自旋锁,用于硬件控制 */
        struct nand_chip *active;   /* 正在处理MTD设备    */
        wait_queue_head_t wq;       /* 等待队列            */
};
6. nand_chip
include/linux/mtd/nand.h
struct nand_chip {
        void  __iomem   *IO_ADDR_R; /* 读地址 */
        void  __iomem   *IO_ADDR_W; /* 写地址 */ /* 字节操作   */       
        u_char          (*read_b yte)(struct mtd_info *mtd);  /* 读一个字节 */       
        void            (*w rite_byte)(struct mtd_info *mtd, u_char byte); /* 写一个字节 */      
        /* 双字节操作 */
        u16             (*read_w ord)(struct mtd_info mtd);  /* 读一个字   */
        void            (*w rite_word)(struct mtd_info *mtd, u16 word); /* 写一个字 */       
        /* buffer操作 */
        void            (*w rite_buf)(struct mtd_info *mtd, const u_char *buf, int len);
        void            (*read_b uf)(struct mtd_info *mtd, u_char *buf, int len);
        int             (*verify_b uf)(struct mtd_info *mtd, const u_char *buf, int len);
        /* 选择一个操作芯片 */       
        void            (*select _chip)(struct mtd_info *mtd, int chip);
        /* 坏块检查操作 */
        int             (*b lock_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);
        /* 坏块标记操作 */
        int             (*b lock_markbad)(struct mtd_info *mtd, loff_t ofs);
        /* 硬件控制操作 */
        void            (*h wcontrol)(struct mtd_info *mtd, int cmd);
        /* 设备准备操作 */
        int             (*dev_ready) (struct mtd_info *mtd);
        /* 命令发送操作 */
        void            (*cmdfun c)(struct mtd_info *mtd, unsigned command, int column, int
page_addr);
        /* 等待命令完成 */
        int             (*w aitfunc)(struct mtd_info *mtd, struct nand_chip *this, int state);
        /* 计算ECC码操作 */
        int             (*calculat e_ecc)(struct mtd_info *mtd, const u_char *dat, u_char
*ecc_code);
        /* 数据纠错操作  */
        int             (*co rrect_data)(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, 
u_char *calc_ecc) ;
        /* 开启硬件ECC */
        void            (*en able_hwecc)(struct mtd_info *mtd, int mode);
        /* 擦除操作     */
        void            (*erase_cmd) (struct mtd_info *mtd, int page);
        /* 检查坏块表   */
        int             (*scan _bbt)(struct mtd_info *mtd);       
        int             eccmo de;    /* ECC模式      */       
        int             eccsize;    /* E CC 计算时使用的字节数 */
        int             eccb ytes;   /* ECC 码的字节数 */
        int             eccst eps;   /* ECC 码计算的步骤数 */
        int             ch ip_delay; /* 芯片的延迟时间 */
        spinlock_t      chip_lock;  /* 芯片访问的自旋锁 */
        wait_queue_head_t wq;       /* 芯片访问的等待队列 */
        nand_state_t    state;      /* Nand Flash状态 */
        int             pag e_shift; /* 页右移的位数,即column地址位数 */
        int             ph ys_erase_shift; /* 块右移的位数, 即column和页一共的地址位数 */
        int             b bt_erase_shift; /* 坏块页表的位数 */
        int             ch ip_shift; /* 该芯片总共的地址位数 */
        u_char          *dat a_buf;  /* 数据缓冲区  */
        u_char          *o ob_buf;   /* oob缓冲区  */
        int             o obdirty;   /* oob缓冲区是否需要重新初始化 */
        u_char          *dat a_poi;  /* 数据缓冲区指针 */
        unsigned int    options;    /* 芯片专有选项 */
        int             b adblockpos;/* 坏块标示字节在OOB中的位置 */
        int             n umchips;   /* 芯片的个数   */        unsigned long   chipsize;   /* 在多个芯片组中, 一个芯片的大小 */
        int             pag emask;   /* 每个芯片页数的屏蔽字, 通过它取出每个芯片包含多少个页 */
        int             pag ebuf;    /* 在页缓冲区中的页号 */
        struct nand_oobinfo     *autooob; /* oob信息 */
        uint8_t         *bbt;       /* 坏块页表 */
        struct nand_bbt_descr   *bbt_td; /* 坏块表描述 */
        struct nand_bbt_descr   *bbt_md; /* 坏块表镜像描述 */
        struct nand_bbt_descr   *badblock_pattern; /* 坏块检测模板 */
        struct nand_hw_control  *controller; /* 硬件控制 */
        void            *priv; /* 私有数据结构 */
        /* 进行附加错误检查 */
        int             (*errst at)(struct mtd_info *mtd, struct nand_chip *this, int state, int
status, int page);
};
7. mtd_info
include/linux/mtd/mtd.h
struct mtd_info {
        u_char type;      /* 设备类型         */
        u_int32_t flags;  /* 设备标志位组     */
        u_int32_t size;   /* 总共设备的大小   */
        u_int32_t erasesize;  /* 擦除块的大小 */
        u_int32_t oobblock; /* OOB块的大小,如:512个字节有一个OOB */
        u_int32_t oobsize;  /* OOB数据的大小,如:一个OOB块有16个字节 */
        u_int32_t ecctype;  /* ECC校验的类型 */
        u_int32_t eccsize;  /* E CC码的大小   */
        char *name;         /* 设备名称       */
        int index;          /* 设备编号       */
        /* oobinfo信息,它可以通过 MEMSETOOBINFO ioctl命令来设置 */
        struct nand_oobinfo oobinfo;
        u_int32_t oobavail;  /* OOB区的有效字节数,为文件系统提供 */
        /* 数据擦除边界信息         */
        int numeraseregions;
        struct mtd_erase_region_info *eraseregions;
        u_int32_t bank_size; /* 保留 */
        /* 擦除操作 */
        int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
        /* 指向某个执行代码位置 */
        int (*point) (struct mtd_info *mtd, loff_t from,
                       size_t len, size_t *retlen, u_char **mtdbuf);
        /* 取消指向 */
        void (*unpoint) (struct mtd_info *mtd, u_char * addr, lo ff_t from, size_t len);
 /* 读/写操作 */
        int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
        int (*write) (struct mtd_info *mtd, loff_t to, size_t len,
                       size_t *retlen, const u_char *buf);
 /* 带ECC码的读/写操作 */
        int (*read_ecc) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
                          u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel);
        int (*write_ecc) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
                           const u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel);        /* 带OOB码的读/写操作 */
        int (*read_oob) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
                          u_char *buf);
        int (*write_oob) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
                           const u_char *buf);
        /* 提供访问保护寄存器区的方法 */
        int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
        int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len,
                                    size_t *retlen, u_char *buf);
        int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
        int (*read_user_pro t_reg) (struct mtd_info *mtd, loff_t from, size_t len,
                                    size_t *retlen, u_char *buf);
        int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len,
                                     size_t *retlen, u_char *buf);
        int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);
        /* 提供readv和writev方法        */
        int (*readv) (struct mtd_info *mtd, struct kvec *vecs, un signed long count,
                       loff_t from, size_t *retlen);
        int (*readv_ecc) (struct mtd_info *mtd, struct kvec *vecs, un signed long count,
                           loff_t from, size_t *retlen, u_char *eccbuf,
                           struct nand_oobinfo *oobsel);
        int (*writev) (struct mtd_info *mtd, const struct kvec *vecs,
                        unsigned long count, loff_t to, size_t *retlen);
        int (*writev_ecc) (struct mtd_info *mtd, const struct kvec *vecs,
                            unsigned long count, loff_t to, size_t *retlen,
                            u_char *eccbuf, struct nand_oobinfo *oobsel);
        /* 同步操作 */
        void (*sync) (struct mtd_info *mtd);
        /* 芯片级支持的加/解锁操作 */
        int (*lock) (struct mtd_info *mtd, loff_t ofs, size_t len);
        int (*unlock) (struct mtd_info *mtd, loff_t ofs, size_t len);
        /* 电源管理操作            */
        int (*suspend) (struct mtd_info *mtd);
        void (*resume) (struct mtd_info *mtd);
        /* 坏块管理操作            */
        int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
        int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);
 /* 重启前的通知事件        */
        struct notifier_block reboot_notifier;
        void *priv; /* 私有数据结构 */
        struct module *owner; /* 模块所有者 */
        int usecount; /* 使用次数 */
};
4.3 Linux下Nand Flash驱动说明4.3.1 注册driver_register
通过module_init(s3c2410_nand_init);注册Nand Flash驱动. 在 s3c2410_nand_init ()中通过driver_register()注册
s3c2410_nand_driver驱动程序,如下所示:
static struct device_driver s3c2410_n and_driver = {
        .name           = "s3c2410-n and",
        .bus            = &plat form_bus_type, /* 在drivers/base/platform.c中定义 */
        .probe          = s3c2410_n and_probe,
        .remove         = s3c2410_n and_remove,
};
4.3.2 探测设备probe
在注册的Nand Flash驱动程序中, probe方法为s3c2410_nand_probe(). s3c2410_nand_probe()再调用
s3c24xx_nand_probe(). 在该函数中, 把*info作为Nand Flash驱动的私有数据结构, 并通过dev_set_drvdata(dev, 
info)把*info保存在*device的*driver_data字段中.然后通过clk_get(dev, "nand")获取Nand Flash的时钟资
源,  clk_use(in fo->clk)增加时钟资源的使用计数, clk_enable(info->clk)开启资源.填写*info的其它字段,
其中包括:
  1. 通过request_mem_region()为Nand Flash寄存器区申请I/O内存地址空间区,并通过ioremap()把它映射到虚
拟地址空间.
  2.  调用s3c2410_nand_inithw()初始化Nand Flash控制器.
  3.  为mtd设备分配设备信息的存储空间.
  4.  对当前mtd设备,调用s3c2410_nand_init_chip()进行初始化.
  5.  对当前mtd设备, 调用nand_scan()检测Nand Flash芯片, nand_scan()函数在drivers/mtd/nand/nand_base.c中
定义.该函数的作用是初始化struct nand_chip中一些方法, 并从Nand Flash中读取芯片ID, 并初始化struct 
mtd_info中的方法.
  6.  对当前mtd设备,加入其分区信息.
  7. 如果还有更多mtd设备,到4执行.
4.3.3 初始化Nand Flash控制器
s3c2410_nand_inithw()函数会初始化Nand Flash控制器, 通过设置Nand Flash控制寄存器(S3C2410_NFCONF)来
完成, 这里最重要的是根据S3C2410的PCLK计算出tacls, twrph0以及twrph1值.
4.3.4 移除设备
s3c2410_nand_remove()当设备被移除时,被device核心驱动调用.它完成的主要工作如下:
  1. 把*device的*driver_data字段置空.
  2. 释放mtd设备信息.
  3. 释放clk资源.
  4. 通过iounmap()取消映地址空间.
  5. 释放申请的I/O内存资源.
  6. 释放设备私有数据*info的空间.
4.3.5 Nand Flash芯片初始化
s3c2410_nand_init_chip()初始化struct nand_chip中的一些主要字段以及方法.其中主要包括的方法有:
  1. s3c2410_nand_hwcontrol(); 硬件控制
  2. s3c2410_nand_devready(); 设备是否准备好
  3. s3c2410_nand_write_buf(); 写一个buffer到nand flash
  4. s3c2410_nand_read_buf(); 读一个buffer到nand flash
  5. s3c2410_nand_select_chip(); 选择操作芯片
如果支持ECC硬件校验,还设置如下方法:  1. s3c2410_nand_correct_data(); 通过ECC码校正数据
  2. s3c2410_nand_enable_hwecc(); 开启硬件ECC检查
  3. s3c2410_nand_calculate_ecc(); 计算ECC码
4.3.6  读Nand  Flash
    当对nand flash的设备文件(nand flash在/dev下对应的文件)执行系统调用read(),或在某个文件系统中对该
设备进行读操作时. 会调用struct mtd_info中的read方法,他们缺省调用函数为nand_read(),在
drivers/mtd/nand/nand_base.c中定义.nand_read()调用nand_do_read_ecc(),执行读操作. 在
nand_do_read_ecc()函数中,主要完成如下几项工作:
1. 会调用在nand flash驱动中对struct nand_chip重载的select_chip方法,即
s3c2410_nand_select_chip()选择要操作的MTD芯片.
2. 会调用在struct nand_chip中系统缺省的方法cmdfunc发送读命令到nand flash.
3. 会调用在nand flash驱动中对struct nand_chip重载的read_buf(),即s3c2410_nand_read_buf()
从Nand Flash的控制器的数据寄存器中读出数据.
4. 如果有必要的话,会调用在nand flash驱动中对struct nand_chip重载的
enable_hwecc,correct_data以及calculate_ecc方法,进行数据ECC校验。
4.3.7  写Nand Flash
当对nand flash的设备文件(nand flash在/dev下对应的文件)执行系统调用write(),或在某个文件系统中对该设备
进行读操作时, 会调用struct mtd_info中write方法,他们缺省调用函数为nand_write(),这两个函数在
drivers/mtd/nand/nand_base.c中定义. nand_write()调用nand_write_ecc(),执行写操作.在
nand_do_write_ecc()函数中,主要完成如下几项工作:
1. 会调用在nand flash驱动中对struct nand_chip重载的select_chip方法,即
s3c2410_nand_select_chip()选择要操作的MTD芯片.
2. 调用nand_write_page()写一个页.
3. 在nand_write_page()中,会调用在struct nand_chip中系统缺省的方法cmdfunc发送写命令
到nand flash.
4. 在nand_write_page()中,会调用在nand flash驱动中对struct nand_chip重载的
write_buf(),即s3c2410_nand_write_buf()从Nand Flash的控制器的数据寄存器中写入数据.
5. 在nand_write_page()中,会调用在nand flash驱动中对struct nand_chip重载waitfunc方法,
该方法调用系统缺省函数nand_wait(),该方法获取操作状态,并等待nand flash操作完成.等
待操作完成,是调用nand flash驱动中对struct nand_chip中重载的dev_ready方法,即
s3c2410_nand_devready()函数.
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值