mmc: atmel-mci: change the state machine for compatibility with old IP
authorLudovic Desroches <ludovic.desroches@atmel.com>
Wed, 16 May 2012 13:25:59 +0000 (15:25 +0200)
committerChris Ball <cjb@laptop.org>
Thu, 17 May 2012 12:41:34 +0000 (08:41 -0400)
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>
drivers/mmc/host/atmel-mci.c

index 6f56ef025ab5a81c09c5edf28107eafb66891242..1baaaebbeda8a789877dd582f704d50adde0c0b3 100644 (file)
 #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);