diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 80a7bb9301c5..0267825955d0 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -364,6 +364,21 @@ static void dw_mci_wait_while_busy(struct dw_mci *host, u32 cmd_flags) } } +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) { @@ -376,6 +391,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); } @@ -2430,6 +2449,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 */ @@ -2474,6 +2494,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); } @@ -2878,6 +2899,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; @@ -3078,6 +3123,9 @@ int dw_mci_probe(struct dw_mci *host) host->quirks = host->pdata->quirks; + setup_timer(&host->cto_timer, + dw_mci_cto_timer, (unsigned long)host); + if (host->quirks & DW_MCI_QUIRK_BROKEN_DTO) setup_timer(&host->dto_timer, dw_mci_dto_timer, (unsigned long)host); diff --git a/include/linux/mmc/dw_mmc.h b/include/linux/mmc/dw_mmc.h index 7776afb0ffa5..1accb8ca4fde 100644 --- a/include/linux/mmc/dw_mmc.h +++ b/include/linux/mmc/dw_mmc.h @@ -110,6 +110,7 @@ struct dw_mci_dma_slave { * @irq_flags: The flags to be passed to request_irq. * @irq: The irq value to be passed to request_irq. * @sdio_id0: Number of slot0 in the SDIO interrupt registers. + * @cto_timer: Timer for broken command transfer over scheme. * @dto_timer: Timer for broken data transfer over scheme. * * Locking @@ -220,6 +221,7 @@ struct dw_mci { int sdio_id0; struct timer_list cmd11_timer; + struct timer_list cto_timer; struct timer_list dto_timer; };