只读分享解决方案文档原理方案设计📅 2026-10-05🔖 V1.0⏳ 26天有效(至 2026-11-05)
🔧解决方案&应用市场分析 -> 原理方案设计 -> 嵌入式驱动&系统开发 -> Linux_SPI_NOR_驱动开发指南

Linux_SPI_NOR_驱动开发指南

生成时间 2026-10-04 11:49:38 有效期至 2026-11-05 15:32:34(26天有效(至 2026-11-05))
同系列 · 原理方案设计

Linux 系统(SPI NOR)驱动开发指南

编制日期 2026-10-05

面向 XTX XT25Q512F / XT25F512B / XT55Q1G / XT25Q256F 等串行 NOR 器件,覆盖 开发步骤 → 内核配置 → 驱动架构 → 设备树与分区 → 大容量 4 字节地址(>128Mbit)→ 新器件适配 → 测试 → 调试排障 → 性能优化 → 可靠性 → 量产运维 的完整链路。调试章节按 P0~P5 优先级 + 故障树 组织,每一条给出现象 → 判据 → 根因 → 修法; 大容量章节给出 4 字节地址的三种实现方案与可直接复制的 DTS / 源码改动。

文档编号LINUX-SPINOR-DRV-001
版本 / 状态V1.0 · 正式发布
适用器件XTX XT25Q512F / XT25F512B / XT55Q1G / XT25W1G / XT25Q256F / XT25F256B 等 SPI NOR 系列(16Mbit~2Gbit,3.3V 与 1.8V 电压档);文中方法同样适用于 Winbond / GigaDevice / Macronix / Micron / ST / Spansion 等品牌器件
内核版本主线 Linux 5.10 / 5.15 / 6.1 / 6.6 / 6.12 为主(drivers/mtd/spi-nor/ 原生 spi-nor 框架 + spi-mem);4.19~5.4 的差异见 0.3 节
硬件平台ARM/ARM64 SoC(SPI 控制器走 spi-mem 框架)+ 板载 SPI NOR;示例以 3.3V 512Mbit 器件、104MHz Quad 模式为基准
配套文档《SPI_NOR_Flash_电路设计指南.html》(硬件 / 原理图 / 布线)、《SPI_NOR_Flash_软件设计规范.html》(命令 / 寄存器 / 保护机制协议层)

0.1 编写目的与读者对象

