天天看點

Uboot下的Nor Flash的驅動以及使用

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 規範查詢的資訊:

Uboot下的Nor Flash的驅動以及使用
Uboot下的Nor Flash的驅動以及使用
Uboot下的Nor Flash的驅動以及使用
Uboot下的Nor Flash的驅動以及使用
Uboot下的Nor Flash的驅動以及使用

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, 對應手冊的如下資訊:

Uboot下的Nor Flash的驅動以及使用

手冊給出了扇區的資訊, 第一部分說明了扇區(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 使用了同樣的邏輯 , 不同的指令