All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Chaoyi Chen" <kernel@airkyi.com>
Cc: dri-devel@lists.freedesktop.org
Subject: Re: [PATCH v2 3/3] drm/rockchip: vop2: Support 8K output by splicing two video ports
Date: Thu, 29 Oct 2026 08:45:12 +0000	[thread overview]
Message-ID: <sashiko-outbox-188986@kernel.org> (raw)
In-Reply-To: <20261029081429.862-4-kernel@airkyi.com>

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] drm/rockchip: vop2: vop2_plane_atomic_check() adds unrelated CRTC on pageflips
- [High] drm/rockchip: vop2: stale splice_mode and incomplete plane conflict checks
- [High] drm/rockchip: vop2: vop2_create_crtcs() binds splice_win as VP2 primary plane
- [High] drm/rockchip: vop2: broken CFG_DONE latching on splice disable and modeset
- [High] drm/rockchip: vop2: back-to-back rk3568_vop2_setup_layer_mixer() times out
- [Medium] drm/rockchip: vop2: vop2_crtc_mode_valid() checks runtime splice_mode_right
- [Medium] drm/rockchip: vop2: unaligned Esmart downscale and NV15/20/30 split rects
--

commit fb88a534f1ee73c5e200206f6544bce1ff6db310
Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>

drm/rockchip: vop2: Support 8K output by splicing two video ports

This commit adds support for 8K display modes on RK3588 VOP2 by splicing
two video ports and splitting each plane horizontally across a pair of
hardware windows. It updates CRTC, plane, and overlay post-processing
configuration to reserve and program the partner video port and windows.

> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> index b3d6574bbd96a..acf8efe57719f 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
[ ... ]
> @@ -1012,13 +1013,32 @@ static void vop2_crtc_atomic_disable(struct drm_crtc *crtc,
>  				     struct drm_atomic_commit *state)
>  {
>  	struct vop2_video_port *vp = to_vop2_video_port(crtc);
> +	struct rockchip_crtc_state *old_vcstate;
> +	struct vop2_video_port *splice_vp = NULL;
>  	struct vop2 *vop2 = vp->vop2;
>  	struct drm_crtc_state *old_crtc_state;
> +	struct drm_plane *plane;
>  	int ret;
>  
>  	vop2_lock(vop2);
>  
>  	old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
> +	old_vcstate = to_rockchip_crtc_state(old_crtc_state);
> +	splice_vp = old_vcstate->splice_mode ? vop2_get_splice_vp(vp) : NULL;
> +
> +	/*
> +	 * The right windows are not tracked by the plane states, disable
> +	 * them before the video ports are put into standby.
> +	 */
> +	if (splice_vp) {
> +		drm_atomic_crtc_state_for_each_plane(plane, old_crtc_state) {
> +			struct vop2_win *win = to_vop2_win(plane)->splice_win;
> +
> +			if (win)
> +				vop2_win_disable(win);
> +		}
> +	}
> +
>  	drm_atomic_helper_disable_planes_on_crtc(old_crtc_state, false);
>  
>  	drm_crtc_vblank_off(crtc);
> @@ -1034,6 +1054,9 @@ static void vop2_crtc_atomic_disable(struct drm_crtc *crtc,
>  
>  	vop2_crtc_enable_irq(vp, VP_INT_DSP_HOLD_VALID);
>  
> +	if (splice_vp)
> +		vop2->ops->setup_splice(vp, false);
> +
>  	vop2_vp_write(vp, RK3568_VP_DSP_CTRL, RK3568_VP_DSP_CTRL__STANDBY);
[ ... ]
> @@ -1064,6 +1092,202 @@ static void vop2_crtc_atomic_disable(struct drm_crtc *crtc,
[ ... ]
> +static int vop2_calc_splice_rects(struct drm_plane_state *pstate,
> +				  struct drm_crtc_state *cstate,
> +				  struct drm_rect *left_src,
> +				  struct drm_rect *left_dst,
> +				  struct drm_rect *right_src,
> +				  struct drm_rect *right_dst)
> +{
> +	struct drm_rect *src = &pstate->src;
> +	struct drm_rect *dst = &pstate->dst;
> +	struct drm_framebuffer *fb = pstate->fb;
> +	int half_hdisplay = cstate->adjusted_mode.crtc_hdisplay >> 1;
> +	int hscale = drm_rect_calc_hscale(src, dst, 0, INT_MAX);
> +	int dst_w = drm_rect_width(dst);
> +	int src_w = drm_rect_width(src) >> 16;
> +	int left_dst_w, left_src_w, right_dst_w;
> +
> +	if (hscale < 0)
> +		return hscale;
> +
> +	left_dst_w = min(half_hdisplay, dst->x2) - dst->x1;
> +	if (left_dst_w < 0)
> +		left_dst_w = 0;
> +	right_dst_w = dst_w - left_dst_w;
> +
> +	if (!right_dst_w)
> +		left_src_w = src_w;
> +	else
> +		left_src_w = (left_dst_w * hscale) >> 16;
> +
> +	/*
> +	 * Make sure the yrgb/uv mst of the right window are byte aligned
> +	 * with full pixel.
> +	 */
> +	if (right_dst_w) {
> +		if (fb->format->format == DRM_FORMAT_NV15 ||
> +		    fb->format->format == DRM_FORMAT_NV20 ||
> +		    fb->format->format == DRM_FORMAT_NV30)
> +			left_src_w &= ~0x7;
> +		else if (fb->format->is_yuv)
> +			left_src_w &= ~0x1;
> +	}

[Severity: Medium]
Can vop2_calc_splice_rects() in drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
produce unaligned split rectangles for Esmart downscaling and NV15/NV20/NV30
formats?

First, in vop2_plane_atomic_check(), Esmart windows reject downscaling when
the source width is odd:

	if (!vop2_cluster_window(win) && src_w > dest_w && (src_w & 1)) {
		drm_dbg_kms(vop2->drm,
			    "Esmart windows cannot downscale odd-width source regions\n");
		return -EINVAL;
	}

That check only inspects the unsplit plane's src_w and dest_w. When
right_dst_w > 0 and !fb->format->is_yuv (RGB formats), vop2_calc_splice_rects()
computes left_src_w = (left_dst_w * hscale) >> 16 without rounding it to an
even width. An even total src_w downscaled on Esmart0/1 or Esmart2/3 can split
into an odd left_src_w and an odd right_src_w, bypassing the check in
vop2_plane_atomic_check() and programming both Esmart hardware windows to
downscale odd-width source regions.

Second, for DRM_FORMAT_NV15, DRM_FORMAT_NV20, and DRM_FORMAT_NV30 (10-bit
packed YUV where 8 pixels occupy 10 bytes), vop2_calc_splice_rects() masks
left_src_w &= ~0x7 so the right window's start offset is byte-aligned on an
8-pixel boundary. However, right_src->x1 is set to src->x1 + (left_src_w << 16),
and vop2_plane_atomic_check() only checks (src->x1 >> 16) % 2. When
src->x1 >> 16 is 2, 4, or 6, neither left_src->x1 >> 16 nor right_src->x1 >> 16
is a multiple of 8, causing drm_format_info_min_pitch() in
vop2_win_atomic_update() to compute non-byte-aligned yrgb_mst and uv_mst DMA
addresses.

Should vop2_calc_splice_rects() and vop2_plane_atomic_check() enforce 8-pixel
alignment on src_x for NV15/NV20/NV30 and ensure split Esmart downscale source
widths are even?

[ ... ]
> @@ -1122,15 +1347,43 @@ static int vop2_plane_atomic_check(struct drm_plane *plane,
>  		return -EINVAL;
>  	}
>  
> -	if (src_w > vop2_data->max_input.width ||
> -	    src_h > vop2_data->max_input.height) {
> +	max_src_w = vop2_data->max_input.width;
> +	if (vop2_vp_uses_splice(vp, cstate))
> +		max_src_w *= 2;
> +
> +	if (src_w > max_src_w || src_h > vop2_data->max_input.height) {
>  		drm_dbg_kms(vop2->drm, "Invalid source: %dx%d. max input: %dx%d\n",
> -			    src_w, src_h,
> -			    vop2_data->max_input.width,
> -			    vop2_data->max_input.height);
> +			    src_w, src_h, max_src_w, vop2_data->max_input.height);
>  		return -EINVAL;
>  	}
>  
> +	if (vop2_vp_uses_splice(vp, cstate)) {
> +		ret = vop2_plane_splice_check(pstate, cstate);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (win->left_win) {
> +		struct drm_plane_state *lstate;
> +		struct drm_crtc_state *lcstate;
> +
> +		lstate = drm_atomic_get_plane_state(astate, &win->left_win->base);
> +		if (IS_ERR(lstate))
> +			return PTR_ERR(lstate);
> +
> +		if (lstate->crtc) {
> +			lcstate = drm_atomic_get_crtc_state(astate, lstate->crtc);
> +			if (IS_ERR(lcstate))
> +				return PTR_ERR(lcstate);
> +
> +			if (to_rockchip_crtc_state(lcstate)->splice_mode) {
> +				drm_dbg_kms(vop2->drm, "%s is in use by %s in splice mode\n",
> +					    win->left_win->data->name, win->data->name);
> +				return -EBUSY;
> +			}
> +		}
> +	}

[Severity: High]
Does vop2_plane_atomic_check() in drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
pull an unrelated CRTC into non-modeset pageflip commits whenever win->left_win
is set?

In vop2_win_init(), win->left_win is permanently populated at probe time for
Cluster1-win0, Cluster3-win0, Esmart1-win0, and Esmart3-win0. When userspace
submits a normal non-modeset plane update or pageflip (where
astate->allow_modeset is false) using any of those planes (for example, VP1's
primary plane Cluster1-win0 or VP3's primary plane Cluster3-win0, even when no
CRTC is using splice mode), vop2_plane_atomic_check() unconditionally calls
drm_atomic_get_plane_state(astate, &win->left_win->base) and, when
lstate->crtc is non-NULL, drm_atomic_get_crtc_state(astate, lstate->crtc).

When win->left_win is active on another CRTC (such as Cluster0-win0 on VP0),
adding that unrelated CRTC state to a commit with allow_modeset == false
triggers the kernel warning in drm_atomic_check_only():

	if (affected_crtc != requested_crtc) {
		drm_dbg_atomic(dev,
			       "driver added CRTC to commit: requested 0x%x, affected 0x%0x\n",
			       requested_crtc, affected_crtc);
		WARN(!state->allow_modeset, "adding CRTC not allowed without modesets: requested 0x%x, affected 0x%0x\n",
		     requested_crtc, affected_crtc);
	}

In addition, pulling the other CRTC and plane into astate causes stall_checks()
in drm_atomic_helper_setup_commit() to fail independent non-blocking pageflips
with -EBUSY whenever the other CRTC has an in-flight flip, and attaches a new
drm_crtc_commit to the other CRTC so that drm_atomic_helper_commit_planes()
reprograms and waits for vblank on the unrelated CRTC on every flip.

[ ... ]
> @@ -1524,6 +1824,16 @@ static enum drm_mode_status vop2_crtc_mode_valid(struct drm_crtc *crtc,
>  {
>  	struct vop2_video_port *vp = to_vop2_video_port(crtc);
>  
> +	/*
> +	 * For RK3588, VP0 and VP1 will be both used in splice mode. All display
> +	 * modes of the right VP should be set as invalid when vop2 is working in
> +	 * splice mode.
> +	 */
> +	if (vp->splice_mode_right) {
> +		DRM_DEV_DEBUG(vp->vop2->dev, "vp%d is in splice mode right\n", vp->id);
> +		return MODE_BAD;
> +	}
> +
>  	if (mode->hdisplay > vp->data->max_output.width)
>  		return MODE_BAD_HVALUE;

[Severity: Medium]
Is it safe for vop2_crtc_mode_valid() in
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c to reject modes based on the
runtime vp->splice_mode_right flag?

vp->splice_mode_right is updated under vop2_lock(vop2) during commit_tail in
vop2_crtc_atomic_enable() and vop2_crtc_atomic_disable(). Reading it unlocked
in vop2_crtc_mode_valid() causes drm_helper_probe_single_connector_modes() to
prune all modes on VP1's connector while VP0 is running in 8K splice mode.

Furthermore, because drm_atomic_helper_check_modeset() invokes mode_valid()
before atomic_check and before commit_tail clears vp->splice_mode_right, any
atomic commit (or DRM_MODE_ATOMIC_TEST_ONLY check) that switches VP0 out of 8K
splice mode and enables VP1 in the same transaction will be rejected by
vop2_crtc_mode_valid() with MODE_BAD (-EINVAL).

[ ... ]
> @@ -1984,21 +2319,100 @@ static int vop2_crtc_atomic_check(struct drm_crtc *crtc,
>  				  struct drm_atomic_commit *state)
>  {
>  	struct vop2_video_port *vp = to_vop2_video_port(crtc);
> +	struct vop2 *vop2 = vp->vop2;
> +	const struct vop2_video_port_data *vp_data = &vop2->data->vp[vp->id];
> +	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
> +	struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc_state);
> +	struct vop2_video_port *splice_vp;
>  	struct drm_plane *plane;
>  	int nplanes = 0;
> -	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
>  	int ret;
>  
>  	ret = vop2_crtc_atomic_check_gamma(vp, crtc, state, crtc_state);
>  	if (ret)
>  		return ret;
>  
> +	/* Check if left_vp is in use */
> +	if (crtc_state->enable && drm_atomic_crtc_needs_modeset(crtc_state)) {
> +		struct vop2_video_port *left_vp = vop2_find_left_splice_vp(vop2, vp->id);
> +
> +		if (left_vp && left_vp->crtc.state) {
> +			struct drm_crtc_state *lstate, *lold;
> +
> +			lstate = drm_atomic_get_crtc_state(state, &left_vp->crtc);
> +			if (IS_ERR(lstate))
> +				return PTR_ERR(lstate);
> +
> +			lold = drm_atomic_get_old_crtc_state(state, &left_vp->crtc);
> +			if (to_rockchip_crtc_state(lold)->splice_mode) {
> +				drm_dbg_kms(vop2->drm, "vp%d is in use by vp%d in splice mode\n",
> +					    vp->id, left_vp->id);
> +				return -EBUSY;
> +			}
> +		}
> +	}

[Severity: High]
Can the splice conflict checks across vop2_plane_atomic_check() and
vop2_crtc_atomic_check() in drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
examine stale splice_mode state or miss plane conflicts?

There appear to be four related issues in these checks:

1. In drm_atomic_helper_check_planes(), all plane->atomic_check() callbacks
(vop2_plane_atomic_check()) run before any crtc->atomic_check() callbacks
(vop2_crtc_atomic_check()). Because vcstate->splice_mode is only updated in
vop2_crtc_atomic_check(), when vop2_plane_atomic_check() checks
to_rockchip_crtc_state(lcstate)->splice_mode, it reads the stale splice_mode
value duplicated from the previous commit rather than whether lstate->crtc uses
splice mode in the new commit.

2. In vop2_crtc_atomic_check() above, lstate is fetched via
drm_atomic_get_crtc_state(state, &left_vp->crtc) and left unused, while the
check inspects to_rockchip_crtc_state(lold)->splice_mode on the old CRTC state.
If userspace switches left_vp (VP0) from an 8K splice mode to a non-splice mode
(or disables VP0) and enables vp (VP1) in the same atomic commit, both
vop2_crtc_atomic_check() and vop2_plane_atomic_check() falsely reject the
commit with -EBUSY.

3. In vop2_plane_atomic_check(), the early return for invisible planes:

	if (!pstate->visible)
		return 0;

runs before vop2_plane_splice_check() and before the if (win->left_win) check.
A plane attached to a CRTC (pstate->crtc != NULL) that is clipped off-screen
(!pstate->visible) remains in crtc_state->plane_mask. During commit,
rk3568_vop2_setup_overlay() in drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
iterates over crtc->state->plane_mask via drm_atomic_crtc_for_each_plane() and
reassigns the window in RK3568_OVL_PORT_SEL, while vop2_plane_atomic_update()
calls vop2_plane_atomic_disable() -> vop2_win_disable(win) on the active right
splice window.

4. Unlike vop2_crtc_atomic_check() (which checks both rstate->crtc and
old_rstate->crtc), vop2_plane_atomic_check() only checks if (lstate->crtc) and
does not check old_lstate->crtc. If win->left_win is disabled on an active
splice CRTC in the same commit that win is enabled on another CRTC,
vop2_crtc_atomic_begin() on the other CRTC can migrate win's
RK3568_OVL_PORT_SEL before win->left_win is disabled, and
vop2_plane_atomic_disable(win->left_win) will race with
vop2_plane_atomic_update(win) on the shared hardware window.

[ ... ]
> @@ -2018,6 +2432,7 @@ static void vop2_crtc_atomic_flush(struct drm_crtc *crtc,
>  	struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
>  	struct vop2_video_port *vp = to_vop2_video_port(crtc);
>  	struct vop2 *vop2 = vp->vop2;
> +	bool splice = false;
>  
>  	/* In case of modeset, gamma lut update already happened in atomic enable */
>  	if (!drm_atomic_crtc_needs_modeset(crtc_state) && crtc_state->color_mgmt_changed)
> @@ -2025,7 +2440,12 @@ static void vop2_crtc_atomic_flush(struct drm_crtc *crtc,
>  
>  	vop2_post_config(crtc, false, crtc_state, old_crtc_state);
>  
> -	vop2_cfg_done(vp);
> +	/* When the splice_mode state changes, an update is also required */
> +	if (to_rockchip_crtc_state(crtc_state)->splice_mode ||
> +	    to_rockchip_crtc_state(old_crtc_state)->splice_mode)
> +		splice = true;
> +
> +	vop2_cfg_done(vp, splice);

[Severity: High]
What happens to RK3568_REG_CFG_DONE when disabling a spliced CRTC or
modesetting it from an 8K splice mode to a non-splice mode?

In vop2_crtc_atomic_disable() in drivers/gpu/drm/rockchip/rockchip_drm_vop2.c,
when old_vcstate->splice_mode is true, the function disables the right windows
via vop2_win_disable(win), disables the left windows via
drm_atomic_helper_disable_planes_on_crtc(old_crtc_state, false), and clears
RK3568_LUT_PORT_SEL__SPLICE_EN via vop2->ops->setup_splice(vp, false) before
writing RK3568_VP_DSP_CTRL__STANDBY, without calling vop2_cfg_done().

If the CRTC is being disabled (crtc_state->active == false),
drm_atomic_helper_commit_planes() is called with DRM_PLANE_COMMIT_ACTIVE_ONLY,
so vop2_crtc_atomic_flush() is skipped and vop2_cfg_done() is never called to
latch the disabled windows.

Conversely, if the CRTC is modesetting from an 8K splice mode
(old_crtc_state->splice_mode == true) to a non-splice mode
(crtc_state->splice_mode == false, crtc_state->active == true),
vop2_crtc_atomic_disable() has already cleared RK3568_LUT_PORT_SEL__SPLICE_EN
and splice_vp (VP1) remains in standby with no running dclk or vsync. When
vop2_crtc_atomic_flush() then sets splice = true because
to_rockchip_crtc_state(old_crtc_state)->splice_mode is true,
vop2_cfg_done(vp, true) sets BIT(splice_vp_id) in RK3568_REG_CFG_DONE. Because
SPLICE_EN is already 0 and splice_vp is in standby, BIT(splice_vp_id) in
RK3568_REG_CFG_DONE will never clear, causing subsequent layer updates in
rk3568_vop2_setup_layer_mixer() to see cfg_done & ~own_cfg_done_mask != 0 and
busy-wait for 50 ms until timeout in rk3568_vop2_wait_for_layer_cfg_done().

[ ... ]
> @@ -2510,6 +2930,7 @@ static int vop2_create_crtcs(struct vop2 *vop2)
>  	struct device_node *port;
>  	struct vop2_video_port *vp;
>  	struct vop2_win *win;
> +	bool vp_used[ROCKCHIP_MAX_CRTC] = {};
>  	u32 possible_crtcs;
>  	int i, j, nvp, nvps = 0;
>  	int ret;
> @@ -2543,9 +2964,23 @@ static int vop2_create_crtcs(struct vop2 *vop2)
>  			return dev_err_probe(drm->dev, -ENOENT,
>  					     "no port node found for video_port%d\n", i);
>  		vp->crtc.port = port;
> -		nvps++;
> +		vp_used[i] = true;
>  	}
>  
> +	/* reserved vp for 8K mode */
> +	for (i = 0; i < vop2_data->nr_vps; i++) {
> +		if (!(vop2_data->feature & VOP2_FEATURE_SPLICE) || !vp_used[i])
> +			continue;
> +		if (vop2->vps[i].data->max_output.width <= VOP2_MAX_VP_OUTPUT_WIDTH)
> +			continue;
> +
> +		vp_used[vop2->vps[i].data->splice_vp_id] = true;
> +	}
> +
> +	for (i = 0; i < vop2_data->nr_vps; i++)
> +		if (vp_used[i])
> +			nvps++;
> +
>  	nvp = 0;
>  	/* Register a primary plane for every crtc */
>  	for (i = 0; i < vop2_data->nr_vps; i++) {

[Severity: High]
In vop2_create_crtcs() in drivers/gpu/drm/rockchip/rockchip_drm_vop2.c, what
happens on RK3588 boards where VP0 and VP2 (or VP3) are enabled in device tree
while VP1 is unconnected?

The loop above marks vp_used[1] = true for VP1 (splice_vp_id = 1), but
vp1->crtc.port remains NULL when VP1 has no DT remote node. In the primary
plane registration loop immediately following:

	for (i = 0; i < vop2_data->nr_vps; i++) {
		vp = &vop2->vps[i];

		if (!vp->crtc.port)
			continue;

		for (j = 0; j < vop2->registered_num_wins; j++) {
			win = &vop2->win[j];

VP0 (i = 0) gets Cluster0-win0 as its primary plane, VP1 (i = 1) is skipped
because !vp->crtc.port, and then VP2 (i = 2) or VP3 (i = 3) is assigned the
next unregistered DRM_PLANE_TYPE_PRIMARY window, which is Cluster1-win0
(Cluster0-win0's splice_win), with possible_crtcs restricted exclusively to
that CRTC, while Cluster2-win0 becomes an overlay plane.

Because Cluster1-win0 is the right splice partner of VP0's primary plane
Cluster0-win0, won't this make 8K splice mode on VP0 and normal display output
on VP2/VP3 mutually exclusive (-EBUSY) on boards with unconnected VP1?

[ ... ]
> diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
> index c44648e1bc0d6..08f9f8cac8789 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
[ ... ]
> @@ -2286,7 +2331,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp)
>  	if (layer_sel != vop2->old_layer_sel && atv_layer_sel != vop2->old_layer_sel) {
>  		cfg_done = vop2_readl(vop2, RK3568_REG_CFG_DONE);
>  		cfg_done &= (BIT(vop2->data->nr_vps) - 1);
> -		cfg_done &= ~BIT(vp->id);
> +		cfg_done &= ~own_cfg_done_mask;
>  		/*
>  		 * Changes of other VPs' overlays have not taken effect
>  		 */
> @@ -2295,13 +2340,13 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp)
>  	}
>  
>  	if (layer_sel != vop2->old_layer_sel || port_sel != vop2->old_port_sel)
> -		ovl_ctrl |= FIELD_PREP(RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL, vp->id);
> +		ovl_ctrl |= FIELD_PREP(RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL, main_vp->id);
>  	vop2_writel(vop2, RK3568_OVL_CTRL, ovl_ctrl);
>  
>  	if (port_sel != vop2->old_port_sel) {
>  		vop2->old_port_sel = port_sel;
>  		vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel);
> -		vop2_cfg_done(vp);
> +		vop2_cfg_done(main_vp, vcstate->splice_mode);
>  		rk3568_vop2_wait_for_port_mux_done(vop2);
>  	}
[ ... ]
> @@ -2354,33 +2399,59 @@ static void rk3568_vop2_setup_dly_for_windows(struct vop2_video_port *vp)
[ ... ]
> +static void rk3568_vop2_setup_overlay(struct vop2_video_port *vp)
> +{
> +	struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(vp->crtc.state);
> +	struct vop2_video_port *splice_vp = NULL;
> +	struct drm_plane *plane;
> +
> +	vp->win_mask = 0;
> +	if (vcstate->splice_mode) {
> +		splice_vp = vop2_get_splice_vp(vp);
> +		splice_vp->win_mask = 0;
> +	}
> +
> +	/* The right video port shares the plane state of the left one */
> +	drm_atomic_crtc_for_each_plane(plane, &vp->crtc) {
> +		struct vop2_win *win = to_vop2_win(plane);
> +
> +		vp->win_mask |= BIT(win->data->phys_id);
> +		if (splice_vp && win->splice_win)
> +			splice_vp->win_mask |= BIT(win->splice_win->data->phys_id);
> +	}
> +
> +	rk3568_vop2_setup_overlay_for_vp(vp);
> +	if (splice_vp)
> +		rk3568_vop2_setup_overlay_for_vp(splice_vp);
> +}

[Severity: High]
Does calling rk3568_vop2_setup_overlay_for_vp(vp) followed immediately by
rk3568_vop2_setup_overlay_for_vp(splice_vp) in rk3568_vop2_setup_overlay() in
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c cause a 50 ms busy-wait timeout in
rk3568_vop2_setup_layer_mixer()?

During vop2_crtc_atomic_begin(), the first call to
rk3568_vop2_setup_layer_mixer(vp) updates vop2->old_layer_sel = layer_sel and
writes the shadowed register RK3568_OVL_LAYER_SEL. Because vop2_cfg_done() is
not called until vop2_crtc_atomic_flush(), reading RK3568_OVL_LAYER_SEL still
returns the pre-commit active hardware value.

In the immediate second call to rk3568_vop2_setup_layer_mixer(splice_vp):

1. atv_layer_sel = vop2_readl(vop2, RK3568_OVL_LAYER_SEL) still equals the old
hardware value, so atv_layer_sel != vop2->old_layer_sel is true, and
layer_sel != vop2->old_layer_sel is also true when splice_vp updates its
right-half layers. If any other video port (such as VP2 or VP3) has a pending
CFG_DONE bit in RK3568_REG_CFG_DONE, rk3568_vop2_setup_layer_mixer(splice_vp)
calls rk3568_vop2_wait_for_layer_cfg_done(vop2), which busy-polls for
vop2_readl(vop2, RK3568_OVL_LAYER_SEL) == vop2->old_layer_sel. Because
RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL was set to main_vp->id by the first call
and vop2_cfg_done(main_vp, ...) has not been called yet, vop2->old_layer_sel
cannot latch during vop2_crtc_atomic_begin(), guaranteeing a 50 ms spin
timeout.

2. If port_sel != vop2->old_port_sel in the second call
rk3568_vop2_setup_layer_mixer(splice_vp), it calls
vop2_cfg_done(main_vp, vcstate->splice_mode) and waits for vsync in
rk3568_vop2_wait_for_port_mux_done(vop2), which latches the first call's
left-half RK3568_OVL_LAYER_SEL a frame early before splice_vp's
RK3568_OVL_LAYER_SEL and window registers have been programmed.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029081429.862-1-kernel@airkyi.com?part=3

      reply	other threads:[~2026-10-29  8:45 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-29  8:14 [PATCH v2 0/3] drm/rockchip: vop2: Add 8K support for RK3588 vop2 Chaoyi Chen
2026-10-29  8:14 ` Chaoyi Chen
2026-10-29  8:14 ` [PATCH v2 1/3] drm/rockchip: vop2: Use drm_format_info_min_pitch() for plane offsets Chaoyi Chen
2026-10-29  8:14   ` Chaoyi Chen
2026-10-29  8:34   ` sashiko-bot
2026-10-29  8:14 ` [PATCH v2 2/3] drm/rockchip: vop2: Skip UV plane setup for packed YUV formats Chaoyi Chen
2026-10-29  8:14   ` Chaoyi Chen
2026-10-29  8:14 ` [PATCH v2 3/3] drm/rockchip: vop2: Support 8K output by splicing two video ports Chaoyi Chen
2026-10-29  8:14   ` Chaoyi Chen
2026-10-29  8:45   ` sashiko-bot [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=sashiko-outbox-188986@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=kernel@airkyi.com \
    --cc=sashiko-reviews@lists.linux.dev \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.