Commit 22dc132d authored by Yangbo Lu's avatar Yangbo Lu Committed by Ulf Hansson

mmc: sdhci-of-esdhc: fix up erratum A-008171 workaround

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: b1f378ab ("mmc: sdhci-of-esdhc: add erratum A008171 support")
Signed-off-by: default avatarYinbo Zhu <yinbo.zhu@nxp.com>
Signed-off-by: default avatarYangbo Lu <yangbo.lu@nxp.com>
Acked-by: default avatarAdrian Hunter <adrian.hunter@intel.com>
Signed-off-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
parent 6e32f65c
...@@ -51,6 +51,11 @@ ...@@ -51,6 +51,11 @@
#define ESDHC_CLOCK_HCKEN 0x00000002 #define ESDHC_CLOCK_HCKEN 0x00000002
#define ESDHC_CLOCK_IPGEN 0x00000001 #define ESDHC_CLOCK_IPGEN 0x00000001
/* System Control 2 Register */
#define ESDHC_SYSTEM_CONTROL_2 0x3c
#define ESDHC_SMPCLKSEL 0x00800000
#define ESDHC_EXTN 0x00400000
/* Host Controller Capabilities Register 2 */ /* Host Controller Capabilities Register 2 */
#define ESDHC_CAPABILITIES_1 0x114 #define ESDHC_CAPABILITIES_1 0x114
...@@ -59,7 +64,16 @@ ...@@ -59,7 +64,16 @@
#define ESDHC_HS400_WNDW_ADJUST 0x00000040 #define ESDHC_HS400_WNDW_ADJUST 0x00000040
#define ESDHC_HS400_MODE 0x00000010 #define ESDHC_HS400_MODE 0x00000010
#define ESDHC_TB_EN 0x00000004 #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_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 */ /* SD Clock Control Register */
#define ESDHC_SDCLKCTL 0x144 #define ESDHC_SDCLKCTL 0x144
......
...@@ -77,8 +77,10 @@ struct sdhci_esdhc { ...@@ -77,8 +77,10 @@ struct sdhci_esdhc {
bool quirk_incorrect_hostver; bool quirk_incorrect_hostver;
bool quirk_limited_clk_division; bool quirk_limited_clk_division;
bool quirk_unreliable_pulse_detection; 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 quirk_ignore_data_inhibit;
bool in_sw_tuning;
unsigned int peripheral_clock; unsigned int peripheral_clock;
const struct esdhc_clk_fixup *clk_fixup; const struct esdhc_clk_fixup *clk_fixup;
u32 div_ratio; u32 div_ratio;
...@@ -408,6 +410,8 @@ static void esdhc_le_writel(struct sdhci_host *host, u32 val, int reg) ...@@ -408,6 +410,8 @@ static void esdhc_le_writel(struct sdhci_host *host, u32 val, int reg)
static void esdhc_be_writew(struct sdhci_host *host, u16 val, int reg) static void esdhc_be_writew(struct sdhci_host *host, u16 val, int reg)
{ {
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
int base = reg & ~0x3; int base = reg & ~0x3;
u32 value; u32 value;
u32 ret; u32 ret;
...@@ -416,10 +420,24 @@ static void esdhc_be_writew(struct sdhci_host *host, u16 val, int reg) ...@@ -416,10 +420,24 @@ static void esdhc_be_writew(struct sdhci_host *host, u16 val, int reg)
ret = esdhc_writew_fixup(host, reg, val, value); ret = esdhc_writew_fixup(host, reg, val, value);
if (reg != SDHCI_TRANSFER_MODE) if (reg != SDHCI_TRANSFER_MODE)
iowrite32be(ret, host->ioaddr + base); iowrite32be(ret, host->ioaddr + base);
/* Starting SW tuning requires ESDHC_SMPCLKSEL to be set
* 1us later after ESDHC_EXTN is set.
*/
if (base == ESDHC_SYSTEM_CONTROL_2) {
if (!(value & ESDHC_EXTN) && (ret & ESDHC_EXTN) &&
esdhc->in_sw_tuning) {
udelay(1);
ret |= ESDHC_SMPCLKSEL;
iowrite32be(ret, host->ioaddr + base);
}
}
} }
static void esdhc_le_writew(struct sdhci_host *host, u16 val, int reg) static void esdhc_le_writew(struct sdhci_host *host, u16 val, int reg)
{ {
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
int base = reg & ~0x3; int base = reg & ~0x3;
u32 value; u32 value;
u32 ret; u32 ret;
...@@ -428,6 +446,18 @@ static void esdhc_le_writew(struct sdhci_host *host, u16 val, int reg) ...@@ -428,6 +446,18 @@ static void esdhc_le_writew(struct sdhci_host *host, u16 val, int reg)
ret = esdhc_writew_fixup(host, reg, val, value); ret = esdhc_writew_fixup(host, reg, val, value);
if (reg != SDHCI_TRANSFER_MODE) if (reg != SDHCI_TRANSFER_MODE)
iowrite32(ret, host->ioaddr + base); iowrite32(ret, host->ioaddr + base);
/* Starting SW tuning requires ESDHC_SMPCLKSEL to be set
* 1us later after ESDHC_EXTN is set.
*/
if (base == ESDHC_SYSTEM_CONTROL_2) {
if (!(value & ESDHC_EXTN) && (ret & ESDHC_EXTN) &&
esdhc->in_sw_tuning) {
udelay(1);
ret |= ESDHC_SMPCLKSEL;
iowrite32(ret, host->ioaddr + base);
}
}
} }
static void esdhc_be_writeb(struct sdhci_host *host, u8 val, int reg) static void esdhc_be_writeb(struct sdhci_host *host, u8 val, int reg)
...@@ -820,16 +850,21 @@ static int esdhc_signal_voltage_switch(struct mmc_host *mmc, ...@@ -820,16 +850,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 T1040", .revision = "1.0", },
{ .family = "QorIQ T2080", .revision = "1.0", }, { .family = "QorIQ T2080", .revision = "1.0", },
{ .family = "QorIQ T1023", .revision = "1.0", },
{ .family = "QorIQ LS1021A", .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 LS1012A", .revision = "1.0", },
{ .family = "QorIQ LS1043A", .revision = "1.*", }, { .family = "QorIQ LS1043A", .revision = "1.*", },
{ .family = "QorIQ LS1046A", .revision = "1.0", }, { .family = "QorIQ LS1046A", .revision = "1.0", },
{ .family = "QorIQ LS1080A", .revision = "1.0", },
{ .family = "QorIQ LS2080A", .revision = "1.0", },
{ .family = "QorIQ LA1575A", .revision = "1.0", },
{ }, { },
}; };
...@@ -850,15 +885,97 @@ static void esdhc_tuning_block_enable(struct sdhci_host *host, bool enable) ...@@ -850,15 +885,97 @@ static void esdhc_tuning_block_enable(struct sdhci_host *host, bool enable)
esdhc_clock_enable(host, true); 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) static int esdhc_execute_tuning(struct mmc_host *mmc, u32 opcode)
{ {
struct sdhci_host *host = mmc_priv(mmc); struct sdhci_host *host = mmc_priv(mmc);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host); struct sdhci_esdhc *esdhc = sdhci_pltfm_priv(pltfm_host);
u8 window_start, window_end;
int ret, retries = 1;
bool hs400_tuning; bool hs400_tuning;
unsigned int clk; unsigned int clk;
u32 val; u32 val;
int ret;
/* For tuning mode, the sd clock divisor value /* For tuning mode, the sd clock divisor value
* must be larger than 3 according to reference manual. * must be larger than 3 according to reference manual.
...@@ -867,39 +984,73 @@ static int esdhc_execute_tuning(struct mmc_host *mmc, u32 opcode) ...@@ -867,39 +984,73 @@ static int esdhc_execute_tuning(struct mmc_host *mmc, u32 opcode)
if (host->clock > clk) if (host->clock > clk)
esdhc_of_set_clock(host, 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); esdhc_tuning_block_enable(host, true);
hs400_tuning = host->flags & SDHCI_HS400_TUNING; hs400_tuning = host->flags & SDHCI_HS400_TUNING;
ret = sdhci_execute_tuning(mmc, opcode);
if (hs400_tuning) { do {
val = sdhci_readl(host, ESDHC_SDTIMNGCTL); if (esdhc->quirk_limited_clk_division &&
val |= ESDHC_FLW_CTL_BG; hs400_tuning)
sdhci_writel(host, val, ESDHC_SDTIMNGCTL); 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 ret = sdhci_execute_tuning(mmc, opcode);
* program TBPTR[TB_WNDW_START_PTR] = 5*DIV_RATIO if (ret)
*/ break;
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);
/* program the software tuning mode by setting /* If HW tuning fails and triggers erratum,
* TBCTL[TB_MODE]=2'h3 * try workaround.
*/ */
val = sdhci_readl(host, ESDHC_TBCTL); ret = host->tuning_err;
val |= 0x3; if (ret == -EAGAIN &&
sdhci_writel(host, val, ESDHC_TBCTL); (esdhc->quirk_tuning_erratum_type1 ||
sdhci_execute_tuning(mmc, opcode); 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; return ret;
} }
...@@ -1135,10 +1286,15 @@ static int sdhci_esdhc_probe(struct platform_device *pdev) ...@@ -1135,10 +1286,15 @@ static int sdhci_esdhc_probe(struct platform_device *pdev)
pltfm_host = sdhci_priv(host); pltfm_host = sdhci_priv(host);
esdhc = sdhci_pltfm_priv(pltfm_host); esdhc = sdhci_pltfm_priv(pltfm_host);
if (soc_device_match(soc_fixup_tuning)) if (soc_device_match(soc_tuning_erratum_type1))
esdhc->quirk_fixup_tuning = true; 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 else
esdhc->quirk_fixup_tuning = false; esdhc->quirk_tuning_erratum_type2 = false;
if (esdhc->vendor_ver == VENDOR_V_22) if (esdhc->vendor_ver == VENDOR_V_22)
host->quirks2 |= SDHCI_QUIRK2_HOST_NO_CMD23; host->quirks2 |= SDHCI_QUIRK2_HOST_NO_CMD23;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment