mmc: sdhci: Fix voltage switch delay
authorAdrian Hunter <adrian.hunter@intel.com>
Mon, 28 Nov 2022 13:32:56 +0000 (15:32 +0200)
committerUlf Hansson <ulf.hansson@linaro.org>
Tue, 29 Nov 2022 12:42:42 +0000 (13:42 +0100)
Commit 20b92a30b561 ("mmc: sdhci: update signal voltage switch code")
removed voltage switch delays from sdhci because mmc core had been
enhanced to support them. However that assumed that sdhci_set_ios()
did a single clock change, which it did not, and so the delays in mmc
core, which should have come after the first clock change, were not
effective.

Fix by avoiding re-configuring UHS and preset settings when the clock
is turning on and the settings have not changed. That then also avoids
the associated clock changes, so that then sdhci_set_ios() does a single
clock change when voltage switching, and the mmc core delays become
effective.

To do that has meant keeping track of driver strength (host->drv_type),
and cases of reinitialization (host->reinit_uhs).

Note also, the 'turning_on_clk' restriction should not be necessary
but is done to minimize the impact of the change on stable kernels.

Fixes: 20b92a30b561 ("mmc: sdhci: update signal voltage switch code")
Cc: stable@vger.kernel.org
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Link: https://lore.kernel.org/r/20221128133259.38305-2-adrian.hunter@intel.com
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
drivers/mmc/host/sdhci.c
drivers/mmc/host/sdhci.h

index fef03de85b99f3b7dd2b5a2ab46d7ecdbbcd9d7a..c7ad32a75b570a73c849ae7a5945c0564b67e89a 100644 (file)
@@ -373,6 +373,7 @@ static void sdhci_init(struct sdhci_host *host, int soft)
        if (soft) {
                /* force clock reconfiguration */
                host->clock = 0;
+               host->reinit_uhs = true;
                mmc->ops->set_ios(mmc, &mmc->ios);
        }
 }
@@ -2293,11 +2294,46 @@ void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing)
 }
 EXPORT_SYMBOL_GPL(sdhci_set_uhs_signaling);
 
+static bool sdhci_timing_has_preset(unsigned char timing)
+{
+       switch (timing) {
+       case MMC_TIMING_UHS_SDR12:
+       case MMC_TIMING_UHS_SDR25:
+       case MMC_TIMING_UHS_SDR50:
+       case MMC_TIMING_UHS_SDR104:
+       case MMC_TIMING_UHS_DDR50:
+       case MMC_TIMING_MMC_DDR52:
+               return true;
+       };
+       return false;
+}
+
+static bool sdhci_preset_needed(struct sdhci_host *host, unsigned char timing)
+{
+       return !(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN) &&
+              sdhci_timing_has_preset(timing);
+}
+
+static bool sdhci_presetable_values_change(struct sdhci_host *host, struct mmc_ios *ios)
+{
+       /*
+        * Preset Values are: Driver Strength, Clock Generator and SDCLK/RCLK
+        * Frequency. Check if preset values need to be enabled, or the Driver
+        * Strength needs updating. Note, clock changes are handled separately.
+        */
+       return !host->preset_enabled &&
+              (sdhci_preset_needed(host, ios->timing) || host->drv_type != ios->drv_type);
+}
+
 void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 {
        struct sdhci_host *host = mmc_priv(mmc);
+       bool reinit_uhs = host->reinit_uhs;
+       bool turning_on_clk = false;
        u8 ctrl;
 
+       host->reinit_uhs = false;
+
        if (ios->power_mode == MMC_POWER_UNDEFINED)
                return;
 
@@ -2323,6 +2359,8 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                sdhci_enable_preset_value(host, false);
 
        if (!ios->clock || ios->clock != host->clock) {
+               turning_on_clk = ios->clock && !host->clock;
+
                host->ops->set_clock(host, ios->clock);
                host->clock = ios->clock;
 
@@ -2349,6 +2387,17 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 
        host->ops->set_bus_width(host, ios->bus_width);
 
+       /*
+        * Special case to avoid multiple clock changes during voltage
+        * switching.
+        */
+       if (!reinit_uhs &&
+           turning_on_clk &&
+           host->timing == ios->timing &&
+           host->version >= SDHCI_SPEC_300 &&
+           !sdhci_presetable_values_change(host, ios))
+               return;
+
        ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
 
        if (!(host->quirks & SDHCI_QUIRK_NO_HISPD_BIT)) {
@@ -2392,6 +2441,7 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                        }
 
                        sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+                       host->drv_type = ios->drv_type;
                } else {
                        /*
                         * According to SDHC Spec v3.00, if the Preset Value
@@ -2419,19 +2469,14 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                host->ops->set_uhs_signaling(host, ios->timing);
                host->timing = ios->timing;
 
-               if (!(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN) &&
-                               ((ios->timing == MMC_TIMING_UHS_SDR12) ||
-                                (ios->timing == MMC_TIMING_UHS_SDR25) ||
-                                (ios->timing == MMC_TIMING_UHS_SDR50) ||
-                                (ios->timing == MMC_TIMING_UHS_SDR104) ||
-                                (ios->timing == MMC_TIMING_UHS_DDR50) ||
-                                (ios->timing == MMC_TIMING_MMC_DDR52))) {
+               if (sdhci_preset_needed(host, ios->timing)) {
                        u16 preset;
 
                        sdhci_enable_preset_value(host, true);
                        preset = sdhci_get_preset_value(host);
                        ios->drv_type = FIELD_GET(SDHCI_PRESET_DRV_MASK,
                                                  preset);
+                       host->drv_type = ios->drv_type;
                }
 
                /* Re-enable SD Clock */
@@ -3768,6 +3813,7 @@ int sdhci_resume_host(struct sdhci_host *host)
                sdhci_init(host, 0);
                host->pwr = 0;
                host->clock = 0;
+               host->reinit_uhs = true;
                mmc->ops->set_ios(mmc, &mmc->ios);
        } else {
                sdhci_init(host, (mmc->pm_flags & MMC_PM_KEEP_POWER));
@@ -3830,6 +3876,7 @@ int sdhci_runtime_resume_host(struct sdhci_host *host, int soft_reset)
                /* Force clock and power re-program */
                host->pwr = 0;
                host->clock = 0;
+               host->reinit_uhs = true;
                mmc->ops->start_signal_voltage_switch(mmc, &mmc->ios);
                mmc->ops->set_ios(mmc, &mmc->ios);
 
index d750c464bd1e59291fe4c85f7f3d189f3aff1587..87a3aaa0743873e9cf2a8a8e0d717d9d421f5dec 100644 (file)
@@ -524,6 +524,8 @@ struct sdhci_host {
 
        unsigned int clock;     /* Current clock (MHz) */
        u8 pwr;                 /* Current voltage */
+       u8 drv_type;            /* Current UHS-I driver type */
+       bool reinit_uhs;        /* Force UHS-related re-initialization */
 
        bool runtime_suspended; /* Host is runtime suspended */
        bool bus_on;            /* Bus power prevents runtime suspend */