Re: [PATCH 3/3] mmc: sdhci-of-esdhc: fix up erratum A-008171 workaround

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

 



On 17/09/19 7:46 AM, Yangbo Lu wrote:
> A previous patch implemented an incomplete workaround of erratum
> A-008171. The complete workaround is as below. This patch is to
> implement the complete workaround which uses SW tuning if HW tuning
> fails, and retries both HW/SW tuning once with reduced clock if
> workaround fails. This is suggested by hardware team, and the patch
> had been verified on LS1046A eSDHC + Phison 32G eMMC which could
> trigger the erratum.
> 
> Workaround:
> /* For T1040, T2080, LS1021A, T1023 Rev 1: */
> 1. Program TBPTR[TB_WNDW_END_PTR] = 3*DIV_RATIO.
> 2. Program TBPTR[TB_WNDW_START_PTR] = 5*DIV_RATIO.
> 3. Program the software tuning mode by setting TBCTL[TB_MODE] = 2'h3.
> 4. Set SYSCTL2[EXTN] and SYSCTL2[SAMPCLKSEL].
> 5. Issue SEND_TUNING_BLK Command (CMD19 for SD, CMD21 for MMC).
> 6. Wait for IRQSTAT[BRR], buffer read ready, to be set.
> 7. Clear IRQSTAT[BRR].
> 8. Check SYSCTL2[EXTN] to be cleared.
> 9. Check SYSCTL2[SAMPCLKSEL], Sampling Clock Select. It's set value
>    indicate tuning procedure success, and clear indicate failure.
>    In case of tuning failure, fixed sampling scheme could be used by
>    clearing TBCTL[TB_EN].
> /* For LS1080A Rev 1, LS2088A Rev 1.0, LA1575A Rev 1.0: */
> 1. Read the TBCTL[31:0] register. Write TBCTL[11:8]=4'h8 and wait for
>    1ms.
> 2. Read the TBCTL[31:0] register and rewrite again. Wait for 1ms second.
> 3. Read the TBSTAT[31:0] register twice.
> 3.1 Reset data lines by setting ESDHCCTL[RSTD] bit.
> 3.2 Check ESDHCCTL[RSTD] bit.
> 3.3 If ESDHCCTL[RSTD] is 0, go to step 3.4 else go to step 3.2.
> 3.4 Write 32'hFFFF_FFFF to IRQSTAT register.
> 4. if TBSTAT[15:8]-TBSTAT[7:0] > 4*DIV_RATIO or TBSTAT[7:0]-TBSTAT[15:8]
>    > 4*DIV_RATIO , then program TBPTR[TB_WNDW_END_PTR] = 4*DIV_RATIO and
>    program TBPTR[TB_WNDW_START_PTR] = 8*DIV_RATIO.
> /* For LS1012A Rev1, LS1043A Rev 1.x, LS1046A 1.0: */
> 1. Read the TBCTL[0:31] register. Write TBCTL[20:23]=4'h8 and wait for
>    1ms.
> 2. Read the TBCTL[0:31] register and rewrite again. Wait for 1ms second.
> 3. Read the TBSTAT[0:31] register twice.
> 3.1 Reset data lines by setting ESDHCCTL[RSTD] bit.
> 3.2 Check ESDHCCTL[RSTD] bit.
> 3.3 If ESDHCCTL[RSTD] is 0, go to step 3.4 else go to step 3.2.
> 3.4 Write 32'hFFFF_FFFF to IRQSTAT register.
> 4. if TBSTAT[16:23]-TBSTAT[24:31] > 4*DIV_RATIO or TBSTAT[24:31]-
>    TBSTAT[16:23] > 4* DIV_RATIO , then program TBPTR[TB_WNDW_END_PTR] =
>    4*DIV_RATIO and program TBPTR[TB_WNDW_START_PTR] = 8*DIV_RATIO.
> /* For LS1080A Rev 1, LS2088A Rev 1.0, LA1575A Rev 1.0 LS1012A Rev1,
>  * LS1043A Rev 1.x, LS1046A 1.0:
>  */
> 5. else program TBPTR[TB_WNDW_END_PTR] = 3*DIV_RATIO and program
>    TBPTR[TB_WNDW_START_PTR] = 5*DIV_RATIO.
> 6. Program the software tuning mode by setting TBCTL[TB_MODE] = 2'h3.
> 7. Set SYSCTL2[EXTN], wait 1us and SYSCTL2[SAMPCLKSEL].
> 8. Issue SEND_TUNING_BLK Command (CMD19 for SD, CMD21 for MMC).
> 9. Wait for IRQSTAT[BRR], buffer read ready, to be set.
> 10. Clear IRQSTAT[BRR].
> 11. Check SYSCTL2[EXTN] to be cleared.
> 12. Check SYSCTL2[SAMPCLKSEL], Sampling Clock Select. It's set value
>     indicate tuning procedure success, and clear indicate failure.
>     In case of tuning failure, fixed sampling scheme could be used by
>     clearing TBCTL[TB_EN].
> 
> Fixes: b1f378ab5334 ("mmc: sdhci-of-esdhc: add erratum A008171 support")
> Signed-off-by: Yinbo Zhu <yinbo.zhu@xxxxxxx>
> Signed-off-by: Yangbo Lu <yangbo.lu@xxxxxxx>
> ---
>  drivers/mmc/host/sdhci-esdhc.h    |   9 ++
>  drivers/mmc/host/sdhci-of-esdhc.c | 216 ++++++++++++++++++++++++++++++++------
>  2 files changed, 192 insertions(+), 33 deletions(-)
> 
> diff --git a/drivers/mmc/host/sdhci-esdhc.h b/drivers/mmc/host/sdhci-esdhc.h
> index 57b582b..e88dee5 100644
> --- a/drivers/mmc/host/sdhci-esdhc.h
> +++ b/drivers/mmc/host/sdhci-esdhc.h
> @@ -59,7 +59,16 @@
>  #define ESDHC_HS400_WNDW_ADJUST		0x00000040
>  #define ESDHC_HS400_MODE		0x00000010
>  #define ESDHC_TB_EN			0x00000004
> +#define ESDHC_TB_MODE_MASK		0x00000003
> +#define ESDHC_TB_MODE_SW		0x00000003
> +#define ESDHC_TB_MODE_3			0x00000002
> +
> +#define ESDHC_TBSTAT			0x124
> +
>  #define ESDHC_TBPTR			0x128
> +#define ESDHC_WNDW_STRT_PTR_SHIFT	8
> +#define ESDHC_WNDW_STRT_PTR_MASK	(0x7f << 8)
> +#define ESDHC_WNDW_END_PTR_MASK		0x7f
>  
>  /* SD Clock Control Register */
>  #define ESDHC_SDCLKCTL			0x144
> diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c
> index a01d3a5..ea8d35f 100644
> --- a/drivers/mmc/host/sdhci-of-esdhc.c
> +++ b/drivers/mmc/host/sdhci-of-esdhc.c
> @@ -77,8 +77,10 @@ struct sdhci_esdhc {
>  	bool quirk_incorrect_hostver;
>  	bool quirk_limited_clk_division;
>  	bool quirk_unreliable_pulse_detection;
> -	bool quirk_fixup_tuning;
> +	bool quirk_tuning_erratum_type1;
> +	bool quirk_tuning_erratum_type2;
>  	bool quirk_ignore_data_inhibit;
> +	bool in_sw_tuning;
>  	unsigned int peripheral_clock;
>  	const struct esdhc_clk_fixup *clk_fixup;
>  	u32 div_ratio;
> @@ -806,16 +808,21 @@ static int esdhc_signal_voltage_switch(struct mmc_host *mmc,
>  	}
>  }
>  
> -static struct soc_device_attribute soc_fixup_tuning[] = {
> +static struct soc_device_attribute soc_tuning_erratum_type1[] = {
> +	{ .family = "QorIQ T1023", .revision = "1.0", },
>  	{ .family = "QorIQ T1040", .revision = "1.0", },
>  	{ .family = "QorIQ T2080", .revision = "1.0", },
> -	{ .family = "QorIQ T1023", .revision = "1.0", },
>  	{ .family = "QorIQ LS1021A", .revision = "1.0", },
> -	{ .family = "QorIQ LS1080A", .revision = "1.0", },
> -	{ .family = "QorIQ LS2080A", .revision = "1.0", },
> +	{ },
> +};
> +
> +static struct soc_device_attribute soc_tuning_erratum_type2[] = {
>  	{ .family = "QorIQ LS1012A", .revision = "1.0", },
>  	{ .family = "QorIQ LS1043A", .revision = "1.*", },
>  	{ .family = "QorIQ LS1046A", .revision = "1.0", },
> +	{ .family = "QorIQ LS1080A", .revision = "1.0", },
> +	{ .family = "QorIQ LS2080A", .revision = "1.0", },
> +	{ .family = "QorIQ LA1575A", .revision = "1.0", },
>  	{ },
>  };
>  
> @@ -856,15 +863,97 @@ static void esdhc_tuning_block_enable(struct sdhci_host *host, bool enable)
>  	esdhc_clock_enable(host, true);
>  }
>  
> +static void esdhc_prepare_sw_tuning(struct sdhci_host *host, u8 *window_start,
> +				    u8 *window_end)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
> +	u8 tbstat_15_8, tbstat_7_0;
> +	u32 val;
> +
> +	if (esdhc->quirk_tuning_erratum_type1) {
> +		*window_start = 5 * esdhc->div_ratio;
> +		*window_end = 3 * esdhc->div_ratio;
> +		return;
> +	}
> +
> +	/* Write TBCTL[11:8]=4'h8 */
> +	val = sdhci_readl(host, ESDHC_TBCTL);
> +	val &= ~(0xf << 8);
> +	val |= 8 << 8;
> +	sdhci_writel(host, val, ESDHC_TBCTL);
> +
> +	mdelay(1);
> +
> +	/* Read TBCTL[31:0] register and rewrite again */
> +	val = sdhci_readl(host, ESDHC_TBCTL);
> +	sdhci_writel(host, val, ESDHC_TBCTL);
> +
> +	mdelay(1);
> +
> +	/* Read the TBSTAT[31:0] register twice */
> +	val = sdhci_readl(host, ESDHC_TBSTAT);
> +	val = sdhci_readl(host, ESDHC_TBSTAT);
> +
> +	/* Reset data lines by setting ESDHCCTL[RSTD] */
> +	sdhci_reset(host, SDHCI_RESET_DATA);
> +	/* Write 32'hFFFF_FFFF to IRQSTAT register */
> +	sdhci_writel(host, 0xFFFFFFFF, SDHCI_INT_STATUS);
> +
> +	/* If TBSTAT[15:8]-TBSTAT[7:0] > 4 * div_ratio
> +	 * or TBSTAT[7:0]-TBSTAT[15:8] > 4 * div_ratio,
> +	 * then program TBPTR[TB_WNDW_END_PTR] = 4 * div_ratio
> +	 * and program TBPTR[TB_WNDW_START_PTR] = 8 * div_ratio.
> +	 */
> +	tbstat_7_0 = val & 0xff;
> +	tbstat_15_8 = (val >> 8) & 0xff;
> +
> +	if (abs(tbstat_15_8 - tbstat_7_0) > (4 * esdhc->div_ratio)) {
> +		*window_start = 8 * esdhc->div_ratio;
> +		*window_end = 4 * esdhc->div_ratio;
> +	} else {
> +		*window_start = 5 * esdhc->div_ratio;
> +		*window_end = 3 * esdhc->div_ratio;
> +	}
> +}
> +
> +static int esdhc_execute_sw_tuning(struct mmc_host *mmc, u32 opcode,
> +				   u8 window_start, u8 window_end)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc);
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
> +	u32 val;
> +	int ret;
> +
> +	/* Program TBPTR[TB_WNDW_END_PTR] and TBPTR[TB_WNDW_START_PTR] */
> +	val = ((u32)window_start << ESDHC_WNDW_STRT_PTR_SHIFT) &
> +	      ESDHC_WNDW_STRT_PTR_MASK;
> +	val |= window_end & ESDHC_WNDW_END_PTR_MASK;
> +	sdhci_writel(host, val, ESDHC_TBPTR);
> +
> +	/* Program the software tuning mode by setting TBCTL[TB_MODE]=2'h3 */
> +	val = sdhci_readl(host, ESDHC_TBCTL);
> +	val &= ~ESDHC_TB_MODE_MASK;
> +	val |= ESDHC_TB_MODE_SW;
> +	sdhci_writel(host, val, ESDHC_TBCTL);
> +
> +	esdhc->in_sw_tuning = true;
> +	ret = sdhci_execute_tuning(mmc, opcode);
> +	esdhc->in_sw_tuning = false;
> +	return ret;
> +}
> +
>  static int esdhc_execute_tuning(struct mmc_host *mmc, u32 opcode)
>  {
>  	struct sdhci_host *host = mmc_priv(mmc);
>  	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>  	struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
> +	u8 window_start, window_end;
> +	int ret, retries = 1;
>  	bool hs400_tuning;
>  	unsigned int clk;
>  	u32 val;
> -	int ret;
>  
>  	/* For tuning mode, the sd clock divisor value
>  	 * must be larger than 3 according to reference manual.
> @@ -873,39 +962,73 @@ static int esdhc_execute_tuning(struct mmc_host *mmc, u32 opcode)
>  	if (host->clock > clk)
>  		esdhc_of_set_clock(host, clk);
>  
> -	if (esdhc->quirk_limited_clk_division &&
> -	    host->flags & SDHCI_HS400_TUNING)
> -		esdhc_of_set_clock(host, host->clock);
> -
>  	esdhc_tuning_block_enable(host, true);
>  
>  	hs400_tuning = host->flags & SDHCI_HS400_TUNING;
> -	ret = sdhci_execute_tuning(mmc, opcode);
>  
> -	if (hs400_tuning) {
> -		val = sdhci_readl(host, ESDHC_SDTIMNGCTL);
> -		val |= ESDHC_FLW_CTL_BG;
> -		sdhci_writel(host, val, ESDHC_SDTIMNGCTL);
> -	}
> +	do {
> +		if (esdhc->quirk_limited_clk_division &&
> +		    hs400_tuning)
> +			esdhc_of_set_clock(host, host->clock);
>  
> -	if (host->tuning_err == -EAGAIN && esdhc->quirk_fixup_tuning) {
> +		/* Do HW tuning */
> +		val = sdhci_readl(host, ESDHC_TBCTL);
> +		val &= ~ESDHC_TB_MODE_MASK;
> +		val |= ESDHC_TB_MODE_3;
> +		sdhci_writel(host, val, ESDHC_TBCTL);
>  
> -		/* program TBPTR[TB_WNDW_END_PTR] = 3*DIV_RATIO and
> -		 * program TBPTR[TB_WNDW_START_PTR] = 5*DIV_RATIO
> -		 */
> -		val = sdhci_readl(host, ESDHC_TBPTR);
> -		val = (val & ~((0x7f << 8) | 0x7f)) |
> -		(3 * esdhc->div_ratio) | ((5 * esdhc->div_ratio) << 8);
> -		sdhci_writel(host, val, ESDHC_TBPTR);
> +		ret = sdhci_execute_tuning(mmc, opcode);
> +		if (ret)
> +			break;
>  
> -		/* program the software tuning mode by setting
> -		 * TBCTL[TB_MODE]=2'h3
> +		/* If HW tuning fails and triggers erratum,
> +		 * try workaround.
>  		 */
> -		val = sdhci_readl(host, ESDHC_TBCTL);
> -		val |= 0x3;
> -		sdhci_writel(host, val, ESDHC_TBCTL);
> -		sdhci_execute_tuning(mmc, opcode);
> +		ret = host->tuning_err;
> +		if (ret == -EAGAIN &&
> +		    (esdhc->quirk_tuning_erratum_type1 ||
> +		     esdhc->quirk_tuning_erratum_type2)) {
> +			/* Recover HS400 tuning flag */
> +			if (hs400_tuning)
> +				host->flags |= SDHCI_HS400_TUNING;
> +			pr_info("%s: Hold on to use fixed sampling clock. Try SW tuning!\n",
> +				mmc_hostname(mmc));
> +			/* Do SW tuning */
> +			esdhc_prepare_sw_tuning(host, &window_start,
> +						&window_end);
> +			ret = esdhc_execute_sw_tuning(mmc, opcode,
> +						      window_start,
> +						      window_end);
> +			if (ret)
> +				break;
> +
> +			/* Retry both HW/SW tuning with reduced clock. */
> +			ret = host->tuning_err;
> +			if (ret == -EAGAIN && retries) {
> +				/* Recover HS400 tuning flag */
> +				if (hs400_tuning)
> +					host->flags |= SDHCI_HS400_TUNING;
> +
> +				clk = host->max_clk / (esdhc->div_ratio + 1);
> +				esdhc_of_set_clock(host, clk);
> +				pr_info("%s: Hold on to use fixed sampling clock. Try tuning with reduced clock!\n",
> +					mmc_hostname(mmc));
> +			} else {
> +				break;
> +			}
> +		} else {
> +			break;
> +		}
> +	} while (retries--);
> +
> +	if (ret) {
> +		esdhc_tuning_block_enable(host, false);
> +	} else if (hs400_tuning) {
> +		val = sdhci_readl(host, ESDHC_SDTIMNGCTL);
> +		val |= ESDHC_FLW_CTL_BG;
> +		sdhci_writel(host, val, ESDHC_SDTIMNGCTL);
>  	}
> +
>  	return ret;
>  }
>  
> @@ -937,6 +1060,26 @@ static u32 esdhc_irq(struct sdhci_host *host, u32 intmask)
>  	return intmask;
>  }
>  
> +void esdhc_start_tuning(struct sdhci_host *host)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
> +	u16 ctrl;
> +
> +	ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> +	ctrl |= SDHCI_CTRL_EXEC_TUNING;
> +	sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
> +
> +	if (esdhc->in_sw_tuning) {
> +		udelay(1);
> +		ctrl |= SDHCI_CTRL_TUNED_CLK;
> +		sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);

