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++) {

/* Issue FLASH reset command */

flash_cmd_reset(info);

flash_write_cmd (info, 0, flash_offset_cfi[cfi_offset],

FLASH_CMD_CFI);

//向0x20 地址进行查询,  CFI 规定 , 前三个字符应该是 Q, R, Y

if (flash_isequal (info, 0, FLASH_OFFSET_CFI_RESP, ‘Q‘)

&& flash_isequal (info, 0, FLASH_OFFSET_CFI_RESP + 2, ‘R‘)

&& flash_isequal (info, 0, FLASH_OFFSET_CFI_RESP + 4, ‘Y‘)) {

//如果确认为CFI 规范, 那么就按照 struct cfi_qry数据结构进行查询

flash_read_cfi(info, qry, FLASH_OFFSET_CFI_RESP,

sizeof(struct cfi_qry));

//在进行CFI 规范查询之后, 还要将addr_unlock1 , addr_unlock2 进行赋值,  这两个地址分别表示8位宽的地址和16位宽的地址, 可以实现byte和word的操作.

//一般地, 我们只使用addr_unlock1

//在一些代码里, 这两个数值就通过宏定义来实现的

info->addr_unlock1 = 0xaaa;

info->addr_unlock2 = 0x555;

}

下面是flash 芯片手册里CFI 规范查询的信息:

cfi_qry 定义:

struct cfi_qry {

u8  qry[3]; //保存 Q, R, Y

u16 p_id;     //Primary algorithm

u16 p_adr;   //Address for primary algorithm

u16 a_id;    //Alternate

u16 a_adr;    //Address for alternate

u8  vcc_min;  // 最小Vcc

u8  vcc_max;  //最大Vcc

u8  vpp_min;   //最小Vpp

u8  vpp_max;    //最大Vpp

u8  word_write_timeout_typ;   //字节写典型超时

u8  buf_write_timeout_typ;    //缓存写典型超时

u8  block_erase_timeout_typ;  //块擦除典型超时

u8  chip_erase_timeout_typ;    //整片擦除典型超时

u8  word_write_timeout_max;    //字节写最大超时

u8  buf_write_timeout_max;      //缓存写最大超时

u8  block_erase_timeout_max;    //块写最大超时

u8  chip_erase_timeout_max;      //整片擦除最大超时

u8  dev_size;         //芯片大小

u16 interface_desc; //接口描述

u16 max_buf_write_size; //最大缓存写长度

u8  num_erase_regions; //擦除块扇区数量

u32 erase_region_info[NUM_ERASE_REGIONS];        //4个块区的信息

} __attribute__((packed));

从上图可以看到,  是获取了CFI query identification string  , System interface information , Device geometry definition  信息,对照手册,  就可以知道成员的数值

其中, 最为重要的是擦写扇区信息 erase_region_info, 对应手册的如下信息:

手册给出了扇区的信息, 第一部分说明了扇区(block)的个数 : 0xff + 1 = 256
个, 第二部分说明了一个扇区(block)大小: 0x200 * 256 =131072, 即128K字节

我们的flash, 为00ff, 和0200 .那么uint32_t的tmp 的数值应该为:  0x020000ff

tmp = le32_to_cpu(qry.erase_region_info[i]);

debug("erase region %u: 0x%08lx\n", i, tmp);

erase_region_count = (tmp & 0xffff) + 1;

tmp >>= 16;

erase_region_size =  (tmp & 0xffff) ? ((tmp & 0xffff) * 256) : 128;

tmp =  qry.erase_region_info[i] = 0x20000ff

tmp >>=16 后, tmp = 0x200

擦写扇区的大小 erase_region_size = 
(tmp & 0xffff) * 256 = 0x20000 , 即一个扇区的大小为0x2000字节.

擦写扇区的个数 erase_region_count为0x201, 即256个扇区

那么, 可以知道, 整个nor flash 总的容量为: 0x2000 * 256 = 33554432  字节,

验证一下:   33554432 / 1024 / 1024 = 32 M

sect_cnt = 0;

sector = base;//基地址为 0x1dc00000

那么会循环256次.

for (j = 0; j < erase_region_count; j++) {

..

//在256次循环中, 256个start成员保存各个扇区的地址

info->start[sect_cnt] =

(ulong)map_physmem(sector,

info->portwidth,

MAP_NOCACHE);

//计算各个扇区的地址, 地址计算方法为, 扇区的大小 * size_ratio(  为 size_ratio = 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\n", 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\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;

/*

通过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\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  /* CONFIG_SYS_FLASH_PROTECTION */

}

}

}

#if defined(CONFIG_SYS_FLASH_PROTECTION)

puts (" done\n");

