[PATCH] mmc: sdhci: add qurik SDHCI_QUIRK2_DISABLE_CLOCK_BEFORE_RESET

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This quirk is used to add workaround for silicon erratum A-003980, and
another erratum A-005055 shares the same workaround since they are the
same issue.
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>
---
 drivers/mmc/host/sdhci-esdhc.h    |  4 ++
 drivers/mmc/host/sdhci-of-esdhc.c | 78 ++++++++++++++++++++++++++++++++++++++-
 drivers/mmc/host/sdhci.c          |  6 +++
 drivers/mmc/host/sdhci.h          |  4 ++
 4 files changed, 90 insertions(+), 2 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..af8b1b1 100644
--- a/drivers/mmc/host/sdhci-of-esdhc.c
+++ b/drivers/mmc/host/sdhci-of-esdhc.c
@@ -22,8 +22,13 @@
 #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;
@@ -202,6 +207,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 +224,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,13 +244,52 @@ 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_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_of_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;
 
+	svr =  mfspr(SPRN_SVR);
 	vvn = in_be32(host->ioaddr + SDHCI_SLOT_INT_STATUS);
 	vvn = (vvn & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT;
 	if (vvn == VENDOR_V_22)
@@ -252,6 +297,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 and A-003980: 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_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)))
+		host->quirks2 |= SDHCI_QUIRK2_DISABLE_CLOCK_BEFORE_RESET;
 }
 
 static void esdhc_pltfm_set_bus_width(struct sdhci_host *host, int width)
@@ -295,6 +367,8 @@ static const struct sdhci_ops sdhci_esdhc_ops = {
 	.enable_dma = esdhc_of_enable_dma,
 	.get_max_clock = esdhc_of_get_max_clock,
 	.get_min_clock = esdhc_of_get_min_clock,
+	.platform_reset_enter = esdhc_of_platform_reset_enter,
+	.platform_reset_exit = esdhc_of_platform_reset_exit,
 	.platform_init = esdhc_of_platform_init,
 	.adma_workaround = esdhci_of_adma_workaround,
 	.set_bus_width = esdhc_pltfm_set_bus_width,
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index c80287a..89bba64 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -179,6 +179,9 @@ void sdhci_reset(struct sdhci_host *host, u8 mask)
 {
 	unsigned long timeout;
 
+	if (host->ops->platform_reset_enter)
+		host->ops->platform_reset_enter(host, mask);
+
 	sdhci_writeb(host, mask, SDHCI_SOFTWARE_RESET);
 
 	if (mask & SDHCI_RESET_ALL) {
@@ -202,6 +205,9 @@ void sdhci_reset(struct sdhci_host *host, u8 mask)
 		timeout--;
 		mdelay(1);
 	}
+
+	if (host->ops->platform_reset_exit)
+		host->ops->platform_reset_exit(host, mask);
 }
 EXPORT_SYMBOL_GPL(sdhci_reset);
 
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index e639b7f..565790a 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -409,6 +409,8 @@ struct sdhci_host {
 #define SDHCI_QUIRK2_SUPPORT_SINGLE			(1<<13)
 /* Controller broken with using ACMD23 */
 #define SDHCI_QUIRK2_ACMD23_BROKEN			(1<<14)
+/* Controller need to disable clock before reset all */
+#define SDHCI_QUIRK2_DISABLE_CLOCK_BEFORE_RESET		(1<<15)
 
 	int irq;		/* Device IRQ */
 	void __iomem *ioaddr;	/* Mapped address */
@@ -541,6 +543,8 @@ struct sdhci_ops {
 	void	(*platform_init)(struct sdhci_host *host);
 	void    (*card_event)(struct sdhci_host *host);
 	void	(*voltage_switch)(struct sdhci_host *host);
+	void	(*platform_reset_enter)(struct sdhci_host *host, u8 mask);
+	void	(*platform_reset_exit)(struct sdhci_host *host, u8 mask);
 };
 
 #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
-- 
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




[Index of Archives]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux