本节里面我们实现一个简单的nandflash驱动程序,我们先来看代码:
/* 参考
* 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;
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. 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");
分析:首先我们先来看一下nandflash驱动的编写步骤:
(1)分配一个nand_chip
(2)设置这个nand_chip
(3)硬件相关的设置
(4)使用nand_scan和add_mtd_partitions
那么下面我们就根据这个步骤来一步一步分析这个程序:
我们先分配一个nand_chip,对这个结构体的设置是一个比较纠结的过程,但是韦东山老师告诉我们一个好办法,nand_chip设置了是给 nand_scan用的,那么我们只要看看它是怎么用这个结构体的不就可以学着设置了吗?果然是好主要!
我们先分配一个nand_chip,对这个结构体的设置是一个比较纠结的过程,但是韦东山老师告诉我们一个好办法,nand_chip设置了是给 nand_scan用的,那么我们只要看看它是怎么用这个结构体的不就可以学着设置了吗?果然是好主要!
我们从 nand_scan出发,一步一步往下分析:
(1)我们发现有这么个函数:select_chip(mtd, 0)
我们到函数:nand_set_defaults(chip, busw);里面去看默认的设置,发现对
chip->select_chip 这个函数的设置比较坑爹,所以我们必须自己完成这个函数。这个函数的作用就是选通芯片,或者不选通芯片,这由
NFCONT 的第一位来决定,为0是表示选通,1时表示不选通, 还是比较容易设置的。
(2)接下来又出现了函数:chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);我们到
nand_set_defaults(chip, busw);里面去看它的默认设置,然后发现
chip->cmdfunc函数最终会调用:chip->cmd_ctrl(mtd, readcmd, ctrl);这个函数,那么我们需要对chip->cmd_ctrl(mtd, readcmd, ctrl);进行实现。这个函数的作用就是根据
ctrl来判断传递的是地址、命令还是数据。也很容易实现。
(3)接下来是:maf_id = chip->read_byte(mtd);我们还是去看默认设置,发现这么一句:readb(chip->IO_ADDR_R);里面的参数当然就是读的地址了,所以我们还必须要设置读地址。同样写地址也要设这的,方法一样的。
(4)我们还需要获取的是nandflash的状态,因为我们需要判断nandflash是不是忙。
(5)设置校验码:关于校验码我们还得好好说道一下呢!我们下来看一下nandflash的组织图:
![块设备驱动之nandflash——简单程序分析 - 小白 - 小白的博客](http://img7.ph.126.net/uyYZvMwemKi6mW8W9eXVpg==/668221594728665347.jpg)
我们看到一页里面有2k加64b,其实存储真正的数据的是2k,64b是用作ecc校验的。它的原理如下:
当我们写nandflash时,会根据写入的数据计算出来ecc码,并将ecc码写入64b的空间里面。当我们读数据时,会根据读出来的数据从新计算出来ecc校验码,然后跟存放在64b空间里面的ecc校验码相比较,如果一致说明没有发生错误,否则是发生了错误。
(6)我们还需要一个struct mtd_info结构体,并使其私有数据指向我们上面所设置的struct nand_chip结构体。
(7)接下来就是硬件相关的设置了,首先我们需要设置时钟。我们直接用内核函数先获得时钟,然后使能时钟。除此之外我们还需要设置一些延时参数:
先贴两张图:
这两张是s3c2440上的:
![块设备驱动之nandflash——简单程序分析 - 小白 - 小白的博客](http://img2.ph.126.net/fBzcVcQMpJ5YtOEkcltFUA==/2485424044372477412.jpg)
![块设备驱动之nandflash——简单程序分析 - 小白 - 小白的博客](http://img8.ph.126.net/KI8ClUOliJnEI_hGo8gIyQ==/2582814386314553348.jpg)
这两张是nandflash芯片资料上面的:
![块设备驱动之nandflash——简单程序分析 - 小白 - 小白的博客](http://img7.ph.126.net/e2NiHu9GoWr7U_wBzlwrKQ==/671880769425912927.jpg)
![块设备驱动之nandflash——简单程序分析 - 小白 - 小白的博客](http://img7.ph.126.net/DMf2n9hSh8yOzBUOUrOFJg==/625437398268646877.jpg)
我们先来说说我们要设置的三个时间参数的含义吧:
TACLS: 发出CLE/ALE之后多长时间才发出nWE信号
TWRPH0: nWE的脉冲宽度, HCLK x ( TWRPH0 + 1 )
TWRPH1: nWE变为高电平后多长时间CLE/ALE才能变为低电平
比较两幅时序图我们发下如下对应关系(HCLK=100Mhz):
TACLS*HCLK=tCLS-tWP=12-12=0,所以
TACLS=0
(TWRPH0+1)*HCLK>=12ns,所以
TWRPH0>=1
(TWRPH1+1)*HCLK>=5ns, 所以
TWRPH1>=0
于是可以设置:s3c_nand_regs->nfconf = (0<<12) | (1<<8) | (0<<4);
(8)对于NFCONT,我们要先使能控制寄存器,但是要先取消片选。