NAND FLASH驱动程序

来源:互联网 发布:linux当前文件路径 编辑:程序博客网 时间:2024/05/16 10:50

NAND FLASH是一个存储芯片,下面围绕NAND FLASH提出几个问题。

问1. 原理图上NAND FLASH和S3C2440之间只有数据线,怎么传输地址?
答1.在DATA0~DATA7上既传输数据,又传输地址,当ALE为高电平时传输的是地址。

问2. 从NAND FLASH芯片手册可知,要操作NAND FLASH需要先发出命令,怎么传入命令?
答2.在DATA0~DATA7上既传输数据,又传输地址,也传输命令:当ALE为高电平时传输的是地址,当CLE为高电平时传输的是命令,当ALE和CLE都为低电平时传输的是数据。

问3. 数据线既接到NAND FLASH,也接到NOR FLASH,还接到SDRAM、DM9000等等,那么怎么避免干扰?
答3. 这些设备,要访问之必须”选中”,没有选中的芯片不会工作,相当于没接一样

问4. 假设烧写NAND FLASH,把命令、地址、数据发给它之后,NAND FLASH肯定不可能瞬间完成烧写的, 怎么判断烧写完成?
答4. 通过状态引脚RnB来判断:它为高电平表示就绪,它为低电平表示正忙

问5. 怎么操作NAND FLASH呢?
从NAND FLASH的芯片手册可以很容易看出来:

          NAND FLASH                      S3C2440发命令    选中芯片                             CLE设为高电平                   NFCMMD=命令值               在DATA0~DATA7上输出命令值          发出一个写脉冲发地址    选中芯片                        NFADDR=地址值          ALE设为高电平          在DATA0~DATA7上输出地址值          发出一个写脉冲发数据    选中芯片                        NFDATA=数据值          ALE,CLE设为低电平          在DATA0~DATA7上输出数据值          发出一个写脉冲读数据    选中芯片                        val=NFDATA          发出读脉冲          读DATA0~DATA7的数据

下面我们通过用UBOOT来体验NAND FLASH的操作。
1.读ID。时序图如下:
这里写图片描述

1. 读ID                               S3C2440                 u-boot 选中                           NFCONT的bit1设为0   md.l 0x4E000004 1; mw.l 0x4E000004  1发出命令0x90                   NFCMMD=0x90         mw.b 0x4E000008 0x90 发出地址0x00                   NFADDR=0x00         mw.b 0x4E00000C 0x00读数据得到0xEC                 val=NFDATA          md.b 0x4E000010 1读数据得到device code          val=NFDATA          md.b 0x4E000010 1          0xda退出读ID的状态                 NFCMMD=0xff         mw.b 0x4E000008 0xff

可得到ID为0xDA。其中,NFCONT的bit1设为0之所以要用mw.l 0x4E000004 1因为上一次读取的值是0x3。

2.读内容: 读0地址的数据
有两种方法:
①使用UBOOT命令nand dump 0,最后得到的结果是:
17 00 00 ea 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5
②直接操作寄存器,时序图如下:
这里写图片描述

                               S3C2440                 u-boot 选中                           NFCONT的bit1设为0   md.l 0x4E000004 1; mw.l 0x4E000004  1发出命令0x00                   NFCMMD=0x00         mw.b 0x4E000008 0x00 发出地址0x00                   NFADDR=0x00         mw.b 0x4E00000C 0x00发出地址0x00                   NFADDR=0x00         mw.b 0x4E00000C 0x00发出地址0x00                   NFADDR=0x00         mw.b 0x4E00000C 0x00发出地址0x00                   NFADDR=0x00         mw.b 0x4E00000C 0x00发出地址0x00                   NFADDR=0x00         mw.b 0x4E00000C 0x00发出命令0x30                   NFCMMD=0x30         mw.b 0x4E000008 0x30 读数据得到0x17                 val=NFDATA          md.b 0x4E000010 1读数据得到0x00                 val=NFDATA          md.b 0x4E000010 1读数据得到0x00                 val=NFDATA          md.b 0x4E000010 1读数据得到0xea                 val=NFDATA          md.b 0x4E000010 1退出读状态                     NFCMMD=0xff         mw.b 0x4E000008 0xff

得到的结果和第①个是一样的。

下面分析一下NAND FLASH驱动程序层次。
从内核启动的信息来看,
S3C24XX NAND Driver, (c) 2004 Simtec Electronics
s3c2440-nand s3c2440-nand: Tacls=3, 30ns Twrph0=7 70ns, Twrph1=3 30ns
NAND device: Manufacturer ID: 0xec, Chip ID: 0xda (Samsung NAND 256MiB 3,3V 8-bit)

我们搜”S3C24XX NAND Driver”得到S3c2410.c (drivers\mtd\nand),从这个文件开始分析。

s3c2410_nand_inithw//初始化硬件s3c2410_nand_init_chip//初始化芯片nand_scan  // drivers/mtd/nand/nand_base.c 根据nand_chip的底层操作函数识别NAND FLASH,构造mtd_info    nand_scan_ident        nand_set_defaults           nand_get_flash_type    nand_scan_tail            mtd->erase = nand_erase;            mtd->read = nand_read;            mtd->write = nand_write;s3c2410_nand_add_partition    add_mtd_partitions        add_mtd_device            list_for_each(this, &mtd_notifiers) { // 问. mtd_notifiers在哪设置                                                  // 答. drivers/mtd/mtdchar.c,mtd_blkdev.c调用register_mtd_user                struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);                not->add(mtd);                // mtd_notify_add  和 blktrans_notify_add                先看字符设备的mtd_notify_add                        class_device_create                        class_device_create                再看块设备的blktrans_notify_add                    list_for_each(this, &blktrans_majors) { // 问. blktrans_majors在哪设置                                                            // 答. drivers\mtd\mdblock.c或mtdblock_ro.c   register_mtd_blktrans                        struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);                                      tr->add_mtd(tr, mtd);                                mtdblock_add_mtd (drivers\mtd\mdblock.c)                                    add_mtd_blktrans_dev                                        alloc_disk                                        gd->queue = tr->blkcore_priv->rq; // tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);                                        add_disk   

如下图:
这里写图片描述

下面开始真正写一个NAND FLASH驱动程序。
先写一个大概的框架:

#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>static struct nand_chip *s3c_nand;static struct mtd_info *s3c_mtd;static void s3c2440_select_chip(struct mtd_info *mtd, int chipnr){    if (chipnr == -1)    {        /* 取消选中: NFCONT[1]设为0 */    }    else    {        /* 选中: NFCONT[1]设为1 */    }}static void s3c2440_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl){    if (ctrl & NAND_CLE)    {        /* 发命令: NFCMMD=dat */    }    else    {        /* 发地址: NFADDR=dat */    }}static int s3c2440_dev_ready(struct mtd_info *mtd){    return "NFSTAT的bit[0]";}static int s3c_nand_init(void){    /* 1. 分配一个nand_chip结构体 */    s3c_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);    /* 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   = "NFDATA的虚拟地址";    s3c_nand->IO_ADDR_W   = "NFDATA的虚拟地址";    s3c_nand->dev_ready   = s3c2440_dev_ready;    /* 3. 硬件相关的设置 */    /* 4. 使用: nand_scan */    s3c_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);    s3c_mtd->owner = THIS_MODULE;    s3c_mtd->priv  = s3c_nand;   //将mtd_info和nand_chip建立联系    nand_scan(s3c_mtd, 1);  /* 识别NAND FLASH, 构造mtd_info */    /* 5. add_mtd_partitions */    return 0;}static void s3c_nand_exit(void){}module_init(s3c_nand_init);module_exit(s3c_nand_exit);MODULE_LICENSE("GPL");

