summaryrefslogtreecommitdiff
path: root/drivers/mmc
diff options
context:
space:
mode:
authorHaijun Zhang <haijun.zhang@freescale.com>2013-04-11 00:21:55 (GMT)
committerFleming Andrew-AFLEMING <AFLEMING@freescale.com>2013-04-15 14:01:46 (GMT)
commit5cdd18bfd1c6e7dff38225ec39831367bfb01439 (patch)
treee9671a409535efcf58c99692157e41932e525ca4 /drivers/mmc
parenteffc77ff7b48fd16ef2b4387e8d24a18cf097ec2 (diff)
downloadlinux-fsl-qoriq-5cdd18bfd1c6e7dff38225ec39831367bfb01439.tar.xz
eSDHC: Workaround for DMA error occured on system transaction
A-004388: eSDHC DMA might not stop if error occurs on system transaction Description: eSDHC DMA(SDMA/ADMA) might not stop if an error occurs in the last system transaction. It may continue initiating additional transactions until software reset for data/all is issued during error recovery. There is not any data corruption to the SD data. The IRQSTAT[DMAE] is set when the erratum occurs. The only conditions under which issues occur are the following: 1. SDMA - For SD Write , the error occurs in the last system transaction. No issue for SD read. 2. ADMA a. Block count is enabled: For SD write, the error occurs in the last system transaction. There is no issue for SD read when block count is enabled. b. Block count is disabled: Block count is designated by the ADMA descriptor table, and the error occurs in the last system transaction when ADMA is executing last descriptor line of table. Impact: eSDHC may initiate additional system transactions. There is no data integrity issue for case 1 and 2a described below. For case 2b, system data might be corrupted. Workaround: Set eSDHC_SYSCTL[RSTD] when IRQSTAT[DMAE] is set. For cases 2a and 2b above, add an extra descriptor line with zero data next to the last descriptor line. Signed-off-by: Haijun Zhang <Haijun.Zhang@freescale.com> Signed-off-by: Jerry Huang <Chang-Ming.Huang@freescale.com> Change-Id: Ic41ab52be5fb3ad2092b035406bd4ef80028e5ad Reviewed-on: http://git.am.freescale.net:8181/917 Tested-by: Fleming Andrew-AFLEMING <AFLEMING@freescale.com> Reviewed-by: Fleming Andrew-AFLEMING <AFLEMING@freescale.com>
Diffstat (limited to 'drivers/mmc')
-rw-r--r--drivers/mmc/host/sdhci-of-esdhc.c94
-rw-r--r--drivers/mmc/host/sdhci.c2
-rw-r--r--drivers/mmc/host/sdhci.h1
3 files changed, 87 insertions, 10 deletions
diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c
index eb3301b..f67ba19 100644
--- a/drivers/mmc/host/sdhci-of-esdhc.c
+++ b/drivers/mmc/host/sdhci-of-esdhc.c
@@ -20,6 +20,7 @@
#include <linux/mmc/host.h>
#include "sdhci-pltfm.h"
#include "sdhci-esdhc.h"
+#include <asm/mpc85xx.h>
#define VENDOR_V_22 0x12
#define VENDOR_V_23 0x13
@@ -180,19 +181,94 @@ static void esdhci_of_adma_workaround(struct sdhci_host *host, u32 intmask)
applicable = (intmask & SDHCI_INT_DATA_END) &&
(intmask & SDHCI_INT_BLK_GAP) &&
(tmp == VENDOR_V_23);
- if (!applicable)
+ if (applicable) {
+
+ sdhci_reset(host, SDHCI_RESET_DATA);
+ host->data->error = 0;
+ 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;
+ sdhci_writel(host, dmanow, SDHCI_DMA_ADDRESS);
+
return;
+ }
- host->data->error = 0;
- dmastart = sg_dma_address(host->data->sg);
- dmanow = dmastart + host->data->bytes_xfered;
/*
- * Force update to the next DMA block boundary.
+ * Check for A-004388: eSDHC DMA might not stop if error
+ * occurs on system transaction
+ * Impact list:
+ * T4240-R1.0 B4860-R1.0 P1010-R1.0
*/
- dmanow = (dmanow & ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1)) +
- SDHCI_DEFAULT_BOUNDARY_SIZE;
- host->data->bytes_xfered = dmanow - dmastart;
- sdhci_writel(host, dmanow, SDHCI_DMA_ADDRESS);
+ if (!((fsl_svr_is(SVR_T4240) && fsl_svr_rev_is(1, 0)) ||
+ (fsl_svr_is(SVR_B4860) && fsl_svr_rev_is(1, 0)) ||
+ (fsl_svr_is(SVR_P1010) && fsl_svr_rev_is(1, 0))))
+ return;
+
+ if (host->flags & SDHCI_USE_ADMA) {
+ u32 mod, i, offset;
+ u8 *desc;
+ dma_addr_t addr;
+ struct scatterlist *sg;
+
+ mod = esdhc_readl(host, SDHCI_TRANSFER_MODE);
+ if (mod & SDHCI_TRNS_BLK_CNT_EN) {
+ /* In case read transfer there is no data
+ * was corrupted
+ */
+ if (host->data->flags & MMC_DATA_READ)
+ return;
+ host->data->error = 0;
+ sdhci_reset(host, SDHCI_RESET_DATA);
+ }
+
+ sdhci_reset(host, SDHCI_RESET_DATA);
+
+ BUG_ON(!host->data);
+ desc = host->adma_desc;
+ for_each_sg(host->data->sg, sg, host->sg_count, i) {
+ addr = sg_dma_address(sg);
+ offset = (4 - (addr & 0x3)) & 0x3;
+ if (offset)
+ desc += 8;
+ desc += 8;
+ }
+
+ /*
+ * Add an extra zero descriptor next to the
+ * terminating descriptor.
+ */
+ desc += 8;
+ WARN_ON((desc - host->adma_desc) > (128 * 2 + 1) * 4);
+
+ if (host->quirks & SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC) {
+ desc -= 8;
+ desc[0] |= 0x2; /* end */
+ } else {
+ __le32 *dataddr = (__le32 __force *)(desc + 4);
+ __le16 *cmdlen = (__le16 __force *)desc;
+
+ cmdlen[0] = cpu_to_le16(0x3);
+ cmdlen[1] = cpu_to_le16(0);
+
+ dataddr[0] = cpu_to_le32(0);
+ }
+
+ return;
+ }
+
+ if ((host->flags & SDHCI_USE_SDMA)) {
+ if (host->data->flags & MMC_DATA_READ)
+ return;
+
+ host->data->error = 0;
+ sdhci_reset(host, SDHCI_RESET_DATA);
+ return;
+ }
}
static int esdhc_of_enable_dma(struct sdhci_host *host)
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 4f67f21..644b4fc 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -172,7 +172,7 @@ static void sdhci_disable_card_detection(struct sdhci_host *host)
sdhci_set_card_detection(host, false);
}
-static void sdhci_reset(struct sdhci_host *host, u8 mask)
+void sdhci_reset(struct sdhci_host *host, u8 mask)
{
unsigned long timeout;
u32 uninitialized_var(ier);
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 59551f7..68a7c57 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -379,6 +379,7 @@ static inline void *sdhci_priv(struct sdhci_host *host)
return (void *)host->private;
}
+extern void sdhci_reset(struct sdhci_host *host, u8 mask);
extern void sdhci_card_detect(struct sdhci_host *host);
extern int sdhci_add_host(struct sdhci_host *host);
extern void sdhci_remove_host(struct sdhci_host *host, int dead);