Re: [PATCH] mmc: sdhci: add qurik SDHCI_QUIRK2_DISABLE_CLOCK_BEFORE_RESET

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

 



Hi,

CC'd maintainer.

On 05/15/2015 06:19 PM, Yangbo Lu wrote:
> 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.

Then is this workaround patch? Is it not controller problem?

> 
> 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(-)

Separate esdhc and sdhci core parts.

> 
> 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>

I think this is not good. it has dependent with architecture.

> +
>  #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;

Why don't use the dt-file?

>  }
>  
>  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,

Well, i don't know whether this ops is really needs.

Best Regards,
Jaehoon Chung

>  	.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
> 

--
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