[toc]
MMCI(ARM PrimeCell PL18x 主机控制器驱动):全面了解与深度解析
文件路径 / 技术中文名 / 功能概述
- 文件路径:
drivers/mmc/host/mmci.c - 技术中文名:MMCI(ARM PrimeCell PL180/PL181 及衍生控制器的 MMC/SD/SDIO Host 驱动)
- 功能概述:该文件是 Linux MMC host 层驱动实现,负责把 MMC core 发下来的命令与数据请求,转换成 MMCI/SDMMC 控制器寄存器操作,覆盖命令发送、响应接收、数据通路控制、PIO 与 DMA 传输、中断处理、时钟与电源控制,以及不同硬件变体的差异适配。
介绍
drivers/mmc/host/mmci.c 属于 Linux MMC 子系统里的 Host Controller Driver。它不负责块层调度,也不负责文件系统,而是直接驱动一类具体的 MMC/SD/SDIO 控制器 IP。
从主线实现看,这个文件不是“单一芯片专用驱动”,而是一个覆盖面较广的家族驱动骨架。它最初面向 ARM PrimeCell PL180/PL181,后续又吸收了 ST、Ux500、STM32、Qualcomm 等衍生实现。也就是说,mmci.c 的核心价值不只是“驱动一个控制器”,而是通过统一骨架管理多种相似但不完全一致的控制器变体。
本文分析范围以 Linux 主线 drivers/mmc/host/mmci.c 和相邻头文件 drivers/mmc/host/mmci.h 为主,重点解释:
- 驱动在 MMC 子系统中的位置
- 寄存器语义与执行路径
- 变体抽象方式
- PIO / DMA / busy detect 等关键机制
- 与
sdhci.c、dw_mmc.c、mmc_spi.c的差异
本文不展开 MMC 协议本身的所有细节,也不展开块层与文件系统行为。
历史与背景
1. 诞生背景
这类驱动出现的根本原因,是 PL180/PL181 这类控制器并不遵循 SDHCI 标准寄存器模型,不能直接套用 sdhci.c 这类通用主机驱动。Linux 需要一个专门面向这类控制器寄存器布局和状态机设计的 Host 驱动。
在它出现之前,Linux 生态中解决 MMC/SD 控制器支持问题的方式,通常是:
- 对标准 SD Host Controller 使用 SDHCI 驱动
- 对 Synopsys DesignWare 控制器使用
dw_mmc - 对没有原生 MMC Host 的场景,用
mmc_spi之类的替代路径 - 对专有控制器单独写 Host 驱动
旧方案的痛点不是“没有存储卡支持”,而是控制器模型高度异构:
- 寄存器布局不同
- 时钟控制方式不同
- 数据通路状态机不同
- DMA 路径不同
- busy 检测和 SDIO 支持差异较大
因此,PL18x 这一类控制器必须有自己的驱动实现。
2. 发展历程
从源码版权和当前主线实现结构看,mmci.c 是一条历史较长的驱动线。它早期主要服务 ARM PrimeCell PL180/PL181,后续逐步吸收了 ST-Ericsson、Ux500、STM32 和 Qualcomm 等变体支持。
对今天使用方式影响最大的演进最大的演进有三类:
第一类是变体抽象的形成。
当前实现不是为每个 SoC 重写一份驱动,而是通过 variant_data 把差异抽象出来。不同变体可以有不同的:
- FIFO 大小
datalength_bitsdatactrl_blocksz- 时钟寄存器位定义
- 是否支持任意 block size
- 是否支持 busy detect
- 是否支持 SDIO IRQ
- DMA 方式与限制
- 最大频率上限
这使得 mmci.c 从“一个驱动一个控制器”演进成“一个骨架驱动多个相近控制器”。
第二类是设备树绑定的规范化。
早期很多板级信息依赖 platform data 或早期 DT 约定,后来逐步转向统一的绑定描述。当前绑定文档明确把 PL180、PL181、PL18x 通配以及 STM32 的相关兼容项纳入统一 schema,约束了寄存器窗口、中断数量、时钟数量、DMA 通道名等资源表达方式。
第三类是厂商增强逻辑外提。
主线中已经存在与 mmci.c 配套的变体文件,例如 STM32 和 Qualcomm 的增强逻辑文件。这说明主线维护方向不是把所有厂商特性继续堆进一个大文件,而是保留 mmci.c 作为公共骨架,再把强变体特性拆出。
3. 社区与生态现状
从主线文档和当前源码组织方式看,这条驱动线仍在维护,并未废弃。
它的典型应用场景主要在:
- ARM 参考设计平台
- ST / Ux500 / Nomadik 一类历史 ARM SoC
- STM32MP 及相关 SDMMC 路线
- 某些 Qualcomm 衍生控制器场景
- 嵌入式 Linux、工业控制、板级 BSP
它不是云平台或通用服务器环境里最常被直接讨论的“平台技术选型点”,但在嵌入式 Linux 和 SoC BSP 层面仍然非常实际。
核心原理与设计
1. 核心工作原理
1.1 在 Linux 系统中的位置
mmci.c 位于 MMC 子系统的 host 层。它接收 MMC core 传下来的请求,并将其翻译成具体寄存器操作。核心职责包括:
- 设置命令参数与命令寄存器
- 启动命令路径状态机
- 配置数据长度、方向、块大小与 DMA 位
- 启动数据路径状态机
- 响应命令中断、数据中断、FIFO 中断
- 在 PIO 和 DMA 之间切换
- 在请求结束时回收状态并通知 MMC core
因此,这个驱动的本质是“MMC core 与具体控制器硬件之间的桥接层”。
1.2 寄存器模型
从 mmci.h 可以看出,这个驱动的寄存器模型非常直接,关键寄存器包括:
MMCIPOWER:电源控制MMCICLOCK:时钟控制MMCIARGUMENT:命令参数MMCICOMMAND:命令寄存器MMCIDATATIMER:数据超时MMCIDATALENGTH:数据长度MMCIDATACTRL:数据控制MMCISTATUS:状态寄存器MMCICLEAR:中断与状态清除MMCIMASK0/MMCIMASK1:中断屏蔽
其中两个状态机最关键:
- CPSM:Command Path State Machine,负责命令发送与响应接收
- DPSM:Data Path State Machine,负责数据读写路径
驱动围绕这两个状态机组织命令与数据请求,整体逻辑很贴近硬件。
1.3 关键执行流程
结合文件中的核心辅助函数,可以把主路径概括为:
- 上层提交一个
mmc_request - 驱动校验数据请求是否合法
- 根据请求类型配置命令与数据参数
- 写入参数寄存器、数据长度寄存器、数据控制寄存器
- 启动命令路径状态机
- 如有数据传输,则通过 PIO 或 DMA 推进数据通路
- 由命令中断、数据中断、FIFO 中断驱动状态推进
- 结束数据路径,清理中断屏蔽与状态位
- 将请求结果回送给 MMC core
对该文件来说,真正重要的不是某一个单独函数,而是“命令通路 + 数据通路 + 中断推进 + 结束回调”这一整条链。
1.4 变体抽象方式
这是 mmci.c 最核心的设计点。
驱动没有把所有控制器差异硬编码在大量 if (soc == ...) 里,而是通过 variant_data 描述硬件差异,通过 mmci_host_ops 描述可替换行为。
variant_data 里直接体现的差异包括:
- FIFO 容量与半满阈值
- 最大时钟上限
- 数据长度位宽
- 块大小位域
- 是否允许任意块大小
- 是否支持 busy detect
- 是否支持 SDIO IRQ
- 是否 STM32 特有时钟分频
- 是否存在 QCOM 特有时钟/数据位
- 是否存在 DMA LLI 能力
这意味着阅读 mmci.c 时,不能把某一条路径当成全部硬件都成立的“统一真相”。许多行为都依赖 variant_data。
1.5 数据路径约束
mmci_validate_data() 明确体现了一个关键硬件边界:
- 如果变体没有声明支持任意 block size,那么 block size 必须是 2 的幂
- 否则驱动会直接返回
-EINVAL
这说明很多“请求失败”并不是 MMC core 的抽象问题,而是控制器寄存器编码能力本身的限制。
同时,不同变体的 datalength_bits 和 datactrl_blocksz 也不同,直接影响:
- 单次请求最大可描述数据长度
- 块大小编码上限
- 总块数能力边界
这是生产环境做大块传输、DMA 分段和性能分析时必须考虑的硬件前提。
1.6 PIO 与 DMA
mmci.c 并不强制 DMA,它保留了 PIO 和 DMA 两条路径。
DMA 相关实现显示,这个驱动会尝试申请 "rx" 与 "tx" 两个 DMA 通道。若资源不足,DMA 可能被关闭,回退到 PIO。这里有几个实际工程意义很强的点:
- 如果只有 RX 通道存在,驱动会尝试复用它
- 如果 RX/TX 资源不足,DMA 可能整体不可用
- DMA finalize 阶段会检查 FIFO 里是否还残留数据
- 如果发现 DMA 控制器无法正确处理某些 scatter-gather 场景,驱动会记录为错误,并采取“放弃 DMA、转回 PIO”的保守策略
这说明 mmci.c 在 DMA 路径上不是“激进追求理论性能”,而是优先保证正确性与可回退性。
1.7 busy detect
Ux500 / STM32 相关变体的一个重要增强能力是 busy detect。
从 mmci.c 中的 busy 处理逻辑可以看出,某些变体会监视 DAT0 忙状态,并通过状态机跟踪:
- 是否进入忙状态
- 是否等到忙开始中断
- 是否等到忙结束中断
- 是否发生忙等待超时
这类逻辑的存在,说明驱动不仅处理普通命令完成,还要处理响应后设备继续拉低 DAT0 的时序场景。R1B 类命令、某些 SDIO/存储器操作,以及厂商硬件差异,都会影响这里的行为。
1.8 时钟与电源控制
mmci.h 表明,这类控制器的时钟和电源寄存器语义在不同变体之间差异明显:
- 原始 ARM 变体使用一套较早的时钟/电源位定义
- ST 变体复用了部分位做方向控制
- STM32 变体的时钟和电源寄存器含义又发生变化
所以 set_ios 这一类操作在这个驱动中不是“简单设置频率和总线宽度”,而是需要结合变体位定义生成寄存器值。对板级 bring-up 来说,这一点很关键,因为同样的 bus-width、时钟和电压切换需求,在不同变体上的寄存器动作不相同。
2. 设计目标
mmci.c 的设计目标可以概括为:
兼容性
用一个驱动骨架覆盖 PL180/PL181 及多个衍生实现。可维护性
通过variant_data和可替换操作减少重复驱动代码。资源控制与性能平衡
支持 DMA,但保留 PIO 回退;优先保证可靠性。适配现实硬件差异
明确吸收不同厂商在 FIFO、busy detect、时钟位、DMA 行为上的差异。可观测性与可排障性
错误路径会保留较明确的日志与失败回退行为,便于定位 DMA、块大小、FIFO、忙检测问题。
3. 核心优势
优势一:适合做家族化控制器适配
对于 PL18x 系列及其衍生控制器,这个驱动的复用性很强。BSP 团队不需要为每个 SoC 分叉一份完整驱动,只需在合理范围内扩展变体数据和少量变体操作。
优势二:寄存器语义清晰,调试路径直接
它不经过一层行业标准寄存器抽象,而是直接使用控制器原生语义组织命令和数据通路。对于硬件调试、时序排障、寄存器对照分析来说,这种结构是有利的。
优势三:对硬件边界表达明确
这个驱动对块大小、FIFO、DMA、busy detect 等硬件边界表达得比较直接。虽然这会增加代码分支,但也让很多异常行为能快速回到硬件约束本身,而不是停留在“MMC 请求失败”这种模糊层面。
优势四:保守而实用的 DMA 策略
DMA 路径遇到不可控问题时,驱动会主动降级,而不是继续冒险。这种设计在嵌入式量产环境里通常比追求极限吞吐更有价值。
4. 局限性与缺点
局限一:变体复杂度高
variant_data 虽然提升了复用性,但也让代码理解成本显著上升。阅读时如果忽略当前实际变体,很容易把某个变体的行为误认为通用行为。
局限二:硬件能力边界明显
这类控制器在以下方面往往存在天然限制:
- 块大小编码方式
- 数据长度位数
- DMA 能力不一致
- FIFO 深度有限
- busy detect 并非所有变体都有
- 时钟与电源寄存器语义不统一
这意味着它并不是一类“能力完全现代化、统一化”的主机控制器驱动。
局限三:板级依赖较重
时钟、复位、供电、GPIO、DMA 通道、中断线、pinctrl 描述稍有偏差,就可能出现:
- 探测成功但读写不稳定
- DMA 不生效
- PIO 中断异常
- 高速模式不稳定
- 电压切换失败
局限四:默认参数容易引发误判
源码里模块参数 fmax 的默认值很保守。如果板级资源没有正确提供最大频率信息,驱动可能工作但性能异常低,容易被误判为总线质量问题或 DMA 故障。
5. 不适用场景
场景一:控制器本身是标准 SDHCI
如果硬件是标准 SD Host Controller,优先使用 SDHCI 驱动。此时使用 MMCI 思路没有意义,因为两者的寄存器模型和能力组织方式不同。
场景二:控制器是 DesignWare MMC
如果硬件是 Synopsys DesignWare 路线,应优先使用 dw_mmc,因为它有自己完整的寄存器模型、IDMAC/EDMAC 逻辑和平台适配层。
场景三:系统没有原生 MMC Host,只能走 SPI
这时应使用 mmc_spi 一类驱动。mmci.c 不适合这种场景。
场景四:需要高度依赖更统一的现代标准能力
如果项目特别强调标准控制器能力、通用平台兼容性和更大生态复用,通常优先考虑 SDHCI 家族,而不是 MMCI 家族。
使用场景
1. 适合使用的场景
场景一:基于 PL180 / PL181 的参考设计或历史 ARM 平台
- 场景背景:控制器与 ARM PrimeCell PL180/PL181 兼容
- 为什么适合:这是该驱动最原生的适配对象
- 实际收益:驱动路径短,寄存器映射清晰,bring-up 成本低
- 注意问题:先确认 DT 或平台资源中的时钟、电源与中断信息完整
场景二:ST / Ux500 / Nomadik 派生平台
- 场景背景:控制器在 PL18x 基础上加入 ST 方向控制、busy detect、扩展位
- 为什么适合:主线已经存在对应变体抽象
- 实际收益:无需长期维护大规模厂商私有分叉
- 注意问题:DT 属性和方向控制相关资源要准确描述
场景三:STM32 SDMMC 路线
- 场景背景:控制器沿用 MMCI 家族思路,但寄存器和能力已有较大扩展
- 为什么适合:主线
mmci.c已纳入 STM32 v1/v2/v3 相关变体支持 - 实际收益:能直接利用主线对 FIFO、busy detect、IDMA/LLI 等差异的处理
- 注意问题:STM32 能力不能外推为所有 MMCI 控制器都具备
场景四:老平台量产维护
- 场景背景:系统已经大规模部署,关注的是稳定性和内核升级可控性
- 为什么适合:主线对这条驱动线的兼容性维护较长
- 实际收益:升级时可继续沿主线修复思路推进
- 注意问题:升级时必须重点回归 DMA、频率、电压与中断路径
2. 不推荐使用的场景
- 标准 SDHCI 控制器平台
- DesignWare MMC 平台
- 只通过 SPI 接卡的平台
- 追求统一标准主机能力矩阵的平台
- 板级资源描述长期不规范、维护成本过高的平台
原因通常来自:
- 实现模型不匹配
- 性能路径不匹配
- 运维复杂度不划算
- 硬件边界与项目需求不匹配
3. 生产实践建议
配置关注点
生产环境最应该优先检查的配置点是:
max-frequencybus-width- 中断数量与 wiring
vmmc-supply/vqmmc-supply- DMA 通道定义
- pinctrl 与方向控制相关资源
- reset 资源
- 变体专用属性
监控指标
建议至少关注:
- 中断计数是否持续增长
- DMA 是否真正启用
- 是否频繁回退到 PIO
- 命令超时、数据超时、CRC 错误数量
- 高频读写时的稳定性
- 实际工作频率是否符合预期
排障方法
建议按以下顺序排查:
- 看启动日志是否正确识别控制器
- 看板级时钟和供电是否到位
- 看
/proc/interrupts中命令 IRQ / FIFO IRQ 是否增长 - 判断当前走的是 DMA 还是 PIO
- 用读写压力测试复现
- 再回头核查 block size、scatter-gather、FIFO 与 busy detect 相关边界
风险控制
- 升级内核时优先灰度验证 DMA 路径
- 不要只测小文件读写,要测长时间顺序读写和高并发 I/O
- 对 SDIO 设备要单独验证中断与 busy 行为
- 对历史平台要保留 PIO 回退验证路径
4. 命令、配置与排障示例
常用命令
1 | dmesg | grep -iE 'mmc|mmci' |
常见验证命令
1 | cat /sys/kernel/debug/clk/clk_summary | grep -i mmc |
使用 mmc_test 做 Host 驱动验证
1 | modprobe mmc_test |
典型 DT 关注点
1 | mmc@... { |
常见误区
- 驱动加载成功就认为时钟、电压、DMA 都正确
- 把某个变体能力当成所有 MMCI 变体通用能力
- 忽略
max-frequency - 只测 PIO 小流量,不测 DMA 长时间压力
- 只看卡能枚举,不看高速模式是否稳定
对比分析
这里把 mmci.c 与 sdhci.c、dw_mmc.c、mmc_spi.c 做横向比较。
1. 实现方式
MMCI
基于 PL18x 家族寄存器模型实现,是一个“公共骨架 + 变体数据 + 变体操作”的专用驱动。
SDHCI
基于行业标准 SD Host Controller Interface 寄存器模型实现,平台侧通常通过 glue 层适配。
DesignWare MMC
基于 Synopsys 专有控制器实现,通常带有自己的一整套 DMA、描述符与平台适配逻辑。
MMC over SPI
本质不是原生 MMC Host 控制器,而是通过 SPI 协议胶水层去访问存储卡。
2. 性能开销
MMCI
性能上限较依赖具体变体和 DMA 质量。DMA 成熟时可用性较好,但 PIO 回退代价明显。
SDHCI
通常在主流平台上性能和标准能力更均衡,尤其在 ADMA 等能力完整时更有优势。
DesignWare MMC
在 DMA 体系完善的平台上性能通常较好,但驱动路径更重,调试复杂度也更高。
MMC over SPI
性能明显弱于原生 Host 模式,更适合作为替代方案而不是高性能方案。
3. 资源占用
MMCI
驱动本身常驻开销不高,但对 DMA 通道、FIFO、中断与板级资源依赖较强。
SDHCI
资源占用通常也不高,但现代平台上常叠加 PHY、tuning、平台 glue。
DesignWare MMC
常见地会引入更复杂的 DMA 描述符和平台数据结构,整体更重。
MMC over SPI
不需要原生 MMC Host IP,但会占用 SPI 控制器与总线时序资源。
4. 隔离级别
这几类技术都不是安全隔离机制。
它们都运行在内核态,所谓差异主要体现在控制器功能边界、DMA 路径和总线模型,而不是进程级或硬件级安全隔离。
5. 启动速度
MMCI
初始化路径相对直接,尤其在老平台和基础变体上较轻量。
SDHCI
初始化也较快,但现代控制器可能附带更多能力探测和参数配置。
DesignWare MMC
初始化通常更复杂,尤其涉及 DMA 和平台 glue 时。
MMC over SPI
通常不以启动快见长,且受 SPI 交互模式限制。
6. 运维复杂度
MMCI
中等偏高。问题不在代码量本身,而在变体差异和板级资源正确性。
SDHCI
通常中等。标准化程度高,生态经验更丰富。
DesignWare MMC
通常偏高。平台差异和 DMA 复杂度都更明显。
MMC over SPI
实现边界简单,但性能与总线共享问题限制很大。
7. 适用场景差异
- 优先选 MMCI:硬件明确属于 PL18x 家族或其衍生实现
- 优先选 SDHCI:硬件兼容标准 SDHCI
- 优先选 DesignWare:硬件是 Synopsys DesignWare MMC
- 优先选 MMC over SPI:没有原生 MMC Host,只能走 SPI
总结表
| 对比维度 | MMCI | SDHCI | DesignWare MMC | MMC over SPI |
|---|---|---|---|---|
| 实现方式 | PL18x 家族专用骨架 + 变体抽象 | 标准寄存器模型 + 平台 glue | 专用控制器驱动 + 自有 DMA 体系 | SPI 协议胶水层 |
| 性能开销 | 依赖变体与 DMA 质量,PIO 回退代价明显 | 通常较均衡,标准能力成熟 | 通常较强,但实现更重 | 较高,原生模式通常更优 |
| 资源占用 | 中等 | 中等 | 中等偏高 | 占用 SPI 总线资源 |
| 隔离级别 | 内核态驱动,无额外安全隔离 | 内核态驱动,无额外安全隔离 | 内核态驱动,无额外安全隔离 | 内核态驱动,无额外安全隔离 |
| 启动速度 | 较快 | 较快到中等 | 中等 | 中等 |
| 运维复杂度 | 中等偏高 | 中等 | 偏高 | 中等 |
| 推荐场景 | PL18x 及衍生控制器 | 标准 SDHCI 控制器 | DesignWare 控制器 | 无原生 MMC Host 的替代路径 |
总结
1. 关键特性总结
mmci.c 最关键的特性可以总结为:
- 它是 Linux 主线中面向 PL18x 家族的 Host 驱动骨架
- 它的核心设计不是单一控制器驱动,而是变体抽象
- 它直接围绕 CPSM / DPSM 与状态寄存器组织执行路径
- 它同时支持 PIO 与 DMA,并保留保守回退策略
- 它非常依赖板级资源描述的正确性
- 它的很多行为都必须在具体变体前提下理解
mmci_probe mmci_remove mmci_save mmci_restore mmci_runtime_suspend mmci_runtime_resume mmci_dev_pm_ops mmci_ids MODULE_DEVICE_TABLE mmci_driver module_amba_driver module_param MODULE_DESCRIPTION MODULE_LICENSE 探测建链、变体分流、运行时电源管理与 AMBA 注册路径梳理
作用与实现要点
这段代码把 MMCI 主机的“设备出现后如何接入 Linux MMC 子系统”拆成四层:
先由 AMBA 总线用 mmci_ids 做匹配,把命中的 id->data 交给 mmci_probe() 作为 variant;probe()
再把时钟、复位、寄存器映射、OCR、电气能力、IRQ、DMA、PM 参数和 mmc_host 串起来;mmci_dev_pm_ops 把运行时挂起/恢复与系统休眠复用到同一组保存/恢复寄存器路径;module_amba_driver(mmci_driver) 则把整个 mmci_driver 对象注册进 AMBA 框架。struct amba_id 的 mask 决定比较哪些硬件 ID 位,data 是驱动私有数据,命中后会成为后续能力分支的总开关;struct dev_pm_ops 同时承载系统休眠与运行时 PM 回调;device_driver.probe_type = PROBE_PREFER_ASYNCHRONOUS 表示该驱动倾向异步探测以减少启动阻塞。
PrimeCell 设备在 Devicetree 下也会以 amba_device 形式出现,因此这里同时接受平台数据和 DT 路径。([Linux内核文档][1])
代码里还有一个容易忽略的点:mmc->ops 与 host->mmc_ops 都指向全局 mmci_ops,
而 probe() 又会按变体去写 mmci_ops.card_busy、enable_sdio_irq、ack_sdio_irq。
这意味着这里不是只给当前 host 绑定私有回调,而是在修改一个全局回调表,再由后续 MMC 核心经 mmc->ops 进入忙检测和 SDIO IRQ 控制路径。
另一条重要回退链是 module_param(fmax, uint, 0444):当板级或 DT 没有给出 mmc->f_max 时,probe() 会退回到 fmax,
再和输入时钟或变体上限比较后取较小值。模块参数既可通过内核命令行携带,也可在模块装载时通过 modprobe 传入;MODULE_DEVICE_TABLE() 会把匹配表导出给自动装载链路;MODULE_LICENSE() 则给模块装载器和用户态工具提供许可信息。([Linux内核文档][2])
mmci_probe 设备探测、资源建链与主机注册
1 | /** |
mmci_remove 设备解绑与硬件静默
1 | /** |
mmci_save 运行时挂起前保存并静默寄存器
1 | /** |
mmci_restore 恢复寄存器并重新打开中断
1 | /** |
mmci_runtime_suspend 运行时挂起入口
1 | /** |
mmci_runtime_resume 运行时恢复入口
1 | /** |
mmci_dev_pm_ops 电源管理回调表
这张表会通过 mmci_driver.drv.pm 挂到驱动对象上,由 PM 核心在系统休眠和运行时 PM 阶段读取。SYSTEM_SLEEP_PM_OPS() 让系统休眠复用 runtime force suspend/resume 封装,RUNTIME_PM_OPS() 则把日常空闲挂起/恢复明确绑定到 mmci_runtime_suspend() 与 mmci_runtime_resume()。struct dev_pm_ops 正是 Linux 设备电源管理操作的标准承载体。([Linux内核文档][4])
1 | /** |
mmci_ids AMBA 设备匹配表
mmci_ids 会被 AMBA 总线框架拿来做设备匹配;匹配规则是 ((hardware device ID) & mask) == id,而 data 是驱动私有数据,所以这里的 &variant_* 会直接决定 mmci_probe() 中的 variant 指针,进而改写时钟分频、busy detect、SDIO IRQ、open-drain、寄存器位宽和初始化函数等能力选择。PrimeCell 节点在 DT 下也会被注册成 amba_device,因此这张表既服务于传统 PrimeCell ID 匹配,也服务于 DT 创建设备后的 AMBA 身份。([Linux内核文档][1])
1 | /** |
MODULE_DEVICE_TABLE 自动装载导出项
MODULE_DEVICE_TABLE(amba, mmci_ids) 会把这张 AMBA 匹配表导出给构建与自动装载链路,使用户态能根据设备别名找到这个模块。内核文档明确说明驱动通常会把设备 ID 表通过 MODULE_DEVICE_TABLE() 导出给用户空间;模块自动装载体系会利用这些映射。([Linux内核文档][1])
1 | /** |
mmci_driver AMBA 驱动描述符
mmci_driver 是真正交给 AMBA 框架消费的驱动对象:.id_table = mmci_ids 决定谁能命中,.probe = mmci_probe 决定命中后怎么建主机,.remove = mmci_remove 决定解绑时怎么静默并回收,.drv.pm = pm_ptr(&mmci_dev_pm_ops) 决定 PM 核心在这个驱动上的挂起/恢复入口,.probe_type = PROBE_PREFER_ASYNCHRONOUS 表示驱动愿意异步探测。struct device_driver 的 pm 与 probe_type 含义都由驱动核心统一解释。([Linux内核文档][5])
1 | /** |
module_amba_driver 驱动注册封装宏
module_amba_driver(mmci_driver) 不是新的驱动对象,而是把“注册/注销这个 mmci_driver”封成模块入口与退出代码。历史补丁说明这个宏正是为了省掉手写 amba_driver_register() / amba_driver_unregister() 与 module_init() / module_exit() 的样板代码而加入的。([lists.infradead.org][6])
1 | /** |
module_param 模块参数入口
module_param(fmax, uint, 0444) 把 fmax 暴露成模块参数。对这段代码来说,它最重要的影响不是“参数存在”,而是 mmci_probe() 里当 mmc->f_max 没有被板级或 DT 填出来时,会退回到 fmax。内核文档说明模块参数既可以从内核命令行传入,也可以在 modprobe 装载模块时传入。([Linux内核文档][7])
1 | /** |










