本文主要是關于Nor Flash的相關介紹,并著重對Nor Flash的編寫及驅(qū)動程序進行了相近的闡述。
Nor Flash驅(qū)動編寫
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,用于少量的系統(tǒng)初始化代碼。而2個8KB的塊用于存儲參數(shù),余下的32KB的塊叫做Main Block,用于存儲應用程序的代碼。
2.
Sector(扇區(qū))
此處的sector(扇區(qū))也就是flash里面的最小的擦除單位:塊(block)。
所以sector count,也就是有多少個block。
3.
Sector Count (扇區(qū)數(shù))和Sector List
此處的Nor Flash,M29W320DB,一共有正常的63個64KB的block,加上上面提到的原先是正常的1個64KB分成的4個小塊,所以是63+4=67個。
而所謂的驅(qū)動中的sector list,也就是block list,代碼注釋寫的也很清楚了:
ulong
start[CFG_MAX_FLASH_SECT];
/* physical sector start addresses */
用于存儲每一個塊的起始地址的。也是需要你驅(qū)動初始化的。對于這里的M29W320DB,也很簡單了,從開始順序加上塊大小64K,直到最后3個,計算一下對應地址,填進去就可以了。
4.
Protect(寫保護)
Nor Flash從軟件的寄存器配置和硬件上,都提供了對應的保護機制,目的是,防止有意或無意間,把那些不希望被改動/刪除的數(shù)據(jù)破壞了。比如有些機制把Flash的一些系統(tǒng)啟動參數(shù)存儲Nor Flash,或者是其他某種原因,只允許你使用部分Nor Flash的空間,所以,把這類需要保護的部分,在uboot的flash_info_t的結構體中,把對應的位設置成1:
uchar
protect[CFG_MAX_FLASH_SECT]; /* sector protection status
*/
這樣,之后程序就可以避免有意無意的擦除有用的數(shù)據(jù)了。
【寫Nor Flash驅(qū)動時的一些注意事項】
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的驅(qū)動(參考的是oardoxcflash.c),發(fā)出的命令去讀ID,也都是X8(8位)的:
addr[0x0AAA] = 0xAA;
addr[0x0555] = 0x55;
addr[0x0AAA] = 0x90;
所以,導致讀取Manufacture ID 和Device ID,都無法讀正確,讀的都是0xFF。后來重新去確認,在配置MPMCStaticConfig的時候,是配置的X16模式,然后再發(fā)命令,也對應的是按照X16模式發(fā)命令,可以參考: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發(fā)的地址,是不一樣的,而且順序也不同。
而且還有一小點要注意的是,記得轉(zhuǎn)換地址成對應的類型:
X8是vu_char *
X16是 vu_short *
這樣再寫入對應的地址和數(shù)值,就可以了。
3.
reset命令
看了uboot中的代碼,好像是其他設備,多數(shù)的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的驅(qū)動問題
nor flash 的使用特點是 : 讀操作可以按地址讀, 寫之前必須進行擦除, 一旦擦除必須擦除整個扇區(qū)。
新型的flash 使用3V 的電壓便可以進行整個扇區(qū)的擦除和寫入操作
任何芯片的使用, 都離不開驅(qū)動的支持。 uboot下的nor flash的驅(qū)動邏輯非常簡單。 而且, 對于符合 CFI ( Common Flash Interface )規(guī)范的flash芯片,驅(qū)動有很大的通用性。
uboot 提供了很好的 flash 驅(qū)動邏輯 和 flash的使用范例, 這些基本的使用方法在linux里也是同樣的邏輯,只不過linux下需要加上一層分區(qū)信息。 結合flash 芯片手冊, 可以對nor flash的使用邏輯有較為清晰的理解。
nor flash的驅(qū)動初始化部分:
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 規(guī)范的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 接口, 那么有統(tǒng)一的查詢規(guī)范, 將查詢到的信息保存到 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 規(guī)定 , 前三個字符應該是 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 規(guī)范, 那么就按照 struct cfi_qry數(shù)據(jù)結構進行查詢
flash_read_cfi(info, qry, FLASH_OFFSET_CFI_RESP,
sizeof(struct cfi_qry));
…
//在進行CFI 規(guī)范查詢之后, 還要將addr_unlock1 , addr_unlock2 進行賦值, 這兩個地址分別表示8位寬的地址和16位寬的地址, 可以實現(xiàn)byte和word的操作。
//一般地, 我們只使用addr_unlock1
//在一些代碼里, 這兩個數(shù)值就通過宏定義來實現(xiàn)的
info-》addr_unlock1 = 0xaaa;
info-》addr_unlock2 = 0x555;
…
}
下面是flash 芯片手冊里CFI 規(guī)范查詢的信息:
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; //字節(jié)寫典型超時
u8 buf_write_timeout_typ; //緩存寫典型超時
u8 block_erase_timeout_typ; //塊擦除典型超時
u8 chip_erase_timeout_typ; //整片擦除典型超時
u8 word_write_timeout_max; //字節(jié)寫最大超時
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; //擦除塊扇區(qū)數(shù)量
u32 erase_region_info[NUM_ERASE_REGIONS]; //4個塊區(qū)的信息
} __attribute__((packed));
從上圖可以看到, 是獲取了CFI query identification string , System interface information , Device geometry definition 信息,對照手冊, 就可以知道成員的數(shù)值
其中, 最為重要的是擦寫扇區(qū)信息 erase_region_info, 對應手冊的如下信息:
手冊給出了扇區(qū)的信息, 第一部分說明了扇區(qū)(block)的個數(shù) : 0xff + 1 = 256 個, 第二部分說明了一個扇區(qū)(block)大?。?0x200 * 256 =131072, 即128K字節(jié)
我們的flash, 為00ff, 和0200 。那么uint32_t的tmp 的數(shù)值應該為: 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
擦寫扇區(qū)的大小 erase_region_size = (tmp & 0xffff) * 256 = 0x20000 , 即一個扇區(qū)的大小為0x2000字節(jié)。
擦寫扇區(qū)的個數(shù) erase_region_count為0x201, 即256個扇區(qū)
那么, 可以知道, 整個nor flash 總的容量為: 0x2000 * 256 = 33554432 字節(jié),
驗證一下: 33554432 / 1024 / 1024 = 32 M
sect_cnt = 0;
sector = base;//基地址為 0x1dc00000
…
那么會循環(huán)256次。
for (j = 0; j 《 erase_region_count; j++) {
。。
//在256次循環(huán)中, 256個start成員保存各個扇區(qū)的地址
info-》start[sect_cnt] =
(ulong)map_physmem(sector,
info-》portwidth,
MAP_NOCACHE);
//計算各個扇區(qū)的地址, 地址計算方法為, 扇區(qū)的大小 * size_ratio( 為 size_ratio = info-》portwidth / info-》chipwidth;,比值為1)
//可以看出, 各個扇區(qū)的地址相隔一個扇區(qū)的大小
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);
…
}
循環(huán)結束后, sect_cnt 的數(shù)值為 256
現(xiàn)在, 所有扇區(qū)的地址都保存到了init-》start數(shù)組里。 那么現(xiàn)在如果要向flash里燒寫一個文件, 在知道文件的大小的情況下,就可以計算出要使用幾個扇區(qū)。
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 就是按照如上的思路來實現(xiàn)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);
//獲取環(huán)境變量loadaddr的數(shù)值, 這是要更新的文件在內(nèi)存里的起始地址
if (getenv(“l(fā)oadaddr”) != NULL)
addr = simple_strtoul(getenv(“l(fā)oadaddr”), 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 里要使用幾個扇區(qū)。
/*
一, 先取消要使用的扇區(qū)保護, 參數(shù)0 表示取消保護
*/
if ((rcode = flash_sect_protect(0, addr_first, addr_last)) != 0)
return rcode;
//擦除要使用到的扇區(qū)
if ((rcode = flash_sect_erase(addr_first, addr_last)) != 0)
return rcode;
//向要使用到的扇區(qū)寫入數(shù)據(jù)
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的起始地址和結束地址, 計算出起始扇區(qū)和結束扇區(qū), 以及要使用到的扇區(qū)個數(shù), 分別保存到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;
//初始化參數(shù)
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 */
}
//只有一次循環(huán)
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 */
//最后一個扇區(qū)的標號
s_end = info-》sector_count - 1; /* last sector */
//遍歷所有扇區(qū), 即256個扇區(qū)
for (sect=0; sect 《 info-》sector_count; ++sect) {
ulong end; /* last address in current sect */
//當前扇區(qū)的最后地址
end = (sect == s_end) ? b_end : info-》start[sect + 1] - 1;
if (addr_first 》 end)
continue;
//當uboot的結束地址小于當前扇區(qū)的地址時, 直接判斷下個扇區(qū)。 目的是快速找到uboot的結束地址所在flash的扇區(qū)。
if (addr_last 《 info-》start[sect])
continue;
//當文件起始地址等于扇區(qū)起始地址, 將當前扇區(qū)地址保存到s_first[0] 中。
if (addr_first == info-》start[sect]) {
s_first[bank] = sect;
}
//當文件結束地址等于當前扇區(qū)結束地址時, 將當前扇區(qū)標號保存到s_last[0]中。。 這個部分uboot的代碼需要優(yōu)化, 正常的邏輯下, 這個時候可以直接break了。 無須再進入循環(huán)。 本人已經(jīng)驗證通過
if (addr_last == end) {
s_last[bank] = sect;
}
}
//如果s_first[0]有數(shù)值, 即查找成功的話, 計算出占有了幾個扇區(qū)。
if (s_first[bank] 》= 0) {
//如果沒有找到s_last, 有兩種情況, 如果目標文件大于flash的大小, 那么設定s_last 為最后一個扇區(qū)。 否則是邏輯錯誤。
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;
}
} //如果得到的結果是結束的扇區(qū)標號小于起始扇區(qū)標號, 也是邏輯錯誤
if (s_last[bank] 《 s_first[bank]) {
puts (“Error: end sector”
“ precedes start sector ”);
rcode = 1;
break;
}
//記錄結束扇區(qū)的編號。
sect = s_last[bank];
addr_first = (sect == s_end) ? b_end + 1: info-》start[sect + 1];
//s_last[bank] - s_first[bank] + 1 就是中間的扇區(qū)個數(shù)
(*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;
//為獲取到的扇區(qū)取消保護
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;
//跟之前取消保護一樣, 也需要通過給定地址計算出要操作的扇區(qū)。 這個地方實在多余, 完全可以使用之前已經(jīng)獲取到的數(shù)據(jù)作為參數(shù)傳下來。
//總之 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++) {
////如果扇區(qū)處于保護狀態(tài), 將無法擦除
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;
…
}
/*
根據(jù)手冊, 扇區(qū)的擦寫動作指令為:
#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, 向扇區(qū)地址 寫入30
__RESET 由(1) 完成
1,2 由 (2) 完成
3 由 (3)完成
4,5由(4)完成
6 由 (5)完成
*/
/*
指令的下發(fā)后, 還要使用狀態(tài)查詢函數(shù), 等待指令的完成, 即硬件的執(zhí)行完成。 這個過程是最耗時的。
*/
if (use_flash_status_poll(info)) {
cfiword_t cword = (cfiword_t)0xffffffffffffffffULL;
void *dest;
//獲取扇區(qū)的內(nèi)存地址
dest = flash_map(info, sect, 0);
//傳入的超時時間為 info-》erase_blk_tout, 這個數(shù)值為: (1 《《 qry.block_erase_timeout_typ) * (1 《《 qry.block_erase_timeout_max)
//根據(jù)手冊, 計算出扇區(qū)最大超時時間為: 4096s, 意味著, 如果4096s內(nèi)扇區(qū)還沒有擦寫完成, 那么就超時退出
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:
/*根據(jù)flash 的位寬(portwidth), 判斷目的地址的數(shù)值是否等于src地址的數(shù)值。 上面?zhèn)飨聛韘rc的數(shù)值為全f, dst地址是當前扇區(qū)的0地址,
那么flash_erase 的擦寫指令完成的判斷條件是: 當前扇區(qū)的0地址的數(shù)值為0xff
如果判斷條件成立后跳出循環(huán), 否則udelay后, 再次進入循環(huán) */
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, 扇區(qū)擦寫完成后, 調(diào)用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里, 只有一次循環(huán)
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];
//如果要操作的扇區(qū)沒有取消保護, 直接返回
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調(diào)用 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的數(shù)據(jù)結構, src為源文件的內(nèi)存地址, 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的數(shù)值為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的情況,就是按字節(jié)寫的情況
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 的整數(shù)倍, 那么i 就等于 buffer_size.256 字節(jié)。
//可以看到, 按緩存寫的話 , 總共會執(zhí)行 (文件長度 / 256 + 1 次) 。 如果要寫入的長度為 0xdffff, 那么要執(zhí)行的次數(shù)為 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; //要寫入的內(nè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);
}
對于字節(jié)寫和緩存寫, 分別 有flash_write_cfiword 和flash_write_cfibuffer 實現(xiàn)
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;
//根據(jù)端口寬度 , 判斷要操作的地址上的數(shù)值是否為cword的數(shù)值。
//上面?zhèn)鞯腸word 為0 , 那么要判斷要寫的地址的數(shù)值是否為0 , 如果判斷結果為假,那么退出,返回ERR_NOT_ERASE錯誤數(shù)值。提示沒有經(jīng)過擦寫。
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在執(zhí)行燒些前, 要先取消保護, 再進行擦除, 當兩者都成功后, 才可以進行write
//在執(zhí)行燒些過程中, 關閉全部中斷, 所有的中斷新號會被忽略
flag = disable_interrupts ();
//根據(jù)不同廠商,執(zhí)行對應的指令。
switch (info-》vendor) {
case CFI_CMDSET_INTEL_PROG_REGIONS:
case CFI_CMDSET_INTEL_EXTENDED:
case CFI_CMDSET_INTEL_STANDARD://intel 的規(guī)范
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 的規(guī)范
//根據(jù)目的地址找到要操作的扇區(qū)
sect = find_sector(info, dest);
//解鎖扇區(qū)
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的相關介紹就到這了,如有不足之處歡迎指正。
-
驅(qū)動
+關注
關注
12文章
1866瀏覽量
85951 -
NOR flash
+關注
關注
2文章
92瀏覽量
23124
發(fā)布評論請先 登錄
相關推薦
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驅(qū)動問題
關于Nor Flash的各種挑戰(zhàn)
關于NOR Flash的幾大應用領域淺析
NAND Flash和NOR Flash的區(qū)別

評論