[PATCH v2 06/11] mci: imx-esdhc: support HS400 and HS400ES on i.MX8M
Johannes Schneider <[email protected]> Sat, 4 Jul 2026 12:26:23 +0000
| Newsgroups | org.infradead.lists.barebox |
|---|---|
| Message-ID | <[email protected]> |
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 <[email protected]> --- Notes: v2: - No functional change; rebased on next. 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