NAND flash驱动程序(2)
前言
上一篇文章(NAND flash驱动程序(1))我们已经分析过了书写一个nand flash的大致框架是什么样的,现在我们再次回忆一下大致的流程:
(1)分配一个nand_chip和mtd_info结构体
(2)根据自己的需要,构造nand_chip结构体。以及一些硬件相关的设置
(3)最后就是调用nand_scan()和add_mtd_partitions()函数
下面就根据这个流程,写出我们自己的NAND FLASH驱动程序。
正文
我已经根据上面的流程写出了一个简单的程序例子,先给出代码再做解释。
/* 参考:* drivers/mtd/nand/at91_nand.c* drivers/mtd/nand/s3c2410.c*/#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include #include
#include
#include
#include #include #include
#include static struct nand_chip *s3c_nand_chip;
static struct mtd_info *s3c_mtd;
static struct clk *clk;static struct mtd_partition s3clt_nand_part[] = {[0] = {.name = "bootloader",.size = 0x00040000,.offset = 0,},[1] = {.name = "params",.offset = MTDPART_OFS_APPEND, /*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, /*MTDPART_SIZ_FULL代表剩下的所有空间大小*/}
};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 s3c_nand_regs *s3c_nand_regs = NULL;static void s3c2440_cmd_ctrl(struct mtd_info *mtd, int data, unsigned int ctrl)
{if (ctrl & NAND_CLE) {/* 发命令:NFCMMD=data */s3c_nand_regs->nfcmd = data; } else {/* 发地址:NFADDR=data */s3c_nand_regs->nfaddr = data;}
}static void s3c2440_select_chip(struct mtd_info *mtd, int chip)
{switch (chip) {case -1:/* 取消选中 */s3c_nand_regs->nfcont |= (1<<1);break;case 0:/* 选中芯片 */s3c_nand_regs->nfcont &= ~(1<<1);break;default:BUG();}
}static int s3c2440_nand_device_ready(struct mtd_info *mtd)
{return (s3c_nand_regs->nfstat & (1<<0));
}static int s3c_nand_init(void)
{/*1. 分配一个nand_chip结构体*/s3c_nand_chip = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);if (!s3c_nand_chip) {printk("s2c_nand_init s3c_nand_chip kzalloc failed\n");return -ENOMEM;}s3c_nand_regs = ioremap(0x4E000000, sizeof(struct s3c_nand_regs));if (s3c_nand_regs == NULL) {printk(KERN_ERR "s3c_nand: ioremap failed\n");goto err0;}/*2. 设置*//* 构造nand_chip结构体给nand_chip使用,如果不知道怎么设置,可以看一下nand_scan怎么使用* 它应该提供:选中、发命令、发地址、发数据、读数据、判断状态的功能*/s3c_nand_chip->select_chip = s3c2440_select_chip;s3c_nand_chip->cmd_ctrl = s3c2440_cmd_ctrl;s3c_nand_chip->IO_ADDR_R = &s3c_nand_regs->nfdata;s3c_nand_chip->IO_ADDR_W = &s3c_nand_regs->nfdata;s3c_nand_chip->dev_ready = s3c2440_nand_device_ready;s3c_nand_chip->ecc.mode = NAND_ECC_SOFT; /* enable ECC *//*使能NAND FLASH控制器的时钟*/clk = clk_get(NULL, "nand");if (IS_ERR(clk)) {printk("failed to get clock");goto err1; }clk_enable(clk); /* 相当于设置了CLKCON寄存器的bit[4] *//* 3. 硬件相关的设置: 根据NAND FLSAH手册设置时间参数*//* HCLK = 100MHz = 10的负8次方/秒 = 10ns* TACLS:CLE/ALE信号变为高电平后,nWE信号多久能变为低电平。由NAND FLASH手册可知,CLE/ALE和nWE能同时发出,所以TACLS可以为0* TWRPH0:nWE的脉冲宽度。Duration = HCLK x ( TWRPH0 + 1 )。由NAND FLASH手册可以看出(tWP),Duration最小值为12ns,所以TWRPH0>=0.2,取TWRPH0=1* TWRPH1:nWE信号又低电平变为高电平后,CLE/ALE信号多久才由高电平变为低电平。Duration = HCLK x ( TWRPH1 + 1 )。由NAND FLASH手册可以看出(tCLH),Duration最小值为5ns,所以TWRPH1>=-0.5,取TWRPH1=0*/
#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0s3c_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);if (!s3c_mtd) {printk("s2c_nand_init s3c_mtd kzalloc failed\n");goto err1;}s3c_mtd->priv = s3c_nand_chip;s3c_mtd->owner = THIS_MODULE;nand_scan(s3c_mtd, 1); /* 识别NAND FLASH,构造mtd_info *//*5. add_mtd_partitions,如果整个NAND FLASH不划分,就一块的话,可以用add_mtd_device(s3c_mtd)*/add_mtd_partitions(s3c_mtd, s3clt_nand_part, 4);return 0;err1:iounmap(s3c_nand_regs);err0:kfree(s3c_nand_chip);return -1;}static void s3c_nand_exit(void)
{kfree(s3c_mtd);iounmap(s3c_nand_regs);kfree(s3c_nand_chip);
}module_init(s3c_nand_init);
module_exit(s3c_nand_exit);
MODULE_LICENSE("GPL");
(1)分配nand_chip结构体
老规矩,我们先从初始化函数_init函数开始分析。很明显,按照我们一开始说的,先分配一个nand_chip结构体。然后就是驱动程序的大头,构造我们的nand_chip结构体。
(2)构造我们自己的nand_chip结构体
但是到底nand_chip有什么作用呢?其实我的上一篇文章已经分析过(NAND flash驱动程序(1)),我们还分配了一个mtd_info结构体,它其中的一项是void *priv,就是指向了我们的nand_chip结构体,并最后将mtd_info结构体作为参数,传递给nand_scan()。并且在nand_scan()函数中用到了nand_chip结构体,所以我们可以进入到nand_scan()函数,看一下这个结构体具体做什么动作。
其实就是调用nand_chip结构体中的一些函数,主要是一个NAND flash驱动程序所需要提供的操作函数,比如:选中、发命令、发地址、发数据、读数据、判断状态等功能。
struct nand_chip {void __iomem *IO_ADDR_R; //读地址void __iomem *IO_ADDR_W; //写地址...void (*select_chip)(struct mtd_info *mtd, int chip); //选中...void (*cmd_ctrl)(struct mtd_info *mtd, int dat, //发命令函数unsigned int ctrl);...
};
本身上层的框架就已经提供了默认的操作函数赋值给我们的nand_chip结构体(nand_set_defaults函数中) ,但是默认的函数并不一定适合我们的驱动程序使用,所以,在构造nand_chip结构时,我们就需要设置合适的操作函数,下面给出一个例子,示范一下怎么设置适合我们自己的操作函数。
我们分别进入到 :nand_scan() -> nand_scan_ident() -> nand_get_flash_type(),看一下需要用到什么操作函数。
int nand_scan_ident(struct mtd_info *mtd, int maxchips)
{int i, busw, nand_maf_id;struct nand_chip *chip = mtd->priv;struct nand_flash_dev *type;/* Get buswidth to select the correct functions */busw = chip->options & NAND_BUSWIDTH_16;/* Set the default functions */nand_set_defaults(chip, busw);/* Read the flash type */type = nand_get_flash_type(mtd, chip, busw, &nand_maf_id);...
}
可以看到,在读取flash类型前,我们进入到nand_set_defaults(),作用就是,如果我们没有设置自己的操作函数,那么系统就自动为我们设置默认的。
static void nand_set_defaults(struct nand_chip *chip, int busw)
{/* check for proper chip_delay setup, set 20us if not */if (!chip->chip_delay)chip->chip_delay = 20;/* check, if a user supplied command function given */if (chip->cmdfunc == NULL)chip->cmdfunc = nand_command; //设置默认的函数/* check, if a user supplied wait function given */if (chip->waitfunc == NULL)chip->waitfunc = nand_wait;if (!chip->select_chip) //选中chip->select_chip = nand_select_chip;if (!chip->read_byte) //字节为单位读chip->read_byte = busw ? nand_read_byte16 : nand_read_byte;if (!chip->read_word)chip->read_word = nand_read_word;if (!chip->block_bad)chip->block_bad = nand_block_bad;if (!chip->block_markbad)chip->block_markbad = nand_default_block_markbad;if (!chip->write_buf) //写函数chip->write_buf = busw ? nand_write_buf16 : nand_write_buf;if (!chip->read_buf)chip->read_buf = busw ? nand_read_buf16 : nand_read_buf;if (!chip->verify_buf)chip->verify_buf = busw ? nand_verify_buf16 : nand_verify_buf;if (!chip->scan_bbt)chip->scan_bbt = nand_default_bbt;if (!chip->controller) {chip->controller = &chip->hwcontrol;spin_lock_init(&chip->controller->lock);init_waitqueue_head(&chip->controller->wq);}}
所以,想知道默认的函数是否适合我们用,我们可以直接进默认的函数看一下。 比如在nand_get_flash_type()读取flash类型的函数中,我们就用到了“选中”这个操作函数。,所以我们看一下默认的“选中”函数做了什么。
/*** nand_select_chip - [DEFAULT] control CE line* @mtd: MTD device structure* @chipnr: chipnumber to select, -1 for deselect** Default select function for 1 chip devices.*/
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();}
}
由注释我们可以看到,到传入的chipnr为-1时,是取消选中,为0时是选中。但是默认的函数里面什么都没做,所以我们就需要写符合我们芯片的选中函数了。比如我下面写的“选中”函数,而至于为什么这么设置寄存器,可以参考我这篇文章:NAND FLASH的读操作及原理。
static void s3c2440_select_chip(struct mtd_info *mtd, int chip)
{switch (chip) {case -1:/* 取消选中 */s3c_nand_regs->nfcont |= (1<<1);break;case 0:/* 选中芯片 */s3c_nand_regs->nfcont &= ~(1<<1);break;default:BUG();}
}
写完我们自己的函数后,只要赋值给nand_chip结构体就好了
s3c_nand_chip->select_chip = s3c2440_select_chip;
其他的操作函数也是类似,不符合我们要求的,就自己写,比如读写函数就需要知道相应寄存器的地址,所以也是需要我们自己设置的:
s3c_nand_chip->IO_ADDR_R = &s3c_nand_regs->nfdata;s3c_nand_chip->IO_ADDR_W = &s3c_nand_regs->nfdata;
(3)硬件相关的设置
上面介绍完怎么设置nand_chip结构体后,我们还需要做设置一些硬件相关的,比如NAND FLASH控制器的使能和时钟频率。
因为s3c2440芯片为了节省电能,在上电后很多模块都是关闭的,需要我们自己使能:
/*使能NAND FLASH控制器的时钟*/clk = clk_get(NULL, "nand");if (IS_ERR(clk)) {printk("failed to get clock");goto err1; }clk_enable(clk); /* 相当于设置了CLKCON寄存器的bit[4] */
而NAND FLASH的时钟频率,就是要同时参考s3c2440芯片手册和NAND FLASH芯片手册了。
下面是2440芯片手册中的时序图:

