From 4215d5757595e7ec7ca146c2b901beb177f415d8 Mon Sep 17 00:00:00 2001 From: Yangbo Lu <yangbo.lu@nxp.com> Date: Wed, 17 Jan 2018 15:37:13 +0800 Subject: [PATCH 24/30] mmc: layerscape support This is an 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 | 265 ++++++++++++++++++++++++++++++++++++-- drivers/mmc/host/sdhci.c | 45 ++++--- drivers/mmc/host/sdhci.h | 3 + 5 files changed, 320 insertions(+), 46 deletions(-) --- 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. --- 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 -#define ESDHC_HOST_CONTROL_RES 0x01 +/* 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 + +/* 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 */ --- 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 sdhc 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 sdhc 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 sdhc 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_h 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_h 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 sd 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,12 +466,15 @@ static void esdhc_of_set_clock(struct sd 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) @@ -454,9 +502,15 @@ static void esdhc_of_set_clock(struct sd clock -= 5000000; } + /* Workaround to reduce the clock frequency for ls1021a esdhc */ + if (of_find_compatible_node(NULL, NULL, "fsl,ls1021a-esdhc")) { + if (clock == 50000000) + clock = 46500000; + } + 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) @@ -476,7 +530,20 @@ static void esdhc_of_set_clock(struct sd | (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) @@ -501,12 +568,136 @@ static void esdhc_pltfm_set_bus_width(st 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) { + u32 val; + sdhci_reset(host, mask); sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); + + if (mask & SDHCI_RESET_ALL) { + val = sdhci_readl(host, ESDHC_TBCTL); + val &= ~ESDHC_TB_EN; + sdhci_writel(host, val, ESDHC_TBCTL); + } +} + +/* 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 @@ -589,10 +780,19 @@ static const struct sdhci_pltfm_data sdh .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); @@ -602,6 +802,36 @@ static void esdhc_init(struct platform_d 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) @@ -624,6 +854,11 @@ static int sdhci_esdhc_probe(struct plat 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); --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1631,26 +1631,24 @@ static void sdhci_set_ios(struct mmc_hos 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); /* @@ -1963,7 +1961,7 @@ static int sdhci_prepare_hs400_tuning(st 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; @@ -2022,6 +2020,9 @@ static int sdhci_execute_tuning(struct m 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) @@ -2134,9 +2135,10 @@ static int sdhci_execute_tuning(struct m 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); /* @@ -2172,6 +2174,7 @@ out_unlock: 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, @@ -3004,6 +3007,8 @@ struct sdhci_host *sdhci_alloc_host(stru host->flags = SDHCI_SIGNALING_330; + host->tuning_delay = -1; + return host; } --- 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_ 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);