因为WiFi驱动比较复杂,所以WiFi驱动的博客将多分几篇来写。
本篇博客主要介绍Linux下的SDIO架构及源码分析。
本文部分内容摘抄自网络,若有侵权,请联系删除。
SDIO-Wifi模块是基于SDIO接口的符合WiFi无线网络标准的嵌入式模块,内置无线网络协议IEEE802.11协议栈以及TCP/IP协议栈,能够实现用户主平台数据通过SDIO口到无线网络之间的转换。SDIO具有传输数据快,兼容SD、MMC接口等特点。
对于SDIO接口的WiFi,首先,它是一个SDIO的卡设备,然后具备了WiFi的功能。所以,注册的时候还是先以sdio设备去注册,然后检测到卡之后再去实现它的wifi功能。显然,他是用SDIO的协议,通过发命令和数据来控制实现WiFi功能。
SD 是一种flash memory card的标准,也就是一般常见的SD记忆卡,而MMC则是较早的一种记忆卡标准,目前已经被SD标准所取代。
SDIO 故名思义,就是 SD 的 I/O 接口(interface)的意思,不过这样解释可能还有点抽像。更具体的说明,SD 本来是记忆卡的标准,但是现在也可以把 SD 拿来插上一些外围接口使用,这样的技术便是 SDIO。
所以 SDIO 本身是一种相当单纯的技术,透过 SD 的 I/O 引脚来连接外围,并且透过 SD 上的 I/O 数据引脚与这些外围传输数据,而且 SD 协会会员也推出很完整的 SDIO stack 驱动程序,使得 SDIO 外围(我们称为 SDIO 卡)的开发与应用变得相当热门。
现在已经有非常多的手机或是手持装置都支持 SDIO 的功能(SD 标准原本就是针对 mobile device 而制定),而且许多 SDIO 外围也都被开发出来,让手机外接外围更加容易,并且开发上更有弹性(不需要内建外围)。目前常见的 SDIO 外围(SDIO 卡)有:
SDIO的传输模式有三种:
依据 SD 标准,所有的 SD(记忆卡)与 SDIO(外围)都必须支持 SPI mode,因此 SPI mode 是「required」。
可以看到,SDIO接口总共有七个引脚,分别是DAT0-3(数据),CLK(时钟),CMD(命令),VIO(电源)。
这里的MMC子系统框架包含了Linux的SDIO驱动架构,所以姑且将其看作SDIO的驱动架构。
linux/driver/mmc目录下有三个子目录:card 、core、host,分别表示区块层、核心层、主机控制层。其中card层、core层是Linux封装好的、标准的东西,不需要修改,需要修改的是host层,这一层需要驱动开发工程师根据平台来完成。
MMC子系统框架图。
Linux MMC子系统主要分成三个部分:
SDIO总线 和 USB总线 类似,SDIO也有两端,其中一端是HOST端,另一端是device端。所有的通信都是由HOST端发送命令开始的,Device端只要能解析命令,就可以相互通信。对于SDIO总线,它的HOST端是开发板mmc控制器,而device端则是各种带SDIO接口的模块,比如SDIO WiFi模块。
SDIO协议是由SD卡协议演化升级而来的,很多地方保留了SD卡的读写协议,同时SDIO协议又在SD卡协议之上添加了CMD52和CMD53命令。由于这个,SDIO和SD卡规范间的一个重要区别就是增加了低速标准。低速卡的目标应用是以最小的硬件来支持低速I/O能力。
SD总线通信是基于指令和数据比特流,起始位开始和停止位结束。SD总线通信有三个元素:
所谓SDIO信号,其实就是SDIO的pin脚所发出的信号。
SDIO有哪些pin脚在上面介绍SDIO时已经介绍过了,现在来说一下它们所发出的信号有哪些:
SDIO总线协议规定:在1BIT模式下,DAT0用来传输数据,DAT1用作中断线。在SDIO的4BIT模式下DAT0-DAT3用来传输数据,其中DAT1复用做中断线。
以下是用于控制卡设备的指令类型,每个command都是固定的48位长度:
指令格式:
下面介绍几个主要的寄存器:
所有的response都通过CMD线发送到host端,R4和R5响应类型是SDIO中特有的:
mmc_bus,sdio_bus 两条虚拟总线 : 在 drivers/mmc/core/core.c 中创建 mmc、sdio两条总线。
static int __init mmc_init(void)
{
int ret;
ret = mmc_register_bus();//创建 mmc总线
ret = sdio_register_bus();//创建 sdio总线
return ret;
}
subsys_initcall(mmc_init);
初始化注册块设备,即card设备 : 在 drivers/mmc/card/block.c 中注册一个块设备,并将他挂载到前面创建的 mmc总线上。
static struct mmc_driver mmc_driver = {
.drv = {
.name = "mmcblk",
},
.probe = mmc_blk_probe,
.remove = mmc_blk_remove,
.suspend = mmc_blk_suspend,
.resume = mmc_blk_resume,
};
static int __init mmc_blk_init(void)
{
int res;
res = register_blkdev(MMC_BLOCK_MAJOR, "mmc");//块设备注册
res = mmc_register_driver(&mmc_driver);//注册 mmc_driver 即 card driver 到 mmc总线上
return res;
}
初始化注册sdio设备驱动,在 drivers/mmc/card/sdio_uart.c 中注册一个sdio设备,并将他挂载到前面创建的 sdio总线上。
static struct sdio_driver sdio_uart_driver = {
.probe = sdio_uart_probe,
.remove = sdio_uart_remove,
.name = "sdio_uart",
.id_table = sdio_uart_ids,
};
static int __init sdio_uart_init(void)
{
int ret;
...
ret = sdio_register_driver(&sdio_uart_driver);
return ret;
}
初始化注册主控驱动 : drivers/mmc/host/sunxi-mmc.c
//匹配 linux-3.10/arch/arm/boot/dts/sun8iw17p1-carvout.dtsi : compatible = "allwinner,sunxi-mmc-v4p1x";
static const struct of_device_id sunxi_mmc_of_match[] = {
...
{.compatible = "allwinner,sunxi-mmc-v4p1x",},//有!!
...
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sunxi_mmc_of_match);
static struct platform_driver sunxi_mmc_driver = {
.driver = {
.name = "sunxi-mmc",
.of_match_table = of_match_ptr(sunxi_mmc_of_match),
.pm = sunxi_mmc_pm_ops,
},
.probe = sunxi_mmc_probe,
.remove = sunxi_mmc_remove,
.shutdown = sunxi_shutdown_mmc,
};
module_platform_driver(sunxi_mmc_driver);//注册 host driver
此时Host的设备和驱动都已经创建完成,platform_bus_type总线匹配其driver 和 device 后调用Host对应的prob函数,该函数内部会进行检测初始化slave等操作,检测sdio设备,并且创建 card 设备。
具体内容有:
static int sunxi_mmc_probe(struct platform_device *pdev)
{
struct sunxi_mmc_host *host; //平台控制器信息结构体
struct mmc_host *mmc;//描述卡控制器 结构体
int ret;
/* 4.1
1、分配 struct mmc_host结构体空间
2、设置 host->detect = mmc_rescan 延期工作的任务初始化
*/
mmc = mmc_alloc_host(sizeof(struct sunxi_mmc_host), &pdev->dev);//定义于drivers/mmc/core/host.c
/*4.2
1、sunxi_mmc_host资源初始化 !!!
2、注册中断线程化 并完成 中断函数 以及 线程处理函数
*/
ret = sunxi_mmc_resource_request(host, pdev);
...
/*4.3
1、卡控制器 操作函数集合初始化
*/
mmc->ops = &sunxi_mmc_ops;
...
/*4.4
1、添加mmc_host !!! core : host.c中提供
*/
ret = mmc_add_host(mmc);
...
}
分配 struct mmc_host结构体空间;
设置 host->detect = mmc_rescan 初始化延期工作的任务,该任务的工作是扫描插入的卡并添加卡。
a : mmc_alloc_host
说明:经过分析代码 mmc_alloc_host()的主要工作只有两个,如下:
1、struct mmc_host结构体空间
2、初始化延期工作的任务:mmc_rescan()
2.1、调用关系如下: 检测是否有卡,有的话扫描该卡,并将该卡设备添加到设备模型
mmc_rescan()
mmc_rescan_try_freq()
mmc_attach_sdio()
mmc_add_card()
device_add()//挂载到sdio总线
mmc_alloc_host()定义于 drivers/mmc/core/host.c。
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
int err;
struct mmc_host *host;
// 分配 struct mmc_host结构体空间
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
...
//设置 host->detect = mmc_rescan 延期工作的任务初始化
//注意:在这里只是初始化,并没有去调用它
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
...
}
EXPORT_SYMBOL(mmc_alloc_host);
b: mmc_rescan
注意:在4.1步骤只是去初始化这个工作任务,并没有实际去调用它,调用的地方在4.2注册的中断函数和4.4mmc_add_host中。
mmc_rescan 定义于 drivers/mmc/core/core.c,经过分析,它大概做了两件事:
第一 : 检测卡是否插入有效
第二 : 扫描该卡
/*
host扫描
*/
void mmc_rescan(struct work_struct *work)
{
/* 1、检测卡是否插入有效 */
...
...
/* 2、扫描该卡 */
for (i = 0; i < ARRAY_SIZE(freqs); i++) {
//扫描该卡
if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min))) {
...
}
...
}
mmc_release_host(host);
}
c : 分析 mmc_rescan_try_freq
看一下 mmc_rescan_try_freq是怎么扫描的,可以看出,主要的工作就是按顺序检测 sdio设备、sd设备、mmc设备。我们的WiFi是SDIO设备,所以只需要关注mmc_attach_sdio(host) 就可以了。
/*
检测卡类型添加卡设备
*/
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
...
/* 检测卡的类型 : SDIO类 SD类 MMC类
host在扫描卡的过程中,其识别的顺序为SDIO SD MMC
*/
if (!mmc_attach_sdio(host))
return 0;
if (!mmc_attach_sd(host))
return 0;
if (!mmc_attach_mmc(host))
return 0;
mmc_power_off(host);//掉电
return -EIO;
}
d : mmc_attach_sdio
mmc_attach_sdio()定义于 drivers/mmc/core/sdio.c。
/*
host 扫描检测SDIO的识别过程 :
*/
int mmc_attach_sdio(struct mmc_host *host)
{
struct mmc_card *card;
1、向卡发送CMD5命令,该命令有两个作用
第一,通过判断卡是否有反馈信息来判断是否为SDIO设备(只有SDIO设备才对CMD5命令有反馈,其他卡是没有反馈的)
第二,如果是SDIO设备,就会给host反馈电压信息,就是说告诉host,本卡所能支持的电压是多少多少。
2、host根据SDIO卡反馈回来的电压要求,给其提供合适的电压。
3、初始化该SDIO卡 :
mmc_sdio_init_card();
card->type = MMC_TYPE_SDIO;//设置卡的类型为 MMC_TYPE_SDIO
4、注册SDIO的各个功能模块
5、添加SDIO卡
err = mmc_add_card(host->card);
}
e : mmc_add_card
drivers/mmc/core/bus.c
int mmc_add_card(struct mmc_card *card)
{
int ret;
//将card注册进linux设备模型 注册结果就是可以在/sys/bus/sdio/devices目录下见到card 的名字
ret = device_add(&card->dev); //struct device dev;
return 0;
}
sunxi_mmc_host 资源初始化 ;
注册中断线程化并完成中断函数以及线程处理函数。其中中断函数中调到最后其实就是mmc_schedule_delayed_work(&host->detect, delay);即执行延期工作的任务调用host->detect = mmc_rescan 检测扫描添加 mmc设备。此处中断的扫描操作应该是为了识别那些在mmc_host 被添加之前插入的卡。
1、资源初始化
……
2、注册中断线程函数
devm_request_threaded_irq(&pdev->dev, host->irq, sunxi_mmc_irq,sunxi_mmc_handle_bottom_half, 0,"sunxi-mmc", host);
sunxi_mmc_irq()
mmc_detect_change(host->mmc, msecs_to_jiffies(500));//检测卡插入情况
mmc_schedule_delayed_work(&host->detect, delay);//延迟 delay 调用host->detect = mmc_rescan 检测扫描添加 mmc设备
卡控制器,操作函数集合初始化。
创建host->class_dev设备(为代表当前设备host的device节点),并注册它到sysfs中;开始添加host,mmc_start_host(host),该函数调用到最后也mmc_schedule_delayed_work(&host->detect, delay) 即执行延期工作的任务,调用host->detect = mmc_rescan 检测扫描添加 mmc设备。
ret = mmc_add_host(mmc); //主要工作只有两个
1、创建一个设备,并注册它到sysfs中
2、开始添加host : mmc_start_host(host)
mmc_start_host(host)
mmc_detect_change(host, 0);
mmc_schedule_delayed_work(&host->detect, delay);//延迟 delay 调用host->detect = mmc_rescan 检测 mmc设备
int mmc_add_host(struct mmc_host *host)
{
int err;
WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
!host->ops->enable_sdio_irq);
//创建一个设备,并注册它到sysfs中 。 struct device class_dev; 代表本节点 host->class_dev为代表当前设备的 device节点
err = device_add(&host->class_dev);
...
/*开始添加host ,定义于drivers/mmc/core/core.c */
mmc_start_host(host);
...
return 0;
}
void mmc_start_host(struct mmc_host *host)
{
...
mmc_detect_change(host, 0);//调用host->detect 检测 mmc设备
}
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
...
mmc_schedule_delayed_work(&host->detect, delay);//延迟 delay 调用host->detect = mmc_rescan 检测 mmc设备
}
EXPORT_SYMBOL(mmc_detect_change);
经过以上步骤,mmc虚拟总线、sdio虚拟总线和host主机控制器驱动就注册好了。
检测是否有sdio设备的是4.1中初始化的工作任务,当调用这个工作任务检测到设备时,就会将这个设备注册添加到相应的虚拟总线上。注册添加成功之后,系统最终会找到设备(实现WLAN功能)的驱动,调用它的probe。
至此,SDIO的驱动架构/MMC子系统就介绍完毕了,具体的WiFi功能相关的设备驱动下一篇博客再作介绍。