SPI NOR 在 Linux 下几乎不需要从零写驱动——主线内核已有完整的 spi-mem + spi-nor 框架、SFDP 自动探测与厂商器件表。真正的工作量集中在: 写对设备树、打开 4 字节地址模式把容量用满、适配新器件、把 JFFS2/UBI 跑通、 以及量产阶段的可靠性与运维。本文把这些工作按可执行的顺序固化下来,并把调试经验系统化。

  • 驱动工程师:按第 3~7 章完成配置、DTS、大容量适配与新器件支持;按第 10 章定位问题。
  • 硬件工程师:重点读第 2 章(WP#/HOLD#/RESET#、电压域、测试点)与第 6.10 节(容量只读到一半的硬件侧原因)。
  • 测试工程师:按第 9 章执行功能 / 大容量专项 / 压力 / 性能测试,按第 14.2 节交付清单验收。
  • 量产与运维:按第 12 章做可靠性设计,按第 15 章做日志采集、寿命监控与 A/B 分区 OTA。
不在本文范围:SPI NAND 驱动开发(见同目录《Linux_SPI_NAND_驱动开发指南.html》); eMMC / SDIO 驱动;裸机与 RTOS 下的 NOR 驱动实现;JESD216 SFDP 标准的逐字段规范解读。
本文贯穿的四条铁律

① SPI NOR 可以 XIP,但 Linux 下基本不用:内核把它当 MTD 设备管理;SoC 启动阶段由 bootloader 决定是否 XIP。
② >128Mbit 必须显式处理 4 字节地址:3 字节地址只能覆盖 16MiB。容量上到 256Mbit 就必须靠 4B opcode 或 EN4B 命令,配置错了「容量只认一半」甚至「读不到」(第 6 章)。
③ 分区表必须在 U-Boot、设备树、内核命令行三处一致——量产阶段最高频的故障源(见 5.3)。
④ 写保护三道闸:硬件 WP# 引脚、器件 BP 位、驱动层 ro 挂载。只查一处就会漏(见 12.2)。

一句话结论:SPI NOR 在 Linux 里的「驱动开发」,本质是 把器件能力(JEDEC ID / 容量 / 擦除粒度 / 地址宽度 / 读协议 / 保护机制)正确喂给 spi-nor 框架, 再把 MTD → 文件系统这条链路打通;80% 的工作量在设备树、4 字节地址、分区表、量产烧录与可靠性验证上, 而不是在 drivers/mtd/spi-nor/ 里写新代码。

0.2 术语与缩略语

下表是本文(以及内核 MTD 邮件列表、datasheet)里高频出现的词。驱动调试时大量日志来自这些子系统, 先建立词与层级的对应关系,能省掉一半读日志的时间。

缩写 / 术语中文所在层级含义与要点
MTD存储技术设备内核 subsystemLinux 对裸 Flash 的统一抽象层,向上提供字符设备 /dev/mtdX 与只读块设备 /dev/mtdblockX,向下接 raw nand / spinand / nor。
spi-memSPI 存储器抽象drivers/spi把 Flash 操作抽象成「命令-地址-dummy-数据」四段式 spi_mem_op,屏蔽单/双/四线差异。spi-nor 框架完全依赖它。
spi-norSPI NOR 框架drivers/mtd/spi-nor内核 SPI NOR 驱动框架,模块名 spi_nor(CONFIG_MTD_SPI_NOR)。注意 6.6 起文件名从 spi-nor.c 改为 core.c。
SFDP串行 Flash 可发现参数JESD216 规范器件内部只读参数表,描述容量、擦除类型、读协议、4 字节地址能力等。读命令 0x5A(RSFDP)。
BFPT基本闪存参数表SFDP 子表SFDP 的第一张子表,DW16 的 bit31:29 就是 4 字节地址模式支持位(见 6.4)。
JEDEC ID器件身份码读命令 0x9F3 或 5 字节:厂商 ID + 器件类型 + 容量码。容量码按 2 的幂次编码(0x18=128Mbit,0x19=256Mbit,0x20=512Mbit…)。
4 字节地址4-Byte Addressing器件命令层用 4 个字节传地址,突破 3 字节地址 16MiB 的天花板。三种实现见 6.2。
4B opcode专用四字节命令协议变体一组独立 opcode 实现 4 字节地址(0x13 / 0x03 / 0x12 / 0x21 / 0xDC…)。无状态,CPU 复位后自动回到 3 字节,最省心。
EN4B / EX4B进入 / 退出四字节模式命令 0xB7 / 0xE9Winbond / Macronix / GigaDevice 风格:发一条命令切换地址宽度。有状态,复位后失效。
BRWR / bank regbank 寄存器命令 0x17(Spansion 风格)写 1 字节到 bank 寄存器,bit7 使能 4 字节模式、bit6:0 给 A[30:24]。有状态。
addr_nbytes地址字节数内核 struct spi_nor当前实际发出的地址字节数。容量 >16MiB 且无 4B opcode 时内核自动置 4。
SNOR_F_4B_OPCODES支持专用 4B 命令ID 表 flags置位后内核把读/写/擦 opcode 换成 4 字节版本(SNOR_F_4B_OPCODES = BIT(3))。
SNOR_F_HAS_4BAIT一次性可编程 4BID 表 flags器件通过一次性熔丝固化 4 字节模式,内核不再替换 opcode(SNOR_F_HAS_4BAIT = BIT(4))。
hwcaps硬件能力位内核能力协商控制器与器件能力的交集决定最终用哪种读协议(1-1-1 / 1-1-4 / 1-4-4 / 1-8-8…)。
dummy cycles空周期读命令时序读命令地址之后、数据之前的等待周期数。配错直接读出垃圾。
BP 位块保护位SR1 / SR2器件内部的写保护位,flash_erase / flashcp 报 EIO 十有八九是它。
DTR双沿传输读协议变体Double Transfer Rate,时钟双边沿都传输数据,理论带宽翻倍(如 0x0B / 0xEE)。
JFFS2日志型 Flash 文件系统fs/jffs2NOR 上最常见的可写文件系统,带磨损均衡与掉电保护。
UBI / UBIFS无序块镜像 / 文件系统drivers/mtd/ubiUBI 做卷管理与磨损均衡,UBIFS 是其上的文件系统。NOR 上也可用。
XIP片内执行boot 阶段把 Flash 映射到内存地址空间直接执行。SPI NOR 支持 XIP,但需 bootloader 配合。
mtdparts命令行分区内核 cmdline用内核命令行指定分区表的老方式,与设备树 fixed-partitions 二选一。
最容易混淆的四组概念

① /dev/mtdX ≠ /dev/mtdblockX:前者是字符设备(正确用法),后者是无坏块管理的「假块设备」,只能挂只读或 JFFS2,不能挂 ext4。
② 容量「Mb」与「MiB」: datasheet 标 512Mbit = 64MiB,不是 512MB。分区表算错会直接导致分区越界。
③ 4 字节地址 ≠ 4 字节数据:地址字节数增加后,读命令的 dummy 周期与时序可能变,严格按 datasheet 的 4B 时序表核对。
③ JEDEC ID 读不到 ≠ 器件坏了:器件处于 4 字节模式时,0x9F 后面要多发字节才能读对(见 6.7)。

0.3 内核版本与接口差异

spi-nor 子系统在 4.x → 5.x → 6.x 之间做过多次重构,照抄旧教程会直接编译不过或行为异常。 下表按版本给出关键差异,适配前先确认自己的内核基线。

内核版本关键状态对 SPI NOR 驱动的影响
4.14 ~ 4.19单文件 spi-nor.c(约 5000 行)ID 表、厂商 fixups、SFDP 全在一个文件里;DT 属性少。本文 6.3 的决策链在这几个版本字段名不同(nor->addr_width 而非 addr_nbytes)。
5.4 ~ 5.10引入 spi-mem;spi_nor_hwcaps 体系开始有 params->hwcaps;厂商表从 spi-nor.c 拆到各厂商文件。DT 增加 jedec-id 系列覆盖。
5.15spi_nor_set_4byte_addr_mode() 公开化厂商驱动可作为「最后手段」强制进入 4 字节模式(2023 年 3 月合入,源自 6.1 周期)。
6.1SFDP 大改:BFPT/基本表解析重构struct spi_nor_flash_parameter 成为能力载体;spi_nor_post_bfpt_fixups() 钩子可用。
6.6单文件彻底拆分spi-nor.c 更名为 core.c,厂商文件独立(winbond.c / gigadevice.c / macronix.c / micron-st.c…),新增 controllers/ 子目录。写 patch 时路径要跟着改。
6.9 ~ 6.12SR 掩码、spi_nor_helpers、OTP 独立文件状态寄存器位操作收敛到 spi_nor_get_feature();otp.c 提供一次性可编程区支持。
主线 masterDT binding 文档化官方要求新增 flash 提交时附上 sfdp / jedec_id / manufacturer / partname 的 sysfs 转储与校验和(见附录 A)。
怎么确认自己的内核是哪一版布局

# 1) 看内核版本号 Rev 1.4 -r
# 2) 看模块名与是否存在 core.o
ls /lib/modules/$(uname -r)/kernel/drivers/mtd/spi-nor/
# 6.6+ : core.ko winbond.ko gigadevice.ko ... controllers/
# 5.15 : spi-nor.ko
# 3) 看驱动打印的 banner(开 dynamic_debug 可见)
dmesg | grep -i "spi-nor\|spi_mem"
配置阶段就要确认,不要等写 patch 时才发现路径不对。

0.4 硬件平台与参考器件

本文以「ARM64 SoC + 单片 SPI NOR + 独立 SPI 控制器」为基准平台。为便于对照,列出 XTX 主力量产料号的 容量/电压/封装矩阵,调试时可在 /sys/.../spi-nor/ 下核对实际识别结果。

料号容量电压封装典型用途4B 地址要求
XT25F32FSOIGT32Mbit / 4MiB2.7~3.6VSOP8 150milBootloader、小容量参数区不需要(<16MiB)
XT25F64FSOIGT64Mbit / 8MiB2.7~3.6VSOP8 150mil引导 + 配置 + 日志不需要
XT25F128FSSIGT128Mbit / 16MiB2.7~3.6VSOP8 208mil3 字节地址的临界点临界(>16MiB 才需要)
XT25F256BSFIGT256Mbit / 32MiB2.7~3.6VSOP16 300mil多镜像 / A/B OTA / rootfs必须
XT25F512BSFIGT512Mbit / 64MiB2.7~3.6VSOP16 300mil大 rootfs + 双备份 + 日志轮转必须
XT25F1GBSFIGT1Gbit / 128MiB2.7~3.6VSOP16 300mil大数据缓存 / 双系统必须
XT25Q512FSFIGA512Mbit / 64MiB1.7~2.0VSOP16 300mil低电压平台主推必须
XT55Q1GFSFIGA1Gbit / 128MiB1.7~2.0VSOP16 300mil1.8V 大容量必须
XT25Q256FSFIGA256Mbit / 32MiB1.65~2.0VSOP16 300mil1.8V 容量升级必须
XT55Q2GFBGIGA2Gbit / 256MiB1.7~2.0VBGA24 6x8大存储 / 双系统必须
料号与容量的换算务必一次做对

Mbit(兆比特)→ MiB(兆字节)= ÷ 8。256Mbit = 32MiB,不是 256MB。 分区表 size 属性与 U-Boot mtdparts 都按字节写,写错就是分区越界或最后一块识别不到。 调试时用 cat /proc/mtd 交叉核对:size 那一列应当等于 分区大小 × 分区数(约等于器件容量)。

1 · 前置知识与开发环境

SPI NOR 的调试难度不在「写代码」,而在对器件行为与内核行为的双向理解。 本章把后续反复用到的三个前提讲清楚:器件本质、驱动基础要求、构建与调试环境。

1.1 SPI NOR 器件本质

SPI NOR Flash 是随机可读、页可编程、块可擦除的存储介质。这三句话决定了后面所有设计:

特性含义对软件栈的约束
随机可读(Random Read)任意字节地址可以直接读,不需要按页/块对齐可以像内存一样访问 → 支持 XIP;文件系统的「读」不需要 FTL 参与
页可编程(Page Program)一页(典型 256B)内可写,但只能从 0 写 1,不能从 1 写 0修改数据前必须先擦除;这就是 JFFS2 / UBI 存在的根本原因
块可擦除(Block Erase)擦除粒度典型 4KB / 32KB / 64KB,擦除后全变 0xFF文件系统按 erase block 管理;MTD 的 erasesize 就是它
存储几何的关键换算

以 XTX 512Mbit 器件为例:64MiB = 256 页 × 256B/页 = 4096 个 64KB 块, 也等于 1024 个 64KB 块 × 64 个 4KB 扇区。三个数必须能互相推导, 否则分区表和擦除命令会算错。调试第一步就是把 datasheet 的几何参数抄进表格并核对这三者一致。

512Mbit 器件的存储几何(XTX XT25F512B 系列为例) 整个阵列 64MiB = 256 页 页 0 页 1 页 2 …… 中间 252 页 …… 页 255 每页 256 字节,只能写不能擦 4KB 扇区 = 16 页  64KB 块 = 256 页 = 16 个扇区  4KB 扇区共 16384 个 擦除粒度由 JEDEC 4BA 表描述,内核从 SFDP 解析得到 读:任意地址 0x03 / 0x0B / 0x6B 不需对齐,可 XIP 写:页内 0→1 0x02 / 0x32 / 0x42 写前必须 WREN 擦:块级归 0xFF 0x20 / 0x52 / 0xD8 擦除很慢,需轮询 WIP 三条命令序列决定了驱动代码的全部形态 读:CS↓ → opcode → addr(3/4B) → dummy(4/6) → data → CS↑ 写:CS↓ → WREN(0x06) → CS↑ → CS↓ → opcode → addr → data(≤页大小) → CS↑ → 轮询 WIP 擦:CS↓ → WREN(0x06) → CS↑ → CS↓ → opcode → addr → CS↑ → 轮询 WIP 直到 0
图 1 · SPI NOR 存储几何与三类命令序列
为什么 NOR 也要「文件系统」

NOR 虽然可以随机读,但不能把任意已写数据改写成另一份数据。 若要存放结构化文件(配置、数据库、用户数据),必须由文件系统来管理「日志写入 + 垃圾回收」, 否则写一次配置就要擦整个扇区。JFFS2 / UBIFS 就是干这个的—— 直接把 ext4 挂到 NOR 上会立即损坏文件系统,这是规范性要求,不是可选项。

1.2 Linux 驱动基础要求

上手本文需要具备以下内核侧基础。缺失哪一块,第 10 章对应小节会额外展开:

知识点必须掌握到什么程度对应章节
platform / spimem 驱动模型看懂 probe 回调、设备与驱动的匹配机制(compatible)4.3
设备树 DTS / DTBO能独立写出 SPI 控制器 + Flash 子节点 + 分区表,并能编译验证3.3 / 5.1 / 5.2
spi-mem 四段式操作理解 struct spi_mem_op 的 opcode / addr / dummy / data 四个 phase4.6
MTD 子系统接口知道 mtd_info、mtd_device_register()、erase / write / read 三条回调4.6 / 8.1
dynamic_debug / kmsg_dump会开内核调试日志,会过滤关键子系统日志10.1
JEDEC SFDP 概念知道 SFDP 是器件自描述表,能读懂 BFPT 的关键 DW 字段4.5 / 6.4

1.3 交叉编译与构建环境

本文所有示例以 AArch64 为例,ARM32 / RISC-V 把工具链前缀换成对应即可。核心流程如下:

  1. 拉 SDK / 内核源码:厂商 SDK 通常带 kernel/、u-boot/、buildroot/ 或 yocto/。本文以 Linux 6.6 为基准线。
  2. 配置并交叉编译内核:
    export ARCH=arm64
    export CROSS_COMPILE=/path/to/toolchain/bin/aarch64-linux-gnu-
    make O=out mrproper
    make O=out defconfig
    # 合并板级配置片段(推荐用 scripts/kconfig/merge_config.sh)
    ./scripts/kconfig/merge_config.sh -O out out/.config board.fragment
    make O=out olddefconfig
    make O=out -j$(nproc) Image dtbs modules
  3. 单独编译 MTD 工具(强烈建议静态编译,方便拷到板子上直接跑):
    cd mtd-utils
    ./autogen.sh
    ./configure --enable-static --disable-werror \
        CROSS_COMPILE=aarch64-linux-gnu- \
        CFLAGS="-I${KERNEL_OUT/include -I${KERNEL_SRC/include}" \
        LDFLAGS="-L${KERNEL_OUT}"
    make -j$(nproc)
    # 产物:flash_erase flashcp flashdump nanddump mtdinfo ...
  4. 部署到板子:scp mtd-utils/flash_* root@板子:/usr/sbin/,或打进 rootfs 镜像。
  5. 设置交叉工具链环境(把日志重定向到主机,方便剪贴板):
    # 主机侧
    picocom -b 115200 /dev/ttyUSB0 | tee boot.log
    # 或
    screen /dev/ttyUSB0 115200
用 fragment 而不是直接改 .config

把本项目需要的配置写成一个 board.fragment 文本文件(内容是 CONFIG_XXX=y 这样的行), 用 merge_config.sh 合并。好处是:配置可版本化、可复用、可在 CI 里校验, 换 SoC 平台时不用重新勾一遍。完整 fragment 模板见 3.1。

1.4 调试环境搭建

调试 SPI NOR,「软件日志 + 硬件波形」双线并行效率最高。硬件侧只需三样便宜工具:

必备工具

  • 逻辑分析仪(8 通道 24MHz 级别即可,淘宝 50 元档):抓 CS# / SCLK / MOSI / MISO 波形,是定位 90% 硬件问题的唯一高效手段。
  • 万用表:量 VCC 实际电压、量 WP#/HOLD# 电平、量信号是否短到地。
  • 可编程 SPI 嗅探器 / 逻辑分析仪的 SPI 协议解码:把波形直接解成 opcode 字节,省去人工数位。

软件侧准备

  • 串口日志实时重定向到主机文件(tee)。
  • dynamic_debug 已打开(见 10.1)。
  • /proc/mtd、/sys/class/mtd/ 可读。
  • 主机上跑 mtd-utils 与 flashrom 备用(用 flashrom -p linux_spi:/dev/spidevX.Y 做交叉验证)。
  • 一个能改 DTS 的构建流程(overlay 或直接改板级 dts)。
调试阶段的信息闭环

现象(dmesg / 命令返回) → 读内核内部状态(debugfs params / capabilities) → 读器件真实状态(RDSR / SFDP / JEDEC ID) → 抓总线波形确认时序。 这四层从上到下依次排除,不要跳步:第 2 层能告诉你内核「以为」器件是什么, 第 3 层告诉你器件「实际」是什么,两者不一致就说明 probe 阶段出了问题——此时去抓波形往往白费力气。

2 · 硬件设计规范(驱动视角)

驱动工程师不需要画原理图,但必须能读懂原理图并指出哪些地方会让驱动跑不起来。 SPI NOR 硬件上最关键的三根线是 WP#、HOLD#、RESET#——它们决定了「写不进去」「读会停住」 这类诡异故障的物理原因。本章只讲与驱动行为直接相关的硬件约定;器件级电气参数以《SPI_NOR_Flash_电路设计指南》与 datasheet 为准。

2.1 原理图信号设计要点

信号 / 项目设计要求驱动侧症状(设计错时的表现)
SCLK尽量短、少过孔;与其他高速信号保持间距;串联阻尼电阻(典型 22~33Ω)靠近 SoC 端高频下读回数据错位、ECC 报错;降低 spi-max-frequency 后恢复正常 → 基本可判定为 SI 问题。
MOSI / MISO与 SCLK 大致等长;避免形成长分支(Stub);DTR 模式下两者严格等长要求特定命令(如 Quad 读 0x6B/0xEB、DTR 0x0B)返回全 FF 或全 00,但 0x03 正常。
CS#每器件独占;必须有确定的上拉(避免上电/复位期间误选中);不要复用其他功能多器件时 ID 读错;上电偶发 probe 到「不存在的器件」。
IO0~IO3 / WP# / HOLD#WP# 与 HOLD# 绝不可浮空;接了 GPIO 的要保证上电默认电平正确WP# 浮空 → 写入被随机禁止或随机允许;HOLD# 浮空 → 读数据中途停住、返回半截数据。
RESET#建议接 GPIO 或硬件 RC 复位;上电复位脉冲宽度须满足 datasheet tVSL掉电/热复位后器件状态机卡死,驱动只能等 WIP 超时(spi-nor: probe failed 或 Timeout)。
VCC / 去耦按 datasheet 放 1~2 个去耦(0.1µF + 4.7µF),紧靠器件电源脚擦除大块(64KB)瞬间电流跌落 → 擦除后数据是错的或干脆失败。
走线长度Quad 模式下四根数据线等长(±5mm),避免 IO2/IO3 比 IO0/IO1 短很多1-4-4 读偶尔出错,1-1-1 正常。这是典型的「四线不等长」特征。

2.2 WP# / HOLD# / RESET# 三根关键线

这三根线各自对应一类完全不同的软件故障。调试时按下表对号入座,可以省掉大量试错:

引脚低电平时器件行为典型故障现象快速判据
WP#(IO2)禁止 Page Program 与 Erase(状态寄存器仍可读)flash_erase 报 Error: unable to write MTD;flashcp 报 EIO;但 flashdump 一切正常万用表量 WP# 是否为低;flash_erase 前 RDSR 的 BP 位是否非 0
HOLD#(IO3)暂停输出但保持当前指令状态(相当于把时钟冻结)读数据在某一位置停住或全 FF;注意 WP#/HOLD# 在 IO2/IO3 上,是四线模式的复用引脚,用四线命令时如果没置 QE 就会暴露抓 MISO 波形:CS# 拉低后数据中途停止、SCLK 仍在跳
RESET#(RST#)硬件复位整个状态机(含 4 字节地址模式、QE 位、SR 保护位)掉电重启后 容量只认出 16MiB、写入仍被禁、或 probe 完全失败(器件还在 WIP 状态)抓 RESET# 是否有效;掉电重启后立即 cat /sys/.../jedec_id 看是否变化
最经典的坑:RESET# 与 4 字节地址模式

像 0xB7(EN4B)这类命令切换的 4 字节模式是易失状态—— 硬件复位或掉电后器件自动回到 3 字节模式。如果 bootloader 在 4 字节模式下写了数据、然后硬件复位、 内核再按 3 字节模式去读同一地址,会读出错误数据但不报错。 这正是第 6.9 节要专门讲「U-Boot 与内核模式一致性」的原因, 而专用 4B opcode(第 6.2 节方案 A)之所以更省心,就是因为它无状态。

三根控制线与「读/写/容量」三类故障的对应关系 WP# 拉低 禁止 Program 与 Erase 读操作完全正常 SR 的 BP 位被硬件强制 表现为:写入一律 EIO HOLD# 拉低 输出暂停,时钟继续 指令状态保持不变 表现为:读到一半变 FF 多发一字节地址即可恢复 RESET# 拉低 状态机整体复位 4B 模式 / QE / 保护位 全部回到默认 表现为:重启后容量变 16M 设计建议(三条都写进硬件评审 checklist) 1. WP# / HOLD# 各加 10k 上拉到 VCC,量产可选 0Ω 下拉占位以便调试期强制拉低 2. 需要软件可控写保护的场合,WP# 接 GPIO 且上电默认高,DTS 里配 gpio 控制器 3. RESET# 接 GPIO 并在 probe 前复位一次;RC 方案要核算上电斜率与最小脉宽 4. 三个测试点:WP#、HOLD#、RESET# 各引一根测试针,便于逻辑分析仪直接夹
图 2 · WP# / HOLD# / RESET# 与故障现象的对应关系

2.3 电压域与 IO 电平

SPI NOR 常见两个电压档:2.7~3.6V(XT25F 系列)与 1.65/1.7~2.0V(XT25Q / XT55Q 系列)。 混用是硬件评审的高频问题:

项目3.3V 器件(XT25F)1.8V 器件(XT25Q / XT55Q)混用后果
VCC2.7~3.6V1.7~2.0V3.3V 供 1.8V 器件:永久损坏风险(超绝对最大额定值)
输入阈值 Vih0.7×VCC ≈ 2.31V0.7×VCC ≈ 1.26V1.8V 器件误把 1.8V 输出当高,判定随机 → 容量认错、ID 读错
SoC IO 电平需 3.3V IO;若 SoC 是 1.8V 域需电平转换可直接接 1.8V 域缺电平转换:QPI 读全 FF,SPIDEV 完全不通
上拉电阻阻值典型 10k@3.3V典型 10k@1.8V(不宜太大,高阻时上升沿慢)上拉过弱 → 高频下上升沿不达标,读错数据
1.8V 平台的一条实战经验

1.8V 器件的输入阈值比 3.3V 低很多,上拉电阻阻值要相应减小(4.7k~10k 更常见,取决于总线电容与频率)。 若 1.8V 器件在你板子上「低速正常、高速随机错」,先怀疑上拉过弱 + 走线过长, 其次才是试 spi-max-frequency。另外确认 SoC 侧引脚配置里有没有开施密特触发(Schmitt), 开上以后上升沿的鲁棒性差别很大。

2.4 必留测试点与联调顺序

硬件联调按「先量电平、再抓波形、最后改驱动」的顺序做,不要一上来就改代码:

硬件联调五步法(顺序不可颠倒) 第 1 步 量电平 VCC 是否在范围 WP#/HOLD#/RESET# 是否被拉高 对地无短路 第 2 步 读 ID 逻辑分析仪抓 0x9F 事务 回读 3~5 字节 与 datasheet 比对 第 3 步 读 SFDP 抓 0x5A 事务 确认 SFDP 头 是否 0x50444653 看 DW16 的 4B 位 第 4 步 测边界 在 0 与 16MiB 附近各读写一次 确认 4B 地址 真的生效 第 5 步 压测 全盘写读校验 长稳压测 高频与降频对比 记录基线数据 第 2~4 步的具体抓包判据 读 ID(0x9F):CS↓ → 0x9F → 3 字节 dummy(0x00)→ 3~5 字节 ID → CS↑ 读 SFDP(0x5A):CS↓ → 0x5A → 3 字节地址(0x00 00 00)→ 1 字节 dummy → 表数据 → CS↑ 判据一:ID 第一个字节 = 厂商码(XTX 一般为 0x0B 或 0x5E 系列,以 datasheet 为准) 判据二:SFDP 头四字节应回读 0x53 0x46 0x44 0x50(小端 ASCII 字母 SFDP)
图 3 · 硬件联调五步法与抓包判据
为什么必须先抓 ID 再改驱动

「probe 失败」这一个现象,根因可能有十几种:pinctrl 配错、时钟频率超规格、CS# 上拉缺失、 器件处于 4 字节模式、WP# 被拉低、SPI 控制器 mem_ops 不支持 4 线…… 但抓一次 0x9F 波形就能砍掉一大半:能读出正确的 ID 说明硬件通路没问题, 问题在软件配置;读不出则先解决硬件或时钟。这比在 DTS 里反复试参数快得多。

2.5 BOM 选型约束

约束项要求违反时的症状
电压档必须与 SoC IO 域匹配3.3V IO → XT25F;1.8V IO → XT25Q / XT55Q轻则 ID 读错,重则器件损坏
最大时钟频率主频看 datasheet 的 fCLK(Quad 模式典型 104~133MHz;DTR 模式典型 80~100MHz)超规格时数据错乱,且降频就正常——这是最有价值的判据
封装与球位SOP8 / SOP16 / WSON8 / DFN8 / BGA24,注意 BGA 需 X-Ray 检测焊接不良表现为间歇性读错,返修后可能"就好了"(掩盖问题)
温度等级工业级 -40~85°C 或增强级 -40~105°C高温环境下超时、复位、写入失败
是否带 SFDP主流器件都带;不带 SFDP 的必须在内核 ID 表里加条目不带 SFDP 且 ID 表也没有 → unrecognized JEDEC id bytes
4 字节地址能力容量 >128Mbit 必须确认是 4B opcode 还是 EN4B 模式容量只认出 16MiB,或 >16MiB 区域读写乱码

3 · 内核配置与编译构建

SPI NOR 的内核配置项不多,但有两个必须打开且常被忽略的开关 (CONFIG_MTD_OF_PARTS 与 CONFIG_SPI_NOR_SFDP_RUNTIME)。本章给出可直接使用的 fragment 模板与逐项说明。

3.1 内核 config 清单

把下面内容保存为 board.fragment,用 merge_config.sh 合并:

# === SPI NOR 核心 ===
CONFIG_MTD=y
CONFIG_MTD_BLOCK=y
CONFIG_MTD_CFI=y
CONFIG_MTD_CFI_GEOMETRY=y
CONFIG_MTD_SPI_NOR=y
CONFIG_MTD_OF_PARTS=y                 # 关键:设备树 fixed-partitions 分区表
CONFIG_MTD_PARTITION=y
CONFIG_MTD_OF_IOMUX=y                 # 若用 pinctrl 绑定 flash 节点

# === SPI / spi-mem 框架 ===
CONFIG_SPI=y
CONFIG_SPI_MASTER=y
CONFIG_SPI_MEM=y                      # 关键:四段式内存操作抽象
CONFIG_SPI_LOOPBACK_TEST=y            # 建议开:spidev 自测

# === 文件系统(按实际需要裁剪) ===
CONFIG_JFFS2_FS=y
CONFIG_JFFS2_FS_WRITEBUFFER=y        # 强烈建议:写缓冲显著降低挂载时间与磨损
CONFIG_JFFS2_SUMMARY=y
CONFIG_UBIFS_FS=y
CONFIG_UBIFS_FS_ADVANCED_COMPR=y
CONFIG_UBIFS_FS_LZO=y
CONFIG_UBIFS_FS_ZLIB=y
CONFIG_SQUASHFS=y
CONFIG_SQUASHFS_ZLIB=y                # 只读 rootfs 推荐
CONFIG_SQUASHFS_FILE_DIRECT=y

# === 调试(量产前可关) ===
CONFIG_DEBUG_FS=y                     # 关键:/sys/kernel/debug/spi-nor/* 下有 params 视图
CONFIG_DYNAMIC_DEBUG=y
CONFIG_DEBUG_KERNEL=y
CONFIG_MTD_SPI_NOR_DEBUG=y            # 若存在该旧选项(旧内核用)
CONFIG_JFFS2_DEBUG=y                 # 排 JFFS2 问题时有日志(较吵)
CONFIG_MTD_CFI_GEOMETRY_DEBUG=y

# === MTD 工具需要的支持 ===
CONFIG_COMPAT=y                       # 32 位系统跑 64 位工具时需要(可选)
配置项级别不开会怎样
MTD_SPI_NOR必须没有 spi-nor 驱动,SPI 控制器上挂的 Flash 完全不被识别
SPI_MEM必须spi-nor 依赖它表达读/写/擦事务;不开会编译失败
MTD_OF_PARTS必须设备树里的 partitions 子节点被忽略 → 只有一个裸 mtd0,没有分区 → 一切文件系统操作失败。这是最典型的「配错」故障
DEBUG_FS强烈建议看不到 /sys/kernel/debug/spi-nor/<dev>/params,等于调试瞎了一半(本文 10 章大量依赖它)
JFFS2_FS_WRITEBUFFER强烈建议不带写缓冲的 JFFS2 挂载极慢且磨损翻倍
JFFS2_SUMMARY建议JFFS2 挂载时扫描全盘找擦除块,缺 SUMMARY 会显著变慢
DYNAMIC_DEBUG建议无法按模块开日志,只能全开或全关
SQUASHFS按需只读 rootfs 方案必备
确认配置生效的三个检查点

# 1) 内核命令行确认
cat /proc/config.gz | gunzip | grep -E "MTD_SPI_NOR|SPI_MEM|MTD_OF_PARTS|DEBUG_FS"
# 或(未开 IKCONFIG 时)在编译目录查
grep -E "CONFIG_(MTD_SPI_NOR|SPI_MEM|MTD_OF_PARTS|DEBUG_FS)" .config
# 2) 运行期确认模块与设备
lsmod | grep -i spi_nor
ls -l /dev/mtd* /dev/mtdblock* 2>/dev/null
# 3) 设备树分区是否被采纳(关键)
cat /proc/mtd
第三步如果只看到 mtd0 一个分区且没有 name,说明 fixed-partitions 没生效, 回去查 CONFIG_MTD_OF_PARTS 与节点写法(见 5.2)。

3.2 内置 vs ko 模块

方式写法适用场景注意
内置(推荐)CONFIG_MTD_SPI_NOR=y量产产品、启动早期就要挂载 rootfs无加载顺序问题;modprobe 依赖 udev 的场景下更可靠
模块CONFIG_MTD_SPI_NOR=m多平台共用一份内核、调试期快速替换需把 spi_mem 一起编成模块;insmod 顺序不能反
模块 + 依赖modinfo spi-nor.ko 查 depends—6.6 起文件名从 spi-nor 改 spi-nor(模块名仍是 spi_nor),厂商表独立成 ko 时要注意配套安装
# 模块方式的加载顺序
modprobe spi_mem        # 若单独编成模块
modprobe spi_nor
# 或直接 insmod 并检查依赖
modinfo spi_nor | head -5

3.3 DTS 编译与 overlay

板级 DTS 里 Flash 节点的改动频率最高(换料、加密位、4B 属性),因此建议用 overlay 而不是改主 DTS:

# 1) 把 Flash 节点抽成独立片段,便于复用
#    board/flash/xtx-spi-nor-512mbit.dtsi
&qspi0 {
    flash@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;
        spi-max-frequency = <104000000>;
        spi-rx-bus-width = <4>;
        spi-tx-bus-width = <4>;
    };
};

# 2) 编译设备树
make O=out ARCH=arm64 dtbs
# 产物:out/arch/arm64/boot/dts/<厂商>/<板子>.dtb

# 3) 反查 DTB 里的实际内容(验证语法与继承是否正确)
dtc -I dtb -O dts out/arch/arm64/boot/dts/<厂商>/<板子>.dtb | grep -A 20 "flash@0"

# 4) 启动时确认内核收到的是同一个 dtb
cat /proc/device-tree/$(cat /proc/device-tree/aliases)/spi*/flash@0/compatible; echo
改 DTS 后 90% 的「改了没生效」

① DTS 根本没被重新编译(板级 dts 被 dtsi 覆盖了但你改错了文件);
② bootloader 传的是另一个 dtb(很多平台从 eMMC/SD 读 dtb,改了源码里的板级 dts 没用);
③ 改的是 .dts 但实际生效的是 .dtb 且没 make dtbs;
④ 有 overlay 覆盖了它。

最快的自检:cat /proc/device-tree/.../flash@0/jedec-id 或用 dtc -I dtb 反编译 板子正在跑的那个 dtb,确认节点内容与源码一致。不一致就是 ①②③。

3.4 Rootfs 与 mtd-utils

SPI NOR 上放什么,取决于启动方案。三种典型形态:

