1.nand_init_chip()顺序调用board_nand_init()和nand_scan()实现NAND FLASH初始化工作。
2.其中board_nand_init()实现S3C2440 NAND FLASH控制器相关的初始化,设置控制器的时序,设置寄存器的读写地址。
3.nand_scan()主要实现NAND FLASH各种参数设置(比如自动识别NAND FLASH的大小、每一页的大小、数据宽度等信息),设置对NAND FLASH的片选、读写函数以及NAND FLASH的控制命令。
二、读数据函数调用关系
调用到最后,使用的是一个宏readb,读取nand_chip->IO_ADDR_R地址处的数据,就完成了读数据的过程。对于S3C2440来说,nand_chip->IO_ADDR_R对应的就是NAND FLASH控制器的数据寄存器NFDATA,地址为0x4E000010。
三、关键数据结构
控制NAND FLASH时,通过结构体mtd_info的*priv成员找到对应的结构体nand_chip,然后调用读写、片选等函数,最终实现对NANDFLASH的控制。
struct nand_flash_dev {
char *name;
int id;
unsigned long pagesize;
unsigned long chipsize;
unsigned long erasesize;
unsigned long options;
};
结构nand_flash_dev的内容比较少,其各项含义为:
name:NAND FLASH的厂家名字
id: NAND FLASH的ID
pagesize:一页的大小,单位为字节Byte
chipsize:整个NAND FLASH 的大小,单位为MB
erasesize:最小擦除大小单位为字节Byte
options:选项
四、获知外接NAND容量大小
u-boot先读取外接NAND FLASH的ID,根据ID 在struct nand_flash_dev nand_flash_ids[]定义的数组中找到匹配的项,这样就确定NAND FLASH的大小了。
struct nand_flash_dev nand_flash_ids[] = {
……
{"NAND 256MiB 1,8V 8-bit",0xAA, 0, 256, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
};
首先在配置文件smdk2410.h(路径:include/configs/smdk2410.h)的宏CONFIG_COMMANDS中增加CFG_CMD_NAND
然后,增加NAND FLASH的一些宏,启用NAND FLASH设备。在配置文件smdk2410.h(路径:include/configs/smdk2410.h)中增加以下有关NAND FLASH的宏定义:
/*NAND FLASH config*/
#define CFG_NAND_BASE 0
#define CFG_MAX_NAND_DEVICE 1
#define NAND_MAX_CHIPS 1
2.增加NAND FLASH底层驱动
board_nand_init()主要实现开发板上NAND FLASH芯片K9F2G08的底层驱动。包括芯片时序设置,底层函数的实现。
3.解决其他错误
增加驱动后,再次编译,出现错误:
In function `s3c2440_nand_select_chip':
undefined reference to `S3C2440_GetBase_NAND'
提示函数S3C2440_GetBase_NAND()未定义,参考S3C2410_GetBase_NAND(),在s3c2410.h(路径:include/s3c2410.h)中增加函数
static inline S3C2440_NAND * const S3C2440_GetBase_NAND(void)
{
return (S3C2440_NAND * const)S3C2440_NAND_BASE;
}
然后增加S3C2440_NAND_BASE的基址
#define S3C2410_NAND_BASE 0x4E000000
#define S3C2440_NAND_BASE 0x4E000000
将nand视作一个MTD设备
uboot将nand视作一个mtd设备,所以使用mtd机制对nand设备进行管理。单个nand设备用nand_info_t来描述。而nand_info_t实际上就是mtd结构。最多支持的nand设备数CONFIG_SYS_MAX_NAND_DEVICE可自行配置。
typedef struct mtd_info nand_info_t;
nand_info_t nand_info[CONFIG_SYS_MAX_NAND_DEVICE];
但是仅使用mtd结构来描述不够,因为MTD只是一个通用的存储描述结构,而Nand设备特定的某些属性,如ECC布局等不能简单的添加到mtd结构中。所以,uboot定义了nand_chip结构。
在nand_init_chip()中通过以下代码关联nand_info结构和nand_chip结构:
mtd->priv = nand;
使得一个MTD结构既可以描述MTD结构的通用性,又能在需要时访问nand设备特定的属性和操作函数。
基本的nand操作算法
uboot提供了nand设备的通用操作算法,这些操作算法集中在nand_base.c。
对于移植而言,开发者需要做的是重新定义board_nand_init(),在该函数中完成NAND设备中的MTD结构部分接口函数和属性的设置。其中某些属性是必须设置的,如下所示:
int board_nand_init(struct nand_chip *nand)
nand->IO_ADDR_R = (void __iomem *)(NFDATA);
nand->IO_ADDR_W = (void __iomem *)(NFDATA);
nand->cmd_ctrl = s3c_nand_hwcontrol;
nand->dev_ready = s3c_nand_device_ready;
nand->ecc.mode = NAND_ECC_HW;
虽然Nand型号非常多;但是其基本接口都一致,并且读写Nand块、页中数据的算法都是类似的。所以,uboot在nand_base.c中提供了与nand控制器无关的操作算法,而与硬件相关的部分则由用户驱动实现。
ECC管理机制
借助存储在OOB区的ECC码,可以检测出读取的数据是否有错误位。而当出现的错误位数在容许的范围内时,可对数据进行纠正,保证读取数据正确,避免丢弃整个块。
uboot使用nand_ecc_ctrl结构来定义ecc相关的操作模式和接口。
对于ECC操作而言,最主要的两个操作为根据输入计算ECC码(calculate)和根据ECC码对数据进行纠错(correct)。
在board_nand_init()中初始化nand驱动接口时,可以根据需要初始化ECC操作接口。
ECC的分组处理
通常ECC码需要分组计算,即将整个nand页划分为成多个部分,每部分数据计算一组ECC码,最后统一放置到OOB区。例如,s5pv210的8Bit硬件ECC计算,每512字节生成一组ECC,单个页的ECC计算代码如下:
for (i = 0; eccsteps; eccsteps–, i += eccbytes, p += eccsize) {
s3c_nand_enable_hwecc_8bit(mtd, NAND_ECC_WRITE);
chip->write_buf(mtd, p, eccsize);
s3c_nand_calculate_ecc_8bit(mtd, p, &ecc_calc[i]);
}
硬件ECC
s5pv210支持1位、4位、8位、12位、16位的硬件ECC,相应的ECC检错和纠错代码已经包含在nand驱动源码中。
坏块管理
uBoot对Nand设备的坏块管理有两种:一种是读写时跳过坏块;一种是基于坏块表。
跳过坏块是最简单也是最常见处理方式,在nand_util.c中nand_read_skip_bad和nand_write_skip_bad提供了该种处理方式。
Nand flash芯片工作原理:
------------------------------------
Nand flash芯片型号为Samsung K9F2G08U0A,数据存储容量为256MB,总线宽度为8bit,页大小为2048字节,需要5个寻址命令,采用块页式存储管理。8个I/O引脚充当数据、地址、命令的复用端口。
芯片内部存储布局及存储操作特点:
一片Nand flash为一个设备(device), 其数据存储分层为:
1 (Device) = 2048 (Blocks)
1 (Block) -= 64 (Pages/Rows) 页与行是相同的意思,叫法不一样
1 (Page) = 数据块大小(2KB) + OOB 块大小(64Bytes)
在每一页中,最后64个字节(又称OOB)用于Nand Flash命令执行完后设置状态用,剩余2k字节又分为前半部分和后半部分。可以通过Nand Flash命令00h/01h/50h分别对前半部、后半部、OOB进行定位通过Nand Flash内置的指针指向各自的首地址。
存储操作特点:
1. 擦除操作的最小单位是块。
2. Nand Flash芯片每一位(bit)只能从1变为0,而不能从0变为1,所以在对其进行写入操作之前要一定将相应块擦除(擦除即是将相应块得位全部变为1).
3. 对于现在常见的页大小为2K的nand flash,把块中第一页的OOB部分的第1个字节标志为是否是坏块,如果不是坏块该值为FF,否则为坏块。
4. 除OOB第1字节外,通常至少把OOB的前3个字节存放Nand Flash硬件ECC码
------------------------------------
10.nand_default_bby()选择一个坏块描述表,返回时调用本文件中的nand_scan_bbt()。
11.nand_scan_bbt()寻找建立一个坏块描述表。
下面对命令nand read addr ofs size的执行流程进行分析:
1.nand read addr ofs size命令的作用是从nand flash地址的偏移量ofs处读取长度为size字节的数据存储到内存地址addr处。
2.common/main.c文件中的main_loop()主要执行read_line()读取命令行。
3.read_line()读取到命令行后会调用common/main.c文件中的run_command()。
4.run_command()调用common/command.c文件中的find_cmd()在.u_boot_cmd段中寻找该命令的cmd_tbl_t结构,找到后返回该结构。该命令的结构是通过定义在include/command.h中的宏定义U_BOOT_CMD登记进.u_boot_cmd段中的。
5.run_command()找到该命令的cmd_tbl_t结构后则执行该命令对应的函数。对于本情景是nand命令对应的函数do_nand()。
Nand flash读写模式下的管脚配置
D[7:0] : 数据/命令/地址 输入/输出端口(三线共享)
CLE : Command Latch Enable (output)
ALE : Address Latch Enable (output)
nFCE : NAND Flash Chip Enable (output)
nFRE : NAND Flash Read Enable (output)
nFWE : NAND Flash Write Enable (output)
R/nB : NAND Flash Ready/nBusy (input)
在copy_main中,已经介绍的nand_flash的最底层的操作,结合SPC来看,以HY系列的NAND为例,首先是nand_flash的芯片,关注的是IO CE WE RE ALE CLE RB,这里WE RE与AT91sam9260直接相连,CE 片选 CLE 命令锁存, ALE地址锁存, R/B ready/busy
(1)与flash相连的相关口线的配置
(2) 发送命令ReadId 读取marker Code和Device Code,分析而到页大小,块大小,OOB大小,对于页大小大于512字节的,需要再读后面的2字节
(3)发送read命令读取连续数据
Sequential read 命令为00,页内地址,页偏移,等待ready,读取一页的数据,包括oob,然后再等待ready,在进行读
这里读完一页之后busy的原因是,此时flash将下一页数据从flash写到缓存中,不允许读
(4)校验通过ECC
如果使用的是硬件ECC,在读取数据之后会将校验值放在相关寄存器中,读取相关寄存器,进行判断即可。
这里移植或者开发的时候需要注意 ECC校验位长度和起始字节 坏块的起始和长度
(5)擦除命令
发送命令0x60,设置需要擦除的页地址(擦除一块),写擦除命令0Xd0,等待擦除完毕,读取I/O0数值,为0则表示擦除成功
(6)写数据
写数据先发送命令0X80,页内地址,页偏移,后数据(在一页范围内数据),
Program command 0X10,接着FLASH进入BUSY,等待ready之后,写0x70(read status Command),之后通过读取I/O0判断写数据是否正确
以上就是NAND FLASH中一些比较基本的命令(read_oob之类略)
二层:封装(mtd)
对于MTD(memory technology device)
将所有的方法封装在两个结构体中 mtd_info 和 nand_chip
在注册驱动时,调用nand_init_chip,最终注册的就是这两个结构体
上图是mtd_info 和 nand_chip的一些结构封装,对于移植(更换新的NAND FLASH,或者不同的SOC),只要改写底层(就是第二层)的相关函数就可以。
比如读数据的命令,在MTD封装中包括两部分,一部分是nand_chip->cmdctrl发送命令,一部分是nand_chip->read_page,而这两部分都封装在mtd_info *info的info->read中,Write和erase,isbad_block类似。
至于ECC部分(移植相关,可以参看stage1中copy_main函数的实现),根据不同的页大小配置不同的ECC.Layout
其相关操作在nand_chip中
所以移植过程只要注意修改nand_chip结构下的操作方法即可。
三层:驱动的注册和相关调用
U-BOOT中注册驱动的时候调用 nand_init_chip
有四个调用,Board_nand_init(mtd),Nand_scan_ident,Board_nand_ecc_init,
Nand_scan_tail
对于这四个调用不细说,注册之后只要学会第三层相关调用。
得到对应的mtd_info 结构指针之后,就可以调用相应方法了
包括
Info->read,