SDIO_WiFi驱动学习之SDIO架构介绍及源码分析

一、引言

因为WiFi驱动比较复杂,所以WiFi驱动的博客将多分几篇来写。

本篇博客主要介绍Linux下的SDIO架构及源码分析。

本文部分内容摘抄自网络,若有侵权,请联系删除。

二、SDIO WiFi

SDIO-Wifi模块是基于SDIO接口的符合WiFi无线网络标准的嵌入式模块,内置无线网络协议IEEE802.11协议栈以及TCP/IP协议栈,能够实现用户主平台数据通过SDIO口到无线网络之间的转换。SDIO具有传输数据快,兼容SD、MMC接口等特点。

对于SDIO接口的WiFi,首先,它是一个SDIO的卡设备,然后具备了WiFi的功能。所以,注册的时候还是先以sdio设备去注册,然后检测到卡之后再去实现它的wifi功能。显然,他是用SDIO的协议,通过发命令和数据来控制实现WiFi功能。

三、相关概念

1、MMC与SD

SD 是一种flash memory card的标准,也就是一般常见的SD记忆卡,而MMC则是较早的一种记忆卡标准,目前已经被SD标准所取代。

2、SDIO

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 卡)有:

  • Wi-Fi card(无线网络卡)
  • CMOS sensor card(照相模块)
  • GPS card
  • GSM/GPRS modem card
  • Bluetooth card
  • Radio/TV card

2.1、SDIO的传输模式

SDIO的传输模式有三种:

  • SPI mode(required)
  • 1-bit mode
  • 4-bit mode

依据 SD 标准,所有的 SD(记忆卡)与 SDIO(外围)都必须支持 SPI mode,因此 SPI mode 是「required」。

2.2、SDIO的硬件电路

SDIO_WiFi驱动学习之SDIO架构介绍及源码分析_第1张图片

可以看到,SDIO接口总共有七个引脚,分别是DAT0-3(数据),CLK(时钟),CMD(命令),VIO(电源)。

四、MMC子系统框架

这里的MMC子系统框架包含了Linux的SDIO驱动架构,所以姑且将其看作SDIO的驱动架构。

linux/driver/mmc目录下有三个子目录:card 、core、host,分别表示区块层、核心层、主机控制层。其中card层、core层是Linux封装好的、标准的东西,不需要修改,需要修改的是host层,这一层需要驱动开发工程师根据平台来完成。

SDIO_WiFi驱动学习之SDIO架构介绍及源码分析_第2张图片

MMC子系统框架图。

Linux MMC子系统主要分成三个部分:

  • MMC核心层:完成不同协议和规范的实现,为host层和设备驱动层提供接口函数。MMC核心层由三个部分组成:MMC,SD和SDIO,分别为三类设备驱动提供接口函数;
  • Host 驱动层:针对不同主机端的SDHC、MMC控制器的驱动;
  • Client 驱动层:针对不同客户端的设备驱动程序。如SD卡、T-flash卡、SDIO接口的GPS和wi-fi等设备驱动。

五、SDIO总线协议

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总线通信有三个元素:

  • Command:由host发送到卡设备,使用CMD线发送;
  • Response:从card端发送到host端,作为对前一个CMD的相应,通过CMD线发送;
  • Data:即能从host传输到card,也能从card传输到host,通过data线传输。

1、SDIO信号

所谓SDIO信号,其实就是SDIO的pin脚所发出的信号。

SDIO有哪些pin脚在上面介绍SDIO时已经介绍过了,现在来说一下它们所发出的信号有哪些:

  • CLK信号:HOST给Device的时钟信号;
  • CMD信号:双向的信号,用于传送命令和反应;
  • DAT0-DAT3信号:四条用于传输数据;
  • VDD信号:电源信号;
  • VSS1/2信号:电源地信号。

SDIO总线协议规定:在1BIT模式下,DAT0用来传输数据,DAT1用作中断线。在SDIO的4BIT模式下DAT0-DAT3用来传输数据,其中DAT1复用做中断线。

2、SDIO命令

以下是用于控制卡设备的指令类型,每个command都是固定的48位长度:

  • broadcast commands(bc),no response:广播类型的指令,不需要有响应;
  • broadcast commands with response(bcr):广播类型的指令且需要响应;
  • addressed(point-to-point) commands(ac):由HOST发送到指定的卡设备,没有数据的传输;
  • address(point-to-point) data transfercommands(adtc):由HOST发送到指定的卡设备且伴随有数据传输。

指令格式:

SDIO_WiFi驱动学习之SDIO架构介绍及源码分析_第3张图片

3、SDIO寄存器

下面介绍几个主要的寄存器:

  • Operation condition register(OCR):32位的OCR包含卡设备支持的工作电压表;
  • Card identification number register (CID):包含用于在卡识别阶段的卡信息,包括制造商ID,产品名等;
  • Card specific data register(CSD):CSD寄存器提供了如何访问卡设备的信息,包括定义了数据格式,错误校验类型,最大访问次数,数据传输率等;
  • Relative card address register(RCA):存放在卡识别阶段分配的相对卡地址,缺省相对卡地址为0000h;
  • SD card configuration register(SCR):SCR是一个配置寄存器,用于配置SD memory card的特殊功能。

4、SDIO Reponse

所有的response都通过CMD线发送到host端,R4和R5响应类型是SDIO中特有的:

  • R1(normal response command):用来响应常用指令;
  • R2(CID,CSD register):用来响应CMD2和CMD10或CMD9,并把CID或CSD寄存器作为响应数据;
  • R3(OCR register):用来响应ACMD41指令,并把OCR寄存器作为响应数据;
  • R4(CMD5):响应CMD5,并把OCR寄存器作为响应数据;
  • R5(CMD52):CMD52是一个读写寄存器的指令,R5用于CMD52的响应;
  • R6(published RCA response):分配相对卡地址的响应;
  • R7(card interface condition):响应CMD8,返回卡支持的电压信息;

六、MMC子系统的注册流程分析

1、注册虚拟总线

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);

2、初始化并挂载设备驱动

初始化注册块设备,即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;
}

3、初始化注册主控驱动

初始化注册主控驱动 : 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

4、Host驱动的prob

此时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);

...
}

4.1、mmc_alloc_host

分配 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;
}

4.2、sunxi_mmc_resource_request

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设备

4.3、mmc->ops = &sunxi_mmc_ops

卡控制器,操作函数集合初始化。

4.4、mmc_add_host

创建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);

5、匹配设备与驱动,并调用驱动prob

经过以上步骤,mmc虚拟总线、sdio虚拟总线和host主机控制器驱动就注册好了。

检测是否有sdio设备的是4.1中初始化的工作任务,当调用这个工作任务检测到设备时,就会将这个设备注册添加到相应的虚拟总线上。注册添加成功之后,系统最终会找到设备(实现WLAN功能)的驱动,调用它的probe。

至此,SDIO的驱动架构/MMC子系统就介绍完毕了,具体的WiFi功能相关的设备驱动下一篇博客再作介绍。

 

 

你可能感兴趣的:(SDIO_WiFi驱动,Linux驱动开发笔记)