S3C2440 Nand Flash驱动(分析MTD层并制作NAND驱动)(二十三)

http://www.cnblogs.com/lifexy/p/7701181.html

1、本节使用的nand flash型号为K9F2G08U0C,它的命令如下:

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口)

 

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要小写)

刚好对用了我们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 Adress = 2k = 2^11(A10~A0)

 

1.7 我们现在读0地址的内容

使用命令 nand dump 0读出nand flash 0地址的内容

从这一节当中的第一张图,可以发现Read,先发出00h命令,再发出30h命令。

(md.b 后面的1表示读1次)

 

2、接下来我们来参考自带的nand flash,位于drivers/mtd/nand/s3c2410.c中

2.1 为什么nand在mtd目录下

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

2.2 首先来看s3c2440.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平台设备驱动,也是说当与nand flash设备匹配时,就会调用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);    //扫描nand flash
    ... ...
	s3c2410_nand_add_partition(info, nmtd, sets);    //来添加mtd分区
    ... ...
}

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

 

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_tail()来构造mtd设备的成员(实现对nand flash的读,写,擦除等)

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 匹配成功,便打印nand flash参数
	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)
//函数成员介绍:
//master:就是要创建的mtd设备
//parts:分区信息的数组,它的结构体是mtd_partition,该结构体如下所示:

/*
    struct mtd_partition {
	    char *name;			//分区名
	    u_int32_t size;		//分区大小
    	u_int32_t offset;	//分区所在的偏移值
    	u_int32_t mask_flags;	//掩码标志
    	struct nand_ecclayout *ecclayout;	//OOB布局
    	struct mtd_info **mtdp;		//MTD的指针,不常用
    };

}
*/

//nbparts:等于分区信息的数组个数,表示要创建分区的个数

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

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结构体
    {
        //通过list_head找到struct mtd_notifier *not
	    struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);
	    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%d ro)、一个块设备(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);//将notifier添加到mtd_notifiers链表中
	return 0;
}

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

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

4.6.2 如上图,我们进入notifiers是mtd_nofify_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_nitifiers链表是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_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结构体

	return 0;
}

总结出:

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

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

 

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

1)设置mtd_info结构体成员

2)设置nand_chip结构体成员

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

4)通过nand_scan()来扫描nand flash

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

5.1 mtd_info结构体介绍:

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

5.2nand_chip结构体介绍:

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

当我们不设置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(提供nand flash的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()来扫描nand flash

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

6.3 在exit入口函数中

1)卸载分区,卸载字符/块设备

2)释放mtd

3)释放nand flash寄存器

4)释放nand_chip

驱动代码如下:


/* 参考
 * drivers\mtd\nand\s3c2410.c
 * drivers\mtd\nand\at91_nand.c
 */
 
#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 s3c_nand_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 nand_chip *s3c_nand;
static struct mtd_info *s3c_mtd;
static struct s3c_nand_regs *s3c_nand_regs;//nand寄存器

//分区,在common-smdk.c里
static struct mtd_partition s3c_nand_part[] = {
	[0] = {
        .name   = "bootloader",//分区名
        .size   = 0x00040000,//分区大小
		.offset	= 0,//分区偏移值
	},
	[1] = {
        .name   = "params",
        .offset = MTDPART_OFS_APPEND,//APPEND紧跟上一个的分区
        .size   = 0x00020000,
	},
	[2] = {
        .name   = "kernel",
        .offset = MTDPART_OFS_APPEND,//APPEND紧跟上一个的分区
        .size   = 0x00200000,
	},
	[3] = {
        .name   = "root",
        .offset = MTDPART_OFS_APPEND,//APPEND紧跟上一个的分区
        .size   = MTDPART_SIZ_FULL,//剩下的所有空间
	}
};


//片选
static void s3c2440_select_chip(struct mtd_info *mtd, int chipnr)
{
	if (chipnr == -1)
	{
		/* 取消选中CE:NFCONT[1]设为1        */
		s3c_nand_regs->nfcont |= (1<<1);
	}
	else
	{
		/* 选中CE:NFCONT[1]设为0 */
		s3c_nand_regs->nfcont &= ~(1<<1);
	}
} 

//发命令或地址
static void s3c2440_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
	/* 使用ctrl分辨是命令还是地址,dat是数据值 */
	if (ctrl & NAND_CLE)
	{
		/* 发命令:NFCOMD=ctrl     */
		s3c_nand_regs->nfcmd = dat;
	}
	else
	{
		/* 发地址:NFADDR=ctrl     */
		s3c_nand_regs->nfaddr = dat;
	}
}

//判断状态
static int s3c2440_dev_ready(struct mtd_info *mtd)
{
	return (s3c_nand_regs->nfstat & (1<<0));
}


