芯参谋(86):Linux_SD_NAND_驱动开发指南
Linux SD NAND 驱动开发指南
编制日期 2026-10-28
MMC 块设备路线为主线 · SPI 模式与自研路线作对照 · 面向 SoC BSP 与存储原厂联调 | 2026-10-04 | 版本号 Rev 1.0
SD NAND 在 Linux 里不需要你写驱动 —— 它是标准 SD 卡,接在 SoC 的 SD/MMC 控制器上,内核 MMC 子系统会自动把它认成 /dev/mmcblk0。真正的工作量在设备树、pinmux、供电时序和掉电安全策略上。本文档给出「先走主线 MMC、确有必要再自研」的完整落地路径与全部踩坑点。
报告总结
- 先破除最大的误解。网上大量「SD NAND Linux MTD 驱动」文章其实是把 SPI NAND 驱动改名套用。本文第 1 章用内核目录事实说明:主线没有
drivers/mtd/nand/raw/sdnand.c,SD NAND 在 Linux 里的正确归宿是 MMC 子系统 + 块设备。 - 把主线路线写细。第 3~8 章按「硬件前提 → MMC 内核栈 → 设备树 → host 驱动适配 → 编译烧写 → 枚举排障 → 性能调优 → 挂载与文件系统」的顺序,把路线 A 每一步的命令、日志、配置项都写出来,可以照着做。
- 把自研路线讲透边界。第 9~10 章讲清楚什么情况下才需要自研 MMC host 或自研块设备,以及 SPI 模式在 Linux 里的真实位置(不进 MTD,只能自己实现 SD-over-SPI 栈)。
- 低功耗与掉电是主线。第 11 章把 VCC 负载开关门控、内核
runtime PM、suspend/resume三者的配合写清楚 —— 这是 SD NAND 相对 TF 卡最大的差异化收益。 - 速查表和核查清单。第 12~14 章是故障现象速查表、交付核查清单与附录命令表,可以直接当 review checklist 用。
读者路径
PATH
路线 A · 首次点亮
从原理图确认硬件前提,一路走到 mount 成功。板级 BSP 工程师主读 1→8→12 章。
读者:SoC BSP / 板级驱动工程师
PATH
路线 A · 性能与可靠性
跑满 4-bit + High-Speed,做掉电测试与寿命预算。存储产品与测试工程师主读 8、12~14 章。
读者:存储 FAE / 测试工程师
PATH
路线 B · 自研评估
确认主线走不通的原因,评估自研 host 或自研块设备的成本。SDK 团队主读 2、9、10 章。
读者:SoC SDK / IP 团队
PATH
原厂视角 · 联调
站在存储芯片原厂侧看 Linux 侧的接入点,做硅片验证与一致性测试。XTX 内部与 FAE 主读 2、8、13、14 章。
读者:存储原厂 FAE / 产品经理
与同系列文档的关系
| 文档 | 关注重点 | 本文何时读它 |
|---|---|---|
| SD_NAND_Flash 电路设计指南 | SPI/SDIO 双模式、VCC 负载开关门控、PCB Layout、ESD | 本文第 3 章确认硬件前提时 |
| SD_NAND_Flash 软件设计规范 | 命令流、忙等双确认、AU 对齐、错误码、CID 绑定 | 本文第 5 章 host 驱动移植时 |
| RT-Thread / FreeRTOS / AliOS 驱动开发指南 | RTOS 侧设备模型与驱动开发方法论 | 同一器件在 RTOS 侧落地时 |
1 · 先把事实说清楚:SD NAND 在 Linux 里到底是什么
这一章解决一个根子上的认知问题:SD NAND 是不是「一种 NAND」?如果你按 NAND 的思路去找驱动、找文件系统、找坏块表,会一路走错。本章给出内核目录层面的事实、SD NAND 的封装原理,以及两条技术路线的取舍依据。
1.1 一个必须先接受的事实:主线没有 SD NAND 的 MTD 驱动
Linux 内核 drivers/mtd/nand/raw/ 目录下共有 70 余个文件,覆盖从早期的 plat_nand.c、diskonchip.c 到现代的 qcom_nandc.c、denali.c、cadence-nand-controller.c 等各类 NAND 控制器,以及 nand_*.c 系列芯片厂商表。其中没有任何一个文件叫 sdnand.c,也没有任何一个文件的 compatible 是 SD NAND。
为什么原理上就不该有
MTD 子系统处理的是裸 Flash:它暴露 Page 读写、OOB 读写、块擦除、坏块标记,由主机负责 ECC、坏块重映射、磨损均衡(UBI/UBIFS 就是干这个的)。
而 SD NAND 是NAND 阵列 + 内部 SD 控制器 + 片内 FTL 的封装。片内控制器已经完成了坏块管理、ECC、磨损均衡,对主机只暴露 512 字节逻辑块(LBA)。它没有 OOB 可读写,没有裸块擦除命令,主机也无从得知物理坏块位置。
换句话说:SD NAND 抛给主机的接口面就是块设备接口,MTD 抽象对它没有意义。这和 eMMC、SD 卡、U 盘完全同理 —— 它们都不在 /proc/mtd 里,都不用 mtd-utils。
与之相对,Linux 里确实存在「把 SD 卡当 MTD 用」的第三方实现(例如某些 MCU 厂商 SDK 里的 sdmtd),它们做的是把 SD 卡的块映射成一个带 erase 语义的伪 MTD 设备,供 UBIFS 或 FTL 使用。这类实现与本文第 9 章讨论的自研路线属于同一类东西,它不是内核主线能力,向上兼容、内核升级风险都要自己承担。
SD NAND 在 Linux 里的两条归宿:绝大多数项目走上面一条SoC 的 SD/MMC 控制器CLK / CMD / DAT0~3 / VCC / 可选 CD#SD NAND 器件NAND 阵列 + 内部控制器 + 片内 FTL / ECC / BBM路线 A:MMC 子系统 → 块设备(主线能力)推荐 · 量产项目唯一现实选择/dev/mmcblk0 · /dev/mmcblk0p1内核配置:CONFIG_MMC / MMC_BLOCK / MMC_SD设备树:compatible + bus-width + max-frequency文件:mmc/core/sd.c、core/block.c、host/sdhci*.c工作量:写设备树 + 调试 pinmux/电压,0 行 C 代码路线 B:自研驱动挂 MTD / 自己写块设备仅特殊需求 · 需自己承担全部正确性责任自定节点:/dev/sdctl0 或 /dev/mtdX适用:主控无 SD 控制器、需自定义 FTL/寿命策略不适用:绝大多数带 SDHCI/eMMC 控制器的 SoC代价:DMA、超时、掉电、并发全部自己写工作量:800~2500 行 C 代码,且需长期维护关键事实:Linux 主线没有 drivers/mtd/nand/raw/sdnand.c —— 网上多数「SD NAND MTD 驱动」文章是把 SPI NAND 驱动改名套用,不能直接信
图 1 · SD NAND 在 Linux 里的两条归宿
1.2 SD NAND 的封装原理决定了它的接口面
要理解后面的所有结论,只需要理解一张图:SD NAND 内部已经把 NAND 的物理特性全部吃掉了。
SD NAND 分层职责:片内控制器替你做了 70%,剩下 30% 仍要主机负责器件内部(片内控制器 / FTL)对主机表现为:标准 SD 协议的 512 B 块设备没有 Page Read、没有 OOB、没有裸块擦除命令→ 因此内核的 MTD 裸片框架在原理上就不适用主机侧(SoC + Linux 驱动)主机必须做:① 上电时序:tPOR 后才能发 CMD0② 初始化频率 ≤400 kHz,识别后再升频③ 4-bit / High-Speed 协商,失败必须能降级④ 空闲预留 10%~20%,不要把逻辑空间写满⑤ 关键结构写后读回校验(片内 FTL 不等于掉电安全)两者之间的唯一接口:SD 物理层命令CMD0 / CMD8 / ACMD41 / CMD2 / CMD3 / CMD9 / CMD7 / ACMD6 / CMD6 / CMD17 / CMD18 / CMD24 / CMD25 / CMD12 / ACMD23 / CMD13上层挂 ext4 / f2fs / squashfs / vfat 均可 —— 因为它已经是块设备了三个最常见的认知错误错误 1:把它当 SPI NAND,发 Page Read(0x13) 读页SD NAND 的 SPI 模式封装的是 SD 命令(0x13 是 SD 的 READ 变体),不是 NAND 的 0x13+GET_FEATURE 流程。套 SPI NAND 驱动必然读出乱码。错误 2:扫 OOB 找坏块标记、自己算 ECC这些全在器件内部。主机去读 OOB 只会读到未定义内容,写 OOB 可能直接触发器件内部状态机异常。错误 3:以为它能像 eMMC 一样 CMD5 进 SLEEP 省电SD NAND 规范没有定义 SLEEP/AWAKE 指令。停时钟只是 Standby,电流仍在百 µA 量级。要 µA 级待机只能靠 VCC 负载开关门控。
图 2 · SD NAND 分层职责:器件做什么、主机做什么
1.3 两条技术路线
把「SD NAND 怎么接进 Linux」这个问题拆开,只有两种答案。
路线 A:MMC 子系统 → 块设备(主线)
- SoC 已有 SD/MMC 控制器(SDHCI、dwcmsc、dw_mmc、sdhci-msm 等绝大多数 SoC 都有)
- 设备树里描述控制器 + 子节点,内核 MMC 核心走标准 SD 协议枚举
- 识别后注册
/dev/mmcblk0,直接mkfs+mount - 你可能一行 C 代码都不写
路线 B:自研驱动
- 触发条件很少:主控确实没有 SD 控制器(如某些 MCU、DSP、FPGA 软核),或者产品需要内核块设备之外的自定义语义(自定义寿命管理、端到端加密、分区密钥)
- 可以自研 MMC host(复用内核 MMC 核心),也可以自研裸块设备(绕过 MMC 核心)
- 工作量 800~2500 行,DMA、超时、并发、掉电全自己负责
- 内核升级要重新适配
两条路线工作量与风险对比:差距不在代码行数,在「谁来保证正确性」对比维度路线 A:MMC → mmcblk路线 B:自研驱动需要写的 C 代码0 行(绝大多数 SoC)800 ~ 2500 行需要改的地方设备树 + 可能的 pinmux/时钟驱动 + 设备树 + Kconfig + Makefile协议正确性内核 sd.c 已验证十余年全部自己写、自己测DMA / 中断 / 缓存一致性host 驱动已实现从零实现,含 ARM D-Cache 一致性掉电 / 并发 / 超时内核统一处理全部自己处理4-bit / HS 协商与降级内核自动完成自己写,且失败无法回退就变砖跟进内核安全更新自动受益每次内核升级都要重新适配空盘挂载到可用半天2 ~ 6 周失败后的现场dmesg 有完整 MMC 栈日志需要自己加调试日志适合的量产项目绝大多数板载存储需求SoC 无 SD 控制器等少数特例结论:先用路线 A 把设备跑通;只有当路线 A 确实走不通时,才评估路线 B
图 3 · 两条路线工作量与风险对比
如果有人让你「在 Linux 下写 SD NAND 驱动」
先问清对方要解决的是哪个问题。八成情况下真正要做的是:改设备树(总线宽度、频率、电压、regulator)、补 pinmux、确认供电时序、做掉电保护策略。这几件事做好,设备就跑起来了。
只有当对方明确说「我们主控没有 SD 控制器」或「我们需要块设备之外的自定义语义」,才进入路线 B 的讨论。
2 · SD NAND 器件与 Linux 侧的接口面
这一章把「器件提供了什么」和「Linux 关心什么」对齐,列出 host 驱动与文件系统真正依赖的字段,为第 5 章移植代码做准备。
2.1 SD NAND 的关键几何与能力参数
SD NAND 遵循 SD Physical Layer 规范,容量不做分档,但几何、擦除粒度、AU 大小写在 CSD/SCR 里,Linux 内核并不解析这些字段(它只按 512 B 扇区看待)。对 Linux 开发者来说,只有下面几组参数真正影响软件行为:
| 参数 | 来源 | Linux 是否使用 | 对软件的实际影响 |
|---|---|---|---|
| 容量(C_SIZE / OCR) | CSD / OCR | 是 | 决定 /dev/mmcblk0 大小与分区大小 |
| 块长度 512 B | 固定 | 是 | 块设备扇区大小恒为 512 |
| SCR(SD 配置寄存器) | ACMD51 | 部分 | 决定是否支持 1.8 V 切换、SDR50/DDR50 能力 |
| AU_SIZE(分配单元) | ACMD13 | 否 | 内核不感知。写放大由器件内部 FTL 承担,但应用写对齐仍能显著提升寿命 |
| 擦除块 / 页几何 | CSD 或器件私有 | 否 | 主控无法发裸块擦除;只影响 fio/flashbench 类工具的实测结果 |
| CID / CSD / OCR | CMD2 / CMD9 / ACMD41 | 是(绑定用) | 可用于设备身份绑定、防错料、产线追溯 |
| 厂商私有 SMART | 厂商自定义命令 | 否 | 剩余寿命需厂商私有命令读;需自己写 ioctl 工具 |
注:ACMD13 / AU_SIZE 的解析在嵌入式自研驱动里常见,但 Linux MMC 核心不读它。若需要,须在 host 驱动或用户态工具里自行发 ACMD13。
Linux 视角下最重要的一个结论
因为 AU_SIZE、擦除块大小对 Linux 不可见,「让应用写对齐」这件事在 Linux 里只能靠应用与文件系统侧自觉,无法由驱动强制。Linux 侧真正能做的优化是:写合并(block 层 blk-mq 与调度器)、减少 fsync、选择合适的文件系统(f2fs 比 ext4 更适合闪存)。
2.2 电气与时序前提(驱动前必须对表)
| 项目 | 典型值 | 驱动侧约束 | 违反后的现象 |
|---|---|---|---|
| 供电电压 | 3.3 V(2.7~3.6 V)或 1.8 V(1.7~1.9 V) | regulator 配对;I/O 电压与 SoC bank 匹配 | CMD 无响应 / 读到全 0xFF / 枚举超时 |
| tPOR(上电到首条命令) | ≥1 ms(典型 5 ms) | regulator 使能后留足延时 | 偶发首条 CMD 无响应,重试后正常 |
| 识别时钟 | 100~400 kHz | 设备树 max-frequency 在识别阶段由内核自动降到 400 kHz |
CMD8/ACMD41 失败,能耗异常 |
| 工作时钟 | 50 MHz(HS);部分器件支持更高 | 识别成功后由内核切 High-Speed | 数据 CRC 错、随机读失败 |
| 总线宽度 | 4-bit(HS 前提) | bus-width = <4> |
日志显示 1-bit,带宽降到 1/4 |
| 峰值电流 | 读 15~30 mA;写 / 擦 30~70 mA(峰值 100 mA) | regulator 限流留足裕量;去耦按 datasheet | 枚举成功但负载下读写报错、复位 |
| Standby 电流 | 80~350 µA | 停时钟只到这个量级 | 误以为能省电,实际待机电流不降 |
| 断电后漏电流 | <1 µA(取决于负载开关) | VCC 门控方案的目标值 | 待机功耗不达标 |
不要把 SD NAND 当成有 SLEEP 指令的设备
eMMC 有 CMD5 SLEEP/AWAKE,可以进低功耗状态;SD NAND 规范没有定义这类指令。停掉时钟后器件进入 Standby,电流仍在百 µA 量级。
要达到 µA 级待机,只能靠主控 GPIO 控制负载开关切断 VCC,并严格遵守「先收 IO、再断电」的顺序。这条路线在第 11 章完整展开。
2.3 Linux 侧真正会被用到的命令
内核 MMC 核心发往 SD NAND 的命令集合是固定的、标准的。理解这批命令的用途,既有助于读懂 dmesg 日志,也有助于移植 host 驱动时确认哪些路径必须实现。
| 命令 | 方向 | 阶段 | 作用 | 驱动关注点 |
|---|---|---|---|---|
| CMD0 | H→C | 识别前 | 复位 / 转入 Idle | arg = 0x95 时走 SPI 模式锁定;SD 模式下为 0 |
| CMD8 | H→C | 识别 | SD2.0 版本协商(arg=0x1AA) | 响应非法应走 v1 分支;host 需正确回 R7 |
| ACMD41 | H→C | 识别 | 操作条件协商,直到 OCR Busy=1 | 超时上限要放足(约 1 s) |
| CMD2 / CMD3 | H→C | 识别 | 取 CID / 分配 RCA | RCA 值来自卡,注意 SD NAND 可能是 0x0001 |
| CMD9 | H→C | 识别 | 读 CSD(容量、几何) | 容量解析在此完成 |
| CMD7 | H→C | 识别后 | 选卡进入传输态 | 总线宽度切换前后都要保持状态机正确 |
| ACMD51 / SCR | H→C | 配置 | 读 SCR(电压 / 时序能力) | 决定 1.8 V 与 UHS 能力 |
| ACMD6 | H→C | 配置 | 切 4-bit 总线 | 失败必须能回退到 1-bit,否则设备变砖 |
| CMD6 | H→C | 配置 | 切 High-Speed(SDR) | 失败必须能降频继续工作 |
| CMD17 / CMD18 | H→C | 读 | 单块读 / 多块读(CMD12 停止) | 多块读必须正确处理 STOP |
| CMD24 / CMD25 | H→C | 写 | 单块写 / 多块写(CMD12 停止) | 写后 busy;内核已做 busy 等待 |
| ACMD23 | H→C | 写 | 预擦除块数声明 | 大块写前可发,提升写入效率 |
| CMD13 | H→C | 任意 | 读卡状态(R2 含错误位) | 内核用于错误诊断 |
| CMD0(复位后重枚举) | H→C | 错误恢复 | 卡挂死后重新走完整识别流 | 内核 mmc_rescan 路径 |
说明:SD NAND 的 SPI 模式命令是 SD 命令的 SPI 封装(如 0x13 读、0x14 写、0x15 擦),与 SPI NAND 的 0x13/0x0F/0x03 三段式缓存读流程不是一回事,详见第 10 章。
2.4 芯天下 XTX SD NAND 型号对照(供设备树与验证参考)
| 电压域 | 容量 | 料号 | 封装 | 接口形态 |
|---|---|---|---|---|
| 3.3 V | 1 Gb | XTSDG01GWSIGA | WSON-8 8×6 mm | SDIO 4-bit / SPI |
| 3.3 V | 2 Gb | XTSDG02GWSIGA | WSON-8 8×6 mm | SDIO 4-bit / SPI |
| 3.3 V | 4 Gb | XTSDG04GWSIGA | WSON-8 8×6 mm | SDIO 4-bit / SPI |
| 3.3 V | 8 Gb | XTSDG08GWSIGA | WSON-8 8×6 mm | SDIO 4-bit / SPI |
| 1.8 V | 1 Gb | XTSDQ01GWSIGA | WSON-8 8×6 mm | SDIO 4-bit(I/O 1.8 V) |
| 1.8 V | 2 Gb | XTSDQ02GWSIGA | WSON-8 8×6 mm | SDIO 4-bit(I/O 1.8 V) |
| 1.8 V | 4 Gb | XTSDQ04GWSIGA | WSON-8 8×6 mm | SDIO 4-bit(I/O 1.8 V) |
| 1.8 V | 8 Gb | XTSDQ08GWSIGA | WSON-8 8×6 mm | SDIO 4-bit(I/O 1.8 V) |
| 1.8 V | 1 / 2 Gb | XTSDQ01GLAIGA / XTSDQ02GLAIGA | LGA-8 6×5 mm | 小体积版本 |
料号与封装必须逐一对照 datasheet
WSON-8 与 LGA-8 的 pin order 不通用,不同容量批次也可能不同。建库时不要套用其他家的封装符号,务必按具体料号 datasheet 的引脚图录入。具体电气参数(时序、电流、时钟上限)同样以正式 datasheet 为准。
3 · 硬件前提核查:软件之前先把板子确认一遍
这一章解决「代码还没写就注定失败」的那部分问题。SD NAND 在 Linux 下不需要写驱动,但硬件上有 6 个前提必须逐条确认,任何一条不满足都会表现为「枚举失败」,而根因在板子上。
3.1 原理图检查清单
拿到一块板子,先按下面 6 条逐项确认。这 6 条对应 SD NAND 的最小系统,缺一条都不会枚举成功。
| # | 检查项 | 合格标准 | 不满足时的现象 |
|---|---|---|---|
| 1 | 信号线完整性 | CLK、CMD、DAT0~DAT3 六根线全部接到 SoC 对应引脚,无错接 | 完全无响应;或 1-bit 能识别但 4-bit 失败 |
| 2 | 总线方向引脚复用 | DAT1~DAT3 配为 MMC 功能,不是 GPIO 或测试点 | 内核只能枚举到 1-bit,bus-width 声明 4 但实际 1 |
| 3 | VCC 供电 | 3.3 V 或 1.8 V 稳压,限流能力 ≥200 mA,去耦按 datasheet | 小负载正常、读写时复位或报错 |
| 4 | VCC 门控(可选) | 负载开关 + 主控 GPIO,允许完全断电 | 无法做低功耗待机 |
| 5 | CMD / DAT 上拉 | CMD 与 DAT0~3 各有 4.7~10 kΩ 上拉到 I/O 电压 | 总线浮空,总线宽度协商随机失败 |
| 6 | 无热插拔需求 | SD NAND 贴片常驻,设备树用 non-removable |
反复探测导致启动变慢与额外功耗 |
最常见的一板走错:DAT1~DAT3 被当成 GPIO 或悬空
这一条造成的现象极具迷惑性:dmesg 里能看到 bus width 4 已经设置,但 cat /sys/block/mmcblk0/device/.. 下的实际宽度是 1,或者吞吐只有理论值的四分之一。很多人会去怀疑驱动、去改时钟、去换器件,绕一大圈。
正确的排查顺序是:先量信号。用示波器看 DAT2/DAT3 在 ACMD6 之后有没有翻转为低,再查 pinmux 配置表里这几根线是否被复用了。
3.2 SoC 控制器能力确认
设备树写之前,先在 SoC 手册或内核源码里确认控制器类型与能力。这决定了后面用哪条 host 驱动路径。
| 控制器类型 | 典型 SoC | 内核 host 驱动 | 驱动文件 | 支持 4-bit / HS |
|---|---|---|---|---|
| SDHCI | 高通、联发科部分、瑞昱、英伟达部分 | sdhci-pci / sdhci-of-dwcmshc / sdhci-of-arzoo |
drivers/mmc/host/sdhci*.c | 支持 |
| DesignWare MMC | 瑞萨、东芝、海思、博通、安霸 | dw_mmc + 平台 glue |
drivers/mmc/host/dw_mmc*.c | 支持 |
| dwcmsc(Synopsys) | 联发科 MT676x/MT678x | dwcmsc-dw |
drivers/mmc/host/dwcmsc-dw.c | 支持 |
| sdhci-msm | 高通 MSM / 骁龙 | sdhci_msm |
drivers/mmc/host/sdhci-msm.c | 支持 |
| 无 SD 控制器 | 部分 MCU、DSP、FPGA | — | — | 需路线 B(见第 10 章) |
怎么快速确认自己板子上是哪种
两条路都可以:
① 查 SoC 内核源码里的设备树:grep -r "mmc@" arch/arm64/boot/dts/,看节点的 compatible 字符串;
② 启动后看 dmesg | grep -i mmc,日志里会出现驱动名(如 mmcblk0: mmc0:0001 ... 之前那几行里的控制器标识)。
3.3 供电与电压域:三个容易出错的地方
- 数量级不够。SD NAND 写 / 擦瞬间电流可达 70 mA(峰值 100 mA)。若 regulator 限流设得偏低(如默认 100 mA 且压降明显),会出现「读正常、写几秒后失败」。建议留到 200 mA 以上裕量,去耦按 datasheet 的高频电容。
- I/O 电压不匹配。3.3 V 版本(XTSDG 系列)不能接在 1.8 V 域的 MMC bank 上;1.8 V 版本(XTSDQ 系列)同理。SoC 的 MMC 控制器通常有
vqmmc-supply(I/O 电压)与vmmc-supply(核心电压)两个 regulator,1.8 V 版本需要额外电平或直接落在 1.8 V bank。 - 上电时序未留延时。regulator 使能后需要等 tPOR(≥1 ms)。内核 MMC 核心在探测时会发 74 个时钟并轮询 ACMD41,天然覆盖了大部分情况;但如果用
mmc-pwrseq自定义上电序列,必须显式加延时。
3.4 布线与信号完整性
| 信号 | 要求 | 不满足的后果 |
|---|---|---|
| CLK | 连续参考平面,跨层换参考要加过孔对;避免与 DAT 并行走长距离 | 边沿畸变,High-Speed 下数据 CRC 错、随机读失败 |
| CMD | 开漏信号,必须有上拉(4.7~10 kΩ 到 I/O 电压);不能加 RC 滤波 | 上升沿过慢,识别阶段就可能超时 |
| DAT0~3 | 并行走线等长(组内 5 mm 内);跨层同时换参考 | 总线宽度切换失败,或高负载下偶发 CRC 错 |
| VCC 去耦 | 近端 0.1 µF + 1~10 µF 多级 | 写负载瞬时压降,器件复位 |
「识别成功但读写随机失败」= 信号完整性问题
这类现象的判定方法很明确:把 max-frequency 降到 25 MHz 甚至 12.5 MHz,如果故障消失,就是信号完整性问题;如果降频后依然错,才往协议层与驱动层查。降频法是这类问题最快的二分手段,应作为标准排查步骤写进团队的调试手册。
4 · MMC 子系统解剖
这一章解决「内核到底替我做了什么」。理解 MMC 内核的三层结构与枚举时序,才能读懂 dmesg、才能判断失败发生在哪一层、才能知道 host 驱动必须实现什么。
4.1 三层结构与调用链
Linux MMC 子系统分为三层:核心层(core)实现 SD 协议;主机层(host)实现具体控制器的寄存器与中断;卡层(block)提供块设备与分区。
用户态 mount / fdisk / mkfs.ext4 /dev/mmcblk0p1
│ VFS 路径
卡层 drivers/mmc/core/block.c → /dev/mmcblk0
│ bio / request_queue
核心层 drivers/mmc/core/core.c 协议状态机、命令队列、超时
drivers/mmc/core/sd.c SD 专用:CMD0/8/ACMD41/2/3/9/7 + CMD17/18/24/25
drivers/mmc/core/block.c 卡状态(present / readonly / write-protected)
│ struct mmc_host_ops
主机层 drivers/mmc/host/sdhci.c SDHCI 通用
drivers/mmc/host/dw_mmc*.c DesignWare MMC
drivers/mmc/host/sdhci-msm.c 高通私有
│ 寄存器 + 中断 + DMA
硬件 SoC SD/MMC 控制器 → CLK/CMD/DAT0~3 → SD NAND
SD NAND 走的是 core/sd.c 这条路
内核的 SD 协议实现写在 drivers/mmc/core/sd.c,SD/eMMC/SDIO 的区分体现在 struct mmc_card 的类型与 mmc_attach_sd() 流程。SD NAND 对内核来说就是一张焊死在板上的 SD 卡,没有任何一行内核代码知道它是 NAND。
4.2 枚举时序逐拍
理解这段时序有两个用处:一是能对照 dmesg 判断卡在哪一步;二是移植 host 驱动或用 GPIO 模拟时知道每一步的预期响应。
内核 MMC 核心的 SD 枚举流程(简化):
① mmc_of_parse() 读设备树:bus-width / max-frequency / pwrseq
② 供电与 pwrseq 使能 vmmc / vqmmc,按 pwrseq 拉 GPIO,等 tPOR
③ 送 74+ 个时钟 清总线残留状态(总线宽度初始为 1)
④ CMD0 → 期望 R1 = 0x01(Idle)
⑤ CMD8 (arg=0x1AA) → 期望 R7 回显 0x1AA(v2.00)
失败则走 v1.x 分支(无 CMD8)
⑥ CMD55 + ACMD41 循环 直到 OCR[31] = 1(Busy)
超时上限约 1 s,器件实际通常几十 ms 完成
⑦ CMD2 → 取 128 bit CID
⑧ CMD3 → 卡分配 RCA(SD NAND 常为 0x0001)
⑨ CMD9 → 取 128 bit CSD(容量在 CSD[69:48] 22 bit)
⑩ 解析 CSD → 算出容量(块数 × 512 B)
⑪ CMD7 (rca) → 选卡,进 Transfer 状态
⑫ ACMD51 → 读 SCR,决定电压切换与时序能力
⑬ ACMD6 = 4 → 切 4-bit;失败回退 1-bit
⑭ CMD6 (SDR50/HS) → 切 High-Speed;失败留在默认速
⑮ 分配 mmcblk → register_blkdev(),生成 /dev/mmcblk0
⑯ 扫描分区表 → msdos / GPT,挂载为 mmcblk0p1 …
第 ⑤ 步的 R7 校验失败最容易被忽略
CMD8 的响应里必须包含与参数相同的 0x1AA。host 驱动若没有正确填充 R7 寄存器回显,内核会判定 v2.00 协商失败并回退到 v1.x 分支 —— v1.x 分支下不会走 ACMD41,也不会做 4-bit 与 High-Speed 协商,最终表现为「能识别但很慢」。
因此:如果你的设备是 1-bit 且 25 MHz,那就是 R7 回显或 CMD8 通路的问题,而不是器件限制。
4.3 关键结构体与移植接口
host 驱动要实现的,是 struct mmc_host_ops 里的几个回调。理解这些回调的语义,就理解了 host 驱动的全部工作量。
| 回调 | 作用 | SD NAND 场景下的实现要点 | 谁帮你写好了 |
|---|---|---|---|
.request |
提交一个 MMC 请求(命令 + 数据) | 把命令写进控制器 FIFO、启动中断 / DMA | host 驱动(SDHCI / dw_mmc) |
.send_data |
发送命令的数据段 | 配置 DMA 描述符与目标缓冲区 | host 驱动 |
.send_scr / .get_ro / .get_csd / .get_cid |
读 SCR / CSD / CID | 从控制器 FIFO 读即可 | host 驱动 |
.card_busy |
查询卡是否忙(写 / 擦后) | 读 DAT0 线电平;SD 模式为低电平表示忙 | host 驱动 |
.set_ios |
切换时钟 / 总线宽度 / 时序 | 在 CMD6 / ACMD6 之后被调用 | host 驱动 |
.init_host |
控制器上电初始化 | 设置初始 400 kHz 时钟、总线宽度 1 | host 驱动 |
.get_ro |
判断是否只读 | 无写保护引脚时返回 0 | host 驱动 |
.pre_request / .post_request |
请求前后钩子 | 可用于功耗管理、DMA 映射 | host 驱动或平台 glue |
.enable_sdma_irq / .hsq |
高速路径优化 | 6.x 内核可开 HSQ 提升并发 | host 驱动 |
绝大多数 SoC 上这 9 个回调都已经写好了
这就是「不需要写驱动」的含义:这些回调由内核的 sdhci.c 或 dw_mmc.c 通用实现,SoC 厂商只提供寄存器映射、时钟、pinctrl 与电源的 glue 代码。你的板子如果用这些通用 host 驱动,改设备树就够了。
只有在 SoC 用了厂商私有 host 驱动,或 SoC 的控制器与通用实现有偏差时,才需要动这一层。届时本文第 6 章给出适配清单。
4.4 源码位置速查
| 想搞清楚的问题 | 去看的文件 | 关键符号 |
|---|---|---|
| SD 协议怎么发的 | drivers/mmc/core/sd.c | mmc_attach_sd、mmc_sd_init_card、mmc_decode_ext_csd |
| 命令队列与超时 | drivers/mmc/core/core.c | mmc_start_request、__mmc_start_timeout、mmc_uhs_... |
| 块设备怎么来的 | drivers/mmc/core/block.c | mmc_blk_alloc、mmc_blk_issue_rq、register_blkdev |
| 设备树属性怎么解析 | drivers/mmc/core/of.c | mmc_of_parse、mmc_of_dwcmsc_probe |
| SDHCI 通用实现 | drivers/mmc/host/sdhci.c | sdhci_send_command、sdhci_send_data |
| DesignWare 实现 | drivers/mmc/host/dw_mmc.c | dw_mmc_send_cmd、dw_mmc_setup_data |
| 卡状态与错误 | drivers/mmc/core/block.c | mmc_blk_status、MMC_STAT_CARD |
| 日志怎么加 | drivers/mmc/core/*.c | pr_info / mmc_ 前缀的 dev_info |
5 · 设备树完全指南
这一章是路线 A 的主战场。设备树写对了,设备就通了;写错了,表现为「没反应」「只有 1-bit」「一 mount 就崩」。本章给出可直接使用的完整示例与错误对照表。
5.1 控制器节点必填属性
| 属性 | 作用 | SD NAND 推荐取值 | 写错的后果 |
|---|---|---|---|
status |
节点使能 | "okay" |
"disabled" 则完全不存在 |
bus-width |
数据总线宽度 | <4> |
写 1 则吞吐掉到 1/4 |
max-frequency |
最大工作时钟(Hz) | <50000000>(HS) |
超规格则数据 CRC 错 / 枚举失败 |
non-removable |
标记常驻设备 | 存在 | 不加则反复探测卡检测引脚,启动变慢、功耗上升 |
cap-sd-highspeed |
声明支持 SD High-Speed | 存在 | 不加则停在默认速 25 MHz |
vmmc-supply |
核心供电 regulator | 指向板级稳压节点 | 不配则用默认电源,可能限流不足 |
vqmmc-supply |
I/O 供电 regulator | 1.8 V 器件必配 | I/O 电平不对则识别失败 |
disable-wp |
禁用写保护引脚检测 | 存在(SD NAND 无 WP 脚) | 无此引脚时不配会有告警 |
cd-gpios |
卡检测引脚 | 不配(用 non-removable) | 配了常有效电平也能工作,但不如 non-removable 干净 |
pinctrl-0 / -names |
引脚复用 | 指向 4-bit MMC 引脚组 | DAT1~3 未复用则只跑 1-bit |
clocks / clock-names |
控制器时钟 | 指向 SoC 的 MMC 时钟源 | 时钟错误则控制器工作异常 |
5.2 SD NAND 相关能力属性详解
non-removable:SD NAND 贴片常驻,没有插入拔出事件。加这个属性后内核不再监听卡检测脚,枚举一次就结束。cap-sd-highspeed:声明控制器与卡都支持 SD High-Speed(50 MHz)。SD NAND 支持此模式。不加则停在默认速 25 MHz。cap-sd-uhs-sdr50等 UHS 属性:SD NAND 一般不支持 UHS 模式,不要加。加了内核会去尝试协商 SDR50,失败后回退,虽不至于不通但会留下误报日志。no-1-8-v:如果 SoC 支持 1.8 V 切换而板上是 3.3 V 器件,加这个属性避免内核做无用的电压切换尝试。sdhci-caps-mask:限速神器。内核默认会尝试从最高速率往下协商,把不需要的模式掩掉可以让启动更干净。例如只跑 SD HS:sdhci-caps-mask = <0x00000007 0x00000000>。cap-power-off-card:声明支持断电。配合 VCC 门控电路时,内核可以在 suspend 时真正断电(第 11 章展开)。
5.3 可直接使用的完整示例
5.3.1 3.3 V SD NAND(WSON-8)完整节点
&soc {
sdhci0: mmc0: mmc@fe000000 {
compatible = "arm,pl011-dw-apb-uart"; /* 占位,按 SoC 实际改 */
/* 上面这行必须是该 SoC 对应的 host 驱动 compatible */
reg = <0x0 0xfe000000 0x0 0x1000>;
interrupts = <0 12 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk_mmc>;
clock-names = "ahb";
/* 4-bit 总线,建议 50 MHz(High-Speed) */
bus-width = <4>;
max-frequency = <50000000>;
/* SD NAND 贴片常驻 */
non-removable;
/* 支持 SD High-Speed */
cap-sd-highspeed;
/* 不做 1.8 V 切换(3.3 V 器件) */
no-1-8-v;
/* 声明支持断电,配合 VCC 门控 */
cap-power-off-card;
/* SD NAND 无 WP 脚 */
disable-wp;
/* 电源 */
vmmc-supply = <&vdd_sd>;
vqmmc-supply = <&vdd_sd_io>;
/* 限速到 SD HS,避免内核尝试更高速率 */
sdhci-caps-mask = <0x00000007 0x00000000>;
/* 引脚复用 */
pinctrl-names = "default";
pinctrl-0 = <&sd0_mmc_pins>;
status = "okay";
};
/* 供电:能力要留足,峰值 100 mA,建议 ≥200 mA */
vdd_sd: vdd-sd {
compatible = "regulator-fixed";
regulator-name = "vdd-sd";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-enable-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
};
};
&pinctrl {
/* 必须是 4-bit 组:CLK + CMD + DAT0~3 */
sd0_mmc_pins: sd0-mmc {
pinmux = <PINMUX_MMC0_CLK PINMUX_FUNC_SDMMC0 7>,
<PINMUX_MMC0_CMD PINMUX_FUNC_SDMMC0 7>,
<PINMUX_MMC0_DAT0 PINMUX_FUNC_SDMMC0 7>,
<PINMUX_MMC0_DAT1 PINMUX_FUNC_SDMMC0 7>,
<PINMUX_MMC0_DAT2 PINMUX_FUNC_SDMMC0 7>,
<PINMUX_MMC0_DAT3 PINMUX_FUNC_SDMMC0 7>;
};
};
说明:compatible、reg、interrupts、clocks 必须按 SoC 手册改。pinmux 宏名各 SoC 不同。这段是模板,不是可直接编译的代码。
5.3.2 用 GPIO 控制负载开关(VCC 门控)的写法
&soc {
mmc0: mmc@fe000000 {
/* ... 同 5.3.1 ... */
vmmc-supply = <&vdd_sd_gated>;
cap-power-off-card;
non-removable;
};
/* 带使能脚的负载开关 regulator */
vdd_sd_gated: vdd-sd-gated {
compatible = "regulator-gpio";
regulator-name = "vdd-sd-gated";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
enable-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
enable-active-high;
startup-delay-us = <1000>; /* 关键:等待 tPOR */
};
};
/* 用 mmc-pwrseq 显式控制上电顺序(配合 SD NAND 的 VCC 严格时序) */
&mmc0 {
mmc-pwrseq = <&sd_nand_pwrseq>;
};
&sd_nand_pwrseq {
compatible = "mmc-poweroff-sequence";
pwrseq-tokens = "reset-off", "vdd-sd-power-on", "reset-on";
reset-off-ms = <10>;
vdd-sd-power-on-delay-ms = <2>; /* 必须 > tPOR */
};
&vdd_sd_gated {
vdd-sd-power-on-supply = <&vdd_3v3>; /* 上游 3.3 V */
};
/* 顺序标签定义(部分内核版本需要,命名以 SDK 为准) */
&pwrseq_reset_off { reset-off-gpios = <&gpio1 6 GPIO_ACTIVE_LOW>; };
mmc-pwrseq 的语法各版本有差异
设备树里的 mmc-pwrseq 属性在 Linux 4.x~6.x 之间经历过多轮调整:早期用 pwrseq-tokens + *-delay-us,较新版本改为 reset-gpios、vmmc-supply-delay-us 等更细的字段。写之前先看内核源码 drivers/mmc/core/of.c 里的 mmc_of_parse_mmc_pwrseq(),按你手上这版内核实际支持的字段名写。
多数情况其实不需要写 pwrseq —— regulator 的 startup-delay-us 加上内核自带的 74 时钟已足够。
5.4 设备树常见错误对照表
| 症状 | 最可能的原因 | 检查点 | 修复 |
|---|---|---|---|
| 完全没有 mmc 相关日志 | 节点 status="disabled",或 host 驱动未编译 |
dtb 是否真的被加载(cat /proc/device-tree/...) |
改 status;确认 CONFIG_MMC_xxx |
mmc0: Timeout waiting for hardware interrupt |
CMD 线浮空 / 电压不对 / tPOR 不足 | 示波器看 CMD 是否有响应;量 VCC 实际电压 | 加上拉;改 regulator 电压;加 startup-delay |
mmc0: error -84 while trying to send r/w command |
CRC 错,通常是信号完整性 | 降 max-frequency 到 25 MHz 试 |
由低到高逐级提速定位临界频率;查布线 |
| 能识别但只有 1-bit | DAT1~3 pinmux 未配 / ACMD6 失败 | cat /sys/kernel/debug/mmc0/ios |
配 4-bit pinmux 组;量 DAT2/DAT3 翻转 |
| 能识别但只有 25 MHz | 缺 cap-sd-highspeed,或 CMD6 切换失败 |
cat /sys/kernel/debug/mmc0/ios 看 clock |
加 cap-sd-highspeed;查 R7 回显 |
Got data interrupt 0x... even though no data operation was in progress |
残留中断状态,host 驱动未清干净 | 看这条之前是否有 CRC 错 | 先解决真正的 CRC 错;这条是后果不是原因 |
mmcblk0: error -84 写入时随机失败 |
供电不足 / 峰值电流超限流 | 量 VCC 在写负载下的跌落波形 | 提高 regulator 限流;加强去耦 |
写入时报 readonly |
内核判定卡只读 | 看设备树有无 wp-gpios 而该引脚悬空 |
改用 non-removable + 去掉 wp-gpios |
Device or resource busy 挂载失败 |
分区表被占用 / 已有分区 | lsblk / cat /proc/partitions |
确认设备名与分区号 |
| 改了 dtb 但行为没变 | 加载的不是你改的那份 dtb | ls -l /boot/dtb/;启动日志里的 dtb 路径 |
改对文件;某些平台需重新打包 boot.img |
/sys/kernel/debug/mmc*/ios 是最有用的一行命令
它直接打印内核当前认为的时钟、总线宽度与时序模式:
cat /sys/kernel/debug/mmc0/ios
clock: 50000000 Hz
actual clock: 49631200 Hz
vdd: 18 19 20 21 22 23 24 25 26 27 28 29 30 31
bus mode: 2 (4-bit)
timing spec: 3 (sd high-speed)
signal voltage: 0 (3.30 V)
driver type: 0 (driver type B)
需要先 mount -t debugfs none /sys/kernel/debug。排查「为什么只有 1-bit」「为什么没提速」时,先看这四行:bus mode、timing spec、actual clock。
后续章节请下载「芯参谋」查看
……(篇幅原因, 还有10章· 详见芯参谋→方案设计)
打开路径:🔧解决方案&应用市场分析 -> 原理方案设计 -> 嵌入式驱动&系统开发 -> Linux_SD_NAND_驱动开发指南
相关报告 · 1
- 雾化器(Nebulizer)_市场分析市场应用分析
- 信创硬件(XC Hardware)_市场分析市场应用分析
- 分区错位_坏块跳过不一致_量产排查调试&解决方案
- FreeRTOS_系统开发指南原理方案设计
- 炬芯(Actions)平台_SPI_NOR_量产排查调试&解决方案
相关报告 · 2

浙公网安备 33010602011771号