在上一节中,不知道你有没有注意到,以NOR方式启动u-boot后,运行有一行信息:

输出Flash信息这一部分代码是位于board_init_r阶段,执行initr_flash()函数的输出结果。

我们开发板上搭载了型号为S29AL016D70TF102的2MB大小的NOR FLASH,这里输出NOR FALSH大小为0字节,很明显没有能正确识别 2M的NOR FLASH了。

这一节我们将会介绍u-boot如何支持我们的NOR FLASH,这样我们后续就可以通过命令行对NOR FALSH进行读写操作。

一、Mini2440 NOR FLASH介绍

在之前的裸机开发中,我们没有介绍NOR FLASH的裸机程序,如果直接动手修改u-boot程序使其支持NOR FALSH,你可能会很懵逼,这里我们先补充一下NOR FLASH的基础知识。

NOR FLASH存储器接口标准包含CFI和JEDEC:

  • CFI为公共Flash接口(Common Flash Interface),CFI是一个公开的标准的从Flash Memory器件中读取数据的接口。它可以使系统软件查询已安装的Flash Memory器件的各种参数,包括器件阵列结构参数、电气和时间参数以及器件支持的功能等。利用CFI可以不用修改系统软件 就可以用新型的和改进的产品代替旧版本的产品。例如:如果新型的Flash Memory的擦除时间只有旧版本的一半,系统软件只要通过CFI读取新器件的擦除时间等参数,修改一下定时器的时间参数即可。
  • JEDEC是由生产厂商们制定的国际性协议,主要为 计算机 内存制定。JEDEC用来帮助程序读取Flash的制造商ID和设备ID,以确定Flash的大小和算法,如果芯片不支持CFI,就需使用JEDEC了。工业标准的内存通常指的是符合JEDEC标准的一组内存。

老式的NOR FLASH一般是JEDEC规范,其一般只包含识别 ID、擦除芯片、烧写数据的命令。要想知道其容量大小等信息,就需要先读出其芯片id,根据芯片id匹配uboot中drivers/mtd/jedec_flash.c里的的jedec_table数组,来确定NOR FLASH的哥哥参数(名称、容量、位宽)等,比较麻烦。另外如果内核jedec_table数组中事先没有对应芯片id的信息,还需要先在该数组中添加。

目前的NOR FLASH一般都支持CFI规范,其除了提供识别 ID、擦除芯片、烧写数据的命令之后,还提供了进入CFI模式的命令,进入CFI模式后就可以通过读取相应地址的数据获取芯片属性信息,如容量、电压等信息。 进入CFI模式(使用CFI Query命令)。

1.1 S29AL016D70TF102

Mini2440开发板就是将2M的NOR FLASH(型号S29AL016D70TF102)焊接在了Bank0上,S29AL016D70TF102这款芯片具有以下性质:

  • 灵活的扇区架构:
    • 一个 16 KB、两个 8 KB、一个 32 KB 和三十一个64 KB 扇区(字节模式);
    • — 1 个 8 Kword、2 个 4 Kword、1 个 16 Kword 和 31 Kword 扇区(字模式);
  • Top or bottom boot block configurations available;
  • 扇区保护特性:
    • A hardware method of locking a sector to prevent any program or erase operations within that sector.
    • Sectors can be locked in-system(通过NOR FLASH命令寄存器控制) or via programming equipment(编程器,需要高电压8.5V~12.5V).
    • Temporary Sector Unprotect feature allows code changes in previously locked sectors.
  • 与 JEDEC 标准的兼容性;
  • CFI(通用闪存接口)兼容;
  • 擦除挂起/擦除恢复;
  • 容量2MB;

Spansion 标准产品有多种封装和操作范围。 芯片型号(有效组合)由下面的元素组成,可以看出S29AL016D70TF102型号的参数MODEL NUMBER为02,bottom boot sector device。

S29AL016D70TF102支持8位和16位两种模式,具体选择哪种模式取决于芯片BYTE引脚信号;这里我们Mini2440采用的是16位模式,S3C2440的A1~A22连接NOR FLASH的A0~A21。需要错位连接的原因是:S3C2440处理器的每个地址对应的是一个BYTE的数据单元,而16位模式下,NOR FLASH的每个地址对应的是一个HALF-WORD(16-BIT)的数据单元。为了保持匹配,所以必须错位连接。这样,从S3C2440处理器发送出来的地址信号的最低位A0对16位Flash来说就被屏蔽掉了。

补充说明:

  • 一般来说,ARM处理器内部要设置相应的寄存器,告诉处理器外部扩展的Flash的位宽(8-BIT/16-BIT/32-BIT)。这样,处理器才知道在访问的时候如何从FLASH正确的读取数据;
  • 有些ARM处理器内部可以设置地址的错位。对于支持软件选择地址错位的处理器,在连接16-BIT FLASH的时候,硬件上可以不需要把地址线错位。
  • 如果处理器支持内部设置地址错位,在实际访问的时候,送出的地址实际上是在MCU内部做了错位处理,其作用是等效于硬件连接上的错位的。

1.2 指令集

Legend:

  • X = Don’t care
  • RA = Address of the memory location to be read.
  • RD = Data read from location RA during read operation.
  • PA = Address of the memory location to be programmed. Addresses latch on the falling edge of the WE# or CE# pulse, whichever happens later.
  • PD = Data to be programmed at location PA. Data latches on the rising edge of WE# or CE# pulse, whichever happens first.
  • SA = Address of the sector to be verified (in autoselect mode) or erased. Address bits A19–A12 uniquely select any sector.

