* [PATCH v1 1/7] dma: make dma_mapping_error() NULL-safe
@ 2026-06-27 19:43 Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 2/7] mci: sdhci: bail out on ADMA/transfer errors instead of hanging Johannes Schneider
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Johannes Schneider @ 2026-06-27 19:43 UTC (permalink / raw)
To: barebox; +Cc: Johannes Schneider
dma_mapping_error() dereferenced dev->dma_mask unconditionally, while the
neighbouring cpu_to_dma()/dma_to_cpu() helpers already guard with 'dev &&'.
Callers with no device (e.g. the PBL SDHCI layer, which has no mci/device
yet) thus dereferenced a NULL pointer. Guard the dereference.
Assisted-by: Claude Opus 4.8 (1M context)
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
include/dma.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/include/dma.h b/include/dma.h
index b8016c2e89..8f8ac78cdc 100644
--- a/include/dma.h
+++ b/include/dma.h
@@ -70,7 +70,7 @@ static inline void dma_set_mask(struct device *dev, u64 dma_mask)
static inline int dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
{
return dma_addr == DMA_ERROR_CODE ||
- (dev->dma_mask && dma_addr > dev->dma_mask);
+ (dev && dev->dma_mask && dma_addr > dev->dma_mask);
}
static inline dma_addr_t cpu_to_dma(struct device *dev, void *cpu_addr)
base-commit: da91ab6cb505fb09415bf8df3fae552e7b37c5e1
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v1 2/7] mci: sdhci: bail out on ADMA/transfer errors instead of hanging
2026-06-27 19:43 [PATCH v1 1/7] dma: make dma_mapping_error() NULL-safe Johannes Schneider
@ 2026-06-27 19:43 ` Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 3/7] mci: sdhci: honor BROKEN_ADMA_ZEROLEN_DESC / NO_ENDATTR_IN_NOPDESC quirks Johannes Schneider
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Johannes Schneider @ 2026-06-27 19:43 UTC (permalink / raw)
To: barebox; +Cc: Johannes Schneider
sdhci_transfer_data_dma() only watched for the completion and DMA-boundary
interrupts, so a transfer/ADMA error (SDHCI_INT_ERROR, e.g. an ADMA
descriptor fault) left it spinning until the 10s timeout. Check the error
bits and abort the transfer with -EIO, dumping the ADMA error status.
Assisted-by: Claude Opus 4.8 (1M context)
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
drivers/mci/sdhci.c | 8 ++++++++
drivers/mci/sdhci.h | 1 +
2 files changed, 9 insertions(+)
diff --git a/drivers/mci/sdhci.c b/drivers/mci/sdhci.c
index 3474ef129b..ccd5d4b83c 100644
--- a/drivers/mci/sdhci.c
+++ b/drivers/mci/sdhci.c
@@ -779,6 +779,14 @@ int sdhci_transfer_data_dma(struct sdhci *sdhci, struct mci_cmd *cmd,
goto out;
}
+ /* e.g. ADMA error: SDHCI_INT_ERROR without a data-error bit */
+ if (irqstat & SDHCI_INT_ERROR) {
+ dev_err(dev, "transfer error: int 0x%08x adma 0x%02x\n",
+ irqstat, sdhci_read8(sdhci, SDHCI_ADMA_ERROR));
+ ret = -EIO;
+ goto out;
+ }
+
/*
* We currently don't do anything fancy with DMA
* boundaries, but as we can't disable the feature
diff --git a/drivers/mci/sdhci.h b/drivers/mci/sdhci.h
index d1f05ac968..558469e8ab 100644
--- a/drivers/mci/sdhci.h
+++ b/drivers/mci/sdhci.h
@@ -111,6 +111,7 @@
#define SDHCI_RESET_DATA BIT(2)
#define SDHCI_INT_STATUS 0x30
#define SDHCI_INT_NORMAL_STATUS 0x30
+#define SDHCI_INT_ADMA_ERROR BIT(25)
#define SDHCI_INT_DATA_END_BIT BIT(22)
#define SDHCI_INT_DATA_CRC BIT(21)
#define SDHCI_INT_DATA_TIMEOUT BIT(20)
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v1 3/7] mci: sdhci: honor BROKEN_ADMA_ZEROLEN_DESC / NO_ENDATTR_IN_NOPDESC quirks
2026-06-27 19:43 [PATCH v1 1/7] dma: make dma_mapping_error() NULL-safe Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 2/7] mci: sdhci: bail out on ADMA/transfer errors instead of hanging Johannes Schneider
@ 2026-06-27 19:43 ` Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 4/7] mci: imx-esdhc: mark the uSDHC ADMA2 descriptor quirks Johannes Schneider
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Johannes Schneider @ 2026-06-27 19:43 UTC (permalink / raw)
To: barebox; +Cc: Johannes Schneider
The ADMA2 table builder always emitted a 64 KiB chunk as a length-0 descriptor
(the 16-bit length field encodes 65536 as 0) and terminated the table with a
separate NOP-END descriptor. Some controllers mishandle these forms; Linux
gates the workarounds behind SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC (cap the
length below 64 KiB so it is never 0) and SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC
(mark END on the last transfer descriptor instead of a trailing NOP-END).
Add both quirks to the shared SDHCI layer and honor them in the table builder.
Controllers that set neither keep the previous behaviour exactly, so this is a
no-op for everyone until a host driver opts in.
Assisted-by: Claude Opus 4.8 (1M context)
Assisted-by: GitHub Copilot CLI (gpt-5.5)
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
drivers/mci/sdhci.c | 35 ++++++++++++++++++++++++-----------
drivers/mci/sdhci.h | 4 ++++
2 files changed, 28 insertions(+), 11 deletions(-)
diff --git a/drivers/mci/sdhci.c b/drivers/mci/sdhci.c
index ccd5d4b83c..8ed8a59342 100644
--- a/drivers/mci/sdhci.c
+++ b/drivers/mci/sdhci.c
@@ -646,31 +646,44 @@ static int sdhci_adma_write_desc(struct sdhci *host, void **desc,
static int sdhci_adma_build_table(struct sdhci *host, dma_addr_t addr,
unsigned int len)
{
+ unsigned int max_len = SDHCI_ADMA2_MAX_LEN;
void *desc = host->adma_table;
int ret;
+ /*
+ * Some controllers (e.g. the i.MX uSDHC) cannot handle a length-0
+ * descriptor, which the 16-bit length field uses to encode the full
+ * SDHCI_ADMA2_MAX_LEN. Cap the chunk just below that so the length is
+ * never 0.
+ */
+ if (host->quirks & SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC)
+ max_len = SDHCI_ADMA2_MAX_LEN - 4;
+
while (len) {
- unsigned int chunk = min_t(unsigned int, len,
- SDHCI_ADMA2_MAX_LEN);
+ unsigned int chunk = min_t(unsigned int, len, max_len);
+ unsigned int attr = ADMA2_TRAN_VALID;
+ len -= chunk;
/*
- * The length field is 16-bit; a length of 0 encodes
- * SDHCI_ADMA2_MAX_LEN bytes per the SD Host Controller
- * specification.
+ * Controllers that ignore the END attribute in a trailing NOP
+ * descriptor want END on the last transfer descriptor instead
+ * (SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC).
*/
- ret = sdhci_adma_write_desc(host, &desc, addr, chunk & 0xffff,
- ADMA2_TRAN_VALID);
+ if (!len && (host->quirks & SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC))
+ attr |= ADMA2_END;
+
+ ret = sdhci_adma_write_desc(host, &desc, addr, chunk & 0xffff, attr);
if (ret)
return ret;
addr += chunk;
- len -= chunk;
}
- /* Append a terminating descriptor (nop, end, valid). */
- ret = sdhci_adma_write_desc(host, &desc, 0, 0, ADMA2_NOP_END_VALID);
+ if (host->quirks & SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC)
+ return 0;
- return ret;
+ /* Append a terminating descriptor (nop, end, valid). */
+ return sdhci_adma_write_desc(host, &desc, 0, 0, ADMA2_NOP_END_VALID);
}
void sdhci_setup_data_dma(struct sdhci *sdhci, struct mci_data *data,
diff --git a/drivers/mci/sdhci.h b/drivers/mci/sdhci.h
index 558469e8ab..cb3b6d2b17 100644
--- a/drivers/mci/sdhci.h
+++ b/drivers/mci/sdhci.h
@@ -315,6 +315,10 @@ struct sdhci {
bool v4_mode; /* Host Version 4 Enable */
unsigned int quirks;
+/* Controller ignores the END attribute in a NOP ADMA2 descriptor */
+#define SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC BIT(6)
+/* Controller cannot handle a length-0 (== 64 KiB) ADMA2 descriptor */
+#define SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC BIT(11)
#define SDHCI_QUIRK_MISSING_CAPS BIT(27)
unsigned int quirks2;
/* The system physically doesn't support 1.8v, even if the host does */
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v1 4/7] mci: imx-esdhc: mark the uSDHC ADMA2 descriptor quirks
2026-06-27 19:43 [PATCH v1 1/7] dma: make dma_mapping_error() NULL-safe Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 2/7] mci: sdhci: bail out on ADMA/transfer errors instead of hanging Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 3/7] mci: sdhci: honor BROKEN_ADMA_ZEROLEN_DESC / NO_ENDATTR_IN_NOPDESC quirks Johannes Schneider
@ 2026-06-27 19:43 ` Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 5/7] mci: imx-esdhc: support HS400 and HS400ES on i.MX8M Johannes Schneider
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Johannes Schneider @ 2026-06-27 19:43 UTC (permalink / raw)
To: barebox; +Cc: Johannes Schneider
The i.MX uSDHC ADMA2 engine cannot handle a length-0 (64 KiB) descriptor and
ignores the END attribute in a trailing NOP descriptor; large ADMA transfers
hang otherwise. Set SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC and
SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC so the shared table builder emits a safe
descriptor shape, matching Linux sdhci-esdhc-imx. esdhc_populate_sdhci() runs
for both the PBL and the proper driver, so this covers every uSDHC ADMA path.
Assisted-by: Claude Opus 4.8 (1M context)
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
drivers/mci/imx-esdhc-common.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/mci/imx-esdhc-common.c b/drivers/mci/imx-esdhc-common.c
index eb6a71915f..d31a2ad779 100644
--- a/drivers/mci/imx-esdhc-common.c
+++ b/drivers/mci/imx-esdhc-common.c
@@ -285,6 +285,14 @@ void esdhc_populate_sdhci(struct fsl_esdhc_host *host)
host->sdhci.read16 = esdhc_op_read16_le_tuning;
host->sdhci.write16 = esdhc_op_write16_le_tuning;
}
+
+ /*
+ * The uSDHC ADMA2 engine mishandles a length-0 (64 KiB) descriptor and
+ * the END attribute in a trailing NOP descriptor.
+ */
+ if (esdhc_is_usdhc(host))
+ host->sdhci.quirks |= SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC |
+ SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC;
}
static bool esdhc_use_pio_mode(void)
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v1 5/7] mci: imx-esdhc: support HS400 and HS400ES on i.MX8M
2026-06-27 19:43 [PATCH v1 1/7] dma: make dma_mapping_error() NULL-safe Johannes Schneider
` (2 preceding siblings ...)
2026-06-27 19:43 ` [PATCH v1 4/7] mci: imx-esdhc: mark the uSDHC ADMA2 descriptor quirks Johannes Schneider
@ 2026-06-27 19:43 ` Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 6/7] mci: imx-esdhc: make the transfer mode selectable (PIO/SDMA/ADMA2) Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 7/7] mci: imx-esdhc: support DMA in the i.MX8M PBL eMMC loader Johannes Schneider
5 siblings, 0 replies; 7+ messages in thread
From: Johannes Schneider @ 2026-06-27 19:43 UTC (permalink / raw)
To: barebox; +Cc: Johannes Schneider
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@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
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v1 6/7] mci: imx-esdhc: make the transfer mode selectable (PIO/SDMA/ADMA2)
2026-06-27 19:43 [PATCH v1 1/7] dma: make dma_mapping_error() NULL-safe Johannes Schneider
` (3 preceding siblings ...)
2026-06-27 19:43 ` [PATCH v1 5/7] mci: imx-esdhc: support HS400 and HS400ES on i.MX8M Johannes Schneider
@ 2026-06-27 19:43 ` Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 7/7] mci: imx-esdhc: support DMA in the i.MX8M PBL eMMC loader Johannes Schneider
5 siblings, 0 replies; 7+ messages in thread
From: Johannes Schneider @ 2026-06-27 19:43 UTC (permalink / raw)
To: barebox; +Cc: Johannes Schneider
The driver only offered a PIO-or-DMA toggle (CONFIG_MCI_IMX_ESDHC_PIO) and
never called sdhci_setup_adma(), so the controller-resident ADMA2 engine was
unreachable and DMA always meant SDMA.
Replace the single bool with a choice of PIO/SDMA/ADMA2, defaulting to SDMA so
existing configs keep their current behaviour, and call sdhci_setup_adma() in
probe under the ADMA2 option (mirroring the Rockchip dwcmshc driver). It
returns -ENOTSUPP and cleanly falls back to SDMA on controllers without ADMA2
capability, so the new option is safe on all i.MX/Layerscape variants.
Assisted-by: Claude Opus 4.8 (1M context)
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
drivers/mci/Kconfig | 19 ++++++++++++++++---
drivers/mci/imx-esdhc.c | 7 +++++++
2 files changed, 23 insertions(+), 3 deletions(-)
diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig
index b38f7a3bdf..cd8862722c 100644
--- a/drivers/mci/Kconfig
+++ b/drivers/mci/Kconfig
@@ -169,11 +169,24 @@ config MCI_IMX_ESDHC
Enable this entry to add support to read and write SD cards on a
Freescale i.MX25/35/51 based system.
-config MCI_IMX_ESDHC_PIO
- bool "use PIO mode"
+choice
+ prompt "i.MX esdhc transfer mode"
+ default MCI_IMX_ESDHC_SDMA
depends on MCI_IMX_ESDHC
help
- mostly useful for debugging. Normally you should use DMA.
+ How the driver moves data words to/from the controller. SDMA and
+ ADMA2 use the controller's DMA engine; PIO is CPU-driven and slow.
+
+config MCI_IMX_ESDHC_PIO
+ bool "PIO"
+
+config MCI_IMX_ESDHC_SDMA
+ bool "SDMA"
+
+config MCI_IMX_ESDHC_ADMA2
+ bool "ADMA2"
+
+endchoice
config MCI_OMAP_HSMMC
bool "OMAP HSMMC"
diff --git a/drivers/mci/imx-esdhc.c b/drivers/mci/imx-esdhc.c
index e123bd3ede..3536ebd6b8 100644
--- a/drivers/mci/imx-esdhc.c
+++ b/drivers/mci/imx-esdhc.c
@@ -698,6 +698,13 @@ static int fsl_esdhc_probe(struct device *dev)
if (ret)
goto err_clk_disable;
+ if (IS_ENABLED(CONFIG_MCI_IMX_ESDHC_ADMA2)) {
+ ret = sdhci_setup_adma(&host->sdhci);
+ if (ret && ret != -ENOTSUPP)
+ dev_warn(dev, "ADMA setup failed (%pe), falling back to SDMA\n",
+ ERR_PTR(ret));
+ }
+
if (esdhc_is_usdhc(host) || esdhc_is_layerscape(host))
mci->host_caps |= MMC_CAP_3_3V_DDR | MMC_CAP_1_8V_DDR;
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v1 7/7] mci: imx-esdhc: support DMA in the i.MX8M PBL eMMC loader
2026-06-27 19:43 [PATCH v1 1/7] dma: make dma_mapping_error() NULL-safe Johannes Schneider
` (4 preceding siblings ...)
2026-06-27 19:43 ` [PATCH v1 6/7] mci: imx-esdhc: make the transfer mode selectable (PIO/SDMA/ADMA2) Johannes Schneider
@ 2026-06-27 19:43 ` Johannes Schneider
5 siblings, 0 replies; 7+ messages in thread
From: Johannes Schneider @ 2026-06-27 19:43 UTC (permalink / raw)
To: barebox; +Cc: Johannes Schneider
The PBL eMMC loader was hardwired to PIO via esdhc_use_pio_mode(): IN_PBL
forced PIO unconditionally. PIO is slow -- on an i.MX8MM board loading the
~1.6 MB next stage took ~559 ms; SDMA/ADMA2 do it in ~21 ms.
Let esdhc_use_pio_mode() in the PBL honour the SDHCI DMA flags, add a
transfer-mode choice for the loader (PIO default / SDMA / ADMA2), and route
both the imx8m and imx8mp/imx8mn loaders through shared helpers. The PBL has
no mci and runs before BL31, so host.sdhci.version is set by hand (no
__sdhci_read_caps()) and the ADMA2 descriptor table lives in a static buffer
(the PBL has no allocator), compiled in only for the ADMA2 option.
On the imx8mp loader the DMA mode is set up *before* the bootpart EXT_CSD
read so that read also goes through ADMA: a PIO transfer preceding the first
ADMA transfer wedges the uSDHC ADMA engine. Either way the loader retries in
PIO if the DMA attempt fails, so a misconfigured board still boots.
SDMA and ADMA2 perform identically here (the transfer is bus-bound), and SDMA
needs no descriptor table, so it is the simpler default for boards that just
want the speedup.
Assisted-by: Claude Opus 4.8 (1M context)
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
drivers/mci/Kconfig | 21 +++++++++++
drivers/mci/imx-esdhc-common.c | 16 ++++++--
drivers/mci/imx-esdhc-pbl.c | 68 +++++++++++++++++++++++++++++++---
3 files changed, 96 insertions(+), 9 deletions(-)
diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig
index cd8862722c..e071bb5650 100644
--- a/drivers/mci/Kconfig
+++ b/drivers/mci/Kconfig
@@ -288,6 +288,27 @@ config MCI_IMX_ESDHC_PBL
bool
select MCI_SDHCI
+choice
+ prompt "i.MX8M PBL eMMC loader transfer mode"
+ default MCI_IMX_ESDHC_PBL_PIO
+ depends on MCI_IMX_ESDHC_PBL
+ help
+ How the i.MX8M PBL reads the next boot stage from eMMC. DMA is much
+ faster than PIO; on failure the loader falls back to PIO so the board
+ still boots. ADMA2 additionally needs the board PBL to set up an early
+ malloc pool (pbl_malloc_init) for the descriptor table; SDMA does not.
+
+config MCI_IMX_ESDHC_PBL_PIO
+ bool "PIO"
+
+config MCI_IMX_ESDHC_PBL_SDMA
+ bool "SDMA"
+
+config MCI_IMX_ESDHC_PBL_ADMA2
+ bool "ADMA2"
+
+endchoice
+
config MCI_ATMEL_PBL
bool
select MCI_ATMEL
diff --git a/drivers/mci/imx-esdhc-common.c b/drivers/mci/imx-esdhc-common.c
index a0c2dee32d..1bf70d4e65 100644
--- a/drivers/mci/imx-esdhc-common.c
+++ b/drivers/mci/imx-esdhc-common.c
@@ -295,9 +295,17 @@ void esdhc_populate_sdhci(struct fsl_esdhc_host *host)
SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC;
}
-static bool esdhc_use_pio_mode(void)
+static bool esdhc_use_pio_mode(struct fsl_esdhc_host *host)
{
- return IN_PBL || IS_ENABLED(CONFIG_MCI_IMX_ESDHC_PIO);
+ /*
+ * In the PBL default to PIO, unless a caller explicitly opted into
+ * ADMA2 (SDHCI_USE_ADMA, descriptor table set up) or SDMA
+ * (SDHCI_USE_SDMA).
+ */
+ if (IN_PBL)
+ return !(host->sdhci.flags & (SDHCI_USE_ADMA | SDHCI_USE_SDMA));
+
+ return IS_ENABLED(CONFIG_MCI_IMX_ESDHC_PIO);
}
static int esdhc_setup_data(struct fsl_esdhc_host *host, struct mci_data *data,
@@ -317,7 +325,7 @@ static int esdhc_setup_data(struct fsl_esdhc_host *host, struct mci_data *data,
host->sdhci.sdma_boundary = 0;
- if (esdhc_use_pio_mode())
+ if (esdhc_use_pio_mode(host))
sdhci_setup_data_pio(&host->sdhci, data);
else
sdhci_setup_data_dma(&host->sdhci, data, dma);
@@ -411,7 +419,7 @@ int __esdhc_send_cmd(struct fsl_esdhc_host *host, struct mci_cmd *cmd,
/* Wait until all of the blocks are transferred */
if (data) {
- if (esdhc_use_pio_mode())
+ if (esdhc_use_pio_mode(host))
ret = sdhci_transfer_data_pio(&host->sdhci, cmd, data);
else
ret = sdhci_transfer_data_dma(&host->sdhci, cmd, data, dma);
diff --git a/drivers/mci/imx-esdhc-pbl.c b/drivers/mci/imx-esdhc-pbl.c
index fe06d418b0..80e80b24a0 100644
--- a/drivers/mci/imx-esdhc-pbl.c
+++ b/drivers/mci/imx-esdhc-pbl.c
@@ -33,6 +33,12 @@
static u8 ext_csd[512] __aligned(64);
+#ifdef CONFIG_MCI_IMX_ESDHC_PBL_ADMA2
+/* ADMA2 descriptor table for the PBL eMMC loader (the PBL has no allocator). */
+static u8 esdhc_adma_table[SDHCI_DEFAULT_ADMA_DESCS * SDHCI_ADMA2_32_DESC_SZ]
+ __aligned(8);
+#endif
+
static int esdhc_send_ext_csd(struct fsl_esdhc_host *host)
{
struct mci_cmd cmd = {};
@@ -252,6 +258,51 @@ int imx7_esdhc_start_image(int instance)
* Return: If image successfully loaded, returns 0.
* A negative error code is returned when this function fails.
*/
+/*
+ * Configure the PBL eMMC loader's DMA mode (ADMA2 or SDMA per Kconfig). The
+ * PBL has no mci, so host->sdhci.version is set by hand because
+ * __sdhci_read_caps() cannot run.
+ */
+static void imx8m_esdhc_pbl_setup_dma(struct fsl_esdhc_host *host)
+{
+ if (IS_ENABLED(CONFIG_MCI_IMX_ESDHC_PBL_SDMA)) {
+ host->sdhci.version = SDHCI_SPEC_300;
+ host->sdhci.flags |= SDHCI_USE_SDMA;
+ }
+
+#ifdef CONFIG_MCI_IMX_ESDHC_PBL_ADMA2
+ host->sdhci.version = SDHCI_SPEC_300;
+ host->sdhci.adma_table = esdhc_adma_table;
+ host->sdhci.adma_addr = virt_to_phys(esdhc_adma_table);
+ host->sdhci.desc_sz = SDHCI_ADMA2_32_DESC_SZ;
+ host->sdhci.adma_table_cnt = SDHCI_DEFAULT_ADMA_DESCS;
+ host->sdhci.adma_table_sz = sizeof(esdhc_adma_table);
+ host->sdhci.flags |= SDHCI_USE_ADMA;
+#endif
+}
+
+static int imx8m_esdhc_load_image_dma(struct fsl_esdhc_host *host,
+ struct esdhc_soc_data *data, int instance,
+ void *bl33, u32 offset, u32 ivt_offset)
+{
+ int ret;
+
+ ret = esdhc_load_image(host, MX8M_DDR_CSD1_BASE_ADDR,
+ (ptrdiff_t)bl33, offset, ivt_offset, false);
+ if (!ret)
+ return 0;
+
+ /* Fall back to PIO so a failed DMA attempt still boots. */
+ if (host->sdhci.flags & (SDHCI_USE_ADMA | SDHCI_USE_SDMA)) {
+ host->sdhci.flags &= ~(SDHCI_USE_ADMA | SDHCI_USE_SDMA);
+ imx8m_esdhc_init(host, data, instance);
+ ret = esdhc_load_image(host, MX8M_DDR_CSD1_BASE_ADDR,
+ (ptrdiff_t)bl33, offset, ivt_offset, false);
+ }
+
+ return ret;
+}
+
int imx8m_esdhc_load_image(int instance, void *bl33)
{
struct esdhc_soc_data data;
@@ -262,9 +313,10 @@ int imx8m_esdhc_load_image(int instance, void *bl33)
if (ret)
return ret;
- return esdhc_load_image(&host, MX8M_DDR_CSD1_BASE_ADDR,
- (ptrdiff_t)bl33, SZ_32K, SZ_1K,
- false);
+ imx8m_esdhc_pbl_setup_dma(&host);
+
+ return imx8m_esdhc_load_image_dma(&host, &data, instance, bl33,
+ SZ_32K, SZ_1K);
}
/**
@@ -291,10 +343,16 @@ int imx8mp_esdhc_load_image(int instance, void *bl33)
if (ret)
return ret;
+ /*
+ * Set up DMA first so the EXT_CSD read below also goes through ADMA --
+ * a PIO transfer before the first ADMA transfer wedges the 8MP uSDHC.
+ */
+ imx8m_esdhc_pbl_setup_dma(&host);
+
offset = esdhc_bootpart_active(&host)? 0 : SZ_32K;
- return esdhc_load_image(&host, MX8M_DDR_CSD1_BASE_ADDR,
- (ptrdiff_t)bl33, offset, 0, false);
+ return imx8m_esdhc_load_image_dma(&host, &data, instance, bl33,
+ offset, 0);
}
int imx8mn_esdhc_load_image(int instance, void *bl33)
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-06-27 19:45 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-27 19:43 [PATCH v1 1/7] dma: make dma_mapping_error() NULL-safe Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 2/7] mci: sdhci: bail out on ADMA/transfer errors instead of hanging Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 3/7] mci: sdhci: honor BROKEN_ADMA_ZEROLEN_DESC / NO_ENDATTR_IN_NOPDESC quirks Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 4/7] mci: imx-esdhc: mark the uSDHC ADMA2 descriptor quirks Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 5/7] mci: imx-esdhc: support HS400 and HS400ES on i.MX8M Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 6/7] mci: imx-esdhc: make the transfer mode selectable (PIO/SDMA/ADMA2) Johannes Schneider
2026-06-27 19:43 ` [PATCH v1 7/7] mci: imx-esdhc: support DMA in the i.MX8M PBL eMMC loader Johannes Schneider
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox