引言
随着嵌入式系统的日趋复杂 , 它对大容量数据存储的需求越来越紧迫。而嵌入式设备低功耗、小体积以及低成本的要求 , 使硬盘无法得到广泛的应用。 NAND 闪存设备就是为了满足这种需求而迅速发展起来的。目前关于 U-BOOT 的移植解决方案主要面向的是微处理器中的 NOR 闪存,如果能在微处理器上的 NAND 闪存中实现 U-BOOT 的启动,则会给实际应用带来极大的方便。
U-BOOT 简介
U-BOOT 支持ARM PowerPC 等多种架构的处理器,也支持 Linux NetBSD VxWorks 等多种操作系统,主要用来开发嵌入式系统初始化代码 bootloader bootloader 是芯片复位后进入操作系统之前执行的一段代码,完成由硬件启动到操作系统启动的过渡,为运行操作系统提供基本的运行环境,如初始化 CPU 、堆栈、初始化存储器系统等,其功能类似于 PC 机的 BIOS U-BOOT 执行流程图如图 1 所示。
 NAND Flash上启动U-Boot_休闲
NAND 闪存工作原理
S3C2440 开发板的 NAND 闪存NAND 闪存控制器 ( 集成在 S3C2440 CPU ) NAND 闪存芯片 (K9F1208U0C) 两大部分组成。当要访问 NAND 闪存芯片中的数据时 , 必须通过 NAND 闪存控制器发送命令才能完成。所以 , NAND 闪存相当于 S3C2440 的一个外设 , 而不位于它的内存地址区。
NAND闪存 (K9F1208U0C) 的数据存储结构分层为: 1 设备 (Device) = 4096 (Block);1 = 32 / (Page/row);1 = 528B = 数据块 (512B) + OOB (16B)  在每一页中,最后 16 个字节 ( 又称 OOB) NAND闪存命令执行完毕后设置状态,剩余 512 个字节又分为前半部分和后半部分。可以通过 NAND闪存命令 00h/01h/50h 分别对前半部、后半部、 OOB 进行定位,通过 NAND闪存内置的指针指向各自的首地址。
NAND 闪存的操作特点为:擦除操作的最小单位是块; NAND闪存芯片每一位只能从 1 变为 0 ,而不能从 0 变为 1 ,所以在对其进行写入操作之前一定要将相应块擦除; OOB 部分的第 6 字节为坏快标志,即如果不是坏块该值为 FF ,否则为坏块;除 OOB 6 字节外,通常用 OOB 的前 3 个字节存放 NAND 闪存的硬件 ECC( 校验寄存器 ) 码;
NAND 闪存启动 U-BOOT 的设计思路
如果 S3C2440 被配置成从 NAND闪存启动 , 上电后, S3C2440 NAND闪存控制器会自动把 NAND闪存中的前 4K 数据搬移到内部 RAM , 并把 0x00000000 设置为内部 RAM 的起始地址 , CPU 从内部 RAM 0x00000000 位置开始启动。因此要把最核心的启动程序放在 NAND 闪存的前 4K 中。
由于 NAND 闪存控制器从 NAND 闪存中搬移到内部 RAM 的代码是有限的 , 所以 , 在启动代码的前 4K , 必须完成 S3C2440 的核心配置,并把启动代码的剩余部分搬到 RAM 中运行。在 U-BOOT , 4K 完成的主要工作就是 U-BOOT 启动的第一个阶段 (stage1)
根据 U-BOOT 的执行流程图,可知要实现从 NAND 闪存中启动 U-BOOT ,首先需要初始化 NAND 闪存 , 并从 NAND 闪存中把 U-BOOT 搬移到 RAM 中,最后需要让 U-BOOT 支持 NAND 闪存的命令操作。
开发环境
本设计中目标板硬件环境如下: CPU S3C2440 SDRAM HY57V561620 NAND 闪存为 64MB K9F1208U0C
主机软件环境为 fedora8 u-boot- 1.3.4 gcc 2.95.3
 
具体设计
支持 NAND 闪存的启动程序设计
2440板的NAND Flash初始化和2410基本类似,下面先以2410为例,进行介绍。
UbootSMDK2410板的NAND Flash初始化部分没有写,即lib_arm/board.c中的start_armboot函数中有这么一句:
 
#if defined(CONFIG_CMD_NAND)
    puts ("NAND:  ");
    nand_init();        /* go init the NAND */
#endif
 
但是在board/smdk2410目录下任何源文件中都没有定义nand_init这个函数。
所以需要我们补充这个函数以及这个函数涉及的底层操作。
我们可以仿照VCMA9板的nand_init函数,VCMA9板是一款用S3C2410CPUDEMO Board,因此这部分操作和SMDK2410 Demo Board很相似。大部分代码可以照搬。
首先将board/mpl/vcma9/vcma9.c中下面代码拷贝到common/cmd_nand.c中来。
/*
 * NAND flash initialization.
 */
