mirror of
https://github.com/xemu-project/xemu.git
synced 2024-11-24 03:59:52 +00:00
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…
Reference in New Issue
Block a user