本文约3800字,今天基于君正 A1 平台新增了国产品牌东芯的DS35Q1GB 和 DS35M1GB两款型号Flash,我们只需要中一款,我写代码时将datasheet中的两款型号一并支持进去了,本文完整记录 SPI NAND Flash 适配的全过程,涵盖 SPL/U-Boot 和 Linux 内核两个阶段,并总结出一套可复用的方法。
关注公众号, 即可获得与Linux相关的电子书籍(含《硬件架构的艺术》)以及常用开发工具,文末有文档清单,本公众号提供的电子书均只可作为个人学习使用,不可用做商业用途。
在嵌入式产品生命周期中,由于以下原因经常需要更换 Flash 型号:
数据手册是适配工作的唯一权威依据。需要从手册中提取以下关键参数(以 DS35Q1GB 为例):
| 身份标识 | |||
| 存储结构 | |||
| ECC 特性 | |||
| 时序参数 | |||
| 0xF1 | |||
| 128 字节 | |||
| 3 位(bit6:4) | 最关键差异 | ||
| 8-bit |
⚠️ 特别注意:即使是同一厂商的不同型号,ECC 状态寄存器定义也可能不同。0x71 只用 2 位,0xF1 用 3 位——这是适配过程中最容易踩的坑。
├── tools/ingenic-tools/│ ├── nand_device/│ │ ├── dosilicon_nand.c ← 【修改点 1】厂商参数定义│ │ ├── winbond_nand.c│ │ └── ...│ ├── sfc_nand_params.c ← 参数生成工具源码│ └── Makefile│├── include/generated/│ └── sfc_nand_params.h ← 【DO NOT MODIFY】自动生成│└── common/spl/ └── spl_sfc_nand.c ← SPL NAND 加载逻辑nand_device/*_nand.c ↓ (编译工具)sfc_nand_params 可执行文件 ↓ (执行生成)include/generated/sfc_nand_params.h ← 被 SPL/U-Boot 包含⚠️ 关键原则:generated/ 目录下的文件禁止手动修改,会被 make clean 覆盖。所有修改必须在源文件 nand_device/*_nand.c 中进行。
tools/ingenic-tools/nand_device/dosilicon_nand.cStep 1:定义 ECC 错误状态数组
/* 原有型号 ECC 状态 */staticunsignedchar ds_x1ga[] = {0x2};/* 新增型号 ECC 状态(同样是检测 0x2 为不可纠正错误) */staticunsignedchar ds_x1gb_33v[] = {0x2}; // 3.3V 版本staticunsignedchar ds_x1gb_18v[] = {0x2}; // 1.8V 版本Step 2:在 device[] 数组中添加新条目
staticstructdevice_structdevice[] = {/* 原有型号 - 保持不变 */ DEVICE_STRUCT(0x71, 2048, 2, 4, 2, 1, ds_x1ga),/* ===== 新增 DS35Q1GB ===== */ DEVICE_STRUCT(0xF1, // device_id(来自数据手册 Table 3.2)2048, // pagesize(2KB)2, // addr_len(列地址 12 位)4, // ecc_bit(从 bit4 开始)3, // bit_counts(ECC_S[2:0] 占 3 位)← 关键!1, // eccstat_count(检测 1 个错误状态) ds_x1gb_33v // eccerrstatus({0x2} = 不可纠正错误) ),/* ===== 新增 DS35M1GB ===== */ DEVICE_STRUCT(0xA1, 2048, 2, 4, 3, 1, ds_x1gb_18v),};Step 3:重新编译
# 重新生成参数工具make tools/ingenic-tools/sfc_nand_params# 全量编译make cleanmake烧录后串口输出应显示:
SFC_NAND: ****sfc init!Note: nand manufactory name [DS35Q1GB] ← 识别成功!Board: ISVP (Ingenic xBurst A1 Soc)DRAM: 512 MiBNAND: 0 MiB如果看到 ERR: don't support this kind of nand device,说明参数表中没有匹配的 ID,需要检查 device_id 是否正确。
drivers/mtd/nand/spi/ingenic/dosilicon_nand.cdrivers/mtd/nand/spi/ingenic/dosilicon_nand.cStep 1:在参数数组中添加新条目
staticstructingenic_sfcnand_base_paramdosilicon_param[] = {/* 原有型号保持不动 */ [0] = { /* DS35X1GAXXX */ }, [1] = { /* DS35Q2GAXXX */ },/* ===== 新增 DS35Q1GB ===== */ [2] = { .pagesize = 2 * 1024, .blocksize = 2 * 1024 * 64, .oobsize = 128, /* ← 注意:新款是 128 字节 */ .flashsize = 2 * 1024 * 64 * 1024, /* 1Gbit */ .tHOLD = THOLD, .tSETUP = TSETUP, .tSHSL_R = TSHSL_R, .tSHSL_W = TSHSL_W, .tRD = 90, /* 数据手册 Table 4.6 */ .tPP = TPP, .tBE = TBE, .plane_select = 0, /* 1Gbit 单 Plane */ .ecc_max = 0x8, /* 8-bit ECC */ .need_quad = 1, /* 支持 Quad SPI */ },/* ===== 新增 DS35M1GB ===== */ [3] = { .pagesize = 2 * 1024, .blocksize = 2 * 1024 * 64, .oobsize = 128, .flashsize = 2 * 1024 * 64 * 1024, .tHOLD = THOLD, .tSETUP = TSETUP, .tSHSL_R = TSHSL_R, .tSHSL_W = TSHSL_W, .tRD = 90, .tPP = TPP, .tBE = TBE, .plane_select = 0, .ecc_max = 0x8, .need_quad = 1, },};Step 2:在设备 ID 列表中添加匹配
staticstructdevice_id_structdevice_id[] = { DEVICE_ID_STRUCT(0x71, "DS35X1GAXXX", &dosilicon_param[0]), DEVICE_ID_STRUCT(0x72, "DS35Q2GAXXX", &dosilicon_param[1]), DEVICE_ID_STRUCT(0xF1, "DS35Q1GB", &dosilicon_param[2]), /* 新增 */ DEVICE_ID_STRUCT(0xA1, "DS35M1GB", &dosilicon_param[3]), /* 新增 */};Step 3:更新 CDT 参数获取函数
staticcdt_params_t *dosilicon_get_cdt_params(struct sfc_flash *flash, uint8_t device_id){ CDT_PARAMS_INIT(dosilicon_nand->cdt_params);switch(device_id) {case0x71:case0x72:case0xF1: /* 新增 */case0xA1: /* 新增 */break;default: dev_err(flash->dev, "device_id err: 0x%02x\n", device_id);returnNULL; }return &dosilicon_nand->cdt_params;}Step 4:更新 ECC 状态处理函数(关键!)
staticinlineintdeal_ecc_status(struct sfc_flash *flash, uint8_t device_id, uint8_t ecc_status){int ret = 0;switch(device_id) {/* 旧型号 0x71:ECC 状态只用 2 位 */case0x71:switch((ecc_status >> 0x4) & 0x3) {case0x2: dev_err(flash->dev, "ECC >8 bits error, uncorrectable.\n"); ret = -EBADMSG;break;default: ret = 0; }break;/* * 新型号 0x72/0xF1/0xA1:ECC 状态用 3 位(ECC_S[2:0]) * 根据数据手册 Table 3.6 */case0x72:case0xF1: /* 新增 */case0xA1: /* 新增 */switch((ecc_status >> 0x4) & 0x7) {case0x0: /* 0 error */ ret = 0;break;case0x1: /* 1-3 bits corrected */case0x3: /* 4-6 bits corrected */case0x5: /* 7-8 bits corrected */ ret = 0;break;case0x2: /* >8 bits uncorrectable */ dev_err(flash->dev, "ECC >8 bits error, uncorrectable.\n"); ret = -EBADMSG;break;default: /* Reserved */ dev_err(flash->dev, "Unknown ECC status: 0x%x\n", (ecc_status >> 0x4) & 0x7); ret = -EIO;break; }break;default: dev_err(flash->dev, "device_id err: 0x%02x\n", device_id); ret = -EIO; }return ret;}内核启动日志应显示:
[ 0.000000] SFC_NAND: ****sfc init![ 0.000000] Note: nand manufactory name [DS35Q1GB][ 0.000000] Creating 1 MTD partitions on "spi-nand0":[ 0.000000] 0x000000000000-0x000001000000 : "spi_nand"graph TD A[获取数据手册] --> B[提取关键参数] B --> C[对比新旧型号差异] C --> D{差异点} D -->|Device ID| E[修改 ID 匹配表] D -->|OOB Size| F[更新备用区大小] D -->|ECC 位宽| G[更新 ECC 解析逻辑] D -->|时序参数| H[更新 tRD/tPP/tBE] E --> I[修改 SPL/U-Boot 工具数据源] F --> I G --> I H --> I I --> J[重新编译工具和固件] J --> K[烧录验证 SPL/U-Boot] K --> L[修改 Linux 内核驱动] L --> M[重新编译内核] M --> N[烧录验证内核] N --> O[功能测试]id_manufactory | id_manufactory | ||
device_id | device_id | ||
pagesize | .pagesize | ||
oobsize | .oobsize | ||
bit_counts | deal_ecc_status() | 最容易出错 | |
ecc_max | .ecc_max | ||
.tRD |
ERR: don't support this kind of nand device | device_id[] 中添加匹配 | |
bit_counts | ||
oobsize | ||
need_quad 和初始化代码 |
| SPL 工具数据源 | tools/ingenic-tools/nand_device/*_nand.c | |
| SPL 参数生成工具 | tools/ingenic-tools/sfc_nand_params.c | |
| 生成的头文件 | include/generated/sfc_nand_params.h | |
| SPL 主逻辑 | common/spl/spl_sfc_nand.c | |
| 内核驱动 | drivers/mtd/nand/spi/ingenic/*_nand.c | |
| SFC 控制器驱动 | drivers/mtd/devices/jz_sfc_v1/*nand.c | |
| 板级配置 | drivers/mtd/nand/*_nand.c |
永远不要修改 generated/ 目录下的文件
make clean 后会被覆盖数据手册是唯一权威
新旧型号对比要全面
SPL/U-Boot 和内核的参数必须一致
误以为可以直接修改 sfc_nand_params.h
忽略了 bit_counts 参数
混淆了 addr_len 的含义
内核和 SPL 的修改不同步
# 烧录后观察串口输出# 预期看到:Note: nand manufactory name [DS35Q1GB]# 查看 MTD 分区cat /proc/mtd# 测试读写flash_erase /dev/mtd0 0 0dd if=/dev/urandom of=/tmp/test.bin bs=1M count=1flashcp /tmp/test.bin /dev/mtd0cmp /tmp/test.bin /tmp/read.bin# 查看内核日志dmesg | grep -i nanddmesg | grep -i ecc本文以 DS35Q1GA → DS35Q1GB 升级为例,完整记录了 SPI NAND Flash 适配的全链路过程:
这套方法论可以复用到其他 SPI NAND Flash 型号的适配中。关键在于:

15年+嵌入式软件开发经验兼二胎宝妈
分享读书心得、工作经验,自我成长和生活方式。
希望我的文字能对你有所帮助