aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/brcm2708/patches-4.4/0415-bcm2835-sdhost-Improvements-to-error-recovery.patch
blob: 88d9d4db5029730377b7afdd58ee25f050e6b55c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
From edeb743e7562d9fa4d5f19c557bf558036031973 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);