需要注意的是,在构造nand_chip 的时候,需要明白nand_scan是如何设置默认参数的,其中需要调用nand_set_defaults和nand_get_flash_type,下面分析一下这两个函数。
如果我们偷懒,想要用系统提供的函数,看看会出现什么后果。在nand_get_flash_type的调用如下:

chip->select_chip(mtd, 0);chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);*maf_id = chip->read_byte(mtd);dev_id = chip->read_byte(mtd);

先看select_chip:

static void nand_select_chip(struct mtd_info *mtd, int chipnr){    struct nand_chip *chip = mtd->priv;    switch (chipnr) {    case -1:        chip->cmd_ctrl(mtd, NAND_CMD_NONE, 0 | NAND_CTRL_CHANGE);        break;    case 0:        break;    default:        BUG();    }}

这个函数提供了两种情况,但很明显,由于我们的s3c2440的片选需要通过操作 NFCONT的bit1设置为0,取消需要设置为1,而这个默认函数里根本没有对寄存器的操作,所以这个默认的函数不能用。
再看cmdfunc,分析这个函数,这个默认函数最终会调用nand_chip的->cmdfunc,因此,我们需要构造这个函数。
还有read_byte也需要调用我们自己构造的读函数。因此对于nand_set_defaults总结如下:

if (!chip->select_chip)                chip->select_chip = nand_select_chip; // 默认值不适用            if (chip->cmdfunc == NULL)                chip->cmdfunc = nand_command;//需要调用chip->cmd_ctrl(mtd, command, ctrl);            if (!chip->read_byte)                chip->read_byte = nand_read_byte;//需要调用readb(chip->IO_ADDR_R);            if (chip->waitfunc == NULL)                chip->waitfunc = nand_wait;//需要调用chip->dev_readychip->dev_ready

所以,我们需要

/* 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   = "NFDATA的虚拟地址";    s3c_nand->IO_ADDR_W   = "NFDATA的虚拟地址";    s3c_nand->dev_ready   = s3c2440_dev_ready;

这样,一个半成品的驱动程序就完成了,下面实现那些未完成的伪代码。

接下来先要映射寄存器,完成自己构造的几个函数,还需要在入口函数里完成对硬件的操作:

/* 参考  * 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  ;    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 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;    /* 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. add_mtd_partitions */    return 0;}static void s3c_nand_exit(void){    kfree(s3c_mtd);    iounmap(s3c_nand_regs);    kfree(s3c_nand);}module_init(s3c_nand_init);module_exit(s3c_nand_exit);MODULE_LICENSE("GPL");

其中硬件相关的设置,主要是使能NAND FLASH控制器和根据NAND FLASH的手册设置时间参数,也就是设置NFCONF寄存器的三个时间参数。时序图如下:
这里写图片描述
而具体的计算过程上面已经给出,不再赘述。把驱动装载之后,可以看到:

NAND device: Manufacturer ID: 0xec, Chip ID: 0xda (Samsung NAND 256MiB 3,3V 8-bit)NAND_ECC_NONE selected by board driver. This is not recommended !!Scanning device for bad blocksBad eraseblock 98 at 0x00c40000Bad eraseblock 100 at 0x00c80000Bad eraseblock 102 at 0x00cc0000Bad eraseblock 106 at 0x00d40000Bad eraseblock 112 at 0x00e00000Bad eraseblock 715 at 0x05960000Bad eraseblock 1886 at 0x0ebc0000

其中,提示不实用ECC是不推荐的,我们可以再在初始化nand_chip的时候加上

    s3c_nand->ecc.mode    = NAND_ECC_SOFT;

这样,就实现了ECC的添加。

最后,实现添加分区。
如果想把FLASH当作只有一个分区,需要在相关部分写上:

add_mtd_device(s3c_mtd);

但如果想要实现多个分区的话,需要构造一个分区表,并在相关部分写上:

add_mtd_partitions(s3c_mtd, s3c_nand_parts, 4);

其中,分区表可以参考内核中的:

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

这样,一个完整的NAND FLASH驱动就完成了。

0 0
原创粉丝点击