Uboot 下 CFI Nor Flash 的使用
韩大卫@吉林师范大学
2015.1.23
Flash : Micron Technology. 32MB.
Uboot: 2_3_0
CPU平台: Cavium Inc
交叉编译器: mips64-octeon-linux-gnu-gcc (Cavium Inc. Version: 2_3_0 build 128) 4.3.3
nor flash 的使用特点是 : 读操作可以按地址读, 写之前必须进行擦除, 一旦擦除必须擦除整个扇区.
新型的flash 使用3V 的电压便可以进行整个扇区的擦除和写入操作
任何芯片的使用, 都离不开驱动的支持. uboot下的nor flash的驱动逻辑非常简单. 而且, 对于符合 CFI ( Common Flash Interface )规范的flash芯片,驱动有很大的通用性.
uboot 提供了很好的 flash 驱动逻辑 和 flash的使用范例, 这些基本的使用方法在linux里也是同样的逻辑,只不过linux下需要加上一层分区信息. 结合flash 芯片手册, 可以对nor flash的使用逻辑有较为清晰的理解.
nor flash的驱动初始化部分:
arch/mips/cpu/octeon/start.S
board_init_r -> flash_init()
drivers/mtd/cfi_flash.c
unsigned long flash_init (void){
for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; ++i) {
flash_info[i].flash_id = FLASH_UNKNOWN;
…
//由于使用的flash 是新型的CFI 规范的flash, 没有使用 CONFIG_FLASH_CFI_LEGACY 这个宏, 所以flash_detect_legacy直接返回0
if (!flash_detect_legacy(cfi_flash_bank_addr(i), i))
flash_get_size(cfi_flash_bank_addr(i), i);
size += flash_info[i].size;
ulong flash_get_size (phys_addr_t base, int banknum)
{
flash_info_t *info = &flash_info[banknum];
int i, j;
flash_sect_t sect_cnt;
phys_addr_t sector;
unsigned long tmp;
int size_ratio;
uchar num_erase_regions;
int erase_region_size;
int erase_region_count;
struct cfi_qry qry;
unsigned long max_size;
memset(&qry, 0, sizeof(qry));
info->ext_addr = 0;
info->cfi_version = 0;
#ifdef CONFIG_SYS_FLASH_PROTECTION
info->legacy_unlock = 0;
#endif
info->start[0] = (ulong)map_physmem(base, info->portwidth, MAP_NOCACHE);
//如果是CFI 接口, 那么有统一的查询规范, 将查询到的信息保存到 qry中
if (flash_detect_cfi (info, &qry)) {
info->vendor = le16_to_cpu(qry.p_id);
info->ext_addr = le16_to_cpu(qry.p_adr) * 2;
debug("extended address is 0x%x\n", info->ext_addr);
num_erase_regions = qry.num_erase_regions;
if (info->ext_addr) {
#define FLASH_OFFSET_CFI_RESP 0x20
flash_detect_cfi ->
static int __flash_detect_cfi (flash_info_t * info, struct cfi_qry *qry)
{
int cfi_offset;
for (cfi_offset=0;
cfi_offset < sizeof(flash_offset_cfi) / sizeof(uint);
cfi_offset++) {
/* Issue FLASH reset command */
flash_cmd_reset(info);
flash_write_cmd (info, 0, flash_offset_cfi[cfi_offset],
FLASH_CMD_CFI);
//向0x20 地址进行查询, CFI 规定 , 前三个字符应该是 Q, R, Y
if (flash_isequal (info, 0, FLASH_OFFSET_CFI_RESP, 'Q')
&& flash_isequal (info, 0, FLASH_OFFSET_CFI_RESP + 2, 'R')
&& flash_isequal (info, 0, FLASH_OFFSET_CFI_RESP + 4, 'Y')) {
//如果确认为CFI 规范, 那么就按照 struct cfi_qry数据结构进行查询
flash_read_cfi(info, qry, FLASH_OFFSET_CFI_RESP,
sizeof(struct cfi_qry));
…
//在进行CFI 规范查询之后, 还要将addr_unlock1 , addr_unlock2 进行赋值, 这两个地址分别表示8位宽的地址和16位宽的地址, 可以实现byte和word的操作.
//一般地, 我们只使用addr_unlock1
//在一些代码里, 这两个数值就通过宏定义来实现的
info->addr_unlock1 = 0xaaa;
info->addr_unlock2 = 0x555;
…
}
下面是flash 芯片手册里CFI 规范查询的信息:
cfi_qry 定义:
struct cfi_qry {
u8 qry[3]; //保存 Q, R, Y
u16 p_id; //Primary algorithm
u16 p_adr; //Address for primary algorithm
u16 a_id; //Alternate
u16 a_adr; //Address for alternate
u8 vcc_min; // 最小Vcc
u8 vcc_max; //最大Vcc
u8 vpp_min; //最小Vpp
u8 vpp_max; //最大Vpp
u8 word_write_timeout_typ; //字节写典型超时
u8 buf_write_timeout_typ; //缓存写典型超时
u8 block_erase_timeout_typ; //块擦除典型超时
u8 chip_erase_timeout_typ; //整片擦除典型超时
u8 word_write_timeout_max; //字节写最大超时
u8 buf_write_timeout_max; //缓存写最大超时
u8 block_erase_timeout_max; //块写最大超时
u8 chip_erase_timeout_max; //整片擦除最大超时
u8 dev_size; //芯片大小
u16 interface_desc; //接口描述
u16 max_buf_write_size; //最大缓存写长度
u8 num_erase_regions; //擦除块扇区数量
u32 erase_region_info[NUM_ERASE_REGIONS]; //4个块区的信息
} __attribute__((packed));
从上图可以看到, 是获取了CFI query identification string , System interface information , Device geometry definition 信息,对照手册, 就可以知道成员的数值
其中, 最为重要的是擦写扇区信息 erase_region_info, 对应手册的如下信息:
手册给出了扇区的信息, 第一部分说明了扇区(block)的个数 : 0xff + 1 = 256 个, 第二部分说明了一个扇区(block)大小: 0x200 * 256 =131072, 即128K字节
我们的flash, 为00ff, 和0200 .那么uint32_t的tmp 的数值应该为: 0x020000ff
tmp = le32_to_cpu(qry.erase_region_info[i]);
debug("erase region %u: 0x%08lx\n", i, tmp);
erase_region_count = (tmp & 0xffff) + 1;
tmp >>= 16;
erase_region_size = (tmp & 0xffff) ? ((tmp & 0xffff) * 256) : 128;
tmp = qry.erase_region_info[i] = 0x20000ff
tmp >>=16 后, tmp = 0x200
擦写扇区的大小 erase_region_size = (tmp & 0xffff) * 256 = 0x20000 , 即一个扇区的大小为0x2000字节.
擦写扇区的个数 erase_region_count为0x201, 即256个扇区
那么, 可以知道, 整个nor flash 总的容量为: 0x2000 * 256 = 33554432 字节,
验证一下: 33554432 / 1024 / 1024 = 32 M
sect_cnt = 0;
sector = base;//基地址为 0x1dc00000
…
那么会循环256次.
for (j = 0; j < erase_region_count; j++) {
..
//在256次循环中, 256个start成员保存各个扇区的地址
info->start[sect_cnt] =
(ulong)map_physmem(sector,
info->portwidth,
MAP_NOCACHE);
//计算各个扇区的地址, 地址计算方法为, 扇区的大小 * size_ratio( 为 size_ratio = in