MMC子系统识别SD设备过程简述
一 :引子–WIFI 模块移植
二 : MMC 识别 SD设备过程
- 第一步: 注册虚拟总线
- 第二步: 初始化并挂载设备驱动
- 第三步:初始化注册主控驱动
- 第四步:Host驱动的prob()
- 4.1 mmc_alloc_host
- 4.2 sunxi_mmc_resource_request()
- 4.3 mmc->ops = &sunxi_mmc_ops;
- 4.4 ret = mmc_add_host(mmc);
- 4.5 host驱动的prob总结
- 4.6 :4.1 ~ 4.5 代码具体分析
4.6.1 分析 mmc_alloc_host
- a : 分析 4.1 mmc_alloc_host
- b: 分析 mmc_rescan()
- c :析 mmc_rescan_try_freq()
- d : 分析 mmc_attach_sdio()
- e : 分析mmc_add_card()
4.6.2 分析 4.2 sunxi_mmc_resource_request(host, pdev);
4.6.3 : 分析4.3 卡控制器 操作函数集合初始化
4.6.4 : 分析4.4 mmc_add_host(mmc);
- 第五步 : 匹配设备与驱动,并调用驱动prob
三 : MMC子系统
- 1 MMC子系统简述
- 2 SD/MMC/SDIO概念区分
- 3 从 bus,device,driver看mmc子系统
四 : 代码分析草稿
一 : 引子–WIFI 模块移植
SDIO-Wifi模块是基于SDIO接口的符合wifi无线网络标准的嵌入式模块,内置无线网络协议IEEE802.11协议栈以及TCP/IP协议栈,能够实现用户主平台数据通过SDIO口到无线网络之间的转换。SDIO具有传输数据快,兼容SD、MMC接口等特点。对于SDIO接口的wifi,首先,它是一个sdio的卡的设备,然后具备了wifi的功能,所以,注册的时候还是先以sdio的卡的设备去注册的。然后检测到卡之后就要驱动他的wifi功能了,显然,他是用sdio的协议,通过发命令和数据来控制的。下面先简单回顾一下SDIO的相关知识。
1 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
SDIO 的应用将是未来嵌入式系统最重要的接口技术之一,并且也会取代目前 GPIO 式的 SPI 接口。
2 SDIO总线
SDIO总线 和 USB总线 类似,SDIO也有两端,其中一端是HOST端,另一端是device端。所有的通信都是由HOST端 发送 命令 开始的,Device端只要能解析命令,就可以相互通信。
CLK信号:HOST给DEVICE的 时钟信号,每个时钟周期传输一个命令。
CMD信号:双向 的信号,用于传送 命令 和 反应。
DAT0-DAT3 信号:四条用于传送的数据线。
VDD信号:电源信号。
VSS1,VSS2:电源地信号。
3 SDIO热插拔原理
方法:设置一个 定时器检查 或 插拔中断检测
硬件:假如GPG10(EINT18)用于SD卡检测
GPG10 为高电平 即没有插入SD卡
GPG10为低电平 即插入了SD卡
4 WIFI涉及到的管脚:
power管脚:
VCC_WIFI(wlan_power)
IO_VDD(wlan_io_regulator)
使能管脚:
WIFI_PDn(PL07):使能脚
WIFI_WAKE(PL05):中断脚
数据接口:
SD_CMD<---->PG01
SD_CLK<---->PG00
SD_D0 <---->PG02
SD_D1 <---->PG03
SD_D2 <---->PG04
SD_D3 <---->PG05
二 : MMC 识别SD设备过程
镇楼天书:
MMC子系统识别SD设备大致过程如下:
第一步 : 创建 mmc_bus,sdio_bus 两条虚拟总线
第二步 : 创建卡设备驱动,并挂载到其对应的总线上去 mmc设备驱动 : 初始化注册块设备驱动,在 drivers/mmc/card/block.c 中注册一个块设备,并将他挂载到前面创建的 mmc总线上。sdio设备驱动 : 初始化注册sdio设备驱动,在 drivers/mmc/card/sdio_uart.c 中注册一个sdio设备,并将他挂载到前面创建的 sdio总线上。
第三步 : 初始化 struct mmc_host结构体,
第四步 : 初始化 host->detect = mmc_rescan,该函数用于检测扫描添加卡设备,检测到卡设备后,根据卡类型创建 初始化该卡,并添加到对应的总线上去,本例是sdio设备,所以创建并初始化struct mmc_card 设备,并将其挂在sdio总线上!!!。
第五步 : 设置中断函数,内部是 mmc_schedule_delayed_work(&host->detect, delay);即 执行延期工作的任务 调用host->detect = mmc_rescan 检测扫描添加 mmc设备。此处中断中这是该扫描操作应该是为了识别那些在mmc_host 被添加之前插入的卡。
第六步 : 添加 mmc_host ,并开始执行 mmc_schedule_delayed_work(&host->detect, delay);即 执行延期工作的任务 调用host->detect = mmc_rescan 检测扫描添加 mmc设备。
第七步 : 等到识别到卡插入,添加struct mmc_card 设备到其对应的总线 其所在的总线开始调用match()进行匹配,匹配与其对应的驱动,我们这个是sdio设备,可以知道,匹配的就是第二步中的sdio设备驱动, 匹配成功后 调用总线驱动的prob, prob中再调用sdio设备驱动的prob。完成!!
具体分析如下:
第一步: 注册虚拟总线
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总线上。
初始化注册sdio设备驱动,在 drivers/mmc/card/sdio_uart.c 中注册一个sdio设备,并将他挂载到前面创建的 sdio总线上。
drivers/mmc/card/block.c
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;
}
drivers/mmc/card/sdio_uart.c
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驱动的prob()
此时Host的设备和驱动都已经创建完成,platform_bus_type总线匹配其driver 和 device 后调用Host对应的prob函数,该函数内部会进行 检测 初始化 slave等操作,检测sdio设备,并且创建 card 设备。
具体内容有:
4.1 mmc_alloc_host
1 分配 struct mmc_host结构体空间
2 设置 host->detect = mmc_rescan 初始化 延期工作的任务,该任务的工作是扫描插入的卡并添加卡
4.2 sunxi_mmc_resource_request()
1 sunxi_mmc_host 资源初始化 !!!
2 注册中断线程化 并完成 中断函数 以及 线程处理函数。其中 中断函数中调到最后其实就是
mmc_schedule_delayed_work(&host->detect, delay);即 执行延期工作的任务 调用host->detect = mmc_rescan 检测扫描添加 mmc设备
4.3 mmc->ops = &sunxi_mmc_ops;
1 卡控制器 操作函数集合初始化
4.4 ret = mmc_add_host(mmc);
1 创建 host->class_dev 设备(为代表当前设备host的 device节点),并注册它到sysfs中
2 开始添加host,mmc_start_host(host),该函数调用到最后也是mmc_schedule_delayed_work(&host->detect, delay) 即 执行延期工作的任务 调用host->detect = mmc_rescan 检测扫描添加 mmc设备
4.5 host驱动的prob总结
总结下来就是
1 初始化 struct mmc_host结构体,
2 初始化 host->detect = mmc_rescan,该函数用于检测扫描添加卡设备,检测到卡设备后,根据卡类型创建 初始化该卡,并添加到对应的总线上去,本例是sdio设备,所以创建并初始化struct mmc_card 设备,并将其挂在sdio总线上!!!。
3 设置中断函数,内部是 mmc_schedule_delayed_work(&host->detect, delay);即 执行延期工作的任务 调用host->detect = mmc_rescan 检测扫描添加 mmc设备。此处中断中这是该扫描操作应该是为了识别那些在mmc_host 被添加之前插入的卡。
4 添加 mmc_host ,并开始执行 mmc_schedule_delayed_work(&host->detect, delay);即 执行延期工作的任务 调用host->detect = mmc_rescan 检测扫描添加 mmc设备。
4.6 : 4.1 ~ 4.5 代码具体分析
具体分析如下:
/*总体框架如下*/
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.21 sunxi_mmc_host资源初始化 !!! 2 注册中断线程化 并完成 中断函数 以及 线程处理函数*/ret = sunxi_mmc_resource_request(host, pdev);.../*4.31 卡控制器 操作函数集合初始化*/mmc->ops = &sunxi_mmc_ops;
.../*4.41 添加mmc_host !!! core : host.c中提供*/ret = mmc_add_host(mmc);...
}
4.6.1 分析 mmc_alloc_host
a : 分析4.1 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 如下:drivers/mmc/core/host.cstruct 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()
而 mmc_rescan 定义于 drivers/mmc/core/core.c 分析一下 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_SDIO4 注册SDIO的各个功能模块5 添加SDIO卡err = mmc_add_card(host->card);}
e : 分析mmc_add_card()
定义于drivers/mmc/core/bus.c
drivers/mmc/core/bus.cint mmc_add_card(struct mmc_card *card)
{int ret;//将card注册进linux设备模型 注册结果就是可以在/sys/bus/mmc/devices目录下见到card 的名字ret = device_add(&card->dev);//struct device dev;/* the device */return 0;
}
4.6.2 : 分析 4.2 sunxi_mmc_resource_request(host, pdev);
分析 4.2 : ret = sunxi_mmc_resource_request(host, pdev);
说明: sunxi_mmc_resource_request(host, pdev); 主要的工作有两个,如下:
1 sunxi_mmc_host资源初始化 !!! 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设备
关键的工作是 注册中断线程化 并完成 中断函数 以及 线程处理函数。代码如下:
static int sunxi_mmc_resource_request(struct sunxi_mmc_host *host,struct platform_device *pdev)
{struct device_node *np = pdev->dev.of_node;/* 一 : sunxi_mmc_host资源初始化 !!! */1 初始化 host->ctl_spec_cap : 获取 该设备的设备节点的设备书关联节点 np 的 ctl-spec-caps 属性 = 0x1 ,用于初始化 host->ctl_spec_cap2 初始化 sunxi_mmc_host *host 成员1 初始化 struct sunxi_mmc_host *host->version_priv_dat = 设置好了的truct sunxi_mmc_ver_priv2 初始化 host->sunxi_mmc_clk_set_rate3 host->dma_tl4 host->idma_des_size_bits5 host->sunxi_mmc_thld_ctl6 host->sunxi_mmc_save_spec_reg7 host->sunxi_mmc_restore_spec_reg8 host->sunxi_mmc_set_rdtmout_reg9 host->sunxi_mmc_set_acmda10 host->phy_index = 1 即挂在sdc1下面11 host->sunxi_mmc_oclk_en = sunxi_mmc_oclk_onoff;12 host->sunxi_mmc_judge_retry = sunxi_mmc_judge_retry_v4p1x3 初始化 sunxi_mmc_host 的 卡控制器 struct mmc_host 的 mmc 供电信息
4 申请 struct sunxi_mmc_hos pin资源
5 struct sunxi_mmc_hos IO 映射相关初始化
6 struct sunxi_mmc_hos 时钟相关初始化
7 通过读写寄存器 复位,用到了精度为10ms的定时器 /* 二 : 注册中断线程化 并完成 中断函数 以及 线程处理函数 *///获取一个设备的中断号 并初始化 host->irq
host->irq = platform_get_irq(pdev, 0);//->request_threaded_irq : 申请中断进程
/*
int devm_request_threaded_irq(struct device *dev, unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id)request_threaded_irq(irq, handler, thread_fn, irqflags, devname,dev_id);//request_threaded_irq 是在将上半部的硬件中断处理缩短为只确定硬体中断来自我们要处理的装置,唤醒kernel thread 执行后续中断任务。request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id) ;
irq:表示申请的中断号 : host->irq
handler:表示中断服务例程 : sunxi_mmc_irq ---> 有检测卡插入
thread_fn:中断线程化 : sunxi_mmc_handle_bottom_half,
irqflags : 表示中断标志位 : 0
*devnam : 表示请求中断的设备的名称 : "sunxi-mmc"
dev_id: 对应于request_irq()函数中所传递的第五个参数,可取任意值,但必须唯一能够代表发出中断请求的设备,通常取描述该设备的结构体。 共享中断时所用 : host
*/ret = devm_request_threaded_irq(&pdev->dev, host->irq, sunxi_mmc_irq,sunxi_mmc_handle_bottom_half, 0,"sunxi-mmc", host);disable_irq(host->irq);//禁止中断return ret;
}// mhr 设备 sunxi-mmc 请求的中断服务 : 有检测卡插入
static irqreturn_t sunxi_mmc_irq(int irq, void *dev_id)
{...mmc_detect_change(host->mmc, msecs_to_jiffies(500));//检测卡插入情况
...}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);
4.6.3 : 分析4.3 卡控制器 操作函数集合初始化
卡控制器 操作函数集合初始化 这没什么好说的
4.6.4 : 分析4.4 mmc_add_host(mmc);
添加mmc_host:mmc_add_host(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);
第五步 : 匹配设备与驱动,并调用驱动prob
添加设备或驱动到总线后 总线匹配成功 调用总线驱动的prob, prob中调用驱动的prob
drivers/mmc/core/sdio_bus.c
#define to_sdio_driver(d) container_of(d, struct sdio_driver, drv)//每次添加到该总线设备或驱动都会 调用sdio总线匹配函数遍历 匹配成功后 调用prob
static int sdio_bus_match(struct device *dev, struct device_driver *drv)
{struct sdio_func *func = dev_to_sdio_func(dev);struct sdio_driver *sdrv = to_sdio_driver(drv);if (sdio_match_device(func, sdrv))return 1;return 0;
}static int sdio_bus_probe(struct device *dev)
{struct sdio_driver *drv = to_sdio_driver(dev->driver);
...int ret;id = sdio_match_device(func, drv); //匹配...ret = drv->probe(func, id);//调用driver的probreturn 0;}static struct bus_type sdio_bus_type = {.name = "sdio",.dev_attrs = sdio_dev_attrs,.match = sdio_bus_match,//总线匹配函数,当一个新设备或者驱动被添加到这个总线时,这个方法会被调用一次或多次.uevent = sdio_bus_uevent,.probe = sdio_bus_probe,//匹配成功 调用prob.remove = sdio_bus_remove,.pm = SDIO_PM_OPS_PTR,
};
三 : MMC子系统简述
1 MMC子系统简述
Linux kernel把 mmc,sd,sdio三者的驱动代码整合到一起,称为MMC子系统,源码位于 drivers/mmc下,分为 三部分,分别是 card,host,core三个目录。其中 card 用于构建一个块设备作为mmc子系统与用户空间沟通的桥梁,core抽象出了 mmc,sd,sdio 三者通用的操作,host是各个平台上的host驱动代码,如目前笔者使用的全志T7平台。
core : MMC核心层,抽象出了 mmc,sd,sdio 三者通用的操作,完成不同协议和规范的实现,为host层和设备驱动层提供接口函数。MMC核心层由三个部分组成,分别是 MMC,SD和SDIO,分别为三类设备驱动提供接口函数。
host : 主机驱动层:针对不同平台主机端的SDHC、MMC控制器的驱动。
card : 设备驱动层,可以理解为驱动层,针对不同的设备驱动程序。如SD卡、T-flash卡、SDIO接口的GPS和wifi等设备驱动,对于mmc设备,card层通常会构建一个块设备作为mmc子系统与用户空间沟通的桥梁。
mmc设备对应的是 驱动为 block.c
sdio设备对应的是 驱动为 sdio_uart.c
作为驱动工程师,需要完成的部分一般只有host层面。
2 SD/MMC/SDIO概念区分
SD 是一种 flash memory card 的标准,也就是一般常见的 SD 记忆卡,而 MMC 则是较早的一种记忆卡标准,目前已经被 SD 标准所取代。而sdio上面已经说过了。
3 从 bus,device,driver看mmc子系统
linux 任何的驱动在内核中最终都是抽象为 bus,device,driver三者之间互相作用,MMC子系统涉及到了三条总线:
Host驱动(MMC主机控制器驱动)相对应的 driver 和 device 挂载在linux虚拟抽象总线platform_bus_type上。card驱动相对应的 driver 挂载在MMC子系统自己创建的虚拟总线 mmc_bus_type或者sdio_bus_type上。card设备,如 sdio设备 wfi模块或者mmc卡等等,其对应的 device 挂载在MMC子系统自己创建的虚拟总线 mmc_bus_type或者sdio_bus_type上。
并且这些 bus driver device对象的初始化流程如下:
第一 步: Host驱动的 device : 设备树
第二步 : mmc_bus,sdio_bus 两条虚拟总线
core驱动 MMC核心层代码 core.c中的 subsys_initcall()创建 mmc_bus,sdio_bus 两条虚拟总线
第三步 : 驱动注册挂载
mmc驱动
card 驱动初始化时, block.c中的module_init(mmc_blk_init); mmc_register_driver(&mmc_driver mmc_driver);将mmc设备驱动挂载到 mmc_bus_type总线上
sdio 驱动
sdio 驱动初始化时, sdio_uart.c中的module_init(sdio_uart_init); sdio_register_driver(&sdio_uart_driver);;将sdio设备驱动挂载到 sdio_bus_type总线上
第四步
Host驱动的 driver : drivers/mmc/host/sunxi-mmc.c
第五 sdio 、mmc 设备
此时 Host的设备和驱动都已经创建完成,匹配后调用Host对应的prob函数,该函数内部会进行 检测 初始化 slave等操作,检测card 设备,并且创建 card 设备。如检测到sdio设备后 创建一个device对象 并挂在到 sdio_bus_type上。总线驱动匹配device 和 driver 成功后,调用总线prob,内部调用 driver的prob。
四 : 代码分析草稿
以下是分析代码的笔记,方便随时翻阅
主要有如下文件
drivers/mmc/host/sunxi-mmc.c
drivers/mmc/host/sunxi-mmc-v4p1x.c
drivers/mmc/core/host.c
drivers/mmc/core/core.c
drivers/mmc/core/sdio.c
drivers/mmc/core/sd.c
drivers/mmc/core/bus.c
drivers/mmc/card/block.c
drivers/mmc/card/sdio_uart.c
drivers/mmc/host/sunxi-mmc.c
static irqreturn_t sunxi_mmc_handle_bottom_half(int irq, void *dev_id)
{
...
}// mhr 设备 sunxi-mmc 请求的中断服务 : 有检测卡插入
static irqreturn_t sunxi_mmc_irq(int irq, void *dev_id)
{struct sunxi_mmc_host *host = dev_id;struct mmc_request *mrq;u32 msk_int, idma_int;bool finalize = false;bool sdio_int = false;irqreturn_t ret = IRQ_HANDLED;
...if (host->dat3_imask) {if (msk_int & SDXC_CARD_INSERT) {mmc_writel(host, REG_RINTR, SDXC_CARD_INSERT);mmc_detect_change(host->mmc, msecs_to_jiffies(500));//检测卡插入情况goto out;}if (msk_int & SDXC_CARD_REMOVE) {mmc_writel(host, REG_RINTR, SDXC_CARD_REMOVE);mmc_detect_change(host->mmc, msecs_to_jiffies(50));//检测卡插入情况goto out;}
}}/* sunxi_mmc_host 资源初始化
1 初始化 host->ctl_spec_cap : 获取 该设备的设备节点的设备书关联节点 np 的 ctl-spec-caps 属性 = 0x1 ,用于初始化 host->ctl_spec_cap
2 初始化 sunxi_mmc_host *host 成员1 初始化 struct sunxi_mmc_host *host->version_priv_dat = 设置好了的truct sunxi_mmc_ver_priv2 初始化 host->sunxi_mmc_clk_set_rate3 host->dma_tl4 host->idma_des_size_bits5 host->sunxi_mmc_thld_ctl6 host->sunxi_mmc_save_spec_reg7 host->sunxi_mmc_restore_spec_reg8 host->sunxi_mmc_set_rdtmout_reg9 host->sunxi_mmc_set_acmda10 host->phy_index = 1 即挂在sdc1下面11 host->sunxi_mmc_oclk_en = sunxi_mmc_oclk_onoff;12 host->sunxi_mmc_judge_retry = sunxi_mmc_judge_retry_v4p1x3 初始化 sunxi_mmc_host 的 卡控制器 struct mmc_host 的 mmc 供电信息
4 申请 struct sunxi_mmc_hos pin资源
5 struct sunxi_mmc_hos IO 映射相关初始化
6 struct sunxi_mmc_hos 时钟相关初始化
7 通过读写寄存器 复位,用到了精度为10ms的定时器
8 获取一个设备的中断号 并初始化 host->irq
9 注册中断线程化 并完成 中断函数 以及 线程处理函数
10 关闭中断
*/
static int sunxi_mmc_resource_request(struct sunxi_mmc_host *host,struct platform_device *pdev)
{struct device_node *np = pdev->dev.of_node;int ret;u32 caps_val = 0;struct gpio_config flags;/*int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz);读取设备结点np的属性名为propname,类型为8、16、32、64位整型数组的属性。存储在out_values 对于32位处理器来讲,最常用的是of_property_read_u32_array()。全志是是将设备数写成了 config.fex文件,但是看这里解析依然是设备树的解析方式,说明 fex文件最后还是会编辑成设备数供代码检索。所以可以吧fex文件当成设备数
tools/pack/chips/sun8iw17p1/configs/t7-p1/sys_config.fex :
[sdc1] :
...
ctl-spec-caps = 0x1 //控制特殊功能控制,满足客户需求
...
*///读取设备结点np的属性名为ctl-spec-cap的属性 存储在 caps_val
ret = of_property_read_u32(np, "ctl-spec-caps", &caps_val);
if (!ret) {host->ctl_spec_cap |= caps_val; //获取设备数 ctl-spec-caps 值 初始化给 struct sunxi_mmc_host->ctl_spec_capdev_info(&pdev->dev, "***ctl-spec-caps*** %x\n", host->ctl_spec_cap);
}/*
int of_device_is_compatible(const struct device_node *device,const char *compat);
判断设备结点的compatible 属性是否包含compat指定的字符串
allwinner,sunxi-mmc-v4p1x : sdc0 1 3
*/
if (of_device_is_compatible(np, "allwinner,sunxi-mmc-v4p1x")) {int phy_index = 0;//选择sdc总线号 :对比 device_type 属性项的字符是否匹配,由于我们之打开了sdc1,所以 phy_index = 1;if (of_property_match_string(np, "device_type", "sdc0") == 0) {phy_index = 0;} else if (of_property_match_string(np, "device_type", "sdc1") == 0) {phy_index = 1;} else if (of_property_match_string(np, "device_type", "sdc2") == 0) {phy_index = 2;} else if (of_property_match_string(np, "device_type", "sdc3") == 0) {phy_index = 3;} else {dev_err(&pdev->dev, "No sdmmc device,check dts\n");}/*初始化 sunxi_mmc_host *host 成员1 初始化 struct sunxi_mmc_host *host->version_priv_dat = 设置好了的truct sunxi_mmc_ver_priv2 初始化 host->sunxi_mmc_clk_set_rate3 host->dma_tl4 host->idma_des_size_bits5 host->sunxi_mmc_thld_ctl6 host->sunxi_mmc_save_spec_reg7 host->sunxi_mmc_restore_spec_reg8 host->sunxi_mmc_set_rdtmout_reg9 host->sunxi_mmc_set_acmda10 host->phy_index = 1 即挂在sdc1下面11 host->sunxi_mmc_oclk_en = sunxi_mmc_oclk_onoff;12 host->sunxi_mmc_judge_retry = sunxi_mmc_judge_retry_v4p1x*/sunxi_mmc_init_priv_v4p1x(host, pdev, phy_index);
}...//初始化 卡控制器 struct mmc_host 的 mmc 供电信息
ret = sunxi_mmc_regulator_get_supply(host->mmc);
if (ret) {return ret;
}
...//从设备树中读取 card-pwr-gpios 的 GPIO 配置编号和标志,并没有在设备数和fex文件中找到该 引脚host->card_pwr_gpio = of_get_named_gpio_flags(np, "card-pwr-gpios", 0, (enum of_gpio_flags *)&flags);//判断该gpio是否合法 合法有效返回0if (gpio_is_valid(host->card_pwr_gpio)) {//如果不合法//请求一个gpio 名为 card-pwr-gpio,并进行初始化ret = devm_gpio_request_one(&pdev->dev, host->card_pwr_gpio, GPIOF_DIR_OUT, "card-pwr-gpios");}//申请pin资源。/*struct pinctrl *pinctrl;struct pinctrl_state *pins_default;struct pinctrl_state *pins_sleep;*/host->pinctrl = devm_pinctrl_get(&pdev->dev);host->pins_default = pinctrl_lookup_state(host->pinctrl,PINCTRL_STATE_DEFAULT);host->pins_sleep = pinctrl_lookup_state(host->pinctrl,PINCTRL_STATE_SLEEP);//IO 映射相关初始化
host->reg_base = devm_ioremap_resource(&pdev->dev,platform_get_resource(pdev,IORESOURCE_MEM, 0));/*时钟相关初始化
struct clk *clk_ahb;
struct clk *clk_mmc;
struct clk *clk_rst;
*/
host->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
host->clk_mmc = devm_clk_get(&pdev->dev, "mmc");
host->clk_rst = devm_clk_get(&pdev->dev, "rst");...//platform_get_irq(pdev, 0):获取一个设备的中断号。 pdev:平台总线设备; num: 中断号索引,即想要获取的第几个中断号,从0开始//获取一个设备的中断号 并初始化 host->irqhost->irq = platform_get_irq(pdev, 0);//->request_threaded_irq : 申请中断进程
/*
int devm_request_threaded_irq(struct device *dev, unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id)request_threaded_irq(irq, handler, thread_fn, irqflags, devname,dev_id);//request_threaded_irq 是在将上半部的硬件中断处理缩短为只确定硬体中断来自我们要处理的装置,唤醒kernel thread 执行后续中断任务。request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id) ;
irq:表示申请的中断号 : host->irq
handler:表示中断服务例程 : sunxi_mmc_irq ---> 有检测卡插入
thread_fn:中断线程化 : sunxi_mmc_handle_bottom_half,
irqflags : 表示中断标志位 : 0
*devnam : 表示请求中断的设备的名称 : "sunxi-mmc"
dev_id: 对应于request_irq()函数中所传递的第五个参数,可取任意值,但必须唯一能够代表发出中断请求的设备,通常取描述该设备的结构体。 共享中断时所用 : host
*/ret = devm_request_threaded_irq(&pdev->dev, host->irq, sunxi_mmc_irq,sunxi_mmc_handle_bottom_half, 0,"sunxi-mmc", host);disable_irq(host->irq);//禁止中断return ret;
}/*
//匹配 sun8iw17p1-carvout.dtsi中 compatible = "allwinner,sunxi-mmc-v4p1x"; 成功
1 分配 struct sunxi_mmc_host 结构体
2 将 struct mmc_host描述卡控制器 绑定到struct sunxi_mmc_host
3 初始化 struct sunxi_mmc_host 资源
4 初始化 struct mmc_host 描述卡控制器
5 添加 mmc_host
*/
static int sunxi_mmc_probe(struct platform_device *pdev)
{struct sunxi_mmc_host *host; //平台控制器信息结构体struct mmc_host *mmc;//描述卡控制器 结构体int ret;/*
1. 申请 描述卡控制器 结构体
2. 设置 host->detect = mmc_rescan
*/
mmc = mmc_alloc_host(sizeof(struct sunxi_mmc_host), &pdev->dev);//定义于drivers/mmc/core/host.c// return (void *)mmc_host->private; 获取卡控制器私有指针
host = mmc_priv(mmc);
host->mmc = mmc; //将 卡控制器结构体 绑定到 mmc 主机端信息结构体的 mmc
spin_lock_init(&host->lock);//sunxi_mmc_host资源初始化 !!!
ret = sunxi_mmc_resource_request(host, pdev);...//卡控制器 结构体 操作函数集合初始化mmc->ops = &sunxi_mmc_ops;
.../* 400kHz ~ 50MHz 最大最小频率*/
mmc->f_min = 400000;
mmc->f_max = 50000000;
//主机能力初始化
mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED | MMC_CAP_ERASE | MMC_CAP_WAIT_WHILE_BUSY;
mmc->max_busy_timeout = SUNXI_MAX_R1B_TIMEOUT_MS; /*ms 最大忙超时(毫秒)*/
...//添加mmc_host !!! core : host.c中提供
ret = mmc_add_host(mmc);ret = mmc_create_sys_fs(host, pdev);
platform_set_drvdata(pdev, mmc);
...
}//匹配 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
drivers/mmc/host/sunxi-mmc-v4p1x.c
/*
初始化 sunxi_mmc_host *host 成员
1 初始化 struct sunxi_mmc_host *host->version_priv_dat = 设置好了的truct sunxi_mmc_ver_priv
2 初始化 host->sunxi_mmc_clk_set_rate
3 host->dma_tl
4 host->idma_des_size_bits
5 host->sunxi_mmc_thld_ctl
6 host->sunxi_mmc_save_spec_reg
7 host->sunxi_mmc_restore_spec_reg
8 host->sunxi_mmc_set_rdtmout_reg
9 host->sunxi_mmc_set_acmda
10 host->phy_index = 1 即挂在sdc1下面
11 host->sunxi_mmc_oclk_en = sunxi_mmc_oclk_onoff;
12 host->sunxi_mmc_judge_retry = sunxi_mmc_judge_retry_v4p1x;*/
void sunxi_mmc_init_priv_v4p1x(struct sunxi_mmc_host *host, struct platform_device *pdev, int phy_index)
{struct sunxi_mmc_ver_priv *ver_priv =devm_kzalloc(&pdev->dev, sizeof(struct sunxi_mmc_ver_priv),GFP_KERNEL);host->version_priv_dat = ver_priv; //将 truct sunxi_mmc_ver_priv *ver_priv绑定到 struct sunxi_mmc_host *host->version_priv_dat 即主机信息结构提私有数据/*初始化不同时钟对应的 配置信息*/
...host->sunxi_mmc_clk_set_rate = sunxi_mmc_clk_set_rate_for_sdmmc_v4p1x; //初始化 struct sunxi_mmc_host *host->sunxi_mmc_clk_set_rate 为该函数 sunxi_mmc_clk_set_rate_for_sdmmc_v4p1xhost->dma_tl = SUNXI_DMA_TL_SDMMC_V4P1X; //#define SUNXI_DMA_TL_SDMMC_V4P1X ((0x2<<28)|(7<<16)|248) DMA触发电平设置 host->idma_des_size_bits = SUNXI_DES_SIZE_SDMMC_V4P1X;//#define SUNXI_DES_SIZE_SDMMC_V4P1X (15) 一个DMA DES可以传输数据大小
...host->phy_index = phy_index; //初始化 struct sunxi_mmc_host *host->phy_index = 1 即挂在sdc1下面
...
}
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);int mmc_add_host(struct mmc_host *host)
{int err;WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&!host->ops->enable_sdio_irq);
//创建一个设备,并注册它到sysfs中 。 host->class_dev为代表当前设备的 device节点
err = device_add(&host->class_dev);
...mmc_host_clk_sysfs_init(host);/*开始添加host ,定义于drivers/mmc/core/core.c */mmc_start_host(host);
...return 0;
}
drivers/mmc/core/core.c
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
#ifdef CONFIG_MMC_DEBUGunsigned long flags;spin_lock_irqsave(&host->lock, flags);WARN_ON(host->removed);spin_unlock_irqrestore(&host->lock, flags);
#endifhost->detect_change = 1;//mmc 设备检测标志位//Needs polling for card-detection 轮循卡检测
if(!(host->caps&MMC_CAP_NEEDS_POLL))wake_lock(&host->detect_wake_lock);
mmc_schedule_delayed_work(&host->detect, delay);//延迟 delay 调用host->detect = mmc_rescan 检测 mmc设备
}EXPORT_SYMBOL(mmc_detect_change);/*
检测卡类型添加卡设备
*/
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;
}/* host扫描*/
void mmc_rescan(struct work_struct *work)
{/*struct mmc_host/struct delayed_work/struct work_struct*/struct mmc_host *host = container_of(work, struct mmc_host, detect.work);//通过struct work_struct work的地址 得到 struct mmc_host 首地址inbool extend_wakelock = false;bool present = false;struct mmc_bus_ops * pre_bus_ops = NULL;//总线驱动的操作函数,即SD总线所具备的驱动能力 //如果设置了 禁止 mmc设备检测 直接返回
if (host->rescan_disable)return;/* If there is a non-removable card registered, only scan once / 如果注册了不可移动卡(即 不可热拔插设备 如emmc设备),则只扫描一次,直接返回#define MMC_CAP_NONREMOVABLE (1 << 8)//N不可移动mmc设备,如EMMC
int rescan_entered;// used with nonremovable devices 不可热拔插设备标志位*/
if ((host->caps & MMC_CAP_NONREMOVABLE) && host->rescan_entered){wake_unlock(&host->detect_wake_lock);return;
}
host->rescan_entered = 1;//总线被使用的引用计数 +1
mmc_bus_get(host);//初始化总线驱动的操作函数 ,绑定为 (struct mmc_bus_ops *)host->bus_ops;
pre_bus_ops = (struct mmc_bus_ops *)host->bus_ops;/*如果是可热拔插卡设备,检查该卡是否仍然存在* if there is a _removable_ card registered, check whether it is* still present/
host->bus_ops : 总线驱动的操作函数集合
host->bus_ops->detect : 总线驱动的操作函数集合中的检测函数
host->bus_dead : bus has been released
host->caps :Host capabilities
MMC_CAP_NONREMOVABL : Nonremovable e.g. eMMC 不可移动mmc设备,如EMM*/
if (host->bus_ops && host->bus_ops->detect && !host->bus_dead&& !(host->caps & MMC_CAP_NONREMOVABLE))host->bus_ops->detect(host);//调用总线驱动的检测函数host->detect_change = 0;//设置 card detect flag = 0/* If the card was removed the bus will be marked* as dead - extend the wakelock so userspace* can respond / 如果卡已经不存在了
*/
if (host->bus_dead){//总线被释放标志 代表卡已经不存在了extend_wakelock = 1;present = false;
}/* 如果我们发现卡已不存在,让mmc总线释放 总线以及总线操作* Let mmc_bus_put() free the bus/bus_ops if we've found that* the card is no longer present.*/
mmc_bus_put(host);//总线被使用引用计数-1 如果是最后一个引用 则释放总线
mmc_bus_get(host);//总线被使用引用计数+1//如果设置了 host->caps & MMC_CAP_NEEDS_POLL 轮循检测卡标志
if((host->caps&MMC_CAP_NEEDS_POLL)){//轮循检测五次 每次间隔1ms 如果连续五次都检测 卡存在 则代表卡存在 返回 1。如果连续五次都是0 则代表卡不存在 返回0int cd_sta = sunxi_mmc_debdetect(host);//也是调用 gpio_val += host->ops->get_cd(host);/*如果 卡控制器的操作函数 get_cd 存在,并且 host->rescan_pre_state^cd_sta 位异或 结果不为0如果控制器的操作函数 get_cd() 存在,并且本次检测的结果和上一次检测的结果不一样 则进入执行*/ if((host->ops->get_cd)&&(host->rescan_pre_state^cd_sta)){host->rescan_pre_state = cd_sta;//初始化 host->rescan_pre_statepr_err("*%s detect cd change*\n",mmc_hostname(host));wake_lock(&host->detect_wake_lock);//加锁pr_err("*%s lock*\n",mmc_hostname(host));/*如果已经走过 pre_bus_ops = (struct mmc_bus_ops *)host->bus_ops; pre_bus_ops不为空但是此时host->bus_ops == NUL 为空。则说明 卡忽然拔掉了。要重新扫描*/}else if((pre_bus_ops != NULL)&& (host->bus_ops == NULL)){//如果卡拔差过快,get_cd()失效,要使用 host->bus_ops->detect(host),即总线驱动的操作函数的检测函数去检测卡是否处于空闲状态//如果如果卡不在或者处于空闲状态,我们必须重新扫描它//If card insert and put out so quict,get_cd function maybe not effective//So we use host->bus_ops->detect(host) to find if the card is in or in idle state//If card is not in or in idle state,we must rescan itpr_err("*%s detect card change*\n",mmc_hostname(host));wake_lock(&host->detect_wake_lock);/加锁pr_err("*%s lock*\n",mmc_hostname(host));//轮循检测(中断轮循???)}else{mmc_bus_put(host);//总线被使用引用计数-1 如果是最后一个引用 则释放总线mmc_schedule_delayed_work(&host->detect, HZ);//重新调用 mmc_rescanreturn;}
}/* if there still is a card present, stop here 如果卡仍存在, stop here*/
if (host->bus_ops != NULL) {mmc_bus_put(host);present = true;goto out;//停在这里
}.../*
static struct workqueue_struct *workqueue;
static const unsigned freqs[] = { 400000, 300000, 200000, 100000 };
i=4
*/for (i = 0; i < ARRAY_SIZE(freqs); i++) {//轮循的频率不能小于所设定的最小频率if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min))) {char *mmc_sunxi_event[] = { "mmc_sunxi_event= mmc core:init ok", NULL };kobject_uevent_env(&mmc_dev(host)->kobj, KOBJ_CHANGE, mmc_sunxi_event);extend_wakelock = true;present = true;break;}if (freqs[i] <= host->f_min) {char *mmc_sunxi_event[] = { "mmc_sunxi_event= mmc core:init failed", NULL };kobject_uevent_env(&mmc_dev(host)->kobj, KOBJ_CHANGE, mmc_sunxi_event);break;}
}
mmc_release_host(host);}void mmc_start_host(struct mmc_host *host)
{host->f_init = max(freqs[0], host->f_min);host->rescan_disable = 0;//disable card detection 禁止mmc设备检测/*host->caps2 : More host capabilities#define MMC_CAP2_NO_PRESCAN_POWERUP (1 << 14) Don't power up before scan host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP = > 扫描前不要通电 *///如果是 扫描前不要通电if (host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP)mmc_power_off(host);// 掉电else{//如果 不 需要轮询以检测mmc设备if(!(host->caps&MMC_CAP_NEEDS_POLL))mmc_power_up(host);//上电}...mmc_detect_change(host, 0);//调用host->detect 检测 mmc设备
}static int __init mmc_init(void)
{int ret;workqueue = alloc_ordered_workqueue("kmmcd", 0);
if (!workqueue)return -ENOMEM;ret = mmc_register_bus();//创建 mmc总线
if (ret)goto destroy_workqueue;ret = mmc_register_host_class();
if (ret)goto unregister_bus;ret = sdio_register_bus();//创建 sdio总线
if (ret)goto unregister_host_class;return 0;unregister_host_class:mmc_unregister_host_class();
unregister_bus:mmc_unregister_bus();
destroy_workqueue:destroy_workqueue(workqueue);return ret;
}
drivers/mmc/core/sdio.c
/*屏蔽我们不支持的任何电压并选择最低电压
断host实际所支持的电压与card所需要的电压是否匹配,如果匹配,那么ocr的值就非0,否则就为0*/
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr)
{int bit;ocr &= host->ocr_avail;//判断host实际所支持的电压与card所需要的电压是否匹配,如果匹配,那么ocr的值就非0,否则就为0bit = ffs(ocr);
if (bit) {bit -= 1;ocr &= 3 << bit;mmc_host_clk_hold(host);host->ios.vdd = bit;mmc_set_ios(host);mmc_host_clk_release(host);
} else {pr_warning("%s: host doesn't support card's voltages\n",mmc_hostname(host));ocr = 0;
}return ocr;
}//初始化sdio设备
/*
1 通过函数mmc_alloc_card分配一个mmc_card的变量card
2 通过读取R4命令中的bit27(也就是Memory Present)来判断此卡是纯IO卡 ,还是同时包含存储功能,所以card->type = MMC_TYPE_SDIO
3 通过发送CMD3命令获取设备的从地址(relative addr),并且存放在变量card->rca中
4 通过发送CMD7,选中相应从地址的卡
5 通过调用函数mmc_set_clock设置卡工作的时钟频率
6 通过发送CMD52命令,设置4位数据传输模式
*/
static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr, struct mmc_card *oldcard, int powered_resume)
{struct mmc_card *card;int err;int retries = 10;/** 1 struct mmc_card申请分配空间*/
card = mmc_alloc_card(host, NULL);
if (IS_ERR(card)) {err = PTR_ERR(card);goto err;
}//通过读取R4命令中的bit27(也就是Memory Present)来判断此卡是纯IO卡 ,还是同时包含存储功能,所以card->type = MMC_TYPE_SDIO
if ((ocr & R4_MEMORY_PRESENT) &&mmc_sd_get_cid(host, host->ocr & ocr, card->raw_cid, NULL) == 0) {card->type = MMC_TYPE_SD_COMBO;//设置卡类型 SD combo (IO+mem) cardif (oldcard && (oldcard->type != MMC_TYPE_SD_COMBO ||memcmp(card->raw_cid, oldcard->raw_cid, sizeof(card->raw_cid)) != 0)) {mmc_remove_card(card);return -ENOENT;}
} else {card->type = MMC_TYPE_SDIO;if (oldcard && oldcard->type != MMC_TYPE_SDIO) {mmc_remove_card(card);return -ENOENT;}
}/* 通过发送CMD3命令获取设备的从地址(relative addr),并且存放在变量card->rca中*/
if (!powered_resume && !mmc_host_is_spi(host)) {err = mmc_send_relative_addr(host, &card->rca);if (err)goto remove;if (oldcard)oldcard->rca = card->rca;
}/* 通过发送CMD7,选中相应从地址的卡* Select card, as all following commands rely on that.*/
if (!powered_resume && !mmc_host_is_spi(host)) {err = mmc_select_card(card);if (err)goto remove;
}if (card->quirks & MMC_QUIRK_NONSTD_SDIO) {//通过调用函数mmc_set_clock设置卡工作的时钟频率mmc_set_clock(host, card->cis.max_dtr);if (card->cccr.high_speed) {mmc_card_set_highspeed(card);mmc_set_timing(card->host, MMC_TIMING_SD_HS);}goto finish;
}//通过发送CMD52命令,设置4位数据传输模式
err = sdio_enable_4bit_bus(card);}//卡控制器操作函数集合
static const struct mmc_bus_ops mmc_sdio_ops = {.remove = mmc_sdio_remove,.detect = mmc_sdio_detect,.suspend = mmc_sdio_suspend,.resume = mmc_sdio_resume,.power_restore = mmc_sdio_power_restore,.alive = mmc_sdio_alive,
};//初始化功能模块
/*
1 分配sdio_func结构体变量,该结构体存储了功能块的参数
2 给功能块编号,编号是从1到7(因为一个SDIO设备最多只有7个功能块),存储在变量func->num中
3 读取SDIO卡中的FBR寄存器中关于该卡的功能类型的数据,存储在func->class变量中
4 读取SDIO卡中的CIS寄存器的内容
*/
static int sdio_init_func(struct mmc_card *card, unsigned int fn)
{int ret;struct sdio_func *func;BUG_ON(fn > SDIO_MAX_FUNCS);func = sdio_alloc_func(card);//分配sdio_func结构体变量,该结构体存储了功能块的参数if (IS_ERR(func))return PTR_ERR(func);func->num = fn;//给功能块编号,编号是从1到7(因为一个SDIO设备最多只有7个功能块),存储在变量func->num中if (!(card->quirks & MMC_QUIRK_NONSTD_SDIO)) {ret = sdio_read_fbr(func);//读取SDIO卡中的FBR寄存器中关于该卡的功能类型的数据,存储在func->class变量中if (ret)goto fail;ret = sdio_read_func_cis(func);//读取SDIO卡中的CIS寄存器的内容if (ret)goto fail;
} else {func->vendor = func->card->cis.vendor;func->device = func->card->cis.device;func->max_blksize = func->card->cis.blksize;
}card->sdio_func[fn - 1] = func;return 0;fail:/** It is okay to remove the function here even though we hold* the host lock as we haven't registered the device yet.*/sdio_remove_func(func);return ret;
}/*host 扫描检测SDIO的识别过程 :1 向卡发送CMD5命令,该命令有两个作用第一,通过判断卡是否有反馈信息来判断是否为SDIO设备(只有SDIO设备才对CMD5命令有反馈,其他卡是没有回馈的)第二,如果是SDIO设备,就会给host反馈电压信息,就是说告诉host,本卡所能支持的电压是多少多少。
2 host根据SDIO卡反馈回来的电压要求,给其提供合适的电压。
3 初始化该SDIO卡
4 注册SDIO的各个功能模块
5 注册SDIO卡
*/
int mmc_attach_sdio(struct mmc_host *host)
{int err, i, funcs;u32 ocr;struct mmc_card *card;BUG_ON(!host);
WARN_ON(!host->claimed);/* 1 判断是否是SDIO卡协议卡
ocr 是指 card 內部的 Operation Condition Register (OCR) 讀出來的值,發送 CMD41 CMD55 讀取 OCR 的值
向卡发送CMD5命令,最后一个参数ocr,它是存储反馈命令的,SDIO设备对CMD5的反馈命令为R4。该命令有两个作用
1 通过判断卡是否有反馈信息来判断是否为SDIO设备(只有SDIO设备才对CMD5命令有反馈,其他卡是没有回馈的)
2 如果是SDIO设备,就会给host反馈电压信息,就是说告诉host,本卡所能支持的电压是多少多少
*/
err = mmc_send_io_op_cond(host, 0, &ocr);/* 2 绑定了卡 的操作集合 到 卡控制器的操作函数集合
设置host的bus操作函数是mmc_sdio_ops,即初始化 host->bus_ops 为 struct mmc_bus_ops mmc_sdio_ops
*/
mmc_attach_bus(host, &mmc_sdio_ops);/* 3
host根据SDIO卡反馈回来的电压要求,给其提供合适的电压*/
if (ocr & 0x7F) {ocr &= ~0x7F;
}//屏蔽我们不支持的任何电压并选择最低电压,并初始化 host->ocr 电压
host->ocr = mmc_select_voltage(host, ocr);/** Can we support the voltage(s) of the card(s)?*/
if (!host->ocr) {err = -EINVAL;goto err;
}/* 初始化该SDIO卡
1 通过函数mmc_alloc_card分配一个mmc_card的变量card
2 通过读取R4命令中的bit27(也就是Memory Present)来判断此卡是纯IO卡 ,还是同时包含存储功能,所以card->type = MMC_TYPE_SDIO
3 通过发送CMD3命令获取设备的从地址(relative addr),并且存放在变量card->rca中
4 通过发送CMD7,选中相应从地址的卡
5 通过调用函数mmc_set_clock设置卡工作的时钟频率
6 通过发送CMD52命令,设置4位数据传输模式
*/
err = mmc_sdio_init_card(host, host->ocr, NULL, 0);card = host->card;//绑定 连接到此主控制器的SD卡设备 到 控制器//变量funcs存储该SDIO卡所包含的IO功能块的个数
funcs = (ocr & 0x70000000) >> 28;
card->sdio_funcs = 0;/* 初始化SDIO的各个功能模块*/
for (i = 0; i < funcs; i++, card->sdio_funcs++) {//初始化功能模块sdio_init_func(host->card, i + 1);
}/* 将功能模块逐个的注册进设备模型*/
for (i = 0;i < funcs;i++) {err = sdio_add_func(host->card->sdio_func[i]);if (err)goto remove_added;
}/*注册SDIO卡
1 给card命名,格式为host名字:从地址,如: mmc2:0001
2 根据card->type来分辨出card的类型,给赋予相应的字符串
3 打印信息,具体不解释 如 mmc2:new high speed SDIO card at address 0001(通常可以通过查看内核启动信息中是否有该语句来判断card是否被正确识别)
4 将card注册进linux设备模型 注册结果就是可以在/sys/bus/mmc/devices目录下见到card 的名字,如 mmc2:0001
*/
err = mmc_add_card(host->card);}
drivers/mmc/core/bus.c
/*** mmc_register_driver - register a media driver* @drv: MMC media driver*/
int mmc_register_driver(struct mmc_driver *drv)
{drv->drv.bus = &mmc_bus_type;return driver_register(&drv->drv);
}EXPORT_SYMBOL(mmc_register_driver);static int mmc_bus_match(struct device *dev, struct device_driver *drv)
{return 1;/*永远匹配成功*/
}//匹配成功后调用总线的prob
static int mmc_bus_probe(struct device *dev)
{struct mmc_driver *drv = to_mmc_driver(dev->driver);struct mmc_card *card = mmc_dev_to_card(dev);return drv->probe(card);//最终调用到 mmc_driver->prob
}static struct bus_type mmc_bus_type = {.name = "mmc",.dev_attrs = mmc_dev_attrs,.match = mmc_bus_match,//总线匹配函数.uevent = mmc_bus_uevent,.probe = mmc_bus_probe,.remove = mmc_bus_remove,.pm = &mmc_bus_pm_ops,
};//创建 mmc总线
int mmc_register_bus(void)
{return bus_register(&mmc_bus_type);
}/** Allocate and initialise a new MMC card structure.*/
struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type)
{struct mmc_card *card;card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL);if (!card)return ERR_PTR(-ENOMEM);card->host = host;device_initialize(&card->dev);card->dev.parent = mmc_classdev(host);card->dev.bus = &mmc_bus_type; //设置总线类型为 mmc_bus_typecard->dev.release = mmc_release_card;card->dev.type = type;return card;
}/*
1 给card命名,格式为host名字:从地址,如: mmc2:0001
2 根据card->type来分辨出card的类型,给赋予相应的字符串
3 打印信息,具体不解释 如 mmc2:new high speed SDIO card at address 0001(通常可以通过查看内核启动信息中是否有该语句来判断card是否被正确识别)
4 将card注册进linux设备模型 注册结果就是可以在/sys/bus/mmc/devices目录下见到card 的名字,如 mmc2:0001
*/
int mmc_add_card(struct mmc_card *card)
{int ret;const char *type;const char *uhs_bus_speed_mode = "";static const char *const uhs_speeds[] = {[UHS_SDR12_BUS_SPEED] = "SDR12 ",[UHS_SDR25_BUS_SPEED] = "SDR25 ",[UHS_SDR50_BUS_SPEED] = "SDR50 ",[UHS_SDR104_BUS_SPEED] = "SDR104 ",[UHS_DDR50_BUS_SPEED] = "DDR50 ",};//给card命名,格式为host名字:从地址,如: mmc2:0001
dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);//根据card->type来分辨出card的类型,给赋予相应的字符串
switch (card->type) {
case MMC_TYPE_MMC:type = "MMC";break;
case MMC_TYPE_SD:type = "SD";if (mmc_card_blockaddr(card)) {if (mmc_card_ext_capacity(card))type = "SDXC";elsetype = "SDHC";}break;
case MMC_TYPE_SDIO:type = "SDIO";break;
case MMC_TYPE_SD_COMBO:type = "SD-combo";if (mmc_card_blockaddr(card))type = "SDHC-combo";break;
default:type = "?";break;
}//打印信息,具体不解释 笔者的打印信息为 mmc2:new high speed SDIO card at address 0001(通常可以通过查看内核启动信息中是否有该语句来判断card是否被正确识别)
if (mmc_sd_card_uhs(card) &&(card->sd_bus_speed < ARRAY_SIZE(uhs_speeds)))uhs_bus_speed_mode = uhs_speeds[card->sd_bus_speed];if (mmc_host_is_spi(card->host)) {pr_info("%s: new %s%s%s card on SPI\n", mmc_hostname(card->host), mmc_card_highspeed(card) ? "high speed " : "", mmc_card_ddr_mode(card) ? "DDR " : "", type);} else {
pr_info("%s: new %s%s%s%s%s card at address %04x\n",mmc_hostname(card->host),mmc_card_uhs(card) ? "ultra high speed " :(mmc_card_highspeed(card) ? "high speed " : ""),mmc_card_hs400(card) ? "HS400 " :(mmc_card_hs200(card) ? "HS200 " : ""),mmc_card_ddr_mode(card) ? "DDR " : "",uhs_bus_speed_mode, type, card->rca);}//将card注册进linux设备模型 注册结果就是可以在/sys/bus/mmc/devices目录下见到card 的名字,笔者的就是mmc2:0001 ret = device_add(&card->dev);return 0;
}
drivers/mmc/core/sdio_bus.c
int sdio_register_driver(struct sdio_driver *drv)
{drv->drv.name = drv->name;drv->drv.bus = &sdio_bus_type;return driver_register(&drv->drv);
}
EXPORT_SYMBOL_GPL(sdio_register_driver);//sdio总线匹配函数
static int sdio_bus_match(struct device *dev, struct device_driver *drv)
{struct sdio_func *func = dev_to_sdio_func(dev);struct sdio_driver *sdrv = to_sdio_driver(drv);if (sdio_match_device(func, sdrv))return 1;return 0;
}/** Register a new SDIO function with the driver model.*/
int sdio_add_func(struct sdio_func *func)
{int ret;//注册的名称(name),它是由三部分组成的,每部分之间用冒号隔开,即 host的名称:rca:功能块编号dev_set_name(&func->dev, "%s:%d", mmc_card_id(func->card), func->num);sdio_acpi_set_handle(func);ret = device_add(&func->dev);if (ret == 0) {sdio_func_set_present(func);acpi_dev_pm_attach(&func->dev, false);}return ret;
}static int sdio_bus_probe(struct device *dev)
{struct sdio_driver *drv = to_sdio_driver(dev->driver);struct sdio_func *func = dev_to_sdio_func(dev);const struct sdio_device_id *id;int ret;id = sdio_match_device(func, drv); //匹配...ret = drv->probe(func, id);//调用driver的probreturn 0;}static struct bus_type sdio_bus_type = {.name = "sdio",.dev_attrs = sdio_dev_attrs,.match = sdio_bus_match,//总线匹配函数,当一个新设备或者驱动被添加到这个总线时,这个方法会被调用一次或多次.uevent = sdio_bus_uevent,.probe = sdio_bus_probe,//匹配成功 调用prob.remove = sdio_bus_remove,.pm = SDIO_PM_OPS_PTR,
};//创建 sdio总线
int sdio_register_bus(void)
{return bus_register(&sdio_bus_type);
}
drivers/mmc/card/block.c
static int mmc_blk_probe(struct mmc_card *card)
{struct mmc_blk_data *md, *part_md;char cap_str[10];md = mmc_blk_alloc(card);... if (mmc_add_disk(md))goto out;list_for_each_entry(part_md, &md->part, part) {if (mmc_add_disk(part_md))goto out;}
...
}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;max_devices = 256 / perdev_minors;res = register_blkdev(MMC_BLOCK_MAJOR, "mmc");res = mmc_register_driver(&mmc_driver);}
drivers/mmc/card/sdio_uart.c
static const struct sdio_device_id sdio_uart_ids[] = {{ SDIO_DEVICE_CLASS(SDIO_CLASS_UART) },{ SDIO_DEVICE_CLASS(SDIO_CLASS_GPS) },{ /* end: all zeroes */ },
};MODULE_DEVICE_TABLE(sdio, sdio_uart_ids);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);}module_init(sdio_uart_init);
结构体 :
//mmc 主机端信息
struct sunxi_mmc_host {struct mmc_host *mmc;struct reset_control *reset;/* IO mapping base IO映射相关*/void __iomem *reg_base;/* clock management */struct clk *clk_ahb;struct clk *clk_mmc;struct clk *clk_rst;int (*sunxi_mmc_clk_set_rate)(struct sunxi_mmc_host *host, struct mmc_ios *ios);/* irq 中断相关*/spinlock_t lock;int irq; //中断号u32 int_sum;u32 sdio_imask;/* dma */u32 idma_des_size_bits;dma_addr_t sg_dma;void *sg_cpu;bool wait_dma;u32 dma_tl;u64 dma_mask;void (*sunxi_mmc_thld_ctl )(struct sunxi_mmc_host *host,struct mmc_ios *ios, struct mmc_data *data);struct mmc_request *mrq;struct mmc_request *mrq_busy;struct mmc_request *manual_stop_mrq;int ferror;u32 power_on;/* pinctrl handles */ //pinctr 句柄struct pinctrl *pinctrl;struct pinctrl_state *pins_default;struct pinctrl_state *pins_sleep;/*sys node*/struct device_attribute maual_insert;struct device_attribute *dump_register;struct device_attribute dump_clk_dly;void (*sunxi_mmc_dump_dly_table)(struct sunxi_mmc_host *host);/* backup register structrue */struct sunxi_mmc_ctrl_regs bak_regs;void (*sunxi_mmc_save_spec_reg)(struct sunxi_mmc_host *host);void (*sunxi_mmc_restore_spec_reg)(struct sunxi_mmc_host *host);void (*sunxi_mmc_set_acmda)(struct sunxi_mmc_host *host);void (*sunxi_mmc_shutdown)(struct platform_device * pdev);/*really controller id,no logic id*/int phy_index;u32 dat3_imask;/*no wait busy if wrtie end, only for customer need*/#define NO_MANUAL_WAIT_BUSY_WRITE_END 0x1#define NO_REINIT_SHUTDOWN 0x2#define CARD_PWR_GPIO_HIGH_ACTIVE 0x4/*control specal function control,for customer need*/u32 ctl_spec_cap;int card_pwr_gpio;void *version_priv_dat;
};//当前IO总线设置
struct mmc_ios {unsigned int clock; /* clock rate */unsigned short vdd;/* vdd stores the bit number of the selected voltage range from below. */unsigned char bus_mode; /* command output mode */#define MMC_BUSMODE_OPENDRAIN 1
#define MMC_BUSMODE_PUSHPULL 2unsigned char chip_select; /* SPI chip select */#define MMC_CS_DONTCARE 0
#define MMC_CS_HIGH 1
#define MMC_CS_LOW 2unsigned char power_mode; /* power supply mode */#define MMC_POWER_OFF 0
#define MMC_POWER_UP 1
#define MMC_POWER_ON 2unsigned char bus_width; /* data bus width */#define MMC_BUS_WIDTH_1 0
#define MMC_BUS_WIDTH_4 2
#define MMC_BUS_WIDTH_8 3unsigned char timing; /* timing specification used */#define MMC_TIMING_LEGACY 0
#define MMC_TIMING_MMC_HS 1
#define MMC_TIMING_SD_HS 2
#define MMC_TIMING_UHS_SDR12 3
#define MMC_TIMING_UHS_SDR25 4
#define MMC_TIMING_UHS_SDR50 5
#define MMC_TIMING_UHS_SDR104 6
#define MMC_TIMING_UHS_DDR50 7
#define MMC_TIMING_MMC_HS200 8
#define MMC_TIMING_MMC_HS400 9#define MMC_SDR_MODE 0
#define MMC_1_2V_DDR_MODE 1
#define MMC_1_8V_DDR_MODE 2
#define MMC_1_2V_SDR_MODE 3
#define MMC_1_8V_SDR_MODE 4unsigned char signal_voltage; /* signalling voltage (1.8V or 3.3V) */#define MMC_SIGNAL_VOLTAGE_330 0
#define MMC_SIGNAL_VOLTAGE_180 1
#define MMC_SIGNAL_VOLTAGE_120 2unsigned char drv_type; /* driver type (A, B, C, D) */#define MMC_SET_DRIVER_TYPE_B 0
#define MMC_SET_DRIVER_TYPE_A 1
#define MMC_SET_DRIVER_TYPE_C 2
#define MMC_SET_DRIVER_TYPE_D 3
};//用于描述MMC系统中的供电信息
//truct mmc_supply中保存了两个struct regulator指针(如下),用于控制MMC子系统有关的供电(vmmc和vqmmc)。vmmc是卡的供电电压,一般连接到卡的VDD管脚上。而vqmmc则用于上拉信号线(CMD、CLK和DATA[6])。通常情况下vqmmc使用和vmmc相同的regulator,同时供电即可。后来,一些高速卡(例如UHS SD)要求在高速模式下,vmmc为3.3v,vqmmc为1.8v,这就需要两个不同的regulator独立控制。struct mmc_supply {struct regulator *vmmc; /* Card power supply */struct regulator *vqmmc; /* Optional Vccq supply */};//mmc主控制器的操作函数,即该控制器所具备的驱动能力
struct mmc_host_ops {/** 'enable' is called when the host is claimed and 'disable' is called* when the host is released. 'enable' and 'disable' are deprecated.*/int (*enable)(struct mmc_host *host);int (*disable)(struct mmc_host *host);/** It is optional for the host to implement pre_req and post_req in* order to support double buffering of requests (prepare one* request while another request is active).* pre_req() must always be followed by a post_req().* To undo a call made to pre_req(), call post_req() with* a nonzero err condition.*/void (*post_req)(struct mmc_host *host, struct mmc_request *req,int err);void (*pre_req)(struct mmc_host *host, struct mmc_request *req,bool is_first_req);void (*request)(struct mmc_host *host, struct mmc_request *req);/** Avoid calling these three functions too often or in a "fast path",* since underlaying controller might implement them in an expensive* and/or slow way.** Also note that these functions might sleep, so don't call them* in the atomic contexts!** Return values for the get_ro callback should be:* 0 for a read/write card* 1 for a read-only card* -ENOSYS when not supported (equal to NULL callback)* or a negative errno value when something bad happened** Return values for the get_cd callback should be:* 0 for a absent card //卡不存在?* 1 for a present card //卡存在?* -ENOSYS when not supported (equal to NULL callback)* or a negative errno value when something bad happened*/void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);int (*get_ro)(struct mmc_host *host);int (*get_cd)(struct mmc_host *host);void (*enable_sdio_irq)(struct mmc_host *host, int enable);/* optional callback for HC quirks */void (*init_card)(struct mmc_host *host, struct mmc_card *card);int (*start_signal_voltage_switch)(struct mmc_host *host, struct mmc_ios *ios);/* Check if the card is pulling dat[0:3] low */int (*card_busy)(struct mmc_host *host);/* The tuning command opcode value is different for SD and eMMC cards */int (*execute_tuning)(struct mmc_host *host, u32 opcode);int (*select_drive_strength)(unsigned int max_dtr, int host_drv, int card_drv);void (*hw_reset)(struct mmc_host *host);void (*card_event)(struct mmc_host *host);
};// mmc 总线驱动的操作函数,即SD总线所具备的驱动能力 current bus driver
struct mmc_bus_ops {int (*awake)(struct mmc_host *);int (*sleep)(struct mmc_host *);void (*remove)(struct mmc_host *);void (*detect)(struct mmc_host *);int (*suspend)(struct mmc_host *);int (*resume)(struct mmc_host *);int (*power_save)(struct mmc_host *);int (*power_restore)(struct mmc_host *);int (*alive)(struct mmc_host *);
};//用于描述卡控制器,linux为SD卡控制器专门准备的一个类,该类里面的成员是所有SD卡控制器都需要的,放之四海而皆准的数据结构
//该类具体化了一个对象struct mmc_host *mmc,此mmc指针即指代着该ARM芯片SD卡控制器的一个具体化对象。
//用于与core层的命令请求,数据 传输等信息,
struct mmc_host {struct device *parent;//该设备的父节点 一般是该设备所从属的bus、controller等设备struct device class_dev;//代表本节点int index;const struct mmc_host_ops *ops;// mmc主控制器的操作函数,即该控制器所具备的驱动能力unsigned int f_min;//卡最小频率unsigned int f_max;//卡最大频率unsigned int f_init;//所初始化频率u32 ocr_avail;u32 ocr_avail_sdio; /* SDIO-specific OCR */u32 ocr_avail_sd; /* SD-specific OCR */u32 ocr_avail_mmc; /* MMC-specific OCR */struct notifier_block pm_notify;u32 max_current_330;u32 max_current_300;u32 max_current_180;#define MMC_VDD_165_195 0x00000080 /* VDD voltage 1.65 - 1.95 */
#define MMC_VDD_20_21 0x00000100 /* VDD voltage 2.0 ~ 2.1 */
#define MMC_VDD_21_22 0x00000200 /* VDD voltage 2.1 ~ 2.2 */
#define MMC_VDD_22_23 0x00000400 /* VDD voltage 2.2 ~ 2.3 */
#define MMC_VDD_23_24 0x00000800 /* VDD voltage 2.3 ~ 2.4 */
#define MMC_VDD_24_25 0x00001000 /* VDD voltage 2.4 ~ 2.5 */
#define MMC_VDD_25_26 0x00002000 /* VDD voltage 2.5 ~ 2.6 */
#define MMC_VDD_26_27 0x00004000 /* VDD voltage 2.6 ~ 2.7 */
#define MMC_VDD_27_28 0x00008000 /* VDD voltage 2.7 ~ 2.8 */
#define MMC_VDD_28_29 0x00010000 /* VDD voltage 2.8 ~ 2.9 */
#define MMC_VDD_29_30 0x00020000 /* VDD voltage 2.9 ~ 3.0 */
#define MMC_VDD_30_31 0x00040000 /* VDD voltage 3.0 ~ 3.1 */
#define MMC_VDD_31_32 0x00080000 /* VDD voltage 3.1 ~ 3.2 */
#define MMC_VDD_32_33 0x00100000 /* VDD voltage 3.2 ~ 3.3 */
#define MMC_VDD_33_34 0x00200000 /* VDD voltage 3.3 ~ 3.4 */
#define MMC_VDD_34_35 0x00400000 /* VDD voltage 3.4 ~ 3.5 */
#define MMC_VDD_35_36 0x00800000 /* VDD voltage 3.5 ~ 3.6 */u32 caps; /* Host capabilities */#define MMC_CAP_4_BIT_DATA (1 << 0) /* Can the host do 4 bit transfers */
#define MMC_CAP_MMC_HIGHSPEED (1 << 1) /* Can do MMC high-speed timing */
#define MMC_CAP_SD_HIGHSPEED (1 << 2) /* Can do SD high-speed timing */
#define MMC_CAP_SDIO_IRQ (1 << 3) /* Can signal pending SDIO IRQs */
#define MMC_CAP_SPI (1 << 4) /* Talks only SPI protocols */
#define MMC_CAP_NEEDS_POLL (1 << 5) /* Needs polling for card-detection 轮循检测卡功能 */
#define MMC_CAP_8_BIT_DATA (1 << 6) /* Can the host do 8 bit transfers */#define MMC_CAP_NONREMOVABLE (1 << 8) /* Nonremovable e.g. eMMC 不可移动mmc设备,如EMMC */
#define MMC_CAP_WAIT_WHILE_BUSY (1 << 9) /* Waits while card is busy */
#define MMC_CAP_ERASE (1 << 10) /* Allow erase/trim commands */
#define MMC_CAP_1_8V_DDR (1 << 11) /* can support *//* DDR mode at 1.8V */
#define MMC_CAP_1_2V_DDR (1 << 12) /* can support *//* DDR mode at 1.2V */
#define MMC_CAP_POWER_OFF_CARD (1 << 13) /* Can power off after boot */
#define MMC_CAP_BUS_WIDTH_TEST (1 << 14) /* CMD14/CMD19 bus width ok */
#define MMC_CAP_UHS_SDR12 (1 << 15) /* Host supports UHS SDR12 mode */
#define MMC_CAP_UHS_SDR25 (1 << 16) /* Host supports UHS SDR25 mode */
#define MMC_CAP_UHS_SDR50 (1 << 17) /* Host supports UHS SDR50 mode */
#define MMC_CAP_UHS_SDR104 (1 << 18) /* Host supports UHS SDR104 mode */
#define MMC_CAP_UHS_DDR50 (1 << 19) /* Host supports UHS DDR50 mode */
#define MMC_CAP_DRIVER_TYPE_A (1 << 23) /* Host supports Driver Type A */
#define MMC_CAP_DRIVER_TYPE_C (1 << 24) /* Host supports Driver Type C */
#define MMC_CAP_DRIVER_TYPE_D (1 << 25) /* Host supports Driver Type D */
#define MMC_CAP_CMD23 (1 << 30) /* CMD23 supported. */
#define MMC_CAP_HW_RESET (1 << 31) /* Hardware reset 硬件复位*/u32 caps2; /* More host capabilities */#define MMC_CAP2_BOOTPART_NOACC (1 << 0) /* Boot partition no access */
#define MMC_CAP2_CACHE_CTRL (1 << 1) /* Allow cache control */
#define MMC_CAP2_POWEROFF_NOTIFY (1 << 2) /* Notify poweroff supported */
#define MMC_CAP2_NO_MULTI_READ (1 << 3) /* Multiblock reads don't work */
#define MMC_CAP2_NO_SLEEP_CMD (1 << 4) /* Don't allow sleep command */
#define MMC_CAP2_HS200_1_8V_SDR (1 << 5) /* can support */
#define MMC_CAP2_HS200_1_2V_SDR (1 << 6) /* can support */
#define MMC_CAP2_HS200 (MMC_CAP2_HS200_1_8V_SDR | \MMC_CAP2_HS200_1_2V_SDR)
#define MMC_CAP2_BROKEN_VOLTAGE (1 << 7) /* Use the broken voltage */
#define MMC_CAP2_DETECT_ON_ERR (1 << 8) /* On I/O err check card removal */
#define MMC_CAP2_HC_ERASE_SZ (1 << 9) /* High-capacity erase size */
#define MMC_CAP2_CD_ACTIVE_HIGH (1 << 10) /* Card-detect signal active high */
#define MMC_CAP2_RO_ACTIVE_HIGH (1 << 11) /* Write-protect signal active high */
#define MMC_CAP2_PACKED_RD (1 << 12) /* Allow packed read */
#define MMC_CAP2_PACKED_WR (1 << 13) /* Allow packed write */
#define MMC_CAP2_PACKED_CMD (MMC_CAP2_PACKED_RD | \MMC_CAP2_PACKED_WR)
#define MMC_CAP2_NO_PRESCAN_POWERUP (1 << 14) /* Don't power up before scan */
#define MMC_CAP2_HS400_1_8V (1 << 15) /* Can support HS400 1.8V */
#define MMC_CAP2_HS400_1_2V (1 << 16) /* Can support HS400 1.2V */
#define MMC_CAP2_HS400 (MMC_CAP2_HS400_1_8V | \MMC_CAP2_HS400_1_2V)#define MMC_SUNXI_CAP3_DAT3_DET (1 << 0)u32 sunxi_caps3;mmc_pm_flag_t pm_caps; /* supported pm features */#ifdef CONFIG_MMC_CLKGATEint clk_requests; /* internal reference counter */unsigned int clk_delay; /* number of MCI clk hold cycles */bool clk_gated; /* clock gated */struct delayed_work clk_gate_work; /* delayed clock gate */unsigned int clk_old; /* old clock value cache */spinlock_t clk_lock; /* lock for clk fields */struct mutex clk_gate_mutex; /* mutex for clock gating */struct device_attribute clkgate_delay_attr;unsigned long clkgate_delay;
#endif/* host specific block data */unsigned int max_seg_size; /* see blk_queue_max_segment_size */unsigned short max_segs; /* see blk_queue_max_segments */unsigned short unused;unsigned int max_req_size; /* maximum number of bytes in one req */unsigned int max_blk_size; /* maximum size of one mmc block */unsigned int max_blk_count; /* maximum number of blocks in one req 一个请求中的最大块数 */unsigned int max_busy_timeout; /* max busy timeout in ms 最大忙超时(毫秒) *//* private data */spinlock_t lock; /* lock for claim and bus ops */struct mmc_ios ios; /* current io bus settings */// 配置时钟、总线、电源、片选、时序等u32 ocr; /* the current OCR setting 最合适的电压 *//* group bitfields together to minimize padding */unsigned int use_spi_crc:1;unsigned int claimed:1; /* host exclusively claimed */unsigned int bus_dead:1; /* bus has been released 总线被释放 */
#ifdef CONFIG_MMC_DEBUGunsigned int removed:1; /* host is being removed */
#endifint rescan_disable; /* disable card detection 禁止mmc设备检测*/int rescan_entered; /* used with nonremovable devices 不可热拔插设备*/int rescan_pre_state; //上一次扫描结果?struct mmc_card *card; /* device attached to this host */// 连接到此主控制器的SD卡设备wait_queue_head_t wq;struct task_struct *claimer; /* task that has host claimed */int claim_cnt; /* "claim" nesting count */struct delayed_work detect;//声明一个延期工作描述实例 用于检测mmc设备插入 = mmc_rescanstruct wake_lock detect_wake_lock;int detect_change; /* card detect flag */struct mmc_slot slot;const struct mmc_bus_ops *bus_ops; /* current bus driver */// mmc 总线驱动的操作函数,即SD总线所具备的驱动能力unsigned int bus_refs; /* reference counter 总线被使用的引用计数*/unsigned int bus_resume_flags;
#define MMC_BUSRESUME_MANUAL_RESUME (1 << 0)
#define MMC_BUSRESUME_NEEDS_RESUME (1 << 1)unsigned int sdio_irqs;struct task_struct *sdio_irq_thread;bool sdio_irq_pending;atomic_t sdio_irq_thread_abort;mmc_pm_flag_t pm_flags; /* requested pm features */struct led_trigger *led; /* activity led */#ifdef CONFIG_REGULATORbool regulator_enabled; /* regulator state */
#endifstruct mmc_supply supply;//用于描述MMC系统中的供电信息struct dentry *debugfs_root;struct mmc_async_req *areq; /* active async req */struct mmc_context_info context_info; /* async synchronization info */#ifdef CONFIG_FAIL_MMC_REQUESTstruct fault_attr fail_mmc_request;
#endifunsigned int actual_clock; /* Actual HC clock rate */unsigned int slotno; /* used for sdio acpi binding */#ifdef CONFIG_MMC_EMBEDDED_SDIOstruct {struct sdio_cis *cis;struct sdio_cccr *cccr;struct sdio_embedded_func *funcs;int num_funcs;} embedded_sdio_data;
#endifunsigned long private[0] ____cacheline_aligned;
};struct delayed_work {struct work_struct work;struct timer_list timer;/* target workqueue and CPU ->timer uses to queue ->work */struct workqueue_struct *wq;int cpu;
};/** MMC device*/
struct mmc_card {struct mmc_host *host; /* the host this device belongs to */struct device dev; /* the device */unsigned int rca; /* relative card address of device */unsigned int type; /* card type */
#define MMC_TYPE_MMC 0 /* MMC card */
#define MMC_TYPE_SD 1 /* SD card */
#define MMC_TYPE_SDIO 2 /* SDIO card */
#define MMC_TYPE_SD_COMBO 3 /* SD combo (IO+mem) card */unsigned int state; /* (our) card state */
#define MMC_STATE_PRESENT (1<<0) /* present in sysfs */
#define MMC_STATE_READONLY (1<<1) /* card is read-only */
#define MMC_STATE_HIGHSPEED (1<<2) /* card is in high speed mode */
#define MMC_STATE_BLOCKADDR (1<<3) /* card uses block-addressing */
#define MMC_STATE_HIGHSPEED_DDR (1<<4) /* card is in high speed mode */
#define MMC_STATE_ULTRAHIGHSPEED (1<<5) /* card is in ultra high speed mode */
#define MMC_CARD_SDXC (1<<6) /* card is SDXC */
#define MMC_CARD_REMOVED (1<<7) /* card has been removed */
#define MMC_STATE_HIGHSPEED_200 (1<<8) /* card is in HS200 mode */
#define MMC_STATE_HIGHSPEED_400 (1<<9) /* card is in HS400 mode */
#define MMC_STATE_DOING_BKOPS (1<<10) /* card is doing BKOPS */unsigned int quirks; /* card quirks */
#define MMC_QUIRK_LENIENT_FN0 (1<<0) /* allow SDIO FN0 writes outside of the VS CCCR range */
#define MMC_QUIRK_BLKSZ_FOR_BYTE_MODE (1<<1) /* use func->cur_blksize *//* for byte mode */
#define MMC_QUIRK_NONSTD_SDIO (1<<2) /* non-standard SDIO card attached *//* (missing CIA registers) */
#define MMC_QUIRK_BROKEN_CLK_GATING (1<<3) /* clock gating the sdio bus will make card fail */
#define MMC_QUIRK_NONSTD_FUNC_IF (1<<4) /* SDIO card has nonstd function interfaces */
#define MMC_QUIRK_DISABLE_CD (1<<5) /* disconnect CD/DAT[3] resistor */
#define MMC_QUIRK_INAND_CMD38 (1<<6) /* iNAND devices have broken CMD38 */
#define MMC_QUIRK_BLK_NO_CMD23 (1<<7) /* Avoid CMD23 for regular multiblock */
#define MMC_QUIRK_BROKEN_BYTE_MODE_512 (1<<8) /* Avoid sending 512 bytes in */
#define MMC_QUIRK_LONG_READ_TIME (1<<9) /* Data read time > CSD says */
#define MMC_QUIRK_SEC_ERASE_TRIM_BROKEN (1<<10) /* Skip secure for erase/trim *//* byte mode */unsigned int erase_size; /* erase size in sectors */unsigned int erase_shift; /* if erase unit is power 2 */unsigned int pref_erase; /* in sectors */u8 erased_byte; /* value of erased bytes */u32 raw_cid[4]; /* raw card CID */u32 raw_csd[4]; /* raw card CSD */u32 raw_scr[2]; /* raw card SCR */struct mmc_cid cid; /* card identification */struct mmc_csd csd; /* card specific */struct mmc_ext_csd ext_csd; /* mmc v4 extended card specific */struct sd_scr scr; /* extra SD information */struct sd_ssr ssr; /* yet more SD information */struct sd_switch_caps sw_caps; /* switch (CMD6) caps */unsigned int sdio_funcs; /* number of SDIO functions */struct sdio_cccr cccr; /* common card info */struct sdio_cis cis; /* common tuple info */struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; /* SDIO functions (devices) */struct sdio_func *sdio_single_irq; /* SDIO function when only one IRQ active */unsigned num_info; /* number of info strings */const char **info; /* info strings */struct sdio_func_tuple *tuples; /* unknown common tuples */unsigned int sd_bus_speed; /* Bus Speed Mode set for the card */struct dentry *debugfs_root;struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical partitions */unsigned int nr_parts;
};
MMC子系统识别SD设备过程简述相关推荐
- Linux内核中识别USB设备过程
1.usb全速模式和高速模式的区别,名字上感觉速度应该差不多?? Usb1.1又是usb2.0全速模式,传输速率只有12Mbps Usb2.0高速模式达到了480mbps 擦类这明显不是一个等级... ...
- Windows识别USB设备过程
集线器检测新设备 主机集线器监视着每个端口的信号电压,当有新设备接入时便可觉察.(集线器端口的两根信号线的每一根都有15kΩ的下拉电阻,而每一个设备在D+都有一个1.5kΩ的上拉电阻.当用USB线将P ...
- linux设备模型之mmc子系统
翻开mmc子系统驱动代码在Linux源码中的位置linux-3.4.y/drivers/mmc,分别有card.core和host三个文件夹, card.core和host这三层的关系,如下图: 从这 ...
- linux mmc 子系统,linux2.6.28块设备mmc_sd卡mmc子系统核心初始化
参考http://blog.csdn.net/wavemcu/article/details/7366852 // / /// MMC/SD设备驱动代码在Linux源码中的位置/linux-2.6.3 ...
- LINUX MMC 子系统分析之五 MMC driver模块分析
前面我们介绍了MMC 子系统驱动模型.mmc host模块,本篇主要介绍MMC Driver模块,在前面几篇文章中,我们已经说明mmc 子系统已实现mmc driver,即针对所有的mmc card( ...
- mmc子系统分析(一)
文章目录 前言 一.MMC子系统介绍 1.1 基本概念 1.2 mmc规范简介 1.2.1 卡的规范 1.2.2 总线的规范 1.2.3 控制器的规范 二.mmc代码分布 三.mmc子系统框架 四.S ...
- 浅析Linux内核之mmc子系统-sdio
现在的Linux内核中,mmc不仅是一个驱动,而是一个子系统.这里通过分析Linux3.2.0内核,结合TI的arm335x平台及omap_hsmmcd host分析下mmc子系统,重点关注sdio及 ...
- Linux驱动分析之MMC子系统框架
前言 上一篇<一文搞懂SDIO>简单介绍了SDIO接口及相关的协议.接下来来看一下Linux提供的驱动框架. MMC子系统介绍 Linux内核中,MMC不仅是一个驱动,而是一个子系统.内核 ...
- mmc子系统分析(二)
文章目录 前言 一.mcc host驱动介绍 二.相关数据结构 1.struct mmc_host 2.struct mmc_host_ops 3.struct mmc_pwrseq 4.struct ...
最新文章
- Centos7 上安装 mysql5.7
- 【风控体系】互联网反欺诈体系漫谈
- T-SQL 之 多表联合更新
- java 正则提取及替换字符串
- .NET Core 开源项目 Anet 在路上
- Java设计模式笔记(5)建造者模式
- LeetCode 968. 监控二叉树(DFS)
- isag java_ISAG - WEB源码|JSP源码/Java|源代码 - 源码中国
- JDBC06 其他操作及批处理Batch
- 2021网安保研之路-----中科院信工所
- 变速齿轮修改服务器时间,可改变游戏速度!游戏变速齿轮安装使用说明
- 商品详情页php代码,微信小程序商品详情页规格属性选择示例代码
- gradient设置上下渐变_CSS3 线性渐变(linear-gradient)
- golden ticket和sliver ticket的区别是什么?
- WPF学习之深入浅出话模板
- ERP的实施--把握三大计划
- 2015最新经典语句搞笑段子
- 纯css3制作简易钟表时钟
- [BZOJ]4180: 字符串计数 SAM+矩阵乘法+二分
- Unity 性能优化(力荐)
热门文章
- 四川翌加:想提高抖音小店转化率该怎么做
- 药店零售管理php系统,小型药店销售管理系统
- 吃鸡服务器维护6月23日,4月23日吃鸡更新 | 手游网游页游攻略大全
- 荣耀note10 鸿蒙,荣耀Note10曝光 三星W2019将采用双摄
- 带id跳转页面,另一个页面拿id
- 分圆多项式 cyclotomic polynomial
- python 设置列宽
- 小程序--民宿(首页(房屋列表、房屋详情)、订单(订单列表、详情)、个人中心(登录、优惠券))
- jQuery实现简单分页(jqPaginator插件)
- gstfileserver文件服务器,使用gst-rtsp-server流式传输H264文件