Note:

  • 1. See Table 1 for description of bus operations.
  • 2. All values are in hexadecimal.
  • 3. Except for the read cycle and the fourth cycle of the autoselect command sequence, all bus cycles are write cycles.
  • 4. Data bits DQ15–DQ8 are don’t cares for unlock and command cycles.
  • 5. Address bits A19–A11 are don’t cares for unlock and command cycles, unless SA or PA required.
  • 6. No unlock or command cycles required when reading array data.
  • 7. The Reset command is required to return to reading array data when device is in the autoselect mode, or if DQ5 goes high (while the device is providing status data).
  • 8. The fourth cycle of the autoselect command sequence is a read cycle.
  • 9. The data is 00h for an unprotected sector and 01h for a protected sector. See “Autoselect Command Sequence” for more information.
  • 10. Command is valid when device is ready to read array data or when device is in autoselect mode.
  • 11. The Unlock Bypass command is required prior to the Unlock Bypass Program command.
  • 12. The Unlock Bypass Reset command is required to return to reading array data when the device is in the unlock bypass mode. F0 is also acceptable.
  • 13. The system may read and program in non-erasing sectors, or enter the autoselect mode, when in the Erase Suspend mode. The Erase Suspend command is valid only during a sector erase operation.
  • 14. The Erase Resume command is valid only during the Erase Suspend mode.

以Autoselect Command Sequence为例,当采用 16 位位宽时,先向地址“555”处写入“AA”,再向地址“2AA”处写 入“55”,接着向地址“555”处写入“90” ,最后从 0 地址“X00”处可以读到“01”。 周期“First”和“Second”是“解锁”,周期“Third”是发出命令。 需要注意的是,以地址"555"为例,每个地址对应的数据长度为2个字节,对应8位位宽地址555<<1=AAA。

以读取厂家ID为例,编写代码时序如下:

  • 解锁:向地址AAAH(555<<1)写入AAH,向地址554(2AA<<1)写入55H;
  • 命令:向地址AAAH(555<<1)写入90H;
  • 读地址0x00得到厂家ID;

由于Mini2440的A1接到NOR FLASH的A0,所以2440发出(555<<1),NOR FLASH才能收到“555”这个地址;同理,只要是2440发出的地址都需要在NOR FLASH的地址基础上<<1。

注意:

  • 这里有一点需要注意的是,在解锁和命令总线周期地址A19~A11是被忽略的,所以地址555与5555等价,2AA与2AAA是等价的,因为A0~A10位是一致的。

关于如何对NOR FLASH进行读写、擦除,我就简单说一下:

  • 读:NOR FLASH上电后处于数据读取状态(Reading Array Data)。此状态可以进行正常的读,这和读取SDRAM/SRAM/ROM一样。(要是不一样的话,芯片上电后如何从NOR FLASH中读取启动代码)。需要注意的是进入自动选择(Autoselect Command)模式后需要发送复位命令才能回到数据读取状态(Reading Array Data)。
  • 擦除:在完成信息获取后一般就要擦除数据。NOR FLASH支持扇区擦除(Sector Erase)和整片擦除(Chip Erase),这2种模式都有对应的命令序列,在完成擦除命令后会自动返回到数据读取(Reading Array Data)状态,在返回前可查询编程的状态。
  • 编程(写):完成擦除后就需要对芯片进行写入操作也就是编程,这就需要进入编程(Program)状态。在完成编程命令后会自动返回到数据读取(Reading Array Data)状态,在返回前可查询编程的状态,注意:编程前一定要先擦除。因为编程只能将'1'改写为'0',通过擦写可以将数据全部擦写为'1'。如果绕过解锁模式Unlock Bypass,编程只需要两个周期,而不是4个周期。

1.3 裸机程序

这里就不展示裸机程序了,有兴趣的可以参考mini2440硬件篇之Nor Flash

二、NOR FLASH支持

2.1 宏定义

在include/configs/smdk2440.h文件有NOR FLASH相关的定义,如下:

#define PHYS_FLASH_1        0x00000000 /* Flash Bank #0 */

#define CONFIG_SYS_FLASH_BASE    PHYS_FLASH_1    // NOR FLASH物理基地址


/*-----------------------------------------------------------------------
 * FLASH and environment organization
 */

#define CONFIG_SYS_FLASH_CFI
#define CONFIG_FLASH_CFI_DRIVER
#define CONFIG_FLASH_CFI_LEGACY
#define CONFIG_SYS_FLASH_LEGACY_512Kx16    // jedec_table数组支持哪些容量大小的芯片 这里表示芯片大小为512kb,位宽为16 我们可以忽略这个
#define CONFIG_FLASH_SHOW_PROGRESS    45

#define CONFIG_SYS_MAX_FLASH_BANKS    1     // NOR FLASH芯片数量                        
#define CONFIG_SYS_FLASH_BANKS_LIST     { CONFIG_SYS_FLASH_BASE }
#define CONFIG_SYS_MAX_FLASH_SECT    (19)   // NOR FLASH最大扇区数量

2.2 分析启动信息

在u-boot以NOR方式启动时,如果我们想打印更加详细的信息,可以在include/configs/smdk2440.h中加入如下宏,重新编译,下载运行,则可以打印更多调试相关信息:

#define DEBUG

这里信息比较多,我就不截图了:

Flash: fwc addr 00000000 cmd f0 00f0 16bit x 16 bit  # 复位命令 地址0x00写入0xFO
fwc addr 0000aaaa cmd aa 00aa 16bit x 16 bit         # 地址0xaaaa写入0xaa      
fwc addr 00005554 cmd 55 0055 16bit x 16 bit         # 地址0x5554写入55  
fwc addr 0000aaaa cmd 90 0090 16bit x 16 bit         # 地址0xaaaa写入0x90
fwc addr 00000000 cmd f0 00f0 16bit x 16 bit         # 复位命令 
JEDEC PROBE: ID 1 2249 0                              
fwc addr 00000000 cmd ff 00ff 16bit x 16 bit
fwc addr 00000000 cmd 90 0090 16bit x 16 bit
fwc addr 00000000 cmd ff 00ff 16bit x 16 bit
JEDEC PROBE: ID be ea00 0
0 Bytes