形态布局优点风险
只读 rootfs + 可写数据卷boot(SquashFS) + rootfs(SquashFS) + data(JFFS2/UBIFS)根分区不可被写坏,安全;可写区磨损可控根分区升级需 OTA 机制(不能原地覆盖正在运行的分区)
全可写 JFFS2单个 rootfs(JFFS2)简单,升级方便升级过程掉电会砖机;必须做双备份或 A/B
纯数据器件(不挂 rootfs)多个 JFFS2/UBIFS 数据分区风险最低需要 bootloader 本身不从 NOR 启动

Rootfs 里必须包含的工具:

  • mtd-utils:mtdinfo / flash_erase / flashcp / flashdump / nanddump(NOR 上也能用做只读 dump)。
  • e2fsprogs:mkfs.jffs2 / jffs2dump / jffs2loader(flashcp -j 写 JFFS2 镜像时需要)。
  • mtd-utils-ubifs:mkfs.ubifs / ubinize(用 UBI 方案时)。
  • mtdinfo 替代品:cat /proc/mtd 任何时候可用,做自动化脚本时优先用它。

3.5 U-Boot 配套配置

U-Boot 与内核必须在三处保持一致(详见 5.3)。与 SPI NOR 相关的 U-Boot 配置:

# ===== U-Boot Kconfig =====
CONFIG_MTD_NOR_FLASH=y
CONFIG_SPI_FLASH=y
CONFIG_SPI_FLASH_MTD=y          # 让 sf 命令走 MTD 框架,分区表与内核统一
CONFIG_CMD_SF=y
CONFIG_CMD_SF_TEST=y            # sf test 可做全盘擦写校验,产线自检很有用
CONFIG_ENV_IS_IN_SPI_FLASH=y
CONFIG_SPI_FLASH_SHOW_PROGRESS=y

