Skip to content

Commit

Permalink
bcm2835-sdhost: Improvements to error recovery
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Phil Elwell authored and popcornmix committed Jul 1, 2016
1 parent 60d4b89 commit d3995c1
Showing 1 changed file with 74 additions and 43 deletions.
117 changes: 74 additions & 43 deletions drivers/mmc/host/bcm2835-sdhost.c
Expand Up @@ -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>
Expand Down Expand Up @@ -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) */
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -324,7 +326,7 @@ static void bcm2835_sdhost_dumpcmd(struct bcm2835_host *host,
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,
Expand All @@ -339,61 +341,61 @@ static void bcm2835_sdhost_dumpregs(struct bcm2835_host *host)
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,
host->mrq->data->error);
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));
}

Expand Down Expand Up @@ -608,7 +610,7 @@ static void bcm2835_sdhost_read_block_pio(struct bcm2835_host *host)
(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)
Expand Down Expand Up @@ -698,7 +700,7 @@ static void bcm2835_sdhost_write_block_pio(struct bcm2835_host *host)
(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)
Expand Down Expand Up @@ -953,9 +955,10 @@ bool bcm2835_sdhost_send_command(struct bcm2835_host *host,

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;
Expand Down Expand Up @@ -1213,10 +1216,12 @@ static void bcm2835_sdhost_finish_command(struct bcm2835_host *host,

/* 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) {
Expand All @@ -1238,15 +1243,14 @@ static void bcm2835_sdhost_finish_command(struct bcm2835_host *host,
} 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);
Expand Down Expand Up @@ -1370,8 +1374,10 @@ static void bcm2835_sdhost_busy_irq(struct bcm2835_host *host, u32 intmask)
} 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);
Expand Down Expand Up @@ -1595,7 +1601,7 @@ void bcm2835_sdhost_set_clock(struct bcm2835_host *host, unsigned int clock)
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;
}
Expand All @@ -1605,6 +1611,11 @@ void bcm2835_sdhost_set_clock(struct bcm2835_host *host, unsigned int clock)
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 */
Expand Down Expand Up @@ -1678,13 +1689,15 @@ static void bcm2835_sdhost_request(struct mmc_host *mmc, struct mmc_request *mrq
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();
Expand Down Expand Up @@ -1814,16 +1827,19 @@ static void bcm2835_sdhost_tasklet_finish(unsigned long param)
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;
}
}
Expand All @@ -1848,6 +1864,21 @@ static void bcm2835_sdhost_tasklet_finish(unsigned long param)
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);
}
Expand Down Expand Up @@ -2023,7 +2054,7 @@ static int bcm2835_sdhost_probe(struct platform_device *pdev)
&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);
Expand Down

0 comments on commit d3995c1

Please sign in to comment.