Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f5177547 authored by Ludovic Desroches's avatar Ludovic Desroches Committed by Chris Ball
Browse files

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: default avatarLudovic Desroches <ludovic.desroches@atmel.com>
Signed-off-by: default avatarChris Ball <cjb@laptop.org>
parent 7a90dcc2
Loading
Loading
Loading
Loading
+162 −116
Original line number Original line Diff line number Diff line
@@ -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)
	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)
		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)
 */
 */
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,


	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,


	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,
		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)
					break;
					break;
				case STATE_SENDING_CMD:
				case STATE_SENDING_CMD:
					mrq->cmd->error = -ENOMEDIUM;
					mrq->cmd->error = -ENOMEDIUM;
					if (!mrq->data)
					if (mrq->data)
						host->stop_transfer(host);
					break;
					break;
					/* fall through */
				case STATE_DATA_XFER:
				case STATE_SENDING_DATA:
					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:
					if (mrq->data->error == -EINPROGRESS)
					mrq->data->error = -ENOMEDIUM;
					mrq->data->error = -ENOMEDIUM;
					if (!mrq->stop)
					break;
					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)
	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)
			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;

			atmci_set_completed(host, EVENT_NOTBUSY);


			prev_state = state = STATE_SENDING_STOP;
			if (host->data) {
			if (!data->error)
				/*
				 * 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);
					atmci_send_stop_cmd(host, data);
			/* fall through */
					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,
		case STATE_DATA_ERROR:
				             ATMCI_TXRDY | ATMCI_RXRDY
			if (!atmci_test_and_clear_pending(host,
				             | ATMCI_DATA_ERROR_FLAGS);
						EVENT_XFER_COMPLETE))
				state = STATE_END_REQUEST;
			} else {
				atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY);
				state = STATE_WAITING_NOTBUSY;
			}
			break;
			break;


			state = STATE_DATA_BUSY;
		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;
				}
			}

			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)
						| 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)
						| 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)
	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)


		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)
			}
			}
		}
		}


		/*
		 * 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);