From: Henry Zhang <henryzhang62@gmail.com>
To: Maxime Ripard <mripard@kernel.org>,
Dave Stevenson <dave.stevenson@raspberrypi.com>
Cc: "Henry Zhang" <henryzhang62@gmail.com>,
"Maíra Canal" <mcanal@igalia.com>,
"Raspberry Pi Kernel Maintenance" <kernel-list@raspberrypi.com>,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Thomas Zimmermann" <tzimmermann@suse.de>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org
Subject: [PATCH v3 4/4] drm/vc4: dsi: Recover from a timed-out transfer
Date: Mon, 26 Oct 2026 12:14:58 -0700 [thread overview]
Message-ID: <20261026191618.9430-5-henryzhang62@gmail.com> (raw)
In-Reply-To: <20261026191618.9430-1-henryzhang62@gmail.com>
When a transfer times out, vc4_dsi_host_transfer() disables the
command, resets the FIFOs and returns -ETIMEDOUT. That is not always
enough: after an LP receive timeout during a read (the peripheral did
not complete the bus turnaround), every following transfer times out
as well, with no interrupt and STAT reading back 0. Only a modeset,
which goes through post_disable/pre_enable, brings the block back.
Reset the DSI block and its analog front end after a timeout and restore
the configuration programmed in pre_enable, in the same order as
pre_enable: the block is ungated and the AFE released only after the
timings are programmed. Before resetting, wait for the interrupt
handler, so that a late completion interrupt of the timed-out transfer
cannot complete the next one, and mask the interrupts until the AFE is
out of reset: the error interrupts otherwise fire continuously while it
is held in reset. The timed-out message is not resent, as it may already
have been executed by the peripheral; the error is returned and the
caller decides whether to retry.
Only do this while the block is enabled. Between post_disable and
pre_enable the clocks are off and there is nothing to restore; a
transfer then times out as before. The transfer lock keeps the
recovery from running in the middle of pre_enable or post_disable.
This does not prevent the timeouts themselves. With video running,
vc4 does not keep commands within vertical blanking, and transfers can
still time out, sometimes several in a row after a reset. That is a
separate issue.
Signed-off-by: Henry Zhang <henryzhang62@gmail.com>
---
drivers/gpu/drm/vc4/vc4_dsi.c | 130 ++++++++++++++++++++++++++++++++--
1 file changed, 123 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c
index ea3a6d06b..42f50d2ca 100644
--- a/drivers/gpu/drm/vc4/vc4_dsi.c
+++ b/drivers/gpu/drm/vc4/vc4_dsi.c
@@ -601,8 +601,11 @@ struct vc4_dsi {
* bridge enable/disable paths, which reprogram the block.
*/
struct mutex transfer_lock;
+ /* The block is powered and configured. Protected by transfer_lock. */
+ bool enabled;
struct completion xfer_completion;
int xfer_result;
+ int irq;
struct debugfs_regset32 regset;
};
@@ -826,6 +829,8 @@ static void vc4_dsi_bridge_post_disable(struct drm_bridge *bridge,
guard(mutex)(&dsi->transfer_lock);
+ dsi->enabled = false;
+
clk_disable_unprepare(dsi->pll_phy_clock);
clk_disable_unprepare(dsi->escape_clock);
clk_disable_unprepare(dsi->pixel_clock);
@@ -1151,6 +1156,8 @@ static void vc4_dsi_bridge_pre_enable(struct drm_bridge *bridge,
DSI_PORT_WRITE(DISP0_CTRL,
DSI_DISP0_COMMAND_MODE);
}
+
+ dsi->enabled = true;
}
static void vc4_dsi_bridge_enable(struct drm_bridge *bridge,
@@ -1184,10 +1191,9 @@ static int vc4_dsi_bridge_attach(struct drm_bridge *bridge,
&dsi->bridge, flags);
}
-static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
- const struct mipi_dsi_msg *msg)
+static ssize_t vc4_dsi_transfer(struct vc4_dsi *dsi,
+ const struct mipi_dsi_msg *msg)
{
- struct vc4_dsi *dsi = host_to_dsi(host);
struct drm_device *drm = dsi->bridge.dev;
struct mipi_dsi_packet packet;
u32 pkth = 0, pktc = 0;
@@ -1195,8 +1201,6 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
bool is_long = mipi_dsi_packet_format_is_long(msg->type);
u32 cmd_fifo_len = 0, pix_fifo_len = 0;
- guard(mutex)(&dsi->transfer_lock);
-
mipi_dsi_create_packet(&packet, msg);
pkth |= VC4_SET_FIELD(packet.header[0], DSI_TXPKT1H_BC_DT);
@@ -1374,6 +1378,114 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
return ret;
}
+/*
+ * Reset the DSI block and its analog front end, then restore the
+ * configuration done in pre_enable. Resetting the FIFOs alone does not
+ * clear the state the block can be left in after a transfer timed out
+ * (e.g. on an LP receive timeout): every following transfer then times
+ * out as well, until the next modeset.
+ */
+static void vc4_dsi_reset_and_restore(struct vc4_dsi *dsi)
+{
+ u32 en = dsi->variant->port ? DSI1_CTRL_EN : DSI0_CTRL_CTRL0;
+ u32 ctrl = DSI_PORT_READ(CTRL);
+ u32 afec0 = DSI_PORT_READ(PHY_AFEC0);
+ u32 afec1 = DSI_PORT_READ(PHY_AFEC1);
+ u32 disp0 = DSI_PORT_READ(DISP0_CTRL) & ~DSI_DISP0_ENABLE;
+ u32 disp1 = DSI_PORT_READ(DISP1_CTRL);
+ u32 hstx_to = DSI_PORT_READ(HSTX_TO_CNT);
+ u32 lprx_to = DSI_PORT_READ(LPRX_TO_CNT);
+ u32 ta_to = DSI_PORT_READ(TA_TO_CNT);
+ u32 pr_to = DSI_PORT_READ(PR_TO_CNT);
+ u32 phyc = DSI_PORT_READ(PHYC);
+ u32 clt0 = DSI_PORT_READ(HS_CLT0);
+ u32 clt1 = DSI_PORT_READ(HS_CLT1);
+ u32 clt2 = DSI_PORT_READ(HS_CLT2);
+ u32 dlt3 = DSI_PORT_READ(HS_DLT3);
+ u32 dlt4 = DSI_PORT_READ(HS_DLT4);
+ u32 dlt5 = DSI_PORT_READ(HS_DLT5);
+ u32 dlt6 = DSI_PORT_READ(HS_DLT6);
+ u32 dlt7 = DSI_PORT_READ(HS_DLT7);
+
+ /*
+ * Mask the interrupts while the block and its AFE are in reset: the
+ * error interrupts would otherwise fire continuously.
+ */
+ DSI_PORT_WRITE(INT_EN, 0);
+ DSI_PORT_WRITE(DISP0_CTRL, disp0);
+ DSI_PORT_WRITE(TXPKT1C, 0);
+ DSI_PORT_WRITE(CTRL, DSI_CTRL_SOFT_RESET_CFG |
+ DSI_PORT_BIT(CTRL_RESET_FIFOS));
+ DSI_PORT_WRITE(PHY_AFEC0, afec0 | DSI_PORT_BIT(PHY_AFEC0_RESET));
+ /* AFEC reset hold time */
+ mdelay(1);
+
+ /*
+ * Same order as pre_enable: the block stays gated and the AFE in
+ * reset until the timings are programmed.
+ */
+ DSI_PORT_WRITE(CTRL, ctrl & ~(DSI_CTRL_CAL_BYTE | en));
+ DSI_PORT_WRITE(STAT, DSI_PORT_READ(STAT));
+ DSI_PORT_WRITE(PHY_AFEC1, afec1);
+ DSI_PORT_WRITE(HS_CLT0, clt0);
+ DSI_PORT_WRITE(HS_CLT1, clt1);
+ DSI_PORT_WRITE(HS_CLT2, clt2);
+ DSI_PORT_WRITE(HS_DLT3, dlt3);
+ DSI_PORT_WRITE(HS_DLT4, dlt4);
+ DSI_PORT_WRITE(HS_DLT5, dlt5);
+ DSI_PORT_WRITE(HS_DLT6, dlt6);
+ DSI_PORT_WRITE(HS_DLT7, dlt7);
+ DSI_PORT_WRITE(PHYC, phyc);
+ DSI_PORT_WRITE(CTRL, ctrl & ~en);
+ DSI_PORT_WRITE(HSTX_TO_CNT, hstx_to);
+ DSI_PORT_WRITE(LPRX_TO_CNT, lprx_to);
+ DSI_PORT_WRITE(TA_TO_CNT, ta_to);
+ DSI_PORT_WRITE(PR_TO_CNT, pr_to);
+ DSI_PORT_WRITE(DISP1_CTRL, disp1);
+ /* Ungate the block. */
+ DSI_PORT_WRITE(CTRL, ctrl);
+ /* Bring AFE out of reset. */
+ DSI_PORT_WRITE(PHY_AFEC0, afec0 & ~DSI_PORT_BIT(PHY_AFEC0_RESET));
+ DSI_PORT_WRITE(DISP0_CTRL, disp0);
+
+ DSI_PORT_WRITE(INT_STAT, DSI_PORT_READ(INT_STAT));
+ DSI_PORT_WRITE(INT_EN, DSI_PORT_BIT(INTERRUPTS_ALWAYS_ENABLED));
+}
+
+static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg)
+{
+ struct vc4_dsi *dsi = host_to_dsi(host);
+ u32 disp0_ctrl;
+ ssize_t ret;
+
+ guard(mutex)(&dsi->transfer_lock);
+
+ ret = vc4_dsi_transfer(dsi, msg);
+ /* Only a running block can be reset and restored. */
+ if (ret != -ETIMEDOUT || !dsi->enabled)
+ return ret;
+
+ /*
+ * Leave the block usable for the next transfer. The message is not
+ * resent: it may already have reached the peripheral, and only the
+ * caller knows whether it is safe to repeat.
+ */
+ disp0_ctrl = DSI_PORT_READ(DISP0_CTRL);
+ dev_warn(&dsi->pdev->dev,
+ "transfer timed out (stat 0x%08x), resetting DSI\n",
+ DSI_PORT_READ(STAT));
+ /*
+ * The completion interrupt of the timed-out transfer may still be
+ * in flight; let it finish so that it cannot complete the next one.
+ */
+ synchronize_irq(dsi->irq);
+ vc4_dsi_reset_and_restore(dsi);
+ DSI_PORT_WRITE(DISP0_CTRL, disp0_ctrl);
+
+ return ret;
+}
+
static const struct component_ops vc4_dsi_ops;
static int vc4_dsi_host_attach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
@@ -1743,14 +1855,18 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
/* Clear any existing interrupt state. */
DSI_PORT_WRITE(INT_STAT, DSI_PORT_READ(INT_STAT));
+ dsi->irq = platform_get_irq(pdev, 0);
+ if (dsi->irq < 0)
+ return dsi->irq;
+
if (dsi->reg_dma_mem)
- ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0),
+ ret = devm_request_threaded_irq(dev, dsi->irq,
vc4_dsi_irq_defer_to_thread_handler,
vc4_dsi_irq_handler,
IRQF_ONESHOT,
"vc4 dsi", dsi);
else
- ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
+ ret = devm_request_irq(dev, dsi->irq,
vc4_dsi_irq_handler, 0, "vc4 dsi", dsi);
if (ret) {
if (ret != -EPROBE_DEFER)
--
2.47.3
prev parent reply other threads:[~2026-10-26 19:17 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-26 19:14 [PATCH v3 0/4] drm/vc4: dsi: Make failed reads visible and recoverable Henry Zhang
2026-10-26 19:14 ` [PATCH v3 1/4] drm/vc4: dsi: Don't return error reports as read data Henry Zhang
2026-10-26 19:14 ` [PATCH v3 2/4] drm/vc4: dsi: Rate-limit error interrupt messages Henry Zhang
2026-10-26 19:14 ` [PATCH v3 3/4] drm/vc4: dsi: Serialize host transfers Henry Zhang
2026-10-26 19:14 ` Henry Zhang [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261026191618.9430-5-henryzhang62@gmail.com \
--to=henryzhang62@gmail.com \
--cc=airlied@gmail.com \
--cc=dave.stevenson@raspberrypi.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=kernel-list@raspberrypi.com \
--cc=linux-kernel@vger.kernel.org \
--cc=maarten.lankhorst@linux.intel.com \
--cc=mcanal@igalia.com \
--cc=mripard@kernel.org \
--cc=simona@ffwll.ch \
--cc=tzimmermann@suse.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®