# DT 中给 U-Boot 用的快读使能(非标准属性,注意厂商差异)
# 很多平台用 m25p,fast-read 打开 0x0B 快读;具体看该平台 U-Boot 代码
U-Boot 阶段的高频坑(按出现频率排序)

① U-Boot 用 3 字节、内核用 4 字节(或反过来) → 大容量分区数据错乱。见 6.9。
② U-Boot 分区表与内核不一致 → 内核挂载错分区。见 5.3。
③ bootcmd 里 bootargs 的 mtdparts 覆盖了设备树分区 → 内核命令行优先级高于设备树,写错就以命令行为准。见 5.3。
④ U-Boot 打开 CONFIG_SPI_FLASH_MTD 但没在 DT 里写 partitions → U-Boot 里 sf probe 能识别,但 mtd list 是空的。
⑤ env 放在 NOR 上且与分区重叠 → env 被 flashcp 覆盖,设备变砖。

4 · 驱动架构与代码框架

理解 spi-nor 框架的内部结构,是能读懂 dmesg 报错、能写对 vendor patch、 能判断「内核认为器件是什么」的前提。本章按「分层 → 文件 → probe 流程 → 结构体 → SFDP → 三条路径」的顺序展开。

4.1 软件分层总览

SPI NOR 在 Linux 中的软件分层与可观测点 用户态 /dev/mtdX(字符)  /dev/mtdblockX(块)  mtd-utils(JFFS2 / UBIFS / SquashFS) 可观测:cat /proc/mtd  ls /sys/class/mtd/  strace 应用的 ioctl 调用 MTD 子系统 mtd_info 注册、分区管理(fixed-partitions)、dirmap、擦写类型统一 可观测:/proc/mtd  /proc/partitions  内核日志里的 Bad partition table 警告 spi-nor 框架 JEDEC ID 匹配、SFDP 解析、能力协商(hwcaps)、4 字节地址、QE、保护位 drivers/mtd/spi-nor/core.c(6.6+) 厂商文件 winbond.c / gigadevice.c ... 可观测:dmesg  /sys/kernel/debug/spi-nor/<dev>/{params,capabilities}     /sys/bus/spi/devices/.../spi-nor/{jedec_id,manufacturer,partname,sfdp} spi-mem 四段式事务(opcode / addr / dummy / data)、控制器 hwcaps 上报 可观测:/sys/bus/spi/devices/spiX.Y/mode  spidev 测试 SPI 控制器驱动 片选控制、时钟分频、DMA / 中断、XIP 映射(部分平台支持) 可观测:控制器寄存器 debugfs / debugfs 下的时钟与 hwcaps 硬件层:SPI NOR 器件 + WP# / HOLD# / RESET#(第 2 章)
图 4 · SPI NOR Linux 软件分层与每层的可观测点
调试时的下钻方向

