在基于MTD 的NAND driver 的probe 函数中,主要可以分为两部分内容,其一是与很多外设driver 类似的一些工作,如申请地址,中断,DMA 等资源,kzalloc 及初始化一些结构体,分配DMA 用的内存等等;其二就是与MTD 相关的一些特定的工作,在这里我们将只描述第二部分内容。
(1)probe 函数中与MTD 相关的结构体
在probe 函数中,我们需要为三个与MTD 相关的结构体分配内存以及初始化,它们是struct mtd_info 、struct mtd_partition 和struct nand_chip 。其中前两者已经在四节做过说明,这里只对struct nand_chip 做一些介绍。struct nand_chip 是一个与NAND 芯片密切相关的结构体,主要包含三方面内容:
A)指向一些操作NAND 芯片的函数的指针,稍后将对这些函数指针作一些说明;
B)表示NAND 芯片特性的成员变量,主要有:
unsigned int options :与具体的NAND 芯片相关的一些选项,如NAND_BUSWIDTH_16 等,可以参考<linux/mtd/nand.h>
int page_shift :用位表示的NAND 芯片的page 大小,如某片NAND 芯片的一个page 有512 个字节,那么page_shift 就是9 ;
int phys_erase_shift :用位表示的NAND 芯片的每次可擦除的大小,如某片NAND 芯片每次可擦除16K 字节( 通常就是一个block 的大小) ,那么phys_erase_shift 就是14 ;
int bbt_erase_shift :用位表示的bad block table 的大小,通常一个bbt 占用一个block ,所以bbt_erase_shift 通常与phys_erase_shift 相等;
int numchips :表示系统中有多少片NAND 芯片;
unsigned long chipsize :NAND 芯片的大小;
int pagemask :计算page number 时的掩码,总是等于chipsize/page 大小 - 1 ;
int pagebuf :用来保存当前读取的NAND 芯片的page number ,这样一来,下次读取的数据若还是属于同一个page ,就不必再从NAND 芯片读取了,而是从data_buf 中直接得到;
int badblockpos :表示坏块信息保存在oob 中的第几个字节。对于绝大多数的NAND 芯片,若page size > 512 ,那么坏块信息从Byte 0 开始存储,否则就存储在Byte 5 ,即第六个字节。
C. 与ecc ,oob 和bbt (bad block table) 相关的一些结构体,对于坏块及坏块管理,将在稍后做专门介绍。
(2)对NAND 芯片进行实际操作的函数
前面已经说过,MTD 为我们提供了许多default 的操作NAND 的函数,这些函数与具体的硬件( 即NAND controller) 相关,而现有的NAND controller 都有各自的特性和配置方式,MTD 当然不可能为所有的NAND controller 都提供一套这样的函数,所以在MTD 中定义的这些函数只适用于通用的NAND controller( 使用PIO 模式) 。
如果你的NAND controller 在操作或者说读写NAND 时有自己独特的方式,那就必须自己定义适用于你的NAND controller 的函数。一般来说,这些与硬件相关的函数都在struct nand_chip 结构体中定义,或者应该说是给此结构体中的函数指针赋值。
struct nand_chip {
void __iomem * IO_ADDR_R;
void __iomem * IO_ADDR_W;
uint8_t ( * read_byte) ( struct mtd_info * mtd) ;
u16 ( * read_word) ( struct mtd_info * mtd) ;
void ( * write_buf) ( struct mtd_info * mtd, const uint8_t * buf, int len) ;
void ( * read_buf) ( struct mtd_info * mtd, uint8_t * buf, int len) ;
int ( * verify_buf) ( struct mtd_info * mtd, const uint8_t * buf, int len) ;
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 ( * cmd_ctrl) ( struct mtd_info * mtd, int dat, unsigned int ctrl) ;
int ( * dev_ready) ( struct mtd_info * mtd) ;
void ( * cmdfunc) ( struct mtd_info * mtd, unsigned command, int column, int page_addr) ;
int ( * waitfunc) ( struct mtd_info * mtd, struct nand_chip * this ) ;
void ( * erase_cmd) ( struct mtd_info * mtd, int page) ;
int ( * scan_bbt) ( struct mtd_info * mtd) ;
int ( * errstat) ( struct mtd_info * mtd, struct nand_chip * this , int state, int status, int page) ;
int ( * write_page) ( struct mtd_info * mtd, struct nand_chip * chip, const uint8_t * buf, int page, int cached, int raw) ;
……
struct nand_ecc_ctrl ecc;
……
}
IO_ADDR_R 和IO_ADDR_W :8 位NAND 芯片的读写地址,如果你的NAND controller 是用PIO 模式与NAND 芯片交互,那么只要把这两个值赋上合适的地址,就完全可以使用MTD 提供的default 的读写函数来操作NAND 芯片了。所以这两个变量视具体的NAND controller 而定,不一定用得着;
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定义的默认函数赋值。
顺便提一下,以上所说的读写NAND 芯片的流程并不是唯一的,如果你的NAND controller 在读写NAND 芯片时有自己独特的方式,那么完全可以按照自己的方式来做。就比如我们公司芯片的NAND controller ,因为它使用DMA 的方式从NAND 芯片中读写数据,所以在我的NAND driver 中,读数据的流程是这样的:首先在cmdfunc 函数中初始化DMA 专用的buffer ,配置NAND 地址,发起命令等,在cmdfunc 中我几乎做了所有需要与NAND 芯片交互的事情,总之等cmdfunc 函数返回后,NAND 芯片中的数据就已经在DMA 专用的buffer 中了,之后MTD 会再调用read_buf 函数,所以我的read_buf 函数其实只是把数据从DMA 专用的buffer 中,拷贝到MTD 提供的buffer 中罢了。
(3)最后,struct nand_chip 结构体中还包含了一个很重要的结构体,即struct nand_ecc_ctrl ,它的定义如下:
struct nand_ecc_ctrl {
……
void ( * hwctl) ( struct mtd_info * mtd, int mode) ;
int ( * calculate) ( struct mtd_info * mtd, const uint8_t * dat, uint8_t * ecc_code) ;
int ( * correct) ( struct mtd_info * mtd, uint8_t * dat, uint8_t * read_ecc, uint8_t * calc_ecc) ;
int ( * read_page_raw) ( struct mtd_info * mtd, struct nand_chip * chip, uint8_t * buf) ;
void ( * write_page_raw) ( struct mtd_info * mtd, struct nand_chip * chip, const uint8_t * buf) ;
int ( * read_page) ( struct mtd_info * mtd, struct nand_chip * chip, uint8_t * buf) ;
void ( * write_page) ( struct mtd_info * mtd, struct nand_chip * chip, const uint8_t * buf) ;
int ( * read_oob) ( struct mtd_info * mtd, struct nand_chip * chip, int page, int sndcmd) ;
int ( * write_oob) ( struct mtd_info * mtd, struct nand_chip * chip, int page) ;
} ;
hwctl :这个函数用来控制硬件产生ecc ,其实它主要的工作就是控制NAND controller 向NAND 芯片发出NAND_ECC_READ 、NAND_ECC_WRITE 和NAND_ECC_READSYN 等命令,与struct nand_chip 结构体中的cmdfunc 类似,只不过发起的命令是ECC 相关的罢了;
calculate :根据data 计算ecc 值;
correct :根据ecc 值,判断读写数据时是否有错误发生,若有错,则立即试着纠正,纠正失败则返回错误;
read_page_raw 和write_page_raw :从NAND 芯片中读取一个page 的原始数据和向NAND 芯片写入一个page 的原始数据,所谓的原始数据,即不对读写的数据做ecc 处理,该读写什么值就读写什么值。另外,这两个函数会读写整个page 中的所有内容,即不但会读写一个page 中MAIN 部分,还会读写OOB 部分。
read_page 和write_page :与read_page_raw 和write_page_raw 类似,但不同的是,read_page 和write_page 在读写过程中会加入ecc 的计算,校验,和纠正等处理。
read_oob 和write_oob :读写oob 中的内容,不包括MAIN 部分。
其实,以上提到的这几个read_xxx 和write_xxx 函数,最终都会调用struct nand_chip 中的read_buf 和write_buf 这两个函数,所以如果没有特殊需求的话,我认为不必自己实现,使用MTD 提供的default 的函数即可。
参考原文:http://blog.csdn.net/leibniz_zsu/archive/2009/12/10/4977853.aspx