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都能搞定。
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的驱动适配,核心就三步:
- 识别Flash芯片——通过读取JEDEC ID来确认型号
- 配置SPI控制器——设置时钟频率、极性、相位
- 注册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用编程器重烧。建议先用空扇区做测试。