* [PATCH v2 0/3] drm/rockchip: vop2: Add 8K support for RK3588 vop2 @ 2026-10-29 8:14 ` Chaoyi Chen 0 siblings, 0 replies; 10+ messages in thread From: Chaoyi Chen @ 2026-10-29 8:14 UTC (permalink / raw) To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel, Chaoyi Chen, Cristian Ciocaltea, Sebastian Reichel From: Chaoyi Chen <chaoyi.chen@rock-chips.com> Hi, This series adds support for 8K output on the RK3588 VOP2. HDMI is not ready yet, but I believe many people might be interested in this. 8K output on the RK3588 is done by splicing two VPs together, with VP0 outputting the left half of the screen and VP1 the right half. In addition, 8K planes have to be split in half as well to stay within the width limit of a single plane. Similarly, there are a few post-processing details that need to be handled. Note that splice mode is not only usable for 8K output. This means the mode can be tested at 4K and even lower resolutions, by simply changing this one line: -#define VOP2_MAX_VP_OUTPUT_WIDTH 4096 +#define VOP2_MAX_VP_OUTPUT_WIDTH (3840 / 2) /* for 4K test */ And yes, this has been tested at 4K over HDMI on the RK3588 EVB1 :) Changes in v2: - Link to v1: https://lore.kernel.org/all/20261022071737.508-1-kernel@airkyi.com/ - Missing removal of the replaced line(Heiko). - vop2_win_get_pstate(): use the left window's state only in splice mode(Sashiko). - Reject splice windows already in use by another CRTC(Sashiko). - Check the splice partner via drm_atomic_get_crtc_state() instead of the runtime flag and unlocked read(Sashiko). - Set splice_mode_right/left_vp before the error paths of vop2_crtc_atomic_enable()(Sashiko). - Validate each split half against the min width 4 and max input width; align NV20/NV30 like NV15(Sashiko). - Latch overlay registers at the left VP's vsync; don't wait for the partner's own cfg-done(Sashiko). - Program the color key on the window actually being updated(Sashiko). Cc: Cristian Ciocaltea <cristian.ciocaltea@collabora.com> Cc: Sebastian Reichel <sebastian.reichel@collabora.com> --- Chaoyi Chen (3): drm/rockchip: vop2: Use drm_format_info_min_pitch() for plane offsets drm/rockchip: vop2: Skip UV plane setup for packed YUV formats drm/rockchip: vop2: Support 8K output by splicing two video ports drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 5 + drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 535 +++++++++++++++++-- drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 66 ++- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 204 +++++-- 4 files changed, 721 insertions(+), 89 deletions(-) -- 2.53.0 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 0/3] drm/rockchip: vop2: Add 8K support for RK3588 vop2 @ 2026-10-29 8:14 ` Chaoyi Chen 0 siblings, 0 replies; 10+ messages in thread From: Chaoyi Chen @ 2026-10-29 8:14 UTC (permalink / raw) To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter Cc: linux-kernel, dri-devel, Chaoyi Chen, linux-rockchip, Sebastian Reichel, linux-arm-kernel From: Chaoyi Chen <chaoyi.chen@rock-chips.com> Hi, This series adds support for 8K output on the RK3588 VOP2. HDMI is not ready yet, but I believe many people might be interested in this. 8K output on the RK3588 is done by splicing two VPs together, with VP0 outputting the left half of the screen and VP1 the right half. In addition, 8K planes have to be split in half as well to stay within the width limit of a single plane. Similarly, there are a few post-processing details that need to be handled. Note that splice mode is not only usable for 8K output. This means the mode can be tested at 4K and even lower resolutions, by simply changing this one line: -#define VOP2_MAX_VP_OUTPUT_WIDTH 4096 +#define VOP2_MAX_VP_OUTPUT_WIDTH (3840 / 2) /* for 4K test */ And yes, this has been tested at 4K over HDMI on the RK3588 EVB1 :) Changes in v2: - Link to v1: https://lore.kernel.org/all/20261022071737.508-1-kernel@airkyi.com/ - Missing removal of the replaced line(Heiko). - vop2_win_get_pstate(): use the left window's state only in splice mode(Sashiko). - Reject splice windows already in use by another CRTC(Sashiko). - Check the splice partner via drm_atomic_get_crtc_state() instead of the runtime flag and unlocked read(Sashiko). - Set splice_mode_right/left_vp before the error paths of vop2_crtc_atomic_enable()(Sashiko). - Validate each split half against the min width 4 and max input width; align NV20/NV30 like NV15(Sashiko). - Latch overlay registers at the left VP's vsync; don't wait for the partner's own cfg-done(Sashiko). - Program the color key on the window actually being updated(Sashiko). Cc: Cristian Ciocaltea <cristian.ciocaltea@collabora.com> Cc: Sebastian Reichel <sebastian.reichel@collabora.com> --- Chaoyi Chen (3): drm/rockchip: vop2: Use drm_format_info_min_pitch() for plane offsets drm/rockchip: vop2: Skip UV plane setup for packed YUV formats drm/rockchip: vop2: Support 8K output by splicing two video ports drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 5 + drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 535 +++++++++++++++++-- drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 66 ++- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 204 +++++-- 4 files changed, 721 insertions(+), 89 deletions(-) -- 2.53.0 _______________________________________________ Linux-rockchip mailing list Linux-rockchip@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-rockchip ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 1/3] drm/rockchip: vop2: Use drm_format_info_min_pitch() for plane offsets 2026-10-29 8:14 ` Chaoyi Chen @ 2026-10-29 8:14 ` Chaoyi Chen -1 siblings, 0 replies; 10+ messages in thread From: Chaoyi Chen @ 2026-10-29 8:14 UTC (permalink / raw) To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel, Chaoyi Chen, Cristian Ciocaltea, Sebastian Reichel From: Chaoyi Chen <chaoyi.chen@rock-chips.com> Use the block aware helper instead of cpp[], the latter aliases char_per_block and is wrong for packed 10-bit formats like NV15, NV20 and NV30. Tested with modetest[0]: ``` modetest -s 86:3840x2160 -P \ 59@83:3940x2160~96,0,3840,2160+0+0@NV20 -a ``` fb size (3940x2160), src at (96, 0) with size (3840x2160), dst at (0, 0) with size (3840x2160) [0]: https://github.com/windowsair/libdrm/tree/src-pos-support Assisted-by: LLM Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> --- Changes in v2: - Missing removal of the replaced line(Heiko). --- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 4cce3e336f5b..b38a32d623b5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -1290,7 +1290,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, afbc_en = drm_is_afbc(fb->modifier); - offset = (src->x1 >> 16) * fb->format->cpp[0]; + offset = drm_format_info_min_pitch(fb->format, 0, src->x1 >> 16); /* * AFBC HDR_PTR must set to the zero offset of the framebuffer. @@ -1309,7 +1309,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, int hsub = fb->format->hsub; int vsub = fb->format->vsub; - offset = (src->x1 >> 16) * fb->format->cpp[1] / hsub; + offset = drm_format_info_min_pitch(fb->format, 1, src->x1 >> 16) / hsub; offset += (src->y1 >> 16) * fb->pitches[1] / vsub; if ((pstate->rotation & DRM_MODE_REFLECT_Y) && !afbc_en) -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 1/3] drm/rockchip: vop2: Use drm_format_info_min_pitch() for plane offsets @ 2026-10-29 8:14 ` Chaoyi Chen 0 siblings, 0 replies; 10+ messages in thread From: Chaoyi Chen @ 2026-10-29 8:14 UTC (permalink / raw) To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter Cc: linux-kernel, dri-devel, Chaoyi Chen, linux-rockchip, Sebastian Reichel, linux-arm-kernel From: Chaoyi Chen <chaoyi.chen@rock-chips.com> Use the block aware helper instead of cpp[], the latter aliases char_per_block and is wrong for packed 10-bit formats like NV15, NV20 and NV30. Tested with modetest[0]: ``` modetest -s 86:3840x2160 -P \ 59@83:3940x2160~96,0,3840,2160+0+0@NV20 -a ``` fb size (3940x2160), src at (96, 0) with size (3840x2160), dst at (0, 0) with size (3840x2160) [0]: https://github.com/windowsair/libdrm/tree/src-pos-support Assisted-by: LLM Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> --- Changes in v2: - Missing removal of the replaced line(Heiko). --- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 4cce3e336f5b..b38a32d623b5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -1290,7 +1290,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, afbc_en = drm_is_afbc(fb->modifier); - offset = (src->x1 >> 16) * fb->format->cpp[0]; + offset = drm_format_info_min_pitch(fb->format, 0, src->x1 >> 16); /* * AFBC HDR_PTR must set to the zero offset of the framebuffer. @@ -1309,7 +1309,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, int hsub = fb->format->hsub; int vsub = fb->format->vsub; - offset = (src->x1 >> 16) * fb->format->cpp[1] / hsub; + offset = drm_format_info_min_pitch(fb->format, 1, src->x1 >> 16) / hsub; offset += (src->y1 >> 16) * fb->pitches[1] / vsub; if ((pstate->rotation & DRM_MODE_REFLECT_Y) && !afbc_en) -- 2.53.0 _______________________________________________ Linux-rockchip mailing list Linux-rockchip@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-rockchip ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v2 1/3] drm/rockchip: vop2: Use drm_format_info_min_pitch() for plane offsets 2026-10-29 8:14 ` Chaoyi Chen (?) @ 2026-10-29 8:34 ` sashiko-bot -1 siblings, 0 replies; 10+ messages in thread From: sashiko-bot @ 2026-10-29 8:34 UTC (permalink / raw) To: Chaoyi Chen; +Cc: dri-devel Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] drm/rockchip: vop2: 64-bit division by hsub in vop2_plane_atomic_update() -- commit 6053e1180bcd99e2d0019c04165b9318bc1a9fc3 Author: Chaoyi Chen <chaoyi.chen@rock-chips.com> drm/rockchip: vop2: Use drm_format_info_min_pitch() for plane offsets This commit replaces fb->format->cpp[] with drm_format_info_min_pitch() when computing horizontal plane offsets in vop2_plane_atomic_update() to support packed 10-bit formats such as NV15, NV20, and NV30. > diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c > index bfb3841425687..adee018c6ef07 100644 > --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c > +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c [ ... ] > @@ -1309,7 +1309,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, > int hsub = fb->format->hsub; > int vsub = fb->format->vsub; > > - offset = (src->x1 >> 16) * fb->format->cpp[1] / hsub; > + offset = drm_format_info_min_pitch(fb->format, 1, src->x1 >> 16) / hsub; [Severity: Medium] Does dividing the return value of drm_format_info_min_pitch() by hsub in vop2_plane_atomic_update() cause a 64-bit division linker regression on 32-bit architectures? drm_format_info_min_pitch() returns a uint64_t: drivers/gpu/drm/drm_fourcc.c:drm_format_info_min_pitch() { if (!info || plane < 0 || plane >= info->num_planes) return 0; return DIV_ROUND_UP_ULL((u64)buffer_width * info->char_per_block[plane], drm_format_info_block_width(info, plane) * drm_format_info_block_height(info, plane)); } Because hsub is a runtime int variable, dividing the uint64_t result directly with '/' promotes the expression to a 64-bit unsigned division before assigning to unsigned long offset. Since CONFIG_ROCKCHIP_VOP2 can be built on 32-bit targets (via 32-bit ARM ARCH_ROCKCHIP or COMPILE_TEST), this emits an unresolved call to __aeabi_uldivmod or __udivdi3 at link time. Additionally, drm_format_info.block_w[1] is defined in units of subsampled chroma pixels (for example, block_w is { 4, 2, 0 } for NV15 and NV20), so drm_format_info_min_pitch() expects buffer_width for plane 1 to already be subsampled. Dividing by hsub after DIV_ROUND_UP_ULL() rounds up before subsampling and then truncates. Should (src->x1 >> 16) / hsub be passed as the buffer_width argument to drm_format_info_min_pitch(), or should offset be assigned before dividing by hsub as is done in vop_plane_atomic_update() in rockchip_drm_vop.c? -- Sashiko AI review · https://sashiko.dev/#/patchset/20261029081429.862-1-kernel@airkyi.com?part=1 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 2/3] drm/rockchip: vop2: Skip UV plane setup for packed YUV formats 2026-10-29 8:14 ` Chaoyi Chen @ 2026-10-29 8:14 ` Chaoyi Chen -1 siblings, 0 replies; 10+ messages in thread From: Chaoyi Chen @ 2026-10-29 8:14 UTC (permalink / raw) To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel, Chaoyi Chen, Cristian Ciocaltea, Sebastian Reichel From: Chaoyi Chen <chaoyi.chen@rock-chips.com> Packed YUV formats store chroma interleaved with luma in the same plane, so they have no plane 1 that the UV address registers could point at. Restrict both the plane 1 offset calculation and the UV register writes to formats with more than one plane. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> --- (no changes since v1) --- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index b38a32d623b5..06291a13320d 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -1305,7 +1305,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, rk_obj = to_rockchip_obj(fb->obj[0]); yrgb_mst = rk_obj->dma_addr + offset + fb->offsets[0]; - if (fb->format->is_yuv) { + if (fb->format->is_yuv && fb->format->num_planes > 1) { int hsub = fb->format->hsub; int vsub = fb->format->vsub; @@ -1460,7 +1460,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, uv_swap = vop2_win_uv_swap(fb->format->format); vop2_win_write(win, VOP2_WIN_UV_SWAP, uv_swap); - if (fb->format->is_yuv) { + if (fb->format->is_yuv && fb->format->num_planes > 1) { vop2_win_write(win, VOP2_WIN_UV_VIR, DIV_ROUND_UP(fb->pitches[1], 4)); vop2_win_write(win, VOP2_WIN_UV_MST, uv_mst); } -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 2/3] drm/rockchip: vop2: Skip UV plane setup for packed YUV formats @ 2026-10-29 8:14 ` Chaoyi Chen 0 siblings, 0 replies; 10+ messages in thread From: Chaoyi Chen @ 2026-10-29 8:14 UTC (permalink / raw) To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter Cc: linux-kernel, dri-devel, Chaoyi Chen, linux-rockchip, Sebastian Reichel, linux-arm-kernel From: Chaoyi Chen <chaoyi.chen@rock-chips.com> Packed YUV formats store chroma interleaved with luma in the same plane, so they have no plane 1 that the UV address registers could point at. Restrict both the plane 1 offset calculation and the UV register writes to formats with more than one plane. Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> --- (no changes since v1) --- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index b38a32d623b5..06291a13320d 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -1305,7 +1305,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, rk_obj = to_rockchip_obj(fb->obj[0]); yrgb_mst = rk_obj->dma_addr + offset + fb->offsets[0]; - if (fb->format->is_yuv) { + if (fb->format->is_yuv && fb->format->num_planes > 1) { int hsub = fb->format->hsub; int vsub = fb->format->vsub; @@ -1460,7 +1460,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, uv_swap = vop2_win_uv_swap(fb->format->format); vop2_win_write(win, VOP2_WIN_UV_SWAP, uv_swap); - if (fb->format->is_yuv) { + if (fb->format->is_yuv && fb->format->num_planes > 1) { vop2_win_write(win, VOP2_WIN_UV_VIR, DIV_ROUND_UP(fb->pitches[1], 4)); vop2_win_write(win, VOP2_WIN_UV_MST, uv_mst); } -- 2.53.0 _______________________________________________ Linux-rockchip mailing list Linux-rockchip@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-rockchip ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 3/3] drm/rockchip: vop2: Support 8K output by splicing two video ports 2026-10-29 8:14 ` Chaoyi Chen @ 2026-10-29 8:14 ` Chaoyi Chen -1 siblings, 0 replies; 10+ messages in thread From: Chaoyi Chen @ 2026-10-29 8:14 UTC (permalink / raw) To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel, Chaoyi Chen, Cristian Ciocaltea, Sebastian Reichel From: Chaoyi Chen <chaoyi.chen@rock-chips.com> A single RK3588 video port can only drive 4096 pixels per line, so modes wider than that (e.g. 7680x4320) cannot be output by one video port alone. The VOP2 can instead merge video ports and two windows: VP0 drives the left half of the screen, VP1 the right half, and each plane of the CRTC is split horizontally between a window pair (Cluster0/1, Cluster2/3, Esmart0/1, Esmart2/3). Three components need to be configured: the CRTC, the plane and the post-processing block. 1. For CRTC In splice mode the CRTC reserved for VP1 is taken over, so make the following changes: vop2_crtc_atomic_check() sets the new splice_mode flag for modes wider than VOP2_MAX_VP_OUTPUT_WIDTH, reserves the partner video port (rejecting the commit if it is already active) and verifies that both video ports have enough layers for the planes of the CRTC. The reserved video port is taken into account when the layers are distributed over the video ports, and it is registered so that its windows exist, but its own CRTC is kept unusable while it serves as the right half. 2. For Plane To avoid requiring additional userspace changes, the plane split is performed in the driver. Userspace can thereforekeep updating a single plane that is 8K wide. To do so, make the following changes: vop2_plane_atomic_check() accepts a source width of twice the maximum input width and validates the split: the plane must use a window with the WIN_FEATURE_SPLICE_LEFT flag, must not be rotated or x-mirrored, and cluster windows that scale down may only do so centred on the screen with a factor below 1.2. vop2_plane_atomic_update() calculates the left and right source and destination rectangles and programs both windows of a plane pair from the same plane state. 3. For post-processing block The overlay, alpha and layer mixer setup iterate over the window mask of a video port instead of the planes of its CRTC, because the right video port has no planes of its own. A right window resolves its plane state through left_win. Gamma LUT and background colour setup are applied to the partner video port as well. Assisted-by: LLM Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> --- Changes since v2: - vop2_win_get_pstate(): use the left window's state only in splice mode(Sashiko). - Reject splice windows already in use by another CRTC(Sashiko). - Check the splice partner via drm_atomic_get_crtc_state() instead of the runtime flag and unlocked read(Sashiko). - Set splice_mode_right/left_vp before the error paths of vop2_crtc_atomic_enable()(Sashiko). - Validate each split half against the min width 4 and max input width; align NV20/NV30 like NV15(Sashiko). - Latch overlay registers at the left VP's vsync; don't wait for the partner's own cfg-done(Sashiko). - Program the color key on the window actually being updated(Sashiko). --- drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 5 + drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 527 +++++++++++++++++-- drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 66 ++- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 204 +++++-- 4 files changed, 717 insertions(+), 85 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index 4705dc6b8bd7..db4d50dd3dc9 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -54,6 +54,11 @@ struct rockchip_crtc_state { int output_flags; bool enable_afbc; bool yuv_overlay; + /** + * @splice_mode: two video ports are joined together to drive a + * display that is wider than a single video port can handle. + */ + bool splice_mode; u32 bus_format; u32 bus_flags; int color_space; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 06291a13320d..bacfe473e365 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -150,6 +150,7 @@ static void vop2_unlock(struct vop2 *vop2) static void vop2_win_disable(struct vop2_win *win) { vop2_win_write(win, VOP2_WIN_ENABLE, 0); + vop2_win_write(win, VOP2_WIN_YUV_CLIP, 0); if (vop2_cluster_window(win)) vop2_win_write(win, VOP2_WIN_CLUSTER_ENABLE, 0); @@ -426,9 +427,9 @@ static bool vop2_half_block_enable(struct drm_plane_state *pstate) } static u32 vop2_afbc_transform_offset(struct drm_plane_state *pstate, + struct drm_rect *src, bool afbc_half_block_en) { - struct drm_rect *src = &pstate->src; struct drm_framebuffer *fb = pstate->fb; u32 bpp = vop2_get_bpp(fb->format); u32 vir_width = (fb->pitches[0] << 3) / bpp; @@ -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); ret = wait_for_completion_timeout(&vp->dsp_hold_completion, @@ -1043,6 +1066,11 @@ static void vop2_crtc_atomic_disable(struct drm_crtc *crtc, vop2_crtc_disable_irq(vp, VP_INT_DSP_HOLD_VALID); + if (splice_vp) { + splice_vp->splice_mode_right = false; + splice_vp->left_vp = NULL; + } + if (vp->dclk_src) clk_set_parent(vp->dclk, vp->dclk_src); @@ -1064,6 +1092,202 @@ static void vop2_crtc_atomic_disable(struct drm_crtc *crtc, } } +static bool vop2_vp_uses_splice(struct vop2_video_port *vp, + struct drm_crtc_state *cstate) +{ + if (!(vp->vop2->data->feature & VOP2_FEATURE_SPLICE)) + return false; + + return cstate->adjusted_mode.hdisplay > VOP2_MAX_VP_OUTPUT_WIDTH; +} + +static struct vop2_video_port * +vop2_find_left_splice_vp(struct vop2 *vop2, int right_vp_id) +{ + const struct vop2_data *vop2_data = vop2->data; + int i; + + if (!(vop2_data->feature & VOP2_FEATURE_SPLICE)) + return NULL; + + for (i = 0; i < vop2_data->nr_vps; i++) { + const struct vop2_video_port_data *vp_data = &vop2_data->vp[i]; + + if (vp_data->max_output.width <= VOP2_MAX_VP_OUTPUT_WIDTH) + continue; + + if (vp_data->splice_vp_id == right_vp_id) + return &vop2->vps[i]; + } + + return NULL; +} + +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; + } + + if (left_src_w > src_w) + left_src_w = src_w; + + left_src->x1 = src->x1; + left_src->x2 = src->x1 + (left_src_w << 16); + left_dst->x1 = dst->x1; + left_dst->x2 = dst->x1 + left_dst_w; + + right_src->x1 = left_src->x2; + right_src->x2 = src->x2; + right_dst->x1 = dst->x1 + left_dst_w - half_hdisplay; + if (right_dst->x1 < 0) + right_dst->x1 = 0; + right_dst->x2 = right_dst->x1 + right_dst_w; + + left_src->y1 = right_src->y1 = src->y1; + left_src->y2 = right_src->y2 = src->y2; + left_dst->y1 = right_dst->y1 = dst->y1; + left_dst->y2 = right_dst->y2 = dst->y2; + + return 0; +} + +static int vop2_splice_part_size_check(struct vop2_win *win, + struct drm_rect *src, + struct drm_rect *dst) +{ + struct vop2 *vop2 = win->vop2; + + if (!drm_rect_width(dst)) + return 0; + + if (drm_rect_width(dst) < 4 || (drm_rect_width(src) >> 16) < 4 || + (drm_rect_width(src) >> 16) > vop2->data->max_input.width) { + drm_dbg_kms(vop2->drm, + "%s: split %dx%d->%dx%d, min width is 4, max input width is %d\n", + win->data->name, drm_rect_width(src) >> 16, + drm_rect_height(src) >> 16, drm_rect_width(dst), + drm_rect_height(dst), vop2->data->max_input.width); + return -EINVAL; + } + + return 0; +} + +static int vop2_cluster_splice_scale_check(struct vop2_win *win, + struct drm_plane_state *pstate, + int hdisplay) +{ + struct drm_rect *src = &pstate->src; + struct drm_rect *dst = &pstate->dst; + int half_hdisplay = hdisplay >> 1; + + /* scale up is ok */ + if ((drm_rect_width(src) >> 16) <= drm_rect_width(dst)) + return 0; + + if ((drm_rect_width(src) >> 16) <= VOP2_MAX_VP_OUTPUT_WIDTH) + return 0; + /* + * Cluster scale down limitation in splice mode: + * If scale down, must display at horizontal center + */ + if (dst->x1 < half_hdisplay && dst->x2 > half_hdisplay) { + if (dst->x2 + dst->x1 != hdisplay) { + drm_dbg_kms(win->vop2->drm, + "%s must scale down at center in splice mode\n", + win->data->name); + return -EINVAL; + } + + if (drm_rect_calc_hscale(src, dst, 1, FRAC_16_16(6, 5)) < 0) { + drm_dbg_kms(win->vop2->drm, + "%s scale down factor should be < 1.2 in splice mode\n", + win->data->name); + return -EINVAL; + } + } + + return 0; +} + +static int vop2_plane_splice_check(struct drm_plane_state *pstate, + struct drm_crtc_state *cstate) +{ + struct vop2_win *win = to_vop2_win(pstate->plane); + struct vop2 *vop2 = win->vop2; + struct vop2_win *splice_win = win->splice_win; + struct drm_rect left_src, left_dst, right_src, right_dst; + int ret; + + if (!(win->data->feature & WIN_FEATURE_SPLICE_LEFT) || !splice_win) { + drm_dbg_kms(vop2->drm, "%s cannot be used in splice mode\n", + win->data->name); + return -EINVAL; + } + + if (pstate->rotation & (DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X)) { + drm_dbg_kms(vop2->drm, "%s can't rotate 270/90 and xmirror in splice mode\n", + win->data->name); + return -EINVAL; + } + + if (vop2_cluster_window(win)) { + ret = vop2_cluster_splice_scale_check(win, pstate, + cstate->adjusted_mode.hdisplay); + if (ret) + return ret; + } + + ret = vop2_calc_splice_rects(pstate, cstate, &left_src, &left_dst, + &right_src, &right_dst); + if (ret) + return ret; + + ret = vop2_splice_part_size_check(win, &left_src, &left_dst); + if (ret) + return ret; + + return vop2_splice_part_size_check(splice_win, &right_src, &right_dst); +} + static int vop2_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_commit *astate) { @@ -1081,6 +1305,7 @@ static int vop2_plane_atomic_check(struct drm_plane *plane, int max_scale = FRAC_16_16(8, 1); int src_x, src_w, src_h; int dest_w, dest_h; + int max_src_w; int format; int ret; @@ -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; + } + } + } + /* * Src.x1 can be odd when do clip, but yuv plane start point * need align with 2 pixel. @@ -1180,8 +1433,10 @@ static void vop2_plane_atomic_disable(struct drm_plane *plane, struct drm_atomic_commit *state) { struct drm_plane_state *old_pstate = NULL; + struct drm_crtc_state *old_cstate = NULL; struct vop2_win *win = to_vop2_win(plane); struct vop2 *vop2 = win->vop2; + bool splice = false; drm_dbg(vop2->drm, "%s disable\n", win->data->name); @@ -1190,19 +1445,26 @@ static void vop2_plane_atomic_disable(struct drm_plane *plane, if (old_pstate && !old_pstate->crtc) return; + if (old_pstate) { + old_cstate = drm_atomic_get_old_crtc_state(state, old_pstate->crtc); + splice = to_rockchip_crtc_state(old_cstate)->splice_mode; + } + + if (splice && win->splice_win) + vop2_win_disable(win->splice_win); + vop2_win_disable(win); - vop2_win_write(win, VOP2_WIN_YUV_CLIP, 0); } /* * The color key is 10 bit, so all format should * convert to 10 bit here. */ -static void vop2_plane_setup_color_key(struct drm_plane *plane, u32 color_key) +static void vop2_win_setup_color_key(struct vop2_win *win, + struct drm_plane_state *pstate, + u32 color_key) { - struct drm_plane_state *pstate = plane->state; struct drm_framebuffer *fb = pstate->fb; - struct vop2_win *win = to_vop2_win(plane); u32 color_key_en = 0; u32 r = 0; u32 g = 0; @@ -1244,12 +1506,11 @@ static void vop2_plane_setup_color_key(struct drm_plane *plane, u32 color_key) vop2_win_write(win, VOP2_WIN_COLOR_KEY, (r << 20) | (g << 10) | b); } -static void vop2_plane_atomic_update(struct drm_plane *plane, - struct drm_atomic_commit *state) +static void vop2_win_atomic_update(struct vop2_win *win, struct drm_rect *src, + struct drm_rect *dest, + struct drm_plane_state *pstate) { - struct drm_plane_state *pstate = plane->state; struct drm_crtc *crtc = pstate->crtc; - struct vop2_win *win = to_vop2_win(plane); struct vop2_video_port *vp = to_vop2_video_port(crtc); struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode; struct vop2 *vop2 = win->vop2; @@ -1261,8 +1522,6 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, u32 afbc_format; u32 rb_swap; u32 uv_swap; - struct drm_rect *src = &pstate->src; - struct drm_rect *dest = &pstate->dst; u32 afbc_tile_num; u32 transform_offset; bool dither_up; @@ -1283,8 +1542,10 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, if (WARN_ON(!crtc)) return; - if (!pstate->visible) { - vop2_plane_atomic_disable(plane, state); + /* disable no visible window */ + if (!drm_rect_width(src) || !drm_rect_height(src) || + !drm_rect_width(dest) || !drm_rect_height(dest)) { + vop2_win_disable(win); return; } @@ -1424,7 +1685,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, vop2_win_write(win, VOP2_WIN_AFBC_PLD_OFFSET, yrgb_mst); } - transform_offset = vop2_afbc_transform_offset(pstate, half_block_en); + transform_offset = vop2_afbc_transform_offset(pstate, src, half_block_en); vop2_win_write(win, VOP2_WIN_AFBC_HDR_PTR, yrgb_mst); vop2_win_write(win, VOP2_WIN_AFBC_PIC_SIZE, act_info); vop2_win_write(win, VOP2_WIN_TRANSFORM_OFFSET, transform_offset); @@ -1467,7 +1728,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, vop2_setup_scale(vop2, win, src_w, src_h, dsp_w, dsp_h, fb->format->format); if (!vop2_cluster_window(win)) - vop2_plane_setup_color_key(plane, 0); + vop2_win_setup_color_key(win, pstate, 0); vop2_win_write(win, VOP2_WIN_ACT_INFO, act_info); vop2_win_write(win, VOP2_WIN_DSP_INFO, dsp_info); vop2_win_write(win, VOP2_WIN_DSP_ST, dest->y1 << 16 | (dest->x1 & 0xffff)); @@ -1487,6 +1748,45 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, } } +static void vop2_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *pstate = plane->state; + struct drm_crtc *crtc = pstate->crtc; + struct vop2_win *win = to_vop2_win(plane); + struct drm_crtc_state *cstate; + struct rockchip_crtc_state *vcstate; + struct drm_rect left_src, left_dst, right_src, right_dst; + + if (WARN_ON(!crtc)) + return; + + if (!pstate->visible) { + vop2_plane_atomic_disable(plane, state); + return; + } + + cstate = drm_atomic_get_new_crtc_state(state, crtc); + if (!cstate) + cstate = crtc->state; + vcstate = to_rockchip_crtc_state(cstate); + + if (!vcstate->splice_mode) { + vop2_win_atomic_update(win, &pstate->src, &pstate->dst, pstate); + return; + } + + if (!win->splice_win) + return; + + if (WARN_ON(vop2_calc_splice_rects(pstate, cstate, &left_src, &left_dst, + &right_src, &right_dst))) + return; + + vop2_win_atomic_update(win, &left_src, &left_dst, pstate); + vop2_win_atomic_update(win->splice_win, &right_src, &right_dst, pstate); +} + static const struct drm_plane_helper_funcs vop2_plane_helper_funcs = { .atomic_check = vop2_plane_atomic_check, .atomic_update = vop2_plane_atomic_update, @@ -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; @@ -1561,8 +1871,9 @@ static void vop2_crtc_atomic_set_gamma_seamless(struct vop2 *vop2, struct vop2_video_port *vp, struct drm_crtc *crtc) { - vop2_writel(vop2, RK3568_LUT_PORT_SEL, - FIELD_PREP(RK3588_LUT_PORT_SEL__GAMMA_AHB_WRITE_SEL, vp->id)); + regmap_update_bits(vop2->map, RK3568_LUT_PORT_SEL, + RK3588_LUT_PORT_SEL__GAMMA_AHB_WRITE_SEL, + FIELD_PREP(RK3588_LUT_PORT_SEL__GAMMA_AHB_WRITE_SEL, vp->id)); vop2_vp_dsp_lut_enable(vp); vop2_crtc_write_gamma_lut(vop2, crtc); vop2_vp_dsp_lut_update_enable(vp); @@ -1573,7 +1884,7 @@ static void vop2_crtc_atomic_set_gamma_rk356x(struct vop2 *vop2, struct drm_crtc *crtc) { vop2_vp_dsp_lut_disable(vp); - vop2_cfg_done(vp); + vop2_cfg_done(vp, false); if (!vop2_vp_dsp_lut_poll_disabled(vp)) return; @@ -1587,18 +1898,25 @@ static void vop2_crtc_atomic_try_set_gamma(struct vop2 *vop2, struct drm_crtc *crtc, struct drm_crtc_state *crtc_state) { + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc_state); + if (!vop2->lut_regs) return; if (!crtc_state->gamma_lut) { vop2_vp_dsp_lut_disable(vp); + if (vcstate->splice_mode) + vop2_vp_dsp_lut_disable(vop2_get_splice_vp(vp)); return; } - if (vop2_supports_seamless_gamma_lut_update(vop2)) + if (vop2_supports_seamless_gamma_lut_update(vop2)) { vop2_crtc_atomic_set_gamma_seamless(vop2, vp, crtc); - else + if (vcstate->splice_mode) + vop2_crtc_atomic_set_gamma_seamless(vop2, vop2_get_splice_vp(vp), crtc); + } else { vop2_crtc_atomic_set_gamma_rk356x(vop2, vp, crtc); + } } static inline void vop2_crtc_atomic_try_set_gamma_locked(struct vop2 *vop2, @@ -1640,13 +1958,12 @@ static void vop2_dither_setup(struct drm_crtc *crtc, u32 *dsp_ctrl) DITHER_DOWN_ALLEGRO); } -static void vop2_bgcolor_setup(struct drm_crtc *crtc, bool force, +static void vop2_bgcolor_setup(struct vop2_video_port *vp, bool force, struct drm_crtc_state *new_crtc_state, struct drm_crtc_state *old_crtc_state) { struct rockchip_crtc_state *new_vcstate = to_rockchip_crtc_state(new_crtc_state); struct rockchip_crtc_state *old_vcstate = to_rockchip_crtc_state(old_crtc_state); - struct vop2_video_port *vp = to_vop2_video_port(crtc); u64 bgcolor = new_crtc_state->background_color; u16 y, cb, cr; u32 val; @@ -1690,6 +2007,8 @@ static void vop2_post_config(struct drm_crtc *crtc, bool force, struct drm_crtc_state *old_crtc_state) { struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct rockchip_crtc_state *new_vcstate = to_rockchip_crtc_state(new_crtc_state); + struct vop2_video_port *splice_vp = NULL; struct vop2 *vop2 = vp->vop2; struct drm_display_mode *mode = &new_crtc_state->adjusted_mode; u16 vtotal = mode->crtc_vtotal; @@ -1704,7 +2023,11 @@ static void vop2_post_config(struct drm_crtc *crtc, bool force, u16 hact_end, vact_end; u32 val; + splice_vp = new_vcstate->splice_mode ? vop2_get_splice_vp(vp) : NULL; + vop2->ops->setup_bg_dly(vp); + if (splice_vp) + vop2->ops->setup_bg_dly(splice_vp); vsize = rounddown(vsize, 2); hsize = rounddown(hsize, 2); @@ -1737,7 +2060,9 @@ static void vop2_post_config(struct drm_crtc *crtc, bool force, vop2_vp_write(vp, RK3568_VP_POST_DSP_VACT_INFO_F1, val); } - vop2_bgcolor_setup(crtc, force, new_crtc_state, old_crtc_state); + vop2_bgcolor_setup(vp, force, new_crtc_state, old_crtc_state); + if (splice_vp) + vop2_bgcolor_setup(splice_vp, force, new_crtc_state, old_crtc_state); } static int us_to_vertical_line(struct drm_display_mode *mode, int us) @@ -1749,6 +2074,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_commit *state) { struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct vop2_video_port *splice_vp = NULL; struct vop2 *vop2 = vp->vop2; const struct vop2_data *vop2_data = vop2->data; const struct vop2_video_port_data *vp_data = &vop2_data->vp[vp->id]; @@ -1780,6 +2106,12 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, vop2_lock(vop2); + if (vcstate->splice_mode) { + splice_vp = vop2_get_splice_vp(vp); + splice_vp->splice_mode_right = true; + splice_vp->left_vp = vp; + } + ret = clk_prepare_enable(vp->dclk); if (ret < 0) { drm_err(vop2->drm, "failed to enable dclk for video port%d - %d\n", @@ -1940,9 +2272,12 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, clk_set_rate(vp->dclk, clock); + if (splice_vp) + vop2->ops->setup_splice(vp, true); + vop2_post_config(crtc, true, crtc_state, old_crtc_state); - vop2_cfg_done(vp); + vop2_cfg_done(vp, vcstate->splice_mode); vop2_vp_write(vp, RK3568_VP_DSP_CTRL, dsp_ctrl); @@ -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; + } + } + } + drm_atomic_crtc_state_for_each_plane(plane, crtc_state) nplanes++; if (nplanes > vp->nlayers) return -EINVAL; + vcstate->splice_mode = false; + if (crtc_state->enable && vop2_vp_uses_splice(vp, crtc_state)) { + struct drm_crtc_state *splice_state; + + if (vp_data->max_output.width <= VOP2_MAX_VP_OUTPUT_WIDTH) + return -EINVAL; + + splice_vp = vop2_get_splice_vp(vp); + if (drm_atomic_crtc_needs_modeset(crtc_state) && splice_vp->crtc.state) { + splice_state = drm_atomic_get_crtc_state(state, &splice_vp->crtc); + if (IS_ERR(splice_state)) + return PTR_ERR(splice_state); + + if (splice_state->enable) { + drm_dbg_kms(vop2->drm, "vp%d is already in use\n", splice_vp->id); + return -EBUSY; + } + } + + /* + * The right half uses its own set of OVL layers, both video + * ports need enough layers for the planes of the left CRTC. + */ + if (nplanes > min(vp->nlayers, splice_vp->nlayers)) + return -EINVAL; + + /* Check whether the right window is used by another CRTC. */ + drm_atomic_crtc_state_for_each_plane(plane, crtc_state) { + struct vop2_win *win = to_vop2_win(plane); + struct vop2_win *rwin = win->splice_win; + struct drm_plane_state *rstate, *old_rstate; + + /* For windows that cannot be spliced */ + if (!rwin) { + drm_dbg_kms(vop2->drm, "%s cannot be used in splice mode\n", + win->data->name); + return -EINVAL; + } + + rstate = drm_atomic_get_plane_state(state, &rwin->base); + if (IS_ERR(rstate)) + return PTR_ERR(rstate); + + /* If window is already in use by another CRTC, then reject it. */ + old_rstate = drm_atomic_get_old_plane_state(state, &rwin->base); + if (rstate->crtc || (old_rstate && old_rstate->crtc)) { + drm_dbg_kms(vop2->drm, "%s is in use on another CRTC\n", + rwin->data->name); + return -EBUSY; + } + } + + vcstate->splice_mode = true; + } + return 0; } @@ -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); spin_lock_irq(&crtc->dev->event_lock); @@ -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++) { @@ -2653,7 +3088,7 @@ static int vop2_create_crtcs(struct vop2 *vop2) for (i = 0; i < vop2->data->nr_vps; i++) { struct vop2_video_port *vp = &vop2->vps[i]; - if (vp->crtc.port) + if (vp_used[i]) vp->nlayers = vop2_data->win_size / nvps; } @@ -2726,7 +3161,7 @@ static int vop2_win_init(struct vop2 *vop2) { const struct vop2_data *vop2_data = vop2->data; struct vop2_win *win; - int i, ret; + int i, j, ret; for (i = 0; i < vop2_data->win_size; i++) { const struct vop2_win_data *win_data = &vop2_data->win[i]; @@ -2747,6 +3182,28 @@ static int vop2_win_init(struct vop2 *vop2) return ret; } + /* Link the windows used for the two halves of a spliced plane */ + for (i = 0; i < vop2_data->win_size; i++) { + win = &vop2->win[i]; + + if (!(win->data->feature & WIN_FEATURE_SPLICE_LEFT)) + continue; + + for (j = 0; j < vop2_data->win_size; j++) { + if (vop2->win[j].data->phys_id != win->data->splice_win_id) + continue; + + win->splice_win = &vop2->win[j]; + win->splice_win->left_win = win; + break; + } + + if (!win->splice_win) + return dev_err_probe(vop2->dev, -EINVAL, + "no splice window for %s\n", + win->data->name); + } + vop2->registered_num_wins = vop2_data->win_size; return 0; @@ -2825,6 +3282,8 @@ static int vop2_bind(struct device *dev, struct device *master, void *data) drm->mode_config.min_width = 4; drm->mode_config.min_height = 4; drm->mode_config.max_width = vop2_data->max_input.width; + if (vop2_data->feature & VOP2_FEATURE_SPLICE) + drm->mode_config.max_width = vop2_data->max_output.width; drm->mode_config.max_height = vop2_data->max_input.height; ret = vop2_win_init(vop2); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h index ffcb39c130aa..87c7fcdb26df 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h @@ -24,14 +24,24 @@ #define VOP2_VP_FEATURE_OUTPUT_10BIT BIT(0) +/* + * The maximum output width of a single video port. Modes wider than + * this are driven by splicing two video ports together. + */ +#define VOP2_MAX_VP_OUTPUT_WIDTH 4096 + #define VOP2_FEATURE_HAS_SYS_GRF BIT(0) #define VOP2_FEATURE_HAS_VO0_GRF BIT(1) #define VOP2_FEATURE_HAS_VO1_GRF BIT(2) #define VOP2_FEATURE_HAS_VOP_GRF BIT(3) #define VOP2_FEATURE_HAS_SYS_PMU BIT(4) +/* splice two windows and two VPs to support resolutions > 4096 */ +#define VOP2_FEATURE_SPLICE BIT(5) #define WIN_FEATURE_AFBDC BIT(0) #define WIN_FEATURE_CLUSTER BIT(1) +/* Left window in splice mode */ +#define WIN_FEATURE_SPLICE_LEFT BIT(6) /* * the delay number of a window in different mode. @@ -178,6 +188,13 @@ struct vop2_win_data { const char *name; unsigned int phys_id; + /** + * @splice_win_id: phy id of the window driving the right half of + * this window in splice mode. Only valid for windows with + * %WIN_FEATURE_SPLICE_LEFT. + */ + u8 splice_win_id; + u32 base; u32 possible_vp_mask; enum drm_plane_type type; @@ -217,10 +234,26 @@ struct vop2_win { u32 offset; enum drm_plane_type type; + + /** + * @splice_win: window used for the right half of this window when + * the display is driven in splice mode. + */ + struct vop2_win *splice_win; + /** + * @left_win: window driving the left half of this window when this + * window is used as the right half in splice mode. + */ + struct vop2_win *left_win; }; struct vop2_video_port_data { unsigned int id; + /** + * @splice_vp_id: id of the video port driving the right half of the + * screen when this video port works in splice mode. + */ + u8 splice_vp_id; u32 feature; u16 gamma_lut_len; u16 cubic_lut_len; @@ -252,6 +285,17 @@ struct vop2_video_port { struct drm_pending_vblank_event *event; unsigned int nlayers; + + /** + * @splice_mode_right: this video port drives the right half of the + * display in splice mode. + */ + bool splice_mode_right; + /** + * @left_vp: video port driving the left half when + * @splice_mode_right is set. + */ + struct vop2_video_port *left_vp; }; /** @@ -264,6 +308,11 @@ struct vop2_ops { unsigned long (*setup_intf_mux)(struct vop2_video_port *vp, int ep_id, u32 polflags); void (*setup_bg_dly)(struct vop2_video_port *vp); void (*setup_overlay)(struct vop2_video_port *vp); + /* + * @setup_splice: join or split this video port and its partner to + * drive a mode wider than a single video port can handle. + */ + void (*setup_splice)(struct vop2_video_port *vp, bool enable); }; struct vop2_data { @@ -712,6 +761,7 @@ enum dst_factor_mode { #define RK3588_DSP_IF_POL__DP0_PIN_POL GENMASK(10, 8) #define RK3588_LUT_PORT_SEL__GAMMA_AHB_WRITE_SEL GENMASK(13, 12) +#define RK3568_LUT_PORT_SEL__SPLICE_EN BIT(16) #define RK3568_VP0_MIPI_CTRL__DCLK_DIV2_PHASE_LOCK BIT(5) #define RK3568_VP0_MIPI_CTRL__DCLK_DIV2 BIT(4) @@ -848,6 +898,14 @@ static inline struct vop2_win *to_vop2_win(struct drm_plane *p) return container_of(p, struct vop2_win, base); } +static inline struct vop2_video_port * +vop2_get_splice_vp(struct vop2_video_port *vp) +{ + struct vop2 *vop2 = vp->vop2; + + return &vop2->vps[vop2->data->vp[vp->id].splice_vp_id]; +} + /* * Note: * The write mask function is documented but missing on rk3566/8, writes @@ -857,13 +915,19 @@ static inline struct vop2_win *to_vop2_win(struct drm_plane *p) * GLB_CFG_DONE_EN has no write mask bit. * */ -static inline void vop2_cfg_done(struct vop2_video_port *vp) +static inline void vop2_cfg_done(struct vop2_video_port *vp, bool splice) { struct vop2 *vop2 = vp->vop2; u32 val = RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN; val |= BIT(vp->id) | (BIT(vp->id) << 16); + if (splice) { + int splice_vp_id = vop2->data->vp[vp->id].splice_vp_id; + + val |= BIT(splice_vp_id) | (BIT(splice_vp_id) << 16); + } + regmap_set_bits(vop2->map, RK3568_REG_CFG_DONE, val); } diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index c44648e1bc0d..08f9f8cac878 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -1056,10 +1056,11 @@ static const struct vop2_regs_dump rk3576_regs_dump[] = { static const struct vop2_video_port_data rk3588_vop_video_ports[] = { { .id = 0, + .splice_vp_id = 1, .feature = VOP2_VP_FEATURE_OUTPUT_10BIT, .gamma_lut_len = 1024, .cubic_lut_len = 9 * 9 * 9, /* 9x9x9 */ - .max_output = { 4096, 2304 }, + .max_output = { 7680, 4320 }, /* hdr2sdr sdr2hdr hdr2hdr sdr2sdr */ .pre_scan_max_dly = { 76, 65, 65, 54 }, .offset = 0xc00, @@ -1121,6 +1122,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { { .name = "Cluster0-win0", .phys_id = ROCKCHIP_VOP2_CLUSTER0, + .splice_win_id = ROCKCHIP_VOP2_CLUSTER1, .base = 0x1000, .possible_vp_mask = BIT(0) | BIT(1) | BIT(2) | BIT(3), .formats = formats_cluster, @@ -1136,7 +1138,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { .max_downscale_factor = 4, .dly = { 4, 26, 29 }, .type = DRM_PLANE_TYPE_PRIMARY, - .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER, + .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER | WIN_FEATURE_SPLICE_LEFT, }, { .name = "Cluster1-win0", .phys_id = ROCKCHIP_VOP2_CLUSTER1, @@ -1159,6 +1161,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { }, { .name = "Cluster2-win0", .phys_id = ROCKCHIP_VOP2_CLUSTER2, + .splice_win_id = ROCKCHIP_VOP2_CLUSTER3, .base = 0x1400, .possible_vp_mask = BIT(0) | BIT(1) | BIT(2) | BIT(3), .formats = formats_cluster, @@ -1174,7 +1177,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { .max_upscale_factor = 4, .max_downscale_factor = 4, .dly = { 4, 26, 29 }, - .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER, + .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER | WIN_FEATURE_SPLICE_LEFT, }, { .name = "Cluster3-win0", .phys_id = ROCKCHIP_VOP2_CLUSTER3, @@ -1197,6 +1200,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { }, { .name = "Esmart0-win0", .phys_id = ROCKCHIP_VOP2_ESMART0, + .splice_win_id = ROCKCHIP_VOP2_ESMART1, .possible_vp_mask = BIT(0) | BIT(1) | BIT(2) | BIT(3), .formats = formats_esmart, .nformats = ARRAY_SIZE(formats_esmart), @@ -1211,6 +1215,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { .max_upscale_factor = 8, .max_downscale_factor = 8, .dly = { 23, 45, 48 }, + .feature = WIN_FEATURE_SPLICE_LEFT, }, { .name = "Esmart1-win0", .phys_id = ROCKCHIP_VOP2_ESMART1, @@ -1231,6 +1236,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { }, { .name = "Esmart2-win0", .phys_id = ROCKCHIP_VOP2_ESMART2, + .splice_win_id = ROCKCHIP_VOP2_ESMART3, .base = 0x1c00, .possible_vp_mask = BIT(0) | BIT(1) | BIT(2) | BIT(3), .formats = formats_esmart, @@ -1245,6 +1251,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { .max_upscale_factor = 8, .max_downscale_factor = 8, .dly = { 23, 45, 48 }, + .feature = WIN_FEATURE_SPLICE_LEFT, }, { .name = "Esmart3-win0", .phys_id = ROCKCHIP_VOP2_ESMART3, @@ -1387,6 +1394,19 @@ static struct vop2_win *vop2_find_win_by_phys_id(struct vop2 *vop2, uint8_t phys return NULL; } +/* + * The right window in splice mode shares the plane state of the left one, + * only its registers and position on the screen are different. + */ +static struct drm_plane_state * +vop2_win_get_pstate(struct vop2_video_port *vp, struct vop2_win *win) +{ + if (win->left_win && vp->splice_mode_right) + return win->left_win->base.state; + + return win->base.state; +} + static unsigned long rk3568_set_intf_mux(struct vop2_video_port *vp, int id, u32 polflags) { struct vop2 *vop2 = vp->vop2; @@ -1873,7 +1893,7 @@ static int vop2_find_start_mixer_id_for_vp(struct vop2_video_port *vp) for (i = 0; i < 31; i += 4) { match = false; - for_each_set_bit(phys_id, &win_mask, ROCKCHIP_VOP2_ESMART3) { + for_each_set_bit(phys_id, &win_mask, BITS_PER_TYPE(u32)) { win = vop2_find_win_by_phys_id(vop2, phys_id); if (win->data->layer_sel_id[vp->id] == ((layer_sel >> i) & 0xf)) { match = true; @@ -1890,11 +1910,11 @@ static int vop2_find_start_mixer_id_for_vp(struct vop2_video_port *vp) return used_layer; } -static void vop2_setup_cluster_alpha(struct vop2 *vop2, struct vop2_win *main_win) +static void vop2_setup_cluster_alpha(struct vop2 *vop2, struct vop2_win *main_win, + struct drm_plane_state *pstate) { struct vop2_alpha_config alpha_config; struct vop2_alpha alpha; - struct drm_plane_state *bottom_win_pstate; bool src_pixel_alpha_en = false; u16 src_glb_alpha_val, dst_glb_alpha_val; u32 src_color_ctrl_reg, dst_color_ctrl_reg, src_alpha_ctrl_reg, dst_alpha_ctrl_reg; @@ -1903,11 +1923,10 @@ static void vop2_setup_cluster_alpha(struct vop2 *vop2, struct vop2_win *main_wi bool swap = false; /* At one win mode, win0 is dst/bottom win, and win1 is a all zero src/top win */ - bottom_win_pstate = main_win->base.state; src_glb_alpha_val = 0; - dst_glb_alpha_val = main_win->base.state->alpha; + dst_glb_alpha_val = pstate->alpha; - if (!bottom_win_pstate->fb) + if (!pstate->fb) return; alpha_config.src_premulti_en = premulti_en; @@ -1959,7 +1978,10 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) struct drm_framebuffer *fb; struct vop2_alpha_config alpha_config; struct vop2_alpha alpha; - struct drm_plane *plane; + struct drm_plane_state *pstate; + struct vop2_win *win; + unsigned long win_mask = vp->win_mask; + int phys_id; int pixel_alpha_en; int premulti_en, gpremulti_en = 0; int mixer_id; @@ -1975,11 +1997,12 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) alpha_config.dst_pixel_alpha_en = true; /* alpha value need transfer to next mix */ - drm_atomic_crtc_for_each_plane(plane, &vp->crtc) { - struct vop2_win *win = to_vop2_win(plane); + for_each_set_bit(phys_id, &win_mask, BITS_PER_TYPE(u32)) { + win = vop2_find_win_by_phys_id(vop2, phys_id); + pstate = vop2_win_get_pstate(vp, win); - if (plane->state->normalized_zpos == 0 && - !is_opaque(plane->state->alpha) && + if (pstate->normalized_zpos == 0 && + !is_opaque(pstate->alpha) && !vop2_cluster_window(win)) { /* * If bottom layer have global alpha effect [except cluster layer, @@ -1987,7 +2010,7 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) * at cluster mix], bottom layer mix need deal with global alpha. */ bottom_layer_alpha_en = true; - dst_global_alpha = plane->state->alpha; + dst_global_alpha = pstate->alpha; } } @@ -2003,9 +2026,12 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) dst_alpha_ctrl_reg = RK3576_OVL_MIX0_DST_ALPHA_CTRL(vp->id); } - drm_atomic_crtc_for_each_plane(plane, &vp->crtc) { - struct vop2_win *win = to_vop2_win(plane); - int zpos = plane->state->normalized_zpos; + for_each_set_bit(phys_id, &win_mask, BITS_PER_TYPE(u32)) { + int zpos; + + win = vop2_find_win_by_phys_id(vop2, phys_id); + pstate = vop2_win_get_pstate(vp, win); + zpos = pstate->normalized_zpos; /* * Need to configure alpha from second layer. @@ -2013,13 +2039,12 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) if (zpos == 0) continue; - if (plane->state->pixel_blend_mode == DRM_MODE_BLEND_PREMULTI) + if (pstate->pixel_blend_mode == DRM_MODE_BLEND_PREMULTI) premulti_en = 1; else premulti_en = 0; - plane = &win->base; - fb = plane->state->fb; + fb = pstate->fb; pixel_alpha_en = fb->format->has_alpha; @@ -2030,7 +2055,7 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) /* Cd = Cs + (1 - As) * Cd * Agd */ alpha_config.dst_premulti_en = false; alpha_config.src_pixel_alpha_en = pixel_alpha_en; - alpha_config.src_glb_alpha_value = plane->state->alpha; + alpha_config.src_glb_alpha_value = pstate->alpha; alpha_config.dst_glb_alpha_value = dst_global_alpha; } else if (vop2_cluster_window(win)) { /* Mix output data only have pixel alpha */ @@ -2042,7 +2067,7 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) /* Cd = Cs + (1 - As) * Cd */ alpha_config.dst_premulti_en = true; alpha_config.src_pixel_alpha_en = pixel_alpha_en; - alpha_config.src_glb_alpha_value = plane->state->alpha; + alpha_config.src_glb_alpha_value = pstate->alpha; alpha_config.dst_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE; } @@ -2132,7 +2157,10 @@ static void rk3568_vop2_wait_for_layer_cfg_done(struct vop2 *vop2) static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) { struct vop2 *vop2 = vp->vop2; - struct drm_plane *plane; + struct drm_crtc *crtc = &vp->crtc; + struct drm_plane_state *pstate; + struct vop2_win *win; + unsigned long win_mask; u32 layer_sel; u32 port_sel; u32 atv_layer_sel; @@ -2142,11 +2170,25 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) unsigned int ofs; u32 ovl_ctrl; u32 cfg_done; + int phys_id; int i; struct vop2_video_port *vp0 = &vop2->vps[0]; struct vop2_video_port *vp1 = &vop2->vps[1]; struct vop2_video_port *vp2 = &vop2->vps[2]; - struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(vp->crtc.state); + struct rockchip_crtc_state *vcstate; + struct vop2_video_port *main_vp = vp; + u32 own_cfg_done_mask; + + /* right vp share the same crtc state in splice mode */ + if (vp->splice_mode_right) + main_vp = vp->left_vp; + crtc = &main_vp->crtc; + vcstate = to_rockchip_crtc_state(crtc->state); + + /* In splice mode, update main_vp and splice vp */ + own_cfg_done_mask = BIT(main_vp->id); + if (vcstate->splice_mode) + own_cfg_done_mask |= BIT(vop2->data->vp[main_vp->id].splice_vp_id); mutex_lock(&vop2->ovl_lock); ovl_ctrl = vop2_readl(vop2, RK3568_OVL_CTRL); @@ -2190,11 +2232,14 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) for (i = 0; i < vp->id; i++) ofs += vop2->vps[i].nlayers; - drm_atomic_crtc_for_each_plane(plane, &vp->crtc) { - struct vop2_win *win = to_vop2_win(plane); + win_mask = vp->win_mask; + for_each_set_bit(phys_id, &win_mask, BITS_PER_TYPE(u32)) { struct vop2_win *old_win; - layer_id = (u8)(plane->state->normalized_zpos + ofs); + win = vop2_find_win_by_phys_id(vop2, phys_id); + pstate = vop2_win_get_pstate(vp, win); + + layer_id = (u8)(pstate->normalized_zpos + ofs); /* * Find the layer this win bind in old state. */ @@ -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) vop2_writel(vop2, RK3568_SMART_DLY_NUM, sdly); } -static void rk3568_vop2_setup_overlay(struct vop2_video_port *vp) +static void rk3568_vop2_setup_overlay_for_vp(struct vop2_video_port *vp) { struct vop2 *vop2 = vp->vop2; - struct drm_crtc *crtc = &vp->crtc; - struct drm_plane *plane; + unsigned long win_mask = vp->win_mask; + struct vop2_win *win; + int phys_id; - vp->win_mask = 0; + if (!win_mask) + return; - drm_atomic_crtc_for_each_plane(plane, crtc) { - struct vop2_win *win = to_vop2_win(plane); + for_each_set_bit(phys_id, &win_mask, BITS_PER_TYPE(u32)) { + win = vop2_find_win_by_phys_id(vop2, phys_id); + if (WARN_ON(!win)) + continue; win->delay = win->data->dly[VOP2_DLY_MODE_DEFAULT]; - vp->win_mask |= BIT(win->data->phys_id); - if (vop2_cluster_window(win)) - vop2_setup_cluster_alpha(vop2, win); + vop2_setup_cluster_alpha(vop2, win, + vop2_win_get_pstate(vp, win)); } - if (!vp->win_mask) - return; - rk3568_vop2_setup_layer_mixer(vp); vop2_setup_alpha(vp); rk3568_vop2_setup_dly_for_windows(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); +} + static void rk3576_vop2_setup_layer_mixer(struct vop2_video_port *vp) { struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(vp->crtc.state); @@ -2435,7 +2506,8 @@ static void rk3576_vop2_setup_overlay(struct vop2_video_port *vp) vp->win_mask |= BIT(win->data->phys_id); if (vop2_cluster_window(win)) - vop2_setup_cluster_alpha(vop2, win); + vop2_setup_cluster_alpha(vop2, win, + vop2_win_get_pstate(vp, win)); } if (!vp->win_mask) @@ -2449,17 +2521,38 @@ static void rk3576_vop2_setup_overlay(struct vop2_video_port *vp) static void rk3568_vop2_setup_bg_dly(struct vop2_video_port *vp) { struct drm_crtc *crtc = &vp->crtc; - struct drm_display_mode *mode = &crtc->state->adjusted_mode; - u16 hdisplay = mode->crtc_hdisplay; - u16 hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; + struct rockchip_crtc_state *vcstate; + struct drm_display_mode *mode; + u16 hdisplay, hsync_len; u32 bg_dly; u32 pre_scan_dly; + /* right vp share the same crtc state in splice mode */ + if (vp->splice_mode_right) + crtc = &vp->left_vp->crtc; + + vcstate = to_rockchip_crtc_state(crtc->state); + mode = &crtc->state->adjusted_mode; + hdisplay = mode->crtc_hdisplay; + hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; + bg_dly = vp->data->pre_scan_max_dly[3]; vop2_writel(vp->vop2, RK3568_VP_BG_MIX_CTRL(vp->id), FIELD_PREP(RK3568_VP_BG_MIX_CTRL__BG_DLY, bg_dly)); - pre_scan_dly = ((bg_dly + (hdisplay >> 1) - 1) << 16) | hsync_len; + /* + * splice mode: hdisplay must roundup as 4 pixel, + * no splice mode: hdisplay must roundup as 2 pixel. + */ + if (vcstate->splice_mode) + pre_scan_dly = bg_dly + (roundup(hdisplay, 4) >> 2) - 1; + else + pre_scan_dly = bg_dly + (roundup(hdisplay, 2) >> 1) - 1; + + if (vp->vop2->version == VOP_VERSION_RK3588 && hsync_len < 8) + hsync_len = 8; + + pre_scan_dly = (pre_scan_dly << 16) | hsync_len; vop2_vp_write(vp, RK3568_VP_PRE_SCAN_HTIMING, pre_scan_dly); } @@ -2483,6 +2576,15 @@ static void rk3576_vop2_setup_bg_dly(struct vop2_video_port *vp) vop2_vp_write(vp, RK3568_VP_PRE_SCAN_HTIMING, pre_scan_dly); } +static void rk3588_vop2_setup_splice(struct vop2_video_port *vp, bool enable) +{ + struct vop2 *vop2 = vp->vop2; + + regmap_update_bits(vop2->map, RK3568_LUT_PORT_SEL, + RK3568_LUT_PORT_SEL__SPLICE_EN, + enable ? RK3568_LUT_PORT_SEL__SPLICE_EN : 0); +} + static const struct vop2_ops rk3568_vop_ops = { .setup_intf_mux = rk3568_set_intf_mux, .setup_bg_dly = rk3568_vop2_setup_bg_dly, @@ -2499,6 +2601,7 @@ static const struct vop2_ops rk3588_vop_ops = { .setup_intf_mux = rk3588_set_intf_mux, .setup_bg_dly = rk3568_vop2_setup_bg_dly, .setup_overlay = rk3568_vop2_setup_overlay, + .setup_splice = rk3588_vop2_setup_splice, }; static const struct vop2_data rk3566_vop = { @@ -2561,10 +2664,11 @@ static const struct vop2_data rk3576_vop = { static const struct vop2_data rk3588_vop = { .version = VOP_VERSION_RK3588, .feature = VOP2_FEATURE_HAS_SYS_GRF | VOP2_FEATURE_HAS_VO1_GRF | - VOP2_FEATURE_HAS_VOP_GRF | VOP2_FEATURE_HAS_SYS_PMU, + VOP2_FEATURE_HAS_VOP_GRF | VOP2_FEATURE_HAS_SYS_PMU | + VOP2_FEATURE_SPLICE, .nr_vps = 4, .max_input = { 4096, 4320 }, - .max_output = { 4096, 4320 }, + .max_output = { 7680, 4320 }, .vp = rk3588_vop_video_ports, .win = rk3588_vop_win_data, .win_size = ARRAY_SIZE(rk3588_vop_win_data), -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 3/3] drm/rockchip: vop2: Support 8K output by splicing two video ports @ 2026-10-29 8:14 ` Chaoyi Chen 0 siblings, 0 replies; 10+ messages in thread From: Chaoyi Chen @ 2026-10-29 8:14 UTC (permalink / raw) To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter Cc: linux-kernel, dri-devel, Chaoyi Chen, linux-rockchip, Sebastian Reichel, linux-arm-kernel From: Chaoyi Chen <chaoyi.chen@rock-chips.com> A single RK3588 video port can only drive 4096 pixels per line, so modes wider than that (e.g. 7680x4320) cannot be output by one video port alone. The VOP2 can instead merge video ports and two windows: VP0 drives the left half of the screen, VP1 the right half, and each plane of the CRTC is split horizontally between a window pair (Cluster0/1, Cluster2/3, Esmart0/1, Esmart2/3). Three components need to be configured: the CRTC, the plane and the post-processing block. 1. For CRTC In splice mode the CRTC reserved for VP1 is taken over, so make the following changes: vop2_crtc_atomic_check() sets the new splice_mode flag for modes wider than VOP2_MAX_VP_OUTPUT_WIDTH, reserves the partner video port (rejecting the commit if it is already active) and verifies that both video ports have enough layers for the planes of the CRTC. The reserved video port is taken into account when the layers are distributed over the video ports, and it is registered so that its windows exist, but its own CRTC is kept unusable while it serves as the right half. 2. For Plane To avoid requiring additional userspace changes, the plane split is performed in the driver. Userspace can thereforekeep updating a single plane that is 8K wide. To do so, make the following changes: vop2_plane_atomic_check() accepts a source width of twice the maximum input width and validates the split: the plane must use a window with the WIN_FEATURE_SPLICE_LEFT flag, must not be rotated or x-mirrored, and cluster windows that scale down may only do so centred on the screen with a factor below 1.2. vop2_plane_atomic_update() calculates the left and right source and destination rectangles and programs both windows of a plane pair from the same plane state. 3. For post-processing block The overlay, alpha and layer mixer setup iterate over the window mask of a video port instead of the planes of its CRTC, because the right video port has no planes of its own. A right window resolves its plane state through left_win. Gamma LUT and background colour setup are applied to the partner video port as well. Assisted-by: LLM Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com> --- Changes since v2: - vop2_win_get_pstate(): use the left window's state only in splice mode(Sashiko). - Reject splice windows already in use by another CRTC(Sashiko). - Check the splice partner via drm_atomic_get_crtc_state() instead of the runtime flag and unlocked read(Sashiko). - Set splice_mode_right/left_vp before the error paths of vop2_crtc_atomic_enable()(Sashiko). - Validate each split half against the min width 4 and max input width; align NV20/NV30 like NV15(Sashiko). - Latch overlay registers at the left VP's vsync; don't wait for the partner's own cfg-done(Sashiko). - Program the color key on the window actually being updated(Sashiko). --- drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 5 + drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 527 +++++++++++++++++-- drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 66 ++- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 204 +++++-- 4 files changed, 717 insertions(+), 85 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index 4705dc6b8bd7..db4d50dd3dc9 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -54,6 +54,11 @@ struct rockchip_crtc_state { int output_flags; bool enable_afbc; bool yuv_overlay; + /** + * @splice_mode: two video ports are joined together to drive a + * display that is wider than a single video port can handle. + */ + bool splice_mode; u32 bus_format; u32 bus_flags; int color_space; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 06291a13320d..bacfe473e365 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -150,6 +150,7 @@ static void vop2_unlock(struct vop2 *vop2) static void vop2_win_disable(struct vop2_win *win) { vop2_win_write(win, VOP2_WIN_ENABLE, 0); + vop2_win_write(win, VOP2_WIN_YUV_CLIP, 0); if (vop2_cluster_window(win)) vop2_win_write(win, VOP2_WIN_CLUSTER_ENABLE, 0); @@ -426,9 +427,9 @@ static bool vop2_half_block_enable(struct drm_plane_state *pstate) } static u32 vop2_afbc_transform_offset(struct drm_plane_state *pstate, + struct drm_rect *src, bool afbc_half_block_en) { - struct drm_rect *src = &pstate->src; struct drm_framebuffer *fb = pstate->fb; u32 bpp = vop2_get_bpp(fb->format); u32 vir_width = (fb->pitches[0] << 3) / bpp; @@ -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); ret = wait_for_completion_timeout(&vp->dsp_hold_completion, @@ -1043,6 +1066,11 @@ static void vop2_crtc_atomic_disable(struct drm_crtc *crtc, vop2_crtc_disable_irq(vp, VP_INT_DSP_HOLD_VALID); + if (splice_vp) { + splice_vp->splice_mode_right = false; + splice_vp->left_vp = NULL; + } + if (vp->dclk_src) clk_set_parent(vp->dclk, vp->dclk_src); @@ -1064,6 +1092,202 @@ static void vop2_crtc_atomic_disable(struct drm_crtc *crtc, } } +static bool vop2_vp_uses_splice(struct vop2_video_port *vp, + struct drm_crtc_state *cstate) +{ + if (!(vp->vop2->data->feature & VOP2_FEATURE_SPLICE)) + return false; + + return cstate->adjusted_mode.hdisplay > VOP2_MAX_VP_OUTPUT_WIDTH; +} + +static struct vop2_video_port * +vop2_find_left_splice_vp(struct vop2 *vop2, int right_vp_id) +{ + const struct vop2_data *vop2_data = vop2->data; + int i; + + if (!(vop2_data->feature & VOP2_FEATURE_SPLICE)) + return NULL; + + for (i = 0; i < vop2_data->nr_vps; i++) { + const struct vop2_video_port_data *vp_data = &vop2_data->vp[i]; + + if (vp_data->max_output.width <= VOP2_MAX_VP_OUTPUT_WIDTH) + continue; + + if (vp_data->splice_vp_id == right_vp_id) + return &vop2->vps[i]; + } + + return NULL; +} + +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; + } + + if (left_src_w > src_w) + left_src_w = src_w; + + left_src->x1 = src->x1; + left_src->x2 = src->x1 + (left_src_w << 16); + left_dst->x1 = dst->x1; + left_dst->x2 = dst->x1 + left_dst_w; + + right_src->x1 = left_src->x2; + right_src->x2 = src->x2; + right_dst->x1 = dst->x1 + left_dst_w - half_hdisplay; + if (right_dst->x1 < 0) + right_dst->x1 = 0; + right_dst->x2 = right_dst->x1 + right_dst_w; + + left_src->y1 = right_src->y1 = src->y1; + left_src->y2 = right_src->y2 = src->y2; + left_dst->y1 = right_dst->y1 = dst->y1; + left_dst->y2 = right_dst->y2 = dst->y2; + + return 0; +} + +static int vop2_splice_part_size_check(struct vop2_win *win, + struct drm_rect *src, + struct drm_rect *dst) +{ + struct vop2 *vop2 = win->vop2; + + if (!drm_rect_width(dst)) + return 0; + + if (drm_rect_width(dst) < 4 || (drm_rect_width(src) >> 16) < 4 || + (drm_rect_width(src) >> 16) > vop2->data->max_input.width) { + drm_dbg_kms(vop2->drm, + "%s: split %dx%d->%dx%d, min width is 4, max input width is %d\n", + win->data->name, drm_rect_width(src) >> 16, + drm_rect_height(src) >> 16, drm_rect_width(dst), + drm_rect_height(dst), vop2->data->max_input.width); + return -EINVAL; + } + + return 0; +} + +static int vop2_cluster_splice_scale_check(struct vop2_win *win, + struct drm_plane_state *pstate, + int hdisplay) +{ + struct drm_rect *src = &pstate->src; + struct drm_rect *dst = &pstate->dst; + int half_hdisplay = hdisplay >> 1; + + /* scale up is ok */ + if ((drm_rect_width(src) >> 16) <= drm_rect_width(dst)) + return 0; + + if ((drm_rect_width(src) >> 16) <= VOP2_MAX_VP_OUTPUT_WIDTH) + return 0; + /* + * Cluster scale down limitation in splice mode: + * If scale down, must display at horizontal center + */ + if (dst->x1 < half_hdisplay && dst->x2 > half_hdisplay) { + if (dst->x2 + dst->x1 != hdisplay) { + drm_dbg_kms(win->vop2->drm, + "%s must scale down at center in splice mode\n", + win->data->name); + return -EINVAL; + } + + if (drm_rect_calc_hscale(src, dst, 1, FRAC_16_16(6, 5)) < 0) { + drm_dbg_kms(win->vop2->drm, + "%s scale down factor should be < 1.2 in splice mode\n", + win->data->name); + return -EINVAL; + } + } + + return 0; +} + +static int vop2_plane_splice_check(struct drm_plane_state *pstate, + struct drm_crtc_state *cstate) +{ + struct vop2_win *win = to_vop2_win(pstate->plane); + struct vop2 *vop2 = win->vop2; + struct vop2_win *splice_win = win->splice_win; + struct drm_rect left_src, left_dst, right_src, right_dst; + int ret; + + if (!(win->data->feature & WIN_FEATURE_SPLICE_LEFT) || !splice_win) { + drm_dbg_kms(vop2->drm, "%s cannot be used in splice mode\n", + win->data->name); + return -EINVAL; + } + + if (pstate->rotation & (DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X)) { + drm_dbg_kms(vop2->drm, "%s can't rotate 270/90 and xmirror in splice mode\n", + win->data->name); + return -EINVAL; + } + + if (vop2_cluster_window(win)) { + ret = vop2_cluster_splice_scale_check(win, pstate, + cstate->adjusted_mode.hdisplay); + if (ret) + return ret; + } + + ret = vop2_calc_splice_rects(pstate, cstate, &left_src, &left_dst, + &right_src, &right_dst); + if (ret) + return ret; + + ret = vop2_splice_part_size_check(win, &left_src, &left_dst); + if (ret) + return ret; + + return vop2_splice_part_size_check(splice_win, &right_src, &right_dst); +} + static int vop2_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_commit *astate) { @@ -1081,6 +1305,7 @@ static int vop2_plane_atomic_check(struct drm_plane *plane, int max_scale = FRAC_16_16(8, 1); int src_x, src_w, src_h; int dest_w, dest_h; + int max_src_w; int format; int ret; @@ -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; + } + } + } + /* * Src.x1 can be odd when do clip, but yuv plane start point * need align with 2 pixel. @@ -1180,8 +1433,10 @@ static void vop2_plane_atomic_disable(struct drm_plane *plane, struct drm_atomic_commit *state) { struct drm_plane_state *old_pstate = NULL; + struct drm_crtc_state *old_cstate = NULL; struct vop2_win *win = to_vop2_win(plane); struct vop2 *vop2 = win->vop2; + bool splice = false; drm_dbg(vop2->drm, "%s disable\n", win->data->name); @@ -1190,19 +1445,26 @@ static void vop2_plane_atomic_disable(struct drm_plane *plane, if (old_pstate && !old_pstate->crtc) return; + if (old_pstate) { + old_cstate = drm_atomic_get_old_crtc_state(state, old_pstate->crtc); + splice = to_rockchip_crtc_state(old_cstate)->splice_mode; + } + + if (splice && win->splice_win) + vop2_win_disable(win->splice_win); + vop2_win_disable(win); - vop2_win_write(win, VOP2_WIN_YUV_CLIP, 0); } /* * The color key is 10 bit, so all format should * convert to 10 bit here. */ -static void vop2_plane_setup_color_key(struct drm_plane *plane, u32 color_key) +static void vop2_win_setup_color_key(struct vop2_win *win, + struct drm_plane_state *pstate, + u32 color_key) { - struct drm_plane_state *pstate = plane->state; struct drm_framebuffer *fb = pstate->fb; - struct vop2_win *win = to_vop2_win(plane); u32 color_key_en = 0; u32 r = 0; u32 g = 0; @@ -1244,12 +1506,11 @@ static void vop2_plane_setup_color_key(struct drm_plane *plane, u32 color_key) vop2_win_write(win, VOP2_WIN_COLOR_KEY, (r << 20) | (g << 10) | b); } -static void vop2_plane_atomic_update(struct drm_plane *plane, - struct drm_atomic_commit *state) +static void vop2_win_atomic_update(struct vop2_win *win, struct drm_rect *src, + struct drm_rect *dest, + struct drm_plane_state *pstate) { - struct drm_plane_state *pstate = plane->state; struct drm_crtc *crtc = pstate->crtc; - struct vop2_win *win = to_vop2_win(plane); struct vop2_video_port *vp = to_vop2_video_port(crtc); struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode; struct vop2 *vop2 = win->vop2; @@ -1261,8 +1522,6 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, u32 afbc_format; u32 rb_swap; u32 uv_swap; - struct drm_rect *src = &pstate->src; - struct drm_rect *dest = &pstate->dst; u32 afbc_tile_num; u32 transform_offset; bool dither_up; @@ -1283,8 +1542,10 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, if (WARN_ON(!crtc)) return; - if (!pstate->visible) { - vop2_plane_atomic_disable(plane, state); + /* disable no visible window */ + if (!drm_rect_width(src) || !drm_rect_height(src) || + !drm_rect_width(dest) || !drm_rect_height(dest)) { + vop2_win_disable(win); return; } @@ -1424,7 +1685,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, vop2_win_write(win, VOP2_WIN_AFBC_PLD_OFFSET, yrgb_mst); } - transform_offset = vop2_afbc_transform_offset(pstate, half_block_en); + transform_offset = vop2_afbc_transform_offset(pstate, src, half_block_en); vop2_win_write(win, VOP2_WIN_AFBC_HDR_PTR, yrgb_mst); vop2_win_write(win, VOP2_WIN_AFBC_PIC_SIZE, act_info); vop2_win_write(win, VOP2_WIN_TRANSFORM_OFFSET, transform_offset); @@ -1467,7 +1728,7 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, vop2_setup_scale(vop2, win, src_w, src_h, dsp_w, dsp_h, fb->format->format); if (!vop2_cluster_window(win)) - vop2_plane_setup_color_key(plane, 0); + vop2_win_setup_color_key(win, pstate, 0); vop2_win_write(win, VOP2_WIN_ACT_INFO, act_info); vop2_win_write(win, VOP2_WIN_DSP_INFO, dsp_info); vop2_win_write(win, VOP2_WIN_DSP_ST, dest->y1 << 16 | (dest->x1 & 0xffff)); @@ -1487,6 +1748,45 @@ static void vop2_plane_atomic_update(struct drm_plane *plane, } } +static void vop2_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *pstate = plane->state; + struct drm_crtc *crtc = pstate->crtc; + struct vop2_win *win = to_vop2_win(plane); + struct drm_crtc_state *cstate; + struct rockchip_crtc_state *vcstate; + struct drm_rect left_src, left_dst, right_src, right_dst; + + if (WARN_ON(!crtc)) + return; + + if (!pstate->visible) { + vop2_plane_atomic_disable(plane, state); + return; + } + + cstate = drm_atomic_get_new_crtc_state(state, crtc); + if (!cstate) + cstate = crtc->state; + vcstate = to_rockchip_crtc_state(cstate); + + if (!vcstate->splice_mode) { + vop2_win_atomic_update(win, &pstate->src, &pstate->dst, pstate); + return; + } + + if (!win->splice_win) + return; + + if (WARN_ON(vop2_calc_splice_rects(pstate, cstate, &left_src, &left_dst, + &right_src, &right_dst))) + return; + + vop2_win_atomic_update(win, &left_src, &left_dst, pstate); + vop2_win_atomic_update(win->splice_win, &right_src, &right_dst, pstate); +} + static const struct drm_plane_helper_funcs vop2_plane_helper_funcs = { .atomic_check = vop2_plane_atomic_check, .atomic_update = vop2_plane_atomic_update, @@ -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; @@ -1561,8 +1871,9 @@ static void vop2_crtc_atomic_set_gamma_seamless(struct vop2 *vop2, struct vop2_video_port *vp, struct drm_crtc *crtc) { - vop2_writel(vop2, RK3568_LUT_PORT_SEL, - FIELD_PREP(RK3588_LUT_PORT_SEL__GAMMA_AHB_WRITE_SEL, vp->id)); + regmap_update_bits(vop2->map, RK3568_LUT_PORT_SEL, + RK3588_LUT_PORT_SEL__GAMMA_AHB_WRITE_SEL, + FIELD_PREP(RK3588_LUT_PORT_SEL__GAMMA_AHB_WRITE_SEL, vp->id)); vop2_vp_dsp_lut_enable(vp); vop2_crtc_write_gamma_lut(vop2, crtc); vop2_vp_dsp_lut_update_enable(vp); @@ -1573,7 +1884,7 @@ static void vop2_crtc_atomic_set_gamma_rk356x(struct vop2 *vop2, struct drm_crtc *crtc) { vop2_vp_dsp_lut_disable(vp); - vop2_cfg_done(vp); + vop2_cfg_done(vp, false); if (!vop2_vp_dsp_lut_poll_disabled(vp)) return; @@ -1587,18 +1898,25 @@ static void vop2_crtc_atomic_try_set_gamma(struct vop2 *vop2, struct drm_crtc *crtc, struct drm_crtc_state *crtc_state) { + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc_state); + if (!vop2->lut_regs) return; if (!crtc_state->gamma_lut) { vop2_vp_dsp_lut_disable(vp); + if (vcstate->splice_mode) + vop2_vp_dsp_lut_disable(vop2_get_splice_vp(vp)); return; } - if (vop2_supports_seamless_gamma_lut_update(vop2)) + if (vop2_supports_seamless_gamma_lut_update(vop2)) { vop2_crtc_atomic_set_gamma_seamless(vop2, vp, crtc); - else + if (vcstate->splice_mode) + vop2_crtc_atomic_set_gamma_seamless(vop2, vop2_get_splice_vp(vp), crtc); + } else { vop2_crtc_atomic_set_gamma_rk356x(vop2, vp, crtc); + } } static inline void vop2_crtc_atomic_try_set_gamma_locked(struct vop2 *vop2, @@ -1640,13 +1958,12 @@ static void vop2_dither_setup(struct drm_crtc *crtc, u32 *dsp_ctrl) DITHER_DOWN_ALLEGRO); } -static void vop2_bgcolor_setup(struct drm_crtc *crtc, bool force, +static void vop2_bgcolor_setup(struct vop2_video_port *vp, bool force, struct drm_crtc_state *new_crtc_state, struct drm_crtc_state *old_crtc_state) { struct rockchip_crtc_state *new_vcstate = to_rockchip_crtc_state(new_crtc_state); struct rockchip_crtc_state *old_vcstate = to_rockchip_crtc_state(old_crtc_state); - struct vop2_video_port *vp = to_vop2_video_port(crtc); u64 bgcolor = new_crtc_state->background_color; u16 y, cb, cr; u32 val; @@ -1690,6 +2007,8 @@ static void vop2_post_config(struct drm_crtc *crtc, bool force, struct drm_crtc_state *old_crtc_state) { struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct rockchip_crtc_state *new_vcstate = to_rockchip_crtc_state(new_crtc_state); + struct vop2_video_port *splice_vp = NULL; struct vop2 *vop2 = vp->vop2; struct drm_display_mode *mode = &new_crtc_state->adjusted_mode; u16 vtotal = mode->crtc_vtotal; @@ -1704,7 +2023,11 @@ static void vop2_post_config(struct drm_crtc *crtc, bool force, u16 hact_end, vact_end; u32 val; + splice_vp = new_vcstate->splice_mode ? vop2_get_splice_vp(vp) : NULL; + vop2->ops->setup_bg_dly(vp); + if (splice_vp) + vop2->ops->setup_bg_dly(splice_vp); vsize = rounddown(vsize, 2); hsize = rounddown(hsize, 2); @@ -1737,7 +2060,9 @@ static void vop2_post_config(struct drm_crtc *crtc, bool force, vop2_vp_write(vp, RK3568_VP_POST_DSP_VACT_INFO_F1, val); } - vop2_bgcolor_setup(crtc, force, new_crtc_state, old_crtc_state); + vop2_bgcolor_setup(vp, force, new_crtc_state, old_crtc_state); + if (splice_vp) + vop2_bgcolor_setup(splice_vp, force, new_crtc_state, old_crtc_state); } static int us_to_vertical_line(struct drm_display_mode *mode, int us) @@ -1749,6 +2074,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_commit *state) { struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct vop2_video_port *splice_vp = NULL; struct vop2 *vop2 = vp->vop2; const struct vop2_data *vop2_data = vop2->data; const struct vop2_video_port_data *vp_data = &vop2_data->vp[vp->id]; @@ -1780,6 +2106,12 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, vop2_lock(vop2); + if (vcstate->splice_mode) { + splice_vp = vop2_get_splice_vp(vp); + splice_vp->splice_mode_right = true; + splice_vp->left_vp = vp; + } + ret = clk_prepare_enable(vp->dclk); if (ret < 0) { drm_err(vop2->drm, "failed to enable dclk for video port%d - %d\n", @@ -1940,9 +2272,12 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, clk_set_rate(vp->dclk, clock); + if (splice_vp) + vop2->ops->setup_splice(vp, true); + vop2_post_config(crtc, true, crtc_state, old_crtc_state); - vop2_cfg_done(vp); + vop2_cfg_done(vp, vcstate->splice_mode); vop2_vp_write(vp, RK3568_VP_DSP_CTRL, dsp_ctrl); @@ -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; + } + } + } + drm_atomic_crtc_state_for_each_plane(plane, crtc_state) nplanes++; if (nplanes > vp->nlayers) return -EINVAL; + vcstate->splice_mode = false; + if (crtc_state->enable && vop2_vp_uses_splice(vp, crtc_state)) { + struct drm_crtc_state *splice_state; + + if (vp_data->max_output.width <= VOP2_MAX_VP_OUTPUT_WIDTH) + return -EINVAL; + + splice_vp = vop2_get_splice_vp(vp); + if (drm_atomic_crtc_needs_modeset(crtc_state) && splice_vp->crtc.state) { + splice_state = drm_atomic_get_crtc_state(state, &splice_vp->crtc); + if (IS_ERR(splice_state)) + return PTR_ERR(splice_state); + + if (splice_state->enable) { + drm_dbg_kms(vop2->drm, "vp%d is already in use\n", splice_vp->id); + return -EBUSY; + } + } + + /* + * The right half uses its own set of OVL layers, both video + * ports need enough layers for the planes of the left CRTC. + */ + if (nplanes > min(vp->nlayers, splice_vp->nlayers)) + return -EINVAL; + + /* Check whether the right window is used by another CRTC. */ + drm_atomic_crtc_state_for_each_plane(plane, crtc_state) { + struct vop2_win *win = to_vop2_win(plane); + struct vop2_win *rwin = win->splice_win; + struct drm_plane_state *rstate, *old_rstate; + + /* For windows that cannot be spliced */ + if (!rwin) { + drm_dbg_kms(vop2->drm, "%s cannot be used in splice mode\n", + win->data->name); + return -EINVAL; + } + + rstate = drm_atomic_get_plane_state(state, &rwin->base); + if (IS_ERR(rstate)) + return PTR_ERR(rstate); + + /* If window is already in use by another CRTC, then reject it. */ + old_rstate = drm_atomic_get_old_plane_state(state, &rwin->base); + if (rstate->crtc || (old_rstate && old_rstate->crtc)) { + drm_dbg_kms(vop2->drm, "%s is in use on another CRTC\n", + rwin->data->name); + return -EBUSY; + } + } + + vcstate->splice_mode = true; + } + return 0; } @@ -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); spin_lock_irq(&crtc->dev->event_lock); @@ -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++) { @@ -2653,7 +3088,7 @@ static int vop2_create_crtcs(struct vop2 *vop2) for (i = 0; i < vop2->data->nr_vps; i++) { struct vop2_video_port *vp = &vop2->vps[i]; - if (vp->crtc.port) + if (vp_used[i]) vp->nlayers = vop2_data->win_size / nvps; } @@ -2726,7 +3161,7 @@ static int vop2_win_init(struct vop2 *vop2) { const struct vop2_data *vop2_data = vop2->data; struct vop2_win *win; - int i, ret; + int i, j, ret; for (i = 0; i < vop2_data->win_size; i++) { const struct vop2_win_data *win_data = &vop2_data->win[i]; @@ -2747,6 +3182,28 @@ static int vop2_win_init(struct vop2 *vop2) return ret; } + /* Link the windows used for the two halves of a spliced plane */ + for (i = 0; i < vop2_data->win_size; i++) { + win = &vop2->win[i]; + + if (!(win->data->feature & WIN_FEATURE_SPLICE_LEFT)) + continue; + + for (j = 0; j < vop2_data->win_size; j++) { + if (vop2->win[j].data->phys_id != win->data->splice_win_id) + continue; + + win->splice_win = &vop2->win[j]; + win->splice_win->left_win = win; + break; + } + + if (!win->splice_win) + return dev_err_probe(vop2->dev, -EINVAL, + "no splice window for %s\n", + win->data->name); + } + vop2->registered_num_wins = vop2_data->win_size; return 0; @@ -2825,6 +3282,8 @@ static int vop2_bind(struct device *dev, struct device *master, void *data) drm->mode_config.min_width = 4; drm->mode_config.min_height = 4; drm->mode_config.max_width = vop2_data->max_input.width; + if (vop2_data->feature & VOP2_FEATURE_SPLICE) + drm->mode_config.max_width = vop2_data->max_output.width; drm->mode_config.max_height = vop2_data->max_input.height; ret = vop2_win_init(vop2); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h index ffcb39c130aa..87c7fcdb26df 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h @@ -24,14 +24,24 @@ #define VOP2_VP_FEATURE_OUTPUT_10BIT BIT(0) +/* + * The maximum output width of a single video port. Modes wider than + * this are driven by splicing two video ports together. + */ +#define VOP2_MAX_VP_OUTPUT_WIDTH 4096 + #define VOP2_FEATURE_HAS_SYS_GRF BIT(0) #define VOP2_FEATURE_HAS_VO0_GRF BIT(1) #define VOP2_FEATURE_HAS_VO1_GRF BIT(2) #define VOP2_FEATURE_HAS_VOP_GRF BIT(3) #define VOP2_FEATURE_HAS_SYS_PMU BIT(4) +/* splice two windows and two VPs to support resolutions > 4096 */ +#define VOP2_FEATURE_SPLICE BIT(5) #define WIN_FEATURE_AFBDC BIT(0) #define WIN_FEATURE_CLUSTER BIT(1) +/* Left window in splice mode */ +#define WIN_FEATURE_SPLICE_LEFT BIT(6) /* * the delay number of a window in different mode. @@ -178,6 +188,13 @@ struct vop2_win_data { const char *name; unsigned int phys_id; + /** + * @splice_win_id: phy id of the window driving the right half of + * this window in splice mode. Only valid for windows with + * %WIN_FEATURE_SPLICE_LEFT. + */ + u8 splice_win_id; + u32 base; u32 possible_vp_mask; enum drm_plane_type type; @@ -217,10 +234,26 @@ struct vop2_win { u32 offset; enum drm_plane_type type; + + /** + * @splice_win: window used for the right half of this window when + * the display is driven in splice mode. + */ + struct vop2_win *splice_win; + /** + * @left_win: window driving the left half of this window when this + * window is used as the right half in splice mode. + */ + struct vop2_win *left_win; }; struct vop2_video_port_data { unsigned int id; + /** + * @splice_vp_id: id of the video port driving the right half of the + * screen when this video port works in splice mode. + */ + u8 splice_vp_id; u32 feature; u16 gamma_lut_len; u16 cubic_lut_len; @@ -252,6 +285,17 @@ struct vop2_video_port { struct drm_pending_vblank_event *event; unsigned int nlayers; + + /** + * @splice_mode_right: this video port drives the right half of the + * display in splice mode. + */ + bool splice_mode_right; + /** + * @left_vp: video port driving the left half when + * @splice_mode_right is set. + */ + struct vop2_video_port *left_vp; }; /** @@ -264,6 +308,11 @@ struct vop2_ops { unsigned long (*setup_intf_mux)(struct vop2_video_port *vp, int ep_id, u32 polflags); void (*setup_bg_dly)(struct vop2_video_port *vp); void (*setup_overlay)(struct vop2_video_port *vp); + /* + * @setup_splice: join or split this video port and its partner to + * drive a mode wider than a single video port can handle. + */ + void (*setup_splice)(struct vop2_video_port *vp, bool enable); }; struct vop2_data { @@ -712,6 +761,7 @@ enum dst_factor_mode { #define RK3588_DSP_IF_POL__DP0_PIN_POL GENMASK(10, 8) #define RK3588_LUT_PORT_SEL__GAMMA_AHB_WRITE_SEL GENMASK(13, 12) +#define RK3568_LUT_PORT_SEL__SPLICE_EN BIT(16) #define RK3568_VP0_MIPI_CTRL__DCLK_DIV2_PHASE_LOCK BIT(5) #define RK3568_VP0_MIPI_CTRL__DCLK_DIV2 BIT(4) @@ -848,6 +898,14 @@ static inline struct vop2_win *to_vop2_win(struct drm_plane *p) return container_of(p, struct vop2_win, base); } +static inline struct vop2_video_port * +vop2_get_splice_vp(struct vop2_video_port *vp) +{ + struct vop2 *vop2 = vp->vop2; + + return &vop2->vps[vop2->data->vp[vp->id].splice_vp_id]; +} + /* * Note: * The write mask function is documented but missing on rk3566/8, writes @@ -857,13 +915,19 @@ static inline struct vop2_win *to_vop2_win(struct drm_plane *p) * GLB_CFG_DONE_EN has no write mask bit. * */ -static inline void vop2_cfg_done(struct vop2_video_port *vp) +static inline void vop2_cfg_done(struct vop2_video_port *vp, bool splice) { struct vop2 *vop2 = vp->vop2; u32 val = RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN; val |= BIT(vp->id) | (BIT(vp->id) << 16); + if (splice) { + int splice_vp_id = vop2->data->vp[vp->id].splice_vp_id; + + val |= BIT(splice_vp_id) | (BIT(splice_vp_id) << 16); + } + regmap_set_bits(vop2->map, RK3568_REG_CFG_DONE, val); } diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index c44648e1bc0d..08f9f8cac878 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -1056,10 +1056,11 @@ static const struct vop2_regs_dump rk3576_regs_dump[] = { static const struct vop2_video_port_data rk3588_vop_video_ports[] = { { .id = 0, + .splice_vp_id = 1, .feature = VOP2_VP_FEATURE_OUTPUT_10BIT, .gamma_lut_len = 1024, .cubic_lut_len = 9 * 9 * 9, /* 9x9x9 */ - .max_output = { 4096, 2304 }, + .max_output = { 7680, 4320 }, /* hdr2sdr sdr2hdr hdr2hdr sdr2sdr */ .pre_scan_max_dly = { 76, 65, 65, 54 }, .offset = 0xc00, @@ -1121,6 +1122,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { { .name = "Cluster0-win0", .phys_id = ROCKCHIP_VOP2_CLUSTER0, + .splice_win_id = ROCKCHIP_VOP2_CLUSTER1, .base = 0x1000, .possible_vp_mask = BIT(0) | BIT(1) | BIT(2) | BIT(3), .formats = formats_cluster, @@ -1136,7 +1138,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { .max_downscale_factor = 4, .dly = { 4, 26, 29 }, .type = DRM_PLANE_TYPE_PRIMARY, - .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER, + .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER | WIN_FEATURE_SPLICE_LEFT, }, { .name = "Cluster1-win0", .phys_id = ROCKCHIP_VOP2_CLUSTER1, @@ -1159,6 +1161,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { }, { .name = "Cluster2-win0", .phys_id = ROCKCHIP_VOP2_CLUSTER2, + .splice_win_id = ROCKCHIP_VOP2_CLUSTER3, .base = 0x1400, .possible_vp_mask = BIT(0) | BIT(1) | BIT(2) | BIT(3), .formats = formats_cluster, @@ -1174,7 +1177,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { .max_upscale_factor = 4, .max_downscale_factor = 4, .dly = { 4, 26, 29 }, - .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER, + .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER | WIN_FEATURE_SPLICE_LEFT, }, { .name = "Cluster3-win0", .phys_id = ROCKCHIP_VOP2_CLUSTER3, @@ -1197,6 +1200,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { }, { .name = "Esmart0-win0", .phys_id = ROCKCHIP_VOP2_ESMART0, + .splice_win_id = ROCKCHIP_VOP2_ESMART1, .possible_vp_mask = BIT(0) | BIT(1) | BIT(2) | BIT(3), .formats = formats_esmart, .nformats = ARRAY_SIZE(formats_esmart), @@ -1211,6 +1215,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { .max_upscale_factor = 8, .max_downscale_factor = 8, .dly = { 23, 45, 48 }, + .feature = WIN_FEATURE_SPLICE_LEFT, }, { .name = "Esmart1-win0", .phys_id = ROCKCHIP_VOP2_ESMART1, @@ -1231,6 +1236,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { }, { .name = "Esmart2-win0", .phys_id = ROCKCHIP_VOP2_ESMART2, + .splice_win_id = ROCKCHIP_VOP2_ESMART3, .base = 0x1c00, .possible_vp_mask = BIT(0) | BIT(1) | BIT(2) | BIT(3), .formats = formats_esmart, @@ -1245,6 +1251,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { .max_upscale_factor = 8, .max_downscale_factor = 8, .dly = { 23, 45, 48 }, + .feature = WIN_FEATURE_SPLICE_LEFT, }, { .name = "Esmart3-win0", .phys_id = ROCKCHIP_VOP2_ESMART3, @@ -1387,6 +1394,19 @@ static struct vop2_win *vop2_find_win_by_phys_id(struct vop2 *vop2, uint8_t phys return NULL; } +/* + * The right window in splice mode shares the plane state of the left one, + * only its registers and position on the screen are different. + */ +static struct drm_plane_state * +vop2_win_get_pstate(struct vop2_video_port *vp, struct vop2_win *win) +{ + if (win->left_win && vp->splice_mode_right) + return win->left_win->base.state; + + return win->base.state; +} + static unsigned long rk3568_set_intf_mux(struct vop2_video_port *vp, int id, u32 polflags) { struct vop2 *vop2 = vp->vop2; @@ -1873,7 +1893,7 @@ static int vop2_find_start_mixer_id_for_vp(struct vop2_video_port *vp) for (i = 0; i < 31; i += 4) { match = false; - for_each_set_bit(phys_id, &win_mask, ROCKCHIP_VOP2_ESMART3) { + for_each_set_bit(phys_id, &win_mask, BITS_PER_TYPE(u32)) { win = vop2_find_win_by_phys_id(vop2, phys_id); if (win->data->layer_sel_id[vp->id] == ((layer_sel >> i) & 0xf)) { match = true; @@ -1890,11 +1910,11 @@ static int vop2_find_start_mixer_id_for_vp(struct vop2_video_port *vp) return used_layer; } -static void vop2_setup_cluster_alpha(struct vop2 *vop2, struct vop2_win *main_win) +static void vop2_setup_cluster_alpha(struct vop2 *vop2, struct vop2_win *main_win, + struct drm_plane_state *pstate) { struct vop2_alpha_config alpha_config; struct vop2_alpha alpha; - struct drm_plane_state *bottom_win_pstate; bool src_pixel_alpha_en = false; u16 src_glb_alpha_val, dst_glb_alpha_val; u32 src_color_ctrl_reg, dst_color_ctrl_reg, src_alpha_ctrl_reg, dst_alpha_ctrl_reg; @@ -1903,11 +1923,10 @@ static void vop2_setup_cluster_alpha(struct vop2 *vop2, struct vop2_win *main_wi bool swap = false; /* At one win mode, win0 is dst/bottom win, and win1 is a all zero src/top win */ - bottom_win_pstate = main_win->base.state; src_glb_alpha_val = 0; - dst_glb_alpha_val = main_win->base.state->alpha; + dst_glb_alpha_val = pstate->alpha; - if (!bottom_win_pstate->fb) + if (!pstate->fb) return; alpha_config.src_premulti_en = premulti_en; @@ -1959,7 +1978,10 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) struct drm_framebuffer *fb; struct vop2_alpha_config alpha_config; struct vop2_alpha alpha; - struct drm_plane *plane; + struct drm_plane_state *pstate; + struct vop2_win *win; + unsigned long win_mask = vp->win_mask; + int phys_id; int pixel_alpha_en; int premulti_en, gpremulti_en = 0; int mixer_id; @@ -1975,11 +1997,12 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) alpha_config.dst_pixel_alpha_en = true; /* alpha value need transfer to next mix */ - drm_atomic_crtc_for_each_plane(plane, &vp->crtc) { - struct vop2_win *win = to_vop2_win(plane); + for_each_set_bit(phys_id, &win_mask, BITS_PER_TYPE(u32)) { + win = vop2_find_win_by_phys_id(vop2, phys_id); + pstate = vop2_win_get_pstate(vp, win); - if (plane->state->normalized_zpos == 0 && - !is_opaque(plane->state->alpha) && + if (pstate->normalized_zpos == 0 && + !is_opaque(pstate->alpha) && !vop2_cluster_window(win)) { /* * If bottom layer have global alpha effect [except cluster layer, @@ -1987,7 +2010,7 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) * at cluster mix], bottom layer mix need deal with global alpha. */ bottom_layer_alpha_en = true; - dst_global_alpha = plane->state->alpha; + dst_global_alpha = pstate->alpha; } } @@ -2003,9 +2026,12 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) dst_alpha_ctrl_reg = RK3576_OVL_MIX0_DST_ALPHA_CTRL(vp->id); } - drm_atomic_crtc_for_each_plane(plane, &vp->crtc) { - struct vop2_win *win = to_vop2_win(plane); - int zpos = plane->state->normalized_zpos; + for_each_set_bit(phys_id, &win_mask, BITS_PER_TYPE(u32)) { + int zpos; + + win = vop2_find_win_by_phys_id(vop2, phys_id); + pstate = vop2_win_get_pstate(vp, win); + zpos = pstate->normalized_zpos; /* * Need to configure alpha from second layer. @@ -2013,13 +2039,12 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) if (zpos == 0) continue; - if (plane->state->pixel_blend_mode == DRM_MODE_BLEND_PREMULTI) + if (pstate->pixel_blend_mode == DRM_MODE_BLEND_PREMULTI) premulti_en = 1; else premulti_en = 0; - plane = &win->base; - fb = plane->state->fb; + fb = pstate->fb; pixel_alpha_en = fb->format->has_alpha; @@ -2030,7 +2055,7 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) /* Cd = Cs + (1 - As) * Cd * Agd */ alpha_config.dst_premulti_en = false; alpha_config.src_pixel_alpha_en = pixel_alpha_en; - alpha_config.src_glb_alpha_value = plane->state->alpha; + alpha_config.src_glb_alpha_value = pstate->alpha; alpha_config.dst_glb_alpha_value = dst_global_alpha; } else if (vop2_cluster_window(win)) { /* Mix output data only have pixel alpha */ @@ -2042,7 +2067,7 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) /* Cd = Cs + (1 - As) * Cd */ alpha_config.dst_premulti_en = true; alpha_config.src_pixel_alpha_en = pixel_alpha_en; - alpha_config.src_glb_alpha_value = plane->state->alpha; + alpha_config.src_glb_alpha_value = pstate->alpha; alpha_config.dst_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE; } @@ -2132,7 +2157,10 @@ static void rk3568_vop2_wait_for_layer_cfg_done(struct vop2 *vop2) static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) { struct vop2 *vop2 = vp->vop2; - struct drm_plane *plane; + struct drm_crtc *crtc = &vp->crtc; + struct drm_plane_state *pstate; + struct vop2_win *win; + unsigned long win_mask; u32 layer_sel; u32 port_sel; u32 atv_layer_sel; @@ -2142,11 +2170,25 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) unsigned int ofs; u32 ovl_ctrl; u32 cfg_done; + int phys_id; int i; struct vop2_video_port *vp0 = &vop2->vps[0]; struct vop2_video_port *vp1 = &vop2->vps[1]; struct vop2_video_port *vp2 = &vop2->vps[2]; - struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(vp->crtc.state); + struct rockchip_crtc_state *vcstate; + struct vop2_video_port *main_vp = vp; + u32 own_cfg_done_mask; + + /* right vp share the same crtc state in splice mode */ + if (vp->splice_mode_right) + main_vp = vp->left_vp; + crtc = &main_vp->crtc; + vcstate = to_rockchip_crtc_state(crtc->state); + + /* In splice mode, update main_vp and splice vp */ + own_cfg_done_mask = BIT(main_vp->id); + if (vcstate->splice_mode) + own_cfg_done_mask |= BIT(vop2->data->vp[main_vp->id].splice_vp_id); mutex_lock(&vop2->ovl_lock); ovl_ctrl = vop2_readl(vop2, RK3568_OVL_CTRL); @@ -2190,11 +2232,14 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) for (i = 0; i < vp->id; i++) ofs += vop2->vps[i].nlayers; - drm_atomic_crtc_for_each_plane(plane, &vp->crtc) { - struct vop2_win *win = to_vop2_win(plane); + win_mask = vp->win_mask; + for_each_set_bit(phys_id, &win_mask, BITS_PER_TYPE(u32)) { struct vop2_win *old_win; - layer_id = (u8)(plane->state->normalized_zpos + ofs); + win = vop2_find_win_by_phys_id(vop2, phys_id); + pstate = vop2_win_get_pstate(vp, win); + + layer_id = (u8)(pstate->normalized_zpos + ofs); /* * Find the layer this win bind in old state. */ @@ -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) vop2_writel(vop2, RK3568_SMART_DLY_NUM, sdly); } -static void rk3568_vop2_setup_overlay(struct vop2_video_port *vp) +static void rk3568_vop2_setup_overlay_for_vp(struct vop2_video_port *vp) { struct vop2 *vop2 = vp->vop2; - struct drm_crtc *crtc = &vp->crtc; - struct drm_plane *plane; + unsigned long win_mask = vp->win_mask; + struct vop2_win *win; + int phys_id; - vp->win_mask = 0; + if (!win_mask) + return; - drm_atomic_crtc_for_each_plane(plane, crtc) { - struct vop2_win *win = to_vop2_win(plane); + for_each_set_bit(phys_id, &win_mask, BITS_PER_TYPE(u32)) { + win = vop2_find_win_by_phys_id(vop2, phys_id); + if (WARN_ON(!win)) + continue; win->delay = win->data->dly[VOP2_DLY_MODE_DEFAULT]; - vp->win_mask |= BIT(win->data->phys_id); - if (vop2_cluster_window(win)) - vop2_setup_cluster_alpha(vop2, win); + vop2_setup_cluster_alpha(vop2, win, + vop2_win_get_pstate(vp, win)); } - if (!vp->win_mask) - return; - rk3568_vop2_setup_layer_mixer(vp); vop2_setup_alpha(vp); rk3568_vop2_setup_dly_for_windows(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); +} + static void rk3576_vop2_setup_layer_mixer(struct vop2_video_port *vp) { struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(vp->crtc.state); @@ -2435,7 +2506,8 @@ static void rk3576_vop2_setup_overlay(struct vop2_video_port *vp) vp->win_mask |= BIT(win->data->phys_id); if (vop2_cluster_window(win)) - vop2_setup_cluster_alpha(vop2, win); + vop2_setup_cluster_alpha(vop2, win, + vop2_win_get_pstate(vp, win)); } if (!vp->win_mask) @@ -2449,17 +2521,38 @@ static void rk3576_vop2_setup_overlay(struct vop2_video_port *vp) static void rk3568_vop2_setup_bg_dly(struct vop2_video_port *vp) { struct drm_crtc *crtc = &vp->crtc; - struct drm_display_mode *mode = &crtc->state->adjusted_mode; - u16 hdisplay = mode->crtc_hdisplay; - u16 hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; + struct rockchip_crtc_state *vcstate; + struct drm_display_mode *mode; + u16 hdisplay, hsync_len; u32 bg_dly; u32 pre_scan_dly; + /* right vp share the same crtc state in splice mode */ + if (vp->splice_mode_right) + crtc = &vp->left_vp->crtc; + + vcstate = to_rockchip_crtc_state(crtc->state); + mode = &crtc->state->adjusted_mode; + hdisplay = mode->crtc_hdisplay; + hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; + bg_dly = vp->data->pre_scan_max_dly[3]; vop2_writel(vp->vop2, RK3568_VP_BG_MIX_CTRL(vp->id), FIELD_PREP(RK3568_VP_BG_MIX_CTRL__BG_DLY, bg_dly)); - pre_scan_dly = ((bg_dly + (hdisplay >> 1) - 1) << 16) | hsync_len; + /* + * splice mode: hdisplay must roundup as 4 pixel, + * no splice mode: hdisplay must roundup as 2 pixel. + */ + if (vcstate->splice_mode) + pre_scan_dly = bg_dly + (roundup(hdisplay, 4) >> 2) - 1; + else + pre_scan_dly = bg_dly + (roundup(hdisplay, 2) >> 1) - 1; + + if (vp->vop2->version == VOP_VERSION_RK3588 && hsync_len < 8) + hsync_len = 8; + + pre_scan_dly = (pre_scan_dly << 16) | hsync_len; vop2_vp_write(vp, RK3568_VP_PRE_SCAN_HTIMING, pre_scan_dly); } @@ -2483,6 +2576,15 @@ static void rk3576_vop2_setup_bg_dly(struct vop2_video_port *vp) vop2_vp_write(vp, RK3568_VP_PRE_SCAN_HTIMING, pre_scan_dly); } +static void rk3588_vop2_setup_splice(struct vop2_video_port *vp, bool enable) +{ + struct vop2 *vop2 = vp->vop2; + + regmap_update_bits(vop2->map, RK3568_LUT_PORT_SEL, + RK3568_LUT_PORT_SEL__SPLICE_EN, + enable ? RK3568_LUT_PORT_SEL__SPLICE_EN : 0); +} + static const struct vop2_ops rk3568_vop_ops = { .setup_intf_mux = rk3568_set_intf_mux, .setup_bg_dly = rk3568_vop2_setup_bg_dly, @@ -2499,6 +2601,7 @@ static const struct vop2_ops rk3588_vop_ops = { .setup_intf_mux = rk3588_set_intf_mux, .setup_bg_dly = rk3568_vop2_setup_bg_dly, .setup_overlay = rk3568_vop2_setup_overlay, + .setup_splice = rk3588_vop2_setup_splice, }; static const struct vop2_data rk3566_vop = { @@ -2561,10 +2664,11 @@ static const struct vop2_data rk3576_vop = { static const struct vop2_data rk3588_vop = { .version = VOP_VERSION_RK3588, .feature = VOP2_FEATURE_HAS_SYS_GRF | VOP2_FEATURE_HAS_VO1_GRF | - VOP2_FEATURE_HAS_VOP_GRF | VOP2_FEATURE_HAS_SYS_PMU, + VOP2_FEATURE_HAS_VOP_GRF | VOP2_FEATURE_HAS_SYS_PMU | + VOP2_FEATURE_SPLICE, .nr_vps = 4, .max_input = { 4096, 4320 }, - .max_output = { 4096, 4320 }, + .max_output = { 7680, 4320 }, .vp = rk3588_vop_video_ports, .win = rk3588_vop_win_data, .win_size = ARRAY_SIZE(rk3588_vop_win_data), -- 2.53.0 _______________________________________________ Linux-rockchip mailing list Linux-rockchip@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-rockchip ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v2 3/3] drm/rockchip: vop2: Support 8K output by splicing two video ports 2026-10-29 8:14 ` Chaoyi Chen (?) @ 2026-10-29 8:45 ` sashiko-bot -1 siblings, 0 replies; 10+ messages in thread From: sashiko-bot @ 2026-10-29 8:45 UTC (permalink / raw) To: Chaoyi Chen; +Cc: dri-devel 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 ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-10-29 8:45 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 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 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.