[PATCH v2] drm/bridge: nwl-dsi: Correct MIPI DSI horizontal sync timing

From: Sebastian Krzyszkowiak

Date: Tue Feb 17 2026 - 03:31:43 EST


From: Robert Chiras <robert.chiras@xxxxxxx>

The NWL MIPI Host controller registers set the number of bytes for the
horzontal front porch, sync pulse, and back porch, not the number of
pixels. The formula converts the hfp, hsa, and hbp to bytes then subtracts
the number of packet overhead bytes in the horizontal line which totals 32.
The overhead is split into three proportional chunks and subtracted from
fp, hsa, and hbp.

Signed-off-by: Robert Chiras <robert.chiras@xxxxxxx>
Signed-off-by: Oliver F. Brown <oliver.brown@xxxxxxxxxxx>
Fixes: 44cfc6233447 ("drm/bridge: Add NWL MIPI DSI host controller support")
[SK: Replaced division operator with div64_u64 in 64-bit divisions]
Signed-off-by: Sebastian Krzyszkowiak <sebastian.krzyszkowiak@xxxxxxx>
---
Taken from the NXP linux-imx fork. This makes it possible to e.g.
correctly drive the Librem 5's internal DSI panel at 60 Hz.
---
Changes in v2:
- Replaced division operator with div64_u64 to fix build failure on 32-bit
archs reported by kernel test robot.
- Link to v1: https://lore.kernel.org/r/20260216-nwl-sync-timing-v1-1-b0ff6ecf204a@xxxxxxx
---
drivers/gpu/drm/bridge/nwl-dsi.c | 66 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 63 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c
index 2f7429b24fc2..b99cb841983e 100644
--- a/drivers/gpu/drm/bridge/nwl-dsi.c
+++ b/drivers/gpu/drm/bridge/nwl-dsi.c
@@ -260,6 +260,10 @@ static int nwl_dsi_config_dpi(struct nwl_dsi *dsi)
bool burst_mode;
int hfront_porch, hback_porch, vfront_porch, vback_porch;
int hsync_len, vsync_len;
+ int hfp, hbp, hsa;
+ unsigned long long pclk_period;
+ unsigned long long hs_period;
+ int h_blank, pkt_hdr_len, pkt_len;

hfront_porch = dsi->mode.hsync_start - dsi->mode.hdisplay;
hsync_len = dsi->mode.hsync_end - dsi->mode.hsync_start;
@@ -313,9 +317,65 @@ static int nwl_dsi_config_dpi(struct nwl_dsi *dsi)
dsi->mode.hdisplay);
}

- nwl_dsi_write(dsi, NWL_DSI_HFP, hfront_porch);
- nwl_dsi_write(dsi, NWL_DSI_HBP, hback_porch);
- nwl_dsi_write(dsi, NWL_DSI_HSA, hsync_len);
+ pclk_period = ALIGN(PSEC_PER_SEC, dsi->mode.clock * 1000);
+ do_div(pclk_period, dsi->mode.clock * 1000);
+ DRM_DEV_DEBUG_DRIVER(dsi->dev, "pclk_period: %llu\n", pclk_period);
+
+ hs_period = ALIGN(PSEC_PER_SEC, dsi->phy_cfg.mipi_dphy.hs_clk_rate);
+ do_div(hs_period, dsi->phy_cfg.mipi_dphy.hs_clk_rate);
+ DRM_DEV_DEBUG_DRIVER(dsi->dev, "hs_period: %llu\n", hs_period);
+
+ /*
+ * Calculate the bytes needed, according to the RM formula:
+ * Time of DPI event = time to transmit x number of bytes on the DSI
+ * interface
+ * dpi_event_size * dpi_pclk_period = dsi_bytes * 8 * hs_bit_period /
+ * num_lanes
+ * ===>
+ * dsi_bytes = dpi_event_size * dpi_pclk_period * num_lanes /
+ * (8 * hs_bit_period)
+ */
+ hfp = div64_u64(hfront_porch * pclk_period * dsi->lanes, 8 * hs_period);
+ hbp = div64_u64(hback_porch * pclk_period * dsi->lanes, 8 * hs_period);
+ hsa = div64_u64(hsync_len * pclk_period * dsi->lanes, 8 * hs_period);
+
+ /* Make sure horizontal blankins are even numbers */
+ hfp = roundup(hfp, 2);
+ hbp = roundup(hbp, 2);
+ hsa = roundup(hsa, 2);
+
+ /*
+ * We need to subtract the packet header length: 32
+ * In order to make sure we don't get negative values,
+ * subtract a proportional value to the total length of the
+ * horizontal blanking duration.
+ */
+ h_blank = hfp + hbp + hsa;
+
+ pkt_len = roundup(((hfp * 100 / h_blank) * 32) / 100, 2);
+ pkt_hdr_len = pkt_len;
+ hfp -= pkt_len;
+
+ pkt_len = roundup(((hbp * 100 / h_blank) * 32) / 100, 2);
+ pkt_hdr_len += pkt_len;
+ hbp -= pkt_len;
+
+ hsa -= (32 - pkt_hdr_len);
+
+ if (dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_NO_HFP)
+ hfp = hfront_porch;
+ if (dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_NO_HBP)
+ hbp = hback_porch;
+ if (dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_NO_HSA)
+ hsa = hsync_len;
+
+ DRM_DEV_DEBUG_DRIVER(dsi->dev, "Actual hfp: %d\n", hfp);
+ DRM_DEV_DEBUG_DRIVER(dsi->dev, "Actual hbp: %d\n", hbp);
+ DRM_DEV_DEBUG_DRIVER(dsi->dev, "Actual hsa: %d\n", hsa);
+
+ nwl_dsi_write(dsi, NWL_DSI_HFP, hfp);
+ nwl_dsi_write(dsi, NWL_DSI_HBP, hbp);
+ nwl_dsi_write(dsi, NWL_DSI_HSA, hsa);

nwl_dsi_write(dsi, NWL_DSI_ENABLE_MULT_PKTS, 0x0);
nwl_dsi_write(dsi, NWL_DSI_BLLP_MODE, 0x1);

---
base-commit: 0f2acd3148e0ef42bdacbd477f90e8533f96b2ac
change-id: 20260216-nwl-sync-timing-78902ab690bd

Best regards,
--
Sebastian Krzyszkowiak <sebastian.krzyszkowiak@xxxxxxx>