Linux MMC HOST驅動整理(以RV1126+Kernel4.19為例)


1. mmc子系統驅動簡介

mmc子系統驅動分為三層,分別為:

Block層:主要作用是對接通用塊層,創建塊設備及上層請求處理等工作。
Core層:主要提供協議層的內容,為Block層、Host層提供相應接口。
Host層:主要是對接SOC的MMC控制器,是比較底層的寄存器操作及中斷操作。

上下層之間的交互大概如下圖所示:

 注:上圖中mtd指的是flash設備,scsi是硬盤設備,它們和mmc設備都是文件系統的承載,不在此文描述范圍。

 

mmc子系統實際包含三種設備的驅動

mmc(emmc)設備
SD設備
SDIO設備
MMC、SD、SDIO的技術本質是一樣的(使用相同的總線規范,等等),都是從MMC規范演化而來;MMC強調的是多媒體存儲(MM,MultiMedia),SD強調的是安全和數據保護(S,Secure);SDIO是從SD演化出來的,強調的是接口(IO,Input/Output),不再關注另一端的具體形態(可以是WIFI設備、Bluetooth設備、GPS等等)。

雖然這三種設備共用這一套子系統,但對應到實際的設備時,這三種不同的設備有不同的物理層規范協議。

2. DTS描述

 

打開設備dtsi文件(kernel/arch/arm/boot/dts/rv1126.dtsi), 其中描述了三種類型的mmc host設備:

    emmc: dwmmc@ffc50000 {
        compatible = "rockchip,rv1126-dw-mshc", "rockchip,rk3288-dw-mshc";
        reg = <0xffc50000 0x4000>;
        interrupts = <GIC_SPI 78 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&cru HCLK_EMMC>, <&cru CLK_EMMC>,
             <&cru SCLK_EMMC_DRV>, <&cru SCLK_EMMC_SAMPLE>;
        clock-names = "biu", "ciu", "ciu-drive", "ciu-sample";
        fifo-depth = <0x100>;
        max-frequency = <200000000>;
        pinctrl-names = "default";
        pinctrl-0 = <&emmc_clk &emmc_cmd &emmc_bus8>;
        power-domains = <&power RV1126_PD_NVM>;
        rockchip,use-v2-tuning;
        status = "disabled";
    };

    sdmmc: dwmmc@ffc60000 {
        compatible = "rockchip,rv1126-dw-mshc", "rockchip,rk3288-dw-mshc";
        reg = <0xffc60000 0x4000>;
        interrupts = <GIC_SPI 76 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&cru HCLK_SDMMC>, <&cru CLK_SDMMC>,
             <&cru SCLK_SDMMC_DRV>, <&cru SCLK_SDMMC_SAMPLE>;
        clock-names = "biu", "ciu", "ciu-drive", "ciu-sample";
        fifo-depth = <0x100>;
        max-frequency = <200000000>;
        pinctrl-names = "default";
        pinctrl-0 = <&sdmmc0_clk &sdmmc0_cmd &sdmmc0_det &sdmmc0_bus4>;
        status = "disabled";
    };

    sdio: dwmmc@ffc70000 {
        compatible = "rockchip,rv1126-dw-mshc", "rockchip,rk3288-dw-mshc";
        reg = <0xffc70000 0x4000>;
        interrupts = <GIC_SPI 77 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&cru HCLK_SDIO>, <&cru CLK_SDIO>,
             <&cru SCLK_SDIO_DRV>, <&cru SCLK_SDIO_SAMPLE>;
        clock-names = "biu", "ciu", "ciu-drive", "ciu-sample";
        fifo-depth = <0x100>;
        max-frequency = <200000000>;
        pinctrl-names = "default";
        pinctrl-0 = <&sdmmc1_clk &sdmmc1_cmd &sdmmc1_bus4>;
        power-domains = <&power RV1126_PD_SDIO>;
        status = "disabled";
    };

注意上述的三個設備描述狀態都是disabled(status = "disabled";),也就是處於未啟用狀態,打開dts文件(kernel/arch/arm/boot/dts/rv1109-38-v10-spi-nand.dts),其中只有sdmmc設備的狀態得到了更新(status = "okay";),也就是實際只配置了一個host用於sdmmc設備。

