The common SDHCI core pauses SDMA at the selected buffer boundary and waits for software to rewrite the system address. Freescale eSDHC and i.MX uSDHC expose BLKATTR without that programmable boundary field.
T4240 eSDHC transfers the complete block-count times block-size range from DSADDR, and DINT reports successful DMA completion. Its BLKSIZE is 12 bits and bits 15:12 are reserved. i.MX6UL uSDHC also completes the contiguous transfer, but uses a 13-bit block-size field with bits 15:13 reserved. Neither needs the synthetic 512 KiB boundary pause. Enable the no-boundary quirk for both eSDHC byte orders and uSDHC. Remove the artificial boundary encoding in their shared translation. Keep the standard SDHCI boundary handshake, block-gap stops and DMA error handling unchanged. References: T4240RM (Rev. 4), sections 15.3.2, 15.3.13 and 15.4.1.3 https://www.nxp.com/webapp/Download?colCode=T4240RM IMX6ULRM (Rev. 2), sections 56.8.2 and 56.8.13 https://www.nxp.com/webapp/Download?colCode=IMX6ULRM Signed-off-by: Bin Meng <[email protected]> Reviewed-by: Bernhard Beschow <[email protected]> --- Changes in v3: - Enable the no-boundary quirk for both eSDHC byte orders - Remove the synthetic 512 KiB boundary field translation include/hw/sd/sdhci.h | 5 +++++ hw/sd/sdhci.c | 42 +++++++++++++++++++++--------------------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/include/hw/sd/sdhci.h b/include/hw/sd/sdhci.h index c5297310f6..7bd39bf61d 100644 --- a/include/hw/sd/sdhci.h +++ b/include/hw/sd/sdhci.h @@ -113,6 +113,11 @@ typedef struct SDHCIState SDHCIState; * busy. */ #define SDHCI_QUIRK_NO_BUSY_IRQ BIT(0) +/* + * Controller completes contiguous SDMA transfers without the standard + * intermediate buffer-boundary stop and address-update handshake + */ +#define SDHCI_QUIRK_NO_SDMA_BOUNDARY BIT(1) #define TYPE_PCI_SDHCI "sdhci-pci" DECLARE_INSTANCE_CHECKER(SDHCIState, PCI_SDHCI, diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index 3bcad3e518..6289176628 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -687,12 +687,14 @@ static void sdhci_advance_sdma_address(SDHCIState *s, uint32_t bytes) /* Multi block SDMA transfer */ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) { + bool boundary_enabled = + !(s->quirks & SDHCI_QUIRK_NO_SDMA_BOUNDARY); bool page_aligned = false; unsigned int begin; const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK; - uint32_t boundary_chk = 1 << (((s->blksize & ~BLOCK_SIZE_MASK) >> 12) + 12); + uint32_t boundary_chk = 0; uint64_t sdma_address = sdhci_sdma_address(s); - uint32_t boundary_count = boundary_chk - (sdma_address % boundary_chk); + uint32_t boundary_count = 0; if (!(s->trnmod & SDHC_TRNS_BLK_CNT_EN) || !s->blkcnt) { qemu_log_mask(LOG_UNIMP, "infinite transfer is not supported\n"); @@ -704,8 +706,11 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) * possible stop at page boundary if initial address is not page aligned, * allow them to work properly */ - if ((sdma_address % boundary_chk) == 0) { - page_aligned = true; + if (boundary_enabled) { + boundary_chk = + 1 << (((s->blksize & ~BLOCK_SIZE_MASK) >> 12) + 12); + boundary_count = boundary_chk - (sdma_address % boundary_chk); + page_aligned = (sdma_address % boundary_chk) == 0; } s->prnsts |= SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE; @@ -721,7 +726,9 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) boundary_count = 0; } else { s->data_count = block_size; - boundary_count -= block_size - begin; + if (boundary_enabled) { + boundary_count -= block_size - begin; + } if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { s->blkcnt--; } @@ -746,7 +753,9 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) boundary_count = 0; } else { s->data_count = block_size; - boundary_count -= block_size - begin; + if (boundary_enabled) { + boundary_count -= block_size - begin; + } } dma_memory_read(s->dma_as, sdhci_sdma_address(s), &s->fifo_buffer[begin], @@ -2017,18 +2026,6 @@ esdhc_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) sdhci_write(opaque, offset, value, 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; @@ -2056,7 +2053,8 @@ static void fsl_esdhc_be_init(Object *obj) DeviceState *dev = DEVICE(obj); s->io_ops = &esdhc_mmio_be_ops; - s->quirks = SDHCI_QUIRK_NO_BUSY_IRQ; + s->quirks = SDHCI_QUIRK_NO_BUSY_IRQ | + SDHCI_QUIRK_NO_SDMA_BOUNDARY; qdev_prop_set_uint8(dev, "sd-spec-version", 2); } @@ -2081,7 +2079,8 @@ static void fsl_esdhc_le_init(Object *obj) DeviceState *dev = DEVICE(obj); s->io_ops = &esdhc_mmio_le_ops; - s->quirks = SDHCI_QUIRK_NO_BUSY_IRQ; + s->quirks = SDHCI_QUIRK_NO_BUSY_IRQ | + SDHCI_QUIRK_NO_SDMA_BOUNDARY; qdev_prop_set_uint8(dev, "sd-spec-version", 2); } @@ -2203,7 +2202,8 @@ static void imx_usdhc_init(Object *obj) DeviceState *dev = DEVICE(obj); s->io_ops = &usdhc_mmio_ops; - s->quirks = SDHCI_QUIRK_NO_BUSY_IRQ; + s->quirks = SDHCI_QUIRK_NO_BUSY_IRQ | + SDHCI_QUIRK_NO_SDMA_BOUNDARY; qdev_prop_set_uint8(dev, "sd-spec-version", 3); } -- 2.53.0
