On 19 May 2015 at 07:40, Yangbo Lu <yangbo.lu@xxxxxxxxxxxxx> wrote: > 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: Yangbo Lu <yangbo.lu@xxxxxxxxxxxxx> > --- > Changes for v2 > - Used sdhci_ops reset instead of platform_reset_enter/out's work > - Changed commit message > --- > drivers/mmc/host/sdhci-esdhc.h | 4 ++ > drivers/mmc/host/sdhci-of-esdhc.c | 90 +++++++++++++++++++++++++++++++++++++-- > 2 files changed, 91 insertions(+), 3 deletions(-) > > diff --git a/drivers/mmc/host/sdhci-esdhc.h b/drivers/mmc/host/sdhci-esdhc.h > index 3497cfa..ede49f5 100644 > --- a/drivers/mmc/host/sdhci-esdhc.h > +++ b/drivers/mmc/host/sdhci-esdhc.h > @@ -27,10 +27,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 > > diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c > index 22e9111..db78b75 100644 > --- a/drivers/mmc/host/sdhci-of-esdhc.c > +++ b/drivers/mmc/host/sdhci-of-esdhc.c > @@ -21,9 +21,16 @@ > #include <linux/mmc/host.h> > #include "sdhci-pltfm.h" > #include "sdhci-esdhc.h" > +#ifdef CONFIG_PPC > +#include <asm/mpc85xx.h> As Jaehoon already pointed out, we don't want to include machine specific headers in drivers. Please find another solution to this. Kind regards Uffe > +#endif > > #define VENDOR_V_22 0x12 > #define VENDOR_V_23 0x13 > + > +#ifdef CONFIG_PPC > +static u32 svr; > +#endif > static u32 esdhc_readl(struct sdhci_host *host, int reg) > { > u32 ret; > @@ -202,6 +209,7 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) > int pre_div = 2; > int div = 1; > u32 temp; > + u32 timeout; > > host->mmc->actual_clock = 0; > > @@ -218,7 +226,7 @@ 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); > + | ESDHC_CLOCK_MASK | ESDHC_CLOCK_CRDEN); > sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL); > > while (host->max_clk / pre_div / 16 > clock && pre_div < 256) > @@ -238,7 +246,21 @@ 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 = 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); > } > > static void esdhc_of_platform_init(struct sdhci_host *host) > @@ -276,9 +298,71 @@ static void esdhc_pltfm_set_bus_width(struct sdhci_host *host, int width) > ESDHC_CTRL_BUSWIDTH_MASK, ctrl); > } > > +/* > + * A-003980: SDHC: Glitch is generated on the card clock with software reset > + * or clock divider change > + * 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. > + */ > +static int esdhc_of_reset_workaround(struct sdhci_host *host, u8 mask) > +{ > + u16 clk; > + bool disable_clk_before_reset = false; > + > +#ifdef CONFIG_PPC > + svr = mfspr(SPRN_SVR); > + /* > + * Check for A-003980 > + * 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_P5040) && (SVR_REV(svr) <= 0x21)) || > + ((SVR_SOC_VER(svr) == SVR_P5020) && (SVR_REV(svr) <= 0x20)) || > + ((SVR_SOC_VER(svr) == SVR_P5021) && (SVR_REV(svr) <= 0x21)) || > + ((SVR_SOC_VER(svr) == SVR_P5010) && (SVR_REV(svr) <= 0x20)) || > + ((SVR_SOC_VER(svr) == SVR_P3041) && (SVR_REV(svr) <= 0x20)) || > + ((SVR_SOC_VER(svr) == SVR_P2041) && (SVR_REV(svr) <= 0x20)) || > + ((SVR_SOC_VER(svr) == SVR_P2040) && (SVR_REV(svr) <= 0x20)) || > + ((SVR_SOC_VER(svr) == SVR_P1014) && (SVR_REV(svr) == 0x10)) || > + ((SVR_SOC_VER(svr) == SVR_P1010) && (SVR_REV(svr) == 0x10))) > + disable_clk_before_reset = true; > +#endif > + > + if (disable_clk_before_reset && (mask & SDHCI_RESET_ALL)) { > + clk = esdhc_readw(host, SDHCI_CLOCK_CONTROL); > + clk &= ~ESDHC_CLOCK_CRDEN; > + esdhc_writew(host, clk, SDHCI_CLOCK_CONTROL); > + sdhci_reset(host, mask); > + clk = esdhc_readw(host, SDHCI_CLOCK_CONTROL); > + clk |= ESDHC_CLOCK_CRDEN; > + esdhc_writew(host, clk, SDHCI_CLOCK_CONTROL); > + return 1; > + } > + return 0; > +} > + > static void esdhc_reset(struct sdhci_host *host, u8 mask) > { > - sdhci_reset(host, mask); > + int ret; > + > + ret = esdhc_of_reset_workaround(host, mask); > + > + if (!ret) > + sdhci_reset(host, mask); > > sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); > sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); > -- > 2.1.0.27.g96db324 > -- 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