diff options
author | Haijun Zhang <haijun.zhang@freescale.com> | 2013-04-11 00:21:55 (GMT) |
---|---|---|
committer | Fleming Andrew-AFLEMING <AFLEMING@freescale.com> | 2013-04-15 14:01:46 (GMT) |
commit | 5cdd18bfd1c6e7dff38225ec39831367bfb01439 (patch) | |
tree | e9671a409535efcf58c99692157e41932e525ca4 /drivers/mmc | |
parent | effc77ff7b48fd16ef2b4387e8d24a18cf097ec2 (diff) | |
download | linux-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.c | 94 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.c | 2 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.h | 1 |
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); |