From 0fda18b7f588382ed9f433fb0a7ec25fc61ab3fc Mon Sep 17 00:00:00 2001 From: David Laight Date: Mon, 8 Jun 2026 10:55:03 +0100 Subject: [PATCH 01/43] mmc: core: Replace strcpy() in loop with memcpy() outside When copying the info strings and generating a char ** list copy all the strings into the kmalloced buffer outside the loop and use strchr(str, 0) to find the next string inside the loop. Removes some strcpy() that static tools might think are unbounded. Signed-off-by: David Laight Signed-off-by: Ulf Hansson --- drivers/mmc/core/sdio_cis.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c index afaa6cab1adc..0b818cb478ce 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -57,12 +57,11 @@ static int cistpl_vers_1(struct mmc_card *card, struct sdio_func *func, return -ENOMEM; string = (char*)(buffer + nr_strings); + memcpy(string, buf, size); for (i = 0; i < nr_strings; i++) { buffer[i] = string; - strcpy(string, buf); - string += strlen(string) + 1; - buf += strlen(buf) + 1; + string = strchr(string, 0) + 1; } if (func) { From 555bb7b7c1497accc0894423220f772f006a9584 Mon Sep 17 00:00:00 2001 From: Judith Mendez Date: Thu, 6 Aug 2026 11:22:01 -0500 Subject: [PATCH 02/43] dt-bindings: mmc: Add power-off-delay-us property Add power-off-delay-us property to MMC controller common. This property shall be used to specify value of delay after deasserting power during MMC power cycles. Default for delay is 1000us but custom delay can be passed in to work around hardware issues such as slow RC discharge on MMC VDD rails. Signed-off-by: Judith Mendez Acked-by: Rob Herring (Arm) Signed-off-by: Ulf Hansson --- .../devicetree/bindings/mmc/mmc-controller-common.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/mmc-controller-common.yaml b/Documentation/devicetree/bindings/mmc/mmc-controller-common.yaml index 3d7195e9461c..8b9b3b036129 100644 --- a/Documentation/devicetree/bindings/mmc/mmc-controller-common.yaml +++ b/Documentation/devicetree/bindings/mmc/mmc-controller-common.yaml @@ -275,6 +275,16 @@ properties: not available. default: 10 + power-off-delay-us: + description: + Delay in microseconds after card power is deasserted during a power + cycle to allow time for proper power discharge. Larger values can be + configured to work around hardware issues such as slow RC discharge + on MMC VDD rails. + minimum: 0 + maximum: 10000000 + default: 1000 + supports-cqe: $ref: /schemas/types.yaml#/definitions/flag description: From 3c6f98b6e64e565ec548c63e32e58af2af2c1811 Mon Sep 17 00:00:00 2001 From: Judith Mendez Date: Thu, 6 Aug 2026 11:22:02 -0500 Subject: [PATCH 03/43] mmc: core: Add power-off-delay-us support Add support for power-off-delay-us which shall be used to specify value of delay after deasserting power during MMC power cycles. Default for delay is 1000us but custom delay can be passed in to work around hardware issues such as slow RC discharge on MMC VDD rails. Signed-off-by: Judith Mendez Signed-off-by: Ulf Hansson --- drivers/mmc/core/core.c | 2 +- drivers/mmc/core/core.h | 8 ++++++++ drivers/mmc/core/host.c | 4 ++++ include/linux/mmc/host.h | 1 + 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 29e80e5f928e..9472041fe1c2 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1388,7 +1388,7 @@ void mmc_power_off(struct mmc_host *host) * XO-1.5, require a short delay after poweroff before the card * can be successfully turned on again. */ - mmc_delay(1); + mmc_delay_us(host->ios.power_off_delay_us); } void mmc_power_cycle(struct mmc_host *host, u32 ocr) diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index a028b48be164..601c73761e88 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -73,6 +73,14 @@ static inline void mmc_delay(unsigned int ms) msleep(ms); } +static inline void mmc_delay_us(unsigned int us) +{ + if (us == 0) + return; + + fsleep(us); +} + void mmc_rescan(struct work_struct *work); void mmc_start_host(struct mmc_host *host); void __mmc_stop_host(struct mmc_host *host); diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index b7ce3137d452..ac1846e0e7fb 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -421,6 +421,9 @@ int mmc_of_parse(struct mmc_host *host) device_property_read_u32(dev, "post-power-on-delay-ms", &host->ios.power_delay_ms); + device_property_read_u32(dev, "power-off-delay-us", + &host->ios.power_off_delay_us); + return mmc_pwrseq_alloc(host); } @@ -574,6 +577,7 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) host->fixed_drv_type = -EINVAL; host->ios.power_delay_ms = 10; + host->ios.power_off_delay_us = 1000; host->ios.power_mode = MMC_POWER_UNDEFINED; return host; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index ba84f02c2a10..714417466707 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -23,6 +23,7 @@ struct mmc_ios { unsigned int clock; /* clock rate */ unsigned short vdd; unsigned int power_delay_ms; /* waiting for stable power */ + unsigned int power_off_delay_us; /* waiting for power discharge */ /* vdd stores the bit number of the selected voltage range from below. */ From b57cc4ec84a887e452ea2c7872a4eb13f03608e6 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 13 Aug 2026 12:34:02 +0700 Subject: [PATCH 04/43] mmc: meson-gx: Handle errors from optional IRQ lookup platform_get_irq_optional() returns a positive IRQ number on success or a negative error code on failure. For an optional IRQ, -ENXIO indicates that no optional IRQ is available. Other errors, such as -EPROBE_DEFER and -EINVAL, should be propagated so that the caller can handle them appropriately. However, the driver currently stores the return value directly in cd_irq and continues probing. Propagate negative errors other than -ENXIO, and only assign the IRQ to cd_irq when a valid IRQ number is returned. Signed-off-by: bui duc phuc Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 694bb443d5f3..d2903e3dfd6f 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -1141,7 +1141,8 @@ static int meson_mmc_probe(struct platform_device *pdev) struct meson_host *host; struct mmc_host *mmc; struct clk *core_clk; - int cd_irq, ret; + int cd_irq = -ENXIO; + int ret; mmc = devm_mmc_alloc_host(&pdev->dev, sizeof(struct meson_host)); if (!mmc) @@ -1185,7 +1186,11 @@ static int meson_mmc_probe(struct platform_device *pdev) if (host->irq < 0) return host->irq; - cd_irq = platform_get_irq_optional(pdev, 1); + ret = platform_get_irq_optional(pdev, 1); + if (ret < 0 && ret != -ENXIO) + return ret; + if (ret > 0) + cd_irq = ret; mmc_gpio_set_cd_irq(mmc, cd_irq); host->pinctrl = devm_pinctrl_get(&pdev->dev); From 31a43e75e2d4b2b21fbd825a26550cbf6a3ba7b1 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 13 Aug 2026 12:34:03 +0700 Subject: [PATCH 05/43] mmc: davinci: Handle errors from optional IRQ lookup platform_get_irq_optional() returns a positive IRQ number on success or a negative error code on failure. For an optional IRQ, -ENXIO indicates that no optional IRQ is available. Other errors, such as -EPROBE_DEFER and -EINVAL, should be propagated so that the caller can handle them appropriately. However, the driver currently stores the return value directly in host->sdio_irq and continues probing. Propagate negative errors other than -ENXIO. Signed-off-by: bui duc phuc Signed-off-by: Ulf Hansson --- drivers/mmc/host/davinci_mmc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mmc/host/davinci_mmc.c b/drivers/mmc/host/davinci_mmc.c index cdb9fa94b56d..773577a10512 100644 --- a/drivers/mmc/host/davinci_mmc.c +++ b/drivers/mmc/host/davinci_mmc.c @@ -1249,6 +1249,10 @@ static int davinci_mmcsd_probe(struct platform_device *pdev) host->use_dma = use_dma; host->mmc_irq = irq; host->sdio_irq = platform_get_irq_optional(pdev, 1); + if (host->sdio_irq < 0 && host->sdio_irq != -ENXIO) { + ret = host->sdio_irq; + goto parse_fail; + } if (host->use_dma) { ret = davinci_acquire_dma_channels(host); From bcd0398aaa2c302d03fc11b485665ea2aed29fcb Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 13 Aug 2026 12:34:04 +0700 Subject: [PATCH 06/43] mmc: davinci: Handle optional IRQ return value correctly host->sdio_irq is assigned from platform_get_irq_optional(), which returns a positive IRQ number on success or a negative error code on failure. Therefore, 0 is not a possible return value from this API. Check for a positive IRQ number before requesting the SDIO IRQ instead of treating zero as a valid IRQ. Signed-off-by: bui duc phuc Signed-off-by: Ulf Hansson --- drivers/mmc/host/davinci_mmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/davinci_mmc.c b/drivers/mmc/host/davinci_mmc.c index 773577a10512..c503e5f5c886 100644 --- a/drivers/mmc/host/davinci_mmc.c +++ b/drivers/mmc/host/davinci_mmc.c @@ -1303,7 +1303,7 @@ static int davinci_mmcsd_probe(struct platform_device *pdev) if (ret) goto mmc_add_host_fail; - if (host->sdio_irq >= 0) { + if (host->sdio_irq > 0) { ret = devm_request_irq(&pdev->dev, host->sdio_irq, mmc_davinci_sdio_irq, 0, mmc_hostname(mmc), host); From 9dc0aebe7b3391ff9161d0a79b34709211de2001 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:06:03 +0200 Subject: [PATCH 07/43] mmc: meson-gx: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Reviewed-by: Brian Masney Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index d2903e3dfd6f..4d0a8f0891af 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -418,7 +418,7 @@ static int meson_mmc_clk_set(struct meson_host *host, unsigned long rate, */ static int meson_mmc_clk_init(struct meson_host *host) { - struct clk_init_data init; + struct clk_init_data init = {}; struct clk_mux *mux; struct clk_divider *div; char clk_name[32]; From a0884cb7603b5b8176458b2684f02c2086ccdca9 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:06:04 +0200 Subject: [PATCH 08/43] mmc: sdhci-of-arasan: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Acked-by: Adrian Hunter Reviewed-by: Brian Masney Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-arasan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-of-arasan.c b/drivers/mmc/host/sdhci-of-arasan.c index 4ca73e7d799e..0ee22923ad56 100644 --- a/drivers/mmc/host/sdhci-of-arasan.c +++ b/drivers/mmc/host/sdhci-of-arasan.c @@ -1552,8 +1552,8 @@ sdhci_arasan_register_sdcardclk(struct sdhci_arasan_data *sdhci_arasan, struct device *dev) { struct sdhci_arasan_clk_data *clk_data = &sdhci_arasan->clk_data; + struct clk_init_data sdcardclk_init = {}; struct device_node *np = dev->of_node; - struct clk_init_data sdcardclk_init; const char *parent_clk_name; int ret; @@ -1604,8 +1604,8 @@ sdhci_arasan_register_sampleclk(struct sdhci_arasan_data *sdhci_arasan, struct device *dev) { struct sdhci_arasan_clk_data *clk_data = &sdhci_arasan->clk_data; + struct clk_init_data sampleclk_init = {}; struct device_node *np = dev->of_node; - struct clk_init_data sampleclk_init; const char *parent_clk_name; int ret; From ddbf084a77740dcb8f46fcd3625d9310d47e197a Mon Sep 17 00:00:00 2001 From: Sean Rhodes Date: Mon, 24 Aug 2026 21:39:29 +0100 Subject: [PATCH 09/43] mmc: rtsx_usb_sdmmc: start card power-up at 3.3V A UHS session can leave the SD pads and SD18 regulator configured for 1.8V. The power-off path disables card power and suspends the regulator, but does not restore their voltage selection. On the next power-up, this stale state remains until after the MMC core requests its initial signal voltage. Restore the SD pads and SD18 regulator to 3.3V before enabling card power, as the old rts5139 driver did. Tested: StarLite ADL with an RTS5129 tray reader; repeated 1.8V UHS sessions, power cycles, and tray removal/reinsertion. Tested: StarFighter MTL with an RTS5129 trayless reader; repeated 1.8V UHS sessions, power cycles, and card removal/reinsertion. Tested: Both systems re-enumerated the card after every cycle. Signed-off-by: Sean Rhodes Signed-off-by: Ulf Hansson --- drivers/mmc/host/rtsx_usb_sdmmc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c index bf37fab233cf..d9b33aa79165 100644 --- a/drivers/mmc/host/rtsx_usb_sdmmc.c +++ b/drivers/mmc/host/rtsx_usb_sdmmc.c @@ -980,6 +980,11 @@ static int sd_power_on(struct rtsx_usb_sdmmc *host) } dev_dbg(sdmmc_dev(host), "%s\n", __func__); rtsx_usb_init_cmd(ucr); + /* Start SD init at 3.3V, like the old rts5139 driver. */ + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PAD_CTL, + SD_IO_USING_1V8, SD_IO_USING_3V3); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, LDO_POWER_CFG, + TUNE_SD18_MASK, TUNE_SD18_3V3); rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SELECT, 0x07, SD_MOD_SEL); rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SHARE_MODE, CARD_SHARE_MASK, CARD_SHARE_SD); From b29d45eb80139628b6471d2d75ddfdf9a4863907 Mon Sep 17 00:00:00 2001 From: Nitin Rawat Date: Tue, 25 Aug 2026 14:07:02 +0530 Subject: [PATCH 10/43] dt-bindings: mmc: sdhci-msm: Document the Maili compatible Document the compatible for SDHCI on Qualcomm Maili SoC. Signed-off-by: Nitin Rawat Acked-by: Conor Dooley Signed-off-by: Ulf Hansson --- Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml b/Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml index b3fcc1673c10..69bd44e65ad7 100644 --- a/Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml +++ b/Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml @@ -47,6 +47,7 @@ properties: - qcom,ipq9574-sdhci - qcom,ipq9650-sdhci - qcom,kaanapali-sdhci + - qcom,maili-sdhci - qcom,milos-sdhci - qcom,qcm2290-sdhci - qcom,qcs404-sdhci From 860a4d0f52e8443d4b46593ae6e68903569607c6 Mon Sep 17 00:00:00 2001 From: Neeraj Soni Date: Wed, 26 Aug 2026 15:22:16 +0530 Subject: [PATCH 11/43] mmc: core: Allow host driver to control the re-programming of crypto keys The register context for the crypto profile belongs to the sdhci/cqhci host and not the mmc card, hence re-programming should be managed by the host driver itself. Add the flag MMC_CAP2_CRYPTO_NO_REPROG as part of host->caps2 to control reprogramming keys to crypto engine for SoCs which do not require this feature. Signed-off-by: Seshu Madhavi Puppala Co-developed-by: Ram Prakash Gupta Signed-off-by: Ram Prakash Gupta Co-developed-by: Sarthak Garg Signed-off-by: Sarthak Garg Signed-off-by: Debraj Mukhopadhyay Reviewed-by: Eric Biggers Signed-off-by: Neeraj Soni Signed-off-by: Ulf Hansson --- drivers/mmc/core/crypto.c | 2 +- include/linux/mmc/host.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/crypto.c b/drivers/mmc/core/crypto.c index fec4fbf16a5b..a5a90bfc634e 100644 --- a/drivers/mmc/core/crypto.c +++ b/drivers/mmc/core/crypto.c @@ -15,7 +15,7 @@ void mmc_crypto_set_initial_state(struct mmc_host *host) { /* Reset might clear all keys, so reprogram all the keys. */ - if (host->caps2 & MMC_CAP2_CRYPTO) + if ((host->caps2 & MMC_CAP2_CRYPTO) && !(host->caps2 & MMC_CAP2_CRYPTO_NO_REPROG)) blk_crypto_reprogram_all_keys(&host->crypto_profile); } diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 714417466707..ddb32bc2946f 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -464,6 +464,7 @@ struct mmc_host { #define MMC_CAP2_CRYPTO 0 #endif #define MMC_CAP2_ALT_GPT_TEGRA (1 << 28) /* Host with eMMC that has GPT entry at a non-standard location */ +#define MMC_CAP2_CRYPTO_NO_REPROG (1 << 29) /* Host handles inline crypto key reprogramming */ bool uhs2_sd_tran; /* UHS-II flag for SD_TRAN state */ bool uhs2_app_cmd; /* UHS-II flag for APP command */ From 8b190e9c0b36aac5c107fe8003e23cf7a2afabc8 Mon Sep 17 00:00:00 2001 From: Ram Prakash Gupta Date: Wed, 26 Aug 2026 15:22:17 +0530 Subject: [PATCH 12/43] mmc: sdhci-msm: Use pm ops instead of macro to restore crypto keys Inline Crypto Engine (ICE) keys are lost after hibernation entry and this needs to be restored when hibernation exits. ICE keys are re-programmed during sdhci_msm_ice_init() but it may not cover cases where the hibernation image is already restored. Unwrap the pm ops and use directly in driver to add the call to restore Inline Crypto Engine (ICE) keys. This ensures that ICE is brought into same state as before hibernation. If hibernation image creation itself fails then device boots through normal flow where there is no need to reprogram the keys. Also set MMC_CAP2_CRYPTO_NO_REPROG to indicate that re-programming of ICE keys is not needed during MMC runtime suspend/resume or suspend-to-RAM since the rail powering the ICE will not be turned off. During CQE recovery, key would be lost only when BCR reset is performed which do not happen right now and will be taken up once it is fixed as part of recovery flow. Signed-off-by: Ram Prakash Gupta Signed-off-by: Seshu Madhavi Puppala Co-developed-by: Ram Prakash Gupta Signed-off-by: Ram Prakash Gupta Co-developed-by: Sarthak Garg Signed-off-by: Sarthak Garg Signed-off-by: Debraj Mukhopadhyay Signed-off-by: Neeraj Soni Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-msm.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 4aff965f0e2e..460994164d2d 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1959,6 +1959,7 @@ static int sdhci_msm_ice_init(struct sdhci_msm_host *msm_host, } mmc->caps2 |= MMC_CAP2_CRYPTO; + mmc->caps2 |= MMC_CAP2_CRYPTO_NO_REPROG; return 0; } @@ -2055,6 +2056,12 @@ static int sdhci_msm_ice_prepare_key(struct blk_crypto_profile *profile, return qcom_ice_prepare_key(msm_host->ice, lt_key, lt_key_size, eph_key); } +static void sdhci_msm_ice_restore(struct sdhci_host *host) +{ + if (host->mmc->caps2 & MMC_CAP2_CRYPTO) + blk_crypto_reprogram_all_keys(&host->mmc->crypto_profile); +} + static void sdhci_msm_non_cqe_ice_init(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -2149,6 +2156,10 @@ sdhci_msm_ice_suspend(struct sdhci_msm_host *msm_host) { return 0; } + +static inline void sdhci_msm_ice_restore(struct sdhci_host *host) +{ +} #endif /* !CONFIG_MMC_CRYPTO */ /*****************************************************************************\ @@ -2983,9 +2994,28 @@ static int sdhci_msm_runtime_resume(struct device *dev) return ret; } +static int sdhci_msm_restore(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + sdhci_msm_ice_restore(host); + + return ret; +} + static const struct dev_pm_ops sdhci_msm_pm_ops = { - SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) RUNTIME_PM_OPS(sdhci_msm_runtime_suspend, sdhci_msm_runtime_resume, NULL) + .suspend = pm_sleep_ptr(pm_runtime_force_suspend), + .resume = pm_sleep_ptr(pm_runtime_force_resume), + .freeze = pm_sleep_ptr(pm_runtime_force_suspend), + .restore = pm_sleep_ptr(sdhci_msm_restore), + .thaw = pm_sleep_ptr(pm_runtime_force_resume), + .poweroff = pm_sleep_ptr(pm_runtime_force_suspend), }; static struct platform_driver sdhci_msm_driver = { From ce0043b9a302876cbef06c1d82811899df1e4699 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 27 Aug 2026 15:58:03 +0800 Subject: [PATCH 13/43] mmc: dw_mmc: add central watchdog and convert CTO onto it The driver keeps three independent fallback timers (cmd11, cto, dto) whose callbacks all re-implement the same race handling: peek MINTSTS in case the interrupt is in flight, check whether the event was delivered meanwhile, verify host->state matches the leg being guarded, and finally synthesize the missed event. This series replaces them with a single hrtimer watchdog. This first step introduces the watchdog and moves the command timeout onto it; cto_timer is deleted. Later steps convert the data timeout and the voltage-switch timer onto the same watch. The new protocol relies on two simple facts which hold for all current event producers: * every site posting EVENT_CMD_COMPLETE (dw_mci_cmd_interrupt() and the command-error branch of dw_mci_interrupt()) runs under irq_lock, * every site arming a watch does so under irq_lock as well. Consequently a watchdog callback holding irq_lock can neither miss nor race an already-delivered event: the bookkeeping part of the former 're-read MINTSTS' paranoia is subsumed by checking the awaited mask against pending_events under the same lock the producers use. The hardware-latency part of that paranoia is kept verbatim, see below. A callback that raced past every check nonetheless degrades to at most one idempotent extra state machine run instead of completing a foreign leg. The callback classifies what expired by comparing the awaited set against the named DW_MCI_WD_{CMD,DATA}_EVENTS masks so that subsequent conversions only add call sites. dw_mci_wd_arm() takes a separate 'already delivered' precheck mask because guarding the data legs must tolerate a DATA_ERROR that arrived while still waiting for the paired completion -- exactly like mod_timer() paths did before. Behavioral notes for review: * dw_mci_wd_arm() replaces any previously armed watch. During a voltage switch (CMD11) both cto_timer and cmd11_timer were armed concurrently before, racing each other with duplicated warnings; now only the last arm on that path survives. * The stale-timer defensiveness of dw_mci_clear_pending_cmd_complete() (WARN_ON + timer_delete_sync) is dropped because the callback is now idempotent by construction; timer_delete_sync from the BH would also be wrong-context sleeping on hrtimers. * Before declaring a timeout the callback re-reads MINTSTS: when the completion interrupt is already latched in hardware and only its handler has not been scheduled yet, the firing grants further DW_MCI_WD_INFLIGHT_GRACE_MS rounds instead of failing an about-to- complete transfer. This replicates the interrupt-latency paranoia of the retired cto_timer()/dto_timer() callbacks; unlike them it keeps re-watching rather than going passive, so if that latched interrupt is ultimately lost the request still unwedges with a timeout error instead of hanging forever. No functional change intended beyond the deduplication described above. Signed-off-by: Shawn Lin Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc.c | 216 +++++++++++++++++++++++++------------- drivers/mmc/host/dw_mmc.h | 11 +- 2 files changed, 150 insertions(+), 77 deletions(-) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index cf5cecc5491d..cefc873b5113 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -44,6 +44,135 @@ #define DW_MCI_FREQ_MAX 200000000 /* unit: HZ */ #define DW_MCI_FREQ_MIN 100000 /* unit: HZ */ +/* Event sets guarded by the central watchdog, see below. */ +#define DW_MCI_WD_CMD_EVENTS BIT(EVENT_CMD_COMPLETE) +#define DW_MCI_WD_DATA_EVENTS (BIT(EVENT_DATA_ERROR) | \ + BIT(EVENT_DATA_COMPLETE)) +/* Extra watch rounds granted while the completion IRQ is merely late. */ +#define DW_MCI_WD_INFLIGHT_GRACE_MS 10 + +/* + * Central watchdog + * ================ + * + * A single hrtimer guards the legs of a request (command, data, voltage + * switch) against the hardware going silent: whenever a completion event + * that was expected within a bounded time does not arrive in time, the + * callback synthesizes it so that the request state machine can error out. + * + * @wd_events holds the pending_events bits still being waited for and + * @wd_states the host->state values for which the watch is valid; both + * are protected by irq_lock. Every producer of the guarded events posts + * them under irq_lock as well, so a callback holding irq_lock can never + * miss an already-delivered event. A stale callback which raced past + * all checks therefore degrades to at most one redundant state-machine + * run instead of completing a foreign leg. Arming replaces whatever + * watch was outstanding before: command and data legs are strictly + * serial, so consecutive arms on the same path collapse onto the latest + * deadline. + * + * On expiry, before declaring a timeout, the callback double-checks that + * the completion interrupt is not already latched in hardware; if it is, + * only the interrupt latency is to blame and the watch is granted extra + * rounds instead of failing a transfer that is about to complete. + */ +static void dw_mci_wd_deliver(struct dw_mci *host, unsigned long events) +{ + host->wd_events &= ~events; + if (!host->wd_events) + hrtimer_try_to_cancel(&host->wd_timer); +} + +/* + * Arm a new watch for @events valid while host->state is one of @states, + * expiring after @ms milliseconds. @already lists bits whose delivery + * makes the watch pointless before it is even armed: arming is skipped + * when any of them has been posted concurrently. Must be called with + * irq_lock held. + */ +static void dw_mci_wd_arm(struct dw_mci *host, unsigned long ms, + unsigned long already, unsigned long events, + unsigned long states) +{ + lockdep_assert_held(&host->irq_lock); + + if (host->pending_events & already) { + /* + * The event was delivered concurrently; no need for a + * watch anymore. + */ + host->wd_events = 0; + hrtimer_try_to_cancel(&host->wd_timer); + return; + } + + host->wd_events = events; + host->wd_states = states; + hrtimer_start(&host->wd_timer, ms_to_ktime(ms), HRTIMER_MODE_REL); +} + +/* Called from hardirq context on watchdog expiry. */ +static enum hrtimer_restart dw_mci_watchdog_fn(struct hrtimer *t) +{ + struct dw_mci *host = container_of(t, struct dw_mci, wd_timer); + unsigned long irqflags; + u32 pending; + + spin_lock_irqsave(&host->irq_lock, irqflags); + + /* + * Last line of defense against a false timeout: when the completion + * interrupt is already latched in MINTSTS but its handler has not + * been scheduled yet, the leg is about to finish normally. Warn + * about the latency and keep watching instead of failing the + * transfer; delivery by the real interrupt will stop the watch. + * The retired per-timer callbacks went passive here and relied on + * the latched interrupt arriving; rearming bounds the damage if + * that assumption is ever broken. + */ + if (host->wd_events) { + pending = mci_readl(host, MINTSTS); /* read-only mask reg */ + if ((host->wd_events == DW_MCI_WD_CMD_EVENTS && + (pending & (DW_MCI_CMD_ERROR_FLAGS | + SDMMC_INT_CMD_DONE))) || + (host->wd_events == DW_MCI_WD_DATA_EVENTS && + (pending & (SDMMC_INT_DATA_OVER | + DW_MCI_DATA_ERROR_FLAGS)))) { + dev_warn(host->dev, + "Interrupt latency, state %d (watched %#lx)\n", + host->state, host->wd_events); + hrtimer_forward_now(&host->wd_timer, + ms_to_ktime(DW_MCI_WD_INFLIGHT_GRACE_MS)); + spin_unlock_irqrestore(&host->irq_lock, irqflags); + return HRTIMER_RESTART; + } + } + + if (host->wd_events == DW_MCI_WD_DATA_EVENTS) { + dev_warn(host->dev, + "Data timeout, state %d\n", host->state); + host->data_status = SDMMC_INT_DRTO; + set_bit(EVENT_DATA_ERROR, &host->pending_events); + set_bit(EVENT_DATA_COMPLETE, &host->pending_events); + } else if (host->wd_events) { + dev_warn(host->dev, + "Command timeout, state %d (watched %#lx)\n", + host->state, host->wd_events); + host->cmd_status = SDMMC_INT_RTO; + set_bit(EVENT_CMD_COMPLETE, &host->pending_events); + } + + if (host->wd_events) + queue_work(system_bh_wq, &host->bh_work); + + host->wd_events = 0; + host->wd_states = 0; + + spin_unlock_irqrestore(&host->irq_lock, irqflags); + + return HRTIMER_NORESTART; +} + #define IDMAC_INT_CLR (SDMMC_IDMAC_INT_AI | SDMMC_IDMAC_INT_NI | \ SDMMC_IDMAC_INT_CES | SDMMC_IDMAC_INT_DU | \ SDMMC_IDMAC_INT_FBE | SDMMC_IDMAC_INT_RI | \ @@ -366,18 +495,17 @@ static inline void dw_mci_set_cto(struct dw_mci *host) * extra careful about synchronization here. Specifically in hardware a * command timeout is _at most_ 5.1 ms, so that means we expect an * interrupt (either command done or timeout) to come rather quickly - * after the mci_writel. ...but just in case we have a long interrupt - * latency let's add a bit of paranoia. + * after the mci_writel. * - * In general we'll assume that at least an interrupt will be asserted - * in hardware by the time the cto_timer runs. ...and if it hasn't - * been asserted in hardware by that time then we'll assume it'll never - * come. + * dw_mci_wd_arm() skips arming when the event was already delivered, + * and the watchdog callback rechecks under the same lock before + * declaring a timeout, so both orderings of the race are covered. */ spin_lock_irqsave(&host->irq_lock, irqflags); - if (!test_bit(EVENT_CMD_COMPLETE, &host->pending_events)) - mod_timer(&host->cto_timer, - jiffies + msecs_to_jiffies(cto_ms) + 1); + dw_mci_wd_arm(host, cto_ms, DW_MCI_WD_CMD_EVENTS, + DW_MCI_WD_CMD_EVENTS, + BIT(STATE_SENDING_CMD) | BIT(STATE_SENDING_STOP) | + BIT(STATE_SENDING_CMD11)); spin_unlock_irqrestore(&host->irq_lock, irqflags); } @@ -1881,14 +2009,6 @@ static bool dw_mci_clear_pending_cmd_complete(struct dw_mci *host) if (!test_bit(EVENT_CMD_COMPLETE, &host->pending_events)) return false; - /* - * Really be certain that the timer has stopped. This is a bit of - * paranoia and could only really happen if we had really bad - * interrupt latency and the interrupt routine and timeout were - * running concurrently so that the timer_delete() in the interrupt - * handler couldn't run. - */ - WARN_ON(timer_delete_sync(&host->cto_timer)); clear_bit(EVENT_CMD_COMPLETE, &host->pending_events); return true; @@ -2633,7 +2753,7 @@ static void dw_mci_write_data_pio(struct dw_mci *host) static void dw_mci_cmd_interrupt(struct dw_mci *host, u32 status) { - timer_delete(&host->cto_timer); + dw_mci_wd_deliver(host, BIT(EVENT_CMD_COMPLETE)); if (!host->cmd_status) host->cmd_status = status; @@ -2680,7 +2800,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id) if (pending & DW_MCI_CMD_ERROR_FLAGS) { spin_lock(&host->irq_lock); - timer_delete(&host->cto_timer); + dw_mci_wd_deliver(host, BIT(EVENT_CMD_COMPLETE)); mci_writel(host, RINTSTS, DW_MCI_CMD_ERROR_FLAGS); host->cmd_status = pending; smp_wmb(); /* drain writebuffer */ @@ -3008,61 +3128,6 @@ static void dw_mci_cmd11_timer(struct timer_list *t) queue_work(system_bh_wq, &host->bh_work); } -static void dw_mci_cto_timer(struct timer_list *t) -{ - struct dw_mci *host = timer_container_of(host, t, cto_timer); - unsigned long irqflags; - u32 pending; - - spin_lock_irqsave(&host->irq_lock, irqflags); - - /* - * If somehow we have very bad interrupt latency it's remotely possible - * that the timer could fire while the interrupt is still pending or - * while the interrupt is midway through running. Let's be paranoid - * and detect those two cases. Note that this is paranoia is somewhat - * justified because in this function we don't actually cancel the - * pending command in the controller--we just assume it will never come. - */ - pending = mci_readl(host, MINTSTS); /* read-only mask reg */ - if (pending & (DW_MCI_CMD_ERROR_FLAGS | SDMMC_INT_CMD_DONE)) { - /* The interrupt should fire; no need to act but we can warn */ - dev_warn(host->dev, "Unexpected interrupt latency\n"); - goto exit; - } - if (test_bit(EVENT_CMD_COMPLETE, &host->pending_events)) { - /* Presumably interrupt handler couldn't delete the timer */ - dev_warn(host->dev, "CTO timeout when already completed\n"); - goto exit; - } - - /* - * Continued paranoia to make sure we're in the state we expect. - * This paranoia isn't really justified but it seems good to be safe. - */ - switch (host->state) { - case STATE_SENDING_CMD11: - case STATE_SENDING_CMD: - case STATE_SENDING_STOP: - /* - * If CMD_DONE interrupt does NOT come in sending command - * state, we should notify the driver to terminate current - * transfer and report a command timeout to the core. - */ - host->cmd_status = SDMMC_INT_RTO; - set_bit(EVENT_CMD_COMPLETE, &host->pending_events); - queue_work(system_bh_wq, &host->bh_work); - break; - default: - dev_warn(host->dev, "Unexpected command timeout, state %d\n", - host->state); - break; - } - -exit: - spin_unlock_irqrestore(&host->irq_lock, irqflags); -} - static void dw_mci_dto_timer(struct timer_list *t) { struct dw_mci *host = timer_container_of(host, t, dto_timer); @@ -3261,8 +3326,9 @@ int dw_mci_probe(struct dw_mci *host) } } + hrtimer_setup(&host->wd_timer, dw_mci_watchdog_fn, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); timer_setup(&host->cmd11_timer, dw_mci_cmd11_timer, 0); - timer_setup(&host->cto_timer, dw_mci_cto_timer, 0); timer_setup(&host->dto_timer, dw_mci_dto_timer, 0); spin_lock_init(&host->lock); diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index 38610c89d54a..7a14f3f2b4f0 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -122,8 +122,12 @@ struct dw_mci_dma_slave { * @irq_flags: The flags to be passed to request_irq. * @irq: The irq value to be passed to request_irq. * @sdio_irq: SDIO interrupt bit in interrupt registers. + * @wd_timer: Central watchdog guarding request legs against hardware going + * silent; fires when an expected completion event fails to arrive in + * time so that the state machine can synthesize an error. + * @wd_events: pending_events bits still awaited by the armed watch. + * @wd_states: host->state values for which the armed watch is valid. * @cmd11_timer: Timer for SD3.0 voltage switch over scheme. - * @cto_timer: Timer for broken command transfer over scheme. * @dto_timer: Timer for broken data transfer over scheme. * @mmc: The mmc_host representing this dw_mci. * @flags: Random state bits associated with the host. @@ -235,8 +239,11 @@ struct dw_mci { int sdio_irq; + struct hrtimer wd_timer; + unsigned long wd_events; + unsigned long wd_states; + struct timer_list cmd11_timer; - struct timer_list cto_timer; struct timer_list dto_timer; #ifdef CONFIG_FAULT_INJECTION From ec0db371236ecbed61e6d155623441f76a47eb1c Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 27 Aug 2026 15:58:04 +0800 Subject: [PATCH 14/43] mmc: dw_mmc: convert DTO onto the central watchdog The data timeout joins the command timeout on the central watchdog; dto_timer is deleted. dw_mci_set_drto() arms DW_MCI_WD_DATA_EVENTS with EVENT_DATA_COMPLETE as its precheck mask: a DATA_ERROR that arrived while still waiting for the paired completion must not prevent the watch -- the legacy mod_timer() guard tested exactly that one bit, and the fault-injection machinery relies on this by injecting DATA_ERROR early. The EXTENDED_TMOUT quirk semantics fall out naturally now: * On quirk hosts the data-error branch delivers the whole watched set, stopping the watch since no further data events will come -- this mirrors the former conditional timer_delete() plus the manual EVENT_DATA_COMPLETE side-post. * Without the quirk nothing is delivered there and the outstanding watch keeps guarding until a genuine DATA_OVER arrives, exactly like leaving dto_timer running did. The DATA_OVER branch delivers unconditionally, superseding its unconditional timer_delete(). The stale-timer WARN_ON + timer_delete_sync() dance in dw_mci_clear_pending_data_complete() goes away for the same reason as on the command leg: a callback racing past its checks is idempotent under irq_lock. No functional change intended. Signed-off-by: Shawn Lin Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc.c | 64 +++------------------------------------ drivers/mmc/host/dw_mmc.h | 2 -- 2 files changed, 5 insertions(+), 61 deletions(-) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index cefc873b5113..31cf72803800 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1998,9 +1998,9 @@ static void dw_mci_set_drto(struct dw_mci *host) drto_ms += 10; spin_lock_irqsave(&host->irq_lock, irqflags); - if (!test_bit(EVENT_DATA_COMPLETE, &host->pending_events)) - mod_timer(&host->dto_timer, - jiffies + msecs_to_jiffies(drto_ms)); + dw_mci_wd_arm(host, drto_ms, BIT(EVENT_DATA_COMPLETE), + DW_MCI_WD_DATA_EVENTS, + BIT(STATE_SENDING_DATA) | BIT(STATE_DATA_BUSY)); spin_unlock_irqrestore(&host->irq_lock, irqflags); } @@ -2019,8 +2019,6 @@ static bool dw_mci_clear_pending_data_complete(struct dw_mci *host) if (!test_bit(EVENT_DATA_COMPLETE, &host->pending_events)) return false; - /* Extra paranoia just like dw_mci_clear_pending_cmd_complete() */ - WARN_ON(timer_delete_sync(&host->dto_timer)); clear_bit(EVENT_DATA_COMPLETE, &host->pending_events); return true; @@ -2813,7 +2811,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id) spin_lock(&host->irq_lock); if (host->quirks & DW_MMC_QUIRK_EXTENDED_TMOUT) - timer_delete(&host->dto_timer); + dw_mci_wd_deliver(host, DW_MCI_WD_DATA_EVENTS); /* if there is an error report DATA_ERROR */ mci_writel(host, RINTSTS, DW_MCI_DATA_ERROR_FLAGS); @@ -2834,7 +2832,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id) if (pending & SDMMC_INT_DATA_OVER) { spin_lock(&host->irq_lock); - timer_delete(&host->dto_timer); + dw_mci_wd_deliver(host, DW_MCI_WD_DATA_EVENTS); mci_writel(host, RINTSTS, SDMMC_INT_DATA_OVER); if (!host->data_status) @@ -3128,57 +3126,6 @@ static void dw_mci_cmd11_timer(struct timer_list *t) queue_work(system_bh_wq, &host->bh_work); } -static void dw_mci_dto_timer(struct timer_list *t) -{ - struct dw_mci *host = timer_container_of(host, t, dto_timer); - unsigned long irqflags; - u32 pending; - - spin_lock_irqsave(&host->irq_lock, irqflags); - - /* - * The DTO timer is much longer than the CTO timer, so it's even less - * likely that we'll these cases, but it pays to be paranoid. - */ - pending = mci_readl(host, MINTSTS); /* read-only mask reg */ - if (pending & SDMMC_INT_DATA_OVER) { - /* The interrupt should fire; no need to act but we can warn */ - dev_warn(host->dev, "Unexpected data interrupt latency\n"); - goto exit; - } - if (test_bit(EVENT_DATA_COMPLETE, &host->pending_events)) { - /* Presumably interrupt handler couldn't delete the timer */ - dev_warn(host->dev, "DTO timeout when already completed\n"); - goto exit; - } - - /* - * Continued paranoia to make sure we're in the state we expect. - * This paranoia isn't really justified but it seems good to be safe. - */ - switch (host->state) { - case STATE_SENDING_DATA: - case STATE_DATA_BUSY: - /* - * If DTO interrupt does NOT come in sending data state, - * we should notify the driver to terminate current transfer - * and report a data timeout to the core. - */ - host->data_status = SDMMC_INT_DRTO; - set_bit(EVENT_DATA_ERROR, &host->pending_events); - set_bit(EVENT_DATA_COMPLETE, &host->pending_events); - queue_work(system_bh_wq, &host->bh_work); - break; - default: - dev_warn(host->dev, "Unexpected data timeout, state %d\n", - host->state); - break; - } - -exit: - spin_unlock_irqrestore(&host->irq_lock, irqflags); -} - static int dw_mci_parse_dt(struct dw_mci *host) { struct device *dev = host->dev; @@ -3329,7 +3276,6 @@ int dw_mci_probe(struct dw_mci *host) hrtimer_setup(&host->wd_timer, dw_mci_watchdog_fn, CLOCK_MONOTONIC, HRTIMER_MODE_REL); timer_setup(&host->cmd11_timer, dw_mci_cmd11_timer, 0); - timer_setup(&host->dto_timer, dw_mci_dto_timer, 0); spin_lock_init(&host->lock); spin_lock_init(&host->irq_lock); diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index 7a14f3f2b4f0..7af2b452b31d 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -128,7 +128,6 @@ struct dw_mci_dma_slave { * @wd_events: pending_events bits still awaited by the armed watch. * @wd_states: host->state values for which the armed watch is valid. * @cmd11_timer: Timer for SD3.0 voltage switch over scheme. - * @dto_timer: Timer for broken data transfer over scheme. * @mmc: The mmc_host representing this dw_mci. * @flags: Random state bits associated with the host. * @ctype: Card type for this host. @@ -244,7 +243,6 @@ struct dw_mci { unsigned long wd_states; struct timer_list cmd11_timer; - struct timer_list dto_timer; #ifdef CONFIG_FAULT_INJECTION struct fault_attr fail_data_crc; From c6306a88b74661dbf113c388f265cc5d23d1d784 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 27 Aug 2026 15:58:05 +0800 Subject: [PATCH 15/43] mmc: dw_mmc: absorb CMD11 timeout into the central watchdog The voltage switch (CMD11) keeps its dedicated 500ms deadline, but it is now just another arm of the central watchdog; cmd11_timer is deleted. The synthesized payload is identical to what the command leg watchdog produces (cmd_status = RTO plus EVENT_CMD_COMPLETE), so the request state machine cannot tell the difference. Behavior notes for review: * The extra jiffy in the legacy '500ms + 1' arming was pure jiffies rollover paranoia and disappears together with the jiffies math. * Since patch 1 arms the regular command watch on every RESP_EXP command -- including voltage switches -- the subsequent arm here replaces it, as documented there. For a genuinely stuck CMD11 the abort latency therefore becomes exactly 500ms instead of racing min(cto_ms, 500ms) between two timers as before; the reported error (-ETIMEDOUT either way) is unchanged. * dw_mci_cmd_interrupt() already delivers the watched events under irq_lock on any completion path, so the former out-of-lock timer_delete() next to the VOLT_SWITCH branch simply goes away. No functional change intended. Signed-off-by: Shawn Lin Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc.c | 30 ++++-------------------------- drivers/mmc/host/dw_mmc.h | 3 --- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 31cf72803800..6852c056f4ef 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1393,17 +1393,16 @@ static void dw_mci_start_request(struct dw_mci *host, struct mmc_command *cmd) /* * Databook says to fail after 2ms w/ no response, but evidence * shows that sometimes the cmd11 interrupt takes over 130ms. - * We'll set to 500ms, plus an extra jiffy just in case jiffies - * is just about to roll over. + * We'll set to 500ms. * * We do this whole thing under spinlock and only if the * command hasn't already completed (indicating the irq * already ran so we don't want the timeout). */ spin_lock_irqsave(&host->irq_lock, irqflags); - if (!test_bit(EVENT_CMD_COMPLETE, &host->pending_events)) - mod_timer(&host->cmd11_timer, - jiffies + msecs_to_jiffies(500) + 1); + dw_mci_wd_arm(host, 500, DW_MCI_WD_CMD_EVENTS, + DW_MCI_WD_CMD_EVENTS, + BIT(STATE_SENDING_CMD11)); spin_unlock_irqrestore(&host->irq_lock, irqflags); } @@ -2784,15 +2783,9 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id) mci_writel(host, RINTSTS, SDMMC_INT_VOLT_SWITCH); pending &= ~SDMMC_INT_VOLT_SWITCH; - /* - * Hold the lock; we know cmd11_timer can't be kicked - * off after the lock is released, so safe to delete. - */ spin_lock(&host->irq_lock); dw_mci_cmd_interrupt(host, pending); spin_unlock(&host->irq_lock); - - timer_delete(&host->cmd11_timer); } if (pending & DW_MCI_CMD_ERROR_FLAGS) { @@ -3112,20 +3105,6 @@ static void dw_mci_init_dma(struct dw_mci *host) host->use_dma = TRANS_MODE_PIO; } -static void dw_mci_cmd11_timer(struct timer_list *t) -{ - struct dw_mci *host = timer_container_of(host, t, cmd11_timer); - - if (host->state != STATE_SENDING_CMD11) { - dev_warn(host->dev, "Unexpected CMD11 timeout\n"); - return; - } - - host->cmd_status = SDMMC_INT_RTO; - set_bit(EVENT_CMD_COMPLETE, &host->pending_events); - queue_work(system_bh_wq, &host->bh_work); -} - static int dw_mci_parse_dt(struct dw_mci *host) { struct device *dev = host->dev; @@ -3275,7 +3254,6 @@ int dw_mci_probe(struct dw_mci *host) hrtimer_setup(&host->wd_timer, dw_mci_watchdog_fn, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - timer_setup(&host->cmd11_timer, dw_mci_cmd11_timer, 0); spin_lock_init(&host->lock); spin_lock_init(&host->irq_lock); diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index 7af2b452b31d..7b70392fac15 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -127,7 +127,6 @@ struct dw_mci_dma_slave { * time so that the state machine can synthesize an error. * @wd_events: pending_events bits still awaited by the armed watch. * @wd_states: host->state values for which the armed watch is valid. - * @cmd11_timer: Timer for SD3.0 voltage switch over scheme. * @mmc: The mmc_host representing this dw_mci. * @flags: Random state bits associated with the host. * @ctype: Card type for this host. @@ -242,8 +241,6 @@ struct dw_mci { unsigned long wd_events; unsigned long wd_states; - struct timer_list cmd11_timer; - #ifdef CONFIG_FAULT_INJECTION struct fault_attr fail_data_crc; struct hrtimer fault_timer; From b9bcc3a0e4b1197bfab6f3b2ddd1fb5694d59a7e Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 27 Aug 2026 15:58:06 +0800 Subject: [PATCH 16/43] mmc: dw_mmc: expose the watchdog state in debugfs Debugging hangs on the request legs now means asking 'what was the watchdog guarding and until when?' Expose the awaited events, the valid states, and the absolute deadline of the current watch next to the existing pending_events/completed_events nodes; all three zero out once a leg is settled or the watch fired. Signed-off-by: Shawn Lin Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc.c | 6 ++++++ drivers/mmc/host/dw_mmc.h | 1 + 2 files changed, 7 insertions(+) diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 6852c056f4ef..ff0b25d287a9 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -108,6 +108,7 @@ static void dw_mci_wd_arm(struct dw_mci *host, unsigned long ms, host->wd_events = events; host->wd_states = states; + host->wd_deadline_ns = ktime_get_ns() + (u64)ms * NSEC_PER_MSEC; hrtimer_start(&host->wd_timer, ms_to_ktime(ms), HRTIMER_MODE_REL); } @@ -167,6 +168,7 @@ static enum hrtimer_restart dw_mci_watchdog_fn(struct hrtimer *t) host->wd_events = 0; host->wd_states = 0; + host->wd_deadline_ns = 0; spin_unlock_irqrestore(&host->irq_lock, irqflags); @@ -300,6 +302,10 @@ static void dw_mci_init_debugfs(struct dw_mci *host) &host->pending_events); debugfs_create_xul("completed_events", 0400, root, &host->completed_events); + debugfs_create_x64("wd_deadline_ns", 0400, root, + &host->wd_deadline_ns); + debugfs_create_xul("wd_events", 0400, root, &host->wd_events); + debugfs_create_xul("wd_states", 0400, root, &host->wd_states); #ifdef CONFIG_FAULT_INJECTION fault_create_debugfs_attr("fail_data_crc", root, &host->fail_data_crc); #endif diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index 7b70392fac15..12b88e799a17 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -240,6 +240,7 @@ struct dw_mci { struct hrtimer wd_timer; unsigned long wd_events; unsigned long wd_states; + u64 wd_deadline_ns; #ifdef CONFIG_FAULT_INJECTION struct fault_attr fail_data_crc; From e2ecc51b2756efb5b6ef46e52281e5b89a632f1b Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 31 Aug 2026 17:38:16 -0700 Subject: [PATCH 17/43] mmc: sdhci-pxav3: disable clock inversion for SD HS cards The SDIO3 Configuration register branch of pxav3_set_uhs_signaling() only clears the clock-inversion and feedback-clock bits for MMC_TIMING_MMC_HS. As a result, MMC_TIMING_SD_HS (ordinary SD High Speed) falls through to the default case, which sets SDIO3_CONF_CLK_INV and leaves the feedback clock cleared. According to erratum FE-2946959, clock inversion is only needed for slow frequencies when the card hold-time requirement is high and is not required nor desirable for high-speed modes. SD High Speed runs at 50 MHz, so the same timing argument that applies to MMC High Speed holds. Treat MMC_TIMING_SD_HS the same as MMC_TIMING_MMC_HS and clear the clock-inversion and feedback-clock bits for both. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-pxav3.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index d082c4e21aa9..181b4989c74d 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -284,7 +284,8 @@ static void pxav3_set_uhs_signaling(struct sdhci_host *host, unsigned int uhs) uhs == MMC_TIMING_UHS_DDR50) { reg_val &= ~SDIO3_CONF_CLK_INV; reg_val |= SDIO3_CONF_SD_FB_CLK; - } else if (uhs == MMC_TIMING_MMC_HS) { + } else if (uhs == MMC_TIMING_MMC_HS || + uhs == MMC_TIMING_SD_HS) { reg_val &= ~SDIO3_CONF_CLK_INV; reg_val &= ~SDIO3_CONF_SD_FB_CLK; } else { From 9d6af0e6ce9d3c769421796bf19ac69dcc4cba33 Mon Sep 17 00:00:00 2001 From: Tanmay Kathpalia Date: Tue, 1 Sep 2026 11:48:10 -0700 Subject: [PATCH 18/43] dt-bindings: mmc: add Cadence SD6HC binding Cadence SD6HC is a sixth-generation SD/SDIO/eMMC host controller with a dedicated Cadence Combo PHY interface. It uses separate card-interface (CIU/SDMCLK) and bus-interface (BIU) clock domains. PHY timing depends on board IO-cell input/output delays and the DLL delay-element size. On Altera Agilex5, the SDHC, Combo PHY, and SDMMC OCP bridge are each reset independently through the HPS Reset Manager. Signed-off-by: Tanmay Kathpalia Reviewed-by: Krzysztof Kozlowski Signed-off-by: Ulf Hansson --- .../devicetree/bindings/mmc/cdns,sd6hc.yaml | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 Documentation/devicetree/bindings/mmc/cdns,sd6hc.yaml diff --git a/Documentation/devicetree/bindings/mmc/cdns,sd6hc.yaml b/Documentation/devicetree/bindings/mmc/cdns,sd6hc.yaml new file mode 100644 index 000000000000..d5ea2717904b --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/cdns,sd6hc.yaml @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mmc/cdns,sd6hc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cadence SD6HC SD/SDIO/eMMC Host Controller + +maintainers: + - Tanmay Kathpalia + +description: + Cadence SD6HC is a sixth-generation SD/SDIO/eMMC host controller with an + interface to a Cadence Combo PHY. The controller has separate card interface + and bus interface clock domains. + +properties: + compatible: + items: + - const: altr,agilex5-sd6hc + - const: cdns,sd6hc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: card interface unit (CIU / SDMCLK) clock + - description: bus interface unit (BIU) clock + + clock-names: + items: + - const: ciu + - const: biu + + resets: + items: + - description: SDHC controller reset + - description: Combo PHY reset + - description: SDMMC OCP/AXI reset + + reset-names: + items: + - const: sdhc-reset + - const: combophy + - const: sdmmc-ocp + + iommus: + maxItems: 1 + + dma-coherent: true + + cdns,iocell-input-delay-ps: + description: + Input delay across the IO cells, used for SD6HC PHY timing calculations. + minimum: 0 + maximum: 20000 + default: 2500 + + cdns,iocell-output-delay-ps: + description: + Output delay across the IO cells, used for SD6HC PHY timing calculations. + minimum: 0 + maximum: 20000 + default: 2500 + + cdns,delay-element-ps: + description: + DLL delay element size, used for SD6HC PHY timing calculations. + minimum: 1 + maximum: 1000 + default: 24 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - resets + - reset-names + +allOf: + - $ref: sdhci-common.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + + mmc@10808000 { + compatible = "altr,agilex5-sd6hc", "cdns,sd6hc"; + reg = <0x10808000 0x1000>; + interrupts = ; + clocks = <&clkmgr 0>, <&clkmgr 1>; + clock-names = "ciu", "biu"; + resets = <&rst 0>, <&rst 1>, <&rst 2>; + reset-names = "sdhc-reset", "combophy", "sdmmc-ocp"; + iommus = <&smmu 5>; + dma-coherent; + }; From 85c3831b36e25f4a6654039e0000d474950cdd99 Mon Sep 17 00:00:00 2001 From: Tanmay Kathpalia Date: Tue, 1 Sep 2026 11:48:15 -0700 Subject: [PATCH 19/43] mmc: sdhci-cadence: rename SD4HC symbols for SD6HC groundwork SD4HC PHY helpers and the default ops/drv_data are not marked as version-specific, so it is unclear what is shared versus SD4HC-only ahead of SD6HC support. Rename those symbols with a cdns4 prefix to separate the SD4HC paths from the shared driver core and avoid clashes when SD6HC is added. No functional change. Signed-off-by: Tanmay Kathpalia Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-cadence.c | 55 +++++++++++++++----------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c index 435603c8c00b..8a6ef6d9d1dd 100644 --- a/drivers/mmc/host/sdhci-cadence.c +++ b/drivers/mmc/host/sdhci-cadence.c @@ -78,7 +78,7 @@ */ #define SDHCI_CDNS_MAX_TUNING_LOOP 40 -struct sdhci_cdns_phy_param { +struct sdhci_cdns4_phy_param { u8 addr; u8 data; }; @@ -91,10 +91,10 @@ struct sdhci_cdns_priv { void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg); struct reset_control *rst_hw; unsigned int nr_phy_params; - struct sdhci_cdns_phy_param phy_params[]; + struct sdhci_cdns4_phy_param phy_params[]; }; -struct sdhci_cdns_phy_cfg { +struct sdhci_cdns4_phy_cfg { const char *property; u8 addr; }; @@ -104,7 +104,7 @@ struct sdhci_cdns_drv_data { const struct sdhci_pltfm_data pltfm_data; }; -static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = { +static const struct sdhci_cdns4_phy_cfg sdhci_cdns4_phy_cfgs[] = { { "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, }, { "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, }, { "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, }, @@ -124,8 +124,7 @@ static inline void cdns_writel(struct sdhci_cdns_priv *priv, u32 val, writel(val, reg); } -static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, - u8 addr, u8 data) +static int sdhci_cdns4_write_phy_reg(struct sdhci_cdns_priv *priv, u8 addr, u8 data) { void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04; u32 tmp; @@ -156,44 +155,42 @@ static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv, return ret; } -static unsigned int sdhci_cdns_phy_param_count(struct device_node *np) +static unsigned int sdhci_cdns4_phy_param_count(struct device_node *np) { unsigned int count = 0; int i; - for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) - if (of_property_present(np, sdhci_cdns_phy_cfgs[i].property)) + for (i = 0; i < ARRAY_SIZE(sdhci_cdns4_phy_cfgs); i++) + if (of_property_present(np, sdhci_cdns4_phy_cfgs[i].property)) count++; return count; } -static void sdhci_cdns_phy_param_parse(struct device_node *np, - struct sdhci_cdns_priv *priv) +static void sdhci_cdns4_phy_param_parse(struct device_node *np, struct sdhci_cdns_priv *priv) { - struct sdhci_cdns_phy_param *p = priv->phy_params; + struct sdhci_cdns4_phy_param *p = priv->phy_params; u32 val; int ret, i; - for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) { - ret = of_property_read_u32(np, sdhci_cdns_phy_cfgs[i].property, - &val); + for (i = 0; i < ARRAY_SIZE(sdhci_cdns4_phy_cfgs); i++) { + ret = of_property_read_u32(np, sdhci_cdns4_phy_cfgs[i].property, &val); if (ret) continue; - p->addr = sdhci_cdns_phy_cfgs[i].addr; + p->addr = sdhci_cdns4_phy_cfgs[i].addr; p->data = val; p++; } } -static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv) +static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv) { int ret, i; for (i = 0; i < priv->nr_phy_params; i++) { - ret = sdhci_cdns_write_phy_reg(priv, priv->phy_params[i].addr, - priv->phy_params[i].data); + ret = sdhci_cdns4_write_phy_reg(priv, priv->phy_params[i].addr, + priv->phy_params[i].data); if (ret) return ret; } @@ -470,7 +467,7 @@ static int elba_drv_init(struct platform_device *pdev) return 0; } -static const struct sdhci_ops sdhci_cdns_ops = { +static const struct sdhci_ops sdhci_cdns4_ops = { .set_clock = sdhci_set_clock, .get_timeout_clock = sdhci_cdns_get_timeout_clock, .set_bus_width = sdhci_set_bus_width, @@ -481,7 +478,7 @@ static const struct sdhci_ops sdhci_cdns_ops = { static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = { .pltfm_data = { - .ops = &sdhci_cdns_ops, + .ops = &sdhci_cdns4_ops, .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, }, }; @@ -495,14 +492,14 @@ static const struct sdhci_cdns_drv_data sdhci_elba_drv_data = { static const struct sdhci_cdns_drv_data sdhci_eyeq_drv_data = { .pltfm_data = { - .ops = &sdhci_cdns_ops, + .ops = &sdhci_cdns4_ops, .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, }, }; -static const struct sdhci_cdns_drv_data sdhci_cdns_drv_data = { +static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = { .pltfm_data = { - .ops = &sdhci_cdns_ops, + .ops = &sdhci_cdns4_ops, }, }; @@ -560,9 +557,9 @@ static int sdhci_cdns_probe(struct platform_device *pdev) data = of_device_get_match_data(dev); if (!data) - data = &sdhci_cdns_drv_data; + data = &sdhci_cdns4_drv_data; - nr_phy_params = sdhci_cdns_phy_param_count(dev->of_node); + nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node); host = sdhci_pltfm_init(pdev, &data->pltfm_data, struct_size(priv, phy_params, nr_phy_params)); if (IS_ERR(host)) @@ -593,9 +590,9 @@ static int sdhci_cdns_probe(struct platform_device *pdev) if (ret) return ret; - sdhci_cdns_phy_param_parse(dev->of_node, priv); + sdhci_cdns4_phy_param_parse(dev->of_node, priv); - ret = sdhci_cdns_phy_init(priv); + ret = sdhci_cdns4_phy_init(priv); if (ret) return ret; @@ -622,7 +619,7 @@ static int sdhci_cdns_resume(struct device *dev) if (ret) return ret; - ret = sdhci_cdns_phy_init(priv); + ret = sdhci_cdns4_phy_init(priv); if (ret) goto disable_clk; From 7ff784f7b12bdc968dd9880f243df9b19bb7366c Mon Sep 17 00:00:00 2001 From: Tanmay Kathpalia Date: Tue, 1 Sep 2026 11:48:16 -0700 Subject: [PATCH 20/43] mmc: sdhci-cadence: refactor driver structure for V6 controller support Refactor the sdhci-cadence driver in preparation for adding SD6HC (V6 controller) support. Separate PHY parameter handling into a dedicated sdhci_cdns4_phy structure and move PHY initialization logic into a dedicated sdhci_cdns4_phy_probe() function. This allows different controller versions to manage their PHY configurations independently while keeping shared logic in the main driver. Each compatible entry now carries its own driver data, so drop the silent fallback to sdhci_cdns4_drv_data and return an error if platform data is missing. No functional change. Signed-off-by: Tanmay Kathpalia Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-cadence.c | 54 ++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c index 8a6ef6d9d1dd..1fb23051a047 100644 --- a/drivers/mmc/host/sdhci-cadence.c +++ b/drivers/mmc/host/sdhci-cadence.c @@ -83,6 +83,11 @@ struct sdhci_cdns4_phy_param { u8 data; }; +struct sdhci_cdns4_phy { + unsigned int nr_phy_params; + struct sdhci_cdns4_phy_param phy_params[]; +}; + struct sdhci_cdns_priv { void __iomem *hrs_addr; void __iomem *ctl_addr; /* write control */ @@ -90,8 +95,7 @@ struct sdhci_cdns_priv { bool enhanced_strobe; void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg); struct reset_control *rst_hw; - unsigned int nr_phy_params; - struct sdhci_cdns4_phy_param phy_params[]; + struct sdhci_cdns4_phy *phy; }; struct sdhci_cdns4_phy_cfg { @@ -167,9 +171,9 @@ static unsigned int sdhci_cdns4_phy_param_count(struct device_node *np) return count; } -static void sdhci_cdns4_phy_param_parse(struct device_node *np, struct sdhci_cdns_priv *priv) +static void sdhci_cdns4_phy_param_parse(struct device_node *np, struct sdhci_cdns4_phy *phy) { - struct sdhci_cdns4_phy_param *p = priv->phy_params; + struct sdhci_cdns4_phy_param *p = phy->phy_params; u32 val; int ret, i; @@ -186,11 +190,12 @@ static void sdhci_cdns4_phy_param_parse(struct device_node *np, struct sdhci_cdn static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv) { + struct sdhci_cdns4_phy *phy = priv->phy; int ret, i; - for (i = 0; i < priv->nr_phy_params; i++) { - ret = sdhci_cdns4_write_phy_reg(priv, priv->phy_params[i].addr, - priv->phy_params[i].data); + for (i = 0; i < phy->nr_phy_params; i++) { + ret = sdhci_cdns4_write_phy_reg(priv, phy->phy_params[i].addr, + phy->phy_params[i].data); if (ret) return ret; } @@ -539,6 +544,24 @@ static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc) usleep_range(300, 1000); } +static int sdhci_cdns4_phy_probe(struct platform_device *pdev, struct sdhci_cdns_priv *priv) +{ + struct device *dev = &pdev->dev; + struct sdhci_cdns4_phy *phy; + unsigned int nr_phy_params; + + nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node); + phy = devm_kzalloc(dev, struct_size(phy, phy_params, nr_phy_params), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->nr_phy_params = nr_phy_params; + sdhci_cdns4_phy_param_parse(dev->of_node, phy); + priv->phy = phy; + + return sdhci_cdns4_phy_init(priv); +} + static int sdhci_cdns_probe(struct platform_device *pdev) { struct sdhci_host *host; @@ -546,7 +569,6 @@ static int sdhci_cdns_probe(struct platform_device *pdev) struct sdhci_pltfm_host *pltfm_host; struct sdhci_cdns_priv *priv; struct clk *clk; - unsigned int nr_phy_params; int ret; struct device *dev = &pdev->dev; static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT; @@ -557,11 +579,9 @@ static int sdhci_cdns_probe(struct platform_device *pdev) data = of_device_get_match_data(dev); if (!data) - data = &sdhci_cdns4_drv_data; + return dev_err_probe(dev, -EINVAL, "missing platform driver data\n"); - nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node); - host = sdhci_pltfm_init(pdev, &data->pltfm_data, - struct_size(priv, phy_params, nr_phy_params)); + host = sdhci_pltfm_init(pdev, &data->pltfm_data, sizeof(*priv)); if (IS_ERR(host)) return PTR_ERR(host); @@ -569,7 +589,6 @@ static int sdhci_cdns_probe(struct platform_device *pdev) pltfm_host->clk = clk; priv = sdhci_pltfm_priv(pltfm_host); - priv->nr_phy_params = nr_phy_params; priv->hrs_addr = host->ioaddr; priv->enhanced_strobe = false; priv->priv_writel = cdns_writel; @@ -590,9 +609,7 @@ static int sdhci_cdns_probe(struct platform_device *pdev) if (ret) return ret; - sdhci_cdns4_phy_param_parse(dev->of_node, priv); - - ret = sdhci_cdns4_phy_init(priv); + ret = sdhci_cdns4_phy_probe(pdev, priv); if (ret) return ret; @@ -650,7 +667,10 @@ static const struct of_device_id sdhci_cdns_match[] = { .compatible = "mobileye,eyeq-sd4hc", .data = &sdhci_eyeq_drv_data, }, - { .compatible = "cdns,sd4hc" }, + { + .compatible = "cdns,sd4hc", + .data = &sdhci_cdns4_drv_data, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sdhci_cdns_match); From 882fb340a9568d2c2a976cb2982921a17303569e Mon Sep 17 00:00:00 2001 From: Tanmay Kathpalia Date: Tue, 1 Sep 2026 11:48:17 -0700 Subject: [PATCH 21/43] mmc: sdhci-cadence: add Cadence SD6HC support The Cadence SD6HC is a sixth-generation SD/SDIO/eMMC host controller with an integrated combo-PHY. PHY timing depends on the active speed mode, the SD clock period, and board-level IO-cell and DLL delay- element characteristics. SD6HC provides separate card-interface (CIU) and bus-interface (BIU) clocks, and asserts eMMC hardware reset through an internal controller register rather than an external RST_n line. The "cdns,sd6hc" compatible string identifies this IP in device tree. Split the existing driver into sdhci-cadence-core.c and sdhci-cadence-phy-v6.c, and add sdhci-cadence.h for shared private state. Signed-off-by: Tanmay Kathpalia Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- MAINTAINERS | 7 + drivers/mmc/host/Makefile | 1 + .../{sdhci-cadence.c => sdhci-cadence-core.c} | 135 ++- drivers/mmc/host/sdhci-cadence-phy-v6.c | 975 ++++++++++++++++++ drivers/mmc/host/sdhci-cadence.h | 118 +++ 5 files changed, 1193 insertions(+), 43 deletions(-) rename drivers/mmc/host/{sdhci-cadence.c => sdhci-cadence-core.c} (85%) create mode 100644 drivers/mmc/host/sdhci-cadence-phy-v6.c create mode 100644 drivers/mmc/host/sdhci-cadence.h diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..978fe6999d3b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24689,6 +24689,13 @@ L: linux-mmc@vger.kernel.org S: Maintained F: drivers/mmc/host/sdhci-brcmstb* +SECURE DIGITAL HOST CONTROLLER INTERFACE (SDHCI) CADENCE DRIVER +M: Tanmay Kathpalia +L: linux-mmc@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/mmc/cdns,sd6hc.yaml +F: drivers/mmc/host/sdhci-cadence* + SECURE DIGITAL HOST CONTROLLER INTERFACE (SDHCI) DRIVER M: Adrian Hunter L: linux-mmc@vger.kernel.org diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index ee412e6b84d6..799ce7ad43d5 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -80,6 +80,7 @@ obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o obj-$(CONFIG_MMC_SDHCI_CADENCE) += sdhci-cadence.o +sdhci-cadence-y += sdhci-cadence-core.o sdhci-cadence-phy-v6.o obj-$(CONFIG_MMC_SDHCI_ESDHC_MCF) += sdhci-esdhc-mcf.o obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o obj-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence-core.c similarity index 85% rename from drivers/mmc/host/sdhci-cadence.c rename to drivers/mmc/host/sdhci-cadence-core.c index 1fb23051a047..ea2517e8f72a 100644 --- a/drivers/mmc/host/sdhci-cadence.c +++ b/drivers/mmc/host/sdhci-cadence-core.c @@ -2,6 +2,7 @@ /* * Copyright (C) 2016 Socionext Inc. * Author: Masahiro Yamada + * Copyright (C) 2026 Altera Corporation */ #include @@ -14,10 +15,10 @@ #include #include -#include "sdhci-pltfm.h" +#include "sdhci-cadence.h" /* HRS - Host Register Set (specific to Cadence) */ -#define SDHCI_CDNS_HRS04 0x10 /* PHY access port */ +/* HRS04 (PHY access) bitfields (SD4HC) */ #define SDHCI_CDNS_HRS04_ACK BIT(26) #define SDHCI_CDNS_HRS04_RD BIT(25) #define SDHCI_CDNS_HRS04_WR BIT(24) @@ -71,13 +72,6 @@ #define SDHCI_CDNS_PHY_DLY_HSMMC 0x0c #define SDHCI_CDNS_PHY_DLY_STROBE 0x0d -/* - * The tuned val register is 6 bit-wide, but not the whole of the range is - * available. The range 0-42 seems to be available (then 43 wraps around to 0) - * but I am not quite sure if it is official. Use only 0 to 39 for safety. - */ -#define SDHCI_CDNS_MAX_TUNING_LOOP 40 - struct sdhci_cdns4_phy_param { u8 addr; u8 data; @@ -88,16 +82,6 @@ struct sdhci_cdns4_phy { struct sdhci_cdns4_phy_param phy_params[]; }; -struct sdhci_cdns_priv { - void __iomem *hrs_addr; - void __iomem *ctl_addr; /* write control */ - spinlock_t wrlock; /* write lock */ - bool enhanced_strobe; - void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg); - struct reset_control *rst_hw; - struct sdhci_cdns4_phy *phy; -}; - struct sdhci_cdns4_phy_cfg { const char *property; u8 addr; @@ -203,13 +187,6 @@ static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv) return 0; } -static void *sdhci_cdns_priv(struct sdhci_host *host) -{ - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - - return sdhci_pltfm_priv(pltfm_host); -} - static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host) { /* @@ -245,6 +222,9 @@ static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val) u32 tmp; int i, ret; + if (host->version >= SDHCI_SPEC_420) + return sdhci_cdns6_set_tune_val(host, val); + if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val))) return -EINVAL; @@ -325,8 +305,10 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode) * The delay is set by probe, based on the DT properties. */ if (host->timing != MMC_TIMING_MMC_HS200 && - host->timing != MMC_TIMING_UHS_SDR104) + host->timing != MMC_TIMING_UHS_SDR104) { + dev_dbg(mmc_dev(host->mmc), "Tuning skipped (timing: %d)\n", host->timing); return 0; + } for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) { if (sdhci_cdns_set_tune_val(host, i) || @@ -350,6 +332,10 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode) if (ret) return ret; + /* Block gap tuning is only required for SD4HC, not for SD6HC */ + if (host->version >= SDHCI_SPEC_420) + return 0; + return sdhci_cdns_tune_blkgap(host->mmc); } @@ -385,6 +371,10 @@ static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host, /* For SD, fall back to the default handler */ if (mode == SDHCI_CDNS_HRS06_MODE_SD) sdhci_set_uhs_signaling(host, timing); + + /* For host controller V6, set SDHCI and PHY registers for UHS signaling */ + if (host->version >= SDHCI_SPEC_420) + sdhci_cdns6_set_uhs_signaling(host, timing); } /* Elba control register bits [6:3] are byte-lane enables */ @@ -481,6 +471,16 @@ static const struct sdhci_ops sdhci_cdns4_ops = { .set_uhs_signaling = sdhci_cdns_set_uhs_signaling, }; +static const struct sdhci_ops sdhci_cdns6_ops = { + .set_clock = sdhci_cdns6_set_clock, + .get_timeout_clock = sdhci_cdns_get_timeout_clock, + .set_bus_width = sdhci_set_bus_width, + .reset = sdhci_reset, + .platform_execute_tuning = sdhci_cdns_execute_tuning, + .set_uhs_signaling = sdhci_cdns_set_uhs_signaling, + .hw_reset = sdhci_cdns6_hw_reset, +}; + static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = { .pltfm_data = { .ops = &sdhci_cdns4_ops, @@ -508,6 +508,12 @@ static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = { }, }; +static const struct sdhci_cdns_drv_data sdhci_cdns6_drv_data = { + .pltfm_data = { + .ops = &sdhci_cdns6_ops, + }, +}; + static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc, struct mmc_ios *ios) { @@ -526,6 +532,14 @@ static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc, if (mode == SDHCI_CDNS_HRS06_MODE_MMC_HS400ES && !ios->enhanced_strobe) sdhci_cdns_set_emmc_mode(priv, SDHCI_CDNS_HRS06_MODE_MMC_HS400); + + /* + * Reprogram the SD6HC PHY for the current timing. Enhanced strobe + * affects PHY delays, and this callback may run with either HS400 + * (enabling/disabling ES) or Legacy (initial bus state). + */ + if (host->version >= SDHCI_SPEC_420) + sdhci_cdns6_set_uhs_signaling(host, ios->timing); } static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc) @@ -569,13 +583,24 @@ static int sdhci_cdns_probe(struct platform_device *pdev) struct sdhci_pltfm_host *pltfm_host; struct sdhci_cdns_priv *priv; struct clk *clk; + struct clk *biu_clk; int ret; struct device *dev = &pdev->dev; static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT; - clk = devm_clk_get_enabled(dev, NULL); - if (IS_ERR(clk)) - return PTR_ERR(clk); + if (of_device_is_compatible(dev->of_node, "cdns,sd6hc")) { + clk = devm_clk_get_enabled(dev, "ciu"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "failed to enable ciu clock\n"); + + biu_clk = devm_clk_get_enabled(dev, "biu"); + if (IS_ERR(biu_clk)) + return dev_err_probe(dev, PTR_ERR(biu_clk), "failed to enable biu clock\n"); + } else { + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + } data = of_device_get_match_data(dev); if (!data) @@ -601,30 +626,46 @@ static int sdhci_cdns_probe(struct platform_device *pdev) return ret; } sdhci_enable_v4_mode(host); - __sdhci_read_caps(host, &version, NULL, NULL); - sdhci_get_of_property(pdev); ret = mmc_of_parse(host->mmc); if (ret) return ret; - ret = sdhci_cdns4_phy_probe(pdev, priv); - if (ret) - return ret; + /* + * For SD4HC, read capabilities with fixed version override and set up + * the optional eMMC card RST_n reset control. + * For SD6HC, sdhci_add_host() will automatically read capabilities + * and version from the host controller registers. + */ + if (of_device_is_compatible(dev->of_node, "cdns,sd4hc")) { + __sdhci_read_caps(host, &version, NULL, NULL); + ret = sdhci_cdns4_phy_probe(pdev, priv); + if (ret) + return ret; - if (host->mmc->caps & MMC_CAP_HW_RESET) { - priv->rst_hw = devm_reset_control_get_optional_exclusive(dev, NULL); - if (IS_ERR(priv->rst_hw)) - return dev_err_probe(mmc_dev(host->mmc), PTR_ERR(priv->rst_hw), - "reset controller error\n"); - if (priv->rst_hw) - host->mmc_host_ops.card_hw_reset = sdhci_cdns_mmc_hw_reset; + if (host->mmc->caps & MMC_CAP_HW_RESET) { + priv->rst_hw = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(priv->rst_hw)) + return dev_err_probe(mmc_dev(host->mmc), PTR_ERR(priv->rst_hw), + "reset controller error\n"); + if (priv->rst_hw) + host->mmc_host_ops.card_hw_reset = sdhci_cdns_mmc_hw_reset; + } + } else { + ret = sdhci_cdns6_phy_probe(pdev, priv); + if (ret) + return ret; } return sdhci_add_host(host); } +/* + * Only the CIU clock is gated on suspend. The SD6HC "biu" clock is not + * toggled here as it may be a shared bus clock; a dedicated biu clock + * would need explicit PM gating added here. + */ static int sdhci_cdns_resume(struct device *dev) { struct sdhci_host *host = dev_get_drvdata(dev); @@ -636,7 +677,11 @@ static int sdhci_cdns_resume(struct device *dev) if (ret) return ret; - ret = sdhci_cdns4_phy_init(priv); + if (host->version >= SDHCI_SPEC_420) + ret = sdhci_cdns6_phy_init(priv); + else + ret = sdhci_cdns4_phy_init(priv); + if (ret) goto disable_clk; @@ -671,6 +716,10 @@ static const struct of_device_id sdhci_cdns_match[] = { .compatible = "cdns,sd4hc", .data = &sdhci_cdns4_drv_data, }, + { + .compatible = "cdns,sd6hc", + .data = &sdhci_cdns6_drv_data, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sdhci_cdns_match); diff --git a/drivers/mmc/host/sdhci-cadence-phy-v6.c b/drivers/mmc/host/sdhci-cadence-phy-v6.c new file mode 100644 index 000000000000..22d56bb46d75 --- /dev/null +++ b/drivers/mmc/host/sdhci-cadence-phy-v6.c @@ -0,0 +1,975 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PHY and host controller support for Cadence SD6HC SDHCI + * + * This file provides support for Cadence's sixth-generation SDHCI controller (SD6HC). + * Implements PHY initialization, DLL management, per-speed-mode timing calculations, + * and host controller register programming for the SD6HC integrated combo-PHY. + * + * Copyright (C) 2026 Altera Corporation + * Author: Tanmay Kathpalia + */ + +#include +#include +#include +#include + +#include "sdhci-cadence.h" + +/* IO Delay Information */ +#define SDHCI_CDNS_HRS07 0x1c +#define SDHCI_CDNS_HRS07_RW_COMPENSATE GENMASK(20, 16) +#define SDHCI_CDNS_HRS07_IDELAY_VAL GENMASK(4, 0) + +/* PHY Control and Status */ +#define SDHCI_CDNS_HRS09 0x24 +#define SDHCI_CDNS_HRS09_RDDATA_EN BIT(16) +#define SDHCI_CDNS_HRS09_RDCMD_EN BIT(15) +#define SDHCI_CDNS_HRS09_EXTENDED_WR_MODE BIT(3) +#define SDHCI_CDNS_HRS09_EXTENDED_RD_MODE BIT(2) +#define SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE BIT(1) +#define SDHCI_CDNS_HRS09_PHY_SW_RESET BIT(0) + +/* SDCLK start point adjustment */ +#define SDHCI_CDNS_HRS10 0x28 +#define SDHCI_CDNS_HRS10_HCSDCLKADJ GENMASK(19, 16) + +/* eMMC Control */ +#define SDHCI_CDNS_HRS11 0x2c +#define SDHCI_CDNS_HRS11_EMMC_RST BIT(0) /* eMMC reset */ + +/* CMD/DAT output delay */ +#define SDHCI_CDNS_HRS16 0x40 +#define SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY GENMASK(31, 28) +#define SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY GENMASK(27, 24) +#define SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY GENMASK(23, 20) +#define SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY GENMASK(19, 16) +#define SDHCI_CDNS_HRS16_WRDATA1_DLY GENMASK(15, 12) +#define SDHCI_CDNS_HRS16_WRDATA0_DLY GENMASK(11, 8) +#define SDHCI_CDNS_HRS16_WRCMD1_DLY GENMASK(7, 4) +#define SDHCI_CDNS_HRS16_WRCMD0_DLY GENMASK(3, 0) + +/* PHY Special Function Registers */ +/* DQ timing */ +#define SDHCI_CDNS6_PHY_DQ_TIMING_REG 0x2000 +#define SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON BIT(31) +#define SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END GENMASK(29, 27) +#define SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START GENMASK(26, 24) +#define SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END GENMASK(2, 0) + +/* DQS timing */ +#define SDHCI_CDNS6_PHY_DQS_TIMING_REG 0x2004 +#define SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS BIT(22) +#define SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS BIT(21) +#define SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS BIT(20) +#define SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD BIT(19) + +/* Gate and loopback control */ +#define SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG 0x2008 +#define SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD BIT(31) +#define SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL GENMASK(24, 19) +#define SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS BIT(18) +#define SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON BIT(6) + +/* Master DLL logic */ +#define SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_REG 0x200c +#define SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_BYPASS_MODE BIT(23) +#define SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_PHASE_DETECT_SEL GENMASK(22, 20) +#define SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_LOCK_NUM GENMASK(18, 16) +#define SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_START_POINT GENMASK(7, 0) + +/* Slave DLL logic */ +#define SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG 0x2010 +#define SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY GENMASK(31, 24) +#define SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WRDQS_DELAY GENMASK(23, 16) +#define SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WR_DELAY GENMASK(15, 8) +#define SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY GENMASK(7, 0) + +/* Global control settings */ +#define SDHCI_CDNS6_PHY_CTRL_REG 0x2080 +#define SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING GENMASK(9, 4) + +/* Default PHY settings */ +#define SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY 2500 +#define SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT 24 +#define SDHCI_CDNS6_PHY_DEFAULT_RD_DEL_SEL 52 +#define SDHCI_CDNS6_PHY_DEFAULT_DLL_START 4 +#define SDHCI_CDNS6_PHY_DEFAULT_PHASE_DETECT_SEL 2 +#define SDHCI_CDNS6_PHY_DEFAULT_DLL_LOCK_NUM 0 +#define SDHCI_CDNS6_PHY_DEFAULT_DATA_SELECT_OE_END 1 + +/* Scale tuning tap (0..39) to 8-bit PHY DLL delay field (0..255) */ +#define SDHCI_CDNS6_PHY_DLL_FIELD_SIZE 256 + +struct sdhci_cdns6_phy { + /* + * Mode-specific timing constraints (in picoseconds) + * These define valid output windows per SD/eMMC spec + */ + u32 t_cmd_output_min; + u32 t_cmd_output_max; + u32 t_dat_output_min; + u32 t_dat_output_max; + + /* + * PHY delay configuration (in picoseconds) + * Derived from clock period and board-level IO cell delays + */ + u32 phy_sdclk_delay; + u32 phy_cmd_o_delay; + u32 phy_dat_o_delay; + u32 iocell_input_delay; + u32 iocell_output_delay; + /* Configured delay element (ps); preserved across clock changes */ + u32 delay_element_org; + /* Active delay element (ps); doubled when one SDMCLK requires > 256 steps */ + u32 delay_element; + + /* PHY_DLL_SLAVE_CTRL register fields */ + u8 cp_read_dqs_cmd_delay; /* bits [31:24] */ + u8 cp_clk_wrdqs_delay; /* bits [23:16] */ + u8 cp_clk_wr_delay; /* bits [15:8] */ + u8 cp_read_dqs_delay; /* bits [7:0] */ + + /* PHY_DLL_MASTER_CTRL register fields */ + bool cp_dll_bypass_mode; /* bit [23] */ + + /* PHY_DQ_TIMING register fields */ + u8 cp_io_mask_end; /* bits [29:27] */ + u8 cp_io_mask_start; /* bits [26:24] */ + + /* PHY_DQS_TIMING register fields */ + bool cp_use_phony_dqs; /* bit [20] */ + bool cp_use_phony_dqs_cmd; /* bit [19] */ + + /* HRS07 register - IO delay Information */ + u8 sdhc_rw_compensate; /* bits [20:16] */ + u8 sdhc_idelay_val; /* bits [4:0] */ + + /* HRS09 register - PHY control and Status */ + bool sdhc_extended_wr_mode; /* bit [3] */ + bool sdhc_extended_rd_mode; /* bit [2] */ + + /* HRS10 register - SDCLK start point adjustment */ + u8 sdhc_hcsdclkadj; /* bits [19:16] */ + + /* HRS16 register fields - CMD/DAT output delay control */ + u8 sdhc_wrdata1_sdclk_dly; /* bits [31:28] */ + u8 sdhc_wrdata0_sdclk_dly; /* bits [27:24] */ + u8 sdhc_wrcmd1_sdclk_dly; /* bits [23:20] */ + u8 sdhc_wrcmd0_sdclk_dly; /* bits [19:16] */ + u8 sdhc_wrdata1_dly; /* bits [15:12] */ + u8 sdhc_wrdata0_dly; /* bits [11:8] */ + u8 sdhc_wrcmd1_dly; /* bits [7:4] */ + u8 sdhc_wrcmd0_dly; /* bits [3:0] */ + + /* DLL calculation intermediate values, used during PHY timing calculations */ + u32 t_sdmclk_calc; /* DLL-quantized SDMCLK period */ + u32 dll_max_value; /* DLL delay field ceiling */ + + /* Tuning value for HS200/HS400 modes */ + u8 hs200_tune_val; + + /* Clock periods (in picoseconds) */ + u32 t_sdmclk; /* Master clock period */ + u32 t_sdclk; /* SD card clock period */ + + /* Current operating state */ + bool strobe_cmd; /* Enhanced strobe for CMD line */ + unsigned int mode; /* Current MMC_TIMING_* mode */ +}; + +/** + * init_ds() - Initialize PHY timing for Default Speed mode (25 MHz). + * @phy: Pointer to SD6HC PHY state. + * @t_sdclk: SD clock period in picoseconds. + */ +static void init_ds(struct sdhci_cdns6_phy *phy, u32 t_sdclk) +{ + phy->t_cmd_output_min = 5000; + phy->t_cmd_output_max = t_sdclk - 5000; + phy->t_dat_output_min = 5000; + phy->t_dat_output_max = t_sdclk - 5000; +} + +/** + * init_hs() - Initialize PHY timing for High Speed mode (50 MHz). + * @phy: Pointer to SD6HC PHY state. + * @t_sdclk: SD clock period in picoseconds. + */ +static void init_hs(struct sdhci_cdns6_phy *phy, u32 t_sdclk) +{ + phy->t_cmd_output_min = 2000; + phy->t_cmd_output_max = t_sdclk - 6000; + phy->t_dat_output_min = 2000; + phy->t_dat_output_max = t_sdclk - 6000; +} + +/** + * init_uhs_sdr12() - Initialize PHY timing for UHS SDR12 mode (25 MHz). + * @phy: Pointer to SD6HC PHY state. + * @t_sdclk: SD clock period in picoseconds. + */ +static void init_uhs_sdr12(struct sdhci_cdns6_phy *phy, u32 t_sdclk) +{ + phy->t_cmd_output_min = 800; + phy->t_cmd_output_max = t_sdclk - 3000; + phy->t_dat_output_min = 800; + phy->t_dat_output_max = t_sdclk - 3000; +} + +/** + * init_uhs_sdr25() - Initialize PHY timing for UHS SDR25 mode (50 MHz). + * @phy: Pointer to SD6HC PHY state. + * @t_sdclk: SD clock period in picoseconds. + */ +static void init_uhs_sdr25(struct sdhci_cdns6_phy *phy, u32 t_sdclk) +{ + phy->t_cmd_output_min = 800; + phy->t_cmd_output_max = t_sdclk - 3000; + phy->t_dat_output_min = 800; + phy->t_dat_output_max = t_sdclk - 3000; +} + +/** + * init_uhs_sdr50() - Initialize PHY timing for UHS SDR50 mode (100 MHz). + * @phy: Pointer to SD6HC PHY state. + * @t_sdclk: SD clock period in picoseconds. + */ +static void init_uhs_sdr50(struct sdhci_cdns6_phy *phy, u32 t_sdclk) +{ + phy->t_cmd_output_min = 800; + phy->t_cmd_output_max = t_sdclk - 3000; + phy->t_dat_output_min = 800; + phy->t_dat_output_max = t_sdclk - 3000; +} + +/** + * init_uhs_sdr104() - Initialize PHY timing for UHS SDR104 mode (200 MHz). + * @phy: Pointer to SD6HC PHY state. + * @t_sdclk: SD clock period in picoseconds. + */ +static void init_uhs_sdr104(struct sdhci_cdns6_phy *phy, u32 t_sdclk) +{ + phy->t_cmd_output_min = 800; + phy->t_cmd_output_max = t_sdclk - 1400; + phy->t_dat_output_min = 800; + phy->t_dat_output_max = t_sdclk - 1400; +} + +/** + * init_uhs_ddr50() - Initialize PHY timing for UHS DDR50 mode (50 MHz). + * @phy: Pointer to SD6HC PHY state. + * @t_sdclk: SD clock period in picoseconds. + */ +static void init_uhs_ddr50(struct sdhci_cdns6_phy *phy, u32 t_sdclk) +{ + phy->t_cmd_output_min = 800; + phy->t_cmd_output_max = t_sdclk - 3000; + phy->t_dat_output_min = 800; + phy->t_dat_output_max = t_sdclk - 3000; +} + +/** + * init_emmc_sdr() - Initialize PHY timing for eMMC legacy/SDR mode. + * @phy: Pointer to SD6HC PHY state. + * @t_sdclk: SD clock period in picoseconds. + */ +static void init_emmc_sdr(struct sdhci_cdns6_phy *phy, u32 t_sdclk) +{ + phy->t_cmd_output_min = 3000; + phy->t_cmd_output_max = t_sdclk - 3000; + phy->t_dat_output_min = 3000; + phy->t_dat_output_max = t_sdclk - 3000; +} + +/** + * init_emmc_ddr() - Initialize PHY timing for eMMC DDR52 mode. + * @phy: Pointer to SD6HC PHY state. + * @t_sdclk: SD clock period in picoseconds. + */ +static void init_emmc_ddr(struct sdhci_cdns6_phy *phy, u32 t_sdclk) +{ + phy->t_cmd_output_min = 3000; + phy->t_cmd_output_max = t_sdclk - 3000; + phy->t_dat_output_min = 2500; + phy->t_dat_output_max = t_sdclk - 2500; +} + +/** + * init_emmc_hs200() - Initialize PHY timing for eMMC HS200 mode (200 MHz). + * @phy: Pointer to SD6HC PHY state. + * @t_sdclk: SD clock period in picoseconds. + */ +static void init_emmc_hs200(struct sdhci_cdns6_phy *phy, u32 t_sdclk) +{ + phy->t_cmd_output_min = 800; + phy->t_cmd_output_max = t_sdclk - 1400; + phy->t_dat_output_min = 800; + phy->t_dat_output_max = t_sdclk - 1400; +} + +/** + * init_emmc_hs400() - Initialize PHY timing for eMMC HS400/HS400ES mode. + * @phy: Pointer to SD6HC PHY state. + * @t_sdclk: SD clock period in picoseconds. + */ +static void init_emmc_hs400(struct sdhci_cdns6_phy *phy, u32 t_sdclk) +{ + phy->t_cmd_output_min = 800; + phy->t_cmd_output_max = t_sdclk - 1400; + phy->t_dat_output_min = 400; + phy->t_dat_output_max = t_sdclk - 400; +} + +/* + * init_timings - PHY timing initializers indexed by MMC_TIMING_* value. + * + * Each entry corresponds to a MMC_TIMING_* constant and sets the appropriate cmd/dat + * output timing windows in the PHY state struct. + */ +static void (* const init_timings[])(struct sdhci_cdns6_phy *, u32) = { + [MMC_TIMING_LEGACY] = init_ds, + [MMC_TIMING_MMC_HS] = init_emmc_sdr, + [MMC_TIMING_SD_HS] = init_hs, + [MMC_TIMING_UHS_SDR12] = init_uhs_sdr12, + [MMC_TIMING_UHS_SDR25] = init_uhs_sdr25, + [MMC_TIMING_UHS_SDR50] = init_uhs_sdr50, + [MMC_TIMING_UHS_SDR104] = init_uhs_sdr104, + [MMC_TIMING_UHS_DDR50] = init_uhs_ddr50, + [MMC_TIMING_MMC_DDR52] = init_emmc_ddr, + [MMC_TIMING_MMC_HS200] = init_emmc_hs200, + [MMC_TIMING_MMC_HS400] = init_emmc_hs400, +}; + +static unsigned int sdhci_cdns6_read_phy_reg(struct sdhci_cdns_priv *priv, const u32 address) +{ + writel(address, priv->hrs_addr + SDHCI_CDNS_HRS04); + return readl(priv->hrs_addr + SDHCI_CDNS_HRS05); +} + +static void sdhci_cdns6_write_phy_reg(struct sdhci_cdns_priv *priv, const u32 address, + const u32 value) +{ + writel(address, priv->hrs_addr + SDHCI_CDNS_HRS04); + writel(value, priv->hrs_addr + SDHCI_CDNS_HRS05); +} + +static int sdhci_cdns6_phy_lock_dll(struct sdhci_cdns6_phy *phy) +{ + u32 delay_element = phy->delay_element_org; + u32 delay_elements_in_sdmclk; + + delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk, delay_element); + if (delay_elements_in_sdmclk > 256) { + delay_element *= 2; + delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk, delay_element); + + if (delay_elements_in_sdmclk > 256) + return -EINVAL; + + phy->dll_max_value = 127; + } else { + phy->dll_max_value = 255; + } + + phy->t_sdmclk_calc = delay_element * delay_elements_in_sdmclk; + phy->delay_element = delay_element; + phy->cp_dll_bypass_mode = false; + + return 0; +} + +static void sdhci_cdns6_phy_dll_bypass(struct sdhci_cdns6_phy *phy) +{ + phy->dll_max_value = 255; + phy->cp_dll_bypass_mode = true; +} + +static void sdhci_cdns6_phy_configure_dll(struct sdhci_cdns6_phy *phy) +{ + if (!phy->sdhc_extended_wr_mode) { + if (sdhci_cdns6_phy_lock_dll(phy) == 0) + return; + } + sdhci_cdns6_phy_dll_bypass(phy); +} + +static void sdhci_cdns6_phy_calc_out(struct sdhci_cdns6_phy *phy, bool cmd_not_dat) +{ + u32 wr0_dly = 0, wr1_dly = 0, output_min, output_max, phy_o_delay, + clk_wr_delay = 0, wr0_sdclk_dly = 0, wr1_sdclk_dly = 0; + bool ddr = (phy->mode == MMC_TIMING_UHS_DDR50) || (phy->mode == MMC_TIMING_MMC_DDR52) || + (phy->mode == MMC_TIMING_MMC_HS400); + bool data_ddr = ddr && !cmd_not_dat; + int t; + + if (cmd_not_dat) { + output_min = phy->t_cmd_output_min; + output_max = phy->t_cmd_output_max; + phy_o_delay = phy->phy_cmd_o_delay; + } else { + output_min = phy->t_dat_output_min; + output_max = phy->t_dat_output_max; + phy_o_delay = phy->phy_dat_o_delay; + } + + if (data_ddr) { + wr0_sdclk_dly = 1; + wr1_sdclk_dly = 1; + } + + t = phy_o_delay - phy->phy_sdclk_delay - output_min; + if (t < 0 && phy->sdhc_extended_wr_mode) { + u32 n_half_cycle = DIV_ROUND_UP(-t * 2, phy->t_sdmclk); + + wr0_dly = (n_half_cycle + 1) / 2; + if (data_ddr) + wr1_dly = (n_half_cycle + 1) / 2; + else + wr1_dly = (n_half_cycle + 1) % 2 + wr0_dly - 1; + } + + if (!phy->sdhc_extended_wr_mode) { + u32 out_hold, out_setup, out_hold_margin; + u32 n; + + if (!data_ddr) + wr0_dly = 1; + + out_setup = output_max; + out_hold = output_min; + out_hold_margin = DIV_ROUND_UP(out_setup - out_hold, 4); + out_hold += out_hold_margin; + + if (!phy->cp_dll_bypass_mode) + n = DIV_ROUND_UP(256 * out_hold, phy->t_sdmclk_calc); + else + n = DIV_ROUND_UP(out_hold, phy->delay_element) - 1; + + if (n <= phy->dll_max_value) + clk_wr_delay = n; + else + clk_wr_delay = 255; + } else { + /* sdhc_extended_wr_mode set => PHY IO cell work in SDR mode */ + clk_wr_delay = 0; + } + + if (cmd_not_dat) { + phy->sdhc_wrcmd0_dly = wr0_dly; + phy->sdhc_wrcmd1_dly = wr1_dly; + phy->cp_clk_wrdqs_delay = clk_wr_delay; + phy->sdhc_wrcmd0_sdclk_dly = wr0_sdclk_dly; + phy->sdhc_wrcmd1_sdclk_dly = wr1_sdclk_dly; + } else { + phy->sdhc_wrdata0_dly = wr0_dly; + phy->sdhc_wrdata1_dly = wr1_dly; + phy->cp_clk_wr_delay = clk_wr_delay; + phy->sdhc_wrdata0_sdclk_dly = wr0_sdclk_dly; + phy->sdhc_wrdata1_sdclk_dly = wr1_sdclk_dly; + } +} + +static void sdhci_cdns6_phy_calc_cmd_out(struct sdhci_cdns6_phy *phy) +{ + sdhci_cdns6_phy_calc_out(phy, true); +} + +static void sdhci_cdns6_phy_calc_cmd_in(struct sdhci_cdns6_phy *phy) +{ + phy->cp_io_mask_end = ((phy->iocell_output_delay + phy->iocell_input_delay) * 2) / + phy->t_sdmclk; + + /* cp_io_mask_end is a 3-bit field, clamp to max value of 7 */ + phy->cp_io_mask_end = min_t(u8, phy->cp_io_mask_end, 7); + + if (phy->strobe_cmd && phy->cp_io_mask_end > 0) + phy->cp_io_mask_end--; + + if (phy->strobe_cmd) { + phy->cp_use_phony_dqs_cmd = false; + phy->cp_read_dqs_cmd_delay = 64; + } else { + phy->cp_use_phony_dqs_cmd = true; + phy->cp_read_dqs_cmd_delay = 0; + } + + if ((phy->mode == MMC_TIMING_MMC_HS400 && !phy->strobe_cmd) || + phy->mode == MMC_TIMING_MMC_HS200) + phy->cp_read_dqs_cmd_delay = phy->hs200_tune_val; +} + +static void sdhci_cdns6_phy_calc_dat_in(struct sdhci_cdns6_phy *phy) +{ + u32 hcsdclkadj = 0; + bool strobe_dat = (phy->mode == MMC_TIMING_MMC_HS400); + + if (strobe_dat) { + phy->cp_use_phony_dqs = false; + phy->cp_read_dqs_delay = 64; + } else { + phy->cp_use_phony_dqs = true; + phy->cp_read_dqs_delay = 0; + } + + if (phy->mode == MMC_TIMING_MMC_HS200) + phy->cp_read_dqs_delay = phy->hs200_tune_val; + + if (strobe_dat) { + /* dqs loopback input via IO cell */ + hcsdclkadj += phy->iocell_input_delay; + /* dfi_dqs_in: mem_dqs -> clean_dqs_mod; delay of hic_dll_dqs_nand2 */ + hcsdclkadj += phy->delay_element / 2; + /* delay line */ + hcsdclkadj += phy->t_sdclk / 2; + /* PHY FIFO write pointer */ + hcsdclkadj += phy->t_sdclk / 2 + phy->delay_element; + /* 1st synchronizer */ + hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk) * phy->t_sdmclk - hcsdclkadj; + /* + * 2nd synchronizer + PHY FIFO read pointer + PHY rddata + * + PHY rddata registered, + FIFO 1st ciu_en + */ + hcsdclkadj += 5 * phy->t_sdmclk; + /* FIFO 2nd ciu_en */ + hcsdclkadj += phy->t_sdclk; + hcsdclkadj /= phy->t_sdclk; + } else { + u32 n; + + /* rebar PHY delay */ + hcsdclkadj += 2 * phy->t_sdmclk; + /* rebar output via IO cell */ + hcsdclkadj += phy->iocell_output_delay; + /* dqs loopback input via IO cell */ + hcsdclkadj += phy->iocell_input_delay; + /* dfi_dqs_in: mem_dqs -> clean_dqs_mod delay of hic_dll_dqs_nand2 */ + hcsdclkadj += phy->delay_element / 2; + /* dll: one delay element between SIGI_0 and SIGO_0 */ + hcsdclkadj += phy->delay_element; + /* dfi_dqs_in: mem_dqs_delayed -> clk_dqs delay of hic_dll_dqs_nand2 */ + hcsdclkadj += phy->delay_element / 2; + /* deskew DLL: clk_dqs -> clk_dqN: one delay element */ + hcsdclkadj += phy->delay_element; + + if (phy->t_sdclk == phy->t_sdmclk) + n = (hcsdclkadj - 2 * phy->t_sdmclk) / phy->t_sdclk; + else + n = hcsdclkadj / phy->t_sdclk; + + /* phase shift within one t_sdclk clock cycle caused by rebar - lbk dqs delay */ + hcsdclkadj = hcsdclkadj % phy->t_sdclk; + /* PHY FIFO write pointer */ + hcsdclkadj += phy->t_sdclk / 2; + /* 1st synchronizer */ + hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk) * phy->t_sdmclk - hcsdclkadj; + /* + * 2nd synchronizer + PHY FIFO read pointer + PHY rddata + PHY rddata registered + */ + hcsdclkadj += 4 * phy->t_sdmclk; + + if ((phy->t_sdclk / phy->t_sdmclk) > 1) { + u32 tmp1, tmp2; + + tmp1 = hcsdclkadj; + tmp2 = (hcsdclkadj / phy->t_sdclk) * phy->t_sdclk + phy->t_sdclk - + phy->t_sdmclk; + if (tmp1 == tmp2) + tmp2 += phy->t_sdclk; + + /* FIFO aligns to clock cycle before ciu_en */ + hcsdclkadj += tmp2 - tmp1; + } + + /* FIFO 1st ciu_en */ + hcsdclkadj += phy->t_sdmclk; + /* FIFO 2nd ciu_en */ + hcsdclkadj += phy->t_sdclk; + hcsdclkadj /= phy->t_sdclk; + hcsdclkadj += n; + + if ((phy->t_sdclk / phy->t_sdmclk) >= 2) { + if (phy->mode == MMC_TIMING_UHS_DDR50 || phy->mode == MMC_TIMING_MMC_DDR52) + hcsdclkadj -= 2; + else + hcsdclkadj -= 1; + } else if ((phy->t_sdclk / phy->t_sdmclk) == 1) { + hcsdclkadj += 2; + } + + if (phy->mode == MMC_TIMING_UHS_SDR104 || phy->mode == MMC_TIMING_MMC_HS200) + hcsdclkadj -= 1; + } + + /* hcsdclkadj is a 4-bit field, clamp to max value of 15 */ + if (hcsdclkadj > 15) + hcsdclkadj = 15; + + phy->sdhc_hcsdclkadj = hcsdclkadj; +} + +static void sdhci_cdns6_phy_calc_dat_out(struct sdhci_cdns6_phy *phy) +{ + sdhci_cdns6_phy_calc_out(phy, false); +} + +static void sdhci_cdns6_phy_calc_io(struct sdhci_cdns6_phy *phy) +{ + u32 rw_compensate; + + rw_compensate = ((phy->iocell_input_delay + phy->iocell_output_delay) / phy->t_sdmclk) + + phy->sdhc_wrdata0_dly + 5 + 3; + + phy->sdhc_idelay_val = (2 * phy->iocell_input_delay) / phy->t_sdmclk; + + phy->cp_io_mask_start = 0; + if (phy->t_sdclk == phy->t_sdmclk && rw_compensate > 10) + phy->cp_io_mask_start = 2 * (rw_compensate - 10); + + if (phy->mode == MMC_TIMING_UHS_SDR104) + phy->cp_io_mask_start++; + + if (phy->t_sdclk == phy->t_sdmclk && phy->mode == MMC_TIMING_UHS_SDR50) + phy->cp_io_mask_start++; + + /* cp_io_mask_start is a 3-bit field, clamp to max value of 7 */ + phy->cp_io_mask_start = min_t(u8, phy->cp_io_mask_start, 7); + + phy->sdhc_rw_compensate = rw_compensate; +} + +static void sdhci_cdns6_phy_calc_settings(struct sdhci_cdns6_phy *phy) +{ + sdhci_cdns6_phy_calc_cmd_out(phy); + sdhci_cdns6_phy_calc_cmd_in(phy); + sdhci_cdns6_phy_calc_dat_out(phy); + sdhci_cdns6_phy_calc_dat_in(phy); + sdhci_cdns6_phy_calc_io(phy); +} + +static int sdhci_cdns6_dll_reset(struct sdhci_cdns_priv *priv, bool reset) +{ + u32 reg; + int ret = 0; + + reg = readl(priv->hrs_addr + SDHCI_CDNS_HRS09); + if (reset) + reg &= ~SDHCI_CDNS_HRS09_PHY_SW_RESET; + else + reg |= SDHCI_CDNS_HRS09_PHY_SW_RESET; + + writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS09); + + /* After releasing PHY from reset, wait until PHY_INIT_COMPLETE is set within 3000us */ + if (!reset) { + ret = readl_poll_timeout(priv->hrs_addr + SDHCI_CDNS_HRS09, reg, (reg & + SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE), 0, 3000); + } + + return ret; +} + +int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv) +{ + struct sdhci_cdns6_phy *phy = priv->phy; + u32 reg; + int ret; + + sdhci_cdns6_dll_reset(priv, true); + + reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_DQS_TIMING_REG); + reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS; + reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD; + reg |= SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS; + reg |= SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS; + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS, phy->cp_use_phony_dqs); + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD, phy->cp_use_phony_dqs_cmd); + sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DQS_TIMING_REG, reg); + + reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG); + reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL; + reg |= SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS; + reg |= SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON; + reg |= SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD; + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL, + SDHCI_CDNS6_PHY_DEFAULT_RD_DEL_SEL); + sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG, reg); + + reg = FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_BYPASS_MODE, phy->cp_dll_bypass_mode); + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_PHASE_DETECT_SEL, + SDHCI_CDNS6_PHY_DEFAULT_PHASE_DETECT_SEL); + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_LOCK_NUM, + SDHCI_CDNS6_PHY_DEFAULT_DLL_LOCK_NUM); + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_START_POINT, + SDHCI_CDNS6_PHY_DEFAULT_DLL_START); + sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_REG, reg); + + reg = FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY, + phy->cp_read_dqs_cmd_delay); + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WRDQS_DELAY, phy->cp_clk_wrdqs_delay); + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WR_DELAY, phy->cp_clk_wr_delay); + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY, phy->cp_read_dqs_delay); + sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG, reg); + + reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG); + reg &= ~SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING; + sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG, reg); + + /* + * Ensure all preceding PHY register writes complete and reach the controller before + * releasing the PHY from reset. Without this, SDR104 has been observed to fail + * intermittently on some boards. + */ + wmb(); + + ret = sdhci_cdns6_dll_reset(priv, false); + if (ret) + return ret; + + reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_DQ_TIMING_REG); + reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON; + reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END; + reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START; + reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END; + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END, phy->cp_io_mask_end); + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START, phy->cp_io_mask_start); + reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END, + SDHCI_CDNS6_PHY_DEFAULT_DATA_SELECT_OE_END); + sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DQ_TIMING_REG, reg); + + /* Ensure DQ timing programming is visible before HRS09 follow-up writes */ + wmb(); + + reg = readl(priv->hrs_addr + SDHCI_CDNS_HRS09); + if (phy->sdhc_extended_wr_mode) + reg |= SDHCI_CDNS_HRS09_EXTENDED_WR_MODE; + else + reg &= ~SDHCI_CDNS_HRS09_EXTENDED_WR_MODE; + + if (phy->sdhc_extended_rd_mode) + reg |= SDHCI_CDNS_HRS09_EXTENDED_RD_MODE; + else + reg &= ~SDHCI_CDNS_HRS09_EXTENDED_RD_MODE; + + reg |= SDHCI_CDNS_HRS09_RDDATA_EN; + reg |= SDHCI_CDNS_HRS09_RDCMD_EN; + writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS09); + + reg = FIELD_PREP(SDHCI_CDNS_HRS10_HCSDCLKADJ, phy->sdhc_hcsdclkadj); + writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS10); + + reg = FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY, phy->sdhc_wrdata1_sdclk_dly); + reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY, phy->sdhc_wrdata0_sdclk_dly); + reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY, phy->sdhc_wrcmd1_sdclk_dly); + reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY, phy->sdhc_wrcmd0_sdclk_dly); + reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_DLY, phy->sdhc_wrdata1_dly); + reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_DLY, phy->sdhc_wrdata0_dly); + reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_DLY, phy->sdhc_wrcmd1_dly); + reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_DLY, phy->sdhc_wrcmd0_dly); + writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS16); + + reg = FIELD_PREP(SDHCI_CDNS_HRS07_RW_COMPENSATE, phy->sdhc_rw_compensate); + reg |= FIELD_PREP(SDHCI_CDNS_HRS07_IDELAY_VAL, phy->sdhc_idelay_val); + writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS07); + + /* Allow 5 to 5.5 ms for clock and PHY signals to stabilize after configuration */ + usleep_range(5000, 5500); + + return 0; +} + +int sdhci_cdns6_set_tune_val(struct sdhci_host *host, unsigned int val) +{ + struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); + struct sdhci_cdns6_phy *phy = priv->phy; + u32 tmp, tuneval; + int ret; + + /* + * Scale tuning tap (val in [0, SDHCI_CDNS_MAX_TUNING_LOOP-1]) to the + * 8-bit PHY DLL slave delay field [0, 255]. With MAX_TUNING_LOOP=40 + * and FIELD_SIZE=256, the result fits in 8 bits. + */ + tuneval = (val * SDHCI_CDNS6_PHY_DLL_FIELD_SIZE) / SDHCI_CDNS_MAX_TUNING_LOOP; + + phy->hs200_tune_val = tuneval; + phy->cp_read_dqs_cmd_delay = tuneval; + phy->cp_read_dqs_delay = tuneval; + + tmp = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG); + tmp &= ~(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY | + SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY); + tmp |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY, tuneval); + tmp |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY, tuneval); + + sdhci_cdns6_dll_reset(priv, true); + sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG, tmp); + + ret = sdhci_cdns6_dll_reset(priv, false); + if (ret) + dev_warn(mmc_dev(host->mmc), "%s: DLL reset release failed: %d\n", __func__, ret); + + return ret; +} + +static int sdhci_cdns6_phy_update_timings(struct sdhci_host *host) +{ + struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); + struct sdhci_cdns6_phy *phy = priv->phy; + u32 t_sdmclk = phy->t_sdmclk; + + /* Validate mode is within supported range */ + if (phy->mode >= ARRAY_SIZE(init_timings)) + return -EINVAL; + + /* initialize input */ + init_timings[phy->mode](phy, phy->t_sdclk); + + phy->strobe_cmd = false; + + if (priv->enhanced_strobe) + phy->strobe_cmd = true; + + phy->phy_sdclk_delay = 2 * t_sdmclk; + + /* + * CMD and DAT output delays are currently identical, but kept separate to allow + * independent tuning for specific modes (e.g., HS400) or board-specific optimizations + * in the future. + */ + phy->phy_cmd_o_delay = 2 * t_sdmclk + t_sdmclk / 2; + phy->phy_dat_o_delay = 2 * t_sdmclk + t_sdmclk / 2; + + if (phy->t_sdclk == phy->t_sdmclk) { + phy->sdhc_extended_wr_mode = false; + phy->sdhc_extended_rd_mode = false; + } else { + phy->sdhc_extended_wr_mode = true; + phy->sdhc_extended_rd_mode = true; + } + + sdhci_cdns6_phy_configure_dll(phy); + sdhci_cdns6_phy_calc_settings(phy); + + return 0; +} + +int sdhci_cdns6_phy_probe(struct platform_device *pdev, struct sdhci_cdns_priv *priv) +{ + struct device *dev = &pdev->dev; + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_cdns6_phy *phy; + unsigned long val; + int ret; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + val = clk_get_rate(pltfm_host->clk); + if (!val) + return dev_err_probe(dev, -EINVAL, "failed to get controller clock rate\n"); + + phy->t_sdmclk = DIV_ROUND_DOWN_ULL(1000000000000ULL, val); + + /* + * Optional board-level PHY timing properties. When omitted, use the + * Cadence-recommended defaults (2500 ps IO-cell delays, 24 ps DLL + * delay element) that are valid for typical SoC packaging. + */ + ret = of_property_read_u32(dev->of_node, "cdns,iocell-input-delay-ps", + &phy->iocell_input_delay); + if (ret) + phy->iocell_input_delay = SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY; + + ret = of_property_read_u32(dev->of_node, "cdns,iocell-output-delay-ps", + &phy->iocell_output_delay); + if (ret) + phy->iocell_output_delay = SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY; + + ret = of_property_read_u32(dev->of_node, "cdns,delay-element-ps", &phy->delay_element); + if (ret) { + phy->delay_element = SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT; + } else if (!phy->delay_element) { + dev_warn(dev, "cdns,delay-element-ps is 0, using default %u\n", + SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT); + phy->delay_element = SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT; + } + + phy->delay_element_org = phy->delay_element; + + priv->phy = phy; + + return 0; +} + +static void sdhci_cdns6_phy_reconfigure(struct sdhci_host *host, unsigned int timing) +{ + struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); + struct sdhci_cdns6_phy *phy = priv->phy; + unsigned int rate = host->mmc->actual_clock ?: host->mmc->ios.clock; + u32 t_sdclk; + int ret; + + if (!rate) + return; + + t_sdclk = DIV_ROUND_DOWN_ULL(1000000000000ULL, rate); + if (phy->mode == timing && phy->t_sdclk == t_sdclk && + phy->strobe_cmd == priv->enhanced_strobe) + return; + + phy->t_sdclk = t_sdclk; + phy->mode = timing; + + ret = sdhci_cdns6_phy_update_timings(host); + if (ret) { + phy->t_sdclk = 0; + dev_warn(mmc_dev(host->mmc), "%s: update timings failed: %d\n", __func__, ret); + return; + } + + ret = sdhci_cdns6_phy_init(priv); + if (ret) { + phy->t_sdclk = 0; + dev_warn(mmc_dev(host->mmc), "%s: phy init failed: %d\n", __func__, ret); + } +} + +void sdhci_cdns6_set_clock(struct sdhci_host *host, unsigned int clock) +{ + struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); + struct sdhci_cdns6_phy *phy = priv->phy; + + sdhci_set_clock(host, clock); + + if (!clock) { + /* Force PHY reprogramming on the next non-zero clock. */ + phy->t_sdclk = 0; + return; + } + + sdhci_cdns6_phy_reconfigure(host, host->mmc->ios.timing); +} + +void sdhci_cdns6_set_uhs_signaling(struct sdhci_host *host, unsigned int timing) +{ + sdhci_cdns6_phy_reconfigure(host, timing); +} + +void sdhci_cdns6_hw_reset(struct sdhci_host *host) +{ + struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); + void __iomem *reg; + + reg = priv->hrs_addr + SDHCI_CDNS_HRS11; + writel(SDHCI_CDNS_HRS11_EMMC_RST, reg); + /* eMMC HW reset assertion: spec requires >= 1us, give margin */ + usleep_range(10, 20); + writel(0, reg); + /* For eMMC, minimum is 200us but give it 300us for good measure */ + usleep_range(300, 1000); +} diff --git a/drivers/mmc/host/sdhci-cadence.h b/drivers/mmc/host/sdhci-cadence.h new file mode 100644 index 000000000000..2ac11befb53b --- /dev/null +++ b/drivers/mmc/host/sdhci-cadence.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026 Altera Corporation + * Author: Tanmay Kathpalia + * + * Cadence SD/SDIO/eMMC Host Controller driver - common header + * Shared definitions and structures for the Cadence SDHCI driver. + * Contains private data and declarations for SD6HC-specific functions + * called by the main driver in sdhci-cadence-core.c. + */ + +#ifndef _MMC_HOST_SDHCI_CADENCE_H +#define _MMC_HOST_SDHCI_CADENCE_H + +#include +#include +#include + +#include "sdhci-pltfm.h" + +struct reset_control; + +/* HRS - Host Register Set (specific to Cadence) */ +#define SDHCI_CDNS_HRS04 0x10 /* PHY access: address port */ +#define SDHCI_CDNS_HRS05 0x14 /* PHY access: data port */ + +/* + * The tuned val register is 6 bit-wide, but not the whole of the range is + * available. The range 0-42 seems to be available (then 43 wraps around to 0) + * but I am not quite sure if it is official. Use only 0 to 39 for safety. + */ +#define SDHCI_CDNS_MAX_TUNING_LOOP 40 + +/** + * struct sdhci_cdns_priv - Cadence SDHCI private controller data + * @hrs_addr: Base address of Cadence Host Register Set (HRS) registers. + * @ctl_addr: Base address for write control registers. + * Used only for "amd,pensando-elba-sd4hc" compatible controllers to enable + * byte-lane writes. + * @wrlock: Spinlock for protecting register writes (Elba only). + * @enhanced_strobe: Flag indicating if Enhanced Strobe (HS400ES) is enabled. + * @priv_writel: Optional SoC-specific write function for register access. + * Used for Elba to ensure correct byte-lane enable. + * @rst_hw: Hardware reset control for the eMMC card RST_n pin (SD4HC only). + * @phy: Opaque pointer to variant-specific PHY data. + * For SD4HC: points to struct sdhci_cdns4_phy. + * For SD6HC: points to struct sdhci_cdns6_phy. + */ +struct sdhci_cdns_priv { + void __iomem *hrs_addr; + void __iomem *ctl_addr; /* write control */ + spinlock_t wrlock; /* write lock */ + bool enhanced_strobe; + void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg); + struct reset_control *rst_hw; + void *phy; +}; + +/** + * sdhci_cdns_priv - Helper to retrieve Cadence private data from sdhci_host + * @host: Pointer to struct sdhci_host. + * + * Return: Pointer to struct sdhci_cdns_priv. + */ +static inline void *sdhci_cdns_priv(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + + return sdhci_pltfm_priv(pltfm_host); +} + +/** + * sdhci_cdns6_set_clock - Set SDCLK and reprogram SD6HC PHY timings. + * @host: Pointer to struct sdhci_host. + * @clock: Requested clock frequency in Hz (0 to disable). + */ +void sdhci_cdns6_set_clock(struct sdhci_host *host, unsigned int clock); + +/** + * sdhci_cdns6_set_uhs_signaling - Program PHY registers for a specific timing mode. + * @host: Pointer to struct sdhci_host. + * @timing: MMC timing mode (MMC_TIMING_*). + */ +void sdhci_cdns6_set_uhs_signaling(struct sdhci_host *host, unsigned int timing); + +/** + * sdhci_cdns6_set_tune_val - Set the PHY tuning value. + * @host: Pointer to struct sdhci_host. + * @val: Tuning value to program. + * + * Return: 0 on success, -ETIMEDOUT if DLL reset release times out. + */ +int sdhci_cdns6_set_tune_val(struct sdhci_host *host, unsigned int val); + +/** + * sdhci_cdns6_phy_probe - Probe and initialize Cadence SD6HC PHY parameters + * @pdev: Platform device pointer + * @priv: Pointer to Cadence private data structure + * + * Return: 0 on success or a negative error code. + */ +int sdhci_cdns6_phy_probe(struct platform_device *pdev, struct sdhci_cdns_priv *priv); + +/** + * sdhci_cdns6_hw_reset - Perform hardware reset of the Cadence SDHCI controller. + * @host: Pointer to struct sdhci_host. + */ +void sdhci_cdns6_hw_reset(struct sdhci_host *host); + +/** + * sdhci_cdns6_phy_init - Initialize the SD6HC PHY with current settings. + * @priv: Pointer to Cadence private data structure. + * + * Return: 0 on success, -ETIMEDOUT if PHY initialization times out. + */ +int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv); + +#endif /* _MMC_HOST_SDHCI_CADENCE_H */ From fc377602ea16a04b5b1110a46ad774a5c2741dd5 Mon Sep 17 00:00:00 2001 From: Tanmay Kathpalia Date: Tue, 1 Sep 2026 11:48:18 -0700 Subject: [PATCH 22/43] mmc: sdhci-cadence: add Altera Agilex5 SD6HC support The Altera Agilex5 SoC integrates a Cadence SD6HC controller that needs platform-specific configuration to operate correctly. The SoC requires three named resets: "sdhc-reset", "combophy", and "sdmmc-ocp". All three are exclusive and must be asserted together before being released, so the SDHCI, SoftPHY, and OCP/AXI clock domains cross the reset boundary simultaneously. SoftPHY is shared with NAND at the SoC level, but only one of SDMMC or NAND is enabled on a given board. The IOMMU maps DMA addresses within a 40-bit physical address space, so the DMA mask is capped at 40 bits to prevent allocation beyond the controller's reach. The silicon requires the MULTIBLOCK_READ_ACMD12, CAP_CLOCK_BASE_BROKEN, PRESET_VALUE_BROKEN, and ACMD23_BROKEN quirks. Since CAP_CLOCK_BASE_BROKEN prevents reading the base clock from the capabilities register, the maximum clock is supplied from the platform clock instead. Signed-off-by: Tanmay Kathpalia Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-cadence-core.c | 89 +++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/drivers/mmc/host/sdhci-cadence-core.c b/drivers/mmc/host/sdhci-cadence-core.c index ea2517e8f72a..745455743b01 100644 --- a/drivers/mmc/host/sdhci-cadence-core.c +++ b/drivers/mmc/host/sdhci-cadence-core.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -90,6 +91,7 @@ struct sdhci_cdns4_phy_cfg { struct sdhci_cdns_drv_data { int (*init)(struct platform_device *pdev); const struct sdhci_pltfm_data pltfm_data; + u64 dma_mask; }; static const struct sdhci_cdns4_phy_cfg sdhci_cdns4_phy_cfgs[] = { @@ -196,6 +198,23 @@ static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host) return host->max_clk; } +static int sdhci_cdns_set_dma_mask(struct sdhci_host *host) +{ + const struct sdhci_cdns_drv_data *data; + struct device *dev = mmc_dev(host->mmc); + int ret; + + data = of_device_get_match_data(dev); + if (!data || !data->dma_mask) + return 0; + + ret = dma_set_mask_and_coherent(dev, data->dma_mask); + if (ret) + return dev_err_probe(dev, ret, "failed to set DMA mask\n"); + + return 0; +} + static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_priv *priv, u32 mode) { u32 tmp; @@ -462,6 +481,48 @@ static int elba_drv_init(struct platform_device *pdev) return 0; } +static const struct reset_control_bulk_data sdhci_cdns6_agilex5_reset_ids[] = { + { .id = "sdhc-reset" }, + { .id = "combophy" }, + { .id = "sdmmc-ocp" }, +}; + +static int sdhci_cdns6_agilex5_init(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct reset_control_bulk_data *resets; + int ret; + + resets = devm_kmemdup(dev, sdhci_cdns6_agilex5_reset_ids, + sizeof(sdhci_cdns6_agilex5_reset_ids), GFP_KERNEL); + if (!resets) + return -ENOMEM; + + /* + * Assert SDHCI, SoftPHY (combophy), and SDMMC OCP/AXI resets together + * so their active periods overlap before all domains are released. + * SoftPHY is shared with NAND, but only one of SDMMC + * or NAND is enabled on a given board. + */ + ret = devm_reset_control_bulk_get_exclusive(dev, ARRAY_SIZE(sdhci_cdns6_agilex5_reset_ids), + resets); + if (ret) + return dev_err_probe(dev, ret, "failed to get resets\n"); + + ret = reset_control_bulk_assert(ARRAY_SIZE(sdhci_cdns6_agilex5_reset_ids), resets); + if (ret) + return dev_err_probe(dev, ret, "failed to assert resets\n"); + + /* Hold resets asserted long enough for all clock domains to capture. */ + usleep_range(10, 20); + + ret = reset_control_bulk_deassert(ARRAY_SIZE(sdhci_cdns6_agilex5_reset_ids), resets); + if (ret) + return dev_err_probe(dev, ret, "failed to deassert resets\n"); + + return 0; +} + static const struct sdhci_ops sdhci_cdns4_ops = { .set_clock = sdhci_set_clock, .get_timeout_clock = sdhci_cdns_get_timeout_clock, @@ -481,6 +542,18 @@ static const struct sdhci_ops sdhci_cdns6_ops = { .hw_reset = sdhci_cdns6_hw_reset, }; +static const struct sdhci_ops sdhci_cdns6_agilex5_ops = { + .set_clock = sdhci_cdns6_set_clock, + .get_max_clock = sdhci_pltfm_clk_get_max_clock, + .get_timeout_clock = sdhci_cdns_get_timeout_clock, + .set_bus_width = sdhci_set_bus_width, + .reset = sdhci_reset, + .platform_execute_tuning = sdhci_cdns_execute_tuning, + .set_uhs_signaling = sdhci_cdns_set_uhs_signaling, + .hw_reset = sdhci_cdns6_hw_reset, + .set_dma_mask = sdhci_cdns_set_dma_mask, +}; + static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = { .pltfm_data = { .ops = &sdhci_cdns4_ops, @@ -508,6 +581,18 @@ static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = { }, }; +static const struct sdhci_cdns_drv_data sdhci_cdns6_agilex5_drv_data = { + .init = sdhci_cdns6_agilex5_init, + .pltfm_data = { + .ops = &sdhci_cdns6_agilex5_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | + SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | + SDHCI_QUIRK2_ACMD23_BROKEN, + }, + .dma_mask = DMA_BIT_MASK(40), +}; + static const struct sdhci_cdns_drv_data sdhci_cdns6_drv_data = { .pltfm_data = { .ops = &sdhci_cdns6_ops, @@ -716,6 +801,10 @@ static const struct of_device_id sdhci_cdns_match[] = { .compatible = "cdns,sd4hc", .data = &sdhci_cdns4_drv_data, }, + { + .compatible = "altr,agilex5-sd6hc", + .data = &sdhci_cdns6_agilex5_drv_data, + }, { .compatible = "cdns,sd6hc", .data = &sdhci_cdns6_drv_data, From a76c2e905879c3bc6f599bd1a9301431383bdd82 Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sat, 5 Sep 2026 16:32:31 +0100 Subject: [PATCH 23/43] dt-bindings: mmc: amlogic,meson-gx-mmc: document the T7 pipeline clock The SD/eMMC controllers of the T7 SoC are physically far from the NIC_MATRIX bus fabric, so the hardware design inserts a pipeline stage in the middle of the bus path to help timing closure. This stage is fed by its own gate clock and, when that clock is disabled, a controller that starts a DMA transfer can never complete it. Allow a fourth clock entry, named "pipeline", and require it for the amlogic,t7-mmc compatible. The other controllers of this family keep the existing three clocks. Assisted-by: Claude:claude-fable-5 Signed-off-by: Lucas Tanure Acked-by: Conor Dooley Signed-off-by: Ulf Hansson --- .../bindings/mmc/amlogic,meson-gx-mmc.yaml | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mmc/amlogic,meson-gx-mmc.yaml b/Documentation/devicetree/bindings/mmc/amlogic,meson-gx-mmc.yaml index 976f36de2091..12342b1d78e5 100644 --- a/Documentation/devicetree/bindings/mmc/amlogic,meson-gx-mmc.yaml +++ b/Documentation/devicetree/bindings/mmc/amlogic,meson-gx-mmc.yaml @@ -15,6 +15,23 @@ maintainers: allOf: - $ref: mmc-controller.yaml# + - if: + properties: + compatible: + contains: + const: amlogic,t7-mmc + then: + properties: + clocks: + minItems: 4 + clock-names: + minItems: 4 + else: + properties: + clocks: + maxItems: 3 + clock-names: + maxItems: 3 properties: compatible: @@ -38,13 +55,23 @@ properties: - description: card detect clocks: - maxItems: 3 + minItems: 3 + items: + - description: core clock + - description: clock input 0 of the internal mux + - description: clock input 1 of the internal mux + - description: + clock of the pipeline stage inserted in the bus path between + the controller and the DRAM. Without it, the controller cannot + complete DMA transfers. clock-names: + minItems: 3 items: - const: core - const: clkin0 - const: clkin1 + - const: pipeline resets: maxItems: 1 From a5dbd6e7ed29507681dbab2cc2d01c6df104a02a Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sat, 5 Sep 2026 16:32:32 +0100 Subject: [PATCH 24/43] mmc: meson-gx: enable the bus pipeline clock on T7 On the T7 SoC, the bus path between the SD/eMMC controllers and the NIC_MATRIX fabric goes through a pipeline stage inserted by the hardware design to help timing closure. The stage has its own gate clock and, when that clock is disabled, a controller that starts a DMA transfer can never complete it, hanging the storage devices and, from there, the whole system. Add a dedicated match data for the amlogic,t7-mmc compatible that makes the driver claim and enable the "pipeline" clock for as long as the device is bound. The clock is deliberately not optional: the hardware cannot do DMA without it, and failing the probe with a clear error is preferable to booting and hitting an undiagnosable DMA hang later. Assisted-by: Claude:claude-fable-5 Signed-off-by: Lucas Tanure Reviewed-by: Neil Armstrong Signed-off-by: Ulf Hansson --- drivers/mmc/host/meson-gx-mmc.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c index 4d0a8f0891af..7ef16d7c03cb 100644 --- a/drivers/mmc/host/meson-gx-mmc.c +++ b/drivers/mmc/host/meson-gx-mmc.c @@ -139,6 +139,7 @@ struct meson_mmc_data { unsigned int always_on; unsigned int adjust; unsigned int irq_sdio_sleep; + bool has_pipeline_clk; }; struct sd_emmc_desc { @@ -1209,6 +1210,15 @@ static int meson_mmc_probe(struct platform_device *pdev) if (IS_ERR(core_clk)) return PTR_ERR(core_clk); + if (host->data->has_pipeline_clk) { + struct clk *pipe_clk; + + pipe_clk = devm_clk_get_enabled(&pdev->dev, "pipeline"); + if (IS_ERR(pipe_clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pipe_clk), + "missing pipeline clock\n"); + } + ret = meson_mmc_clk_init(host); if (ret) return ret; @@ -1327,12 +1337,22 @@ static const struct meson_mmc_data meson_axg_data = { .irq_sdio_sleep = CLK_V3_IRQ_SDIO_SLEEP, }; +static const struct meson_mmc_data meson_t7_data = { + .tx_delay_mask = CLK_V3_TX_DELAY_MASK, + .rx_delay_mask = CLK_V3_RX_DELAY_MASK, + .always_on = CLK_V3_ALWAYS_ON, + .adjust = SD_EMMC_V3_ADJUST, + .irq_sdio_sleep = CLK_V3_IRQ_SDIO_SLEEP, + .has_pipeline_clk = true, +}; + static const struct of_device_id meson_mmc_of_match[] = { { .compatible = "amlogic,meson-gx-mmc", .data = &meson_gx_data }, { .compatible = "amlogic,meson-gxbb-mmc", .data = &meson_gx_data }, { .compatible = "amlogic,meson-gxl-mmc", .data = &meson_gx_data }, { .compatible = "amlogic,meson-gxm-mmc", .data = &meson_gx_data }, { .compatible = "amlogic,meson-axg-mmc", .data = &meson_axg_data }, + { .compatible = "amlogic,t7-mmc", .data = &meson_t7_data }, {} }; MODULE_DEVICE_TABLE(of, meson_mmc_of_match); From 6d05845f5c77d7327376f3db26266958f4dfe74e Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:16:32 +0530 Subject: [PATCH 25/43] mmc: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Ulf Hansson --- drivers/memstick/core/ms_block.c | 2 +- drivers/memstick/host/r592.c | 2 +- drivers/mmc/core/block.c | 2 +- drivers/mmc/core/core.c | 2 +- drivers/mmc/core/mmc.c | 2 +- drivers/mmc/host/bcm2835.c | 2 +- drivers/mmc/host/dw_mmc.h | 2 +- drivers/mmc/host/mvsdio.c | 2 +- drivers/mmc/host/sdhci-msm.c | 2 +- drivers/mmc/host/sdhci-of-esdhc.c | 2 +- drivers/mmc/host/sdhci-omap.c | 2 +- drivers/mmc/host/sdhci.h | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/memstick/core/ms_block.c b/drivers/memstick/core/ms_block.c index 65154e569a9e..cd4ff1b79a6d 100644 --- a/drivers/memstick/core/ms_block.c +++ b/drivers/memstick/core/ms_block.c @@ -720,7 +720,7 @@ static int h_msb_parallel_switch(struct memstick_dev *card, static int msb_switch_to_parallel(struct msb_data *msb); -/* Reset the card, to guard against hw errors beeing treated as bad blocks */ +/* Reset the card, to guard against hw errors being treated as bad blocks */ static int msb_reset(struct msb_data *msb, bool full) { diff --git a/drivers/memstick/host/r592.c b/drivers/memstick/host/r592.c index 605b2265536f..dc3de63901be 100644 --- a/drivers/memstick/host/r592.c +++ b/drivers/memstick/host/r592.c @@ -289,7 +289,7 @@ static int r592_transfer_fifo_dma(struct r592_device *dev) dev->dma_error = 0; reinit_completion(&dev->dma_done); - /* TODO: hidden assumption about nenth beeing always 1 */ + /* TODO: hidden assumption about nenth being always 1 */ sg_count = dma_map_sg(&dev->pci_dev->dev, &dev->req->sg, 1, is_write ? DMA_TO_DEVICE : DMA_FROM_DEVICE); diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index e760d13ee325..a02bb2fee874 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -86,7 +86,7 @@ MODULE_ALIAS("mmc:block"); static DEFINE_MUTEX(block_mutex); /* - * The defaults come from config options but can be overriden by module + * The defaults come from config options but can be overridden by module * or bootarg options. */ static int perdev_minors = CONFIG_MMC_BLOCK_MINORS; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 9472041fe1c2..879b1355f7d5 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2004,7 +2004,7 @@ unsigned int mmc_calc_max_discard(struct mmc_card *card) /* * Without erase_group_def set, MMC erase timeout depends on clock - * frequence which can change. In that case, the best choice is + * frequency which can change. In that case, the best choice is * just the preferred erase size. */ if (mmc_card_mmc(card) && !(card->ext_csd.erase_group_def & 1)) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 05444ecf3909..a02b04938e1a 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1015,7 +1015,7 @@ static void mmc_set_bus_speed(struct mmc_card *card) } /* - * Select the bus width amoung 4-bit and 8-bit(SDR). + * Select the bus width among 4-bit and 8-bit(SDR). * If the bus width is changed successfully, return the selected width value. * Zero is returned instead of error value if the wide width is not supported. */ diff --git a/drivers/mmc/host/bcm2835.c b/drivers/mmc/host/bcm2835.c index c949a3eaae79..efd1903c3ccd 100644 --- a/drivers/mmc/host/bcm2835.c +++ b/drivers/mmc/host/bcm2835.c @@ -1095,7 +1095,7 @@ static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock) int div; /* The SDCDIV register has 11 bits, and holds (div - 2). But - * in data mode the max is 50MHz wihout a minimum, and only + * in data mode the max is 50MHz without a minimum, and only * the bottom 3 bits are used. Since the switch over is * automatic (unless we have marked the card as slow...), * chosen values have to make sense in both modes. Ident mode diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index 12b88e799a17..dd43a577ec33 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -69,7 +69,7 @@ struct dw_mci_dma_slave { * @cmd: The command currently being sent to the card, or NULL. * @data: The data currently being transferred, or NULL if no data * transfer is in progress. - * @stop_abort: The command currently prepared for stoping transfer. + * @stop_abort: The command currently prepared for stopping transfer. * @prev_blksz: The former transfer blksz record. * @timing: Record of current ios timing. * @use_dma: Which DMA channel is in use for the current transfer, zero diff --git a/drivers/mmc/host/mvsdio.c b/drivers/mmc/host/mvsdio.c index cf705b265e21..7d86426c6001 100644 --- a/drivers/mmc/host/mvsdio.c +++ b/drivers/mmc/host/mvsdio.c @@ -717,7 +717,7 @@ static int mvsd_probe(struct platform_device *pdev) /* * Some non-DT platforms do not pass a clock, and the clock * frequency is passed through platform_data. On DT platforms, - * a clock must always be passed, even if there is no gatable + * a clock must always be passed, even if there is no gateable * clock associated to the SDIO interface (it can simply be a * fixed rate clock). */ diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 460994164d2d..aecd425bf095 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1770,7 +1770,7 @@ static void sdhci_msm_handle_pwr_irq(struct sdhci_host *host, int irq) /* * The driver has to acknowledge the interrupt, switch voltages and - * report back if it succeded or not to this register. The voltage + * report back if it succeeded or not to this register. The voltage * switches are handled by the sdhci core, so just report success. */ msm_host_writel(msm_host, irq_ack, host, diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c index 8345e2c5a034..1ca3562d8f73 100644 --- a/drivers/mmc/host/sdhci-of-esdhc.c +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -1381,7 +1381,7 @@ static void esdhc_init(struct platform_device *pdev, struct sdhci_host *host) if (!IS_ERR(clk)) { /* * esdhc->peripheral_clock would be assigned with a value - * which is eSDHC base clock when use periperal clock. + * which is eSDHC base clock when use peripheral clock. * For some platforms, the clock value got by common clk * API is peripheral clock while the eSDHC base clock is * 1/2 peripheral clock. diff --git a/drivers/mmc/host/sdhci-omap.c b/drivers/mmc/host/sdhci-omap.c index b5d7c1a80a92..7a198fb26628 100644 --- a/drivers/mmc/host/sdhci-omap.c +++ b/drivers/mmc/host/sdhci-omap.c @@ -332,7 +332,7 @@ static int sdhci_omap_execute_tuning(struct mmc_host *mmc, u32 opcode) u32 reg; int i; - /* clock tuning is not needed for upto 52MHz */ + /* clock tuning is not needed for up to 52MHz */ if (ios->clock <= 52000000) return 0; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index f0fd81409a86..98c1fe20bfcd 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -409,7 +409,7 @@ struct sdhci_adma2_64_desc { #define SDHCI_MAX_MRQS 2 /* - * 48bit command and 136 bit response in 100KHz clock could take upto 2.48ms. + * 48bit command and 136 bit response in 100KHz clock could take up to 2.48ms. * However since the start time of the command, the time between * command and response, and the time between response and start of data is * not known, set the command transfer time to 10ms. From fd01b061c89bd3c688bfdd230f48e233c3eeb205 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 11 Sep 2026 22:09:43 +0200 Subject: [PATCH 26/43] CREDITS: Add Pierre Ossman Now I have a bad conscience for removing Pierre's name from every dmesg without properly adding him to the CREDITS. OK, it's an easy fix. Signed-off-by: Linus Walleij Signed-off-by: Ulf Hansson --- CREDITS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CREDITS b/CREDITS index a1455b471051..517eb1ca0d92 100644 --- a/CREDITS +++ b/CREDITS @@ -3109,6 +3109,11 @@ N: Peter Oruba D: AMD Microcode loader driver S: Germany +N: Pierre Ossman +D: MMC maintainer 2006-2009 +D: First SDIO implementation +S: Sweden + N: Jens Osterkamp E: jens@de.ibm.com D: Maintainer of Spidernet network driver for Cell From e5725cf4e36a6d975f7129560a2f00c1d236bf97 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 9 Sep 2026 15:24:32 -0700 Subject: [PATCH 27/43] mmc: sdhci-pxav3: avoid of_node Use device handlers instead of of_node ones for simplicity. As this driver is effectively OF only, it ends up behaving the same. Change is_bool to present as no-1-8-v is not specified as a bool in dts, but as either present or not. Signed-off-by: Rosen Penev Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-pxav3.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index 181b4989c74d..f39764ffd4a0 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -123,7 +123,7 @@ static int mv_conf_mbus_windows(struct platform_device *pdev, static int armada_38x_quirks(struct platform_device *pdev, struct sdhci_host *host) { - struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_pxa *pxa = sdhci_pltfm_priv(pltfm_host); struct resource *res; @@ -155,7 +155,7 @@ static int armada_38x_quirks(struct platform_device *pdev, * controller has different capabilities than the ones shown * in its registers */ - if (of_property_read_bool(np, "no-1-8-v")) { + if (device_property_present(dev, "no-1-8-v")) { host->caps &= ~SDHCI_CAN_VDD_180; host->mmc->caps &= ~MMC_CAP_1_8V_DDR; } else { @@ -364,14 +364,13 @@ MODULE_DEVICE_TABLE(of, sdhci_pxav3_of_match); static struct sdhci_pxa_platdata *pxav3_get_mmc_pdata(struct device *dev) { struct sdhci_pxa_platdata *pdata; - struct device_node *np = dev->of_node; u32 clk_delay_cycles; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return NULL; - if (!of_property_read_u32(np, "mrvl,clk-delay-cycles", + if (!device_property_read_u32(dev, "mrvl,clk-delay-cycles", &clk_delay_cycles)) pdata->clk_delay_cycles = clk_delay_cycles; @@ -402,7 +401,6 @@ static int sdhci_pxav3_probe(struct platform_device *pdev) struct sdhci_pltfm_host *pltfm_host; struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data; struct device *dev = &pdev->dev; - struct device_node *np = pdev->dev.of_node; struct sdhci_host *host = NULL; struct sdhci_pxa *pxa = NULL; const struct of_device_id *match; @@ -433,7 +431,7 @@ static int sdhci_pxav3_probe(struct platform_device *pdev) /* enable 1/8V DDR capable */ host->mmc->caps |= MMC_CAP_1_8V_DDR; - if (of_device_is_compatible(np, "marvell,armada-380-sdhci")) { + if (device_is_compatible(dev, "marvell,armada-380-sdhci")) { ret = armada_38x_quirks(pdev, host); if (ret < 0) goto err_mbus_win; From acd7a6a427e587d0bb724dfdd0b6e86679d0b237 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Mon, 14 Sep 2026 09:08:11 +0800 Subject: [PATCH 28/43] mmc: dw_mmc: remove unused slot member struct dw_mci_slot does not exist anymore and nothing ever references host->slot; the member is a leftover from the multi-slot design this driver was upstreamed with, where the slot struct lived in the same header. Signed-off-by: Shawn Lin Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc.h | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index dd43a577ec33..5c2a8d0e9fb4 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -216,7 +216,6 @@ struct dw_mci { void *priv; struct clk *biu_clk; struct clk *ciu_clk; - struct dw_mci_slot *slot; /* FIFO push and pull */ int fifo_depth; From ddcec9ed9f24e92679994e04b6f302981b4c8115 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 14 Sep 2026 13:16:42 +0200 Subject: [PATCH 29/43] mmc: vub300: Remove the driver for the obsolete HW The Elan Digital Systems controller has been obsolete for many years. In fact, its corresponding driver that was introduced in 2011 only received one initial commit, but has since then never been actively maintained. In this regards, we have lately started to receive a lot of AI generated bug fixes as the driver is a real mess. Rather than continue this path instead of making a proper rework of the driver, which is what would be needed, let's just remove the driver altogether. Acked-by: Greg Kroah-Hartman Acked-by: Johan Hovold Acked-by: Arnd Bergmann Signed-off-by: Ulf Hansson --- MAINTAINERS | 5 - drivers/mmc/host/Kconfig | 31 - drivers/mmc/host/Makefile | 1 - drivers/mmc/host/vub300.c | 2493 ------------------------------------- 4 files changed, 2530 deletions(-) delete mode 100644 drivers/mmc/host/vub300.c diff --git a/MAINTAINERS b/MAINTAINERS index 978fe6999d3b..5576f7d5f0a5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29303,11 +29303,6 @@ L: linux-hwmon@vger.kernel.org S: Maintained F: drivers/hwmon/vt8231.c -VUB300 USB to SDIO/SD/MMC bridge chip -L: linux-mmc@vger.kernel.org -S: Orphan -F: drivers/mmc/host/vub300.c - W1 DALLAS'S 1-WIRE BUS M: Krzysztof Kozlowski S: Maintained diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 4f060d3e5636..79bfc425e7c7 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -917,37 +917,6 @@ config MMC_JZ4740 If you have a board based on such a SoC and with a SD/MMC slot, say Y or M here. -config MMC_VUB300 - tristate "VUB300 USB to SDIO/SD/MMC Host Controller support" - depends on USB - help - This selects support for Elan Digital Systems' VUB300 chip. - - The VUB300 is a USB-SDIO Host Controller Interface chip - that enables the host computer to use SDIO/SD/MMC cards - via a USB 2.0 or USB 1.1 host. - - The VUB300 chip will be found in both physically separate - USB to SDIO/SD/MMC adapters and embedded on some motherboards. - - The VUB300 chip supports SD and MMC memory cards in addition - to single and multifunction SDIO cards. - - Some SDIO cards will need a firmware file to be loaded and - sent to VUB300 chip in order to achieve better data throughput. - Download these "Offload Pseudocode" from Elan Digital Systems' - web-site http://www.elandigitalsystems.com/support/downloads.php - and put them in /lib/firmware. Note that without these additional - firmware files the VUB300 chip will still function, but not at - the best obtainable data rate. - - To compile this mmc host controller driver as a module, - choose M here: the module will be called vub300. - - If you have a computer with an embedded VUB300 chip - or if you intend connecting a USB adapter based on a - VUB300 chip say Y or M here. - config MMC_USHC tristate "USB SD Host Controller (USHC) support" depends on USB diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 799ce7ad43d5..fd043afab57e 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -60,7 +60,6 @@ obj-$(CONFIG_MMC_DW_ROCKCHIP) += dw_mmc-rockchip.o obj-$(CONFIG_MMC_DW_STARFIVE) += dw_mmc-starfive.o obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o -obj-$(CONFIG_MMC_VUB300) += vub300.o obj-$(CONFIG_MMC_USHC) += ushc.o obj-$(CONFIG_MMC_WMT) += wmt-sdmmc.o obj-$(CONFIG_MMC_MESON_GX) += meson-gx-mmc.o diff --git a/drivers/mmc/host/vub300.c b/drivers/mmc/host/vub300.c deleted file mode 100644 index 2dae474dcd06..000000000000 --- a/drivers/mmc/host/vub300.c +++ /dev/null @@ -1,2493 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Remote VUB300 SDIO/SDmem Host Controller Driver - * - * Copyright (C) 2010 Elan Digital Systems Limited - * - * based on USB Skeleton driver - 2.2 - * - * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) - * - * VUB300: is a USB 2.0 client device with a single SDIO/SDmem/MMC slot - * Any SDIO/SDmem/MMC device plugged into the VUB300 will appear, - * by virtue of this driver, to have been plugged into a local - * SDIO host controller, similar to, say, a PCI Ricoh controller - * This is because this kernel device driver is both a USB 2.0 - * client device driver AND an MMC host controller driver. Thus - * if there is an existing driver for the inserted SDIO/SDmem/MMC - * device then that driver will be used by the kernel to manage - * the device in exactly the same fashion as if it had been - * directly plugged into, say, a local pci bus Ricoh controller - * - * RANT: this driver was written using a display 128x48 - converting it - * to a line width of 80 makes it very difficult to support. In - * particular functions have been broken down into sub functions - * and the original meaningful names have been shortened into - * cryptic ones. - * The problem is that executing a fragment of code subject to - * two conditions means an indentation of 24, thus leaving only - * 56 characters for a C statement. And that is quite ridiculous! - * - * Data types: data passed to/from the VUB300 is fixed to a number of - * bits and driver data fields reflect that limit by using - * u8, u16, u32 - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct host_controller_info { - u8 info_size; - u16 firmware_version; - u8 number_of_ports; -} __packed; - -#define FIRMWARE_BLOCK_BOUNDARY 1024 -struct sd_command_header { - u8 header_size; - u8 header_type; - u8 port_number; - u8 command_type; /* Bit7 - Rd/Wr */ - u8 command_index; - u8 transfer_size[4]; /* ReadSize + ReadSize */ - u8 response_type; - u8 arguments[4]; - u8 block_count[2]; - u8 block_size[2]; - u8 block_boundary[2]; - u8 reserved[44]; /* to pad out to 64 bytes */ -} __packed; - -struct sd_irqpoll_header { - u8 header_size; - u8 header_type; - u8 port_number; - u8 command_type; /* Bit7 - Rd/Wr */ - u8 padding[16]; /* don't ask why !! */ - u8 poll_timeout_msb; - u8 poll_timeout_lsb; - u8 reserved[42]; /* to pad out to 64 bytes */ -} __packed; - -struct sd_common_header { - u8 header_size; - u8 header_type; - u8 port_number; -} __packed; - -struct sd_response_header { - u8 header_size; - u8 header_type; - u8 port_number; - u8 command_type; - u8 command_index; - u8 command_response[]; -} __packed; - -struct sd_status_header { - u8 header_size; - u8 header_type; - u8 port_number; - u16 port_flags; - u32 sdio_clock; - u16 host_header_size; - u16 func_header_size; - u16 ctrl_header_size; -} __packed; - -struct sd_error_header { - u8 header_size; - u8 header_type; - u8 port_number; - u8 error_code; -} __packed; - -struct sd_interrupt_header { - u8 header_size; - u8 header_type; - u8 port_number; -} __packed; - -struct offload_registers_access { - u8 command_byte[4]; - u8 Respond_Byte[4]; -} __packed; - -#define INTERRUPT_REGISTER_ACCESSES 15 -struct sd_offloaded_interrupt { - u8 header_size; - u8 header_type; - u8 port_number; - struct offload_registers_access reg[INTERRUPT_REGISTER_ACCESSES]; -} __packed; - -struct sd_register_header { - u8 header_size; - u8 header_type; - u8 port_number; - u8 command_type; - u8 command_index; - u8 command_response[6]; -} __packed; - -#define PIGGYBACK_REGISTER_ACCESSES 14 -struct sd_offloaded_piggyback { - struct sd_register_header sdio; - struct offload_registers_access reg[PIGGYBACK_REGISTER_ACCESSES]; -} __packed; - -union sd_response { - struct sd_common_header common; - struct sd_status_header status; - struct sd_error_header error; - struct sd_interrupt_header interrupt; - struct sd_response_header response; - struct sd_offloaded_interrupt irq; - struct sd_offloaded_piggyback pig; -} __packed; - -union sd_command { - struct sd_command_header head; - struct sd_irqpoll_header poll; -} __packed; - -enum SD_RESPONSE_TYPE { - SDRT_UNSPECIFIED = 0, - SDRT_NONE, - SDRT_1, - SDRT_1B, - SDRT_2, - SDRT_3, - SDRT_4, - SDRT_5, - SDRT_5B, - SDRT_6, - SDRT_7, -}; - -#define RESPONSE_INTERRUPT 0x01 -#define RESPONSE_ERROR 0x02 -#define RESPONSE_STATUS 0x03 -#define RESPONSE_IRQ_DISABLED 0x05 -#define RESPONSE_IRQ_ENABLED 0x06 -#define RESPONSE_PIGGYBACKED 0x07 -#define RESPONSE_NO_INTERRUPT 0x08 -#define RESPONSE_PIG_DISABLED 0x09 -#define RESPONSE_PIG_ENABLED 0x0A -#define SD_ERROR_1BIT_TIMEOUT 0x01 -#define SD_ERROR_4BIT_TIMEOUT 0x02 -#define SD_ERROR_1BIT_CRC_WRONG 0x03 -#define SD_ERROR_4BIT_CRC_WRONG 0x04 -#define SD_ERROR_1BIT_CRC_ERROR 0x05 -#define SD_ERROR_4BIT_CRC_ERROR 0x06 -#define SD_ERROR_NO_CMD_ENDBIT 0x07 -#define SD_ERROR_NO_1BIT_DATEND 0x08 -#define SD_ERROR_NO_4BIT_DATEND 0x09 -#define SD_ERROR_1BIT_UNEXPECTED_TIMEOUT 0x0A -#define SD_ERROR_4BIT_UNEXPECTED_TIMEOUT 0x0B -#define SD_ERROR_ILLEGAL_COMMAND 0x0C -#define SD_ERROR_NO_DEVICE 0x0D -#define SD_ERROR_TRANSFER_LENGTH 0x0E -#define SD_ERROR_1BIT_DATA_TIMEOUT 0x0F -#define SD_ERROR_4BIT_DATA_TIMEOUT 0x10 -#define SD_ERROR_ILLEGAL_STATE 0x11 -#define SD_ERROR_UNKNOWN_ERROR 0x12 -#define SD_ERROR_RESERVED_ERROR 0x13 -#define SD_ERROR_INVALID_FUNCTION 0x14 -#define SD_ERROR_OUT_OF_RANGE 0x15 -#define SD_ERROR_STAT_CMD 0x16 -#define SD_ERROR_STAT_DATA 0x17 -#define SD_ERROR_STAT_CMD_TIMEOUT 0x18 -#define SD_ERROR_SDCRDY_STUCK 0x19 -#define SD_ERROR_UNHANDLED 0x1A -#define SD_ERROR_OVERRUN 0x1B -#define SD_ERROR_PIO_TIMEOUT 0x1C - -#define FUN(c) (0x000007 & (c->arg>>28)) -#define REG(c) (0x01FFFF & (c->arg>>9)) - -static bool limit_speed_to_24_MHz; -module_param(limit_speed_to_24_MHz, bool, 0644); -MODULE_PARM_DESC(limit_speed_to_24_MHz, "Limit Max SDIO Clock Speed to 24 MHz"); - -static bool pad_input_to_usb_pkt; -module_param(pad_input_to_usb_pkt, bool, 0644); -MODULE_PARM_DESC(pad_input_to_usb_pkt, - "Pad USB data input transfers to whole USB Packet"); - -static bool disable_offload_processing; -module_param(disable_offload_processing, bool, 0644); -MODULE_PARM_DESC(disable_offload_processing, "Disable Offload Processing"); - -static bool force_1_bit_data_xfers; -module_param(force_1_bit_data_xfers, bool, 0644); -MODULE_PARM_DESC(force_1_bit_data_xfers, - "Force SDIO Data Transfers to 1-bit Mode"); - -static bool force_polling_for_irqs; -module_param(force_polling_for_irqs, bool, 0644); -MODULE_PARM_DESC(force_polling_for_irqs, "Force Polling for SDIO interrupts"); - -static int firmware_irqpoll_timeout = 1024; -module_param(firmware_irqpoll_timeout, int, 0644); -MODULE_PARM_DESC(firmware_irqpoll_timeout, "VUB300 firmware irqpoll timeout"); - -static int force_max_req_size = 128; -module_param(force_max_req_size, int, 0644); -MODULE_PARM_DESC(force_max_req_size, "set max request size in kBytes"); - -#ifdef SMSC_DEVELOPMENT_BOARD -static int firmware_rom_wait_states = 0x04; -#else -static int firmware_rom_wait_states = 0x1C; -#endif - -module_param(firmware_rom_wait_states, int, 0644); -MODULE_PARM_DESC(firmware_rom_wait_states, - "ROM wait states byte=RRRIIEEE (Reserved Internal External)"); - -#define ELAN_VENDOR_ID 0x2201 -#define VUB300_VENDOR_ID 0x0424 -#define VUB300_PRODUCT_ID 0x012C -static const struct usb_device_id vub300_table[] = { - {USB_DEVICE(ELAN_VENDOR_ID, VUB300_PRODUCT_ID)}, - {USB_DEVICE(VUB300_VENDOR_ID, VUB300_PRODUCT_ID)}, - {} /* Terminating entry */ -}; -MODULE_DEVICE_TABLE(usb, vub300_table); - -static struct workqueue_struct *cmndworkqueue; -static struct workqueue_struct *pollworkqueue; -static struct workqueue_struct *deadworkqueue; - -static inline int interface_to_InterfaceNumber(struct usb_interface *interface) -{ - if (!interface) - return -1; - if (!interface->cur_altsetting) - return -1; - return interface->cur_altsetting->desc.bInterfaceNumber; -} - -struct sdio_register { - unsigned func_num:3; - unsigned sdio_reg:17; - unsigned activate:1; - unsigned prepared:1; - unsigned regvalue:8; - unsigned response:8; - unsigned sparebit:26; -}; - -struct vub300_mmc_host { - struct usb_device *udev; - struct usb_interface *interface; - struct kref kref; - struct mutex cmd_mutex; - struct mutex irq_mutex; - char vub_name[3 + (9 * 8) + 4 + 1]; /* max of 7 sdio fn's */ - u8 cmnd_out_ep; /* EndPoint for commands */ - u8 cmnd_res_ep; /* EndPoint for responses */ - u8 data_out_ep; /* EndPoint for out data */ - u8 data_inp_ep; /* EndPoint for inp data */ - bool card_powered; - bool card_present; - bool read_only; - bool large_usb_packets; - bool app_spec; /* ApplicationSpecific */ - bool irq_enabled; /* by the MMC CORE */ - bool irq_disabled; /* in the firmware */ - unsigned bus_width:4; - u8 total_offload_count; - u8 dynamic_register_count; - u8 resp_len; - u32 datasize; - int errors; - int usb_transport_fail; - int usb_timed_out; - int irqs_queued; - struct sdio_register sdio_register[16]; - struct offload_interrupt_function_register { -#define MAXREGBITS 4 -#define MAXREGS (1<mmc; - - usb_free_urb(vub300->command_out_urb); - vub300->command_out_urb = NULL; - usb_free_urb(vub300->command_res_urb); - vub300->command_res_urb = NULL; - usb_put_dev(vub300->udev); - mmc_free_host(mmc); - /* - * and hence also frees vub300 - * which is contained at the end of struct mmc - */ -} - -static void vub300_queue_cmnd_work(struct vub300_mmc_host *vub300) -{ - kref_get(&vub300->kref); - if (queue_work(cmndworkqueue, &vub300->cmndwork)) { - /* - * then the cmndworkqueue was not previously - * running and the above get ref is obvious - * required and will be put when the thread - * terminates by a specific call - */ - } else { - /* - * the cmndworkqueue was already running from - * a previous invocation and thus to keep the - * kref counts correct we must undo the get - */ - kref_put(&vub300->kref, vub300_delete); - } -} - -static void vub300_queue_poll_work(struct vub300_mmc_host *vub300, int delay) -{ - kref_get(&vub300->kref); - if (queue_delayed_work(pollworkqueue, &vub300->pollwork, delay)) { - /* - * then the pollworkqueue was not previously - * running and the above get ref is obvious - * required and will be put when the thread - * terminates by a specific call - */ - } else { - /* - * the pollworkqueue was already running from - * a previous invocation and thus to keep the - * kref counts correct we must undo the get - */ - kref_put(&vub300->kref, vub300_delete); - } -} - -static void vub300_queue_dead_work(struct vub300_mmc_host *vub300) -{ - kref_get(&vub300->kref); - if (queue_work(deadworkqueue, &vub300->deadwork)) { - /* - * then the deadworkqueue was not previously - * running and the above get ref is obvious - * required and will be put when the thread - * terminates by a specific call - */ - } else { - /* - * the deadworkqueue was already running from - * a previous invocation and thus to keep the - * kref counts correct we must undo the get - */ - kref_put(&vub300->kref, vub300_delete); - } -} - -static void irqpoll_res_completed(struct urb *urb) -{ /* urb completion handler - hardirq */ - struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context; - if (urb->status) - vub300->usb_transport_fail = urb->status; - complete(&vub300->irqpoll_complete); -} - -static void irqpoll_out_completed(struct urb *urb) -{ /* urb completion handler - hardirq */ - struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context; - if (urb->status) { - vub300->usb_transport_fail = urb->status; - complete(&vub300->irqpoll_complete); - return; - } else { - int ret; - unsigned int pipe = - usb_rcvbulkpipe(vub300->udev, vub300->cmnd_res_ep); - usb_fill_bulk_urb(vub300->command_res_urb, vub300->udev, pipe, - &vub300->resp, sizeof(vub300->resp), - irqpoll_res_completed, vub300); - vub300->command_res_urb->actual_length = 0; - ret = usb_submit_urb(vub300->command_res_urb, GFP_ATOMIC); - if (ret) { - vub300->usb_transport_fail = ret; - complete(&vub300->irqpoll_complete); - } - return; - } -} - -static void send_irqpoll(struct vub300_mmc_host *vub300) -{ - /* cmd_mutex is held by vub300_pollwork_thread */ - int retval; - int timeout = 0xFFFF & (0x0001FFFF - firmware_irqpoll_timeout); - vub300->cmnd.poll.header_size = 22; - vub300->cmnd.poll.header_type = 1; - vub300->cmnd.poll.port_number = 0; - vub300->cmnd.poll.command_type = 2; - vub300->cmnd.poll.poll_timeout_lsb = 0xFF & (unsigned)timeout; - vub300->cmnd.poll.poll_timeout_msb = 0xFF & (unsigned)(timeout >> 8); - usb_fill_bulk_urb(vub300->command_out_urb, vub300->udev, - usb_sndbulkpipe(vub300->udev, vub300->cmnd_out_ep) - , &vub300->cmnd, sizeof(vub300->cmnd) - , irqpoll_out_completed, vub300); - retval = usb_submit_urb(vub300->command_out_urb, GFP_KERNEL); - if (0 > retval) { - vub300->usb_transport_fail = retval; - vub300_queue_poll_work(vub300, 1); - complete(&vub300->irqpoll_complete); - return; - } else { - return; - } -} - -static void new_system_port_status(struct vub300_mmc_host *vub300) -{ - int old_card_present = vub300->card_present; - int new_card_present = - (0x0001 & vub300->system_port_status.port_flags) ? 1 : 0; - vub300->read_only = - (0x0010 & vub300->system_port_status.port_flags) ? 1 : 0; - if (new_card_present && !old_card_present) { - dev_info(&vub300->udev->dev, "card just inserted\n"); - vub300->card_present = 1; - vub300->bus_width = 0; - if (disable_offload_processing) - strscpy(vub300->vub_name, "EMPTY Processing Disabled", - sizeof(vub300->vub_name)); - else - vub300->vub_name[0] = 0; - mmc_detect_change(vub300->mmc, 1); - } else if (!new_card_present && old_card_present) { - dev_info(&vub300->udev->dev, "card just ejected\n"); - vub300->card_present = 0; - mmc_detect_change(vub300->mmc, 0); - } else { - /* no change */ - } -} - -static void __add_offloaded_reg_to_fifo(struct vub300_mmc_host *vub300, - struct offload_registers_access - *register_access, u8 func) -{ - u8 r = vub300->fn[func].offload_point + vub300->fn[func].offload_count; - memcpy(&vub300->fn[func].reg[MAXREGMASK & r], register_access, - sizeof(struct offload_registers_access)); - vub300->fn[func].offload_count += 1; - vub300->total_offload_count += 1; -} - -static void add_offloaded_reg(struct vub300_mmc_host *vub300, - struct offload_registers_access *register_access) -{ - u32 Register = ((0x03 & register_access->command_byte[0]) << 15) - | ((0xFF & register_access->command_byte[1]) << 7) - | ((0xFE & register_access->command_byte[2]) >> 1); - u8 func = ((0x70 & register_access->command_byte[0]) >> 4); - u8 regs = vub300->dynamic_register_count; - u8 i = 0; - while (0 < regs-- && 1 == vub300->sdio_register[i].activate) { - if (vub300->sdio_register[i].func_num == func && - vub300->sdio_register[i].sdio_reg == Register) { - if (vub300->sdio_register[i].prepared == 0) - vub300->sdio_register[i].prepared = 1; - vub300->sdio_register[i].response = - register_access->Respond_Byte[2]; - vub300->sdio_register[i].regvalue = - register_access->Respond_Byte[3]; - return; - } else { - i += 1; - continue; - } - } - __add_offloaded_reg_to_fifo(vub300, register_access, func); -} - -static void check_vub300_port_status(struct vub300_mmc_host *vub300) -{ - /* - * cmd_mutex is held by vub300_pollwork_thread, - * vub300_deadwork_thread or vub300_cmndwork_thread - */ - int retval; - retval = - usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0), - GET_SYSTEM_PORT_STATUS, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0x0000, 0x0000, &vub300->system_port_status, - sizeof(vub300->system_port_status), 1000); - if (sizeof(vub300->system_port_status) == retval) - new_system_port_status(vub300); -} - -static void __vub300_irqpoll_response(struct vub300_mmc_host *vub300) -{ - /* cmd_mutex is held by vub300_pollwork_thread */ - if (vub300->command_res_urb->actual_length == 0) - return; - - switch (vub300->resp.common.header_type) { - case RESPONSE_INTERRUPT: - mutex_lock(&vub300->irq_mutex); - if (vub300->irq_enabled) - mmc_signal_sdio_irq(vub300->mmc); - else - vub300->irqs_queued += 1; - vub300->irq_disabled = 1; - mutex_unlock(&vub300->irq_mutex); - break; - case RESPONSE_ERROR: - if (vub300->resp.error.error_code == SD_ERROR_NO_DEVICE) - check_vub300_port_status(vub300); - break; - case RESPONSE_STATUS: - vub300->system_port_status = vub300->resp.status; - new_system_port_status(vub300); - if (!vub300->card_present) - vub300_queue_poll_work(vub300, HZ / 5); - break; - case RESPONSE_IRQ_DISABLED: - { - int offloaded_data_length = vub300->resp.common.header_size - 3; - int register_count = offloaded_data_length >> 3; - int ri = 0; - while (register_count--) { - add_offloaded_reg(vub300, &vub300->resp.irq.reg[ri]); - ri += 1; - } - mutex_lock(&vub300->irq_mutex); - if (vub300->irq_enabled) - mmc_signal_sdio_irq(vub300->mmc); - else - vub300->irqs_queued += 1; - vub300->irq_disabled = 1; - mutex_unlock(&vub300->irq_mutex); - break; - } - case RESPONSE_IRQ_ENABLED: - { - int offloaded_data_length = vub300->resp.common.header_size - 3; - int register_count = offloaded_data_length >> 3; - int ri = 0; - while (register_count--) { - add_offloaded_reg(vub300, &vub300->resp.irq.reg[ri]); - ri += 1; - } - mutex_lock(&vub300->irq_mutex); - if (vub300->irq_enabled) - mmc_signal_sdio_irq(vub300->mmc); - else - vub300->irqs_queued += 1; - vub300->irq_disabled = 0; - mutex_unlock(&vub300->irq_mutex); - break; - } - case RESPONSE_NO_INTERRUPT: - vub300_queue_poll_work(vub300, 1); - break; - default: - break; - } -} - -static void __do_poll(struct vub300_mmc_host *vub300) -{ - /* cmd_mutex is held by vub300_pollwork_thread */ - unsigned long commretval; - mod_timer(&vub300->inactivity_timer, jiffies + HZ); - init_completion(&vub300->irqpoll_complete); - send_irqpoll(vub300); - commretval = wait_for_completion_timeout(&vub300->irqpoll_complete, - msecs_to_jiffies(500)); - if (vub300->usb_transport_fail) { - /* no need to do anything */ - } else if (commretval == 0) { - vub300->usb_timed_out = 1; - usb_kill_urb(vub300->command_out_urb); - usb_kill_urb(vub300->command_res_urb); - } else { /* commretval > 0 */ - __vub300_irqpoll_response(vub300); - } -} - -/* this thread runs only when the driver - * is trying to poll the device for an IRQ - */ -static void vub300_pollwork_thread(struct work_struct *work) -{ /* NOT irq */ - struct vub300_mmc_host *vub300 = container_of(work, - struct vub300_mmc_host, pollwork.work); - if (!vub300->interface) { - kref_put(&vub300->kref, vub300_delete); - return; - } - mutex_lock(&vub300->cmd_mutex); - if (vub300->cmd) { - vub300_queue_poll_work(vub300, 1); - } else if (!vub300->card_present) { - /* no need to do anything */ - } else { /* vub300->card_present */ - mutex_lock(&vub300->irq_mutex); - if (!vub300->irq_enabled) { - mutex_unlock(&vub300->irq_mutex); - } else if (vub300->irqs_queued) { - vub300->irqs_queued -= 1; - mmc_signal_sdio_irq(vub300->mmc); - mod_timer(&vub300->inactivity_timer, jiffies + HZ); - mutex_unlock(&vub300->irq_mutex); - } else { /* NOT vub300->irqs_queued */ - mutex_unlock(&vub300->irq_mutex); - __do_poll(vub300); - } - } - mutex_unlock(&vub300->cmd_mutex); - kref_put(&vub300->kref, vub300_delete); -} - -static void vub300_deadwork_thread(struct work_struct *work) -{ /* NOT irq */ - struct vub300_mmc_host *vub300 = - container_of(work, struct vub300_mmc_host, deadwork); - if (!vub300->interface) { - kref_put(&vub300->kref, vub300_delete); - return; - } - mutex_lock(&vub300->cmd_mutex); - if (vub300->cmd) { - /* - * a command got in as the inactivity - * timer expired - so we just let the - * processing of the command show if - * the device is dead - */ - } else if (vub300->card_present) { - check_vub300_port_status(vub300); - } else if (vub300->mmc && vub300->mmc->card) { - /* - * the MMC core must not have responded - * to the previous indication - lets - * hope that it eventually does so we - * will just ignore this for now - */ - } else { - check_vub300_port_status(vub300); - } - mod_timer(&vub300->inactivity_timer, jiffies + HZ); - mutex_unlock(&vub300->cmd_mutex); - kref_put(&vub300->kref, vub300_delete); -} - -static void vub300_inactivity_timer_expired(struct timer_list *t) -{ /* softirq */ - struct vub300_mmc_host *vub300 = timer_container_of(vub300, t, - inactivity_timer); - if (!vub300->interface) { - kref_put(&vub300->kref, vub300_delete); - } else if (vub300->cmd) { - mod_timer(&vub300->inactivity_timer, jiffies + HZ); - } else { - vub300_queue_dead_work(vub300); - mod_timer(&vub300->inactivity_timer, jiffies + HZ); - } -} - -static int vub300_response_error(u8 error_code) -{ - switch (error_code) { - case SD_ERROR_PIO_TIMEOUT: - case SD_ERROR_1BIT_TIMEOUT: - case SD_ERROR_4BIT_TIMEOUT: - return -ETIMEDOUT; - case SD_ERROR_STAT_DATA: - case SD_ERROR_OVERRUN: - case SD_ERROR_STAT_CMD: - case SD_ERROR_STAT_CMD_TIMEOUT: - case SD_ERROR_SDCRDY_STUCK: - case SD_ERROR_UNHANDLED: - case SD_ERROR_1BIT_CRC_WRONG: - case SD_ERROR_4BIT_CRC_WRONG: - case SD_ERROR_1BIT_CRC_ERROR: - case SD_ERROR_4BIT_CRC_ERROR: - case SD_ERROR_NO_CMD_ENDBIT: - case SD_ERROR_NO_1BIT_DATEND: - case SD_ERROR_NO_4BIT_DATEND: - case SD_ERROR_1BIT_DATA_TIMEOUT: - case SD_ERROR_4BIT_DATA_TIMEOUT: - case SD_ERROR_1BIT_UNEXPECTED_TIMEOUT: - case SD_ERROR_4BIT_UNEXPECTED_TIMEOUT: - return -EILSEQ; - case 33: - return -EILSEQ; - case SD_ERROR_ILLEGAL_COMMAND: - return -EINVAL; - case SD_ERROR_NO_DEVICE: - return -ENOMEDIUM; - default: - return -ENODEV; - } -} - -static void command_res_completed(struct urb *urb) -{ /* urb completion handler - hardirq */ - struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context; - if (urb->status) { - /* we have to let the initiator handle the error */ - } else if (vub300->command_res_urb->actual_length == 0) { - /* - * we have seen this happen once or twice and - * we suspect a buggy USB host controller - */ - } else if (!vub300->data) { - /* this means that the command (typically CMD52) succeeded */ - } else if (vub300->resp.common.header_type != 0x02) { - /* - * this is an error response from the VUB300 chip - * and we let the initiator handle it - */ - } else if (vub300->urb) { - vub300->cmd->error = - vub300_response_error(vub300->resp.error.error_code); - usb_unlink_urb(vub300->urb); - } else { - vub300->cmd->error = - vub300_response_error(vub300->resp.error.error_code); - usb_sg_cancel(&vub300->sg_request); - } - complete(&vub300->command_complete); /* got_response_in */ -} - -static void command_out_completed(struct urb *urb) -{ /* urb completion handler - hardirq */ - struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context; - if (urb->status) { - complete(&vub300->command_complete); - } else { - int ret; - unsigned int pipe = - usb_rcvbulkpipe(vub300->udev, vub300->cmnd_res_ep); - usb_fill_bulk_urb(vub300->command_res_urb, vub300->udev, pipe, - &vub300->resp, sizeof(vub300->resp), - command_res_completed, vub300); - vub300->command_res_urb->actual_length = 0; - ret = usb_submit_urb(vub300->command_res_urb, GFP_ATOMIC); - if (ret == 0) { - /* - * the urb completion handler will call - * our completion handler - */ - } else { - /* - * and thus we only call it directly - * when it will not be called - */ - complete(&vub300->command_complete); - } - } -} - -/* - * the STUFF bits are masked out for the comparisons - */ -static void snoop_block_size_and_bus_width(struct vub300_mmc_host *vub300, - u32 cmd_arg) -{ - if ((0xFBFFFE00 & cmd_arg) == 0x80022200) - vub300->fbs[1] = (cmd_arg << 8) | (0x00FF & vub300->fbs[1]); - else if ((0xFBFFFE00 & cmd_arg) == 0x80022000) - vub300->fbs[1] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[1]); - else if ((0xFBFFFE00 & cmd_arg) == 0x80042200) - vub300->fbs[2] = (cmd_arg << 8) | (0x00FF & vub300->fbs[2]); - else if ((0xFBFFFE00 & cmd_arg) == 0x80042000) - vub300->fbs[2] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[2]); - else if ((0xFBFFFE00 & cmd_arg) == 0x80062200) - vub300->fbs[3] = (cmd_arg << 8) | (0x00FF & vub300->fbs[3]); - else if ((0xFBFFFE00 & cmd_arg) == 0x80062000) - vub300->fbs[3] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[3]); - else if ((0xFBFFFE00 & cmd_arg) == 0x80082200) - vub300->fbs[4] = (cmd_arg << 8) | (0x00FF & vub300->fbs[4]); - else if ((0xFBFFFE00 & cmd_arg) == 0x80082000) - vub300->fbs[4] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[4]); - else if ((0xFBFFFE00 & cmd_arg) == 0x800A2200) - vub300->fbs[5] = (cmd_arg << 8) | (0x00FF & vub300->fbs[5]); - else if ((0xFBFFFE00 & cmd_arg) == 0x800A2000) - vub300->fbs[5] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[5]); - else if ((0xFBFFFE00 & cmd_arg) == 0x800C2200) - vub300->fbs[6] = (cmd_arg << 8) | (0x00FF & vub300->fbs[6]); - else if ((0xFBFFFE00 & cmd_arg) == 0x800C2000) - vub300->fbs[6] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[6]); - else if ((0xFBFFFE00 & cmd_arg) == 0x800E2200) - vub300->fbs[7] = (cmd_arg << 8) | (0x00FF & vub300->fbs[7]); - else if ((0xFBFFFE00 & cmd_arg) == 0x800E2000) - vub300->fbs[7] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[7]); - else if ((0xFBFFFE03 & cmd_arg) == 0x80000E00) - vub300->bus_width = 1; - else if ((0xFBFFFE03 & cmd_arg) == 0x80000E02) - vub300->bus_width = 4; -} - -static void send_command(struct vub300_mmc_host *vub300) -{ - /* cmd_mutex is held by vub300_cmndwork_thread */ - struct mmc_command *cmd = vub300->cmd; - struct mmc_data *data = vub300->data; - int retval; - int i; - u8 response_type; - if (vub300->app_spec) { - switch (cmd->opcode) { - case 6: - response_type = SDRT_1; - vub300->resp_len = 6; - if (0x00000000 == (0x00000003 & cmd->arg)) - vub300->bus_width = 1; - else if (0x00000002 == (0x00000003 & cmd->arg)) - vub300->bus_width = 4; - else - dev_err(&vub300->udev->dev, - "unexpected ACMD6 bus_width=%d\n", - 0x00000003 & cmd->arg); - break; - case 13: - response_type = SDRT_1; - vub300->resp_len = 6; - break; - case 22: - response_type = SDRT_1; - vub300->resp_len = 6; - break; - case 23: - response_type = SDRT_1; - vub300->resp_len = 6; - break; - case 41: - response_type = SDRT_3; - vub300->resp_len = 6; - break; - case 42: - response_type = SDRT_1; - vub300->resp_len = 6; - break; - case 51: - response_type = SDRT_1; - vub300->resp_len = 6; - break; - case 55: - response_type = SDRT_1; - vub300->resp_len = 6; - break; - default: - vub300->resp_len = 0; - cmd->error = -EINVAL; - complete(&vub300->command_complete); - return; - } - vub300->app_spec = 0; - } else { - switch (cmd->opcode) { - case 0: - response_type = SDRT_NONE; - vub300->resp_len = 0; - break; - case 1: - response_type = SDRT_3; - vub300->resp_len = 6; - break; - case 2: - response_type = SDRT_2; - vub300->resp_len = 17; - break; - case 3: - response_type = SDRT_6; - vub300->resp_len = 6; - break; - case 4: - response_type = SDRT_NONE; - vub300->resp_len = 0; - break; - case 5: - response_type = SDRT_4; - vub300->resp_len = 6; - break; - case 6: - response_type = SDRT_1; - vub300->resp_len = 6; - break; - case 7: - response_type = SDRT_1B; - vub300->resp_len = 6; - break; - case 8: - response_type = SDRT_7; - vub300->resp_len = 6; - break; - case 9: - response_type = SDRT_2; - vub300->resp_len = 17; - break; - case 10: - response_type = SDRT_2; - vub300->resp_len = 17; - break; - case 12: - response_type = SDRT_1B; - vub300->resp_len = 6; - break; - case 13: - response_type = SDRT_1; - vub300->resp_len = 6; - break; - case 15: - response_type = SDRT_NONE; - vub300->resp_len = 0; - break; - case 16: - for (i = 0; i < ARRAY_SIZE(vub300->fbs); i++) - vub300->fbs[i] = 0xFFFF & cmd->arg; - response_type = SDRT_1; - vub300->resp_len = 6; - break; - case 17: - case 18: - case 24: - case 25: - case 27: - response_type = SDRT_1; - vub300->resp_len = 6; - break; - case 28: - case 29: - response_type = SDRT_1B; - vub300->resp_len = 6; - break; - case 30: - case 32: - case 33: - response_type = SDRT_1; - vub300->resp_len = 6; - break; - case 38: - response_type = SDRT_1B; - vub300->resp_len = 6; - break; - case 42: - response_type = SDRT_1; - vub300->resp_len = 6; - break; - case 52: - response_type = SDRT_5; - vub300->resp_len = 6; - snoop_block_size_and_bus_width(vub300, cmd->arg); - break; - case 53: - response_type = SDRT_5; - vub300->resp_len = 6; - break; - case 55: - response_type = SDRT_1; - vub300->resp_len = 6; - vub300->app_spec = 1; - break; - case 56: - response_type = SDRT_1; - vub300->resp_len = 6; - break; - default: - vub300->resp_len = 0; - cmd->error = -EINVAL; - complete(&vub300->command_complete); - return; - } - } - /* - * it is a shame that we can not use "sizeof(struct sd_command_header)" - * this is because the packet _must_ be padded to 64 bytes - */ - vub300->cmnd.head.header_size = 20; - vub300->cmnd.head.header_type = 0x00; - vub300->cmnd.head.port_number = 0; /* "0" means port 1 */ - vub300->cmnd.head.command_type = 0x00; /* standard read command */ - vub300->cmnd.head.response_type = response_type; - vub300->cmnd.head.command_index = cmd->opcode; - vub300->cmnd.head.arguments[0] = cmd->arg >> 24; - vub300->cmnd.head.arguments[1] = cmd->arg >> 16; - vub300->cmnd.head.arguments[2] = cmd->arg >> 8; - vub300->cmnd.head.arguments[3] = cmd->arg >> 0; - if (cmd->opcode == 52) { - int fn = 0x7 & (cmd->arg >> 28); - vub300->cmnd.head.block_count[0] = 0; - vub300->cmnd.head.block_count[1] = 0; - vub300->cmnd.head.block_size[0] = (vub300->fbs[fn] >> 8) & 0xFF; - vub300->cmnd.head.block_size[1] = (vub300->fbs[fn] >> 0) & 0xFF; - vub300->cmnd.head.command_type = 0x00; - vub300->cmnd.head.transfer_size[0] = 0; - vub300->cmnd.head.transfer_size[1] = 0; - vub300->cmnd.head.transfer_size[2] = 0; - vub300->cmnd.head.transfer_size[3] = 0; - } else if (!data) { - vub300->cmnd.head.block_count[0] = 0; - vub300->cmnd.head.block_count[1] = 0; - vub300->cmnd.head.block_size[0] = (vub300->fbs[0] >> 8) & 0xFF; - vub300->cmnd.head.block_size[1] = (vub300->fbs[0] >> 0) & 0xFF; - vub300->cmnd.head.command_type = 0x00; - vub300->cmnd.head.transfer_size[0] = 0; - vub300->cmnd.head.transfer_size[1] = 0; - vub300->cmnd.head.transfer_size[2] = 0; - vub300->cmnd.head.transfer_size[3] = 0; - } else if (cmd->opcode == 53) { - int fn = 0x7 & (cmd->arg >> 28); - if (0x08 & vub300->cmnd.head.arguments[0]) { /* BLOCK MODE */ - vub300->cmnd.head.block_count[0] = - (data->blocks >> 8) & 0xFF; - vub300->cmnd.head.block_count[1] = - (data->blocks >> 0) & 0xFF; - vub300->cmnd.head.block_size[0] = - (data->blksz >> 8) & 0xFF; - vub300->cmnd.head.block_size[1] = - (data->blksz >> 0) & 0xFF; - } else { /* BYTE MODE */ - vub300->cmnd.head.block_count[0] = 0; - vub300->cmnd.head.block_count[1] = 0; - vub300->cmnd.head.block_size[0] = - (vub300->datasize >> 8) & 0xFF; - vub300->cmnd.head.block_size[1] = - (vub300->datasize >> 0) & 0xFF; - } - vub300->cmnd.head.command_type = - (MMC_DATA_READ & data->flags) ? 0x00 : 0x80; - vub300->cmnd.head.transfer_size[0] = - (vub300->datasize >> 24) & 0xFF; - vub300->cmnd.head.transfer_size[1] = - (vub300->datasize >> 16) & 0xFF; - vub300->cmnd.head.transfer_size[2] = - (vub300->datasize >> 8) & 0xFF; - vub300->cmnd.head.transfer_size[3] = - (vub300->datasize >> 0) & 0xFF; - if (vub300->datasize < vub300->fbs[fn]) { - vub300->cmnd.head.block_count[0] = 0; - vub300->cmnd.head.block_count[1] = 0; - } - } else { - vub300->cmnd.head.block_count[0] = (data->blocks >> 8) & 0xFF; - vub300->cmnd.head.block_count[1] = (data->blocks >> 0) & 0xFF; - vub300->cmnd.head.block_size[0] = (data->blksz >> 8) & 0xFF; - vub300->cmnd.head.block_size[1] = (data->blksz >> 0) & 0xFF; - vub300->cmnd.head.command_type = - (MMC_DATA_READ & data->flags) ? 0x00 : 0x80; - vub300->cmnd.head.transfer_size[0] = - (vub300->datasize >> 24) & 0xFF; - vub300->cmnd.head.transfer_size[1] = - (vub300->datasize >> 16) & 0xFF; - vub300->cmnd.head.transfer_size[2] = - (vub300->datasize >> 8) & 0xFF; - vub300->cmnd.head.transfer_size[3] = - (vub300->datasize >> 0) & 0xFF; - if (vub300->datasize < vub300->fbs[0]) { - vub300->cmnd.head.block_count[0] = 0; - vub300->cmnd.head.block_count[1] = 0; - } - } - if (vub300->cmnd.head.block_size[0] || vub300->cmnd.head.block_size[1]) { - u16 block_size = vub300->cmnd.head.block_size[1] | - (vub300->cmnd.head.block_size[0] << 8); - u16 block_boundary = FIRMWARE_BLOCK_BOUNDARY - - (FIRMWARE_BLOCK_BOUNDARY % block_size); - vub300->cmnd.head.block_boundary[0] = - (block_boundary >> 8) & 0xFF; - vub300->cmnd.head.block_boundary[1] = - (block_boundary >> 0) & 0xFF; - } else { - vub300->cmnd.head.block_boundary[0] = 0; - vub300->cmnd.head.block_boundary[1] = 0; - } - usb_fill_bulk_urb(vub300->command_out_urb, vub300->udev, - usb_sndbulkpipe(vub300->udev, vub300->cmnd_out_ep), - &vub300->cmnd, sizeof(vub300->cmnd), - command_out_completed, vub300); - retval = usb_submit_urb(vub300->command_out_urb, GFP_KERNEL); - if (retval < 0) { - cmd->error = retval; - complete(&vub300->command_complete); - return; - } else { - return; - } -} - -/* - * timer callback runs in atomic mode - * so it cannot call usb_kill_urb() - */ -static void vub300_sg_timed_out(struct timer_list *t) -{ - struct vub300_mmc_host *vub300 = timer_container_of(vub300, t, - sg_transfer_timer); - vub300->usb_timed_out = 1; - usb_sg_cancel(&vub300->sg_request); - usb_unlink_urb(vub300->command_out_urb); - usb_unlink_urb(vub300->command_res_urb); -} - -static u16 roundup_to_multiple_of_64(u16 number) -{ - return 0xFFC0 & (0x3F + number); -} - -/* - * this is a separate function to solve the 80 column width restriction - */ -static void __download_offload_pseudocode(struct vub300_mmc_host *vub300, - const struct firmware *fw) -{ - u8 register_count = 0; - u16 ts = 0; - u16 interrupt_size = 0; - const u8 *data = fw->data; - int size = fw->size; - u8 c; - dev_info(&vub300->udev->dev, "using %s for SDIO offload processing\n", - vub300->vub_name); - do { - c = *data++; - } while (size-- && c); /* skip comment */ - dev_info(&vub300->udev->dev, "using offload firmware %s %s\n", fw->data, - vub300->vub_name); - if (size < 4) { - dev_err(&vub300->udev->dev, - "corrupt offload pseudocode in firmware %s\n", - vub300->vub_name); - strscpy(vub300->vub_name, "corrupt offload pseudocode", - sizeof(vub300->vub_name)); - return; - } - interrupt_size += *data++; - size -= 1; - interrupt_size <<= 8; - interrupt_size += *data++; - size -= 1; - if (interrupt_size < size) { - u16 xfer_length = roundup_to_multiple_of_64(interrupt_size); - u8 *xfer_buffer = kmalloc(xfer_length, GFP_KERNEL); - if (xfer_buffer) { - int retval; - memcpy(xfer_buffer, data, interrupt_size); - memset(xfer_buffer + interrupt_size, 0, - xfer_length - interrupt_size); - size -= interrupt_size; - data += interrupt_size; - retval = - usb_control_msg(vub300->udev, - usb_sndctrlpipe(vub300->udev, 0), - SET_INTERRUPT_PSEUDOCODE, - USB_DIR_OUT | USB_TYPE_VENDOR | - USB_RECIP_DEVICE, 0x0000, 0x0000, - xfer_buffer, xfer_length, 1000); - kfree(xfer_buffer); - if (retval < 0) - goto copy_error_message; - } else { - dev_err(&vub300->udev->dev, - "not enough memory for xfer buffer to send" - " INTERRUPT_PSEUDOCODE for %s %s\n", fw->data, - vub300->vub_name); - strscpy(vub300->vub_name, - "SDIO interrupt pseudocode download failed", - sizeof(vub300->vub_name)); - return; - } - } else { - dev_err(&vub300->udev->dev, - "corrupt interrupt pseudocode in firmware %s %s\n", - fw->data, vub300->vub_name); - strscpy(vub300->vub_name, "corrupt interrupt pseudocode", - sizeof(vub300->vub_name)); - return; - } - ts += *data++; - size -= 1; - ts <<= 8; - ts += *data++; - size -= 1; - if (ts < size) { - u16 xfer_length = roundup_to_multiple_of_64(ts); - u8 *xfer_buffer = kmalloc(xfer_length, GFP_KERNEL); - if (xfer_buffer) { - int retval; - memcpy(xfer_buffer, data, ts); - memset(xfer_buffer + ts, 0, - xfer_length - ts); - size -= ts; - data += ts; - retval = - usb_control_msg(vub300->udev, - usb_sndctrlpipe(vub300->udev, 0), - SET_TRANSFER_PSEUDOCODE, - USB_DIR_OUT | USB_TYPE_VENDOR | - USB_RECIP_DEVICE, 0x0000, 0x0000, - xfer_buffer, xfer_length, 1000); - kfree(xfer_buffer); - if (retval < 0) - goto copy_error_message; - } else { - dev_err(&vub300->udev->dev, - "not enough memory for xfer buffer to send" - " TRANSFER_PSEUDOCODE for %s %s\n", fw->data, - vub300->vub_name); - strscpy(vub300->vub_name, - "SDIO transfer pseudocode download failed", - sizeof(vub300->vub_name)); - return; - } - } else { - dev_err(&vub300->udev->dev, - "corrupt transfer pseudocode in firmware %s %s\n", - fw->data, vub300->vub_name); - strscpy(vub300->vub_name, "corrupt transfer pseudocode", - sizeof(vub300->vub_name)); - return; - } - register_count += *data++; - size -= 1; - if (register_count * 4 == size) { - int I = vub300->dynamic_register_count = register_count; - int i = 0; - while (I--) { - unsigned int func_num = 0; - vub300->sdio_register[i].func_num = *data++; - size -= 1; - func_num += *data++; - size -= 1; - func_num <<= 8; - func_num += *data++; - size -= 1; - func_num <<= 8; - func_num += *data++; - size -= 1; - vub300->sdio_register[i].sdio_reg = func_num; - vub300->sdio_register[i].activate = 1; - vub300->sdio_register[i].prepared = 0; - i += 1; - } - dev_info(&vub300->udev->dev, - "initialized %d dynamic pseudocode registers\n", - vub300->dynamic_register_count); - return; - } else { - dev_err(&vub300->udev->dev, - "corrupt dynamic registers in firmware %s\n", - vub300->vub_name); - strscpy(vub300->vub_name, "corrupt dynamic registers", - sizeof(vub300->vub_name)); - return; - } - -copy_error_message: - strscpy(vub300->vub_name, "SDIO pseudocode download failed", - sizeof(vub300->vub_name)); -} - -/* - * if the binary containing the EMPTY PseudoCode can not be found - * vub300->vub_name is set anyway in order to prevent an automatic retry - */ -static void download_offload_pseudocode(struct vub300_mmc_host *vub300) -{ - struct mmc_card *card = vub300->mmc->card; - int sdio_funcs = card->sdio_funcs; - const struct firmware *fw = NULL; - int l = snprintf(vub300->vub_name, sizeof(vub300->vub_name), - "vub_%04X%04X", card->cis.vendor, card->cis.device); - int n = 0; - int retval; - for (n = 0; n < sdio_funcs; n++) { - struct sdio_func *sf = card->sdio_func[n]; - l += scnprintf(vub300->vub_name + l, - sizeof(vub300->vub_name) - l, "_%04X%04X", - sf->vendor, sf->device); - } - snprintf(vub300->vub_name + l, sizeof(vub300->vub_name) - l, ".bin"); - dev_info(&vub300->udev->dev, "requesting offload firmware %s\n", - vub300->vub_name); - retval = request_firmware(&fw, vub300->vub_name, &card->dev); - if (retval < 0) { - strscpy(vub300->vub_name, "vub_default.bin", - sizeof(vub300->vub_name)); - retval = request_firmware(&fw, vub300->vub_name, &card->dev); - if (retval < 0) { - strscpy(vub300->vub_name, - "no SDIO offload firmware found", - sizeof(vub300->vub_name)); - } else { - __download_offload_pseudocode(vub300, fw); - release_firmware(fw); - } - } else { - __download_offload_pseudocode(vub300, fw); - release_firmware(fw); - } -} - -static void vub300_usb_bulk_msg_completion(struct urb *urb) -{ /* urb completion handler - hardirq */ - complete((struct completion *)urb->context); -} - -static int vub300_usb_bulk_msg(struct vub300_mmc_host *vub300, - unsigned int pipe, void *data, int len, - int *actual_length, int timeout_msecs) -{ - /* cmd_mutex is held by vub300_cmndwork_thread */ - struct usb_device *usb_dev = vub300->udev; - struct completion done; - int retval; - vub300->urb = usb_alloc_urb(0, GFP_KERNEL); - if (!vub300->urb) - return -ENOMEM; - usb_fill_bulk_urb(vub300->urb, usb_dev, pipe, data, len, - vub300_usb_bulk_msg_completion, NULL); - init_completion(&done); - vub300->urb->context = &done; - vub300->urb->actual_length = 0; - retval = usb_submit_urb(vub300->urb, GFP_KERNEL); - if (unlikely(retval)) - goto out; - if (!wait_for_completion_timeout - (&done, msecs_to_jiffies(timeout_msecs))) { - retval = -ETIMEDOUT; - usb_kill_urb(vub300->urb); - } else { - retval = vub300->urb->status; - } -out: - *actual_length = vub300->urb->actual_length; - usb_free_urb(vub300->urb); - vub300->urb = NULL; - return retval; -} - -static int __command_read_data(struct vub300_mmc_host *vub300, - struct mmc_command *cmd, struct mmc_data *data) -{ - /* cmd_mutex is held by vub300_cmndwork_thread */ - int linear_length = vub300->datasize; - int padded_length = vub300->large_usb_packets ? - ((511 + linear_length) >> 9) << 9 : - ((63 + linear_length) >> 6) << 6; - if ((padded_length == linear_length) || !pad_input_to_usb_pkt) { - int result; - unsigned pipe; - pipe = usb_rcvbulkpipe(vub300->udev, vub300->data_inp_ep); - result = usb_sg_init(&vub300->sg_request, vub300->udev, - pipe, 0, data->sg, - data->sg_len, 0, GFP_KERNEL); - if (result < 0) { - usb_unlink_urb(vub300->command_out_urb); - usb_unlink_urb(vub300->command_res_urb); - cmd->error = result; - data->bytes_xfered = 0; - return 0; - } else { - vub300->sg_transfer_timer.expires = - jiffies + msecs_to_jiffies(2000 + - (linear_length / 16384)); - add_timer(&vub300->sg_transfer_timer); - usb_sg_wait(&vub300->sg_request); - timer_delete(&vub300->sg_transfer_timer); - if (vub300->sg_request.status < 0) { - cmd->error = vub300->sg_request.status; - data->bytes_xfered = 0; - return 0; - } else { - data->bytes_xfered = vub300->datasize; - return linear_length; - } - } - } else { - u8 *buf = kmalloc(padded_length, GFP_KERNEL); - if (buf) { - int result; - unsigned pipe = usb_rcvbulkpipe(vub300->udev, - vub300->data_inp_ep); - int actual_length = 0; - result = vub300_usb_bulk_msg(vub300, pipe, buf, - padded_length, &actual_length, - 2000 + (padded_length / 16384)); - if (result < 0) { - cmd->error = result; - data->bytes_xfered = 0; - kfree(buf); - return 0; - } else if (actual_length < linear_length) { - cmd->error = -EREMOTEIO; - data->bytes_xfered = 0; - kfree(buf); - return 0; - } else { - sg_copy_from_buffer(data->sg, data->sg_len, buf, - linear_length); - kfree(buf); - data->bytes_xfered = vub300->datasize; - return linear_length; - } - } else { - cmd->error = -ENOMEM; - data->bytes_xfered = 0; - return 0; - } - } -} - -static int __command_write_data(struct vub300_mmc_host *vub300, - struct mmc_command *cmd, struct mmc_data *data) -{ - /* cmd_mutex is held by vub300_cmndwork_thread */ - unsigned pipe = usb_sndbulkpipe(vub300->udev, vub300->data_out_ep); - int linear_length = vub300->datasize; - int modulo_64_length = linear_length & 0x003F; - int modulo_512_length = linear_length & 0x01FF; - if (linear_length < 64) { - int result; - int actual_length; - sg_copy_to_buffer(data->sg, data->sg_len, - vub300->padded_buffer, - sizeof(vub300->padded_buffer)); - memset(vub300->padded_buffer + linear_length, 0, - sizeof(vub300->padded_buffer) - linear_length); - result = vub300_usb_bulk_msg(vub300, pipe, vub300->padded_buffer, - sizeof(vub300->padded_buffer), - &actual_length, 2000 + - (sizeof(vub300->padded_buffer) / - 16384)); - if (result < 0) { - cmd->error = result; - data->bytes_xfered = 0; - } else { - data->bytes_xfered = vub300->datasize; - } - } else if ((!vub300->large_usb_packets && (0 < modulo_64_length)) || - (vub300->large_usb_packets && (64 > modulo_512_length)) - ) { /* don't you just love these work-rounds */ - int padded_length = ((63 + linear_length) >> 6) << 6; - u8 *buf = kmalloc(padded_length, GFP_KERNEL); - if (buf) { - int result; - int actual_length; - sg_copy_to_buffer(data->sg, data->sg_len, buf, - padded_length); - memset(buf + linear_length, 0, - padded_length - linear_length); - result = - vub300_usb_bulk_msg(vub300, pipe, buf, - padded_length, &actual_length, - 2000 + padded_length / 16384); - kfree(buf); - if (result < 0) { - cmd->error = result; - data->bytes_xfered = 0; - } else { - data->bytes_xfered = vub300->datasize; - } - } else { - cmd->error = -ENOMEM; - data->bytes_xfered = 0; - } - } else { /* no data padding required */ - int result; - unsigned char buf[64 * 4]; - sg_copy_to_buffer(data->sg, data->sg_len, buf, sizeof(buf)); - result = usb_sg_init(&vub300->sg_request, vub300->udev, - pipe, 0, data->sg, - data->sg_len, 0, GFP_KERNEL); - if (result < 0) { - usb_unlink_urb(vub300->command_out_urb); - usb_unlink_urb(vub300->command_res_urb); - cmd->error = result; - data->bytes_xfered = 0; - } else { - vub300->sg_transfer_timer.expires = - jiffies + msecs_to_jiffies(2000 + - linear_length / 16384); - add_timer(&vub300->sg_transfer_timer); - usb_sg_wait(&vub300->sg_request); - if (cmd->error) { - data->bytes_xfered = 0; - } else { - timer_delete(&vub300->sg_transfer_timer); - if (vub300->sg_request.status < 0) { - cmd->error = vub300->sg_request.status; - data->bytes_xfered = 0; - } else { - data->bytes_xfered = vub300->datasize; - } - } - } - } - return linear_length; -} - -static bool __vub300_command_response(struct vub300_mmc_host *vub300, - struct mmc_command *cmd, - struct mmc_data *data, int data_length) -{ - /* cmd_mutex is held by vub300_cmndwork_thread */ - long respretval; - int msec_timeout = 1000 + data_length / 4; - respretval = - wait_for_completion_timeout(&vub300->command_complete, - msecs_to_jiffies(msec_timeout)); - if (respretval == 0) { /* TIMED OUT */ - /* we don't know which of "out" and "res" if any failed */ - vub300->usb_timed_out = 1; - usb_kill_urb(vub300->command_out_urb); - usb_kill_urb(vub300->command_res_urb); - cmd->error = -ETIMEDOUT; - return true; - } else if (respretval < 0) { - /* we don't know which of "out" and "res" if any failed */ - usb_kill_urb(vub300->command_out_urb); - usb_kill_urb(vub300->command_res_urb); - cmd->error = respretval; - } else if (cmd->error) { - /* - * the error occurred sending the command - * or receiving the response - */ - } else if (vub300->command_out_urb->status) { - vub300->usb_transport_fail = vub300->command_out_urb->status; - cmd->error = -EPROTO == vub300->command_out_urb->status ? - -ESHUTDOWN : vub300->command_out_urb->status; - } else if (vub300->command_res_urb->status) { - vub300->usb_transport_fail = vub300->command_res_urb->status; - cmd->error = -EPROTO == vub300->command_res_urb->status ? - -ESHUTDOWN : vub300->command_res_urb->status; - } else if (vub300->resp.common.header_type == 0x00) { - /* - * the command completed successfully - * and there was no piggybacked data - */ - } else if (vub300->resp.common.header_type == RESPONSE_ERROR) { - cmd->error = - vub300_response_error(vub300->resp.error.error_code); - if (vub300->data) - usb_sg_cancel(&vub300->sg_request); - } else if (vub300->resp.common.header_type == RESPONSE_PIGGYBACKED) { - int offloaded_data_length = - vub300->resp.common.header_size - - sizeof(struct sd_register_header); - int register_count = offloaded_data_length >> 3; - int ri = 0; - while (register_count--) { - add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]); - ri += 1; - } - vub300->resp.common.header_size = - sizeof(struct sd_register_header); - vub300->resp.common.header_type = 0x00; - cmd->error = 0; - } else if (vub300->resp.common.header_type == RESPONSE_PIG_DISABLED) { - int offloaded_data_length = - vub300->resp.common.header_size - - sizeof(struct sd_register_header); - int register_count = offloaded_data_length >> 3; - int ri = 0; - while (register_count--) { - add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]); - ri += 1; - } - mutex_lock(&vub300->irq_mutex); - if (vub300->irqs_queued) { - vub300->irqs_queued += 1; - } else if (vub300->irq_enabled) { - vub300->irqs_queued += 1; - vub300_queue_poll_work(vub300, 0); - } else { - vub300->irqs_queued += 1; - } - vub300->irq_disabled = 1; - mutex_unlock(&vub300->irq_mutex); - vub300->resp.common.header_size = - sizeof(struct sd_register_header); - vub300->resp.common.header_type = 0x00; - cmd->error = 0; - } else if (vub300->resp.common.header_type == RESPONSE_PIG_ENABLED) { - int offloaded_data_length = - vub300->resp.common.header_size - - sizeof(struct sd_register_header); - int register_count = offloaded_data_length >> 3; - int ri = 0; - while (register_count--) { - add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]); - ri += 1; - } - mutex_lock(&vub300->irq_mutex); - if (vub300->irqs_queued) { - vub300->irqs_queued += 1; - } else if (vub300->irq_enabled) { - vub300->irqs_queued += 1; - vub300_queue_poll_work(vub300, 0); - } else { - vub300->irqs_queued += 1; - } - vub300->irq_disabled = 0; - mutex_unlock(&vub300->irq_mutex); - vub300->resp.common.header_size = - sizeof(struct sd_register_header); - vub300->resp.common.header_type = 0x00; - cmd->error = 0; - } else { - cmd->error = -EINVAL; - } - - return false; -} - -static void construct_request_response(struct vub300_mmc_host *vub300, - struct mmc_command *cmd) -{ - int resp_len = vub300->resp_len; - int less_cmd = (17 == resp_len) ? resp_len : resp_len - 1; - int bytes = 3 & less_cmd; - int words = less_cmd >> 2; - u8 *r = vub300->resp.response.command_response; - - if (!resp_len) - return; - if (bytes == 3) { - cmd->resp[words] = (r[1 + (words << 2)] << 24) - | (r[2 + (words << 2)] << 16) - | (r[3 + (words << 2)] << 8); - } else if (bytes == 2) { - cmd->resp[words] = (r[1 + (words << 2)] << 24) - | (r[2 + (words << 2)] << 16); - } else if (bytes == 1) { - cmd->resp[words] = (r[1 + (words << 2)] << 24); - } - while (words-- > 0) { - cmd->resp[words] = (r[1 + (words << 2)] << 24) - | (r[2 + (words << 2)] << 16) - | (r[3 + (words << 2)] << 8) - | (r[4 + (words << 2)] << 0); - } - if ((cmd->opcode == 53) && (0x000000FF & cmd->resp[0])) - cmd->resp[0] &= 0xFFFFFF00; -} - -/* this thread runs only when there is an upper level command req outstanding */ -static void vub300_cmndwork_thread(struct work_struct *work) -{ - struct vub300_mmc_host *vub300 = - container_of(work, struct vub300_mmc_host, cmndwork); - if (!vub300->interface) { - kref_put(&vub300->kref, vub300_delete); - return; - } else { - struct mmc_request *req = vub300->req; - struct mmc_command *cmd = vub300->cmd; - struct mmc_data *data = vub300->data; - bool reset_device; - int data_length; - mutex_lock(&vub300->cmd_mutex); - init_completion(&vub300->command_complete); - if (likely(vub300->vub_name[0]) || !vub300->mmc->card) { - /* - * the name of the EMPTY Pseudo firmware file - * is used as a flag to indicate that the file - * has been already downloaded to the VUB300 chip - */ - } else if (0 == vub300->mmc->card->sdio_funcs) { - strscpy(vub300->vub_name, "SD memory device", - sizeof(vub300->vub_name)); - } else { - download_offload_pseudocode(vub300); - } - send_command(vub300); - if (!data) - data_length = 0; - else if (MMC_DATA_READ & data->flags) - data_length = __command_read_data(vub300, cmd, data); - else - data_length = __command_write_data(vub300, cmd, data); - reset_device = __vub300_command_response(vub300, cmd, - data, data_length); - vub300->req = NULL; - vub300->cmd = NULL; - vub300->data = NULL; - if (cmd->error) { - if (cmd->error == -ENOMEDIUM) - check_vub300_port_status(vub300); - mutex_unlock(&vub300->cmd_mutex); - if (reset_device) { - int result; - - result = usb_lock_device_for_reset(vub300->udev, - vub300->interface); - if (result == 0) { - result = usb_reset_device(vub300->udev); - usb_unlock_device(vub300->udev); - } - } - mmc_request_done(vub300->mmc, req); - kref_put(&vub300->kref, vub300_delete); - return; - } else { - construct_request_response(vub300, cmd); - vub300->resp_len = 0; - mutex_unlock(&vub300->cmd_mutex); - kref_put(&vub300->kref, vub300_delete); - mmc_request_done(vub300->mmc, req); - return; - } - } -} - -static int examine_cyclic_buffer(struct vub300_mmc_host *vub300, - struct mmc_command *cmd, u8 Function) -{ - /* cmd_mutex is held by vub300_mmc_request */ - u8 cmd0 = 0xFF & (cmd->arg >> 24); - u8 cmd1 = 0xFF & (cmd->arg >> 16); - u8 cmd2 = 0xFF & (cmd->arg >> 8); - u8 cmd3 = 0xFF & (cmd->arg >> 0); - int first = MAXREGMASK & vub300->fn[Function].offload_point; - struct offload_registers_access *rf = &vub300->fn[Function].reg[first]; - if (cmd0 == rf->command_byte[0] && - cmd1 == rf->command_byte[1] && - cmd2 == rf->command_byte[2] && - cmd3 == rf->command_byte[3]) { - u8 checksum = 0x00; - cmd->resp[1] = checksum << 24; - cmd->resp[0] = (rf->Respond_Byte[0] << 24) - | (rf->Respond_Byte[1] << 16) - | (rf->Respond_Byte[2] << 8) - | (rf->Respond_Byte[3] << 0); - vub300->fn[Function].offload_point += 1; - vub300->fn[Function].offload_count -= 1; - vub300->total_offload_count -= 1; - return 1; - } else { - int delta = 1; /* because it does not match the first one */ - u8 register_count = vub300->fn[Function].offload_count - 1; - u32 register_point = vub300->fn[Function].offload_point + 1; - while (0 < register_count) { - int point = MAXREGMASK & register_point; - struct offload_registers_access *r = - &vub300->fn[Function].reg[point]; - if (cmd0 == r->command_byte[0] && - cmd1 == r->command_byte[1] && - cmd2 == r->command_byte[2] && - cmd3 == r->command_byte[3]) { - u8 checksum = 0x00; - cmd->resp[1] = checksum << 24; - cmd->resp[0] = (r->Respond_Byte[0] << 24) - | (r->Respond_Byte[1] << 16) - | (r->Respond_Byte[2] << 8) - | (r->Respond_Byte[3] << 0); - vub300->fn[Function].offload_point += delta; - vub300->fn[Function].offload_count -= delta; - vub300->total_offload_count -= delta; - return 1; - } else { - register_point += 1; - register_count -= 1; - delta += 1; - continue; - } - } - return 0; - } -} - -static int satisfy_request_from_offloaded_data(struct vub300_mmc_host *vub300, - struct mmc_command *cmd) -{ - /* cmd_mutex is held by vub300_mmc_request */ - u8 regs = vub300->dynamic_register_count; - u8 i = 0; - u8 func = FUN(cmd); - u32 reg = REG(cmd); - while (0 < regs--) { - if ((vub300->sdio_register[i].func_num == func) && - (vub300->sdio_register[i].sdio_reg == reg)) { - if (!vub300->sdio_register[i].prepared) { - return 0; - } else if ((0x80000000 & cmd->arg) == 0x80000000) { - /* - * a write to a dynamic register - * nullifies our offloaded value - */ - vub300->sdio_register[i].prepared = 0; - return 0; - } else { - u8 checksum = 0x00; - u8 rsp0 = 0x00; - u8 rsp1 = 0x00; - u8 rsp2 = vub300->sdio_register[i].response; - u8 rsp3 = vub300->sdio_register[i].regvalue; - vub300->sdio_register[i].prepared = 0; - cmd->resp[1] = checksum << 24; - cmd->resp[0] = (rsp0 << 24) - | (rsp1 << 16) - | (rsp2 << 8) - | (rsp3 << 0); - return 1; - } - } else { - i += 1; - continue; - } - } - if (vub300->total_offload_count == 0) - return 0; - else if (vub300->fn[func].offload_count == 0) - return 0; - else - return examine_cyclic_buffer(vub300, cmd, func); -} - -static void vub300_mmc_request(struct mmc_host *mmc, struct mmc_request *req) -{ /* NOT irq */ - struct mmc_command *cmd = req->cmd; - struct vub300_mmc_host *vub300 = mmc_priv(mmc); - if (!vub300->interface) { - cmd->error = -ESHUTDOWN; - mmc_request_done(mmc, req); - return; - } else { - struct mmc_data *data = req->data; - if (!vub300->card_powered) { - cmd->error = -ENOMEDIUM; - mmc_request_done(mmc, req); - return; - } - if (!vub300->card_present) { - cmd->error = -ENOMEDIUM; - mmc_request_done(mmc, req); - return; - } - if (vub300->usb_transport_fail) { - cmd->error = vub300->usb_transport_fail; - mmc_request_done(mmc, req); - return; - } - if (!vub300->interface) { - cmd->error = -ENODEV; - mmc_request_done(mmc, req); - return; - } - kref_get(&vub300->kref); - mutex_lock(&vub300->cmd_mutex); - mod_timer(&vub300->inactivity_timer, jiffies + HZ); - /* - * for performance we have to return immediately - * if the requested data has been offloaded - */ - if (cmd->opcode == 52 && - satisfy_request_from_offloaded_data(vub300, cmd)) { - cmd->error = 0; - mutex_unlock(&vub300->cmd_mutex); - kref_put(&vub300->kref, vub300_delete); - mmc_request_done(mmc, req); - return; - } else { - vub300->cmd = cmd; - vub300->req = req; - vub300->data = data; - if (data) - vub300->datasize = data->blksz * data->blocks; - else - vub300->datasize = 0; - vub300_queue_cmnd_work(vub300); - mutex_unlock(&vub300->cmd_mutex); - kref_put(&vub300->kref, vub300_delete); - /* - * the kernel lock diagnostics complain - * if the cmd_mutex * is "passed on" - * to the cmndwork thread, - * so we must release it now - * and re-acquire it in the cmndwork thread - */ - } - } -} - -static void __set_clock_speed(struct vub300_mmc_host *vub300, u8 buf[8], - struct mmc_ios *ios) -{ - int buf_array_size = 8; /* ARRAY_SIZE(buf) does not work !!! */ - int retval; - u32 kHzClock; - if (ios->clock >= 48000000) - kHzClock = 48000; - else if (ios->clock >= 24000000) - kHzClock = 24000; - else if (ios->clock >= 20000000) - kHzClock = 20000; - else if (ios->clock >= 15000000) - kHzClock = 15000; - else if (ios->clock >= 200000) - kHzClock = 200; - else - kHzClock = 0; - { - int i; - u64 c = kHzClock; - for (i = 0; i < buf_array_size; i++) { - buf[i] = c; - c >>= 8; - } - } - retval = - usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0), - SET_CLOCK_SPEED, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0x00, 0x00, buf, buf_array_size, 1000); - if (retval != 8) { - dev_err(&vub300->udev->dev, "SET_CLOCK_SPEED" - " %dkHz failed with retval=%d\n", kHzClock, retval); - } else { - dev_dbg(&vub300->udev->dev, "SET_CLOCK_SPEED" - " %dkHz\n", kHzClock); - } -} - -static void vub300_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) -{ /* NOT irq */ - struct vub300_mmc_host *vub300 = mmc_priv(mmc); - if (!vub300->interface) - return; - kref_get(&vub300->kref); - mutex_lock(&vub300->cmd_mutex); - if ((ios->power_mode == MMC_POWER_OFF) && vub300->card_powered) { - vub300->card_powered = 0; - usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0), - SET_SD_POWER, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0x0000, 0x0000, NULL, 0, 1000); - /* must wait for the VUB300 u-proc to boot up */ - msleep(600); - } else if ((ios->power_mode == MMC_POWER_UP) && !vub300->card_powered) { - usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0), - SET_SD_POWER, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0x0001, 0x0000, NULL, 0, 1000); - msleep(600); - vub300->card_powered = 1; - } else if (ios->power_mode == MMC_POWER_ON) { - u8 *buf = kmalloc(8, GFP_KERNEL); - if (buf) { - __set_clock_speed(vub300, buf, ios); - kfree(buf); - } - } else { - /* this should mean no change of state */ - } - mutex_unlock(&vub300->cmd_mutex); - kref_put(&vub300->kref, vub300_delete); -} - -static int vub300_mmc_get_ro(struct mmc_host *mmc) -{ - struct vub300_mmc_host *vub300 = mmc_priv(mmc); - return vub300->read_only; -} - -static void vub300_enable_sdio_irq(struct mmc_host *mmc, int enable) -{ /* NOT irq */ - struct vub300_mmc_host *vub300 = mmc_priv(mmc); - if (!vub300->interface) - return; - kref_get(&vub300->kref); - if (enable) { - set_current_state(TASK_RUNNING); - mutex_lock(&vub300->irq_mutex); - if (vub300->irqs_queued) { - vub300->irqs_queued -= 1; - mmc_signal_sdio_irq(vub300->mmc); - } else if (vub300->irq_disabled) { - vub300->irq_disabled = 0; - vub300->irq_enabled = 1; - vub300_queue_poll_work(vub300, 0); - } else if (vub300->irq_enabled) { - /* this should not happen, so we will just ignore it */ - } else { - vub300->irq_enabled = 1; - vub300_queue_poll_work(vub300, 0); - } - mutex_unlock(&vub300->irq_mutex); - set_current_state(TASK_INTERRUPTIBLE); - } else { - vub300->irq_enabled = 0; - } - kref_put(&vub300->kref, vub300_delete); -} - -static const struct mmc_host_ops vub300_mmc_ops = { - .request = vub300_mmc_request, - .set_ios = vub300_mmc_set_ios, - .get_ro = vub300_mmc_get_ro, - .enable_sdio_irq = vub300_enable_sdio_irq, -}; - -static int vub300_probe(struct usb_interface *interface, - const struct usb_device_id *id) -{ /* NOT irq */ - struct vub300_mmc_host *vub300; - struct usb_host_interface *iface_desc; - struct usb_device *udev = usb_get_dev(interface_to_usbdev(interface)); - int i; - int retval = -ENOMEM; - struct urb *command_out_urb; - struct urb *command_res_urb; - struct mmc_host *mmc; - char manufacturer[48]; - char product[32]; - char serial_number[32]; - usb_string(udev, udev->descriptor.iManufacturer, manufacturer, - sizeof(manufacturer)); - usb_string(udev, udev->descriptor.iProduct, product, sizeof(product)); - usb_string(udev, udev->descriptor.iSerialNumber, serial_number, - sizeof(serial_number)); - dev_info(&udev->dev, "probing VID:PID(%04X:%04X) %s %s %s\n", - le16_to_cpu(udev->descriptor.idVendor), - le16_to_cpu(udev->descriptor.idProduct), - manufacturer, product, serial_number); - command_out_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!command_out_urb) { - retval = -ENOMEM; - goto err_put_udev; - } - command_res_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!command_res_urb) { - retval = -ENOMEM; - goto err_free_out_urb; - } - /* this also allocates memory for our VUB300 mmc host device */ - mmc = mmc_alloc_host(sizeof(*vub300), &udev->dev); - if (!mmc) { - retval = -ENOMEM; - dev_err(&udev->dev, "not enough memory for the mmc_host\n"); - goto err_free_res_urb; - } - /* MMC core transfer sizes tunable parameters */ - mmc->caps = 0; - if (!force_1_bit_data_xfers) - mmc->caps |= MMC_CAP_4_BIT_DATA; - if (!force_polling_for_irqs) - mmc->caps |= MMC_CAP_SDIO_IRQ; - mmc->caps &= ~MMC_CAP_NEEDS_POLL; - /* - * MMC_CAP_NEEDS_POLL causes core.c:mmc_rescan() to poll - * for devices which results in spurious CMD7's being - * issued which stops some SDIO cards from working - */ - if (limit_speed_to_24_MHz) { - mmc->caps |= MMC_CAP_MMC_HIGHSPEED; - mmc->caps |= MMC_CAP_SD_HIGHSPEED; - mmc->f_max = 24000000; - dev_info(&udev->dev, "limiting SDIO speed to 24_MHz\n"); - } else { - mmc->caps |= MMC_CAP_MMC_HIGHSPEED; - mmc->caps |= MMC_CAP_SD_HIGHSPEED; - mmc->f_max = 48000000; - } - mmc->f_min = 200000; - mmc->max_blk_count = 511; - mmc->max_blk_size = 512; - mmc->max_segs = 128; - if (force_max_req_size) - mmc->max_req_size = force_max_req_size * 1024; - else - mmc->max_req_size = 64 * 1024; - mmc->max_seg_size = mmc->max_req_size; - mmc->ocr_avail = 0; - mmc->ocr_avail |= MMC_VDD_165_195; - mmc->ocr_avail |= MMC_VDD_20_21; - mmc->ocr_avail |= MMC_VDD_21_22; - mmc->ocr_avail |= MMC_VDD_22_23; - mmc->ocr_avail |= MMC_VDD_23_24; - mmc->ocr_avail |= MMC_VDD_24_25; - mmc->ocr_avail |= MMC_VDD_25_26; - mmc->ocr_avail |= MMC_VDD_26_27; - mmc->ocr_avail |= MMC_VDD_27_28; - mmc->ocr_avail |= MMC_VDD_28_29; - mmc->ocr_avail |= MMC_VDD_29_30; - mmc->ocr_avail |= MMC_VDD_30_31; - mmc->ocr_avail |= MMC_VDD_31_32; - mmc->ocr_avail |= MMC_VDD_32_33; - mmc->ocr_avail |= MMC_VDD_33_34; - mmc->ocr_avail |= MMC_VDD_34_35; - mmc->ocr_avail |= MMC_VDD_35_36; - mmc->ops = &vub300_mmc_ops; - vub300 = mmc_priv(mmc); - vub300->mmc = mmc; - vub300->card_powered = 0; - vub300->bus_width = 0; - vub300->cmnd.head.block_size[0] = 0x00; - vub300->cmnd.head.block_size[1] = 0x00; - vub300->app_spec = 0; - mutex_init(&vub300->cmd_mutex); - mutex_init(&vub300->irq_mutex); - vub300->command_out_urb = command_out_urb; - vub300->command_res_urb = command_res_urb; - vub300->usb_timed_out = 0; - vub300->dynamic_register_count = 0; - - for (i = 0; i < ARRAY_SIZE(vub300->fn); i++) { - vub300->fn[i].offload_point = 0; - vub300->fn[i].offload_count = 0; - } - - vub300->total_offload_count = 0; - vub300->irq_enabled = 0; - vub300->irq_disabled = 0; - vub300->irqs_queued = 0; - - for (i = 0; i < ARRAY_SIZE(vub300->sdio_register); i++) - vub300->sdio_register[i++].activate = 0; - - vub300->udev = udev; - vub300->interface = interface; - vub300->cmnd_res_ep = 0; - vub300->cmnd_out_ep = 0; - vub300->data_inp_ep = 0; - vub300->data_out_ep = 0; - - for (i = 0; i < ARRAY_SIZE(vub300->fbs); i++) - vub300->fbs[i] = 512; - - /* - * set up the endpoint information - * - * use the first pair of bulk-in and bulk-out - * endpoints for Command/Response+Interrupt - * - * use the second pair of bulk-in and bulk-out - * endpoints for Data In/Out - */ - vub300->large_usb_packets = 0; - iface_desc = interface->cur_altsetting; - for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { - struct usb_endpoint_descriptor *endpoint = - &iface_desc->endpoint[i].desc; - dev_info(&vub300->udev->dev, - "vub300 testing %s EndPoint(%d) %02X\n", - usb_endpoint_is_bulk_in(endpoint) ? "BULK IN" : - usb_endpoint_is_bulk_out(endpoint) ? "BULK OUT" : - "UNKNOWN", i, endpoint->bEndpointAddress); - if (endpoint->wMaxPacketSize > 64) - vub300->large_usb_packets = 1; - if (usb_endpoint_is_bulk_in(endpoint)) { - if (!vub300->cmnd_res_ep) { - vub300->cmnd_res_ep = - endpoint->bEndpointAddress; - } else if (!vub300->data_inp_ep) { - vub300->data_inp_ep = - endpoint->bEndpointAddress; - } else { - dev_warn(&vub300->udev->dev, - "ignoring" - " unexpected bulk_in endpoint"); - } - } else if (usb_endpoint_is_bulk_out(endpoint)) { - if (!vub300->cmnd_out_ep) { - vub300->cmnd_out_ep = - endpoint->bEndpointAddress; - } else if (!vub300->data_out_ep) { - vub300->data_out_ep = - endpoint->bEndpointAddress; - } else { - dev_warn(&vub300->udev->dev, - "ignoring" - " unexpected bulk_out endpoint"); - } - } else { - dev_warn(&vub300->udev->dev, - "vub300 ignoring EndPoint(%d) %02X", i, - endpoint->bEndpointAddress); - } - } - if (vub300->cmnd_res_ep && vub300->cmnd_out_ep && - vub300->data_inp_ep && vub300->data_out_ep) { - dev_info(&vub300->udev->dev, - "vub300 %s packets" - " using EndPoints %02X %02X %02X %02X\n", - vub300->large_usb_packets ? "LARGE" : "SMALL", - vub300->cmnd_out_ep, vub300->cmnd_res_ep, - vub300->data_out_ep, vub300->data_inp_ep); - /* we have the expected EndPoints */ - } else { - dev_err(&vub300->udev->dev, - "Could not find two sets of bulk-in/out endpoint pairs\n"); - retval = -EINVAL; - goto err_free_host; - } - retval = - usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0), - GET_HC_INF0, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0x0000, 0x0000, &vub300->hc_info, - sizeof(vub300->hc_info), 1000); - if (retval < 0) - goto err_free_host; - retval = - usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0), - SET_ROM_WAIT_STATES, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - firmware_rom_wait_states, 0x0000, NULL, 0, 1000); - if (retval < 0) - goto err_free_host; - dev_info(&vub300->udev->dev, - "operating_mode = %s %s %d MHz %s %d byte USB packets\n", - (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL", - (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit", - mmc->f_max / 1000000, - pad_input_to_usb_pkt ? "padding input data to" : "with", - vub300->large_usb_packets ? 512 : 64); - retval = - usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0), - GET_SYSTEM_PORT_STATUS, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0x0000, 0x0000, &vub300->system_port_status, - sizeof(vub300->system_port_status), 1000); - if (retval < 0) { - goto err_free_host; - } else if (sizeof(vub300->system_port_status) == retval) { - vub300->card_present = - (0x0001 & vub300->system_port_status.port_flags) ? 1 : 0; - vub300->read_only = - (0x0010 & vub300->system_port_status.port_flags) ? 1 : 0; - } else { - retval = -EINVAL; - goto err_free_host; - } - usb_set_intfdata(interface, vub300); - INIT_DELAYED_WORK(&vub300->pollwork, vub300_pollwork_thread); - INIT_WORK(&vub300->cmndwork, vub300_cmndwork_thread); - INIT_WORK(&vub300->deadwork, vub300_deadwork_thread); - kref_init(&vub300->kref); - timer_setup(&vub300->sg_transfer_timer, vub300_sg_timed_out, 0); - kref_get(&vub300->kref); - timer_setup(&vub300->inactivity_timer, - vub300_inactivity_timer_expired, 0); - vub300->inactivity_timer.expires = jiffies + HZ; - add_timer(&vub300->inactivity_timer); - if (vub300->card_present) - dev_info(&vub300->udev->dev, - "USB vub300 remote SDIO host controller[%d]" - "connected with SD/SDIO card inserted\n", - interface_to_InterfaceNumber(interface)); - else - dev_info(&vub300->udev->dev, - "USB vub300 remote SDIO host controller[%d]" - "connected with no SD/SDIO card inserted\n", - interface_to_InterfaceNumber(interface)); - retval = mmc_add_host(mmc); - if (retval) - goto err_stop_io; - - return 0; - -err_stop_io: - vub300->interface = NULL; - kref_put(&vub300->kref, vub300_delete); - - return retval; - -err_free_host: - mmc_free_host(mmc); - /* - * and hence also frees vub300 - * which is contained at the end of struct mmc - */ -err_free_res_urb: - usb_free_urb(command_res_urb); -err_free_out_urb: - usb_free_urb(command_out_urb); -err_put_udev: - usb_put_dev(udev); - - return retval; -} - -static void vub300_disconnect(struct usb_interface *interface) -{ /* NOT irq */ - struct vub300_mmc_host *vub300 = usb_get_intfdata(interface); - if (!vub300 || !vub300->mmc) { - return; - } else { - struct mmc_host *mmc = vub300->mmc; - if (!vub300->mmc) { - return; - } else { - int ifnum = interface_to_InterfaceNumber(interface); - usb_set_intfdata(interface, NULL); - /* prevent more I/O from starting */ - vub300->interface = NULL; - mmc_remove_host(mmc); - kref_put(&vub300->kref, vub300_delete); - pr_info("USB vub300 remote SDIO host controller[%d]" - " now disconnected", ifnum); - return; - } - } -} - -#ifdef CONFIG_PM -static int vub300_suspend(struct usb_interface *intf, pm_message_t message) -{ - return 0; -} - -static int vub300_resume(struct usb_interface *intf) -{ - return 0; -} -#else -#define vub300_suspend NULL -#define vub300_resume NULL -#endif -static int vub300_pre_reset(struct usb_interface *intf) -{ /* NOT irq */ - struct vub300_mmc_host *vub300 = usb_get_intfdata(intf); - mutex_lock(&vub300->cmd_mutex); - return 0; -} - -static int vub300_post_reset(struct usb_interface *intf) -{ /* NOT irq */ - struct vub300_mmc_host *vub300 = usb_get_intfdata(intf); - /* we are sure no URBs are active - no locking needed */ - vub300->errors = -EPIPE; - mutex_unlock(&vub300->cmd_mutex); - return 0; -} - -static struct usb_driver vub300_driver = { - .name = "vub300", - .probe = vub300_probe, - .disconnect = vub300_disconnect, - .suspend = vub300_suspend, - .resume = vub300_resume, - .pre_reset = vub300_pre_reset, - .post_reset = vub300_post_reset, - .id_table = vub300_table, - .supports_autosuspend = 1, -}; - -static int __init vub300_init(void) -{ /* NOT irq */ - int result; - - pr_info("VUB300 Driver rom wait states = %02X irqpoll timeout = %04X", - firmware_rom_wait_states, 0x0FFFF & firmware_irqpoll_timeout); - - cmndworkqueue = create_singlethread_workqueue("kvub300c"); - if (!cmndworkqueue) - return -ENOMEM; - - pollworkqueue = create_singlethread_workqueue("kvub300p"); - if (!pollworkqueue) { - result = -ENOMEM; - goto err_destroy_cmdwq; - } - - deadworkqueue = create_singlethread_workqueue("kvub300d"); - if (!deadworkqueue) { - result = -ENOMEM; - goto err_destroy_pollwq; - } - - result = usb_register(&vub300_driver); - if (result) - goto err_destroy_deadwq; - - return 0; - -err_destroy_deadwq: - destroy_workqueue(deadworkqueue); -err_destroy_pollwq: - destroy_workqueue(pollworkqueue); -err_destroy_cmdwq: - destroy_workqueue(cmndworkqueue); - - return result; -} - -static void __exit vub300_exit(void) -{ - usb_deregister(&vub300_driver); - flush_workqueue(cmndworkqueue); - flush_workqueue(pollworkqueue); - flush_workqueue(deadworkqueue); - destroy_workqueue(cmndworkqueue); - destroy_workqueue(pollworkqueue); - destroy_workqueue(deadworkqueue); -} - -module_init(vub300_init); -module_exit(vub300_exit); - -MODULE_AUTHOR("Tony Olech "); -MODULE_DESCRIPTION("VUB300 USB to SD/MMC/SDIO adapter driver"); -MODULE_LICENSE("GPL"); From 91def853c3cc5a3394a55324a17b88dc6fa9798f Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Tue, 15 Sep 2026 15:20:45 +0200 Subject: [PATCH 30/43] staging: greybus: sdio: Convert to devm_mmc_alloc_host() Simplify the code by converting from mmc_alloc_host() to the resource managed devm_mmc_alloc_host(). Signed-off-by: Ulf Hansson Acked-by: Rui Miguel Silva Acked-by: Greg Kroah-Hartman --- drivers/staging/greybus/sdio.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/staging/greybus/sdio.c b/drivers/staging/greybus/sdio.c index 3952f3d225db..642d785fe918 100644 --- a/drivers/staging/greybus/sdio.c +++ b/drivers/staging/greybus/sdio.c @@ -767,17 +767,15 @@ static int gb_sdio_probe(struct gbphy_device *gbphy_dev, struct gb_sdio_host *host; int ret = 0; - mmc = mmc_alloc_host(sizeof(*host), &gbphy_dev->dev); + mmc = devm_mmc_alloc_host(&gbphy_dev->dev, sizeof(*host)); if (!mmc) return -ENOMEM; connection = gb_connection_create(gbphy_dev->bundle, le16_to_cpu(gbphy_dev->cport_desc->id), gb_sdio_request_handler); - if (IS_ERR(connection)) { - ret = PTR_ERR(connection); - goto exit_mmc_free; - } + if (IS_ERR(connection)) + return PTR_ERR(connection); host = mmc_priv(mmc); host->mmc = mmc; @@ -835,8 +833,6 @@ static int gb_sdio_probe(struct gbphy_device *gbphy_dev, gb_connection_disable(connection); exit_connection_destroy: gb_connection_destroy(connection); -exit_mmc_free: - mmc_free_host(mmc); return ret; } @@ -863,7 +859,6 @@ static void gb_sdio_remove(struct gbphy_device *gbphy_dev) mmc_remove_host(mmc); gb_connection_disable(connection); gb_connection_destroy(connection); - mmc_free_host(mmc); } static const struct gbphy_device_id gb_sdio_id_table[] = { From c98cbf6ce8b9f9f535600b15a5f34a77982b226b Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Tue, 15 Sep 2026 15:20:46 +0200 Subject: [PATCH 31/43] mmc: core: Turn mmc_alloc|free_host() into static functions As there are no longer any users of these functions, let's make them internal to the mmc core. While at it, let's also flip the order of the in-parameters to mmc_alloc_host() to be consistent with devm_alloc_host(). Signed-off-by: Ulf Hansson Reviewed-by: Shawn Lin --- drivers/mmc/core/host.c | 35 +++++++++-------------------------- include/linux/mmc/host.h | 2 -- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 1bcf0e917b59..8e9e61839198 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -505,14 +505,7 @@ static int mmc_first_nonreserved_index(void) return max + 1; } -/** - * mmc_alloc_host - initialise the per-host structure. - * @extra: sizeof private data structure - * @dev: pointer to host device model structure - * - * Initialise the per-host structure. - */ -struct mmc_host *mmc_alloc_host(int extra, struct device *dev) +static struct mmc_host *mmc_alloc_host(struct device *dev, int extra) { int index; struct mmc_host *host; @@ -583,7 +576,13 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) return host; } -EXPORT_SYMBOL(mmc_alloc_host); +static void mmc_free_host(struct mmc_host *host) +{ + cancel_delayed_work_sync(&host->detect); + cancel_work_sync(&host->sdio_irq_work); + mmc_pwrseq_free(host); + put_device(&host->class_dev); +} static void devm_mmc_host_release(struct device *dev, void *res) { @@ -598,7 +597,7 @@ struct mmc_host *devm_mmc_alloc_host(struct device *dev, int extra) if (!dr) return NULL; - host = mmc_alloc_host(extra, dev); + host = mmc_alloc_host(dev, extra); if (!host) { devres_free(dr); return NULL; @@ -692,19 +691,3 @@ void mmc_remove_host(struct mmc_host *host) } EXPORT_SYMBOL(mmc_remove_host); - -/** - * mmc_free_host - free the host structure - * @host: mmc host - * - * Free the host once all references to it have been dropped. - */ -void mmc_free_host(struct mmc_host *host) -{ - cancel_delayed_work_sync(&host->detect); - cancel_work_sync(&host->sdio_irq_work); - mmc_pwrseq_free(host); - put_device(&host->class_dev); -} - -EXPORT_SYMBOL(mmc_free_host); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index ddb32bc2946f..198554e48346 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -585,11 +585,9 @@ struct mmc_host { struct device_node; -struct mmc_host *mmc_alloc_host(int extra, struct device *); struct mmc_host *devm_mmc_alloc_host(struct device *dev, int extra); int mmc_add_host(struct mmc_host *); void mmc_remove_host(struct mmc_host *); -void mmc_free_host(struct mmc_host *); void mmc_of_parse_clk_phase(struct device *dev, struct mmc_clk_phase_map *map); int mmc_of_parse(struct mmc_host *host); From 59f97e4fe6b6d912c5db150ea9ae3a9ca466da91 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 28 Sep 2026 17:50:57 +0200 Subject: [PATCH 32/43] mmc: cavium-thunderx: Drop redundant calls to get|put_device() Since commit 522811e944ed ("of: platform: stop accessing invalid dev in of_platform_device_destroy"), there is no need to take a reference for the device before calling of_platform_device_destroy(), hence let's drop this. Signed-off-by: Ulf Hansson Signed-off-by: Ulf Hansson --- drivers/mmc/host/cavium-thunderx.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/mmc/host/cavium-thunderx.c b/drivers/mmc/host/cavium-thunderx.c index 84ff6d82ae3c..eecac003e546 100644 --- a/drivers/mmc/host/cavium-thunderx.c +++ b/drivers/mmc/host/cavium-thunderx.c @@ -157,11 +157,8 @@ static int thunder_mmc_probe(struct pci_dev *pdev, for (i = 0; i < CAVIUM_MAX_MMC; i++) { if (host->slot[i]) cvm_mmc_of_slot_remove(host->slot[i]); - if (host->slot_pdev[i]) { - get_device(&host->slot_pdev[i]->dev); + if (host->slot_pdev[i]) of_platform_device_destroy(&host->slot_pdev[i]->dev, NULL); - put_device(&host->slot_pdev[i]->dev); - } } clk_disable_unprepare(host->clk); return ret; From 80aa4f00b218fd8a0a533a56a99b96ac001f0947 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 17 Sep 2026 16:48:35 +0800 Subject: [PATCH 33/43] docs: mmc: document eMMC health status support in mmc-tools mmc-utils added eMMC health status support, add a line for reference. Signed-off-by: Shawn Lin Signed-off-by: Ulf Hansson --- Documentation/driver-api/mmc/mmc-tools.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/driver-api/mmc/mmc-tools.rst b/Documentation/driver-api/mmc/mmc-tools.rst index eee1c2ccfa8f..728d3aa67eda 100644 --- a/Documentation/driver-api/mmc/mmc-tools.rst +++ b/Documentation/driver-api/mmc/mmc-tools.rst @@ -32,6 +32,7 @@ The mmc-utils tools can do the following: - Write to rpmb device from data file. - Enable the eMMC cache feature. - Disable the eMMC cache feature. + - Print the eMMC health status. - Print and parse CID data. - Print and parse CSD data. - Print and parse SCR data. From d0d409fc6c662f8f0e0a86c008f8065fb1b7fd93 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 17 Sep 2026 16:48:36 +0800 Subject: [PATCH 34/43] docs: mmc: fix stale async request documentation mmc-async-req.rst still documents interfaces that no longer exist: - mmc_start_req() was renamed to mmc_start_areq() by commit c3399ef55d8e ("mmc: core: rename mmc_start_req() to *areq()") and then removed together with the rest of the asynchronous request machinery when the block driver switched to blk-mq, by commits 81196976ed94 ("mmc: block: Add blk-mq support") and 126b62700386 ("mmc: core: Remove code no longer needed after the switch to blk-mq"). - mmc_blk_issue_rw_rq() was removed by commit 0fbfd1251830 ("mmc: block: Remove code no longer needed after the switch to blk-mq"), and was replaced by mmc_blk_mq_issue_rw_rq(). - The is_first_req argument of the ->pre_req() callback was dropped by commit d3c6aac3bdfe ("mmc: delete is_first_req parameter from pre-request callback"), so the "Optimize for the first request" section is obsolete as well. - The Linaro wiki page holding the IOZone/mmc_test measurements no longer exists. Update the document to describe the current request lifecycle: mmc_pre_req(), mmc_start_request(), mmc_wait_for_req() and mmc_post_req()), and drop the sections describing the long-gone interfaces and the dead link. Signed-off-by: Shawn Lin Signed-off-by: Ulf Hansson --- .../driver-api/mmc/mmc-async-req.rst | 64 ++++--------------- 1 file changed, 13 insertions(+), 51 deletions(-) diff --git a/Documentation/driver-api/mmc/mmc-async-req.rst b/Documentation/driver-api/mmc/mmc-async-req.rst index 0f7197c9c3b5..d9d4ac74b846 100644 --- a/Documentation/driver-api/mmc/mmc-async-req.rst +++ b/Documentation/driver-api/mmc/mmc-async-req.rst @@ -23,7 +23,7 @@ MMC request. MMC block driver ================ -The mmc_blk_issue_rw_rq() in the MMC block driver is made non-blocking. +The mmc_blk_mq_issue_rw_rq() in the MMC block driver is made non-blocking. The increase in throughput is proportional to the time it takes to prepare (major part of preparations are dma_map_sg() and dma_unmap_sg()) @@ -34,21 +34,20 @@ platform. In power save mode, when clocks run on a lower frequency, the DMA preparation may cost even more. As long as these slower preparations are run in parallel with the transfer performance won't be affected. -Details on measurements from IOZone and mmc_test -================================================ +MMC core API +============ -https://wiki.linaro.org/WorkingGroups/Kernel/Specs/StoragePerfMMC-async-req +The preparation of a request is separated from starting the transfer, so +that a host can prepare a request while another one is still in progress: -MMC core API extension -====================== - -There is one new public function mmc_start_req(). - -It starts a new MMC command request for a host. The function isn't -truly non-blocking. If there is an ongoing async request it waits -for completion of that request and starts the new one and returns. It -doesn't wait for the new request to complete. If there is no ongoing -request it starts the new request and returns immediately. + * mmc_pre_req() prepares a request before it is started. It may be called + while another request is running on the host. + * mmc_start_request() starts a prepared request without waiting for it to + complete. mmc_wait_for_req() starts a request and waits for it to + complete as well. + * mmc_post_req() releases the resources allocated by mmc_pre_req() after + the request has completed. It may likewise run while another request is + active. MMC host extensions =================== @@ -59,40 +58,3 @@ to before and after the actual mmc_host_ops.request() function is called. In the DMA case pre_req() may do dma_map_sg() and prepare the DMA descriptor, and post_req() runs the dma_unmap_sg(). - -Optimize for the first request -============================== - -The first request in a series of requests can't be prepared in parallel -with the previous transfer, since there is no previous request. - -The argument is_first_req in pre_req() indicates that there is no previous -request. The host driver may optimize for this scenario to minimize -the performance loss. A way to optimize for this is to split the current -request in two chunks, prepare the first chunk and start the request, -and finally prepare the second chunk and start the transfer. - -Pseudocode to handle is_first_req scenario with minimal prepare overhead:: - - if (is_first_req && req->size > threshold) - /* start MMC transfer for the complete transfer size */ - mmc_start_command(MMC_CMD_TRANSFER_FULL_SIZE); - - /* - * Begin to prepare DMA while cmd is being processed by MMC. - * The first chunk of the request should take the same time - * to prepare as the "MMC process command time". - * If prepare time exceeds MMC cmd time - * the transfer is delayed, guesstimate max 4k as first chunk size. - */ - prepare_1st_chunk_for_dma(req); - /* flush pending desc to the DMAC (dmaengine.h) */ - dma_issue_pending(req->dma_desc); - - prepare_2nd_chunk_for_dma(req); - /* - * The second issue_pending should be called before MMC runs out - * of the first chunk. If the MMC runs out of the first data chunk - * before this call, the transfer is delayed. - */ - dma_issue_pending(req->dma_desc); From ec60f36da4100c5fd6f102b5995b4df6c926fe5a Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 17 Sep 2026 16:48:37 +0800 Subject: [PATCH 35/43] mmc: core: fix stale mmc_start_req() references in comments Two comments still refer to mmc_start_req(). That function was renamed to mmc_start_areq() then removed. So, let's update the mmc_is_req_done() and mmc_pre_req() comments to refer to the current APIs: mmc_start_request() and mmc_wait_for_req(). Signed-off-by: Shawn Lin Signed-off-by: Ulf Hansson --- drivers/mmc/core/core.c | 4 ++-- drivers/mmc/core/core.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 879b1355f7d5..677e445dc48c 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -589,8 +589,8 @@ EXPORT_SYMBOL(mmc_cqe_recovery); * mmc_is_req_done() is used with requests that have * mrq->cap_cmd_during_tfr = true. mmc_is_req_done() must be called after * starting a request and before waiting for it to complete. That is, - * either in between calls to mmc_start_req(), or after mmc_wait_for_req() - * and before mmc_wait_for_req_done(). If it is called at other times the + * after mmc_start_request() or mmc_wait_for_req() and before + * mmc_wait_for_req_done(). If it is called at other times the * result is not meaningful. */ bool mmc_is_req_done(struct mmc_host *host, struct mmc_request *mrq) diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index 601c73761e88..9be2875608ce 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -170,7 +170,7 @@ int mmc_cqe_recovery(struct mmc_host *host); * @host: MMC host to prepare command * @mrq: MMC request to prepare for * - * mmc_pre_req() is called in prior to mmc_start_req() to let + * mmc_pre_req() is called in prior to mmc_start_request() to let * host prepare for the new request. Preparation of a request may be * performed while another request is running on the host. */ From e16268a118e75cde4eda6bc5a95d8e959aec582c Mon Sep 17 00:00:00 2001 From: Andrew Gaylard Date: Thu, 17 Sep 2026 12:49:59 +0200 Subject: [PATCH 36/43] mmc: sunplus: initialise rd_crc_dly=1 for DDR modes After spmmc_controller_init() resets the controller, all timing fields are zero. The hs_en path initialises the write timing delays (wr_cmd_dly, wr_dat_dly) but leaves the read side at zero. In DDR52 mode, rd_crc_dly=0 causes the first write to fail with CRC_TOKEN_CHECK_ERROR, triggering the auto-tuning retry loop and causing a multi-second delay on the first cold-boot write. Set rd_crc_dly=1 when enabling DDR mode to avoid this. Tested on a LTPP3G2, booting via eMMC in DDR52 mode, on linux-next-20260915. Signed-off-by: Andrew Gaylard Signed-off-by: Ulf Hansson --- drivers/mmc/host/sunplus-mmc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mmc/host/sunplus-mmc.c b/drivers/mmc/host/sunplus-mmc.c index 63279760239c..e947c84eef94 100644 --- a/drivers/mmc/host/sunplus-mmc.c +++ b/drivers/mmc/host/sunplus-mmc.c @@ -294,6 +294,11 @@ static void spmmc_set_bus_timing(struct spmmc_host *host, unsigned int timing) value = readl(host->base + SPMMC_SD_CONFIG0_REG); value |= SPMMC_SD_DDR_MODE; writel(value, host->base + SPMMC_SD_CONFIG0_REG); + /* DDR mode needs rd_crc_dly=1 */ + value = readl(host->base + SPMMC_SD_TIMING_CONFIG0_REG); + value &= ~SPMMC_SD_READ_CRC_DELAY; + value |= FIELD_PREP(SPMMC_SD_READ_CRC_DELAY, 1); + writel(value, host->base + SPMMC_SD_TIMING_CONFIG0_REG); } else { value = readl(host->base + SPMMC_SD_CONFIG0_REG); value &= ~SPMMC_SD_DDR_MODE; From 6d34411a0fb9205bbff94b0834800c63f81b6cf9 Mon Sep 17 00:00:00 2001 From: Alper Ak Date: Thu, 17 Sep 2026 14:13:35 +0300 Subject: [PATCH 37/43] mmc: sdhci-pci: Replace deprecated PCI functions pcim_iomap_regions() and pcim_iomap_table() have been deprecated. Replace them with pcim_iomap_region(), which requests and maps a single BAR and returns the mapping directly. pcim_iomap_region() returns an IOMEM_ERR_PTR() on failure, so check the result with IS_ERR() and propagate the error with ERR_CAST(). Signed-off-by: Alper Ak Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-pci-core.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index b121d896a804..a9000f3bd419 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -2170,14 +2170,12 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot( host->irq = pdev->irq; - ret = pcim_iomap_regions(pdev, BIT(bar), mmc_hostname(host->mmc)); - if (ret) { + host->ioaddr = pcim_iomap_region(pdev, bar, mmc_hostname(host->mmc)); + if (IS_ERR(host->ioaddr)) { dev_err(&pdev->dev, "cannot request region\n"); - return ERR_PTR(ret); + return ERR_CAST(host->ioaddr); } - host->ioaddr = pcim_iomap_table(pdev)[bar]; - if (chip->fixes && chip->fixes->probe_slot) { ret = chip->fixes->probe_slot(slot); if (ret) From 6e8e189728cb0b03fd0cacb83acac519b922b26a Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 18 Sep 2026 22:18:04 +0800 Subject: [PATCH 38/43] mmc: use assign_bit() where applicable Convert open-coded if/else with set_bit/clear_bit and their non-atomic __set_bit/__clear_bit variants to the assign_bit/__assign_bit API. Done with Coccinelle semantic patch and manual fixups. Signed-off-by: Peng Fan Acked-by: Aubin Constans Signed-off-by: Ulf Hansson --- drivers/mmc/host/atmel-mci.c | 5 +---- drivers/mmc/host/ushc.c | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c index 8f4df250a77a..98933be5f174 100644 --- a/drivers/mmc/host/atmel-mci.c +++ b/drivers/mmc/host/atmel-mci.c @@ -1677,10 +1677,7 @@ static void atmci_detect_change(struct timer_list *t) spin_lock(&host->lock); - if (!present) - clear_bit(ATMCI_CARD_PRESENT, &slot->flags); - else - set_bit(ATMCI_CARD_PRESENT, &slot->flags); + assign_bit(ATMCI_CARD_PRESENT, &slot->flags, present); /* Clean up queue if present */ mrq = slot->mrq; diff --git a/drivers/mmc/host/ushc.c b/drivers/mmc/host/ushc.c index 7e8af5a06ac9..410e5bb62752 100644 --- a/drivers/mmc/host/ushc.c +++ b/drivers/mmc/host/ushc.c @@ -388,10 +388,7 @@ static void ushc_enable_sdio_irq(struct mmc_host *mmc, int enable) { struct ushc_data *ushc = mmc_priv(mmc); - if (enable) - set_bit(INT_EN, &ushc->flags); - else - clear_bit(INT_EN, &ushc->flags); + assign_bit(INT_EN, &ushc->flags, enable); } static void ushc_clean_up(struct ushc_data *ushc) From 4b286ace03225a30930bda6a410f1127fd60ed1e Mon Sep 17 00:00:00 2001 From: Muzaffer Kadir Date: Mon, 21 Sep 2026 21:18:56 +0300 Subject: [PATCH 39/43] dt-bindings: mmc: sdhci-msm: Add MSM8952 compatible Document the compatible string for the SDHCI controller on the MSM8952 platform. Signed-off-by: Muzaffer Kadir Acked-by: Krzysztof Kozlowski Signed-off-by: Ulf Hansson --- Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml b/Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml index 69bd44e65ad7..9d4409c958fa 100644 --- a/Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml +++ b/Documentation/devicetree/bindings/mmc/qcom,sdhci-msm.yaml @@ -26,6 +26,7 @@ properties: - qcom,ipq4019-sdhci - qcom,ipq8074-sdhci - qcom,msm8226-sdhci + - qcom,msm8952-sdhci - qcom,msm8953-sdhci - qcom,msm8974-sdhci - qcom,msm8976-sdhci From 7e355997ddeac0dc76cb3d0e2cd497758c96500e Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Tue, 22 Sep 2026 17:40:07 +0800 Subject: [PATCH 40/43] mmc: sdhci-of-dwcmshc: Disable CQE error halt request for Rockchip In CMDQ mode an I/O error in one task (data_crc_err, data_end_bit_err, etc.) makes the controller raise a halt request. While the halt request is pending, the controller rejects doorbell writes with a slave response error, which hangs the CPU (or raises SError on ARM) on Rockchip platforms. A software workaround[1] clears CQHCI_CTL right before ringing the doorbell to narrow down the window, but a race remains: an I/O error can still happen in another slot between the CQHCI_CTL write and the doorbell kick, so the hang is not fully solved. To fix this once and for all, newer silicon introduces SW_ERR_HALR_REQ_DISABLE (CQHCI_CTL[1]) which stops the controller from raising a halt request on I/O errors. Set the bit when the CQE is enabled, which is the last CQHCI_CTL write before any doorbell write. Older SoCs keep this bit reserved, so writing it is a no-op and nothing changes in the wild, thus no fixes tag is needed. [1] https://github.com/rockchip-linux/kernel/commit/4b7ea0500a1a5c186807b737fecfc6bb4d4e06f8 Signed-off-by: Shawn Lin Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-dwcmshc.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index 1baa2379c9c3..09702022e708 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -239,6 +239,8 @@ SDHCI_TRNS_BLK_CNT_EN | \ SDHCI_TRNS_DMA) +#define CQHCI_SW_ERR_HALT_REQ_DISABLE BIT(1) + #define to_pltfm_data(priv, name) \ container_of((priv)->dwcmshc_pdata, struct name##_pltfm_data, dwcmshc_pdata) @@ -702,6 +704,8 @@ static void rk35xx_sdhci_cqe_pre_enable(struct mmc_host *mmc) static void rk35xx_sdhci_cqe_enable(struct mmc_host *mmc) { struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); u32 reg; reg = sdhci_readl(host, SDHCI_PRESENT_STATE); @@ -712,6 +716,15 @@ static void rk35xx_sdhci_cqe_enable(struct mmc_host *mmc) sdhci_writew(host, DWCMSHC_SDHCI_CQE_TRNS_MODE, SDHCI_TRANSFER_MODE); + /* + * Disable the halt request raised on an I/O error, otherwise a + * doorbell write gets a slave response error while the halt request + * is pending and hangs the CPU. This bit is reserved on older SoCs, + * so the write is a no-op there. + */ + sdhci_writel(host, CQHCI_SW_ERR_HALT_REQ_DISABLE, + dwc_priv->vendor_specific_area2 + CQHCI_CTL); + sdhci_cqe_enable(mmc); } From ccf7f7a9a4fef14958fdcf19c37955144f2f25d9 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 24 Sep 2026 16:50:22 +0800 Subject: [PATCH 41/43] mmc: core: add mmc_send_tuning_timeout() Add mmc_send_tuning_timeout() which takes the data timeout in milliseconds, and turn mmc_send_tuning() into a thin wrapper keeping the current 150 ms, so there is no behaviour change for the existing callers. This lets drivers pass a tighter timeout for software tuning, e.g. hosts which scan the tuning window phase by phase. Suggested-by: Adrian Hunter Signed-off-by: Shawn Lin Signed-off-by: Ulf Hansson --- drivers/mmc/core/mmc_ops.c | 22 +++++++++++++++------- include/linux/mmc/host.h | 2 ++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index a952cc8265af..d59ff82fb899 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -673,7 +673,8 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, } EXPORT_SYMBOL_GPL(mmc_switch); -int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error) +int mmc_send_tuning_timeout(struct mmc_host *host, u32 opcode, int *cmd_error, + u32 timeout_ms) { struct mmc_request mrq = {}; struct mmc_command cmd = {}; @@ -707,12 +708,7 @@ int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error) data.blocks = 1; data.flags = MMC_DATA_READ; - /* - * According to the tuning specs, Tuning process - * is normally shorter 40 executions of CMD19, - * and timeout value should be shorter than 150 ms - */ - data.timeout_ns = 150 * NSEC_PER_MSEC; + data.timeout_ns = timeout_ms * NSEC_PER_MSEC; data.sg = &sg; data.sg_len = 1; @@ -740,6 +736,18 @@ int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error) kfree(data_buf); return err; } +EXPORT_SYMBOL_GPL(mmc_send_tuning_timeout); + +int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error) +{ + /* + * The eMMC and SD specs give the tuning process an overall + * budget of 150 ms. mmc_send_tuning() simply applies that as + * the timeout of every single tuning command. Drivers which + * want a different timeout can use mmc_send_tuning_timeout(). + */ + return mmc_send_tuning_timeout(host, opcode, cmd_error, 150); +} EXPORT_SYMBOL_GPL(mmc_send_tuning); int mmc_send_abort_tuning(struct mmc_host *host, u32 opcode) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 198554e48346..032c018650e0 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -753,6 +753,8 @@ static inline int mmc_card_uhs2_hd_mode(struct mmc_host *host) int mmc_sd_switch(struct mmc_card *card, bool mode, int group, u8 value, u8 *resp); int mmc_send_status(struct mmc_card *card, u32 *status); +int mmc_send_tuning_timeout(struct mmc_host *host, u32 opcode, int *cmd_error, + u32 timeout_ms); int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error); int mmc_send_abort_tuning(struct mmc_host *host, u32 opcode); int mmc_get_ext_csd(struct mmc_card *card, u8 **new_ext_csd); From 8761866e5aaed979e071353a921220f27275d689 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 24 Sep 2026 16:50:23 +0800 Subject: [PATCH 42/43] mmc: dw_mmc-rockchip: use a tighter per-command tuning timeout dw_mmc-rockchip's execute_tuning() scans the tuning window phase by phase through mmc_send_tuning(), which applied the whole-sequence 150 ms bound as the data timeout of every single CMD19/CMD21. With HS200 eMMC the TMOUT register saturates at ~112 ms for the requested 150 ms, so every phase that misses the window stalls that long, and multi-second boot slowdowns have been reported[1]. Use 5 ms per tuning command via mmc_send_tuning_timeout() instead: ~1.3x the spec-implied per-execution device budget (150 ms / 40 = 3.75 ms, excluding host overhead), 30x below the 150 ms ceiling, and the reporter verified that tuning keeps passing with it. A wrong sampling phase normally fails fast with a CRC error anyway, so the timeout only catches devices which never return the tuning block at all. This also bounds the cost of runtime re-tuning. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221781 [1] Signed-off-by: Shawn Lin Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc-rockchip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/dw_mmc-rockchip.c b/drivers/mmc/host/dw_mmc-rockchip.c index 75c82ff20f17..a07707a99a78 100644 --- a/drivers/mmc/host/dw_mmc-rockchip.c +++ b/drivers/mmc/host/dw_mmc-rockchip.c @@ -327,7 +327,7 @@ static int dw_mci_rk3288_execute_tuning(struct dw_mci *host, u32 opcode) i, priv->num_phases)); - v = !mmc_send_tuning(mmc, opcode, NULL); + v = !mmc_send_tuning_timeout(mmc, opcode, NULL, 5); if (i == 0) first_v = v; From fd9af27f6c2319713bb23832066585f8a59a1771 Mon Sep 17 00:00:00 2001 From: Pablo Mazzini Date: Sun, 27 Sep 2026 16:10:13 +0100 Subject: [PATCH 43/43] dt-bindings: mmc: sun4i-a10-mmc: add Allwinner B288 Add the MMC and eMMC compatibles. Signed-off-by: Pablo Mazzini Acked-by: Krzysztof Kozlowski Signed-off-by: Ulf Hansson --- .../devicetree/bindings/mmc/allwinner,sun4i-a10-mmc.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/allwinner,sun4i-a10-mmc.yaml b/Documentation/devicetree/bindings/mmc/allwinner,sun4i-a10-mmc.yaml index 9e9610bb1eda..c2ec7151dccf 100644 --- a/Documentation/devicetree/bindings/mmc/allwinner,sun4i-a10-mmc.yaml +++ b/Documentation/devicetree/bindings/mmc/allwinner,sun4i-a10-mmc.yaml @@ -32,10 +32,12 @@ properties: - items: - enum: - allwinner,sun8i-a83t-mmc + - allwinner,sun8i-b288-mmc - allwinner,suniv-f1c100s-mmc - const: allwinner,sun7i-a20-mmc - items: - enum: + - allwinner,sun8i-b288-emmc - allwinner,sun8i-r40-emmc - allwinner,sun50i-h5-emmc - allwinner,sun50i-h6-emmc