android SD卡的热插拔实现,及调试

dts设备树。

aliases {
    sdhc1 = &sdhc_1; /* SDC1 eMMC slot */
    sdhc2 = &sdhc_2; /* SDC2 SD card slot */
};
sdhc_2: sdhci@f98a4900 {
    cell-index = <2>;
    compatible = "qcom,sdhci-msm";
    reg = <0xf98a4900 0x11c>, <0xf98a4000 0x800>;
    reg-names = "hc_mem", "core_mem";

    interrupts = <0 125 0>, <0 221 0>;
    interrupt-names = "hc_irq", "pwr_irq";

    qcom,bus-width = <4>;
    qcom,irq_gpios = <42>;
    qcom,cpu-dma-latency-us = <701>;
    status = "disabled";
};
&sdhc_2 {
    vdd-supply = <&pm8110_l18>;
    qcom,vdd-voltage-level = <2950000 2950000>;
    qcom,vdd-current-level = <15000 400000>;

    vdd-io-supply = <&pm8110_l21>;
    qcom,vdd-io-voltage-level = <1800000 2950000>;
    qcom,vdd-io-current-level = <200 50000>;

    qcom,pad-pull-on = <0x0 0x3 0x3>; /* no-pull, pull-up, pull-up */
    qcom,pad-pull-off = <0x0 0x3 0x3>; /* no-pull, pull-up, pull-up */
    qcom,pad-drv-on = <0x4 0x4 0x4>; /* 10mA, 10mA, 10mA */
    qcom,pad-drv-off = <0x0 0x0 0x0>; /* 2mA, 2mA, 2mA */

    qcom,clk-rates = <400000 25000000 50000000>;
    qcom,sup-voltages = <2950 2950>;
    #address-cells = <0>;
    interrupt-parent = <&sdhc_2>;
    interrupts = <0 1 2>;
    #interrupt-cells = <1>;
    interrupt-map-mask = <0xffffffff>;
    interrupt-map = <0 &intc 0 125 0
            1 &intc 0 221 0
            2 &msmgpio 42 0x3>;
    interrupt-names = "hc_irq", "pwr_irq", "status_irq";
    cd-gpios = <&msmgpio 42 0x0>;

    status = "ok";
};

Makefile

#
# Makefile for the kernel mmc device drivers.
#

subdir-ccflags-$(CONFIG_MMC_DEBUG) := -DDEBUG

obj-$(CONFIG_MMC)      += core/
obj-$(CONFIG_MMC)      += card/
obj-$(subst m,y,$(CONFIG_MMC))  += host/
#core/
obj-$(CONFIG_MMC_BLOCK)        += mmc_block.o
mmc_block-objs          := block.o queue.o
obj-$(CONFIG_MMC_TEST)     += mmc_test.o
obj-$(CONFIG_SDIO_UART)        += sdio_uart.o
obj-$(CONFIG_MMC_BLOCK_TEST)   += mmc_block_test.o
obj-$(CONFIG_MMC)      += mmc_core.o
#card/
mmc_core-y          := core.o bus.o host.o \
                   mmc.o mmc_ops.o sd.o sd_ops.o \
                   sdio.o sdio_ops.o sdio_bus.o \
                   sdio_cis.o sdio_io.o sdio_irq.o \
                   quirks.o cd-gpio.o
mmc_core-$(CONFIG_DEBUG_FS)    += debugfs.o
#host/
obj-$(CONFIG_MMC_MSM_SPS_SUPPORT) += msm_sdcc_dml.o
obj-$(CONFIG_MMC_MSM)  += msm_sdcc.o
obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o
obj-$(CONFIG_MMC_SDHCI)        += sdhci.o
obj-$(CONFIG_MMC_SDHCI_PLTFM)  += sdhci-pltfm.o

Kconfig

#
# MMC subsystem configuration
#

menuconfig MMC
    tristate "MMC/SD/SDIO card support"
    depends on HAS_IOMEM
    help
      This selects MultiMediaCard, Secure Digital and Secure
      Digital I/O support.

      If you want MMC/SD/SDIO support, you should say Y here and
      also to your specific host controller driver.

config MMC_DEBUG
    bool "MMC debugging"
    depends on MMC != n
    help
      This is an option for use by developers; most people should
      say N here.  This enables MMC core and driver debugging.

config MMC_PERF_PROFILING
    bool "MMC performance profiling"
    depends on MMC != n
    default n
    help
      If you say Y here, support will be added for collecting
      performance numbers at the MMC Queue and Host layers.

if MMC

source "drivers/mmc/core/Kconfig"

source "drivers/mmc/card/Kconfig"

source "drivers/mmc/host/Kconfig"

endif # MMC

首先我们要认识到SD卡是一个块设备, 按照内核的机制,一个块设备是应该挂载在总线上的。
所以也就有了MMC和SD总线的。
既然有总线那就要注册总线。
重点内容

subsys_initcall(mmc_init);
module_exit(mmc_exit);

无可厚非的初始化,先它先于module_init() 的。优先级的问题。
看看初始化的过程:

static int __init mmc_init(void)
{
    int ret;

    workqueue = alloc_ordered_workqueue("kmmcd", 0);
    if (!workqueue)
        return -ENOMEM;

    ret = mmc_register_bus();
    if (ret)
        goto destroy_workqueue;

    ret = mmc_register_host_class();
    if (ret)
        goto unregister_bus;

    ret = sdio_register_bus();
    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;
}

