[PATCH] hw/sd/sdhci: model the i.MX uSDHC clock, reset values and DMA boundary

Pablo Mazzini <[email protected]> Mon, 3 Aug 2026 09:26:53 -0700
Newsgroups gmane.comp.emulators.qemu.block,gmane.comp.emulators.qemu
Message-ID <[email protected]>
The i.MX uSDHC differs from the Freescale eSDHC that this file already
models in three ways that a vendor U-Boot depends on.

Clock enables.  The classic eSDHC keeps them in SYSCTL.  The uSDHC moved
them to VENDOR_SPEC bits 11-14.  Teach SDHC_CLOCK_IS_ON() about the
VENDOR_SPEC enable.

Interrupt status enables.  Unlike the SD Host Controller standard, the
eSDHC does not reset these to zero, which commit d060b2789f7 already
models for fsl-esdhc-be/le.  The uSDHC behaves the same way.

SDMA buffer boundary.  The uSDHC's internal DMA has none; it transfers
the whole programmed block count and reports only Transfer Complete.

Signed-off-by: Pablo Mazzini <[email protected]>
---
 hw/sd/sdhci-internal.h |  7 +++++--
 hw/sd/sdhci.c          | 14 ++++++++++++--
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/hw/sd/sdhci-internal.h b/hw/sd/sdhci-internal.h
index 4aeed120bf..60bed5af30 100644
--- a/hw/sd/sdhci-internal.h
+++ b/hw/sd/sdhci-internal.h
@@ -124,8 +124,11 @@ FIELD(SDHC_PWRCON, BUS_VOLTAGE,        1, 3);
 #define SDHC_CLOCK_INT_EN              0x0001
 #define SDHC_CLOCK_SDCLK_EN            (1 << 2)
 #define SDHC_CLOCK_CHK_MASK            0x0007
-#define SDHC_CLOCK_IS_ON(x)            \
-    (((x) & SDHC_CLOCK_CHK_MASK) == SDHC_CLOCK_CHK_MASK)
+/* The i.MX uSDHC keeps its clock enables in VENDOR_SPEC instead */
+#define ESDHC_CKEN                     (1 << 14)
+#define SDHC_CLOCK_IS_ON(clkcon, vendor_spec)                  \
+    ((((clkcon) & SDHC_CLOCK_CHK_MASK) == SDHC_CLOCK_CHK_MASK) \
+     || ((vendor_spec) & ESDHC_CKEN))
 
 /* R/W Timeout Control Register 0x0 */
 #define SDHC_TIMEOUTCON                0x2E
diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c
index e58a610397..f64159c38b 100644
--- a/hw/sd/sdhci.c
+++ b/hw/sd/sdhci.c
@@ -308,7 +308,8 @@ static void sdhci_reset(SDHCIState *s)
     s->stopped_state = sdhc_not_stopped;
     s->pending_insert_state = false;
     if (object_dynamic_cast(OBJECT(s), TYPE_FSL_ESDHC_BE) ||
-            object_dynamic_cast(OBJECT(s), TYPE_FSL_ESDHC_LE)) {
+            object_dynamic_cast(OBJECT(s), TYPE_FSL_ESDHC_LE) ||
+            object_dynamic_cast(OBJECT(s), TYPE_IMX_USDHC)) {
         s->norintstsen = 0x013f;
         s->errintstsen = 0x117f;
     }
@@ -619,6 +620,15 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s)
         page_aligned = true;
     }
 
+    /*
+     * The i.MX uSDHC has no "Host SDMA Buffer Boundary": its internal DMA
+     * transfers the whole programmed block count and reports only Transfer
+     * Complete, so it never pauses at a boundary.
+     */
+    if (object_dynamic_cast(OBJECT(s), TYPE_IMX_USDHC)) {
+        page_aligned = false;
+    }
+
     s->prnsts |= SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE;
     if (s->trnmod & SDHC_TRNS_READ) {
         s->prnsts |= SDHC_DOING_READ;
@@ -994,7 +1004,7 @@ static void sdhci_data_transfer(void *opaque)
 
 static bool sdhci_can_issue_command(SDHCIState *s)
 {
-    if (!SDHC_CLOCK_IS_ON(s->clkcon) ||
+    if (!SDHC_CLOCK_IS_ON(s->clkcon, s->vendor_spec) ||
         (((s->prnsts & SDHC_DATA_INHIBIT) || s->stopped_state) &&
         ((s->cmdreg & SDHC_CMD_DATA_PRESENT) ||
         ((s->cmdreg & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY &&
-- 
2.53.0