#endif  /* CONFIG_SYS_FLASH_PROTECTION */

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) { /* 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\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:

/*根据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\n",

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 使用了同样的逻辑 , 不同的指令

时间: 2024-10-22 00:13:54

Uboot下的Nor Flash的驱动以及使用的相关文章

nuc900 nand flash mtd 驱动

nuc900 nand flash mtd 驱动,请参考! /* * Copyright © 2009 Nuvoton technology corporation. * * Wan ZongShun <[email protected]> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License

Linux下Chromium使用flash的办法

环境说明: 系统:  CentOS 6.5 X64 很简单,主要原因是在启动Chromium的时候指定了自有的Flash,我们可以在启动参数上去除指定的Flash! sudo vim  /usr/bin/chromium-browser CHROMIUM_RHEL_FLAGS="--enable-plugins --enable-extensions --enable-user-scripts --enable-printing --enable-sync --auto-ssl-client-a

Kubuntu 14.04 环境下安装:flash 火狐插件,搜狗输入法,更改分辨率1366x768

本文档的pdf文件网盘地址:http://pan.baidu.com/s/1hqgQId2 Kubuntu 14.04 环境下安装:flash 火狐插件,搜狗输入法,更改分辨率 1366x768http://www.kubuntu.org/ 系统安装后没有中文输入法,可以联网的话,建议使用:百度在线输入法(见下链接)Ubuntu安装Fcitx(小企鹅五笔输入法)http://www.cnblogs.com/conanboa/archive/2010/03/04/1678402.html 安装目标

Solaris 10下Qt编译Oracle 10g驱动

上回书讲到<Oracle 10g在Solaris 10中安装详解>,现在开始用Qt来编译下Oracle 10g驱动吧!这样就可以通过Qt程序联入Oracle数据库了! Oracle的环境变量: ORACLE_BASE=/oracle ORACLE_HOME=$ORACLE_BASE/product/10.0.2 Qt的编译文件在Solaris 10下的路径: /export/home/qt-4.3.1/qt-X11-commercial-src-4.3.1 Qt的环境变量: QTDIR=/us

uboot下tftp传输文件

uboot下通过tftp工具传输文件,tftp与ftp是完全不同的工具或协议. 1) 下载并安装程序 sudo apt-get install tftp-hpa tftpd-hpa tftp-hpa是客户端程序,tftpd-hpa是服务器端程序. 2) 建立tftp服务器目录 建立一个tftp客户端访问服务器的目录,如下: 先进入到ubuntu系统的根目录,然后再建立一个目录,并修改目录属性: cd / sudo mkdir tftpboot sudo chmod 777 tftpboot 若在

uboot下netconsole的原理及使用方法

最近发现uboot下一个很有意思也很实用的功能:netconsole,uboot下的netconsole类似于kernel下的telnet等网络终端功能,将网络作为输入输出的终端,这样就便于我们在PC端通过网络登录设备uboot中运行命令(更准确的说是通过网络向uboot发送命令和接收uboot的反馈信息,netconsole没有登录检查). kernel下也有netconsole机制,不过由于内核下的console只有write功能(因为内核启动是没有交互的,只有输出信息),因此kernel下

关于uboot下data abort的问题

在uboot下,经常会出现一些非常让人揪心的问题.解决这些问题的关键就是方法与方向,把握好这两点,一切问题都可以迎刃而解.同时也要自信.如下: 有时我们会遇到如下的出错信息,这时CPU会reboot, data abort MAYBE you should read doc/README.arm-unaligned-accesses pc : [<1ff60148>] lr : [<1ff6019c>] sp : 1fb0a508 ip : 00000000 fp : 000000

linux 下手动编译安装无线网卡驱动

//先参照 <本地yum源安装GCC >安装好gcc hp的笔记本上安装了CentOS6.3,没有安装无线网卡驱动,安装这个驱动,在Google上找了好多资料,最后终于解决了这个问题.在这里做点记录,希望也能帮到别人. 我的机子是32位,CentOS的内核版本是2.6.32-279.19.1.el6.i686,下载的无线网卡驱动是hybrid-portsrc_x86_32-v5_100_82_112.tar.gz 下面是具体的步骤 一:确定无线网卡的型号,驱动下载 第一步要确定机子的无线网卡型

Ubuntu下手动安装Nvidia显卡驱动

1. 下载最新版的nVidia驱动. http://www.nvidia.com/page/drivers.html 2.编辑blacklist.conf. sudo gedit /etc/modprobe.d/blacklist.conf 添加以下部分并保存: blacklist vga16fb blacklist nouveau blacklist rivafb blacklist nvidiafb blacklist rivatv (这里有一行空格) 3. 删除之前所安装的nVidia驱动