#if defined(CONFIG_CMD_NAND & CFG_CMD_NAND)
extern ulong
nand_probe(ulong physadr);
 
 
static inline void NF_Reset(void)
{
    int i;
 
    NF_SetCE(NFCE_LOW);
    NF_Cmd(0xFF);       /* reset command */
    for(i = 0; i < 10; i++);    /* tWB = 100ns. */
    NF_WaitRB();        /* wait 200~500us; */
    NF_SetCE(NFCE_HIGH);
}
 
 
static inline void NF_Init(void)
{
#if 0 /* a little bit too optimistic */
#define TACLS   0
#define TWRPH0  3
#define TWRPH1  0
#else
#define TACLS   0
#define TWRPH0  4
#define TWRPH1  2
#endif
 
    NF_Conf((1<<15)|(0<<14)|(0<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0));
    /*nand->NFCONF = (1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0); */
    /* 1  1    1     1,   1      xxx,  r xxx,   r xxx */
    /* En 512B 4step ECCR nFCE=H tACLS   tWRPH0   tWRPH1 */
 
    NF_Reset();
}
 
void
nand_init(void)
{
    S3C2410_NAND * const nand = S3C2410_GetBase_NAND();
 
    NF_Init();
#ifdef DEBUG
    printf("NAND flash probing at 0x%.8lX\n", (ulong)nand);
#endif
    printf ("%4lu MB\n", nand_probe((ulong)nand) >> 20);
}
#endif
 
再将board/mpl/vcma9/vcma9.h中下面代码拷贝到common/cmd_nand.c中来。
#if defined(CONFIG_CMD_NAND & CFG_CMD_NAND)
typedef enum {
    NFCE_LOW,
    NFCE_HIGH
} NFCE_STATE;
 
static inline void NF_Conf(u16 conf)
{
    S3C2410_NAND * const nand = S3C2410_GetBase_NAND();
 
    nand->NFCONF = conf;
}
 
static inline void NF_Cmd(u8 cmd)
{
    S3C2410_NAND * const nand = S3C2410_GetBase_NAND();
 
    nand->NFCMD = cmd;
}
 
static inline void NF_CmdW(u8 cmd)
{
    NF_Cmd(cmd);
    udelay(1);
}
 
static inline void NF_Addr(u8 addr)
{
    S3C2410_NAND * const nand = S3C2410_GetBase_NAND();
 
    nand->NFADDR = addr;
}
 
static inline void NF_SetCE(NFCE_STATE s)
{
    S3C2410_NAND * const nand = S3C2410_GetBase_NAND();
 
    switch (s) {
        case NFCE_LOW:
            nand->NFCONF &= ~(1<<11);
            break;
 
        case NFCE_HIGH:
            nand->NFCONF |= (1<<11);
            break;
    }
}
 
static inline void NF_WaitRB(void)
{
    S3C2410_NAND * const nand = S3C2410_GetBase_NAND();
 
    while (!(nand->NFSTAT & (1<<0)));
}
 
static inline void NF_Write(u8 data)
{
    S3C2410_NAND * const nand = S3C2410_GetBase_NAND();
 
    nand->NFDATA = data;
}
 
static inline u8 NF_Read(void)
{
    S3C2410_NAND * const nand = S3C2410_GetBase_NAND();
 
    return(nand->NFDATA);
}
 
static inline void NF_Init_ECC(void)
{
    S3C2410_NAND * const nand = S3C2410_GetBase_NAND();
 
    nand->NFCONF |= (1<<12);
}
 
static inline u32 NF_Read_ECC(void)
{
    S3C2410_NAND * const nand = S3C2410_GetBase_NAND();
 
    return(nand->NFECC);
}
 
#endif
 
再在common/cmd_nand.c中添加头文件:#include <s3c2410.h>
 
再将include/configs/vcma.9中下面代码拷贝到include/configs/smdk2410.h中来。
 
/*-----------------------------------------------------------------------
 * NAND flash settings
 */
#if defined(CONFIG_CMD_NAND & CFG_CMD_NAND)
 
#define CFG_NAND_LEGACY
#define CFG_MAX_NAND_DEVICE 1   /* Max number of NAND devices       */
#define SECTORSIZE 512
 
#define ADDR_COLUMN 1
#define ADDR_PAGE 2
#define ADDR_COLUMN_PAGE 3
 
#define NAND_ChipID_UNKNOWN 0x00
#define NAND_MAX_FLOORS 1
#define NAND_MAX_CHIPS 1
 
