openwrt/target/linux/brcm2708/patches-4.4/0416-bcm2835-sdhost-Improve...

313 lines
11 KiB
Diff

From d90c379ffd993c72371252fe7840c14e4428410b Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.org>
Date: Wed, 15 Jun 2016 17:13:55 +0100
Subject: [PATCH] bcm2835-sdhost: Improvements to error recovery
1) Try to avoid reducing overclock when a card is removed.
2) Reset overclock on card insertion.
3) Reduce logging when errors occur, lowering the severity of
some messages and making others conditional on the debug
flag.
4) Attempt to identify a disconnected SD bus earlier, treating a
zero returned OCR (voltage support) as an error condition.
Signed-off-by: Phil Elwell <phil@raspberrypi.org>
---
drivers/mmc/host/bcm2835-sdhost.c | 117 ++++++++++++++++++++++++--------------
1 file changed, 74 insertions(+), 43 deletions(-)
--- a/drivers/mmc/host/bcm2835-sdhost.c
+++ b/drivers/mmc/host/bcm2835-sdhost.c
@@ -38,6 +38,7 @@
#include <linux/mmc/mmc.h>
#include <linux/mmc/host.h>
#include <linux/mmc/sd.h>
+#include <linux/mmc/sdio.h>
#include <linux/scatterlist.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
@@ -206,6 +207,7 @@ 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 delay_after_this_stop; /* minimum time between this stop and subsequent data transfer */
+ u32 user_overclock_50; /* User's preferred frequency to use when 50MHz is requested (in MHz) */
u32 overclock_50; /* frequency to use when 50MHz is requested (in MHz) */
u32 overclock; /* Current frequency if overclocked, else zero */
u32 pio_limit; /* Maximum block count for PIO (0 = always DMA) */
@@ -282,7 +284,7 @@ static void log_dump(void)
do {
entry = sdhost_log_buf + idx;
if (entry->event[0] != '\0')
- pr_err("[%08x] %.4s %x %x\n",
+ pr_info("[%08x] %.4s %x %x\n",
entry->timestamp,
entry->event,
entry->param1,
@@ -324,7 +326,7 @@ static void bcm2835_sdhost_dumpcmd(struc
const char *label)
{
if (cmd)
- pr_err("%s:%c%s op %d arg 0x%x flags 0x%x - resp %08x %08x %08x %08x, err %d\n",
+ 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,
@@ -339,7 +341,7 @@ static void bcm2835_sdhost_dumpregs(stru
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",
+ pr_info("%s: data blocks %x blksz %x - err %d\n",
mmc_hostname(host->mmc),
host->mrq->data->blocks,
host->mrq->data->blksz,
@@ -347,53 +349,53 @@ static void bcm2835_sdhost_dumpregs(stru
bcm2835_sdhost_dumpcmd(host, host->mrq->stop, "stop");
}
- pr_err("%s: =========== REGISTER DUMP ===========\n",
+ pr_info("%s: =========== REGISTER DUMP ===========\n",
mmc_hostname(host->mmc));
- pr_err("%s: SDCMD 0x%08x\n",
+ pr_info("%s: SDCMD 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDCMD));
- pr_err("%s: SDARG 0x%08x\n",
+ pr_info("%s: SDARG 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDARG));
- pr_err("%s: SDTOUT 0x%08x\n",
+ pr_info("%s: SDTOUT 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDTOUT));
- pr_err("%s: SDCDIV 0x%08x\n",
+ pr_info("%s: SDCDIV 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDCDIV));
- pr_err("%s: SDRSP0 0x%08x\n",
+ pr_info("%s: SDRSP0 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDRSP0));
- pr_err("%s: SDRSP1 0x%08x\n",
+ pr_info("%s: SDRSP1 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDRSP1));
- pr_err("%s: SDRSP2 0x%08x\n",
+ pr_info("%s: SDRSP2 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDRSP2));
pr_err("%s: SDRSP3 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDRSP3));
- pr_err("%s: SDHSTS 0x%08x\n",
+ pr_info("%s: SDHSTS 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDHSTS));
- pr_err("%s: SDVDD 0x%08x\n",
+ pr_info("%s: SDVDD 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDVDD));
- pr_err("%s: SDEDM 0x%08x\n",
+ pr_info("%s: SDEDM 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDEDM));
- pr_err("%s: SDHCFG 0x%08x\n",
+ pr_info("%s: SDHCFG 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDHCFG));
- pr_err("%s: SDHBCT 0x%08x\n",
+ pr_info("%s: SDHBCT 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDHBCT));
- pr_err("%s: SDHBLC 0x%08x\n",
+ pr_info("%s: SDHBLC 0x%08x\n",
mmc_hostname(host->mmc),
bcm2835_sdhost_read(host, SDHBLC));
- pr_err("%s: ===========================================\n",
+ pr_info("%s: ===========================================\n",
mmc_hostname(host->mmc));
}
@@ -608,7 +610,7 @@ static void bcm2835_sdhost_read_block_pi
(fsm_state != SDEDM_FSM_READCRC)) {
hsts = bcm2835_sdhost_read(host,
SDHSTS);
- pr_err("%s: fsm %x, hsts %x\n",
+ pr_info("%s: fsm %x, hsts %x\n",
mmc_hostname(host->mmc),
fsm_state, hsts);
if (hsts & SDHSTS_ERROR_MASK)
@@ -698,7 +700,7 @@ static void bcm2835_sdhost_write_block_p
(fsm_state != SDEDM_FSM_WRITESTART2)) {
hsts = bcm2835_sdhost_read(host,
SDHSTS);
- pr_err("%s: fsm %x, hsts %x\n",
+ pr_info("%s: fsm %x, hsts %x\n",
mmc_hostname(host->mmc),
fsm_state, hsts);
if (hsts & SDHSTS_ERROR_MASK)
@@ -953,9 +955,10 @@ bool bcm2835_sdhost_send_command(struct
while (bcm2835_sdhost_read(host, SDCMD) & SDCMD_NEW_FLAG) {
if (timeout == 0) {
- pr_err("%s: previous command never completed.\n",
+ pr_warn("%s: previous command never completed.\n",
mmc_hostname(host->mmc));
- bcm2835_sdhost_dumpregs(host);
+ if (host->debug)
+ bcm2835_sdhost_dumpregs(host);
cmd->error = -EILSEQ;
tasklet_schedule(&host->finish_tasklet);
return false;
@@ -1213,10 +1216,12 @@ static void bcm2835_sdhost_finish_comman
/* Check for errors */
if (sdcmd & SDCMD_NEW_FLAG) {
- pr_err("%s: command never completed.\n",
- mmc_hostname(host->mmc));
- bcm2835_sdhost_dumpregs(host);
- host->cmd->error = -EIO;
+ if (host->debug) {
+ pr_err("%s: command %d never completed.\n",
+ mmc_hostname(host->mmc), host->cmd->opcode);
+ bcm2835_sdhost_dumpregs(host);
+ }
+ host->cmd->error = -EILSEQ;
tasklet_schedule(&host->finish_tasklet);
return;
} else if (sdcmd & SDCMD_FAIL_FLAG) {
@@ -1238,15 +1243,14 @@ static void bcm2835_sdhost_finish_comman
} else {
if (sdhsts & SDHSTS_CMD_TIME_OUT) {
if (host->debug)
- pr_err("%s: command %d timeout\n",
+ pr_warn("%s: command %d timeout\n",
mmc_hostname(host->mmc),
host->cmd->opcode);
host->cmd->error = -ETIMEDOUT;
} else {
- pr_err("%s: unexpected command %d error\n",
+ pr_warn("%s: unexpected command %d error\n",
mmc_hostname(host->mmc),
host->cmd->opcode);
- bcm2835_sdhost_dumpregs(host);
host->cmd->error = -EILSEQ;
}
tasklet_schedule(&host->finish_tasklet);
@@ -1370,8 +1374,10 @@ static void bcm2835_sdhost_busy_irq(stru
} else if (intmask & SDHSTS_CMD_TIME_OUT)
host->cmd->error = -ETIMEDOUT;
- log_dump();
- bcm2835_sdhost_dumpregs(host);
+ if (host->debug) {
+ log_dump();
+ bcm2835_sdhost_dumpregs(host);
+ }
}
else
bcm2835_sdhost_finish_command(host, NULL);
@@ -1595,7 +1601,7 @@ void bcm2835_sdhost_set_clock(struct bcm
host->overclock_50 = (clock/MHZ);
if (clock != host->overclock) {
- pr_warn("%s: overclocking to %dHz\n",
+ pr_info("%s: overclocking to %dHz\n",
mmc_hostname(host->mmc), clock);
host->overclock = clock;
}
@@ -1605,6 +1611,11 @@ void bcm2835_sdhost_set_clock(struct bcm
pr_warn("%s: cancelling overclock\n",
mmc_hostname(host->mmc));
}
+ } else if (input_clock == 0) {
+ /* Reset the preferred overclock when the clock is stopped.
+ * This always happens during initialisation. */
+ host->overclock_50 = host->user_overclock_50;
+ host->overclock = 0;
}
/* Set the timeout to 500ms */
@@ -1678,13 +1689,15 @@ static void bcm2835_sdhost_request(struc
log_event("REQ<", (u32)mrq, edm);
if ((fsm != SDEDM_FSM_IDENTMODE) &&
(fsm != SDEDM_FSM_DATAMODE)) {
- pr_err("%s: previous command (%d) not complete (EDM %x)\n",
- mmc_hostname(host->mmc),
- bcm2835_sdhost_read(host, SDCMD) & SDCMD_CMD_MASK,
- edm);
log_event("REQ!", (u32)mrq, edm);
- log_dump();
- bcm2835_sdhost_dumpregs(host);
+ if (host->debug) {
+ pr_warn("%s: previous command (%d) not complete (EDM %x)\n",
+ mmc_hostname(host->mmc),
+ bcm2835_sdhost_read(host, SDCMD) & SDCMD_CMD_MASK,
+ edm);
+ log_dump();
+ bcm2835_sdhost_dumpregs(host);
+ }
mrq->cmd->error = -EILSEQ;
tasklet_schedule(&host->finish_tasklet);
mmiowb();
@@ -1814,16 +1827,19 @@ static void bcm2835_sdhost_tasklet_finis
mrq = host->mrq;
/* Drop the overclock after any data corruption, or after any
- error overclocked */
+ * error while overclocked. Ignore errors for status commands,
+ * as they are likely when a card is ejected. */
if (host->overclock) {
- if ((mrq->cmd && mrq->cmd->error) ||
+ if ((mrq->cmd && mrq->cmd->error &&
+ (mrq->cmd->opcode != MMC_SEND_STATUS)) ||
(mrq->data && mrq->data->error) ||
- (mrq->stop && mrq->stop->error)) {
+ (mrq->stop && mrq->stop->error) ||
+ (mrq->sbc && mrq->sbc->error)) {
host->overclock_50--;
pr_warn("%s: reducing overclock due to errors\n",
mmc_hostname(host->mmc));
host->reset_clock = 1;
- mrq->cmd->error = -EILSEQ;
+ mrq->cmd->error = -ETIMEDOUT;
mrq->cmd->retries = 1;
}
}
@@ -1848,6 +1864,21 @@ static void bcm2835_sdhost_tasklet_finis
mmc_hostname(host->mmc), err);
}
+ /* The SDHOST block doesn't report any errors for a disconnected
+ interface. All cards and SDIO devices should report some supported
+ voltage range, so a zero response to SEND_OP_COND, IO_SEND_OP_COND
+ or APP_SEND_OP_COND can be treated as an error. */
+ if (((mrq->cmd->opcode == MMC_SEND_OP_COND) ||
+ (mrq->cmd->opcode == SD_IO_SEND_OP_COND) ||
+ (mrq->cmd->opcode == SD_APP_OP_COND)) &&
+ (mrq->cmd->error == 0) &&
+ (mrq->cmd->resp[0] == 0)) {
+ mrq->cmd->error = -ETIMEDOUT;
+ if (host->debug)
+ pr_info("%s: faking timeout due to zero OCR\n",
+ mmc_hostname(host->mmc));
+ }
+
mmc_request_done(host->mmc, mrq);
log_event("TSK>", (u32)mrq, 0);
}
@@ -2023,7 +2054,7 @@ static int bcm2835_sdhost_probe(struct p
&host->delay_after_stop);
of_property_read_u32(node,
"brcm,overclock-50",
- &host->overclock_50);
+ &host->user_overclock_50);
of_property_read_u32(node,
"brcm,pio-limit",
&host->pio_limit);