Why not do that in the ->write_w() callback for SDHCI_HOST_CONTROL2
instead of adding ->start_tuning()?

> +	}
> +
> +	sdhci_writel(host, SDHCI_INT_DATA_AVAIL, SDHCI_INT_ENABLE);
> +	sdhci_writel(host, SDHCI_INT_DATA_AVAIL, SDHCI_SIGNAL_ENABLE);
> +}
> +
>  #ifdef CONFIG_PM_SLEEP
>  static u32 esdhc_proctl;
>  static int esdhc_of_suspend(struct device *dev)
> @@ -985,6 +1128,7 @@ static const struct sdhci_ops sdhci_esdhc_be_ops = {
>  	.reset = esdhc_reset,
>  	.set_uhs_signaling = esdhc_set_uhs_signaling,
>  	.irq = esdhc_irq,
> +	.start_tuning = esdhc_start_tuning,
>  };
>  
>  static const struct sdhci_ops sdhci_esdhc_le_ops = {
> @@ -1003,6 +1147,7 @@ static const struct sdhci_ops sdhci_esdhc_le_ops = {
>  	.reset = esdhc_reset,
>  	.set_uhs_signaling = esdhc_set_uhs_signaling,
>  	.irq = esdhc_irq,
> +	.start_tuning = esdhc_start_tuning,
>  };
>  
>  static const struct sdhci_pltfm_data sdhci_esdhc_be_pdata = {
> @@ -1140,10 +1285,15 @@ static int sdhci_esdhc_probe(struct platform_device *pdev)
>  
>  	pltfm_host = sdhci_priv(host);
>  	esdhc = sdhci_pltfm_priv(pltfm_host);
> -	if (soc_device_match(soc_fixup_tuning))
> -		esdhc->quirk_fixup_tuning = true;
> +	if (soc_device_match(soc_tuning_erratum_type1))
> +		esdhc->quirk_tuning_erratum_type1 = true;
> +	else
> +		esdhc->quirk_tuning_erratum_type1 = false;
> +
> +	if (soc_device_match(soc_tuning_erratum_type2))
> +		esdhc->quirk_tuning_erratum_type2 = true;
>  	else
> -		esdhc->quirk_fixup_tuning = false;
> +		esdhc->quirk_tuning_erratum_type2 = false;
>  
>  	if (esdhc->vendor_ver == VENDOR_V_22)
>  		host->quirks2 |= SDHCI_QUIRK2_HOST_NO_CMD23;
> 




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

  Powered by Linux