分析了一下Uboot中Nandflash的驱动,u-boot-2009.08使用的是和Linux内核一样的MTD(内存技术设备)架构。在Uboot下对Nand的支持体现在命令行下实现对nand flash的操作,为:nand info,nand device,nand read,nand write,nand erease,nand bad。用到的主要数据结构有:struct nand_flash_dev,struct nand_chip。前者包括主要的芯片型号,存储容量,设备ID,I/O总线宽度等信息;后者是具体对nand flash进行操作时用到的信息。
对Nandflash驱动代码的分析:
1)lib_arm/board.c中start_armboot()函数中调用了nand_init()。
2)nand_init()函数定义在drivers/mtd/nand/Nand.c文件中,调用了同文件下的nand_init_chip()函数和board_nand_select_device()函数,并累加Nandflash的总大小。
3)nand_init_chip()函数初始化了IO_ADDR_R和IO_ADDR_W,调用board_nand_init()和nand_scan()。(在drivers/mtd/nand/S3c2410_nand.c中)
4)board_nand_init()函数在drivers/mtd/nand/S3c2410_nand.c中,初始化NFCONF配置寄存器。主要对struct nand_chip结构体的函数指针赋值,让他们指向自己为nand驱动编写的一些函数,该数据结构在include/linux/mtd/nand.h中定义。
5)nand_scan()函数在drivers/mtd/nand/Nand_base.c文件中定义,并调用了nand_scan_ident()和nand_scan_tail()函数。
6)nand_scan_ident()调用了同文件下的nand_set_defaults()和nand_get_flash_type(),nand_set_defaults()函数对struct nand_chip结构体的函数指针进行了赋值。在此函数中cmdfunc映射到了nand_command,nand_get_flash_type()读取了厂商和设备ID,并对struct nand_chip结构体的变量和mtd_info进行初始化操作。
7)nand_scan_tail()进行了ECC的设置和剩下的MTD驱动函数的初始化。
8)nand_select_device()函数用来打开或关闭nand芯片,-1是打开,0是关闭。chip->cmd_ctrl映射到hwcontrol(在drivers/mtd/nand/S3c2410_nand.c的board_nand_init()函数中)。
9)返回nand_init(),这样nand的初始化就完成了。
移植Nandflash驱动,主要是修改S3c2410_nand.c。
看一个Read操作的例子:
以common/env_nand.c里读取Nandflash的环境变量为例,
env_nand.c中调用了nand_read(&nand_info[0], offset, &len, char_ptr);
nand_read在include/nand.h中定义。
static inline int nand_read(nand_info_t *info, loff_t ofs, size_t *len, u_char *buf)
{
return info->read(info, ofs, *len, (size_t *)len, buf);
}
nand_read又调用info->read,即mtd_info的read,mtd_info与nand_info_t同名。
mtd_info的read函数在drivers/nand/nand_base.c中的nand_scan_tail函数中被映射到nand_read(),在nand_read()中调用nand_do_read_ops()。这是最后一层,通过调用nand_chip中的函数完成带ECC的读操作。
现在进行移植:
修改include/configs/TE2440II.h如下
/*
* Command line configuration.
*/
#include <config_cmd_default.h>
#define CONFIG_CMD_CACHE
#define CONFIG_CMD_DATE
#define CONFIG_CMD_ELF
#define CONFIG_CMD_NAND
#define CONFIG_CMDLINE_EDITING
#ifdef CONFIG_CMDLINE_EDITING
#undef CONFIG_AUTO_COMPLETE
#else
#define CONFIG_AUTO_COMLETE
#endif
/*
* NAND flash setting
*/
#if defined(CONFIG_CMD_NAND)
#define CONFIG_SYS_NAND_BASE 0x4e000000
#define CONFIG_SYS_MAX_NAND_DEVICE 1
#define CONFIG_MTD_NAND_VERIFY_WRITE 1
#define NAND_SAMSUNG_LP_OPTIONS 1
#define CONFIG_NAND_S3C2440 1
#endif
环境变量那部分修改:
//#define CONFIG_ENV_IS_IN_FLASH 1
#define CONFIG_ENV_IS_IN_NAND 1 /*环境变量的保存位置*/
在drivers/mtd/nand/Makefile文件中添加:
COBJS-y += s3c2440_nand.o
COBJS-$(CONFIG_NAND_S3C2440) += s3c2440_nand.o
我们最重要的是修改s3c2410_nand.c,把它复制重命名为s3c2440_nand.c,因为s3c2410和s3c2440在nand控制器方面有很多不同,所以要进行移植。
修改s3c2440_nand.c为:
#define NF_BASE 0x4e000000
#define NFCONF __REGi(NF_BASE + 0x0)
#define NFCONT __REGi(NF_BASE + 0x4)
#define NFCMD __REGb(NF_BASE + 0x8)
#define NFADDR __REGb(NF_BASE + 0xc)
#define NFDATA __REGb(NF_BASE + 0x10)
#define NFMECCD0 __REGi(NF_BASE + 0x14)
#define NFMECCD1 __REGi(NF_BASE + 0x18)
#define NFSECCD __REGi(NF_BASE + 0x1C)
#define NFSTAT __REGb(NF_BASE + 0x20)
#define NFSTAT0 __REGi(NF_BASE + 0x24)
#define NFSTAT1 __REGi(NF_BASE + 0x28)
#define NFMECC0 __REGi(NF_BASE + 0x2C)
#define NFMECC1 __REGi(NF_BASE + 0x30)
#define NFSECC __REGi(NF_BASE + 0x34)
#define NFSBLK __REGi(NF_BASE + 0x38)
#define NFEBLK __REGi(NF_BASE + 0x3c)
#define S3C2440_NFCONT_nCE (1<<1)
#define S3C2440_ADDR_NALE 0x0c
#define S3C2440_ADDR_NCLE 0x08
ulong IO_ADDR_W = NF_BASE;
static void s3c2440_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
{
struct nand_chip *chip = mtd->priv;
DEBUGN("hwcontrol(): 0x%02x 0x%02x\n", cmd, ctrl);
if (ctrl & NAND_CTRL_CHANGE) {
IO_ADDR_W = NF_BASE;
if (!(ctrl & NAND_CLE)) //要写的是地址
{
IO_ADDR_W |= S3C2440_ADDR_NALE;}
if (!(ctrl & NAND_ALE)) //要写的是命令
{
IO_ADDR_W |= S3C2440_ADDR_NCLE;}
if (ctrl & NAND_NCE)
{NFCONT &= ~S3C2440_NFCONT_nCE; //使能nand flash
//DEBUGN("NFCONT is 0x%x ",NFCONT);
//DEBUGN("nand Enable ");
}
else
{NFCONT |= S3C2440_NFCONT_nCE; //禁止nand flash
//DEBUGN("nand disable ");
}
}
if (cmd != NAND_CMD_NONE)
writeb(cmd,(void *)IO_ADDR_W);
}
static int s3c2440_dev_ready(struct mtd_info *mtd)
{
DEBUGN("dev_ready\n");
return (NFSTAT & 0x01);
}
/******************************************************************************************/
int board_nand_init(struct nand_chip *nand)
{
u_int32_t cfg;
u_int8_t tacls, twrph0, twrph1;
S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
DEBUGN("board_nand_init()\n");
clk_power->CLKCON |= (1 << 4);
DEBUGN("CONFIG_S3C2440\n");
twrph0 = 4; twrph1 = 2; tacls = 0;
cfg = (tacls<<12)|(twrph0<<8)|(twrph1<<4);
NFCONF = cfg;
//DEBUGN("cfg is %x\n",cfg);
//DEBUGN("NFCONF is %lx\n",NFCONF);
cfg = (1<<6)|(1<<4)|(0<<1)|(1<<0);
NFCONT = cfg;
//DEBUGN("cfg is %lx\n",cfg);
//DEBUGN("NFCONT is %x\n",NFCONT);
/* initialize nand_chip data structure */
nand->IO_ADDR_R = nand->IO_ADDR_W = (void *)0x4e000010;
/* read_buf and write_buf are default */
/* read_byte and write_byte are default */
/* hwcontrol always must be implemented */
nand->cmd_ctrl = s3c2440_hwcontrol;
nand->dev_ready = s3c2440_dev_ready;
return 0;
}
s3c2440_hwcontrol函数主要控制是写命令还是写地址。
我遇到的问题,由于board_nand_init()函数我没有添加return 0,导致后面的判断没法进行,结果读出的NAND为0MiB,所以移植一定要仔细阅读源码,还有个问题就是uboot的打印信息中出现NAND_ECC_NONE selected by board driver. This is not recommended!!,这根ECC的校验模式有关,有人说“据说vivi或uboot通过软件算法产生的ecc校验码于S3C2410 NAND Flash 控制器产生的ecc校验码不一致”,所以我就没改什么,我把那个打印信息注释掉了。