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

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
...@@ -29,6 +29,7 @@ ...@@ -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) ...@@ -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) ...@@ -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, ...@@ -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, ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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;
} }
......
...@@ -15,6 +15,7 @@ ...@@ -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 { ...@@ -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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment