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