转载自:http://blog.csdn.net/wang_zheng_kai/article/details/18988521
有了前面的基础(Nandflash详解:https://blog.csdn.net/Golden_Chen/article/details/89470673),我们就可以研究MTD下的nand驱动了,我这里用的是jz4780grus开发板,我将以下面几个部分做一个介绍,如果有没有涉及的或者需要详细介绍的我还会再开几篇博客进行介绍,敬请参阅!!
一、MTD概述
二、MTD系统层次
三、MTD设备节点的创建
四、MTD子系统的注册
五、NAND FLASH驱动层
六、MTD下NAND FLASH读操作分析
七、如何调试新的NAND FLASH芯片
一、MTD的概述
1、MTD(memory technology device)是用于访问memory设备(比如NOR Flash、NAND Flash)的Linux的子系统。
2、MTD在硬件和上层之间提供了一个抽象的接口。
MTD将 Nand Flash,nor flash 和其他类型的 flash 等设备,统一抽象成MTD 设备来管理,根据这些设备的特点,上层实现了常见的操作函数封装,而底层具体的内部实现(具体的内部硬件设备的读/写/擦除函数),就需要驱动设计者自己来实现了。
3、MTD的主要目的是为了使新的存储设备的驱动更加简单并有通用接口函数可用。
MTD将文件系统与底层的Flash存储器进行了隔离,使Flash驱动工程师无须关心Flash作为字符设备和块设备与Linux内核的接口。
4、MTD 的所有源代码在kernel/drivers/mtd 子目录下:
5、MTD子系统的层次框图
二、linux MTD系统层次
如下图所示:在引入MTD后,linux系统中的flash设备驱动及接口可分为4层:设备节点、MTD设备层、MTD原始层和硬件驱动。
MTD的源代码都在kernel/drivers/mtd目录中。
设备节点:
通过mknod在dev子目录下建立MTD字符设备节点(90)和MTD块设备节点(31)基于MTD原始设备,由linux系统定义出MTD的块设备和字符设备构成。
字符设备的定义在mtdchar.c中实现,通过注册一系列file_operation函数可实现对MTD设备的读写和控制。
MTD的块设备则是定义了一个描述MTD块设备的结构mtd_dev,并声明了一个名为mtdblks的指针数组,该数组中的每一个mtd_dev都与mtd_info一一对应。
完成flash的基本操作。
用户通过访问此设备节点即可访问MTD字符设备和块设备
设备层:
基于MTD原始设备,由linux系统定义出MTD的块设备和字符设备构成。
字符设备的定义在mtdchar.c中实现,通过注册一系列file_operation函数可实现对MTD设备的读写和控制。
MTD的块设备则是定义了一个描述MTD块设备的结构mtd_dev,并声明了一个名为mtdblks的指针数组,该数组中的每一个mtd_dev都与mtd_info一一对应。
完成flash的基本操作。
原始设备层:
MTD原始设备的通用代码(mtdcore.c),(mtdpart.c).其中mtdcore.c中定义了描述mtd设备的核心结构mtdinfo.
硬件驱动层:
负责Flash硬件设备的读、写、擦除,Linux MTD设备的NAND型flash的
驱动代码位于/driver/mtd/nand子目录下。
了解了上面的知识,我们就可以了解下他们各个层的接口关系,这样我们能更好的熟悉代码,和他们之间的接口调用关系,接口图如下所示:
MTD下NAND的布局中几个重要文件的诠释和MTD涉及的几个重要的结构体(更好的理解接口)
1、内核中的NAND代码布局
在Linux 内核中,MTD 源代码放在/driver/mtd 目录中,该目录中包含chips 、devices 、maps 、nand 、onenand 和ubi 六个子目录。其中只有nand 和onenand 目录中的代码才与NAND 驱动相关,不过nand 目录中的代码比较通用,而onenand 目录中的代码相对于nand 中的代码而言则简化了很多,它是针对三星公司开发的另一类Flash芯片。
本次报告是基于MTD 的NAND 驱动程序,我们需要关注的代码就基本上全在drivers/mtd/nand 目录中了,而该目录中也不是所有的代码文件都与我们将要开发的NAND 驱动有关,除了Makefile 和Kconfig 之外,其中真正与NAND 驱动有关的代码文件只有6 个,即:
1)nand_base.c :
定义了NAND 驱动中对NAND 芯片最基本的操作函数和操作流程,如擦除、读写page 、读写oob 等。当然这些函数都只是进行一些default 的操作,若你的系统在对NAND 操作时有一些特殊的动作,则需要在你自己的驱动代码中进行定义,然后Replace 这些default 的函数。
2)nand_bbt.c :
定义了NAND 驱动中与坏块管理有关的函数和结构体。
3)nand_ids.c :
定义了两个全局类型的结构体:struct nand_flash_dev nand_flash_ids[ ] 和struct nand_manufacturers nand_manuf_ids[ ] 。其中前者定义了一些NAND 芯片的类型,后者定义了NAND 芯片的几个厂商。NAND 芯片的ID 至少包含两项内容:厂商ID 和厂商为自己的NAND 芯片定义的芯片ID 。当NAND 驱动被加载的时候,它会去读取具体NAND 芯片的ID ,然后根据读取的内容到上述定义的nand_manuf_ids[ ] 和nand_flash_ids[ ] 两个结构体中去查找,以此判断该NAND 芯片是那个厂商的产品,以及该NAND 芯片的类型。若查找不到,则NAND 驱动就会加载失败,因此在开发NAND 驱动前必须事先将你的NAND 芯片添加到这两个结构体中去(其实这两个结构体中已经定义了市场上绝大多数的NAND 芯片,所以除非你的NAND 芯片实在比较特殊,否则一般不需要额外添加)。
值得一提的是,nand_flash_ids[ ] 中有三项属性比较重要,即pagesize 、chipsize 和erasesize ,驱动就是依据这三项属性来决定对NAND 芯片进行擦除,读写等操作时的大小的。其中pagesize 即NAND 芯片的页大小,一般为256 、512 或2048 ;chipsize 即NAND 芯片的容量;erasesize 即每次擦除操作的大小,通常就是NAND 芯片的block 大小。
4)nand_ecc.c :
定义了NAND 驱动中与softeware ECC 有关的函数和结构体,若你的系统支持hardware ECC ,且不需要software ECC ,则该文件也不需理会。
5)nandsim.c :
定义了Nokia 开发的模拟NAND 设备,默认是Toshiba NAND 8MiB 1,8V 8-bit (根据ManufactureID ),开发普通NAND 驱动时不用理会。
6)diskonchip.c :
定义了片上磁盘(DOC) 相关的一些函数,开发普通NAND 驱动时不用理会。
除 了上述六个文件之外,nand 目录中其他文件基本都是特定系统的NAND 驱动程序例子,但看来真正有参考价值的还有cafe_nand.c 和jz4780_nand.c 两个,而其中又尤以cafe_nand.c 更为详细,另外,nand 目录中也似乎只有cafe_nand.c 中的驱动程序在读写NAND 芯片时用到了DMA 操作。
芯片级驱动需要实现nand_chip结构体
MTD使用nand_chip来表示一个NAND FLASH芯片, 该结构体包含了关于Nand Flash的地址信息,读写方法,ECC模式,硬件控制等一系列底层机制。
2、MTD下NAND所涉及的几个重要的结构体
1)mtd_info数据结构。(重要的结构体之一)
mtd_info结构是MTD原始设备层的一个重要结构,表示MTD原始设备的结构,该结构定义了大量的关于MTD的数据和操作,定义在include/linux/mtd/mtd.h头文件。mtd_info结构成员主要由数据成员和操作函数两部分组成。
每个分区也被实现为一个mtd_info,如果有两个MTD原始设备,每个上有三个分区,在系统中就一共有6个mtd_info结构,这些mtd_info的指针被存放在名为mtd_table的数组里.
要强调的是:包含的这些函数指针指向的函数是MTD驱动提供的接口函数(每一个驱动程序的最终目的就是提供一些接口函数实现对底层硬件设备的操作),这些函数是在整个MTD驱动框架中层次最高的函数,他们可以在应用程序中直接调用实现对MTD设备底层硬件的操作。如:static int nand_read_ecc ()。它实现了对NAND flash的读操作。这些函数一般是都在u-boot源码目录下的driver/nand/nand_base.c中实现的通用操作函数。MTD驱动保证这些通用的操作函数支持对各种NAND flash芯片的操作。
struct mtd_info {
u_char type;
//内存技术类型,例如MTD_RAM,MTD_ROM,MTD_NORFLASH,MTD_NAND_FLASH等
u_int32_t flags;
//标志位,MTD设备的性能描述
u_int32_t size;
//MTD设备的大小
u_int32_t erasesize;
//最小的擦除块大小
u_int32_t writesize;
//编程大小
u_int32_t oobsize;
//oob(Out of band)块大小
u_int32_t ecctype;
//ECC校验类型
u_int32_t eccsize;
#define MTD_PROGREGION_CTRLMODE_VALID(mtd) (mtd)->oobsize
#define MTD_PROGREGION_CTRLMODE_INVALID(mtd) (mtd)->ecctype
char *name;
int index;
struct nand_ecclayout *ecclayout;
//eec布局结构
int numeraseregions;
//擦除区域个数,通常为1
struct mtd_erase_region_info *eraseregions;
//擦除区域的区域信息地址
u_int32_t bank_size;
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
//函数指针,erase函数的功能是将一个erase_info加入擦除队列
int (*point) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf);
//point函数功能是允许片内执行(XIP)
void (*unpoint) (struct mtd_info *mtd, u_char * addr, loff_t from, size_t len);
//unpoint函数与point函数相反,是禁止片内执行(XIP)
int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
//MTD设备的读写函数
int (*read_oob) (struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops);
int (*write_oob) (struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops);
//用于MTD设备的OBB数据读写
int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);
//访问保护寄存器区
int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);
//基于kevc的读写方法
void (*sync) (struct mtd_info *mtd);
//MTD设备的同步函数
int (*lock) (struct mtd_info *mtd, loff_t ofs, size_t len);
int (*unlock) (struct mtd_info *mtd, loff_t ofs, size_t len);
//芯片的加锁和解锁
int (*suspend) (struct mtd_info *mtd);
void (*resume) (struct mtd_info *mtd);
//支持电源管理函数
int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);
//坏块管理函数
struct notifier_block reboot_notifier;
struct mtd_ecc_stats ecc_stats; //ECC状态信息
void *priv;
//私有数据指针 ,这个指针指向MTD驱动中另一个重要的数据结构struct nand_chip
struct module *owner;
int usecount;
};
2)mtd_part结构体信息
mtd_part(mtd_part.c)是用于表示 MTD 原始设备分区的结构,其中包含了 mtd_info,因为每一个分区都是被看成一个MTD 原始设备加在 mtd_table 中的,mtd_part.mtd_info中的大部分数据都从该分区的主分区 mtd_part->master中获得。
static LIST_HEAD(mtd_partitions);
//分区链表
/* Our partition node structure */
//分区结构信息
struct mtd_part {
struct mtd_info mtd; //mtd_info数据结构体,加入到mtd_table中
struct mtd_info *master;//该分区的主分区
uint64_t offset;//该分区的偏移量
struct list_head list;
};
3)mtd_notifier结构体
mtd_notifier:MTD通知器,加入/删除MTD设备和原始设备时调用的函数,在设备层,当MTD字符设备或块设备注册时,如果定义了CONFIG_DEVFS_FS,则会将一个mtd_notifier加入MTD原始设备层的mtd_notifiers链表,其中的函数会在两种情况下被调用,一是加入/删除新的MTD字符/块设备时,此时调用该MTD字符/块设备的notifier对下层所有的MTD原始设备操作一遍,二是加入/删除新的MTD原始设备时,此时调用所有的notifier对该原始设备执行一遍。
//MTD设备通知结构体
struct mtd_notifier {
void (*add)(struct mtd_info *mtd);
//加入MTD原始/字符/块设备时执行
void (*remove)(struct mtd_info *mtd);
//移除MTD原始/字符/块设备时执行
struct list_head list;
//list是双向链表,定义在include/linux/list.h
};
4)mtd_fops 结构体
在/drivers/mtdchar.c 字符型mtd设备中:
字符设备中定义了mtd_fops字符类的文件指针操作函数,完成字符设备读、写、打开、关闭等功能。
static const struct file_operations mtd_fops = {
.owner = THIS_MODULE,
.llseek = mtdchar_lseek,
.read = mtdchar_read,
.write = mtdchar_write,
.unlocked_ioctl = mtdchar_unlocked_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = mtdchar_compat_ioctl,
#endif
.open = mtdchar_open,
.release = mtdchar_close,
.mmap = mtdchar_mmap,
#ifndef CONFIG_MMU
.get_unmapped_area = mtdchar_get_unmapped_area,
#endif
};
5)mtd_table
mtd_table(mtdcore.c)则是所有 MTD 原始设备的列表
struct mtd_info *mtd_table[MAX_MTD_DEVICES];
最多可以有MAX_MTD_DEVICES(默认定义为16)个设备,每个MTD 分区也算一个MTD 设备。
add_mtd_device添加MTD原始设备到mtd_table中
del_mtd_device从mtd_table中删除MTD原始设备
上面两个函数是底层驱动(nand flash)用来添加/删除其设置好的mtd_info结构。
6)nand_chip(重要结构体之二)
这个数据结构从结构名nand_chip就可以知道它主要针对MTD设备中NAND flash的描述。在这个数据结构中包含了许多参数和函数指针。其中的成员大致可以分为以下几类:
a)与芯片有关的参数
如:page_shift、phys_erase_shift、bbt_erase_shift、chip_shift、chipsize、numchips
b)与坏块管理、ECC校验及oob区管理有关的参数:
如:eccmode、eccsize、eccbyte、eccsteps、calculate_ecc、oob_buf、oobdirty、autooob、bbt、badblockpos、bbt_td、bbt_md、badblock_pattern
c)与NAND flash控制器寄存器操作有关的参数
如:IO_ADDR_R、IO_ADDR_W
d)与NAND flash控制器寄存器操作有关的函数指
如:read_byte、write_byte、read_word、write_word、hwcontrol、dev_ready、cmdfunc、select_chip
e)与NAND flash操作功能有关的函数指针
如:write_buf、read_buf、verify_buf、waitfunc、erase_cmd
f)与坏块管理、ECC校验及oob区管理有关的函数指针:
如:block_bad、block_markbad、calculate_ecc、correct_data、enable_hwecc、scan_bbt
综上,nand_chip主要包括了MTD驱动的低层和底层硬件操作函数的函数指针以及坏块管理、ECC校验和oob区管理需要相关函数的函数指针,这些指针指向的函数一部分在源码目录下的driver/nand/nand_base.c定义为通用函数,一部分需要用户在移植时自行编写。那些需要用户自行编写在后续的分析将会提到。nand_chip还包括了一些参数,这些参数与具体芯片型号及坏块管理策略有关,它们会在nand_scan()函数中被初始化。
struct nand_chip的结构声明
它在include/linux/mtd/nand.h中被声明定义,去掉一些编译选项、无关成员及原有注释,添加一些注释后如下;
struct nand_chip {
void __iomem *IO_ADDR_R; //NAND flash控制器的寄存器读访问指针,
void __iomem *IO_ADDR_W; //NAND flash控制器的寄存器写访问指针,
u_char (*read_byte)(struct mtd_info *mtd);
//写一字节到NAND flash控制器的寄存器函数的函数指针
void (*write_byte)(struct mtd_info *mtd, u_char byte);
//从NAND flash控制器的寄存器读一字节函数的函数指针
u16 (*read_word)(struct mtd_info *mtd); //读字函数指针
void (*write_word)(struct mtd_info *mtd, u16 word); //写字函数指针
//读写缓冲区函数指针,所谓缓冲区无非就是自行定义的一个数组
void (*write_buf)(struct mtd_info *mtd, const u_char *buf, int len);
void (*read_buf)(struct mtd_info *mtd, u_char *buf, int len);
int (*verify_buf)(struct mtd_info *mtd, const u_char *buf, int len);
//缓存校验韩式指针,验证从Flash中读取的内容是否与缓存中一致
void (*select_chip)(struct mtd_info *mtd, int chip);
//芯片片选函数指针,由于涉及底层寄存器操作该函数在移植时一般要自己编写
int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);
//读取坏块标记函数指针
int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
//标记坏块函数指针
void (*hwcontrol)(struct mtd_info *mtd, int cmd);
//寄存器访问控制函数指针,该函数根据第二个参数cmd使O_ADDR_R/W指向不同的寄存器
int (*dev_ready)(struct mtd_info *mtd);
//设备状态读取函数指针,该函数读取NAND flash控制器的NFSTAT的bit0,获取R/B状态
void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);
//向NAND flash写一个命令字,如果形参column、page_addr不都为-1则还向NAND flash写一个地址
int (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this, int state);
//操作超时处理函数指针,该函数根据命令的不同设定不同的超时时间并时刻检查R/B位直到R/B状态进入Ready状态否则为操作超时。
int (*calculate_ecc)(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code);
//ECC编码计算函数指针,该函数通过软件算法计算256字节的3字节ECC编码
int (*correct_data)(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc);
//ECC检测纠正函数,该函数检测并纠正256字节块中的1位错误。
void (*enable_hwecc)(struct mtd_info *mtd, int mode);
//使能硬件计算ECC码函数指针
void (*erase_cmd)(struct mtd_info *mtd, int page);
//擦除指定块函数的函数指针
int (*scan_bbt)(struct mtd_info *mtd);
//坏块标记表创建函数指针
int eccmode;// ECC的计算方法, NAND_ECC_SOFT时意指软件运算,即采用calculate_ecc进行运算,利用correct_data进行校正
int eccsize;// ECC校准的数据长度,用calculate_ecc进行校准时,数据长度固定为256字节
int eccbytes;// ECC校验码字节数,软件校准时为3字节
int eccsteps;// ECC校验的步数,当FLASH每页的字节数为512字节,而calculate_ecc每次仅能校验256字节,则需要两步才能校验完成。
int chip_delay;// 等待时间,一般用于等待Flash的R/B管脚
int page_shift;// 页的地址移位数,当为512(2^9)字节的空间,其该值为9
int phys_erase_shift;// 块的地址移位数
int bbt_erase_shift;// 在bbt表中,相间隔两个内容之间的地址差的位数,也即是块的地址位数
int chip_shift;// 芯片总的地址位数
u_char *data_buf; //数据缓冲区指针
u_char *oob_buf;//oob缓冲区指针
int oobdirty;// 表示oob_buf内是否有内容,如为0表示oob_buf为空(0xFF),为1表示oob_buf中已有内容
u_char *data_poi;// 指向一个临时使用的数据区
unsigned int options;
//MTD驱动中配置NAND FALSH芯片的扩展功能,如NAND_IS_AND、NAND_USE_FLASH_BBT、NAND_COPYBACK等等和重要参数,如位宽NAND_BUSWIDTH_16。上述的这些宏在include/linux/mtd/nand.h均有定义,这些宏的定义能保证当它们进行或运算是能保证options能同时接受这些扩展功能和参数。及options某bit位为1时则使用对应的扩展功能。如有:
#define NAND_COPYBACK 0x00000010
那么如果options的bit4位为1则使用copyback功能,在MTD中大量使用了这种技巧,需要注意
int badblockpos;// 坏块标记的位置。当芯片为小页面NAND flashs时值为NAND_SMALL_BADBLOCK_POS(即为5);为大页面时值为NAND_BIG _BADBLOCK_POS(即为0)
int numchips;//开发板上NAND flash芯片的片数
unsigned long chipsize;// 芯片的容量
int pagemask;//页地址掩码
int pagebuf;// 存储Data_buf内的相关的页号
struct nand_oobinfo*autooob;
// oob区布局设计结构体指针。从功能上看,oob区和数据存储区一样可以存储数据,但是oob区一般不会用于存储数据,而是做为坏块标记和ECC校验数据存储。其中坏块标记位置一般有约定俗成的位置,而oob区的布局设计有很多不同的设计,MTD使用一个结构体来描述这种设计,oob区布局设计结构体在include/linux/mtd/mtd_abi.h中定义如下:
其中useecc 为使用ECC校验标志,eccbyte为ECC校验的位数,eccpos为ECC校验所存的位号,oobfree为OOB中ECC未使用到的字节(起始地址+长度)
uint8_t *bbt;// 指向内存中坏块表的指针
struct nand_bbt_descr*bbt_td;// 用以Flash中坏块表搜索的相关描述
struct nand_bbt_descr *bbt_md;// 上述描述的镜像
struct nand_hw_control*controller;// 用以实现互锁操作,此处并未使用
void *priv;//用途不明
};
MTD 使用 nand_chip 数据结构表示一个NAND Flash 芯片,这个结构体中包含了关于 NAND Flash 的地址信息、读写方法、ECC模式、硬件控制等一系列底层机制。
read_byte 和read_word :
从NAND 芯片读一个字节或一个字,通常MTD 会在读取NAND 芯片的ID ,STATUS 和OOB 中的坏块信息时调用这两个函数,具体是这样的流程,首先MTD 调用cmdfunc 函数,发起相应的命令,NAND 芯片收到命令后就会做好准备,最后MTD 就会调用read_byte 或read_word 函数从NAND 芯片中读取芯片的ID ,STATUS 或者OOB ;
read_buf 、write_buf 和verify_buf:
分别是从NAND 芯片读取数据到buffer、把buffer 中的数据写入到NAND 芯片、和从NAND 芯片中读取数据并验证。调用read_buf 时的流程与read_byte 和read_word 类似,MTD 也是先调用cmdfunc 函数发起读命令( 如NAND_CMD_READ0 命令) ,接着NAND 芯片收到命令后做好准备,最后MTD 再调用read_buf 函数把NAND 芯片中的数据读取到buffer 中。调用write_buf 函数的流程与read_buf 相似;
select_chip :因为系统中可能有不止一片NAND 芯片,所以在对NAND 芯片进行操作前,需要这个函数来指定一片NAND 芯片
cmdfunc :向NAND 芯片发起命令;
waitfunc :
NAND 芯片在接收到命令后,并不一定能立即响应NAND controller 的下一步动作,对有些命令,比如erase ,program 等命令,NAND 芯片需要一定的时间来完成,所以就需要这个waitfunc 来等待NAND 芯片完成命令,并再次进入准备好状态;
write_page :
把一个page 的数据写入NAND 芯片,这个函数一般不需我们实现,因为它会调用struct nand_ecc_ctrl 中的write_page_raw 或者write_page 函数,关于这两个函数将在稍后介绍。
以上提到的这些函数指针,都是REPLACEABLE 的,也就是说都是可以被替换的,根据你的NAND controller ,如果你需要自己实现相应的函数,那么只需要把你的函数赋值给这些函数指针就可以了,如果你没有赋值,那么MTD 会把它自己定义的default 的函数赋值给它们。在本地代码上,以上函数指针都采用的默认的方式,通过s3c_nand_probe-》nand_scan-》nand_scan_ident-》nand_set_defaults,在该函数中以上的函数指针都被nand_base.c定义的默认函数赋值。
7)数据结构struct nand_flash_dev分析
这个数据结构主要描述的是具体的NAND flash芯片型号。这个数据结构比较简单,它在include/linux/mtd/nand.h中被声明定义为一个结构体:
struct nand_flash_dev {
char *name; //NAND flash的名称
int id; //NAND flashd 的设备ID
unsigned long pagesize;//页面大小,单位为KB
unsigned long chipsize;//芯片容量,单位为MB
unsigned long erasesize;//擦除单位大小
unsigned long options;// NAND flashd的扩展功能配置及位宽配置
};
结构体struct nand_flash_dev在drivers/mtd/nand_ids.c中这个数据结构被定义为一个结构体数组并被初始化。其中包含的每一个数组元素即为MTD驱动支持的NAND flashd芯片型号。初始化如下:
struct nand_flash_dev nand_flash_ids[] = {
………………………………………………………….
{"NAND 128MiB 1,8V 16-bit", 0x72, 512, 128, 0x4000, NAND_BUSWIDTH_16},
{"NAND 128MiB 3,3V 16-bit", 0x74, 512, 128, 0x4000, NAND_BUSWIDTH_16},
/* 1 Gigabit */
{"NAND 128MiB 1,8V 8-bit", 0xA1, 0, 128, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
{"NAND 128MiB 3,3V 8-bit", 0xF1, 0, 128, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
/* 2 Gigabit */
{"NAND 256MiB 1,8V 8-bit", 0xAA, 0, 256, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
{"NAND 256MiB 3,3V 8-bit", 0xDA, 0, 256, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
/* 4 Gigabit */
{"NAND 512MiB 1,8V 8-bit", 0xAC, 0, 512, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
/* 8 Gigabit */
{"NAND 1GiB 1,8V 8-bit", 0xA3, 0, 1024, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
/* 16 Gigabit */
{"NAND 2GiB 1,8V 8-bit", 0xA5, 0, 2048, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
{NULL,}
};
要强调的是:移植MTD驱动时一定要确定要移植的NAND flash芯片在这里有对应的nand_flash_dev结构体,没有的话要自行添加。在nand_scan()中,会先读出芯片的设备ID再到该结构体数组中逐个对比,如果没有找到匹配项则会导致在nand_scan()初始化失败返回。
三、MTD创建设备节点
MTD子系统下如何创建设备节点?
第一步:MTD设备层。(MTD子系统)
register_chrdev注册字符型mtd设备,并添加该设备到内核,主设备号为90。但是此时还未在/dev下形成mtd设备节点。
第二步:MTD原始设备层。(MTD子系统)
class_register注册一个mtd类mtd_class,后面再注册mtd设备时会用到该class。
第三步:驱动层。(调用接口完成)
在添加MTD设备同时,在原始设备层注册的那个class接口上创建了设备节点。/dev/mtdXXX出现
内核和驱动中代码执行的流程:
1)mtdchar.c:
init_mtdchar ---> __register_chrdev();---> cdev_add
2)mtdcore.c:
init_mtd --->class_register(&mtd_class);//创建mtd类
3)jz4780_nand.c:
device_create(); ---> add_mtd_partitions---> add_mtd_device ---> device_create();
其程序代码分析如
第一步:
static int __init init_mtdchar(void)
{
int ret;
ret = __register_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS,
"mtd", &mtd_fops); //申请设备号并注册
if (ret < 0) {
pr_notice("Can't allocate major number %d for "
"Memory Technology Devices.\n", MTD_CHAR_MAJOR);
return ret;
}
ret = register_filesystem(&mtd_inodefs_type); //注册mtd_inodefs_type文件系统
if (ret) {
pr_notice("Can't register mtd_inodefs filesystem: %d\n", ret);
goto err_unregister_chdev;
}
return ret;
err_unregister_chdev:
__unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd"); //释放设备号
return ret;
}
int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const charchar *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
int err = -ENOMEM;
cd = __register_chrdev_region(major, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, baseminor, count));
return err;
}
流程:
init_mtdchar ------>__register_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS,"mtd", &mtd_fops);------>cdev_add
分析:这是创建设备节点的第一步:MTD设备层。
register_chrdev注册字符型mtd设备,并添加该设备到内核,主设备号为90。但是要注意的是此时还未在/dev下形成mtd设备节点。
第二步:
static int __init init_mtd(void)
{
int ret;
ret = class_register(&mtd_class);//创建mtd类
if (ret)
goto err_reg;
ret = mtd_bdi_init(&mtd_bdi_unmappable, "mtd-unmap");//支持无映射设备
if (ret)
goto err_bdi1;
ret = mtd_bdi_init(&mtd_bdi_ro_mappable, "mtd-romap");//支持R/O映射设备
if (ret)
goto err_bdi2;
ret = mtd_bdi_init(&mtd_bdi_rw_mappable, "mtd-rwmap");//支持可写可映射设备
if (ret)
goto err_bdi3;
#ifdef CONFIG_PROC_FS
proc_mtd = proc_create("mtd", 0, NULL, &mtd_proc_ops);//创建proc文件体系接口"/proc/mtd"
#endif /* CONFIG_PROC_FS */
return 0;
err_bdi3:
bdi_destroy(&mtd_bdi_ro_mappable);
err_bdi2:
bdi_destroy(&mtd_bdi_unmappable);
err_bdi1:
class_unregister(&mtd_class);
err_reg:
pr_err("Error registering mtd class or bdi: %d\n", ret);
return ret;
}
流程:
init_mtd -> ret = class_register(&mtd_class);
分析:这是创建设备节点的第二步:MTD原始设备层。(MTD子系统)
class_register注册一个mtd类mtd_class,后面再注册mtd设备时会用到该class。
第三步:
static int jz4780_nand_probe(struct platform_device *pdev)
{
/*
* MTD register
*/
ret = mtd_device_parse_register(mtd, NULL, NULL,
pdata->part_table, pdata->num_part);
if (ret) {
dev_err(&pdev->dev, "Failed to add MTD device\n");
goto err_unreloc_hot;
}
}
int mtd_device_parse_register(struct mtd_info *mtd, const charchar **types,
struct mtd_part_parser_data *parser_data,
const struct mtd_partition *parts,
int nr_parts)
{
int err;
struct mtd_partition *real_parts;
err = parse_mtd_partitions(mtd, types, &real_parts, parser_data);
if (err <= 0 && nr_parts && parts) {
real_parts = kmemdup(parts, sizeof(*parts) * nr_parts,
GFP_KERNEL);
if (!real_parts)
err = -ENOMEM;
else
err = nr_parts;
}
if (err > 0) {
err = add_mtd_partitions(mtd, real_parts, err);
kfree(real_parts);
} else if (err == 0) {
err = add_mtd_device(mtd);
if (err == 1)
err = -ENODEV;
}
return err;
}
EXPORT_SYMBOL_GPL(mtd_device_parse_register);
int add_mtd_device(struct mtd_info *mtd)
{
struct mtd_notifier *not;
int i, error;
//设置mtd_info结构体信息
if (!mtd->backing_dev_info) {
switch (mtd->type) {
case MTD_RAM:
mtd->backing_dev_info = &mtd_bdi_rw_mappable;
break;
case MTD_ROM:
mtd->backing_dev_info = &mtd_bdi_ro_mappable;
break;
default:
mtd->backing_dev_info = &mtd_bdi_unmappable;
break;
}
}
BUG_ON(mtd->writesize == 0);//mtd写操作单位不能为0
mutex_lock(&mtd_table_mutex);//初始化mtd_table_mutex,上锁
do {
if (!idr_pre_get(&mtd_idr, GFP_KERNEL))//为mtd_idr分配内存
goto fail_locked;
error = idr_get_new(&mtd_idr, mtd, &i);//将mtd_idr和id关联起来
} while (error == -EAGAIN);//判断是否上锁,否则会报错
if (error)
goto fail_locked;
mtd->index = i;//索引值设为当前数组项的下标
mtd->usecount = 0;//引用计数设为零
/* default value if not set by driver *///没有被设置的话将会被设置成默认值
if (mtd->bitflip_threshold == 0)
mtd->bitflip_threshold = mtd->ecc_strength;
if (is_power_of_2(mtd->erasesize))//最小的擦出块大小
mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
else
mtd->erasesize_shift = 0;
if (is_power_of_2(mtd->writesize))//编程块大小
mtd->writesize_shift = ffs(mtd->writesize) - 1;
else
mtd->writesize_shift = 0;
mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
/* Some chips always power up locked. Unlock them now */
if ((mtd->flags & MTD_WRITEABLE) && (mtd->flags & MTD_POWERUP_LOCK)) {
error = mtd_unlock(mtd, 0, mtd->size);
if (error && error != -EOPNOTSUPP)
printk(KERN_WARNING
"%s: unlock failed, writes may not work\n",
mtd->name);
}
/* Caller should have set dev.parent to match the
* physical device.
*/
mtd->dev.type = &mtd_devtype;
mtd->dev.class = &mtd_class;
mtd->dev.devt = MTD_DEVT(i);
dev_set_name(&mtd->dev, "mtd%d", i);//设置mtd设备名
dev_set_drvdata(&mtd->dev, mtd);//设置mtd设备信息mtd_info
if (device_register(&mtd->dev) != 0)//注册设备
goto fail_added;
if (MTD_DEVT(i))//创建设备
device_create(&mtd_class, mtd->dev.parent,
MTD_DEVT(i) + 1,
NULL, "mtd%dro", i);
pr_debug("mtd: Giving out device %d to %s\n", i, mtd->name);
/* No need to get a refcount on the module containing
the notifier, since we hold the mtd_table_mutex */
//遍历list链表将每一个mtd_notifier执行add()函数,对新加入的mtd设备操作,通知所有的MTD user新的MTD设备的到来
list_for_each_entry(not, &mtd_notifiers, list)
not->add(mtd);
mutex_unlock(&mtd_table_mutex);//解锁信号量
/* We _know_ we aren't being removed, because
our caller is still holding us here. So none
of this try_ nonsense, and no bitching about it
either. :) */
__module_get(THIS_MODULE);
return 0;
fail_added:
idr_remove(&mtd_idr, i);
fail_locked:
mutex_unlock(&mtd_table_mutex);
return 1;
}
流程:
device_create(&mtd_class, mtd->dev.parent,MTD_DEVT(i) + 1,NULL, "mtd%dro", i);
->add_mtd_partitions->add_mtd_device-> device_create(&mtd_class, ...);
分析:这是创建设备节点的第三步:驱动层.
在添加MTD设备同时,在原始设备层注册的那个class接口上创建了设备节点。/dev/mtdXXX出现
四、Register到MTD子系统
两种注册方式:(我们需要完成的)
1)直接注册整个flash设备(MTD Device)到MTD。
ret = add_mtd_device(mtd);
2)分partion添加到mtd_table,并将每个partion当成一个mtd设备注册到MTD。
ret = add_mtd_partitions(mtd, parts,num_part);
其中:
add_mtd_partitions()->add_one_partition()
slave->mtd.xxx= master->mtd.xxx;
add_mtd_device(&slave->mtd);
从master中获得type,read,write等参数。
并将slave作为一个mtd设备添加到mtd_table。
最后注册设备及创建dev下面的设备节点。
五、NAND FLASH驱动层
Linux内核在MTD的下层实现了通用的NAND驱动( 主要通过drivers/mtd/nand/nand_base.c文件实现),因此芯片级的NAND 驱动不再需要实现mtd_info中的read()、write()、read_oob()、write_oob()等成员函数,而主体转移到了nand_chip数据结构。
MTD使用nand_chip数据结构表示一个NANDFlash芯片,这个结构体中包含了关于NAND Flash的地址信息、读写方法、ECC模式、硬件控制等一些底层机制。
驱动实现层:jz4780_nand.c
a)通过platform注册。(加载驱动)
platform_driver_register(&jz4780_nand_driver);
b)探测函数jz4780_nand_probe(重要,驱动实现的第一步)
在probe函数中申请资源、硬件初始化、实现nand_chip结构体的函数指针以及与NAND芯片进行交互,添加mtd设备到MTD。
c)实现一些nand_base.c没有实现的以及其他的一些操作一些操作如:read_buf()等。
d)remove函数
当任意一方卸载时,会释放资源,当该驱动函数或者驱动函数正在操作的设备被移除时,内核会调用驱动函数中的remove函数调用,进行一些设备卸载相应的操作
e)注销jz4780_nand驱动(卸载驱动)
platform_driver_unregister(&jz4780_nand_driver);
1、 驱动的加载程序流程
流程如下图:
具体NAND驱动加载的代码分析如下。
device_driver和platform driver
Platform device是一种device自己是不会做事情的,要有人为它做事情,那就是platform driver。platform driver遵循linux系统的driver model。对于device的discovery/enumerate都不是driver自己完成的而是由由系统的driver注册机制完成。 driver编写人员只要将注册必须的数据结构初始化并调用注册driver的kernel API就可以了。
接下来来看platform_driver结构体的原型定义,代码如下:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
可见,它包含了设备操作的几个功能函数,同时包含了一个device_driver结构,说明device_driver是 platform_driver的基类。驱动程序中需要初始化这个变量。下面看一下这个变量的定义
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
struct attribute_group **groups;
struct driver_private *p;
};
device_driver提供了一些操作接口,但其并没有实现,相当于一些虚函数,由派生类platform_driver进行重载,无论何种类型的 driver都是基于device_driver派生而来的,具体的各种操作都是基于统一的基类接口的,这样就实现了面向对象的设计。 需要注意这两个变量:name和owner。其作用主要是为了和相关的platform_device关联起来,owner的作用是说明模块的所有者,驱动程序中一般初始化为THIS_MODULE。 device_driver结构中也有一个name变量。platform_driver从字面上来看就知道是设备驱动。设备驱动是为谁服务的呢?当然是设备了。内核正是通过这个一致性来为驱动程序找到资源,即 platform_device中的resource。
1)jz4780_nand_init
static int __init jz4780_nand_init(void)
{
//创建DebugFS,是一种用于内核调试的虚拟文件系统,内核开发者通过debugfs和用户空间交换数据。
#ifdef CONFIG_DEBUG_FS
debugfs_root = debugfs_create_dir(DRVNAME, NULL);
if (IS_ERR(debugfs_root))
return PTR_ERR(debugfs_root);
#endif
return platform_driver_register(&jz4780_nand_driver);
}
2)platform_driver_registe
int platform_driver_register(struct platform_driver *drv)
{
/*设置成platform_bus_type(当前驱动所在的总线,平台设备和平台驱动处在同一条总线上 )这个很重要,因为driver和device是通过bus联系在一起的,具体在本例中是通过platform_bus_type中注册的回调例程和属性来是实现的, driver与device的匹配就是通过 platform_bus_type注册的回调例程platform_match ()来完成的。*/
表明该 dev 是 platform_bus_type 类型的,并且该 dev
也会被添加到 platform_bus_type 的 devices_kset 容器中和 klist_devices 列表中。
drv->driver.bus = &platform_bus_type;
//在really_probe函数中,回调了platform_drv_probe函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);//把驱动注册到总线上
}
代码中, 设置成 platform_bus_type 这个很重要,因为 driver 和 device 是通过 bus 联系在一起的,具体在本例中是通过 platform_bus_type 中注册的回调例程和属性来是实现的, driver 与 device 的匹配就是通过 platform_bus_type 注册的回到例程mach()来完成的。下面就分析 driver_register()。
3)driver_register
不要被上面的platform_drv_XXX吓倒了,它们其实很简单,就是将struct device转换为struct platform_device和struct platform_driver,然后调用platform_driver中的相应接口函数。那为什么不直接调用platform_drv_XXX等接口呢?这就是Linux内核中面向对象的设计思想。device_driver提供了一些操作接口,但其并没有实现,相当于一些虚函数,由派生类platform_driver进行重载,无论何种类型的 driver都是基于device_driver派生而来的,device_driver中具体的各种操作都是基于统一的基类接口的,这样就实现了面向对象的设计。
在此函数中,初始化结构体struct device_driver中的klist_device和unloaded字段,通过klist_device字段, 可以保存此驱动支持的设备链表,通过“完成”接口机制,完成线程间的同步。
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;
BUG_ON(!drv->bus->p);
// 如果总线的方法和设备自己的方法同时存在,将打印告警信息,对于platform bus,其没有probe等接口
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
printk(KERN_WARNING "Driver '%s' needs updating - please use "
"bus_type methods\n", drv->name);
/*通过驱动的名字查找 driver,如果找到了,说明已经注册过,返回错误代码,后面会
分析*/
other = driver_find(drv->name, drv->bus);
if (other) {
put_driver(other);
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
/* 将 driver 加入到 bus 的 kset,并生成些文件夹和链接文件,将程序挂在到总线上,通过总线来驱动程序
ret = bus_add_driver(drv); //将本driver驱动注册登记到drv->bus所在的总线上。
if (ret)
return ret;
/* 添加 attribute_group,本例中没有设置 drv->groups */
ret = driver_add_groups(drv, drv->groups);
if (ret)
bus_remove_driver(drv);
return ret;
}
代码中,
1)正如该例程的英文注释所言,大部分工作在 bus_add_driver()例程中完成。
2)driver_find()例程通过驱动所属 bus 的 driver 容器 drivers_kset 来查找
struct device_driver *driver_find(const char *name, struct bus_type *bus)
{
// 在 drivers_kset 容器中查找
struct kobject *k = kset_find_obj(bus->p->drivers_kset, name);
struct driver_private *priv;
if (k) {
priv = to_driver(k);
return priv->driver;
//返回找到的driver
}
return NULL;
}
代码中, 通过 kset_find_obj(bus->p->drivers_kset, name)查找该 driver 的 kobj,其代码如下,
struct kobject *kset_find_obj_hinted(struct kset *kset, const char *name,struct kobject *hint)
{
struct kobject *k;
struct kobject *ret = NULL;
spin_lock(&kset->list_lock);//锁定自旋锁
if (!hint)
goto slow_search;
/* end of list detection */
if (hint->entry.next == kset->list.next)
goto slow_search;
k = container_of(hint->entry.next, struct kobject, entry);
if (!kobject_name(k) || strcmp(kobject_name(k), name))
goto slow_search;
ret = kobject_get(k);
goto unlock_exit;
slow_search:
list_for_each_entry(k, &kset->list, entry) {// 遍历 kset->list 列表获取 kobj
if (kobject_name(k) && !strcmp(kobject_name(k), name)) { // 比较 name 字符
ret = kobject_get(k); // 如果找到就增加引用并返回
break;
}
}
unlock_exit:
spin_unlock(&kset->list_lock);//释放自旋锁
if (hint) //这里hint为NULL
kobject_put(hint);
return ret;
}
4)bus_add_driver(struct device_driver *drv)
显然,所有同类型的 driver 都注册到了 一个 bus->p->drivers_kset->list 中,所以可通过其查找已经注册的 driver。下面分析 bus_add_driver()例程,该代码主要用来将驱动设备注册到bus总线中。
int bus_add_driver(struct device_driver *drv) //将本driver驱动注册登记到drv->bus所在的总线上。
{
struct bus_type *bus;
struct driver_private *priv;
int error = 0;
bus = bus_get(drv->bus);//获取总线内容,即前面定义的
if (!bus)
return -EINVAL;
pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
//为驱动私有成员申请内存且清0, 这个结构体中存放着 kobj 相关的数据,主要存放的是私有的数据。
if (!priv) {
error = -ENOMEM;
goto out_put_bus;
}
klist_init(&priv->klist_devices, NULL, NULL);//初始化驱动所支持的设备链表
priv->driver = drv; // 将匹配的 driver 指针关联到 dev,以便后续使用
drv->p = priv; // 将 priv 保存到 device_driver,
/* 指向 bus 的 drivers_kset 容器,该容器的作用与 device_kset 容器相同,前者是包含所有注册到该 bus 的 driver,后者是包含所有注册到该 bus 的 device。*/
priv->kobj.kset = bus->p->drivers_kset;
/*初始化 priv->kobj,并将其添加到 bus->p->drivers_kset 中,在本例中生成/sys/bus/platform/drivers/jz4780_nand 目录,后面会分析 drivers_kset 的初始化及/sys/bus/platform/drivers/目录的生成 */
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,"%s", drv->name);
//初始化并添加驱动kobject对象到总线驱动中
if (error)
goto out_unregister;
if (drv->bus->p->drivers_autoprobe) {// 在 bus_register()例程中已经设置为 1 了
error = driver_attach(drv); // 所以会寻找匹配的 device,后面分析
if (error)
goto out_unregister;
}
// 将 driver 链接到 klist_drivers,方便后续快速查找 driver//将驱动节点加入到总线链表中
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
// 将 driver 添加到 module 模块,后面会分析
module_add_driver(drv->owner, drv);
/* 生成/sys//sys/bus/platform/drivers/属性文件,其作用与 device 中的
uevent 类似*/
error = driver_create_file(drv, &driver_attr_uevent);
if (error) {
printk(KERN_ERR "%s: uevent attr (%s) failed\n",__func__, drv->name);
}
/* 添加 bus 的公有属性文件到/sys//sys/bus/platform/drivers/目录,所有的 driver
都添加 */
error = driver_add_attrs(bus, drv);
if (error) {
/* How the hell do we get out of this pickle? Give up */
printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",__func__, drv->name);
}
if (!drv->suppress_bind_attrs) {
/* 如果配置了”CONFIG_HOTPLUG",则生成“bind”和"unbind”属性文件,可用于手动匹
配和移除 device 与 driver 之间的关联 */
error = add_bind_files(drv);
if (error) {
/* Ditto */
printk(KERN_ERR "%s: add_bind_files(%s) failed\n",__func__, drv->name);
}
}
// 通过 uevent 设置几个环境变量并通知用户空间,以便调用程序来完成相关设置
kobject_uevent(&priv->kobj, KOBJ_ADD);
return 0;
out_unregister:
kobject_put(&priv->kobj);
kfree(drv->p);
drv->p = NULL;
out_put_bus:
bus_put(bus);
return error;
}
代码中,struct driver_private 来存放 kobj 相关的数据,将 struct driver_private 嵌入在 struct
device_driver 中。struct driver_private 定义如下:
struct driver_private {
struct kobject kobj;
//代表该bus,里面的kobj是该bus的主kobj,也就是最顶层
struct klist klist_devices;
//其作用与devices_kset->list作用相同
struct klist_node knode_bus;
struct module_kobject *mkobj;
struct device_driver *driver;
};
5)driver_attach(struct device_driver *drv)
//如果总线上的driver是自动probe的话,则将该总线上的driver和device绑定起来。
/**
* driver_attach - try to bind driver to devices.
* @drv: driver.
* Walk the list of devices that the bus has on it and try to
* match the driver with each one. If driver_probe_device()
* returns 0 and the @dev->driver is set, we've found a
* compatible pair.
*/
int driver_attach(struct device_driver *drv)
{
/* 遍历 bus 的 klist_devices 列表,对每个 device 使用回调函数__driver_attach()来鉴别是否
和 driver 匹配 */
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
EXPORT_SYMBOL_GPL(driver_attach);
int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
{
struct klist_iter i;
struct device *dev;
int error = 0;
if (!bus)
return -EINVAL;
klist_iter_init_node(&bus->p->klist_devices, &i, //设置i
(start ? &start->p->knode_bus : NULL));
while ((dev = next_device(&i)) && !error)//使用i进行遍历
error = fn(dev, data);//使用回调例程,处理
klist_iter_exit(&i);
return error;
}
EXPORT_SYMBOL_GPL(bus_for_each_dev);
//扫描该总线上的每一个设备,将当前driver和总线上的设备进行match,如果匹配成功,则将设备和driver绑定起来。
回调例程__driver_attach()
函数__driver_attach()在调用driver_probe_device()函数前,需要进行线程间的互斥处理。
6)__driver_attach(struct device *dev, void *data)
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
/*
* Lock device and try to bind to it. We drop the error
* here and always return 0, because we need to keep trying
* to bind to devices and some drivers will return an error
* simply if it didn't support the device.
* driver_probe_device() will spit a warning if there
* is an error.
*/
/* 调用 bus 的 match (),在这里是 platform_bus_type 的 mach(),即 platform_match()例
程 */
if (!driver_match_device(drv, dev))
return 0;
if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
if (!dev->driver) //如果该设备尚没有匹配的driver,则尝试匹配。
driver_probe_device(drv, dev);// 这里开始 probe
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
return 0;
}
7)driver_probe_device(struct device_driver *drv, struct device *dev)
在driver_probe_device()函数中,调用了match函数platform_match(),如果它返回0,
表示驱动与设备不一致,函数返回;否则,调用really_probe()函数。
/** * driver_probe_device - attempt to bind device & driver together
* @drv: driver to bind a device to * @dev: device to try to bind to the driver * * First,
/**
* driver_probe_device - attempt to bind device & driver together
* @drv: driver to bind a device to
* @dev: device to try to bind to the driver
* This function returns -ENODEV if the device is not registered,
* 1 if the device is bound successfully and 0 otherwise.
* This function must be called with @dev lock held. When called for a
* USB interface, @dev->parent lock must be held as well.
*/
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0;
if (!device_is_registered(dev))//判断dev是否已经注册
return -ENODEV;
pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
pm_runtime_get_noresume(dev);
pm_runtime_barrier(dev);
ret = really_probe(dev, drv);
pm_runtime_put_sync(dev);
return ret;
}
8)really_probe(struct device *dev, struct device_driver *drv)
//在此函数中,回调了我们在jz4780.c文件中实现的探测函数jz4780_nand_probe(),至此,平台驱动的注册过程结束。
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
atomic_inc(&probe_count);
//原子锁上锁
pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
drv->bus->name, __func__, drv->name, dev_name(dev));
WARN_ON(!list_empty(&dev->devres_head));
dev->driver = drv;
// 将匹配的 driver 指针关联到 dev,以便后续使用
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
// 如果设置了 dev->bus->probe,则调用,在 platform_bus_type 没有设置
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
// 将 device 添加到 driver 列表中,并通知 bus 上的设备,表明 BOUND_DRIVER。
//设备与驱动绑定后,对系统中已注册的组件进行事件通知。
driver_bound(dev);
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
goto done;
probe_failed:
devres_release_all(dev);
driver_sysfs_remove(dev);
dev->driver = NULL;
if (ret != -ENODEV && ret != -ENXIO) {
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev_name(dev), ret);
}
/*
* Ignore errors returned by ->probe so that the next driver can try
* its luck.
*/
ret = 0;
done:
atomic_dec(&probe_count);
wake_up(&probe_waitqueue);
return ret;
}
2、驱动中probe函数的分析
其中探测函数代码执行流程为:
在probe函数中主要是完成了NAND芯片级的初始化,主要有以下几个作用:
-分配nand_chip内存,根据目标板及NAND控制器初始化nand_chip中成员函数(若未初始化则使用nand_base.c中的默认函数),将mtd_info中的priv指向nand_chip(或板相关私有结构),设置ecc模式及处理函数
-以mtd_info为参数调用nand_scan_ident()和nand_scan_tail()函数,探测NAND Flash并进行初始化。
nand_scan_ident()会读取nand芯片ID,并根据mtd->priv即nand_chip中成员初始化mtd_info
nand_scan_tail()进行了ECC的设置和剩下的MTD驱动函数的初始化。
-若有分区,则以mtd_info和mtd_partition为参数调用add_mtd_partitions()添加分区信息
nand_scan_ident()
首先初始化mtd_info中一个重要的指针priv,使这个指针指向nand_chip变量;
然后调用了同文件下的nand_set_defaults()和nand_get_flash_type(),nand_set_defaults()函数对struct nand_chip结构体的函数指针进行了赋值。在此函数中cmdfunc映射到了nand_command,nand_get_flash_type()读取了厂商和设备ID进行匹配,并对struct nand_chip结构体的变量进行初始化操作。
nand_scan_tail()
a)初始化oob区、ecc校验相关参数和函数指针。
b)初始化MTD驱动接口函数。
c)调用nand_bbt()创建坏块表。
probe程序代码分析如下:
static int jz4780_nand_probe(struct platform_device *pdev)
{
int ret = 0;
int bank = 0;
int i = 0, j = 0, k = 0, m = 0;
int eccpos_start;
struct nand_chip *chip;
struct mtd_info *mtd;
struct jz4780_nand *nand;
struct jz4780_nand_platform_data *pdata;
nand_flash_if_t *nand_if;
《1》
/*
* sanity check主要是获取平台设备信息设备的相关参数
*/
pdata = dev_get_platdata(&pdev->dev);//获取平台设备的数据
if (!pdata) {
dev_err(&pdev->dev, "Failed to get platform_data.\n");
return -ENXIO;
}
nand = kzalloc(sizeof(struct jz4780_nand), GFP_KERNEL);//为设备分配内存空间
if (!nand) {
dev_err(&pdev->dev,
"Failed to allocate jz4780_nand.\n");
return -ENOMEM;
}
nand->pdev = pdev;
nand->pdata = pdata;
platform_set_drvdata(pdev, nand);//将nand保存为平台总线的私有数据,将nand设备的数据信息传递到系统平台设备中去
nand->num_nand_flash_if = pdata->num_nand_flash_if;//所用nand flash的片数
nand->xfer_type = pdata->xfer_type;
nand->ecc_type = pdata->ecc_type;
《2》
/*
* request GPEMC banks获取每一个bank的相关参数(busy,protect,timeout..)申请cs_gpio引脚
*/
for (i = 0; i < nand->num_nand_flash_if; i++, j = i) {
nand_if = &pdata->nand_flash_if_table[i];
nand->nand_flash_if_table[i] = nand_if;
bank = nand_if->bank;
ret = gpemc_request_cs(&pdev->dev, &nand_if->cs, bank);
if (ret) {
dev_err(&pdev->dev,
"Failed to request busy"
" gpio irq for bank%d\n", bank);
goto err_free_busy_irq;
}
}
《3》
/*
* request busy GPIO interrupt申请busy_gpio引脚
*/
switch (nand->xfer_type) {
case NAND_XFER_CPU_IRQ:
case NAND_XFER_DMA_IRQ://中断方式读取数据
for (i = 0; i < nand->num_nand_flash_if; i++, k = i) {
nand_if = &pdata->nand_flash_if_table[i];
if (nand_if->busy_gpio < 0)
continue;
ret = request_busy_irq(nand_if);
if (ret) {
dev_err(&pdev->dev,
"Failed to request busy"
" gpio irq for bank%d\n", bank);
goto err_free_busy_irq;
}
}
break;
case NAND_XFER_CPU_POLL:
case NAND_XFER_DMA_POLL://poll机制轮询读取数据(类似与底半部方式)
for (i = 0; i < nand->num_nand_flash_if; i++, k = i) {
nand_if = &pdata->nand_flash_if_table[i];
if (nand_if->busy_gpio < 0)
continue;
ret = request_busy_poll(nand_if);
if (ret) {
dev_err(&pdev->dev,
"Failed to request busy"
" gpio irq for bank%d\n", bank);
goto err_free_busy_irq;
}
}
nand->busy_poll = 1;
break;
default:
WARN(1, "Unsupport transfer type.\n");
BUG();
break;
}
《4》
/*
* request WP GPIO//申请写保护引脚
*/
for (i = 0; i < nand->num_nand_flash_if; i++, m = i) {
nand_if = &pdata->nand_flash_if_table[i];
if (nand_if->wp_gpio < 0)
continue;
if (!gpio_is_valid(nand_if->wp_gpio)) {
dev_err(&pdev->dev,
"Invalid wp GPIO:%d\n", nand_if->wp_gpio);
ret = -EINVAL;
goto err_free_wp_gpio;
}
bank = nand_if->bank;
ret = gpio_request(nand_if->wp_gpio, label_wp_gpio[bank]);
if (ret) {
dev_err(&pdev->dev,
"Failed to request wp GPIO:%d\n", nand_if->wp_gpio);
goto err_free_wp_gpio;
}
gpio_direction_output(nand_if->wp_gpio, 0);//设定gipo为输出模式
/* Write protect disabled by default */
jz4780_nand_enable_wp(nand_if, 0); //disable 写保护
}
《5》
/*
* NAND flash devices support list override
*/
nand->nand_flash_table = pdata->nand_flash_table ?
pdata->nand_flash_table : builtin_nand_flash_table;
nand->num_nand_flash = pdata->nand_flash_table ?
pdata->num_nand_flash :
ARRAY_SIZE(builtin_nand_flash_table);
/*
* attach to MTD subsystem
链接MTD的子系统-----涉及到如何传输数据
* struct nand_chip是一个与NAND芯片密切相关的结构体,主要包含三方面内 容:
*指向一些操作NAND芯片的函数的指针;
*表示NAND芯片特性的成员变量,主要有:
*与ecc,oob和bbt (bad block table)相关的一些结构体,对于坏块及坏块管理
*/
chip = &nand->chip;
chip->chip_delay = MAX_RB_DELAY_US;
chip->cmdfunc = jz4780_nand_command;
chip->dev_ready = jz4780_nand_dev_is_ready;
chip->select_chip = jz4780_nand_select_chip;
chip->cmd_ctrl = jz4780_nand_cmd_ctrl;
chip->onfi_get_features = jz4780_nand_onfi_get_features;
chip->onfi_set_features = jz4780_nand_onfi_set_features;
switch (nand->xfer_type) {
case NAND_XFER_DMA_IRQ:
case NAND_XFER_DMA_POLL:
/*
* DMA transfer DMA方式传输数据
*/
ret = jz4780_nand_request_dma(nand);
if (ret) {
dev_err(&pdev->dev, "Failed to request DMA channel.\n");
goto err_free_wp_gpio;
}
chip->read_buf = jz4780_nand_read_buf;//将芯片中的数据读到缓冲区中
chip->write_buf = jz4780_nand_write_buf;//将缓冲区中的数据写入芯片
nand->use_dma = 1;
break;
case NAND_XFER_CPU_IRQ:
case NAND_XFER_CPU_POLL:
/*
* CPU transfer CPU方式传输数据
*/
chip->read_buf = jz4780_nand_cpu_read_buf;
chip->write_buf = jz4780_nand_cpu_write_buf;
jz4780_nand_write_buf 和 jz4780_nand_read_buf:这是两个最基本的操作函数,其功能,就是往你的Nand Flash的控制器中的FIFO读写数据。一般情况下,是MTD上层的操作,比如要读取一页的数据,那么在发送完相关的读命令和等待时间之后,就会调用到你底层的read_buf,去Nand Flash的FIFO中,一点点把我们要的数据,读取出来,放到我们制定的内存的缓存中去。写操作也是类似,将我们内存中的数据,写到Nand Flash的FIFO中去。
break;
default:
WARN(1, "Unsupport transfer type.\n");
BUG();
break;
}
mtd = &nand->mtd;//填充mtd_info结构体相关信息
mtd->priv = chip;//把指向struct nand_chip结构体的指针赋给struct mtd_info的priv成员变量,
//因为MTD Core中很多函数之间的调用都只传递struct mtd_info,它需要通过priv成员变量得到struct nand_chip。
mtd->name = dev_name(&pdev->dev);
mtd->owner = THIS_MODULE;
/*
* if you use u-boot BBT creation code,specifying
* this flag will let the kernel fish out the BBT
* from the NAND, and also skip the full NAND scan
* that can take 1/2s or so. little things...
*/
if (pdata->flash_bbt) {//当flashbbt=1的时候系统在启动的时候将跳过对bbt(bad block table)的扫描
chip->bbt_options |= NAND_BBT_USE_FLASH;
chip->options |= NAND_SKIP_BBTSCAN;
}
/*
* nand_base handle subpage write by fill space
* where are outside of the subpage with 0xff,
* that make things totally wrong, so disable it.
*/
chip->options |= NAND_NO_SUBPAGE_WRITE;
/*
* for relocation
*/
nand->gpemc_enable_nand_flash = gpemc_enable_nand_flash;
nand->nand_wait_ready = nand_wait_ready;
nand->gpio_get_value = gpio_get_value;
nand->wait_for_completion_timeout = wait_for_completion_timeout;
nand->msecs_to_jiffies = msecs_to_jiffies;
nand->printk = printk;
nand->udelay = __udelay;
nand->ndelay = __ndelay;
《6》
/*
* Detect NAND flash chips 侦测NAND flash chips
*/
/* step1. relax bank timings to scan 定时扫描空闲的bank*/
for (bank = 0; bank < nand->num_nand_flash_if; bank++) {
nand_if = nand->nand_flash_if_table[bank];
gpemc_relax_bank_timing(&nand_if->cs);
}
if (nand_scan_ident(mtd, nand->num_nand_flash_if,
nand->nand_flash_table)) {
ret = -ENXIO;
dev_err(&pdev->dev, "Failed to detect NAND flash.\n");
goto err_dma_release_channel;
}
/*
* post configure bank timing by detected NAND device 通过侦测的NAND设备配置bank时序寄存器
*/
/* step1. match NAND chip information */
nand->curr_nand_flash_info = jz4780_nand_match_nand_chip_info(nand);
if (!nand->curr_nand_flash_info) {
ret = -ENODEV;
goto err_dma_release_channel;
}
/*
* step2. preinitialize NAND flash 预初始化NAND闪存
*/
ret = jz4780_nand_pre_init(nand);
if (ret) {
dev_err(&nand->pdev->dev, "Failed to"
" preinitialize NAND chip.\n");
goto err_dma_release_channel;
}
/* step3. replace NAND command function with large page version */// 用对页面的操作命令函数替代NAND的功能命令函数
if (mtd->writesize > 512)
chip->cmdfunc = jz4780_nand_command_lp;
/* step4. configure bank timings */ //配置BANK的时序
switch (nand->curr_nand_flash_info->type) {
case BANK_TYPE_NAND:
for (bank = 0; bank < nand->num_nand_flash_if; bank++) {
nand_if = nand->nand_flash_if_table[bank];
gpemc_fill_timing_from_nand(&nand_if->cs,
&nand->curr_nand_flash_info->
nand_timing.common_nand_timing);//对bank时序进行赋值操作
ret = gpemc_config_bank_timing(&nand_if->cs);//对bank时序寄存器进行配置
if (ret) {
dev_err(&pdev->dev,
"Failed to configure timings for bank%d\n"
, nand_if->bank);
goto err_dma_release_channel;
}
}
break;
case BANK_TYPE_TOGGLE:
for (bank = 0; bank < nand->num_nand_flash_if; bank++) {
nand_if = nand->nand_flash_if_table[bank];
gpemc_fill_timing_from_toggle(&nand_if->cs,
&nand->curr_nand_flash_info->
nand_timing.toggle_nand_timing);
ret = gpemc_config_bank_timing(&nand_if->cs);
if (ret) {
dev_err(&pdev->dev,
"Failed to configure timings for bank%d\n"
, nand_if->bank);
goto err_dma_release_channel;
}
}
break;
default:
WARN(1, "Unsupported NAND type.\n");
BUG();
break;
}
《7》
/*
* initialize ECC control//初始化NAND ECC控制器
*/
/* step1. configure ECC step */
switch (nand->ecc_type) {
case NAND_ECC_TYPE_SW: //软件实现ECC检测
/*
* valid ECC configuration ?
*/
if (nand->curr_nand_flash_info->
ecc_step.data_size % 8
|| nand->curr_nand_flash_info->
ecc_step.ecc_bits % 8) {
ret = -EINVAL;
dev_err(&nand->pdev->dev, "Failed when configure ECC,"
" ECC size, and ECC bits must be a multiple of 8.\n");//ECC的大小和ECC位大小必须是8位
goto err_dma_release_channel;
}
chip->ecc.mode = NAND_ECC_SOFT_BCH;//ECC算法的模式为软件BCH模式
chip->ecc.size =
nand->curr_nand_flash_info->ecc_step.data_size;
chip->ecc.bytes = (fls(8 * chip->ecc.size) *
(nand->curr_nand_flash_info->ecc_step.ecc_bits) + 7) / 8;
break;
case NAND_ECC_TYPE_HW: //硬件实现ECC的检测,选取的算法同样是BCH
nand->bch_req.dev = &nand->pdev->dev;
nand->bch_req.complete = jz4780_nand_bch_req_complete;
nand->bch_req.ecc_level =
nand->curr_nand_flash_info->ecc_step.ecc_bits;
nand->bch_req.blksz =
nand->curr_nand_flash_info->ecc_step.data_size;
nand->bch_req.errrept_data = kzalloc(MAX_ERRREPT_DATA_SIZE,
GFP_KERNEL);
if (!nand->bch_req.errrept_data) {
dev_err(&pdev->dev,
"Failed to allocate ECC errrept_data buffer\n");
ret = -ENOMEM;
goto err_dma_release_channel;
}
init_completion(&nand->bch_req_done);
chip->ecc.mode = NAND_ECC_HW;
chip->ecc.calculate = jz4780_nand_ecc_calculate_bch;
chip->ecc.correct = jz4780_nand_ecc_correct_bch;
chip->ecc.hwctl = jz4780_nand_ecc_hwctl;
chip->ecc.size =
nand->curr_nand_flash_info->ecc_step.data_size;
chip->ecc.bytes = bch_ecc_bits_to_bytes(
nand->curr_nand_flash_info->ecc_step.ecc_bits);
chip->ecc.strength = nand->bch_req.ecc_level;
break;
default :
WARN(1, "Unsupported ECC type.\n");
BUG();
break;
}
/*
/* step2. generate ECC layout *///产生出ECC的布局
/*
* eccbytes = eccsteps * eccbytes_prestep;//计算ECC的字节数
*/
nand->ecclayout.eccbytes =
mtd->writesize / chip->ecc.size * chip->ecc.bytes;
//判断是否ECC字节数超出OOB的空间大小
if (mtd->oobsize < (nand->ecclayout.eccbytes +
chip->badblockpos + 2)) {
WARN(1, "ECC codes are out of OOB area.\n");
BUG();
}
/*
* ECC codes are right aligned ECC码为右对齐
* start position = oobsize - eccbytes 起始位置的计算
*/
eccpos_start = mtd->oobsize - nand->ecclayout.eccbytes; //ECC码的起始位置
for (bank = 0; bank < nand->ecclayout.eccbytes; bank++)
nand->ecclayout.eccpos[bank] = eccpos_start + bank;
nand->ecclayout.oobfree->offset = chip->badblockpos + 2;
nand->ecclayout.oobfree->length =
mtd->oobsize - (nand->ecclayout.eccbytes
+ chip->badblockpos + 2);
chip->ecc.layout = &nand->ecclayout;
《8》
/*
* second phase NAND scan //第二阶段NAND扫描
*/
if (nand_scan_tail(mtd)) {
ret = -ENXIO;
goto err_free_ecc;
}
#ifdef CONFIG_DEBUG_FS
nand->debugfs_entry = jz4780_nand_debugfs_init(nand);
if (IS_ERR(nand->debugfs_entry)) {
dev_err(&pdev->dev, "Failed to register debugfs entry.\n");
ret = PTR_ERR(nand->debugfs_entry);
goto err_free_ecc;
}
#endif
/*
* relocate hot functions to TCSM
*/
if (pdata->try_to_reloc_hot) {
ret = jz4780_nand_reloc_hot_to_tcsm(nand);
if (ret) {
dev_err(&pdev->dev, "Failed to relocate hot functions.\n");
goto err_debugfs_remove;
}
}
《9》
/*
* MTD register
*/
ret = mtd_device_parse_register(mtd, NULL, NULL,
pdata->part_table, pdata->num_part);
if (ret) {
dev_err(&pdev->dev, "Failed to add MTD device\n");
goto err_unreloc_hot;
}
dev_info(&pdev->dev,
"Successfully registered JZ4780 SoC NAND controller driver.\n");
return 0;
err_unreloc_hot:
if (pdata->try_to_reloc_hot)
jz4780_nand_unreloc_hot_from_tcsm(nand);
err_debugfs_remove:
#ifdef CONFIG_DEBUG_FS
debugfs_remove_recursive(nand->debugfs_entry);
#endif
err_free_ecc:
if (pdata->ecc_type == NAND_ECC_TYPE_HW)
kfree(nand->bch_req.errrept_data);
err_dma_release_channel:
if (nand->xfer_type == NAND_XFER_DMA_IRQ ||
nand->xfer_type == NAND_XFER_DMA_POLL)
dma_release_channel(nand->dma_pipe_nand.chan);
err_free_wp_gpio:
for (bank = 0; bank < m; bank++) {
nand_if = &pdata->nand_flash_if_table[bank];
if (nand_if->wp_gpio < 0)
continue;
gpio_free(nand_if->wp_gpio);
}
err_free_busy_irq:
for (bank = 0; bank < k; bank++) {
nand_if = &pdata->nand_flash_if_table[bank];
if (nand_if->busy_gpio < 0)
continue;
if (pdata->xfer_type == NAND_XFER_CPU_IRQ ||
pdata->xfer_type ==NAND_XFER_DMA_IRQ)
free_irq(nand_if->busy_irq, nand_if);
gpio_free(nand_if->busy_gpio);
}
err_release_cs:
for (bank = 0; bank < j; bank++) {
nand_if = &pdata->nand_flash_if_table[bank];
gpemc_release_cs(&nand_if->cs);
}
kfree(nand);
return ret;
}
nand_scan_ident()程序代码分析:
int nand_scan_ident(struct mtd_info *mtd, int maxchips,struct nand_flash_dev *table)
{
int i, busw, nand_maf_id, nand_dev_id;
struct nand_chip *chip = mtd->priv;
// mtd->priv在probe函数中被初始化为数据结构nand_chip的变量nand_chip[i],所以这里的this指针指向的就是变量nand_chip[i]。
struct nand_flash_dev *type;
/* Get buswidth to select the correct functions */
busw = chip->options & NAND_BUSWIDTH_16;
//位宽设置,options bit1设置为0是busw为0,表示位宽为8。options会在该函数后续被初始化为nand_flash_ids[i].options。如果用户需要配置扩展功能只能在nand_flash_ids[i].options配置。
/* Set the default functions */
nand_set_defaults(chip, busw);
//nand_set_defaults()函数对struct nand_chip结构体的函数指针进行了赋值。在此函数中cmdfunc映射到了nand_command,
/* Set default functions */
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->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);
}
}
/* Read the flash type */
type = nand_get_flash_type(mtd, chip, busw,
&nand_maf_id, &nand_dev_id, table);
//nand_get_flash_type()读取了厂商和设备ID,并对struct nand_chip结构体的变量进行初始化操作
if (IS_ERR(type)) {
if (!(chip->options & NAND_SCAN_SILENT_NODEV))
pr_warn("No NAND device found\n");
chip->select_chip(mtd, -1);
return PTR_ERR(type);
}
chip->select_chip(mtd, -1);
/* Check for a chip array */
for (i = 1; i < maxchips; i++) {
chip->select_chip(mtd, i);
/* See comment in nand_get_flash_type for reset */
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
/* Send the command for reading device ID */
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
/* Read manufacturer and device IDs */
if (nand_maf_id != chip->read_byte(mtd) ||
nand_dev_id != chip->read_byte(mtd)) {
chip->select_chip(mtd, -1);
break;
}
chip->select_chip(mtd, -1);
}
if (i > 1)
pr_info("%d NAND chips detected\n", i);
/* Store the number of chips and calc total size for mtd */
chip->numchips = i;
mtd->size = i * chip->chipsize;
return 0;
}
EXPORT_SYMBOL(nand_scan_ident);
nand_scan_tail()代码分析:
int nand_scan_tail(struct mtd_info *mtd)
{
int i;
struct nand_chip *chip = mtd->priv;
/* New bad blocks should be marked in OOB, flash-based BBT, or both */
BUG_ON((chip->bbt_options & NAND_BBT_NO_OOB_BBM) &&
!(chip->bbt_options & NAND_BBT_USE_FLASH));
if (!(chip->options & NAND_OWN_BUFFERS))
chip->buffers = kmalloc(sizeof(*chip->buffers), GFP_KERNEL);
if (!chip->buffers)
return -ENOMEM;
/* Set the internal oob buffer location, just after the page data */
chip->oob_poi = chip->buffers->databuf + mtd->writesize;//设置缓存位置,仅仅需要在数据页后面就行
* If no default placement scheme is given, select an appropriate one.如果没有预设的方案,就选择合适的一个
*/
if (!chip->ecc.layout && (chip->ecc.mode != NAND_ECC_SOFT_BCH)) {
switch (mtd->oobsize) {
case 8:
chip->ecc.layout = &nand_oob_8;
break;
case 16:
chip->ecc.layout = &nand_oob_16;
break;
case 64:
chip->ecc.layout = &nand_oob_64;
break;
case 128:
chip->ecc.layout = &nand_oob_128;
break;
default:
pr_warn("No oob scheme defined for oobsize %d\n",
mtd->oobsize);
BUG();
}
}
if (!chip->write_page)
chip->write_page = nand_write_page;
/* set for ONFI nand *///设置ONFI NAND的功能
if (!chip->onfi_set_features)
chip->onfi_set_features = nand_onfi_set_features;
if (!chip->onfi_get_features)
chip->onfi_get_features = nand_onfi_get_features;
//对chip结构题的ecc相关的初始化
/*
* Check ECC mode, default to software if 3byte/512byte hardware ECC is
* selected and we have 256 byte pagesize fallback to software ECC
*///如果硬件检测选择的是3/512那么我们默认软件ECC模式,我们每页有256bytes为ECC备用
// /*以下都是对chip赋值,对应nand_chip中的函数*/
/*Nand_scan是在初始化nand的时候对nand进行的一步非常好重要的操作,
*在nand_scan中会对我们所写的关于特定芯片的读写函数重载到nand_chip结构中去,
*并会将mtd_info结构体中的函数用nand的函数来重载,实现了mtd到底层驱动的联系。
*并且在nand_scan函数中会通过读取nand芯片的设备号和厂家号自动在芯片列表中寻找相应的型号和参数,并将其注册进去。*/
switch (chip->ecc.mode) {
case NAND_ECC_HW_OOB_FIRST:
/* Similar to NAND_ECC_HW, but a separate read_page handle */
if (!chip->ecc.calculate || !chip->ecc.correct ||
!chip->ecc.hwctl) {
pr_warn("No ECC functions supplied; "
"hardware ECC not possible\n");
BUG();
}
if (!chip->ecc.read_page)
chip->ecc.read_page = nand_read_page_hwecc_oob_first;
case NAND_ECC_HW:
/* Use standard hwecc read page function? */
if (!chip->ecc.read_page)
chip->ecc.read_page = nand_read_page_hwecc;
if (!chip->ecc.write_page)
chip->ecc.write_page = nand_write_page_hwecc;
if (!chip->ecc.read_page_raw)
chip->ecc.read_page_raw = nand_read_page_raw;
if (!chip->ecc.write_page_raw)
chip->ecc.write_page_raw = nand_write_page_raw;
if (!chip->ecc.read_oob)
chip->ecc.read_oob = nand_read_oob_std;
if (!chip->ecc.write_oob)
chip->ecc.write_oob = nand_write_oob_std;
case NAND_ECC_HW_SYNDROME:
if ((!chip->ecc.calculate || !chip->ecc.correct ||
!chip->ecc.hwctl) &&
(!chip->ecc.read_page ||
chip->ecc.read_page == nand_read_page_hwecc ||
!chip->ecc.write_page ||
chip->ecc.write_page == nand_write_page_hwecc)) {
pr_warn("No ECC functions supplied; "
"hardware ECC not possible\n");
BUG();
}
/* Use standard syndrome read/write page function? */
if (!chip->ecc.read_page)
chip->ecc.read_page = nand_read_page_syndrome;
if (!chip->ecc.write_page)
chip->ecc.write_page = nand_write_page_syndrome;
if (!chip->ecc.read_page_raw)
chip->ecc.read_page_raw = nand_read_page_raw_syndrome;
if (!chip->ecc.write_page_raw)
chip->ecc.write_page_raw = nand_write_page_raw_syndrome;
if (!chip->ecc.read_oob)
chip->ecc.read_oob = nand_read_oob_syndrome;
if (!chip->ecc.write_oob)
chip->ecc.write_oob = nand_write_oob_syndrome;
if (mtd->writesize >= chip->ecc.size) {
if (!chip->ecc.strength) {
pr_warn("Driver must set ecc.strength when using hardware ECC\n");
BUG();
}
break;
}
pr_warn("%d byte HW ECC not possible on "
"%d byte page size, fallback to SW ECC\n",
chip->ecc.size, mtd->writesize);
chip->ecc.mode = NAND_ECC_SOFT;
case NAND_ECC_SOFT:
chip->ecc.calculate = nand_calculate_ecc;
chip->ecc.correct = nand_correct_data;
chip->ecc.read_page = nand_read_page_swecc;
chip->ecc.read_subpage = nand_read_subpage;
chip->ecc.write_page = nand_write_page_swecc;
chip->ecc.read_page_raw = nand_read_page_raw;
chip->ecc.write_page_raw = nand_write_page_raw;
chip->ecc.read_oob = nand_read_oob_std;
chip->ecc.write_oob = nand_write_oob_std;
if (!chip->ecc.size)
chip->ecc.size = 256;
chip->ecc.bytes = 3;
chip->ecc.strength = 1;
break;
case NAND_ECC_SOFT_BCH:
if (!mtd_nand_has_bch()) {
pr_warn("CONFIG_MTD_ECC_BCH not enabled\n");
BUG();
}
chip->ecc.calculate = nand_bch_calculate_ecc;
chip->ecc.correct = nand_bch_correct_data;
chip->ecc.read_page = nand_read_page_swecc;
chip->ecc.read_subpage = nand_read_subpage;
chip->ecc.write_page = nand_write_page_swecc;
chip->ecc.read_page_raw = nand_read_page_raw;
chip->ecc.write_page_raw = nand_write_page_raw;
chip->ecc.read_oob = nand_read_oob_std;
chip->ecc.write_oob = nand_write_oob_std;
/*
* Board driver should supply ecc.size and ecc.bytes values to
* select how many bits are correctable; see nand_bch_init()
* for details. Otherwise, default to 4 bits for large pag
* devices.
*/
if (!chip->ecc.size && (mtd->oobsize >= 64)) {
chip->ecc.size = 512;
chip->ecc.bytes = 7;
}//初始化NAND BCH 纠错
chip->ecc.priv = nand_bch_init(mtd,
chip->ecc.size,
chip->ecc.bytes,
&chip->ecc.layout);//建立坏块表
if (!chip->ecc.priv) {
pr_warn("BCH ECC initialization failed!\n");
BUG();
}
//Driver must set ecc.strength when using hardware ECC
chip->ecc.strength =
chip->ecc.bytes * 8 / fls(8 * chip->ecc.size);
break;
case NAND_ECC_NONE:
pr_warn("NAND_ECC_NONE selected by board driver. "
"This is not recommended!\n");
chip->ecc.read_page = nand_read_page_raw;
chip->ecc.write_page = nand_write_page_raw;
chip->ecc.read_oob = nand_read_oob_std;
chip->ecc.read_page_raw = nand_read_page_raw;
chip->ecc.write_page_raw = nand_write_page_raw;
chip->ecc.write_oob = nand_write_oob_std;
chip->ecc.size = mtd->writesize;
chip->ecc.bytes = 0;
chip->ecc.strength = 0;
break;
default:
pr_warn("Invalid NAND_ECC_MODE %d\n", chip->ecc.mode);
BUG();
}
/* For many systems, the standard OOB write also works for raw */
if (!chip->ecc.read_oob_raw)
chip->ecc.read_oob_raw = chip->ecc.read_oob;
if (!chip->ecc.write_oob_raw)
chip->ecc.write_oob_raw = chip->ecc.write_oob;
/*
* The number of bytes available for a client to place data into
* the out of band area.
*/
chip->ecc.layout->oobavail = 0;
for (i = 0; chip->ecc.layout->oobfree[i].length
&& i < ARRAY_SIZE(chip->ecc.layout->oobfree); i++)
chip->ecc.layout->oobavail +=
chip->ecc.layout->oobfree[i].length;
mtd->oobavail = chip->ecc.layout->oobavail;
/*
* Set the number of read / write steps for one page depending on ECC
* mode.
*/
chip->ecc.steps = mtd->writesize / chip->ecc.size;
if (chip->ecc.steps * chip->ecc.size != mtd->writesize) {
pr_warn("Invalid ECC parameters\n");
BUG();
}
chip->ecc.total = chip->ecc.steps * chip->ecc.bytes;
//subpage相关的初始化
/* Allow subpage writes up to ecc.steps. Not possible for MLC flash */
if (!(chip->options & NAND_NO_SUBPAGE_WRITE) &&
!(chip->cellinfo & NAND_CI_CELLTYPE_MSK)) {
switch (chip->ecc.steps) {
case 2:
mtd->subpage_sft = 1;
break;
case 4:
case 8:
case 16:
mtd->subpage_sft = 2;
break;
}
}
chip->subpagesize = mtd->writesize >> mtd->subpage_sft;
/* Initialize state */
chip->state = FL_READY;
/* Invalidate the pagebuffer reference */
chip->pagebuf = -1;
//本开发板用不到
/* Large page NAND with SOFT_ECC should support subpage reads */
if ((chip->ecc.mode == NAND_ECC_SOFT) && (chip->page_shift > 9))
chip->options |= NAND_SUBPAGE_READ;
//初始化剩余的mtd_info结构题
/* Fill in remaining MTD driver data */
mtd->type = MTD_NANDFLASH;
mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM :
MTD_CAP_NANDFLASH;
mtd->_erase = nand_erase;
mtd->_point = NULL;
mtd->_unpoint = NULL;
mtd->_read = nand_read;
mtd->_write = nand_write;
mtd->_panic_write = panic_nand_write;
mtd->_read_oob = nand_read_oob;
mtd->_write_oob = nand_write_oob;
mtd->_sync = nand_sync;
mtd->_lock = NULL;
mtd->_unlock = NULL;
mtd->_suspend = nand_suspend;
mtd->_resume = nand_resume;
mtd->_block_isbad = nand_block_isbad;
mtd->_block_markbad = nand_block_markbad;
mtd->writebufsize = mtd->writesize;
//把chip中的ecc信息传递给mtd结构题,初始化mtd_info
/* propagate ecc info to mtd_info */
mtd->ecclayout = chip->ecc.layout;
mtd->ecc_strength = chip->ecc.strength;
/*
* Initialize bitflip_threshold to its default prior scan_bbt() call.
* scan_bbt() might invoke mtd_read(), thus bitflip_threshold must be
* properly set.
*/
if (!mtd->bitflip_threshold)
mtd->bitflip_threshold = mtd->ecc_strength;
/* Check, if we should skip the bad block table scan */
//判断是否跳过坏块表检测
if (chip->options & NAND_SKIP_BBTSCAN)
return 0;
/* Build bad block table *///扫描并建立坏块表
return chip->scan_bbt(mtd);
}
EXPORT_SYMBOL(nand_scan_tail);
六、驱动层之Flash读操作
MTD对NAND芯片的读写 主要分三部分:
A、struct mtd_info中的读写函数,如read,write_oob等,这是MTD原始设备层与FLASH硬件层之间的接口;
B、struct nand_ecc_ctrl中的读写函数,如read_page_raw,write_page等,主要用来做一些与ecc有关的操作;
C、struct nand_chip中的读写函数,如read_buf,cmdfunc等,与具体的NANDcontroller相关,就是这部分函数与硬件交互,通常需要我们自己来实现。
注: nand_chip中的读写函数虽然与具体的NAND controller相关,但是MTD也为我们提供了默认的读写函数,如果NAND controller比较通用(使用PIO模式),那么对NAND芯片的读写与MTD提供的这些函数一致,就不必自己实现这些函数。
上面三部分读写函数相互配合完成对NAND芯片的读写,具体流程如下:
首先,MTD上层需要读写NAND芯片时,会调用struct mtd_info中的读写函数,接着struct mtd_info中的读写函数就会调用struct nand_chip或struct nand_ecc_ctrl中的读写函数,最后,若调用的是struct nand_ecc_ctrl中的读写函数,那么它又会接着调用struct nand_chip中的读写函数。
以读为例:
MTD上层会调用struct mtd_info中的读page函数,即nand_read函数。
接着nand_read函数会调用struct nand_chip中cmdfunc函数,这个cmdfunc函数与具体的NAND controller相关,它的作用是使NAND controller向NAND芯片发出读命令,NAND芯片收到命令后,就会做好准备等待NAND controller下一步的读取。接着nand_read函数又会调用struct nand_ecc_ctrl中的read_page函数,而read_page函数又会调用struct nand_chip中read_buf函数,从而真正把NAND芯片中的数据读取到buffer中(所以这个read_buf的意思其实应该是read into buffer,另外,这个buffer是struct mtd_info中的nand_read函数传下来的)。
read_buf函数返回后,read_page函数就会对buffer中的数据做一些处理,比如校验ecc,以及若数据有错,就根据ecc对数据修正之类的,最后read_page函数返回到nand_read函数中。
对NAND芯片的其它操作,如写,擦除等,都与读操作类似
JZ4780之NAND FLASH读函数调用流程:
mtd上层选中并调用mtd_info中的读函数
->nand_read(mtd_info)
->nand_do_read_ops
->chip->cmdfunc(mtd,NAND_CMD_READ0, 0x00, page);
->chip->ecc.read_page()
->read_buf()(read into buffer)
-> 调用一系列函数进行相关的ecc校验
问题:nand_chip(nand flash的描述符)的读写操作是怎么和MTD的读写操作联系起来的呢?
a)probe->scan_tail;
在填充MTD的时候,使用mtd->read = nand_read;这里和mtd挂钩。
b)在nand_read实现中又调用了nand_do_read_ops(mtd,from, &chip->ops);这里和nand_chip联系起来了。
以读为例对代码进行分析如下:
1)nand_read代码如下:
static int nand_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, uint8_t *buf)
{
struct mtd_oob_ops ops;
int ret;
nand_get_device(mtd, FL_READING);
ops.len = len;
ops.datbuf = buf;
ops.oobbuf = NULL;
ops.mode = MTD_OPS_PLACE_OOB;
ret = nand_do_read_ops(mtd, from, &ops);
*retlen = ops.retlen;
nand_release_device(mtd);
return ret;
}
2)nand_do_read_ops代码如下:
static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops)
{
/******省略****/
。。。。。。。。。。。。。。
while(1) {
/******省略****/
.。。。。。。。。。。。。。。。
if (likely(sndcmd)) {/*#define NAND_CMD_READ0 0*/
/*1)***读取数据前肯定要先发送对应的读页命令******/
chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
sndcmd = 0;
}
/* Now read the page into the buffer */
if (unlikely(ops->mode == MTD_OOB_RAW))
ret = chip->ecc.read_page_raw(mtd, chip,bufpoi, page);
else if (!aligned && NAND_SUBPAGE_READ(chip) && !oob)
ret = chip->ecc.read_subpage(mtd, chip, col, bytes, bufpoi);
else
/******执行到这里read_page函数读取对应的数据了******/
ret = chip->ecc.read_page(mtd, chip, bufpoi,page);
if (ret < 0)
break;
/* Transfer not aligned data */
if (!aligned) {
if (!NAND_SUBPAGE_READ(chip) && !oob)
chip->pagebuf = realpage;
memcpy(buf, chip->buffers->databuf + col, bytes);
}
buf += bytes;
。。。。。。。。。。。。。。。。。。
if (mtd->ecc_stats.failed - stats.failed)
return -EBADMSG;
return mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0;
}
上面这些代码都不需要我们去实现的,使用MTD层的自定义代码就行。下面将要分析chip->cmdfunc,我们从probe函数中可以知道
/* step3. replace NAND command function with large page version */
if (mtd->writesize > 512)
chip->cmdfunc = jz4780_nand_command_lp;
3)jz4780_nand_command_lp的分析
static void jz4780_nand_command_lp(struct mtd_info *mtd,
unsigned int command, int column, int page_addr)
{
register struct nand_chip *chip = mtd->priv;
struct jz4780_nand *nand;
nand_flash_if_t *nand_if;
nand_flash_info_t *nand_info;
nand = mtd_to_jz4780_nand(mtd);
nand_if = nand->nand_flash_if_table[nand->curr_nand_flash_if];
nand_if->curr_command = command;
nand_info = nand->curr_nand_flash_info;
/* Emulate NAND_CMD_READOOB */
if (command == NAND_CMD_READOOB) {
column += mtd->writesize;
command = NAND_CMD_READ0;
}
/* Command latch cycle */
/* 此处就是就是发送读命令的第一个周期1st Cycle的命令,即0x00, 对应着上述步骤中的① */
chip->cmd_ctrl(mtd, command & 0xff,
NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
jz4780_nand_delay_after_command(nand, nand_info, command);
if (column != -1 || page_addr != -1) {
int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE;
/* Serially input address */
/* 发送两个column列地址,对应着上述步骤中的② */
if (column != -1) {
chip->cmd_ctrl(mtd, column, ctrl);
ctrl &= ~NAND_CTRL_CHANGE;
chip->cmd_ctrl(mtd, column >> 8, ctrl);
}
if (page_addr != -1) {
/* 接下来是发送三个Row,行地址,对应着上述步骤中的② */
chip->cmd_ctrl(mtd, page_addr, ctrl);
chip->cmd_ctrl(mtd, page_addr >> 8,
NAND_NCE | NAND_ALE);
/* One more address cycle for devices > 128MiB */
if (chip->chipsize > (128 << 20))
chip->cmd_ctrl(mtd, page_addr >> 16,
NAND_NCE | NAND_ALE);
}
}
jz4780_nand_delay_after_address(nand, nand_info, command);
chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
switch (command) {
... .... /* *省略* */
/* 接下来发送读命令的第二个周期2nd Cycle的命令,即0x30,对应着 上述步骤中的④ */
case NAND_CMD_READ0:
chip->cmd_ctrl(mtd, NAND_CMD_READSTART,
NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
chip->cmd_ctrl(mtd, NAND_CMD_NONE,
NAND_NCE | NAND_CTRL_CHANGE);
/* This applies to read commands */
default:
/*
* If we don't have access to the busy pin, we apply the given
* command delay.
*/
if (!chip->dev_ready) {
nand->udelay(chip->chip_delay);
return;
}
}
/*
* Apply this short delay always to ensure that we do wait tWB in
* any case on any machine.
*/
/* 此处是对应着④中的tWB的等待时间*/
nand->ndelay(100);
/* 接下来就是要等待一定的时间,使得Nand Flash硬件上准备好数据,以供你之后读取,即对应着步骤⑤ */
nand->nand_wait_ready(mtd);
}
/*还有一个步骤没有实现那就是步骤⑥了一点一点的把数据读出来*/
4)nand_read_page_hwecc分析
static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
uint8_t *buf, int oob_required, int page)
{
int i, eccsize = chip->ecc.size;
int eccbytes = chip->ecc.bytes;
int eccsteps = chip->ecc.steps;
uint8_t *p = buf;
uint8_t *ecc_calc = chip->buffers->ecccalc;
uint8_t *ecc_code = chip->buffers->ecccode;
uint32_t *eccpos = chip->ecc.layout->eccpos;
unsigned int max_bitflips = 0;
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
chip->ecc.hwctl(mtd, NAND_ECC_READ);
chip->read_buf(mtd, p, eccsize);//这个函数必须有我们来实现
chip->ecc.calculate(mtd, p, &ecc_calc[i]);
}
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
for (i = 0; i < chip->ecc.total; i++)
ecc_code[i] = chip->oob_poi[eccpos[i]];
eccsteps = chip->ecc.steps;
p = buf;
for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
int stat;
stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]);
if (stat < 0) {
mtd->ecc_stats.failed++;
} else {
mtd->ecc_stats.corrected += stat;
max_bitflips = max_t(unsigned int, max_bitflips, stat);
}
}
return max_bitflips;
}
上面的 read_buf,就是真正的去读取数据的函数了,由于不同的Nand Flash controller 控制器所实现的方式不同,所以这个函数必须在你的 Nand Flash驱动中实现,即MTD 层,能帮我们实现的都实现了,不能实现的,那肯定是我们自己的事情了。。。
七、如何调试新的NAND FLASH芯片
调试新的NAND FLASH芯片步骤如下:
1、驱动程序的修改,例如:jz4780_nand.c中的修改
1)加入NAND flash 名称和 Nand flash 设备id
*******************************************************
* NAND flash chip name & ID 加入
*******************************************************
例如:
#defineNAND_FLASH_MT29F32G08CBACAWP_NAME "MT29F32G08CBACAWP“
#defineNAND_FLASH_MT29F32G08CBACAWP_ID 0x68
2)加入 NAND flash 匹配信息(这里用的是重载)
*******************************************************
* Supported NAND flash chips
*******************************************************
数值 builtin_nand_flash_table[] 中,加入 NAND flash匹配信息
例如:
{ NAND_FLASH_MT29F32G08CBACAWP_NAME,NAND_FLASH_MT29F32G08CBACAWP_ID, 0,4096, 0, LP_OPTIONS },
3)加入NAND flash 芯片信息
*******************************************************
* Supported NAND flash chips 加入
*******************************************************
加入NAND flash芯片信息
例如:
{ /*
* Datasheet of MT29F32G08CBACA, Rev-E, P109,Table-17
* ECC : 24bit/1080bytes
*/
COMMON_NAND_CHIP_INFO(NAND_FLASH_MT29F32G08CBACA3W_NAME,NAND_MFR_MICRON, NAND_FLASH_MT29F32G08CBACA3W_ID,
1024, 24, 0,10,5, 10, 5, 15, 5, 7, 5, 10, 7,
20, 20, 70, 200, 100, 60, 200,10, 20, 0, 100,
100, 100 *1000, 0, 0, 0, 5, BUS_WIDTH_8,
NAND_OUTPUT_NORMAL_DRIVER, NAND_RB_DOWN_FULL_DRIVER,
micron_nand_pre_init)
},
其中包含了针对特定Nandflash的:
.ECC 强度
.时序信息
.驱动力调整
.预初始化函数
更具体的定义,请参考arch/mips/xburst/soc-4780/include/mach/jz4780_nand.h
4)修改于预始化函数
根据目标芯片的芯片手册,配置Timing mode 、Programmable output drive strength以及ProgrammableRB# pull-down strength等等。
例如:
根据MT29F32G08CBCAC手册中的ConfigurationOperations
在函数micron_nand_pre_init(structjz4780_nand *nand)中进行相关配置
2、修改mtd/nand/nand_ids.c文件
定义了两个全局类型的结构体:
structnand_flash_devnand_flash_ids[ ]
structnand_manufacturersnand_manuf_ids[ ]
其中前者定义了一些NAND芯片的类型,后者定义了NAND芯片的几个厂商。NAND芯片的ID至少包含两项内容:厂商ID和厂商为自己的NAND芯片定义的芯片ID。
当NAND驱动被加载的时候,它会去读取具体NAND芯片的ID,然后根据读取的内容到上述定义的nand_manuf_ids[ ]和nand_flash_ids[ ]两个结构体中去查找,以此判断该NAND芯片是那个厂商的产品,以及该NAND芯片的类型。若查找不到,则NAND驱动就会加载失败,因此在开发NAND驱动前必须事先将你的NAND芯片添加到这两个结构体中去(其实这两个结构体中已经定义了市场上绝大多数的NAND芯片,所以除非你的NAND芯片实在比较特殊,否则一般不需要额外添加)。
例如:
nand_manuf_ids[]
{NAND_MFR_MICRON, "Micron"},
nand_flash_ids[]
{"NAND 4GiB 3,3V 8-bit",0x68, 0,4096, 0, LP_OPTIONS},
3、对bootloader中相关文件的修改
1)在bootable/bootloader/xboot/boot/nand/nand_ids.c中添加相应的nand的相关信息,这里以MT29F32G08为例:
{"MICRON_MT29F32G08CBADAWP",0x2C44, 0x00A94B44, 2, 1, 10, 5, 15, 15, 100, 60, 200, 20, 100, 70, 0, 8192,2048*1024, 744, 3, 74, 2128, 1024, 40,8, 0, MICRON_NAND, 0x03, 0x02, 0x01, 0x00, 0x04},
以上关于nand的信息,包括id,extid,plane的个数,页大小,块大小,冗余区大小,采用多少位的ecc,以及对应的几个时间参数都要按照芯片手册上的说明进行填写,这样才可以进行nand的读取写入等操作
2)在bootable/bootloader/xboot/include/asm/jz_mem_nand_configs/目录下添加新增nand对应的头文件:NAND_MT29F32G08CBACA.h,内容如下:
#ifndef __NAND_CONFIG_H#define__NAND_CONFIG_H
/* *
*This file contains the nand configuration parameters for thecygnusboard.
* */
/*-----------------------------------------------------------------------
* * NAND FLASH configuration * */
#define CONFIG_NAND_K9GAG08U0D#defineCFG_NAND_BCH_BIT 24 /*Speif () {
y the hardware BCH algorithm for nand(4|8) */
#define CFG_NAND_BW8 1 /* Data bus width: 0-16bit, 1-8bit */
#define CFG_NAND_PAGE_SIZE 4096
#define CFG_NAND_OOB_SIZE 224 /* Size of OOB space per page (e.g. 64 128 etc.) */
#define CFG_NAND_ROW_CYCLE 3
#define CFG_NAND_BLOCK_SIZE (1024 << 10) /* NAND chip block size*/
#define CFG_NAND_BADBLOCK_PAGE 0 /* NAND bad block was marked at this page in a block, starting from 0 */
#define CFG_NAND_TOTAL_BLOCKS (1024*4) /*4Gnand*/
#define X_DELAY_TRR 20
#define X_DELAY_TADL 70
#define X_DELAY_TWHR 60
#endif /* __NAND_CONFIG_H */~
这里同样要正确设置nand的相关信息,比如ecc是多少位,页大小,块大小,冗余区大小等,如果这里设置的不对,在启动时候会出错。
3)在bootable/bootloader/xboot/include/configs/grus.h中包含新加的头文件:
#include“asm/jz_mem_nand_configs/NAND_MT29F32G08CBACA.h”