On 24/09/19 6:11 AM, Y.b. Lu wrote: > Hi Adrian, > >> -----Original Message----- >> From: linux-mmc-owner@xxxxxxxxxxxxxxx <linux-mmc-owner@xxxxxxxxxxxxxxx> >> On Behalf Of Adrian Hunter >> Sent: Monday, September 23, 2019 8:56 PM >> To: Y.b. Lu <yangbo.lu@xxxxxxx>; linux-mmc@xxxxxxxxxxxxxxx; Ulf Hansson >> <ulf.hansson@xxxxxxxxxx> >> Cc: Yinbo Zhu <yinbo.zhu@xxxxxxx> >> 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@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()? > > [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; >>> >