将nand视作一个MTD设备
uboot将nand视作一个mtd设备,所以使用mtd机制对nand设备进行管理。单个nand设备用nand_info_t来描述。而nand_info_t实际上就是mtd结构。最多支持的nand设备数CONFIG_SYS_MAX_NAND_DEVICE可由开发者自行配置。不过一般目标板上只有一块Nand设备,所以取值通常为1。
typedef struct mtd_info nand_info_t;
nand_info_t nand_info[CONFIG_SYS_MAX_NAND_DEVICE];
但是仅使用mtd结构来描述不够,因为MTD只是一个通用的存储描述结构,而Nand设备特定的某些属性,如ECC布局等不能简单的添加到mtd结构中。所以,uboot定义了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 (*init_size)(struct mtd_info *mtd, struct nand_chip *this,
u8 *id_data);
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);int chip_delay;
unsigned int options;int page_shift;
int phys_erase_shift;
int bbt_erase_shift;
int chip_shift;
int numchips;
uint64_t chipsize;
int pagemask;
int pagebuf;
int subpagesize;
uint8_t cellinfo;
int badblockpos;
int badblockbits;int onfi_version;
#ifdef CONFIG_SYS_NAND_ONFI_DETECTION
struct nand_onfi_params onfi_params;
#endifint state;
uint8_t *oob_poi;
struct nand_hw_control *controller;
struct nand_ecclayout *ecclayout;struct nand_ecc_ctrl ecc;
struct nand_buffers *buffers;
struct nand_hw_control hwcontrol;struct mtd_oob_ops ops;
uint8_t *bbt;
struct nand_bbt_descr *bbt_td;
struct nand_bbt_descr *bbt_md;struct nand_bbt_descr *badblock_pattern;
void *priv;
};
然后,分配了CONFIG_SYS_MAX_NAND_DEVICE个nand_chip结构。
struct nand_chip nand_chip[CONFIG_SYS_MAX_NAND_DEVICE];
最后,在nand_init_chip()中通过以下代码关联nand_info结构和nand_chip结构:
mtd->priv = nand;
这样做使得一个MTD结构既可以描述MTD结构的通用性,又能在需要时访问nand设备特定的属性和操作函数。
基本的nand操作算法
uboot提供了nand设备的通用操作算法,这些操作算法集中在nand_base.c。Nand驱动层的初始化由nand_init()完成,该函数调用nand_init_chip()逐个初始化nand_info表中的各项。而nand_init_chip()则调用board_nand_init()完成目标板Nand控制器初始化,mtd结构中驱动接口函数初始化,之后调用nand_scan()自动检测该控制器上的nand芯片信息。检测完毕后,再调用nand_register()向MTD管理器中注册该设备。
其中nand_scan最为关键,它负责检测nand的芯片型号,并根据型号填充nand操作算法接口。
nand_scan操作分为两步。
第一步,nand_scan_ident()会尝试读取芯片ID,并根据ID计算出器件的属性,然后将该属性及对应的操作接口函数填充到nand_chip结构中。
第二步,nand_scan_tail()负责设置ECC布局,并且根据设定的ECC模式填充ECC相关算法函数,以及使用nand操作函数填充MTD结构中操作接口函数。
具体的算法函数比较多,且多与硬件设备有关,所以这里不深究细节,不再详细分析。
驱动移植最基本的接口
对于移植而言,开发者需要做的是重新定义board_nand_init(),在该函数中完成NAND设备中的MTD结构部分接口函数和属性的设置。其中某些属性是必须设置的,如下所示:
int board_nand_init(struct nand_chip *nand)
nand->IO_ADDR_R = (void __iomem *)(NFDATA);
nand->IO_ADDR_W = (void __iomem *)(NFDATA);
nand->cmd_ctrl = s3c_nand_hwcontrol;
nand->dev_ready = s3c_nand_device_ready;
nand->ecc.mode = NAND_ECC_HW;
如果具体到一个特定的Nand设备,为了能访问该设备,驱动代码能够向nand设备发送命令、读写数据、检测器件是否忙。虽然Nand型号非常多;但是其基本接口都一致,并且读写Nand块、页中数据的算法都是类似的。所以,uboot在nand_base.c中提供了与nand控制器无关的操作算法,而与硬件相关的部分则由用户驱动实现。这样做的好处便是用户不需要自己再实现这些操作。
ECC管理机制
由于Nand本身的特性,在读写过程中容易出现错误,即存储器中的某一个或多个位可能会失效,而无法再写入。为了处理方便,这些错误位的所在的整个页或者整个块就不能再用,常见的做法是将这个块标记为坏块,不再使用。而Nand块通常为几十K到几百K,为了避免仅仅因为错误几个位导致整个块浪费,所以使用了ECC机制。借助存储在OOB区的ECC码,可以检测出读取的数据是否有错误位。而当出现的错误位数在容许的范围内时,可对数据进行纠正,保证读取数据正确,避免丢弃整个块。
当然,ECC码只能纠正错误位数较少的情况,错误太多时用ECC也是无法纠正的,只能丢弃整个块。即便如此,ECC的存在能减少浪费。
uboot使用nand_ecc_ctrl结构来定义ecc相关的操作模式和接口。
struct nand_ecc_ctrl {
nand_ecc_modes_t mode;
int steps;
int size;
int bytes;
int total;
int prepad;
int postpad;
struct nand_ecclayout *layout;
void *priv;
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, int page);
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, int page);
int (*read_subpage)(struct mtd_info *mtd, struct nand_chip *chip, uint32_t offs, uint32_t len, 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);
};
对于ECC操作而言,最主要的两个操作为根据输入计算ECC码(calculate)和根据ECC码对数据进行纠错(correct)。
在board_nand_init()中初始化nand驱动接口时,可以根据需要初始化ECC操作接口。s5pv210的部分设置代码如下:
nand->ecc.mode = NAND_ECC_HW;
nand->ecc.hwctl = s3c_nand_enable_hwecc;
nand->ecc.calculate = s3c_nand_calculate_ecc;
nand->ecc.correct = s3c_nand_correct_data;
nand->ecc.read_page = s3c_nand_read_page_8bit;
nand->ecc.write_page = s3c_nand_write_page_8bit;
nand->ecc.read_oob = s3c_nand_read_oob_8bit;
nand->ecc.write_oob = s3c_nand_write_oob_8bit;
nand->ecc.layout = &s3c_nand_oob_128;
nand->ecc.hwctl = s3c_nand_enable_hwecc_8bit;
nand->ecc.calculate = s3c_nand_calculate_ecc_8bit;
nand->ecc.correct = s3c_nand_correct_data_8bit;
nand->ecc.size = 512;
nand->ecc.bytes = 13;
nand->options |= NAND_NO_SUBPAGE_WRITE;
ECC码计算方式有两种,一种是纯粹通过uboot软件计算得出;二是调用nand控制器由硬件计算出ECC。通常情况下,以使用硬件ECC计算为主。
ECC的分组处理
通常ECC码需要分组计算,即将整个nand页划分为成多个部分,每部分数据计算一组ECC码,最后统一放置到OOB区。例如,s5pv210的8Bit硬件ECC计算,每512字节生成一组ECC,单个页的ECC计算代码如下:
for (i = 0; eccsteps; eccsteps–, i += eccbytes, p += eccsize) {
s3c_nand_enable_hwecc_8bit(mtd, NAND_ECC_WRITE);
chip->write_buf(mtd, p, eccsize);
s3c_nand_calculate_ecc_8bit(mtd, p, &ecc_calc[i]);
}
软件ECC
使用软件ECC时,需要自行实现ECC码检错和纠错的算法。最新的uboot中提供了一种ecc计算方法,在nand_ecc.c中。其中nand_calculate_ecc()根据输入的源数据计算出ecc码,nand_correct_data根据输入的ecc码对数据进行检错和纠错。
具体的算法细节这里不关注。
硬件ECC
s5pv210支持1位、4位、8位、12位、16位的硬件ECC,相应的ECC检错和纠错代码已经包含在nand驱动源码中。
坏块管理
uBoot对Nand设备的坏块管理有两种:一种是读写时跳过坏块;一种是基于坏块表。
跳过坏块是最简单也是最常见处理方式,在nand_util.c中nand_read_skip_bad和nand_write_skip_bad提供了该种处理方式。
而基于坏块表的方式,暂没有见到过。
原文网址:
http://www.eefocus.com/lishutong/blog/13-06/295365_90ddf.html