十七、Linux驱动之nand flash驱动

1. 基本概念

    Nand-flash存储器是flash存储器的一种,其内部采用非线性宏单元模式,为固态大容量内存的实现提供了廉价有效的解决方案。Nand-flash存储器具有容量较大,改写速度快等优点,适用于大量数据的存储,因而在业界得到了越来越广泛的应用,如嵌入式产品中包括数码相机、MP3随身听记忆卡、体积小巧的U盘等。

2. 硬件分析

    本人使用的是韦东山老师的JZ2440开发板,CPUS3C2440A,上面所使用的NAND FLASH芯片为K9F2G08U0C

2.1 启动参数(通过硬件连接配置CPU)

    在S3C2440A芯片手册第6章NAND FLASH CONTORLLER中提到引脚配置相关内容:
十七、Linux驱动之nand flash驱动_第1张图片
    查看开发板上的nand flashK9F2G08U0C芯片手册有如下信息:
    十七、Linux驱动之nand flash驱动_第2张图片
    十七、Linux驱动之nand flash驱动_第3张图片

    可以得知K9F2G08U0C的信息:
        1. Page Size为2KByte
        2. 8bit bus width
        2. 5 address cycles
    所以对于我们的S3C2440A芯片应该设置引脚NCON为1(即接上拉电阻)引脚GPG13为1(即接上拉电阻)引脚GPG14为1(即接上拉电阻),引脚GPG15为0(即接上下拉电阻)。这些只需要硬件上进行配置即可。原理图连接如下:

    十七、Linux驱动之nand flash驱动_第4张图片
    十七、Linux驱动之nand flash驱动_第5张图片

2.2 芯片连接

    K9F2G08U0C芯片与CPU的连接如图:
    十七、Linux驱动之nand flash驱动_第6张图片
    十七、Linux驱动之nand flash驱动_第7张图片

2.2.1 R/B(忙等待信号)

    在K9F2G08U0C芯片手册中有如下定义:

   
    该引脚指示程序、擦除或随机读取操作正在进行中,完成后返回到高状态。连接到S3C2440RnB引脚,可以通过判断S3C2440NFSTAT寄存器的bit0位,为0表示该NAND Flash正忙,此时不可操作。
   
   

2.2.2 CLE (命令锁存使能)

    高电平有效。

2.2.3 ALE (地址锁存使能)

    高电平有效。

2.2.4 CE (选中使能)

    连接到S3C2440nFCE引脚,可以通过设置S3C2440NFCNT寄存器的bit1位,为0表示选中该NAND Flash芯片,为1表示取消选中。

   
   

2.2.5 WE (写使能)

   在信号脉冲上升沿进行命令、地址和数据的写。

2.2.6 RE (读使能)

   在信号脉冲上升沿进行读出8个i/o数据。

2.3 如何操作NAND Flash

2.3.1 不使用S3C2440的NAND FLASH控制器读ID

    以读ID为例,时序图如下:

    十七、Linux驱动之nand flash驱动_第8张图片

    当不使用S3C2440ANAND FLASH控制器来操作NAND FLASH时,我们需要执行以下步骤来读ID
      1. 使能CE片选
      2. 使能CLE
      3. 发送0X90命令,并发出WE写脉冲
      4. 复位CLE,然后使能ALE
      5. 发送0X00地址,并发出WE写脉冲
      6. 设CLE和ALE为低电平
      7. while判断nRE(读使能)是否为低电平
      8. 读出8个I/O的数据,并发出RE上升沿脉冲

2.3.2 使用S3C2440A的NAND FLASH控制器读ID

    S3C2440A中有个NAND FLASH控制器,它会自动控制CLEALE那些控制引脚,我们只需要配置控制器,就可以直接写命令,写地址,读写数据到它的寄存器中便能完成(读写数据之前需要判断RnB脚),如下图所示:
十七、Linux驱动之nand flash驱动_第9张图片

    当使用NAND FLASH控制器时,我们读ID就只需要如下几步:
      1. 设置寄存器NFCONT(0x4E000004)的bit1=0,使能片选
      2. 写入寄存器NFCMMD(0x4E000008)=0X90,发送命令
      3. 写入寄存器NFADDR(0x4E00000C)=0X00,发送地址
      4. while判断nRE(读使能)是否为低电平
      5. 读取寄存器NFDATA(0x4E000010),来读取数据
    在开发板UBOOT操作如下:

    十七、Linux驱动之nand flash驱动_第10张图片
   

    可以看到读出正确的厂家ID和设备ID了。退出读ID、读数据或写数据都执行“mw.b 0x4e000008 0xff”即可。

2.3.3 读操作

    读操作的时序如下:

