常常说自己是老年人记忆,因为看过的东西很快就忘。秉承着看自己写的东西肯定要比看别人写东西要好理解的原则。写下这个系列的u-boot移植,同时也加深自己的理解。其实到现在网上大神很多,给出了很多的解决方案。我也以他们为参考。与之不同我会加上一些我自己对某些地方的理解来进行处理。希望对大家有帮助。
开发板:天嵌TQ2440
u-boot:u-boot-1.1.6
手里的开发板是天嵌的TQ2440,看的书是韦东山老师的《嵌入式Linux开发完全手册》,有些许不同的地方,增加了移植困得,当应该也更加深自己的理解。
NAND FLASH型号:K9F2G08U0A
对于NAND FLASH,我们需要了解如下的信息。K9F2G08UXA 是一个具有128K页,2048块,每一块64页,每一页具有(2K+64)byte,2K是主区,64是信息区域,每一块是(128K+4K)byte的空间。所有的操作都是通过一个8位的IO实现。对每一页的访问通过列地址进行访问,2K空间一共需要11个位宽,因此列访问需要两次,第一次前8位,
第二次前4位,后4位为0。行地址和列地址是分开访问的。页访问通过行地址实现,一共128K,则需要17位位宽,这样8位IO口需要三次才能访问完全,前两次的8位,后一次的1位,其他位都必须为0。因此K9F2G08UXA 的地址是5次地址方式。这才导致了5个周期输入。实质上存储空间不止256M。具体的操作的方式是通过一些命令实现的,因此需要熟悉命令,才能实现控制。我们以一张图来看看NAND FLASH的构架。
首先我们在include/configs/TQ2440.h文件中的CONFIG_COMMANDS中增加CFG_CMD_NAND的命令,如下所示。
#define CONFIG_COMMANDS \
(CONFIG_CMD_DFL | \
CFG_CMD_CACHE | \
CFG_CMD_NAND | \
CFG_CMD_REGINFO | \
CFG_CMD_DATE | \
CFG_CMD_PING | \
CFG_CMD_ELF)
定义的CFG_CMD_NAND实际上就相当于u-boot对nand支持的总开关,定义之后u-boot对于NAND的应用就被打开了。我们先编译一下,看看现在是什么状态。make all,发现了如下的报错信息。
/work/system/u-boot-1.1.6/include/linux/mtd/nand.h:412: error: `NAND_MAX_CHIPS' undeclared here (not in a function)
nand.c:35: error: `CFG_MAX_NAND_DEVICE' undeclared here (not in a function)
nand.c:38: error: `CFG_NAND_BASE' undeclared here (not in a function)
nand.c:35: error: storage size of `nand_info' isn't known
nand.c:37: error: storage size of `nand_chip' isn't known
nand.c:38: error: storage size of `base_address' isn't known
nand.c:37: warning: 'nand_chip' defined but not used
nand.c:38: warning: 'base_address' defined but not used
可以看到实际上虽然我们打开了对于NAND FLASH的支持,但是我们并没有添加NAND的信息,这样NAND肯定是有意见的。我们没有定义这三个宏定义,也缺少一些基本信息。那么我们首先在include/configs/TQ2440.h中添加三个宏定义信息。如下所示
/*-----------------------------------------------------------------------
* NAND FLASH environment organization
*/
#define CFG_NAND_BASE 0x4E000000 /* define the base address */
#define CFG_MAX_NAND_DEVICE 1 /* the count of device */
#define NAND_MAX_CHIPS 1 /* every device is consist of 1 chip */
可以看到我们定义的CFG_NAND_BASE是使用S3C2410_NAND_BASE来定义的,这是因为S3C2440的NAND控制器首地址是与S3C2410相同的,另外定义CFG_NAND_BASE实际上就是指的S3C2440的NAND控制器的所有寄存器的首地址。所以这里定义的CFG_NAND_BASE即为S3C2410_NAND_BASE所代表的0x4E000000.
对于u-boot来说,他的启动分两个阶段,第一阶段的文件为cpu/arm920t/start.s和board/TQ2440/low_level_init.s文件,前者是和平台相关的,后者是和开发板相关的。第二阶段是从lib_arm/board.c中的start_armboot函数开始的,如下图所示
在start_armboot函数中flash_init完成之后,如果定了的nand则会初始化nand,函数为nand_init(),nand_init()是drivers/nand/nand.c文件中函数,在nand_init()中又调用了同文件中的nand_init_chip();,然后nand_init_chip()又调用同文件中的board_nand_init()函数,可以看到这个函数也在nand.c文件中,只不过只有个名字,没有被实现。我们需要自己来实现这个函数。
在编写nand_init_chip()之前我们应该知道s3c2410和s3c2440还是有差异存在。这其中就包括了nand控制寄存器的差异。在s3c2410中,nand控制寄存器地址范围为0x4E000000到0x4E000014(6个32位寄存器)。而在s3c2440在nand控制寄存器地址范围为0x4E000000到0x4E00003C(12个32位寄存器)。s3c2410的寄存器的定义是在include/S3c24x0.h中定义的(S3c2410.h包含这个头文件),代码中寄存器的定义是按照2410的寄存器定义的,因为s3c2440的nand控制器的寄存器更多一些,我们新建一个S3C2440_NAND结构体来定义我们要用的关于S3C2440的寄存器。具体添加什么寄存器,及对应顺序可以去s3c2440的数据手册里去查。添加完成后的两个片子的nand控制器寄存器结构体如下所示。
/* NAND FLASH (see S3C2410 manual chapter 6) */
typedef struct {
S3C24X0_REG32 NFCONF;
S3C24X0_REG32 NFCMD;
S3C24X0_REG32 NFADDR;
S3C24X0_REG32 NFDATA;
S3C24X0_REG32 NFSTAT;
S3C24X0_REG32 NFECC;
} /*__attribute__((__packed__))*/ S3C2410_NAND;
/* NAND FLASH (see S3C2440) */
typedef struct {
S3C24X0_REG32 NFCONF;
S3C24X0_REG32 NFCONT;
S3C24X0_REG32 NFCMD;
S3C24X0_REG32 NFADDR;
S3C24X0_REG32 NFDATA;
S3C24X0_REG32 NFMECCD0;
S3C24X0_REG32 NFMECCD1;
S3C24X0_REG32 NFSECCD;
S3C24X0_REG32 NFSTAT;
S3C24X0_REG32 NFESTAT0;
S3C24X0_REG32 NFESTAT1;
S3C24X0_REG32 NFMECC0;
S3C24X0_REG32 NFMECC1;
S3C24X0_REG32 NFSECC;
S3C24X0_REG32 NFSBLK;
S3C24X0_REG32 NFEBLK;
} /*__attribute__((__packed__))*/ S3C2440_NAND;
好接下来我们只要实现board_nand_init()函数就好了。为了能够更好地层次化处理我们代码我们在单独编写一个s3c2440_nand.c文件来单独实现一个s3c2440_nand_init()函数以及其他几个用到的函数。这样我们就可以直接在drivers/nand/nand.c中用board_nand_init()直接调用函数s3c2440_nand_init函数就可以了。
首先为了更好地编写s3c2440_nand.c文件,我们在include/s3c2410.h中添加对于获取nand地址的函数,即仿照S3C2410_GetBase_NAND函数定义实现一个函数名为S3C2410_GetBase_NAND的函数来获取地址,如下所示
static inline S3C2410_NAND * const S3C2410_GetBase_NAND(void)
{
return (S3C2410_NAND * const)S3C2410_NAND_BASE;
}
static inline S3C2440_NAND * const S3C2440_GetBase_NAND(void)
{
return (S3C2440_NAND * const)S3C2440_NAND_BASE;
}
这里的S3C2440_NAND_BASE我们需要在前面的宏定义添加一下,这个地址是芯片寄存器已经定义好的。宏定义如下所示
#define S3C2440_NAND_BASE 0x4E000000
(这里需要说明一下,这里定义的S3C2440_NAND_BASE地址和我们之前定义的CFG_NAND_BASE一样都指向了S3C2440片子的NAND控制器寄存器的首地址。)
然后编写的s3c2440_nand.c文件代码为(s3c2440_nand.c的文件路径是/cpu/arm920t/s3c24x0/s3c2440_nand.c)
/*
* Nand flash interface of s3c2410/s3c2440, by www.100ask.net
* Changed from drivers/mtd/nand/s3c2410.c of kernel 2.6.13
*/
#include <common.h>
#if (CONFIG_COMMANDS & CFG_CMD_NAND) && !defined(CFG_NAND_LEGACY)
#include <s3c2410.h>
#include <nand.h>
DECLARE_GLOBAL_DATA_PTR;
#define S3C2440_NFSTAT_READY (1<<0)
#define S3C2440_NFCONT_nFCE (1<<1)
/* select chip, for s3c2440 */
static void s3c2440_nand_select_chip(struct mtd_info *mtd, int chip)
{
S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
if (chip == -1) {
s3c2440nand->NFCONT |= S3C2440_NFCONT_nFCE;
} else {
s3c2440nand->NFCONT &= ~S3C2440_NFCONT_nFCE;
}
}
/* command and control functions */
static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd)
{
S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
struct nand_chip *chip = mtd->priv;
switch (cmd) {
case NAND_CTL_SETNCE:
case NAND_CTL_CLRNCE:
printf("%s: called for NCE\n", __FUNCTION__);
break;
case NAND_CTL_SETCLE:
chip->IO_ADDR_W = (void *)&s3c2440nand->NFCMD;
break;
case NAND_CTL_SETALE:
chip->IO_ADDR_W = (void *)&s3c2440nand->NFADDR;
break;
/* NAND_CTL_CLRCLE: */
/* NAND_CTL_CLRALE: */
default:
chip->IO_ADDR_W = (void *)&s3c2440nand->NFDATA;
break;
}
}
/* s3c2440_nand_devready()
*
* returns 0 if the nand is busy, 1 if it is ready
*/
static int s3c2440_nand_devready(struct mtd_info *mtd)
{
S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
return (s3c2440nand->NFSTAT & S3C2440_NFSTAT_READY);
}
/*
* Nand flash hardware initialization:
* Set the timing, enable NAND flash controller
*/
static void s3c2440_nand_inithw(void)
{
S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
#define TACLS 0
#define TWRPH0 4
#define TWRPH1 2
/* Set flash memory timing */
s3c2440nand->NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
/* Initialize ECC, enable chip select, NAND flash controller enable */
s3c2440nand->NFCONT = (1<<4)|(0<<1)|(1<<0);
}
/*
* Called by drivers/nand/nand.c, initialize the interface of nand flash
*/
void s3c2440_nand_init(struct nand_chip *chip)
{
S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
s3c2440_nand_inithw();
chip->IO_ADDR_R = (void *)&s3c2440nand->NFDATA;
chip->IO_ADDR_W = (void *)&s3c2440nand->NFDATA;
chip->hwcontrol = s3c2440_nand_hwcontrol;
chip->dev_ready = s3c2440_nand_devready;
chip->select_chip = s3c2440_nand_select_chip;
chip->options = 0;
chip->eccmode = NAND_ECC_SOFT;
}
#endif
(这段代码是直接由韦东山老师的代码改的)
(详细的研究下这段代码的作用,多研究http://blog.csdn.net/ying_seven/article/details/6888923)
接着修改drivers/nand/nand.c文件在其中建立函数board_nand_init(),其代码为
void board_nand_init(struct nand_chip *nand)
{
s3c2440_nand_init(nand);
}
最后修改将s3c2440_nand.c文件加入到编译链中,就可以了。修改cpu/arm920t/s3c24x0/Makefile文件
修改前:
COBJS = i2c.o interrupts.o serial.o speed.o \
usb_ohci.o
修改后:
COBJS = i2c.o interrupts.o serial.o speed.o \
usb_ohci.o s3c2440_nand.o
这样就大功告成了,我们编译一下make all,然后编译通过,就可以了
最后说一下关于nand中的调用关系实际上是比较复杂,也比较完善的。我这里并没有提及这些东西,因为我也不是搞的很清楚,本博文的移植实际上也只是皮毛的移植。如果想深入了解,这里有一篇好博文大家可以去看。我自己也深受其益。http://blog.csdn.net/ying_seven/article/details/6888923
参考文献:1、《嵌入式linux开发完全手册》、韦东山
2、http://blog.csdn.NET/zhaocj (这位博主下面的u-boot系列)
3、http://blog.csdn.net/ying_seven/article/details/6888923