diff options
author | Alex Dubov <oakad@yahoo.com> | 2007-04-12 03:05:23 -0400 |
---|---|---|
committer | Pierre Ossman <drzeus@drzeus.cx> | 2007-05-01 07:04:14 -0400 |
commit | 72dc9d9619dd4682f4197e7a7f19af22fd6516a7 (patch) | |
tree | fc761d3189979ddfec88848fb744d8275cf812c3 | |
parent | dfef26d9aad4f983da232b259ee7f7faec479b2d (diff) |
tifm_sd: replace command completion state machine with full checking
State machine used to to track mmc command state was found to be fragile
and unreliable, making many cards unusable. The safer solution is to perform
all needed checks at every card event.
Signed-off-by: Alex Dubov <oakad@yahoo.com>
Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
-rw-r--r-- | drivers/mmc/tifm_sd.c | 259 | ||||
-rw-r--r-- | include/linux/tifm.h | 1 |
2 files changed, 138 insertions, 122 deletions
diff --git a/drivers/mmc/tifm_sd.c b/drivers/mmc/tifm_sd.c index 52499548abe8..103060f490a6 100644 --- a/drivers/mmc/tifm_sd.c +++ b/drivers/mmc/tifm_sd.c | |||
@@ -70,19 +70,14 @@ module_param(fixed_timeout, bool, 0644); | |||
70 | #define TIFM_MMCSD_CMD_AC 0x2000 | 70 | #define TIFM_MMCSD_CMD_AC 0x2000 |
71 | #define TIFM_MMCSD_CMD_ADTC 0x3000 | 71 | #define TIFM_MMCSD_CMD_ADTC 0x3000 |
72 | 72 | ||
73 | typedef enum { | ||
74 | IDLE = 0, | ||
75 | CMD, /* main command ended */ | ||
76 | BRS, /* block transfer finished */ | ||
77 | SCMD, /* stop command ended */ | ||
78 | CARD, /* card left busy state */ | ||
79 | FIFO, /* FIFO operation completed (uncertain) */ | ||
80 | READY | ||
81 | } card_state_t; | ||
82 | |||
83 | enum { | 73 | enum { |
84 | FIFO_RDY = 0x0001, /* hardware dependent value */ | 74 | CMD_READY = 0x0001, |
85 | CARD_BUSY = 0x0010 | 75 | FIFO_READY = 0x0002, |
76 | BRS_READY = 0x0004, | ||
77 | SCMD_ACTIVE = 0x0008, | ||
78 | SCMD_READY = 0x0010, | ||
79 | CARD_BUSY = 0x0020, | ||
80 | DATA_CARRY = 0x0040 | ||
86 | }; | 81 | }; |
87 | 82 | ||
88 | struct tifm_sd { | 83 | struct tifm_sd { |
@@ -92,7 +87,7 @@ struct tifm_sd { | |||
92 | open_drain:1, | 87 | open_drain:1, |
93 | no_dma:1; | 88 | no_dma:1; |
94 | unsigned short cmd_flags; | 89 | unsigned short cmd_flags; |
95 | card_state_t state; | 90 | |
96 | unsigned int clk_freq; | 91 | unsigned int clk_freq; |
97 | unsigned int clk_div; | 92 | unsigned int clk_div; |
98 | unsigned long timeout_jiffies; | 93 | unsigned long timeout_jiffies; |
@@ -234,87 +229,76 @@ static void tifm_sd_fetch_resp(struct mmc_command *cmd, struct tifm_dev *sock) | |||
234 | | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x00); | 229 | | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x00); |
235 | } | 230 | } |
236 | 231 | ||
237 | static void tifm_sd_process_cmd(struct tifm_dev *sock, struct tifm_sd *host, | 232 | static void tifm_sd_check_status(struct tifm_sd *host) |
238 | unsigned int host_status) | ||
239 | { | 233 | { |
234 | struct tifm_dev *sock = host->dev; | ||
240 | struct mmc_command *cmd = host->req->cmd; | 235 | struct mmc_command *cmd = host->req->cmd; |
241 | 236 | ||
242 | change_state: | 237 | if (cmd->error != MMC_ERR_NONE) |
243 | switch (host->state) { | 238 | goto finish_request; |
244 | case IDLE: | 239 | |
240 | if (!(host->cmd_flags & CMD_READY)) | ||
245 | return; | 241 | return; |
246 | case CMD: | 242 | |
247 | if (host_status & (TIFM_MMCSD_EOC | TIFM_MMCSD_CERR)) { | 243 | if (cmd->data) { |
248 | tifm_sd_fetch_resp(cmd, sock); | 244 | if (cmd->data->error != MMC_ERR_NONE) { |
249 | if (cmd->data) { | 245 | if ((host->cmd_flags & SCMD_ACTIVE) |
250 | host->state = BRS; | 246 | && !(host->cmd_flags & SCMD_READY)) |
251 | } else { | 247 | return; |
252 | host->state = READY; | 248 | |
253 | } | 249 | goto finish_request; |
254 | goto change_state; | ||
255 | } | ||
256 | break; | ||
257 | case BRS: | ||
258 | if (tifm_sd_transfer_data(sock, host, host_status)) { | ||
259 | if (cmd->data->flags & MMC_DATA_WRITE) { | ||
260 | host->state = CARD; | ||
261 | } else { | ||
262 | if (host->no_dma) { | ||
263 | if (host->req->stop) { | ||
264 | tifm_sd_exec(host, host->req->stop); | ||
265 | host->state = SCMD; | ||
266 | } else { | ||
267 | host->state = READY; | ||
268 | } | ||
269 | } else { | ||
270 | host->state = FIFO; | ||
271 | } | ||
272 | } | ||
273 | goto change_state; | ||
274 | } | ||
275 | break; | ||
276 | case SCMD: | ||
277 | if (host_status & TIFM_MMCSD_EOC) { | ||
278 | tifm_sd_fetch_resp(host->req->stop, sock); | ||
279 | host->state = READY; | ||
280 | goto change_state; | ||
281 | } | 250 | } |
282 | break; | 251 | |
283 | case CARD: | 252 | if (!(host->cmd_flags & BRS_READY)) |
284 | dev_dbg(&sock->dev, "waiting for CARD, have %zd blocks\n", | 253 | return; |
285 | host->written_blocks); | 254 | |
286 | if (!(host->cmd_flags & CARD_BUSY) | 255 | if (!(host->no_dma || (host->cmd_flags & FIFO_READY))) |
287 | && (host->written_blocks == cmd->data->blocks)) { | 256 | return; |
288 | if (host->no_dma) { | 257 | |
289 | if (host->req->stop) { | 258 | if (cmd->data->flags & MMC_DATA_WRITE) { |
259 | if (host->req->stop) { | ||
260 | if (!(host->cmd_flags & SCMD_ACTIVE)) { | ||
261 | host->cmd_flags |= SCMD_ACTIVE; | ||
262 | writel(TIFM_MMCSD_EOFB | ||
263 | | readl(sock->addr | ||
264 | + SOCK_MMCSD_INT_ENABLE), | ||
265 | sock->addr | ||
266 | + SOCK_MMCSD_INT_ENABLE); | ||
290 | tifm_sd_exec(host, host->req->stop); | 267 | tifm_sd_exec(host, host->req->stop); |
291 | host->state = SCMD; | 268 | return; |
292 | } else { | 269 | } else { |
293 | host->state = READY; | 270 | if (!(host->cmd_flags & SCMD_READY) |
271 | || (host->cmd_flags & CARD_BUSY)) | ||
272 | return; | ||
273 | writel((~TIFM_MMCSD_EOFB) | ||
274 | & readl(sock->addr | ||
275 | + SOCK_MMCSD_INT_ENABLE), | ||
276 | sock->addr | ||
277 | + SOCK_MMCSD_INT_ENABLE); | ||
294 | } | 278 | } |
295 | } else { | 279 | } else { |
296 | host->state = FIFO; | 280 | if (host->cmd_flags & CARD_BUSY) |
281 | return; | ||
282 | writel((~TIFM_MMCSD_EOFB) | ||
283 | & readl(sock->addr | ||
284 | + SOCK_MMCSD_INT_ENABLE), | ||
285 | sock->addr + SOCK_MMCSD_INT_ENABLE); | ||
297 | } | 286 | } |
298 | goto change_state; | 287 | } else { |
299 | } | ||
300 | break; | ||
301 | case FIFO: | ||
302 | if (host->cmd_flags & FIFO_RDY) { | ||
303 | host->cmd_flags &= ~FIFO_RDY; | ||
304 | if (host->req->stop) { | 288 | if (host->req->stop) { |
305 | tifm_sd_exec(host, host->req->stop); | 289 | if (!(host->cmd_flags & SCMD_ACTIVE)) { |
306 | host->state = SCMD; | 290 | host->cmd_flags |= SCMD_ACTIVE; |
307 | } else { | 291 | tifm_sd_exec(host, host->req->stop); |
308 | host->state = READY; | 292 | return; |
293 | } else { | ||
294 | if (!(host->cmd_flags & SCMD_READY)) | ||
295 | return; | ||
296 | } | ||
309 | } | 297 | } |
310 | goto change_state; | ||
311 | } | 298 | } |
312 | break; | ||
313 | case READY: | ||
314 | tasklet_schedule(&host->finish_tasklet); | ||
315 | return; | ||
316 | } | 299 | } |
317 | 300 | finish_request: | |
301 | tasklet_schedule(&host->finish_tasklet); | ||
318 | } | 302 | } |
319 | 303 | ||
320 | /* Called from interrupt handler */ | 304 | /* Called from interrupt handler */ |
@@ -322,21 +306,25 @@ static void tifm_sd_data_event(struct tifm_dev *sock) | |||
322 | { | 306 | { |
323 | struct tifm_sd *host; | 307 | struct tifm_sd *host; |
324 | unsigned int fifo_status = 0; | 308 | unsigned int fifo_status = 0; |
309 | struct mmc_data *r_data = NULL; | ||
325 | 310 | ||
326 | spin_lock(&sock->lock); | 311 | spin_lock(&sock->lock); |
327 | host = mmc_priv((struct mmc_host*)tifm_get_drvdata(sock)); | 312 | host = mmc_priv((struct mmc_host*)tifm_get_drvdata(sock)); |
328 | |||
329 | fifo_status = readl(sock->addr + SOCK_DMA_FIFO_STATUS); | 313 | fifo_status = readl(sock->addr + SOCK_DMA_FIFO_STATUS); |
330 | writel(fifo_status, sock->addr + SOCK_DMA_FIFO_STATUS); | 314 | dev_dbg(&sock->dev, "data event: fifo_status %x, flags %x\n", |
315 | fifo_status, host->cmd_flags); | ||
331 | 316 | ||
332 | host->cmd_flags |= fifo_status & FIFO_RDY; | 317 | if (host->req) { |
318 | r_data = host->req->cmd->data; | ||
333 | 319 | ||
334 | if (host->req) | 320 | if (r_data && (fifo_status & TIFM_FIFO_READY)) { |
335 | tifm_sd_process_cmd(sock, host, 0); | 321 | host->cmd_flags |= FIFO_READY; |
322 | tifm_sd_check_status(host); | ||
323 | } | ||
324 | } | ||
336 | 325 | ||
337 | dev_dbg(&sock->dev, "fifo_status %x\n", fifo_status); | 326 | writel(fifo_status, sock->addr + SOCK_DMA_FIFO_STATUS); |
338 | spin_unlock(&sock->lock); | 327 | spin_unlock(&sock->lock); |
339 | |||
340 | } | 328 | } |
341 | 329 | ||
342 | /* Called from interrupt handler */ | 330 | /* Called from interrupt handler */ |
@@ -344,60 +332,88 @@ static void tifm_sd_card_event(struct tifm_dev *sock) | |||
344 | { | 332 | { |
345 | struct tifm_sd *host; | 333 | struct tifm_sd *host; |
346 | unsigned int host_status = 0; | 334 | unsigned int host_status = 0; |
347 | int error_code = 0; | 335 | int cmd_error = MMC_ERR_NONE; |
336 | struct mmc_command *cmd = NULL; | ||
337 | unsigned long flags; | ||
348 | 338 | ||
349 | spin_lock(&sock->lock); | 339 | spin_lock(&sock->lock); |
350 | host = mmc_priv((struct mmc_host*)tifm_get_drvdata(sock)); | 340 | host = mmc_priv((struct mmc_host*)tifm_get_drvdata(sock)); |
341 | host_status = readl(sock->addr + SOCK_MMCSD_STATUS); | ||
342 | dev_dbg(&sock->dev, "host event: host_status %x, flags %x\n", | ||
343 | host_status, host->cmd_flags); | ||
351 | 344 | ||
352 | 345 | if (host->req) { | |
353 | host_status = readl(sock->addr + SOCK_MMCSD_STATUS); | 346 | cmd = host->req->cmd; |
354 | writel(host_status, sock->addr + SOCK_MMCSD_STATUS); | ||
355 | |||
356 | if (!host->req) | ||
357 | goto done; | ||
358 | 347 | ||
359 | if (host_status & TIFM_MMCSD_ERRMASK) { | 348 | if (host_status & TIFM_MMCSD_ERRMASK) { |
360 | if (host_status & (TIFM_MMCSD_CTO | TIFM_MMCSD_DTO)) | 349 | writel(host_status & TIFM_MMCSD_ERRMASK, |
361 | error_code = MMC_ERR_TIMEOUT; | 350 | sock->addr + SOCK_MMCSD_STATUS); |
362 | else if (host_status | 351 | if (host_status & TIFM_MMCSD_CTO) |
363 | & (TIFM_MMCSD_CCRC | TIFM_MMCSD_DCRC)) | 352 | cmd_error = MMC_ERR_TIMEOUT; |
364 | error_code = MMC_ERR_BADCRC; | 353 | else if (host_status & TIFM_MMCSD_CCRC) |
354 | cmd_error = MMC_ERR_BADCRC; | ||
355 | |||
356 | if (cmd->data) { | ||
357 | if (host_status & TIFM_MMCSD_DTO) | ||
358 | cmd->data->error = MMC_ERR_TIMEOUT; | ||
359 | else if (host_status & TIFM_MMCSD_DCRC) | ||
360 | cmd->data->error = MMC_ERR_BADCRC; | ||
361 | } | ||
365 | 362 | ||
366 | writel(TIFM_FIFO_INT_SETALL, | 363 | writel(TIFM_FIFO_INT_SETALL, |
367 | sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR); | 364 | sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR); |
368 | writel(TIFM_DMA_RESET, sock->addr + SOCK_DMA_CONTROL); | 365 | writel(TIFM_DMA_RESET, sock->addr + SOCK_DMA_CONTROL); |
369 | 366 | ||
370 | if (host->req->stop) { | 367 | if (host->req->stop) { |
371 | if (host->state == SCMD) { | 368 | if (host->cmd_flags & SCMD_ACTIVE) { |
372 | host->req->stop->error = error_code; | 369 | host->req->stop->error = cmd_error; |
373 | } else if (host->state == BRS | 370 | host->cmd_flags |= SCMD_READY; |
374 | || host->state == CARD | 371 | } else { |
375 | || host->state == FIFO) { | 372 | cmd->error = cmd_error; |
376 | host->req->cmd->error = error_code; | 373 | host->cmd_flags |= SCMD_ACTIVE; |
377 | tifm_sd_exec(host, host->req->stop); | 374 | tifm_sd_exec(host, host->req->stop); |
378 | host->state = SCMD; | ||
379 | goto done; | 375 | goto done; |
380 | } else { | ||
381 | host->req->cmd->error = error_code; | ||
382 | } | 376 | } |
383 | } else { | 377 | } else |
384 | host->req->cmd->error = error_code; | 378 | cmd->error = cmd_error; |
379 | } else { | ||
380 | if (host_status & (TIFM_MMCSD_EOC | TIFM_MMCSD_CERR)) { | ||
381 | if (!(host->cmd_flags & CMD_READY)) { | ||
382 | host->cmd_flags |= CMD_READY; | ||
383 | tifm_sd_fetch_resp(cmd, sock); | ||
384 | } else if (host->cmd_flags & SCMD_ACTIVE) { | ||
385 | host->cmd_flags |= SCMD_READY; | ||
386 | tifm_sd_fetch_resp(host->req->stop, | ||
387 | sock); | ||
388 | } | ||
385 | } | 389 | } |
386 | host->state = READY; | 390 | if (host_status & TIFM_MMCSD_BRS) |
391 | host->cmd_flags |= BRS_READY; | ||
387 | } | 392 | } |
388 | 393 | ||
389 | if (host_status & TIFM_MMCSD_CB) | 394 | if (host->no_dma && cmd->data) { |
390 | host->cmd_flags |= CARD_BUSY; | 395 | if (host_status & TIFM_MMCSD_AE) |
391 | if ((host_status & TIFM_MMCSD_EOFB) | 396 | writel(host_status & TIFM_MMCSD_AE, |
392 | && (host->cmd_flags & CARD_BUSY)) { | 397 | sock->addr + SOCK_MMCSD_STATUS); |
393 | host->written_blocks++; | 398 | |
394 | host->cmd_flags &= ~CARD_BUSY; | 399 | if (host_status & (TIFM_MMCSD_AE | TIFM_MMCSD_AF |
400 | | TIFM_MMCSD_BRS)) { | ||
401 | local_irq_save(flags); | ||
402 | tifm_sd_transfer_data(sock, host, host_status); | ||
403 | local_irq_restore(flags); | ||
404 | host_status &= ~TIFM_MMCSD_AE; | ||
405 | } | ||
395 | } | 406 | } |
396 | 407 | ||
397 | if (host->req) | 408 | if (host_status & TIFM_MMCSD_EOFB) |
398 | tifm_sd_process_cmd(sock, host, host_status); | 409 | host->cmd_flags &= ~CARD_BUSY; |
410 | else if (host_status & TIFM_MMCSD_CB) | ||
411 | host->cmd_flags |= CARD_BUSY; | ||
412 | |||
413 | tifm_sd_check_status(host); | ||
414 | } | ||
399 | done: | 415 | done: |
400 | dev_dbg(&sock->dev, "host_status %x\n", host_status); | 416 | writel(host_status, sock->addr + SOCK_MMCSD_STATUS); |
401 | spin_unlock(&sock->lock); | 417 | spin_unlock(&sock->lock); |
402 | } | 418 | } |
403 | 419 | ||
@@ -522,7 +538,7 @@ static void tifm_sd_request(struct mmc_host *mmc, struct mmc_request *mrq) | |||
522 | 538 | ||
523 | host->req = mrq; | 539 | host->req = mrq; |
524 | mod_timer(&host->timer, jiffies + host->timeout_jiffies); | 540 | mod_timer(&host->timer, jiffies + host->timeout_jiffies); |
525 | host->state = CMD; | 541 | host->cmd_flags = 0; |
526 | writel(TIFM_CTRL_LED | readl(sock->addr + SOCK_CONTROL), | 542 | writel(TIFM_CTRL_LED | readl(sock->addr + SOCK_CONTROL), |
527 | sock->addr + SOCK_CONTROL); | 543 | sock->addr + SOCK_CONTROL); |
528 | tifm_sd_exec(host, mrq->cmd); | 544 | tifm_sd_exec(host, mrq->cmd); |
@@ -553,7 +569,6 @@ static void tifm_sd_end_cmd(unsigned long data) | |||
553 | del_timer(&host->timer); | 569 | del_timer(&host->timer); |
554 | mrq = host->req; | 570 | mrq = host->req; |
555 | host->req = NULL; | 571 | host->req = NULL; |
556 | host->state = IDLE; | ||
557 | 572 | ||
558 | if (!mrq) { | 573 | if (!mrq) { |
559 | printk(KERN_ERR DRIVER_NAME ": no request to complete?\n"); | 574 | printk(KERN_ERR DRIVER_NAME ": no request to complete?\n"); |
diff --git a/include/linux/tifm.h b/include/linux/tifm.h index 82da028d8c07..c8449fcea0c7 100644 --- a/include/linux/tifm.h +++ b/include/linux/tifm.h | |||
@@ -67,6 +67,7 @@ enum { | |||
67 | #define TIFM_SOCK_STATE_POWERED 0x00000080 | 67 | #define TIFM_SOCK_STATE_POWERED 0x00000080 |
68 | 68 | ||
69 | #define TIFM_FIFO_ENABLE 0x00000001 /* Meaning of this constant is unverified */ | 69 | #define TIFM_FIFO_ENABLE 0x00000001 /* Meaning of this constant is unverified */ |
70 | #define TIFM_FIFO_READY 0x00000001 /* Meaning of this constant is unverified */ | ||
70 | #define TIFM_FIFO_INT_SETALL 0x0000ffff | 71 | #define TIFM_FIFO_INT_SETALL 0x0000ffff |
71 | #define TIFM_FIFO_INTMASK 0x00000005 /* Meaning of this constant is unverified */ | 72 | #define TIFM_FIFO_INTMASK 0x00000005 /* Meaning of this constant is unverified */ |
72 | 73 | ||