uboot移植与源码分析总结-Nand驱动

从功能上来说,nand flash与norflash并无太大差异,主要区别在于操作接口和方式。Nand基于非sram总线接口,使用nand接口,所以一般需要mcu具有nand控制器才可与其连接。在读取时,以页为单位;擦除和写入时,以块为单位。

将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; 
#endif

    int 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值