diff options
Diffstat (limited to 'drivers/mmc/host')
-rw-r--r-- | drivers/mmc/host/dw_mmc-rockchip.c | 3 | ||||
-rw-r--r-- | drivers/mmc/host/dw_mmc.c | 64 |
2 files changed, 65 insertions, 2 deletions
diff --git a/drivers/mmc/host/dw_mmc-rockchip.c b/drivers/mmc/host/dw_mmc-rockchip.c index de15121bba7d..bc76aa22473e 100644 --- a/drivers/mmc/host/dw_mmc-rockchip.c +++ b/drivers/mmc/host/dw_mmc-rockchip.c @@ -73,6 +73,9 @@ static int dw_mci_rockchip_init(struct dw_mci *host) /* It is slot 8 on Rockchip SoCs */ host->sdio_id0 = 8; + /* It needs this quirk on all Rockchip SoCs */ + host->pdata->quirks |= DW_MCI_QUIRK_BROKEN_DTO; + return 0; } diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index f8ac14a14693..fcbf5524fd31 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1574,6 +1574,20 @@ static int dw_mci_data_complete(struct dw_mci *host, struct mmc_data *data) return data->error; } +static void dw_mci_set_drto(struct dw_mci *host) +{ + unsigned int drto_clks; + unsigned int drto_ms; + + drto_clks = mci_readl(host, TMOUT) >> 8; + drto_ms = DIV_ROUND_UP(drto_clks, host->bus_hz / 1000); + + /* add a bit spare time */ + drto_ms += 10; + + mod_timer(&host->dto_timer, jiffies + msecs_to_jiffies(drto_ms)); +} + static void dw_mci_tasklet_func(unsigned long priv) { struct dw_mci *host = (struct dw_mci *)priv; @@ -1651,8 +1665,16 @@ static void dw_mci_tasklet_func(unsigned long priv) } if (!test_and_clear_bit(EVENT_XFER_COMPLETE, - &host->pending_events)) + &host->pending_events)) { + /* + * If all data-related interrupts don't come + * within the given time in reading data state. + */ + if ((host->quirks & DW_MCI_QUIRK_BROKEN_DTO) && + (host->dir_status == DW_MCI_RECV_STATUS)) + dw_mci_set_drto(host); break; + } set_bit(EVENT_XFER_COMPLETE, &host->completed_events); @@ -1685,8 +1707,17 @@ static void dw_mci_tasklet_func(unsigned long priv) case STATE_DATA_BUSY: if (!test_and_clear_bit(EVENT_DATA_COMPLETE, - &host->pending_events)) + &host->pending_events)) { + /* + * If data error interrupt comes but data over + * interrupt doesn't come within the given time. + * in reading data state. + */ + if ((host->quirks & DW_MCI_QUIRK_BROKEN_DTO) && + (host->dir_status == DW_MCI_RECV_STATUS)) + dw_mci_set_drto(host); break; + } host->data = NULL; set_bit(EVENT_DATA_COMPLETE, &host->completed_events); @@ -2259,6 +2290,9 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id) } if (pending & SDMMC_INT_DATA_OVER) { + if (host->quirks & DW_MCI_QUIRK_BROKEN_DTO) + del_timer(&host->dto_timer); + mci_writel(host, RINTSTS, SDMMC_INT_DATA_OVER); if (!host->data_status) host->data_status = pending; @@ -2644,6 +2678,28 @@ static void dw_mci_cmd11_timer(unsigned long arg) tasklet_schedule(&host->tasklet); } +static void dw_mci_dto_timer(unsigned long arg) +{ + struct dw_mci *host = (struct dw_mci *)arg; + + switch (host->state) { + case STATE_SENDING_DATA: + case STATE_DATA_BUSY: + /* + * If DTO interrupt does NOT come in sending data state, + * we should notify the driver to terminate current transfer + * and report a data timeout to the core. + */ + host->data_status = SDMMC_INT_DRTO; + set_bit(EVENT_DATA_ERROR, &host->pending_events); + set_bit(EVENT_DATA_COMPLETE, &host->pending_events); + tasklet_schedule(&host->tasklet); + break; + default: + break; + } +} + #ifdef CONFIG_OF static struct dw_mci_of_quirks { char *quirk; @@ -2822,6 +2878,10 @@ int dw_mci_probe(struct dw_mci *host) host->quirks = host->pdata->quirks; + if (host->quirks & DW_MCI_QUIRK_BROKEN_DTO) + setup_timer(&host->dto_timer, + dw_mci_dto_timer, (unsigned long)host); + spin_lock_init(&host->lock); spin_lock_init(&host->irq_lock); INIT_LIST_HEAD(&host->queue); |