
简介这份SDIO驱动程序资料面向嵌入式Linux驱动开发者与相关专业学生围绕SDIO协议基础、驱动结构、工作流程及Linux内核MMC框架展开帮助读者理解Wi-Fi、蓝牙、GPS等SDIO设备的驱动开发与调试方法。压缩包共23个文件约1.3MB包含PDF规格文档、C与H源码、Keil工程文件、原理图与PCB图、项目总结文档及编译中间文件覆盖从协议规范到软硬件实现的完整链路。资源中既有Simplified SDIO Card Specification、SD Memory Card Specifications等官方文档也有SDIO项目总结与原理图、PCB图便于对照硬件理解寄存器配置、命令响应与中断机制。目前已有660人学习下载适合需要掌握设备探测、初始化、数据传输、中断处理及性能优化思路的开发者参考也可作为驱动移植与排错的学习素材。1. SDIO 驱动到底在驱动什么从一张 TF 卡说起很多人第一次接触 SDIO是在调试一块 Wi-Fi 模组或者蓝牙芯片的时候。硬件同事把模组焊上去告诉你「走 SDIO 接口」然后你打开内核配置发现mmc子系统下面挂着一堆东西sdhci、mmc_core、sdio_uart、brcmfmac……一时不知道从哪下手。SDIO 驱动这个标题说的就是这条链路主机控制器怎么把命令发出去SDIO 设备怎么被枚举出来function driver 怎么和它绑定最后数据怎么在两者之间搬。它解决的问题很具体SoC 上通常只有一两个原生 SD 控制器但外设需要高速总线Wi-Fi、蓝牙、GPS、NFC 模组大量采用 SDIO 接口。你不可能每个外设都去改一遍控制器驱动所以内核把 SDIO 拆成三层——host controller 层、core 层、function 层。理解这三层的边界是能不能把驱动调通的关键。适合谁看写过字符设备但没碰过 MMC 子系统的嵌入式工程师或者拿到模组厂商给的驱动包、却不知道怎么往自己板子上移植的人。2. SDIO 的三层架构与枚举流程命令是怎么一步步发出去的2.1 主机控制器、core、function 各自管什么SDIO 的软件栈从上到下大致是这样分的。最底下是 host controller driver比如常见的sdhci系列它直接操作寄存器负责发命令、收响应、搬数据、处理中断和 DMA。中间是mmc_core它实现了 SD 协议的状态机、命令封装、卡枚举、总线宽度和时钟切换、以及 SDIO 特有的 function 管理。最上面是 function driver比如 Wi-Fi 驱动它通过sdio_readb、sdio_writeb、sdio_memcpy_fromio这些 API 去读写设备的寄存器和 FIFO。这个分层的好处是换 SoC 只需要换 host 层换模组只需要换 function 层core 层基本不动。但调试的时候麻烦也在这里——一个读写失败可能是 host 寄存器配错可能是 core 枚举没走完也可能是 function driver 的 CIS 解析有问题。所以排查第一步永远是确认枚举到了哪一步。枚举流程大致是上电后 host 发 CMD0 复位CMD8 确认电压ACMD41 查询 OCR 直到卡就绪CMD2 拿 CIDCMD3 分配 RCACMD9 拿 CSDCMD7 选中卡。对于 SDIO 设备CMD5 用来查询和设置 OCRCMD3 之后会进入 SDIO 模式然后 CMD52 读 CIS 区域CMD53 做块传输。CIS 里记录了每个 function 的厂商 ID、设备 ID、以及需要多少空间、支持什么速度。2.2 用设备树描述一个 SDIO 控制器和它的模组现在大多数 ARM 平台用设备树描述硬件。一个典型的 SDIO 控制器节点长这样sdhci1 { status okay; bus-width 4; non-removable; cap-power-off-card; keep-power-in-suspend; mmc-pwrseq wifi_pwrseq; #address-cells 1; #size-cells 0; wifi1 { compatible vendor,chip-wifi; reg 1; interrupt-parent gpio; interrupts 12 IRQ_TYPE_LEVEL_HIGH; }; };bus-width 4表示用 4 位数据线SDIO 模组常见的是 1 位和 4 位4 位能跑更高吞吐。non-removable告诉 core 这张卡不可插拔跳过一些检测流程。cap-power-off-card和keep-power-in-suspend关系到电源管理如果模组在休眠时需要保持供电这两个要配对使用。mmc-pwrseq指向一个电源序列节点控制模组的复位和供电时序很多模组上电需要严格的时序少了这个节点会出现枚举时好时坏。子节点wifi1里的reg 1就是 SDIO 的 function number。SDIO 设备最多有 7 个 functionfunction 0 是 CIS 和公共控制区function 1 到 7 是具体功能。Wi-Fi 模组通常把 Wi-Fi 放在 function 1蓝牙放在 function 2。compatible用来匹配 function driverinterrupts是模组的数据就绪中断线这个中断没接对驱动会一直轮询或者干脆收不到数据。2.3 从零写一个最小 SDIO function driver 的骨架如果模组厂商没给驱动或者你想自己写一个简单的测试驱动骨架大概是这样#include linux/module.h #include linux/sdio.h #define VENDOR_ID 0x1234 #define DEVICE_ID 0x5678 static int my_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) { int ret; u8 val; /* 使能 function之后才能访问它的寄存器 */ ret sdio_enable_func(func); if (ret) { dev_err(func-dev, enable func failed %d\n, ret); return ret; } /* 设置块大小影响 CMD53 的传输粒度 */ ret sdio_set_block_size(func, 64); if (ret) { dev_err(func-dev, set block size failed %d\n, ret); goto err_disable; } /* 读一个厂商自定义寄存器确认设备活着 */ val sdio_readb(func, 0x00, ret); if (ret) { dev_err(func-dev, read reg failed %d\n, ret); goto err_disable; } dev_info(func-dev, chip id reg 0x%02x\n, val); sdio_set_drvdata(func, some_private_data); return 0; err_disable: sdio_disable_func(func); return ret; } static void my_sdio_remove(struct sdio_func *func) { sdio_disable_func(func); } static const struct sdio_device_id my_sdio_ids[] { { SDIO_DEVICE(VENDOR_ID, DEVICE_ID) }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(sdio, my_sdio_ids); static struct sdio_driver my_sdio_driver { .name my_sdio, .id_table my_sdio_ids, .probe my_sdio_probe, .remove my_sdio_remove, }; module_sdio_driver(my_sdio_driver); MODULE_LICENSE(GPL);sdio_enable_func是必须的第一步没使能之前读写寄存器会返回错误。sdio_set_block_size设置的是 CMD53 块传输的块大小常见值有 32、64、128、256、512具体支持哪些要看 CIS 里的描述设错了会在传输时报错。sdio_readb读的是 function 的寄存器空间地址 0x00 是厂商自定义的不同芯片不一样。SDIO_DEVICE宏用厂商 ID 和设备 ID 匹配这两个值从 CIS 里读出来或者由模组厂商提供。3. 让 SDIO 真正跑起来时钟、总线宽度与传输模式的调参3.1 时钟频率不是越高越好SDIO 的时钟由 host controller 分频得到。默认枚举阶段用 400kHz枚举完成后可以切到 25MHz、50MHz 甚至更高。但时钟拉高之后信号完整性问题会暴露出来。我遇到过一块板子25MHz 下枚举正常切到 50MHz 后读写随机失败示波器一看 CLK 和 DATA 线上的过冲很严重最后在模组端串了 22 欧姆电阻才稳定。调时钟的地方在 host controller 的max-frequency属性或者运行时通过mmc_set_clock设置。如果设备树里写了max-frequency 50000000但实际走线质量撑不住可以在驱动里限制到 25MHz 先跑通再逐步往上试。判断时钟是否稳定的办法是跑连续读写测试看有没有 CRC 错误或者超时。# 查看当前 mmc 设备的时钟和总线宽度 cat /sys/kernel/debug/mmc1/ios这个 debugfs 节点会打印clock、bus width、timing spec等信息。如果clock显示 400000说明还停在枚举频率没切上去要检查 host 驱动的set_ios回调。3.2 总线宽度从 1 位切到 4 位的时机SDIO 支持 1 位和 4 位数据总线。1 位模式下只用 DAT04 位模式下 DAT0 到 DAT3 都用上吞吐能差将近四倍。切换总线宽度是在枚举完成后通过 ACMD6 或者 SDIO 的 CCCR 寄存器设置。设备树里的bus-width 4只是告诉 core 硬件支持 4 位实际切换由 core 在合适的时候发起。如果切到 4 位后数据出错先检查 DAT1、DAT2、DAT3 这三根线有没有虚焊或者被别的功能复用。有些 SoC 的引脚复用表里SDIO 的 DAT2 和 DAT3 默认是别的功能需要在 pinctrl 里显式配成 SDIO。另外4 位模式下 DAT3 还兼做卡检测如果硬件上没接上拉可能会误触发。3.3 CMD52 和 CMD53 的选择与块大小设置SDIO 的传输命令就两条CMD52 和 CMD53。CMD52 用于读写单个字节走的是 IO_RW_DIRECT适合访问寄存器和少量配置数据。CMD53 用于块传输走 IO_RW_EXTENDED适合搬 FIFO 数据。CMD53 又分字节模式和块模式块模式的效率更高但要求传输长度是块大小的整数倍。块大小在sdio_set_block_size里设置常见做法是设成 64 或 128。设好之后sdio_memcpy_fromio和sdio_memcpy_toio会按块传输。如果传输长度不是块大小整数倍core 会自动拆成块传输加字节传输但效率会下降。所以 Wi-Fi 驱动在收包时通常会把 buffer 对齐到块大小。/* 按块大小对齐的接收示例 */ #define BLK_SIZE 64 u8 *buf kmalloc(round_up(len, BLK_SIZE), GFP_KERNEL); sdio_memcpy_fromio(func, buf, FIFO_REG, round_up(len, BLK_SIZE));这里round_up把长度向上取整到块大小的倍数避免 core 拆包。代价是可能多读几个字节但换来的是稳定的块传输。如果 FIFO 不支持多读就要用字节模式或者精确控制长度。4. SDIO 驱动调试避坑枚举失败、中断丢失、吞吐上不去4.1 枚举卡在 CMD5 或 CMD3现象上电后内核日志停在mmc1: card never left busy state或者mmc1: error -110 whilst initialising SDIO card。原因通常是电源时序不对或者复位脚没拉。解决先查mmc-pwrseq节点里的 GPIO 和延时确保模组上电后有一段稳定的复位时间。用万用表量模组的供电和复位脚确认电平正确。如果模组需要外部 32.768kHz 时钟还要确认这个时钟在枚举前已经起振。4.2 中断收不到导致数据积压现象Wi-Fi 能扫到热点但连不上或者 ping 延迟越来越大。原因多半是 SDIO 的数据就绪中断没配好。SDIO 模组通常有一根 DAT1 或者单独的 GPIO 做中断线设备树里的interrupts要对应正确的 GPIO 和触发方式。有些模组默认用 DAT1 中断需要在 CCCR 里使能中断功能并且 host 要支持 SDIO 中断。如果 host 不支持就只能退化成轮询延迟和 CPU 占用都会上去。4.3 吞吐只有几 Mbps 的常见原因现象理论 4 位 50MHz 能跑 100Mbps 以上实测只有几 Mbps。原因可能是时钟没切上去、总线还是 1 位、块大小设得太小、或者每次传输都走了字节模式。排查顺序先看/sys/kernel/debug/mmc1/ios确认 clock 和 bus width再看驱动里sdio_set_block_size设了多少最后抓 CMD53 的传输长度是不是块大小的整数倍。另外如果 host controller 的 DMA 没使能数据靠 CPU 搬吞吐也会受限。4.4 休眠唤醒后设备消失现象系统进入 suspend 再唤醒SDIO 设备不见了。原因通常是休眠时模组断电但 host 没有重新枚举。解决在设备树里加keep-power-in-suspend让模组在休眠时保持供电如果必须断电就要在 resume 回调里重新走一遍枚举流程。有些 host controller 在 resume 后需要重新初始化寄存器和时钟这部分要在 host 驱动的resume里做。5. 用 sdio_uart 和 trace 事件验证链路一个可复现的调试技巧5.1 用 sdio_uart 做回环测试如果手头没有 Wi-Fi 模组但想验证 SDIO 链路是否通可以用sdio_uart驱动。它把 SDIO 设备模拟成串口适合做回环测试。内核配置里打开CONFIG_SERIAL_SDIO然后确认设备树里的 SDIO 节点有对应的子节点。加载后会出现/dev/ttySDIO0短接模组的 TX 和 RX用echo和cat就能测收发。# 回环测试写入的数据应该能读回来 stty -F /dev/ttySDIO0 115200 raw -echo cat /dev/ttySDIO0 echo sdio loopback test /dev/ttySDIO0如果读回来一致说明 CMD52、CMD53、中断、DMA 这条链路基本通了。不一致或者没反应就回到枚举和时钟那一步查。5.2 打开 mmc trace 事件看命令流内核的 trace 子系统里有 mmc 相关的事件能打印每条命令和响应。打开方式# 使能 mmc 的 trace 事件 echo 1 /sys/kernel/debug/tracing/events/mmc/enable cat /sys/kernel/debug/tracing/trace_pipe你会看到mmc_request_start、mmc_request_done这样的记录里面包含 opcode、arg、flags、ret。枚举阶段重点看 CMD0、CMD5、CMD3、CMD7 有没有返回错误。数据传输阶段看 CMD53 的 arg 和实际传输长度。如果某条命令一直重试trace 里会连续出现同一条命令这时候就要查硬件或者时序。5.3 一个我常犯的错误早期调 SDIO 的时候我总想一步到位把时钟拉到最高、总线开到 4 位、块大小设到 512结果枚举都过不去。后来养成习惯先用 400kHz、1 位、块大小 64 把枚举和基本读写跑通确认 trace 里命令都正常返回再逐项往上加。每次只改一个参数改完跑一遍回环测试。这样出问题的时候能立刻知道是哪个改动引入的。SDIO 这层没有后悔药参数是一层层加上去的跳步迟早要还回来。希望帮到你。本文还有配套的精品资源点击获取