workqueue = alloc_ordered_workqueue(“kmmcd”, 0);工作队列的实现是创建一个单独的线程来执行相应的work. 但是最新的内核实现却不是这样的,原先的工作队列的接口都快要废弃了,
New API:
alloc_workqueue(name, flags, max_active)
alloc_ordered_workqueue(const char *name, unsigned int flags)
{
return alloc_workqueue(name, WQ_UNBOUND | flags, 1);
}
当向一个工作队列提交一个工作时,它并不是在指定的线程里运行,系统会维护一个Worker Pool, 每个Worker跑在一个单独的线程里,每一个CPU都有一个Worker Pool. 当有work需要处理时,就唤醒一个Worker,这样就减少了系统资源的占用(原先的实现是每创建一个工作队列,系统就创建一个线程,由于每个线程都需要有task_struct, pid等资源, 这样当系统中工作队列一多的话,资源占用率就很高了). 由于内核空间是所有进程共享的一块地址空间,因此在不同进程向工作队列提交的工作时,用户其实不用关心我这个工作到底是在哪个进程中处理的,但是这样的话,如果两个工作需要同步的话(比如访问一个共享的资源时),就得仔细考虑了,两个工作向同一个工作队列提交时,可能会被同时执行(分别跑在不同的CPU上), 这样RACE就产生了, 为了解决这个问题, 引入了WQ_UNBOUND标志 和 max_acitve = 1, 这两个参数指明了向这个工作队列提交一个工作时,这个工作不会绑定在特定的CPU上(如果没有指明WQ_UNBOUND 标志的话,在哪个CPU上提交的工作一定会在那个CPU上执行), max_active指明了这个工作队列后台有多少个worker线程与之绑定,默认参数为0,让系统来指定后台线程数。

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,
    .shutdown        = mmc_bus_shutdown,
    .pm     = &mmc_bus_pm_ops,
};
int mmc_register_bus(void)
{
    return bus_register(&mmc_bus_type);
}

这里就涉及到总线的注册,kobject.kset,ktype.这三个结构联合起来一起构成了整个设备模型的基石.而bus.device.device_driver.则是基于kobject.kset.ktype之上的架构.
稍微解释一下:
bus_register(),需要的干的是,现创造一个壳,就是包含bus这个架构的一个数据结构(bus_type_private),然后这个数据结构将先承载bus_type的数据结构, bus_type_private->bus = bus_type;bus_type->p=bus_type_private.
retval = kobject_set_name(&priv->subsys.kobj, “%s”, bus->name);
kobject_set_name的能力就是在/sys/bus/下创建一个mmc的壳(好吧!文件夹),然后就是一个完整的数据结构展示/sys/bus/mmc/
devices : 设备的文件化
drivers : 驱动的文件化
drivers_autoprobe :是否自动,支持读写的。(cat,echo)
drivers_probe :驱动匹配
uevent :在 sysfs 下的很多 kobject 下都有 uevent 属性,它主要用于内核与 udev (自动设备发现程序)之间的一个通信接口
好了看看下面吧。

static struct class mmc_host_class = {
    .name       = "mmc_host",
    .dev_release    = mmc_host_classdev_release,
    .pm     = &mmc_host_pm_ops,
};

int mmc_register_host_class(void)
{
    return class_register(&mmc_host_class);
}
ret = mmc_register_host_class();

这步注册的是host这个类(类是一个设备的高层视图,它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能,而不用关心设备是如何连接和工作的。类成员通常由上层代码所控制,而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。类存在的真正目的是给作为类成员的各个设备提供一个容器)
在相关的路径下就有了/sys/class/mmc_host/

ret = sdio_register_bus();
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,
    .remove     = sdio_bus_remove,
    .pm     = SDIO_PM_OPS_PTR,
};

int sdio_register_bus(void)
{
    return bus_register(&sdio_bus_type);
}

其实SDIO 的感觉就是SD卡的IO的使用,也就是协议的问题,通过
1. CLK信号:HOST给DEVICE的时钟信号.
2. CMD信号:双向的信号,用于传送命令和反应。
3. DAT0-DAT3 信号:四条用于传送的数据线。
4. VDD信号:电源信号。
5. VSS1,VSS2:电源地信号。
实现数据的交流。也就可以不仅仅局限于SD卡了。
以上只是构建总线和host。
正真的设备在:compatible = “qcom,sdhci-msm”。
设备于驱动的probe就要通过compatible = “qcom,sdhci-msm”;的字段。

static const struct of_device_id sdhci_msm_dt_match[] = {
    {.compatible = "qcom,sdhci-msm"},
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match);

static struct platform_driver sdhci_msm_driver = {
    .probe      = sdhci_msm_probe,
    .remove     = __devexit_p(sdhci_msm_remove),
    .driver     = {
        .name   = "sdhci_msm",
        .owner  = THIS_MODULE,
        .of_match_table = sdhci_msm_dt_match,
        .pm = SDHCI_MSM_PMOPS,
    },
};

module_platform_driver(sdhci_msm_driver);

解释下module_platform_driver宏

include/linux/platform_device.h
 #define module_platform_driver(__platform_driver) \  
    module_driver(__platform_driver, platform_driver_register, \  
            platform_driver_unregister)  
include/linux/device.h        
 #define module_driver(__driver, __register, __unregister, ...) \  
static int __init __driver##_init(void) \  
{ \  
    return __register(&(__driver) , ##__VA_ARGS__); \  
} \  
module_init(__driver##_init); \  
static void __exit __driver##_exit(void) \  
{ \  
    __unregister(&(__driver) , ##__VA_ARGS__); \  
} \  
module_exit(__driver##_exit);  
最后:
static int __init xxx_init(void)
{
        return platform_driver_register(&xxx);
}
module_init(xxx_init);
static void __exit xxx_init(void)
{
        return platform_driver_unregister(&xxx);
}
module_exit(xxx_exit);

可以说就是“一劳永逸”。

只要设备树添加了对应的设备就会probe,
sdhci_msm_probe:

static int __devinit sdhci_msm_probe(struct platform_device *pdev)
{
    struct sdhci_host *host;
    struct sdhci_pltfm_host *pltfm_host;
    struct sdhci_msm_host *msm_host;
    struct resource *core_memres = NULL;
    int ret = 0, dead = 0;
    u32 vdd_max_current;
    u16 host_version;
    u32 pwr, irq_status, irq_ctl;
    unsigned long flags;

    pr_debug("%s: Enter %s\n", dev_name(&pdev->dev), __func__);
    msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host),
                GFP_KERNEL);
    if (!msm_host) {
        ret = -ENOMEM;
        goto out;
    }

    msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops;
    host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata);
    if (IS_ERR(host)) {
        ret = PTR_ERR(host);
        goto out;
    }

    pltfm_host = sdhci_priv(host);
    pltfm_host->priv = msm_host;
    msm_host->mmc = host->mmc;
    msm_host->pdev = pdev;

    /* Extract platform data */
    if (pdev->dev.of_node) {
        ret = of_alias_get_id(pdev->dev.of_node, "sdhc");
        if (ret < 0) {
            dev_err(&pdev->dev, "Failed to get slot index %d\n",
                ret);
            goto pltfm_free;
        }
        if (disable_slots & (1 << (ret - 1))) {
            dev_info(&pdev->dev, "%s: Slot %d disabled\n", __func__,
                ret);
            ret = -ENODEV;
            goto pltfm_free;
        }

        msm_host->pdata = sdhci_msm_populate_pdata(&pdev->dev);
        if (!msm_host->pdata) {
            dev_err(&pdev->dev, "DT parsing error\n");
            goto pltfm_free;
        }
    } else {
        dev_err(&pdev->dev, "No device tree node\n");
        goto pltfm_free;
    }

    /* Setup Clocks */

    /* Setup SDCC bus voter clock. */
    msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus_clk");
    if (!IS_ERR_OR_NULL(msm_host->bus_clk)) {
        /* Vote for max. clk rate for max. performance */
        ret = clk_set_rate(msm_host->bus_clk, INT_MAX);
        if (ret)
            goto pltfm_free;
        ret = clk_prepare_enable(msm_host->bus_clk);
        if (ret)
            goto pltfm_free;
    }

    /* Setup main peripheral bus clock */
    msm_host->pclk = devm_clk_get(&pdev->dev, "iface_clk");
    if (!IS_ERR(msm_host->pclk)) {
        ret = clk_prepare_enable(msm_host->pclk);
        if (ret)
            goto bus_clk_disable;
    }
    atomic_set(&msm_host->controller_clock, 1);

    /* Setup SDC MMC clock */
    msm_host->clk = devm_clk_get(&pdev->dev, "core_clk");
    if (IS_ERR(msm_host->clk)) {
        ret = PTR_ERR(msm_host->clk);
        goto pclk_disable;
    }

    /* Set to the minimum supported clock frequency */
    ret = clk_set_rate(msm_host->clk, sdhci_msm_get_min_clock(host));
    if (ret) {
        dev_err(&pdev->dev, "MClk rate set failed (%d)\n", ret);
        goto pclk_disable;
    }
    ret = clk_prepare_enable(msm_host->clk);
    if (ret)
        goto pclk_disable;

    msm_host->clk_rate = sdhci_msm_get_min_clock(host);
    atomic_set(&msm_host->clks_on, 1);

    /* Setup CDC calibration fixed feedback clock */
    msm_host->ff_clk = devm_clk_get(&pdev->dev, "cal_clk");
    if (!IS_ERR(msm_host->ff_clk)) {
        ret = clk_prepare_enable(msm_host->ff_clk);
        if (ret)
            goto clk_disable;
    }

    /* Setup CDC calibration sleep clock */
    msm_host->sleep_clk = devm_clk_get(&pdev->dev, "sleep_clk");
    if (!IS_ERR(msm_host->sleep_clk)) {
        ret = clk_prepare_enable(msm_host->sleep_clk);
        if (ret)
            goto ff_clk_disable;
    }

    msm_host->saved_tuning_phase = INVALID_TUNING_PHASE;

    ret = sdhci_msm_bus_register(msm_host, pdev);
    if (ret)
        goto sleep_clk_disable;

    if (msm_host->msm_bus_vote.client_handle)
        INIT_DELAYED_WORK(&msm_host->msm_bus_vote.vote_work,
                  sdhci_msm_bus_work);
    sdhci_msm_bus_voting(host, 1);

    /* Setup regulators */
    ret = sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, true);
    if (ret) {
        dev_err(&pdev->dev, "Regulator setup failed (%d)\n", ret);
        goto bus_unregister;
    }

    /* Reset the core and Enable SDHC mode */
    core_memres = platform_get_resource_byname(pdev,
                IORESOURCE_MEM, "core_mem");
    msm_host->core_mem = devm_ioremap(&pdev->dev, core_memres->start,
                    resource_size(core_memres));

    if (!msm_host->core_mem) {
        dev_err(&pdev->dev, "Failed to remap registers\n");
        ret = -ENOMEM;
        goto vreg_deinit;
    }

    /* Unset HC_MODE_EN bit in HC_MODE register */
    writel_relaxed(0, (msm_host->core_mem + CORE_HC_MODE));

    /* Set SW_RST bit in POWER register (Offset 0x0) */
    writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_POWER) |
            CORE_SW_RST, msm_host->core_mem + CORE_POWER);
    /*
     * SW reset can take upto 10HCLK + 15MCLK cycles.
     * Calculating based on min clk rates (hclk = 27MHz,
     * mclk = 400KHz) it comes to ~40us. Let's poll for
     * max. 1ms for reset completion.
     */
    ret = readl_poll_timeout(msm_host->core_mem + CORE_POWER,
            pwr, !(pwr & CORE_SW_RST), 10, 1000);

    if (ret) {
        dev_err(&pdev->dev, "reset failed (%d)\n", ret);
        goto vreg_deinit;
    }
    /* Set HC_MODE_EN bit in HC_MODE register */
    writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE));

    /* Set FF_CLK_SW_RST_DIS bit in HC_MODE register */
    writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_HC_MODE) |
            FF_CLK_SW_RST_DIS, msm_host->core_mem + CORE_HC_MODE);

    /*
     * CORE_SW_RST above may trigger power irq if previous status of PWRCTL
     * was either BUS_ON or IO_HIGH_V. So before we enable the power irq
     * interrupt in GIC (by registering the interrupt handler), we need to
     * ensure that any pending power irq interrupt status is acknowledged
     * otherwise power irq interrupt handler would be fired prematurely.
     */
    irq_status = readl_relaxed(msm_host->core_mem + CORE_PWRCTL_STATUS);
    writel_relaxed(irq_status, (msm_host->core_mem + CORE_PWRCTL_CLEAR));
    irq_ctl = readl_relaxed(msm_host->core_mem + CORE_PWRCTL_CTL);
    if (irq_status & (CORE_PWRCTL_BUS_ON | CORE_PWRCTL_BUS_OFF))
        irq_ctl |= CORE_PWRCTL_BUS_SUCCESS;
    if (irq_status & (CORE_PWRCTL_IO_HIGH | CORE_PWRCTL_IO_LOW))
        irq_ctl |= CORE_PWRCTL_IO_SUCCESS;
    writel_relaxed(irq_ctl, (msm_host->core_mem + CORE_PWRCTL_CTL));
    /*
     * Ensure that above writes are propogated before interrupt enablement
     * in GIC.
     */
    mb();

    /*
     * Following are the deviations from SDHC spec v3.0 -
     * 1. Card detection is handled using separate GPIO.
     * 2. Bus power control is handled by interacting with PMIC.
     */
    host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
    host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE;
    host->quirks |= SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN;
    host->quirks2 |= SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK;
    host->quirks2 |= SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE;
    host->quirks2 |= SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD;
    host->quirks2 |= SDHCI_QUIRK2_BROKEN_PRESET_VALUE;
    host->quirks2 |= SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT;

    if (host->quirks2 & SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK)
        host->quirks2 |= SDHCI_QUIRK2_DIVIDE_TOUT_BY_4;

    host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION));
    dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n",
        host_version, ((host_version & SDHCI_VENDOR_VER_MASK) >>
          SDHCI_VENDOR_VER_SHIFT));
    if (((host_version & SDHCI_VENDOR_VER_MASK) >>
        SDHCI_VENDOR_VER_SHIFT) == SDHCI_VER_100) {
        /*
         * Add 40us delay in interrupt handler when
         * operating at initialization frequency(400KHz).
         */
        host->quirks2 |= SDHCI_QUIRK2_SLOW_INT_CLR;
        /*
         * Set Software Reset for DAT line in Software
         * Reset Register (Bit 2).
         */
        host->quirks2 |= SDHCI_QUIRK2_RDWR_TX_ACTIVE_EOT;
    }

    host->quirks2 |= SDHCI_QUIRK2_IGN_DATA_END_BIT_ERROR;

    /* Setup PWRCTL irq */
    msm_host->pwr_irq = platform_get_irq_byname(pdev, "pwr_irq");
    if (msm_host->pwr_irq < 0) {
        dev_err(&pdev->dev, "Failed to get pwr_irq by name (%d)\n",
                msm_host->pwr_irq);
        goto vreg_deinit;
    }
    ret = devm_request_threaded_irq(&pdev->dev, msm_host->pwr_irq, NULL,
                    sdhci_msm_pwr_irq, IRQF_ONESHOT,
                    dev_name(&pdev->dev), host);
    if (ret) {
        dev_err(&pdev->dev, "Request threaded irq(%d) failed (%d)\n",
                msm_host->pwr_irq, ret);
        goto vreg_deinit;
    }

    /* Enable pwr irq interrupts */
    writel_relaxed(INT_MASK, (msm_host->core_mem + CORE_PWRCTL_MASK));

    /* Set clock gating delay to be used when CONFIG_MMC_CLKGATE is set */
    msm_host->mmc->clkgate_delay = SDHCI_MSM_MMC_CLK_GATE_DELAY;

    /* Set host capabilities */
    msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width;
    msm_host->mmc->caps |= msm_host->pdata->caps;

    vdd_max_current = sdhci_msm_get_vreg_vdd_max_current(msm_host);
    if (vdd_max_current >= 800)
        msm_host->mmc->caps |= MMC_CAP_MAX_CURRENT_800;
    else if (vdd_max_current >= 600)
        msm_host->mmc->caps |= MMC_CAP_MAX_CURRENT_600;
    else if (vdd_max_current >= 400)
        msm_host->mmc->caps |= MMC_CAP_MAX_CURRENT_400;
    else
        msm_host->mmc->caps |= MMC_CAP_MAX_CURRENT_200;

    if (vdd_max_current > 150)
        msm_host->mmc->caps |= MMC_CAP_SET_XPC_180 |
                    MMC_CAP_SET_XPC_300|
                    MMC_CAP_SET_XPC_330;

    msm_host->mmc->caps2 |= msm_host->pdata->caps2;
    msm_host->mmc->caps2 |= MMC_CAP2_CORE_RUNTIME_PM;
    msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR;
    msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR_CONTROL;
    msm_host->mmc->caps2 |= (MMC_CAP2_BOOTPART_NOACC |
                MMC_CAP2_DETECT_ON_ERR);
    msm_host->mmc->caps2 |= MMC_CAP2_SANITIZE;
    msm_host->mmc->caps2 |= MMC_CAP2_CACHE_CTRL;
    msm_host->mmc->caps2 |= MMC_CAP2_POWEROFF_NOTIFY;
    msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE;
    msm_host->mmc->caps2 |= MMC_CAP2_STOP_REQUEST;
    msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE;
    msm_host->mmc->caps2 |= MMC_CAP2_CORE_PM;
    msm_host->mmc->pm_caps |= MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ;

    if (msm_host->pdata->nonremovable)
        msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE;

    host->cpu_dma_latency_us = msm_host->pdata->cpu_dma_latency_us;

    init_completion(&msm_host->pwr_irq_completion);

    if (gpio_is_valid(msm_host->pdata->status_gpio)) {
        ret = mmc_cd_gpio_request(msm_host->mmc,  msm_host->pdata->status_gpio);  

        if (ret) {
            dev_err(&pdev->dev, "%s: Failed to request card detection IRQ %d\n",
                    __func__, ret);
            goto vreg_deinit;
        }
    }

    if (dma_supported(mmc_dev(host->mmc), DMA_BIT_MASK(32))) {
        host->dma_mask = DMA_BIT_MASK(32);
        mmc_dev(host->mmc)->dma_mask = &host->dma_mask;
    } else {
        dev_err(&pdev->dev, "%s: Failed to set dma mask\n", __func__);
    }

    msm_host->pdata->sdiowakeup_irq = platform_get_irq_byname(pdev,
                              "sdiowakeup_irq");
    if (msm_host->pdata->sdiowakeup_irq >= 0) {
        msm_host->is_sdiowakeup_enabled = true;
        ret = request_irq(msm_host->pdata->sdiowakeup_irq,
                  sdhci_msm_sdiowakeup_irq,
                  IRQF_SHARED | IRQF_TRIGGER_LOW,
                  "sdhci-msm sdiowakeup", host);
        if (ret) {
            dev_err(&pdev->dev, "%s: request sdiowakeup IRQ %d: failed: %d\n",
                __func__, msm_host->pdata->sdiowakeup_irq, ret);
            msm_host->pdata->sdiowakeup_irq = -1;
            msm_host->is_sdiowakeup_enabled = false;
            goto free_cd_gpio;
        } else {
            spin_lock_irqsave(&host->lock, flags);
            sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false);
            spin_unlock_irqrestore(&host->lock, flags);
        }
    }

    ret = sdhci_add_host(host);
    if (ret) {
        dev_err(&pdev->dev, "Add host failed (%d)\n", ret);
        goto free_cd_gpio;
    }

    msm_host->msm_bus_vote.max_bus_bw.show = show_sdhci_max_bus_bw;
    msm_host->msm_bus_vote.max_bus_bw.store = store_sdhci_max_bus_bw;
    sysfs_attr_init(&msm_host->msm_bus_vote.max_bus_bw.attr);
    msm_host->msm_bus_vote.max_bus_bw.attr.name = "max_bus_bw";
    msm_host->msm_bus_vote.max_bus_bw.attr.mode = S_IRUGO | S_IWUSR;
    ret = device_create_file(&pdev->dev,
            &msm_host->msm_bus_vote.max_bus_bw);
    if (ret)
        goto remove_host;

    if (!gpio_is_valid(msm_host->pdata->status_gpio)) {
        msm_host->polling.show = show_polling;
        msm_host->polling.store = store_polling;
        sysfs_attr_init(&msm_host->polling.attr);
        msm_host->polling.attr.name = "polling";
        msm_host->polling.attr.mode = S_IRUGO | S_IWUSR;
        ret = device_create_file(&pdev->dev, &msm_host->polling);
        if (ret)
            goto remove_max_bus_bw_file;
    }
    ret = pm_runtime_set_active(&pdev->dev);
    if (ret)
        pr_err("%s: %s: pm_runtime_set_active failed: err: %d\n",
               mmc_hostname(host->mmc), __func__, ret);
    else if (mmc_use_core_runtime_pm(host->mmc))
        pm_runtime_enable(&pdev->dev);

    if (msm_host->pdata->mpm_sdiowakeup_int != -1) {
        ret = sdhci_msm_cfg_mpm_pin_wakeup(host, SDC_DAT1_ENABLE);
        if (ret) {
            pr_err("%s: enabling wakeup: failed: ret: %d\n",
                   mmc_hostname(host->mmc), ret);
            ret = 0;
            msm_host->pdata->mpm_sdiowakeup_int = -1;
        }
    }

    device_enable_async_suspend(&pdev->dev);
    /* Successful initialization */
    goto out;

