Linux MMC子系统分析(二)——Host分析
前言
通过前面对mmc子系统的模型分析,我们能够知道host是对应于硬件控制器的具体操作。本文将以sdhci-s3c.c为例对host进行简单的分析。sdhci-s3c.c驱动代码的构成
实际上很多厂商的控制器代码都是基于sdhci.c这个驱动代码来实现的,不同的厂商调用其提供的函数实现自己驱动的个性化功能。下面将结合sdhci.c和sdhci-s3c.c来分析一个host的具体实现。Host驱动的分析
通常分析一个驱动可以从它的init函数为入口进行分析,可是在sdhci-s3c.c文件中并没有相应的init函数,不过最驱动的最末端可以看见一个宏的使用
module_platform_driver(sdhci_s3c_driver)
这里通过使用替代了驱动中常见的init和exit函数,进一步查看这个宏:
define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
可以看见driver的register和unregister函数,使用这个宏可以和在init函数调用register以及在exit中调用unregister达到相同的效果。通过分析可以知道,这里是注册了一个platform driver,看一下sdhci_s3c_driver结构体中有哪些内容:
static struct platform_driver sdhci_s3c_driver = {
.probe = sdhci_s3c_probe,
.remove = sdhci_s3c_remove,
.id_table = sdhci_s3c_driver_ids,
.driver = {
.name = "s3c-sdhci", //驱动名称
.of_match_table = of_match_ptr(sdhci_s3c_dt_match), //设备树匹配
.pm = SDHCI_S3C_PMOPS, //电源管理相关
},
};
dt_match中的内容:
static const struct of_device_id sdhci_s3c_dt_match[] = {
{ .compatible = "samsung,s3c6410-sdhci", },
{ .compatible = "samsung,exynos4210-sdhci",
.data = (void *)EXYNOS4_SDHCI_DRV_DATA },
{},
};
当我们的设备树中有能够与dt_match的成员相匹配时,就会调用probe函数。这里以列举一下s3c64xx.dtsi中关于sdhci节点的内容:
sdhci0: sdhci@7c200000 {
compatible = "samsung,s3c6410-sdhci";
reg = <0x7c200000 0x100>;
interrupt-parent = <&vic1>;
interrupts = <24>;
clock-names = "hsmmc", "mmc_busclk.0", "mmc_busclk.2";
clocks = <&clocks HCLK_HSMMC0>, <&clocks HCLK_HSMMC0>,
<&clocks SCLK_MMC0>;
status = "disabled";
};
可以看见设备树中指定了该控制器的寄存器地址、中断以及时钟相关的内容,并没有指定其引脚等内容,因为这些内容需要根据不同板级来进行配置。设备树的节点,会被转换为相应的device,这里的sdhci0在会被转换为一个platform_device,当加载相应的驱动后便会进行匹配从而调用驱动中的probe函数。
sdhci_s3c_probe函数分析
probe函数中完成的主要工作就是申请并添加host、中断注册、以及设备树内容的解析工作;当然这其中还有一些标志位的设置等。这里去掉了很多关于标志位设置,只有一些个人认为和流程相关的函数内容,并对其中的函数进行相应的分析。
static int sdhci_s3c_probe(struct platform_device *pdev)
{
host = sdhci_alloc_host(dev, sizeof(struct sdhci_s3c));
......
host->ops = &sdhci_s3c_ops; //这里的ops是提供给sdhci.c调用的,需要结合源码进行阅读
......
ret = mmc_of_parse(host->mmc);
ret = sdhci_add_host(host);
}
1、很多芯片厂商使用的驱动都是结合sdhci.c来实现的,不同芯片厂商的功能设置操作可可能有所差异,因此sdhci.c就提供了一个统一的接来进行管理。这个ops中提供的函数在sdhci.c中的一些位置会进行调用。
host->ops = &sdhci_s3c_ops;
/*----------------------------------------------------------------------------*/
static struct sdhci_ops sdhci_s3c_ops = {
.get_max_clock = sdhci_s3c_get_max_clk, //获取能够提供的最大的时钟频率
.set_clock = sdhci_s3c_set_clock, //设置sd卡时钟
.get_min_clock = sdhci_s3c_get_min_clock,
.set_bus_width = sdhci_s3c_set_bus_width, //设置bus位宽 4 or 8bit
.reset = sdhci_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling, //uhs 信号电平切换
};
/*-----------------------------------------------------------------------------*/
2、sdhci_alloc_host函数分析,此函数位于sdhci.c中
sdhci_alloc_host(dev, sizeof(struct sdhci_s3c))
mmc = mmc_alloc_host(sizeof(struct sdhci_host) + priv_size, dev);
host->mmc_host_ops = sdhci_ops; //硬件操作方法接口
mmc->ops = &host->mmc_host_ops;
/*-------------------------------------------------------------------*/
//向core层提供操作方法 按照core提供的统一接口编写的驱动
static const struct mmc_host_ops sdhci_ops = {
.request = sdhci_request,
.post_req = sdhci_post_req,
.pre_req = sdhci_pre_req,
.set_ios = sdhci_set_ios,
.get_cd = sdhci_get_cd,
.get_ro = sdhci_get_ro,
.hw_reset = sdhci_hw_reset,
.enable_sdio_irq = sdhci_enable_sdio_irq,
.start_signal_voltage_switch = sdhci_start_signal_voltage_switch,
.prepare_hs400_tuning = sdhci_prepare_hs400_tuning,
.execute_tuning = sdhci_execute_tuning,
.select_drive_strength = sdhci_select_drive_strength,
.card_event = sdhci_card_event,
.card_busy = sdhci_card_busy,
};
/*-------------------------------------------------------------------*/
mmc_alloc_host函数
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
.........
mmc_gpio_alloc(host); //与SD卡检测 cd引脚相关的数据接口建立
spin_lock_init(&host->lock);
init_waitqueue_head(&host->wq);
INIT_DELAYED_WORK(&host->detect, mmc_rescan); //延迟工作队列 用于后续初始化完成后的SD卡的扫描检测
..........
}
3、mmc_of_parse函数,core层对设备树内容的解析
int mmc_of_parse(struct mmc_host *host) //对设备树中的内容进行解析 列举一些关键内容
{
/* "bus-width" is translated to MMC_CAP_*_BIT_DATA flags */
if (of_property_read_u32(np, "bus-width", &bus_width) < 0) {
dev_dbg(host->parent,
"\"bus-width\" property is missing, assuming 1 bi
bus_width = 1;
}
switch (bus_width) { //bus_width设置
case 8:
host->caps |= MMC_CAP_8_BIT_DATA;
/* Hosts capable of 8-bit transfers can also do 4 bits */
case 4:
host->caps |= MMC_CAP_4_BIT_DATA;
break;
case 1:
break;
default:
dev_err(host->parent,
"Invalid \"bus-width\" value %u!\n", bus_width);
return -EINVAL;
}
of_property_read_u32(np, "max-frequency", &host->f_max);
cd_cap_invert = of_property_read_bool(np, "cd-inverted"); // cd引脚翻转 通常无卡为低电平,如果硬件设计为无卡高电平 则可以在设备树中进行指定
if (of_property_read_bool(np, "broken-cd")) // 在没有cd检测引脚的情况下,可以使用轮询方式进行检测,需要在设备树中指定
host->caps |= MMC_CAP_NEEDS_POLL;
ret = mmc_gpiod_request_cd(host, "cd", 0, true, //获取cd检测引脚的gpio相关信息
0, &cd_gpio_invert);
if (of_property_read_bool(np, "cap-sd-highspeed"))
host->caps |= MMC_CAP_SD_HIGHSPEED;
if (of_property_read_bool(np, "cap-mmc-highspeed"))
host->caps |= MMC_CAP_MMC_HIGHSPEED;
if (of_property_read_bool(np, "sd-uhs-sdr12")) //指定支持哪些速度等级的卡
host->caps |= MMC_CAP_UHS_SDR12;
if (of_property_read_bool(np, "sd-uhs-sdr25")) //可结合源码进行详细了解
host->caps |= MMC_CAP_UHS_SDR25;
if (of_property_read_bool(np, "sd-uhs-sdr50"))
host->caps |= MMC_CAP_UHS_SDR50;
if (of_property_read_bool(np, "sd-uhs-sdr104"))
host->caps |= MMC_CAP_UHS_SDR104;
if (of_property_read_bool(np, "sd-uhs-ddr50"))
host->caps |= MMC_CAP_UHS_DDR50;
if (of_property_read_bool(np, "cap-mmc-hw-reset")) //指定是否支持硬件复位
}
```c
data = pd.read_csv(
'https://labfile.oss.aliyuncs.com/courses/1283/adult.data.csv')
print(data.head())
4、sdhci_add_host
int sdhci_add_host(struct sdhci_host *host)
{
...........
sdhci_do_reset(host, SDHCI_RESET_ALL);
tasklet_init(&host->finish_tasklet,
sdhci_tasklet_finish, (unsigned long)host);
setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host); //设置每次操作的超时定时器
init_waitqueue_head(&host->buf_ready_int);
sdhci_init(host, 0); //初始化控制器
..........
mmc_add_host(mmc); //
}
int mmc_add_host(struct mmc_host *host)
{
........
device_add(&host->class_dev);
mmc_start_host(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;
mmc_claim_host(host); //独占host
if (host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP)
mmc_power_off(host);
else
mmc_power_up(host, host->ocr_avail);
mmc_release_host(host);
mmc_gpiod_request_cd_irq(host); //注册 cd 检测引脚的中断 并设置中断函数 关于SD卡检测处理 后续会有单独说明
_mmc_detect_change(host, 0, false); // 待前面的工作都完成后,就进行一次mmc的探测
}