从「现象」往「根因」下钻的顺序是:用户态报错 → MTD 层状态 → spi-nor 层的 params 视图 → spi-mem/控制器 hwcaps → 总线波形。 第 3 层是性价比最高的一层:cat /sys/kernel/debug/spi-nor/spi0.0/params 一条命令就能看到 内核最终选的读 opcode、dummy 周期、地址字节数、擦除类型表。不用它,等于每次都在猜。

4.2 源码目录与文件职责

以 6.6 / 6.12 为例(5.15 及以前只有 spi-nor.c 单文件,逻辑都在一起):

文件职责你改它做什么
core.c框架主体:probe、scan、ID 匹配、SFDP 调度、读写擦实现、4 字节地址、sysfs/debugfs 注册 seldom 直接改;只有要修框架 bug 或加通用能力时才动
core.hstruct spi_nor、struct spi_nor_flash_parameter、SNOR_F_* 标志、op 宏读它才能知道字段含义与标志位
sfdp.c / sfdp.hSFDP 表解析:BFPT、4BA、JEDEC 4 字节地址参数表(FF84)解析结果不对时看这里
winbond.c / gigadevice.c / macronix.c / micron-st.c / spansion.c / xmc.c / eon.c / esmt.c / fujitsu.c / sst.c / atmel.c / intel.c / issi.c / xilinx.c / catalyst.c / everspin.c各厂商 ID 表与 fixups(厂商私有行为修正)加新型号最常改的就是这里
sysfs.c暴露 jedec_id / manufacturer / partname / sfdp 供用户态读取一般不改
debugfs.c暴露 params / capabilities 视图调试必备,不要裁掉 DEBUG_FS
otp.c一次性可编程区(OTP / fuses)支持,关系到 4BAIT 固化器件通过 OTP 固化 4 字节模式时相关
swp.c软件写保护(Software Write Protection)状态机做全局写保护的固件可能用到
controllers/直接实现 spi_nor 接口的控制器(hisi-sfc、nxp-spifi 等)用的是 spi_mem 的平台不需要碰

4.3 probe 执行全流程

spi_nor_probe() 执行流程与常见失败点 1. devm_kzalloc 分配 struct spi_nor → 失败即 OOM(罕见) nor->spimem / nor->dev 绑定,spi_mem_set_drvdata() 挂私有数据 2. 读 JEDEC ID(0x9F / 0x90 / 0xAB) 失败高发:pinctrl 错、CS# 上拉缺失、频率超规格、器件在 4B 模式、WP# 拉低 报错特征:spi-nor: probe failed / unrecognized JEDEC id bytes xx xx xx 3. 用 ID 查厂商表匹配 flash_info → 决定容量 / 擦除粒度 / flags 无匹配则走 SFDP;都没有则报 unrecognized 4. 读 SFDP 表(0x5A)并解析:BFPT / 4BA / 4 字节地址参数表 得到 size、erase_types、读写协议能力、是否支持 4 字节地址及其进入方式 失败高发:器件 SFDP 表内容被改过 / 魔改 → 需要厂商 fixups 修正 5. spi_nor_setup():与控制器求 hwcaps 交集,选读/写/擦命令 失败高发:控制器不支持 4 线 → 退回 1-1-1(功能正常但性能低,不会报错) 6. spi_nor_set_addr_nbytes():决定地址字节数(本文第 6 章的核心) size > 0x1000000(16MiB)且当前是 3 字节 → 自动置 4 随后若有 4B opcode 则替换 opcode;否则在 init 阶段发 EN4B 命令 7. spi_nor_init():解保护位 → 置 QE(Quad Enable)→ 进入 4 字节模式 失败高发:QE 置位后仍用 1-1-1 读(性能低);或 4B 模式进入失败 8. mtd_device_register():注册 mtd 设备并解析设备树分区表 失败高发:CONFIG_MTD_OF_PARTS 未开 → 分区表被忽略(见 3.1)
图 5 · spi_nor_probe() 的八个步骤与各步失败特征
读 dmesg 时按这个顺序定位

