[v4, 07/11] mmc: fsl_esdhc: support eMMC HS400 mode

Yangbo Lu
2020-09-01T08:58:05Z

The process for eMMC HS400 mode for eSDHC is,

1. Perform the Tuning Process at the HS400 target operating frequency.
Latched the clock division value.
2. if read transaction, then set the SDTIMNGCTL[FLW_CTL_BG].
3. Switch to High Speed mode and then set the card clock frequency to
a value not greater than 52Mhz
4. Clear TBCTL[TB_EN],tuning block enable bit.
5. Change to 8 bit DDR Mode
6. Switch the card to HS400 mode.
7. Set TBCTL[TB_EN], tuning block enable bit.
8. Clear SYSCTL[SDCLKEN]
9. Wait for PRSSTAT[SDSTB] to be set
10. Change the clock division to latched value.Set TBCTL[HS 400 mode]
and Set SDCLKCTL[CMD_CLK_CTRL]
11. Set SYSCTL[SDCLKEN]
12. Wait for PRSSTAT[SDSTB] to be set
13. Set DLLCFG0[DLL_ENABLE] and DLLCFG0[DLL_FREQ_SEL].
14. Wait for delay chain to lock.
15. Set TBCTL[HS400_WNDW_ADJUST]
16. Again clear SYSCTL[SDCLKEN]
17. Wait for PRSSTAT[SDSTB] to be set
18. Set ESDHCCTL[FAF]
19. Wait for ESDHCCTL[FAF] to be cleared
20. Set SYSCTL[SDCLKEN]
21. Wait for PRSSTAT[SDSTB] to be set.

Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com>
---
Changes for v2:
- None.
Changes for v3:
- Added HS400 exit code for downgrade.
Changes for v4:
- None.
---
drivers/mmc/fsl_esdhc.c | 120 ++++++++++++++++++++++++++++++++++--------------
include/fsl_esdhc.h | 12 +++++
2 files changed, 98 insertions(+), 34 deletions(-)

diff --git a/drivers/mmc/fsl_esdhc.c b/drivers/mmc/fsl_esdhc.c
index e7db55d..c53751d 100644
--- a/drivers/mmc/fsl_esdhc.c
+++ b/drivers/mmc/fsl_esdhc.c
@@ -62,7 +62,12 @@ struct fsl_esdhc {
uint hostcapblt2; /* Host controller capabilities register 2 */
char reserved6[8]; /* reserved */
uint tbctl; /* Tuning block control register */
- char reserved7[744]; /* reserved */
+ char reserved7[32]; /* reserved */
+ uint sdclkctl; /* SD clock control register */
+ uint sdtimingctl; /* SD timing control register */
+ char reserved8[20]; /* reserved */
+ uint dllcfg0; /* DLL config 0 register */
+ char reserved9[680]; /* reserved */
uint esdhcctl; /* eSDHC control register */
};

@@ -568,16 +573,80 @@ static void esdhc_clock_control(struct fsl_esdhc_priv *priv, bool enable)
}
}

+static void esdhc_flush_async_fifo(struct fsl_esdhc_priv *priv)
+{
+ struct fsl_esdhc *regs = priv->esdhc_regs;
+ u32 time_out;
+
+ esdhc_setbits32(&regs->esdhcctl, ESDHCCTL_FAF);
+
+ time_out = 20;
+ while (esdhc_read32(&regs->esdhcctl) & ESDHCCTL_FAF) {
+ if (time_out == 0) {
+ printf("fsl_esdhc: Flush asynchronous FIFO timeout.\n");
+ break;
+ }
+ time_out--;
+ mdelay(1);
+ }
+}
+
+static void esdhc_tuning_block_enable(struct fsl_esdhc_priv *priv,
+ bool en)
+{
+ struct fsl_esdhc *regs = priv->esdhc_regs;
+
+ esdhc_clock_control(priv, false);
+ esdhc_flush_async_fifo(priv);
+ if (en)
+ esdhc_setbits32(&regs->tbctl, TBCTL_TB_EN);
+ else
+ esdhc_clrbits32(&regs->tbctl, TBCTL_TB_EN);
+ esdhc_clock_control(priv, true);
+}
+
+static void esdhc_exit_hs400(struct fsl_esdhc_priv *priv)
+{
+ struct fsl_esdhc *regs = priv->esdhc_regs;
+
+ esdhc_clrbits32(&regs->sdtimingctl, FLW_CTL_BG);
+ esdhc_clrbits32(&regs->sdclkctl, CMD_CLK_CTL);
+
+ esdhc_clock_control(priv, false);
+ esdhc_clrbits32(&regs->tbctl, HS400_MODE);
+ esdhc_clock_control(priv, true);
+
+ esdhc_clrbits32(&regs->dllcfg0, DLL_FREQ_SEL | DLL_ENABLE);
+ esdhc_clrbits32(&regs->tbctl, HS400_WNDW_ADJUST);
+
+ esdhc_tuning_block_enable(priv, false);
+}
+
static void esdhc_set_timing(struct fsl_esdhc_priv *priv, enum bus_mode mode)
{
struct fsl_esdhc *regs = priv->esdhc_regs;

+ /* Exit HS400 mode before setting any other mode */
+ if (esdhc_read32(&regs->tbctl) & HS400_MODE &&
+ mode != MMC_HS_400)
+ esdhc_exit_hs400(priv);
+
esdhc_clock_control(priv, false);

if (mode == MMC_HS_200)
esdhc_clrsetbits32(&regs->autoc12err, UHSM_MASK,
UHSM_SDR104_HS200);
+ if (mode == MMC_HS_400) {
+ esdhc_setbits32(&regs->tbctl, HS400_MODE);
+ esdhc_setbits32(&regs->sdclkctl, CMD_CLK_CTL);
+ esdhc_clock_control(priv, true);

+ esdhc_setbits32(&regs->dllcfg0, DLL_ENABLE | DLL_FREQ_SEL);
+ esdhc_setbits32(&regs->tbctl, HS400_WNDW_ADJUST);
+
+ esdhc_clock_control(priv, false);
+ esdhc_flush_async_fifo(priv);
+ }
esdhc_clock_control(priv, true);
}

