引言 <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

随着嵌入式系统的日趋复杂 , 它对大容量数据存储的需求越来越紧迫。而嵌入式设备低功耗、小体积以及低成本的要求 , 使硬盘无法得到广泛的应用。 NAND 闪存设备就是为了满足这种需求而迅速发展起来的。目前关于 U-BOOT 的移植解决方案主要面向的是微处理器中的 NOR 闪存,如果能在微处理器上的 NAND 闪存中实现 U-BOOT 的启动,则会给实际应用带来极大的方便。

U-BOOT 简介

U-BOOT 支持ARM PowerPC 等多种架构的处理器,也支持 Linux NetBSD VxWorks 等多种操作系统,主要用来开发嵌入式系统初始化代码 bootloader bootloader 是芯片复位后进入操作系统之前执行的一段代码,完成由硬件启动到操作系统启动的过渡,为运行操作系统提供基本的运行环境,如初始化 CPU 、堆栈、初始化存储器系统等,其功能类似于 PC 机的 BIOS U-BOOT 执行流程图如图 1 所示。

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /> 

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 在嵌入式系统中运行良好。