&sdmmc {
    bus-width = <4>;
    cap-mmc-highspeed;
    cap-sd-highspeed;
    card-detect-delay = <200>;
    rockchip,default-sample-phase = <90>;
    supports-sd;
    status = "okay";
    vmmc-supply = <&vcc_sd>;
};

 

3. 驅動注冊


打開驅動文件dw_mmc-rockchip.c(位於kernel/drivers/mmc/host),該文件中定義了mmc的平台驅動:

static const struct of_device_id dw_mci_rockchip_match[] = {
    { .compatible = "rockchip,rk2928-dw-mshc",
        .data = &rk2928_drv_data },
    { .compatible = "rockchip,rk3288-dw-mshc",
        .data = &rk3288_drv_data },
    {},
};
MODULE_DEVICE_TABLE(of, dw_mci_rockchip_match);

static struct platform_driver dw_mci_rockchip_pltfm_driver = {
    .probe        = dw_mci_rockchip_probe,
    .remove        = dw_mci_rockchip_remove,
    .driver        = {
        .name        = "dwmmc_rockchip",
        .of_match_table    = dw_mci_rockchip_match,
        .pm        = &dw_mci_rockchip_dev_pm_ops,
    },
};

module_platform_driver(dw_mci_rockchip_pltfm_driver);

匹配字符串.compatible = "rockchip,rk3288-dw-mshc"與DTS文件中 compatible = "rockchip,rv1126-dw-mshc", "rockchip,rk3288-dw-mshc"字段成功匹配,平台驅動的probe函數即會被調用:

static int dw_mci_rockchip_probe(struct platform_device *pdev)
{
    const struct dw_mci_drv_data *drv_data;
    const struct of_device_id *match;
    int ret;
    bool use_rpm = true;

    if (!pdev->dev.of_node)
        return -ENODEV;

    if (!device_property_read_bool(&pdev->dev, "non-removable") &&
        !device_property_read_bool(&pdev->dev, "cd-gpios"))
        use_rpm = false;

    match = of_match_node(dw_mci_rockchip_match, pdev->dev.of_node);
    drv_data = match->data;

    /*
     * increase rpm usage count in order to make
     * pm_runtime_force_resume calls rpm resume callback
     */
    pm_runtime_get_noresume(&pdev->dev);

    if (use_rpm) {
        pm_runtime_set_active(&pdev->dev);
        pm_runtime_enable(&pdev->dev);
        pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
        pm_runtime_use_autosuspend(&pdev->dev);
    }

    ret = dw_mci_pltfm_register(pdev, drv_data);
    if (ret) {
        if (use_rpm) {
            pm_runtime_disable(&pdev->dev);
            pm_runtime_set_suspended(&pdev->dev);
        }
        pm_runtime_put_noidle(&pdev->dev);
        return ret;
    }

    if (use_rpm)
        pm_runtime_put_autosuspend(&pdev->dev);

    return 0;
}

probe函數主要調用了dw_mci_pltfm_register進行平台驅動的進一步注冊,該函數位於dw_mmc-pltfm.c(位於kernel/drivers/mmc/host):

int dw_mci_pltfm_register(struct platform_device *pdev,
              const struct dw_mci_drv_data *drv_data)
{
    struct dw_mci *host;
    struct resource    *regs;

    host = devm_kzalloc(&pdev->dev, sizeof(struct dw_mci), GFP_KERNEL);
    if (!host)
        return -ENOMEM;

    host->irq = platform_get_irq(pdev, 0);
    if (host->irq < 0)
        return host->irq;

    host->drv_data = drv_data;
    host->dev = &pdev->dev;
    host->irq_flags = 0;
    host->pdata = pdev->dev.platform_data;

    regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    host->regs = devm_ioremap_resource(&pdev->dev, regs);
    if (IS_ERR(host->regs))
        return PTR_ERR(host->regs);

    /* Get registers' physical base address */
    host->phy_regs = regs->start;

    platform_set_drvdata(pdev, host);
    return dw_mci_probe(host);
}

