本文主要是关于Nor Flash的相关介绍,并着重对Nor Flash的编写及驱动程序进行了相近的阐述。
Nor Flash驱动编写
1.
Bottom/Top Boot Sect(底部/顶部 启动块)
所谓的boot sect,是指的是Nor Flash和Nand Flash不太一样。Nand Flash从开始到最后,都是由同样大小的page所组成的。
而Nor Flash,一般都是有个boot sect,好像是由于历史原因,常将Nor Flash用于作为存储启动代码的设备,也就是从Nor Flash启动,所以,这个boot sect块,专门设计出来,用于存放启动代码。如果详细解释,按照datasheet中的描述就是,第一个或最后一个,此处是bottom sect,所以是最后一个64KB大小的块,被分为4个独立的块。第一个16KB,用于少量的系统初始化代码。而2个8KB的块用于存储参数,余下的32KB的块叫做Main Block,用于存储应用程序的代码。
2.
Sector(扇区)
此处的sector(扇区)也就是flash里面的最小的擦除单位:块(block)。
所以sector count,也就是有多少个block。
3.
Sector Count (扇区数)和Sector List
此处的Nor Flash,M29W320DB,一共有正常的63个64KB的block,加上上面提到的原先是正常的1个64KB分成的4个小块,所以是63+4=67个。
而所谓的驱动中的sector list,也就是block list,代码注释写的也很清楚了:
ulong
start[CFG_MAX_FLASH_SECT];
/* physical sector start addresses */
用于存储每一个块的起始地址的。也是需要你驱动初始化的。对于这里的M29W320DB,也很简单了,从开始顺序加上块大小64K,直到最后3个,计算一下对应地址,填进去就可以了。
4.
Protect(写保护)
Nor Flash从软件的寄存器配置和硬件上,都提供了对应的保护机制,目的是,防止有意或无意间,把那些不希望被改动/删除的数据破坏了。比如有些机制把Flash的一些系统启动参数存储Nor Flash,或者是其他某种原因,只允许你使用部分Nor Flash的空间,所以,把这类需要保护的部分,在uboot的flash_info_t的结构体中,把对应的位设置成1:
uchar
protect[CFG_MAX_FLASH_SECT]; /* sector protection status
*/
这样,之后程序就可以避免有意无意的擦除有用的数据了。
【写Nor Flash驱动时的一些注意事项】
1.
位宽(bitwidth,X8/X16/X32)
在Nor Flash控制器,此处我这里用的是,ARM的PromeCell PL172,MPMC(MultiPort Memory Controller),可以接多种不同的存储设备,比如SRAM(Static Memory),Nor Flash,而其中又可以分别设置是否支持Page Mode,Extended wait和写保护(启用写保护,就可以看出是ROM了)等。
在硬件上MPMC和Nor Flash连接好了之后。在使用Nor Flash之前,要初始化MPMC。
这里说的,要注意位宽,是因为我开始就没注意到,所以,在开始初始化MPMC的时候,设置MPMCStaticConfig寄存器的时候,设置成了X16(16位),但是,后来去uboot中找到别人的Nor Flash的驱动(参考的是oardoxcflash.c),发出的命令去读ID,也都是X8(8位)的:
addr[0x0AAA] = 0xAA;
addr[0x0555] = 0x55;
addr[0x0AAA] = 0x90;
所以,导致读取Manufacture ID 和Device ID,都无法读正确,读的都是0xFF。后来重新去确认,在配置MPMCStaticConfig的时候,是配置的X16模式,然后再发命令,也对应的是按照X16模式发命令,可以参考:oardmvs1flash.c中的代码,读取ID时是:
addr[0x0555] = 0x00AA;
addr[0x02AA]= 0x0055;
addr[0x0555] = 0x0090;
才能正确读取到期望出的ID:
value = addr[0];
/* manufacturer ID
*/
读出来的是0x20h。
value = addr[1];
/* device ID
*/
读出来的是0x22CB
和datasheet中的匹配:
– Manufacturer Code: 0020h
– Bottom Device Code M29W320DB: 22CBh
2.
不同位宽对应不同的时序
此处说的时序,也就是上面提到的,X8和X16发的地址,是不一样的,而且顺序也不同。
而且还有一小点要注意的是,记得转换地址成对应的类型:
X8是vu_char *
X16是 vu_short *
这样再写入对应的地址和数值,就可以了。
3.
reset命令
看了uboot中的代码,好像是其他设备,多数的reset命令,都是0xFF。
而这里用到的是STM(STMicroelectronics,后来好像改成Intel和ST合资的恒忆(Numonyx)了。。。)的 Nor Flash,M29W320DB (32 M, bottom boot sect)
,比较特殊些,是0xF0。
4.
boot sector的位置
刚刚看了下datasheet,很汗的是,本以为bottom sect是底部的sect,是地址最大的那个,结果datasheet中的是倒叙计算开始处的,也就是地址最大的那个块,是第一个块,所以,此处的boot sector 是块0-3:
# Size (KByte/KWord) Address Range(x8 )/ Address Range (x16)
66 64/32 3F0000h-3FFFFFh
1F8000h-1FFFFFh
。。。。。
。。。。。
3 32/16 008000h-00FFFFh
004000h-007FFFh
2 8/4
006000h-007FFFh
003000h-003FFFh
1 8/4
004000h-005FFFh
002000h-002FFFh
0 16/8 000000h-003FFFh
000000h-001FFFh
Uboot下关于Nor Flash的驱动问题
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 ”, 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 ”, 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 = info-》portwidth / info-》chipwidth;,比值为1)
//可以看出, 各个扇区的地址相隔一个扇区的大小
sector += (erase_region_size * size_ratio);
…
sect_cnt++;
}
info-》sector_count = sect_cnt;
//buffer_size 为 1 《《 8 , 256
info-》buffer_size = 1 《《 (8 * info-》portwidth);
…
}
循环结束后, sect_cnt 的数值为 256
现在, 所有扇区的地址都保存到了init-》start数组里。 那么现在如果要向flash里烧写一个文件, 在知道文件的大小的情况下,就可以计算出要使用几个扇区。
include/flash.h:
#define CONFIG_SYS_MAX_FLASH_SECT (256)
typedef struct {
ulong size; /* total bank size in bytes */
ushort sector_count; /* number of erase units */
ulong flash_id; /* combined device & manufacturer code */
ulong start[CONFIG_SYS_MAX_FLASH_SECT]; /* virtual sector start address */
uchar protect[CONFIG_SYS_MAX_FLASH_SECT]; /* sector protection status */
#ifdef CONFIG_SYS_FLASH_CFI
uchar portwidth; /* the width of the port */
uchar chipwidth; /* the width of the chip */
ushort buffer_size; /* # of bytes in write buffer */
ulong erase_blk_tout; /* maximum block erase timeout */
ulong write_tout; /* maximum write timeout */
ulong buffer_write_tout; /* maximum buffer write timeout */
ushort vendor; /* the primary vendor id */
ushort cmd_reset; /* vendor specific reset command */
ushort interface; /* used for x8/x16 adjustments */
ushort legacy_unlock; /* support Intel legacy (un)locking */
ushort manufacturer_id; /* manufacturer id */
ushort device_id; /* device id */
ushort device_id2; /* extended device id */
ushort ext_addr; /* extended query table address */
ushort cfi_version; /* cfi version */
ushort cfi_offset; /* offset for cfi query */
ulong addr_unlock1; /* unlock address 1 for AMD flash roms */
ulong addr_unlock2; /* unlock address 2 for AMD flash roms */
const char *name; /* human-readable name */
#endif
} flash_info_t;
uboot 就是按照如上的思路来实现uboot的更新, common/cmd_flash.c 有很好的flash使用范例:
int do_upgrade (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int rcode = 0;
ulong addr, addr_first, addr_last;
const bootloader_header_t *header;
if (argc != 4) {
if (argc == 2 || argc == 3) {
if (strcmp(argv[1], “uboot”) != 0)
return cmd_usage(cmdtp);
//获取环境变量loadaddr的数值, 这是要更新的文件在内存里的起始地址
if (getenv(“loadaddr”) != NULL)
addr = simple_strtoul(getenv(“loadaddr”), NULL, 16);
else
return cmd_usage(cmdtp);
//(0x1fc00000 - CONFIG_SYS_FLASH_SIZE) = 0x1dc00000
//计算出uboot的起始地址
addr_first = CONFIG_SYS_FLASH_BASE;
if (argc == 3 && strcmp(argv[2], “all”) == 0) {
addr_last = addr_first + CONFIG_BOOT_SIZE - 1;
}else
//CONFIG_ENV_ADDR = 0x1fbe0000
//addr_last = 0x1fbdffff
//计算出uboot的结束地址
addr_last = CONFIG_ENV_ADDR - 1;
// 验证下载的uboot 释放符合bootload 的格式。
header = (void *)addr;
if (validate_header(header)) {
printf(“Image does not have valid header form addr:0x%lx ”, addr);
return 1;
}
。。.
//知道了uboot的起始,结束地址, 就可以知道uboot在flash 里要使用几个扇区。
/*
一, 先取消要使用的扇区保护, 参数0 表示取消保护
*/
if ((rcode = flash_sect_protect(0, addr_first, addr_last)) != 0)
return rcode;
//擦除要使用到的扇区
if ((rcode = flash_sect_erase(addr_first, addr_last)) != 0)
return rcode;
//向要使用到的扇区写入数据
puts (“Copy to Flash.。。 ”);
if ((rcode = flash_write((char *)addr, addr_first, addr_last - addr_first)) != 0) {
flash_perror(rcode);
return 1;
}
puts (“done ”);
return 0;
}
int flash_sect_protect (int p, ulong addr_first, ulong addr_last)
{
flash_info_t *info;
ulong bank;
int s_first[CONFIG_SYS_MAX_FLASH_BANKS], s_last[CONFIG_SYS_MAX_FLASH_BANKS];
int protected, i;
int planned;
int rcode;
/*
通过flash的起始地址和结束地址, 计算出起始扇区和结束扇区, 以及要使用到的扇区个数, 分别保存到s_first, s_last, planned 中。
*/
rcode = flash_fill_sect_ranges( addr_first, addr_last, s_first, s_last, &planned );
…
static int
flash_fill_sect_ranges (ulong addr_first, ulong addr_last,
int *s_first, int *s_last,
int *s_count )
{
flash_info_t *info;
ulong bank;
int rcode = 0;
*s_count = 0;
//初始化参数
for (bank=0; bank 《 CONFIG_SYS_MAX_FLASH_BANKS; ++bank) {
s_first[bank] = -1; /* first sector to erase */
s_last [bank] = -1; /* last sector to erase */
}
//只有一次循环
for (bank=0,info = &flash_info[0];
(bank 《 CONFIG_SYS_MAX_FLASH_BANKS) && (addr_first 《= addr_last);
++bank, ++info) {
ulong b_end;
int sect;
short s_end;
if (info-》flash_id == FLASH_UNKNOWN) {
continue;
}
//start[0]保存的是flash的起始地址 , size是整个芯片的大小, 那么info-》start[0] + info-》size - 1的 含义就是 整个芯片的结束地址
b_end = info-》start[0] + info-》size - 1; /* bank end addr */
//最后一个扇区的标号
s_end = info-》sector_count - 1; /* last sector */
//遍历所有扇区, 即256个扇区
for (sect=0; sect 《 info-》sector_count; ++sect) {
ulong end; /* last address in current sect */
//当前扇区的最后地址
end = (sect == s_end) ? b_end : info-》start[sect + 1] - 1;
if (addr_first 》 end)
continue;
//当uboot的结束地址小于当前扇区的地址时, 直接判断下个扇区。 目的是快速找到uboot的结束地址所在flash的扇区。
if (addr_last 《 info-》start[sect])
continue;
//当文件起始地址等于扇区起始地址, 将当前扇区地址保存到s_first[0] 中。
if (addr_first == info-》start[sect]) {
s_first[bank] = sect;
}
//当文件结束地址等于当前扇区结束地址时, 将当前扇区标号保存到s_last[0]中。。 这个部分uboot的代码需要优化, 正常的逻辑下, 这个时候可以直接break了。 无须再进入循环。 本人已经验证通过
if (addr_last == end) {
s_last[bank] = sect;
}
}
//如果s_first[0]有数值, 即查找成功的话, 计算出占有了几个扇区。
if (s_first[bank] 》= 0) {
//如果没有找到s_last, 有两种情况, 如果目标文件大于flash的大小, 那么设定s_last 为最后一个扇区。 否则是逻辑错误。
if (s_last[bank] 《 0) {
if (addr_last 》 b_end) {
s_last[bank] = s_end;
} else {
puts (“Error: end address”
“ not on sector boundary ”);
rcode = 1;
break;
}
} //如果得到的结果是结束的扇区标号小于起始扇区标号, 也是逻辑错误
if (s_last[bank] 《 s_first[bank]) {
puts (“Error: end sector”
“ precedes start sector ”);
rcode = 1;
break;
}
//记录结束扇区的编号。
sect = s_last[bank];
addr_first = (sect == s_end) ? b_end + 1: info-》start[sect + 1];
//s_last[bank] - s_first[bank] + 1 就是中间的扇区个数
(*s_count) += s_last[bank] - s_first[bank] + 1;
} else if (addr_first 》= info-》start[0] && addr_first 《 b_end) {
puts (“Error: start address not on sector boundary ”);
rcode = 1;
break;
} else if (s_last[bank] 》= 0) {
puts (“Error: cannot span across banks when they are”
“ mapped in reverse order ”);
rcode = 1;
break;
}
}
return rcode;
}
回到:
#ifndef CONFIG_SYS_NO_FLASH
int flash_sect_protect (int p, ulong addr_first, ulong addr_last)
{
flash_info_t *info;
ulong bank;
int s_first[CONFIG_SYS_MAX_FLASH_BANKS], s_last[CONFIG_SYS_MAX_FLASH_BANKS];
int protected, i;
int planned;
int rcode;
rcode = flash_fill_sect_ranges( addr_first, addr_last, s_first, s_last, &planned );
protected = 0;
if (planned && (rcode == 0)) {
for (bank=0,info = &flash_info[0]; bank 《 CONFIG_SYS_MAX_FLASH_BANKS; ++bank, ++info) {
if (info-》flash_id == FLASH_UNKNOWN) {
continue;
}
if (s_first[bank]》=0 && s_first[bank]《=s_last[bank]) {
debug (“%sProtecting sectors %d..%d in bank %ld ”,
p ? “” : “Un-”,
s_first[bank], s_last[bank], bank+1);
protected += s_last[bank] - s_first[bank] + 1;
//为获取到的扇区取消保护
for (i=s_first[bank]; i《=s_last[bank]; ++i) {
#if defined(CONFIG_SYS_FLASH_PROTECTION)
//就是 改变 info-》addr_unlock1 的标识和将info-》protect 的对应成员置0, 否则后面不能 erase 和write
if (flash_real_protect(info, i, p))
rcode = 1;
putc (‘。’);
#else
info-》protect[i] = p;
#endif /* CONFIG_SYS_FLASH_PROTECTION */
}
}
}
#if defined(CONFIG_SYS_FLASH_PROTECTION)
puts (“ done ”);
#endif /* CONFIG_SYS_FLASH_PROTECTION */
printf (“%sProtected %d sectors ”,
p ? “” : “Un-”, protected);
} else if (rcode == 0) {
puts (“Error: start and/or end address”
“ not on sector boundary ”);
rcode = 1;
}
return rcode;
}
#ifndef CONFIG_SYS_NO_FLASH
int flash_sect_erase (ulong addr_first, ulong addr_last)
{
flash_info_t *info;
ulong bank;
int s_first[CONFIG_SYS_MAX_FLASH_BANKS], s_last[CONFIG_SYS_MAX_FLASH_BANKS];
int erased = 0;
int planned;
int rcode = 0;
//跟之前取消保护一样, 也需要通过给定地址计算出要操作的扇区。 这个地方实在多余, 完全可以使用之前已经获取到的数据作为参数传下来。
//总之 flash_sect_erase 和 flash_sect_protect 的重复度太高
rcode = flash_fill_sect_ranges (addr_first, addr_last,
s_first, s_last, &planned );
if (planned && (rcode == 0)) {
for (bank=0,info = &flash_info[0];
(bank 《 CONFIG_SYS_MAX_FLASH_BANKS) && (rcode == 0);
++bank, ++info) {
if (s_first[bank]》=0) {
erased += s_last[bank] - s_first[bank] + 1;
debug (“Erase Flash from 0x%08lx to 0x%08lx ”
“in Bank # %ld ”,
info-》start[s_first[bank]],
(s_last[bank] == info-》sector_count) ?
info-》start[0] + info-》size - 1:
info-》start[s_last[bank]+1] - 1,
bank+1);
//flash_erase 是drivers/mtd/cfi_flash.c 提供的flash 擦除接口。
rcode = flash_erase (info, s_first[bank], s_last[bank]);
}
}
printf (“Erased %d sectors ”, erased);
} else if (rcode == 0) {
puts (“Error: start and/or end address”
“ not on sector boundary ”);
rcode = 1;
}
return rcode;
}
#endi
int flash_erase (flash_info_t * info, int s_first, int s_last)
{
…
for (sect = s_first; sect 《= s_last; sect++) {
////如果扇区处于保护状态, 将无法擦除
if (info-》protect[sect] == 0) { /* not protected */
switch (info-》vendor) {
…
break;
case CFI_CMDSET_AMD_STANDARD:
case CFI_CMDSET_AMD_EXTENDED:
flash_write_cmd (info, 0, 0, AMD_CMD_RESET); // (1)
flash_unlock_seq (info, sect); //(2)
flash_write_cmd (info, sect, info-》addr_unlock1,AMD_CMD_ERASE_START); //(3)
flash_unlock_seq (info, sect);//(4)
flash_write_cmd (info, sect, 0,AMD_CMD_ERASE_SECTOR);//(5)
break;
…
}
/*
根据手册, 扇区的擦写动作指令为:
#define AMD_CMD_UNLOCK_START 0xAA
#define AMD_CMD_UNLOCK_ACK 0x55
static void flash_unlock_seq (flash_info_t * info, flash_sect_t sect){
flash_write_cmd (info, sect, info-》addr_unlock1, AMD_CMD_UNLOCK_START);
flash_write_cmd (info, sect, info-》addr_unlock2, AMD_CMD_UNLOCK_ACK);
}
全部擦写的操作是,
__RESET
1, 向 0xaaa 写入 aa
2, 向 0x555 写入 55
3, 向 0xaaa 写入80
4, 向 0xaaa 写入aa
5, 向0x555 写入55
6, 向扇区地址 写入30
__RESET 由(1) 完成
1,2 由 (2) 完成
3 由 (3)完成
4,5由(4)完成
6 由 (5)完成
*/
/*
指令的下发后, 还要使用状态查询函数, 等待指令的完成, 即硬件的执行完成。 这个过程是最耗时的。
*/
if (use_flash_status_poll(info)) {
cfiword_t cword = (cfiword_t)0xffffffffffffffffULL;
void *dest;
//获取扇区的内存地址
dest = flash_map(info, sect, 0);
//传入的超时时间为 info-》erase_blk_tout, 这个数值为: (1 《《 qry.block_erase_timeout_typ) * (1 《《 qry.block_erase_timeout_max)
//根据手册, 计算出扇区最大超时时间为: 4096s, 意味着, 如果4096s内扇区还没有擦写完成, 那么就超时退出
st = flash_status_poll(info, &cword, dest, info-》erase_blk_tout, “erase”);
flash_unmap(info, sect, 0, dest);
} else
st = flash_full_status_check(info, sect,
info-》erase_blk_tout,
“erase”);
if (st)
rcode = 1;
else if (flash_verbose)
putc (‘。’);
if (ctrlc()) {
puts(“ Interrupted ”);
return 1;
}
}
}
if (flash_verbose)
puts (“ done ”);
return rcode;
}
static int flash_status_poll(flash_info_t *info, void *src, void *dst,
ulong tout, char *prompt)
{
#ifdef CONFIG_SYS_CFI_FLASH_STATUS_POLL
ulong start;
int ready;
…
start = get_timer(0);
WATCHDOG_RESET();
while (1) {
switch (info-》portwidth) {
case FLASH_CFI_8BIT:
/*根据flash 的位宽(portwidth), 判断目的地址的数值是否等于src地址的数值。 上面传下来src的数值为全f, dst地址是当前扇区的0地址,
那么flash_erase 的擦写指令完成的判断条件是: 当前扇区的0地址的数值为0xff
如果判断条件成立后跳出循环, 否则udelay后, 再次进入循环 */
ready = flash_read8(dst) == flash_read8(src);
break;
case FLASH_CFI_16BIT:
ready = flash_read16(dst) == flash_read16(src);
break;
case FLASH_CFI_32BIT:
ready = flash_read32(dst) == flash_read32(src);
break;
case FLASH_CFI_64BIT:
ready = flash_read64(dst) == flash_read64(src);
break;
default:
ready = 0;
break;
}
if (ready)
break;
if (get_timer(start) 》 tout) {
printf(“Flash %s timeout at address %lx data %lx ”,
prompt, (ulong)dst, (ulong)flash_read8(dst));
return ERR_TIMOUT;
}
udelay(1); /* also triggers watchdog */
}
#endif /* CONFIG_SYS_CFI_FLASH_STATUS_POLL */
return ERR_OK;
}
回到do_upgrade, 扇区擦写完成后, 调用flash_write 进行写入操作
code = flash_write((char *)addr, addr_first, addr_last - addr_first)) != 0) {
src 是要烧些的文件的起始, addr 是要烧写到flash的目的地址, cnt 是要烧写的长度
int flash_write (char *src, ulong addr, ulong cnt){
int i;
ulong end = addr + cnt - 1;
//在单个bank的flash里, 只有一个info, info_first等于info_last
flash_info_t *info_first = addr2info (addr);
flash_info_t *info_last = addr2info (end );
flash_info_t *info;
…
//在单个bank的flash里, 只有一次循环
for (info = info_first; info 《= info_last; ++info) {
ulong b_end = info-》start[0] + info-》size; /* bank end addr */
short s_end = info-》sector_count - 1;
for (i=0; i《info-》sector_count; ++i) {
ulong e_addr = (i == s_end) ? b_end : info-》start[i + 1];
//如果要操作的扇区没有取消保护, 直接返回
if ((end 》= info-》start[i]) && (addr 《 e_addr) &&
(info-》protect[i] != 0) ) {
return (ERR_PROTECTED);
}
}
}
/* finally write data to flash */
for (info = info_first; info 《= info_last && cnt》0; ++info) {
ulong len;
len = info-》start[0] + info-》size - addr;
if (len 》 cnt)
len = cnt;
//单个bank的flash调用 write_buf后返回操作结果
if ((i = write_buff(info, (uchar *)src, addr, len)) != 0) {
return (i);
}
//多个bank的情况
cnt -= len;
addr += len;
src += len;
}
return (ERR_OK);
}
//info 为flash的数据结构, src为源文件的内存地址, addr 为目的flash 地址, cnt 为文件要写的长度
int write_buff (flash_info_t * info, uchar * src, ulong addr, ulong cnt)
{
ulong wp;
uchar *p;
int aln;
cfiword_t cword;
int i, rc;
#ifdef CONFIG_SYS_FLASH_USE_BUFFER_WRITE
int buffered_size;
#endif
#ifdef CONFIG_FLASH_SHOW_PROGRESS
int digit = CONFIG_FLASH_SHOW_PROGRESS;
int scale = 0;
int dots = 0;
/*
* Suppress if there are fewer than CONFIG_FLASH_SHOW_PROGRESS writes.
*/
if (cnt 》= CONFIG_FLASH_SHOW_PROGRESS) {
scale = (int)((cnt + CONFIG_FLASH_SHOW_PROGRESS - 1) /
CONFIG_FLASH_SHOW_PROGRESS);
}
#endif
//wp的数值为addr
wp = (addr & ~(info-》portwidth - 1));
…
buffered_size = (info-》portwidth / info-》chipwidth);
buffered_size *= info-》buffer_size;
//buffered_size 为256
while (cnt 》= info-》portwidth) {
//buffer_size 长度为1的情况,就是按字节写的情况
if (info-》buffer_size == 1) {
cword.l = 0;
for (i = 0; i 《 info-》portwidth; i++)
flash_add_byte (info, &cword, *src++);
if ((rc = flash_write_cfiword (info, wp, cword)) != 0)
return rc;
wp += info-》portwidth;
cnt -= info-》portwidth;
continue;
}
//buffer_size 不为1, 按buffer 写的情况
//如果地址为buffer_size 的整数倍, 那么i 就等于 buffer_size.256 字节。
//可以看到, 按缓存写的话 , 总共会执行 (文件长度 / 256 + 1 次) 。 如果要写入的长度为 0xdffff, 那么要执行的次数为 0xdffff / 256 + 1 = 3584 次。
i = buffered_size - (wp % buffered_size);
if (i 》 cnt)
i = cnt; //如果缓存写长度大于剩余的要写入的文件长度, 那么长度截为cnt
if ((rc = flash_write_cfibuffer (info, wp, src, i)) != ERR_OK)
return rc;
i -= i & (info-》portwidth - 1);
wp += i; //要写入的内容的地址移动 i 长度
src += i; //要写入的文件的地址向后移动 i 长度
cnt -= i; //文件的剩余长度减去 i 长度
FLASH_SHOW_PROGRESS(scale, dots, digit, i);
}
…
if (cnt == 0) {
return (0);
}
/*
* handle unaligned tail bytes
*/
cword.l = 0;
p = (uchar *)wp;
for (i = 0; (i 《 info-》portwidth) && (cnt 》 0); ++i) {
flash_add_byte (info, &cword, *src++);
--cnt;
}
for (; i 《 info-》portwidth; ++i)
flash_add_byte (info, &cword, flash_read8(p + i));
return flash_write_cfiword (info, wp, cword);
}
对于字节写和缓存写, 分别 有flash_write_cfiword 和flash_write_cfibuffer 实现
static int flash_write_cfiword (flash_info_t * info, ulong dest,
cfiword_t cword)
{
void *dstaddr = (void *)dest;
int flag;
flash_sect_t sect = 0;
char sect_found = 0;
//根据端口宽度 , 判断要操作的地址上的数值是否为cword的数值。
//上面传的cword 为0 , 那么要判断要写的地址的数值是否为0 , 如果判断结果为假,那么退出,返回ERR_NOT_ERASE错误数值。提示没有经过擦写。
switch (info-》portwidth) {
case FLASH_CFI_8BIT:
flag = ((flash_read8(dstaddr) & cword.c) == cword.c);
break;
case FLASH_CFI_16BIT:
flag = ((flash_read16(dstaddr) & cword.w) == cword.w);
break;
case FLASH_CFI_32BIT:
flag = ((flash_read32(dstaddr) & cword.l) == cword.l);
break;
case FLASH_CFI_64BIT:
flag = ((flash_read64(dstaddr) & cword.ll) == cword.ll);
break;
default:
flag = 0;
break;
}
if (!flag)
return ERR_NOT_ERASED;
//上面看到, flash在执行烧些前, 要先取消保护, 再进行擦除, 当两者都成功后, 才可以进行write
//在执行烧些过程中, 关闭全部中断, 所有的中断新号会被忽略
flag = disable_interrupts ();
//根据不同厂商,执行对应的指令。
switch (info-》vendor) {
case CFI_CMDSET_INTEL_PROG_REGIONS:
case CFI_CMDSET_INTEL_EXTENDED:
case CFI_CMDSET_INTEL_STANDARD://intel 的规范
flash_write_cmd (info, 0, 0, FLASH_CMD_CLEAR_STATUS);
flash_write_cmd (info, 0, 0, FLASH_CMD_WRITE);
break;
case CFI_CMDSET_AMD_EXTENDED:
case CFI_CMDSET_AMD_STANDARD: //AMD 的规范
//根据目的地址找到要操作的扇区
sect = find_sector(info, dest);
//解锁扇区
flash_unlock_seq (info, sect);
//输入write 指令
flash_write_cmd (info, sect, info-》addr_unlock1, AMD_CMD_WRITE);
sect_found = 1;
break;
…
}
//等待指令完成
switch (info-》portwidth) {
case FLASH_CFI_8BIT:
flash_write8(cword.c, dstaddr);
if (info-》vendor != 1) {
while (flash_read8(dstaddr) != cword.c)
;
}
break;
case FLASH_CFI_16BIT:
flash_write16(cword.w, dstaddr);
if (info-》vendor != 1) {
while (flash_read16(dstaddr) != cword.w)
;
}
break;
case FLASH_CFI_32BIT:
flash_write32(cword.l, dstaddr);
case FLASH_CFI_64BIT:
flash_write64(cword.ll, dstaddr);
if (info-》vendor != 1) {
while (flash_read64(dstaddr) != cword.ll)
;
}
break;
}
//恢复中断
if (flag)
enable_interrupts ();
if (!sect_found)
sect = find_sector (info, dest);
if (use_flash_status_poll(info))
return flash_status_poll(info, &cword, dstaddr,
info-》write_tout, “write”);
else
return flash_full_status_check(info, sect,
info-》write_tout, “write”);
}
flash_write_cfibuffer 使用了同样的逻辑 , 不同的指令
结语
关于Nor Flash的相关介绍就到这了,如有不足之处欢迎指正。
-
驱动
+关注
关注
12文章
1928浏览量
88204 -
NOR flash
+关注
关注
2文章
98浏览量
23841
发布评论请先 登录
arm9 nor flash 地址
从uboot烧写完LCD的程序后从nor flash启动没有引导界面
使用nor flash中UBOOT下载代码到nand flash中uboot也被清除了?
OMAPL138 NOR FLASH启动 uboot移植的资料求分享?
i.MXRT上使能NOR Flash的Continuous read模式在软复位后无法正常启动问题的解决
关于NUC972 SPI NOR flash驱动问题
关于Nor Flash的各种挑战
关于NOR Flash的几大应用领域浅析
NAND Flash和NOR Flash的区别

Uboot下关于Nor Flash的驱动问题
评论