1. 让uboot 支持操作Nand Flash
1.1 uboot 的不足
在第三篇中,我们给uboot移植了Nand 启动的代码,并且成功启动了。不过uboot
程序仍然无法正常去操作Nand Flash,原因是我们并没有配置uboot 中关于Nand Flash操作的相关代码。
1.2 添加对Nand Flash 的操作
1.2.1 添加宏定义和s3c2440_nand.c 文件
-
要让uboot 支持Nand Flash 操作,我们首先需要给include/configs
/s3c2440.h,添加CONFIG_CMD_NAND
的宏定义:
-
拷贝driver/mtd/nand/s3c2410_nand.c 为driver/mtd/nand/s3c2440_nand.
c,仿造s3c2410_nand 来写我们的s3c2440_nand 文件。并且在driver/
mtd/nand/Makefile 中添加s3c2440_nand.c,将其编译进内核:
从图中我们可知,我们还需要往s3c2440.h 中添加宏定义CONFIG_NAND_S3C2440
。
完成上面两步操作后,我们就成功将Nand 的代码添加到我们的uboot 中了。
1.2.2 修改s3c2440_nand.c 使其适配我们的jz2440
我们使用的是s3c2410 的nand 代码,其并不适配我们的s3c2440 芯片。所以我们需要基于s3c2410 的代码上进行修改。
添加宏定义
为了方便进行操作,我们仿造s3c2410 定义一些s3c2440 芯片操作Nand Flash 控制器需要用到宏定义:
// 与时序设置有关,在设置时序时使用到了
#define S3C2440_NFCONF_TACLS(x) ((x)<<12)
#define S3C2440_NFCONF_TWRPH0(x) ((x)<<8)
#define S3C2440_NFCONF_TWRPH1(x) ((x)<<4)
// 控制有关
#define S3C2440_NFCONT_nFCE (1<<1)
#define S3C2440_NFCONT_ENABLE (1)
#define S3C2440_NFCONT_INITECC (1<<4)
#define S3C2440_NFCONT_MAINECCLock (1<<5)
#define S3C2440_NFCONT_SpareECCLock (1<<6)
// 寄存器地址
#define S3C2440_ADDR_NCLE 0x8 // 命令寄存器的偏移地址
#define S3C2440_ADDR_NALE 0xC // 地址寄存器的偏移地址
修改时序
在board_nand_init 中设置上面的时序,Nand Flash 芯片为K9F2G08U0C,其时序分析如下:
从上面的表格数据来看:(HCLK时钟,100MHz,其工作频率为10ns每次)
- 我们CLS/ALS都需要2clk的时间,但仔细看时序图其实tWP 与tCLS 和tALS 的终止时序是同一点。并且刚好它们都为12ns,因此它们是可以同时发出的。所以我们的tACLS 可以取0个周期。
- tWRPH0 为tWP/tRE(需要12ns)因此最少需要2clk,总共20ns。
- tWRPH1 为tWH/tRE(需要10ns)因此可以设置为1clk。
- tWC/tRC 的nWE/nRE 信号的一次周期需要25ns,并且tWC/tRC 等于tWRPH0 + tWRPH1 正好等于30ns(>= 25ns),也满足读写信号的时序要求。
board_nand_init 函数
int board_nand_init(struct nand_chip *nand)
{
u_int32_t cfg;
u_int8_t tacls, twrph0, twrph1;
struct s3c24x0_clock_power *clk_power = s3c24x0_get_base_clock_power();
struct s3c2440_nand *nand_reg = s3c2440_get_base_nand();
debug("board_nand_init()\n");
// 使能Nand Flash控制器,开启Nand Flash模块
writel(readl(&clk_power->clkcon) | (1 << 4), &clk_power->clkcon);
#if defined(CONFIG_S3C24XX_CUSTOM_NAND_TIMING)
tacls = CONFIG_S3C24XX_TACLS;
twrph0 = CONFIG_S3C24XX_TWRPH0;
twrph1 = CONFIG_S3C24XX_TWRPH1;
#else
tacls = 0; // 0ns
twrph0 = 2; // 20ns
twrph1 = 1; // 10ns
#endif
// S3C2440_NFCONT_ENABLE: 开启Nand Flash控制器
// S3C2440_NFCONT_INITECC: 使能ECC
// S3C2440_NFCONT_nFCE: 禁止片选
cfg = S3C2440_NFCONT_ENABLE | S3C2440_NFCONT_INITECC | S3C2440_NFCONT_nFCE;
writel(cfg, &nand_reg->nfcont);
/* 设置时序 */
cfg = 0;
cfg |= S3C2440_NFCONF_TACLS(tacls);
cfg |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);
cfg |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);
writel(cfg, &nand_reg->nfconf);
/* initialize nand_chip data structure */
nand->IO_ADDR_R = (void *)&nand_reg->nfdata; // 设置读寄存器的地址
nand->IO_ADDR_W = (void *)&nand_reg->nfdata; // 设置写寄存器的地址
nand->select_chip = s3c2440_nand_select; // 片选函数
/* read_buf and write_buf are default */
/* read_byte and write_byte are default */
#ifdef CONFIG_NAND_SPL
nand->read_buf = s3c2440_nand_read_buf;
#endif
/* hwcontrol always must be implemented */
nand->cmd_ctrl = s3c2440_hwcontrol; // 必须实现的
nand->dev_ready = s3c2440_dev_ready; // 必须实现的
#ifdef CONFIG_S3C2440_NAND_HWECC
nand->ecc.hwctl = s3c2440_nand_enable_hwecc;
nand->ecc.calculate = s3c2440_nand_calculate_ecc;
nand->ecc.correct = s3c2440_nand_correct_data;
nand->ecc.mode = NAND_ECC_HW;
nand->ecc.size = CONFIG_SYS_NAND_ECCSIZE;
nand->ecc.bytes = CONFIG_SYS_NAND_ECCBYTES;
#else
nand->ecc.mode = NAND_ECC_SOFT;
#endif
#ifdef CONFIG_S3C2440_NAND_BBT
nand->options = NAND_USE_FLASH_BBT;
#else
nand->options = 0;
#endif
debug("end of nand_init\n");
return 0;
}
上面我们实现了s3c2440_hwcontrol、s3c2440_dev_ready,以及ECC操作需
要的相关函数,这里我们先忽略ECC操作,先完成前两个函数。
实现芯片相关的函数
s3c2440_hwcontrol 实现
根据芯片手册,我们基于原理s3c2410_hwcontrol 的代码进行修改,修改使能芯片的操作。
static void s3c2440_hwcontrol(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
struct nand_chip *chip = mtd->priv;
struct s3c2440_nand *nand = s3c2440_get_base_nand();
debug("hwcontrol(): 0x%02x 0x%02x\n", dat, ctrl);
if (ctrl & NAND_CLE) {
writeb(dat, &nand->nfcmd);
}
else if (ctrl & NAND_ALE) {
writeb(dat, &nand->nfaddr);
}
}
s3c2440_dev_ready 实现
static int s3c2440_dev_ready(struct mtd_info *mtd)
{
struct s3c2440_nand *nand = s3c2440_get_base_nand();
debug("dev_ready\n");
return readl(&nand->nfstat) & 0x01;
}
s3c2440_nand_select 实现
这里模仿nand_select_chip 的实现。
static void s3c2440_nand_select(struct mtd_info *mtd, int chipnr)
{
struct nand_chip *chip = mtd->priv;
struct s3c2440_nand *nand = s3c2440_get_base_nand();
struct nand_chip *chip = mtd->priv;
switch (chipnr) {
case -1:
writel(readl(&nand->nfconf) | S3C2440_NFCONT_nFCE, &nand->nfcont);
break;
case 0:
writel(readl(&nand->nfconf) & ~S3C2440_NFCONT_nFCE, &nand->nfcont);
break;
default:
BUG();
}
}
1.3 重新烧写
修改完了s3c2440_nand.c 文件,我们重新编译程序并进行烧写。如下图,我们的uboot 已经成功识别出Nand Flash 的大小,为256 MiB。
2. 总结
为uboot 添加Nand Flash 的支持,我们需要完成以下几件事:
-
我们从board.c 的board_init_r 函数开始分析,发现Nand Flash 的初始化入口函数是nand_init 函数,于是我们从nand_init 函数开始进行追踪;
-
nand_init 会根据配置文件(s3c2440.h)中指定Nand Flash 芯片的个数,对每一个芯片分别调用nand_init_chip 函数,分别对每一个块芯片的nand_chip 结构体(对象)进行初始化,设置相关的操纵函数;
-
nand_init_chip 会调用board_nand_init,进而进入我们自己创建的s3c2440_nand.c 文件中的board_nand_init 函数,进行芯片(与开发板相关的)初始化工作,由我们自己指定相关需要重新实现的操纵函数;
其中s3c2440_hwcontrol、s3c2440_dev_ready 是必须重新实现的操纵函数。除此以外,对于我们的s3c2440 芯片来说还需要设置Nand Flash 控制器操作的时序; -
**board_nand_init 函数返回后,会执行nand_scan 函数,该函数会对我们的Nand Flash 芯片进行扫描,去读芯片型号、厂家信息等,一些列固化在Nand Flash 芯片的信息。**并且对于nand_chip 中没有在board_nand_init 函数中被指定的操纵函数,会在nand_set_defaults 函数内被指定一个默认的函数。
其执行的流程大致如下: