提交 f5177547 编写于 作者: L Ludovic Desroches 提交者: Chris Ball

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: NLudovic Desroches <ludovic.desroches@atmel.com>
Signed-off-by: NChris Ball <cjb@laptop.org>
上级 7a90dcc2
...@@ -45,19 +45,19 @@ ...@@ -45,19 +45,19 @@
#define ATMCI_DMA_THRESHOLD 16 #define ATMCI_DMA_THRESHOLD 16
enum { enum {
EVENT_CMD_COMPLETE = 0, EVENT_CMD_RDY = 0,
EVENT_XFER_COMPLETE, EVENT_XFER_COMPLETE,
EVENT_DATA_COMPLETE, EVENT_NOTBUSY,
EVENT_DATA_ERROR, EVENT_DATA_ERROR,
}; };
enum atmel_mci_state { enum atmel_mci_state {
STATE_IDLE = 0, STATE_IDLE = 0,
STATE_SENDING_CMD, STATE_SENDING_CMD,
STATE_SENDING_DATA, STATE_DATA_XFER,
STATE_DATA_BUSY, STATE_WAITING_NOTBUSY,
STATE_SENDING_STOP, STATE_SENDING_STOP,
STATE_DATA_ERROR, STATE_END_REQUEST,
}; };
enum atmci_xfer_dir { enum atmci_xfer_dir {
...@@ -709,7 +709,6 @@ static void atmci_pdc_complete(struct atmel_mci *host) ...@@ -709,7 +709,6 @@ static void atmci_pdc_complete(struct atmel_mci *host)
if (host->data) { if (host->data) {
atmci_set_pending(host, EVENT_XFER_COMPLETE); atmci_set_pending(host, EVENT_XFER_COMPLETE);
tasklet_schedule(&host->tasklet); 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) ...@@ -835,7 +834,7 @@ atmci_prepare_data_pdc(struct atmel_mci *host, struct mmc_data *data)
iflags |= ATMCI_ENDRX | ATMCI_RXBUFF; iflags |= ATMCI_ENDRX | ATMCI_RXBUFF;
} else { } else {
dir = DMA_TO_DEVICE; dir = DMA_TO_DEVICE;
iflags |= ATMCI_ENDTX | ATMCI_TXBUFE; iflags |= ATMCI_ENDTX | ATMCI_TXBUFE | ATMCI_BLKE;
} }
/* Set BLKLEN */ /* Set BLKLEN */
...@@ -975,8 +974,7 @@ static void atmci_stop_transfer(struct atmel_mci *host) ...@@ -975,8 +974,7 @@ static void atmci_stop_transfer(struct atmel_mci *host)
*/ */
static void atmci_stop_transfer_pdc(struct atmel_mci *host) static void atmci_stop_transfer_pdc(struct atmel_mci *host)
{ {
atmci_set_pending(host, EVENT_XFER_COMPLETE); atmci_writel(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY);
} }
static void atmci_stop_transfer_dma(struct atmel_mci *host) static void atmci_stop_transfer_dma(struct atmel_mci *host)
...@@ -1012,6 +1010,7 @@ static void atmci_start_request(struct atmel_mci *host, ...@@ -1012,6 +1010,7 @@ static void atmci_start_request(struct atmel_mci *host,
host->pending_events = 0; host->pending_events = 0;
host->completed_events = 0; host->completed_events = 0;
host->cmd_status = 0;
host->data_status = 0; host->data_status = 0;
if (host->need_reset) { if (host->need_reset) {
...@@ -1029,7 +1028,7 @@ static void atmci_start_request(struct atmel_mci *host, ...@@ -1029,7 +1028,7 @@ static void atmci_start_request(struct atmel_mci *host,
iflags = atmci_readl(host, ATMCI_IMR); iflags = atmci_readl(host, ATMCI_IMR);
if (iflags & ~(ATMCI_SDIOIRQA | ATMCI_SDIOIRQB)) 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); iflags);
if (unlikely(test_and_clear_bit(ATMCI_CARD_NEED_INIT, &slot->flags))) { 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, ...@@ -1367,19 +1366,6 @@ static void atmci_command_complete(struct atmel_mci *host,
cmd->error = -EIO; cmd->error = -EIO;
else else
cmd->error = 0; 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) static void atmci_detect_change(unsigned long data)
...@@ -1442,23 +1428,21 @@ static void atmci_detect_change(unsigned long data) ...@@ -1442,23 +1428,21 @@ static void atmci_detect_change(unsigned long data)
break; break;
case STATE_SENDING_CMD: case STATE_SENDING_CMD:
mrq->cmd->error = -ENOMEDIUM; mrq->cmd->error = -ENOMEDIUM;
if (!mrq->data) if (mrq->data)
break; host->stop_transfer(host);
/* fall through */ break;
case STATE_SENDING_DATA: case STATE_DATA_XFER:
mrq->data->error = -ENOMEDIUM; mrq->data->error = -ENOMEDIUM;
host->stop_transfer(host); host->stop_transfer(host);
break; break;
case STATE_DATA_BUSY: case STATE_WAITING_NOTBUSY:
case STATE_DATA_ERROR: mrq->data->error = -ENOMEDIUM;
if (mrq->data->error == -EINPROGRESS) break;
mrq->data->error = -ENOMEDIUM;
if (!mrq->stop)
break;
/* fall through */
case STATE_SENDING_STOP: case STATE_SENDING_STOP:
mrq->stop->error = -ENOMEDIUM; mrq->stop->error = -ENOMEDIUM;
break; break;
case STATE_END_REQUEST:
break;
} }
atmci_request_end(host, mrq); atmci_request_end(host, mrq);
...@@ -1486,7 +1470,6 @@ static void atmci_tasklet_func(unsigned long priv) ...@@ -1486,7 +1470,6 @@ static void atmci_tasklet_func(unsigned long priv)
struct atmel_mci *host = (struct atmel_mci *)priv; struct atmel_mci *host = (struct atmel_mci *)priv;
struct mmc_request *mrq = host->mrq; struct mmc_request *mrq = host->mrq;
struct mmc_data *data = host->data; struct mmc_data *data = host->data;
struct mmc_command *cmd = host->cmd;
enum atmel_mci_state state = host->state; enum atmel_mci_state state = host->state;
enum atmel_mci_state prev_state; enum atmel_mci_state prev_state;
u32 status; u32 status;
...@@ -1508,101 +1491,164 @@ static void atmci_tasklet_func(unsigned long priv) ...@@ -1508,101 +1491,164 @@ static void atmci_tasklet_func(unsigned long priv)
break; break;
case STATE_SENDING_CMD: 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, if (!atmci_test_and_clear_pending(host,
EVENT_CMD_COMPLETE)) EVENT_CMD_RDY))
break; break;
host->cmd = NULL; host->cmd = NULL;
atmci_set_completed(host, EVENT_CMD_COMPLETE); atmci_set_completed(host, EVENT_CMD_RDY);
atmci_command_complete(host, mrq->cmd); atmci_command_complete(host, mrq->cmd);
if (!mrq->data || cmd->error) { if (mrq->data) {
atmci_request_end(host, host->mrq); /*
goto unlock; * 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; break;
/* fall through */
case STATE_SENDING_DATA: case STATE_DATA_XFER:
if (atmci_test_and_clear_pending(host, if (atmci_test_and_clear_pending(host,
EVENT_DATA_ERROR)) { EVENT_DATA_ERROR)) {
host->stop_transfer(host); atmci_set_completed(host, EVENT_DATA_ERROR);
if (data->stop) state = STATE_END_REQUEST;
atmci_send_stop_cmd(host, data);
state = STATE_DATA_ERROR;
break; 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, if (!atmci_test_and_clear_pending(host,
EVENT_XFER_COMPLETE)) EVENT_XFER_COMPLETE))
break; break;
atmci_set_completed(host, EVENT_XFER_COMPLETE); atmci_set_completed(host, EVENT_XFER_COMPLETE);
prev_state = state = STATE_DATA_BUSY;
/* fall through */
case STATE_DATA_BUSY: if (host->data->flags & MMC_DATA_WRITE) {
if (!atmci_test_and_clear_pending(host, atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY);
EVENT_DATA_COMPLETE)) state = STATE_WAITING_NOTBUSY;
break; } else if (host->mrq->stop) {
atmci_writel(host, ATMCI_IER, ATMCI_CMDRDY);
host->data = NULL; atmci_send_stop_cmd(host, data);
atmci_set_completed(host, EVENT_DATA_COMPLETE); state = STATE_SENDING_STOP;
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;
}
} else { } else {
host->data = NULL;
data->bytes_xfered = data->blocks * data->blksz; data->bytes_xfered = data->blocks * data->blksz;
data->error = 0; data->error = 0;
atmci_writel(host, ATMCI_IDR, ATMCI_DATA_ERROR_FLAGS); state = STATE_END_REQUEST;
} }
break;
if (!data->stop) { case STATE_WAITING_NOTBUSY:
atmci_request_end(host, host->mrq); /*
goto unlock; * 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; atmci_set_completed(host, EVENT_NOTBUSY);
if (!data->error)
atmci_send_stop_cmd(host, data); if (host->data) {
/* fall through */ /*
* 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: 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, if (!atmci_test_and_clear_pending(host,
EVENT_CMD_COMPLETE)) EVENT_CMD_RDY))
break; break;
host->cmd = NULL; host->cmd = NULL;
host->data = NULL;
data->bytes_xfered = data->blocks * data->blksz;
data->error = 0;
atmci_command_complete(host, mrq->stop); atmci_command_complete(host, mrq->stop);
atmci_request_end(host, host->mrq); if (mrq->stop->error) {
goto unlock; 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: case STATE_END_REQUEST:
if (!atmci_test_and_clear_pending(host, atmci_writel(host, ATMCI_IDR, ATMCI_TXRDY | ATMCI_RXRDY
EVENT_XFER_COMPLETE)) | ATMCI_DATA_ERROR_FLAGS);
break; 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; break;
} }
} while (state != prev_state); } while (state != prev_state);
host->state = state; host->state = state;
unlock:
spin_unlock(&host->lock); spin_unlock(&host->lock);
} }
...@@ -1655,9 +1701,6 @@ static void atmci_read_data_pio(struct atmel_mci *host) ...@@ -1655,9 +1701,6 @@ static void atmci_read_data_pio(struct atmel_mci *host)
| ATMCI_DATA_ERROR_FLAGS)); | ATMCI_DATA_ERROR_FLAGS));
host->data_status = status; host->data_status = status;
data->bytes_xfered += nbytes; data->bytes_xfered += nbytes;
smp_wmb();
atmci_set_pending(host, EVENT_DATA_ERROR);
tasklet_schedule(&host->tasklet);
return; return;
} }
} while (status & ATMCI_RXRDY); } while (status & ATMCI_RXRDY);
...@@ -1726,9 +1769,6 @@ static void atmci_write_data_pio(struct atmel_mci *host) ...@@ -1726,9 +1769,6 @@ static void atmci_write_data_pio(struct atmel_mci *host)
| ATMCI_DATA_ERROR_FLAGS)); | ATMCI_DATA_ERROR_FLAGS));
host->data_status = status; host->data_status = status;
data->bytes_xfered += nbytes; data->bytes_xfered += nbytes;
smp_wmb();
atmci_set_pending(host, EVENT_DATA_ERROR);
tasklet_schedule(&host->tasklet);
return; return;
} }
} while (status & ATMCI_TXRDY); } while (status & ATMCI_TXRDY);
...@@ -1746,16 +1786,6 @@ static void atmci_write_data_pio(struct atmel_mci *host) ...@@ -1746,16 +1786,6 @@ static void atmci_write_data_pio(struct atmel_mci *host)
atmci_set_pending(host, EVENT_XFER_COMPLETE); 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) static void atmci_sdio_interrupt(struct atmel_mci *host, u32 status)
{ {
int i; int i;
...@@ -1784,8 +1814,9 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id) ...@@ -1784,8 +1814,9 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
if (pending & ATMCI_DATA_ERROR_FLAGS) { if (pending & ATMCI_DATA_ERROR_FLAGS) {
atmci_writel(host, ATMCI_IDR, ATMCI_DATA_ERROR_FLAGS atmci_writel(host, ATMCI_IDR, ATMCI_DATA_ERROR_FLAGS
| ATMCI_RXRDY | ATMCI_TXRDY); | ATMCI_RXRDY | ATMCI_TXRDY
pending &= atmci_readl(host, ATMCI_IMR); | ATMCI_ENDRX | ATMCI_ENDTX
| ATMCI_RXBUFF | ATMCI_TXBUFE);
host->data_status = status; host->data_status = status;
smp_wmb(); smp_wmb();
...@@ -1843,23 +1874,38 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id) ...@@ -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) { if (pending & ATMCI_NOTBUSY) {
atmci_writel(host, ATMCI_IDR, atmci_writel(host, ATMCI_IDR, ATMCI_NOTBUSY);
ATMCI_DATA_ERROR_FLAGS | ATMCI_NOTBUSY);
if (!host->data_status)
host->data_status = status;
smp_wmb(); smp_wmb();
atmci_set_pending(host, EVENT_DATA_COMPLETE); atmci_set_pending(host, EVENT_NOTBUSY);
tasklet_schedule(&host->tasklet); tasklet_schedule(&host->tasklet);
} }
if (pending & ATMCI_RXRDY) if (pending & ATMCI_RXRDY)
atmci_read_data_pio(host); atmci_read_data_pio(host);
if (pending & ATMCI_TXRDY) if (pending & ATMCI_TXRDY)
atmci_write_data_pio(host); atmci_write_data_pio(host);
if (pending & ATMCI_CMDRDY) if (pending & ATMCI_CMDRDY) {
atmci_cmd_interrupt(host, status); 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)) if (pending & (ATMCI_SDIOIRQA | ATMCI_SDIOIRQB))
atmci_sdio_interrupt(host, status); atmci_sdio_interrupt(host, status);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册