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++) {
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 = 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;
ushort sector_count;
ulong flash_id;
ulong start[CONFIG_SYS_MAX_FLASH_SECT];
uchar protect[CONFIG_SYS_MAX_FLASH_SECT];
#ifdef CONFIG_SYS_FLASH_CFI
uchar portwidth;
uchar chipwidth;
ushort buffer_size;
ulong erase_blk_tout;
ulong write_tout;
ulong buffer_write_tout;
ushort vendor;
ushort cmd_reset;
ushort interface;
ushort legacy_unlock;
ushort manufacturer_id;
ushort device_id;
ushort device_id2;
ushort ext_addr;
ushort cfi_version;
ushort cfi_offset;
ulong addr_unlock1;
ulong addr_unlock2;
const char *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\n", addr);
return 1;
}
...
//知道了uboot的起始,結束位址, 就可以知道uboot在flash 裡要使用幾個扇區.
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\n");
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;
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;
s_last [bank] = -1;
}
//隻有一次循環
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;
//最後一個扇區的标号
s_end = info->sector_count - 1;
//周遊所有扇區, 即256個扇區
for (sect=0; sect < info->sector_count; ++sect) {
ulong end;
//目前扇區的最後位址
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\n");
rcode = 1;
break;
}
} //如果得到的結果是結束的扇區标号小于起始扇區标号, 也是邏輯錯誤
if (s_last[bank] < s_first[bank]) {
puts ("Error: end sector"
" precedes start sector\n");
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\n");
rcode = 1;
break;
} else if (s_last[bank] >= 0) {
puts ("Error: cannot span across banks when they are"
" mapped in reverse order\n");
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\n",
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
}
}
}
#if defined(CONFIG_SYS_FLASH_PROTECTION)
puts (" done\n");
#endif
printf ("%sProtected %d sectors\n",
p ? "" : "Un-", protected);
} else if (rcode == 0) {
puts ("Error: start and/or end address"
" not on sector boundary\n");
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\n", erased);
} else if (rcode == 0) {
puts ("Error: start and/or end address"
" not on sector boundary\n");
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) {
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;
…
}
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\n");
return 1;
}
}
}
if (flash_verbose)
puts (" done\n");
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:
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\n",
prompt, (ulong)dst, (ulong)flash_read8(dst));
return ERR_TIMOUT;
}
udelay(1);
}
#endif
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;
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);
}
}
}
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;
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);
}
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 使用了同樣的邏輯 , 不同的指令