Message ID | 20171211213007.7353-13-andrew.smirnov@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On 11 December 2017 at 21:30, Andrey Smirnov <andrew.smirnov@gmail.com> 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 <peter.maydell@linaro.org> > Cc: Jason Wang <jasowang@redhat.com> > Cc: Philippe Mathieu-Daudé <f4bug@amsat.org> > Cc: qemu-devel@nongnu.org > Cc: qemu-arm@nongnu.org > Cc: yurovsky@gmail.com > Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com> > --- > + case ESDHC_DLL_CTRL: > + case ESDHC_TUNE_CTRL_STATUS: > + case 0x6c: Isn't there a name we can give 0x6c ? > + 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_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. > + */ Thanks for this explanation, it's very helpful in figuring out what's going on. > + 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 */ We generally write this lowercase: /* fallthrough */ > + default: > + sdhci_write(opaque, offset, val, size); > + break; > + } > +} > diff --git a/include/hw/sd/sdhci.h b/include/hw/sd/sdhci.h > index 0f0c3f1e64..dc1856a33d 100644 > --- a/include/hw/sd/sdhci.h > +++ b/include/hw/sd/sdhci.h > @@ -39,6 +39,7 @@ typedef struct SDHCIState { > }; > SDBus sdbus; > MemoryRegion iomem; > + const MemoryRegionOps *io_ops; > > QEMUTimer *insert_timer; /* timer for 'changing' sd card. */ > QEMUTimer *transfer_timer; > @@ -83,8 +84,13 @@ typedef struct SDHCIState { > /* Force Event Auto CMD12 Error Interrupt Reg - write only */ > /* Force Event Error Interrupt Register- write only */ > /* RO Host Controller Version Register always reads as 0x2401 */ > + > + unsigned long quirks; I asked for this not to be unsigned long in the last round of review. > } SDHCIState; > > +/* Controller does not provide transfer-complete interrupt when not busy */ > +#define SDHCI_QUIRK_NO_BUSY_IRQ BIT(14) I asked for a comment saying we're following the Linux kernel's quirk bit numbering in the last round of review. > + > #define TYPE_PCI_SDHCI "sdhci-pci" > #define PCI_SDHCI(obj) OBJECT_CHECK(SDHCIState, (obj), TYPE_PCI_SDHCI) > > @@ -92,4 +98,6 @@ typedef struct SDHCIState { > #define SYSBUS_SDHCI(obj) \ > OBJECT_CHECK(SDHCIState, (obj), TYPE_SYSBUS_SDHCI) > > +#define TYPE_IMX_USDHC "imx-usdhc" > + > #endif /* SDHCI_H */ > -- > 2.14.3 Otherwise Reviewed-by: Peter Maydell <peter.maydell@linaro.org> thanks -- PMM
On Tue, Dec 12, 2017 at 9:52 AM, Peter Maydell <peter.maydell@linaro.org> wrote: > On 11 December 2017 at 21:30, Andrey Smirnov <andrew.smirnov@gmail.com> 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 <peter.maydell@linaro.org> >> Cc: Jason Wang <jasowang@redhat.com> >> Cc: Philippe Mathieu-Daudé <f4bug@amsat.org> >> Cc: qemu-devel@nongnu.org >> Cc: qemu-arm@nongnu.org >> Cc: yurovsky@gmail.com >> Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com> >> --- > >> + case ESDHC_DLL_CTRL: >> + case ESDHC_TUNE_CTRL_STATUS: >> + case 0x6c: > > Isn't there a name we can give 0x6c ? > Unfortunately, not that I know of. It's a mystery register not listed in RM and the only place I can found it being mentioned is in Linux driver as a part of errata ESDHC_FLAG_ERR004536 fix, where it is used nameless as well. >> + 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_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. >> + */ > > Thanks for this explanation, it's very helpful in figuring out what's > going on. > >> + 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 */ > > We generally write this lowercase: /* fallthrough */ > OK, will fix in v2. >> + default: >> + sdhci_write(opaque, offset, val, size); >> + break; >> + } >> +} > >> diff --git a/include/hw/sd/sdhci.h b/include/hw/sd/sdhci.h >> index 0f0c3f1e64..dc1856a33d 100644 >> --- a/include/hw/sd/sdhci.h >> +++ b/include/hw/sd/sdhci.h >> @@ -39,6 +39,7 @@ typedef struct SDHCIState { >> }; >> SDBus sdbus; >> MemoryRegion iomem; >> + const MemoryRegionOps *io_ops; >> >> QEMUTimer *insert_timer; /* timer for 'changing' sd card. */ >> QEMUTimer *transfer_timer; >> @@ -83,8 +84,13 @@ typedef struct SDHCIState { >> /* Force Event Auto CMD12 Error Interrupt Reg - write only */ >> /* Force Event Error Interrupt Register- write only */ >> /* RO Host Controller Version Register always reads as 0x2401 */ >> + >> + unsigned long quirks; > > I asked for this not to be unsigned long in the last round of review. > Ugh, missed this one, sorry about that. Will fix in v2. >> } SDHCIState; >> >> +/* Controller does not provide transfer-complete interrupt when not busy */ >> +#define SDHCI_QUIRK_NO_BUSY_IRQ BIT(14) > > I asked for a comment saying we're following the Linux kernel's > quirk bit numbering in the last round of review. > My bad, will fix in v 2. Thanks, Andrey Smirnov
Hi Andrey, On 12/14/2017 11:03 AM, Andrey Smirnov wrote: > On Tue, Dec 12, 2017 at 9:52 AM, Peter Maydell <peter.maydell@linaro.org> wrote: >> On 11 December 2017 at 21:30, Andrey Smirnov <andrew.smirnov@gmail.com> wrote: [...] >>> + case ESDHC_DLL_CTRL: >>> + case ESDHC_TUNE_CTRL_STATUS: >>> + case 0x6c: >> >> Isn't there a name we can give 0x6c ? >> > > Unfortunately, not that I know of. It's a mystery register not listed > in RM and the only place I can found it being mentioned is in Linux > driver as a part of errata ESDHC_FLAG_ERR004536 fix, where it is used > nameless as well. This sets the SD CLK/RCLK frequency (10-bit) for the 104MB/sec bus speed (UHS-I mode). The "Sampling Clock Tuning Procedure" figure in the Spec v3 is helpful. In my tree (where I work on UHS-I/II) I have: #define SDHC_PRESET_SDR104 0x6c FIELD(SDHC_PRESET_SDR104, CLKFREQ, 0, 10); /* since v3 */ FIELD(SDHC_PRESET_SDR104, CLKGEN, 10, 1); /* since v3 */ FIELD(SDHC_PRESET_SDR104, DRIVE_STRENGTH, 14, 2); /* since v3 (UHS-I only) */ Regards, Phil.
On Thu, Dec 14, 2017 at 7:32 AM, Philippe Mathieu-Daudé <f4bug@amsat.org> wrote: > Hi Andrey, > > On 12/14/2017 11:03 AM, Andrey Smirnov wrote: >> On Tue, Dec 12, 2017 at 9:52 AM, Peter Maydell <peter.maydell@linaro.org> wrote: >>> On 11 December 2017 at 21:30, Andrey Smirnov <andrew.smirnov@gmail.com> wrote: > [...] >>>> + case ESDHC_DLL_CTRL: >>>> + case ESDHC_TUNE_CTRL_STATUS: >>>> + case 0x6c: >>> >>> Isn't there a name we can give 0x6c ? >>> >> >> Unfortunately, not that I know of. It's a mystery register not listed >> in RM and the only place I can found it being mentioned is in Linux >> driver as a part of errata ESDHC_FLAG_ERR004536 fix, where it is used >> nameless as well. > > This sets the SD CLK/RCLK frequency (10-bit) for the 104MB/sec bus speed > (UHS-I mode). > > The "Sampling Clock Tuning Procedure" figure in the Spec v3 is helpful. > I am a bit hesitant to agree that this is indeed the case. ERR004536 errata's title is "uSDHC: ADMA Length Mismatch Error may occur for longer read latencies" and recommented workaround is "Use SDMA (or ADMA1) in case the AHB latency is larger than the “minimal time for one block”. On top of that corresponding code in Linux (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/mmc/host/sdhci-esdhc-imx.c?h=v4.15-rc3#n1061) doesn't seem to use it as a 10-bit frequency field, treating it more like a single-bit flag. Thanks, Andrey Smirnov
On Thu, Dec 14, 2017 at 1:05 PM, Andrey Smirnov <andrew.smirnov@gmail.com> wrote: > On Thu, Dec 14, 2017 at 7:32 AM, Philippe Mathieu-Daudé <f4bug@amsat.org> wrote: >> [...] >>>>> + case ESDHC_DLL_CTRL: >>>>> + case ESDHC_TUNE_CTRL_STATUS: >>>>> + case 0x6c: >>>> >>>> Isn't there a name we can give 0x6c ? >>>> >>> >>> Unfortunately, not that I know of. It's a mystery register not listed >>> in RM and the only place I can found it being mentioned is in Linux >>> driver as a part of errata ESDHC_FLAG_ERR004536 fix, where it is used >>> nameless as well. >> >> This sets the SD CLK/RCLK frequency (10-bit) for the 104MB/sec bus speed >> (UHS-I mode). >> >> The "Sampling Clock Tuning Procedure" figure in the Spec v3 is helpful. >> > > I am a bit hesitant to agree that this is indeed the case. ERR004536 > errata's title is "uSDHC: ADMA Length Mismatch Error may occur for > longer read latencies" and recommented workaround is "Use SDMA (or > ADMA1) in case the AHB latency is larger than the “minimal time for > one block”. On top of that corresponding code in Linux > (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/mmc/host/sdhci-esdhc-imx.c?h=v4.15-rc3#n1061) > doesn't seem to use it as a 10-bit frequency field, treating it more > like a single-bit flag. Ok, I'll have a better look when applying these SD patches on top of my current SD tree. Regards, Phil.
diff --git a/hw/sd/sdhci-internal.h b/hw/sd/sdhci-internal.h index 161177cf39..b86ac0791b 100644 --- a/hw/sd/sdhci-internal.h +++ b/hw/sd/sdhci-internal.h @@ -85,12 +85,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 @@ -229,4 +235,17 @@ enum { 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) + + #endif diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index 6d6a791ee9..758af067f9 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -265,7 +265,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; } @@ -1191,6 +1192,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) @@ -1347,7 +1350,7 @@ static void sdhci_sysbus_realize(DeviceState *dev, Error ** errp) s->buf_maxsz = sdhci_get_fifolen(s); s->fifo_buffer = g_malloc0(s->buf_maxsz); sysbus_init_irq(sbd, &s->irq); - memory_region_init_io(&s->iomem, OBJECT(s), &sdhci_mmio_ops, s, "sdhci", + memory_region_init_io(&s->iomem, OBJECT(s), s->io_ops, s, "sdhci", SDHC_REGISTERS_MAP_SIZE); sysbus_init_mmio(sbd, &s->iomem); } @@ -1386,11 +1389,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 0x6c: + 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_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) diff --git a/include/hw/sd/sdhci.h b/include/hw/sd/sdhci.h index 0f0c3f1e64..dc1856a33d 100644 --- a/include/hw/sd/sdhci.h +++ b/include/hw/sd/sdhci.h @@ -39,6 +39,7 @@ typedef struct SDHCIState { }; SDBus sdbus; MemoryRegion iomem; + const MemoryRegionOps *io_ops; QEMUTimer *insert_timer; /* timer for 'changing' sd card. */ QEMUTimer *transfer_timer; @@ -83,8 +84,13 @@ typedef struct SDHCIState { /* Force Event Auto CMD12 Error Interrupt Reg - write only */ /* Force Event Error Interrupt Register- write only */ /* RO Host Controller Version Register always reads as 0x2401 */ + + unsigned long quirks; } SDHCIState; +/* Controller does not provide transfer-complete interrupt when not busy */ +#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) @@ -92,4 +98,6 @@ typedef struct SDHCIState { #define SYSBUS_SDHCI(obj) \ OBJECT_CHECK(SDHCIState, (obj), TYPE_SYSBUS_SDHCI) +#define TYPE_IMX_USDHC "imx-usdhc" + #endif /* SDHCI_H */
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: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org Cc: yurovsky@gmail.com Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com> --- hw/sd/sdhci-internal.h | 19 +++++ hw/sd/sdhci.c | 228 ++++++++++++++++++++++++++++++++++++++++++++++++- include/hw/sd/sdhci.h | 8 ++ 3 files changed, 253 insertions(+), 2 deletions(-)