简介:这份资源面向嵌入式开发初学者与驱动工程师,聚焦SDIO驱动程序的原理理解与开发实践,帮助读者打通从协议基础到Linux内核框架的完整认知链路。压缩包共23个文件,约1.3MB,包含PDF规格文档、C与H源码、Keil工程文件(uv2、wsp、opt等)、原理图与PCB图以及项目总结文档,覆盖协议规范、硬件设计与软件实现三个层面。其中SDIO Card Specification、SDIO CIA等PDF便于查阅命令集与中断机制,源码与工程文件可用于对照学习设备探测、初始化、数据传输与中断处理流程。已有665人学习下载,适合需要结合真实工程理解SDIO总线、并为Wi-Fi或蓝牙模块驱动开发打基础的读者参考。
1. SDIO 驱动:为什么一块“插上就能用”的卡,背后要跑一套状态机
把一张 SDIO 接口的无线网卡插进开发板,系统几秒内就识别出设备、加载固件、跑起数据通路,看起来像“插上就能用”。但如果你把内核日志打开,会看到一条相当克制的初始化链路:上电、发 CMD5 探活、读 OCR 电压窗口、发 CMD3 拿 RCA、切到数据传输模式、逐个 function 使能、协商块大小、最后才轮到驱动自己的 probe。任何一步时序错位,卡就停在某个中间态,表现为“识别不到”“枚举超时”“读 CIS 失败”这类玄学现象。SDIO 驱动的本质,是把 SD 总线这套命令/响应协议,包装成一个能被上层设备模型消费的枚举与传输机制。搞懂它,你才能在枚举失败时知道该抓哪条命令,而不是盲目改时序。
2. SDIO 枚举机制:CMD5 到 function 使能到底在协商什么
2.1 从 CMD5 说起:卡是怎么被“问”出来的
SDIO 与普通 SD 存储卡共用物理总线和命令帧格式,但枚举入口不同。存储卡走 CMD0/CMD8/ACMD41/CMD2/CMD3,SDIO 设备则先发 CMD5(IO_SEND_OP_COND)。CMD5 的响应里带一个 OCR 字段,低 24 位是电压窗口,bit24 起是“IO 就绪”标志和功能数量。主机反复发 CMD5 直到卡把 ready 位置起,这一步等价于“你上电稳了吗、你要几伏”。
常见的实现思路是:主机控制器先按 3.3V 上电,发 CMD5 带一个电压掩码,卡若支持该窗口就回 ready。若一直不 ready,多半是供电爬升太慢或时钟给太早。这里有个容易忽略的点——CMD5 是“带响应但不带数据”的命令,它跑在识别时钟(通常 400kHz)上,而不是高速时钟。很多枚举翻车就翻在初始化阶段时钟给太快,卡还没完成内部上电复位,命令帧就被采样错了。
/* 简化示意:CMD5 轮询直到 IO ready 或超时 */ for (retry = 0; retry < MAX_RETRY; retry++) { resp = sdio_send_cmd5(host, voltage_mask); if (resp & OCR_IO_READY) /* bit24:卡已就绪 */ break; mdelay(1); /* 给卡内部上电复位留时间 */ } if (!(resp & OCR_IO_READY)) return -ETIMEDOUT; /* 典型现象:枚举超时 */这段逻辑里,voltage_mask是主机支持的电压集合,OCR_IO_READY是就绪位,MAX_RETRY决定容忍窗口。参数怎么改:如果板子供电偏弱,把重试间隔从 1ms 拉到 5ms 往往比加大重试次数更有效,因为问题在爬升速率而非次数。
2.2 CMD3 与 RCA:给卡分配一个“门牌号”
卡就绪后,主机发 CMD3(SEND_RELATIVE_ADDR),卡回一个 16 位的 RCA(Relative Card Address)。这个地址后续所有命令都要带上,相当于总线上的门牌号。SDIO 规范里 CMD3 的响应还携带卡的状态位,主机据此判断是否进入数据传输模式。RCA 为 0 是保留值,若读到 0 说明协商异常。
这一步的设计动机很直接:SD 总线是共享的,可能挂多张卡,必须有寻址机制。CMD3 让卡自己报一个地址,主机记录下来,之后 CMD7(SELECT_CARD)用它选中目标卡。对单卡系统这看似多余,但协议栈不区分单卡多卡,统一走这套流程,代价只是一条命令。
2.3 function 使能与 CIS:驱动 probe 之前的前置条件
SDIO 设备最多有 7 个 function(function 0 是 CIA,即 Card Information Area,管公共控制)。主机在枚举完成后,要逐个读 function 的 CIS(Card Information Structure),拿到厂商 ID、设备 ID、块大小、最大传输速度等元数据,再决定加载哪个驱动。CIS 是一段 TLV 结构的数据,存在卡内部,通过 CMD52 逐字节读出来。
这里的关键机制是:驱动 probe 发生在 function 使能之后,而不是枚举一结束就 probe。内核的 SDIO 核心先完成 CMD5/CMD3/CMD7,再读 CIS 建立sdio_func结构,然后才匹配sdio_device_id表调用驱动 probe。所以“驱动没加载”和“卡没枚举到”是两回事,排查时要先看 CIS 读没读成功。
/* 读 CIS 的典型路径:CMD52 逐字节访问 function 0 的 CIS 指针区 */ addr = CIS_PTR_REG; /* CIA 里的 CIS 起始地址寄存器 */ sdio_read_byte(func0, addr, &cis_ptr); while (cis_ptr != CIS_END) { sdio_read_byte(func0, cis_ptr, &tuple_id); sdio_read_byte(func0, cis_ptr + 1, &tuple_len); /* 按 tuple_id 解析厂商/设备/块大小等 */ cis_ptr += tuple_len + 2; }CIS_PTR_REG是 CIA 固定寄存器偏移,tuple_id标识元组类型(如 0x20 是厂商信息),tuple_len是该元组长度。读 CIS 失败最常见的原因是 CMD52 的读写标志位搞反,或者 function 0 还没使能就去读——CIA 虽然常驻,但部分卡要求先写 CCCR 的 IOEx 寄存器使能对应 function。
3. 命令通路与数据通路:CMD52/CMD53 为什么是两套语义
3.1 CMD52 单字节访问:控制面的“万能钥匙”
CMD52 是 SDIO 的控制面命令,一次读写一个字节,寻址方式是“function 号 + 寄存器地址”。它不需要块对齐,适合访问 CCCR、FBR、CIS 这类小寄存器。设计上它牺牲吞吐换灵活性:任何寄存器都能单字节碰,代价是每次都要走完整的命令/响应周期。
CMD52 的参数字段里有一个 RW 标志位(读/写)、function 号(3 位)、寄存器地址(17 位)、写入数据(8 位)。响应里回读数据。很多驱动在初始化阶段大量用 CMD52 配置 function 的块大小寄存器(FBR 里的 0x10 偏移),因为块大小必须按字节写,不能走块传输。
/* 配置 function 1 的块大小为 512 字节 */ sdio_write_byte(func1, FBR_BLOCK_SIZE_0, 512 & 0xFF); /* 低字节 */ sdio_write_byte(func1, FBR_BLOCK_SIZE_1, 512 >> 8); /* 高字节 */ sdio_write_byte(func1, FBR_BLOCK_SIZE_2, 0);FBR_BLOCK_SIZE_0/1/2是 function 基本寄存器里块大小的三个字节。注意写入顺序:部分卡要求先写低字节再写高字节,顺序反了会读到中间态。这是血泪经验里很典型的一条。
3.2 CMD53 块/字节传输:数据面的吞吐主力
CMD53 是数据面命令,支持块传输和字节传输两种模式。块传输按 function 配置的块大小搬数据,适合大流量;字节传输按任意长度搬,适合小包。CMD53 的参数里带“块模式/字节模式”标志、function 号、寄存器地址、块数或字节数。
机制上的关键点是:CMD53 的地址是 function 内部地址空间的偏移,不是物理地址。卡内部把这段地址映射到自己的 FIFO 或内存,主机只管往里灌数据。块模式下,主机控制器会连续发数据块,卡按块边界接收;字节模式下,长度可以不是块大小的整数倍,但效率低。
/* 块模式写:把 buf 的 nblks 个块写到 function 1 的地址 addr */ sdio_writel_blocks(func1, addr, buf, nblks, block_size); /* 内部会:发 CMD53(块模式、写、nblks)→ 连续搬 nblks*block_size 字节 */addr是 function 内偏移,nblks是块数,block_size必须与 FBR 里配置的一致。不一致的典型现象是数据错位或卡回 CRC 错误——因为卡按自己配置的块大小切分,主机按另一个大小发,边界对不上。
3.3 中断与轮询:卡怎么通知主机“我有数据了”
SDIO 设备通过 DAT1 线发中断(SDIO 中断模式),或在 CCCR 里置位中断 pending 标志让主机轮询。中断模式下,卡拉低 DAT1 表示有事件,主机控制器捕获后读 CCCR 的 INT_PENDING 寄存器,再分发给对应 function 的处理函数。
设计取舍在这里很明显:中断模式省 CPU 但有共享线竞争问题(多 function 同时中断要软件仲裁),轮询模式简单但费 CPU。常见的实现思路是初始化时优先尝试中断模式,失败则退回轮询。排查“数据收不到”时,先确认 INT_ENABLE 寄存器有没有使能对应 function 的中断位,再看 INT_PENDING 有没有置起——两步都过还是没数据,才去怀疑数据通路。
4. 避坑与排查:SDIO 枚举和数据通路的常见翻车现场
4.1 现象:CMD5 一直不 ready,日志停在枚举超时
原因通常是供电爬升慢或初始化时钟过快。卡内部有上电复位电路,电压没到阈值前不会响应 ready。解决:把识别阶段时钟降到 400kHz 以下,重试间隔从 1ms 加到 5ms,并确认板级供电在卡座处实测电压达标,而不是只看电源芯片输出。
4.2 现象:CIS 读出来全是 0xFF
原因多半是 CMD52 的读写标志位配反,或者 function 0 的 IOEx 寄存器没使能就访问 CIS。0xFF 是总线空闲态电平,说明卡根本没驱动数据线。解决:先确认 CMD52 参数里 RW 位为读,再检查是否先写了 CCCR 的 IOEx 使能位;部分卡还要求先发 CMD7 选中卡再读 CIS。
4.3 现象:块传输偶发 CRC 错误,重传就好
原因是主机配置的块大小与卡 FBR 里的块大小不一致,或者时钟在数据阶段切得太快。解决:读回 FBR 的块大小寄存器确认一致,数据阶段时钟先降一档跑通再逐步提频;另外检查 DAT 线的上拉电阻,缺上拉在高速下容易采样错。
4.4 现象:中断收不到,但轮询能读到数据
原因是 DAT1 中断线没使能,或 CCCR 的 INT_ENABLE 没置位对应 function。解决:确认主机控制器中断模式已开、卡侧 INT_ENABLE 写入了 function 位;若硬件把 DAT1 复用成别的功能,只能退回轮询模式,这是板级设计决定的,软件改不了。
4.5 现象:驱动 probe 没被调用,但卡已枚举
原因是 CIS 里的厂商/设备 ID 与驱动sdio_device_id表不匹配,或 function 使能失败导致sdio_func没建立。解决:打印读到的 CIS 元组,和驱动 ID 表逐字段比对;再确认 function 使能时 CMD52 写 IOEx 是否返回成功。
5. 设计取舍:为什么 SDIO 驱动要把枚举和传输拆成两层
SDIO 驱动栈的分层不是随意切的。底下是主机控制器驱动(管时钟、命令帧、DMA),中间是 SDIO 核心(管枚举、CIS 解析、function 抽象),上面才是具体设备驱动(网卡、GPS、蓝牙)。这样切的设计动机是:枚举逻辑对所有 SDIO 设备一致,没必要每个驱动重写一遍 CMD5 轮询;而数据传输的语义又因设备而异,核心层只提供 CMD52/CMD53 原语,具体怎么用交给设备驱动。
这个取舍的代价是抽象泄漏:核心层假设卡遵守规范,但现实中总有卡在某个寄存器上行为古怪,设备驱动不得不绕过核心层直接发命令。我一般会这么理解——分层是为了覆盖 90% 的常规路径,剩下 10% 的玄学要靠驱动里的 workaround 兜底。验证一套 SDIO 驱动是否扎实,不看它跑通了多少卡,而看它在枚举超时、CIS 异常、块大小不一致这些边界上有没有明确的错误码和日志,而不是静默重试到天荒地老。
从那以后我每次调 SDIO,都强制先把识别时钟压到 400kHz、把 CMD5 到 CIS 的每一步日志打全,再谈提频和吞吐。希望帮到你。
本文还有配套的精品资源,点击获取