remove_max_bus_bw_file:
    device_remove_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw);
remove_host:
    dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff);
    sdhci_remove_host(host, dead);
free_cd_gpio:
    if (gpio_is_valid(msm_host->pdata->status_gpio))
        mmc_cd_gpio_free(msm_host->mmc);
    if (sdhci_is_valid_gpio_wakeup_int(msm_host))
        free_irq(msm_host->pdata->sdiowakeup_irq, host);
vreg_deinit:
    sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false);
bus_unregister:
    if (msm_host->msm_bus_vote.client_handle)
        sdhci_msm_bus_cancel_work_and_set_vote(host, 0);
    sdhci_msm_bus_unregister(msm_host);
sleep_clk_disable:
    if (!IS_ERR(msm_host->sleep_clk))
        clk_disable_unprepare(msm_host->sleep_clk);
ff_clk_disable:
    if (!IS_ERR(msm_host->ff_clk))
        clk_disable_unprepare(msm_host->ff_clk);
clk_disable:
    if (!IS_ERR(msm_host->clk))
        clk_disable_unprepare(msm_host->clk);
pclk_disable:
    if (!IS_ERR(msm_host->pclk))
        clk_disable_unprepare(msm_host->pclk);
bus_clk_disable:
    if (!IS_ERR_OR_NULL(msm_host->bus_clk))
        clk_disable_unprepare(msm_host->bus_clk);
