mirror of
https://github.com/torvalds/linux.git
synced 2026-06-07 14:04:54 +02:00
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:
parent
2f03855b44
commit
1d1a1f03f3
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 @@ static void dw_mci_read_data_pio(struct dw_mci *host, bool dto)
|
|||
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 @@ static void dw_mci_cto_timer(struct timer_list *t)
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user