@@ -592,6 +661,9 @@ static int esdhc_set_ios_common(struct fsl_esdhc_priv *priv, struct mmc *mmc)
esdhc_clock_control(priv, true);
}

+ if (mmc->selected_mode == MMC_HS_400)
+ esdhc_tuning_block_enable(priv, true);
+
/* Set the clock speed */
if (priv->clock != mmc->clock)
set_sysctl(priv, mmc, mmc->clock);
@@ -948,38 +1020,6 @@ static int fsl_esdhc_reinit(struct udevice *dev)
}

#ifdef MMC_SUPPORTS_TUNING
-static void esdhc_flush_async_fifo(struct fsl_esdhc_priv *priv)
-{
- struct fsl_esdhc *regs = priv->esdhc_regs;
- u32 time_out;
-
- esdhc_setbits32(&regs->esdhcctl, ESDHCCTL_FAF);
-
- time_out = 20;
- while (esdhc_read32(&regs->esdhcctl) & ESDHCCTL_FAF) {
- if (time_out == 0) {
- printf("fsl_esdhc: Flush asynchronous FIFO timeout.\n");
- break;
- }
- time_out--;
- mdelay(1);
- }
-}
-
-static void esdhc_tuning_block_enable(struct fsl_esdhc_priv *priv,
- bool en)
-{
- struct fsl_esdhc *regs = priv->esdhc_regs;
-
- esdhc_clock_control(priv, false);
- esdhc_flush_async_fifo(priv);
- if (en)
- esdhc_setbits32(&regs->tbctl, TBCTL_TB_EN);
- else
- esdhc_clrbits32(&regs->tbctl, TBCTL_TB_EN);
- esdhc_clock_control(priv, true);
-}
-
static int fsl_esdhc_execute_tuning(struct udevice *dev, uint32_t opcode)
{
struct fsl_esdhc_plat *plat = dev_get_platdata(dev);
@@ -1007,8 +1047,11 @@ static int fsl_esdhc_execute_tuning(struct udevice *dev, uint32_t opcode)

esdhc_write32(&regs->irqstaten, irqstaten);

- if (i != MAX_TUNING_LOOP)
+ if (i != MAX_TUNING_LOOP) {
+ if (plat->mmc.hs400_tuning)
+ esdhc_setbits32(&regs->sdtimingctl, FLW_CTL_BG);
return 0;
+ }

printf("fsl_esdhc: tuning failed!\n");
esdhc_clrbits32(&regs->autoc12err, SMPCLKSEL);
@@ -1018,6 +1061,14 @@ static int fsl_esdhc_execute_tuning(struct udevice *dev, uint32_t opcode)
}
#endif

+int fsl_esdhc_hs400_prepare_ddr(struct udevice *dev)
+{
+ struct fsl_esdhc_priv *priv = dev_get_priv(dev);
+
+ esdhc_tuning_block_enable(priv, false);
+ return 0;
+}
+
static const struct dm_mmc_ops fsl_esdhc_ops = {
.get_cd = fsl_esdhc_get_cd,
.send_cmd = fsl_esdhc_send_cmd,
@@ -1026,6 +1077,7 @@ static const struct dm_mmc_ops fsl_esdhc_ops = {
.execute_tuning = fsl_esdhc_execute_tuning,
#endif
.reinit = fsl_esdhc_reinit,
+ .hs400_prepare_ddr = fsl_esdhc_hs400_prepare_ddr,
};

static const struct udevice_id fsl_esdhc_ids[] = {
diff --git a/include/fsl_esdhc.h b/include/fsl_esdhc.h
index 0a426e1..cc11966 100644
--- a/include/fsl_esdhc.h
+++ b/include/fsl_esdhc.h
@@ -172,6 +172,18 @@

/* Tuning block control register */
#define TBCTL_TB_EN 0x00000004
+#define HS400_MODE 0x00000010
+#define HS400_WNDW_ADJUST 0x00000040
+
+/* SD clock control register */
+#define CMD_CLK_CTL 0x00008000
+
+/* SD timing control register */
+#define FLW_CTL_BG 0x00008000
+
+/* DLL config 0 register */
+#define DLL_ENABLE 0x80000000
+#define DLL_FREQ_SEL 0x08000000

#define MAX_TUNING_LOOP 40

--
2.7.4