[PATCH v1 5/7] mci: imx-esdhc: support HS400 and HS400ES on i.MX8M
Johannes Schneider
johannes.schneider at leica-geosystems.com
Sat Jun 27 12:43:22 PDT 2026
The i.MX8M uSDHC can run HS400/HS400ES, but the barebox driver topped out at
HS200: fsl,imx8mm-usdhc was routed to usdhc_imx6sx_data (HS200 only) and the
HS400 timing/strobe-DLL path was absent.
Add a usdhc_imx8mm_data with ESDHC_FLAG_HS200 | HS400 | HS400_ES and point the
imx8mm/imx8mn/imx8mp compatibles at it. Add the strobe-DLL register block and
esdhc_set_strobe_dll() (gate the card clock, pulse reset, program the read
delay target, poll for REF/SLV lock), an HS400 case in usdhc_set_timing()
(set MIX_CTRL DDREN|HS400_EN, re-apply the clock, run the strobe DLL),
HS400ES handling, and MMC_CAP2_HS400[_ES] advertisement gated on the flags.
The MIXCTRL preserve-mask in __esdhc_send_cmd() is widened from bits 22-25 to
22-27 so the HS400 mode bits survive command writes. Strobe read delay is
configurable via the existing fsl,strobe-dll-delay-target DT binding
(default 0x7).
usdhc_imx8mm_data keeps ESDHC_FLAG_STD_TUNING: barebox already drove these
parts (fsl,imx8mm-usdhc) at HS200 with standard tuning, and U-Boot uses it for
the i.MX8M family too. (Linux uses manual tuning for imx8mm; moving barebox to
manual tuning would be a separate, separately-tested change.)
Register sequence mirrors Linux sdhci-esdhc-imx.c; the barebox idiom follows
the Rockchip dwcmshc HS400 support.
Assisted-by: Claude Opus 4.8 (1M context)
Signed-off-by: Johannes Schneider <johannes.schneider at leica-geosystems.com>
---
drivers/mci/imx-esdhc-common.c | 4 +-
drivers/mci/imx-esdhc.c | 91 ++++++++++++++++++++++++++++++++--
drivers/mci/imx-esdhc.h | 14 ++++++
3 files changed, 103 insertions(+), 6 deletions(-)
diff --git a/drivers/mci/imx-esdhc-common.c b/drivers/mci/imx-esdhc-common.c
index d31a2ad779..a0c2dee32d 100644
--- a/drivers/mci/imx-esdhc-common.c
+++ b/drivers/mci/imx-esdhc-common.c
@@ -360,8 +360,8 @@ int __esdhc_send_cmd(struct fsl_esdhc_host *host, struct mci_cmd *cmd,
if (esdhc_is_usdhc(host)) {
/* write lower-half of xfertyp to mixctrl */
mixctrl = xfertyp;
- /* Keep the bits 22-25 of the register as is */
- mixctrl |= (sdhci_read32(&host->sdhci, IMX_SDHCI_MIXCTRL) & (0xF << 22));
+ /* keep tuning bits (22-25) and HS400 mode bits (26-27) as is */
+ mixctrl |= (sdhci_read32(&host->sdhci, IMX_SDHCI_MIXCTRL) & (0x3F << 22));
mixctrl |= mci_timing_is_ddr(host->sdhci.timing) ? MIX_CTRL_DDREN : 0;
sdhci_write32(&host->sdhci, IMX_SDHCI_MIXCTRL, mixctrl);
}
diff --git a/drivers/mci/imx-esdhc.c b/drivers/mci/imx-esdhc.c
index ba33daffc4..e123bd3ede 100644
--- a/drivers/mci/imx-esdhc.c
+++ b/drivers/mci/imx-esdhc.c
@@ -274,12 +274,51 @@ static int esdhc_change_pinstate(struct fsl_esdhc_host *host,
}
+/* Ported from Linux esdhc_set_strobe_dll() in drivers/mmc/host/sdhci-esdhc-imx.c */
+static void esdhc_set_strobe_dll(struct fsl_esdhc_host *host)
+{
+ u32 strobe_delay, v;
+ int ret;
+
+ /* disable the card clock before (re)programming the strobe DLL */
+ sdhci_write32(&host->sdhci, ESDHC_VENDOR_SPEC,
+ sdhci_read32(&host->sdhci, ESDHC_VENDOR_SPEC) &
+ ~ESDHC_VENDOR_SPEC_FRC_SDCLK_ON);
+ ret = esdhc_poll(host, ESDHC_PRSSTAT, v, (v & ESDHC_CLOCK_GATE_OFF),
+ 100 * USECOND);
+ if (ret)
+ dev_warn(host->dev, "card clock not gated off before strobe DLL\n");
+
+ /* force a reset on the strobe DLL, then clear it before any setting */
+ sdhci_write32(&host->sdhci, ESDHC_STROBE_DLL_CTRL,
+ ESDHC_STROBE_DLL_CTRL_RESET);
+ sdhci_write32(&host->sdhci, ESDHC_STROBE_DLL_CTRL, 0);
+
+ if (host->boarddata.strobe_dll_delay_target)
+ strobe_delay = host->boarddata.strobe_dll_delay_target;
+ else
+ strobe_delay = ESDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_DEFAULT;
+
+ v = ESDHC_STROBE_DLL_CTRL_ENABLE |
+ ESDHC_STROBE_DLL_CTRL_SLV_UPDATE_INT_DEFAULT |
+ (strobe_delay << ESDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_SHIFT);
+ sdhci_write32(&host->sdhci, ESDHC_STROBE_DLL_CTRL, v);
+
+ ret = esdhc_poll(host, ESDHC_STROBE_DLL_STATUS, v,
+ (v & ESDHC_STROBE_DLL_STS_REF_LOCK) &&
+ (v & ESDHC_STROBE_DLL_STS_SLV_LOCK),
+ 50 * USECOND);
+ if (ret)
+ dev_warn(host->dev,
+ "HS400 strobe DLL not locked in 50us, status 0x%08x\n", v);
+}
+
static void usdhc_set_timing(struct fsl_esdhc_host *host, enum mci_timing timing)
{
u32 mixctrl;
mixctrl = sdhci_read32(&host->sdhci, IMX_SDHCI_MIXCTRL);
- mixctrl &= ~MIX_CTRL_DDREN;
+ mixctrl &= ~(MIX_CTRL_DDREN | MIX_CTRL_HS400_EN);
switch (timing) {
case MMC_TIMING_UHS_DDR50:
@@ -296,6 +335,14 @@ static void usdhc_set_timing(struct fsl_esdhc_host *host, enum mci_timing timing
sdhci_write32(&host->sdhci, IMX_SDHCI_DLL_CTRL, v);
}
break;
+ case MMC_TIMING_MMC_HS400:
+ /*
+ * HS400 is a DDR mode; the strobe DLL is set up later from
+ * esdhc_set_ios() once the HS400 clock is running.
+ */
+ mixctrl |= MIX_CTRL_DDREN | MIX_CTRL_HS400_EN;
+ sdhci_write32(&host->sdhci, IMX_SDHCI_MIXCTRL, mixctrl);
+ break;
case MMC_TIMING_UHS_SDR12:
case MMC_TIMING_UHS_SDR25:
case MMC_TIMING_UHS_SDR50:
@@ -339,6 +386,19 @@ static void layerscape_set_timing(struct fsl_esdhc_host *host, enum mci_timing t
host->sdhci.timing = timing;
}
+static void esdhc_hs400_enhanced_strobe(struct mci_host *mci, struct mci_ios *ios)
+{
+ struct fsl_esdhc_host *host = to_fsl_esdhc(mci);
+ u32 m;
+
+ m = sdhci_read32(&host->sdhci, IMX_SDHCI_MIXCTRL);
+ if (ios->enhanced_strobe)
+ m |= MIX_CTRL_HS400_ES;
+ else
+ m &= ~MIX_CTRL_HS400_ES;
+ sdhci_write32(&host->sdhci, IMX_SDHCI_MIXCTRL, m);
+}
+
static void esdhc_set_ios(struct mci_host *mci, struct mci_ios *ios)
{
struct fsl_esdhc_host *host = to_fsl_esdhc(mci);
@@ -386,6 +446,13 @@ static void esdhc_set_ios(struct mci_host *mci, struct mci_ios *ios)
return;
}
+ /*
+ * (Re)program the strobe DLL on every HS400 set_ios(): mci-core selects
+ * HS400 timing while still at the old clock and only raises the clock on
+ * a later set_ios(), so the DLL must lock against the final read clock.
+ */
+ if (ios->timing == MMC_TIMING_MMC_HS400 && ios->clock)
+ esdhc_set_strobe_dll(host);
}
static int esdhc_card_present(struct mci_host *mci)
@@ -511,6 +578,7 @@ static const struct mci_ops fsl_esdhc_ops = {
.set_ios = esdhc_set_ios,
.init = esdhc_init,
.card_present = esdhc_card_present,
+ .hs400_enhanced_strobe = esdhc_hs400_enhanced_strobe,
};
static void fsl_esdhc_probe_dt(struct device *dev, struct fsl_esdhc_host *host)
@@ -528,6 +596,9 @@ static void fsl_esdhc_probe_dt(struct device *dev, struct fsl_esdhc_host *host)
boarddata->tuning_start_tap = ESDHC_TUNING_START_TAP_DEFAULT;
if (of_property_read_u32(np, "fsl,delay-line", &boarddata->delay_line))
boarddata->delay_line = 0;
+ if (of_property_read_u32(np, "fsl,strobe-dll-delay-target",
+ &boarddata->strobe_dll_delay_target))
+ boarddata->strobe_dll_delay_target = 0;
if (esdhc_is_usdhc(host) && !IS_ERR(host->pinctrl)) {
host->pins_100mhz = pinctrl_lookup_state(host->pinctrl,
@@ -567,6 +638,11 @@ static bool usdhc_setup_tuning(struct fsl_esdhc_host *host)
mci->ops.execute_tuning = usdhc_execute_tuning;
mci->caps2 |= MMC_CAP2_HS200;
+ if (host->socdata->flags & ESDHC_FLAG_HS400)
+ mci->caps2 |= MMC_CAP2_HS400_1_8V;
+ if (host->socdata->flags & ESDHC_FLAG_HS400_ES)
+ mci->caps2 |= MMC_CAP2_HS400_ES;
+
return true;
}
@@ -684,6 +760,13 @@ static struct esdhc_soc_data usdhc_imx6sx_data = {
.clkidx = "per",
};
+static struct esdhc_soc_data usdhc_imx8mm_data = {
+ .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING
+ | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200
+ | ESDHC_FLAG_HS400 | ESDHC_FLAG_HS400_ES,
+ .clkidx = "per",
+};
+
static struct esdhc_soc_data esdhc_ls_be_data = {
.flags = ESDHC_FLAG_MULTIBLK_NO_INT | ESDHC_FLAG_BIGENDIAN |
ESDHC_FLAG_LAYERSCAPE,
@@ -702,9 +785,9 @@ static __maybe_unused struct of_device_id fsl_esdhc_compatible[] = {
{ .compatible = "fsl,imx6sl-usdhc", .data = &usdhc_imx6sl_data },
{ .compatible = "fsl,imx6sx-usdhc", .data = &usdhc_imx6sx_data },
{ .compatible = "fsl,imx8mq-usdhc", .data = &usdhc_imx6sx_data },
- { .compatible = "fsl,imx8mm-usdhc", .data = &usdhc_imx6sx_data },
- { .compatible = "fsl,imx8mn-usdhc", .data = &usdhc_imx6sx_data },
- { .compatible = "fsl,imx8mp-usdhc", .data = &usdhc_imx6sx_data },
+ { .compatible = "fsl,imx8mm-usdhc", .data = &usdhc_imx8mm_data },
+ { .compatible = "fsl,imx8mn-usdhc", .data = &usdhc_imx8mm_data },
+ { .compatible = "fsl,imx8mp-usdhc", .data = &usdhc_imx8mm_data },
{ .compatible = "fsl,ls1028a-esdhc",.data = &esdhc_ls_le_data },
{ .compatible = "fsl,ls1046a-esdhc",.data = &esdhc_ls_be_data },
{ /* sentinel */ }
diff --git a/drivers/mci/imx-esdhc.h b/drivers/mci/imx-esdhc.h
index df8aca18ad..5562371652 100644
--- a/drivers/mci/imx-esdhc.h
+++ b/drivers/mci/imx-esdhc.h
@@ -66,6 +66,17 @@
#define IMX_SDHCI_DLL_CTRL_OVERRIDE_EN_SHIFT 8
#define IMX_SDHCI_MIX_CTRL_FBCLK_SEL BIT(25)
+/* HS400 data strobe line delay-locked loop (read clock) */
+#define ESDHC_STROBE_DLL_CTRL 0x70
+#define ESDHC_STROBE_DLL_CTRL_ENABLE BIT(0)
+#define ESDHC_STROBE_DLL_CTRL_RESET BIT(1)
+#define ESDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_SHIFT 3
+#define ESDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_DEFAULT 0x7
+#define ESDHC_STROBE_DLL_CTRL_SLV_UPDATE_INT_DEFAULT (4 << 20)
+#define ESDHC_STROBE_DLL_STATUS 0x74
+#define ESDHC_STROBE_DLL_STS_REF_LOCK BIT(1)
+#define ESDHC_STROBE_DLL_STS_SLV_LOCK BIT(0)
+
/* pltfm-specific */
#define ESDHC_HOST_CONTROL_LE 0x20
@@ -163,6 +174,8 @@
#define ESDHC_FLAG_BIGENDIAN BIT(10)
/* Layerscape variant ls1046a, ls1028a, ls1088a, revisit for ls1012a */
#define ESDHC_FLAG_LAYERSCAPE BIT(11)
+/* The IP supports HS400ES mode */
+#define ESDHC_FLAG_HS400_ES BIT(12)
struct esdhc_soc_data {
u32 flags;
@@ -177,6 +190,7 @@ struct esdhc_platform_data {
unsigned int delay_line;
unsigned int tuning_step; /* The delay cell steps in tuning procedure */
unsigned int tuning_start_tap; /* The start delay cell point in tuning procedure */
+ unsigned int strobe_dll_delay_target; /* HS400 strobe read-clock delay cell */
};
struct fsl_esdhc_host {
--
2.43.0
More information about the barebox
mailing list