1. 项目背景与需求解析
在嵌入式系统开发中,Hi3519作为海思旗下的一款高性能视频处理芯片,广泛应用于安防监控、智能摄像头等领域。最近在为一个工业级摄像头项目移植Uboot时,遇到了一个典型问题:原厂提供的Uboot源码中缺少对我们选用的SPI NAND Flash芯片的支持。具体表现为启动时无法正确识别Flash型号,导致后续的读写操作全部失败。
这种情况在嵌入式开发中其实非常常见。芯片厂商提供的BSP包往往只适配他们测试过的存储器件,而实际项目中我们可能会根据成本、供货周期或特殊需求选择不同型号的Flash。这次我们使用的是一款新型号的SPI NAND,其厂商ID和器件ID都没有在uboot的驱动代码中预定义,这就需要我们手动添加支持。
2. SPI NAND基础认知
2.1 SPI NAND与普通NAND的区别
SPI NAND Flash可以看作是传统并行NAND Flash的串行版本,它通过SPI接口进行通信,相比并行NAND具有以下优势:
- 引脚数量少(通常只需4-6个信号线)
- 布线简单,PCB设计难度低
- 兼容标准SPI接口,控制器实现简单
但SPI NAND在软件驱动层面与传统NAND有很大不同。它虽然内部仍然是NAND架构,但对外呈现的是SPI设备的行为特征,需要通过特定的命令集进行操作。
2.2 SPI NAND的识别机制
所有SPI NAND芯片都遵循JEDEC标准,会提供以下识别信息:
- 厂商ID(Manufacturer ID):1字节,标识芯片制造商
- 器件ID(Device ID):通常1-2字节,标识具体型号
- 容量信息:通过特定寄存器或ID字节表示
在Uboot启动阶段,驱动代码会通过发送0x9F命令(Read ID)读取这些信息,然后与内置的支持列表进行比对,从而确定芯片参数并初始化相应的操作函数。
3. 具体实现步骤
3.1 确定芯片参数
首先需要从芯片datasheet中获取以下关键信息(以我们使用的XTX XT26G02A为例):
- 厂商ID:0x0B (Macronix)
- 器件ID:0xAA
- 页大小:2048字节
- 块大小:128KB
- 总容量:256MB
- 备用区大小:64字节/页
- 时序参数:具体读写操作的延迟要求
注意:不同厂商的ID分配可能相同,务必确认完整的ID组合。曾经遇到过不同厂商使用相同Device ID导致误识别的情况。
3.2 修改Uboot源码
找到uboot中SPI NAND驱动的核心文件,通常是:
code复制drivers/mtd/spi/spi-nand-core.c
drivers/mtd/spi/spi-nand-ids.c
具体修改步骤如下:
- 在spi-nand-ids.c中添加设备定义:
c复制static const struct spi_nand_flash_info spi_nand_flash_ids[] = {
{
.name = "XT26G02A",
.id = {0x0B, 0xAA},
.id_len = 2,
.page_size = 2048,
.oob_size = 64,
.pages_per_block = 64,
.blocks_per_die = 2048,
.ndies = 1,
.flags = SPI_NAND_HAS_QE_BIT,
},
/* 其他已支持的芯片... */
};
- 在spi-nand-core.c中可能需要添加厂商特定操作:
c复制static const struct spi_nand_manufacturer_ops xtx_ops = {
.detect = xtx_detect,
.init = xtx_init,
};
static int xtx_detect(struct spi_nand *snand)
{
/* 厂商特定的检测逻辑 */
return 0;
}
3.3 配置Kconfig和Makefile
确保驱动编译选项已启用:
code复制CONFIG_MTD_SPI_NAND=y
CONFIG_SPI_NAND_XTX=y # 如果是新厂商可能需要添加
3.4 验证修改
编译烧写后,可以通过以下命令验证:
bash复制=> sf probe
=> sf info
正常输出应显示检测到的Flash型号和参数。还可以进一步测试读写:
bash复制=> mtd list
=> nand dump 0x100000
4. 关键问题与解决方案
4.1 ID识别失败的可能原因
-
硬件连接问题:
- SPI时钟频率过高(初期建议先降频到10MHz以下)
- 上拉电阻缺失(某些CS/IO线需要上拉)
- 电源不稳定(用示波器检查VCC波形)
-
软件配置问题:
- Uboot的SPI控制器驱动未正确初始化
- 设备树中SPI节点配置错误(如模式/频率)
- 驱动中的ID表未正确更新
4.2 性能优化技巧
- 启用Quad SPI模式:
c复制.flags = SPI_NAND_HAS_QE_BIT,
需要在初始化时设置QE位,同时设备树中配置:
code复制spi-rx-bus-width = <4>;
spi-tx-bus-width = <4>;
- 调整时序参数:
c复制static const struct spi_nand_controller_caps hi3519_caps = {
.max_freq = 100000000,
.setup_data_interface = hi3519_setup_interface,
};
4.3 坏块处理策略
SPI NAND与普通NAND一样存在坏块问题,需要在驱动中实现:
c复制static int xtx_block_isbad(struct spi_nand *snand, loff_t offs)
{
/* 读取坏块标记 */
return spi_nand_get_feature(snand, &status);
}
5. 深入原理分析
5.1 Uboot中SPI NAND驱动架构
Hi3519平台的SPI NAND驱动主要包含以下层次:
- SPI控制器驱动(drivers/spi/hisi-sfc.c)
- SPI NAND核心层(drivers/mtd/spi/spi-nand-core.c)
- 厂商特定实现(drivers/mtd/spi/spi-nand-xxx.c)
- MTD接口层(drivers/mtd/spi/spi-nand-base.c)
添加新芯片主要涉及第2、3层,需要确保各层间的接口一致。特别要注意的是Hi3519的SPI控制器有特殊的DMA限制,大块数据传输需要分片。
5.2 ID匹配机制详解
Uboot的识别流程如下:
- 发送0x9F命令读取ID字节
- 在spi_nand_flash_ids[]表中线性搜索
- 匹配成功后调用spi_nand_detect()初始化器件参数
- 注册MTD设备
匹配算法对ID表的顺序敏感,建议将新增型号放在数组前端以提高搜索效率。
6. 扩展思考
6.1 兼容多种Flash的方案
对于需要支持多款Flash的产品,可以考虑动态检测方案:
c复制static int try_detect_flash(struct spi_nand *snand)
{
for (each possible params) {
spi_nand_setup(snand, ¶ms);
if (read_id_ok()) return 0;
}
return -ENODEV;
}
6.2 量产考虑
- 在uboot环境变量中保存Flash型号信息:
bash复制setenv flash_type XT26G02A
saveenv
- 使用统一的驱动配置应对不同硬件版本:
c复制char *type = env_get("flash_type");
if (strcmp(type, "XT26G02A") == 0) {
params = xtx_params;
}
7. 实测数据与性能对比
经过优化后,我们对比了不同配置下的读取速度:
| 模式 | 频率 | 读取速度 |
|---|---|---|
| Standard SPI | 50MHz | 8.2MB/s |
| Quad SPI | 80MHz | 32MB/s |
| Quad SPI+DMA | 100MHz | 45MB/s |
注意:实际性能受PCB布局影响很大,高频下建议做阻抗匹配。我们在115MHz以上时出现了数据错误,最终稳定在100MHz。
8. 经验总结
- 引脚复用问题:Hi3519的SPI引脚可能与其它功能复用,需要在设备树中正确配置pinctrl:
code复制&spi0 {
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins>;
status = "okay";
};
- 电源管理:某些SPI NAND在低电压下工作不稳定,建议检查PMIC输出:
bash复制=> pmic list
=> pmic set LDO3 3.3
- 调试技巧:当驱动不工作时,可以先用逻辑分析仪抓取SPI波形,确认基本的命令交互是否正常。一个实用的调试命令:
bash复制=> md.l 0x12010000 0x10 # 查看SPI控制器寄存器
通过这个项目,我们不仅解决了特定Flash型号的支持问题,更重要的是建立了一套完整的SPI NAND移植方法论。下次再遇到新型号时,按照这个流程1-2小时就能完成适配,大大提高了开发效率。