summaryrefslogtreecommitdiffstats
path: root/drivers/mmc/host/dw_mmc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc/host/dw_mmc.c')
-rw-r--r--drivers/mmc/host/dw_mmc.c48
1 files changed, 48 insertions, 0 deletions
diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
index 5ccf46501ce1..860313bd952a 100644
--- a/drivers/mmc/host/dw_mmc.c
+++ b/drivers/mmc/host/dw_mmc.c
@@ -398,6 +398,21 @@ static u32 dw_mci_prep_stop_abort(struct dw_mci *host, struct mmc_command *cmd)
return cmdr;
}
+static inline void dw_mci_set_cto(struct dw_mci *host)
+{
+ unsigned int cto_clks;
+ unsigned int cto_ms;
+
+ cto_clks = mci_readl(host, TMOUT) & 0xff;
+ cto_ms = DIV_ROUND_UP(cto_clks, host->bus_hz / 1000);
+
+ /* add a bit spare time */
+ cto_ms += 10;
+
+ mod_timer(&host->cto_timer,
+ jiffies + msecs_to_jiffies(cto_ms) + 1);
+}
+
static void dw_mci_start_command(struct dw_mci *host,
struct mmc_command *cmd, u32 cmd_flags)
{
@@ -410,6 +425,10 @@ static void dw_mci_start_command(struct dw_mci *host,
wmb(); /* drain writebuffer */
dw_mci_wait_while_busy(host, cmd_flags);
+ /* response expected command only */
+ if (cmd_flags & SDMMC_CMD_RESP_EXP)
+ dw_mci_set_cto(host);
+
mci_writel(host, CMD, cmd_flags | SDMMC_CMD_START);
}
@@ -2599,6 +2618,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
}
if (pending & DW_MCI_CMD_ERROR_FLAGS) {
+ del_timer(&host->cto_timer);
mci_writel(host, RINTSTS, DW_MCI_CMD_ERROR_FLAGS);
host->cmd_status = pending;
smp_wmb(); /* drain writebuffer */
@@ -2642,6 +2662,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
}
if (pending & SDMMC_INT_CMD_DONE) {
+ del_timer(&host->cto_timer);
mci_writel(host, RINTSTS, SDMMC_INT_CMD_DONE);
dw_mci_cmd_interrupt(host, pending);
}
@@ -2914,6 +2935,30 @@ static void dw_mci_cmd11_timer(unsigned long arg)
tasklet_schedule(&host->tasklet);
}
+static void dw_mci_cto_timer(unsigned long arg)
+{
+ struct dw_mci *host = (struct dw_mci *)arg;
+
+ switch (host->state) {
+ case STATE_SENDING_CMD11:
+ case STATE_SENDING_CMD:
+ case STATE_SENDING_STOP:
+ /*
+ * If CMD_DONE interrupt does NOT come in sending command
+ * state, we should notify the driver to terminate current
+ * transfer and report a command timeout to the core.
+ */
+ host->cmd_status = SDMMC_INT_RTO;
+ set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
+ tasklet_schedule(&host->tasklet);
+ break;
+ default:
+ dev_warn(host->dev, "Unexpected command timeout, state %d\n",
+ host->state);
+ break;
+ }
+}
+
static void dw_mci_dto_timer(unsigned long arg)
{
struct dw_mci *host = (struct dw_mci *)arg;
@@ -3085,6 +3130,9 @@ int dw_mci_probe(struct dw_mci *host)
setup_timer(&host->cmd11_timer,
dw_mci_cmd11_timer, (unsigned long)host);
+ setup_timer(&host->cto_timer,
+ dw_mci_cto_timer, (unsigned long)host);
+
setup_timer(&host->dto_timer,
dw_mci_dto_timer, (unsigned long)host);