1.先看硬件原理图,nand是怎样接到主控芯片的哪里,nand芯片的各个管脚是什么意义?各个管脚要怎样配合才可以访问nand;

    主控芯片的nand控制器的RnB管脚接到---->nand芯片的R/B管脚,这个管脚是判断nand芯片是否正忙的管脚,主控芯片通过读nand控制器的RnB为0说明nand正忙(读寄存器NFSTAT的bit0);

  主控芯片的nand控制器的CLE管脚接到---->nand芯片的非CLE管脚,当主控芯片的CLE管脚拉低时,出现在data0~data7的数据是命令;(把命令值写到NFCMMD就是拉低CLE了)

  主控芯片的nand控制器的ALE管脚接到---->nand芯片的非ALE管脚,当主控芯片的ALE管脚拉低时,出现在data0~data7的数据是地址;(把地址值写到NFADDR就是拉低ALE了)

  主控芯片的nand控制器的nFCE管脚接到---->nand芯片的非CE管脚,当主控芯片的CE管脚拉低时,表示选中nand,也就是片选管脚;(操作寄存器NFCONT的bit1)

  主控芯片的nand控制器的nFWE管脚接到---->nand芯片的非WE管脚,是nand flash写使能管脚,说明数据传输的方向是(主控芯片->nand);将数据写到NFDATA寄存器就自动拉低nFWE管脚了

  主控芯片的nand控制器的nFRE管脚接到---->nand芯片的非RE管脚,是nand flash读使能管脚,说明数据传输的方向是(主控芯片<-nand);读NFDATA寄存器就自动拉低nFRE管脚了

   

  小结:nand的硬件协议就是各个管脚是怎样配合实现对nand的访问的,但是通过主控芯片的nand控制器访问时,有些步骤已经被合成了,驱动不用做那么多个步骤,比如nFWE,nFRE管脚驱动的体现就不是那么明显;

   

