news 2026/10/6 7:35:03

RT-Thread SDIO/MMC 设备模型(DM):基于 Device Model 的宿主控制器驱动框架详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RT-Thread SDIO/MMC 设备模型(DM):基于 Device Model 的宿主控制器驱动框架详解
  • 操作系统
  • 嵌入式
  • 物联网
  • 嵌入式OS
  • RTOS

【免费下载链接】rt-thread

RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/

项目地址:https://gitcode.com/gh_mirrors/rt/rt-thread
点击查看免费下载

RT-Thread 的 SDIO/MMC 设备模型(Device Model,简称 DM)是在传统 MMC/SD 核心层之上新增的一层驱动辅助框架,它让 MMC/SD 宿主控制器(Host Controller)的命名、设备树(OFW)解析、电源供给(vmmc/vqmmc)以及 SDHCI 平台驱动全部纳入统一的 DM 设备管理体系中。本文以components/drivers/sdio目录下的实际源码为准,讲解 DM 层的编译开关、核心 API(sdio_host_set_name、sdio_ofw_parse、sdio_regulator_*)与 SDHCI 平台接入方式,帮助读者理解如何基于 DM 快速编写可移植、可配置的 SD/MMC 宿主驱动。

一、MMC/SD Core 与 DM 层的分层关系

RT-Thread 的 SDIO 驱动体系分为两个层次:

  • MMC/SD 核心层:由dev_mmcsd_core.c、dev_sd.c、dev_sdio.c、dev_mmc.c、dev_block.c组成,只需要开启RT_USING_SDIO即可参与编译,它实现了 SD/MMC/eMMC 协议栈与块设备接入。
  • DM 辅助层:由dev_sdio_dm.c(以及dev_sdhci*.c、dev_regulator.c)组成,只有在开启RT_USING_DM后才会被构建系统加入。

这一编译依赖关系可以直接在 components/drivers/sdio/SConscript 中看到:当RT_USING_SDIO未开启时整个目录直接跳过;开启后默认编译核心源文件;当GetDepend(['RT_USING_DM'])为真时追加dev_sdio_dm.c;若同时开启RT_USING_SDHCI则继续追加dev_sdhci.c、dev_sdhci_dm.c、dev_sdhci_host.c;若同时开启RT_USING_REGULATOR则再追加dev_regulator.c。

各特性的 Kconfig 开关与对应源码可汇总如下表:

特性Kconfig 开关(典型配置)对应源码(仅RT_USING_DM时编译)
Host 命名、OFW 解析RT_USING_DM+RT_USING_OFWdev_sdio_dm.c
SDHCI 栈RT_USING_DM+RT_USING_SDIO+RT_USING_SDHCIdev_sdhci.c、dev_sdhci_dm.c、dev_sdhci_host.c
vmmc/vqmmc电源供给RT_USING_DM+RT_USING_REGULATORdev_regulator.c
SoC SDHCI / DW MMC 宿主RT_USING_DM+RT_USING_SDIOsdio/host/*(见 components/drivers/sdio/host/Kconfig)

注意:host/Kconfig中可见RT_SDIO_DW_MMC(Synopsys DesignWare MMC)还额外依赖RT_USING_PINCTRL、RT_USING_RESET、RT_USING_REGULATOR并自动选中RT_USING_DEVICE_IPC与RT_USING_SYSTEM_WORKQUEUE;RT_SDIO_SDHCI_DWCMSHC(Synopsys DWC MSHC SDHCI)则依赖RT_USING_OFW并自动选中RT_USING_SDHCI。

不使用 DM 时的替代方案:若RT_USING_DM未开启,框架不会编译任何dev_sdhci_*或sdio_regulator_*源码,此时需要自行实现struct rt_mmcsd_host_ops并直接对接 SDIO 设备模型文档 描述的接口,所有解析、命名、供电逻辑均由驱动自行处理。

二、Host 命名:sdio_host_set_name

sdio_host_set_name(host, out_devname)是 DM 层提供的稳定 Host 命名接口,实现位于 dev_sdio_dm.c。其核心行为是为host->name填充一个稳定的sd%u格式字符串,命名策略分两级:

  1. OFW 别名优先:当RT_USING_OFW开启且host->ofw_node已设置时,先调用rt_ofw_get_alias_id(host->ofw_node, "mmc"),尝试取设备树aliases { mmc0 = &...; }中定义的别名序号,使 Host 名与板级连线保持一致。
  2. 原子自增兜底:若别名解析失败(返回负值),则通过rt_atomic_add(&uid, 1)分配一个单调递增的数字 id。为防止纯软件 Host 与已占用别名序号冲突,首次分配时以rt_ofw_get_alias_last_id("mmc")为种子(uid_min = 别名最大索引 + 1)。

可选的out_devname参数会收到相同字符串的副本(长度受RT_NAME_MAX限制),方便驱动在 probe 阶段同时保存一份设备名。

实际调用示例

#include "dev_sdio_dm.h" int rt_sdio_host_probe(struct rt_platform_device *pdev) { struct rt_mmcsd_host *host; host = rt_mmcsd_alloc_host(&pdev->parent); if (!host) return -RT_ENOMEM; /* 尽早命名,确保后续枚举时设备名稳定 */ sdio_host_set_name(host, RT_NULL); /* 填充 host->ops 等字段后注册到核心层 ... */ }

