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