后續的調用關系:

dw_mci_pltfm_register  -->  dw_mci_probe(kernel/drivers/mmc/host/dw_mmc.c)    -->  dw_mci_init_slot(kernel/drivers/mmc/host/dw_mmc.c)   --> mmc_alloc_host (kernel/drivers/mmc/core/host.c)  -->INIT_DELAYED_WORK(&host->detect, mmc_rescan);

可以看到mmc_alloc_host函數初始化了一個延遲工作隊列,任務是調用mmc_rescan函數進行設備識別流程。

此時平台驅動已注冊完成,可以在系統節點看到相關信息:

 

 4 .識別流程

mmc_rescan函數實現如下

static const unsigned freqs[] = { 400000, 300000, 200000, 100000 };

void mmc_rescan(struct work_struct *work)
{
    struct mmc_host *host =
        container_of(work, struct mmc_host, detect.work);
    int i;

    if (host->rescan_disable)
        return;

    /* If there is a non-removable card registered, only scan once */
    if (!mmc_card_is_removable(host) && host->rescan_entered)
        return;
    host->rescan_entered = 1;

    if (host->trigger_card_event && host->ops->card_event) {
        mmc_claim_host(host);
        host->ops->card_event(host);
        mmc_release_host(host);
        host->trigger_card_event = false;
    }

    mmc_bus_get(host);

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

    host->detect_change = 0;

    /*
     * 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 */
    if (host->bus_ops != NULL) {
        mmc_bus_put(host);
        goto out;
    }

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

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

    for (i = 0; i < ARRAY_SIZE(freqs); i++) {
        if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min)))
            break;
        if (freqs[i] <= host->f_min)
            break;
    }
    mmc_release_host(host);

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

 

mmc_rescan函數就是用來掃描識別eMMC、SD、SDIO設備的,可以看出該函數核心流程是400KHz,300KHz, 200KHz,100KHz這四種頻率調用mmc_rescan_try_freq去掃描設備, 設備識別時會一個較低的頻率去與設備交互,當設備識別成功后,可以將工作頻率提高。

實際掃描時會先嘗試識別SDIO設備,如果成功則返回;否則,繼續嘗試識別SD設備,如果成功則返回;否則,繼續嘗試識別MMC設備,如果成功則返回;否則返回錯誤。當然,識別前會先對設備上電,硬件復位等,如果都沒有識別到設備,就對設備下電。

 

 

mmc_rescan_try_freq代碼實現如下:
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
    host->f_init = freq;

    printk("%s: %s: trying to init card at %u Hz\n",
        mmc_hostname(host), __func__, host->f_init);


    mmc_power_up(host, host->ocr_avail);

    /*
     * Some eMMCs (with VCCQ always on) may not be reset after power up, so
     * do a hardware reset if possible.
     */
#ifndef CONFIG_ROCKCHIP_THUNDER_BOOT
    mmc_hw_reset_for_init(host);
#endif

#ifdef CONFIG_SDIO_KEEPALIVE
    if (host->support_chip_alive) {
        host->chip_alive = 1;
        if (!mmc_attach_sdio(host)) {
            return 0;
        } else {
            pr_err("%s: chip_alive attach sdio failed.\n", mmc_hostname(host));
            host->chip_alive = 0;
        }
    } else {
        host->chip_alive = 0;
    }
#endif
    /*
     * 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.
     * Skip it if we already know that we do not support SDIO commands
     */
#ifdef MMC_STANDARD_PROBE
    if (!(host->caps2 & MMC_CAP2_NO_SDIO))
        sdio_reset(host);

    mmc_go_idle(host);

    if (!(host->caps2 & MMC_CAP2_NO_SD))
        mmc_send_if_cond(host, host->ocr_avail);
    
    /* Order's important: probe SDIO, then SD, then MMC */
    if (!(host->caps2 & MMC_CAP2_NO_SDIO))
        if (!mmc_attach_sdio(host))
            return 0;
    if (!(host->caps2 & MMC_CAP2_NO_SD))
        if (!mmc_attach_sd(host))
            return 0;
    if (!(host->caps2 & MMC_CAP2_NO_MMC))
        if (!mmc_attach_mmc(host))
            return 0;