下面是NAND FLASH手册的是时序图:

NAND FLASH手册中,有关时间参数的参考值:

所以根据上面的三个截图,很容易就能得到我们想要的三个参数的具体值,再设置到对应的寄存器就好了
/* 3. 硬件相关的设置: 根据NAND FLSAH手册设置时间参数*//* HCLK = 100MHz = 10的负8次方/秒 = 10ns* TACLS:CLE/ALE信号变为高电平后,nWE信号多久能变为低电平。由NAND FLASH手册可知,CLE/ALE和nWE能同时发出,所以TACLS可以为0* TWRPH0:nWE的脉冲宽度。Duration = HCLK x ( TWRPH0 + 1 )。由NAND FLASH手册可以看出(tWP),Duration最小值为12ns,所以TWRPH0>=0.2,取TWRPH0=1* TWRPH1:nWE信号又低电平变为高电平后,CLE/ALE信号多久才由高电平变为低电平。Duration = HCLK x ( TWRPH1 + 1 )。由NAND FLASH手册可以看出(tCLH),Duration最小值为5ns,所以TWRPH1>=-0.5,取TWRPH1=0*/
#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0s3c_nand_regs->nfconf = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);
(4)调用nand_scan()和add_mtd_partitions()函数
前面的工作都做完后,就可以将设置好的nand_chip和mtd_info结构体传给nand_scan()函数了
/*4. 使用:nand_scan*/s3c_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);if (!s3c_mtd) {printk("s2c_nand_init s3c_mtd kzalloc failed\n");goto err1;}s3c_mtd->priv = s3c_nand_chip;s3c_mtd->owner = THIS_MODULE;nand_scan(s3c_mtd, 1); /* 识别NAND FLASH,构造mtd_info */
add_mtd_partitions()是用来划分我们的分区,这里我划分4个分区。另外,函数参数还需要传递一个记录分区划分信息的数组:
static struct mtd_partition s3clt_nand_part[] = {[0] = {.name = "bootloader",.size = 0x00040000,.offset = 0,},[1] = {.name = "params",.offset = MTDPART_OFS_APPEND, /*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, /*MTDPART_SIZ_FULL代表剩下的所有空间大小*/}
};/*5. add_mtd_partitions,如果整个NAND FLASH不划分,就一块的话,可以用add_mtd_device(s3c_mtd)*/add_mtd_partitions(s3c_mtd, s3clt_nand_part, 4);
测试结果

我们的驱动程序执行到nand_scan()这一步后,就会打印上面的信息,我们可以看到红框中的信息,提示我们说没有ECC,这个做法是不推荐的。这里我们简单说一下何为ECC。
我们知道NAND FLASH有个缺点,会发生位反转,也就是原来0的数据变为1,1的数据变为0,导致数据的不准确。所以为了检测出是否发生了位反转,在写数据的时候:
(1)写完一个page数据(2)根据这个page的数据,生成一个ECC码(3)将ECC码写入OOB(out of bank)
相对应的,在读数据的时候:
(1)读一个page数据(2)读OOB中的ECC码(3)算出这个page的ECC码(4)并比较两个ECC码,看是否一致
那么OOB在NAND FLASH的什么地方呢?我们看一下下面这个图,红色框出来的64字节就是OOB区,每一个page都有一个OOB。

所以我们最好为我们的驱动程序加上ECC的校验功能,参考一下已有的驱动程序,其实很简单,就一行代码:
![]()
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
