设为首页 收藏本站
查看: 1284|回复: 0

[经验分享] 24.Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)

[复制链接]

尚未签到

发表于 2017-11-18 10:34:14 | 显示全部楼层 |阅读模式
1.本节使用的nand flash型号为K9F2G08U0M,它的命令如下:

   DSC0000.png
  1.1我们以上图的read id(读ID)为例,它的时序图如下:
   DSC0001.png
首先需要使能CE片选

1)使能CLE

2)发送0X90命令,并发出WE写脉冲

3)复位CLE,然后使能ALE

4)发送0X00地址,并发出WE写脉冲

5)设CLE和ALE为低电平

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

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

1.2 nand flash 控制器介绍

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

DSC0002.png

  若在nand flash 控制器下,我们读ID就只需要如下几步(非常方便):
  1)将寄存器NFCONT(0x4E000004)的bit1=0,来使能片选
  2)写入寄存器NFCMMD(0x4E000008)=0X90,发送命令
  3)写入寄存器NFADDR(0x4E00000C)=0X00,发送地址
  4)读取寄存器NFDATA(0x4E000010),来读取数据
  
  1.3 我们在uboot中测试,通过md和mw命令来实现读id(x要小写)
  如下图所示,最终读取出0XEC  0XDA  0X10  0X95
   DSC0003.png
  刚好对应了我们nand flash手册里的数据(其中0XEC表示厂家ID, 0XDA表示设备ID):
   DSC0004.png
  若我们要退出读ID命令时,只需要reset就行,同样地,要退出读数据/写数据时,也是reset
  1.4 reset的命令为0xff,它的时序图如下所示:
   DSC0005.png
  1.5 同样地,我们再参考读地址时序图来看看:
   DSC0006.png
  其中column Address对应列地址,表示某页里的2k地址
  row Address对应行地址,表示具体的哪一页
  5个地址的周期的图,如下所示:
   DSC0007.png
  因为我们的nand flash=256MB=(2k*128M)b
  所以row Address=128M=2^17(A27~A11)
  所以column Address=2k=2^11( A10~A0)
  
  2.接下来我们来参考自带的nand flash驱动,位于drivers/mtd/nand/s3c2410.c中
  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\n");
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.name != NULL; i++)
   {  
if (dev_id == nand_flash_ids.id)     //匹配设备ID
         {type =  &nand_flash_ids;
break;}
  }
... ...
/* 3.4 匹配成功,便打印nandflash参数   */
printk(KERN_INFO "NAND device: Manufacturer ID:"
" 0x%02x, Chip ID: 0x%02x (%s %s)\n", *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
   DSC0008.png
  所以就匹配到nand_flash_ids[]里的0XDA:
   DSC0009.png
  3.4 然后打印出nand flash参数,我们启动内核就可以看到:
   DSC00010.png
  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的分区信息:
   DSC00011.png
  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链表中
   DSC00012.png
  4.5 继续搜索register_mtd_user(),被哪个调用
   DSC00013.png
  如上图,找到被drivers/mtd/mtdchar.cdrivers/mtd/mtd_blkdevs.c调用(4.6节和4.7节会分析)
  是因为mtd层既提供了字符设备的操作接口(mtdchar.c), 也实现了块设备的操作接口(mtd_blkdevs.c)
  我们在控制台输入ls -l /dev/mtd*,也能找到块MTD设备节点和字符MTD设备节点,如下图所示:
   DSC00014.png
  上图中,可以看到共创了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.\n",MTD_CHAR_MAJOR);
return -EAGAIN;
}

mtd_class = class_create(THIS_MODULE, "mtd");              //创建类
if (IS_ERR(mtd_class)) {
printk(KERN_ERR "Error creating mtd class.\n");
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结构体的:
   DSC00015.png
  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变量:
   DSC00016.png
  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调用
   DSC00017.png
  4.7.3 我们进入drivers/mtd/Mtdblock.c函数中,如下图所示:
   DSC00018.png
  找到执行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=2表示要发送的dat是命令,否则就是dat就是地址*/
    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()来设置为默认值.
  6.接下来我们就来写nand flash块设备驱动
  参考:  drivers/mtd/nand/at91_nand.c
           drivers/mtd/nand/s3c2410.c
  6.1本节需要用到的函数如下所示:



int nand_scan(struct mtd_info *mtd, int maxchips);   //扫描nandflash,扫描成功返回0
int add_mtd_partitions(struct mtd_info *master,const struct mtd_partition *parts,int nbparts);
//将nandflash分成nbparts个分区,会创建多个MTD字符/块设备,成功返回0
//master:就是要创建的mtd设备
//parts:分区信息的数组,它的结构体是mtd_partition
//nbparts:要创建分区的个数,比如上图,那么就等于4
int del_mtd_partitions(struct mtd_info *master);
//卸载分区,并会卸载MTD字符/块设备
  6.2 在init入口函数中


  • 1)通过kzalloc()来分配结构体: mtd_info和nand_chip
  • 2)通过ioremap()来分配获取nand flash 寄存器虚拟地址
  • 3)设置mtd_info结构体成员
  • 4)设置nand_chip结构体成员
  • 5)设置硬件相关
  •      ->5.1) 通过clk_get()和clk_enable()来使能nand flash 时钟
  •      ->5.2)设置时序
  •      ->5.3)关闭片选,并开启nand flash 控制器
  • 6)通过nand_scan()来扫描nandflash
  • 7)通过add_mtd_partitions()来添加分区,创建MTD字符/块设备
  6.3 在exit入口函数中


  • 1)卸载分区,卸载字符/块设备
  • 2)释放mtd
  • 3)释放nand flash寄存器
  • 4)释放nand_chip
  驱动代码如下:



