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驱动,位于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 : ); //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, ); //调用nand_chip结构体的成员select_chip使能flash片选 chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -); //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 = ; 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)\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

所以就匹配到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.c、drivers/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.\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 ;
}

之所以上面没有创建设备节点,是因为此时没有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*),NULL, "mtd%d", mtd->index);
//创建mtd%d字符设备节点 class_device_create(mtd_class, NULL,MKDEV(MTD_CHAR_MAJOR, mtd->index*+),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( << 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()来设置为默认值.

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[] = {
[] = {
.name = "bootloader",
.size = 0x00040000,
.offset = ,
},
[] = {
.name = "params",
.offset = MTDPART_OFS_APPEND,
.size = 0x00020000,
},
[] = {
.name = "kernel",
.offset = MTDPART_OFS_APPEND,
.size = 0x00200000,
},
[] = {
.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==-) //CE Disable
{
my_regs->nfcont|=(0x01<<); //bit1置1
}
else //CE Enable
{
my_regs->nfcont&=~(0x01<<); //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<<) | (TWRPH0<<) | (TWRPH1<<); /*5.3 bit1:关闭片选, bit0:开启nand flash 控制器*/
my_regs->nfcont=(<<)|(<<); /*6.扫描NAND*/
if (nand_scan(my_mtd, )) { // 1:表示只扫描一个nand flash 设备
res = -ENXIO;
goto out;
} /*7.添加分区,创建字符/块设备*/
res = add_mtd_partitions(my_mtd, mynand_part, );
if(res)
    return ; out:
del_mtd_partitions(my_mtd);      //卸载分区,卸载字符/块设备
kfree(my_mtd); //释放mtd
iounmap(my_regs); //释放nand flash寄存器
kfree(mynand_chip); //释放nand_chip
return ; } /*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上

所以设置为nfs文件系统才行.

8.挂载nand flash 驱动

8.1如下图,可以看到共添了4个分区: bootloader、params、kernel、root、

刚好对应了程序中的mynand_part数组里面的分区信息

8.2 如下图,可以看到/dev下共创建了4个MTD块设备(mtdblock%d),4个MTD字符设备(mtd%d、mtd%dro)

8.3 如下图,使用cat /proc/partitions ,可以看到分区信息

其中blocks表示分区的容量,每个blocks是1KB

9. 使用mount来挂载mtd块设备

mount /dev/mtdblock3        /mnt/             //挂载, mount会自动获取该设备的文件类型

进入mnt,可以看到里面就是我们之前存在nand flash上的文件系统

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..tar.bz2           //解压mtd-util工具
cd mtd-utils-05.07./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    //擦除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已经为空了

下章学习:  25.Linux-Nor Flash驱动(详解)

24.Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)的更多相关文章

  1. Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)

    1.本节使用的nand flash型号为K9F2G08U0M,它的命令如下: 1.1我们以上图的read id(读ID)为例,它的时序图如下: 首先需要使能CE片选 1)使能CLE 2)发送0X90命 ...

  2. linux nandflash驱动之MTD层

    MTD,Memory Technology Device即内存技术设备,在Linux内核中,引入MTD层为NOR FLASH和NAND FLASH设备提供统一接口.MTD将文件系统与底层FLASH存储 ...

  3. u-boot分析(九)----nand flash初始化|nand flash读写分析

    u-boot分析(九) 上篇博文我们按照210的启动流程,分析到了初始化串口,由于接下来的取消存储保护不是很重要,所以我们今天按照u-boot的启动流程对nand flash初始化进行分析. 今天我们 ...

  4. tiny4412 串口驱动分析一 --- u-boot中的串口驱动

    作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...

  5. Nand Flash驱动(实现初始化以及读操作)

    简单制作一个Nand Flash驱动(只需要初始化Flash以及读Flash) 打开2440芯片手册,K9F2G08U0M芯片手册(因为2440中Nand Flash是用的256MB(2Gb)内存,8 ...

  6. 如何编写linux下nand flash驱动-2

    [Nand Flash引脚(Pin)的说明] 图3.Nand Flash引脚功能说明 上图是常见的Nand Flash所拥有的引脚(Pin)所对应的功能,简单翻译如下: 1.       I/O0 ~ ...

  7. NAND FLASH 驱动分析

    NAND FLASH是一个存储芯片 那么: 这样的操作很合理"读地址A的数据,把数据B写到地址A" 问1. 原理图上NAND FLASH和S3C2440之间只有数据线,       ...

  8. Linux 下 Nand Flash 驱动说明

    注册 driver_register 通过 module_init(s3c2410_nand_init);注册 Nand Flash 驱动. 在 s3c2410_nand_init ()中通过 dri ...

  9. 如何编写linux下nand flash驱动-1

    1.       硬件特性: [Flash的硬件实现机制] Flash全名叫做Flash Memory,属于非易失性存储设备(Non-volatile Memory Device),与此相对应的是易失 ...

随机推荐

  1. wpf根据X与Y轴获取内部值

    效果图: 环境 Visual Studio ,.Net Framework 4.0 SDK 支持Windows XP,Windows Win7.. 实现方法: 创建ListBox设置ListBox.I ...

  2. HttpClient的帮助类

    /// <summary> /// http请求类 /// </summary> public class HttpHelper { private HttpClient _h ...

  3. Lerning Entity Framework 6 ------ Introduction to TPT

    Sometimes, you've created a table for example named Person. Just then, you want to add some extra in ...

  4. leetcode 152. 乘积最大子序列 java

    题目: 给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数). 示例 1: 输入: [2,3,-2,4] 输出: 6 解释: 子数组 [2,3] 有最大乘积 6. ...

  5. VS2017新建视图中文乱码解决办法

    问题:VS2017 ASP.NET Core 新建视频默认为ASNI编码格式 解决办法 1:直接将视图页面通过记事本打开,然后另存为UTF-8解决. 2:安装扩展TextTools解决视图文件编码问题 ...

  6. 【ElasticSearch】:QueryDSL

    Search API URI Search Response Body Search Query DSL Response Body Search使用Query DSL语句,相对URI Search功 ...

  7. 防止活动上线时 微信openid 被伪造的解决办法

    背景 前不久上线了一个 campaign 项目,一个 h5,后端为php,用户可以在微信中通过网页授权的方式登录,然后用微信 openid 作为唯一标识符进行签到和抽奖的操作. 结果后期出现了很多脏数 ...

  8. HuLu机器学习问题与解答系列(1-8)

    声明:本系列文章转载自微信公众号HULU,本人只是搬运工,仅供学习,如有不妥,后续告知删除. 嗨,欢迎回来,希望你能保持定期回顾的好习惯噢!下面是Hulu机器学习问题与解答系列的前8篇内容,点击主题名 ...

  9. 【Canal源码分析】重要类图

    从Canal的整体架构中,我们可以看出,在Canal中,比较重要的一些领域有Parser.Sink.Store.MetaManager.CanalServer.CanalInstance.CanalC ...

  10. Spring Boot 配置 IDEA&DevTools 自编译重启

    MAVEN 配置 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="ht ...