转载地址:http://blog.sina.com.cn/s/blog_87f8cc4e0102vbxg.html
至此,nand_scan()函数的代码分析全部结束。下面我们来总结一下这个函数都做了些什么:
1、 初始化mtd_info中一个重要的指针priv,使这个指针指向nand_chip变量; 2、 初始化nand_chip中的一些MTD驱动低层函数指针,如:cmdfunc、waitfunc、select_chip、select_chip、read_byte、block_bad、block_markbad、write_buf、read_buf、verify_buf 、read_buf、erase_cmd等;
3、 读芯片ID,并根据读出的设备id比对nand_flash_ids[i]中设备id找到匹配项,根据匹配项中数据和读ID的低4周期数据初始化nandflash芯片相关重要参数,如:chipsize、oobblock、oobsize、erasesize、busw、badblockpos、page_shift、bbt_erase_shift、chip_shift;
4、 查找板上nandflash物理芯片数,并计算出总容量初始化mtd->size;
5、 分配oob_buf、data_buf空间,初始化空间指针;
6、 初始化oob区、ecc校验相关参数和函数指针,如参数:autooob、oobavail、eccsize、eccbytes、eccsteps;函数指针:calculate_ecc、correct_data;
7、 初始化MTD驱动接口函数,如: nand_erase、nand_read、nand_write、nand_read_ecc、nand_write_ecc、nand_read_oob、nand_write_oob、nand_block_isbad、nand_block_markbad
8、 调用nand_bbt()创建坏块表.
总之:函数功能就是初始化,为驱动接口函数提供必要的运行环境。
4.3)board_nand_init()分析
该函数在nand_init_chip()先于nand_scan()被调用,它的主要功能就是完成一些与底层
硬件操作相关的初始化。由于这些底层操作大多为寄存器操作,所以与具体nand芯片型号及nand控制器相关,MTD不可能提供通用函数来完成这些底层操作。因此,用户必须自行编写board_nand_init()来完成这些操作的初始化,因此移植MTD驱动时编写board_nand_init()是一项核心工作。nand_scan()中完成的初始化多与通用的接口函数有关,反倒在移植时很少需要修改。
下面列出了需要在board_nand_init()完成初始化的操作:
1、 nandflash硬件初始化,即设置NFCONF、NFCONT 两个寄存器;
2、 寄存器访问接口指针IO_ADDR_R/W初始化;
3、 寄存器访问控制函数hwcontrol()初始化;这个函数没在nand_scan()中初始化,MTD也没有提供通用函数。所以这个函数不但要在board_nand_init()中初始化,还要自行编写该函数;
4、 R/B状态读取函数dev_ready()的初始化,这个函数没在nand_scan()中初始化,MTD也没有提供通用函数。所以这个函数不但要在board_nand_init()中初始化,还要自行编写该函数;
5、 片选函数select_chip()的初始化,这个函数在nand_scan()中初始化了,MTD也提供通用函数。但是该函数不具备完整功能,用户可在通用函数的基础上修改,也可以用户自行编写并在board_nand_init()中初始化;
6、 功能配置参数options初始化,在board_nand_init()中的功能配置参数options仅仅起到配置位宽的作用,初始化为0位宽为8;初始化为2位宽为16;
7、 ECC校验类型参数初始化,初始化为软件校验方式NAND_ECC_SOFT即可。 8、 chip_delay初始化,该值就是tR,这个值在芯片的datasheet中可以查到。在nand_scan()函数中被默认初始化20us。如果需要修改,可以在board_nand_init()初始化。
3.4.3)MTD驱动在fl2440上的移植步骤
在移植之前首先必须清楚板上nandflash芯片的具体型号,fl2440开发板上为现代HY27UF082G2B 其设备ID为DA,位宽为8,总容量为256MB。关于该芯片的详细情况在之前介绍过了,下面开始移植步骤:
1、 在drivers/nand/nand_ids.c中的结构体数组struct nand_flash_dev nand_flash_ids[]中找到与板上nandflash芯片的匹配项(至少保证设备id和芯片容量两项匹配),如果没有找到则需要自行添加,注意添加时nand_flash_ids[].options的配置;
2、 在include/configs/fl2440.h中的宏CONFIG_COMMANDS中添加CFG_CMD_NAND,使u-boot支持NAND。前面分析过如果不添加这个宏,nand_init()根本不会编译,也就是说NAND不会被初始化;
3、 在include/configs/fl2440.h中添加宏:
#define CFG_NAND_BASE 0 //nand基址地址,它在nand_scan()被赋值给IO_ADDR_R/W,这没有实际意义。IO_ADDR_R/W会在board_nand_init()重新初始化
#define CFG_MAX_NAND_DEVICE 1 //nandflash设备数
#define NAND_MAX_CHIPS 1 //每个nandflash设备物理片数
4、编写board_nand_init()函数完成上述7项初始化,下面详细分析了如何编写board_nand_init()。
1)在board/fl2440/中添加一个nand_flash.c的文件,在该文件中实现board_nand_init()及其他功能函数;
2)为方便board_nand_init()及其他功能函数对nandflash控制器的寄存器的访问,移植
时使用了一个小技巧:将nandflash控制器组定义成一个结构体,并将该寄存器组的基址地址定义成此结构体的指针,通过该指针对结构体中成员访问就是对nandflash控制器的寄存器的访问(u-boot中大量运用了这样的技巧来实现对各寄存器的访问,对该技巧的详细分析前面有):
a、在include/s3c24x0.h或include/s3c2410.h中添加结构体:
typedef struct {
S3C24X0_REG32 NFCONF;
S3C24X0_REG32 NFCONT;
S3C24X0_REG32 NFCMD;
S3C24X0_REG32 NFADDR;
S3C24X0_REG32 NFDATA;
S3C24X0_REG32 NFMECCD0;
S3C24X0_REG32 NFMECCD1;
S3C24X0_REG32 NFSECCD;
S3C24X0_REG32 NFSTAT;
S3C24X0_REG32 NFESTAT0;
S3C24X0_REG32 NFESTAT1;
S3C24X0_REG32 NFMECC0;
S3C24X0_REG32 NFMECC1;
S3C24X0_REG32 NFSECC;
S3C24X0_REG32 NFSBLK;
S3C24X0_REG32 NFEBLK;
} S3C2440_NAND;
注意该结构体中成员的顺序一定要和s3c2440 nand控制器寄存器组的顺序相同
b、在include/s3c2410.h中添加宏:
#define S3C2440_NAND_BASE 0x4E000000
//nandflash控制器的寄存器组基址地址
及函数:
static inline S3C2440_NAND * const S3C2440_GetBase_NAND(void)
{
return (S3C2440_NAND * const)S3C2440_NAND_BASE;
}
该函数将nandflash控制器的寄存器组基址地址作为寄存器组结构体的指针。
在nand_flash.c中实现board_nand_init()及其他功能函数代码如下: 3)
#include
#if (CONFIG_COMMANDS & CFG_CMD_NAND) && !defined(CFG_NAND_LEGACY)
#include
#include
DECLARE_GLOBAL_DATA_PTR;
#define S3C2440_NFSTAT_READY (1<<0)
#define S3C2440_NFCONT_nFCE (1<<1)
static void s3c2440_nand_select_chip(struct mtd_info *mtd, int chip)
{
S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
if (chip == -1) {
s3c2440nand->NFCONT |= S3C2440_NFCONT_nFCE;
} else {
s3c2440nand->NFCONT &= ~S3C2440_NFCONT_nFCE;
}
}
static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd)
{
S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
struct nand_chip *chip = mtd->priv;
switch (cmd) {
case NAND_CTL_SETNCE: //片选芯片
case NAND_CTL_CLRNCE: //取消片选
printf("%s: called for NCE\n", __FUNCTION__);
break;
case NAND_CTL_SETCLE: //访问命令寄存器
chip->IO_ADDR_W = (void *)&s3c2440nand->NFCMD;
break;
case NAND_CTL_SETALE: //访问控制寄存器
chip->IO_ADDR_W = (void *)&s3c2440nand->NFADDR;
break;
default: //默认指向数据寄存器
chip->IO_ADDR_W = (void *)&s3c2440nand->NFDATA;
break;
}
}
static int s3c2440_nand_devready(struct mtd_info *mtd)
{
S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
return (s3c2440nand->NFSTAT & S3C2440_NFSTAT_READY);
}
static void s3c2440_nand_inithw(void)
{
S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0
s3c2440nand->NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
s3c2440nand->NFCONT = (1<<4)|(0<<1)|(1<<0);
}
void board_nand_init(struct nand_chip *chip)
{
S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
s3c2440_nand_inithw();
//寄存器访问接口指针IO_ADDR_R/W初始化
chip->IO_ADDR_R = (void *)&s3c2440nand->NFDATA;
chip->IO_ADDR_W = (void *)&s3c2440nand->NFDATA;
//寄存器访问控制函数hwcontrol()初始化
chip->hwcontrol = s3c2440_nand_hwcontrol;
//R/B状态读取函数dev_ready()函数的初始化
chip->dev_ready = s3c2440_nand_devready;
//片选函数初始化
chip->select_chip = s3c2440_nand_select_chip;
chip->options = 0;
chip->eccmode = NAND_ECC_SOFT;
}
#endif
有必要对上述代码中的三个参数TACLS、TWRPH0、TWRPH1做一下说明。这个三个参数控制的是nandflash控制器输出信号CLE/ALE与WE的时序关系,必须正确地设置这三个参数才能使得nandflash控制器发出正确的时序控制信号。
TACLS:表征 ALE/CLE信号的建立时间(Time of ALE/CLE Setup),即ALE/CLE上升沿到WE下降沿的时间,见下时序图中时间段1
时间段1=tCLS – tWP。tCLS、tWP的值在nandflash芯片的datasheet中查到,下图是HY27UF082G2A datasheet中查到的tCLS、tWP:
tCLS=15ns;tWP=15ns(最小值)所以时间段1的值为0。时间段1的值与TACLS的值关系为:时间段1 < HCLKS周期 * TACLS,不难计算出对HY27UF082G2A而言TACLS的值应大于等于0,源码中取为0。
要特别注意的是:有些芯片的datasheet中表述的tCLS与这里的tCLS – tWP等价。见下图:
TWRPH0:表征WE的脉冲宽度,即上时序图中的时间段2。WE的脉冲宽度即datasheet中的tWP(time of WE Pluse),tWP=15ns(最小值)。tWP与TWRPH0的关系为:
tWP < HCLK周期 * (TWRPH0+1) FL2440移植本u-boot后HCLK周期为10ns,所以不难计算出TWRPH0应大于等于1,该源码中取1。
TWRPH1:表征ALE/CLE的保持时间,即上时序图中时间段3。ALE/CLE的保持时间即WE上升沿到CLE/ALE下降沿时间段。在datasheet中即为tCLH或tALH(time of ale/cle hold),tCLH=tALH=5ns。tCLH或tALH与TWRPH1的关系为:
tCLH或tALH < HCLK周期 * (TWRPH1+1) FL2440移植本u-boot后HCLK周期为10ns,所以不难计算出TWRPH1应大于等于0,该源码中取0。
4)修改board/fl2440/makefile为:COBJS := fl2440.o flash.o nand_flash.o
至此,移植步骤完成。移植结果如下
上图中出现了NAND : 256MiB的字样,表示MTD驱动至少对板上的nandflash芯片初始化成功。键入命令:nand info查看nandflash的基本信息。键入命令nand scrub擦除整个nandflash芯片。(这样连出厂的坏块标记也擦除了,这些标记是不可恢复的,这势必导致nandflash读写的不可靠性。可是没办法,用nand erase命令擦除时提示“skipping bad block”擦除不成功,而且nand wirte、nand read命令执行都不成功。用nand scrub擦除整个nandflash芯片后一切都正常了,这是什么原因待查)
上图执行了loady命令下载了一个bootloader到0x31000000。然后执行命令nand write 0x31000000 0x0 0x20000 把这个bootloader烧写到nandflash 的地址0x0处,烧写数据长度为0x20000。提示烧写成功了。
从上图可以看出,出现了”Hit any key to antuboot: 0”的字样,所以u-boot自动进入了引导模式;出现了“nand read:device0 offset 0x200000,size 0x200000”的字样,所以bootcmd中的nand read命令被正确执行了;出现了”Booting image at 0x31000000 …”的字样,说明bootcmd中的bootm命令被正确执行了,并且bootm的参数也正确。在红线以上的内容为u-boot打印的调试信息。”Booting image at 0x31000000 …”以下,红线以上为do_bootm()函数打印的调试信息。红线以下为linux内核打印的调试信息,这说明引导内核成功了。但是此时的内核还不能完整地运行,因为还没有根文件系统和设备驱动。为了烧写根文件系统,u-boot还应该支持烧写yaffs根文件系统镜像,因为根文件系统镜像烧写nandflash中,而yaffs格式的镜像文件更适合于nandflash。所以下部是u-boot支持yaffs烧写的功能移植。