十七、Linux驱动之nand flash驱动_第11张图片

    K9F2G08U0C芯片实际可用容量为256M字节=128k*2k字节,页大小为2k字节(对应column Address),总共有128k(对应row Address)页,所以使用8个i/o发地址时需要发以下方式的地址(发5个地址周期)
十七、Linux驱动之nand flash驱动_第12张图片

    所以row Address=128k=2^17(A27~A11),所以column Address=2k=2^11( A10~A0)。

3. 分析内核

    接下来分析内核(linux-2.6.22.6)是如何使用NAND Flash的。内核自带的驱动在drivers/mtd/nand/s3c2410.c。
    该驱动使用platform平台设备驱动构造,当与设备匹配后会调用到platform_driver->probes3c2412_nand_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); //扫描nandflash
	... ...
	s3c2410_nand_add_partition(info, nmtd, sets);	//调用add_mtd_partitions()来添加mtd分区
	... ...
}

3.1 nand_scan()函数

    其中nand_scan()函数调用如下:
    nand_scan()      //扫描nandflash
       
nand_set_defaults()            //设置nand_chip默认函数
        nand_scan_ident()   
            nand_get_flash_type()   
//获取flash存储器的类型
            nand_scan_tail()              //构造mtd设备的成员(实现对nandflash的读,写,擦除等)   

3.1.1 nand_get_flash_type()函数

    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); //调用nand_chip结构体的成员cmdfunc,发送读id命令,最后数据保存在mtd结构体里

	 *maf_id = chip->read_byte(mtd); // 获取厂家ID,

	  dev_id = chip->read_byte(mtd);   //获取设备ID

	   /*for循环匹配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;
		}
	}
	... ...

	/*匹配成功,便打印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结构体就是保存与硬件操作相关的函数(重点)。与nand_flash_ids[]数组进行ID匹配,数组有如下红框部分:
十七、Linux驱动之nand flash驱动_第13张图片   
    正好有设备ID 0xDA,所以启动内核时的打印信息如下:  

3.1.2 nand_scan_tail()函数

    nand_scan_tail()函数可以看出,全都是对struct nand_chip的成员的补充(nand_set_defaults() 里填充了一部分操作函数)struct nand_chip结构如下:

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;
};

3.2 s3c2410_nand_add_partition()函数

    s3c2410_nand_add_partition()函数代码如下:

static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info,
				      struct s3c2410_nand_mtd *mtd,
				      struct s3c2410_nand_set *set)
{
	if (set == NULL)
		return add_mtd_device(&mtd->mtd);    //创建1个分区mtd设备

	if (set->nr_partitions > 0 && set->partitions != NULL) {
		return add_mtd_partitions(&mtd->mtd, set->partitions, set->nr_partitions);    //创建多个分区mtd设备
	}

	return add_mtd_device(&mtd->mtd);
}

    其中add_mtd_partitions()函数主要实现多个分区(如常见的4个分区方式:bootloader、params、kernel、root)创建,也就是多次调用add_mtd_device(),当只设置nand_flash为一个分区时,就直接调用add_mtd_device()即可。

3.2.1 add_mtd_partitions()函数

    add_mtd_partitions()函数中有如下代码:

/***************************************************
 * master:要创建的mtd设备
 * parts:分区信息的数组,它的结构体是mtd_partition
 * nbparts:要创建的分区个数
 **************************************************/
int add_mtd_partitions(struct mtd_info *master,
		       const struct mtd_partition *parts,
		       int nbparts)
{
    ... ... 
    printk (KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);    //打印信息
    for (i = 0; i < nbparts; i++)    //循环次数为要创建的分区个数
    {
        ... ...

        printk (KERN_NOTICE "0x%08x-0x%08x : \"%s\"\n", slave->offset,
        slave->offset + slave->mtd.size, slave->mtd.name);    //打印信息
        
        ... ...
        add_mtd_device(&slave->mtd);    //创建一个分区
    }
}

    上面打印信息在内核启动时可以看到(修改过数组参数的内核)
       
    对于s3c2410.c这个内核自带的nand flash驱动文件其中的打印参数实际使用到了下面的数组(位于arch/arm/plat-s3c24xx/common-smdk.c中,修改内核源码过后的分区

static struct mtd_partition smdk_default_nand_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,
	}
};

    接下来我们看创建一个分区 add_mtd_device()函数是怎么操作的。

3.2.2 add_mtd_device()函数

    add_mtd_device()函数部分代码如下:

int add_mtd_device(struct mtd_info *mtd)    //创建一个mtd设备
{
	struct list_head *this;
	... ...
        for (i=0; i < MAX_MTD_DEVICES; i++)
        {
            ... ...
            mtd_table[i] = mtd;    //将这个mtd_info填充到全局数组mtd_table里
            ... ...
	    list_for_each(this, &mtd_notifiers)     //遍历mtd_notifiers链表
	    {
		struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list); //通过list_head找到struct mtd_notifier *not
		not->add(mtd);    //最后调用mtd_notifier的add()函数,里面进行创建设备节点
	    }
        }
	... ...
}

    首先将这个mtd_info结构体指针存到到全局数组mtd_table里,接着遍历mtd_notifiers链表,调用链表里的每一个mtd_notifier结构的成员add()函数。
    查找“mtd_notifiers”定位到mtd_notifiers链表是在register_mtd_user()里添加的:

