From 3b99e3ac668642719abd928743e1fce9fe1c24c9 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);