打印出NOR FLASH的厂家ID=0x01(AMD生产),,设备ID=0x2249,我们查看S29AL016D70TF102芯片的datasheet,发现这两个ID是正确的,但是输出信息中却没有识别到我们NOR FLASH的型号,说明程序底层驱动是对的,但是u-boot没支持我们这个型号的NOR FLASH。

2.2.1 initr_flash

我们定位到 initr_flash(common/board_r.c):

static int initr_flash(void)
{
    ulong flash_size = 0;
    bd_t *bd = gd->bd;

    puts("Flash: ");

    if (board_flash_wp_on())
        printf("Uninitialized - Write Protect Onn");
    else
        flash_size = flash_init();   // 获取NOR FLASH大小

    print_size(flash_size, "");
#ifdef CONFIG_SYS_FLASH_CHECKSUM
    /*
    * Compute and print flash CRC if flashchecksum is set to 'y'
    *
    * NOTE: Maybe we should add some WATCHDOG_RESET()? XXX
    */
    if (getenv_yesno("flashchecksum") == 1) {
        printf("  CRC: %08X", crc32(0,
            (const unsigned char *) CONFIG_SYS_FLASH_BASE,
            flash_size));
    }
#endif /* CONFIG_SYS_FLASH_CHECKSUM */
    putc('n');

    /* update start of FLASH memory    */
#ifdef CONFIG_SYS_FLASH_BASE
    bd->bi_flashstart = CONFIG_SYS_FLASH_BASE;
#endif
    /* size of FLASH memory (final value) */
    bd->bi_flashsize = flash_size;

#if defined(CONFIG_SYS_UPDATE_FLASH_SIZE)
    /* Make a update of the Memctrl. */
    update_flash_size(flash_size);
#endif


#if defined(CONFIG_OXC) || defined(CONFIG_RMU)
    /* flash mapped at end of memory map */
    bd->bi_flashoffset = CONFIG_SYS_TEXT_BASE + flash_size;
#elif CONFIG_SYS_MONITOR_BASE == CONFIG_SYS_FLASH_BASE
    bd->bi_flashoffset = monitor_flash_len;    /* reserved area for monitor */
#endif
    return 0;
}
2.2.2 flash_init

flash_init函数在driversmtdcfi_flash.c中定义:

unsigned long flash_init (void)
{
    unsigned long size = 0;
    int i;

#ifdef CONFIG_SYS_FLASH_PROTECTION
    /* read environment from EEPROM */
    char s[64];
    getenv_f("unlock", s, sizeof(s));
#endif

#ifdef CONFIG_CFI_FLASH /* for driver model */
    cfi_flash_init_dm();
#endif

    /* Init: no FLASHes known */
    for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; ++i) {
        flash_info[i].flash_id = FLASH_UNKNOWN;            //0xFFFF

        /* Optionally write flash configuration register */
        cfi_flash_set_config_reg(cfi_flash_bank_addr(i),   // 什么也没做 里面是空
                     cfi_flash_config_reg(i));

        if (!flash_detect_legacy(cfi_flash_bank_addr(i), i))
            flash_get_size(cfi_flash_bank_addr(i), i);   
        size += flash_info[i].size;
        if (flash_info[i].flash_id == FLASH_UNKNOWN) {
#ifndef CONFIG_SYS_FLASH_QUIET_TEST
            printf ("## Unknown flash on Bank %d "
                "- Size = 0x%08lx = %ld MBn",
                i+1, flash_info[i].size,
                flash_info[i].size >> 20);
#endif /* CONFIG_SYS_FLASH_QUIET_TEST */
        }
#ifdef CONFIG_SYS_FLASH_PROTECTION
        ......
#endif /* CONFIG_SYS_FLASH_PROTECTION */
    }

    flash_protect_default();
#ifdef CONFIG_FLASH_CFI_MTD
    cfi_mtd_init();
#endif

    return (size);
}

其中CONFIG_SYS_MAX_FLASH_BANKS定义为1,FLASH_UNKNOWN 定义为0xFFFF:

#define CONFIG_SYS_MAX_FLASH_BANKS    1
#define FLASH_UNKNOWN 0xFFFF /* unknown flash type */

flash_info定义如下:

#define CFI_MAX_FLASH_BANKS CONFIG_SYS_MAX_FLASH_BANKS
flash_info_t flash_info[CFI_MAX_FLASH_BANKS];    /* FLASH chips info */
2.2.3 flash_info_t结构体

其中flash结构体定义如下:

/*-----------------------------------------------------------------------
 * FLASH Info: contains chip specific data, per FLASH bank
 */

typedef struct {
    ulong    size;            /* total bank size in bytes  容量、单位字节       */
    ushort    sector_count;        /* number of erase units  扇区数量        */
    ulong    flash_id;        /* combined device & manufacturer code  设备id&厂商id   */
    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        /* 支持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    */
    uchar   cmd_erase_sector;    /* vendor specific erase sect. 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 解锁地址1 */
    ulong   addr_unlock2;        /* unlock address 2 for AMD flash roms 解锁地址2 */
    const char *name;        /* human-readable name                    */
#endif
#ifdef CONFIG_MTD
    struct mtd_info *mtd;
#endif
} flash_info_t;

这里我们重点分析flash_detect_legacy和flash_get_size函数。

2.3 flash_detect_legacy

flash_detect_legacy函数在driversmtdcfi_flash.c中定义:

/*-----------------------------------------------------------------------
 * Call board code to request info about non-CFI flash.
 * board_flash_get_legacy needs to fill in at least:
 * info->portwidth, info->chipwidth and info->interface for Jedec probing.
 */