实际就是在该函数里创建设备节点(创建字符设备或或块设备、创建类操作在其他驱动模块里进行的),后面会分析到。

void register_mtd_user (struct mtd_notifier *new)
{
    ... ...
    list_add(&new->list, &mtd_notifiers);
    ... ...
    	for (i=0; i< MAX_MTD_DEVICES; i++)    //循环调用mtd_notifier->add函数
            if (mtd_table[i])    
                new->add(mtd_table[i]);
}

    查找谁调用了register_mtd_user()函数
    十七、Linux驱动之nand flash驱动_第14张图片

    如上图,找到被drivers/mtd/mtdchar.c、drivers/mtd/mtd_blkdevs.c调用。这里有一个巧妙的运用,无论是mtdchar.cmtd_blkdevs.c(调用了register_mtd_user)先被加载还是s3c2410.c(调用了add_mtd_device)先被加载,后来加载的驱动文件都会调用到mtd_notifier->add()函数。
    到这里整理一下思路:
      1. mtdchar.c、mtd_blkdevs.c两个文件调用register_mtd_user()函数为mtd_notifiers链表添加结构
      2. 驱动程序s3c2410.c调用add_mtd_device()函数遍历mtd_notifiers链表里的每一个结构的add()函数
    所以mtd_notifiers链表里有两个结构,如果驱动程序s3c2410.c创建4个分区就会调用4次add_mtd_device()函数遍历mtd_notifiers链表,也就是会调用8次add()函数(两种,一种是mtdchar.c里添加的add函数,一种是mtd_blkdevs.c里添加的add函数),我们先来看结果,在开发板上执行“ls -l /dev/mtd*”如下:
    十七、Linux驱动之nand flash驱动_第15张图片

    可以看到总共有4个分区,对于每个分区分别创建了两个字符设备节点和一个块设备节点,因为mtd层既提供了字符设备的操作接口(mtdchar.c), 也实现了块设备的操作接口(mtd_blkdevs.c)。接下来看是如何创建这些设备的。

3.2.2.1 创建字符设备

    创建字符设备源头在mtdchar.c入口函数中

static int __init init_mtdchar(void)
{
	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(¬ifier);    
	return 0;
}

    当add_mtd_device()时遍历链表,调用链表结构之一not->add(mtd)指向的add()函数是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的字符设备表示为只读。

3.2.2.2 创建块设备

    创建块设备源头在mtdblock.c入口函数中:

/*drivers/mtd/mtdblock.c文件中*/
static int __init init_mtdblock(void)
{
    return register_mtd_blktrans(&mtdblock_tr);
}

/*drivers/mtd/mtd_blkdevs.c文件中*/
int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
{
    ... ...
    register_mtd_user(&blktrans_notifier);
    ... ...
    tr->blkcore_priv = kzalloc(sizeof(*tr->blkcore_priv), GFP_KERNEL);    //分配
    ... ...
    ret = register_blkdev(tr->major, tr->name);    //这里创建块设备
    ... ...
    tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);    //初始化队列
}

    当add_mtd_device()时遍历链表,调用链表结构之一not->add(mtd)指向的add()函数是blktrans_notify_add(),如下:

static struct mtd_notifier blktrans_notifier = {
    .add = blktrans_notify_add,
    .remove = blktrans_notify_remove,
};

    blktrans_notify_add函数又有如下调用:
    blktrans_notify_add
        tr->add_mtd
(指向mtdblock_add_mtd)
            mtdblock_add_mtd
                add_mtd_blktrans_dev
   
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结构体
}

3.3 总结框架

    显然在内核中,mtd已经帮我们做了整个框架,而我们的nand flash驱动只需要以下几步即可:  
1. 设置mtd_info结构体成员
    主要是实现对nandflashread()、write()、read_oob()、write_oob()、erase()等操作,属于软件的部分,它会通过它的成员priv来找到对应的nand_chip结构体,来调用与硬件相关的操作。
2. 设置nand_chip结构体成员
    它是mtd_info结构体的priv成员,主要是对MTD设备中的nandflash硬件相关的描述。当我们不设置nand_chip的成员时,以下的成员就会被mtd自动设为默认值,代码位于:nand_scan()->nand_scan_ident()->nand_set_defaults()。
   
我们需要设置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()来设置为默认值。
3. 设置硬件相关(设置nand控制器时序等)
4 通过nand_scan()来扫描nandflash
5 通过add_mtd_partitions()来添加分区,创建MTD字符/块设备