#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
#include <linux/mtd/partitions.h>
#include <asm/io.h>
#include <asm/arch/regs-nand.h>
#include <asm/arch/nand.h>
struct  mynand_regs {
unsigned long nfconf  ;             //0x4E000000
unsigned long nfcont  ;
unsigned long nfcmd   ;
unsigned long nfaddr  ;
unsigned long nfdata  ;
unsigned long nfeccd0 ;
unsigned long nfeccd1 ;
unsigned long nfeccd  ;
unsigned long nfstat  ;
unsigned long nfestat0;
unsigned long nfestat1;
unsigned long nfmecc0 ;
unsigned long nfmecc1 ;
unsigned long nfsecc  ;
unsigned long nfsblk  ;
unsigned long nfeblk  ;
};
static struct mynand_regs *my_regs;              //nand寄存器
static struct mtd_info *my_mtd;
static struct nand_chip *mynand_chip;      
static struct mtd_partition mynand_part[] = {
[0] = {
.name   = "bootloader",
.size   = 0x00040000,
.offset    = 0,
},
[1] = {
.name   = "params",
.offset = MTDPART_OFS_APPEND,
.size   = 0x00020000,
},
[2] = {
.name   = "kernel",
.offset = MTDPART_OFS_APPEND,
.size   = 0x00200000,
},
[3] = {
.name   = "root",
.offset = MTDPART_OFS_APPEND,
.size   = MTDPART_SIZ_FULL,
}
};
/*nand flash  :CE */
static void mynand_select_chip(struct mtd_info *mtd, int chipnr)
{
if(chipnr==-1)          //CE Disable
       {
my_regs->nfcont|=(0x01<<1);               //bit1置1
       }
else                         //CE Enable
       {
my_regs->nfcont&=~(0x01<<1);        //bit1置0  
}            
}
/*命令/地址控制函数 */
static void mynand__cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
if (ctrl & NAND_CLE)                              //当前为command状态 ,   
my_regs->nfcmd=dat;   
else                   //当前为地址状态 ,  if  (ctrl & NAND_ALE)   
my_regs->nfaddr=dat;
}
/* nand flash 设备就绪函数(获取RnB引脚状态 */
static int mynand__device_ready(struct mtd_info *mtd)
{
return (my_regs->nfstat&0x01);                //获取RnB状态,0:busy       1:ready
}


/*init入口函数*/
static int mynand_init(void)
{
struct clk *nand_clk;
int res;
/*1.分配结构体: mtd_info和nand_chip */
my_mtd=kzalloc(sizeof(struct mtd_info), GFP_KERNEL);      
mynand_chip=kzalloc(sizeof(struct nand_chip), GFP_KERNEL);
/*2.获取nand flash 寄存器虚拟地址*/
my_regs=ioremap(0x4E000000, sizeof(struct mynand_regs));
/*3.设置mtd_info*/
my_mtd->owner=THIS_MODULE;
my_mtd->priv=mynand_chip;                    //私有数据
/*4.设置nand_chip*/
mynand_chip->IO_ADDR_R=&my_regs->nfdata;                 //设置读data
mynand_chip->IO_ADDR_W=&my_regs->nfdata;                 //设置写data
mynand_chip->select_chip=mynand_select_chip;             //设置CE
mynand_chip->cmd_ctrl = mynand__cmd_ctrl;                //设置写command/address
mynand_chip->dev_ready = mynand__device_ready;           //设置RnB
mynand_chip->ecc.mode = NAND_ECC_SOFT;                   //设置软件ECC

/*5.设置硬件相关*/   
/*5.1使能nand flash 时钟*/
nand_clk=clk_get(NULL,"nand");
clk_enable(nand_clk);

/*5.2设置时序*/
#define TACLS       0                    //0nS
#define TWRPH0   1                       //15nS
#define TWRPH1   0                       //5nS
my_regs->nfconf = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);
/*5.3       bit1:关闭片选,       bit0:开启nand flash 控制器*/
my_regs->nfcont=(1<<1)|(1<<0);
/*6.扫描NAND*/
if (nand_scan(my_mtd, 1)) {                   // 1:表示只扫描一个nand flash 设备
res = -ENXIO;
goto out;
}
/*7.添加分区,创建字符/块设备*/
res = add_mtd_partitions(my_mtd, mynand_part, 4);
if(res)
    return 0;
