Linux mmc驱动框架
- Host驱动设备树
- Host驱动
-
- platform_driver数据结构
- 控制器驱动初始化函数
-
- `sunxi_mmc_probe->mmc_add_host`
- `sunxi_mmc_probe->mmc_add_host->mmc_start_host`
- `sunxi_mmc_probe->mmc_add_host->mmc_start_host-> _mmc_detect_change`
- `sunxi_mmc_probe->mmc_add_host->mmc_start_host-> _mmc_detect_change-> mmc_schedule_delayed_work`
- `sunxi_mmc_probe->mmc_alloc_host`
host驱动的编写主要步骤如下:
- 通过mmc_alloc_host分配一个mmc_host结构体
- 定义实现mmc_host_ops数据结构, 并赋值给上面的mmc_host->mmc_host_ops成员变量
- 给mmc_host成员变量赋值, 如ocr_avail、caps等成员变量
- 调用mmc_add_host注册该Host
Host驱动设备树
host驱动设备树用于匹配host驱动,host驱动匹配上设备树,初始化流程才能开始。本文举例全志H3设备树以及mmc控制器驱动。
下面的代码段都是一些基本配置,compatible用于与驱动匹配,reg为控制器IO内存地址。interrupt为中断参数,GIC控制器,中断号等。以及mmc控制器用到的时钟参数。
mmc0: [email protected]01c0f000 {
/* compatible and clocks are in per SoC .dtsi file */
compatible = "allwinner,sun7i-a20-mmc";
reg = <0x01c0f000 0x1000>;
resets = <&ccu RST_BUS_MMC0>;
reset-names = "ahb";
interrupts = <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>;
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
clocks = <&ccu CLK_BUS_MMC0>,
<&ccu CLK_MMC0>,
<&ccu CLK_MMC0_OUTPUT>,
<&ccu CLK_MMC0_SAMPLE>;
clock-names = "ahb",
"mmc",
"output",
"sample";
};
Host驱动
platform_driver数据结构
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,
};
static const struct of_device_id sunxi_mmc_of_match[] = {
{ .compatible = "allwinner,sun4i-a10-mmc", .data = &sun4i_a10_cfg },
{ .compatible = "allwinner,sun5i-a13-mmc", .data = &sun5i_a13_cfg },
{ .compatible = "allwinner,sun7i-a20-mmc", .data = &sun7i_a20_cfg }, (1)
{ .compatible = "allwinner,sun8i-a83t-emmc", .data = &sun8i_a83t_emmc_cfg },
{ .compatible = "allwinner,sun9i-a80-mmc", .data = &sun9i_a80_cfg },
{ .compatible = "allwinner,sun50i-a64-mmc", .data = &sun50i_a64_cfg },
{ .compatible = "allwinner,sun50i-a64-emmc", .data = &sun50i_a64_emmc_cfg },
{ /* sentinel */ }
};
(1)这里的compatible和设备树中对应,内核初始化时驱动匹配上设备树,会调用platform_driver 的probe函数。
控制器驱动初始化函数
首先来看控制器驱动的probe函数:
static int sunxi_mmc_probe(struct platform_device *pdev)
{
struct sunxi_mmc_host *host;
struct mmc_host *mmc;
int ret;
mmc = mmc_alloc_host(sizeof(struct sunxi_mmc_host), &pdev->dev);
if (!mmc) {
dev_err(&pdev->dev, "mmc alloc host failed\n");
return -ENOMEM;
}
......
mmc->ops = &sunxi_mmc_ops;
......
ret = sunxi_mmc_init_host(host);
if (ret)
goto error_free_dma;
......
ret = mmc_add_host(mmc);
......
return ret;
}
probe函数中首先会申请mmc_host数据结构,调用
mmc_alloc_host
函数申请数据结构内存,并设置部分mmc_host数据结构参数。
随后设置
struct mmc_host
的
struct mmc_host_ops
数据结构。下面来看下
sunxi_mmc_ops
数据结构:
static const struct mmc_host_ops sunxi_mmc_ops = {
.request = sunxi_mmc_request,
.set_ios = sunxi_mmc_set_ios,
.get_ro = mmc_gpio_get_ro,
.get_cd = mmc_gpio_get_cd,
.enable_sdio_irq = sunxi_mmc_enable_sdio_irq,
.start_signal_voltage_switch = sunxi_mmc_volt_switch,
.hw_reset = sunxi_mmc_hw_reset,
.card_busy = sunxi_mmc_card_busy,
};
struct mmc_host_ops
数据结构用来描述卡控制器操作接口函数功能,用于从主机控制器层向 core 层注册操作函数,从而将core 层与具体的主机控制器隔离。
core 要操作主机控制器,就用这个 ops 当中给的函数指针操作,不能直接调用具体主控制器的函数,从而实现逻辑层和硬件驱动层的分离。
再继续看
sunxi_mmc_probe
函数,
ret = sunxi_mmc_init_host(host);
这部分代码,主要是涉及到硬件相关的mmc控制器初始化代码,这里就不展开了。
最后调用
mmc_add_host
,顾名思义,添加一个
mmc_host
。下面来看一下
mmc_add_host
代码。
sunxi_mmc_probe->mmc_add_host
sunxi_mmc_probe->mmc_add_host
int mmc_add_host(struct mmc_host *host)
{
int err;
WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
!host->ops->enable_sdio_irq);
err = device_add(&host->class_dev); (1)
if (err)
return err;
led_trigger_register_simple(dev_name(&host->class_dev), &host->led);
#ifdef CONFIG_DEBUG_FS
mmc_add_host_debugfs(host);
#endif
mmc_start_host(host); (2)
mmc_register_pm_notifier(host);
return 0;
}
很简单,就是增加了一个 device ,然后就调用 mmc_start_host 了,下面来看
mmc_start_host
。
sunxi_mmc_probe->mmc_add_host->mmc_start_host
sunxi_mmc_probe->mmc_add_host->mmc_start_host
void mmc_start_host(struct mmc_host *host)
{
host->f_init = max(freqs[0], host->f_min);
host->rescan_disable = 0;
host->ios.power_mode = MMC_POWER_UNDEFINED;
if (!(host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP)) {
mmc_claim_host(host);
mmc_power_up(host, host->ocr_avail);
mmc_release_host(host);
}
mmc_gpiod_request_cd_irq(host);
_mmc_detect_change(host, 0, false);
}
sunxi_mmc_probe->mmc_add_host->mmc_start_host-> _mmc_detect_change
sunxi_mmc_probe->mmc_add_host->mmc_start_host-> _mmc_detect_change
static void _mmc_detect_change(struct mmc_host *host, unsigned long delay,
bool cd_irq)
{
if (cd_irq && !(host->caps & MMC_CAP_NEEDS_POLL) &&
device_can_wakeup(mmc_dev(host)))
pm_wakeup_event(mmc_dev(host), 5000);
host->detect_change = 1;
mmc_schedule_delayed_work(&host->detect, delay);
}
sunxi_mmc_probe->mmc_add_host->mmc_start_host-> _mmc_detect_change-> mmc_schedule_delayed_work
sunxi_mmc_probe->mmc_add_host->mmc_start_host-> _mmc_detect_change-> mmc_schedule_delayed_work
static int mmc_schedule_delayed_work(struct delayed_work *work,
unsigned long delay)
{
return queue_delayed_work(system_freezable_wq, work, delay);
}
mmc_start_host
代码很简单,主要代码为调用了
_mmc_detect_change
函数。
_mmc_detect_change
函数调用
mmc_schedule_delayed_work
。
mmc_schedule_delayed_work
函数最终调用了
queue_delayed_work
queue_delayed_work
具体是干嘛的可以百度,这里
这几个代码告诉我们在 workqueue 这个工作队列当中添加一个延迟的工作任务,而这个工作任务就是由 host->detect 来描述的,在随后的 delay 个 jiffies 后会有一个记录在 host->detect 里面的函数被执行,上面可以看到
_mmc_detect_change
传递的delay参数为0,意思是不延迟?大概是为了预留需要延迟检测卡的情况。
代码执行到这里
sunxi_mmc_probe
这个函数基本上执行完成了,host(控制器)的初始化完成了。
但事情还没有完, workqueue 这个工作队列还在忙,不一会儿它就会调用 host->detect 里面那个函数,这个函数到底是哪个函数,到底是用来干什么的呢?好像没有看到,detect 包含在 host 里面,那估计是在刚才那个申请的地方设置的那个函数,回过头来看一下
mmc_alloc_host
:
sunxi_mmc_probe->mmc_alloc_host
sunxi_mmc_probe->mmc_alloc_host
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
struct mmc_host *host;
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
if (!host)
return NULL;
......
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
......
return host;
}
INIT_DELAYED_WORK
初始化延迟工作队列,延时结束后调用工作队列函数,这里是
mmc_rescan
,这个函数是用来检测卡的。