4. 编写代码

    驱动程序s3c_nand.c完整代码如下:

/* 参考 
 * drivers\mtd\nand\s3c2410.c
 * drivers\mtd\nand\at91_nand.c
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

struct s3c_nand_regs {
	unsigned long nfconf  ;
	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;

static struct mtd_partition s3c_nand_parts[] = {
	[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,
	}
};

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

static void s3c2440_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
	if (ctrl & NAND_CLE)
	{
		/* 发命令: NFCMMD=dat */
		s3c_nand_regs->nfcmd = dat;
	}
	else
	{
		/* 发地址: NFADDR=dat */
		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);

	s3c_nand_regs = ioremap(0x4E000000, sizeof(struct s3c_nand_regs));
	
	/* 2. 设置nand_chip */
	/* 设置nand_chip是给nand_scan函数使用的, 如果不知道怎么设置, 先看nand_scan怎么使用 
	 * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态的功能
	 */
	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;
	
	/* 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_scan(s3c_mtd, 1);  /* 识别NAND FLASH, 构造mtd_info */
	
	/* 5. 创建4个分区 */
	add_mtd_partitions(s3c_mtd, s3c_nand_parts, 4);
	
	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");

Makefile代码如下:

KERN_DIR = /work/system/linux-2.6.22.6    //内核目录

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= s3c_nand.o

5. 测试

内核:linux-2.6.22.6
编译器:arm-linux-gcc-3.4.5
环境:ubuntu9.10

5.1 配置内核

    (必须设置为NFS自动挂载文件系统启动,从网络文件系统下载去掉nandflash的内核 到内存并启动)
    1. 重新配置内核,去掉内核自带的usbmouse鼠标驱动。在linux-2.6.22.6内核目录下执行:
      make menuconfig
    2. 配置步骤如下:
      Device Drivers  --->
          <*> Memory Technology Device (MTD) support  --->
              <*>   NAND Device Support  --->
                  < >   NAND Flash support for S3C2410/S3C2440 SoC

5.2 重烧内核网络下载启动

    1. 编译内核与模块
      make uImage
    2. linux-2.6.22.6/arch/arm/boot下的uImage烧写到开发板
      cp /work/system/linux-2.6.22.6/arch/arm/boot/uImage /work/nfs_root/first_fs
 
   3. 在开发板上通过网络文件系统下载新内核到内存,并启动
      nfs 0x30000000 192.168.1.13:/work/nfs_root/first_fs/uImage
      bootm 30000000

5.3 测试

    查看设备执行如下:
      ls /dev/mtd*
   

    1. 首先编译自己写的nand flash驱动。在ubuntu驱动文件目录下执行:
      make
    2. 在开发板驱动文件目录下执行:
      insmod s3c_nand.ko
   
输出如下:
    十七、Linux驱动之nand flash驱动_第16张图片

    3. 查看设备
      ls /dev/mtd*
   

    4. 挂载分区
      mount dev/mtdblock3 /mnt    (挂载分区4到/mnt,假如nandflash里面没有内容,应该先进行格式化才能挂载成功)
    5. 格式化
      5.1 安装zlib
          下载zlib-1.2.3.tar.gzubuntu进入该文件目录,执行以下命令:
              tar xvf zlib-1.2.3.tar.gz
              cd zlib-1.2.3
              ./configure --shared --prefix=/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux

          修改生成的Makefile,修改以下几项,都加上了“arm-linux-”前缀:
              CC=arm-linux-gcc
              LDSHARED=arm-linux-gcc -shared -Wl,-soname,libz.so.1
              CPP=arm-linux-gcc -E
              AR=arm-linux-ar rc
              RANLIB=arm-linux-ranlib
          最后执行以下命令编译、安装:
              make
              make install

      5.2 安装mtd-utils
          下载mtd-utils-05.07.23ubuntu进入该文件目录,执行以下命令:
              cd mtd-utils-05.07.23/util
              vi Makefile
(将”#CROSS=arm-linux-”改为”CROSS=arm-linux-”)
              make (该目录下就会生成许多工具)
      5.3 拷贝格式化工具
              cp flash_erase flash_eraseall /work/nfs_root/first_fs/bin/
      5.4 擦除分区(这里假设擦除文件系统分区)
              flash_eraseall dev/mtd3  (如果之前有挂载,需要先卸载umount /mnt)
    6. 重新挂载,执行以下命令:
      mount -t yaffs /dev/mtdblock3 /mnt  (因为当前的mtdblock3为空,mount命令无法自动获取mtdblock3的文件类型所以指定格式为yaffs )

    这样就与上一节十六、Linux驱动之块设备驱动使用内存一样,能以文件操作形式使用nand flash了!

你可能感兴趣的:(linux驱动)