pltfm_free:
    sdhci_pltfm_free(pdev);
out:
    pr_debug("%s: Exit %s\n", dev_name(&pdev->dev), __func__);
    return ret;
}

第一步:
msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host)
sdhci_msm_host数据结构的实例化。
第二步:
msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops;
主要是相关的sdio主控制器的参数:由mmc驱动来实现

static struct sdhci_ops sdhci_msm_ops = {
    .set_uhs_signaling = sdhci_msm_set_uhs_signaling,//设置超高速信号
    .check_power_status = sdhci_msm_check_power_status,//检查电源状态
    .execute_tuning = sdhci_msm_execute_tuning,//调整
    .toggle_cdr = sdhci_msm_toggle_cdr,
    .get_max_segments = sdhci_msm_max_segs,
    .set_clock = sdhci_msm_set_clock,//设置时钟
    .get_min_clock = sdhci_msm_get_min_clock,//获取最小时钟
    .get_max_clock = sdhci_msm_get_max_clock,//获取最大时钟
    .disable_data_xfer = sdhci_msm_disable_data_xfer,
    .enable_controller_clock = sdhci_msm_enable_controller_clock,
};

第三步:
创建一个sdhci_host 结构体变量,并初始化赋值
host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata);
获取设备树中的数据
msm_host->pdata = sdhci_msm_populate_pdata(&pdev->dev);
比如 of_property_read_u32(np, “qcom,irq_gpios”, &irq_gpio);
of_node :ret = of_alias_get_id(pdev->dev.of_node, “sdhc”);
np : struct device_node *np = dev->of_node;
第四步:
Setup Clocks
包括:Setup SDCC bus voter clock
Setup main peripheral bus clock
Setup SDC MMC clock
Set to the minimum supported clock frequency
Setup CDC calibration fixed feedback clock
Setup CDC calibration sleep clock