从源码结构看,该函数被设计为在宿主控制器的probe阶段调用,而不是在首次数据传输时才命名,以保证上层块设备名(如sd0、sd1)从一开始就是确定的。

三、Open Firmware 解析:sdio_ofw_parse

当RT_USING_OFW开启时,sdio_ofw_parse(dev_np, host)负责把设备树节点dev_np中的通用 MMC 控制器属性解析进struct rt_mmcsd_host。完整实现见 dev_sdio_dm.c,可解析属性与对应行为如下表:

设备树属性解析效果
bus-width取值为1/4/8,分别设置host->flags中的MMCSD_BUSWIDTH_8/MMCSD_BUSWIDTH_4(1为默认,不置位);非法值打印LOG_E("Invalid \"bus-width\" value %d")并返回-RT_EIO
max-frequency写入host->freq_max
non-removable置位MMCSD_SUP_NONREMOVABLE
cap-sdio-irq置位MMCSD_SUP_SDIO_IRQ
cap-sd-highspeed/cap-mmc-highspeed置位MMCSD_SUP_HIGHSPEED
mmc-ddr-3_3v/mmc-ddr-1_8v/mmc-ddr-1_2v分别置位MMCSD_SUP_DDR_3V3/MMCSD_SUP_DDR_1V8/MMCSD_SUP_DDR_1V2
mmc-hs200-1_8v/mmc-hs200-1_2v置位MMCSD_SUP_HS200_1V8/MMCSD_SUP_HS200_1V2
mmc-hs400-1_8v/mmc-hs400-1_2v置位MMCSD_SUP_HS400_1V8/MMCSD_SUP_HS400_1V2
sd-uhs-sdr50/sd-uhs-sdr104/sd-uhs-ddr50置位MMCSD_SUP_SDR50/MMCSD_SUP_SDR104/MMCSD_SUP_DDR50

此外,sdio_ofw_parse还有两个固定副作用:

  • 若host->ofw_node尚未赋值,则补设为dev_np(源码中即host->ofw_node ? : dev_np);
  • 无条件在host->flags中强制置位MMCSD_MUTBLKWRITE(多块写支持)。

当RT_USING_OFW未开启时,dev_sdio_dm.h中提供了rt_inline的桩实现:sdio_ofw_parse直接返回RT_EOK,不做任何解析。

解析时机:必须在枚举开始前

官方文档强调:调用sdio_ofw_parse应当在填充struct rt_mmcsd_host的过程中、核心层开始枚举(card enumeration)之前完成。理由很直接——如果bus-width非法,函数会返回-RT_EIO,此时应把错误处理放在宿主驱动自己的probe函数中返回,而不是拖到第一次事务时才报错。推荐的 DTS 节点示例:

