mmc: dw_mmc: Add a timeout for sending CMD11

In the Designware databook's description of the "Voltage Switch Normal
Scenario" it instructs us to set a timer and fail the voltage change
if we don't see the voltage change interrupt within 2ms.  Let's
implement that.  Without implementing this I have often been able to
reproduce a hang while trying to send CMD11 on an rk3288-based board
while constantly ejecting and inserting UHS cards.

Signed-off-by: Doug Anderson <dianders@chromium.org>
Signed-off-by: Jaehoon Chung <jh80.chung@samsung.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
Doug Anderson 2015-03-09 16:18:21 -07:00 committed by Ulf Hansson
parent 488b8d63a6
commit 5c935165da
2 changed files with 28 additions and 0 deletions

View File

@ -1021,6 +1021,15 @@ static void __dw_mci_start_request(struct dw_mci *host,
dw_mci_start_command(host, cmd, cmdflags); dw_mci_start_command(host, cmd, cmdflags);
if (cmd->opcode == SD_SWITCH_VOLTAGE) {
/*
* Databook says to fail after 2ms w/ no response; give an
* extra jiffy just in case we're about to roll over.
*/
mod_timer(&host->cmd11_timer,
jiffies + msecs_to_jiffies(2) + 1);
}
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 else
@ -2159,6 +2168,8 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
/* Check volt switch first, since it can look like an error */ /* Check volt switch first, since it can look like an error */
if ((host->state == STATE_SENDING_CMD11) && if ((host->state == STATE_SENDING_CMD11) &&
(pending & SDMMC_INT_VOLT_SWITCH)) { (pending & SDMMC_INT_VOLT_SWITCH)) {
del_timer(&host->cmd11_timer);
mci_writel(host, RINTSTS, SDMMC_INT_VOLT_SWITCH); mci_writel(host, RINTSTS, SDMMC_INT_VOLT_SWITCH);
pending &= ~SDMMC_INT_VOLT_SWITCH; pending &= ~SDMMC_INT_VOLT_SWITCH;
dw_mci_cmd_interrupt(host, pending); dw_mci_cmd_interrupt(host, pending);
@ -2572,6 +2583,18 @@ static bool dw_mci_reset(struct dw_mci *host)
return ret; return ret;
} }
static void dw_mci_cmd11_timer(unsigned long arg)
{
struct dw_mci *host = (struct dw_mci *)arg;
if (host->state != STATE_SENDING_CMD11)
dev_info(host->dev, "Unexpected CMD11 timeout\n");
host->cmd_status = SDMMC_INT_RTO;
set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
tasklet_schedule(&host->tasklet);
}
#ifdef CONFIG_OF #ifdef CONFIG_OF
static struct dw_mci_of_quirks { static struct dw_mci_of_quirks {
char *quirk; char *quirk;
@ -2746,6 +2769,9 @@ int dw_mci_probe(struct dw_mci *host)
} }
} }
setup_timer(&host->cmd11_timer,
dw_mci_cmd11_timer, (unsigned long)host);
host->quirks = host->pdata->quirks; host->quirks = host->pdata->quirks;
spin_lock_init(&host->lock); spin_lock_init(&host->lock);

View File

@ -202,6 +202,8 @@ struct dw_mci {
int irq; int irq;
int sdio_id0; int sdio_id0;
struct timer_list cmd11_timer;
}; };
/* DMA ops for Internal/External DMAC interface */ /* DMA ops for Internal/External DMAC interface */