out:
del_mtd_partitions(my_mtd);                      //卸载分区,卸载字符/块设备
kfree(my_mtd);                                      //释放mtd
iounmap(my_regs);                                //释放nand flash寄存器
kfree(mynand_chip);                             //释放nand_chip
return 0;
}
/*exit出口函数*/
static void mynand_exit(void)
{
del_mtd_partitions(my_mtd);     //卸载分区,卸载字符/块设备
kfree(my_mtd);                          //释放mtd
iounmap(my_regs);                    //释放nand flash寄存器
kfree(mynand_chip);                 //释放nand_chip
}
module_init(mynand_init);
module_exit(mynand_exit);
MODULE_LICENSE("GPL");
  7.编译启动内核
  7.1 重新设置编译内核(去掉默认的nand flash驱动)
  make menuconfig ,进入menu菜单重新设置内核参数:
  进入-> Device Drivers-> Memory Technology Device (MTD) support-> NAND Device Support



< >   NAND Flash support for S3C2410/S3C2440 SoC    //去掉默认的nandflash驱动
  然后make uImage 编译内核
  将新的nandflash驱动模块放入nfs文件系统目录中
  7.2然后烧写内核,启动内核
  如下图,发现内核启动时,卡住了,是因为我们使用的文件系统是存在nand flash上
   DSC00019.png
  所以设置为nfs文件系统才行.
  8.挂载nand flash 驱动
  8.1如下图,可以看到共添了4个分区: bootloader、params、kernel、root、
  刚好对应了程序中的mynand_part数组里面的分区信息
   DSC00020.png
  8.2 如下图,可以看到/dev下共创建了4个MTD块设备(mtdblock%d),4个MTD字符设备(mtd%d、mtd%dro)
   DSC00021.png
  8.3 如下图,使用cat /proc/partitions ,可以看到分区信息
   DSC00022.png
  其中blocks表示分区的容量,每个blocks是1KB
  
  9. 使用mount来挂载mtd块设备



mount /dev/mtdblock3        /mnt/             //挂载, mount会自动获取该设备的文件类型
  进入mnt,可以看到里面就是我们之前存在nand flash上的文件系统
   DSC00023.png
  10. 使用mtd-util 工具擦除mtdblock3(使用nand之前最好擦除一次)
  因为flash的特性如下:
  写入,只能把数据(bit)从1改为0;擦除,只能把所有数据(bit)从0改为1。

所以,要想写入数据之前必须先擦除。因为flash只能写0,写1时其实是保持原来的状态。
  10.1 使用mtd-util工具步骤如下:



tar -xjf mtd-utils-05.07.23.tar.bz2           //解压mtd-util工具
cd mtd-utils-05.07.23/util /                   //进入util目录
vi Makefile                                     //修改交叉编译改为: CROSS=arm-linux-
make                                          //编译,生成flashcp 、flash_erase、flash_eraseall等命令
cp  flash_erase  flash_eraseall  /nfs文件系统目录   //复制命令
  10.2mtd-util工具的常用命令介绍
  命令:flashcp
  作用: copy数据到 flash 中
  实例:  



./flashcp   fs.yaffs2  /dev/mtd0      //将文件系统yaffs2复制到mtd0中
  命令:flash_erase
  常用参数:  
  -j  使用jffs2来格式化分区
  -q  不打印过程信息
  作用:擦除某个分区的指定范围 (其中指定位置必须以0x20000(128K)为倍数)
  实例:



./flash_erase  /dev/mtd0 0x20000 5   //擦除mtd0从0x20000开始的5块数据 ,128K/块
  命令:flash_eraseall
  常用参数:
  -j  使用jffs2来格式化分区(对于norflash才加该参数)
  -q  不打印过程信息
  作用:擦除整个分区的内容
  实例:



./flash_eraseall  -q /dev/mtd0        //擦除mtd0,并不打印过程信息
  10.3为什么这里的实例都是对mtd字符设备进行操作,而不是mtdblock块设备?
  因为每个分区的字符设备,其实就是对应着每个分区块设备。即/dev/mtd3对应/dev/mtdblock3
  flash_eraseall, flash_erase那些命令是以ioctl等基础而实现, 而块设备不支持ioctl, 只有字符设备支持
  10.4 使用flash_eraseall来擦除分区3
  步骤如下:



umount /mnt                                             //擦除之前需要使用umount mnt来取消之前的挂载
./flash_eraseall  /dev/mtd3                             //擦除mtd3
mount -t yaffs /dev/mtdblock3 /mnt/                     //使用yaffs类型来挂载mtdblock3块设备
                                  //因为当前的mtdblock3为空,mount命令无法自动获取mtdblock3的文件类型
  如下图,可以看到分区3已经为空了
   DSC00024.png
  
  
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-408171-1-1.html 上篇帖子: 图文详解linux/windows mysql忘记root密码解决方案 下篇帖子: Linux传统Huge Pages与Transparent Huge Pages再次学习总结
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表