Nandflash驱动
前面介绍过块设备驱动程序
分析内核自带的nand flash驱动程序s3c2410.c (drivers\mtd\nand)。
从入口函数s3c2410_nand_init开始看,根据总线设备驱动模型,注册一个平台drv,platform_driver_register(&s3c2440_nand_driver);内核里如果有同名的平台dev,就会调用.probe函数s3c24xx_nand_probe,如下。
s3c2410_nand_inithw //初始化硬件
s3c2410_nand_init_chip //初始化芯片
nand_scan(struct mtd_info *mtd, int maxchips)
// 在drivers/mtd/nand/nand_base.c中实现,通用文件,所有nand都支持
//根据nand_chip的底层操作函数识别NAND FLASH,构造mtd_info结构体
nand_scan_identnand_scan_ident(mtd, maxchips) //扫描识别
//设置默认函数
nand_set_defaults(chip, busw)
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_ready
//获得flash类型参数
nand_get_flash_type(mtd, chip, busw, &nand_maf_id)
chip->select_chip(mtd, 0); //选中芯片
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); //发出命令来读id
*maf_id = chip->read_byte(mtd); //读厂家ID
dev_id = chip->read_byte(mtd); //读设备ID
//用厂家id和设备id在数组里比较一下就得到flash的参数
for (i = 0; nand_flash_ids[i].name != NULL; i++) {
if (dev_id == nand_flash_ids[i].id) {
type = &nand_flash_ids[i];
break;}
nand_scan_tail(mtd) //尾部,构造mtd_info结构体,里面有操作函数
mtd->erase = nand_erase; //擦除
mtd->read = nand_read; //读
mtd->write = nand_write; //写
//添加分区
s3c2410_nand_add_partition
add_mtd_partitions(mtd, partitions, num_partitions) //添加分区
//添加设备
add_mtd_device(mtd)
list_for_each(this, & mtd_notifiers) {
//对mtd_notifiers链表里的每一项,调用它的add函数
// 问. mtd_notifiers在哪设置
// 答. drivers/mtd/mtdchar.c和mtd_blkdev.c调用register_mtd_user
// 在mtd_blkdevs.c和mtd_char.c中都register_mtd_user注册user,把notifiers结构体放到mtd_notifiers链表里面去。当它发现设备后会调用两个链表里面的add函数。
struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);
not->add(mtd);}
//这两个add函数被调用:mtd_notify_add和blktrans_notify_add
//先看字符设备的mtd_notify_add
class_device_create //创建两个设备节点,如/dev/mtd0和只读的/dev/mtd0ro
class_device_create
//再看块设备的blktrans_notify_add
list_for_each(this, &blktrans_majors) {
//对blktrans_majors链表里的每个成员调用它的add_mtd
// 问. 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 //分配gendisk
gd->queue = tr->blkcore_priv->rq; //队列
add_disk //添加
add_mtd_device时会通知两个user,字符设备和块设备,字符设备在类下面创建设备,块设备最终会add_disk。块设备驱动的核心没变,alloc_disk,gd->queue = tr->blkcore_priv->rq,add_disk。创建的设备都有一个ro只读的设备。
读函数mtd_read中,找到mtd_info结构体,调用它的读函数来操作。在nand_scan扫描过程中已经帮你构造好了这些函数,在nand_scan_tail中。
框架:
硬件相关层构造了nand_chip结构体,专注于怎么发命令、地址、数据; nand flash协议层构造了mtd_info结构体,专注于要发出什么命令、地址来识别、擦除、读写;块设备层知道优化,实现队列;字符设备层直接调用mtd.read、write来读写。
硬件相关层
1 分配nand_chip结构体,
2. 设置nand_chip
3. 硬件相关的设置:
3.1 使能nand控制器时钟
3.2 根据NAND FLASH的手册设置时间参数(TACLS,TWRPH0,TWRPH1),设置NFCONF寄存器
3.3 设置NFCONT寄存器,取消片选,使能nand控制器
4. 使用:
4.1 调用nand_scan根据nand_chip的底层操作函数识别NAND FLASH,构造mtd_info结构体(里面有擦除、读、写扇区函数)
4.2 添加mtd分区
NAND FLASH驱动程序
struct s3c_nand_regs { //nand寄存器
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 struct mtd_partition s3c_nand_parts[] = { //mtd分区数组
[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,}
};
static void s3c2440_select_chip(struct mtd_info *mtd, int chipnr){ //选中芯片
if (chipnr == -1){
s3c_nand_regs->nfcont |= (1<<1);} // 取消选中: NFCONT[1]设为1
else{
s3c_nand_regs->nfcont &= ~(1<<1);} // 选中: NFCONT[1]设为0
}
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; //NFDATA的虚拟地址
s3c_nand->IO_ADDR_W = &s3c_nand_regs->nfdata; //NFDATA的虚拟地址
s3c_nand->dev_ready = s3c2440_dev_ready;
s3c_nand->ecc.mode = NAND_ECC_SOFT; //软件生成ECC
/* 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 (联系nand_chip和mtd_info结构体)
s3c_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); //分配、设置mtd_info结构体
s3c_mtd->owner = THIS_MODULE;
s3c_mtd->priv = s3c_nand; //私有数据为nand_chip结构体
nand_scan(s3c_mtd, 1); //识别NAND FLASH, 构造mtd_info
// 5. 添加mtd分区
add_mtd_partitions(s3c_mtd, s3c_nand_parts, 4);
//add_mtd_device(s3c_mtd); 若只是把NAND分成一个分区
return 0;
}
static void s3c_nand_exit(void){
del_mtd_partitions(s3c_mtd);
kfree(s3c_mtd);
iounmap(s3c_nand_regs);
kfree(s3c_nand);
}
module_init(s3c_nand_init);
module_exit(s3c_nand_exit);
MODULE_LICENSE("GPL");
NAND 的结构是一页一页(一个扇区一个扇区)的结构,一页是 2KB,除了这 2KB 外还有 64B 的空间,这个 64B 区叫“OOB”(out of bank 叫作在 BANK 之外的东西)。
引入 OOB,是因为 NAND 有个缺点:位反转。如读一页数据时,里面很可能有某一位发生了位反转。本来值为 0,读出来为“1”。写的时候也有可能发生“位反转”。这样引入了“ECC”校验。
解决位反转:烧写时
1,写一页数据。
2,用这一页数据生成 ECC 码(校验码)。
3,把 ECC 写入 OOB 里。
读的时候:
1,读整页的数据。
2,读 OOB 里的 ECC 码。
3,通过读出来的一页数据算校验码。
4,比较从 OOB 里读出来的 ECC 码和通过读到的一页数据算出来的 ECC 码是否相同。不同则是发生了位反转。ECC 码是特定设置的,可以通过它知道是哪一位发生了反转。
ECC 校验码,可以用硬件生成,也可以用软件生成。
s3c_nand->ecc.mode = NAND_ECC_SOFT; //用软件生成ECC校验
测试
1 make menuconfig去掉内核自带的NAND FLASH驱动
-> Device Drivers
-> Memory Technology Device (MTD) support
-> NAND Device Support
< > NAND Flash support for S3C2410/S3C2440 SoC
2 make uImage
使用新内核启动, 并且使用NFS作为根文件系统
3 .ls dev/mtd*
insmod s3c_nand.ko //识别坏块有分区
4. 格式化 (参考下面编译工具)
flash_eraseall /dev/mtd3 // yaffs
想要格式化root分区,mtd3是最后一个分区root分区。
5. 挂接
mount -t yaffs /dev/mtdblock3 /mnt
cd /mnt/ //里面为空
6. 在/mnt目录下建文件
重启之后里面文件还在
编译工具:
1 tar xjf mtd-utils-05.07.23.tar.bz2
2. cd mtd-utils-05.07.23/util
修改Makefile:
#CROSS=arm-linux-
改为
CROSS=arm-linux- //去掉#
3. 在util目录下 make //可以ls查看到工具
4. cp flash_erase flash_eraseall /work/nfs_root/first_fs/bin/ //拷工具到网络文件系统bin,
erase是擦除一个扇区,eraseall是擦除整个分区。