[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-devel] [Qemu-arm] [PATCH v5 01/14] sdhci: Add i.MX specific su
From: |
Philippe Mathieu-Daudé |
Subject: |
Re: [Qemu-devel] [Qemu-arm] [PATCH v5 01/14] sdhci: Add i.MX specific subtype of SDHCI |
Date: |
Wed, 7 Feb 2018 11:41:05 -0300 |
User-agent: |
Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.5.2 |
Hi Andrey,
On 02/07/2018 01:24 AM, Andrey Smirnov wrote:
> 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 <address@hidden>
> Cc: Jason Wang <address@hidden>
> Cc: Philippe Mathieu-Daudé <address@hidden>
> Cc: Marcel Apfelbaum <address@hidden>
> Cc: Michael S. Tsirkin <address@hidden>
> Cc: address@hidden
> Cc: address@hidden
> Cc: address@hidden
> Reviewed-by: Peter Maydell <address@hidden>
> Signed-off-by: Andrey Smirnov <address@hidden>
> ---
> hw/sd/sdhci-internal.h | 20 +++++
> hw/sd/sdhci.c | 230
> ++++++++++++++++++++++++++++++++++++++++++++++++-
> include/hw/sd/sdhci.h | 13 +++
> 3 files changed, 262 insertions(+), 1 deletion(-)
>
> diff --git a/hw/sd/sdhci-internal.h b/hw/sd/sdhci-internal.h
> index fc807f08f3..f91b73af59 100644
> --- a/hw/sd/sdhci-internal.h
> +++ b/hw/sd/sdhci-internal.h
> @@ -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,18 @@ 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
> +
> +#define ESDHC_CTRL_4BITBUS (0x1 << 1)
> +#define ESDHC_CTRL_8BITBUS (0x2 << 1)
> +
You can add:
#define ESDHC_UNDOCUMENTED_REG27 0x6c /* errata ERR004536 */
So following 0x6c uses are self-documented.
> #endif
> diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c
> index fac7fa5c72..7c9683b47d 100644
> --- a/hw/sd/sdhci.c
> +++ b/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);
'default' last case feels more natural.
> +
> + 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 0x6c:
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 0x6c:
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:
'default' last case :)
> + 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)
> diff --git a/include/hw/sd/sdhci.h b/include/hw/sd/sdhci.h
> index 1cf70f8c23..f8d1ba3538 100644
> --- a/include/hw/sd/sdhci.h
> +++ b/include/hw/sd/sdhci.h
> @@ -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 */
>
Whether using ESDHC_UNDOCUMENTED_REG27 or not:
Reviewed-by: Philippe Mathieu-Daudé <address@hidden>
- [Qemu-devel] [PATCH v5 00/14] Initial i.MX7 support, Andrey Smirnov, 2018/02/06
- [Qemu-devel] [PATCH v5 02/14] hw: i.MX: Convert i.MX6 to use TYPE_IMX_USDHC, Andrey Smirnov, 2018/02/06
- [Qemu-devel] [PATCH v5 01/14] sdhci: Add i.MX specific subtype of SDHCI, Andrey Smirnov, 2018/02/06
- Re: [Qemu-devel] [Qemu-arm] [PATCH v5 01/14] sdhci: Add i.MX specific subtype of SDHCI,
Philippe Mathieu-Daudé <=
- [Qemu-devel] [PATCH v5 04/14] i.MX: Add code to emulate i.MX2 watchdog IP block, Andrey Smirnov, 2018/02/06
- [Qemu-devel] [PATCH v5 03/14] i.MX: Add code to emulate i.MX7 CCM, PMU and ANALOG IP blocks, Andrey Smirnov, 2018/02/06
- [Qemu-devel] [PATCH v5 05/14] i.MX: Add code to emulate i.MX7 SNVS IP-block, Andrey Smirnov, 2018/02/06
- [Qemu-devel] [PATCH v5 07/14] i.MX: Add i.MX7 GPT variant, Andrey Smirnov, 2018/02/06
- [Qemu-devel] [PATCH v5 08/14] i.MX: Add implementation of i.MX7 GPR IP block, Andrey Smirnov, 2018/02/06
- [Qemu-devel] [PATCH v5 06/14] i.MX: Add code to emulate GPCv2 IP block, Andrey Smirnov, 2018/02/06
- [Qemu-devel] [PATCH v5 09/14] pci: Use pci_config_size in pci_data_* accessors, Andrey Smirnov, 2018/02/06