static int flash_detect_legacy(phys_addr_t base, int banknum)
{
    flash_info_t *info = &flash_info[banknum];

    if (board_flash_get_legacy(base, banknum, info)) {  // 初始化portwidth、chipwidth等参数
        /* board code may have filled info completely. If not, we
           use JEDEC ID probing. */
        if (!info->vendor) {
            int modes[] = {          // 定义两种生产厂商,AMD与INTEL
                CFI_CMDSET_AMD_STANDARD,
                CFI_CMDSET_INTEL_STANDARD
            };
            int i;

            for (i = 0; i < ARRAY_SIZE(modes); i++) { // 循环两次
                info->vendor = modes[i];
                info->start[0] =
                    (ulong)map_physmem(base,
                               info->portwidth,
                               MAP_NOCACHE);
                if (info->portwidth == FLASH_CFI_8BIT
                    && info->interface == FLASH_CFI_X8X16) {
                    info->addr_unlock1 = 0x2AAA;
                    info->addr_unlock2 = 0x5555;
                } else {     
                    info->addr_unlock1 = 0x5555;
                    info->addr_unlock2 = 0x2AAA;
                }
                flash_read_jedec_ids(info);  // 读取芯片id信息
                debug("JEDEC PROBE: ID %x %x %xn",
                        info->manufacturer_id,
                        info->device_id,
                        info->device_id2);
                if (jedec_flash_match(info, info->start[0]))  // 使用jedec规范,通过id比较是否支持该NOR FLASH
                    break;
                else
                    unmap_physmem((void *)info->start[0],
                              info->portwidth);
            }
        }

        switch(info->vendor) {
        case CFI_CMDSET_INTEL_PROG_REGIONS:
        case CFI_CMDSET_INTEL_STANDARD:
        case CFI_CMDSET_INTEL_EXTENDED:
            info->cmd_reset = FLASH_CMD_RESET;
            break;
        case CFI_CMDSET_AMD_STANDARD:
        case CFI_CMDSET_AMD_EXTENDED:
        case CFI_CMDSET_AMD_LEGACY:
            info->cmd_reset = AMD_CMD_RESET;
            break;
        }
        info->flash_id = FLASH_MAN_CFI;
        return 1;
    }
    return 0; /* use CFI */
}

这里首先初始化芯片位宽、以及端口位宽等参数,然后通过函数flash_read_jedec_ids去读取NOR FLASH的厂家ID和设备ID信息,并写入到info成员。

2.3.1 board_flash_get_legacy

board_flash_get_legacy在board/samsung/smdk2440/smdk2440.c中定义:

/*
 * Hardcoded flash setup:
 * Flash 0 is a non-CFI AMD AM29LV800BB flash.
 */
ulong board_flash_get_legacy(ulong base, int banknum, flash_info_t *info)
{
        info->portwidth = FLASH_CFI_16BIT;   // 2
        info->chipwidth = FLASH_CFI_BY16;    // 2 
        info->interface = FLASH_CFI_X16;     // 2
        return 1;
}
2.3.2 flash_read_jedec_ids 

flash_read_jedec_ids函数在driversmtdcfi_flash.c中定义:

static void flash_read_jedec_ids (flash_info_t * info)
{
    info->manufacturer_id = 0;
    info->device_id       = 0;
    info->device_id2      = 0;

    switch (info->vendor) {
    case CFI_CMDSET_INTEL_PROG_REGIONS:
    case CFI_CMDSET_INTEL_STANDARD:
    case CFI_CMDSET_INTEL_EXTENDED:
        cmdset_intel_read_jedec_ids(info);
        break;
    case CFI_CMDSET_AMD_STANDARD:
    case CFI_CMDSET_AMD_EXTENDED:
        cmdset_amd_read_jedec_ids(info);  // AMD芯片 走这里
        break;
    default:
        break;
    }
}

其中cmdset_amd_read_jedec_ids函数采用JEDEC协议,读取设备id信息:

static void cmdset_amd_read_jedec_ids(flash_info_t *info)
{
    ushort bankId = 0;
    uchar  manuId;

    flash_write_cmd(info, 0, 0, AMD_CMD_RESET);   // 复位命令
    flash_unlock_seq(info, 0);                    // 解锁命令
    flash_write_cmd(info, 0, info->addr_unlock1, FLASH_CMD_READ_ID);  // 读取id命令
    udelay(1000); /* some flash are slow to respond */

    manuId = flash_read_uchar (info, FLASH_OFFSET_MANUFACTURER_ID);
    /* JEDEC JEP106Z specifies ID codes up to bank 7 */
    while (manuId == FLASH_CONTINUATION_CODE && bankId < 0x800) {
        bankId += 0x100;
        manuId = flash_read_uchar (info,
            bankId | FLASH_OFFSET_MANUFACTURER_ID);
    }
    info->manufacturer_id = manuId;

    switch (info->chipwidth){
    case FLASH_CFI_8BIT:
        info->device_id = flash_read_uchar (info,
                        FLASH_OFFSET_DEVICE_ID);
        if (info->device_id == 0x7E) {
            /* AMD 3-byte (expanded) device ids */
            info->device_id2 = flash_read_uchar (info,
                        FLASH_OFFSET_DEVICE_ID2);
            info->device_id2 <<= 8;
            info->device_id2 |= flash_read_uchar (info,
                        FLASH_OFFSET_DEVICE_ID3);
        }
        break;
    case FLASH_CFI_16BIT:           // 这里
        info->device_id = flash_read_word (info,
                        FLASH_OFFSET_DEVICE_ID);
        if ((info->device_id & 0xff) == 0x7E) {
            /* AMD 3-byte (expanded) device ids */
            info->device_id2 = flash_read_uchar (info,
                        FLASH_OFFSET_DEVICE_ID2);
            info->device_id2 <<= 8;
            info->device_id2 |= flash_read_uchar (info,
                        FLASH_OFFSET_DEVICE_ID3);
        }
        break;
    default:
        break;
    }
    flash_write_cmd(info, 0, 0, AMD_CMD_RESET);
    udelay(1);
}

需要注意的是这里解锁地址为0x5555、0x2AAAA。当然实际向NOR FALSH芯片发送解锁或命令时,会根据选择的位宽重新计算地址。比如:

/*
 * Write a proper sized command to the correct address
 */
