4、Flash设备驱动:NOR Flash与NAND Flash的区别、SPI Flash驱动适配、QSPI Flash驱动适配

Flash存储,是嵌入式系统的命根子。U-Boot要引导系统,首先得把代码从Flash里读出来。我这些年调过的Flash驱动少说也有几十种,从老掉牙的NOR Flash到现在的QSPI NAND,踩过的坑能写满一个笔记本。

这一章,咱们就聊聊Flash设备驱动。说白了,就是让U-Boot能正确读写你的Flash芯片。我会把NOR和NAND的区别讲清楚,再手把手带你搞定SPI Flash和QSPI Flash的适配。

核心要点:Flash驱动适配的本质,就是告诉U-Boot三件事——怎么发命令、怎么传地址、怎么读写数据。搞懂这三件事,任何Flash都能搞定。

Flash设备驱动知识体系 Flash设备驱动适配知识体系 Flash设备驱动 NOR vs NAND 区别 SPI Flash 驱动适配 QSPI Flash 驱动适配 接口差异 读写方式 坏块管理 ECC需求 SPI控制器 命令集 时序配置 ID识别 四线模式 DDR模式 频率提升 驱动配置 适配核心:命令 + 地址 + 数据,三要素缺一不可 SPI/QSPI 驱动在 drivers/spi/ 下,Flash 驱动在 drivers/mtd/spi/ 下

4.1 NOR Flash 与 NAND Flash 的区别

很多人刚接触Flash时,搞不清NOR和NAND到底差在哪。我简单给你捋一捋。

NOR Flash,像个老朋友。它支持XIP(片上执行),代码可以直接在Flash里跑,不用拷到RAM。读取速度快,写入和擦除慢。接口是SRAM-like的,地址线和数据线分开,想读哪个地址就读哪个地址。

NAND Flash,像个新潮青年。它密度大、成本低,但读写必须按页来,擦除按块来。不支持XIP,代码得先读到RAM里才能跑。而且NAND天生有坏块,出厂就有,用着用着还会产生新的坏块。

我做个表格,对比起来更清楚:

对比项 NOR Flash NAND Flash
接口方式 SRAM-like,随机访问 I/O复用,命令/地址/数据共用
读取速度 快(ns级随机读) 较慢(us级,但连续读快)
写入速度 慢(ms级) 快(us级页编程)
擦除速度 慢(s级块擦除) 快(ms级块擦除)
XIP支持 支持 不支持
坏块管理 无需 必须
ECC需求 通常不需要 必须(硬件或软件ECC)
典型容量 1MB ~ 128MB 128MB ~ 8GB+
典型用途 Bootloader、代码存储 文件系统、大容量数据

我的经验:在U-Boot里,NOR Flash的驱动相对简单,因为不需要处理坏块和ECC。NAND Flash就麻烦多了,你得在drivers/mtd/nand/下配好控制器驱动,还要注册坏块表和ECC引擎。我个人习惯是先确认芯片手册里有没有内置ECC,有的话优先用硬件ECC,性能好很多。

4.2 SPI Flash 驱动适配

SPI Flash,现在嵌入式设备里用得最多的就是它。体积小、引脚少、价格便宜,U-Boot放它上面再合适不过。

SPI Flash的驱动适配,核心就三步:

  1. 识别Flash芯片——通过读取JEDEC ID来确认型号
  2. 配置SPI控制器——设置时钟频率、极性、相位
  3. 注册MTD设备——让U-Boot的MTD层能操作它

U-Boot里,SPI Flash驱动在 drivers/mtd/spi/ 目录下。核心文件是 sf_probe.c 和 spi_flash.c。我带你看看怎么加一个新芯片。

4.2.1 添加Flash芯片支持

假设你手里有个新芯片,比如叫 XM25QH128A。首先,你得在 drivers/mtd/spi/spi_flash_ids.c 里加上它的信息:

// 在 spi_flash_ids 数组中添加
{
    .name       = "xm25qh128a",
    .id         = {0x20, 0x70, 0x18},
    .id_len     = 3,
    .sector_size= 4 * 1024,
    .pagesize   = 256,
    .size       = 16 * 1024 * 1024,  // 16MB
    .flags      = SPI_FLASH_USE_4K_SECTORS | SPI_FLASH_DUAL_READ,
},

这里要注意几点:

  • id 是JEDEC ID,必须和芯片手册一致。我遇到过芯片手册写错ID的情况,折腾了两天才发现。
  • sector_size 通常是4KB,但有些芯片是64KB。搞错了擦除会失败。
  • flags 里可以开启Dual Read、Quad Read等特性,提升读取速度。

4.2.2 配置SPI控制器

SPI控制器的配置在设备树里完成。以i.MX6为例:

&ecspi1 {
    pinctrl-names = "default";
    pinctrl-0 = &pinctrl_ecspi1;
    cs-gpios = &gpio4 9 GPIO_ACTIVE_LOW;
    status = "okay";

    flash: mx25l12805d@0 {
        compatible = "jedec,spi-nor";
        spi-max-frequency = <40000000>;  // 40MHz
        reg = <0>;
    };
};

注意:spi-max-frequency 别设太高。我见过有人直接写80MHz,结果板子跑起来数据全是错的。先按芯片手册的典型值设,调通了再慢慢往上加。另外,CS引脚的极性也要确认,有些板子是低有效,有些是高有效。

4.2.3 验证驱动是否生效

编译烧录后,在U-Boot命令行里执行:

