diff mbox series

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

Message ID 20190917044631.3161-4-yangbo.lu@nxp.com (mailing list archive)
State New, archived
Headers show
Series mmc: sdhci-of-esdhc: fix up erratum A-008171 workaround | expand

Commit Message

Yangbo Lu Sept. 17, 2019, 4:46 a.m. UTC
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@nxp.com>
Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com>
---
 drivers/mmc/host/sdhci-esdhc.h    |   9 ++
 drivers/mmc/host/sdhci-of-esdhc.c | 216 ++++++++++++++++++++++++++++++++------
 2 files changed, 192 insertions(+), 33 deletions(-)

Comments

Adrian Hunter Sept. 23, 2019, 12:56 p.m. UTC | #1
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@nxp.com>
> Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com>
> ---
>  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;
>
Yangbo Lu Sept. 24, 2019, 3:11 a.m. UTC | #2
Hi Adrian,

> -----Original Message-----
> From: linux-mmc-owner@vger.kernel.org <linux-mmc-owner@vger.kernel.org>
> On Behalf Of Adrian Hunter
> Sent: Monday, September 23, 2019 8:56 PM
> To: Y.b. Lu <yangbo.lu@nxp.com>; linux-mmc@vger.kernel.org; Ulf Hansson
> <ulf.hansson@linaro.org>
> Cc: Yinbo Zhu <yinbo.zhu@nxp.com>
> Subject: Re: [PATCH 3/3] mmc: sdhci-of-esdhc: fix up erratum A-008171
> workaround
> 
> 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@nxp.com>
> > Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com>
> > ---
> >  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()?

[Y.b. Lu] The purpose is to set SDHCI_CTRL_TUNED_CLK bit just for starting tuning.
I don’t think ->write_w() could check the condition of starting tuning, with esdhc->in_sw_tuning flag and SDHCI_CTRL_EXEC_TUNING bit set.
There is possibility SDHCI_HOST_CONTROL2 register will be written for several times in tuning in the future.

Thanks.
> 
> > +	}
> > +
> > +	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;
> >
Adrian Hunter Sept. 24, 2019, 8:11 a.m. UTC | #3
On 24/09/19 6:11 AM, Y.b. Lu wrote:
> Hi Adrian,
> 
>> -----Original Message-----
>> From: linux-mmc-owner@vger.kernel.org <linux-mmc-owner@vger.kernel.org>
>> On Behalf Of Adrian Hunter
>> Sent: Monday, September 23, 2019 8:56 PM
>> To: Y.b. Lu <yangbo.lu@nxp.com>; linux-mmc@vger.kernel.org; Ulf Hansson
>> <ulf.hansson@linaro.org>
>> Cc: Yinbo Zhu <yinbo.zhu@nxp.com>
>> Subject: Re: [PATCH 3/3] mmc: sdhci-of-esdhc: fix up erratum A-008171
>> workaround
>>
>> 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@nxp.com>
>>> Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com>
>>> ---
>>>  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()?
> 
> [Y.b. Lu] The purpose is to set SDHCI_CTRL_TUNED_CLK bit just for starting tuning.
> I don’t think ->write_w() could check the condition of starting tuning, with esdhc->in_sw_tuning flag and SDHCI_CTRL_EXEC_TUNING bit set.

It seems to me that 0->1 transition of SDHCI_CTRL_EXEC_TUNING starts tuning
so it is equivalent to a  ->start_tuning() callback.

> There is possibility SDHCI_HOST_CONTROL2 register will be written for several times in tuning in the future.

Can you expand on what you mean?

> 
> Thanks.
>>
>>> +	}
>>> +
>>> +	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;
>>>
>
Yangbo Lu Sept. 24, 2019, 8:19 a.m. UTC | #4
Hi Adrian,

> -----Original Message-----
> From: Adrian Hunter <adrian.hunter@intel.com>
> Sent: Tuesday, September 24, 2019 4:11 PM
> To: Y.b. Lu <yangbo.lu@nxp.com>; linux-mmc@vger.kernel.org; Ulf Hansson
> <ulf.hansson@linaro.org>
> Cc: Yinbo Zhu <yinbo.zhu@nxp.com>
> Subject: Re: [PATCH 3/3] mmc: sdhci-of-esdhc: fix up erratum A-008171
> workaround
> 
> On 24/09/19 6:11 AM, Y.b. Lu wrote:
> > Hi Adrian,
> >
> >> -----Original Message-----
> >> From: linux-mmc-owner@vger.kernel.org
> >> <linux-mmc-owner@vger.kernel.org> On Behalf Of Adrian Hunter
> >> Sent: Monday, September 23, 2019 8:56 PM
> >> To: Y.b. Lu <yangbo.lu@nxp.com>; linux-mmc@vger.kernel.org; Ulf
> >> Hansson <ulf.hansson@linaro.org>
> >> Cc: Yinbo Zhu <yinbo.zhu@nxp.com>
> >> Subject: Re: [PATCH 3/3] mmc: sdhci-of-esdhc: fix up erratum A-008171
> >> workaround
> >>
> >> 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@nxp.com>
> >>> Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com>
> >>> ---
> >>>  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()?
> >
> > [Y.b. Lu] The purpose is to set SDHCI_CTRL_TUNED_CLK bit just for starting
> tuning.
> > I don’t think ->write_w() could check the condition of starting tuning, with
> esdhc->in_sw_tuning flag and SDHCI_CTRL_EXEC_TUNING bit set.
> 
> It seems to me that 0->1 transition of SDHCI_CTRL_EXEC_TUNING starts tuning
> so it is equivalent to a  ->start_tuning() callback.

[Y.b. Lu] I understand your point now :)
Let me send a new version. Thanks.

> 
> > There is possibility SDHCI_HOST_CONTROL2 register will be written for
> several times in tuning in the future.
> 
> Can you expand on what you mean?
> 
> >
> > Thanks.
> >>
> >>> +	}
> >>> +
> >>> +	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;
> >>>
> >
diff mbox series

Patch

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);
+	}
+
+	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;