从零实战:U-Boot SPI NOR Flash驱动移植全流程解析
拿到新开发板时,最令人头疼的莫过于发现原有的固件无法识别板载Flash。上周我就遇到了这样的场景——当我把为W25Q128编译的U-Boot镜像烧录到搭载XT25F128B的验证板时,熟悉的启动日志没有出现,取而代之的是冰冷的报错:unrecognized JEDEC id bytes: 0b, 40, 18。这种情况在嵌入式开发中并不罕见,但解决过程往往让新手感到迷茫。本文将完整还原从错误分析到代码修改的全过程,不仅告诉你"怎么做",更解释"为什么这么做"。
1. 问题定位与JEDEC ID解析
当U-Boot启动时遇到Flash识别错误,第一步永远是仔细阅读报错信息。unrecognized JEDEC id表明系统检测到了Flash芯片,但无法在支持的设备列表中找到匹配项。这里的0b, 40, 18就是关键线索——它们构成了XT25F128B的身份证号码。
JEDEC ID的组成结构:
- 第1字节
0x0B:制造商ID(XTX) - 第2字节
0x40:设备类型 - 第3字节
0x18:容量标识
在Linux内核源码中,这个三元组通常会被合并成一个32位整数表示(小端序):
#define XT25F128B_JEDEC_ID 0x18400b提示:不同厂商的ID分配可在JEDEC官网查询,但实际开发中直接查阅芯片手册更为高效。
2. U-Boot SPI Flash驱动架构剖析
U-Boot的SPI NOR驱动采用经典的"表驱动"设计,所有支持的Flash信息都集中存储在spi_flash_ids[]数组中。这个结构体数组定义在drivers/mtd/spi/spi_flash_ids.c中,每个条目包含以下关键信息:
| 字段 | 说明 | 示例值 |
|---|---|---|
| .id | JEDEC ID数组 | {0x0b, 0x40, 0x18} |
| .id_len | ID字节数 | 3 |
| .sector_size | 擦除扇区大小 | 4096 |
| .n_sectors | 扇区数量 | 4096 |
| .page_size | 编程页大小 | 256 |
驱动初始化时,会通过SPI命令0x9F读取芯片的实际ID,然后遍历这个表进行匹配。我们的任务就是为XT25F128B添加正确的表项。
3. 实战代码修改步骤
找到spi_flash_ids.c文件后,我们需要在合适的位置添加新条目。观察现有代码会发现类似W25Q系列的条目:
INFO("w25q128fw", 0xef4018, 0, 64 * 1024, 256, SPI_FLASH_QUAD_READ),按照这个模板,XT25F128B的添加方式如下:
- 在文件头部添加宏定义(如果尚未存在):
#define SPI_FLASH_XTX_QUAD_READ (SPI_FLASH_QUAD_READ | SPI_FLASH_4B_OPCODES)- 在设备列表中添加新条目:
INFO("xt25f128b", 0x0b4018, 0, 64 * 1024, 256, SPI_FLASH_XTX_QUAD_READ),注意:有些Flash需要额外的2字节扩展ID,此时需要使用
INFO6宏而非INFO。
4. 编译验证与调试技巧
完成代码修改后,按标准流程重新编译U-Boot:
make clean make menuconfig # 确认SPI驱动配置 make -j$(nproc)烧录后观察启动日志,成功识别的输出应类似:
SF: Detected xt25f128b with page size 256 Bytes, sector size 64 KiB, total size 16 MiB如果仍然报错,可以尝试以下调试手段:
- 逻辑分析仪抓取SPI波形:确认实际读出的ID是否正确
- 调整驱动flags:有些芯片需要特殊的读写时序
- 检查硬件连接:特别是WP#和HOLD#引脚的处理
5. 设备树同步与系统集成
U-Boot能识别Flash只是第一步,完整的支持还需要考虑:
- 内核设备树同步:
&spi0 { flash@0 { compatible = "xtx,xt25f128b"; reg = <0>; spi-max-frequency = <50000000>; }; };- 分区表配置:
static struct mtd_partition flash_parts[] = { { .name = "uboot", .offset = 0, .size = 0x100000, }, /* 其他分区... */ };- 擦除/编程参数优化:
CONFIG_SF_DEFAULT_SPEED=50000000 CONFIG_SF_DEFAULT_MODE=0x3 # Quad I/O模式6. 进阶:Flash特性深度适配
不同Flash芯片的细微差异可能导致性能或稳定性问题。以XT25F128B为例,需要特别注意:
- 四线模式使能:需要发送特定写使能命令
- 状态寄存器保护:避免意外修改配置位
- 睡眠/唤醒时序:低功耗设计时的特殊处理
一个完整的Quad Enable实现示例:
static int xt25f128b_quad_enable(struct spi_flash *flash) { u8 sr; int ret; ret = spi_flash_cmd_read_status(flash, &sr); if (ret) return ret; if (!(sr & SR_QUAD_ENABLE)) { sr |= SR_QUAD_ENABLE; ret = spi_flash_cmd_write_status(flash, sr); } return ret; }7. 常见问题排查指南
遇到问题时可参考这个排查矩阵:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 读取ID全FF | 硬件未就绪 | 检查复位电路和供电 |
| 识别容量错误 | ID匹配不完整 | 确认所有ID字节正确 |
| 擦除失败 | 写保护未解除 | 检查WP#引脚电平 |
| 读写异常 | 时序不匹配 | 降低SPI时钟频率 |
在实际项目中,我遇到过最棘手的情况是一个国产Flash需要特殊的写使能序列。后来发现必须在发送0x06命令前先读取一次状态寄存器,这种特殊要求往往只能通过反复试验和仔细阅读数据手册才能发现。