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
