U-Boot's i.MX uSDHC driver enables IPGEN, HCKEN, PEREN and CKEN in
VEND_SPEC rather than the standard SDHCI clock-control fields. QEMU
only checks the standard fields before issuing a command. It therefore
silently drops U-Boot's MMC commands even though the controller clocks
are enabled. U-Boot eventually times out waiting for command completion
and cannot load the kernel and device tree from the SD card.

Accept the complete vendor clock-gate set as another valid clock source
for i.MX uSDHC. Also report SDSTB when the vendor IP and host clocks
are enabled, matching the state U-Boot polls while changing the clock.

Keep this behavior behind an i.MX uSDHC quirk because the shared eSDHC
paths also serve controllers which use the standard SDHCI fields.

Reference: IMX6ULRM (Rev 2), section 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]>
Reviewed-by: Philippe Mathieu-Daudé <[email protected]>

---

Changes in v2:
- rebase on top of the microchip polarfire soc series

 include/hw/sd/sdhci.h |  7 ++++++-
 hw/sd/sdhci.c         | 27 ++++++++++++++++++++++++---
 2 files changed, 30 insertions(+), 4 deletions(-)

diff --git a/include/hw/sd/sdhci.h b/include/hw/sd/sdhci.h
index 2d03e37653..83d465774b 100644
--- a/include/hw/sd/sdhci.h
+++ b/include/hw/sd/sdhci.h
@@ -112,7 +112,12 @@ typedef struct SDHCIState SDHCIState;
  * Controller does not provide transfer-complete interrupt when not
  * busy.
  */
-#define SDHCI_QUIRK_NO_BUSY_IRQ    BIT(0)
+#define SDHCI_QUIRK_NO_BUSY_IRQ          BIT(0)
+/*
+ * Controller uses vendor-specific clock gates in place of the standard
+ * SDHCI clock-control fields
+ */
+#define SDHCI_QUIRK_CLOCKS_IN_VENDOR     BIT(1)
 
 #define TYPE_PCI_SDHCI "sdhci-pci"
 DECLARE_INSTANCE_CHECKER(SDHCIState, PCI_SDHCI,
diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c
index 14c312a691..b6e447a805 100644
--- a/hw/sd/sdhci.c
+++ b/hw/sd/sdhci.c
@@ -1107,9 +1107,11 @@ static void sdhci_data_transfer(SDHCIState *s)
     }
 }
 
+static bool sdhci_clocks_on(SDHCIState *s);
+
 static bool sdhci_can_issue_command(SDHCIState *s)
 {
-    if (!SDHC_CLOCK_IS_ON(s->clkcon) ||
+    if (!sdhci_clocks_on(s) ||
         (((s->prnsts & SDHC_DATA_INHIBIT) || s->stopped_state) &&
         ((s->cmdreg & SDHC_CMD_DATA_PRESENT) ||
         ((s->cmdreg & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY &&
@@ -1810,6 +1812,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
 
@@ -1826,6 +1832,16 @@ static void sdhci_bus_class_init(ObjectClass *klass, 
const void *data)
 #define ESDHC_PRNSTS_SDSTB              (1 << 3)
 #define ESDHC_PRNSTS_CLOCK_GATE_OFF     BIT(7)
 
+static bool sdhci_clocks_on(SDHCIState *s)
+{
+    uint32_t vendor_clocks = ESDHC_VENDOR_IPGEN | ESDHC_VENDOR_HCKEN |
+                             ESDHC_VENDOR_PEREN | ESDHC_VENDOR_CKEN;
+
+    return SDHC_CLOCK_IS_ON(s->clkcon) ||
+           ((s->quirks & SDHCI_QUIRK_CLOCKS_IN_VENDOR) &&
+            (s->vendor_spec & vendor_clocks) == vendor_clocks);
+}
+
 static uint64_t esdhc_read(void *opaque, hwaddr offset, unsigned size)
 {
     SDHCIState *s = SYSBUS_SDHCI(opaque);
@@ -1861,7 +1877,11 @@ static uint64_t esdhc_read(void *opaque, hwaddr offset, 
unsigned size)
     case SDHC_PRNSTS:
         /* Add SDSTB (SD Clock Stable) bit to PRNSTS */
         ret = sdhci_read(opaque, offset, size) & ~ESDHC_PRNSTS_SDSTB;
-        if (s->clkcon & SDHC_CLOCK_INT_STABLE) {
+        if ((s->clkcon & SDHC_CLOCK_INT_STABLE) ||
+            ((s->quirks & SDHCI_QUIRK_CLOCKS_IN_VENDOR) &&
+             (s->vendor_spec & (ESDHC_VENDOR_IPGEN |
+                                ESDHC_VENDOR_HCKEN)) ==
+             (ESDHC_VENDOR_IPGEN | ESDHC_VENDOR_HCKEN))) {
             ret |= ESDHC_PRNSTS_SDSTB;
         }
         break;
@@ -2120,7 +2140,8 @@ static void imx_usdhc_init(Object *obj)
     DeviceState *dev = DEVICE(obj);
 
     s->io_ops = &usdhc_mmio_ops;
-    s->quirks = SDHCI_QUIRK_NO_BUSY_IRQ;
+    s->quirks = SDHCI_QUIRK_NO_BUSY_IRQ |
+                SDHCI_QUIRK_CLOCKS_IN_VENDOR;
     qdev_prop_set_uint8(dev, "sd-spec-version", 3);
 }
 
-- 
2.53.0


Reply via email to