|
@@ -29,6 +29,7 @@
|
|
|
#include <linux/irq.h>
|
|
|
#include <linux/mmc/host.h>
|
|
|
#include <linux/mmc/mmc.h>
|
|
|
+#include <linux/mmc/sdio.h>
|
|
|
#include <linux/mmc/dw_mmc.h>
|
|
|
#include <linux/bitops.h>
|
|
|
#include <linux/regulator/consumer.h>
|
|
@@ -246,10 +247,15 @@ static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd)
|
|
|
|
|
|
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;
|
|
|
else
|
|
|
- cmdr |= SDMMC_CMD_PRV_DAT_WAIT;
|
|
|
+ if (cmd->opcode != MMC_SEND_STATUS && cmd->data)
|
|
|
+ cmdr |= SDMMC_CMD_PRV_DAT_WAIT;
|
|
|
|
|
|
if (cmd->flags & MMC_RSP_PRESENT) {
|
|
|
/* We expect a response, so set this bit */
|
|
@@ -276,6 +282,40 @@ static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd)
|
|
|
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,
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
-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 */
|
|
@@ -828,6 +869,8 @@ static void __dw_mci_start_request(struct dw_mci *host,
|
|
|
|
|
|
if (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,
|
|
@@ -1190,13 +1233,9 @@ static void dw_mci_tasklet_func(unsigned long priv)
|
|
|
|
|
|
if (cmd->data && cmd->error) {
|
|
|
dw_mci_stop_dma(host);
|
|
|
- if (data->stop) {
|
|
|
- send_stop_cmd(host, data);
|
|
|
- state = STATE_SENDING_STOP;
|
|
|
- break;
|
|
|
- } else {
|
|
|
- host->data = NULL;
|
|
|
- }
|
|
|
+ send_stop_abort(host, data);
|
|
|
+ state = STATE_SENDING_STOP;
|
|
|
+ break;
|
|
|
}
|
|
|
|
|
|
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,
|
|
|
&host->pending_events)) {
|
|
|
dw_mci_stop_dma(host);
|
|
|
- if (data->stop)
|
|
|
- send_stop_cmd(host, data);
|
|
|
+ send_stop_abort(host, data);
|
|
|
state = STATE_DATA_ERROR;
|
|
|
break;
|
|
|
}
|
|
@@ -1272,7 +1310,7 @@ static void dw_mci_tasklet_func(unsigned long priv)
|
|
|
data->error = 0;
|
|
|
}
|
|
|
|
|
|
- if (!data->stop) {
|
|
|
+ if (!data->stop && !data->error) {
|
|
|
dw_mci_request_end(host, host->mrq);
|
|
|
goto unlock;
|
|
|
}
|
|
@@ -1284,8 +1322,10 @@ static void dw_mci_tasklet_func(unsigned long priv)
|
|
|
}
|
|
|
|
|
|
prev_state = state = STATE_SENDING_STOP;
|
|
|
- if (!data->error)
|
|
|
- send_stop_cmd(host, data);
|
|
|
+ if (data->stop && !data->error) {
|
|
|
+ /* stop command for open-ended transfer*/
|
|
|
+ send_stop_abort(host, data);
|
|
|
+ }
|
|
|
/* fall through */
|
|
|
|
|
|
case STATE_SENDING_STOP:
|
|
@@ -1304,7 +1344,12 @@ static void dw_mci_tasklet_func(unsigned long priv)
|
|
|
|
|
|
host->cmd = NULL;
|
|
|
host->data = NULL;
|
|
|
- dw_mci_command_complete(host, host->mrq->stop);
|
|
|
+
|
|
|
+ if (host->mrq->stop)
|
|
|
+ dw_mci_command_complete(host, host->mrq->stop);
|
|
|
+ else
|
|
|
+ host->cmd_status = 0;
|
|
|
+
|
|
|
dw_mci_request_end(host, host->mrq);
|
|
|
goto unlock;
|
|
|
|
|
@@ -1888,11 +1933,10 @@ static void dw_mci_work_routine_card(struct work_struct *work)
|
|
|
case STATE_DATA_ERROR:
|
|
|
if (mrq->data->error == -EINPROGRESS)
|
|
|
mrq->data->error = -ENOMEDIUM;
|
|
|
- if (!mrq->stop)
|
|
|
- break;
|
|
|
/* fall through */
|
|
|
case STATE_SENDING_STOP:
|
|
|
- mrq->stop->error = -ENOMEDIUM;
|
|
|
+ if (mrq->stop)
|
|
|
+ mrq->stop->error = -ENOMEDIUM;
|
|
|
break;
|
|
|
}
|
|
|
|