summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-09-06 06:41:58 +0400
committerLinus Torvalds <torvalds@linux-foundation.org>2012-09-06 06:41:58 +0400
commit080909503664641432cc8adf2ee2084775fd992a (patch)
tree115dc3c1c3ac5772dfd3c8b88c66c198cd84eb9e
parentbc6c83641e1df61cff67748987f95ca62953565e (diff)
parent75b53aee2f4fe6375c6007226bf68d75b5c4a929 (diff)
downloadlinux-080909503664641432cc8adf2ee2084775fd992a.tar.xz
Merge tag 'mmc-fixes-for-3.6-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/cjb/mmc
Pull MMC fixes from Chris Ball: - a firmware bug on several Samsung MoviNAND eMMC models causes permanent corruption on the device when secure erase and secure trim requests are made, so we disable those requests on these eMMC devices. - atmel-mci: fix a hang with some SD cards by waiting for not-busy flag. - dw_mmc: low-power mode breaks SDIO interrupts; fix PIO error handling; fix handling of error interrupts. - mxs-mmc: fix deadlocks; fix compile error due to dma.h arch change. - omap: fix broken PIO mode causing memory corruption. - sdhci-esdhc: fix card detection. * tag 'mmc-fixes-for-3.6-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/cjb/mmc: mmc: omap: fix broken PIO mode mmc: card: Skip secure erase on MoviNAND; causes unrecoverable corruption. mmc: dw_mmc: Disable low power mode if SDIO interrupts are used mmc: dw_mmc: fix error handling in PIO mode mmc: dw_mmc: correct mishandling error interrupt mmc: dw_mmc: amend using error interrupt status mmc: atmel-mci: not busy flag has also to be used for read operations mmc: sdhci-esdhc: break out early if clock is 0 mmc: mxs-mmc: fix deadlock caused by recursion loop mmc: mxs-mmc: fix deadlock in SDIO IRQ case mmc: bfin_sdh: fix dma_desc_array build error
-rw-r--r--drivers/mmc/card/block.c26
-rw-r--r--drivers/mmc/host/atmel-mci.c6
-rw-r--r--drivers/mmc/host/bfin_sdh.c7
-rw-r--r--drivers/mmc/host/dw_mmc.c85
-rw-r--r--drivers/mmc/host/mxs-mmc.c14
-rw-r--r--drivers/mmc/host/omap.c14
-rw-r--r--drivers/mmc/host/sdhci-esdhc.h6
-rw-r--r--include/linux/mmc/card.h1
8 files changed, 98 insertions, 61 deletions
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
index f1c84decb192..172a768036d8 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -1411,7 +1411,8 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
/* complete ongoing async transfer before issuing discard */
if (card->host->areq)
mmc_blk_issue_rw_rq(mq, NULL);
- if (req->cmd_flags & REQ_SECURE)
+ if (req->cmd_flags & REQ_SECURE &&
+ !(card->quirks & MMC_QUIRK_SEC_ERASE_TRIM_BROKEN))
ret = mmc_blk_issue_secdiscard_rq(mq, req);
else
ret = mmc_blk_issue_discard_rq(mq, req);
@@ -1716,6 +1717,7 @@ force_ro_fail:
#define CID_MANFID_SANDISK 0x2
#define CID_MANFID_TOSHIBA 0x11
#define CID_MANFID_MICRON 0x13
+#define CID_MANFID_SAMSUNG 0x15
static const struct mmc_fixup blk_fixups[] =
{
@@ -1752,6 +1754,28 @@ static const struct mmc_fixup blk_fixups[] =
MMC_FIXUP(CID_NAME_ANY, CID_MANFID_MICRON, 0x200, add_quirk_mmc,
MMC_QUIRK_LONG_READ_TIME),
+ /*
+ * On these Samsung MoviNAND parts, performing secure erase or
+ * secure trim can result in unrecoverable corruption due to a
+ * firmware bug.
+ */
+ MMC_FIXUP("M8G2FA", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc,
+ MMC_QUIRK_SEC_ERASE_TRIM_BROKEN),
+ MMC_FIXUP("MAG4FA", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc,
+ MMC_QUIRK_SEC_ERASE_TRIM_BROKEN),
+ MMC_FIXUP("MBG8FA", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc,
+ MMC_QUIRK_SEC_ERASE_TRIM_BROKEN),
+ MMC_FIXUP("MCGAFA", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc,
+ MMC_QUIRK_SEC_ERASE_TRIM_BROKEN),
+ MMC_FIXUP("VAL00M", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc,
+ MMC_QUIRK_SEC_ERASE_TRIM_BROKEN),
+ MMC_FIXUP("VYL00M", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc,
+ MMC_QUIRK_SEC_ERASE_TRIM_BROKEN),
+ MMC_FIXUP("KYL00M", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc,
+ MMC_QUIRK_SEC_ERASE_TRIM_BROKEN),
+ MMC_FIXUP("VZL00M", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc,
+ MMC_QUIRK_SEC_ERASE_TRIM_BROKEN),
+
END_FIXUP
};
diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c
index 322412cec4ee..a53c7c478e05 100644
--- a/drivers/mmc/host/atmel-mci.c
+++ b/drivers/mmc/host/atmel-mci.c
@@ -81,6 +81,7 @@ struct atmel_mci_caps {
bool has_bad_data_ordering;
bool need_reset_after_xfer;
bool need_blksz_mul_4;
+ bool need_notbusy_for_read_ops;
};
struct atmel_mci_dma {
@@ -1625,7 +1626,8 @@ static void atmci_tasklet_func(unsigned long priv)
__func__);
atmci_set_completed(host, EVENT_XFER_COMPLETE);
- if (host->data->flags & MMC_DATA_WRITE) {
+ if (host->caps.need_notbusy_for_read_ops ||
+ (host->data->flags & MMC_DATA_WRITE)) {
atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY);
state = STATE_WAITING_NOTBUSY;
} else if (host->mrq->stop) {
@@ -2218,6 +2220,7 @@ static void __init atmci_get_cap(struct atmel_mci *host)
host->caps.has_bad_data_ordering = 1;
host->caps.need_reset_after_xfer = 1;
host->caps.need_blksz_mul_4 = 1;
+ host->caps.need_notbusy_for_read_ops = 0;
/* keep only major version number */
switch (version & 0xf00) {
@@ -2238,6 +2241,7 @@ static void __init atmci_get_cap(struct atmel_mci *host)
case 0x200:
host->caps.has_rwproof = 1;
host->caps.need_blksz_mul_4 = 0;
+ host->caps.need_notbusy_for_read_ops = 1;
case 0x100:
host->caps.has_bad_data_ordering = 0;
host->caps.need_reset_after_xfer = 0;
diff --git a/drivers/mmc/host/bfin_sdh.c b/drivers/mmc/host/bfin_sdh.c
index 03666174ca48..a17dd7363ceb 100644
--- a/drivers/mmc/host/bfin_sdh.c
+++ b/drivers/mmc/host/bfin_sdh.c
@@ -49,13 +49,6 @@
#define bfin_write_SDH_CFG bfin_write_RSI_CFG
#endif
-struct dma_desc_array {
- unsigned long start_addr;
- unsigned short cfg;
- unsigned short x_count;
- short x_modify;
-} __packed;
-
struct sdh_host {
struct mmc_host *mmc;
spinlock_t lock;
diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
index 72dc3cde646d..af40d227bece 100644
--- a/drivers/mmc/host/dw_mmc.c
+++ b/drivers/mmc/host/dw_mmc.c
@@ -627,6 +627,7 @@ static void dw_mci_setup_bus(struct dw_mci_slot *slot)
{
struct dw_mci *host = slot->host;
u32 div;
+ u32 clk_en_a;
if (slot->clock != host->current_speed) {
div = host->bus_hz / slot->clock;
@@ -659,9 +660,11 @@ static void dw_mci_setup_bus(struct dw_mci_slot *slot)
mci_send_cmd(slot,
SDMMC_CMD_UPD_CLK | SDMMC_CMD_PRV_DAT_WAIT, 0);
- /* enable clock */
- mci_writel(host, CLKENA, ((SDMMC_CLKEN_ENABLE |
- SDMMC_CLKEN_LOW_PWR) << slot->id));
+ /* enable clock; only low power if no SDIO */
+ clk_en_a = SDMMC_CLKEN_ENABLE << slot->id;
+ if (!(mci_readl(host, INTMASK) & SDMMC_INT_SDIO(slot->id)))
+ clk_en_a |= SDMMC_CLKEN_LOW_PWR << slot->id;
+ mci_writel(host, CLKENA, clk_en_a);
/* inform CIU */
mci_send_cmd(slot,
@@ -862,6 +865,30 @@ static int dw_mci_get_cd(struct mmc_host *mmc)
return present;
}
+/*
+ * Disable lower power mode.
+ *
+ * Low power mode will stop the card clock when idle. According to the
+ * description of the CLKENA register we should disable low power mode
+ * for SDIO cards if we need SDIO interrupts to work.
+ *
+ * This function is fast if low power mode is already disabled.
+ */
+static void dw_mci_disable_low_power(struct dw_mci_slot *slot)
+{
+ struct dw_mci *host = slot->host;
+ u32 clk_en_a;
+ const u32 clken_low_pwr = SDMMC_CLKEN_LOW_PWR << slot->id;
+
+ clk_en_a = mci_readl(host, CLKENA);
+
+ if (clk_en_a & clken_low_pwr) {
+ mci_writel(host, CLKENA, clk_en_a & ~clken_low_pwr);
+ mci_send_cmd(slot, SDMMC_CMD_UPD_CLK |
+ SDMMC_CMD_PRV_DAT_WAIT, 0);
+ }
+}
+
static void dw_mci_enable_sdio_irq(struct mmc_host *mmc, int enb)
{
struct dw_mci_slot *slot = mmc_priv(mmc);
@@ -871,6 +898,14 @@ static void dw_mci_enable_sdio_irq(struct mmc_host *mmc, int enb)
/* Enable/disable Slot Specific SDIO interrupt */
int_mask = mci_readl(host, INTMASK);
if (enb) {
+ /*
+ * Turn off low power mode if it was enabled. This is a bit of
+ * a heavy operation and we disable / enable IRQs a lot, so
+ * we'll leave low power mode disabled and it will get
+ * re-enabled again in dw_mci_setup_bus().
+ */
+ dw_mci_disable_low_power(slot);
+
mci_writel(host, INTMASK,
(int_mask | SDMMC_INT_SDIO(slot->id)));
} else {
@@ -1429,22 +1464,10 @@ static void dw_mci_read_data_pio(struct dw_mci *host)
nbytes += len;
remain -= len;
} while (remain);
- sg_miter->consumed = offset;
+ sg_miter->consumed = offset;
status = mci_readl(host, MINTSTS);
mci_writel(host, RINTSTS, SDMMC_INT_RXDR);
- if (status & DW_MCI_DATA_ERROR_FLAGS) {
- host->data_status = status;
- data->bytes_xfered += nbytes;
- sg_miter_stop(sg_miter);
- host->sg = NULL;
- smp_wmb();
-
- set_bit(EVENT_DATA_ERROR, &host->pending_events);
-
- tasklet_schedule(&host->tasklet);
- return;
- }
} while (status & SDMMC_INT_RXDR); /*if the RXDR is ready read again*/
data->bytes_xfered += nbytes;
@@ -1497,23 +1520,10 @@ static void dw_mci_write_data_pio(struct dw_mci *host)
nbytes += len;
remain -= len;
} while (remain);
- sg_miter->consumed = offset;
+ sg_miter->consumed = offset;
status = mci_readl(host, MINTSTS);
mci_writel(host, RINTSTS, SDMMC_INT_TXDR);
- if (status & DW_MCI_DATA_ERROR_FLAGS) {
- host->data_status = status;
- data->bytes_xfered += nbytes;
- sg_miter_stop(sg_miter);
- host->sg = NULL;
-
- smp_wmb();
-
- set_bit(EVENT_DATA_ERROR, &host->pending_events);
-
- tasklet_schedule(&host->tasklet);
- return;
- }
} while (status & SDMMC_INT_TXDR); /* if TXDR write again */
data->bytes_xfered += nbytes;
@@ -1547,12 +1557,11 @@ static void dw_mci_cmd_interrupt(struct dw_mci *host, u32 status)
static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
{
struct dw_mci *host = dev_id;
- u32 status, pending;
+ u32 pending;
unsigned int pass_count = 0;
int i;
do {
- status = mci_readl(host, RINTSTS);
pending = mci_readl(host, MINTSTS); /* read-only mask reg */
/*
@@ -1570,7 +1579,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
if (pending & DW_MCI_CMD_ERROR_FLAGS) {
mci_writel(host, RINTSTS, DW_MCI_CMD_ERROR_FLAGS);
- host->cmd_status = status;
+ host->cmd_status = pending;
smp_wmb();
set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
}
@@ -1578,18 +1587,16 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
if (pending & DW_MCI_DATA_ERROR_FLAGS) {
/* if there is an error report DATA_ERROR */
mci_writel(host, RINTSTS, DW_MCI_DATA_ERROR_FLAGS);
- host->data_status = status;
+ host->data_status = pending;
smp_wmb();
set_bit(EVENT_DATA_ERROR, &host->pending_events);
- if (!(pending & (SDMMC_INT_DTO | SDMMC_INT_DCRC |
- SDMMC_INT_SBE | SDMMC_INT_EBE)))
- tasklet_schedule(&host->tasklet);
+ tasklet_schedule(&host->tasklet);
}
if (pending & SDMMC_INT_DATA_OVER) {
mci_writel(host, RINTSTS, SDMMC_INT_DATA_OVER);
if (!host->data_status)
- host->data_status = status;
+ host->data_status = pending;
smp_wmb();
if (host->dir_status == DW_MCI_RECV_STATUS) {
if (host->sg != NULL)
@@ -1613,7 +1620,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
if (pending & SDMMC_INT_CMD_DONE) {
mci_writel(host, RINTSTS, SDMMC_INT_CMD_DONE);
- dw_mci_cmd_interrupt(host, status);
+ dw_mci_cmd_interrupt(host, pending);
}
if (pending & SDMMC_INT_CD) {
diff --git a/drivers/mmc/host/mxs-mmc.c b/drivers/mmc/host/mxs-mmc.c
index a51f9309ffbb..ad3fcea1269e 100644
--- a/drivers/mmc/host/mxs-mmc.c
+++ b/drivers/mmc/host/mxs-mmc.c
@@ -285,11 +285,11 @@ static irqreturn_t mxs_mmc_irq_handler(int irq, void *dev_id)
writel(stat & MXS_MMC_IRQ_BITS,
host->base + HW_SSP_CTRL1(host) + STMP_OFFSET_REG_CLR);
+ spin_unlock(&host->lock);
+
if ((stat & BM_SSP_CTRL1_SDIO_IRQ) && (stat & BM_SSP_CTRL1_SDIO_IRQ_EN))
mmc_signal_sdio_irq(host->mmc);
- spin_unlock(&host->lock);
-
if (stat & BM_SSP_CTRL1_RESP_TIMEOUT_IRQ)
cmd->error = -ETIMEDOUT;
else if (stat & BM_SSP_CTRL1_RESP_ERR_IRQ)
@@ -644,11 +644,6 @@ static void mxs_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
host->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET);
writel(BM_SSP_CTRL1_SDIO_IRQ_EN,
host->base + HW_SSP_CTRL1(host) + STMP_OFFSET_REG_SET);
-
- if (readl(host->base + HW_SSP_STATUS(host)) &
- BM_SSP_STATUS_SDIO_IRQ)
- mmc_signal_sdio_irq(host->mmc);
-
} else {
writel(BM_SSP_CTRL0_SDIO_IRQ_CHECK,
host->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR);
@@ -657,6 +652,11 @@ static void mxs_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
}
spin_unlock_irqrestore(&host->lock, flags);
+
+ if (enable && readl(host->base + HW_SSP_STATUS(host)) &
+ BM_SSP_STATUS_SDIO_IRQ)
+ mmc_signal_sdio_irq(host->mmc);
+
}
static const struct mmc_host_ops mxs_mmc_ops = {
diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c
index 50e08f03aa65..a5999a74496a 100644
--- a/drivers/mmc/host/omap.c
+++ b/drivers/mmc/host/omap.c
@@ -668,7 +668,7 @@ mmc_omap_clk_timer(unsigned long data)
static void
mmc_omap_xfer_data(struct mmc_omap_host *host, int write)
{
- int n;
+ int n, nwords;
if (host->buffer_bytes_left == 0) {
host->sg_idx++;
@@ -678,15 +678,23 @@ mmc_omap_xfer_data(struct mmc_omap_host *host, int write)
n = 64;
if (n > host->buffer_bytes_left)
n = host->buffer_bytes_left;
+
+ nwords = n / 2;
+ nwords += n & 1; /* handle odd number of bytes to transfer */
+
host->buffer_bytes_left -= n;
host->total_bytes_left -= n;
host->data->bytes_xfered += n;
if (write) {
- __raw_writesw(host->virt_base + OMAP_MMC_REG(host, DATA), host->buffer, n);
+ __raw_writesw(host->virt_base + OMAP_MMC_REG(host, DATA),
+ host->buffer, nwords);
} else {
- __raw_readsw(host->virt_base + OMAP_MMC_REG(host, DATA), host->buffer, n);
+ __raw_readsw(host->virt_base + OMAP_MMC_REG(host, DATA),
+ host->buffer, nwords);
}
+
+ host->buffer += nwords;
}
static inline void mmc_omap_report_irq(u16 status)
diff --git a/drivers/mmc/host/sdhci-esdhc.h b/drivers/mmc/host/sdhci-esdhc.h
index b97b2f5dafdb..d25f9ab9a54d 100644
--- a/drivers/mmc/host/sdhci-esdhc.h
+++ b/drivers/mmc/host/sdhci-esdhc.h
@@ -48,14 +48,14 @@ static inline void esdhc_set_clock(struct sdhci_host *host, unsigned int clock)
int div = 1;
u32 temp;
+ if (clock == 0)
+ goto out;
+
temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL);
temp &= ~(ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN
| ESDHC_CLOCK_MASK);
sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL);
- if (clock == 0)
- goto out;
-
while (host->max_clk / pre_div / 16 > clock && pre_div < 256)
pre_div *= 2;
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 111aca5e97f3..4b27f9f503e4 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -239,6 +239,7 @@ struct mmc_card {
#define MMC_QUIRK_BLK_NO_CMD23 (1<<7) /* Avoid CMD23 for regular multiblock */
#define MMC_QUIRK_BROKEN_BYTE_MODE_512 (1<<8) /* Avoid sending 512 bytes in */
#define MMC_QUIRK_LONG_READ_TIME (1<<9) /* Data read time > CSD says */
+#define MMC_QUIRK_SEC_ERASE_TRIM_BROKEN (1<<10) /* Skip secure for erase/trim */
/* byte mode */
unsigned int poweroff_notify_state; /* eMMC4.5 notify feature */
#define MMC_NO_POWER_NOTIFICATION 0