Linux nand设备驱动

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工具将其整个区擦除一遍;
    制作擦除工具:参考https://www.cnblogs.com/lifexy/p/7701181.html
    用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
    读写文件操作;
    
    
    
    

        
        
        
        
        
 

上一篇:仿真 vcs ncverilog


下一篇:论文阅读----Ten Lessons From Three Generations Shaped Google‘s TPU V4i