[PATCH 2/4] mmc: dw_mmc: convert DTO onto the central watchdog

From: Shawn Lin

Date: Thu Aug 27 2026 - 10:53:21 EST


The data timeout joins the command timeout on the central watchdog;
dto_timer is deleted.

dw_mci_set_drto() arms DW_MCI_WD_DATA_EVENTS with EVENT_DATA_COMPLETE
as its precheck mask: a DATA_ERROR that arrived while still waiting
for the paired completion must not prevent the watch -- the legacy
mod_timer() guard tested exactly that one bit, and the fault-injection
machinery relies on this by injecting DATA_ERROR early.

The EXTENDED_TMOUT quirk semantics fall out naturally now:

* On quirk hosts the data-error branch delivers the whole watched
set, stopping the watch since no further data events will come --
this mirrors the former conditional timer_delete() plus the manual
EVENT_DATA_COMPLETE side-post.
* Without the quirk nothing is delivered there and the outstanding
watch keeps guarding until a genuine DATA_OVER arrives, exactly
like leaving dto_timer running did.

The DATA_OVER branch delivers unconditionally, superseding its
unconditional timer_delete(). The stale-timer WARN_ON +
timer_delete_sync() dance in dw_mci_clear_pending_data_complete() goes
away for the same reason as on the command leg: a callback racing past
its checks is idempotent under irq_lock.

No functional change intended.

Signed-off-by: Shawn Lin <shawn.lin@xxxxxxxxxxxxxx>
---

drivers/mmc/host/dw_mmc.c | 64 ++++-------------------------------------------
drivers/mmc/host/dw_mmc.h | 2 --
2 files changed, 5 insertions(+), 61 deletions(-)

diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
index cefc873..31cf728 100644
--- a/drivers/mmc/host/dw_mmc.c
+++ b/drivers/mmc/host/dw_mmc.c
@@ -1998,9 +1998,9 @@ static void dw_mci_set_drto(struct dw_mci *host)
drto_ms += 10;

spin_lock_irqsave(&host->irq_lock, irqflags);
- if (!test_bit(EVENT_DATA_COMPLETE, &host->pending_events))
- mod_timer(&host->dto_timer,
- jiffies + msecs_to_jiffies(drto_ms));
+ dw_mci_wd_arm(host, drto_ms, BIT(EVENT_DATA_COMPLETE),
+ DW_MCI_WD_DATA_EVENTS,
+ BIT(STATE_SENDING_DATA) | BIT(STATE_DATA_BUSY));
spin_unlock_irqrestore(&host->irq_lock, irqflags);
}

@@ -2019,8 +2019,6 @@ static bool dw_mci_clear_pending_data_complete(struct dw_mci *host)
if (!test_bit(EVENT_DATA_COMPLETE, &host->pending_events))
return false;

- /* Extra paranoia just like dw_mci_clear_pending_cmd_complete() */
- WARN_ON(timer_delete_sync(&host->dto_timer));
clear_bit(EVENT_DATA_COMPLETE, &host->pending_events);

return true;
@@ -2813,7 +2811,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
spin_lock(&host->irq_lock);

if (host->quirks & DW_MMC_QUIRK_EXTENDED_TMOUT)
- timer_delete(&host->dto_timer);
+ dw_mci_wd_deliver(host, DW_MCI_WD_DATA_EVENTS);

/* if there is an error report DATA_ERROR */
mci_writel(host, RINTSTS, DW_MCI_DATA_ERROR_FLAGS);
@@ -2834,7 +2832,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
if (pending & SDMMC_INT_DATA_OVER) {
spin_lock(&host->irq_lock);

- timer_delete(&host->dto_timer);
+ dw_mci_wd_deliver(host, DW_MCI_WD_DATA_EVENTS);

mci_writel(host, RINTSTS, SDMMC_INT_DATA_OVER);
if (!host->data_status)
@@ -3128,57 +3126,6 @@ static void dw_mci_cmd11_timer(struct timer_list *t)
queue_work(system_bh_wq, &host->bh_work);
}

-static void dw_mci_dto_timer(struct timer_list *t)
-{
- struct dw_mci *host = timer_container_of(host, t, dto_timer);
- unsigned long irqflags;
- u32 pending;
-
- spin_lock_irqsave(&host->irq_lock, irqflags);
-
- /*
- * The DTO timer is much longer than the CTO timer, so it's even less
- * likely that we'll these cases, but it pays to be paranoid.
- */
- pending = mci_readl(host, MINTSTS); /* read-only mask reg */
- if (pending & SDMMC_INT_DATA_OVER) {
- /* The interrupt should fire; no need to act but we can warn */
- dev_warn(host->dev, "Unexpected data interrupt latency\n");
- goto exit;
- }
- if (test_bit(EVENT_DATA_COMPLETE, &host->pending_events)) {
- /* Presumably interrupt handler couldn't delete the timer */
- dev_warn(host->dev, "DTO timeout when already completed\n");
- goto exit;
- }
-
- /*
- * Continued paranoia to make sure we're in the state we expect.
- * This paranoia isn't really justified but it seems good to be safe.
- */
- 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);
- queue_work(system_bh_wq, &host->bh_work);
- break;
- default:
- dev_warn(host->dev, "Unexpected data timeout, state %d\n",
- host->state);
- break;
- }
-
-exit:
- spin_unlock_irqrestore(&host->irq_lock, irqflags);
-}
-
static int dw_mci_parse_dt(struct dw_mci *host)
{
struct device *dev = host->dev;
@@ -3329,7 +3276,6 @@ int dw_mci_probe(struct dw_mci *host)
hrtimer_setup(&host->wd_timer, dw_mci_watchdog_fn, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
timer_setup(&host->cmd11_timer, dw_mci_cmd11_timer, 0);
- timer_setup(&host->dto_timer, dw_mci_dto_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 7a14f3f..7af2b45 100644
--- a/drivers/mmc/host/dw_mmc.h
+++ b/drivers/mmc/host/dw_mmc.h
@@ -128,7 +128,6 @@ struct dw_mci_dma_slave {
* @wd_events: pending_events bits still awaited by the armed watch.
* @wd_states: host->state values for which the armed watch is valid.
* @cmd11_timer: Timer for SD3.0 voltage switch over scheme.
- * @dto_timer: Timer for broken data transfer over scheme.
* @mmc: The mmc_host representing this dw_mci.
* @flags: Random state bits associated with the host.
* @ctype: Card type for this host.
@@ -244,7 +243,6 @@ struct dw_mci {
unsigned long wd_states;

struct timer_list cmd11_timer;
- struct timer_list dto_timer;

#ifdef CONFIG_FAULT_INJECTION
struct fault_attr fail_data_crc;
--
2.7.4