第五步:
ret = sdhci_msm_bus_register(msm_host, pdev);
将平台设备加入到host
/* Reset the core and Enable SDHC mode */
core_memres = platform_get_resource_byname(pdev,
IORESOURCE_MEM, “core_mem”);
msm_host->core_mem = devm_ioremap(&pdev->dev, core_memres->start,
resource_size(core_memres));

ret = devm_request_threaded_irq(&pdev->dev, msm_host->pwr_irq, NULL,
                sdhci_msm_pwr_irq, IRQF_ONESHOT,
                dev_name(&pdev->dev), host);
7int devm_request_threaded_irq(struct device *dev, unsigned int irq,
48                irq_handler_t handler, irq_handler_t thread_fn,
49                unsigned long irqflags, const char *devname,
50                void *dev_id)
51{
52  struct irq_devres *dr;
53  int rc;
54
55  dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
56            GFP_KERNEL);
57  if (!dr)
58      return -ENOMEM;
59
60  rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
61                dev_id);
62  if (rc) {
63      devres_free(dr);
64      return rc;
65  }
66
67  dr->irq = irq;
68  dr->dev_id = dev_id;
69  devres_add(dev, dr);
70
71  return 0;

罗嗦一下:在linux里,中断处理分为顶半(top half),底半(bottom half),在顶半里处理优先级比较高的事情,要求占用中断时间尽量的短,在处理完成后,就激活底半,有底半处理其余任务。底半的处理方式主要有soft_irq, tasklet, workqueue三种,他们在使用方式和适用情况上各有不同。soft_irq用在对底半执行时间要求比较紧急或者非常重要的场合,主要为一些subsystem用,一般driver基本上用不上。 tasklet和work queue在普通的driver里用的相对较多,主要区别是tasklet是在中断上下文执行,而work queue是在process上下文,因此可以执行可能sleep的操作。
IRQF_ONESHOT 与 IRQF_SHARED 不能同时使用:因为IRQF_ONESHOT会关闭中断线程的中断。
request_threaded_irq(gpio_irq.irq,gpio_irqhandler,gpio_threadhandler,gpio_irq.flags,gpio_irq.name,(void*)0);
hardirq和thread_fn同时出现时,处理thread_fn时该中断是打开的
request_threaded_irq(gpio_irq.irq,NULL,gpio_threadhandler,gpio_irq.flags,gpio_irq.name,(void*)0);
但hardirq和thread_fn只有一个存在时,处理thread_fn时,中断是关闭的
在驱动代码中我们经常会见到一些以devm开头的函数,这一类的函数都是和设备资源管理(Managed Device Resource)相关的,驱动中提供这些函数主要是为了方便对于申请的资源进行释放,比如:irq、regulator、gpio等等。在驱动进行初始化的时候如果失败,那么通常会goto到某个地方释放资源,这样的标签多了之后会让代码看起来不简洁,devm就是为来处理这种情况。devres_add(dev, dr);也就是通过链表管理的。
第六步:
管理热插拔的中断,
mmc_cd_gpio_request(msm_host->mmc, msm_host->pdata->status_gpio);

