]> git.baikalelectronics.ru Git - kernel.git/commitdiff
mmc: sdhci: Fix voltage switch delay
authorAdrian Hunter <adrian.hunter@intel.com>
Mon, 28 Nov 2022 13:32:56 +0000 (15:32 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 8 Dec 2022 10:23:06 +0000 (11:23 +0100)
commit c981cdfb9925f64a364f13c2b4f98f877308a408 upstream.

Commit 1bcb2ef5efa9 ("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: 1bcb2ef5efa9 ("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>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/mmc/host/sdhci.c
drivers/mmc/host/sdhci.h

index f56b279d8e32c33da540f3c9ef79fa055cc4ba17..115feb9aa236440287611f2ec5d8ca639416a167 100644 (file)
@@ -332,6 +332,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);
        }
 }
@@ -1911,11 +1912,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;
 
@@ -1941,6 +1977,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;
 
@@ -1967,6 +2005,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)) {
@@ -2010,6 +2059,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
@@ -2037,19 +2087,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 */
@@ -3327,6 +3372,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, (host->mmc->pm_flags & MMC_PM_KEEP_POWER));
@@ -3389,6 +3435,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 bd74a3ae129908914663619028bd530b1e46643a..00df46a75be574705d1b1eab6c644a6b364e14ad 100644 (file)
@@ -528,6 +528,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 */