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

posted @ 2022-03-17 14:08  DF11G  阅读(3101)  评论(0编辑  收藏  举报