前言
上一篇文章(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 <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_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 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);
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 0
s3c_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的校验功能,参考一下已有的驱动程序,其实很简单,就一行代码: