From 8fdda1cc1033e2bd0d048188af5167faffbf9b38 Mon Sep 17 00:00:00 2001 From: Yangbo Lu Date: Wed, 27 Sep 2017 15:31:31 +0800 Subject: layerscape: add linux 4.9 support This patch is to add linux 4.9 support for layerscape. All these kernel patches are from NXP LSDK 1709 release. Signed-off-by: Yangbo Lu --- .../patches-4.9/812-mmc-layerscape-support.patch | 611 +++++++++++++++++++++ 1 file changed, 611 insertions(+) create mode 100644 target/linux/layerscape/patches-4.9/812-mmc-layerscape-support.patch (limited to 'target/linux/layerscape/patches-4.9/812-mmc-layerscape-support.patch') 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 +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 +Jaehoon Chung +Masahiro Yamada +Signed-off-by: Yangbo Lu +--- + 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 + #include + #include ++#include + #include + #include ++#include ++#include ++#include + #include + #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 + -- cgit v1.2.3