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

Commit 90c2143a authored by Seungwon Jeon's avatar Seungwon Jeon Committed by Chris Ball
Browse files

mmc: dw_mmc: guarantee stop-abort cmd in data errors



In error cases, DTO interrupt may or may not be generated depending
on remained data. Stop/Abort command ensures DTO generation for that
situation. Currently if 'stop' field of data is empty, there is no
stop/abort command. So, it could hang waiting DTO. This change
reinforces these cases.

Signed-off-by: default avatarSeungwon Jeon <tgih.jun@samsung.com>
Tested-by: default avatarAlim Akhtar <alim.akhtar@samsung.com>
Signed-off-by: default avatarChris Ball <cjb@laptop.org>
parent 71abb133
Loading
Loading
Loading
Loading
+64 −20
Original line number Original line Diff line number Diff line
@@ -29,6 +29,7 @@
#include <linux/irq.h>
#include <linux/irq.h>
#include <linux/mmc/host.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/dw_mmc.h>
#include <linux/mmc/dw_mmc.h>
#include <linux/bitops.h>
#include <linux/bitops.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/consumer.h>
@@ -246,9 +247,14 @@ static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd)


	cmdr = cmd->opcode;
	cmdr = cmd->opcode;


	if (cmdr == MMC_STOP_TRANSMISSION)
	if (cmd->opcode == MMC_STOP_TRANSMISSION ||
	    cmd->opcode == MMC_GO_IDLE_STATE ||
	    cmd->opcode == MMC_GO_INACTIVE_STATE ||
	    (cmd->opcode == SD_IO_RW_DIRECT &&
	     ((cmd->arg >> 9) & 0x1FFFF) == SDIO_CCCR_ABORT))
		cmdr |= SDMMC_CMD_STOP;
		cmdr |= SDMMC_CMD_STOP;
	else
	else
		if (cmd->opcode != MMC_SEND_STATUS && cmd->data)
			cmdr |= SDMMC_CMD_PRV_DAT_WAIT;
			cmdr |= SDMMC_CMD_PRV_DAT_WAIT;


	if (cmd->flags & MMC_RSP_PRESENT) {
	if (cmd->flags & MMC_RSP_PRESENT) {
@@ -276,6 +282,40 @@ static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd)
	return cmdr;
	return cmdr;
}
}


static u32 dw_mci_prep_stop_abort(struct dw_mci *host, struct mmc_command *cmd)
{
	struct mmc_command *stop;
	u32 cmdr;

	if (!cmd->data)
		return 0;

	stop = &host->stop_abort;
	cmdr = cmd->opcode;
	memset(stop, 0, sizeof(struct mmc_command));

	if (cmdr == MMC_READ_SINGLE_BLOCK ||
	    cmdr == MMC_READ_MULTIPLE_BLOCK ||
	    cmdr == MMC_WRITE_BLOCK ||
	    cmdr == MMC_WRITE_MULTIPLE_BLOCK) {
		stop->opcode = MMC_STOP_TRANSMISSION;
		stop->arg = 0;
		stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
	} else if (cmdr == SD_IO_RW_EXTENDED) {
		stop->opcode = SD_IO_RW_DIRECT;
		stop->arg |= (1 << 31) | (0 << 28) | (SDIO_CCCR_ABORT << 9) |
			     ((cmd->arg >> 28) & 0x7);
		stop->flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC;
	} else {
		return 0;
	}

	cmdr = stop->opcode | SDMMC_CMD_STOP |
		SDMMC_CMD_RESP_CRC | SDMMC_CMD_RESP_EXP;

	return cmdr;
}

static void dw_mci_start_command(struct dw_mci *host,
static void dw_mci_start_command(struct dw_mci *host,
				 struct mmc_command *cmd, u32 cmd_flags)
				 struct mmc_command *cmd, u32 cmd_flags)
{
{
@@ -290,9 +330,10 @@ static void dw_mci_start_command(struct dw_mci *host,
	mci_writel(host, CMD, cmd_flags | SDMMC_CMD_START);
	mci_writel(host, CMD, cmd_flags | SDMMC_CMD_START);
}
}


static void send_stop_cmd(struct dw_mci *host, struct mmc_data *data)
static inline void send_stop_abort(struct dw_mci *host, struct mmc_data *data)
{
{
	dw_mci_start_command(host, data->stop, host->stop_cmdr);
	struct mmc_command *stop = data->stop ? data->stop : &host->stop_abort;
	dw_mci_start_command(host, stop, host->stop_cmdr);
}
}


/* DMA interface functions */
/* DMA interface functions */
@@ -828,6 +869,8 @@ static void __dw_mci_start_request(struct dw_mci *host,


	if (mrq->stop)
	if (mrq->stop)
		host->stop_cmdr = dw_mci_prepare_command(slot->mmc, mrq->stop);
		host->stop_cmdr = dw_mci_prepare_command(slot->mmc, mrq->stop);
	else
		host->stop_cmdr = dw_mci_prep_stop_abort(host, cmd);
}
}


