简介:这份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 拿 CID,CMD3 分配 RCA,CMD9 拿 CSD,CMD7 选中卡。对于 SDIO 设备,CMD5 用来查询和设置 OCR,CMD3 之后会进入 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>; wifi@1 { 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指向一个电源序列节点,控制模组的复位和供电时序,很多模组上电需要严格的时序,少了这个节点会出现枚举时好时坏。
子节点wifi@1里的reg = <1>就是 SDIO 的 function number。SDIO 设备最多有 7 个 function,function 0 是 CIS 和公共控制区,function 1 到 7 是具体功能。Wi-Fi 模组通常把 Wi-Fi 放在 function 1,蓝牙放在 function 2。compatible用来匹配 function driver,interrupts是模组的数据就绪中断线,这个中断没接对,驱动会一直轮询或者干脆收不到数据。
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 位模式下只用 DAT0,4 位模式下 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 这层没有后悔药,参数是一层层加上去的,跳步迟早要还回来。希望帮到你。
本文还有配套的精品资源,点击获取