#define NAND_WAIT_READY(nand)   NF_WaitRB()
 
#define NAND_DISABLE_CE(nand)   NF_SetCE(NFCE_HIGH)
#define NAND_ENABLE_CE(nand)    NF_SetCE(NFCE_LOW)
 
 
#define WRITE_NAND_COMMAND(d, adr)  NF_Cmd(d)
#define WRITE_NAND_COMMANDW(d, adr) NF_CmdW(d)
#define WRITE_NAND_ADDRESS(d, adr)  NF_Addr(d)
#define WRITE_NAND(d, adr)      NF_Write(d)
#define READ_NAND(adr)          NF_Read()
/* the following functions are NOP's because S3C24X0 handles this in hardware */
#define NAND_CTL_CLRALE(nandptr)
#define NAND_CTL_SETALE(nandptr)
#define NAND_CTL_CLRCLE(nandptr)
#define NAND_CTL_SETCLE(nandptr)
 
#define CONFIG_MTD_NAND_VERIFY_WRITE    1
#define CONFIG_MTD_NAND_ECC_JFFS2   1
 
#endif
 最后在include/configs/smdk2410.h中添加红色显示部分:
/*
 * Command line configuration.
 */
#include <config_cmd_default.h>
 
#define CONFIG_CMD_CACHE
#define CONFIG_CMD_DATE
#define CONFIG_CMD_ELF
#define CFG_CMD_NAND 1
 
*红色为添加部分
 
支持 U-BOOT 命令设计
U-BOOT 下对 nand 闪存的支持主要是在命令行下实现对 nand 闪存的操作。对 nand 闪存实现的命令为: nand info( 打印 nand Flash 信息 ) nand device( 显示某个 nand 闪存设备 ) nand read( 读取 nand 闪存 ) nand write( nand 闪存 ) nand erease( 擦除 nand 闪存 ) nand bad( 显示坏块 ) 等。
用到的主要数据结构有: struct nand_flash_dev struct nand_chip 。前者包括主要的芯片型号、存储容量、设备 ID I/O 总线宽度等信息;后者是具体对 NAND 闪存进行操作时用到的信息。
a.     设置配置选项
修改 /include/configs/ironpeak2440.h, 主要是在 CONFIG_COMMANDS 中打开 CFG_CMD_NAND 选项。定义 NAND 闪存控制器 SFR 区中的起始寄存器地址、页面大小,定义 NAND 闪存命令层的底层接口函数等。
b.      加入 NAND 闪存芯片型号
/include/linux/mtd/ nand_ids.h 中对如下结构体赋值进行修改 :
static struct nand_flash_dev nand_flash_ids[] = {
......
{"Samsung K9F1208U0C", NAND_MFR_SAMSUNG, 0x76, 26, 0, 3, 0x4000, 0},
.......
}
这样对于该款 NAND 闪存芯片的操作才能正确执行。
c. 编写 NAND 闪存初始化函数
/board/ironpeak2440/ironpeak2440.c 中加入 nand_init() 函数。
void nand_init(void)
{
/* 初始化 NAND 闪存控制器 , 以及 NAND 闪存芯片 */
nand_reset();
/* 调用 nand_probe() 来检测芯片类型 */
printf ("%4lu MB\n", nand_probe(CFG_NAND_BASE) >> 20);
}
该函数在启动时被 start_armboot() 调用。
最后重新编译 U-BOOT 并将生成的 u-boot.bin 烧入 NAND 闪存中,目标板上电后从串口输出如下信息:
U-Boot 1.1.3 (Nov 14 2006 - 11:29:50)
U-Boot code: 33F80000 -> 33F9C9E4 BSS: -> 33FA0B28
RAM Configuration:
Bank #0: 30000000 64 MB
## Unknown Flash on Bank 0: ID 0xffff, Size = 0x00000000 = 0 MB
Flash: 0 kB
NAND: 64 MB
In: serial
Out: serial
Err: serial
Hit any key to stop autoboot: 0
ironpeak2440 #
结 语
以往将 U-BOOT 移植到 ARM9 平台中的解决方案主要针对的是 ARM9 中的 NOR 闪存,因为 NOR 闪存的结构特点致使应用程序可以直接在其内部运行,不用把代码读到 RAM 中,移植过程相对简单。从 NAND 闪存中启动 U-BOOT 的设计难点在于 NAND 闪存需要把 U-BOOT 的代码搬移到 RAM 中,并要让 U-BOOT 支持 NAND 闪存的命令操作。本文介绍了实现这一设计的思路及具体程序。移植后, U-BOOT 在嵌入式系统中运行良好。