• 《驱动学习


    1.本节使用的nand flash型号为K9F2G08U0M,它的命令如下

      

    1.1我们以上图的read id(读ID)为例,它的时序图如下:

    首先需要使能CE片选

    1)使能CLE

    2)发送0X90命令,并发出WE写脉冲(就是低电平变成高电平)

    3)复位CLE,然后使能ALE

    4)发送0X00地址,并发出WE写脉冲(就是低电平变成高电平)

    5)设CLE和ALE为低电平

    6)while判断nRE(读使能)是否为低电平

    7)读出8个I/O的数据,并发出RE上升沿脉冲

    (我们的nand flash为8个I/O口,所以型号为K9F2G08U0M)

    1.2 nand flash 控制器介绍

      在2440中有个nand flash 控制器,它会自动控制CLE,ALE那些控制引脚,我们只需要配置控制器,就可以直接写命令,写地址,读写数据到它的寄存器中便能完成(读写数据之前需要判断RnB脚),如下图所示:

    若在nand flash 控制器下,我们读ID就只需要如下几步(非常方便):

    1)将寄存器NFCONT(0x4E000004)的bit1=0,来使能片选

    2)写入寄存器NFCMMD(0x4E000008)=0X90,发送命令

    3)写入寄存器NFADDR(0x4E00000C)=0X00,发送地址

    4)while判断nRE(读使能)是否为低电平

    5)读取寄存器NFDATA(0x4E000010),来读取数据

    1.3 我们在uboot中测试,通过md和mw命令来实现读id(x要小写)

    如下图所示,最终读取出0XEC  0XDA  0X10  0X95

     刚好对应了我们nand flash手册里的数据(其中0XEC表示厂家ID, 0XDA表示设备ID):

    若我们要退出读ID命令时,只需要reset就行,同样地,要退出读数据/写数据时,也是reset。

    1.4 reset的命令为0xff,它的时序图如下所示:

     

     1.5 同样地,我们再参考读地址时序图来看看:

    其中column Address对应列地址,表示某页里的2k地址

    row Address对应行地址,表示具体的哪一页

    5个地址的周期的图,如下所示:

    因为我们的nand flash=256MB=(2k*128M)b

    所以row Address=128M=2^17(A27~A11)

    所以column Address=2k=2^11( A10~A0)

    2.NAND FLASH源码分析

    2.1 为什么nand在mtd目录下?

      因为mtd(memory technology device 存储 技术设备 ) 是用于访问 memory 设备( ROM 、 flash )的Linux 的子系统。 MTD 的主要目的是为了使新的 memory 设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。

    2.2首先来看s3c2410.c的入口函数:

    static int __init s3c2410_nand_init(void)
    {
           printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics
    ");
           platform_driver_register(&s3c2412_nand_driver);     
           platform_driver_register(&s3c2440_nand_driver);     
    
           return platform_driver_register(&s3c2410_nand_driver);
    }

      在入口函数中,注册了一个platform平台设备驱动,也是说当与nandflash设备匹配时,就会调用s3c2440_nand_driver ->probe来初始化。

      我们进入probe函数中,看看是如何初始化

    static int s3c24xx_nand_probe(struct platform_device *pdev, enum s3c_cpu_type cpu_type)
    {
    ... ...
    
    err = s3c2410_nand_inithw(info, pdev);       //初始化硬件hardware,设置TACLS 、TWRPH0、TWRPH1通信时序等
    
    s3c2410_nand_init_chip(info, nmtd, sets);    //初始化芯片
    
    nmtd->scan_res = nand_scan(&nmtd->mtd, (sets) ? sets->nr_chips : 1); //3.扫描nandflash
    ... ...
    s3c2410_nand_add_partition(info, nmtd, sets);         //4.调用add_mtd_partitions()来添加mtd分区
    ... ...
    }

       通过上面代码和注释,得出:驱动主要调用内核的nand_scan()函数,add_mtd_partitions()函数,来完成注册nandflash。

    3.上面probe()里的 nand_scan()扫描函数 位于/drivers/mtd/nand/nand_base.c   

      它会调用nand_scan()->nand_scan_ident()->nand_get_flash_type()来获取flash存储器的类型。

      以及nand_scan()->nand_scan_ident()->nand_scan_tail()来构造mtd设备的成员(实现对nandflash的读,写,擦除等)。

    3.1其中nand_get_flash_type()函数如下所示:

    static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,struct nand_chip *chip,int busw, int *maf_id)
    {
     struct nand_flash_dev *type = NULL;
     int i, dev_id, maf_idx;
     chip->select_chip(mtd, 0);     //调用nand_chip结构体的成员select_chip使能flash片选
    
     chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); //3.2调用nand_chip结构体的成员cmdfunc,发送读id命令,最后数据保存在mtd结构体里
    
     *maf_id = chip->read_byte(mtd); // 获取厂家ID,
    
      dev_id = chip->read_byte(mtd);   //获取设备ID
    
       /* 3.3for循环匹配nand_flash_ids[]数组,找到对应的nandflash信息*/
         for (i = 0; nand_flash_ids[i].name != NULL; i++) 
       {  
             if (dev_id == nand_flash_ids[i].id)     //匹配设备ID
             {type =  &nand_flash_ids[i];
               break;}
      }
           ... ...
    
    /* 3.4 匹配成功,便打印nandflash参数   */
    printk(KERN_INFO "NAND device: Manufacturer ID:"
                         " 0x%02x, Chip ID: 0x%02x (%s %s)
    ", *maf_id,
                         dev_id, nand_manuf_ids[maf_idx].name, mtd->name);  
           ... ...
    }

      从上面代码和注释得出, nand_chip结构体就是保存与硬件相关的函数(后面会讲这个结构体)

    3.2 其中NAND_CMD_READID定义为0x90,也就是发送0X90命令,和0x00地址来读id,最后放到mtd中

    3.3 nand_flash_ids[]数组是个全局变量,这里通过匹配设备ID,来确定我们的nand flash是个多大的存储器

      如下图所示,在芯片手册中,看到nand flash的设备ID=0XDA

     所以就匹配到nand_flash_ids[]里的0XDA:

     3.4 然后打印出nand flash参数,我们启动内核就可以看到:

     

    4. probe()里的s3c2410_nand_add_partition()函数主要是注册mtd设备的nand flash

    最终它调用了s3c2410_nand_add_partition()->add_mtd_partitions() -> add_mtd_device()

    其中add_mtd_partitions()函数主要实现多个分区创建,也就是多次调用add_mtd_device()

    当只设置nand_flash为一个分区时,就直接调用add_mtd_device()即可。

    4.1 add_mtd_partitions()函数原型如下:

    int add_mtd_partitions(struct mtd_info *master, const struct mtd_partition *parts,int nbparts);  //创建多个分区mtd设备
    //函 数 成 员 介 绍 : 
    //master:就是要创建的mtd设备
    //parts:分区信息的数组,它的结构体是mtd_partition,该结构体如下所示:
    /*
    struct mtd_partition {
           char *name;                  //分区名,比如bootloader、params、kernel、root
           u_int32_t size;               //分区大小
           u_int32_t offset;            //分区所在的偏移值
           u_int32_t mask_flags;            //掩码标志
           struct nand_ecclayout *ecclayout; //OOB布局
           struct mtd_info **mtdp;              //MTD的指针,不常用
    };
    */
    //nbparts:等于分区信息的数组个数,表示要创建分区的个数

    比如我们启动内核时,也能找到内核自带的nandflash的分区信息:

     4.2 其中add_mtd_device()函数如下所示:

    int add_mtd_device(struct mtd_info *mtd)    //创建一个mtd设备
    {
     struct list_head *this;
     ... ...
        list_for_each(this, &mtd_notifiers)     //4.3找mtd_notifiers链表里的list_head结构体
      {
       struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list); //通过list_head找到struct mtd_notifier *not
       not->add(mtd);            //最后调用mtd_notifier 的add()函数
      }
     ... ...
    }

    4.3 我们搜索上面函数里的mtd_notifiers链表

      看看里面的list_head结构体,在哪里放入的,就能找到执行的add()是什么了。

    4.4 如下图,发现list_head在register_mtd_user()里放到mtd_notifiers链表中

     4.5 继续搜索register_mtd_user(),被哪个调用

     

    如上图,找到被drivers/mtd/mtdchar.cdrivers/mtd/mtd_blkdevs.c调用(4.6节和4.7节会分析)

    是因为mtd层既提供了字符设备的操作接口(mtdchar.c), 也实现了块设备的操作接口(mtd_blkdevs.c)

    我们在控制台输入ls -l /dev/mtd*,也能找到块MTD设备节点和字符MTD设备节点,如下图所示:

    上图中,可以看到共创了4个分区的设备,每个分区都包含了两个字符设备(mtd%d,mtd%dro)、一个块设备(mtdblock0).

     其中MTD的块设备的主设备号为31,MTD的字符设备的主设备号为90 (后面会讲到在哪被创建)

    4.6 我们进入上面搜到的drivers/mtd/mtdchar.c, 找到它的入口函数是init_mtdchar():

    static int __init init_mtdchar(void)
    {
    
           /*创建字符设备mtd,主设备号为90 ,cat /proc/devices 可以看到 */
           if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) {
              printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.
    ",MTD_CHAR_MAJOR);
              return -EAGAIN;
           }
    
           mtd_class = class_create(THIS_MODULE, "mtd");              //创建类
    
           if (IS_ERR(mtd_class)) {
                  printk(KERN_ERR "Error creating mtd class.
    ");
                  unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
                  return PTR_ERR(mtd_class);
           }
    
           register_mtd_user(&notifier);              //调用register_mtd_user(),将notifier添加到mtd_notifiers链表中
    
           return 0;
    }

      之所以上面没有创建设备节点,是因为此时没有nand flash驱动。

    4.6.1发现上面的notifiers是 mtd_notifier结构体的:

     4.6.2 如上图,我们进入notifie的mtd_notify_add ()函数看看:

    static void mtd_notify_add(struct mtd_info* mtd)
    {
           if (!mtd)
                  return;
    
           /*其中MTD_CHAR_MAJOR主设备定义为90 */
           class_device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2),NULL, "mtd%d", mtd->index);
                                                            //创建mtd%d字符设备节点
    
           class_device_create(mtd_class, NULL,MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1),NULL, "mtd%dro", mtd->index);
                                       //创建mtd%dro字符设备节点
    
    }

      该函数创建了两个字符设备(mtd%d, mtd%dro ),其中ro的字符设备表示为只读。

    总结出:

    mtdchar.c的入口函数 将notifie添加到mtd_notifiers链表中,

    然后在add_mtd_device()函数中当查找到mtd字符设备的list_head时,就调用mtd_notifiers->add()来创建两个字符设备(mtd%d,mtd%dro)

    4.7 同样,我们也进入mtd_blkdevs.c (MTD块设备)中,找到注册到mtd_notifiers链表的是blktrans_notifier变量:

     4.7.1 然后进入blktrans_notifier变量的blktrans_notify_add ()函数:

    static void blktrans_notify_add(struct mtd_info *mtd)
    {
           struct list_head *this;
    
           if (mtd->type == MTD_ABSENT)
                  return;
     
           list_for_each(this, &blktrans_majors) //找blktrans_majors链表里的list_head结构体
        {
            struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);
            tr->add_mtd(tr, mtd);    // 执行mtd_blktrans_ops结构体的add_mtd()
           }
    }

    从上面的代码和注释得出:块设备的add()是查找blktrans_majors链表,然后执行mtd_blktrans_ops结构体的add_mtd()

    4.7.2 我们搜索blktrans_majors链表,看看mtd_blktrans_ops结构体在哪里添加进去的

    找到该链表在register_mtd_blktrans()函数中:

    int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
    {
           ... ...
    ret = register_blkdev(tr->major, tr->name);              //注册块设备
    tr->blkcore_priv->rq=blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);
                                                                                 //分配一个请求队列
    ... ...
           list_add(&tr->list, &blktrans_majors);                //将tr->list 添加到blktrans_majors链表
    }

    继续搜索register_mtd_blktrans(),如下图,找到被drivers/mtd/Mtdblock.c、Mtdblock_ro.c调用

     4.7.3 我们进入drivers/mtd/Mtdblock.c函数中,如下图所示:

    找到执行mtd_blktrans_ops结构体的add_mtd()函数,就是上图的mtdblock_add_mtd()函数

    在mtdblock_add_mtd()函数中最终会调用add_mtd_blktrans_dev()

    4.7.4 add_mtd_blktrans_dev()函数如下所示:

    int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
    {
           ... ...
           gd = alloc_disk(1 << tr->part_bits);                  //分配一个gendisk结构体
    
           gd->major = tr->major;                                //设置gendisk的主设备号
    
           gd->first_minor = (new->devnum) << tr->part_bits;      //设置gendisk的起始此设备号
    
           gd->fops = &mtd_blktrans_ops;                         //设置操作函数
           ... ...        
    
           gd->queue = tr->blkcore_priv->rq;           //设置请求队列
    
           add_disk(gd);                                           //向内核注册gendisk结构体
    }

    总结出:

    mtd_blkdevs()块设备的入口函数 将blktrans_notifier添加到mtd_notifiers链表中,并创建块设备,请求队列.

    然后在add_mtd_device()函数中,当查找到有blktrans_notifier时,就调用blktrans_notifier->add()来分配设置注册gendisk结构体

    5.显然在内核中,mtd已经帮我们做了整个框架,而我们的nand flash驱动只需要以下几步即可:

    1)设置mtd_info结构体成员

    2)设置nand_chip结构体成员

    3)设置硬件相关(设置nand控制器时序等)

    4)通过nand_scan()来扫描nandflash

    5)通过add_mtd_partitions()来添加分区,创建MTD字符/块设备

    5.1 mtd_info结构体介绍:

    主要是实现对nandflash的read()、write()、read_oob()、write_oob()、erase()等操作,属于软件的部分,它会通过它的成员priv来找到对应的nand_chip结构体,来调用与硬件相关的操作.

    5.2 nand_chip结构体介绍:

    它是mtd_info结构体的priv成员,主要是对MTD设备中的nandflash硬件相关的描述.

    当我们不设置nand_chip的成员时,以下的成员就会被mtd自动设为默认值,代码位于: nand_scan()->nand_scan_ident()->nand_set_defaults()

    struct nand_chip {
        void  __iomem      *IO_ADDR_R;         /* 需要读出数据的nandflash地址 */
        void  __iomem      *IO_ADDR_W;        /* 需要写入数据的nandflash地址 */ 
    
           /* 从芯片中读一个字节 */
           uint8_t    (*read_byte)(struct mtd_info *mtd);           
           /* 从芯片中读一个字 */
           u16         (*read_word)(struct mtd_info *mtd);         
           /* 将缓冲区内容写入nandflash地址, len:数据长度*/
           void (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len); 
           /* 读nandflash地址至缓冲区, len:数据长度   */
           void (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);
           /* 验证芯片和写入缓冲区中的数据 */
           int          (*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
        /* 选中芯片,当chip==0表示选中,chip==-1时表示取消选中 */
        void (*select_chip)(struct mtd_info *mtd, int chip);
           /* 检测是否有坏块 */
           int          (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);
    
           /* 标记坏块 */
           int          (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
    
        /* 命令、地址控制函数 ,  dat :要传输的命令/地址 */
        /*当ctrl的bit[1]==1: 表示要发送的dat是命令
                    bit[2]==1: 表示要发送的dat是地址
                    bit[0]==1:表示使能nand , ==0:表示禁止nand
            具体可以参考内核的nand_command_lp()函数,它会调用这个cmd_crtl函数实现功能*/
          void (*cmd_ctrl)(struct mtd_info *mtd, int dat,unsigned int ctrl);
    
        /* 设备是否就绪,当该函数返回的RnB引脚的数据等于1,表示nandflash已就绪 */
        int (*dev_ready)(struct mtd_info *mtd);
        /* 实现命令发送,最终调用nand_chip -> cmd_ctrl来实现  */
           void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);
           /*等待函数,通过nand_chip ->dev_ready来等待nandflash是否就绪 */
           int          (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);
           /* 擦除命令的处理 */
           void (*erase_cmd)(struct mtd_info *mtd, int page);
           /* 扫描坏块 */
           int          (*scan_bbt)(struct mtd_info *mtd);
           int          (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int status, int page);
           /* 写一页 */
           int          (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,const uint8_t *buf, int page, int cached, int raw);
    
           int          chip_delay;                   /* 由板决定的延迟时间 */
    
           /* 与具体的NAND芯片相关的一些选项,默认为8位宽nand,
         比如设置为NAND_BUSWIDTH_16,表示nand的总线宽为16 */
           unsigned int   options; 
    
    
           /* 用位表示的NAND芯片的page大小,如某片NAND芯片
            * 的一个page有512个字节,那么page_shift就是9
            */
           int          page_shift;
    
           /* 用位表示的NAND芯片的每次可擦除的大小,如某片NAND芯片每次可
            * 擦除16K字节(通常就是一个block的大小),那么phys_erase_shift就是14
            */
           int          phys_erase_shift;
    
           /* 用位表示的bad block table的大小,通常一个bbt占用一个block,
            * 所以bbt_erase_shift通常与phys_erase_shift相等
            */
           int          bbt_erase_shift;
           /* 用位表示的NAND芯片的容量 */
           int          chip_shift;
           /* NADN FLASH芯片的数量 */
           int          numchips;
           /* NAND芯片的大小 */
           uint64_t chipsize;
    
           int          pagemask;
           int          pagebuf;
           int          subpagesize;
           uint8_t    cellinfo;
           int          badblockpos;
           nand_state_t   state;
           uint8_t           *oob_poi;
           struct nand_hw_control  *controller;
           struct nand_ecclayout   *ecclayout;     /* ECC布局 */
    /* ECC校验结构体,若不设置, ecc.mode默认为NAND_ECC_NONE(无ECC校验) */
    /*可以为硬件ECC和软件ECC校验,比如:设置ecc.mode=NAND_ECC_SOFT(软件ECC校验)*/
        struct nand_ecc_ctrl ecc;      
           struct nand_buffers *buffers;
           struct nand_hw_control hwcontrol;
           struct mtd_oob_ops ops;
           uint8_t           *bbt;
           struct nand_bbt_descr   *bbt_td;
           struct nand_bbt_descr   *bbt_md;
           struct nand_bbt_descr   *badblock_pattern;
           void        *priv;
    };

    5.3本节驱动我们需要设置nand_chip的成员如下:

    IO_ADDR_R(提供读数据)

    IO_ADDR_W(提供写数据)

    select_chip(提供片选使能/禁止)

    cmd_ctrl(提供写命令/地址)

    dev_ready(提供nandflash的RnB脚,来判断是否就绪)

    ecc.mode(设置ECC为硬件校验/软件校验)

    其它成员会通过nand_scan()->nand_scan_ident()->nand_set_defaults()来设置为默认值.

     

     

     

     

     

  • 相关阅读:
    Java Web工作原理
    Java——入门“HelloWorld”
    Java——介绍
    vscode给 vue 项目 添加 eslint 验证提示遇到的细节问题
    使用全局变量,当多个线程同时修改静态属性第一季
    自编验证码图片识别程序 自定义的FloodFill函数
    nginx卸载ssl django明文
    nginx 7层全转发
    JAVA Socket入门详解(基本用法与代码实现)
    小白进入公司究竟有哪些不同?学生一定要看!
  • 原文地址:https://www.cnblogs.com/zhuangquan/p/11698986.html
Copyright © 2020-2023  润新知