sdhci: Add i.MX specific subtype of SDHCI
IP block found on several generations of i.MX family does not use vanilla SDHCI implementation and it comes with a number of quirks. Introduce i.MX SDHCI subtype of SDHCI block to add code necessary to support unmodified Linux guest driver. Cc: Peter Maydell <peter.maydell@linaro.org> Cc: Jason Wang <jasowang@redhat.com> Cc: Philippe Mathieu-Daudé <f4bug@amsat.org> Cc: Marcel Apfelbaum <marcel.apfelbaum@zoho.com> Cc: Michael S. Tsirkin <mst@redhat.com> Cc: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org Cc: yurovsky@gmail.com Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com> Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org> [PMM: define and use ESDHC_UNDOCUMENTED_REG27] Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
		
							parent
							
								
									955f56d44a
								
							
						
					
					
						commit
						fd1e5c8179
					
				@ -84,12 +84,18 @@
 | 
			
		||||
 | 
			
		||||
/* R/W Host control Register 0x0 */
 | 
			
		||||
#define SDHC_HOSTCTL                   0x28
 | 
			
		||||
#define SDHC_CTRL_LED                  0x01
 | 
			
		||||
#define SDHC_CTRL_DMA_CHECK_MASK       0x18
 | 
			
		||||
#define SDHC_CTRL_SDMA                 0x00
 | 
			
		||||
#define SDHC_CTRL_ADMA1_32             0x08
 | 
			
		||||
#define SDHC_CTRL_ADMA2_32             0x10
 | 
			
		||||
#define SDHC_CTRL_ADMA2_64             0x18
 | 
			
		||||
#define SDHC_DMA_TYPE(x)               ((x) & SDHC_CTRL_DMA_CHECK_MASK)
 | 
			
		||||
#define SDHC_CTRL_4BITBUS              0x02
 | 
			
		||||
#define SDHC_CTRL_8BITBUS              0x20
 | 
			
		||||
#define SDHC_CTRL_CDTEST_INS           0x40
 | 
			
		||||
#define SDHC_CTRL_CDTEST_EN            0x80
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* R/W Power Control Register 0x0 */
 | 
			
		||||
#define SDHC_PWRCON                    0x29
 | 
			
		||||
@ -226,4 +232,21 @@ enum {
 | 
			
		||||
    sdhc_gap_write  = 2   /* SDHC stopped at block gap during write operation */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern const VMStateDescription sdhci_vmstate;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#define ESDHC_MIX_CTRL                  0x48
 | 
			
		||||
#define ESDHC_VENDOR_SPEC               0xc0
 | 
			
		||||
#define ESDHC_DLL_CTRL                  0x60
 | 
			
		||||
 | 
			
		||||
#define ESDHC_TUNING_CTRL               0xcc
 | 
			
		||||
#define ESDHC_TUNE_CTRL_STATUS          0x68
 | 
			
		||||
#define ESDHC_WTMK_LVL                  0x44
 | 
			
		||||
 | 
			
		||||
/* Undocumented register used by guests working around erratum ERR004536 */
 | 
			
		||||
#define ESDHC_UNDOCUMENTED_REG27        0x6c
 | 
			
		||||
 | 
			
		||||
#define ESDHC_CTRL_4BITBUS              (0x1 << 1)
 | 
			
		||||
#define ESDHC_CTRL_8BITBUS              (0x2 << 1)
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										230
									
								
								hw/sd/sdhci.c
									
									
									
									
									
								
							
							
						
						
									
										230
									
								
								hw/sd/sdhci.c
									
									
									
									
									
								
							@ -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)
 | 
			
		||||
 | 
			
		||||
@ -44,6 +44,7 @@ typedef struct SDHCIState {
 | 
			
		||||
    AddressSpace sysbus_dma_as;
 | 
			
		||||
    AddressSpace *dma_as;
 | 
			
		||||
    MemoryRegion *dma_mr;
 | 
			
		||||
    const MemoryRegionOps *io_ops;
 | 
			
		||||
 | 
			
		||||
    QEMUTimer *insert_timer;       /* timer for 'changing' sd card. */
 | 
			
		||||
    QEMUTimer *transfer_timer;
 | 
			
		||||
@ -91,8 +92,18 @@ typedef struct SDHCIState {
 | 
			
		||||
 | 
			
		||||
    /* Configurable properties */
 | 
			
		||||
    bool pending_insert_quirk; /* Quirk for Raspberry Pi card insert int */
 | 
			
		||||
    uint32_t quirks;
 | 
			
		||||
} SDHCIState;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Controller does not provide transfer-complete interrupt when not
 | 
			
		||||
 * busy.
 | 
			
		||||
 *
 | 
			
		||||
 * NOTE: This definition is taken out of Linux kernel and so the
 | 
			
		||||
 * original bit number is preserved
 | 
			
		||||
 */
 | 
			
		||||
#define SDHCI_QUIRK_NO_BUSY_IRQ    BIT(14)
 | 
			
		||||
 | 
			
		||||
#define TYPE_PCI_SDHCI "sdhci-pci"
 | 
			
		||||
#define PCI_SDHCI(obj) OBJECT_CHECK(SDHCIState, (obj), TYPE_PCI_SDHCI)
 | 
			
		||||
 | 
			
		||||
@ -100,4 +111,6 @@ typedef struct SDHCIState {
 | 
			
		||||
#define SYSBUS_SDHCI(obj)                               \
 | 
			
		||||
     OBJECT_CHECK(SDHCIState, (obj), TYPE_SYSBUS_SDHCI)
 | 
			
		||||
 | 
			
		||||
#define TYPE_IMX_USDHC "imx-usdhc"
 | 
			
		||||
 | 
			
		||||
#endif /* SDHCI_H */
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user