mmc: sdhci: work around broken dma boundary behavior
authorMikko Vinni <mmvinni@yahoo.com>
Tue, 12 Apr 2011 13:36:18 +0000 (09:36 -0400)
committerChris Ball <cjb@laptop.org>
Wed, 25 May 2011 01:01:29 +0000 (21:01 -0400)
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>
drivers/mmc/host/sdhci.c
drivers/mmc/host/sdhci.h

index ab17344fc7b32012f1bff310501839bb5c2b7d59..2ac0b6887ef7e29c7acc4c4fdc5cb25496e5b1ea 100644 (file)
@@ -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) {
index 25e8bde600d1e839a3b44caf77558ec8a52d381c..85750a94c612ca43a8983d8c8879ef8ec97559ea 100644 (file)
 #define SDHCI_MAX_DIV_SPEC_200 256
 #define SDHCI_MAX_DIV_SPEC_300 2046
 
+/*
+ * Host SDMA buffer boundary. Valid values from 4K to 512K in powers of 2.
+ */
+#define SDHCI_DEFAULT_BOUNDARY_SIZE  (512 * 1024)
+#define SDHCI_DEFAULT_BOUNDARY_ARG   (ilog2(SDHCI_DEFAULT_BOUNDARY_SIZE) - 12)
+
 struct sdhci_ops {
 #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
        u32             (*read_l)(struct sdhci_host *host, int reg);