欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Nandflash驱动

程序员文章站 2022-03-22 16:42:17
...

前面介绍过块设备驱动程序
分析内核自带的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中。
Nandflash驱动
框架:
硬件相关层构造了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");

Nandflash驱动
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是擦除整个分区。

相关标签: 驱动