diff --git a/drivers/mmc/host/dw_mmc-rockchip.c b/drivers/mmc/host/dw_mmc-rockchip.c index 8c86a800a8fd..992388cd4d3a 100644 --- a/drivers/mmc/host/dw_mmc-rockchip.c +++ b/drivers/mmc/host/dw_mmc-rockchip.c @@ -301,6 +301,12 @@ static int dw_mci_rockchip_init(struct dw_mci *host) "rockchip,rk3288-dw-mshc")) host->bus_hz /= RK3288_CLKGEN_DIV; + if (of_device_is_compatible(host->dev->of_node, + "rockchip,rk3308-dw-mshc")) + host->need_xfer_timer = true; + else + host->need_xfer_timer = false; + return 0; } diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 193affbeadc7..f3ff4cc5b681 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -517,6 +517,10 @@ static void dw_mci_dmac_complete_dma(void *arg) set_bit(EVENT_XFER_COMPLETE, &host->pending_events); tasklet_schedule(&host->tasklet); } + + if (host->need_xfer_timer && + host->dir_status == DW_MCI_RECV_STATUS) + del_timer(&host->xfer_timer); } static int dw_mci_idmac_init(struct dw_mci *host) @@ -1971,6 +1975,30 @@ static void dw_mci_set_drto(struct dw_mci *host) spin_unlock_irqrestore(&host->irq_lock, irqflags); } +static void dw_mci_set_xfer_timeout(struct dw_mci *host) +{ + unsigned int xfer_clks; + unsigned int xfer_div; + unsigned int xfer_ms; + unsigned long irqflags; + + xfer_clks = mci_readl(host, TMOUT) >> 8; + xfer_div = (mci_readl(host, CLKDIV) & 0xff) * 2; + if (xfer_div == 0) + xfer_div = 1; + xfer_ms = DIV_ROUND_UP_ULL((u64)MSEC_PER_SEC * xfer_clks * xfer_div, + host->bus_hz); + + /* add a bit spare time */ + xfer_ms += 10; + + spin_lock_irqsave(&host->irq_lock, irqflags); + if (!test_bit(EVENT_XFER_COMPLETE, &host->pending_events)) + mod_timer(&host->xfer_timer, + jiffies + msecs_to_jiffies(xfer_ms)); + spin_unlock_irqrestore(&host->irq_lock, irqflags); +} + static bool dw_mci_clear_pending_cmd_complete(struct dw_mci *host) { if (!test_bit(EVENT_CMD_COMPLETE, &host->pending_events)) @@ -2109,6 +2137,9 @@ static void dw_mci_tasklet_func(unsigned long priv) */ if (host->dir_status == DW_MCI_RECV_STATUS) dw_mci_set_drto(host); + if (host->need_xfer_timer && + host->dir_status == DW_MCI_RECV_STATUS) + dw_mci_set_xfer_timeout(host); break; } @@ -2583,6 +2614,8 @@ done: host->sg = NULL; smp_wmb(); /* drain writebuffer */ set_bit(EVENT_XFER_COMPLETE, &host->pending_events); + if (host->need_xfer_timer) + del_timer(&host->xfer_timer); } static void dw_mci_write_data_pio(struct dw_mci *host) @@ -2695,6 +2728,9 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id) del_timer(&host->cto_timer); mci_writel(host, RINTSTS, DW_MCI_CMD_ERROR_FLAGS); host->cmd_status = pending; + if ((host->need_xfer_timer) && + host->dir_status == DW_MCI_RECV_STATUS) + del_timer(&host->xfer_timer); smp_wmb(); /* drain writebuffer */ set_bit(EVENT_CMD_COMPLETE, &host->pending_events); @@ -3086,6 +3122,36 @@ exit: spin_unlock_irqrestore(&host->irq_lock, irqflags); } +static void dw_mci_xfer_timer(struct timer_list *t) +{ + struct dw_mci *host = from_timer(host, t, dto_timer); + unsigned long irqflags; + + spin_lock_irqsave(&host->irq_lock, irqflags); + + if (test_bit(EVENT_XFER_COMPLETE, &host->pending_events)) { + /* Presumably interrupt handler couldn't delete the timer */ + dev_warn(host->dev, "xfer when already completed\n"); + goto exit; + } + + switch (host->state) { + case STATE_SENDING_DATA: + 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: + dev_warn(host->dev, "Unexpected xfer timeout, state %d\n", + host->state); + break; + } + +exit: + spin_unlock_irqrestore(&host->irq_lock, irqflags); +} + static void dw_mci_dto_timer(struct timer_list *t) { struct dw_mci *host = from_timer(host, t, dto_timer); @@ -3282,6 +3348,8 @@ int dw_mci_probe(struct dw_mci *host) timer_setup(&host->cmd11_timer, dw_mci_cmd11_timer, 0); timer_setup(&host->cto_timer, dw_mci_cto_timer, 0); timer_setup(&host->dto_timer, dw_mci_dto_timer, 0); + if (host->need_xfer_timer) + timer_setup(&host->xfer_timer, dw_mci_xfer_timer, 0); spin_lock_init(&host->lock); spin_lock_init(&host->irq_lock); diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index 46e9f8ec5398..18c023654b69 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -234,6 +234,8 @@ struct dw_mci { struct timer_list cmd11_timer; struct timer_list cto_timer; struct timer_list dto_timer; + bool need_xfer_timer; + struct timer_list xfer_timer; }; /* DMA ops for Internal/External DMAC interface */