引言 <?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为例,进行介绍。
Uboot对SMDK2410板的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板是一款用S3C2410做CPU的DEMO 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 在嵌入式系统中运行良好。
转载于:https://blog.51cto.com/ironpeak/90946