写一个Linux nand设备驱动,要参考\\10.150.50.230\zhangjiaqi\linux-5.8.5\Documentation\driver-api\mtdnand.rst


     nand driver(driver/mtd/nand/raw/s3c_nand.c)  ---> kernel's MTD subsystem  <--- nand device(nand闪存芯片)
     
 读状态正忙函数:
    return 0, if the device is busy (R/B pin is low);
    return 1, if the device is ready (R/B pin is high). 
    
    如果nand芯片并没有R/B接口:那么the function pointer this->legacy.dev_ready is set to NULL.
    
    nand_chip与mtd_info会互相绑定的;
    
    chip->options 要设置为空,空就代码为8bit设备,NAND_BUSWIDTH_16代表16bit模式
    !chip->legacy.cmdfunc//这个函数可能我们要提供,因为默认的是512小页的,而我们的设备是2048大页的;
    chip->legacy.select_chip//这个片选函数要实现
    chip->legacy.dev_ready(chip)//这个函数要提供
    chip->legacy.read_byte //这个函数不用提供,已经有现成的,只需提供chip->legacy.IO_ADDR_R就行
    chip->legacy.write_buf //这个函数不用提供,已经有现成的,只需提供chip->legacy.IO_ADDR_W就行
    chip->legacy.write_byte//这个函数不用提供,默认的能用,只需提供chip->legacy.IO_ADDR_W就行
    
    mtd->writesize这个要设置为512或者2048
    
    //设备树节点: 
    提供属性nand-bus-width=8,等于8或者16
    nand-is-boot-medium这个bool型属性不知要不要设置,如果是从nand启动系统就要提供这个属性。
    nand-on-flash-bbt:这个bool型属性可能要提供,如果是使用默认的坏块表就要提供这个属性。
    nand-ecc-mode="soft",这个要提供,表示chip->ecc.mode = NAND_ECC_SOFT;
    nand-ecc-algo="hamming",这个要提供。
    nand-ecc-strength=数值,这个要提供表示ecc的长度。不知道要不要提供
    nand-ecc-step-size=数值,不知道要不要提供
    nand-ecc-maximize,这个bool型的属性不知道要不要提供
    
    
        内核会根据标准的read id操作(发出0X90cmd,在发出0x00地址)得到真实的厂家ID存在maf_id变量里,再用maf_id与内核所支持的nand的id进行
    数值对比,内核所支持的nand都在linux-5.8.5\drivers\mtd\nand\raw\nand_ids.c,如果这个nand_ids.c里没有你的nand芯片,就要往这里添加
    你的nand芯片的信息结构体;例如    {NAND_MFR_SAMSUNG, "Samsung", &samsung_nand_manuf_ops},第一个变量NAND_MFR_SAMSUNG是个宏定义,表示读回来的厂家ID;   根据dev_id遍历内核所支持的所有nand的信息,得到一个nand的细节信息结构体(这个结构体含有nand的name,chip_size,),
    这个结构体在\\10.150.50.230\zhangjiaqi\linux-5.8.5\drivers\mtd\nand\raw\nand_ids.c,要往这个文件添加自己nand硬件的信息
    
    总的来说有两处要对比的都在\\10.150.50.230\zhangjiaqi\linux-5.8.5\drivers\mtd\nand\raw\nand_ids.c,这个文件含有两个结构体
    分别是struct nand_manufacturer,struct nand_flash_dev;这两个结构体共同决定了内核支持的某一款nand,这里面代表了linux内核所支持的
    所有nand芯片,里面有两个nand芯片结构体一个使用dev_id对比的,另一个是用Manufacturer_id对比的;
    
    
        /**
      *根据ONFI标准和JEDEC标准填充nand的内核规格信息例如:page_size,ease_size,oob_size
      *如果不符合上面两个标准则该款nand芯片无法使用Linux内核提供的这套标准的nand核心层协议
      *后面也无法成功注册mtd设备;如果是不符合上面两个标准的nand只能自己实现mtd->_write,
      *mtd->_read,mtd->_erase,然后手动注册mtd;
      */
     nand_scan
         nand_scan_with_ids
             nand_scan_ident
                 nand_detect
                     nand_onfi_detect    //检查是否符合ONFI标准
                     nand_jedec_detect   //检查是否符合JEDEC标准 
    
    
    
     /**
      *设置时序的函数
      * Configure the data interface in SDR mode and set the timings to timing mode 0.
      */
     nand_scan
         nand_scan_with_ids
             nand_scan_ident
                 nand_detect
                     nand_reset
                         chip->controller->ops->setup_data_interface(chip, chipnr,&chip->data_interface);//调用到设备驱动实现的函数
    
    
     /*
      * 可以设置硬件初始化相关的
      */
     nand_scan
         nand_scan_with_ids
             nand_attach
                  chip->controller->ops->attach_chip(chip);//调用到设备驱动实现的chip->controller->ops->attach_chip(chip)函数(挂接函数),一般与(卸载函数)chip->controller->ops->detach_chip(chip)配套使用
         
     
 总结及全面源码剖析:
     用内核的nand flash协议写nand flsah设备驱动,主要有三个点;
     1. struct nand_chip,
     2. nand_scan函数
     3. mtd_device_register函数
     
     
     构造nand_chip时并不是要填充这个结构体的所有函数的,要填充哪些函数完全要看nand_scan这个函数,下面进行详细分析;
     nand_scan(struct nand_chip *chip, unsigned int max_chips)//chip为设备驱动申请的nand_chip结构体,max_chips为多少个nand_chip芯片;
         nand_scan_with_ids
             nand_scan_ident//nand_scan第一阶段
                 nanddev_get_memorg(&chip->base);//获取chip->base->memorg,有关page_size,oob_size的信息,这个memorg会在下面补充填充
                 nand_dt_init(chip);//设备树要怎么写就要看这个函数
                 nand_set_defaults//这个函数非常核心;
                     nand_legacy_set_defaults//内核提供了一些可以通用的函数,如果设备驱动没有提供就用这些默认的,但是有三个函数是必须提供的chip->legacy.cmd_ctrl,chip->legacy.dev_ready(chip),chip->legacy.select_chip这三个是必须提供的。
                         if (!chip->legacy.cmdfunc)
                             chip->legacy.cmdfunc = nand_command;/*这个默认函数是适用于小页512byte的*/                        /* check, if a user supplied wait function given */
                         if (chip->legacy.waitfunc == NULL)
                             chip->legacy.waitfunc = nand_wait;/*调用设备驱动的chip->legacy.dev_ready(chip)等待nand操作完成*/                        if (!chip->legacy.select_chip)
                             chip->legacy.select_chip = nand_select_chip;/*这个默认的片选函数里面是空的,无法选中,要设备驱动提供*/                        //在个函数的后半段还会提供有关nand的读写一个byte,和读写buf的函数,要区分硬件的位宽是8还是16(位宽在设备树里指定,在nand_dt_init里提取填充);一般都通用,如果通用就在设备层自己写代替这里的;
                 nand_detect(chip, table);//设置时序,这函数非常核心
                     nand_reset(chip, 0);//设置时序之后复位
                         nand_reset_data_interface
                             chip->controller->ops->setup_data_interface//设备驱动要提供chip->controller,并且要在里面设置时序;
                 nand_readid_op//读设备ID
                 nand_get_manufacturer//根据读回来的厂家ID在nand_ids.c找到nand的形容结构体struct nand_manufacturer,要支持此nand,必须在文件中添加有关自己nand 芯片的信息;
                 type = nand_flash_ids;
                 if (dev_id == type->dev_id)//走这里,找到符合我们的硬件的nand细节信息结构体struct nand_flash_dev并且存在type里了
                 //检查芯片是否符合ONFI,根据硬件填充memorg
                 //检查芯片是否符合JEDEC标准,根据硬件填充memorg
                 nand_legacy_adjust_cmdfunc(chip);//这里非常核心,这里调整为大页nand的发命令函数
             nand_attach
                 chip->controller->ops->attach_chip(chip)//调用到设备驱动实现的函数attach_chip,当驱动与设备挂接上时会调用这个函数,在nand_cleanup会调用卸载函数chip->controller->ops->detach_chip(chip)
             nand_scan_tail(chip);//nand_scan第二阶段,构造mtd的操作函数
                 nand_manufacturer_init(chip);//如果drivers\mtd\nand\raw\nand_ids.c里记录的这款nand自带init函数就在这里调用,我们的nand内核已经支持,里面自带init,所以这里会调用,主要是如果这款芯片的page_size>512,就是对chip->option作大页的标记,以及slc判断;
                 mtd->erase = nand_erase;
                 mtd->point = NULL;
                 mtd->unpoint = NULL;    写nand flash的驱动一般都是通过nand的控制器去控制各项信号的收发的;
     所以可以用nand_chip.controller来做一些控制器的操作工作
     .attach_chip:nand芯片与设备驱动挂接上时要调用的函数;
     .detach_chip: nand芯片与设备驱动分离时要调用的函数,nand_cleanup里面会调用;
     .setup_data_interface: nand_scan的第一阶段调用:里面可以做nand控制器的时序设置已经pinctrl设置;
     
     一般写mtd设置都要实现mtd->_erase,或者mtd->_write,mtd->_read函数,但是如果是用nand_scan向内核注册nand设备,这三个函数不用自己提供;
     写nand设备驱动之前要确保内核是否支持这款nand,在nand_ids.c里面有其所支持的所有厂家的nand芯片;在nand_scan的第一阶段,要读nand芯片的
     厂家ID,设备ID,根据这两个ID扫描nand_ids.c里面的结构体看是否支持;    设备驱动特别要提供的三个函数:片选函数,判断正忙函数(不需要等待,只需要返回状态位),发命令和发地址函数(这个最核心),提供读写的IO地址;
     
     tCLS-tWP
     tRC,tWC    
     tCLH
     
     NFCONF,
         
 测试驱动:
     取消选中原厂的驱动
     -> Device Drivers                                                                                                                                         |
   |       -> Memory Technology Device (MTD) support (MTD [=y])                                                                                                    |
   | (1)     -> Raw/Parallel NAND Device Support (MTD_RAW_NAND [=y])         make menuconfig 取消宏CONFIG_MTD_NAND_S3C2410  
         
 内核挂接的根文件系统改为网络文件系统,因内核中去掉了原厂nand的驱动,已经无法从nand中挂接烧写在里面的驱动了;        
     挂接到网络文件系统之后;
     insmod jaky2440_nand.ko
     ls /dev/mtd*
     /dev/mtd0       /dev/mtd3       /dev/mtd6       /dev/mtdblock2
     /dev/mtd0ro     /dev/mtd3ro     /dev/mtd6ro     /dev/mtdblock3
     /dev/mtd1       /dev/mtd4       /dev/mtd7       /dev/mtdblock4
     /dev/mtd1ro     /dev/mtd4ro     /dev/mtd7ro     /dev/mtdblock5
     /dev/mtd2       /dev/mtd5       /dev/mtdblock0  /dev/mtdblock6
     /dev/mtd2ro     /dev/mtd5ro     /dev/mtdblock1  /dev/mtdblock7        其中/dev/mtd0~/dev/mtd7:为块设备对应的字符设备,用于烧写,擦除所用;
         /dev/mtdblock0~/dev/mtdblock7:为实际的块设备,用于挂接以及读写所用;    在将块设备挂接出来之前最好先用mtd-utils工具将其整个区擦除一遍;
     制作擦除工具:参考
     用arm-linux-gcc-4.3.2作为编译工具(其他的编译工具编译不了);
     tar xjf mtd-utils-05.07.23.tar.bz2 
     cd mtd-utils-05.07.23
     cd utils
     vi Makefile
     添加CROSS=arm-linux-
     make 生成flash_erase flash_eraseall
     cp flash_erase flash_eraseall yangqing@10.150.50.162:/home/share/zjq_162/
     
     
     擦除分区:
     ./flash_eraseall /dev/mtd6  (擦除是用字符设备)    
     
     挂接块设备出来:
     mount -t yaffs /dev/mtdblock6 /ready_folder  //以yaffs的格式将块设备挂接出来;
     cd /ready_folder
     vi test.txt
     读写文件操作;