static void dw_mci_start_request(struct dw_mci *host,
static void dw_mci_start_request(struct dw_mci *host,
@@ -1190,13 +1233,9 @@ static void dw_mci_tasklet_func(unsigned long priv)


			if (cmd->data && cmd->error) {
			if (cmd->data && cmd->error) {
				dw_mci_stop_dma(host);
				dw_mci_stop_dma(host);
				if (data->stop) {
				send_stop_abort(host, data);
					send_stop_cmd(host, data);
				state = STATE_SENDING_STOP;
				state = STATE_SENDING_STOP;
				break;
				break;
				} else {
					host->data = NULL;
				}
			}
			}


			if (!host->mrq->data || cmd->error) {
			if (!host->mrq->data || cmd->error) {
@@ -1211,8 +1250,7 @@ static void dw_mci_tasklet_func(unsigned long priv)
			if (test_and_clear_bit(EVENT_DATA_ERROR,
			if (test_and_clear_bit(EVENT_DATA_ERROR,
					       &host->pending_events)) {
					       &host->pending_events)) {
				dw_mci_stop_dma(host);
				dw_mci_stop_dma(host);
				if (data->stop)
				send_stop_abort(host, data);
					send_stop_cmd(host, data);
				state = STATE_DATA_ERROR;
				state = STATE_DATA_ERROR;
				break;
				break;
			}
			}
@@ -1272,7 +1310,7 @@ static void dw_mci_tasklet_func(unsigned long priv)
				data->error = 0;
				data->error = 0;
			}
			}


			if (!data->stop) {
			if (!data->stop && !data->error) {
				dw_mci_request_end(host, host->mrq);
				dw_mci_request_end(host, host->mrq);
				goto unlock;
				goto unlock;
			}
			}
@@ -1284,8 +1322,10 @@ static void dw_mci_tasklet_func(unsigned long priv)
			}
			}


			prev_state = state = STATE_SENDING_STOP;
			prev_state = state = STATE_SENDING_STOP;
			if (!data->error)
			if (data->stop && !data->error) {
				send_stop_cmd(host, data);
				/* stop command for open-ended transfer*/
				send_stop_abort(host, data);
			}
			/* fall through */
			/* fall through */


		case STATE_SENDING_STOP:
		case STATE_SENDING_STOP:
@@ -1304,7 +1344,12 @@ static void dw_mci_tasklet_func(unsigned long priv)


			host->cmd = NULL;
			host->cmd = NULL;
			host->data = NULL;
			host->data = NULL;

			if (host->mrq->stop)
				dw_mci_command_complete(host, host->mrq->stop);
				dw_mci_command_complete(host, host->mrq->stop);
			else
				host->cmd_status = 0;

			dw_mci_request_end(host, host->mrq);
			dw_mci_request_end(host, host->mrq);
			goto unlock;
			goto unlock;


@@ -1888,10 +1933,9 @@ static void dw_mci_work_routine_card(struct work_struct *work)
					case STATE_DATA_ERROR:
					case STATE_DATA_ERROR:
						if (mrq->data->error == -EINPROGRESS)
						if (mrq->data->error == -EINPROGRESS)
							mrq->data->error = -ENOMEDIUM;
							mrq->data->error = -ENOMEDIUM;
						if (!mrq->stop)
							break;
						/* fall through */
						/* fall through */
					case STATE_SENDING_STOP:
					case STATE_SENDING_STOP:
						if (mrq->stop)
							mrq->stop->error = -ENOMEDIUM;
							mrq->stop->error = -ENOMEDIUM;
						break;
						break;
					}
					}
+2 −0
Original line number Original line Diff line number Diff line
@@ -15,6 +15,7 @@
#define LINUX_MMC_DW_MMC_H
#define LINUX_MMC_DW_MMC_H


#include <linux/scatterlist.h>
#include <linux/scatterlist.h>
#include <linux/mmc/core.h>


#define MAX_MCI_SLOTS	2
#define MAX_MCI_SLOTS	2


@@ -129,6 +130,7 @@ struct dw_mci {
	struct mmc_request	*mrq;
	struct mmc_request	*mrq;
	struct mmc_command	*cmd;
	struct mmc_command	*cmd;
	struct mmc_data		*data;
	struct mmc_data		*data;
	struct mmc_command	stop_abort;
	unsigned int		prev_blksz;
	unsigned int		prev_blksz;
	unsigned char		timing;
	unsigned char		timing;
	struct workqueue_struct	*card_workqueue;
	struct workqueue_struct	*card_workqueue;