summaryrefslogtreecommitdiffstats
path: root/drivers/mmc/host/sdhci.c
diff options
context:
space:
mode:
authorMikko Vinni <mmvinni@yahoo.com>2011-04-12 15:36:18 +0200
committerChris Ball <cjb@laptop.org>2011-05-25 03:01:29 +0200
commitf6a03cbf43e586211f8ea088148c8ecd3fc4b5be (patch)
tree6b3c34b585d7412cf1c82d0eb33b389ea0fa8e0f /drivers/mmc/host/sdhci.c
parentmmc: quirks: Support for block quirks. (diff)
downloadlinux-f6a03cbf43e586211f8ea088148c8ecd3fc4b5be.tar.xz
linux-f6a03cbf43e586211f8ea088148c8ecd3fc4b5be.zip
mmc: sdhci: work around broken dma boundary behavior
Some SD host controllers (noticed on an integrated JMicron SD reader on an HP Pavilion dv5-1250eo laptop) don't update the dma address register before signaling a dma interrupt due to a dma boundary. Update the register manually to the next boundary (by default 512KiB), at which the transfer stopped. As long as each transfer is at most 512KiB in size (guaranteed by a BUG_ON in sdhci_prepare_data()) and the boundary is kept at the default value, this fix is needed at most once per transfer. Smaller boundaries are taken care of by counting the transferred bytes. Fixes: https://bugzilla.kernel.org/show_bug.cgi?id=28462 Signed-off-by: Mikko Vinni <mmvinni@yahoo.com> Signed-off-by: Chris Ball <cjb@laptop.org>
Diffstat (limited to 'drivers/mmc/host/sdhci.c')
-rw-r--r--drivers/mmc/host/sdhci.c30
1 files changed, 25 insertions, 5 deletions
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index ab17344fc7b3..2ac0b6887ef7 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -679,6 +679,7 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
host->data = data;
host->data_early = 0;
+ host->data->bytes_xfered = 0;
if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA))
host->flags |= SDHCI_REQ_USE_DMA;
@@ -814,8 +815,9 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
sdhci_set_transfer_irqs(host);
- /* We do not handle DMA boundaries, so set it to max (512 KiB) */
- sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, data->blksz), SDHCI_BLOCK_SIZE);
+ /* Set the DMA boundary value and block size */
+ sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG,
+ data->blksz), SDHCI_BLOCK_SIZE);
sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT);
}
@@ -1558,10 +1560,28 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
* We currently don't do anything fancy with DMA
* boundaries, but as we can't disable the feature
* we need to at least restart the transfer.
+ *
+ * According to the spec sdhci_readl(host, SDHCI_DMA_ADDRESS)
+ * should return a valid address to continue from, but as
+ * some controllers are faulty, don't trust them.
*/
- if (intmask & SDHCI_INT_DMA_END)
- sdhci_writel(host, sdhci_readl(host, SDHCI_DMA_ADDRESS),
- SDHCI_DMA_ADDRESS);
+ if (intmask & SDHCI_INT_DMA_END) {
+ u32 dmastart, dmanow;
+ dmastart = sg_dma_address(host->data->sg);
+ dmanow = dmastart + host->data->bytes_xfered;
+ /*
+ * Force update to the next DMA block boundary.
+ */
+ dmanow = (dmanow &
+ ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1)) +
+ SDHCI_DEFAULT_BOUNDARY_SIZE;
+ host->data->bytes_xfered = dmanow - dmastart;
+ DBG("%s: DMA base 0x%08x, transferred 0x%06x bytes,"
+ " next 0x%08x\n",
+ mmc_hostname(host->mmc), dmastart,
+ host->data->bytes_xfered, dmanow);
+ sdhci_writel(host, dmanow, SDHCI_DMA_ADDRESS);
+ }
if (intmask & SDHCI_INT_DATA_END) {
if (host->cmd) {