1. 程式人生 > >android SD卡的熱插拔實現,及除錯

android SD卡的熱插拔實現,及除錯

dts裝置樹。

aliases {
    sdhc1 = &sdhc_1; /* SDC1 eMMC slot */
    sdhc2 = &sdhc_2; /* SDC2 SD card slot */
};
sdhc_2: [email protected] {
    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)