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 <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的校验功能,参考一下已有的驱动程序,其实很简单,就一行代码:

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值