From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MCC45TR Date: Tue, 25 Aug 2026 17:31:10 +0300 Subject: [PATCH 07/21] drm: nabu: fix seamless refresh and live touch sync on 7.2 --- drivers/gpu/drm/msm/dsi/dsi_host.c | 25 +++++++++++++++ drivers/gpu/drm/msm/msm_atomic.c | 8 ++++- drivers/gpu/drm/panel/panel-novatek-nt36523.c | 31 ++++++++++++------- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c index 5081cdbc7ebe..ab1645a70f1e 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_host.c +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c @@ -100,6 +100,7 @@ static int dsi_get_version(const void __iomem *base, u32 *major, u32 *minor) DSI_CLK_CTRL_FORCE_ON_DYN_AHBM_HCLK) /* DSI 6G v2.x timing shadow database used for porch-only DFPS updates. */ +#define REG_DSI_TIMING_DB_TRIGGER 0x01e4 #define REG_DSI_TIMING_DB_MODE 0x01e8 struct msm_dsi_host { @@ -1219,11 +1220,26 @@ static void dsi_wait4video_done(struct msm_dsi_host *msm_host) static void dsi_wait4video_eng_busy(struct msm_dsi_host *msm_host) { + const struct msm_dsi_cfg_handler *cfg_hnd = msm_host->cfg_hnd; u32 data; if (!(msm_host->mode_flags & MIPI_DSI_MODE_VIDEO)) return; + /* + * DSI 6G v1.2 and newer can hold a command DMA trigger until the + * active video frame has finished. dsi_host_config() enables that + * behaviour with DSI_TRIG_CTRL_BLOCK_DMA_WITHIN_FRAME. Waiting for a + * VIDEO_DONE interrupt in software as well is both redundant and + * unreliable during a porch-only timing database switch: the interrupt + * can belong to the frame which latched the new timing and be observed + * before this waiter is armed. Let the hardware place the command in + * the following blanking interval instead. + */ + if (cfg_hnd->major == MSM_DSI_VER_MAJOR_6G && + cfg_hnd->minor >= MSM_DSI_6G_VER_MINOR_V1_2) + return; + data = dsi_read(msm_host, REG_DSI_STATUS0); /* if video mode engine is not busy, its because @@ -2691,6 +2707,15 @@ int msm_dsi_host_stage_seamless(struct mipi_dsi_host *host, dsi_write(msm_host, REG_DSI_TIMING_DB_MODE, 1); dsi_timing_setup(msm_host, is_bonded_dsi); msm_host->mode = active_mode; + + /* + * Enabling the timing database only redirects timing writes to the + * shadow bank. DSI 6G requires a separate trigger at 0x1e4 to arm + * those values for the next video frame boundary. Without this write + * DRM reports the new mode while scanout remains at the old refresh. + */ + wmb(); + dsi_write(msm_host, REG_DSI_TIMING_DB_TRIGGER, 1); wmb(); msm_host->seamless_staged = true; diff --git a/drivers/gpu/drm/msm/msm_atomic.c b/drivers/gpu/drm/msm/msm_atomic.c index 786759bdf7c0..72a25482a789 100644 --- a/drivers/gpu/drm/msm/msm_atomic.c +++ b/drivers/gpu/drm/msm/msm_atomic.c @@ -459,8 +459,14 @@ void msm_atomic_commit_tail(struct drm_atomic_commit *state) msm_atomic_mask_seamless_modesets(state, true); drm_atomic_helper_commit_modeset_disables(dev, state); msm_atomic_mask_seamless_modesets(state, false); - msm_atomic_commit_seamless_modesets(state); drm_atomic_helper_commit_planes(dev, state, 0); + /* + * dpu_crtc_atomic_begin(), called by commit_planes(), clears the CTL + * pending-flush mask before programming the planes. Program seamless + * interface timing afterwards so its INTF flush bit survives until + * dpu_kms_flush_commit() latches the new DPU frame period. + */ + msm_atomic_commit_seamless_modesets(state); msm_atomic_mask_seamless_modesets(state, true); drm_atomic_helper_commit_modeset_enables(dev, state); msm_atomic_mask_seamless_modesets(state, false); diff --git a/drivers/gpu/drm/panel/panel-novatek-nt36523.c b/drivers/gpu/drm/panel/panel-novatek-nt36523.c index aed02f1875e9..72d8389792fd 100644 --- a/drivers/gpu/drm/panel/panel-novatek-nt36523.c +++ b/drivers/gpu/drm/panel/panel-novatek-nt36523.c @@ -1252,7 +1252,7 @@ static void nt36523_reset(struct panel_info *pinfo) usleep_range(12000, 13000); } -static int nt36523_sync_pen_fps(struct panel_info *pinfo) +static int nt36523_sync_pen_fps(struct panel_info *pinfo, bool panel_init) { struct mipi_dsi_device *dsi0 = pinfo->dsi[0]; struct mipi_dsi_device *dsi1 = pinfo->dsi[1]; @@ -1266,7 +1266,10 @@ static int nt36523_sync_pen_fps(struct panel_info *pinfo) /* Xiaomi's downstream panel commands synchronize the TDDI scan rate. */ mipi_dsi_dual_dcs_write_seq_multi(dsi_ctx, dsi0, dsi1, 0xff, 0x2a); - mipi_dsi_dual_dcs_write_seq_multi(dsi_ctx, dsi0, dsi1, 0xfb, 0x01); + /* The vendor's live sync sequence omits the page-unlock write. */ + if (panel_init) + mipi_dsi_dual_dcs_write_seq_multi(dsi_ctx, dsi0, dsi1, + 0xfb, 0x01); if (pinfo->refresh_rate == 60) mipi_dsi_dual_dcs_write_seq_multi(dsi_ctx, dsi0, dsi1, 0x23, 0x0c); @@ -1291,19 +1294,25 @@ static void nt36523_mode_set(struct drm_panel *panel, const struct drm_display_mode *mode) { struct panel_info *pinfo = to_panel_info(panel); + int ret; pinfo->refresh_rate = drm_mode_vrefresh(mode); /* - * Do not insert vendor TDDI DCS commands into an active dual-DSI video - * stream. The MSM host cannot complete those transfers at the live - * video boundary on nabu: all four commands time out and the stalled - * stream repeats scanlines, most visibly when switching to 60 Hz. - * - * The host and DPU VFP update remains seamless. nt36523_prepare() - * still programs the TDDI scan band after panel initialization, where - * command transfer is reliable and cannot corrupt active scanout. + * The MSM atomic path calls the downstream panel bridge only after both + * DSI timing databases have latched the new porch and a real CRTC vblank + * has elapsed. Synchronize the integrated touch/pen scan band at that + * point. On DSI 6G the command DMA is held outside the active frame by + * BLOCK_DMA_WITHIN_FRAME, avoiding the VIDEO_DONE timeout and scanline + * repetition seen when this sequence was issued before the timing latch. */ + if (panel->prepared && pinfo->desc->sync_pen_fps) { + ret = nt36523_sync_pen_fps(pinfo, false); + if (ret < 0) + dev_err(pinfo->panel.dev, + "failed live pen synchronization for %u Hz: %d\n", + pinfo->refresh_rate, ret); + } } static int nt36523_prepare(struct drm_panel *panel) @@ -1327,7 +1336,7 @@ static int nt36523_prepare(struct drm_panel *panel) } if (pinfo->desc->sync_pen_fps) { - ret = nt36523_sync_pen_fps(pinfo); + ret = nt36523_sync_pen_fps(pinfo, true); if (ret < 0) { regulator_disable(pinfo->vddio); return ret;