aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/layerscape/patches-4.9/812-mmc-layerscape-support.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/layerscape/patches-4.9/812-mmc-layerscape-support.patch')
-rw-r--r--target/linux/layerscape/patches-4.9/812-mmc-layerscape-support.patch611
1 files changed, 611 insertions, 0 deletions
diff --git a/target/linux/layerscape/patches-4.9/812-mmc-layerscape-support.patch b/target/linux/layerscape/patches-4.9/812-mmc-layerscape-support.patch
new file mode 100644
index 0000000000..ff19a6da03
--- /dev/null
+++ b/target/linux/layerscape/patches-4.9/812-mmc-layerscape-support.patch
@@ -0,0 +1,611 @@
+From b31046c51c72232363711f0c623df08bf28c37e4 Mon Sep 17 00:00:00 2001
+From: Yangbo Lu <yangbo.lu@nxp.com>
+Date: Mon, 25 Sep 2017 12:21:30 +0800
+Subject: [PATCH] mmc: layerscape support
+
+This is a integrated patch for layerscape mmc support.
+
+Adrian Hunter <adrian.hunter@intel.com>
+Jaehoon Chung <jh80.chung@samsung.com>
+Masahiro Yamada <yamada.masahiro@socionext.com>
+Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com>
+---
+ drivers/mmc/host/Kconfig | 1 +
+ drivers/mmc/host/sdhci-esdhc.h | 52 +++++---
+ drivers/mmc/host/sdhci-of-esdhc.c | 251 ++++++++++++++++++++++++++++++++++++--
+ drivers/mmc/host/sdhci.c | 45 ++++---
+ drivers/mmc/host/sdhci.h | 3 +
+ 5 files changed, 306 insertions(+), 46 deletions(-)
+
+diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
+index 5274f503..a1135a92 100644
+--- a/drivers/mmc/host/Kconfig
++++ b/drivers/mmc/host/Kconfig
+@@ -144,6 +144,7 @@ config MMC_SDHCI_OF_ESDHC
+ depends on MMC_SDHCI_PLTFM
+ depends on PPC || ARCH_MXC || ARCH_LAYERSCAPE
+ select MMC_SDHCI_IO_ACCESSORS
++ select FSL_GUTS
+ help
+ This selects the Freescale eSDHC controller support.
+
+diff --git a/drivers/mmc/host/sdhci-esdhc.h b/drivers/mmc/host/sdhci-esdhc.h
+index de132e28..98898a30 100644
+--- a/drivers/mmc/host/sdhci-esdhc.h
++++ b/drivers/mmc/host/sdhci-esdhc.h
+@@ -24,30 +24,46 @@
+ SDHCI_QUIRK_PIO_NEEDS_DELAY | \
+ SDHCI_QUIRK_NO_HISPD_BIT)
+
+-#define ESDHC_PROCTL 0x28
+-
+-#define ESDHC_SYSTEM_CONTROL 0x2c
+-#define ESDHC_CLOCK_MASK 0x0000fff0
+-#define ESDHC_PREDIV_SHIFT 8
+-#define ESDHC_DIVIDER_SHIFT 4
+-#define ESDHC_CLOCK_PEREN 0x00000004
+-#define ESDHC_CLOCK_HCKEN 0x00000002
+-#define ESDHC_CLOCK_IPGEN 0x00000001
+-
+ /* pltfm-specific */
+ #define ESDHC_HOST_CONTROL_LE 0x20
+
+ /*
+- * P2020 interpretation of the SDHCI_HOST_CONTROL register
++ * eSDHC register definition
+ */
+-#define ESDHC_CTRL_4BITBUS (0x1 << 1)
+-#define ESDHC_CTRL_8BITBUS (0x2 << 1)
+-#define ESDHC_CTRL_BUSWIDTH_MASK (0x3 << 1)
+
+-/* OF-specific */
+-#define ESDHC_DMA_SYSCTL 0x40c
+-#define ESDHC_DMA_SNOOP 0x00000040
++/* Present State Register */
++#define ESDHC_PRSSTAT 0x24
++#define ESDHC_CLOCK_STABLE 0x00000008
++
++/* Protocol Control Register */
++#define ESDHC_PROCTL 0x28
++#define ESDHC_VOLT_SEL 0x00000400
++#define ESDHC_CTRL_4BITBUS (0x1 << 1)
++#define ESDHC_CTRL_8BITBUS (0x2 << 1)
++#define ESDHC_CTRL_BUSWIDTH_MASK (0x3 << 1)
++#define ESDHC_HOST_CONTROL_RES 0x01
++
++/* System Control Register */
++#define ESDHC_SYSTEM_CONTROL 0x2c
++#define ESDHC_CLOCK_MASK 0x0000fff0
++#define ESDHC_PREDIV_SHIFT 8
++#define ESDHC_DIVIDER_SHIFT 4
++#define ESDHC_CLOCK_SDCLKEN 0x00000008
++#define ESDHC_CLOCK_PEREN 0x00000004
++#define ESDHC_CLOCK_HCKEN 0x00000002
++#define ESDHC_CLOCK_IPGEN 0x00000001
++
++/* Host Controller Capabilities Register 2 */
++#define ESDHC_CAPABILITIES_1 0x114
++
++/* Tuning Block Control Register */
++#define ESDHC_TBCTL 0x120
++#define ESDHC_TB_EN 0x00000004
+
+-#define ESDHC_HOST_CONTROL_RES 0x01
++/* Control Register for DMA transfer */
++#define ESDHC_DMA_SYSCTL 0x40c
++#define ESDHC_PERIPHERAL_CLK_SEL 0x00080000
++#define ESDHC_FLUSH_ASYNC_FIFO 0x00040000
++#define ESDHC_DMA_SNOOP 0x00000040
+
+ #endif /* _DRIVERS_MMC_SDHCI_ESDHC_H */
+diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c
+index 3c27401c..4b0f375b 100644
+--- a/drivers/mmc/host/sdhci-of-esdhc.c
++++ b/drivers/mmc/host/sdhci-of-esdhc.c
+@@ -16,8 +16,12 @@
+ #include <linux/err.h>
+ #include <linux/io.h>
+ #include <linux/of.h>
++#include <linux/of_address.h>
+ #include <linux/delay.h>
+ #include <linux/module.h>
++#include <linux/sys_soc.h>
++#include <linux/clk.h>
++#include <linux/ktime.h>
+ #include <linux/mmc/host.h>
+ #include "sdhci-pltfm.h"
+ #include "sdhci-esdhc.h"
+@@ -28,8 +32,12 @@
+ struct sdhci_esdhc {
+ u8 vendor_ver;
+ u8 spec_ver;
++ bool quirk_incorrect_hostver;
++ unsigned int peripheral_clock;
+ };
+
++static void esdhc_clock_enable(struct sdhci_host *host, bool enable);
++
+ /**
+ * esdhc_read*_fixup - Fixup the value read from incompatible eSDHC register
+ * to make it compatible with SD spec.
+@@ -80,6 +88,17 @@ static u32 esdhc_readl_fixup(struct sdhci_host *host,
+ return ret;
+ }
+
++ /*
++ * DTS properties of mmc host are used to enable each speed mode
++ * according to soc and board capability. So clean up
++ * SDR50/SDR104/DDR50 support bits here.
++ */
++ if (spec_reg == SDHCI_CAPABILITIES_1) {
++ ret = value & (~(SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_SDR104 |
++ SDHCI_SUPPORT_DDR50));
++ return ret;
++ }
++
+ ret = value;
+ return ret;
+ }
+@@ -87,6 +106,8 @@ static u32 esdhc_readl_fixup(struct sdhci_host *host,
+ static u16 esdhc_readw_fixup(struct sdhci_host *host,
+ int spec_reg, u32 value)
+ {
++ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
++ struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
+ u16 ret;
+ int shift = (spec_reg & 0x2) * 8;
+
+@@ -94,6 +115,12 @@ static u16 esdhc_readw_fixup(struct sdhci_host *host,
+ ret = value & 0xffff;
+ else
+ ret = (value >> shift) & 0xffff;
++ /* Workaround for T4240-R1.0-R2.0 eSDHC which has incorrect
++ * vendor version and spec version information.
++ */
++ if ((spec_reg == SDHCI_HOST_VERSION) &&
++ (esdhc->quirk_incorrect_hostver))
++ ret = (VENDOR_V_23 << SDHCI_VENDOR_VER_SHIFT) | SDHCI_SPEC_200;
+ return ret;
+ }
+
+@@ -235,7 +262,11 @@ static u32 esdhc_be_readl(struct sdhci_host *host, int reg)
+ u32 ret;
+ u32 value;
+
+- value = ioread32be(host->ioaddr + reg);
++ if (reg == SDHCI_CAPABILITIES_1)
++ value = ioread32be(host->ioaddr + ESDHC_CAPABILITIES_1);
++ else
++ value = ioread32be(host->ioaddr + reg);
++
+ ret = esdhc_readl_fixup(host, reg, value);
+
+ return ret;
+@@ -246,7 +277,11 @@ static u32 esdhc_le_readl(struct sdhci_host *host, int reg)
+ u32 ret;
+ u32 value;
+
+- value = ioread32(host->ioaddr + reg);
++ if (reg == SDHCI_CAPABILITIES_1)
++ value = ioread32(host->ioaddr + ESDHC_CAPABILITIES_1);
++ else
++ value = ioread32(host->ioaddr + reg);
++
+ ret = esdhc_readl_fixup(host, reg, value);
+
+ return ret;
+@@ -404,15 +439,25 @@ static int esdhc_of_enable_dma(struct sdhci_host *host)
+ static unsigned int esdhc_of_get_max_clock(struct sdhci_host *host)
+ {
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
++ struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
+
+- return pltfm_host->clock;
++ if (esdhc->peripheral_clock)
++ return esdhc->peripheral_clock;
++ else
++ return pltfm_host->clock;
+ }
+
+ static unsigned int esdhc_of_get_min_clock(struct sdhci_host *host)
+ {
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
++ struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
++ unsigned int clock;
+
+- return pltfm_host->clock / 256 / 16;
++ if (esdhc->peripheral_clock)
++ clock = esdhc->peripheral_clock;
++ else
++ clock = pltfm_host->clock;
++ return clock / 256 / 16;
+ }
+
+ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock)
+@@ -421,17 +466,34 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock)
+ struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
+ int pre_div = 1;
+ int div = 1;
++ ktime_t timeout;
+ u32 temp;
+
+ host->mmc->actual_clock = 0;
+
+- if (clock == 0)
++ if (clock == 0) {
++ esdhc_clock_enable(host, false);
+ return;
++ }
+
+ /* Workaround to start pre_div at 2 for VNN < VENDOR_V_23 */
+ if (esdhc->vendor_ver < VENDOR_V_23)
+ pre_div = 2;
+
++ /*
++ * Limit SD clock to 167MHz for ls1046a according to its datasheet
++ */
++ if (clock > 167000000 &&
++ of_find_compatible_node(NULL, NULL, "fsl,ls1046a-esdhc"))
++ clock = 167000000;
++
++ /*
++ * Limit SD clock to 125MHz for ls1012a according to its datasheet
++ */
++ if (clock > 125000000 &&
++ of_find_compatible_node(NULL, NULL, "fsl,ls1012a-esdhc"))
++ clock = 125000000;
++
+ /* Workaround to reduce the clock frequency for p1010 esdhc */
+ if (of_find_compatible_node(NULL, NULL, "fsl,p1010-esdhc")) {
+ if (clock > 20000000)
+@@ -441,8 +503,8 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock)
+ }
+
+ temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL);
+- temp &= ~(ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN
+- | ESDHC_CLOCK_MASK);
++ temp &= ~(ESDHC_CLOCK_SDCLKEN | ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN |
++ ESDHC_CLOCK_PEREN | ESDHC_CLOCK_MASK);
+ sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL);
+
+ while (host->max_clk / pre_div / 16 > clock && pre_div < 256)
+@@ -462,7 +524,20 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock)
+ | (div << ESDHC_DIVIDER_SHIFT)
+ | (pre_div << ESDHC_PREDIV_SHIFT));
+ sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL);
+- mdelay(1);
++
++ /* Wait max 20 ms */
++ timeout = ktime_add_ms(ktime_get(), 20);
++ while (!(sdhci_readl(host, ESDHC_PRSSTAT) & ESDHC_CLOCK_STABLE)) {
++ if (ktime_after(ktime_get(), timeout)) {
++ pr_err("%s: Internal clock never stabilised.\n",
++ mmc_hostname(host->mmc));
++ return;
++ }
++ udelay(10);
++ }
++
++ temp |= ESDHC_CLOCK_SDCLKEN;
++ sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL);
+ }
+
+ static void esdhc_pltfm_set_bus_width(struct sdhci_host *host, int width)
+@@ -487,6 +562,33 @@ static void esdhc_pltfm_set_bus_width(struct sdhci_host *host, int width)
+ sdhci_writel(host, ctrl, ESDHC_PROCTL);
+ }
+
++static void esdhc_clock_enable(struct sdhci_host *host, bool enable)
++{
++ u32 val;
++ ktime_t timeout;
++
++ val = sdhci_readl(host, ESDHC_SYSTEM_CONTROL);
++
++ if (enable)
++ val |= ESDHC_CLOCK_SDCLKEN;
++ else
++ val &= ~ESDHC_CLOCK_SDCLKEN;
++
++ sdhci_writel(host, val, ESDHC_SYSTEM_CONTROL);
++
++ /* Wait max 20 ms */
++ timeout = ktime_add_ms(ktime_get(), 20);
++ val = ESDHC_CLOCK_STABLE;
++ while (!(sdhci_readl(host, ESDHC_PRSSTAT) & val)) {
++ if (ktime_after(ktime_get(), timeout)) {
++ pr_err("%s: Internal clock never stabilised.\n",
++ mmc_hostname(host->mmc));
++ break;
++ }
++ udelay(10);
++ }
++}
++
+ static void esdhc_reset(struct sdhci_host *host, u8 mask)
+ {
+ sdhci_reset(host, mask);
+@@ -495,6 +597,95 @@ static void esdhc_reset(struct sdhci_host *host, u8 mask)
+ sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
+ }
+
++/* The SCFG, Supplemental Configuration Unit, provides SoC specific
++ * configuration and status registers for the device. There is a
++ * SDHC IO VSEL control register on SCFG for some platforms. It's
++ * used to support SDHC IO voltage switching.
++ */
++static const struct of_device_id scfg_device_ids[] = {
++ { .compatible = "fsl,t1040-scfg", },
++ { .compatible = "fsl,ls1012a-scfg", },
++ { .compatible = "fsl,ls1046a-scfg", },
++ {}
++};
++
++/* SDHC IO VSEL control register definition */
++#define SCFG_SDHCIOVSELCR 0x408
++#define SDHCIOVSELCR_TGLEN 0x80000000
++#define SDHCIOVSELCR_VSELVAL 0x60000000
++#define SDHCIOVSELCR_SDHC_VS 0x00000001
++
++static int esdhc_signal_voltage_switch(struct mmc_host *mmc,
++ struct mmc_ios *ios)
++{
++ struct sdhci_host *host = mmc_priv(mmc);
++ struct device_node *scfg_node;
++ void __iomem *scfg_base = NULL;
++ u32 sdhciovselcr;
++ u32 val;
++
++ /*
++ * Signal Voltage Switching is only applicable for Host Controllers
++ * v3.00 and above.
++ */
++ if (host->version < SDHCI_SPEC_300)
++ return 0;
++
++ val = sdhci_readl(host, ESDHC_PROCTL);
++
++ switch (ios->signal_voltage) {
++ case MMC_SIGNAL_VOLTAGE_330:
++ val &= ~ESDHC_VOLT_SEL;
++ sdhci_writel(host, val, ESDHC_PROCTL);
++ return 0;
++ case MMC_SIGNAL_VOLTAGE_180:
++ scfg_node = of_find_matching_node(NULL, scfg_device_ids);
++ if (scfg_node)
++ scfg_base = of_iomap(scfg_node, 0);
++ if (scfg_base) {
++ sdhciovselcr = SDHCIOVSELCR_TGLEN |
++ SDHCIOVSELCR_VSELVAL;
++ iowrite32be(sdhciovselcr,
++ scfg_base + SCFG_SDHCIOVSELCR);
++
++ val |= ESDHC_VOLT_SEL;
++ sdhci_writel(host, val, ESDHC_PROCTL);
++ mdelay(5);
++
++ sdhciovselcr = SDHCIOVSELCR_TGLEN |
++ SDHCIOVSELCR_SDHC_VS;
++ iowrite32be(sdhciovselcr,
++ scfg_base + SCFG_SDHCIOVSELCR);
++ iounmap(scfg_base);
++ } else {
++ val |= ESDHC_VOLT_SEL;
++ sdhci_writel(host, val, ESDHC_PROCTL);
++ }
++ return 0;
++ default:
++ return 0;
++ }
++}
++
++static int esdhc_execute_tuning(struct mmc_host *mmc, u32 opcode)
++{
++ struct sdhci_host *host = mmc_priv(mmc);
++ u32 val;
++
++ /* Use tuning block for tuning procedure */
++ esdhc_clock_enable(host, false);
++ val = sdhci_readl(host, ESDHC_DMA_SYSCTL);
++ val |= ESDHC_FLUSH_ASYNC_FIFO;
++ sdhci_writel(host, val, ESDHC_DMA_SYSCTL);
++
++ val = sdhci_readl(host, ESDHC_TBCTL);
++ val |= ESDHC_TB_EN;
++ sdhci_writel(host, val, ESDHC_TBCTL);
++ esdhc_clock_enable(host, true);
++
++ return sdhci_execute_tuning(mmc, opcode);
++}
++
+ #ifdef CONFIG_PM_SLEEP
+ static u32 esdhc_proctl;
+ static int esdhc_of_suspend(struct device *dev)
+@@ -575,10 +766,19 @@ static const struct sdhci_pltfm_data sdhci_esdhc_le_pdata = {
+ .ops = &sdhci_esdhc_le_ops,
+ };
+
++static struct soc_device_attribute soc_incorrect_hostver[] = {
++ { .family = "QorIQ T4240", .revision = "1.0", },
++ { .family = "QorIQ T4240", .revision = "2.0", },
++ { },
++};
++
+ static void esdhc_init(struct platform_device *pdev, struct sdhci_host *host)
+ {
+ struct sdhci_pltfm_host *pltfm_host;
+ struct sdhci_esdhc *esdhc;
++ struct device_node *np;
++ struct clk *clk;
++ u32 val;
+ u16 host_ver;
+
+ pltfm_host = sdhci_priv(host);
+@@ -588,6 +788,36 @@ static void esdhc_init(struct platform_device *pdev, struct sdhci_host *host)
+ esdhc->vendor_ver = (host_ver & SDHCI_VENDOR_VER_MASK) >>
+ SDHCI_VENDOR_VER_SHIFT;
+ esdhc->spec_ver = host_ver & SDHCI_SPEC_VER_MASK;
++ if (soc_device_match(soc_incorrect_hostver))
++ esdhc->quirk_incorrect_hostver = true;
++ else
++ esdhc->quirk_incorrect_hostver = false;
++
++ np = pdev->dev.of_node;
++ clk = of_clk_get(np, 0);
++ if (!IS_ERR(clk)) {
++ /*
++ * esdhc->peripheral_clock would be assigned with a value
++ * which is eSDHC base clock when use periperal clock.
++ * For ls1046a, the clock value got by common clk API is
++ * peripheral clock while the eSDHC base clock is 1/2
++ * peripheral clock.
++ */
++ if (of_device_is_compatible(np, "fsl,ls1046a-esdhc"))
++ esdhc->peripheral_clock = clk_get_rate(clk) / 2;
++ else
++ esdhc->peripheral_clock = clk_get_rate(clk);
++
++ clk_put(clk);
++ }
++
++ if (esdhc->peripheral_clock) {
++ esdhc_clock_enable(host, false);
++ val = sdhci_readl(host, ESDHC_DMA_SYSCTL);
++ val |= ESDHC_PERIPHERAL_CLK_SEL;
++ sdhci_writel(host, val, ESDHC_DMA_SYSCTL);
++ esdhc_clock_enable(host, true);
++ }
+ }
+
+ static int sdhci_esdhc_probe(struct platform_device *pdev)
+@@ -610,6 +840,11 @@ static int sdhci_esdhc_probe(struct platform_device *pdev)
+ if (IS_ERR(host))
+ return PTR_ERR(host);
+
++ host->mmc_host_ops.start_signal_voltage_switch =
++ esdhc_signal_voltage_switch;
++ host->mmc_host_ops.execute_tuning = esdhc_execute_tuning;
++ host->tuning_delay = 1;
++
+ esdhc_init(pdev, host);
+
+ sdhci_get_of_property(pdev);
+diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
+index 7d275e72..099c3bf5 100644
+--- a/drivers/mmc/host/sdhci.c
++++ b/drivers/mmc/host/sdhci.c
+@@ -1624,26 +1624,24 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
+
+- if ((ios->timing == MMC_TIMING_SD_HS ||
+- ios->timing == MMC_TIMING_MMC_HS)
+- && !(host->quirks & SDHCI_QUIRK_NO_HISPD_BIT))
+- ctrl |= SDHCI_CTRL_HISPD;
+- else
+- ctrl &= ~SDHCI_CTRL_HISPD;
++ if (!(host->quirks & SDHCI_QUIRK_NO_HISPD_BIT)) {
++ if ((ios->timing == MMC_TIMING_SD_HS ||
++ ios->timing == MMC_TIMING_MMC_HS ||
++ ios->timing == MMC_TIMING_MMC_HS400 ||
++ ios->timing == MMC_TIMING_MMC_HS200 ||
++ ios->timing == MMC_TIMING_MMC_DDR52 ||
++ ios->timing == MMC_TIMING_UHS_SDR50 ||
++ ios->timing == MMC_TIMING_UHS_SDR104 ||
++ ios->timing == MMC_TIMING_UHS_DDR50 ||
++ ios->timing == MMC_TIMING_UHS_SDR25))
++ ctrl |= SDHCI_CTRL_HISPD;
++ else
++ ctrl &= ~SDHCI_CTRL_HISPD;
++ }
+
+ if (host->version >= SDHCI_SPEC_300) {
+ u16 clk, ctrl_2;
+
+- /* In case of UHS-I modes, set High Speed Enable */
+- if ((ios->timing == MMC_TIMING_MMC_HS400) ||
+- (ios->timing == MMC_TIMING_MMC_HS200) ||
+- (ios->timing == MMC_TIMING_MMC_DDR52) ||
+- (ios->timing == MMC_TIMING_UHS_SDR50) ||
+- (ios->timing == MMC_TIMING_UHS_SDR104) ||
+- (ios->timing == MMC_TIMING_UHS_DDR50) ||
+- (ios->timing == MMC_TIMING_UHS_SDR25))
+- ctrl |= SDHCI_CTRL_HISPD;
+-
+ if (!host->preset_enabled) {
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+ /*
+@@ -1956,7 +1954,7 @@ static int sdhci_prepare_hs400_tuning(struct mmc_host *mmc, struct mmc_ios *ios)
+ return 0;
+ }
+
+-static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
++int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
+ {
+ struct sdhci_host *host = mmc_priv(mmc);
+ u16 ctrl;
+@@ -2015,6 +2013,9 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
+ return err;
+ }
+
++ if (host->tuning_delay < 0)
++ host->tuning_delay = opcode == MMC_SEND_TUNING_BLOCK;
++
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ ctrl |= SDHCI_CTRL_EXEC_TUNING;
+ if (host->quirks2 & SDHCI_QUIRK2_TUNING_WORK_AROUND)
+@@ -2127,9 +2128,10 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
+
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+
+- /* eMMC spec does not require a delay between tuning cycles */
+- if (opcode == MMC_SEND_TUNING_BLOCK)
+- mdelay(1);
++ /* Spec does not require a delay between tuning cycles */
++ if (host->tuning_delay > 0)
++ mdelay(host->tuning_delay);
++
+ } while (ctrl & SDHCI_CTRL_EXEC_TUNING);
+
+ /*
+@@ -2165,6 +2167,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
+ spin_unlock_irqrestore(&host->lock, flags);
+ return err;
+ }
++EXPORT_SYMBOL_GPL(sdhci_execute_tuning);
+
+ static int sdhci_select_drive_strength(struct mmc_card *card,
+ unsigned int max_dtr, int host_drv,
+@@ -2997,6 +3000,8 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev,
+
+ host->flags = SDHCI_SIGNALING_330;
+
++ host->tuning_delay = -1;
++
+ return host;
+ }
+
+diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
+index 2570455b..088bed43 100644
+--- a/drivers/mmc/host/sdhci.h
++++ b/drivers/mmc/host/sdhci.h
+@@ -524,6 +524,8 @@ struct sdhci_host {
+ #define SDHCI_TUNING_MODE_1 0
+ #define SDHCI_TUNING_MODE_2 1
+ #define SDHCI_TUNING_MODE_3 2
++ /* Delay (ms) between tuning commands */
++ int tuning_delay;
+
+ unsigned long private[0] ____cacheline_aligned;
+ };
+@@ -689,6 +691,7 @@ void sdhci_set_power_noreg(struct sdhci_host *host, unsigned char mode,
+ void sdhci_set_bus_width(struct sdhci_host *host, int width);
+ void sdhci_reset(struct sdhci_host *host, u8 mask);
+ void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
++int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
+
+ #ifdef CONFIG_PM
+ extern int sdhci_suspend_host(struct sdhci_host *host);
+--
+2.14.1
+