mirror of
https://github.com/torvalds/linux.git
synced 2026-06-08 06:25:52 +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
8c7548af80
commit
976ea163ad
|
|
@ -297,6 +297,9 @@ static int dw_mci_rockchip_init(struct dw_mci *host)
|
|||
"rockchip,rk3288-dw-mshc"))
|
||||
host->bus_hz /= RK3288_CLKGEN_DIV;
|
||||
|
||||
|
||||
host->need_xfer_timer = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -491,6 +491,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)
|
||||
|
|
@ -1923,6 +1927,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))
|
||||
|
|
@ -2060,6 +2088,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;
|
||||
}
|
||||
|
||||
|
|
@ -2534,6 +2565,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)
|
||||
|
|
@ -2646,6 +2679,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);
|
||||
|
||||
|
|
@ -3031,6 +3067,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, xfer_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);
|
||||
|
|
@ -3224,6 +3290,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);
|
||||
|
|
|
|||
|
|
@ -230,6 +230,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