mmc0: mmc@10000000 { compatible = "vendor,sdhci"; reg = <0x10000000 0x1000>; interrupts = <0x2a>; bus-width = <4>; max-frequency = <50000000>; non-removable; cap-sd-highspeed; };

四、电源供给钩子:sdio_regulator_*(DM + Regulator)

dev_regulator.c仅在RT_USING_DM与RT_USING_REGULATOR同时开启时编译(Regulator 框架本身也依赖 DM)。它围绕vmmc(主电源)与vqmmc(信号电平电源)两个供给展开,相关 API 声明见 dev_sdio_dm.h:

API作用
sdio_regulator_get_supply(dev, host)通过rt_regulator_get(dev, "vmmc")/rt_regulator_get(dev, "vqmmc")从设备获取两路电源引用并存入host->supply;任一路获取失败(返回错误指针)时统一走_fail分支释放已获取的供给并返回错误
sdio_regulator_set_ocr(host, supply, vdd_bit)把 OCR 位图换算为电压区间后调用rt_regulator_set_voltage;vdd_bit为 0 时关闭电源(rt_regulator_disable),否则设置电压并在需要时使能
sdio_regulator_set_vqmmc(host, ios)根据ios->signal_voltage(MMCSD_SIGNAL_VOLTAGE_120/_180/_330)切换 vqmmc 电压;1.8V 切换失败时回退到 3.3V 逻辑
sdio_regulator_enable_vqmmc(host)/sdio_regulator_disable_vqmmc(host)使能 / 失能 vqmmc,带vqmmc_enabled状态跟踪
sdio_regulator_supports_vqmmc_voltage(regulator, min, target, max)判断 regulator 是否支持给定电压区间或精确目标电压

实现细节上,ocrbitnum_to_vdd把 OCR 位号换算成电压:位对应 1.65–1.95V 区间时取 1650–1950mV;其余按1900mV + (vdd_bit - ilog2(VDD_165_195)) * 100mV逐档递增。sdio_regulator_set_vqmmc的 3.3V 分支还会结合host->io_cfg.vdd计算允许的电压区间(下限不低于 2.7V、上限不高于 3.6V),并先尝试区间设置、失败后再尝试直接设置目标电压——这是为了兼容仅声明 1.8V 的 GPIO regulator。完整电源语义可进一步参考 SDIO Regulator 文档 与 Regulator 文档。

五、SDHCI 平台驱动:dev_sdhci_dm.c(DM 专属)

RT_USING_SDHCI只在 DM 模式下有仓内实现。平台驱动直接使用 dev_sdhci_dm.c 提供的一组便捷接口完成标准 SDHCI 宿主接入,接口声明见 dev_sdhci_dm.h:

接口作用
rt_sdhci_pltfm_init(pdev, pdata, priv_size)依次执行rt_dm_dev_iomap(dev, 0)映射寄存器、rt_dm_dev_get_irq(dev, 0)获取中断、rt_sdhci_alloc_host分配宿主;随后填充irq、ioaddr、hw_name(来自rt_dm_dev_get_name)、ops(默认sdhci_pltfm_ops:set_clock/set_bus_width/reset/set_uhs_signaling)与quirks/quirks2(来自pdata)
rt_sdhci_pltfm_init_and_add_host(pdev, pdata, priv_size)一键完成init+rt_sdhci_get_property+rt_sdhci_init_host,失败时自动rt_sdhci_pltfm_free
rt_sdhci_get_property(pdev)从设备树读取 SDHCI 特有 quirk 属性(见下表)
rt_sdhci_pltfm_remove(pdev)检测寄存器是否全0xffffffff(判断控制器是否已掉电),执行rt_sdhci_uninit_host并释放
rt_sdhci_pltfm_clk_get_max_clock(host)通过rt_clk_get_rate读取平台时钟频率作为最大时钟
rt_sdhci_pltfm_priv(host)返回平台私有数据区(host->private柔性数组)

rt_sdhci_get_property解析的属性与效果:

