openwrt/target/linux/brcm2708/patches-4.1/0076-bcm2835-sdhost-Improve-error-handling-and-recovery.patch

1089 lines
34 KiB
Diff
Raw Normal View History

From 16e7c9f047567799b3309217c44ac071b4677c63 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.org>
Date: Wed, 17 Jun 2015 11:36:53 +0100
Subject: [PATCH 076/171] bcm2835-sdhost: Improve error handling and recovery
1) Expose the hw_reset method to the MMC framework, removing many
internal calls by the driver.
2) Reduce overclock setting on error.
3) Increase timeout to cope with high capacity cards.
4) Add properties and parameters to control pio_limit and debug.
5) Reduce messages at probe time.
---
arch/arm/boot/dts/overlays/README | 8 +-
arch/arm/boot/dts/overlays/sdhost-overlay.dts | 4 +-
drivers/mmc/host/bcm2835-sdhost.c | 578 ++++++++++++++++++--------
3 files changed, 404 insertions(+), 186 deletions(-)
--- a/arch/arm/boot/dts/overlays/README
+++ b/arch/arm/boot/dts/overlays/README
@@ -408,7 +408,13 @@ Info: Selects the bcm2835-sdhost SD/MM
Load: dtoverlay=sdhost,<param>=<val>
Params: overclock_50 Clock (in MHz) to use when the MMC framework
requests 50MHz
- force_pio Disable DMA support
+
+ force_pio Disable DMA support (default off)
+
+ pio_limit Number of blocks above which to use DMA
+ (default 2)
+
+ debug Enable debug output (default off)
Name: spi-bcm2708
--- a/arch/arm/boot/dts/overlays/sdhost-overlay.dts
+++ b/arch/arm/boot/dts/overlays/sdhost-overlay.dts
@@ -22,6 +22,7 @@
dma-names = "tx", "rx";
brcm,delay-after-stop = <0>;
brcm,overclock-50 = <0>;
+ brcm,pio-limit = <2>;
status = "okay";
};
};
@@ -70,9 +71,10 @@
};
__overrides__ {
- delay_after_stop = <&sdhost>,"brcm,delay-after-stop:0";
overclock_50 = <&sdhost>,"brcm,overclock-50:0";
force_pio = <&sdhost>,"brcm,force-pio?";
+ pio_limit = <&sdhost>,"brcm,pio-limit:0";
+ debug = <&sdhost>,"brcm,debug?";
sdhost_freq = <&clk_sdhost>,"clock-frequency:0";
};
};
--- a/drivers/mmc/host/bcm2835-sdhost.c
+++ b/drivers/mmc/host/bcm2835-sdhost.c
@@ -90,9 +90,8 @@
/* Reserved */
#define SDHSTS_DATA_FLAG 0x01
-#define SDHSTS_TRANSFER_ERROR_MASK (SDHSTS_CRC16_ERROR|SDHSTS_REW_TIME_OUT|SDHSTS_FIFO_ERROR)
+#define SDHSTS_TRANSFER_ERROR_MASK (SDHSTS_CRC7_ERROR|SDHSTS_CRC16_ERROR|SDHSTS_REW_TIME_OUT|SDHSTS_FIFO_ERROR)
#define SDHSTS_ERROR_MASK (SDHSTS_CMD_TIME_OUT|SDHSTS_TRANSFER_ERROR_MASK)
-/* SDHSTS_CRC7_ERROR - ignore this as MMC cards generate this spuriously */
#define SDHCFG_BUSY_IRPT_EN (1<<10)
#define SDHCFG_BLOCK_IRPT_EN (1<<8)
@@ -111,16 +110,7 @@
#define SDEDM_READ_THRESHOLD_SHIFT 14
#define SDEDM_THRESHOLD_MASK 0x1f
-/* the inclusive limit in bytes under which PIO will be used instead of DMA */
-#ifdef CONFIG_MMC_BCM2835_SDHOST_PIO_DMA_BARRIER
-#define PIO_DMA_BARRIER CONFIG_MMC_BCM2835_SDHOST_PIO_DMA_BARRIER
-#else
-#define PIO_DMA_BARRIER 0
-#endif
-
-#define MIN_FREQ 400000
-#define TIMEOUT_VAL 0xE
-#define BCM2835_SDHOST_WRITE_DELAY(f) (((2 * 1000000) / f) + 1)
+#define MHZ 1000000
#ifndef BCM2708_PERI_BASE
#define BCM2708_PERI_BASE 0x20000000
@@ -138,19 +128,20 @@ struct bcm2835_host {
struct mmc_host *mmc;
- u32 timeout;
+ u32 pio_timeout; /* In jiffies */
int clock; /* Current clock speed */
bool slow_card; /* Force 11-bit divisor */
unsigned int max_clk; /* Max possible freq */
- unsigned int timeout_clk; /* Timeout freq (KHz) */
struct tasklet_struct finish_tasklet; /* Tasklet structures */
struct timer_list timer; /* Timer for timeouts */
+ struct timer_list pio_timer; /* PIO error detection timer */
+
struct sg_mapping_iter sg_miter; /* SG state for PIO */
unsigned int blocks; /* remaining PIO blocks */
@@ -170,6 +161,10 @@ struct bcm2835_host {
unsigned int use_busy:1; /* Wait for busy interrupt */
+ unsigned int reduce_overclock:1; /* ...at the next opportunity */
+
+ unsigned int debug:1; /* Enable debug output */
+
u32 thread_isr;
/*DMA part*/
@@ -185,7 +180,8 @@ struct bcm2835_host {
struct timeval stop_time; /* when the last stop was issued */
u32 delay_after_stop; /* minimum time between stop and subsequent data transfer */
u32 overclock_50; /* frequency to use when 50MHz is requested (in MHz) */
- u32 max_overclock; /* Highest reported */
+ u32 overclock; /* Current frequency if overclocked, else zero */
+ u32 pio_limit; /* Maximum block count for PIO (0 = always DMA) */
};
@@ -204,41 +200,79 @@ static inline u32 bcm2835_sdhost_read_re
return readl_relaxed(host->ioaddr + reg);
}
+static void bcm2835_sdhost_dumpcmd(struct bcm2835_host *host,
+ struct mmc_command *cmd,
+ const char *label)
+{
+ if (cmd)
+ pr_info("%s:%c%s op %d arg 0x%x flags 0x%x - resp %08x %08x %08x %08x, err %d\n",
+ mmc_hostname(host->mmc),
+ (cmd == host->cmd) ? '>' : ' ',
+ label, cmd->opcode, cmd->arg, cmd->flags,
+ cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3],
+ cmd->error);
+}
+
static void bcm2835_sdhost_dumpregs(struct bcm2835_host *host)
{
- pr_info(DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n",
+ bcm2835_sdhost_dumpcmd(host, host->mrq->sbc, "sbc");
+ bcm2835_sdhost_dumpcmd(host, host->mrq->cmd, "cmd");
+ if (host->mrq->data)
+ pr_err("%s: data blocks %x blksz %x - err %d\n",
+ mmc_hostname(host->mmc),
+ host->mrq->data->blocks,
+ host->mrq->data->blksz,
+ host->mrq->data->error);
+ bcm2835_sdhost_dumpcmd(host, host->mrq->stop, "stop");
+
+ pr_info("%s: =========== REGISTER DUMP ===========\n",
mmc_hostname(host->mmc));
- pr_info(DRIVER_NAME ": SDCMD 0x%08x\n",
+ pr_info("%s: SDCMD 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDCMD));
- pr_info(DRIVER_NAME ": SDARG 0x%08x\n",
+ pr_info("%s: SDARG 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDARG));
- pr_info(DRIVER_NAME ": SDTOUT 0x%08x\n",
+ pr_info("%s: SDTOUT 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDTOUT));
- pr_info(DRIVER_NAME ": SDCDIV 0x%08x\n",
+ pr_info("%s: SDCDIV 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDCDIV));
- pr_info(DRIVER_NAME ": SDRSP0 0x%08x\n",
+ pr_info("%s: SDRSP0 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDRSP0));
- pr_info(DRIVER_NAME ": SDRSP1 0x%08x\n",
+ pr_info("%s: SDRSP1 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDRSP1));
- pr_info(DRIVER_NAME ": SDRSP2 0x%08x\n",
+ pr_info("%s: SDRSP2 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDRSP2));
- pr_info(DRIVER_NAME ": SDRSP3 0x%08x\n",
+ pr_info("%s: SDRSP3 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDRSP3));
- pr_info(DRIVER_NAME ": SDHSTS 0x%08x\n",
+ pr_info("%s: SDHSTS 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDHSTS));
- pr_info(DRIVER_NAME ": SDVDD 0x%08x\n",
+ pr_info("%s: SDVDD 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDVDD));
- pr_info(DRIVER_NAME ": SDEDM 0x%08x\n",
+ pr_info("%s: SDEDM 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDEDM));
- pr_info(DRIVER_NAME ": SDHCFG 0x%08x\n",
+ pr_info("%s: SDHCFG 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDHCFG));
- pr_info(DRIVER_NAME ": SDHBCT 0x%08x\n",
+ pr_info("%s: SDHBCT 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDHBCT));
- pr_info(DRIVER_NAME ": SDHBLC 0x%08x\n",
+ pr_info("%s: SDHBLC 0x%08x\n",
+ mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDHBLC));
- pr_debug(DRIVER_NAME ": ===========================================\n");
+ pr_info("%s: ===========================================\n",
+ mmc_hostname(host->mmc));
}
@@ -248,12 +282,10 @@ static void bcm2835_sdhost_set_power(str
}
-static void bcm2835_sdhost_reset(struct bcm2835_host *host)
+static void bcm2835_sdhost_reset_internal(struct bcm2835_host *host)
{
u32 temp;
- pr_debug("bcm2835_sdhost_reset\n");
-
bcm2835_sdhost_set_power(host, false);
bcm2835_sdhost_write(host, 0, SDCMD);
@@ -281,6 +313,20 @@ static void bcm2835_sdhost_reset(struct
mmiowb();
}
+
+static void bcm2835_sdhost_reset(struct mmc_host *mmc)
+{
+ struct bcm2835_host *host = mmc_priv(mmc);
+ unsigned long flags;
+ if (host->debug)
+ pr_info("%s: reset\n", mmc_hostname(mmc));
+ spin_lock_irqsave(&host->lock, flags);
+
+ bcm2835_sdhost_reset_internal(host);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
static void bcm2835_sdhost_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
static void bcm2835_sdhost_init(struct bcm2835_host *host, int soft)
@@ -290,7 +336,7 @@ static void bcm2835_sdhost_init(struct b
/* Set interrupt enables */
host->hcfg = SDHCFG_BUSY_IRPT_EN;
- bcm2835_sdhost_reset(host);
+ bcm2835_sdhost_reset_internal(host);
if (soft) {
/* force clock reconfiguration */
@@ -420,6 +466,40 @@ static void bcm2835_sdhost_dma_complete(
spin_unlock_irqrestore(&host->lock, flags);
}
+static bool data_transfer_wait(struct bcm2835_host *host, const char *caller)
+{
+ unsigned long timeout = 1000000;
+ u32 hsts;
+ while (timeout)
+ {
+ hsts = bcm2835_sdhost_read(host, SDHSTS);
+ if (hsts & (SDHSTS_TRANSFER_ERROR_MASK |
+ SDHSTS_DATA_FLAG)) {
+ bcm2835_sdhost_write(host, SDHSTS_TRANSFER_ERROR_MASK,
+ SDHSTS);
+ break;
+ }
+ timeout--;
+ }
+
+ if (hsts & (SDHSTS_CRC16_ERROR |
+ SDHSTS_CRC7_ERROR |
+ SDHSTS_FIFO_ERROR)) {
+ pr_err("%s: data error in %s - HSTS %x\n",
+ mmc_hostname(host->mmc), caller, hsts);
+ host->data->error = -EILSEQ;
+ return false;
+ } else if ((timeout == 0) ||
+ (hsts & (SDHSTS_CMD_TIME_OUT |
+ SDHSTS_REW_TIME_OUT))) {
+ pr_err("%s: timeout in %s - HSTS %x\n",
+ mmc_hostname(host->mmc), caller, hsts);
+ host->data->error = -ETIMEDOUT;
+ return false;
+ }
+ return true;
+}
+
static void bcm2835_sdhost_read_block_pio(struct bcm2835_host *host)
{
unsigned long flags;
@@ -443,35 +523,15 @@ static void bcm2835_sdhost_read_block_pi
buf = (u32 *)host->sg_miter.addr;
while (len) {
- while (1) {
- u32 hsts;
- hsts = bcm2835_sdhost_read(host, SDHSTS);
- if (hsts & SDHSTS_DATA_FLAG)
- break;
-
- if (hsts & SDHSTS_ERROR_MASK) {
- pr_err("%s: Transfer error - HSTS %x, HBCT %x - %x left\n",
- mmc_hostname(host->mmc),
- hsts,
- bcm2835_sdhost_read(host, SDHBCT),
- blksize + len);
- if (hsts & SDHSTS_REW_TIME_OUT)
- host->data->error = -ETIMEDOUT;
- else if (hsts & (SDHSTS_CRC16_ERROR ||
- SDHSTS_CRC7_ERROR))
- host->data->error = -EILSEQ;
- else {
- pr_err("%s: unexpected data error\n",
- mmc_hostname(host->mmc));
- bcm2835_sdhost_dumpregs(host);
- host->cmd->error = -EIO;
- }
- }
- }
+ if (!data_transfer_wait(host, "read_block_pio"))
+ break;
*(buf++) = bcm2835_sdhost_read(host, SDDATA);
len -= 4;
}
+
+ if (host->data->error)
+ break;
}
sg_miter_stop(&host->sg_miter);
@@ -502,11 +562,15 @@ static void bcm2835_sdhost_write_block_p
buf = host->sg_miter.addr;
while (len) {
- while (!(bcm2835_sdhost_read(host, SDHSTS) & SDHSTS_DATA_FLAG))
- continue;
+ if (!data_transfer_wait(host, "write_block_pio"))
+ break;
+
bcm2835_sdhost_write(host, *(buf++), SDDATA);
len -= 4;
}
+
+ if (host->data->error)
+ break;
}
sg_miter_stop(&host->sg_miter);
@@ -519,10 +583,15 @@ static void bcm2835_sdhost_transfer_pio(
{
BUG_ON(!host->data);
- if (host->data->flags & MMC_DATA_READ)
+ if (host->data->flags & MMC_DATA_READ) {
bcm2835_sdhost_read_block_pio(host);
- else
+ } else {
bcm2835_sdhost_write_block_pio(host);
+
+ /* Start a timer in case a transfer error occurs because
+ there is no error interrupt */
+ mod_timer(&host->pio_timer, jiffies + host->pio_timeout);
+ }
}
@@ -607,6 +676,7 @@ static void bcm2835_sdhost_prepare_data(
host->flush_fifo = 0;
host->data->bytes_xfered = 0;
+ host->use_dma = host->have_dma && (data->blocks > host->pio_limit);
if (!host->use_dma) {
int flags;
@@ -619,8 +689,6 @@ static void bcm2835_sdhost_prepare_data(
host->blocks = data->blocks;
}
- host->use_dma = host->have_dma && data->blocks > PIO_DMA_BARRIER;
-
bcm2835_sdhost_set_transfer_irqs(host);
bcm2835_sdhost_write(host, data->blksz, SDHBCT);
@@ -638,22 +706,25 @@ void bcm2835_sdhost_send_command(struct
WARN_ON(host->cmd);
- if (1) {
- pr_debug("bcm2835_sdhost_send_command: %08x %08x (flags %x)\n",
- cmd->opcode, cmd->arg, (cmd->flags & 0xff) | (cmd->data ? cmd->data->flags : 0));
- if (cmd->data)
- pr_debug("bcm2835_sdhost_send_command: %s %d*%x\n",
- (cmd->data->flags & MMC_DATA_READ) ?
- "read" : "write", cmd->data->blocks,
- cmd->data->blksz);
- }
+ if (cmd->data)
+ pr_debug("%s: send_command %d 0x%x "
+ "(flags 0x%x) - %s %d*%d\n",
+ mmc_hostname(host->mmc),
+ cmd->opcode, cmd->arg, cmd->flags,
+ (cmd->data->flags & MMC_DATA_READ) ?
+ "read" : "write", cmd->data->blocks,
+ cmd->data->blksz);
+ else
+ pr_debug("%s: send_command %d 0x%x (flags 0x%x)\n",
+ mmc_hostname(host->mmc),
+ cmd->opcode, cmd->arg, cmd->flags);
/* Wait max 10 ms */
timeout = 1000;
while (bcm2835_sdhost_read(host, SDCMD) & SDCMD_NEW_FLAG) {
if (timeout == 0) {
- pr_err("%s: Previous command never completed.\n",
+ pr_err("%s: previous command never completed.\n",
mmc_hostname(host->mmc));
bcm2835_sdhost_dumpregs(host);
cmd->error = -EIO;
@@ -666,16 +737,16 @@ void bcm2835_sdhost_send_command(struct
if ((1000-timeout)/100 > 1 && (1000-timeout)/100 > host->max_delay) {
host->max_delay = (1000-timeout)/100;
- pr_warning("Warning: SDHost controller hung for %d ms\n", host->max_delay);
+ pr_warning("%s: controller hung for %d ms\n",
+ mmc_hostname(host->mmc),
+ host->max_delay);
}
timeout = jiffies;
-#ifdef CONFIG_ARCH_BCM2835
if (!cmd->data && cmd->busy_timeout > 9000)
timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
else
-#endif
- timeout += 10 * HZ;
+ timeout += 10 * HZ;
mod_timer(&host->timer, timeout);
host->cmd = cmd;
@@ -685,7 +756,7 @@ void bcm2835_sdhost_send_command(struct
bcm2835_sdhost_write(host, cmd->arg, SDARG);
if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
- pr_err("%s: Unsupported response type!\n",
+ pr_err("%s: unsupported response type!\n",
mmc_hostname(host->mmc));
cmd->error = -EINVAL;
tasklet_schedule(&host->finish_tasklet);
@@ -783,13 +854,6 @@ static void bcm2835_sdhost_transfer_comp
pr_debug("transfer_complete(error %d, stop %d)\n",
data->error, data->stop ? 1 : 0);
- if (data->error)
- /*
- * The controller needs a reset of internal state machines
- * upon error conditions.
- */
- bcm2835_sdhost_reset(host);
-
/*
* Need to send CMD12 if -
* a) open-ended multiblock transfer (no CMD23)
@@ -845,7 +909,7 @@ static void bcm2835_sdhost_finish_comman
#endif
if (timeout == 0) {
- pr_err("%s: Command never completed.\n",
+ pr_err("%s: command never completed.\n",
mmc_hostname(host->mmc));
bcm2835_sdhost_dumpregs(host);
host->cmd->error = -EIO;
@@ -875,14 +939,23 @@ static void bcm2835_sdhost_finish_comman
{
u32 sdhsts = bcm2835_sdhost_read(host, SDHSTS);
- pr_debug("%s: error detected - CMD %x, HSTS %03x, EDM %x\n",
- mmc_hostname(host->mmc), sdcmd, sdhsts,
- bcm2835_sdhost_read(host, SDEDM));
-
- if (sdhsts & SDHSTS_CMD_TIME_OUT)
+ if (host->debug)
+ pr_info("%s: error detected - CMD %x, HSTS %03x, EDM %x\n",
+ mmc_hostname(host->mmc), sdcmd, sdhsts,
+ bcm2835_sdhost_read(host, SDEDM));
+
+ if (sdhsts & SDHSTS_CMD_TIME_OUT) {
+ switch (host->cmd->opcode) {
+ case 5: case 52: case 53:
+ /* Don't warn about SDIO commands */
+ break;
+ default:
+ pr_err("%s: command timeout\n",
+ mmc_hostname(host->mmc));
+ break;
+ }
host->cmd->error = -ETIMEDOUT;
- else
- {
+ } else {
pr_err("%s: unexpected command error\n",
mmc_hostname(host->mmc));
bcm2835_sdhost_dumpregs(host);
@@ -897,11 +970,13 @@ static void bcm2835_sdhost_finish_comman
int i;
for (i = 0; i < 4; i++)
host->cmd->resp[3 - i] = bcm2835_sdhost_read(host, SDRSP0 + i*4);
- pr_debug("bcm2835_sdhost_finish_command: %08x %08x %08x %08x\n",
+ pr_debug("%s: finish_command %08x %08x %08x %08x\n",
+ mmc_hostname(host->mmc),
host->cmd->resp[0], host->cmd->resp[1], host->cmd->resp[2], host->cmd->resp[3]);
} else {
host->cmd->resp[0] = bcm2835_sdhost_read(host, SDRSP0);
- pr_debug("bcm2835_sdhost_finish_command: %08x\n",
+ pr_debug("%s: finish_command %08x\n",
+ mmc_hostname(host->mmc),
host->cmd->resp[0]);
}
}
@@ -932,7 +1007,7 @@ static void bcm2835_sdhost_finish_comman
}
}
-static void bcm2835_sdhost_timeout_timer(unsigned long data)
+static void bcm2835_sdhost_timeout(unsigned long data)
{
struct bcm2835_host *host;
unsigned long flags;
@@ -942,7 +1017,7 @@ static void bcm2835_sdhost_timeout_timer
spin_lock_irqsave(&host->lock, flags);
if (host->mrq) {
- pr_err("%s: Timeout waiting for hardware interrupt.\n",
+ pr_err("%s: timeout waiting for hardware interrupt.\n",
mmc_hostname(host->mmc));
bcm2835_sdhost_dumpregs(host);
@@ -964,6 +1039,41 @@ static void bcm2835_sdhost_timeout_timer
spin_unlock_irqrestore(&host->lock, flags);
}
+static void bcm2835_sdhost_pio_timeout(unsigned long data)
+{
+ struct bcm2835_host *host;
+ unsigned long flags;
+
+ host = (struct bcm2835_host *)data;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (host->data) {
+ u32 hsts = bcm2835_sdhost_read(host, SDHSTS);
+
+ if (hsts & SDHSTS_REW_TIME_OUT) {
+ pr_err("%s: transfer timeout\n",
+ mmc_hostname(host->mmc));
+ if (host->debug)
+ bcm2835_sdhost_dumpregs(host);
+ } else {
+ pr_err("%s: unexpected transfer timeout\n",
+ mmc_hostname(host->mmc));
+ bcm2835_sdhost_dumpregs(host);
+ }
+
+ bcm2835_sdhost_write(host, SDHSTS_TRANSFER_ERROR_MASK,
+ SDHSTS);
+
+ host->data->error = -ETIMEDOUT;
+
+ bcm2835_sdhost_finish_data(host);
+ }
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
static void bcm2835_sdhost_enable_sdio_irq_nolock(struct bcm2835_host *host, int enable)
{
if (enable)
@@ -979,7 +1089,7 @@ static void bcm2835_sdhost_enable_sdio_i
struct bcm2835_host *host = mmc_priv(mmc);
unsigned long flags;
- pr_debug("bcm2835_sdhost_enable_sdio_irq(%d)\n", enable);
+ pr_debug("%s: enable_sdio_irq(%d)\n", mmc_hostname(mmc), enable);
spin_lock_irqsave(&host->lock, flags);
bcm2835_sdhost_enable_sdio_irq_nolock(host, enable);
spin_unlock_irqrestore(&host->lock, flags);
@@ -987,11 +1097,12 @@ static void bcm2835_sdhost_enable_sdio_i
static u32 bcm2835_sdhost_busy_irq(struct bcm2835_host *host, u32 intmask)
{
- const u32 handled = (SDHSTS_CMD_TIME_OUT | SDHSTS_CRC16_ERROR |
- SDHSTS_CRC7_ERROR | SDHSTS_FIFO_ERROR);
+ const u32 handled = (SDHSTS_REW_TIME_OUT | SDHSTS_CMD_TIME_OUT |
+ SDHSTS_CRC16_ERROR | SDHSTS_CRC7_ERROR |
+ SDHSTS_FIFO_ERROR);
if (!host->cmd) {
- pr_err("%s: Got command busy interrupt 0x%08x even "
+ pr_err("%s: got command busy interrupt 0x%08x even "
"though no command operation was in progress.\n",
mmc_hostname(host->mmc), (unsigned)intmask);
bcm2835_sdhost_dumpregs(host);
@@ -999,7 +1110,7 @@ static u32 bcm2835_sdhost_busy_irq(struc
}
if (!host->use_busy) {
- pr_err("%s: Got command busy interrupt 0x%08x even "
+ pr_err("%s: got command busy interrupt 0x%08x even "
"though not expecting one.\n",
mmc_hostname(host->mmc), (unsigned)intmask);
bcm2835_sdhost_dumpregs(host);
@@ -1007,14 +1118,28 @@ static u32 bcm2835_sdhost_busy_irq(struc
}
host->use_busy = 0;
- if (intmask & SDHSTS_CMD_TIME_OUT)
- host->cmd->error = -ETIMEDOUT;
- else if (intmask & (SDHSTS_CRC16_ERROR | SDHSTS_CRC7_ERROR |
- SDHSTS_FIFO_ERROR))
- host->cmd->error = -EILSEQ;
+ if (intmask & SDHSTS_ERROR_MASK)
+ {
+ pr_err("sdhost_busy_irq: intmask %x, data %p\n", intmask, host->mrq->data);
+ if (intmask & SDHSTS_CRC7_ERROR)
+ host->cmd->error = -EILSEQ;
+ else if (intmask & (SDHSTS_CRC16_ERROR |
+ SDHSTS_FIFO_ERROR)) {
+ if (host->mrq->data)
+ host->mrq->data->error = -EILSEQ;
+ else
+ host->cmd->error = -EILSEQ;
+ } else if (intmask & SDHSTS_REW_TIME_OUT) {
+ if (host->mrq->data)
+ host->mrq->data->error = -ETIMEDOUT;
+ else
+ host->cmd->error = -ETIMEDOUT;
+ } else if (intmask & SDHSTS_CMD_TIME_OUT)
+ host->cmd->error = -ETIMEDOUT;
- if (host->cmd->error)
+ bcm2835_sdhost_dumpregs(host);
tasklet_schedule(&host->finish_tasklet);
+ }
else
bcm2835_sdhost_finish_command(host);
@@ -1023,8 +1148,9 @@ static u32 bcm2835_sdhost_busy_irq(struc
static u32 bcm2835_sdhost_data_irq(struct bcm2835_host *host, u32 intmask)
{
- const u32 handled = (SDHSTS_CMD_TIME_OUT | SDHSTS_CRC16_ERROR |
- SDHSTS_CRC7_ERROR | SDHSTS_FIFO_ERROR);
+ const u32 handled = (SDHSTS_REW_TIME_OUT |
+ SDHSTS_CRC16_ERROR |
+ SDHSTS_FIFO_ERROR);
/* There are no dedicated data/space available interrupt
status bits, so it is necessary to use the single shared
@@ -1034,13 +1160,19 @@ static u32 bcm2835_sdhost_data_irq(struc
if (!host->data)
return 0;
- // XXX FIFO_ERROR
- if (intmask & SDHSTS_CMD_TIME_OUT)
- host->cmd->error = -ETIMEDOUT;
- else if ((intmask & (SDHSTS_CRC16_ERROR | SDHSTS_CRC7_ERROR)) &&
- ((bcm2835_sdhost_read(host, SDCMD) & SDCMD_CMD_MASK)
- != MMC_BUS_TEST_R))
- host->cmd->error = -EILSEQ;
+ if (intmask & (SDHSTS_CRC16_ERROR |
+ SDHSTS_FIFO_ERROR |
+ SDHSTS_REW_TIME_OUT)) {
+ if (intmask & (SDHSTS_CRC16_ERROR |
+ SDHSTS_FIFO_ERROR))
+ host->data->error = -EILSEQ;
+ else
+ host->data->error = -ETIMEDOUT;
+
+ bcm2835_sdhost_dumpregs(host);
+ tasklet_schedule(&host->finish_tasklet);
+ return handled;
+ }
/* Use the block interrupt for writes after the first block */
if (host->data->flags & MMC_DATA_WRITE) {
@@ -1067,31 +1199,48 @@ static u32 bcm2835_sdhost_block_irq(stru
{
struct dma_chan *dma_chan;
u32 dir_data;
- const u32 handled = (SDHSTS_CMD_TIME_OUT | SDHSTS_CRC16_ERROR |
- SDHSTS_CRC7_ERROR | SDHSTS_FIFO_ERROR);
+ const u32 handled = (SDHSTS_REW_TIME_OUT |
+ SDHSTS_CRC16_ERROR |
+ SDHSTS_FIFO_ERROR);
if (!host->data) {
- pr_err("%s: Got block interrupt 0x%08x even "
+ pr_err("%s: got block interrupt 0x%08x even "
"though no data operation was in progress.\n",
mmc_hostname(host->mmc), (unsigned)intmask);
bcm2835_sdhost_dumpregs(host);
return handled;
}
- if (intmask & SDHSTS_CMD_TIME_OUT)
- host->cmd->error = -ETIMEDOUT;
- else if ((intmask & (SDHSTS_CRC16_ERROR | SDHSTS_CRC7_ERROR)) &&
- ((bcm2835_sdhost_read(host, SDCMD) & SDCMD_CMD_MASK)
- != MMC_BUS_TEST_R))
- host->cmd->error = -EILSEQ;
+ if (intmask & (SDHSTS_CRC16_ERROR |
+ SDHSTS_FIFO_ERROR |
+ SDHSTS_REW_TIME_OUT)) {
+ if (intmask & (SDHSTS_CRC16_ERROR |
+ SDHSTS_FIFO_ERROR))
+ host->data->error = -EILSEQ;
+ else
+ host->data->error = -ETIMEDOUT;
+
+ if (host->debug)
+ bcm2835_sdhost_dumpregs(host);
+ tasklet_schedule(&host->finish_tasklet);
+ return handled;
+ }
if (!host->use_dma) {
BUG_ON(!host->blocks);
host->blocks--;
- if ((host->blocks == 0) || host->data->error)
+ if ((host->blocks == 0) || host->data->error) {
+ /* Cancel the timer */
+ del_timer(&host->pio_timer);
+
bcm2835_sdhost_finish_data(host);
- else
+ } else {
bcm2835_sdhost_transfer_pio(host);
+
+ /* Reset the timer */
+ mod_timer(&host->pio_timer,
+ jiffies + host->pio_timeout);
+ }
} else if (host->data->flags & MMC_DATA_WRITE) {
dma_chan = host->dma_chan_tx;
dir_data = DMA_TO_DEVICE;
@@ -1125,7 +1274,7 @@ static irqreturn_t bcm2835_sdhost_irq(in
SDHSTS_BLOCK_IRPT |
SDHSTS_SDIO_IRPT |
SDHSTS_DATA_FLAG);
- if ((handled == SDHSTS_DATA_FLAG) && // XXX
+ if ((handled == SDHSTS_DATA_FLAG) &&
(loops == 0) && !host->data) {
pr_err("%s: sdhost_irq data interrupt 0x%08x even "
"though no data operation was in progress.\n",
@@ -1177,10 +1326,11 @@ static irqreturn_t bcm2835_sdhost_irq(in
spin_unlock(&host->lock);
if (early)
- pr_debug("%s: early %x (loops %d)\n", mmc_hostname(host->mmc), early, loops);
+ pr_debug("%s: early %x (loops %d)\n",
+ mmc_hostname(host->mmc), early, loops);
if (unexpected) {
- pr_err("%s: Unexpected interrupt 0x%08x.\n",
+ pr_err("%s: unexpected interrupt 0x%08x.\n",
mmc_hostname(host->mmc), unexpected);
bcm2835_sdhost_dumpregs(host);
}
@@ -1227,8 +1377,22 @@ void bcm2835_sdhost_set_clock(struct bcm
int div = 0; /* Initialized for compiler warning */
unsigned int input_clock = clock;
- if (host->overclock_50 && (clock == 50000000))
- clock = host->overclock_50 * 1000000 + 999999;
+ if (host->debug)
+ pr_info("%s: set_clock(%d)\n", mmc_hostname(host->mmc), clock);
+
+ if ((clock == 0) && host->reduce_overclock) {
+ /* This is a reset following data corruption - reduce any
+ overclock */
+ host->reduce_overclock = 0;
+ if (host->overclock_50 > 50) {
+ pr_warn("%s: reducing overclock due to errors\n",
+ mmc_hostname(host->mmc));
+ host->overclock_50--;
+ }
+ }
+
+ if (host->overclock_50 && (clock == 50*MHZ))
+ clock = host->overclock_50 * MHZ + (MHZ - 1);
/* The SDCDIV register has 11 bits, and holds (div - 2).
But in data mode the max is 50MHz wihout a minimum, and only the
@@ -1275,17 +1439,34 @@ void bcm2835_sdhost_set_clock(struct bcm
clock = host->max_clk / (div + 2);
host->mmc->actual_clock = clock;
- if ((clock > input_clock) && (clock > host->max_overclock)) {
- pr_warn("%s: Overclocking to %dHz\n",
- mmc_hostname(host->mmc), clock);
- host->max_overclock = clock;
+ if (clock > input_clock) {
+ /* Save the closest value, to make it easier
+ to reduce in the event of error */
+ host->overclock_50 = (clock/MHZ);
+
+ if (clock != host->overclock) {
+ pr_warn("%s: overclocking to %dHz\n",
+ mmc_hostname(host->mmc), clock);
+ host->overclock = clock;
+ }
+ }
+ else if ((clock == 50 * MHZ) && host->overclock)
+ {
+ pr_warn("%s: cancelling overclock\n",
+ mmc_hostname(host->mmc));
+ host->overclock = 0;
}
host->cdiv = div;
bcm2835_sdhost_write(host, host->cdiv, SDCDIV);
- pr_debug(DRIVER_NAME ": clock=%d -> max_clk=%d, cdiv=%x (actual clock %d)\n",
- input_clock, host->max_clk, host->cdiv, host->mmc->actual_clock);
+ /* Set the timeout to 500ms */
+ bcm2835_sdhost_write(host, host->mmc->actual_clock/2, SDTOUT);
+
+ if (host->debug)
+ pr_info("%s: clock=%d -> max_clk=%d, cdiv=%x (actual clock %d)\n",
+ mmc_hostname(host->mmc), input_clock,
+ host->max_clk, host->cdiv, host->mmc->actual_clock);
}
static void bcm2835_sdhost_request(struct mmc_host *mmc, struct mmc_request *mrq)
@@ -1293,29 +1474,32 @@ static void bcm2835_sdhost_request(struc
struct bcm2835_host *host;
unsigned long flags;
- if (1) {
+ host = mmc_priv(mmc);
+
+ if (host->debug) {
struct mmc_command *cmd = mrq->cmd;
- const char *src = "cmd";
BUG_ON(!cmd);
- pr_debug("bcm2835_sdhost_request: %s %08x %08x (flags %x)\n",
- src, cmd->opcode, cmd->arg, cmd->flags);
if (cmd->data)
- pr_debug("bcm2835_sdhost_request: %s %d*%d\n",
- (cmd->data->flags & MMC_DATA_READ) ?
- "read" : "write", cmd->data->blocks,
- cmd->data->blksz);
+ pr_info("%s: cmd %d 0x%x (flags 0x%x) - %s %d*%d\n",
+ mmc_hostname(mmc),
+ cmd->opcode, cmd->arg, cmd->flags,
+ (cmd->data->flags & MMC_DATA_READ) ?
+ "read" : "write", cmd->data->blocks,
+ cmd->data->blksz);
+ else
+ pr_info("%s: cmd %d 0x%x (flags 0x%x)\n",
+ mmc_hostname(mmc),
+ cmd->opcode, cmd->arg, cmd->flags);
}
if (mrq->data && !is_power_of_2(mrq->data->blksz)) {
- pr_err("%s: Unsupported block size (%d bytes)\n",
+ pr_err("%s: unsupported block size (%d bytes)\n",
mmc_hostname(mmc), mrq->data->blksz);
mrq->cmd->error = -EINVAL;
mmc_request_done(mmc, mrq);
return;
}
- host = mmc_priv(mmc);
-
spin_lock_irqsave(&host->lock, flags);
WARN_ON(host->mrq != NULL);
@@ -1345,9 +1529,12 @@ static void bcm2835_sdhost_set_ios(struc
struct bcm2835_host *host = mmc_priv(mmc);
unsigned long flags;
- pr_debug("bcm2835_sdhost_set_ios: clock %d, pwr %d, bus_width %d, timing %d, vdd %d, drv_type %d\n",
- ios->clock, ios->power_mode, ios->bus_width,
- ios->timing, ios->signal_voltage, ios->drv_type);
+ if (host->debug)
+ pr_info("%s: ios clock %d, pwr %d, bus_width %d, "
+ "timing %d, vdd %d, drv_type %d\n",
+ mmc_hostname(mmc),
+ ios->clock, ios->power_mode, ios->bus_width,
+ ios->timing, ios->signal_voltage, ios->drv_type);
spin_lock_irqsave(&host->lock, flags);
@@ -1396,6 +1583,7 @@ static struct mmc_host_ops bcm2835_sdhos
.request = bcm2835_sdhost_request,
.set_ios = bcm2835_sdhost_set_ios,
.enable_sdio_irq = bcm2835_sdhost_enable_sdio_irq,
+ .hw_reset = bcm2835_sdhost_reset,
.multi_io_quirk = bcm2835_sdhost_multi_io_quirk,
};
@@ -1423,15 +1611,24 @@ static void bcm2835_sdhost_tasklet_finis
mrq = host->mrq;
- /*
- * The controller needs a reset of internal state machines
- * upon error conditions.
- */
- if (((mrq->cmd && mrq->cmd->error) ||
- (mrq->data && (mrq->data->error ||
- (mrq->data->stop && mrq->data->stop->error))))) {
+ /* Drop the overclock after any data corruption, or after any
+ error overclocked */
+ if (mrq->data && (mrq->data->error == -EILSEQ))
+ host->reduce_overclock = 1;
+ else if (host->overclock) {
+ /* Convert timeout errors while overclocked to data errors,
+ because the system recovers better. */
+ if (mrq->cmd && mrq->cmd->error) {
+ host->reduce_overclock = 1;
+ if (mrq->cmd->error == -ETIMEDOUT)
+ mrq->cmd->error = -EILSEQ;
+ }
- bcm2835_sdhost_reset(host);
+ if (mrq->data && mrq->data->error) {
+ host->reduce_overclock = 1;
+ if (mrq->data->error == -ETIMEDOUT)
+ mrq->data->error = -EILSEQ;
+ }
}
host->mrq = NULL;
@@ -1450,35 +1647,37 @@ int bcm2835_sdhost_add_host(struct bcm28
{
struct mmc_host *mmc;
struct dma_slave_config cfg;
+ char pio_limit_string[20];
int ret;
mmc = host->mmc;
- bcm2835_sdhost_reset(host);
+ bcm2835_sdhost_reset_internal(host);
mmc->f_max = host->max_clk;
mmc->f_min = host->max_clk / SDCDIV_MAX_CDIV;
- /* SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK */
- host->timeout_clk = mmc->f_max / 1000;
-#ifdef CONFIG_ARCH_BCM2835
- mmc->max_busy_timeout = (1 << 27) / host->timeout_clk;
-#endif
+ mmc->max_busy_timeout = (~(unsigned int)0)/(mmc->f_max/1000);
+
+ pr_debug("f_max %d, f_min %d, max_busy_timeout %d\n",
+ mmc->f_max, mmc->f_min, mmc->max_busy_timeout);
+
/* host controller capabilities */
mmc->caps |= /* MMC_CAP_SDIO_IRQ |*/ MMC_CAP_4_BIT_DATA |
MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED |
- MMC_CAP_NEEDS_POLL |
+ MMC_CAP_NEEDS_POLL | MMC_CAP_HW_RESET |
(ALLOW_CMD23 * MMC_CAP_CMD23);
spin_lock_init(&host->lock);
if (host->allow_dma) {
- if (!host->dma_chan_tx || !host->dma_chan_rx ||
- IS_ERR(host->dma_chan_tx) || IS_ERR(host->dma_chan_rx)) {
- pr_err("%s: Unable to initialise DMA channels. Falling back to PIO\n", DRIVER_NAME);
+ if (IS_ERR_OR_NULL(host->dma_chan_tx) ||
+ IS_ERR_OR_NULL(host->dma_chan_rx)) {
+ pr_err("%s: unable to initialise DMA channels. "
+ "Falling back to PIO\n",
+ mmc_hostname(mmc));
host->have_dma = false;
} else {
- pr_info("DMA channels allocated for the SDHost driver");
host->have_dma = true;
cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
@@ -1496,7 +1695,6 @@ int bcm2835_sdhost_add_host(struct bcm28
ret = dmaengine_slave_config(host->dma_chan_rx, &cfg);
}
} else {
- pr_info("Forcing PIO mode\n");
host->have_dma = false;
}
@@ -1512,18 +1710,23 @@ int bcm2835_sdhost_add_host(struct bcm28
tasklet_init(&host->finish_tasklet,
bcm2835_sdhost_tasklet_finish, (unsigned long)host);
- setup_timer(&host->timer, bcm2835_sdhost_timeout_timer, (unsigned long)host);
+ setup_timer(&host->timer, bcm2835_sdhost_timeout,
+ (unsigned long)host);
+
+ setup_timer(&host->pio_timer, bcm2835_sdhost_pio_timeout,
+ (unsigned long)host);
bcm2835_sdhost_init(host, 0);
#ifndef CONFIG_ARCH_BCM2835
ret = request_irq(host->irq, bcm2835_sdhost_irq, 0 /*IRQF_SHARED*/,
mmc_hostname(mmc), host);
#else
- ret = request_threaded_irq(host->irq, bcm2835_sdhost_irq, bcm2835_sdhost_thread_irq,
+ ret = request_threaded_irq(host->irq, bcm2835_sdhost_irq,
+ bcm2835_sdhost_thread_irq,
IRQF_SHARED, mmc_hostname(mmc), host);
#endif
if (ret) {
- pr_err("%s: Failed to request IRQ %d: %d\n",
+ pr_err("%s: failed to request IRQ %d: %d\n",
mmc_hostname(mmc), host->irq, ret);
goto untasklet;
}
@@ -1531,10 +1734,13 @@ int bcm2835_sdhost_add_host(struct bcm28
mmiowb();
mmc_add_host(mmc);
- pr_info("Load BCM2835 SDHost driver\n");
- if (host->delay_after_stop)
- pr_info("BCM2835 SDHost: delay_after_stop=%dus\n",
- host->delay_after_stop);
+ pio_limit_string[0] = '\0';
+ if (host->have_dma && (host->pio_limit > 0))
+ sprintf(pio_limit_string, " (>%d)", host->pio_limit);
+ pr_info("%s: %s loaded - DMA %s%s\n",
+ mmc_hostname(mmc), DRIVER_NAME,
+ host->have_dma ? "enabled" : "disabled",
+ pio_limit_string);
return 0;
@@ -1562,7 +1768,7 @@ static int bcm2835_sdhost_probe(struct p
mmc->ops = &bcm2835_sdhost_ops;
host = mmc_priv(mmc);
host->mmc = mmc;
- host->timeout = msecs_to_jiffies(1000);
+ host->pio_timeout = msecs_to_jiffies(500);
spin_lock_init(&host->lock);
iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -1588,8 +1794,12 @@ static int bcm2835_sdhost_probe(struct p
of_property_read_u32(node,
"brcm,overclock-50",
&host->overclock_50);
+ of_property_read_u32(node,
+ "brcm,pio-limit",
+ &host->pio_limit);
host->allow_dma = ALLOW_DMA &&
!of_property_read_bool(node, "brcm,force-pio");
+ host->debug = of_property_read_bool(node, "brcm,debug");
}
if (host->allow_dma) {