mmc: dw_mmc: add xfer timer for avoid DTO without actual data payload

It has proved the controller has a potention broken state with a DTO
interrupt comes while the data payload is missing, which was not
covered by current software state machine. Add a xfer timer to work
around this buggy behaviour introduced by broken design.

Change-Id: I5019c5ba0cdeb59adcdd3a5231a2000b448762bc
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
This commit is contained in:
Shawn Lin
2019-02-21 14:53:44 +08:00
committed by Tao Huang
parent 2f03855b44
commit 1d1a1f03f3
3 changed files with 76 additions and 0 deletions

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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 */