设备树属性效果
sdhci,auto-cmd12置位RT_SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12
sdhci,1-bit-only或bus-width = <1>置位RT_SDHCI_QUIRK_FORCE_1_BIT_DATA
broken-cd置位RT_SDHCI_QUIRK_BROKEN_CARD_DETECTION
no-1-8-v置位RT_SDHCI_QUIRK2_NO_1_8_V
clock-frequency写入pltfm_host->clock
keep-power-in-suspend置位host->mmc->pm_caps的MMC_PM_KEEP_POWER
wakeup-source/enable-sdio-wakeup(legacy)置位MMC_PM_WAKE_SDIO_IRQ

典型的 DM SDHCI 平台驱动 probe 可以这样写:

#include "dev_sdhci_dm.h" static const struct rt_sdhci_pltfm_data my_pdata = { .quirks = RT_SDHCI_QUIRK_BROKEN_CARD_DETECTION, .quirks2 = 0, }; static int my_sdhci_probe(struct rt_platform_device *pdev) { return rt_sdhci_pltfm_init_and_add_host(pdev, &my_pdata, sizeof(struct my_priv)); }

SDHCI 栈的完整实现与寄存器级细节参见 SDHCI 文档。针对具体 SoC,sdio/host/目录还提供了sdhci-dwcmshc(Synopsys DWC MSHC)与sdio-dw(DesignWare MMC)等现成宿主实现,以及 PCI 总线上挂接 SDHCI/DW MMC 的sdhci-pci.c、sdio-dw-pci.c。

六、BSP 驱动接入实战建议

综合官方文档 dm.md 与源码,编写 DM 模式下的宿主驱动时建议遵循以下要点:

  1. 解析先于卡片活动:在填充struct rt_mmcsd_host的过程中调用sdio_ofw_parse,且必须在核心层开始枚举之前;对非法bus-width返回的-RT_EIO要在probe中处理,不要拖到首次事务。
  2. 优先使用稳定的 Host 名:在 DTS 中声明aliases { mmc0 = &...; },让sdio_host_set_name的命名与板级连线、用户态脚本保持一致;纯软件 Host 仍依赖rt_ofw_get_alias_last_id("mmc")避免与已占用的别名序号冲突。
  3. 电源供给按需声明:vmmc/vqmmc依赖 DM + Regulator,二者缺一不可;DTS 中通过vmmc-supply、vqmmc-supply指向对应的 regulator 节点。
  4. SDHCI 只走 DM:RT_USING_SDHCI仅在 DM 下实现,平台驱动应使用dev_sdhci_dm.c的rt_sdhci_pltfm_init、rt_dm_dev_iomap、rt_dm_dev_get_irq、rt_sdhci_get_property组合,而不是自行编写寄存器初始化。

七、关联文档与深入阅读

  • DM 层源码与头文件:components/drivers/sdio/dev_sdio_dm.c、components/drivers/sdio/dev_sdio_dm.h
  • 构建依赖:components/drivers/sdio/SConscript
  • SDHCI DM 平台层:components/drivers/sdio/dev_sdhci_dm.c、components/drivers/sdio/dev_sdhci_dm.h
  • Regulator 钩子:components/drivers/sdio/dev_regulator.c
  • SoC 宿主 Kconfig:components/drivers/sdio/host/Kconfig
  • 协议栈入口:SDIO 设备模型文档、SDHCI 文档、SDIO Regulator 文档
  • 基础机制:总线/驱动绑定文档、OFW 运行时 API 文档
  • 操作系统
  • 嵌入式
  • 物联网
  • 嵌入式OS
  • RTOS

【免费下载链接】rt-thread

RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/

项目地址:https://gitcode.com/gh_mirrors/rt/rt-thread
点击查看免费下载
上一篇:解锁抖音数据价值:Douyin_Spider工具全解析
下一篇:ComfyUI-AnimateDiff-Evolved 终极避坑指南:10个技巧让AI动画生成更稳定

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 7:23:06

PCB设计规则配置指南:Altium Designer对接嘉立创工艺一次过审

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 7:21:45

FPGA直连NVMe SSD:硬件协议栈实现与性能调优实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 7:21:44

macOS 下用 Luatools 烧录 LuatOS 固件:从驱动配置到串口调试全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华