Uboot下关于Nor Flash的驱动问题

电子说

1.2w人已加入

描述

  本文主要是关于Nor Flash的相关介绍,并着重对Nor Flash的编写及驱动程序进行了相近的阐述。

  Nor Flash驱动编写

  1.

  Bottom/Top Boot Sect(底部/顶部 启动块)

  所谓的boot sect,是指的是Nor Flash和Nand Flash不太一样。Nand Flash从开始到最后,都是由同样大小的page所组成的。

  而Nor Flash,一般都是有个boot sect,好像是由于历史原因,常将Nor Flash用于作为存储启动代码的设备,也就是从Nor Flash启动,所以,这个boot sect块,专门设计出来,用于存放启动代码。如果详细解释,按照datasheet中的描述就是,第一个或最后一个,此处是bottom sect,所以是最后一个64KB大小的块,被分为4个独立的块。第一个16KB,用于少量的系统初始化代码。而2个8KB的块用于存储参数,余下的32KB的块叫做Main Block,用于存储应用程序的代码。

  2.

  Sector(扇区)

  此处的sector(扇区)也就是flash里面的最小的擦除单位:块(block)。

  所以sector count,也就是有多少个block。

  3.

  Sector Count (扇区数)和Sector List

  此处的Nor Flash,M29W320DB,一共有正常的63个64KB的block,加上上面提到的原先是正常的1个64KB分成的4个小块,所以是63+4=67个。

  而所谓的驱动中的sector list,也就是block list,代码注释写的也很清楚了:

  ulong

  start[CFG_MAX_FLASH_SECT];

  /* physical sector start addresses */

  用于存储每一个块的起始地址的。也是需要你驱动初始化的。对于这里的M29W320DB,也很简单了,从开始顺序加上块大小64K,直到最后3个,计算一下对应地址,填进去就可以了。

  4.

  Protect(写保护)

  Nor Flash从软件的寄存器配置和硬件上,都提供了对应的保护机制,目的是,防止有意或无意间,把那些不希望被改动/删除的数据破坏了。比如有些机制把Flash的一些系统启动参数存储Nor Flash,或者是其他某种原因,只允许你使用部分Nor Flash的空间,所以,把这类需要保护的部分,在uboot的flash_info_t的结构体中,把对应的位设置成1:

  uchar

  protect[CFG_MAX_FLASH_SECT]; /* sector protection status

  */

  这样,之后程序就可以避免有意无意的擦除有用的数据了。

  【写Nor Flash驱动时的一些注意事项】

  1.

  位宽(bitwidth,X8/X16/X32)

  在Nor Flash控制器,此处我这里用的是,ARM的PromeCell PL172,MPMC(MultiPort Memory Controller),可以接多种不同的存储设备,比如SRAM(Static Memory),Nor Flash,而其中又可以分别设置是否支持Page Mode,Extended wait和写保护(启用写保护,就可以看出是ROM了)等。

  在硬件上MPMC和Nor Flash连接好了之后。在使用Nor Flash之前,要初始化MPMC。

  这里说的,要注意位宽,是因为我开始就没注意到,所以,在开始初始化MPMC的时候,设置MPMCStaticConfig寄存器的时候,设置成了X16(16位),但是,后来去uboot中找到别人的Nor Flash的驱动(参考的是oardoxcflash.c),发出的命令去读ID,也都是X8(8位)的:

  addr[0x0AAA] = 0xAA;

  addr[0x0555] = 0x55;

  addr[0x0AAA] = 0x90;

  所以,导致读取Manufacture ID 和Device ID,都无法读正确,读的都是0xFF。后来重新去确认,在配置MPMCStaticConfig的时候,是配置的X16模式,然后再发命令,也对应的是按照X16模式发命令,可以参考:oardmvs1flash.c中的代码,读取ID时是:

  addr[0x0555] = 0x00AA;

  addr[0x02AA]= 0x0055;

  addr[0x0555] = 0x0090;

  才能正确读取到期望出的ID:

  value = addr[0];

  /* manufacturer ID

  */

  读出来的是0x20h。

  value = addr[1];

  /* device ID

  */

  读出来的是0x22CB

  和datasheet中的匹配:

  – Manufacturer Code: 0020h

  – Bottom Device Code M29W320DB: 22CBh

  2.

  不同位宽对应不同的时序

  此处说的时序,也就是上面提到的,X8和X16发的地址,是不一样的,而且顺序也不同。

  而且还有一小点要注意的是,记得转换地址成对应的类型:

  X8是vu_char *

  X16是 vu_short *

  这样再写入对应的地址和数值,就可以了。

  3.

  reset命令

  看了uboot中的代码,好像是其他设备,多数的reset命令,都是0xFF。

  而这里用到的是STM(STMicroelectronics,后来好像改成Intel和ST合资的恒忆(Numonyx)了。。。)的 Nor Flash,M29W320DB (32 M, bottom boot sect)

  ,比较特殊些,是0xF0。

  4.

  boot sector的位置

  刚刚看了下datasheet,很汗的是,本以为bottom sect是底部的sect,是地址最大的那个,结果datasheet中的是倒叙计算开始处的,也就是地址最大的那个块,是第一个块,所以,此处的boot sector 是块0-3:

  # Size (KByte/KWord) Address Range(x8 )/ Address Range (x16)

  66 64/32 3F0000h-3FFFFFh

  1F8000h-1FFFFFh

  。。。。。

  。。。。。

  3 32/16 008000h-00FFFFh

  004000h-007FFFh

  2 8/4

  006000h-007FFFh

  003000h-003FFFh

  1 8/4

  004000h-005FFFh

  002000h-002FFFh

  0 16/8 000000h-003FFFh

  000000h-001FFFh

  Uboot下关于Nor Flash的驱动问题

  nor flash 的使用特点是 : 读操作可以按地址读, 写之前必须进行擦除, 一旦擦除必须擦除整个扇区。

  新型的flash 使用3V 的电压便可以进行整个扇区的擦除和写入操作

  任何芯片的使用, 都离不开驱动的支持。 uboot下的nor flash的驱动逻辑非常简单。 而且, 对于符合 CFI ( Common Flash Interface )规范的flash芯片,驱动有很大的通用性。

  uboot 提供了很好的 flash 驱动逻辑 和 flash的使用范例, 这些基本的使用方法在linux里也是同样的逻辑,只不过linux下需要加上一层分区信息。 结合flash 芯片手册, 可以对nor flash的使用逻辑有较为清晰的理解。

  nor flash的驱动初始化部分:

  arch/mips/cpu/octeon/start.S

  board_init_r -》 flash_init()

  drivers/mtd/cfi_flash.c

  unsigned long flash_init (void){

  for (i = 0; i 《 CONFIG_SYS_MAX_FLASH_BANKS; ++i) {

  flash_info[i].flash_id = FLASH_UNKNOWN;

  …

  //由于使用的flash 是新型的CFI 规范的flash, 没有使用 CONFIG_FLASH_CFI_LEGACY 这个宏, 所以flash_detect_legacy直接返回0

  if (!flash_detect_legacy(cfi_flash_bank_addr(i), i))

  flash_get_size(cfi_flash_bank_addr(i), i);

  size += flash_info[i].size;

  ulong flash_get_size (phys_addr_t base, int banknum)

  {

  flash_info_t *info = &flash_info[banknum];

  int i, j;

  flash_sect_t sect_cnt;

  phys_addr_t sector;

  unsigned long tmp;

  int size_ratio;

  uchar num_erase_regions;

  int erase_region_size;

  int erase_region_count;

  struct cfi_qry qry;

  unsigned long max_size;

  memset(&qry, 0, sizeof(qry));

  info-》ext_addr = 0;

  info-》cfi_version = 0;

  #ifdef CONFIG_SYS_FLASH_PROTECTION

  info-》legacy_unlock = 0;

  #endif

  info-》start[0] = (ulong)map_physmem(base, info-》portwidth, MAP_NOCACHE);

  //如果是CFI 接口, 那么有统一的查询规范, 将查询到的信息保存到 qry中

  if (flash_detect_cfi (info, &qry)) {

  info-》vendor = le16_to_cpu(qry.p_id);

  info-》ext_addr = le16_to_cpu(qry.p_adr) * 2;

  debug(“extended address is 0x%x ”, info-》ext_addr);

  num_erase_regions = qry.num_erase_regions;

  if (info-》ext_addr) {

  #define FLASH_OFFSET_CFI_RESP 0x20

  flash_detect_cfi -》

  static int __flash_detect_cfi (flash_info_t * info, struct cfi_qry *qry)

  {

  int cfi_offset;

  for (cfi_offset=0;

  cfi_offset 《 sizeof(flash_offset_cfi) / sizeof(uint);

  cfi_offset++) {

  /* Issue FLASH reset command */

  flash_cmd_reset(info);

  flash_write_cmd (info, 0, flash_offset_cfi[cfi_offset],

  FLASH_CMD_CFI);

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

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

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

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

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

  flash_read_cfi(info, qry, FLASH_OFFSET_CFI_RESP,

  sizeof(struct cfi_qry));

  …

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

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

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

  info-》addr_unlock1 = 0xaaa;

  info-》addr_unlock2 = 0x555;

  …

  }

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

  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, 对应手册的如下信息:

  

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

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

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

  debug(“erase region %u: 0x%08lx ”, i, tmp);

  erase_region_count = (tmp & 0xffff) + 1;

  tmp 》》= 16;

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

  tmp = qry.erase_region_info[i] = 0x20000ff

  tmp 》》=16 后, tmp = 0x200

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

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

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

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

  sect_cnt = 0;

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

  …

  那么会循环256次。

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

  。。

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

  info-》start[sect_cnt] =

  (ulong)map_physmem(sector,

  info-》portwidth,

  MAP_NOCACHE);

  //计算各个扇区的地址, 地址计算方法为, 扇区的大小 * size_ratio( 为 size_ratio = info-》portwidth / info-》chipwidth;,比值为1)

  //可以看出, 各个扇区的地址相隔一个扇区的大小

  sector += (erase_region_size * size_ratio);

  …

  sect_cnt++;

  }

  info-》sector_count = sect_cnt;

  //buffer_size 为 1 《《 8 , 256

  info-》buffer_size = 1 《《 (8 * info-》portwidth);

  …

  }

  循环结束后, sect_cnt 的数值为 256

  现在, 所有扇区的地址都保存到了init-》start数组里。 那么现在如果要向flash里烧写一个文件, 在知道文件的大小的情况下,就可以计算出要使用几个扇区。

  include/flash.h:

  #define CONFIG_SYS_MAX_FLASH_SECT (256)

  typedef struct {

  ulong size; /* total bank size in bytes */

  ushort sector_count; /* number of erase units */

  ulong flash_id; /* combined device & manufacturer code */

  ulong start[CONFIG_SYS_MAX_FLASH_SECT]; /* virtual sector start address */

  uchar protect[CONFIG_SYS_MAX_FLASH_SECT]; /* sector protection status */

  #ifdef CONFIG_SYS_FLASH_CFI

  uchar portwidth; /* the width of the port */

  uchar chipwidth; /* the width of the chip */

  ushort buffer_size; /* # of bytes in write buffer */

  ulong erase_blk_tout; /* maximum block erase timeout */

  ulong write_tout; /* maximum write timeout */

  ulong buffer_write_tout; /* maximum buffer write timeout */

  ushort vendor; /* the primary vendor id */

  ushort cmd_reset; /* vendor specific reset command */

  ushort interface; /* used for x8/x16 adjustments */

  ushort legacy_unlock; /* support Intel legacy (un)locking */

  ushort manufacturer_id; /* manufacturer id */

  ushort device_id; /* device id */

  ushort device_id2; /* extended device id */

  ushort ext_addr; /* extended query table address */

  ushort cfi_version; /* cfi version */

  ushort cfi_offset; /* offset for cfi query */

  ulong addr_unlock1; /* unlock address 1 for AMD flash roms */

  ulong addr_unlock2; /* unlock address 2 for AMD flash roms */

  const char *name; /* human-readable name */

  #endif

  } flash_info_t;

  uboot 就是按照如上的思路来实现uboot的更新, common/cmd_flash.c 有很好的flash使用范例:

  int do_upgrade (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])

  {

  int rcode = 0;

  ulong addr, addr_first, addr_last;

  const bootloader_header_t *header;

  if (argc != 4) {

  if (argc == 2 || argc == 3) {

  if (strcmp(argv[1], “uboot”) != 0)

  return cmd_usage(cmdtp);

  //获取环境变量loadaddr的数值, 这是要更新的文件在内存里的起始地址

  if (getenv(“loadaddr”) != NULL)

  addr = simple_strtoul(getenv(“loadaddr”), NULL, 16);

  else

  return cmd_usage(cmdtp);

  //(0x1fc00000 - CONFIG_SYS_FLASH_SIZE) = 0x1dc00000

  //计算出uboot的起始地址

  addr_first = CONFIG_SYS_FLASH_BASE;

  if (argc == 3 && strcmp(argv[2], “all”) == 0) {

  addr_last = addr_first + CONFIG_BOOT_SIZE - 1;

  }else

  //CONFIG_ENV_ADDR = 0x1fbe0000

  //addr_last = 0x1fbdffff

  //计算出uboot的结束地址

  addr_last = CONFIG_ENV_ADDR - 1;

  // 验证下载的uboot 释放符合bootload 的格式。

  header = (void *)addr;

  if (validate_header(header)) {

  printf(“Image does not have valid header form addr:0x%lx ”, addr);

  return 1;

  }

  。。.

  //知道了uboot的起始,结束地址, 就可以知道uboot在flash 里要使用几个扇区。

  /*

  一, 先取消要使用的扇区保护, 参数0 表示取消保护

  */

  if ((rcode = flash_sect_protect(0, addr_first, addr_last)) != 0)

  return rcode;

  //擦除要使用到的扇区

  if ((rcode = flash_sect_erase(addr_first, addr_last)) != 0)

  return rcode;

  //向要使用到的扇区写入数据

  puts (“Copy to Flash.。。 ”);

  if ((rcode = flash_write((char *)addr, addr_first, addr_last - addr_first)) != 0) {

  flash_perror(rcode);

  return 1;

  }

  puts (“done ”);

  return 0;

  }

  int flash_sect_protect (int p, ulong addr_first, ulong addr_last)

  {

  flash_info_t *info;

  ulong bank;

  int s_first[CONFIG_SYS_MAX_FLASH_BANKS], s_last[CONFIG_SYS_MAX_FLASH_BANKS];

  int protected, i;

  int planned;

  int rcode;

  /*

  通过flash的起始地址和结束地址, 计算出起始扇区和结束扇区, 以及要使用到的扇区个数, 分别保存到s_first, s_last, planned 中。

  */

  rcode = flash_fill_sect_ranges( addr_first, addr_last, s_first, s_last, &planned );

  …

  static int

  flash_fill_sect_ranges (ulong addr_first, ulong addr_last,

  int *s_first, int *s_last,

  int *s_count )

  {

  flash_info_t *info;

  ulong bank;

  int rcode = 0;

  *s_count = 0;

  //初始化参数

  for (bank=0; bank 《 CONFIG_SYS_MAX_FLASH_BANKS; ++bank) {

  s_first[bank] = -1; /* first sector to erase */

  s_last [bank] = -1; /* last sector to erase */

  }

  //只有一次循环

  for (bank=0,info = &flash_info[0];

  (bank 《 CONFIG_SYS_MAX_FLASH_BANKS) && (addr_first 《= addr_last);

  ++bank, ++info) {

  ulong b_end;

  int sect;

  short s_end;

  if (info-》flash_id == FLASH_UNKNOWN) {

  continue;

  }

  //start[0]保存的是flash的起始地址 , size是整个芯片的大小, 那么info-》start[0] + info-》size - 1的 含义就是 整个芯片的结束地址

  b_end = info-》start[0] + info-》size - 1; /* bank end addr */

  //最后一个扇区的标号

  s_end = info-》sector_count - 1; /* last sector */

  //遍历所有扇区, 即256个扇区

  for (sect=0; sect 《 info-》sector_count; ++sect) {

  ulong end; /* last address in current sect */

  //当前扇区的最后地址

  end = (sect == s_end) ? b_end : info-》start[sect + 1] - 1;

  if (addr_first 》 end)

  continue;

  //当uboot的结束地址小于当前扇区的地址时, 直接判断下个扇区。 目的是快速找到uboot的结束地址所在flash的扇区。

  if (addr_last 《 info-》start[sect])

  continue;

  //当文件起始地址等于扇区起始地址, 将当前扇区地址保存到s_first[0] 中。

  if (addr_first == info-》start[sect]) {

  s_first[bank] = sect;

  }

  //当文件结束地址等于当前扇区结束地址时, 将当前扇区标号保存到s_last[0]中。。 这个部分uboot的代码需要优化, 正常的逻辑下, 这个时候可以直接break了。 无须再进入循环。 本人已经验证通过

  if (addr_last == end) {

  s_last[bank] = sect;

  }

  }

  //如果s_first[0]有数值, 即查找成功的话, 计算出占有了几个扇区。

  if (s_first[bank] 》= 0) {

  //如果没有找到s_last, 有两种情况, 如果目标文件大于flash的大小, 那么设定s_last 为最后一个扇区。 否则是逻辑错误。

  if (s_last[bank] 《 0) {

  if (addr_last 》 b_end) {

  s_last[bank] = s_end;

  } else {

  puts (“Error: end address”

  “ not on sector boundary ”);

  rcode = 1;

  break;

  }

  } //如果得到的结果是结束的扇区标号小于起始扇区标号, 也是逻辑错误

  if (s_last[bank] 《 s_first[bank]) {

  puts (“Error: end sector”

  “ precedes start sector ”);

  rcode = 1;

  break;

  }

  //记录结束扇区的编号。

  sect = s_last[bank];

  addr_first = (sect == s_end) ? b_end + 1: info-》start[sect + 1];

  //s_last[bank] - s_first[bank] + 1 就是中间的扇区个数

  (*s_count) += s_last[bank] - s_first[bank] + 1;

  } else if (addr_first 》= info-》start[0] && addr_first 《 b_end) {

  puts (“Error: start address not on sector boundary ”);

  rcode = 1;

  break;

  } else if (s_last[bank] 》= 0) {

  puts (“Error: cannot span across banks when they are”

  “ mapped in reverse order ”);

  rcode = 1;

  break;

  }

  }

  return rcode;

  }

  回到:

  #ifndef CONFIG_SYS_NO_FLASH

  int flash_sect_protect (int p, ulong addr_first, ulong addr_last)

  {

  flash_info_t *info;

  ulong bank;

  int s_first[CONFIG_SYS_MAX_FLASH_BANKS], s_last[CONFIG_SYS_MAX_FLASH_BANKS];

  int protected, i;

  int planned;

  int rcode;

  rcode = flash_fill_sect_ranges( addr_first, addr_last, s_first, s_last, &planned );

  protected = 0;

  if (planned && (rcode == 0)) {

  for (bank=0,info = &flash_info[0]; bank 《 CONFIG_SYS_MAX_FLASH_BANKS; ++bank, ++info) {

  if (info-》flash_id == FLASH_UNKNOWN) {

  continue;

  }

  if (s_first[bank]》=0 && s_first[bank]《=s_last[bank]) {

  debug (“%sProtecting sectors %d..%d in bank %ld ”,

  p ? “” : “Un-”,

  s_first[bank], s_last[bank], bank+1);

  protected += s_last[bank] - s_first[bank] + 1;

  //为获取到的扇区取消保护

  for (i=s_first[bank]; i《=s_last[bank]; ++i) {

  #if defined(CONFIG_SYS_FLASH_PROTECTION)

  //就是 改变 info-》addr_unlock1 的标识和将info-》protect 的对应成员置0, 否则后面不能 erase 和write

  if (flash_real_protect(info, i, p))

  rcode = 1;

  putc (‘。’);

  #else

  info-》protect[i] = p;

  #endif /* CONFIG_SYS_FLASH_PROTECTION */

  }

  }

  }

  #if defined(CONFIG_SYS_FLASH_PROTECTION)

  puts (“ done ”);

  #endif /* CONFIG_SYS_FLASH_PROTECTION */

  printf (“%sProtected %d sectors ”,

  p ? “” : “Un-”, protected);

  } else if (rcode == 0) {

  puts (“Error: start and/or end address”

  “ not on sector boundary ”);

  rcode = 1;

  }

  return rcode;

  }

  #ifndef CONFIG_SYS_NO_FLASH

  int flash_sect_erase (ulong addr_first, ulong addr_last)

  {

  flash_info_t *info;

  ulong bank;

  int s_first[CONFIG_SYS_MAX_FLASH_BANKS], s_last[CONFIG_SYS_MAX_FLASH_BANKS];

  int erased = 0;

  int planned;

  int rcode = 0;

  //跟之前取消保护一样, 也需要通过给定地址计算出要操作的扇区。 这个地方实在多余, 完全可以使用之前已经获取到的数据作为参数传下来。

  //总之 flash_sect_erase 和 flash_sect_protect 的重复度太高

  rcode = flash_fill_sect_ranges (addr_first, addr_last,

  s_first, s_last, &planned );

  if (planned && (rcode == 0)) {

  for (bank=0,info = &flash_info[0];

  (bank 《 CONFIG_SYS_MAX_FLASH_BANKS) && (rcode == 0);

  ++bank, ++info) {

  if (s_first[bank]》=0) {

  erased += s_last[bank] - s_first[bank] + 1;

  debug (“Erase Flash from 0x%08lx to 0x%08lx ”

  “in Bank # %ld ”,

  info-》start[s_first[bank]],

  (s_last[bank] == info-》sector_count) ?

  info-》start[0] + info-》size - 1:

  info-》start[s_last[bank]+1] - 1,

  bank+1);

  //flash_erase 是drivers/mtd/cfi_flash.c 提供的flash 擦除接口。

  rcode = flash_erase (info, s_first[bank], s_last[bank]);

  }

  }

  printf (“Erased %d sectors ”, erased);

  } else if (rcode == 0) {

  puts (“Error: start and/or end address”

  “ not on sector boundary ”);

  rcode = 1;

  }

  return rcode;

  }

  #endi

  int flash_erase (flash_info_t * info, int s_first, int s_last)

  {

  …

  for (sect = s_first; sect 《= s_last; sect++) {

  ////如果扇区处于保护状态, 将无法擦除

  if (info-》protect[sect] == 0) { /* not protected */

  switch (info-》vendor) {

  …

  break;

  case CFI_CMDSET_AMD_STANDARD:

  case CFI_CMDSET_AMD_EXTENDED:

  flash_write_cmd (info, 0, 0, AMD_CMD_RESET); // (1)

  flash_unlock_seq (info, sect); //(2)

  flash_write_cmd (info, sect, info-》addr_unlock1,AMD_CMD_ERASE_START); //(3)

  flash_unlock_seq (info, sect);//(4)

  flash_write_cmd (info, sect, 0,AMD_CMD_ERASE_SECTOR);//(5)

  break;

  …

  }

  /*

  根据手册, 扇区的擦写动作指令为:

  

  #define AMD_CMD_UNLOCK_START 0xAA

  #define AMD_CMD_UNLOCK_ACK 0x55

  static void flash_unlock_seq (flash_info_t * info, flash_sect_t sect){

  flash_write_cmd (info, sect, info-》addr_unlock1, AMD_CMD_UNLOCK_START);

  flash_write_cmd (info, sect, info-》addr_unlock2, AMD_CMD_UNLOCK_ACK);

  }

  全部擦写的操作是,

  __RESET

  1, 向 0xaaa 写入 aa

  2, 向 0x555 写入 55

  3, 向 0xaaa 写入80

  4, 向 0xaaa 写入aa

  5, 向0x555 写入55

  6, 向扇区地址 写入30

  __RESET 由(1) 完成

  1,2 由 (2) 完成

  3 由 (3)完成

  4,5由(4)完成

  6 由 (5)完成

  */

  /*

  指令的下发后, 还要使用状态查询函数, 等待指令的完成, 即硬件的执行完成。 这个过程是最耗时的。

  */

  if (use_flash_status_poll(info)) {

  cfiword_t cword = (cfiword_t)0xffffffffffffffffULL;

  void *dest;

  //获取扇区的内存地址

  dest = flash_map(info, sect, 0);

  //传入的超时时间为 info-》erase_blk_tout, 这个数值为: (1 《《 qry.block_erase_timeout_typ) * (1 《《 qry.block_erase_timeout_max)

  //根据手册, 计算出扇区最大超时时间为: 4096s, 意味着, 如果4096s内扇区还没有擦写完成, 那么就超时退出

  st = flash_status_poll(info, &cword, dest, info-》erase_blk_tout, “erase”);

  flash_unmap(info, sect, 0, dest);

  } else

  st = flash_full_status_check(info, sect,

  info-》erase_blk_tout,

  “erase”);

  if (st)

  rcode = 1;

  else if (flash_verbose)

  putc (‘。’);

  if (ctrlc()) {

  puts(“ Interrupted ”);

  return 1;

  }

  }

  }

  if (flash_verbose)

  puts (“ done ”);

  return rcode;

  }

  static int flash_status_poll(flash_info_t *info, void *src, void *dst,

  ulong tout, char *prompt)

  {

  #ifdef CONFIG_SYS_CFI_FLASH_STATUS_POLL

  ulong start;

  int ready;

  …

  start = get_timer(0);

  WATCHDOG_RESET();

  while (1) {

  switch (info-》portwidth) {

  case FLASH_CFI_8BIT:

  /*根据flash 的位宽(portwidth), 判断目的地址的数值是否等于src地址的数值。 上面传下来src的数值为全f, dst地址是当前扇区的0地址,

  那么flash_erase 的擦写指令完成的判断条件是: 当前扇区的0地址的数值为0xff

  如果判断条件成立后跳出循环, 否则udelay后, 再次进入循环 */

  ready = flash_read8(dst) == flash_read8(src);

  break;

  case FLASH_CFI_16BIT:

  ready = flash_read16(dst) == flash_read16(src);

  break;

  case FLASH_CFI_32BIT:

  ready = flash_read32(dst) == flash_read32(src);

  break;

  case FLASH_CFI_64BIT:

  ready = flash_read64(dst) == flash_read64(src);

  break;

  default:

  ready = 0;

  break;

  }

  if (ready)

  break;

  if (get_timer(start) 》 tout) {

  printf(“Flash %s timeout at address %lx data %lx ”,

  prompt, (ulong)dst, (ulong)flash_read8(dst));

  return ERR_TIMOUT;

  }

  udelay(1); /* also triggers watchdog */

  }

  #endif /* CONFIG_SYS_CFI_FLASH_STATUS_POLL */

  return ERR_OK;

  }

  回到do_upgrade, 扇区擦写完成后, 调用flash_write 进行写入操作

  code = flash_write((char *)addr, addr_first, addr_last - addr_first)) != 0) {

  src 是要烧些的文件的起始, addr 是要烧写到flash的目的地址, cnt 是要烧写的长度

  int flash_write (char *src, ulong addr, ulong cnt){

  int i;

  ulong end = addr + cnt - 1;

  //在单个bank的flash里, 只有一个info, info_first等于info_last

  flash_info_t *info_first = addr2info (addr);

  flash_info_t *info_last = addr2info (end );

  flash_info_t *info;

  …

  //在单个bank的flash里, 只有一次循环

  for (info = info_first; info 《= info_last; ++info) {

  ulong b_end = info-》start[0] + info-》size; /* bank end addr */

  short s_end = info-》sector_count - 1;

  for (i=0; i《info-》sector_count; ++i) {

  ulong e_addr = (i == s_end) ? b_end : info-》start[i + 1];

  //如果要操作的扇区没有取消保护, 直接返回

  if ((end 》= info-》start[i]) && (addr 《 e_addr) &&

  (info-》protect[i] != 0) ) {

  return (ERR_PROTECTED);

  }

  }

  }

  /* finally write data to flash */

  for (info = info_first; info 《= info_last && cnt》0; ++info) {

  ulong len;

  len = info-》start[0] + info-》size - addr;

  if (len 》 cnt)

  len = cnt;

  //单个bank的flash调用 write_buf后返回操作结果

  if ((i = write_buff(info, (uchar *)src, addr, len)) != 0) {

  return (i);

  }

  //多个bank的情况

  cnt -= len;

  addr += len;

  src += len;

  }

  return (ERR_OK);

  }

  //info 为flash的数据结构, src为源文件的内存地址, addr 为目的flash 地址, cnt 为文件要写的长度

  int write_buff (flash_info_t * info, uchar * src, ulong addr, ulong cnt)

  {

  ulong wp;

  uchar *p;

  int aln;

  cfiword_t cword;

  int i, rc;

  #ifdef CONFIG_SYS_FLASH_USE_BUFFER_WRITE

  int buffered_size;

  #endif

  #ifdef CONFIG_FLASH_SHOW_PROGRESS

  int digit = CONFIG_FLASH_SHOW_PROGRESS;

  int scale = 0;

  int dots = 0;

  /*

  * Suppress if there are fewer than CONFIG_FLASH_SHOW_PROGRESS writes.

  */

  if (cnt 》= CONFIG_FLASH_SHOW_PROGRESS) {

  scale = (int)((cnt + CONFIG_FLASH_SHOW_PROGRESS - 1) /

  CONFIG_FLASH_SHOW_PROGRESS);

  }

  #endif

  //wp的数值为addr

  wp = (addr & ~(info-》portwidth - 1));

  …

  buffered_size = (info-》portwidth / info-》chipwidth);

  buffered_size *= info-》buffer_size;

  //buffered_size 为256

  while (cnt 》= info-》portwidth) {

  //buffer_size 长度为1的情况,就是按字节写的情况

  if (info-》buffer_size == 1) {

  cword.l = 0;

  for (i = 0; i 《 info-》portwidth; i++)

  flash_add_byte (info, &cword, *src++);

  if ((rc = flash_write_cfiword (info, wp, cword)) != 0)

  return rc;

  wp += info-》portwidth;

  cnt -= info-》portwidth;

  continue;

  }

  //buffer_size 不为1, 按buffer 写的情况

  //如果地址为buffer_size 的整数倍, 那么i 就等于 buffer_size.256 字节。

  //可以看到, 按缓存写的话 , 总共会执行 (文件长度 / 256 + 1 次) 。 如果要写入的长度为 0xdffff, 那么要执行的次数为 0xdffff / 256 + 1 = 3584 次。

  i = buffered_size - (wp % buffered_size);

  if (i 》 cnt)

  i = cnt; //如果缓存写长度大于剩余的要写入的文件长度, 那么长度截为cnt

  if ((rc = flash_write_cfibuffer (info, wp, src, i)) != ERR_OK)

  return rc;

  i -= i & (info-》portwidth - 1);

  wp += i; //要写入的内容的地址移动 i 长度

  src += i; //要写入的文件的地址向后移动 i 长度

  cnt -= i; //文件的剩余长度减去 i 长度

  FLASH_SHOW_PROGRESS(scale, dots, digit, i);

  }

  …

  if (cnt == 0) {

  return (0);

  }

  /*

  * handle unaligned tail bytes

  */

  cword.l = 0;

  p = (uchar *)wp;

  for (i = 0; (i 《 info-》portwidth) && (cnt 》 0); ++i) {

  flash_add_byte (info, &cword, *src++);

  --cnt;

  }

  for (; i 《 info-》portwidth; ++i)

  flash_add_byte (info, &cword, flash_read8(p + i));

  return flash_write_cfiword (info, wp, cword);

  }

  对于字节写和缓存写, 分别 有flash_write_cfiword 和flash_write_cfibuffer 实现

  static int flash_write_cfiword (flash_info_t * info, ulong dest,

  cfiword_t cword)

  {

  void *dstaddr = (void *)dest;

  int flag;

  flash_sect_t sect = 0;

  char sect_found = 0;

  //根据端口宽度 , 判断要操作的地址上的数值是否为cword的数值。

  //上面传的cword 为0 , 那么要判断要写的地址的数值是否为0 , 如果判断结果为假,那么退出,返回ERR_NOT_ERASE错误数值。提示没有经过擦写。

  switch (info-》portwidth) {

  case FLASH_CFI_8BIT:

  flag = ((flash_read8(dstaddr) & cword.c) == cword.c);

  break;

  case FLASH_CFI_16BIT:

  flag = ((flash_read16(dstaddr) & cword.w) == cword.w);

  break;

  case FLASH_CFI_32BIT:

  flag = ((flash_read32(dstaddr) & cword.l) == cword.l);

  break;

  case FLASH_CFI_64BIT:

  flag = ((flash_read64(dstaddr) & cword.ll) == cword.ll);

  break;

  default:

  flag = 0;

  break;

  }

  if (!flag)

  return ERR_NOT_ERASED;

  //上面看到, flash在执行烧些前, 要先取消保护, 再进行擦除, 当两者都成功后, 才可以进行write

  //在执行烧些过程中, 关闭全部中断, 所有的中断新号会被忽略

  flag = disable_interrupts ();

  //根据不同厂商,执行对应的指令。

  switch (info-》vendor) {

  case CFI_CMDSET_INTEL_PROG_REGIONS:

  case CFI_CMDSET_INTEL_EXTENDED:

  case CFI_CMDSET_INTEL_STANDARD://intel 的规范

  flash_write_cmd (info, 0, 0, FLASH_CMD_CLEAR_STATUS);

  flash_write_cmd (info, 0, 0, FLASH_CMD_WRITE);

  break;

  case CFI_CMDSET_AMD_EXTENDED:

  case CFI_CMDSET_AMD_STANDARD: //AMD 的规范

  //根据目的地址找到要操作的扇区

  sect = find_sector(info, dest);

  //解锁扇区

  flash_unlock_seq (info, sect);

  //输入write 指令

  flash_write_cmd (info, sect, info-》addr_unlock1, AMD_CMD_WRITE);

  sect_found = 1;

  break;

  …

  }

  //等待指令完成

  switch (info-》portwidth) {

  case FLASH_CFI_8BIT:

  flash_write8(cword.c, dstaddr);

  if (info-》vendor != 1) {

  while (flash_read8(dstaddr) != cword.c)

  ;

  }

  break;

  case FLASH_CFI_16BIT:

  flash_write16(cword.w, dstaddr);

  if (info-》vendor != 1) {

  while (flash_read16(dstaddr) != cword.w)

  ;

  }

  break;

  case FLASH_CFI_32BIT:

  flash_write32(cword.l, dstaddr);

  case FLASH_CFI_64BIT:

  flash_write64(cword.ll, dstaddr);

  if (info-》vendor != 1) {

  while (flash_read64(dstaddr) != cword.ll)

  ;

  }

  break;

  }

  //恢复中断

  if (flag)

  enable_interrupts ();

  if (!sect_found)

  sect = find_sector (info, dest);

  if (use_flash_status_poll(info))

  return flash_status_poll(info, &cword, dstaddr,

  info-》write_tout, “write”);

  else

  return flash_full_status_check(info, sect,

  info-》write_tout, “write”);

  }

  flash_write_cfibuffer 使用了同样的逻辑 , 不同的指令

  结语

  关于Nor Flash的相关介绍就到这了,如有不足之处欢迎指正。

相关阅读推荐:对于嵌入式为什么要有uboot的深度解析

相关阅读推荐:nand和nor区别

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分