=> sf probe 0:0
SF: Detected xm25qh128a with page size 256 Bytes, erase size 4 KiB, total 16 MiB
=> sf read 0x81000000 0x0 0x100000
device 0 offset 0x0, size 0x100000
SF: 1048576 bytes @ 0x0 Read: OK

看到 "Detected" 字样,说明芯片识别成功了。如果报 "SF: Unsupported flash type",那就是ID没对上,回去检查 spi_flash_ids.c。

4.3 QSPI Flash 驱动适配

QSPI,说白了就是SPI的升级版。它多了两根数据线(共4根),还能支持DDR模式(双沿采样)。速度比普通SPI快好几倍,现在高端板子基本都用它。

QSPI Flash的适配,比SPI多几个坑:

  • 四线模式配置——需要把控制器切换到Quad模式
  • DDR模式使能——时钟上升沿和下降沿都采样数据
  • 频率匹配——QSPI通常跑在80MHz以上,PCB走线要小心

4.3.1 QSPI控制器初始化

以STM32MP1为例,QSPI控制器的初始化代码在 drivers/spi/stm32_qspi.c 里。核心配置如下:

static int stm32_qspi_set_mode(struct udevice *dev, uint8_t mode)
{
    struct stm32_qspi_priv *priv = dev_get_priv(dev);
    u32 cr;

    cr = readl(priv->base + QSPI_CR);
    cr &= ~(QSPI_CR_SMM | QSPI_CR_FMODE_MASK);

    if (mode & SPI_RX_QUAD) {
        cr |= QSPI_CR_FMODE_QUAD;  // 四线模式
    }

    if (mode & SPI_TX_QUAD) {
        cr |= QSPI_CR_SMM;         // 四线写使能
    }

    writel(cr, priv->base + QSPI_CR);
    return 0;
}

这里有个细节:QSPI的读和写可以分别配置。有些芯片读支持四线,写只支持双线。你得看芯片手册确认。

4.3.2 设备树配置

QSPI的设备树配置比SPI复杂一些。以i.MX8M为例:

&flexspi {
    pinctrl-names = "default";
    pinctrl-0 = &pinctrl_flexspi;
    status = "okay";

    flash0: mt35xu512aba@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;
        spi-max-frequency = <133000000>;  // 133MHz
        spi-tx-bus-width = <4>;           // 四线写
        spi-rx-bus-width = <4>;           // 四线读
        m25p,fast-read;
    };
};

避坑指南:我曾经在QSPI上栽过一个跟头。芯片手册说支持133MHz,设备树也配了133MHz,结果读出来的数据每隔几个字节就错一个。后来用示波器一看,时钟信号都变形了。最后降到104MHz才稳定。所以QSPI的频率,别太迷信手册,实际板子上的走线长度、阻抗匹配都会影响。

4.3.3 驱动适配检查清单

我整理了一个检查清单,每次适配QSPI Flash时我都会过一遍:

检查项 说明 常见问题
JEDEC ID 确认芯片ID与驱动匹配 ID写错或芯片有多个ID
四线模式 控制器和芯片都要支持 只配了控制器没配芯片
DDR模式 双沿采样配置 时序不满足导致数据错位
频率 从低到高逐步调试 信号完整性差导致误码
Dummy Cycle 高速读时需要插入等待周期 设少了读不到数据
写保护 确认WP引脚电平 WP拉低导致写操作失败

4.4 实战:从零适配一颗SPI Flash

说了这么多理论,咱们来点实际的。假设你要在某个新板子上适配一颗 W25Q256JV(32MB SPI Flash),该怎么做?

第一步:查芯片手册

找到JEDEC ID:0xEF 0x70 0x19。确认页大小256字节,扇区大小4KB,支持Dual/Quad Read。

第二步:添加芯片信息

在 spi_flash_ids.c 里加上:

{
    .name       = "w25q256jv",
    .id         = {0xef, 0x70, 0x19},
    .id_len     = 3,
    .sector_size= 4 * 1024,
    .pagesize   = 256,
    .size       = 32 * 1024 * 1024,
    .flags      = SPI_FLASH_USE_4K_SECTORS | SPI_FLASH_DUAL_READ | SPI_FLASH_QUAD_READ,
},

第三步:配置设备树

在板级dts里加上SPI Flash节点,时钟先设20MHz,确保能跑通。

第四步:编译测试

执行 make menuconfig,确保选中 SPI_FLASH 和 SPI_FLASH_WINBOND。编译烧录后,用 sf probe 验证。

我的习惯:每次加新芯片,我都会先在 sf probe 成功后,执行 sf erase 0 0x10000 和 sf write 0x81000000 0 0x10000 做读写测试。如果擦除后读出来全是0xFF,写进去再读出来数据一致,那就基本稳了。

4.5 常见问题与调试技巧

Flash驱动调试,说难不难,说简单也不简单。我总结几个高频问题:

  • sf probe 找不到芯片——先量SPI时钟引脚有没有波形,再确认CS引脚电平对不对。我遇到过CS接反了,一直拉高,芯片根本没被选中。
  • 读出来的数据全是0xFF——可能是写保护没关。执行 sf protect off 0 0x1000000 试试。
  • 写进去读出来不一致——频率太高了,降频试试。或者检查Dummy Cycle配置。
  • QSPI模式下读数据错位——确认四线模式的命令是否正确。有些芯片的Quad Read命令是0xEB,不是0x6B。

重要提醒:调试Flash驱动时,千万别在关键数据区反复擦写。我曾经手滑把U-Boot所在的扇区擦掉了,板子直接变砖,最后只能拆Flash用编程器重烧。建议先用空扇区做测试。