probe 失败时 dmesg 里一定有一条明确报错,往上找到第一条 error 即可,后面都是级联噪声。 常见首条报错与含义:
spi-nor: probe failed → 通路问题(pinctrl / CS / 频率 / WP#),去看硬件;
unrecognized JEDEC id bytes: xx xx xx → 读到了数据但不匹配,要么 ID 真读错,要么器件没在 ID 表里; 若 xx 是 ff ff ff 基本是硬件,若像合法厂商码则需要加 ID 表条目;
Error: unable to read JEDEC ID → 连 ID 都没读出来,纯硬件;
spi-nor: SFDP: unexpected magic → SFDP 头不对,可能是 4B 模式导致(见 6.7);
Bad partition table → 分区表问题,与器件无关。

4.4 关键结构体与 flags

调试时最常查的是 struct spi_nor 的几个字段与 SNOR_F_* 标志位。 下表按「与 >128Mbit 相关」的程度排序,6.x 内核源码为准:

字段 / 标志取值 / 位置含义调试用法
addr_nbytesu8当前实际发出的地址字节数:3 或 4debugfs/params 里的 address nbytes。容量只认出 16MiB 时先看它是不是 3
addr_mode_nbytesu8「当前地址模式」的字节数,进/退 4B 模式时同步更新与 addr_nbytes 不一致说明模式切换出过问题
SNOR_F_4B_OPCODESBIT(3)器件支持专用 4 字节 opcode(0x13/0x03/0x12/0x21/0xDC…),内核直接替换 opcode置位时 params 里的 read opcode 应是 0x13 而非 0x03
SNOR_F_HAS_4BAITBIT(4)器件的 4 字节模式可通过 OTP/熔丝一次性固化,内核不替换 opcode置位后地址宽度由器件自身决定,调试时优先确认这个 flag 有没有设对
SNOR_F_HAS_LOCKBIT(5)器件有块保护锁,上电时需清 SR 才能写不设则开机后写入可能 EIO
SNOR_F_HAS_SR_TBBIT(0)SR1 有 TB 状态位仅影响状态解析
SNOR_F_HAS_16BIT_SRBIT(6)状态寄存器是 16 位(读 1 字节 + dummy)影响写保护位判断位置
SNOR_F_NO_WPBIT(16)器件有独立的 no-wp DT 属性 / 芯片无 WP# 引脚与硬件强相关,见 2.2
SNOR_F_SOFT_RESETBIT(12)支持软复位命令 0x66+0x99与 RESET# 等效,可用于救活卡死器件
read_opcode / read_dummyu8 / u32最终选中的读命令与 dummy 周期debugfs/params 直接可见;读出垃圾先核这两个值
erase_opcode / erase_masku8 / loff_t统一擦除命令与大小掩码params 里有完整擦除类型表
params->sizeu64器件字节容量params 的 size;小于实际容量 = 大容量配置错了

4.5 SFDP 解析与能力协商

SFDP(0x5A 命令)是 JESD216 定义的自描述参数表,内核优先依赖它, ID 表只是「SFDP 不可靠时的兜底」。理解 SFDP 是做大容量适配的前提:

SFDP 空间结构

  • 地址 0x000000:8 字节参数头(版本号、表数量、各表偏移)。
  • 首字节回读 0x53('S')→ 有效 SFDP;回读 0xFF → 器件不支持。
  • 头之后是各张参数表:标准表(BFPT、4BA、4 字节地址指令表 FF84)+ 厂商私有表。

BFPT 里对大容量最关键的两个字段

  • Density:器件总位数,size = Density / 8 字节。
  • DW16 bit31:29:4 字节地址支持方式(详见 6.4)。

能力协商(hwcaps)

内核把「器件支持的读协议」与「控制器支持的读协议」求交集,选出最好的一个。 典型优先级:1-8-8 > 1-4-4 > 1-1-4 > 1-1-2 > 1-1-1。

关键理解:控制器不支持四线时内核会静默退回单线,不会报错。 所以「设备能读能写但很慢」要去查 params 里的 read proto,而不是查 DTS 的 spi-rx-bus-width 是否写了 4(那只是期望值)。

SFDP 读不到时怎么办

顺序是:① 先确认 0x5A 事务本身正确(用逻辑分析仪抓,注意 dummy 字节数); ② 确认器件是否处于 4 字节模式(此时 0x5A 也要多发地址字节,见 6.7); ③ 抓 /sys/bus/spi/devices/.../spi-nor/sfdp 原始 hex—— 6.x 已把这个文件暴露出来,直接 xxd 出来分析,不必自己写读函数; ④ 都不行就查厂商是否魔改了 SFDP 内容(某些器件为了省成本故意写坏 SFDP), 这时必须在 spi_nor_fixups 里用 post_bfpt 钩子修正。

4.6 读/写/擦三条路径

三条路径都由 spi_mem_exec_op() 执行,区别只在「opcode + 地址字节数 + dummy + 数据方向」:

三条路径的 spi_mem_op 事务结构(4 字节地址模式示例) 读路径(以 1-1-4 为例,opcode 0x6B;若支持 4B opcode 则为 0xEC) opcode 1 字节 addr 4 字节 A31~A24~A16~A8~A0 dummy 4 周期 data(从器件读出,4 线) len 可任意(内核会按 bouncebuf/页大小分批) 写路径(Page Program,opcode 0x02 / 1-1-4 为 0x32 / 4B 为 0x12) 第 1 个事务:WREN(0x06),1 字节,无地址 第 2 个事务:opcode → addr 4 字节 → data(长度不得超过页大小,典型 256B) 第 3 步:轮询 RDSR(0x05)直到 WIP(bit0) 清零,超时由 max_timeout 控制 跨页写入内核会自动拆分;用户态 flashcp 也做了分页,但手写 ioctl 时必须自己分 擦除路径( Sector Erase 0x20 / 4B 为 0x21;Block 0xD8 / 4B 为 0xDC) WREN → opcode → addr 4 字节 → CS↑ → 轮询 WIP(64KB 块典型 300~1500ms,不能用固定延时) 内核按 erase_mask 拆分跨块擦除请求;擦除后该区域全为 0xFF 若 WIP 一直不灭且报超时,先查是否有写保护位未清(BP≠0 时器件直接不启动擦除)
图 6 · 读 / 写 / 擦三条路径的事务结构

4.7 编码规范与 remove

需要改内核代码时(加 ID 表条目、加 fixups),遵守以下规范,避免被 maintainer 打回:

  • 注释用英文,且说明「为什么」不只「是什么」。引用 datasheet 章节号与页码,例如 /* See section 4.2.1 of DS-xxxx Rev.3 */。
  • 一行不超过 80 列,用 tab 缩进(内核风格)。
  • 不新增无必要的中文注释;与主线风格冲突的本地化注释不进上游。
  • 新增 ID 条目优先加到厂商文件(winbond.c / gigadevice.c …),不要塞进 core.c。
  • 提交信息带 Signed-off-by 与 Tested-by(含控制器名与实测频率),这是主线 review 的硬性要求。
  • 提交前跑 scripts/checkpatch.pl --no-tree。
  • 本地维护与上游提交分成两个 patch 序列:本地 hack 明确标注,方便后续 rebase。

remove / shutdown 路径上,框架会调用 spi_nor_restore() 把器件恢复到「复位后的默认状态」(退出 4 字节模式、关掉连续读模式)。这段代码是 厂商 fixups 里最需要小心的地方:如果你的器件有非易失的状态被错误地「恢复」了, 下次启动行为就会变。写 fixups 时若操作了厂商私有位,务必在 fixups->restore 里对称还原。


文档编号 LINUX-SPINOR-DRV-001 · V1.0 · 命令码、状态寄存器位、器件容量码与 4 字节地址方式均以对应料号的正式 datasheet 为准; 内核结构体与函数名随版本变化,以实际源码 drivers/mtd/spi-nor/ 为准。 配套文档:《SPI_NOR_Flash_电路设计指南.html》《SPI_NOR_Flash_软件设计规范.html》、 《Linux_SPI_NAND_驱动开发指南.html》。

扫码打开本页
微信扫码 · 展会可扫

再分享给同事

同事可直接打开这份资料《Linux_SPI_NOR_驱动开发指南》,在线阅读;完整章节请下载芯参谋。