diff options
author | Ludovic Desroches <ludovic.desroches@atmel.com> | 2012-05-16 15:25:59 +0200 |
---|---|---|
committer | Chris Ball <cjb@laptop.org> | 2012-05-17 08:41:34 -0400 |
commit | f51775471ac6155d3bb8494dcb5c0a13a84f611e (patch) | |
tree | 4d3e837b287c732c867f436df79bfdc559432df9 | |
parent | 7a90dcc2d7ceb64bb37044a8d2ee462b936ddf73 (diff) |
mmc: atmel-mci: change the state machine for compatibility with old IP
The state machine use in atmel-mci can't work with old IP versions
(< 0x200). This patch allows to have a common state machine for all
versions in order to remove at91-mci driver only used for old versions.
Signed-off-by: Ludovic Desroches <ludovic.desroches@atmel.com>
Signed-off-by: Chris Ball <cjb@laptop.org>
-rw-r--r-- | drivers/mmc/host/atmel-mci.c | 278 |
1 files changed, 162 insertions, 116 deletions
diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c index 6f56ef025ab5..1baaaebbeda8 100644 --- a/drivers/mmc/host/atmel-mci.c +++ b/drivers/mmc/host/atmel-mci.c @@ -45,19 +45,19 @@ #define ATMCI_DMA_THRESHOLD 16 enum { - EVENT_CMD_COMPLETE = 0, + EVENT_CMD_RDY = 0, EVENT_XFER_COMPLETE, - EVENT_DATA_COMPLETE, + EVENT_NOTBUSY, EVENT_DATA_ERROR, }; enum atmel_mci_state { STATE_IDLE = 0, STATE_SENDING_CMD, - STATE_SENDING_DATA, - STATE_DATA_BUSY, + STATE_DATA_XFER, + STATE_WAITING_NOTBUSY, STATE_SENDING_STOP, - STATE_DATA_ERROR, + STATE_END_REQUEST, }; enum atmci_xfer_dir { @@ -709,7 +709,6 @@ static void atmci_pdc_complete(struct atmel_mci *host) if (host->data) { atmci_set_pending(host, EVENT_XFER_COMPLETE); tasklet_schedule(&host->tasklet); - atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY); } } @@ -835,7 +834,7 @@ atmci_prepare_data_pdc(struct atmel_mci *host, struct mmc_data *data) iflags |= ATMCI_ENDRX | ATMCI_RXBUFF; } else { dir = DMA_TO_DEVICE; - iflags |= ATMCI_ENDTX | ATMCI_TXBUFE; + iflags |= ATMCI_ENDTX | ATMCI_TXBUFE | ATMCI_BLKE; } /* Set BLKLEN */ @@ -975,8 +974,7 @@ static void atmci_stop_transfer(struct atmel_mci *host) */ static void atmci_stop_transfer_pdc(struct atmel_mci *host) { - atmci_set_pending(host, EVENT_XFER_COMPLETE); - atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY); + atmci_writel(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS); } static void atmci_stop_transfer_dma(struct atmel_mci *host) @@ -1012,6 +1010,7 @@ static void atmci_start_request(struct atmel_mci *host, host->pending_events = 0; host->completed_events = 0; + host->cmd_status = 0; host->data_status = 0; if (host->need_reset) { @@ -1029,7 +1028,7 @@ static void atmci_start_request(struct atmel_mci *host, iflags = atmci_readl(host, ATMCI_IMR); if (iflags & ~(ATMCI_SDIOIRQA | ATMCI_SDIOIRQB)) - dev_warn(&slot->mmc->class_dev, "WARNING: IMR=0x%08x\n", + dev_dbg(&slot->mmc->class_dev, "WARNING: IMR=0x%08x\n", iflags); if (unlikely(test_and_clear_bit(ATMCI_CARD_NEED_INIT, &slot->flags))) { @@ -1367,19 +1366,6 @@ static void atmci_command_complete(struct atmel_mci *host, cmd->error = -EIO; else cmd->error = 0; - - if (cmd->error) { - dev_dbg(&host->pdev->dev, - "command error: status=0x%08x\n", status); - - if (cmd->data) { - host->stop_transfer(host); - host->data = NULL; - atmci_writel(host, ATMCI_IDR, ATMCI_NOTBUSY - | ATMCI_TXRDY | ATMCI_RXRDY - | ATMCI_DATA_ERROR_FLAGS); - } - } } static void atmci_detect_change(unsigned long data) @@ -1442,23 +1428,21 @@ static void atmci_detect_change(unsigned long data) break; case STATE_SENDING_CMD: mrq->cmd->error = -ENOMEDIUM; - if (!mrq->data) - break; - /* fall through */ - case STATE_SENDING_DATA: + if (mrq->data) + host->stop_transfer(host); + break; + case STATE_DATA_XFER: mrq->data->error = -ENOMEDIUM; host->stop_transfer(host); break; - case STATE_DATA_BUSY: - case STATE_DATA_ERROR: - if (mrq->data->error == -EINPROGRESS) - mrq->data->error = -ENOMEDIUM; - if (!mrq->stop) - break; - /* fall through */ + case STATE_WAITING_NOTBUSY: + mrq->data->error = -ENOMEDIUM; + break; case STATE_SENDING_STOP: mrq->stop->error = -ENOMEDIUM; break; + case STATE_END_REQUEST: + break; } atmci_request_end(host, mrq); @@ -1486,7 +1470,6 @@ static void atmci_tasklet_func(unsigned long priv) struct atmel_mci *host = (struct atmel_mci *)priv; struct mmc_request *mrq = host->mrq; struct mmc_data *data = host->data; - struct mmc_command *cmd = host->cmd; enum atmel_mci_state state = host->state; enum atmel_mci_state prev_state; u32 status; @@ -1508,101 +1491,164 @@ static void atmci_tasklet_func(unsigned long priv) break; case STATE_SENDING_CMD: + /* + * Command has been sent, we are waiting for command + * ready. Then we have three next states possible: + * END_REQUEST by default, WAITING_NOTBUSY if it's a + * command needing it or DATA_XFER if there is data. + */ if (!atmci_test_and_clear_pending(host, - EVENT_CMD_COMPLETE)) + EVENT_CMD_RDY)) break; host->cmd = NULL; - atmci_set_completed(host, EVENT_CMD_COMPLETE); + atmci_set_completed(host, EVENT_CMD_RDY); atmci_command_complete(host, mrq->cmd); - if (!mrq->data || cmd->error) { - atmci_request_end(host, host->mrq); - goto unlock; - } + if (mrq->data) { + /* + * If there is a command error don't start + * data transfer. + */ + if (mrq->cmd->error) { + host->stop_transfer(host); + host->data = NULL; + atmci_writel(host, ATMCI_IDR, + ATMCI_TXRDY | ATMCI_RXRDY + | ATMCI_DATA_ERROR_FLAGS); + state = STATE_END_REQUEST; + } else + state = STATE_DATA_XFER; + } else if ((!mrq->data) && (mrq->cmd->flags & MMC_RSP_BUSY)) { + atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY); + state = STATE_WAITING_NOTBUSY; + } else + state = STATE_END_REQUEST; - prev_state = state = STATE_SENDING_DATA; - /* fall through */ + break; - case STATE_SENDING_DATA: + case STATE_DATA_XFER: if (atmci_test_and_clear_pending(host, EVENT_DATA_ERROR)) { - host->stop_transfer(host); - if (data->stop) - atmci_send_stop_cmd(host, data); - state = STATE_DATA_ERROR; + atmci_set_completed(host, EVENT_DATA_ERROR); + state = STATE_END_REQUEST; break; } + /* + * A data transfer is in progress. The event expected + * to move to the next state depends of data transfer + * type (PDC or DMA). Once transfer done we can move + * to the next step which is WAITING_NOTBUSY in write + * case and directly SENDING_STOP in read case. + */ if (!atmci_test_and_clear_pending(host, EVENT_XFER_COMPLETE)) break; atmci_set_completed(host, EVENT_XFER_COMPLETE); - prev_state = state = STATE_DATA_BUSY; - /* fall through */ - case STATE_DATA_BUSY: - if (!atmci_test_and_clear_pending(host, - EVENT_DATA_COMPLETE)) - break; - - host->data = NULL; - atmci_set_completed(host, EVENT_DATA_COMPLETE); - status = host->data_status; - if (unlikely(status & ATMCI_DATA_ERROR_FLAGS)) { - if (status & ATMCI_DTOE) { - dev_dbg(&host->pdev->dev, - "data timeout error\n"); - data->error = -ETIMEDOUT; - } else if (status & ATMCI_DCRCE) { - dev_dbg(&host->pdev->dev, - "data CRC error\n"); - data->error = -EILSEQ; - } else { - dev_dbg(&host->pdev->dev, - "data FIFO error (status=%08x)\n", - status); - data->error = -EIO; - } + if (host->data->flags & MMC_DATA_WRITE) { + atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY); + state = STATE_WAITING_NOTBUSY; + } else if (host->mrq->stop) { + atmci_writel(host, ATMCI_IER, ATMCI_CMDRDY); + atmci_send_stop_cmd(host, data); + state = STATE_SENDING_STOP; } else { + host->data = NULL; data->bytes_xfered = data->blocks * data->blksz; data->error = 0; - atmci_writel(host, ATMCI_IDR, ATMCI_DATA_ERROR_FLAGS); + state = STATE_END_REQUEST; } + break; - if (!data->stop) { - atmci_request_end(host, host->mrq); - goto unlock; - } + case STATE_WAITING_NOTBUSY: + /* + * We can be in the state for two reasons: a command + * requiring waiting not busy signal (stop command + * included) or a write operation. In the latest case, + * we need to send a stop command. + */ + if (!atmci_test_and_clear_pending(host, + EVENT_NOTBUSY)) + break; - prev_state = state = STATE_SENDING_STOP; - if (!data->error) - atmci_send_stop_cmd(host, data); - /* fall through */ + atmci_set_completed(host, EVENT_NOTBUSY); + + if (host->data) { + /* + * For some commands such as CMD53, even if + * there is data transfer, there is no stop + * command to send. + */ + if (host->mrq->stop) { + atmci_writel(host, ATMCI_IER, + ATMCI_CMDRDY); + atmci_send_stop_cmd(host, data); + state = STATE_SENDING_STOP; + } else { + host->data = NULL; + data->bytes_xfered = data->blocks + * data->blksz; + data->error = 0; + state = STATE_END_REQUEST; + } + } else + state = STATE_END_REQUEST; + break; case STATE_SENDING_STOP: + /* + * In this state, it is important to set host->data to + * NULL (which is tested in the waiting notbusy state) + * in order to go to the end request state instead of + * sending stop again. + */ if (!atmci_test_and_clear_pending(host, - EVENT_CMD_COMPLETE)) + EVENT_CMD_RDY)) break; host->cmd = NULL; + host->data = NULL; + data->bytes_xfered = data->blocks * data->blksz; + data->error = 0; atmci_command_complete(host, mrq->stop); - atmci_request_end(host, host->mrq); - goto unlock; + if (mrq->stop->error) { + host->stop_transfer(host); + atmci_writel(host, ATMCI_IDR, + ATMCI_TXRDY | ATMCI_RXRDY + | ATMCI_DATA_ERROR_FLAGS); + state = STATE_END_REQUEST; + } else { + atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY); + state = STATE_WAITING_NOTBUSY; + } + break; - case STATE_DATA_ERROR: - if (!atmci_test_and_clear_pending(host, - EVENT_XFER_COMPLETE)) - break; + case STATE_END_REQUEST: + atmci_writel(host, ATMCI_IDR, ATMCI_TXRDY | ATMCI_RXRDY + | ATMCI_DATA_ERROR_FLAGS); + status = host->data_status; + if (unlikely(status)) { + host->stop_transfer(host); + host->data = NULL; + if (status & ATMCI_DTOE) { + data->error = -ETIMEDOUT; + } else if (status & ATMCI_DCRCE) { + data->error = -EILSEQ; + } else { + data->error = -EIO; + } + } - state = STATE_DATA_BUSY; + atmci_request_end(host, host->mrq); + state = STATE_IDLE; break; } } while (state != prev_state); host->state = state; -unlock: spin_unlock(&host->lock); } @@ -1655,9 +1701,6 @@ static void atmci_read_data_pio(struct atmel_mci *host) | ATMCI_DATA_ERROR_FLAGS)); host->data_status = status; data->bytes_xfered += nbytes; - smp_wmb(); - atmci_set_pending(host, EVENT_DATA_ERROR); - tasklet_schedule(&host->tasklet); return; } } while (status & ATMCI_RXRDY); @@ -1726,9 +1769,6 @@ static void atmci_write_data_pio(struct atmel_mci *host) | ATMCI_DATA_ERROR_FLAGS)); host->data_status = status; data->bytes_xfered += nbytes; - smp_wmb(); - atmci_set_pending(host, EVENT_DATA_ERROR); - tasklet_schedule(&host->tasklet); return; } } while (status & ATMCI_TXRDY); @@ -1746,16 +1786,6 @@ done: atmci_set_pending(host, EVENT_XFER_COMPLETE); } -static void atmci_cmd_interrupt(struct atmel_mci *host, u32 status) -{ - atmci_writel(host, ATMCI_IDR, ATMCI_CMDRDY); - - host->cmd_status = status; - smp_wmb(); - atmci_set_pending(host, EVENT_CMD_COMPLETE); - tasklet_schedule(&host->tasklet); -} - static void atmci_sdio_interrupt(struct atmel_mci *host, u32 status) { int i; @@ -1784,8 +1814,9 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id) if (pending & ATMCI_DATA_ERROR_FLAGS) { atmci_writel(host, ATMCI_IDR, ATMCI_DATA_ERROR_FLAGS - | ATMCI_RXRDY | ATMCI_TXRDY); - pending &= atmci_readl(host, ATMCI_IMR); + | ATMCI_RXRDY | ATMCI_TXRDY + | ATMCI_ENDRX | ATMCI_ENDTX + | ATMCI_RXBUFF | ATMCI_TXBUFE); host->data_status = status; smp_wmb(); @@ -1843,23 +1874,38 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id) } } + /* + * First mci IPs, so mainly the ones having pdc, have some + * issues with the notbusy signal. You can't get it after + * data transmission if you have not sent a stop command. + * The appropriate workaround is to use the BLKE signal. + */ + if (pending & ATMCI_BLKE) { + atmci_writel(host, ATMCI_IDR, ATMCI_BLKE); + smp_wmb(); + atmci_set_pending(host, EVENT_NOTBUSY); + tasklet_schedule(&host->tasklet); + } if (pending & ATMCI_NOTBUSY) { - atmci_writel(host, ATMCI_IDR, - ATMCI_DATA_ERROR_FLAGS | ATMCI_NOTBUSY); - if (!host->data_status) - host->data_status = status; + atmci_writel(host, ATMCI_IDR, ATMCI_NOTBUSY); smp_wmb(); - atmci_set_pending(host, EVENT_DATA_COMPLETE); + atmci_set_pending(host, EVENT_NOTBUSY); tasklet_schedule(&host->tasklet); } + if (pending & ATMCI_RXRDY) atmci_read_data_pio(host); if (pending & ATMCI_TXRDY) atmci_write_data_pio(host); - if (pending & ATMCI_CMDRDY) - atmci_cmd_interrupt(host, status); + if (pending & ATMCI_CMDRDY) { + atmci_writel(host, ATMCI_IDR, ATMCI_CMDRDY); + host->cmd_status = status; + smp_wmb(); + atmci_set_pending(host, EVENT_CMD_RDY); + tasklet_schedule(&host->tasklet); + } if (pending & (ATMCI_SDIOIRQA | ATMCI_SDIOIRQB)) atmci_sdio_interrupt(host, status); |