int mmc_cd_gpio_request(struct mmc_host *host, unsigned int gpio):
irq = gpio_to_irq(gpio);
struct mmc_cd_gpio {
    unsigned int gpio;
    bool status;:
    char label[0];
};
cd = kmalloc(sizeof(*cd) + len, GFP_KERNEL);
snprintf(cd->label, len, "%s cd", dev_name(host->parent));
gpio_request_one(gpio, GPIOF_DIR_IN, cd->label);
cd->gpio = gpio;
host->hotplug.irq = irq;
host->hotplug.handler_priv = cd;
mmc_cd_get_status(host);//判断是否插入SD卡
request_threaded_irq(irq, NULL, mmc_cd_gpio_irqt,
                   IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
                   cd->label, host);
//看看热插拔的中断实现
static irqreturn_t mmc_cd_gpio_irqt(int irq, void *dev_id)
{
    struct mmc_host *host = dev_id;
    struct mmc_cd_gpio *cd = host->hotplug.handler_priv;
    int status;
    printk("mmc_cd_gpio_irqt\n");
    status = mmc_cd_get_status(host);
    if (unlikely(status < 0))
        goto out;

    if (status ^ cd->status) {
        pr_info("%s: slot status change detected (%d -> %d), GPIO_ACTIVE_%s\n",
                mmc_hostname(host), cd->status, status,
                (host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH) ?
                "HIGH" : "LOW");
        cd->status = status;

        /* Schedule a card detection after a debounce timeout */
        mmc_detect_change(host, msecs_to_jiffies(100));
    }
out:
    return IRQ_HANDLED;
}

mmc_detect_change(host, msecs_to_jiffies(100));

void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
#ifdef CONFIG_MMC_DEBUG
    unsigned long flags;
    spin_lock_irqsave(&host->lock, flags);
    WARN_ON(host->removed);
    spin_unlock_irqrestore(&host->lock, flags);
#endif
    host->detect_change = 1;

    mmc_schedule_delayed_work(&host->detect, delay);
}

它是在core.c实现的。
也就是当sd card 的插拔都会触发中断,加入延时工作队列

static int mmc_schedule_delayed_work(struct delayed_work *work,
                     unsigned long delay)
{
    return queue_delayed_work(workqueue, work, delay);
}

workqueue是在一开始就初始化好的,
workqueue = alloc_ordered_workqueue(“kmmcd”, 0);

mmc_host的结构体在kernel/include/linux/mmc/host.h 中定义
其中detect就是其成员。
struct delayed_work detect;
第七步:
如果sd有热插拔的动作,怎么办??延时工作队列干了啥?
其实在probe的时候就初始化啦!
1. mmc = mmc_alloc_host(sizeof(struct msmsdcc_host), &pdev->dev);
2. INIT_DELAYED_WORK(&host->detect, mmc_rescan);

void mmc_rescan(struct work_struct *work)
{
    struct mmc_host *host =
        container_of(work, struct mmc_host, detect.work);
    bool extend_wakelock = false;

    if (host->rescan_disable)
        return;

    mmc_bus_get(host);
    mmc_rpm_hold(host, &host->class_dev);

    /*
     * if there is a _removable_ card registered, check whether it is
     * still present
     */
    if (host->bus_ops && host->bus_ops->detect && !host->bus_dead
        && !(host->caps & MMC_CAP_NONREMOVABLE))
     if (host->bus_ops && host->bus_ops->detect && !host->bus_dead )
        host->bus_ops->detect(host);

/*这里所做的事情:当我们进入delay work时,我们首先要检查一下,目标卡是否依然存在,如果仍然存在而且目标卡是可插拔的,那么我们需要先将该卡在内核留下的一些痕迹清除,重新探测该卡。所以一般如果是目标卡复位的话,一般是调用内核提供的专用的复位接口(如sdio_reset_comm等),而不是调度host的这个delay work*/
    host->detect_change = 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;


    /* 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;

    /*
     * Let mmc_bus_put() free the bus/bus_ops if we've found that
     * the card is no longer present.
     */
    mmc_bus_put(host);
    mmc_bus_get(host);

    /* if there still is a card present, stop here */
    /*//判断是否bus的引用计数是否为0,如果不为0 ,说明bus仍然被引用,不能再继续下面的探测了。也就是说,如果bus_ops为空就是还没有完成探测的,是第一次启动要往下跑去探测来填充bus_ops的操作*/
    if (host->bus_ops != NULL) {
        mmc_rpm_release(host, &host->class_dev);
        mmc_bus_put(host);
        goto out;
    }

    mmc_rpm_release(host, &host->class_dev);

    /*
     * Only we can add a new handler, so it's safe to
     * release the lock here.
     */
    mmc_bus_put(host);

    if (host->ops->get_cd && host->ops->get_cd(host) == 0) {
        mmc_claim_host(host);
        mmc_power_off(host);
        mmc_release_host(host);
        goto out;
    }

    mmc_rpm_hold(host, &host->class_dev);
    mmc_claim_host(host);
    if (!mmc_rescan_try_freq(host, host->f_min))
        extend_wakelock = true;
    mmc_release_host(host);
    mmc_rpm_release(host, &host->class_dev);
 out:
    /* only extend the wakelock, if suspend has not started yet */
    if (extend_wakelock && !host->rescan_disable)
        wake_lock_timeout(&host->detect_wake_lock, HZ / 2);

    if (host->caps & MMC_CAP_NEEDS_POLL)
        mmc_schedule_delayed_work(&host->detect, HZ);
}