void flash_write_cmd (flash_info_t * info, flash_sect_t sect,
                      uint offset, u32 cmd)
{

        void *addr;
        cfiword_t cword;

        addr = flash_map (info, sect, offset);                               // 重新计算地址
        flash_make_cmd (info, cmd, &cword);
        switch (info->portwidth) {                                           // 1
        case FLASH_CFI_8BIT:
                debug ("fwc addr %p cmd %x %x 8bit x %d bitn", addr, cmd,
                       cword.w8, info->chipwidth << CFI_FLASH_SHIFT_WIDTH);
                flash_write8(cword.w8, addr);
                break;
        case FLASH_CFI_16BIT:                                               // 2  
                debug ("fwc addr %p cmd %x %4.4x 16bit x %d bitn", addr,
                       cmd, cword.w16,
                       info->chipwidth << CFI_FLASH_SHIFT_WIDTH);
                flash_write16(cword.w16, addr);
                break;
        case FLASH_CFI_32BIT:                                              // 4
                debug ("fwc addr %p cmd %x %8.8x 32bit x %d bitn", addr,
                       cmd, cword.w32,
                       info->chipwidth << CFI_FLASH_SHIFT_WIDTH);
                flash_write32(cword.w32, addr);
                break;
        case FLASH_CFI_64BIT:
#ifdef DEBUG
                {
                        char str[20];

                        print_longlong (str, cword.w64);

                        debug ("fwrite addr %p cmd %x %s 64 bit x %d bitn",
                               addr, cmd, str,
                               info->chipwidth << CFI_FLASH_SHIFT_WIDTH);
                }
#endif
                flash_write64(cword.w64, addr);
                break;
        }

        /* Ensure all the instructions are fully finished */
        sync();

        flash_unmap(info, sect, offset, addr);
}

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);
}
2.3.3 jedec_flash_match

然后进入jedec_flash_match函数,该函数位于drivers/mtd/jedec_flash.c,这个函数通过遍历jedec_table找到厂家ID、设备ID匹配的NOR FALSH型号信息,然后使用这些信息初始化info成员:

/*-----------------------------------------------------------------------
 * match jedec ids against table. If a match is found, fill flash_info entry
 */
int jedec_flash_match(flash_info_t *info, ulong base)
{
    int ret = 0;
    int i;
    ulong mask = 0xFFFF;
    if (info->chipwidth == 1)
        mask = 0xFF;

    for (i = 0; i < ARRAY_SIZE(jedec_table); i++) {
        if ((jedec_table[i].mfr_id & mask) == (info->manufacturer_id & mask) &&
            (jedec_table[i].dev_id & mask) == (info->device_id & mask)) {
            fill_info(info, &jedec_table[i], base);  // 其中包含了info->size的填充
            ret = 1;
            break;
        }
    }
    return ret;
}

其中jedec_table数组查看:

