|
@@ -244,7 +244,8 @@ static void sdhci_send_command(SDHCIState *s)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if ((s->norintstsen & SDHC_NISEN_TRSCMP) &&
|
|
|
+ if (!(s->quirks & SDHCI_QUIRK_NO_BUSY_IRQ) &&
|
|
|
+ (s->norintstsen & SDHC_NISEN_TRSCMP) &&
|
|
|
(s->cmdreg & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY) {
|
|
|
s->norintsts |= SDHC_NIS_TRSCMP;
|
|
|
}
|
|
@@ -1189,6 +1190,8 @@ static void sdhci_initfn(SDHCIState *s)
|
|
|
|
|
|
s->insert_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sdhci_raise_insertion_irq, s);
|
|
|
s->transfer_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sdhci_data_transfer, s);
|
|
|
+
|
|
|
+ s->io_ops = &sdhci_mmio_ops;
|
|
|
}
|
|
|
|
|
|
static void sdhci_uninitfn(SDHCIState *s)
|
|
@@ -1396,6 +1399,10 @@ static void sdhci_sysbus_realize(DeviceState *dev, Error ** errp)
|
|
|
}
|
|
|
|
|
|
sysbus_init_irq(sbd, &s->irq);
|
|
|
+
|
|
|
+ memory_region_init_io(&s->iomem, OBJECT(s), s->io_ops, s, "sdhci",
|
|
|
+ SDHC_REGISTERS_MAP_SIZE);
|
|
|
+
|
|
|
sysbus_init_mmio(sbd, &s->iomem);
|
|
|
}
|
|
|
|
|
@@ -1447,11 +1454,232 @@ static const TypeInfo sdhci_bus_info = {
|
|
|
.class_init = sdhci_bus_class_init,
|
|
|
};
|
|
|
|
|
|
+static uint64_t usdhc_read(void *opaque, hwaddr offset, unsigned size)
|
|
|
+{
|
|
|
+ SDHCIState *s = SYSBUS_SDHCI(opaque);
|
|
|
+ uint32_t ret;
|
|
|
+ uint16_t hostctl;
|
|
|
+
|
|
|
+ switch (offset) {
|
|
|
+ default:
|
|
|
+ return sdhci_read(opaque, offset, size);
|
|
|
+
|
|
|
+ case SDHC_HOSTCTL:
|
|
|
+ /*
|
|
|
+ * For a detailed explanation on the following bit
|
|
|
+ * manipulation code see comments in a similar part of
|
|
|
+ * usdhc_write()
|
|
|
+ */
|
|
|
+ hostctl = SDHC_DMA_TYPE(s->hostctl) << (8 - 3);
|
|
|
+
|
|
|
+ if (s->hostctl & SDHC_CTRL_8BITBUS) {
|
|
|
+ hostctl |= ESDHC_CTRL_8BITBUS;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (s->hostctl & SDHC_CTRL_4BITBUS) {
|
|
|
+ hostctl |= ESDHC_CTRL_4BITBUS;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = hostctl;
|
|
|
+ ret |= (uint32_t)s->blkgap << 16;
|
|
|
+ ret |= (uint32_t)s->wakcon << 24;
|
|
|
+
|
|
|
+ break;
|
|
|
+
|
|
|
+ case ESDHC_DLL_CTRL:
|
|
|
+ case ESDHC_TUNE_CTRL_STATUS:
|
|
|
+ case ESDHC_UNDOCUMENTED_REG27:
|
|
|
+ case ESDHC_TUNING_CTRL:
|
|
|
+ case ESDHC_VENDOR_SPEC:
|
|
|
+ case ESDHC_MIX_CTRL:
|
|
|
+ case ESDHC_WTMK_LVL:
|
|
|
+ ret = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+usdhc_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
|
|
|
+{
|
|
|
+ SDHCIState *s = SYSBUS_SDHCI(opaque);
|
|
|
+ uint8_t hostctl;
|
|
|
+ uint32_t value = (uint32_t)val;
|
|
|
+
|
|
|
+ switch (offset) {
|
|
|
+ case ESDHC_DLL_CTRL:
|
|
|
+ case ESDHC_TUNE_CTRL_STATUS:
|
|
|
+ case ESDHC_UNDOCUMENTED_REG27:
|
|
|
+ case ESDHC_TUNING_CTRL:
|
|
|
+ case ESDHC_WTMK_LVL:
|
|
|
+ case ESDHC_VENDOR_SPEC:
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SDHC_HOSTCTL:
|
|
|
+ /*
|
|
|
+ * Here's What ESDHCI has at offset 0x28 (SDHC_HOSTCTL)
|
|
|
+ *
|
|
|
+ * 7 6 5 4 3 2 1 0
|
|
|
+ * |-----------+--------+--------+-----------+----------+---------|
|
|
|
+ * | Card | Card | Endian | DATA3 | Data | Led |
|
|
|
+ * | Detect | Detect | Mode | as Card | Transfer | Control |
|
|
|
+ * | Signal | Test | | Detection | Width | |
|
|
|
+ * | Selection | Level | | Pin | | |
|
|
|
+ * |-----------+--------+--------+-----------+----------+---------|
|
|
|
+ *
|
|
|
+ * and 0x29
|
|
|
+ *
|
|
|
+ * 15 10 9 8
|
|
|
+ * |----------+------|
|
|
|
+ * | Reserved | DMA |
|
|
|
+ * | | Sel. |
|
|
|
+ * | | |
|
|
|
+ * |----------+------|
|
|
|
+ *
|
|
|
+ * and here's what SDCHI spec expects those offsets to be:
|
|
|
+ *
|
|
|
+ * 0x28 (Host Control Register)
|
|
|
+ *
|
|
|
+ * 7 6 5 4 3 2 1 0
|
|
|
+ * |--------+--------+----------+------+--------+----------+---------|
|
|
|
+ * | Card | Card | Extended | DMA | High | Data | LED |
|
|
|
+ * | Detect | Detect | Data | Sel. | Speed | Transfer | Control |
|
|
|
+ * | Signal | Test | Transfer | | Enable | Width | |
|
|
|
+ * | Sel. | Level | Width | | | | |
|
|
|
+ * |--------+--------+----------+------+--------+----------+---------|
|
|
|
+ *
|
|
|
+ * and 0x29 (Power Control Register)
|
|
|
+ *
|
|
|
+ * |----------------------------------|
|
|
|
+ * | Power Control Register |
|
|
|
+ * | |
|
|
|
+ * | Description omitted, |
|
|
|
+ * | since it has no analog in ESDHCI |
|
|
|
+ * | |
|
|
|
+ * |----------------------------------|
|
|
|
+ *
|
|
|
+ * Since offsets 0x2A and 0x2B should be compatible between
|
|
|
+ * both IP specs we only need to reconcile least 16-bit of the
|
|
|
+ * word we've been given.
|
|
|
+ */
|
|
|
+
|
|
|
+ /*
|
|
|
+ * First, save bits 7 6 and 0 since they are identical
|
|
|
+ */
|
|
|
+ hostctl = value & (SDHC_CTRL_LED |
|
|
|
+ SDHC_CTRL_CDTEST_INS |
|
|
|
+ SDHC_CTRL_CDTEST_EN);
|
|
|
+ /*
|
|
|
+ * Second, split "Data Transfer Width" from bits 2 and 1 in to
|
|
|
+ * bits 5 and 1
|
|
|
+ */
|
|
|
+ if (value & ESDHC_CTRL_8BITBUS) {
|
|
|
+ hostctl |= SDHC_CTRL_8BITBUS;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (value & ESDHC_CTRL_4BITBUS) {
|
|
|
+ hostctl |= ESDHC_CTRL_4BITBUS;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Third, move DMA select from bits 9 and 8 to bits 4 and 3
|
|
|
+ */
|
|
|
+ hostctl |= SDHC_DMA_TYPE(value >> (8 - 3));
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Now place the corrected value into low 16-bit of the value
|
|
|
+ * we are going to give standard SDHCI write function
|
|
|
+ *
|
|
|
+ * NOTE: This transformation should be the inverse of what can
|
|
|
+ * be found in drivers/mmc/host/sdhci-esdhc-imx.c in Linux
|
|
|
+ * kernel
|
|
|
+ */
|
|
|
+ value &= ~UINT16_MAX;
|
|
|
+ value |= hostctl;
|
|
|
+ value |= (uint16_t)s->pwrcon << 8;
|
|
|
+
|
|
|
+ sdhci_write(opaque, offset, value, size);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case ESDHC_MIX_CTRL:
|
|
|
+ /*
|
|
|
+ * So, when SD/MMC stack in Linux tries to write to "Transfer
|
|
|
+ * Mode Register", ESDHC i.MX quirk code will translate it
|
|
|
+ * into a write to ESDHC_MIX_CTRL, so we do the opposite in
|
|
|
+ * order to get where we started
|
|
|
+ *
|
|
|
+ * Note that Auto CMD23 Enable bit is located in a wrong place
|
|
|
+ * on i.MX, but since it is not used by QEMU we do not care.
|
|
|
+ *
|
|
|
+ * We don't want to call sdhci_write(.., SDHC_TRNMOD, ...)
|
|
|
+ * here becuase it will result in a call to
|
|
|
+ * sdhci_send_command(s) which we don't want.
|
|
|
+ *
|
|
|
+ */
|
|
|
+ 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;
|
|
|
+ case SDHC_BLKSIZE:
|
|
|
+ /*
|
|
|
+ * ESDHCI does not implement "Host SDMA Buffer Boundary", and
|
|
|
+ * Linux driver will try to zero this field out which will
|
|
|
+ * break the rest of SDHCI emulation.
|
|
|
+ *
|
|
|
+ * Linux defaults to maximum possible setting (512K boundary)
|
|
|
+ * and it seems to be the only option that i.MX IP implements,
|
|
|
+ * so we artificially set it to that value.
|
|
|
+ */
|
|
|
+ val |= 0x7 << 12;
|
|
|
+ /* FALLTHROUGH */
|
|
|
+ default:
|
|
|
+ sdhci_write(opaque, offset, val, size);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static const MemoryRegionOps usdhc_mmio_ops = {
|
|
|
+ .read = usdhc_read,
|
|
|
+ .write = usdhc_write,
|
|
|
+ .valid = {
|
|
|
+ .min_access_size = 1,
|
|
|
+ .max_access_size = 4,
|
|
|
+ .unaligned = false
|
|
|
+ },
|
|
|
+ .endianness = DEVICE_LITTLE_ENDIAN,
|
|
|
+};
|
|
|
+
|
|
|
+static void imx_usdhc_init(Object *obj)
|
|
|
+{
|
|
|
+ SDHCIState *s = SYSBUS_SDHCI(obj);
|
|
|
+
|
|
|
+ s->io_ops = &usdhc_mmio_ops;
|
|
|
+ s->quirks = SDHCI_QUIRK_NO_BUSY_IRQ;
|
|
|
+}
|
|
|
+
|
|
|
+static const TypeInfo imx_usdhc_info = {
|
|
|
+ .name = TYPE_IMX_USDHC,
|
|
|
+ .parent = TYPE_SYSBUS_SDHCI,
|
|
|
+ .instance_init = imx_usdhc_init,
|
|
|
+};
|
|
|
+
|
|
|
static void sdhci_register_types(void)
|
|
|
{
|
|
|
type_register_static(&sdhci_pci_info);
|
|
|
type_register_static(&sdhci_sysbus_info);
|
|
|
type_register_static(&sdhci_bus_info);
|
|
|
+ type_register_static(&imx_usdhc_info);
|
|
|
}
|
|
|
|
|
|
type_init(sdhci_register_types)
|