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 <[email protected]> --- 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