#else
#ifdef CONFIG_SDIO_KEEPALIVE
    if ((!(host->chip_alive)) && (host->restrict_caps & RESTRICT_CARD_TYPE_SDIO))
        sdio_reset(host);
#else
    if (host->restrict_caps & RESTRICT_CARD_TYPE_SDIO)
        sdio_reset(host);
#endif

    mmc_go_idle(host);

    if (host->restrict_caps &
        (RESTRICT_CARD_TYPE_SDIO | RESTRICT_CARD_TYPE_SD))
        mmc_send_if_cond(host, host->ocr_avail);
    /* Order's important: probe SDIO, then SD, then MMC */
    if ((host->restrict_caps & RESTRICT_CARD_TYPE_SDIO) &&
        !mmc_attach_sdio(host))
        return 0;
    if ((host->restrict_caps & RESTRICT_CARD_TYPE_SD) &&
        !mmc_attach_sd(host))
        return 0;
    if ((host->restrict_caps & RESTRICT_CARD_TYPE_EMMC) &&
        !mmc_attach_mmc(host))
        return 0;
#endif
    mmc_power_off(host);
    return -EIO;
}

 

mmc_go_idle 
發送CMD0指令(MMC_GO_IDLE_STATE),使mmc card進入idle state。 

mmc_send_if_cond
發送CMD8指令(SD_SEND_IF_COND),用來確定卡的操作條件,這個命令只有SD2.0才會響應,SD2.0物理層協議定義了一個新的CMD8來確定SD卡對電壓范圍的支持,對於1.0可以沒有響應。

 

mmc_attach_sd函數:

/*
 * Starting point for SD card init.
 */
int mmc_attach_sd(struct mmc_host *host)
{
    int err;
    u32 ocr, rocr;

    WARN_ON(!host->claimed);

    err = mmc_send_app_op_cond(host, 0, &ocr);
    if (err)
        return err;

    mmc_attach_bus(host, &mmc_sd_ops);
    if (host->ocr_avail_sd)
        host->ocr_avail = host->ocr_avail_sd;

    /*
     * We need to get OCR a different way for SPI.
     */
    if (mmc_host_is_spi(host)) {
        mmc_go_idle(host);

        err = mmc_spi_read_ocr(host, 0, &ocr);
        if (err)
            goto err;
    }
    /*
     * Some SD cards claims an out of spec VDD voltage range. Let's treat
     * these bits as being in-valid and especially also bit7.
     */
    ocr &= ~0x7FFF;

    rocr = mmc_select_voltage(host, ocr);
    /*
     * Can we support the voltage(s) of the card(s)?
     */
    if (!rocr) {
        err = -EINVAL;
        goto err;
    }
    /*
     * Detect and init the card.
     */
    err = mmc_sd_init_card(host, rocr, NULL);
    if (err)
        goto err;

    mmc_release_host(host);
    err = mmc_add_card(host->card);
    if (err)
        goto remove_card;

    mmc_claim_host(host);
    return 0;

remove_card:
    mmc_remove_card(host->card);
    host->card = NULL;
    mmc_claim_host(host);
err:
    mmc_detach_bus(host);

    pr_err("%s: error %d whilst initialising SD card\n",
        mmc_hostname(host), err);
    printk("%s: error %d whilst initialising SD card\n",
        mmc_hostname(host), err);
    return err;
}

 

mmc_send_app_op_cond

發送CMD55(MMC_APP_CMD)+ACMD41(SD_APP_OP_COND),用來識別滿足host所提供電壓的卡。

根據SD4.0協議,CMD55表示下個命令是特定應用命令,而不是標准命令,后面需要跟着發送特定應用命令(如ACMD41)。

 

后續過程待補充......

==========================================================================================================

參考文章:

https://blog.csdn.net/weixin_38878510/article/details/109027315


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM