U-Boot enables i.MX uSDHC clocks through VEND_SPEC, so checking only
the standard SDHCI clock-control bits drops commands and prevents
firmware from loading its kernel from the SD card.

Present vendor gates to the common clock check during uSDHC command
writes, then restore the guest clock register. Retain the standard
clock path used by Linux without adding vendor quirks to generic code.
Report SDSTB and handle partial vendor accesses in uSDHC MMIO handlers.

Reference: IMX6ULRM (Rev. 2), sections 56.8.10, 56.8.12 and 56.8.26
https://www.nxp.com/webapp/Download?colCode=IMX6ULRM

Signed-off-by: Bin Meng <[email protected]>

---

Changes in v3:
- Translate vendor gates in uSDHC MMIO instead of a generic quirk
- Preserve the standard clock path used by direct Linux boot
- Handle partial vendor accesses and report SDSTB in uSDHC reads
- Migrate the vendor register in an optional subsection

 hw/sd/sdhci.c | 93 ++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 80 insertions(+), 13 deletions(-)

diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c
index 14c312a691..40cf407876 100644
--- a/hw/sd/sdhci.c
+++ b/hw/sd/sdhci.c
@@ -1613,6 +1613,24 @@ static const VMStateDescription sdhci_hostctl2_vmstate = 
{
     },
 };
 
+static bool sdhci_vendor_spec_vmstate_needed(void *opaque)
+{
+    SDHCIState *s = opaque;
+
+    return s->vendor_spec;
+}
+
+static const VMStateDescription sdhci_vendor_spec_vmstate = {
+    .name = "sdhci/vendor-spec",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .needed = sdhci_vendor_spec_vmstate_needed,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT16(vendor_spec, SDHCIState),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
 static bool sdhci_pending_insert_vmstate_needed(void *opaque)
 {
     SDHCIState *s = opaque;
@@ -1632,6 +1650,7 @@ static int sdhci_pre_load(void *opaque)
     SDHCIState *s = opaque;
 
     s->hostctl2 = 0;
+    s->vendor_spec = 0;
     s->sdma_boundary_paused = false;
     return 0;
 }
@@ -1707,6 +1726,7 @@ const VMStateDescription sdhci_vmstate = {
     },
     .subsections = (const VMStateDescription * const []) {
         &sdhci_hostctl2_vmstate,
+        &sdhci_vendor_spec_vmstate,
         &sdhci_pending_insert_vmstate,
         &sdhci_sdma_boundary_paused_vmstate,
         NULL
@@ -1810,6 +1830,10 @@ static void sdhci_bus_class_init(ObjectClass *klass, 
const void *data)
 
 #define ESDHC_VENDOR_SPEC               0xc0
 #define ESDHC_FRC_SDCLK_ON              (1 << 8)
+#define ESDHC_VENDOR_IPGEN              (1 << 11)
+#define ESDHC_VENDOR_HCKEN              (1 << 12)
+#define ESDHC_VENDOR_PEREN              (1 << 13)
+#define ESDHC_VENDOR_CKEN               (1 << 14)
 
 #define ESDHC_DLL_CTRL                  0x60
 
@@ -2060,12 +2084,67 @@ static void fsl_esdhc_le_init(Object *obj)
     qdev_prop_set_uint8(dev, "sd-spec-version", 2);
 }
 
+static bool usdhc_vendor_clocks_on(SDHCIState *s)
+{
+    uint16_t clocks = ESDHC_VENDOR_IPGEN | ESDHC_VENDOR_HCKEN |
+                      ESDHC_VENDOR_PEREN | ESDHC_VENDOR_CKEN;
+
+    return (s->vendor_spec & clocks) == clocks;
+}
+
+static uint64_t usdhc_read(void *opaque, hwaddr offset, unsigned size)
+{
+    SDHCIState *s = SYSBUS_SDHCI(opaque);
+    uint32_t value;
+
+    switch (offset & ~3) {
+    case SDHC_PRNSTS:
+        value = esdhc_read(opaque, SDHC_PRNSTS, 4);
+        if ((s->vendor_spec & (ESDHC_VENDOR_IPGEN | ESDHC_VENDOR_HCKEN)) ==
+            (ESDHC_VENDOR_IPGEN | ESDHC_VENDOR_HCKEN)) {
+            value |= ESDHC_PRNSTS_SDSTB;
+        }
+        break;
+    case ESDHC_VENDOR_SPEC:
+        value = s->vendor_spec;
+        break;
+    default:
+        return esdhc_read(opaque, offset, size);
+    }
+    return extract32(value, (offset & 3) * 8, size * 8);
+}
+
 static void
 usdhc_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
 {
     SDHCIState *s = SYSBUS_SDHCI(opaque);
     uint32_t value = (uint32_t)val;
 
+    if ((offset & ~3) == ESDHC_VENDOR_SPEC) {
+        value = deposit32(s->vendor_spec, (offset & 3) * 8,
+                          size * 8, value);
+        esdhc_write(opaque, ESDHC_VENDOR_SPEC, value, 4);
+        return;
+    }
+    if ((offset & ~3) == SDHC_CLKCON) {
+        esdhc_write(opaque, offset, val, size);
+        return;
+    }
+    if ((offset & ~3) == SDHC_TRNMOD) {
+        uint16_t clkcon = s->clkcon;
+
+        /* Present vendor clocks to the common command check */
+        if (usdhc_vendor_clocks_on(s)) {
+            s->clkcon |= SDHC_CLOCK_CHK_MASK;
+        }
+        if (offset == SDHC_TRNMOD && size == 4) {
+            val |= s->trnmod;
+        }
+        sdhci_write(opaque, offset, val, size);
+        s->clkcon = clkcon;
+        return;
+    }
+
     switch (offset) {
     case ESDHC_MIX_CTRL:
         /*
@@ -2085,18 +2164,6 @@ usdhc_write(void *opaque, hwaddr offset, uint64_t val, 
unsigned size)
         s->trnmod = value & UINT16_MAX;
         break;
 
-    case SDHC_TRNMOD:
-        /*
-         * Similar to above, but this time a write to "Command
-         * Register" will be translated into a 4-byte write to
-         * "Transfer Mode register" where lower 16-bit of value would
-         * be set to zero. So what we do is fill those bits with
-         * cached value from s->trnmod and let the SDHCI
-         * infrastructure handle the rest
-         */
-        sdhci_write(opaque, offset, val | s->trnmod, size);
-        break;
-
     default:
         esdhc_write(opaque, offset, val, size);
         break;
@@ -2104,7 +2171,7 @@ usdhc_write(void *opaque, hwaddr offset, uint64_t val, 
unsigned size)
 }
 
 static const MemoryRegionOps usdhc_mmio_ops = {
-    .read = esdhc_read,
+    .read = usdhc_read,
     .write = usdhc_write,
     .valid = {
         .min_access_size = 1,
-- 
2.53.0


Reply via email to