static int s3c_nand_init(void)
{
	struct clk *clk;

	/* 1.分配一个nand_chip结构体 */
	s3c_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);

	//从0x4E000000开始映射
	s3c_nand_regs = ioremap(0x4E000000, sizeof(struct s3c_nand_regs));

	/* 2.设置nand_chip结构体 */
	/* 设置nand_chip是给nand_scan函数使用的,如果不知道怎么设置,先看nand_scan怎么使用 
     * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态的功能
	 */
	 
	//默认的default sekect函数不适合我们使用,所以我们自己重新设置
	s3c_nand->select_chip = s3c2440_select_chip;	//选中
	s3c_nand->cmd_ctrl    = s3c2440_cmd_ctrl;		//发命令/地址
	s3c_nand->IO_ADDR_R = &s3c_nand_regs->nfdata;	//读数据
	s3c_nand->IO_ADDR_W = &s3c_nand_regs->nfdata;	//写数据
	s3c_nand->dev_ready = s3c2440_dev_ready;		//判断状态
	s3c_nand->ecc.mode = NAND_ECC_SOFT;	/* enable ECC *///设置软件ECC


	/* 3.硬件相关设置:根据NAND FLASH的手册设置时间参数 */	
	/* 使能NAND FLASH控制器的时钟,总开关 */
	clk = clk_get(NULL, "nand");
	clk_enable(clk);	/* CLKCON'bit[4] */
	
	/* HCLK=100MHz 
     * TACLS:发出CLE/ALE之后多长时间才发出nWE信号,从NAND手册可知CLE/ALE与nWE可以同时发出,所以TACLS=0
     * TWRPH0:nWE的脉冲宽度,HCLK x (TWRPH0 + 1),从NAND手册可知,它要>=12ns,所以TWRPH0>=1
     * TWRPH1:nWE变为高电平后多长时间CLE/ALE才能变为低电平,从NAND手册可知它要>=5ns,所以TWRPH1>=0
	 */
#define TACLS  0
#define TWRPH0 1
#define TWRPH1 0
	s3c_nand_regs->nfconf = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);

	/* NFCONT:
	 *bit1-设为1,取消片选 
	 *bit0-设为1,使能NAND FLASH控制器,开关
	 */
	s3c_nand_regs->nfcont = (1<<1) | (1<<0);

	/* 4.nand_scan扫描 */
	s3c_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
	s3c_mtd->owner = THIS_MODULE;
	s3c_mtd->priv = s3c_nand;//私有数据指向nand_chip
	
	nand_scan(s3c_mtd, 1);/* 识别NAND FLASH,构造mtd_info(mtd_info有读/写/擦除的函数) */
	
	/* 5.add_mtd_partitions 增加mtd分区 */
	add_mtd_partitions(s3c_mtd, s3c_nand_part, 4);

	//如果想把整个flash,只有一个分区
	//add_mtd_device(s3c_mtd);
	return 0;
}

static void s3c_nand_exit(void)
{
	del_mtd_partitions(s3c_mtd);
	kfree(s3c_mtd);
	iounmap(s3c_nand_regs);	
	kfree(s3c_nand);
}

module_init(s3c_nand_init);
module_exit(s3c_nand_exit);

MODULE_LICENSE("GPL");


 

7、编译启动内核

7.1 重新设置编译内核(去掉默认的nand flash驱动)

make menuconfig,进入menu菜单重新设置内核参数,去掉内核自带的NAND  FLASH驱动:

进入->Device Drivers->Memory Technology Device (MTD)support->NAND Device Support

< >   NAND Flash support for S3C2410/S3C2440 SoC

7.2 然后 make uImage 编译内核

7.3 将新的nand flash驱动模块(s3c_nand.ko)和在arch/arm/boot/uImage(uImage)拷贝到nfs文件系统目录中(/work/nfsroot/first_fs)

cp arch/arm/boot/uImage /work/nfsroot/uImage_nonand

cp s3c_nand.ko /work/nfsroot/first_fs

PS:因为之前的根文件系统在Nand Flash上,现在把NAND FLASH驱动去掉了,所以使用NFS(网络文件系统 first_fs)作为根文件系统。

7.4 先进入uboot,设置uboot启动参数(参考  Documentation/nfsroot.txt,它会告诉你怎样设置启动参数)

nfsroot=[<server-ip>:](服务器ip)<root-dir>(根文件目录)[,<nfs-options>]

ip=<client-ip>(客户端ip):<server-ip>(服务器ip):<gw-ip>(网关):<netmask>(子网掩码):<hostname>(主机名):<device>(设备):<autoconf>(自动配置)


set bootargs console=ttySAC0 root=/dev/nfs nfsroot=192.168.1.3:/work/nfsroot/first_fs ip=192.168.1.17:192.168.1.3:192.168.1.255:255.255.255.0::eth0:off  (设置uboot启动参数)

save  (保存)


nfs 30000000 192.168.1.3:/work/nfsroot/uImage_nonand (装载新内核)


bootm 30000000  (启动)

 

8、挂载nand flash 驱动:insmod s3c_nand.ko

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

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

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

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

其中blocks表示分区的容量。

 

9、使用mount来挂载mtd块设备:mount /dev/mtdblock3 /mnt

 

10、mtd-util格式化(擦除),使用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  /work/nfsroot/first_fs/bin   //复制命令

10.2 mtd_util工具的常用命令介绍

命令:flashcp

作用:copy数据到flash中

实例:

./flashcp fs.yaffs2 /dev/mtd0    //将文件系统yaffs2复制到mtd0中

 

命令:flash_erase

常用参数:

-j    使用jiffs2来格式化分区

-q    不打印过程信息

作用:擦除某个分区的指定范围(其中指定位置必须以0x20000(128K)为倍数)

实例:

./ flash erase    /dev/mtd0 0x20000 5    //擦除mtd0从0x20000开始的5块数据,128K/块

 

命令:flash_eraseall

常用参数:
-j    使用jiffs2来格式化分区(对于nor flash才加该参数)

-q    不打印过程信息

作用:擦除整个分区的内容

实例:

./flash_eraseall -q /dev/mtd0    //擦除mtd0,并不打印过程信息

10.3 为什么这里的实例都是对mtd字符设备进行操作,而不是mtdblock块设备?

因为每个分区的字符设备,其实就是对应着每个分区设备。即/dev/mtd3对应/dev/mtdclock

flash_eraseall,flash_erase那些命令是以ioctl等基础而实现,而块设备不支持ioctl,只有字符设备支持

10.4 使用flash_eraseall来擦除分区3mtdblock3

umount /mnt                                             //擦除之前需要使用umount mnt来取消之前的挂载
./flash_eraseall  /dev/mtd3                             //擦除mtd3
mount -t yaffs /dev/mtdblock3 /mnt/                     //使用yaffs类型来挂载mtdblock3块设备
                                  //因为当前的mtdblock3为空,mount命令无法自动获取mtdblock3的文件类型

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的SPI NAND Flash驱动代码的示例,基于Linux内核的MTD框架: ``` #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> #include <linux/spi/spi.h> #define SPI_NAND_CMD_READ 0x03 #define SPI_NAND_CMD_READID 0x9F #define SPI_NAND_CMD_RESET 0xFF #define SPI_NAND_PAGE_SIZE 2048 #define SPI_NAND_BLOCK_SIZE (64 * 1024) #define SPI_NAND_CHIP_SIZE (1024 * 1024 * 8) struct spi_nand_chip { struct mtd_info mtd; struct spi_device *spi; u8 *buf; }; static int spi_nand_read_buf(struct spi_nand_chip *chip, u32 addr, u8 *buf, u32 len) { u8 cmd[4]; int ret; cmd[0] = SPI_NAND_CMD_READ; cmd[1] = addr >> 16; cmd[2] = addr >> 8; cmd[3] = addr; ret = spi_write_then_read(chip->spi, cmd, sizeof(cmd), buf, len); if (ret < 0) { dev_err(&chip->spi->dev, "SPI NAND read error: %d\n", ret); return ret; } return 0; } static int spi_nand_read_id(struct spi_nand_chip *chip) { u8 cmd = SPI_NAND_CMD_READID; u8 id[5]; int ret; ret = spi_write_then_read(chip->spi, &cmd, sizeof(cmd), id, sizeof(id)); if (ret < 0) { dev_err(&chip->spi->dev, "SPI NAND read ID error: %d\n", ret); return ret; } dev_info(&chip->spi->dev, "SPI NAND ID: %02x %02x %02x %02x %02x\n", id[0], id[1], id[2], id[3], id[4]); return 0; } static int spi_nand_probe(struct spi_device *spi) { struct spi_nand_chip *chip; struct mtd_info *mtd; int ret; chip = devm_kzalloc(&spi->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip->buf = devm_kmalloc(&spi->dev, SPI_NAND_PAGE_SIZE, GFP_KERNEL); if (!chip->buf) return -ENOMEM; mtd = &chip->mtd; mtd->name = "spi-nand"; mtd->type = MTD_NANDFLASH; mtd->flags = MTD_CAP_NANDFLASH; mtd->writesize = SPI_NAND_PAGE_SIZE; mtd->erasesize = SPI_NAND_BLOCK_SIZE; mtd->size = SPI_NAND_CHIP_SIZE; mtd->_erase = nand_erase; mtd->_read = nand_read; ret = spi_setup(spi); if (ret) return ret; chip->spi = spi; ret = spi_nand_read_id(chip); if (ret) return ret; return mtd_device_register(mtd, NULL, 0); } static int spi_nand_remove(struct spi_device *spi) { struct mtd_info *mtd = spi_get_drvdata(spi); mtd_device_unregister(m

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值