2、SDIO接口基础:SDIO协议概述、SDIO总线拓扑、CMD/Data线时序、SDIO寄存器读写
好,咱们进入第二章。这一章讲的是SDIO接口的基础知识。说实话,很多做WiFi蓝牙驱动的朋友,一开始都卡在SDIO这一关。我当年也是,拿着datasheet看了半天,总觉得这玩意儿跟SD卡差不多,结果一调时序就翻车。嗯,咱们今天就把这块彻底捋清楚。
2.1 SDIO协议概述
SDIO,全称是Secure Digital Input Output。说白了,它就是SD卡标准的一个扩展。你想想看,SD卡本来是用来存数据的,但后来大家发现,这个接口速度够快、管脚又少,拿来接WiFi、蓝牙、GPS这些外设不是挺香吗?于是SDIO就诞生了。
我个人习惯把SDIO理解成「带智能的总线」。为什么这么说?因为它不像SPI那样主设备一发从设备就得回,也不像I2C那样靠地址选设备。SDIO有一套完整的命令-响应机制,设备之间可以「对话」。
这里有个关键点要记住:SDIO协议是兼容SD协议的。也就是说,一个SDIO卡插到普通的SD卡槽里,至少能被识别出来。但反过来,SD卡槽不一定能支持SDIO的全部功能。我在项目中遇到过这种情况——用笔记本的SD卡槽去调试WiFi模块,结果死活枚举不上,后来才发现是卡槽只支持存储卡。
核心要点:SDIO协议基于SD协议,增加了I/O功能。它支持两种模式——SPI模式和SD模式。实际产品中,WiFi/蓝牙模块几乎都用SD模式,因为速度更快。
2.2 SDIO总线拓扑
SDIO的总线拓扑,说白了就是「一主多从」的结构。一个主机(比如CPU的SDIO控制器)可以挂多个从设备(比如WiFi模块、蓝牙模块)。但这里有个坑——最多只能挂一个SDIO卡和一个SD存储卡,或者最多两个SDIO卡。为什么?因为SDIO的地址分配机制有限制。
咱们来看一下典型的连接方式:
| 信号线 | 方向 | 说明 |
|---|---|---|
| CLK | 主机→设备 | 时钟信号,频率可调(0~208MHz) |
| CMD | 双向 | 命令/响应线,双向传输 |
| DAT0~DAT3 | 双向 | 数据线,支持1位或4位模式 |
| VDD/VSS | 电源 | 供电和地 |
你可能会问:为什么数据线要搞4根?其实早期SD卡只有1根数据线(DAT0),后来为了提速才加了DAT1~DAT3。WiFi模块这种吞吐量大的设备,基本都跑4位模式。我记得有一次调试,发现模块速度上不去,查了半天发现是驱动里只配了1位模式——嗯,这种低级错误我也犯过。
实战建议:画PCB时,SDIO的四根数据线尽量等长,CLK要包地处理。我曾经因为CLK走线太长导致信号反射,模块在高温下频繁掉线。后来加了个22Ω的串阻就稳了。
2.3 CMD/Data线时序
时序这块,是SDIO驱动开发的核心。说白了,就是主机和设备之间怎么「对暗号」。
先看CMD线。CMD线上传输的是命令和响应。每个命令是48位,结构如下:
命令格式(48位):
[0:0] 起始位(固定为0)
[1:1] 传输位(1=命令,0=响应)
[2:7] 命令索引(6位,共64个命令)
[8:39] 参数(32位)
[40:46] CRC7校验(7位)
[47:47] 结束位(固定为1)
响应格式类似,但长度可能是48位或136位。136位响应一般用于传输CID、CSD这类寄存器数据。
这里有个细节:CMD线上的命令和响应之间,是有时间间隔的。主机发完命令后,设备需要N个时钟周期来准备响应。这个N值由设备决定,但最大不能超过NAC(Normal Access Time)。我刚开始写驱动时,没注意这个间隔,直接连续发命令,结果设备根本不搭理我。
再看Data线。Data线的时序比CMD线复杂一些。数据是按块传输的,每个块后面跟CRC16校验。4位模式下,数据线是并行传输的,所以CRC也是4位并行算的。
避坑指南:我曾经在调试一款Realtek的WiFi模块时,发现数据块传输偶尔会出错。查了三天,最后发现是DAT3线的上拉电阻没焊。SDIO协议规定,DAT3在上电时要被拉高,否则设备会认为总线处于「忙」状态。嗯,从此以后我画板子都会再三检查上拉电阻。
时序参数方面,有几个关键值你得记住:
- tODLY:输出延迟,主机发完命令到设备开始响应的时间
- tNAC:正常访问时间,设备处理命令的最大时间
- tCC:命令到响应的时间间隔
- tDSU:数据建立时间
这些参数在SDIO规范里有详细定义,但实际调试时,我建议你直接用示波器抓波形。纸上得来终觉浅,绝知此事要躬行——这话放在驱动开发上再合适不过了。
2.4 SDIO寄存器读写
SDIO设备内部有一组寄存器,驱动通过读写这些寄存器来控制设备。这些寄存器分为两类:CIA(Common I/O Area)和FBR(Function Base Register)。
CIA是公共寄存器区,所有SDIO设备都必须实现。它包含设备的基本信息,比如制造商ID、功能数量、版本号等。FBR则是每个功能(Function)自己的寄存器区,WiFi模块的MAC地址、蓝牙模块的BD_ADDR都放在这里。
读写SDIO寄存器,用的是CMD52和CMD53这两个命令:
| 命令 | 功能 | 说明 |
|---|---|---|
| CMD52 | 单字节读写 | 读写一个字节的寄存器,类似I2C的单字节操作 |
| CMD53 | 多字节读写 | 读写多个字节,支持块模式和字节模式 |
CMD52的用法很简单,发一个命令带上地址和数据,设备就给你写进去或者读出来。但CMD53就复杂一些了。它支持两种模式:
- 字节模式:一次传输1~512字节,按字节计数
- 块模式:一次传输1~511个块,每块1~2048字节
WiFi模块的固件下载,通常用CMD53的块模式。我记得有一次下载固件总是失败,后来发现是块大小没对齐——模块要求块大小必须是512字节,我设成了256。这种问题,光看代码很难发现,得配合逻辑分析仪看总线上的数据包。
实战代码片段:Linux内核中读写SDIO寄存器的API
// 单字节读写
int sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret);
void sdio_writeb(struct sdio_func *func, u8 b, unsigned int addr, int *err_ret);
// 多字节读写
int sdio_memcpy_fromio(struct sdio_func *func, void *dst, unsigned int addr, int count);
int sdio_memcpy_toio(struct sdio_func *func, unsigned int addr, void *src, int count);
// 块读写
int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr, int count);
int sdio_writesb(struct sdio_func *func, unsigned int addr, void *src, int count);
使用这些API时,有个容易忽略的点:地址对齐。SDIO寄存器访问要求地址是4字节对齐的,否则会报错。我刚开始写驱动时,直接传了个奇数地址进去,结果内核打印了一堆错误信息。后来养成习惯,每次读写前都检查一下地址。
个人经验:调试SDIO寄存器时,我建议先用CMD52把关键寄存器读出来,确认设备枚举成功。比如读CCCR(Card Common Control Register)的0x00地址,返回值应该是0x00(表示SDIO卡)。如果读到0xFF,说明总线没通——先查硬件连接吧。
好了,这一章的内容就到这里。SDIO接口说复杂也复杂,说简单也简单。你只要记住:命令靠CMD线,数据靠DAT线,控制靠寄存器。下一章咱们会讲SDIO设备枚举的完整流程,到时候你会看到这些知识点是怎么串起来的。