static const struct amd_flash_info jedec_table[] = {
#ifdef CONFIG_SYS_FLASH_LEGACY_256Kx8
    {
        .mfr_id        = (u16)SST_MANUFACT,
        .dev_id        = SST39LF020,
        .name        = "SST 39LF020",
        .uaddr        = {
            [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
        },
        .DevSize    = SIZE_256KiB,
        .CmdSet        = P_ID_AMD_STD,
        .NumEraseRegions= 1,
        .regions    = {
            ERASEINFO(0x01000,64),
        }
    },
#endif
#ifdef CONFIG_SYS_FLASH_LEGACY_512Kx8
    {
        .mfr_id        = (u16)AMD_MANUFACT,
        .dev_id        = AM29LV040B,
        .name        = "AMD AM29LV040B",
        .uaddr        = {
            [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
        },
        .DevSize    = SIZE_512KiB,
        .CmdSet        = P_ID_AMD_STD,
        .NumEraseRegions= 1,
        .regions    = {
            ERASEINFO(0x10000,8),
        }
    },
    {
        .mfr_id        = (u16)SST_MANUFACT,
        .dev_id        = SST39LF040,
        .name        = "SST 39LF040",
        .uaddr        = {
            [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
        },
        .DevSize    = SIZE_512KiB,
        .CmdSet        = P_ID_AMD_STD,
        .NumEraseRegions= 1,
        .regions    = {
            ERASEINFO(0x01000,128),
        }
    },
    {
        .mfr_id        = (u16)STM_MANUFACT,
        .dev_id        = STM_ID_M29W040B,
        .name        = "ST Micro M29W040B",
        .uaddr        = {
            [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
        },
        .DevSize    = SIZE_512KiB,
        .CmdSet        = P_ID_AMD_STD,
        .NumEraseRegions= 1,
        .regions    = {
            ERASEINFO(0x10000,8),
        }
    },
    {
        .mfr_id        = (u16)MX_MANUFACT,
        .dev_id        = MX29LV040,
        .name        = "MXIC MX29LV040",
        .uaddr        = {
            [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
        },
        .DevSize    = SIZE_512KiB,
        .CmdSet        = P_ID_AMD_STD,
        .NumEraseRegions= 1,
        .regions    = {
            ERASEINFO(0x10000, 8),
        }
    },
    {
        .mfr_id        = (u16)WINB_MANUFACT,
        .dev_id        = W39L040A,
        .name        = "WINBOND W39L040A",
        .uaddr        = {
            [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
        },
        .DevSize    = SIZE_512KiB,
        .CmdSet        = P_ID_AMD_STD,
        .NumEraseRegions= 1,
        .regions    = {
            ERASEINFO(0x10000, 8),
        }
    },
    {
        .mfr_id        = (u16)AMIC_MANUFACT,
        .dev_id        = A29L040,
        .name        = "AMIC A29L040",
        .uaddr        = {
            [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
        },
        .DevSize    = SIZE_512KiB,
        .CmdSet        = P_ID_AMD_STD,
        .NumEraseRegions= 1,
        .regions    = {
            ERASEINFO(0x10000, 8),
        }
    },
    {
        .mfr_id        = (u16)EON_MANUFACT,
        .dev_id        = EN29LV040A,
        .name        = "EON EN29LV040A",
        .uaddr        = {
            [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
        },
        .DevSize    = SIZE_512KiB,
        .CmdSet        = P_ID_AMD_STD,
        .NumEraseRegions= 1,
        .regions    = {
            ERASEINFO(0x10000, 8),
        }
    },
#endif
#ifdef CONFIG_SYS_FLASH_LEGACY_512Kx16
    {
        .mfr_id        = (u16)AMD_MANUFACT,
        .dev_id        = AM29F400BB,
        .name        = "AMD AM29F400BB",
        .uaddr        = {
            [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
        },
        .DevSize    = SIZE_512KiB,
        .CmdSet        = CFI_CMDSET_AMD_LEGACY,
        .NumEraseRegions= 4,
        .regions    = {
            ERASEINFO(0x04000, 1),
            ERASEINFO(0x02000, 2),
            ERASEINFO(0x08000, 1),
            ERASEINFO(0x10000, 7),
        }
    },
    {
        .mfr_id        = (u16)AMD_MANUFACT,
        .dev_id        = AM29LV400BB,
        .name        = "AMD AM29LV400BB",
        .uaddr        = {
            [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
        },
        .DevSize    = SIZE_512KiB,
        .CmdSet        = CFI_CMDSET_AMD_LEGACY,
        .NumEraseRegions= 4,
        .regions    = {
            ERASEINFO(0x04000,1),
            ERASEINFO(0x02000,2),
            ERASEINFO(0x08000,1),
            ERASEINFO(0x10000,7),
        }
    },
    {
        .mfr_id        = (u16)AMD_MANUFACT,
        .dev_id        = AM29LV800BB,
        .name        = "AMD AM29LV800BB",
        .uaddr        = {
            [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
        },
        .DevSize    = SIZE_1MiB,
        .CmdSet        = CFI_CMDSET_AMD_LEGACY,
        .NumEraseRegions= 4,
        .regions    = {
            ERASEINFO(0x04000, 1),
            ERASEINFO(0x02000, 2),
            ERASEINFO(0x08000, 1),
            ERASEINFO(0x10000, 15),
        }
    },
    {
        .mfr_id        = (u16)AMD_MANUFACT,
        .dev_id        = AM29LV800BT,
        .name        = "AMD AM29LV800BT",
        .uaddr        = {
            [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
        },
        .DevSize    = SIZE_1MiB,
        .CmdSet        = CFI_CMDSET_AMD_LEGACY,
        .NumEraseRegions= 4,
        .regions    = {
            ERASEINFO(0x10000, 15),
            ERASEINFO(0x08000, 1),
            ERASEINFO(0x02000, 2),
            ERASEINFO(0x04000, 1),
        }
    },
    {
        .mfr_id        = (u16)MX_MANUFACT,
        .dev_id        = AM29LV800BT,
        .name        = "MXIC MX29LV800BT",
        .uaddr        = {
            [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
        },
        .DevSize    = SIZE_1MiB,
        .CmdSet        = CFI_CMDSET_AMD_LEGACY,
        .NumEraseRegions= 4,
        .regions    = {
            ERASEINFO(0x10000, 15),
            ERASEINFO(0x08000, 1),
            ERASEINFO(0x02000, 2),
            ERASEINFO(0x04000, 1),
        }
    },
    {
        .mfr_id        = (u16)EON_ALT_MANU,
        .dev_id        = AM29LV800BT,
        .name        = "EON EN29LV800BT",
        .uaddr        = {
            [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
        },
        .DevSize    = SIZE_1MiB,
        .CmdSet        = CFI_CMDSET_AMD_LEGACY,
        .NumEraseRegions= 4,
        .regions    = {
            ERASEINFO(0x10000, 15),
            ERASEINFO(0x08000, 1),
            ERASEINFO(0x02000, 2),
            ERASEINFO(0x04000, 1),
        }
    },
    {
        .mfr_id        = (u16)STM_MANUFACT,
        .dev_id        = STM29F400BB,
        .name        = "ST Micro M29F400BB",
        .uaddr        = {
            [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
        },
        .DevSize        = SIZE_512KiB,
        .CmdSet            = CFI_CMDSET_AMD_LEGACY,
        .NumEraseRegions    = 4,
        .regions        = {
            ERASEINFO(0x04000, 1),
            ERASEINFO(0x02000, 2),
            ERASEINFO(0x08000, 1),
            ERASEINFO(0x10000, 7),
        }
    },
#endif
};

查询以后发现,确实是这里面没有我们所使用的NOR FLASH型号S29AL016D70TF102。

进入机器ID的宏定义代码,里面找到了这个(比较后四位):

#define AMD_MANUFACT    0x00010001    /* AMD       manuf. ID in D23..D16, D7..D0 */

01就是我们使用的NOR FLASH厂家ID,说明u-boot是能够支持的,只是数组中没有将其写入。更改数组,加入我们自己的NOR FLASH,使其能够支持。

2.3.4 修改jedec_table数组(drivers/mtd/jedec_flash.c)

因为数组中有宏定义,我也不打算破坏它的结构了,随便复制了一个将其粘贴到数组的最后,然后修改为如下内容:

    {
        .mfr_id        = (u16)AMD_MANUFACT,
        .dev_id        = AM29LV160DB,
        .name        = "AMD AM29LV160DB",
        .uaddr        = {
            [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
        },
        .DevSize        = SIZE_2MiB,
        .CmdSet            = P_ID_AMD_STD,
        .NumEraseRegions    = 4,
        .regions        = {
            ERASEINFO(16*1024, 1),
            ERASEINFO(8*1024, 2),
            ERASEINFO(32*1024, 1),
            ERASEINFO(64*1024,31),
        }
    },
  • 厂家ID:mfr_id修改为AMD_MANUFACT;
  • 设备ID:dev_id修改为0x2249,即AM29LV160DB;
  • 名字:name修改为AMD AM29LV160DB;
  • uaddr:解锁地址改为[1] = MTD_UADDR_0x0555_0x02AA 。[0]表示NOR是8位的,我使用的是16位的,因此改为1。NOR FALSH的指令集中有一些命令在写入命令时序之前,需要先进行解锁,即向一些地址写入固定值,解锁地址可以查阅手册。
  • 大小:DevSize修改为SIZE_2MiB;
  • NumEraseRegions:NOR的结构不同,擦除块是不同的。可以查阅手册,有多少种块,就写几。
  • regions:说明擦除块的分布情况,从上往下分别是NOR中地址从低到高排列;

我使用的NOR有4种,在字模式下,分别8Kword 1块,4Kword 2 块,16Kword 1块,32Kword 31块。

所以regions配置如下:

        .regions        = {
            ERASEINFO(16*1024, 1),
            ERASEINFO(8*1024, 2),
            ERASEINFO(32*1024, 1),
            ERASEINFO(64*1024,31),
        }

修改include/configs/smdk2440.h文件,这里使用CONFIG_SYS_MAX_FLASH_SECT定义了我们NOR FLASH最大扇区数量,我们修改为128。 其实这个值多少都可以,只要大于使用的擦除块就行了。

#define CONFIG_SYS_MAX_FLASH_SECT (128)

2.4 flash_get_size

/*
 * The following code cannot be run from FLASH!
 *
 */
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);

    if (flash_detect_cfi (info, &qry)) {
        info->vendor = le16_to_cpu(get_unaligned(&(qry.p_id)));
        info->ext_addr = le16_to_cpu(get_unaligned(&(qry.p_adr)));
        num_erase_regions = qry.num_erase_regions;

        if (info->ext_addr) {
            info->cfi_version = (ushort) flash_read_uchar (info,
                        info->ext_addr + 3) << 8;
            info->cfi_version |= (ushort) flash_read_uchar (info,
                        info->ext_addr + 4);
        }

#ifdef DEBUG
        flash_printqry (&qry);
#endif

        switch (info->vendor) {
        case CFI_CMDSET_INTEL_PROG_REGIONS:
        case CFI_CMDSET_INTEL_STANDARD:
        case CFI_CMDSET_INTEL_EXTENDED:
            cmdset_intel_init(info, &qry);
            break;
        case CFI_CMDSET_AMD_STANDARD:
        case CFI_CMDSET_AMD_EXTENDED:
            cmdset_amd_init(info, &qry);
            break;
        default:
            printf("CFI: Unknown command set 0x%xn",
                    info->vendor);
            /*
             * Unfortunately, this means we don't know how
             * to get the chip back to Read mode. Might
             * as well try an Intel-style reset...
             */
            flash_write_cmd(info, 0, 0, FLASH_CMD_RESET);
            return 0;
        }

        /* Do manufacturer-specific fixups */
        switch (info->manufacturer_id) {
        case 0x0001: /* AMD */
        case 0x0037: /* AMIC */
            flash_fixup_amd(info, &qry);
            break;
        case 0x001f:
            flash_fixup_atmel(info, &qry);
            break;
        case 0x0020:
            flash_fixup_stm(info, &qry);
            break;
        case 0x00bf: /* SST */
            flash_fixup_sst(info, &qry);
            break;
        case 0x0089: /* Numonyx */
            flash_fixup_num(info, &qry);
            break;
        }

        debug ("manufacturer is %dn", info->vendor);
        debug ("manufacturer id is 0x%xn", info->manufacturer_id);
        debug ("device id is 0x%xn", info->device_id);
        debug ("device id2 is 0x%xn", info->device_id2);
        debug ("cfi version is 0x%04xn", info->cfi_version);

        size_ratio = info->portwidth / info->chipwidth;
        /* if the chip is x8/x16 reduce the ratio by half */
        if ((info->interface == FLASH_CFI_X8X16)
            && (info->chipwidth == FLASH_CFI_BY8)) {
            size_ratio >>= 1;
        }
        debug ("size_ratio %d port %d bits chip %d bitsn",
               size_ratio, info->portwidth << CFI_FLASH_SHIFT_WIDTH,
               info->chipwidth << CFI_FLASH_SHIFT_WIDTH);
        info->size = 1 << qry.dev_size;
        /* multiply the size by the number of chips */
        info->size *= size_ratio;
        max_size = cfi_flash_bank_size(banknum);
        if (max_size && (info->size > max_size)) {
            debug("[truncated from %ldMiB]", info->size >> 20);
            info->size = max_size;
        }
        debug ("found %d erase regionsn", num_erase_regions);
        sect_cnt = 0;
        sector = base;
        for (i = 0; i < num_erase_regions; i++) {
            if (i > NUM_ERASE_REGIONS) {
                printf ("%d erase regions found, only %d usedn",
                    num_erase_regions, NUM_ERASE_REGIONS);
                break;
            }

            tmp = le32_to_cpu(get_unaligned(
                        &(qry.erase_region_info[i])));
            debug("erase region %u: 0x%08lxn", i, tmp);

            erase_region_count = (tmp & 0xffff) + 1;
            tmp >>= 16;
            erase_region_size =
                (tmp & 0xffff) ? ((tmp & 0xffff) * 256) : 128;
            debug ("erase_region_count = %d erase_region_size = %dn",
                erase_region_count, erase_region_size);
            for (j = 0; j < erase_region_count; j++) {
                if (sector - base >= info->size)
                    break;
                if (sect_cnt >= CONFIG_SYS_MAX_FLASH_SECT) {
                    printf("ERROR: too many flash sectorsn");
                    break;
                }
                info->start[sect_cnt] =
                    (ulong)map_physmem(sector,
                               info->portwidth,
                               MAP_NOCACHE);
                sector += (erase_region_size * size_ratio);

                /*
                 * Only read protection status from
                 * supported devices (intel...)
                 */
                switch (info->vendor) {
                case CFI_CMDSET_INTEL_PROG_REGIONS:
                case CFI_CMDSET_INTEL_EXTENDED:
                case CFI_CMDSET_INTEL_STANDARD:
                    /*
                     * Set flash to read-id mode. Otherwise
                     * reading protected status is not
                     * guaranteed.
                     */
                    flash_write_cmd(info, sect_cnt, 0,
                            FLASH_CMD_READ_ID);
                    info->protect[sect_cnt] =
                        flash_isset (info, sect_cnt,
                                 FLASH_OFFSET_PROTECT,
                                 FLASH_STATUS_PROTECT);
                    flash_write_cmd(info, sect_cnt, 0,
                            FLASH_CMD_RESET);
                    break;
                case CFI_CMDSET_AMD_EXTENDED:
                case CFI_CMDSET_AMD_STANDARD:
                    if (!info->legacy_unlock) {
                        /* default: not protected */
                        info->protect[sect_cnt] = 0;
                        break;
                    }

                    /* Read protection (PPB) from sector */
                    flash_write_cmd(info, 0, 0,
                            info->cmd_reset);
                    flash_unlock_seq(info, 0);
                    flash_write_cmd(info, 0,
                            info->addr_unlock1,
                            FLASH_CMD_READ_ID);
                    info->protect[sect_cnt] =
                        flash_isset(
                            info, sect_cnt,
                            FLASH_OFFSET_PROTECT,
                            FLASH_STATUS_PROTECT);
                    break;
                default:
                    /* default: not protected */
                    info->protect[sect_cnt] = 0;
                }

                sect_cnt++;
            }
        }

        info->sector_count = sect_cnt;
        info->buffer_size = 1 << le16_to_cpu(qry.max_buf_write_size);
        tmp = 1 << qry.block_erase_timeout_typ;
        info->erase_blk_tout = tmp *
            (1 << qry.block_erase_timeout_max);
        tmp = (1 << qry.buf_write_timeout_typ) *
            (1 << qry.buf_write_timeout_max);

        /* round up when converting to ms */
        info->buffer_write_tout = (tmp + 999) / 1000;
        tmp = (1 << qry.word_write_timeout_typ) *
            (1 << qry.word_write_timeout_max);
        /* round up when converting to ms */
        info->write_tout = (tmp + 999) / 1000;
        info->flash_id = FLASH_MAN_CFI;
        if ((info->interface == FLASH_CFI_X8X16) &&
            (info->chipwidth == FLASH_CFI_BY8)) {
            /* XXX - Need to test on x8/x16 in parallel. */
            info->portwidth >>= 1;
        }

        flash_write_cmd (info, 0, 0, info->cmd_reset);
    }

    return (info->size);
}

三、 编译下载运行

3.1 编译

make distclean
make smdk2440_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux- V=1

3.2 下载到NOR FALSH

我们再次下载程序,并以NOR方式启动运行,其中串口打印NOR FLASH信息如下:

Flash: fwc addr 00000000 cmd f0 00f0 16bit x 16 bit
fwc addr 0000aaaa cmd aa 00aa 16bit x 16 bit
fwc addr 00005554 cmd 55 0055 16bit x 16 bit
fwc addr 0000aaaa cmd 90 0090 16bit x 16 bit
fwc addr 00000000 cmd f0 00f0 16bit x 16 bit
JEDEC PROBE: ID 1 2249 0
Found JEDEC Flash: AMD AM29LV160DB
unlock address index 1
unlock addresses are 0x555/0x2aa
erase_region_count = 1 erase_region_size = 16384
erase_region_count = 2 erase_region_size = 8192
erase_region_count = 1 erase_region_size = 32768
erase_region_count = 31 erase_region_size = 65536
flash_protect ON: from 0x00000000 to 0x0008931B
protect on 0
protect on 1
protect on 2
protect on 3
protect on 4
protect on 5
protect on 6
protect on 7
protect on 8
protect on 9
protect on 10
protect on 11
flash_protect ON: from 0x00070000 to 0x0007FFFF
protect on 10
2 MiB

3.3 CMD命令

在控制台输入flinfo命令(flash info),就能查看flash的信息了:

SMDK2440 # flinfo

Bank # 1: AMD AM29LV160DB flash (16 x 16)  Size: 2 MB in 35 Sectors
  AMD Legacy command set, Manufacturer ID: 0x01, Device ID: 0x2249
  Erase timeout: 30000 ms, write timeout: 100 ms

  Sector Start Addresses:
  00000000   RO   00004000   RO   00006000   RO   00008000   RO   00010000   RO 
  00020000   RO   00030000   RO   00040000   RO   00050000   RO   00060000   RO 
  00070000   RO   00080000   RO   00090000        000A0000        000B0000      
  000C0000        000D0000        000E0000        000F0000        00100000      
  00110000        00120000        00130000        00140000        00150000      
  00160000        00170000        00180000        00190000        001A0000      
  001B0000        001C0000        001D0000        001E0000        001F0000 

然后通过u-boot命令,检测NOR FLASH的读写是否正确,首先去除写保护:

SMDK2440 # protect off all
Un-Protect Flash Bank # 1

查看Flash内容,这里我们查看NOR FLASH起始地址的数据:

SMDK2440 # md.b 0000
00000000: be 00 00 ea 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5    ................
00000010: 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5    ................
00000020: 60 00 00 00 c0 00 00 00 20 01 00 00 80 01 00 00    `....... .......
00000030: e0 01 00 00 40 02 00 00 a0 02 00 00 ef be ad de    ....@...........

由于我们将u-boot下载到了NOR FLASH中,因此我们直接查看u-boot.dis文件,做一个对比即可:

可以看到数据是一样的,低地址数据在低位,高地址数据在高位,采用的小端存储模式。

四、代码下载

Young / s3c2440_project[u-boot-2016.05-nor-flash】

参考文章

[1]S3C2440移植uboot之支持NORFLASH

[2]uboot支持NORFlash

[3]移植uboot第五步:支持NORFlash

[4]S29AL016D70TFI020

[5]mini2440硬件篇之Nor Flash

内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/zyly/p/15615629.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!

相关课程