A-003980: SDHC: Glitch is generated on the card clock with software reset or clock divider change Description: A glitch may occur on the SDHC card clock when the software sets the RSTA bit (software reset) in the system control register. It can also be generated by setting the clock divider value. The glitch produced can cause the external card to switch to an unknown state. The occurrence is not deterministic. Workaround: A simple workaround is to disable the SD card clock before the software reset, and enable it when the module resumes normal operation. The Host and the SD card are in a master-slave relationship. The Host provides clock and control transfer across the interface. Therefore, any existing operation is discarded when the Host controller is reset. The recommended flow is as follows: 1. Software disable bit[3], SDCLKEN, of the System Control Register 2. Trigger software reset and/or set clock divider 3. Check bit[3], SDSTB, of the Present State Register for stable clock 4. Enable bit[3], SDCLKEN, of the System Control Register Using the above method, the eSDHC cannot send command or transfer data when there is a glitch in the clock line, and the glitch does not cause any issue. Signed-off-by: Haijun Zhang <haijun.zhang@xxxxxxxxxxxxx> --- drivers/mmc/host/sdhci-esdhc.h | 25 ++++++++++++++--- drivers/mmc/host/sdhci-of-esdhc.c | 56 +++++++++++++++++++++++++++++++++++++++ include/linux/mmc/sdhci.h | 2 ++ 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc.h b/drivers/mmc/host/sdhci-esdhc.h index e70c4e6..432f9da 100644 --- a/drivers/mmc/host/sdhci-esdhc.h +++ b/drivers/mmc/host/sdhci-esdhc.h @@ -29,10 +29,14 @@ #define ESDHC_CLOCK_MASK 0x0000fff0 #define ESDHC_PREDIV_SHIFT 8 #define ESDHC_DIVIDER_SHIFT 4 +#define ESDHC_CLOCK_CRDEN 0x00000008 #define ESDHC_CLOCK_PEREN 0x00000004 #define ESDHC_CLOCK_HCKEN 0x00000002 #define ESDHC_CLOCK_IPGEN 0x00000001 +#define ESDHCI_PRESENT_STATE 0x24 +#define ESDHC_CLK_STABLE 0x00000008 + /* pltfm-specific */ #define ESDHC_HOST_CONTROL_LE 0x20 @@ -47,7 +51,7 @@ #define ESDHC_DMA_SYSCTL 0x40c #define ESDHC_DMA_SNOOP 0x00000040 -#define ESDHC_HOST_CONTROL_RES 0x05 +#define ESDHC_HOST_CONTROL_RES 0x01 static inline void esdhc_set_clock(struct sdhci_host *host, unsigned int clock, unsigned int host_clock) @@ -55,13 +59,14 @@ static inline void esdhc_set_clock(struct sdhci_host *host, unsigned int clock, int pre_div = 2; int div = 1; u32 temp; + u32 timeout; if (clock == 0) goto out; temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL); temp &= ~(ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN - | ESDHC_CLOCK_MASK); + | ESDHC_CLOCK_MASK | ESDHC_CLOCK_CRDEN); sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL); while (host_clock / pre_div / 16 > clock && pre_div < 256) @@ -82,7 +87,21 @@ static inline void esdhc_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 = 20; + while (!(sdhci_readl(host, ESDHCI_PRESENT_STATE) & ESDHC_CLK_STABLE)) { + if (timeout == 0) { + pr_err("%s: Internal clock never stabilised.\n", + mmc_hostname(host->mmc)); + return; + } + timeout--; + mdelay(1); + } + + temp |= ESDHC_CLOCK_CRDEN; + sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL); if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK) { host->timeout_clk = host->mmc->actual_clock / 1000; diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c index e328252..f5de1e5 100644 --- a/drivers/mmc/host/sdhci-of-esdhc.c +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -21,9 +21,11 @@ #include <linux/mmc/host.h> #include "sdhci-pltfm.h" #include "sdhci-esdhc.h" +#include <asm/mpc85xx.h> #define VENDOR_V_22 0x12 #define VENDOR_V_23 0x13 +static u32 svr; static u32 esdhc_readl(struct sdhci_host *host, int reg) { u32 ret; @@ -225,6 +227,30 @@ static void esdhc_of_resume(struct sdhci_host *host) } #endif +static void esdhc_platform_reset_enter(struct sdhci_host *host, u8 mask) +{ + if ((host->quirks2 & SDHCI_QUIRK2_DISABLE_CLOCK_BEFORE_RESET) && + (mask & SDHCI_RESET_ALL)) { + u16 clk; + + clk = esdhc_readw(host, SDHCI_CLOCK_CONTROL); + clk &= ~ESDHC_CLOCK_CRDEN; + esdhc_writew(host, clk, SDHCI_CLOCK_CONTROL); + } +} + +static void esdhc_platform_reset_exit(struct sdhci_host *host, u8 mask) +{ + if ((host->quirks2 & SDHCI_QUIRK2_DISABLE_CLOCK_BEFORE_RESET) && + (mask & SDHCI_RESET_ALL)) { + u16 clk; + + clk = esdhc_readw(host, SDHCI_CLOCK_CONTROL); + clk |= ESDHC_CLOCK_CRDEN; + esdhc_writew(host, clk, SDHCI_CLOCK_CONTROL); + } +} + static void esdhc_of_platform_init(struct sdhci_host *host) { u32 vvn; @@ -236,6 +262,33 @@ static void esdhc_of_platform_init(struct sdhci_host *host) if (vvn > VENDOR_V_22) host->quirks &= ~SDHCI_QUIRK_NO_BUSY_IRQ; + + /* + * Check for A-005055: A glitch is generated on the card clock + * due to software reset or a clock change + * Impact list: + * T4240-4160-R1.0 B4860-4420-R1.0-R2.0 P3041-R1.0-R1.1-R2.0 + * P2041-2040-R1.0-R1.1-R2.0 P1010-1014-R1.0 P5020-5010-R1.0-R2.0 + * P5040-5021-R1.0-R2.0-R2.1 + */ + if (((SVR_SOC_VER(svr) == SVR_T4240) && (SVR_REV(svr) == 0x10)) || + ((SVR_SOC_VER(svr) == SVR_T4240) && (SVR_REV(svr) == 0x20)) || + ((SVR_SOC_VER(svr) == SVR_T4160) && (SVR_REV(svr) == 0x10)) || + ((SVR_SOC_VER(svr) == SVR_T4160) && (SVR_REV(svr) == 0x20)) || + ((SVR_SOC_VER(svr) == SVR_B4860) && (SVR_REV(svr) == 0x10)) || + ((SVR_SOC_VER(svr) == SVR_B4860) && (SVR_REV(svr) == 0x20)) || + ((SVR_SOC_VER(svr) == SVR_B4420) && (SVR_REV(svr) == 0x10)) || + ((SVR_SOC_VER(svr) == SVR_B4420) && (SVR_REV(svr) == 0x20)) || + ((SVR_SOC_VER(svr) == SVR_P1010) && (SVR_REV(svr) == 0x10)) || + ((SVR_SOC_VER(svr) == SVR_P1014) && (SVR_REV(svr) == 0x10)) || + ((SVR_SOC_VER(svr) == SVR_P3041) && (SVR_REV(svr) <= 0x20)) || + ((SVR_SOC_VER(svr) == SVR_P5020) && (SVR_REV(svr) <= 0x20)) || + ((SVR_SOC_VER(svr) == SVR_P5010) && (SVR_REV(svr) <= 0x20)) || + ((SVR_SOC_VER(svr) == SVR_P5040) && (SVR_REV(svr) <= 0x21)) || + ((SVR_SOC_VER(svr) == SVR_P5021) && (SVR_REV(svr) <= 0x21)) || + ((SVR_SOC_VER(svr) == SVR_P2040) && (SVR_REV(svr) <= 0x20)) || + ((SVR_SOC_VER(svr) == SVR_P2041) && (SVR_REV(svr) <= 0x20))) + host->quirks2 |= SDHCI_QUIRK2_DISABLE_CLOCK_BEFORE_RESET; } static int esdhc_pltfm_bus_width(struct sdhci_host *host, int width) @@ -274,6 +327,8 @@ static const struct sdhci_ops sdhci_esdhc_ops = { .get_max_clock = esdhc_of_get_max_clock, .get_min_clock = esdhc_of_get_min_clock, .platform_init = esdhc_of_platform_init, + .platform_reset_enter = esdhc_platform_reset_enter, + .platform_reset_exit = esdhc_platform_reset_exit, #ifdef CONFIG_PM .platform_suspend = esdhc_of_suspend, .platform_resume = esdhc_of_resume, @@ -299,6 +354,7 @@ static int sdhci_esdhc_probe(struct platform_device *pdev) struct device_node *np; int ret; + svr = mfspr(SPRN_SVR); host = sdhci_pltfm_init(pdev, &sdhci_esdhc_pdata, 0); if (IS_ERR(host)) return PTR_ERR(host); diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h index 3e781b8..1f1e2fa 100644 --- a/include/linux/mmc/sdhci.h +++ b/include/linux/mmc/sdhci.h @@ -98,6 +98,8 @@ struct sdhci_host { #define SDHCI_QUIRK2_CARD_ON_NEEDS_BUS_ON (1<<4) /* Controller has a non-standard host control register */ #define SDHCI_QUIRK2_BROKEN_HOST_CONTROL (1<<5) +/* Controller need to disable clock before reset all */ +#define SDHCI_QUIRK2_DISABLE_CLOCK_BEFORE_RESET (1<<6) int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ -- 1.8.0 -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html