host->bus_ops->detect(host);
mmc_rescan_try_freq();
我们来分析一下mmc_rescan_try_freq,顾名思义,就是用不同的clock去尝试初始化与目标卡的连接:

static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
    host->f_init = freq; //根据mmc_recan函数传进来的参数知道,首先传进来的是400kHZ.一般mmc/sd/sdio的初始化时钟采用的是400kHZ.

#ifdef CONFIG_MMC_DEBUG
    pr_info("%s: %s: trying to init card at %u Hz\n",
        mmc_hostname(host), __func__, host->f_init);
#endif
1.  mmc_power_up(host); //上电,我们知道,在mmc_add_host时,会调用mmc_start_host,而那里首先是将host掉电的。这里上电。

    /*
     * Some eMMCs (with VCCQ always on) may not be reset after power up, so
     * do a hardware reset if possible.
     */
    mmc_hw_reset_for_init(host); //复位硬件,可以选择性实现。

    /* Initialization should be done at 3.3 V I/O voltage. */
    mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_330, 0);//这里其实什么事情都没做,因为一般host控制器并不会实现host->ops->start_signal_voltage_switch

    /*
     * sdio_reset sends CMD52 to reset card.  Since we do not know
     * if the card is being re-initialized, just send it.  CMD52
     * should be ignored by SD/eMMC cards.
     */
    2. sdio_reset(host);//①如果目标卡是纯SD卡(对MMC卡不了解,所以不加评论),则目标卡不会应答,一般主机host的寄存器会报错,但是这个无关紧要,可以不理它。②如果目标卡是纯SDIO卡,那么这里就是复位SDIO卡,通过命令CMD52来实现的。③如果目标卡是SD卡和SDIO卡的组合卡,则需要先发送CMD52来复位SDIO卡,再复位SD卡,因为CMD52要先于CMD0发送。

When the host re-initializes both I/O and Memory controllers, it is strongly recommended that the host either execute a power reset (power off then on) or issues a reset commands to both controllers prior to any other operation. If the host chooses to use the reset commands, it shall issue CMD52 (I/O Reset) first, because it cannot issue CMD52 after CMD0 (see 4.4). After the reset, the host shall re-initialize both the I/O and Memory controller as defined in Figure 3-2.(协议)

    3. mmc_go_idle(host); //发送CMD0 复位SD卡。

    4. mmc_send_if_cond(host, host->ocr_avail); //为了支持sd version 2.0以上的sd卡,在初始化的过程中必须在发送ACMD41之前,先发送CMD8,CMD8一般是用于检测SD卡是否能运行在host提供的电压范围内。大家可能发现,这个调用过程没有检查是否出错,其实一般CMD8是用来辨别目标卡是否是高容量SD卡,如果是,CMD8 会有R7应答,R7应答中会有目标SD卡支持的电压范围以及CMD8中发送过去的“check pattern(一般是0xAA)”,否则,目标卡不会应答,在Linux 内核代码中,判断是这样的,如果应答,目标卡就是SD高容量卡,否则出现应答超时错误,就是标准SD卡!这里的调用,主要作用是为了在发送ACMD41之前发送CMD8,这是version 2.0及以上的规定顺序,后面还会有发送CMD8的地方,那里才是真正检测目标卡的类型的地方。


    /* Order's important: probe SDIO, then SD, then MMC */
    5 .if (!mmc_attach_sdio(host))
        return 0;
    6.if (!mmc_attach_sd(host))
        return 0;
    7.if (!mmc_attach_mmc(host))
        return 0;
    Linux 卡的探测顺序是:先辨别卡是否是sdio功能卡或者是sdio与sd卡的组合卡,然后辨别是否是SD卡,最后才会辨别是否是mmc卡。
    mmc_power_off(host); 
    return -EIO;
}

顺序很重要:probe SDIO,然后SD,然后MMC
在mmc_rescan_try_freq();如果是SD卡就将其:
初始化:

/*
 * Starting point for SD card init.
 */
int mmc_attach_sd(struct mmc_host *host)

填充操作方法:

static const struct mmc_bus_ops mmc_sd_ops = {
    .remove = mmc_sd_remove,
    .detect = mmc_sd_detect,
    .suspend = NULL,
    .resume = NULL,
    .power_restore = mmc_sd_power_restore,
    .alive = mmc_sd_alive,
    .change_bus_speed = mmc_sd_change_bus_speed,
};

static const struct mmc_bus_ops mmc_sd_ops_unsafe = {
    .remove = mmc_sd_remove,
    .detect = mmc_sd_detect,
    .suspend = mmc_sd_suspend,
    .resume = mmc_sd_resume,
    .power_restore = mmc_sd_power_restore,
    .alive = mmc_sd_alive,
    .change_bus_speed = mmc_sd_change_bus_speed,
};

static void mmc_sd_attach_bus_ops(struct mmc_host *host)
{
    const struct mmc_bus_ops *bus_ops;

    if (!mmc_card_is_removable(host))
        bus_ops = &mmc_sd_ops_unsafe;
    else
        bus_ops = &mmc_sd_ops;
    mmc_attach_bus(host, bus_ops);
}

这样就完成了初始化和热插拔的问题。

你可能感兴趣的:(android,LINUX)