* [PATCH 0/8] drm/rockchip: vop2: RK3588 clocking, sync polarity, AXI port and window fixes
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
This series collects fixes for the RK3588 VOP2 found while bringing up
HDMI and DisplayPort output on an Orange Pi 5 (RK3588S).
Patches 1 and 2 fix the RK3588 interface setup: rk3588_calc_cru_cfg()
leaves dividers it does not use uninitialised and the garbage ends up in
VP_CLK_CTRL, and rk3588_get_hdmi_pol() decodes the POLFLAG bits it is
handed as DRM mode flags, so the GRF sync polarity bits are wrong for
most modes.
Patch 3 stops the RK3588 Cluster windows from advertising AFBC with the
lossless colour transform (YTR) for RGB565/BGR565, which the decoder
cannot display (black picture plus POST_BUF_EMPTY).
Patches 4 and 5 deal with POST_BUF_EMPTY underflows. The VOP2 fetches
through two AXI read ports but the port of each window is fixed in the
window table, so the usual primary plane plus overlay configuration of
a video port puts both layers on one port while the other sits idle.
One port cannot sustain two full screen 4K@120Hz layers (measured: the
fetch rate collapses from ~6 GB/s to ~3 GB/s and about half of all
lines underflow). Patch 4 assigns the port when a window starts
fetching, balancing the estimated load; patch 5 rate-limits the
POST_BUF_EMPTY interrupt to once per frame, since an underflowing port
otherwise keeps the CPU in the interrupt handler 50000 to 300000 times
per second.
Patches 6 to 8 are smaller fixes: the start mixer search skipped
Esmart3 (for_each_set_bit() was given the highest window id as the bit
count), a gamma LUT update cleared the other fields of LUT_PORT_SEL
(the VP0/VP2 dclk source selection and the VP0/VP1 merge enable), and
the AFBC tile count and transform offset were wrong for a source that
starts inside a 16 pixel superblock.
Tested on an Orange Pi 5 (RK3588S) with an HDMI 2.1 TV and a
DisplayPort monitor, pictures judged with a camera. Patches 1 and 2:
VP0_CLK_CTRL reads back without the garbage dclk_out divider, the sync
polarities seen by the HDMI controller (VIDEO_INTERFACE_STATUS0) match
the mode flags for +/+ (1080p, 2160p) and -/- (720x480, 640x480) modes,
and the picture is correct over TMDS and FRL modes from 27 to
1188 MHz. Patch 3: RGB565 is offered on the Cluster windows only
without YTR, and those planes scan out correctly on three Cluster
windows. Patches 4 and 5: 3840x2160@120 with a full screen overlay
(previously ~120000 POST_BUF_EMPTY per second and a scrambled picture)
and a dual-display session (HDMI 4K@60 plus DP 4K@30, two video players,
ten minutes) run with zero underflows. Patches 6 to 8 were found while
driving Esmart3 and partial AFBC overlays: an AFBC overlay placed at
x=1001 shows correctly. All eight have been in the board's kernel for
the display test runs since.
This series was developed with Claude (Anthropic) as a coding assistant:
it wrote the code and the commit messages, and I reviewed and tested
every change as described above.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
Arun Iyengar (8):
drm/rockchip: vop2: Don't use uninitialised dividers on RK3588
drm/rockchip: vop2: Fix the HDMI sync polarity on RK3588
drm/rockchip: vop2: Don't accept AFBC YTR for RGB565 on RK3588
drm/rockchip: vop2: Balance window fetches across both AXI ports on RK3588
drm/rockchip: vop2: Report POST_BUF_EMPTY at most once per frame
drm/rockchip: vop2: Find the first mixer of a video port with Esmart3 too
drm/rockchip: vop2: Keep the other LUT_PORT_SEL fields on gamma LUT updates
drm/rockchip: vop2: Fix the AFBC offsets of a source that starts inside a superblock
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 186 +++++++++++++++++++++++++--
drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 24 ++++
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 27 +++-
3 files changed, 223 insertions(+), 14 deletions(-)
---
base-commit: 8deaade4a5a4ff635df91e246e42139ab2b33f6d
change-id: 20261110-vop2-fixes-fa45142799c1
Best regards,
--
Arun Iyengar <aiyengar1990@gmail.com>
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH 0/8] drm/rockchip: vop2: RK3588 clocking, sync polarity, AXI port and window fixes
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
This series collects fixes for the RK3588 VOP2 found while bringing up
HDMI and DisplayPort output on an Orange Pi 5 (RK3588S).
Patches 1 and 2 fix the RK3588 interface setup: rk3588_calc_cru_cfg()
leaves dividers it does not use uninitialised and the garbage ends up in
VP_CLK_CTRL, and rk3588_get_hdmi_pol() decodes the POLFLAG bits it is
handed as DRM mode flags, so the GRF sync polarity bits are wrong for
most modes.
Patch 3 stops the RK3588 Cluster windows from advertising AFBC with the
lossless colour transform (YTR) for RGB565/BGR565, which the decoder
cannot display (black picture plus POST_BUF_EMPTY).
Patches 4 and 5 deal with POST_BUF_EMPTY underflows. The VOP2 fetches
through two AXI read ports but the port of each window is fixed in the
window table, so the usual primary plane plus overlay configuration of
a video port puts both layers on one port while the other sits idle.
One port cannot sustain two full screen 4K@120Hz layers (measured: the
fetch rate collapses from ~6 GB/s to ~3 GB/s and about half of all
lines underflow). Patch 4 assigns the port when a window starts
fetching, balancing the estimated load; patch 5 rate-limits the
POST_BUF_EMPTY interrupt to once per frame, since an underflowing port
otherwise keeps the CPU in the interrupt handler 50000 to 300000 times
per second.
Patches 6 to 8 are smaller fixes: the start mixer search skipped
Esmart3 (for_each_set_bit() was given the highest window id as the bit
count), a gamma LUT update cleared the other fields of LUT_PORT_SEL
(the VP0/VP2 dclk source selection and the VP0/VP1 merge enable), and
the AFBC tile count and transform offset were wrong for a source that
starts inside a 16 pixel superblock.
Tested on an Orange Pi 5 (RK3588S) with an HDMI 2.1 TV and a
DisplayPort monitor, pictures judged with a camera. Patches 1 and 2:
VP0_CLK_CTRL reads back without the garbage dclk_out divider, the sync
polarities seen by the HDMI controller (VIDEO_INTERFACE_STATUS0) match
the mode flags for +/+ (1080p, 2160p) and -/- (720x480, 640x480) modes,
and the picture is correct over TMDS and FRL modes from 27 to
1188 MHz. Patch 3: RGB565 is offered on the Cluster windows only
without YTR, and those planes scan out correctly on three Cluster
windows. Patches 4 and 5: 3840x2160@120 with a full screen overlay
(previously ~120000 POST_BUF_EMPTY per second and a scrambled picture)
and a dual-display session (HDMI 4K@60 plus DP 4K@30, two video players,
ten minutes) run with zero underflows. Patches 6 to 8 were found while
driving Esmart3 and partial AFBC overlays: an AFBC overlay placed at
x=1001 shows correctly. All eight have been in the board's kernel for
the display test runs since.
This series was developed with Claude (Anthropic) as a coding assistant:
it wrote the code and the commit messages, and I reviewed and tested
every change as described above.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
Arun Iyengar (8):
drm/rockchip: vop2: Don't use uninitialised dividers on RK3588
drm/rockchip: vop2: Fix the HDMI sync polarity on RK3588
drm/rockchip: vop2: Don't accept AFBC YTR for RGB565 on RK3588
drm/rockchip: vop2: Balance window fetches across both AXI ports on RK3588
drm/rockchip: vop2: Report POST_BUF_EMPTY at most once per frame
drm/rockchip: vop2: Find the first mixer of a video port with Esmart3 too
drm/rockchip: vop2: Keep the other LUT_PORT_SEL fields on gamma LUT updates
drm/rockchip: vop2: Fix the AFBC offsets of a source that starts inside a superblock
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 186 +++++++++++++++++++++++++--
drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 24 ++++
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 27 +++-
3 files changed, 223 insertions(+), 14 deletions(-)
---
base-commit: 8deaade4a5a4ff635df91e246e42139ab2b33f6d
change-id: 20261110-vop2-fixes-fa45142799c1
Best regards,
--
Arun Iyengar <aiyengar1990@gmail.com>
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH 0/8] drm/rockchip: vop2: RK3588 clocking, sync polarity, AXI port and window fixes
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
This series collects fixes for the RK3588 VOP2 found while bringing up
HDMI and DisplayPort output on an Orange Pi 5 (RK3588S).
Patches 1 and 2 fix the RK3588 interface setup: rk3588_calc_cru_cfg()
leaves dividers it does not use uninitialised and the garbage ends up in
VP_CLK_CTRL, and rk3588_get_hdmi_pol() decodes the POLFLAG bits it is
handed as DRM mode flags, so the GRF sync polarity bits are wrong for
most modes.
Patch 3 stops the RK3588 Cluster windows from advertising AFBC with the
lossless colour transform (YTR) for RGB565/BGR565, which the decoder
cannot display (black picture plus POST_BUF_EMPTY).
Patches 4 and 5 deal with POST_BUF_EMPTY underflows. The VOP2 fetches
through two AXI read ports but the port of each window is fixed in the
window table, so the usual primary plane plus overlay configuration of
a video port puts both layers on one port while the other sits idle.
One port cannot sustain two full screen 4K@120Hz layers (measured: the
fetch rate collapses from ~6 GB/s to ~3 GB/s and about half of all
lines underflow). Patch 4 assigns the port when a window starts
fetching, balancing the estimated load; patch 5 rate-limits the
POST_BUF_EMPTY interrupt to once per frame, since an underflowing port
otherwise keeps the CPU in the interrupt handler 50000 to 300000 times
per second.
Patches 6 to 8 are smaller fixes: the start mixer search skipped
Esmart3 (for_each_set_bit() was given the highest window id as the bit
count), a gamma LUT update cleared the other fields of LUT_PORT_SEL
(the VP0/VP2 dclk source selection and the VP0/VP1 merge enable), and
the AFBC tile count and transform offset were wrong for a source that
starts inside a 16 pixel superblock.
Tested on an Orange Pi 5 (RK3588S) with an HDMI 2.1 TV and a
DisplayPort monitor, pictures judged with a camera. Patches 1 and 2:
VP0_CLK_CTRL reads back without the garbage dclk_out divider, the sync
polarities seen by the HDMI controller (VIDEO_INTERFACE_STATUS0) match
the mode flags for +/+ (1080p, 2160p) and -/- (720x480, 640x480) modes,
and the picture is correct over TMDS and FRL modes from 27 to
1188 MHz. Patch 3: RGB565 is offered on the Cluster windows only
without YTR, and those planes scan out correctly on three Cluster
windows. Patches 4 and 5: 3840x2160@120 with a full screen overlay
(previously ~120000 POST_BUF_EMPTY per second and a scrambled picture)
and a dual-display session (HDMI 4K@60 plus DP 4K@30, two video players,
ten minutes) run with zero underflows. Patches 6 to 8 were found while
driving Esmart3 and partial AFBC overlays: an AFBC overlay placed at
x=1001 shows correctly. All eight have been in the board's kernel for
the display test runs since.
This series was developed with Claude (Anthropic) as a coding assistant:
it wrote the code and the commit messages, and I reviewed and tested
every change as described above.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
Arun Iyengar (8):
drm/rockchip: vop2: Don't use uninitialised dividers on RK3588
drm/rockchip: vop2: Fix the HDMI sync polarity on RK3588
drm/rockchip: vop2: Don't accept AFBC YTR for RGB565 on RK3588
drm/rockchip: vop2: Balance window fetches across both AXI ports on RK3588
drm/rockchip: vop2: Report POST_BUF_EMPTY at most once per frame
drm/rockchip: vop2: Find the first mixer of a video port with Esmart3 too
drm/rockchip: vop2: Keep the other LUT_PORT_SEL fields on gamma LUT updates
drm/rockchip: vop2: Fix the AFBC offsets of a source that starts inside a superblock
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 186 +++++++++++++++++++++++++--
drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 24 ++++
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 27 +++-
3 files changed, 223 insertions(+), 14 deletions(-)
---
base-commit: 8deaade4a5a4ff635df91e246e42139ab2b33f6d
change-id: 20261110-vop2-fixes-fa45142799c1
Best regards,
--
Arun Iyengar <aiyengar1990@gmail.com>
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH 1/8] drm/rockchip: vop2: Don't use uninitialised dividers on RK3588
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
(?)
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
-1 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
rk3588_calc_cru_cfg() converts all four dividers to their register
encoding with ilog2(), but only sets the ones the interface uses:
dclk_out_div is left uninitialised for HDMI, eDP and DPI, and
if_pixclk_div/if_dclk_div for DP and DPI. The garbage is then written to
RK3588_VP_CLK_CTRL; on an Orange Pi 5 the dclk_out divider of the HDMI
video port reads back as 3 (/8) in every mode.
Start all dividers at 1. dclk_out only feeds DP and MIPI (and HDMI with
DSC, which is not supported), so for the other interfaces keep it at the
dclk_core rate, as the vendor kernel does for HDMI with DSC, instead of
letting it run at the full DCLK rate.
Fixes: 5a028e8f062f ("drm/rockchip: vop2: Add support for rk3588")
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
index c44648e1bc0d..659fd0a3ab63 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -1579,6 +1579,15 @@ static unsigned long rk3588_calc_cru_cfg(struct vop2_video_port *vp, int id,
unsigned long if_pixclk_rate;
int K = 1;
+ /*
+ * Not every interface uses every divider: dclk_out only feeds DP and
+ * MIPI, the interface dividers are unused for DP. Start from 1 so that
+ * no divider is left uninitialised when its value is converted below.
+ */
+ *dclk_out_div = 1;
+ *if_pixclk_div = 1;
+ *if_dclk_div = 1;
+
if (vop2_output_if_is_hdmi(id)) {
/*
* K = 2: dclk_core = if_pixclk_rate > if_dclk_rate
@@ -1648,6 +1657,9 @@ static unsigned long rk3588_calc_cru_cfg(struct vop2_video_port *vp, int id,
}
*dclk_core_div = dclk_rate / dclk_core_rate;
+ /* An unused dclk_out must not run faster than dclk_core */
+ if (!vop2_output_if_is_dp(id) && !vop2_output_if_is_mipi(id))
+ *dclk_out_div = *dclk_core_div;
*if_pixclk_div = ilog2(*if_pixclk_div);
*if_dclk_div = ilog2(*if_dclk_div);
*dclk_core_div = ilog2(*dclk_core_div);
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 1/8] drm/rockchip: vop2: Don't use uninitialised dividers on RK3588
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
rk3588_calc_cru_cfg() converts all four dividers to their register
encoding with ilog2(), but only sets the ones the interface uses:
dclk_out_div is left uninitialised for HDMI, eDP and DPI, and
if_pixclk_div/if_dclk_div for DP and DPI. The garbage is then written to
RK3588_VP_CLK_CTRL; on an Orange Pi 5 the dclk_out divider of the HDMI
video port reads back as 3 (/8) in every mode.
Start all dividers at 1. dclk_out only feeds DP and MIPI (and HDMI with
DSC, which is not supported), so for the other interfaces keep it at the
dclk_core rate, as the vendor kernel does for HDMI with DSC, instead of
letting it run at the full DCLK rate.
Fixes: 5a028e8f062f ("drm/rockchip: vop2: Add support for rk3588")
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
index c44648e1bc0d..659fd0a3ab63 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -1579,6 +1579,15 @@ static unsigned long rk3588_calc_cru_cfg(struct vop2_video_port *vp, int id,
unsigned long if_pixclk_rate;
int K = 1;
+ /*
+ * Not every interface uses every divider: dclk_out only feeds DP and
+ * MIPI, the interface dividers are unused for DP. Start from 1 so that
+ * no divider is left uninitialised when its value is converted below.
+ */
+ *dclk_out_div = 1;
+ *if_pixclk_div = 1;
+ *if_dclk_div = 1;
+
if (vop2_output_if_is_hdmi(id)) {
/*
* K = 2: dclk_core = if_pixclk_rate > if_dclk_rate
@@ -1648,6 +1657,9 @@ static unsigned long rk3588_calc_cru_cfg(struct vop2_video_port *vp, int id,
}
*dclk_core_div = dclk_rate / dclk_core_rate;
+ /* An unused dclk_out must not run faster than dclk_core */
+ if (!vop2_output_if_is_dp(id) && !vop2_output_if_is_mipi(id))
+ *dclk_out_div = *dclk_core_div;
*if_pixclk_div = ilog2(*if_pixclk_div);
*if_dclk_div = ilog2(*if_dclk_div);
*dclk_core_div = ilog2(*dclk_core_div);
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 1/8] drm/rockchip: vop2: Don't use uninitialised dividers on RK3588
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
rk3588_calc_cru_cfg() converts all four dividers to their register
encoding with ilog2(), but only sets the ones the interface uses:
dclk_out_div is left uninitialised for HDMI, eDP and DPI, and
if_pixclk_div/if_dclk_div for DP and DPI. The garbage is then written to
RK3588_VP_CLK_CTRL; on an Orange Pi 5 the dclk_out divider of the HDMI
video port reads back as 3 (/8) in every mode.
Start all dividers at 1. dclk_out only feeds DP and MIPI (and HDMI with
DSC, which is not supported), so for the other interfaces keep it at the
dclk_core rate, as the vendor kernel does for HDMI with DSC, instead of
letting it run at the full DCLK rate.
Fixes: 5a028e8f062f ("drm/rockchip: vop2: Add support for rk3588")
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
index c44648e1bc0d..659fd0a3ab63 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -1579,6 +1579,15 @@ static unsigned long rk3588_calc_cru_cfg(struct vop2_video_port *vp, int id,
unsigned long if_pixclk_rate;
int K = 1;
+ /*
+ * Not every interface uses every divider: dclk_out only feeds DP and
+ * MIPI, the interface dividers are unused for DP. Start from 1 so that
+ * no divider is left uninitialised when its value is converted below.
+ */
+ *dclk_out_div = 1;
+ *if_pixclk_div = 1;
+ *if_dclk_div = 1;
+
if (vop2_output_if_is_hdmi(id)) {
/*
* K = 2: dclk_core = if_pixclk_rate > if_dclk_rate
@@ -1648,6 +1657,9 @@ static unsigned long rk3588_calc_cru_cfg(struct vop2_video_port *vp, int id,
}
*dclk_core_div = dclk_rate / dclk_core_rate;
+ /* An unused dclk_out must not run faster than dclk_core */
+ if (!vop2_output_if_is_dp(id) && !vop2_output_if_is_mipi(id))
+ *dclk_out_div = *dclk_core_div;
*if_pixclk_div = ilog2(*if_pixclk_div);
*if_dclk_div = ilog2(*if_dclk_div);
*dclk_core_div = ilog2(*dclk_core_div);
--
2.55.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 2/8] drm/rockchip: vop2: Fix the HDMI sync polarity on RK3588
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
(?)
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
-1 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
rk3588_set_intf_mux() passes the POLFLAG bits of the mode to
rk3588_get_hdmi_pol(), which decodes them as DRM mode flags: the
VSYNC_POSITIVE bit is taken for DRM_MODE_FLAG_NHSYNC and POLFLAG_DCLK_INV
for DRM_MODE_FLAG_NVSYNC. As a result GRF_VO1_CON0 marks the HSYNC of
every +HSync/+VSync mode (e.g. all 1080p, 2160p CTA modes) as negative
and both syncs of -HSync/-VSync modes (e.g. 720x480, 640x480) as
positive.
The bits in GRF_VO1_CON0 are set for a negative sync; that is how the
vendor kernel programs them (vop2_get_hdmi_pol(), called with the mode
flags, sets them for DRM_MODE_FLAG_NHSYNC/NVSYNC on RK3588). Decode the
POLFLAG bits accordingly.
Fixes: 5a028e8f062f ("drm/rockchip: vop2: Add support for rk3588")
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
index 659fd0a3ab63..7c48b440cb29 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -1687,12 +1687,16 @@ static u32 rk3588_get_mipi_port_mux(int vp_id)
return 0;
}
-static u32 rk3588_get_hdmi_pol(u32 flags)
+/*
+ * The HDMI sync polarity bits in GRF_VO1_CON0 are set for an active low
+ * (negative) sync.
+ */
+static u32 rk3588_get_hdmi_pol(u32 polflags)
{
u32 val;
- val = (flags & DRM_MODE_FLAG_NHSYNC) ? BIT(HSYNC_POSITIVE) : 0;
- val |= (flags & DRM_MODE_FLAG_NVSYNC) ? BIT(VSYNC_POSITIVE) : 0;
+ val = (polflags & BIT(HSYNC_POSITIVE)) ? 0 : BIT(HSYNC_POSITIVE);
+ val |= (polflags & BIT(VSYNC_POSITIVE)) ? 0 : BIT(VSYNC_POSITIVE);
return val;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 2/8] drm/rockchip: vop2: Fix the HDMI sync polarity on RK3588
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
rk3588_set_intf_mux() passes the POLFLAG bits of the mode to
rk3588_get_hdmi_pol(), which decodes them as DRM mode flags: the
VSYNC_POSITIVE bit is taken for DRM_MODE_FLAG_NHSYNC and POLFLAG_DCLK_INV
for DRM_MODE_FLAG_NVSYNC. As a result GRF_VO1_CON0 marks the HSYNC of
every +HSync/+VSync mode (e.g. all 1080p, 2160p CTA modes) as negative
and both syncs of -HSync/-VSync modes (e.g. 720x480, 640x480) as
positive.
The bits in GRF_VO1_CON0 are set for a negative sync; that is how the
vendor kernel programs them (vop2_get_hdmi_pol(), called with the mode
flags, sets them for DRM_MODE_FLAG_NHSYNC/NVSYNC on RK3588). Decode the
POLFLAG bits accordingly.
Fixes: 5a028e8f062f ("drm/rockchip: vop2: Add support for rk3588")
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
index 659fd0a3ab63..7c48b440cb29 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -1687,12 +1687,16 @@ static u32 rk3588_get_mipi_port_mux(int vp_id)
return 0;
}
-static u32 rk3588_get_hdmi_pol(u32 flags)
+/*
+ * The HDMI sync polarity bits in GRF_VO1_CON0 are set for an active low
+ * (negative) sync.
+ */
+static u32 rk3588_get_hdmi_pol(u32 polflags)
{
u32 val;
- val = (flags & DRM_MODE_FLAG_NHSYNC) ? BIT(HSYNC_POSITIVE) : 0;
- val |= (flags & DRM_MODE_FLAG_NVSYNC) ? BIT(VSYNC_POSITIVE) : 0;
+ val = (polflags & BIT(HSYNC_POSITIVE)) ? 0 : BIT(HSYNC_POSITIVE);
+ val |= (polflags & BIT(VSYNC_POSITIVE)) ? 0 : BIT(VSYNC_POSITIVE);
return val;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 2/8] drm/rockchip: vop2: Fix the HDMI sync polarity on RK3588
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
rk3588_set_intf_mux() passes the POLFLAG bits of the mode to
rk3588_get_hdmi_pol(), which decodes them as DRM mode flags: the
VSYNC_POSITIVE bit is taken for DRM_MODE_FLAG_NHSYNC and POLFLAG_DCLK_INV
for DRM_MODE_FLAG_NVSYNC. As a result GRF_VO1_CON0 marks the HSYNC of
every +HSync/+VSync mode (e.g. all 1080p, 2160p CTA modes) as negative
and both syncs of -HSync/-VSync modes (e.g. 720x480, 640x480) as
positive.
The bits in GRF_VO1_CON0 are set for a negative sync; that is how the
vendor kernel programs them (vop2_get_hdmi_pol(), called with the mode
flags, sets them for DRM_MODE_FLAG_NHSYNC/NVSYNC on RK3588). Decode the
POLFLAG bits accordingly.
Fixes: 5a028e8f062f ("drm/rockchip: vop2: Add support for rk3588")
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
index 659fd0a3ab63..7c48b440cb29 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -1687,12 +1687,16 @@ static u32 rk3588_get_mipi_port_mux(int vp_id)
return 0;
}
-static u32 rk3588_get_hdmi_pol(u32 flags)
+/*
+ * The HDMI sync polarity bits in GRF_VO1_CON0 are set for an active low
+ * (negative) sync.
+ */
+static u32 rk3588_get_hdmi_pol(u32 polflags)
{
u32 val;
- val = (flags & DRM_MODE_FLAG_NHSYNC) ? BIT(HSYNC_POSITIVE) : 0;
- val |= (flags & DRM_MODE_FLAG_NVSYNC) ? BIT(VSYNC_POSITIVE) : 0;
+ val = (polflags & BIT(HSYNC_POSITIVE)) ? 0 : BIT(HSYNC_POSITIVE);
+ val |= (polflags & BIT(VSYNC_POSITIVE)) ? 0 : BIT(VSYNC_POSITIVE);
return val;
}
--
2.55.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 3/8] drm/rockchip: vop2: Don't accept AFBC YTR for RGB565 on RK3588
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
(?)
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
-1 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
The Cluster windows of the RK3588 VOP2 advertise every AFBC modifier
of format_modifiers_afbc for RGB565 and BGR565, including the ones
with the lossless colour transform (YTR). Scanning out an RGB565
buffer rendered by the Mali-G610 (panfrost) with
AFBC(16x16 | SPARSE | YTR) gives a black picture and POST_BUF_EMPTY
on the video port, on every Cluster window, while the same buffer
rendered with AFBC(16x16 | SPARSE) displays correctly, as do 32 bpp
formats with YTR.
YTR on RGB565 is also a questionable pairing to start with:
Documentation/gpu/afbc.rst requires the R, G, B component order when
YTR is used, with component 0 in the least significant bits, which
DRM_FORMAT_RGB565 (B in the low bits) doesn't have, and the Arm
Mali-D71 (komeda) only accepts AFBC for BGR565.
Stop advertising and accepting YTR for the two 16 bpp RGB formats on
RK3588, so that userspace picks a modifier the decoder handles. The
BGR565 case could not be tested (Mesa can't import BGR565 dma-bufs),
it's rejected as well because the decoder is configured identically
for both formats, apart from the R/B swap after decoding.
Fixes: 5a028e8f062f ("drm/rockchip: vop2: Add support for rk3588")
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 76d151b8bc52..629ea7f11250 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -404,6 +404,18 @@ static bool rockchip_vop2_mod_supported(struct drm_plane *plane, u32 format,
if (vop2_convert_afbc_format(format) == VOP2_AFBC_FMT_INVALID)
return false;
+ /*
+ * The AFBC decoder of the RK3588 Cluster windows can't decode 16 bpp
+ * RGB that uses the lossless colour transform: the picture stays
+ * black and the video port reports POST_BUF_EMPTY, while the same
+ * buffers without YTR display fine.
+ */
+ if (vop2->version == VOP_VERSION_RK3588 && (modifier & AFBC_FORMAT_MOD_YTR) &&
+ (format == DRM_FORMAT_RGB565 || format == DRM_FORMAT_BGR565)) {
+ drm_dbg_kms(vop2->drm, "No AFBC YTR support for 16 bpp RGB\n");
+ return false;
+ }
+
/* Different windows have different format/modifier support */
for (i = 0; i < plane->modifier_count; i++) {
if (plane->modifiers[i] == modifier)
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 3/8] drm/rockchip: vop2: Don't accept AFBC YTR for RGB565 on RK3588
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
The Cluster windows of the RK3588 VOP2 advertise every AFBC modifier
of format_modifiers_afbc for RGB565 and BGR565, including the ones
with the lossless colour transform (YTR). Scanning out an RGB565
buffer rendered by the Mali-G610 (panfrost) with
AFBC(16x16 | SPARSE | YTR) gives a black picture and POST_BUF_EMPTY
on the video port, on every Cluster window, while the same buffer
rendered with AFBC(16x16 | SPARSE) displays correctly, as do 32 bpp
formats with YTR.
YTR on RGB565 is also a questionable pairing to start with:
Documentation/gpu/afbc.rst requires the R, G, B component order when
YTR is used, with component 0 in the least significant bits, which
DRM_FORMAT_RGB565 (B in the low bits) doesn't have, and the Arm
Mali-D71 (komeda) only accepts AFBC for BGR565.
Stop advertising and accepting YTR for the two 16 bpp RGB formats on
RK3588, so that userspace picks a modifier the decoder handles. The
BGR565 case could not be tested (Mesa can't import BGR565 dma-bufs),
it's rejected as well because the decoder is configured identically
for both formats, apart from the R/B swap after decoding.
Fixes: 5a028e8f062f ("drm/rockchip: vop2: Add support for rk3588")
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 76d151b8bc52..629ea7f11250 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -404,6 +404,18 @@ static bool rockchip_vop2_mod_supported(struct drm_plane *plane, u32 format,
if (vop2_convert_afbc_format(format) == VOP2_AFBC_FMT_INVALID)
return false;
+ /*
+ * The AFBC decoder of the RK3588 Cluster windows can't decode 16 bpp
+ * RGB that uses the lossless colour transform: the picture stays
+ * black and the video port reports POST_BUF_EMPTY, while the same
+ * buffers without YTR display fine.
+ */
+ if (vop2->version == VOP_VERSION_RK3588 && (modifier & AFBC_FORMAT_MOD_YTR) &&
+ (format == DRM_FORMAT_RGB565 || format == DRM_FORMAT_BGR565)) {
+ drm_dbg_kms(vop2->drm, "No AFBC YTR support for 16 bpp RGB\n");
+ return false;
+ }
+
/* Different windows have different format/modifier support */
for (i = 0; i < plane->modifier_count; i++) {
if (plane->modifiers[i] == modifier)
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 3/8] drm/rockchip: vop2: Don't accept AFBC YTR for RGB565 on RK3588
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
The Cluster windows of the RK3588 VOP2 advertise every AFBC modifier
of format_modifiers_afbc for RGB565 and BGR565, including the ones
with the lossless colour transform (YTR). Scanning out an RGB565
buffer rendered by the Mali-G610 (panfrost) with
AFBC(16x16 | SPARSE | YTR) gives a black picture and POST_BUF_EMPTY
on the video port, on every Cluster window, while the same buffer
rendered with AFBC(16x16 | SPARSE) displays correctly, as do 32 bpp
formats with YTR.
YTR on RGB565 is also a questionable pairing to start with:
Documentation/gpu/afbc.rst requires the R, G, B component order when
YTR is used, with component 0 in the least significant bits, which
DRM_FORMAT_RGB565 (B in the low bits) doesn't have, and the Arm
Mali-D71 (komeda) only accepts AFBC for BGR565.
Stop advertising and accepting YTR for the two 16 bpp RGB formats on
RK3588, so that userspace picks a modifier the decoder handles. The
BGR565 case could not be tested (Mesa can't import BGR565 dma-bufs),
it's rejected as well because the decoder is configured identically
for both formats, apart from the R/B swap after decoding.
Fixes: 5a028e8f062f ("drm/rockchip: vop2: Add support for rk3588")
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 76d151b8bc52..629ea7f11250 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -404,6 +404,18 @@ static bool rockchip_vop2_mod_supported(struct drm_plane *plane, u32 format,
if (vop2_convert_afbc_format(format) == VOP2_AFBC_FMT_INVALID)
return false;
+ /*
+ * The AFBC decoder of the RK3588 Cluster windows can't decode 16 bpp
+ * RGB that uses the lossless colour transform: the picture stays
+ * black and the video port reports POST_BUF_EMPTY, while the same
+ * buffers without YTR display fine.
+ */
+ if (vop2->version == VOP_VERSION_RK3588 && (modifier & AFBC_FORMAT_MOD_YTR) &&
+ (format == DRM_FORMAT_RGB565 || format == DRM_FORMAT_BGR565)) {
+ drm_dbg_kms(vop2->drm, "No AFBC YTR support for 16 bpp RGB\n");
+ return false;
+ }
+
/* Different windows have different format/modifier support */
for (i = 0; i < plane->modifier_count; i++) {
if (plane->modifiers[i] == modifier)
--
2.55.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 4/8] drm/rockchip: vop2: Balance window fetches across both AXI ports on RK3588
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
(?)
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
-1 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
The RK3588 VOP2 fetches through two AXI read ports. Every window has a
fixed default port in rk3588_vop_win_data: Cluster0/1 and Esmart0/1 use
AXI0, Cluster2/3 and Esmart2/3 use AXI1. The port is chosen by a per
window register (CLUSTER_CTRL[13] / ESMART_AXI_CTRL[1]), though, and it
is programmed on every plane update.
One port can't sustain two full screen 4K@120Hz layers. With the
console on Cluster0 (the primary plane of video port 0) and a video on
Esmart0 (its overlay plane), both on AXI0, HDMI 3840x2160@120 reports
POST_BUF_EMPTY on about half of all lines (~120000 per second) and the
picture is scrambled. The VOP read rate measured with the DDR PMU drops
from the expected 6.3 GB/s to 3.2 GB/s: the fetch collapses instead of
merely saturating. Measured at 4K@120Hz, XR24 primary plane:
Cluster0 + Esmart0 (AXI0 + AXI0) ~120k POST_BUF_EMPTY/s
Cluster2 + Esmart2 (AXI1 + AXI1) ~100k/s
Esmart0 + Esmart1 (AXI0 + AXI0) ~90k/s (XR24 both)
Cluster0 + Esmart2 (AXI0 + AXI1) 0, 9.2 GB/s read
Cluster0 + Cluster2 (AXI0 + AXI1) 0, 8.6 GB/s read
Moving Esmart0 to AXI1 at runtime (with no other change) makes the
first case clean. Raising ACLK_VOP from 750 to 850 MHz, or changing
the NoC QoS priority or mode of the VOP ports, makes no difference,
and neither does the buffer placement (both planes scanning out the
same buffer fail the same way). So the limit is per port, and the
fixed assignment puts both layers of the most common configuration on
the same port while the other one sits idle. Lower modes are affected
too: the same port also carries Cluster1 for a second display, and
under DDR contention from other masters a 4K@60Hz port with the
console, an overlay and a second display underflows for short bursts.
Choose the port of a window when it starts fetching instead: estimate
the fetch rate of each active window (source size x bpp x refresh),
and put the new window on the port that leaves the busier port least
loaded, preferring the default one on a tie. A window must not share
an AXI read ID with another active window on the same port; the IDs in
the table are unique per default port, and mirrored between the two
ports (Cluster0/Cluster2, Esmart0/Esmart2, ...), so a window can always
use the port its mirror isn't on. A window keeps its port while it
stays enabled, so an active layer is never moved.
This is only enabled for RK3588, where it was measured.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 99 +++++++++++++++++++++++++++-
drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 14 ++++
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 3 +-
3 files changed, 114 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 629ea7f11250..9aeb90c6aa4b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -1231,6 +1231,98 @@ static int vop2_plane_atomic_check(struct drm_plane *plane,
return 0;
}
+static bool vop2_win_axi_ids_clash(const struct vop2_win *a, const struct vop2_win *b)
+{
+ u8 ida[] = { a->data->axi_yrgb_r_id, a->data->axi_uv_r_id };
+ u8 idb[] = { b->data->axi_yrgb_r_id, b->data->axi_uv_r_id };
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(ida); i++)
+ for (j = 0; j < ARRAY_SIZE(idb); j++)
+ if (ida[i] == idb[j])
+ return true;
+
+ return false;
+}
+
+/*
+ * The VOP2 on RK3588 has two AXI read ports. Each window has a default port
+ * (&vop2_win_data.axi_bus_id), but the port is selected by a per window
+ * register, so any window can fetch through either one as long as its read
+ * IDs are unique on that port.
+ *
+ * A single port can't sustain much more than one full screen 4K@120Hz
+ * layer: two such layers on the same port (e.g. Cluster0 and Esmart0, the
+ * default primary and overlay planes of video port 0) make the window
+ * line buffers run dry, the fetch rate collapses and the video port
+ * reports POST_BUF_EMPTY on most lines, which scrambles the picture. The
+ * same layers fetched through different ports are fine. Raising ACLK or
+ * the NoC priority of the VOP doesn't help, the limit is per port.
+ *
+ * So pick the port of a window when it starts fetching: use the port that
+ * leaves the busier of the two ports least loaded, preferring the default
+ * one, and never a port on which another active window already uses one of
+ * its read IDs. A window keeps its port while it stays enabled.
+ */
+static u8 vop2_win_get_axi_bus(struct vop2_win *win, u64 load)
+{
+ struct vop2 *vop2 = win->vop2;
+ u64 bus_load[VOP2_SYS_AXI_BUS_NUM] = { };
+ bool clash[VOP2_SYS_AXI_BUS_NUM] = { };
+ u8 def = win->data->axi_bus_id;
+ unsigned long flags;
+ u8 bus = def;
+ int i;
+
+ if (!(vop2->data->feature & VOP2_FEATURE_AXI_BALANCE))
+ return def;
+
+ spin_lock_irqsave(&vop2->axi_lock, flags);
+
+ if (win->axi_active) {
+ win->axi_load = load;
+ bus = win->axi_bus;
+ goto out;
+ }
+
+ for (i = 0; i < vop2->registered_num_wins; i++) {
+ struct vop2_win *w = &vop2->win[i];
+
+ if (w == win || !w->axi_active)
+ continue;
+
+ bus_load[w->axi_bus] += w->axi_load;
+ if (vop2_win_axi_ids_clash(win, w))
+ clash[w->axi_bus] = true;
+ }
+
+ if (!clash[def ^ 1] &&
+ (clash[def] || max(bus_load[def ^ 1] + load, bus_load[def]) <
+ max(bus_load[def] + load, bus_load[def ^ 1])))
+ bus = def ^ 1;
+
+ WARN_ON_ONCE(clash[bus]);
+
+ win->axi_bus = bus;
+ win->axi_load = load;
+ win->axi_active = true;
+out:
+ spin_unlock_irqrestore(&vop2->axi_lock, flags);
+
+ return bus;
+}
+
+static void vop2_win_put_axi_bus(struct vop2_win *win)
+{
+ struct vop2 *vop2 = win->vop2;
+ unsigned long flags;
+
+ spin_lock_irqsave(&vop2->axi_lock, flags);
+ win->axi_active = false;
+ win->axi_load = 0;
+ spin_unlock_irqrestore(&vop2->axi_lock, flags);
+}
+
static void vop2_plane_atomic_disable(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
@@ -1241,6 +1333,8 @@ static void vop2_plane_atomic_disable(struct drm_plane *plane,
drm_dbg(vop2->drm, "%s disable\n", win->data->name);
+ vop2_win_put_axi_bus(win);
+
if (state)
old_pstate = drm_atomic_get_old_plane_state(state, plane);
@@ -1416,7 +1510,9 @@ static void vop2_plane_atomic_update(struct drm_plane *plane,
afbc_en ? "AFBC" : "", &yrgb_mst);
if (vop2->version > VOP_VERSION_RK3568) {
- vop2_win_write(win, VOP2_WIN_AXI_BUS_ID, win->data->axi_bus_id);
+ u64 load = (u64)src_w * src_h * bpp / 8 * drm_mode_vrefresh(adjusted_mode);
+
+ vop2_win_write(win, VOP2_WIN_AXI_BUS_ID, vop2_win_get_axi_bus(win, load));
vop2_win_write(win, VOP2_WIN_AXI_YRGB_R_ID, win->data->axi_yrgb_r_id);
vop2_win_write(win, VOP2_WIN_AXI_UV_R_ID, win->data->axi_uv_r_id);
}
@@ -3005,6 +3101,7 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
mutex_init(&vop2->vop2_lock);
mutex_init(&vop2->ovl_lock);
+ spin_lock_init(&vop2->axi_lock);
ret = devm_request_irq(dev, vop2->irq, vop2_isr, IRQF_SHARED, dev_name(dev), vop2);
if (ret)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
index 1c2d7d97a6c2..f1e35967e67f 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
@@ -29,6 +29,7 @@
#define VOP2_FEATURE_HAS_VO1_GRF BIT(2)
#define VOP2_FEATURE_HAS_VOP_GRF BIT(3)
#define VOP2_FEATURE_HAS_SYS_PMU BIT(4)
+#define VOP2_FEATURE_AXI_BALANCE BIT(5)
#define WIN_FEATURE_AFBDC BIT(0)
#define WIN_FEATURE_CLUSTER BIT(1)
@@ -219,6 +220,16 @@ struct vop2_win {
u32 offset;
enum drm_plane_type type;
+
+ /**
+ * @axi_bus: AXI read port the window currently fetches through,
+ * protected by &vop2.axi_lock.
+ */
+ u8 axi_bus;
+ /** @axi_active: the window is fetching, protected by &vop2.axi_lock */
+ bool axi_active;
+ /** @axi_load: estimated fetch rate in bytes/s, protected by &vop2.axi_lock */
+ u64 axi_load;
};
struct vop2_video_port_data {
@@ -356,6 +367,9 @@ struct vop2 {
*/
struct mutex ovl_lock;
+ /* protects the AXI port assignment of all windows (&vop2_win.axi_bus) */
+ spinlock_t axi_lock;
+
/* must be put at the end of the struct */
struct vop2_win win[];
};
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
index 7c48b440cb29..83203d18865b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -2577,7 +2577,8 @@ 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_AXI_BALANCE,
.nr_vps = 4,
.max_input = { 4096, 4320 },
.max_output = { 4096, 4320 },
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 4/8] drm/rockchip: vop2: Balance window fetches across both AXI ports on RK3588
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
The RK3588 VOP2 fetches through two AXI read ports. Every window has a
fixed default port in rk3588_vop_win_data: Cluster0/1 and Esmart0/1 use
AXI0, Cluster2/3 and Esmart2/3 use AXI1. The port is chosen by a per
window register (CLUSTER_CTRL[13] / ESMART_AXI_CTRL[1]), though, and it
is programmed on every plane update.
One port can't sustain two full screen 4K@120Hz layers. With the
console on Cluster0 (the primary plane of video port 0) and a video on
Esmart0 (its overlay plane), both on AXI0, HDMI 3840x2160@120 reports
POST_BUF_EMPTY on about half of all lines (~120000 per second) and the
picture is scrambled. The VOP read rate measured with the DDR PMU drops
from the expected 6.3 GB/s to 3.2 GB/s: the fetch collapses instead of
merely saturating. Measured at 4K@120Hz, XR24 primary plane:
Cluster0 + Esmart0 (AXI0 + AXI0) ~120k POST_BUF_EMPTY/s
Cluster2 + Esmart2 (AXI1 + AXI1) ~100k/s
Esmart0 + Esmart1 (AXI0 + AXI0) ~90k/s (XR24 both)
Cluster0 + Esmart2 (AXI0 + AXI1) 0, 9.2 GB/s read
Cluster0 + Cluster2 (AXI0 + AXI1) 0, 8.6 GB/s read
Moving Esmart0 to AXI1 at runtime (with no other change) makes the
first case clean. Raising ACLK_VOP from 750 to 850 MHz, or changing
the NoC QoS priority or mode of the VOP ports, makes no difference,
and neither does the buffer placement (both planes scanning out the
same buffer fail the same way). So the limit is per port, and the
fixed assignment puts both layers of the most common configuration on
the same port while the other one sits idle. Lower modes are affected
too: the same port also carries Cluster1 for a second display, and
under DDR contention from other masters a 4K@60Hz port with the
console, an overlay and a second display underflows for short bursts.
Choose the port of a window when it starts fetching instead: estimate
the fetch rate of each active window (source size x bpp x refresh),
and put the new window on the port that leaves the busier port least
loaded, preferring the default one on a tie. A window must not share
an AXI read ID with another active window on the same port; the IDs in
the table are unique per default port, and mirrored between the two
ports (Cluster0/Cluster2, Esmart0/Esmart2, ...), so a window can always
use the port its mirror isn't on. A window keeps its port while it
stays enabled, so an active layer is never moved.
This is only enabled for RK3588, where it was measured.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 99 +++++++++++++++++++++++++++-
drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 14 ++++
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 3 +-
3 files changed, 114 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 629ea7f11250..9aeb90c6aa4b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -1231,6 +1231,98 @@ static int vop2_plane_atomic_check(struct drm_plane *plane,
return 0;
}
+static bool vop2_win_axi_ids_clash(const struct vop2_win *a, const struct vop2_win *b)
+{
+ u8 ida[] = { a->data->axi_yrgb_r_id, a->data->axi_uv_r_id };
+ u8 idb[] = { b->data->axi_yrgb_r_id, b->data->axi_uv_r_id };
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(ida); i++)
+ for (j = 0; j < ARRAY_SIZE(idb); j++)
+ if (ida[i] == idb[j])
+ return true;
+
+ return false;
+}
+
+/*
+ * The VOP2 on RK3588 has two AXI read ports. Each window has a default port
+ * (&vop2_win_data.axi_bus_id), but the port is selected by a per window
+ * register, so any window can fetch through either one as long as its read
+ * IDs are unique on that port.
+ *
+ * A single port can't sustain much more than one full screen 4K@120Hz
+ * layer: two such layers on the same port (e.g. Cluster0 and Esmart0, the
+ * default primary and overlay planes of video port 0) make the window
+ * line buffers run dry, the fetch rate collapses and the video port
+ * reports POST_BUF_EMPTY on most lines, which scrambles the picture. The
+ * same layers fetched through different ports are fine. Raising ACLK or
+ * the NoC priority of the VOP doesn't help, the limit is per port.
+ *
+ * So pick the port of a window when it starts fetching: use the port that
+ * leaves the busier of the two ports least loaded, preferring the default
+ * one, and never a port on which another active window already uses one of
+ * its read IDs. A window keeps its port while it stays enabled.
+ */
+static u8 vop2_win_get_axi_bus(struct vop2_win *win, u64 load)
+{
+ struct vop2 *vop2 = win->vop2;
+ u64 bus_load[VOP2_SYS_AXI_BUS_NUM] = { };
+ bool clash[VOP2_SYS_AXI_BUS_NUM] = { };
+ u8 def = win->data->axi_bus_id;
+ unsigned long flags;
+ u8 bus = def;
+ int i;
+
+ if (!(vop2->data->feature & VOP2_FEATURE_AXI_BALANCE))
+ return def;
+
+ spin_lock_irqsave(&vop2->axi_lock, flags);
+
+ if (win->axi_active) {
+ win->axi_load = load;
+ bus = win->axi_bus;
+ goto out;
+ }
+
+ for (i = 0; i < vop2->registered_num_wins; i++) {
+ struct vop2_win *w = &vop2->win[i];
+
+ if (w == win || !w->axi_active)
+ continue;
+
+ bus_load[w->axi_bus] += w->axi_load;
+ if (vop2_win_axi_ids_clash(win, w))
+ clash[w->axi_bus] = true;
+ }
+
+ if (!clash[def ^ 1] &&
+ (clash[def] || max(bus_load[def ^ 1] + load, bus_load[def]) <
+ max(bus_load[def] + load, bus_load[def ^ 1])))
+ bus = def ^ 1;
+
+ WARN_ON_ONCE(clash[bus]);
+
+ win->axi_bus = bus;
+ win->axi_load = load;
+ win->axi_active = true;
+out:
+ spin_unlock_irqrestore(&vop2->axi_lock, flags);
+
+ return bus;
+}
+
+static void vop2_win_put_axi_bus(struct vop2_win *win)
+{
+ struct vop2 *vop2 = win->vop2;
+ unsigned long flags;
+
+ spin_lock_irqsave(&vop2->axi_lock, flags);
+ win->axi_active = false;
+ win->axi_load = 0;
+ spin_unlock_irqrestore(&vop2->axi_lock, flags);
+}
+
static void vop2_plane_atomic_disable(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
@@ -1241,6 +1333,8 @@ static void vop2_plane_atomic_disable(struct drm_plane *plane,
drm_dbg(vop2->drm, "%s disable\n", win->data->name);
+ vop2_win_put_axi_bus(win);
+
if (state)
old_pstate = drm_atomic_get_old_plane_state(state, plane);
@@ -1416,7 +1510,9 @@ static void vop2_plane_atomic_update(struct drm_plane *plane,
afbc_en ? "AFBC" : "", &yrgb_mst);
if (vop2->version > VOP_VERSION_RK3568) {
- vop2_win_write(win, VOP2_WIN_AXI_BUS_ID, win->data->axi_bus_id);
+ u64 load = (u64)src_w * src_h * bpp / 8 * drm_mode_vrefresh(adjusted_mode);
+
+ vop2_win_write(win, VOP2_WIN_AXI_BUS_ID, vop2_win_get_axi_bus(win, load));
vop2_win_write(win, VOP2_WIN_AXI_YRGB_R_ID, win->data->axi_yrgb_r_id);
vop2_win_write(win, VOP2_WIN_AXI_UV_R_ID, win->data->axi_uv_r_id);
}
@@ -3005,6 +3101,7 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
mutex_init(&vop2->vop2_lock);
mutex_init(&vop2->ovl_lock);
+ spin_lock_init(&vop2->axi_lock);
ret = devm_request_irq(dev, vop2->irq, vop2_isr, IRQF_SHARED, dev_name(dev), vop2);
if (ret)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
index 1c2d7d97a6c2..f1e35967e67f 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
@@ -29,6 +29,7 @@
#define VOP2_FEATURE_HAS_VO1_GRF BIT(2)
#define VOP2_FEATURE_HAS_VOP_GRF BIT(3)
#define VOP2_FEATURE_HAS_SYS_PMU BIT(4)
+#define VOP2_FEATURE_AXI_BALANCE BIT(5)
#define WIN_FEATURE_AFBDC BIT(0)
#define WIN_FEATURE_CLUSTER BIT(1)
@@ -219,6 +220,16 @@ struct vop2_win {
u32 offset;
enum drm_plane_type type;
+
+ /**
+ * @axi_bus: AXI read port the window currently fetches through,
+ * protected by &vop2.axi_lock.
+ */
+ u8 axi_bus;
+ /** @axi_active: the window is fetching, protected by &vop2.axi_lock */
+ bool axi_active;
+ /** @axi_load: estimated fetch rate in bytes/s, protected by &vop2.axi_lock */
+ u64 axi_load;
};
struct vop2_video_port_data {
@@ -356,6 +367,9 @@ struct vop2 {
*/
struct mutex ovl_lock;
+ /* protects the AXI port assignment of all windows (&vop2_win.axi_bus) */
+ spinlock_t axi_lock;
+
/* must be put at the end of the struct */
struct vop2_win win[];
};
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
index 7c48b440cb29..83203d18865b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -2577,7 +2577,8 @@ 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_AXI_BALANCE,
.nr_vps = 4,
.max_input = { 4096, 4320 },
.max_output = { 4096, 4320 },
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 4/8] drm/rockchip: vop2: Balance window fetches across both AXI ports on RK3588
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
The RK3588 VOP2 fetches through two AXI read ports. Every window has a
fixed default port in rk3588_vop_win_data: Cluster0/1 and Esmart0/1 use
AXI0, Cluster2/3 and Esmart2/3 use AXI1. The port is chosen by a per
window register (CLUSTER_CTRL[13] / ESMART_AXI_CTRL[1]), though, and it
is programmed on every plane update.
One port can't sustain two full screen 4K@120Hz layers. With the
console on Cluster0 (the primary plane of video port 0) and a video on
Esmart0 (its overlay plane), both on AXI0, HDMI 3840x2160@120 reports
POST_BUF_EMPTY on about half of all lines (~120000 per second) and the
picture is scrambled. The VOP read rate measured with the DDR PMU drops
from the expected 6.3 GB/s to 3.2 GB/s: the fetch collapses instead of
merely saturating. Measured at 4K@120Hz, XR24 primary plane:
Cluster0 + Esmart0 (AXI0 + AXI0) ~120k POST_BUF_EMPTY/s
Cluster2 + Esmart2 (AXI1 + AXI1) ~100k/s
Esmart0 + Esmart1 (AXI0 + AXI0) ~90k/s (XR24 both)
Cluster0 + Esmart2 (AXI0 + AXI1) 0, 9.2 GB/s read
Cluster0 + Cluster2 (AXI0 + AXI1) 0, 8.6 GB/s read
Moving Esmart0 to AXI1 at runtime (with no other change) makes the
first case clean. Raising ACLK_VOP from 750 to 850 MHz, or changing
the NoC QoS priority or mode of the VOP ports, makes no difference,
and neither does the buffer placement (both planes scanning out the
same buffer fail the same way). So the limit is per port, and the
fixed assignment puts both layers of the most common configuration on
the same port while the other one sits idle. Lower modes are affected
too: the same port also carries Cluster1 for a second display, and
under DDR contention from other masters a 4K@60Hz port with the
console, an overlay and a second display underflows for short bursts.
Choose the port of a window when it starts fetching instead: estimate
the fetch rate of each active window (source size x bpp x refresh),
and put the new window on the port that leaves the busier port least
loaded, preferring the default one on a tie. A window must not share
an AXI read ID with another active window on the same port; the IDs in
the table are unique per default port, and mirrored between the two
ports (Cluster0/Cluster2, Esmart0/Esmart2, ...), so a window can always
use the port its mirror isn't on. A window keeps its port while it
stays enabled, so an active layer is never moved.
This is only enabled for RK3588, where it was measured.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 99 +++++++++++++++++++++++++++-
drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 14 ++++
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 3 +-
3 files changed, 114 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 629ea7f11250..9aeb90c6aa4b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -1231,6 +1231,98 @@ static int vop2_plane_atomic_check(struct drm_plane *plane,
return 0;
}
+static bool vop2_win_axi_ids_clash(const struct vop2_win *a, const struct vop2_win *b)
+{
+ u8 ida[] = { a->data->axi_yrgb_r_id, a->data->axi_uv_r_id };
+ u8 idb[] = { b->data->axi_yrgb_r_id, b->data->axi_uv_r_id };
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(ida); i++)
+ for (j = 0; j < ARRAY_SIZE(idb); j++)
+ if (ida[i] == idb[j])
+ return true;
+
+ return false;
+}
+
+/*
+ * The VOP2 on RK3588 has two AXI read ports. Each window has a default port
+ * (&vop2_win_data.axi_bus_id), but the port is selected by a per window
+ * register, so any window can fetch through either one as long as its read
+ * IDs are unique on that port.
+ *
+ * A single port can't sustain much more than one full screen 4K@120Hz
+ * layer: two such layers on the same port (e.g. Cluster0 and Esmart0, the
+ * default primary and overlay planes of video port 0) make the window
+ * line buffers run dry, the fetch rate collapses and the video port
+ * reports POST_BUF_EMPTY on most lines, which scrambles the picture. The
+ * same layers fetched through different ports are fine. Raising ACLK or
+ * the NoC priority of the VOP doesn't help, the limit is per port.
+ *
+ * So pick the port of a window when it starts fetching: use the port that
+ * leaves the busier of the two ports least loaded, preferring the default
+ * one, and never a port on which another active window already uses one of
+ * its read IDs. A window keeps its port while it stays enabled.
+ */
+static u8 vop2_win_get_axi_bus(struct vop2_win *win, u64 load)
+{
+ struct vop2 *vop2 = win->vop2;
+ u64 bus_load[VOP2_SYS_AXI_BUS_NUM] = { };
+ bool clash[VOP2_SYS_AXI_BUS_NUM] = { };
+ u8 def = win->data->axi_bus_id;
+ unsigned long flags;
+ u8 bus = def;
+ int i;
+
+ if (!(vop2->data->feature & VOP2_FEATURE_AXI_BALANCE))
+ return def;
+
+ spin_lock_irqsave(&vop2->axi_lock, flags);
+
+ if (win->axi_active) {
+ win->axi_load = load;
+ bus = win->axi_bus;
+ goto out;
+ }
+
+ for (i = 0; i < vop2->registered_num_wins; i++) {
+ struct vop2_win *w = &vop2->win[i];
+
+ if (w == win || !w->axi_active)
+ continue;
+
+ bus_load[w->axi_bus] += w->axi_load;
+ if (vop2_win_axi_ids_clash(win, w))
+ clash[w->axi_bus] = true;
+ }
+
+ if (!clash[def ^ 1] &&
+ (clash[def] || max(bus_load[def ^ 1] + load, bus_load[def]) <
+ max(bus_load[def] + load, bus_load[def ^ 1])))
+ bus = def ^ 1;
+
+ WARN_ON_ONCE(clash[bus]);
+
+ win->axi_bus = bus;
+ win->axi_load = load;
+ win->axi_active = true;
+out:
+ spin_unlock_irqrestore(&vop2->axi_lock, flags);
+
+ return bus;
+}
+
+static void vop2_win_put_axi_bus(struct vop2_win *win)
+{
+ struct vop2 *vop2 = win->vop2;
+ unsigned long flags;
+
+ spin_lock_irqsave(&vop2->axi_lock, flags);
+ win->axi_active = false;
+ win->axi_load = 0;
+ spin_unlock_irqrestore(&vop2->axi_lock, flags);
+}
+
static void vop2_plane_atomic_disable(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
@@ -1241,6 +1333,8 @@ static void vop2_plane_atomic_disable(struct drm_plane *plane,
drm_dbg(vop2->drm, "%s disable\n", win->data->name);
+ vop2_win_put_axi_bus(win);
+
if (state)
old_pstate = drm_atomic_get_old_plane_state(state, plane);
@@ -1416,7 +1510,9 @@ static void vop2_plane_atomic_update(struct drm_plane *plane,
afbc_en ? "AFBC" : "", &yrgb_mst);
if (vop2->version > VOP_VERSION_RK3568) {
- vop2_win_write(win, VOP2_WIN_AXI_BUS_ID, win->data->axi_bus_id);
+ u64 load = (u64)src_w * src_h * bpp / 8 * drm_mode_vrefresh(adjusted_mode);
+
+ vop2_win_write(win, VOP2_WIN_AXI_BUS_ID, vop2_win_get_axi_bus(win, load));
vop2_win_write(win, VOP2_WIN_AXI_YRGB_R_ID, win->data->axi_yrgb_r_id);
vop2_win_write(win, VOP2_WIN_AXI_UV_R_ID, win->data->axi_uv_r_id);
}
@@ -3005,6 +3101,7 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
mutex_init(&vop2->vop2_lock);
mutex_init(&vop2->ovl_lock);
+ spin_lock_init(&vop2->axi_lock);
ret = devm_request_irq(dev, vop2->irq, vop2_isr, IRQF_SHARED, dev_name(dev), vop2);
if (ret)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
index 1c2d7d97a6c2..f1e35967e67f 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
@@ -29,6 +29,7 @@
#define VOP2_FEATURE_HAS_VO1_GRF BIT(2)
#define VOP2_FEATURE_HAS_VOP_GRF BIT(3)
#define VOP2_FEATURE_HAS_SYS_PMU BIT(4)
+#define VOP2_FEATURE_AXI_BALANCE BIT(5)
#define WIN_FEATURE_AFBDC BIT(0)
#define WIN_FEATURE_CLUSTER BIT(1)
@@ -219,6 +220,16 @@ struct vop2_win {
u32 offset;
enum drm_plane_type type;
+
+ /**
+ * @axi_bus: AXI read port the window currently fetches through,
+ * protected by &vop2.axi_lock.
+ */
+ u8 axi_bus;
+ /** @axi_active: the window is fetching, protected by &vop2.axi_lock */
+ bool axi_active;
+ /** @axi_load: estimated fetch rate in bytes/s, protected by &vop2.axi_lock */
+ u64 axi_load;
};
struct vop2_video_port_data {
@@ -356,6 +367,9 @@ struct vop2 {
*/
struct mutex ovl_lock;
+ /* protects the AXI port assignment of all windows (&vop2_win.axi_bus) */
+ spinlock_t axi_lock;
+
/* must be put at the end of the struct */
struct vop2_win win[];
};
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
index 7c48b440cb29..83203d18865b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -2577,7 +2577,8 @@ 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_AXI_BALANCE,
.nr_vps = 4,
.max_input = { 4096, 4320 },
.max_output = { 4096, 4320 },
--
2.55.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 5/8] drm/rockchip: vop2: Report POST_BUF_EMPTY at most once per frame
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
(?)
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
-1 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
POST_BUF_EMPTY is raised for every line on which the post processing
stage of a video port finds no data. The interrupt handler only
rate-limits the error message and leaves the interrupt enabled, so an
underflowing video port keeps the CPU in the interrupt handler 50000 to
300000 times per second, for as long as the configuration that causes
the underflow stays in place (e.g. 120000 interrupts per second for
3840x2160@120 with two full screen layers on one AXI port).
Mask POST_BUF_EMPTY when it fires and unmask it at the next frame start.
The frame start (FS) interrupt is enabled for that frame even when
vblank reporting is off, and disabled again once POST_BUF_EMPTY is
re-armed. A new per video port lock serialises the interrupt enable
bookkeeping between the interrupt handler and the vblank callbacks.
The error is still reported, now at most once per frame and video port.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 63 ++++++++++++++++++++++++++--
drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 10 +++++
2 files changed, 70 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 9aeb90c6aa4b..3323e50224c5 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -841,6 +841,48 @@ static void vop2_crtc_disable_irq(struct vop2_video_port *vp, u32 irq)
vop2_writel(vop2, RK3568_VP_INT_EN(vp->id), irq << 16);
}
+/*
+ * POST_BUF_EMPTY is raised for every line the post processing stage finds
+ * no data for, so an underflowing video port can raise it 100000 times per
+ * second. Report it at most once per frame: mask it when it fires and
+ * unmask it at the next frame start, using the FS interrupt even when
+ * vblank reporting is off.
+ */
+static void vop2_crtc_enable_post_buf_empty_irq(struct vop2_video_port *vp)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&vp->irq_lock, flags);
+ vp->post_buf_empty_masked = false;
+ vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
+ spin_unlock_irqrestore(&vp->irq_lock, flags);
+}
+
+static void vop2_crtc_handle_post_buf_empty(struct vop2_video_port *vp)
+{
+ struct vop2 *vop2 = vp->vop2;
+
+ spin_lock(&vp->irq_lock);
+ vop2_crtc_disable_irq(vp, VP_INT_POST_BUF_EMPTY);
+ vp->post_buf_empty_masked = true;
+ /* don't clear a pending FS, it may carry a vblank event */
+ vop2_writel(vop2, RK3568_VP_INT_EN(vp->id),
+ VP_INT_FS_FIELD << 16 | VP_INT_FS_FIELD);
+ spin_unlock(&vp->irq_lock);
+}
+
+static void vop2_crtc_rearm_post_buf_empty(struct vop2_video_port *vp)
+{
+ spin_lock(&vp->irq_lock);
+ if (vp->post_buf_empty_masked) {
+ vp->post_buf_empty_masked = false;
+ vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
+ if (!vp->vblank_enabled)
+ vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
+ }
+ spin_unlock(&vp->irq_lock);
+}
+
static int vop2_core_clks_prepare_enable(struct vop2 *vop2)
{
int ret;
@@ -1668,8 +1710,12 @@ static const struct drm_plane_funcs vop2_plane_funcs = {
static int vop2_crtc_enable_vblank(struct drm_crtc *crtc)
{
struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ unsigned long flags;
+ spin_lock_irqsave(&vp->irq_lock, flags);
+ vp->vblank_enabled = true;
vop2_crtc_enable_irq(vp, VP_INT_FS_FIELD);
+ spin_unlock_irqrestore(&vp->irq_lock, flags);
return 0;
}
@@ -1677,8 +1723,13 @@ static int vop2_crtc_enable_vblank(struct drm_crtc *crtc)
static void vop2_crtc_disable_vblank(struct drm_crtc *crtc)
{
struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ unsigned long flags;
- vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
+ spin_lock_irqsave(&vp->irq_lock, flags);
+ vp->vblank_enabled = false;
+ if (!vp->post_buf_empty_masked)
+ vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
+ spin_unlock_irqrestore(&vp->irq_lock, flags);
}
static enum drm_mode_status vop2_crtc_mode_valid(struct drm_crtc *crtc,
@@ -1977,7 +2028,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
vcstate->yuv_overlay = is_yuv_output(vcstate->bus_format);
- vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
+ vop2_crtc_enable_post_buf_empty_irq(vp);
polflags = 0;
if (vcstate->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
@@ -2535,6 +2586,7 @@ static irqreturn_t rk3576_vp_isr(int irq, void *data)
}
if (irqs & VP_INT_FS_FIELD) {
+ vop2_crtc_rearm_post_buf_empty(vp);
drm_crtc_handle_vblank(crtc);
spin_lock(&crtc->dev->event_lock);
if (vp->event) {
@@ -2552,6 +2604,7 @@ static irqreturn_t rk3576_vp_isr(int irq, void *data)
}
if (irqs & VP_INT_POST_BUF_EMPTY) {
+ vop2_crtc_handle_post_buf_empty(vp);
drm_err_ratelimited(vop2->drm, "POST_BUF_EMPTY irq err at vp%d\n", vp->id);
ret = IRQ_HANDLED;
}
@@ -2591,6 +2644,7 @@ static irqreturn_t vop2_isr(int irq, void *data)
}
if (irqs & VP_INT_FS_FIELD) {
+ vop2_crtc_rearm_post_buf_empty(vp);
drm_crtc_handle_vblank(crtc);
spin_lock(&crtc->dev->event_lock);
if (vp->event) {
@@ -2608,6 +2662,7 @@ static irqreturn_t vop2_isr(int irq, void *data)
}
if (irqs & VP_INT_POST_BUF_EMPTY) {
+ vop2_crtc_handle_post_buf_empty(vp);
drm_err_ratelimited(vop2->drm,
"POST_BUF_EMPTY irq err at vp%d\n",
vp->id);
@@ -2984,7 +3039,7 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
struct vop2 *vop2;
struct resource *res;
size_t alloc_size;
- int ret;
+ int ret, i;
vop2_data = of_device_get_match_data(dev);
if (!vop2_data)
@@ -3102,6 +3157,8 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
mutex_init(&vop2->vop2_lock);
mutex_init(&vop2->ovl_lock);
spin_lock_init(&vop2->axi_lock);
+ for (i = 0; i < ARRAY_SIZE(vop2->vps); i++)
+ spin_lock_init(&vop2->vps[i].irq_lock);
ret = devm_request_irq(dev, vop2->irq, vop2_isr, IRQF_SHARED, dev_name(dev), vop2);
if (ret)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
index f1e35967e67f..7de32bef8d7b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
@@ -272,6 +272,16 @@ struct vop2_video_port {
struct drm_pending_vblank_event *event;
unsigned int nlayers;
+
+ /** @irq_lock: protects @vblank_enabled and @post_buf_empty_masked */
+ spinlock_t irq_lock;
+ /** @vblank_enabled: the FS interrupt is enabled for vblank reporting */
+ bool vblank_enabled;
+ /**
+ * @post_buf_empty_masked: POST_BUF_EMPTY is masked until the next
+ * frame start
+ */
+ bool post_buf_empty_masked;
};
/**
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 5/8] drm/rockchip: vop2: Report POST_BUF_EMPTY at most once per frame
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
POST_BUF_EMPTY is raised for every line on which the post processing
stage of a video port finds no data. The interrupt handler only
rate-limits the error message and leaves the interrupt enabled, so an
underflowing video port keeps the CPU in the interrupt handler 50000 to
300000 times per second, for as long as the configuration that causes
the underflow stays in place (e.g. 120000 interrupts per second for
3840x2160@120 with two full screen layers on one AXI port).
Mask POST_BUF_EMPTY when it fires and unmask it at the next frame start.
The frame start (FS) interrupt is enabled for that frame even when
vblank reporting is off, and disabled again once POST_BUF_EMPTY is
re-armed. A new per video port lock serialises the interrupt enable
bookkeeping between the interrupt handler and the vblank callbacks.
The error is still reported, now at most once per frame and video port.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 63 ++++++++++++++++++++++++++--
drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 10 +++++
2 files changed, 70 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 9aeb90c6aa4b..3323e50224c5 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -841,6 +841,48 @@ static void vop2_crtc_disable_irq(struct vop2_video_port *vp, u32 irq)
vop2_writel(vop2, RK3568_VP_INT_EN(vp->id), irq << 16);
}
+/*
+ * POST_BUF_EMPTY is raised for every line the post processing stage finds
+ * no data for, so an underflowing video port can raise it 100000 times per
+ * second. Report it at most once per frame: mask it when it fires and
+ * unmask it at the next frame start, using the FS interrupt even when
+ * vblank reporting is off.
+ */
+static void vop2_crtc_enable_post_buf_empty_irq(struct vop2_video_port *vp)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&vp->irq_lock, flags);
+ vp->post_buf_empty_masked = false;
+ vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
+ spin_unlock_irqrestore(&vp->irq_lock, flags);
+}
+
+static void vop2_crtc_handle_post_buf_empty(struct vop2_video_port *vp)
+{
+ struct vop2 *vop2 = vp->vop2;
+
+ spin_lock(&vp->irq_lock);
+ vop2_crtc_disable_irq(vp, VP_INT_POST_BUF_EMPTY);
+ vp->post_buf_empty_masked = true;
+ /* don't clear a pending FS, it may carry a vblank event */
+ vop2_writel(vop2, RK3568_VP_INT_EN(vp->id),
+ VP_INT_FS_FIELD << 16 | VP_INT_FS_FIELD);
+ spin_unlock(&vp->irq_lock);
+}
+
+static void vop2_crtc_rearm_post_buf_empty(struct vop2_video_port *vp)
+{
+ spin_lock(&vp->irq_lock);
+ if (vp->post_buf_empty_masked) {
+ vp->post_buf_empty_masked = false;
+ vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
+ if (!vp->vblank_enabled)
+ vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
+ }
+ spin_unlock(&vp->irq_lock);
+}
+
static int vop2_core_clks_prepare_enable(struct vop2 *vop2)
{
int ret;
@@ -1668,8 +1710,12 @@ static const struct drm_plane_funcs vop2_plane_funcs = {
static int vop2_crtc_enable_vblank(struct drm_crtc *crtc)
{
struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ unsigned long flags;
+ spin_lock_irqsave(&vp->irq_lock, flags);
+ vp->vblank_enabled = true;
vop2_crtc_enable_irq(vp, VP_INT_FS_FIELD);
+ spin_unlock_irqrestore(&vp->irq_lock, flags);
return 0;
}
@@ -1677,8 +1723,13 @@ static int vop2_crtc_enable_vblank(struct drm_crtc *crtc)
static void vop2_crtc_disable_vblank(struct drm_crtc *crtc)
{
struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ unsigned long flags;
- vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
+ spin_lock_irqsave(&vp->irq_lock, flags);
+ vp->vblank_enabled = false;
+ if (!vp->post_buf_empty_masked)
+ vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
+ spin_unlock_irqrestore(&vp->irq_lock, flags);
}
static enum drm_mode_status vop2_crtc_mode_valid(struct drm_crtc *crtc,
@@ -1977,7 +2028,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
vcstate->yuv_overlay = is_yuv_output(vcstate->bus_format);
- vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
+ vop2_crtc_enable_post_buf_empty_irq(vp);
polflags = 0;
if (vcstate->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
@@ -2535,6 +2586,7 @@ static irqreturn_t rk3576_vp_isr(int irq, void *data)
}
if (irqs & VP_INT_FS_FIELD) {
+ vop2_crtc_rearm_post_buf_empty(vp);
drm_crtc_handle_vblank(crtc);
spin_lock(&crtc->dev->event_lock);
if (vp->event) {
@@ -2552,6 +2604,7 @@ static irqreturn_t rk3576_vp_isr(int irq, void *data)
}
if (irqs & VP_INT_POST_BUF_EMPTY) {
+ vop2_crtc_handle_post_buf_empty(vp);
drm_err_ratelimited(vop2->drm, "POST_BUF_EMPTY irq err at vp%d\n", vp->id);
ret = IRQ_HANDLED;
}
@@ -2591,6 +2644,7 @@ static irqreturn_t vop2_isr(int irq, void *data)
}
if (irqs & VP_INT_FS_FIELD) {
+ vop2_crtc_rearm_post_buf_empty(vp);
drm_crtc_handle_vblank(crtc);
spin_lock(&crtc->dev->event_lock);
if (vp->event) {
@@ -2608,6 +2662,7 @@ static irqreturn_t vop2_isr(int irq, void *data)
}
if (irqs & VP_INT_POST_BUF_EMPTY) {
+ vop2_crtc_handle_post_buf_empty(vp);
drm_err_ratelimited(vop2->drm,
"POST_BUF_EMPTY irq err at vp%d\n",
vp->id);
@@ -2984,7 +3039,7 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
struct vop2 *vop2;
struct resource *res;
size_t alloc_size;
- int ret;
+ int ret, i;
vop2_data = of_device_get_match_data(dev);
if (!vop2_data)
@@ -3102,6 +3157,8 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
mutex_init(&vop2->vop2_lock);
mutex_init(&vop2->ovl_lock);
spin_lock_init(&vop2->axi_lock);
+ for (i = 0; i < ARRAY_SIZE(vop2->vps); i++)
+ spin_lock_init(&vop2->vps[i].irq_lock);
ret = devm_request_irq(dev, vop2->irq, vop2_isr, IRQF_SHARED, dev_name(dev), vop2);
if (ret)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
index f1e35967e67f..7de32bef8d7b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
@@ -272,6 +272,16 @@ struct vop2_video_port {
struct drm_pending_vblank_event *event;
unsigned int nlayers;
+
+ /** @irq_lock: protects @vblank_enabled and @post_buf_empty_masked */
+ spinlock_t irq_lock;
+ /** @vblank_enabled: the FS interrupt is enabled for vblank reporting */
+ bool vblank_enabled;
+ /**
+ * @post_buf_empty_masked: POST_BUF_EMPTY is masked until the next
+ * frame start
+ */
+ bool post_buf_empty_masked;
};
/**
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 5/8] drm/rockchip: vop2: Report POST_BUF_EMPTY at most once per frame
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
POST_BUF_EMPTY is raised for every line on which the post processing
stage of a video port finds no data. The interrupt handler only
rate-limits the error message and leaves the interrupt enabled, so an
underflowing video port keeps the CPU in the interrupt handler 50000 to
300000 times per second, for as long as the configuration that causes
the underflow stays in place (e.g. 120000 interrupts per second for
3840x2160@120 with two full screen layers on one AXI port).
Mask POST_BUF_EMPTY when it fires and unmask it at the next frame start.
The frame start (FS) interrupt is enabled for that frame even when
vblank reporting is off, and disabled again once POST_BUF_EMPTY is
re-armed. A new per video port lock serialises the interrupt enable
bookkeeping between the interrupt handler and the vblank callbacks.
The error is still reported, now at most once per frame and video port.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 63 ++++++++++++++++++++++++++--
drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 10 +++++
2 files changed, 70 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 9aeb90c6aa4b..3323e50224c5 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -841,6 +841,48 @@ static void vop2_crtc_disable_irq(struct vop2_video_port *vp, u32 irq)
vop2_writel(vop2, RK3568_VP_INT_EN(vp->id), irq << 16);
}
+/*
+ * POST_BUF_EMPTY is raised for every line the post processing stage finds
+ * no data for, so an underflowing video port can raise it 100000 times per
+ * second. Report it at most once per frame: mask it when it fires and
+ * unmask it at the next frame start, using the FS interrupt even when
+ * vblank reporting is off.
+ */
+static void vop2_crtc_enable_post_buf_empty_irq(struct vop2_video_port *vp)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&vp->irq_lock, flags);
+ vp->post_buf_empty_masked = false;
+ vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
+ spin_unlock_irqrestore(&vp->irq_lock, flags);
+}
+
+static void vop2_crtc_handle_post_buf_empty(struct vop2_video_port *vp)
+{
+ struct vop2 *vop2 = vp->vop2;
+
+ spin_lock(&vp->irq_lock);
+ vop2_crtc_disable_irq(vp, VP_INT_POST_BUF_EMPTY);
+ vp->post_buf_empty_masked = true;
+ /* don't clear a pending FS, it may carry a vblank event */
+ vop2_writel(vop2, RK3568_VP_INT_EN(vp->id),
+ VP_INT_FS_FIELD << 16 | VP_INT_FS_FIELD);
+ spin_unlock(&vp->irq_lock);
+}
+
+static void vop2_crtc_rearm_post_buf_empty(struct vop2_video_port *vp)
+{
+ spin_lock(&vp->irq_lock);
+ if (vp->post_buf_empty_masked) {
+ vp->post_buf_empty_masked = false;
+ vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
+ if (!vp->vblank_enabled)
+ vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
+ }
+ spin_unlock(&vp->irq_lock);
+}
+
static int vop2_core_clks_prepare_enable(struct vop2 *vop2)
{
int ret;
@@ -1668,8 +1710,12 @@ static const struct drm_plane_funcs vop2_plane_funcs = {
static int vop2_crtc_enable_vblank(struct drm_crtc *crtc)
{
struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ unsigned long flags;
+ spin_lock_irqsave(&vp->irq_lock, flags);
+ vp->vblank_enabled = true;
vop2_crtc_enable_irq(vp, VP_INT_FS_FIELD);
+ spin_unlock_irqrestore(&vp->irq_lock, flags);
return 0;
}
@@ -1677,8 +1723,13 @@ static int vop2_crtc_enable_vblank(struct drm_crtc *crtc)
static void vop2_crtc_disable_vblank(struct drm_crtc *crtc)
{
struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ unsigned long flags;
- vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
+ spin_lock_irqsave(&vp->irq_lock, flags);
+ vp->vblank_enabled = false;
+ if (!vp->post_buf_empty_masked)
+ vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
+ spin_unlock_irqrestore(&vp->irq_lock, flags);
}
static enum drm_mode_status vop2_crtc_mode_valid(struct drm_crtc *crtc,
@@ -1977,7 +2028,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
vcstate->yuv_overlay = is_yuv_output(vcstate->bus_format);
- vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
+ vop2_crtc_enable_post_buf_empty_irq(vp);
polflags = 0;
if (vcstate->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
@@ -2535,6 +2586,7 @@ static irqreturn_t rk3576_vp_isr(int irq, void *data)
}
if (irqs & VP_INT_FS_FIELD) {
+ vop2_crtc_rearm_post_buf_empty(vp);
drm_crtc_handle_vblank(crtc);
spin_lock(&crtc->dev->event_lock);
if (vp->event) {
@@ -2552,6 +2604,7 @@ static irqreturn_t rk3576_vp_isr(int irq, void *data)
}
if (irqs & VP_INT_POST_BUF_EMPTY) {
+ vop2_crtc_handle_post_buf_empty(vp);
drm_err_ratelimited(vop2->drm, "POST_BUF_EMPTY irq err at vp%d\n", vp->id);
ret = IRQ_HANDLED;
}
@@ -2591,6 +2644,7 @@ static irqreturn_t vop2_isr(int irq, void *data)
}
if (irqs & VP_INT_FS_FIELD) {
+ vop2_crtc_rearm_post_buf_empty(vp);
drm_crtc_handle_vblank(crtc);
spin_lock(&crtc->dev->event_lock);
if (vp->event) {
@@ -2608,6 +2662,7 @@ static irqreturn_t vop2_isr(int irq, void *data)
}
if (irqs & VP_INT_POST_BUF_EMPTY) {
+ vop2_crtc_handle_post_buf_empty(vp);
drm_err_ratelimited(vop2->drm,
"POST_BUF_EMPTY irq err at vp%d\n",
vp->id);
@@ -2984,7 +3039,7 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
struct vop2 *vop2;
struct resource *res;
size_t alloc_size;
- int ret;
+ int ret, i;
vop2_data = of_device_get_match_data(dev);
if (!vop2_data)
@@ -3102,6 +3157,8 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
mutex_init(&vop2->vop2_lock);
mutex_init(&vop2->ovl_lock);
spin_lock_init(&vop2->axi_lock);
+ for (i = 0; i < ARRAY_SIZE(vop2->vps); i++)
+ spin_lock_init(&vop2->vps[i].irq_lock);
ret = devm_request_irq(dev, vop2->irq, vop2_isr, IRQF_SHARED, dev_name(dev), vop2);
if (ret)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
index f1e35967e67f..7de32bef8d7b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
@@ -272,6 +272,16 @@ struct vop2_video_port {
struct drm_pending_vblank_event *event;
unsigned int nlayers;
+
+ /** @irq_lock: protects @vblank_enabled and @post_buf_empty_masked */
+ spinlock_t irq_lock;
+ /** @vblank_enabled: the FS interrupt is enabled for vblank reporting */
+ bool vblank_enabled;
+ /**
+ * @post_buf_empty_masked: POST_BUF_EMPTY is masked until the next
+ * frame start
+ */
+ bool post_buf_empty_masked;
};
/**
--
2.55.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 6/8] drm/rockchip: vop2: Find the first mixer of a video port with Esmart3 too
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
(?)
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
-1 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
vop2_find_start_mixer_id_for_vp() looks for the first layer that holds a
window of the video port, but for_each_set_bit() takes the number of bits
to search, and ROCKCHIP_VOP2_ESMART3 is the highest window id, so Esmart3
is never found. A video port whose bottom layer shows Esmart3 then starts
its alpha setup at the mixer of a higher layer, or, if Esmart3 is its
only window, past all layers, and programs mixers of other video ports.
Search all window ids.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
index 83203d18865b..2843bfb36341 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -1889,7 +1889,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, ROCKCHIP_VOP2_ESMART3 + 1) {
win = vop2_find_win_by_phys_id(vop2, phys_id);
if (win->data->layer_sel_id[vp->id] == ((layer_sel >> i) & 0xf)) {
match = true;
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 6/8] drm/rockchip: vop2: Find the first mixer of a video port with Esmart3 too
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
vop2_find_start_mixer_id_for_vp() looks for the first layer that holds a
window of the video port, but for_each_set_bit() takes the number of bits
to search, and ROCKCHIP_VOP2_ESMART3 is the highest window id, so Esmart3
is never found. A video port whose bottom layer shows Esmart3 then starts
its alpha setup at the mixer of a higher layer, or, if Esmart3 is its
only window, past all layers, and programs mixers of other video ports.
Search all window ids.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
index 83203d18865b..2843bfb36341 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -1889,7 +1889,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, ROCKCHIP_VOP2_ESMART3 + 1) {
win = vop2_find_win_by_phys_id(vop2, phys_id);
if (win->data->layer_sel_id[vp->id] == ((layer_sel >> i) & 0xf)) {
match = true;
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 6/8] drm/rockchip: vop2: Find the first mixer of a video port with Esmart3 too
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
vop2_find_start_mixer_id_for_vp() looks for the first layer that holds a
window of the video port, but for_each_set_bit() takes the number of bits
to search, and ROCKCHIP_VOP2_ESMART3 is the highest window id, so Esmart3
is never found. A video port whose bottom layer shows Esmart3 then starts
its alpha setup at the mixer of a higher layer, or, if Esmart3 is its
only window, past all layers, and programs mixers of other video ports.
Search all window ids.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
index 83203d18865b..2843bfb36341 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -1889,7 +1889,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, ROCKCHIP_VOP2_ESMART3 + 1) {
win = vop2_find_win_by_phys_id(vop2, phys_id);
if (win->data->layer_sel_id[vp->id] == ((layer_sel >> i) & 0xf)) {
match = true;
--
2.55.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 7/8] drm/rockchip: vop2: Keep the other LUT_PORT_SEL fields on gamma LUT updates
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
(?)
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
-1 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
On RK3588 the gamma LUT of a video port is written through a window that
GAMMA_AHB_WRITE_SEL in SYS_CTRL_LUT_PORT_SEL selects. The register also
holds the dclk source selection of video ports 0 and 2 and the enable of
the video port 0/1 merge for modes wider than 4096 pixels, which writing
the whole register clears.
Only update the write select field.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 3323e50224c5..9ad138457204 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -1774,8 +1774,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);
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 7/8] drm/rockchip: vop2: Keep the other LUT_PORT_SEL fields on gamma LUT updates
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
On RK3588 the gamma LUT of a video port is written through a window that
GAMMA_AHB_WRITE_SEL in SYS_CTRL_LUT_PORT_SEL selects. The register also
holds the dclk source selection of video ports 0 and 2 and the enable of
the video port 0/1 merge for modes wider than 4096 pixels, which writing
the whole register clears.
Only update the write select field.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 3323e50224c5..9ad138457204 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -1774,8 +1774,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);
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 7/8] drm/rockchip: vop2: Keep the other LUT_PORT_SEL fields on gamma LUT updates
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
On RK3588 the gamma LUT of a video port is written through a window that
GAMMA_AHB_WRITE_SEL in SYS_CTRL_LUT_PORT_SEL selects. The register also
holds the dclk source selection of video ports 0 and 2 and the enable of
the video port 0/1 merge for modes wider than 4096 pixels, which writing
the whole register clears.
Only update the write select field.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 3323e50224c5..9ad138457204 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -1774,8 +1774,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);
--
2.55.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 8/8] drm/rockchip: vop2: Fix the AFBC offsets of a source that starts inside a superblock
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
(?)
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
-1 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
The AFBC decoder of a window takes the position of the source in the
framebuffer (AFBC_PIC_OFFSET), the number of superblock columns to
decode (AFBC_TILE_NUM) and the offset of the first pixel inside its
superblock (the transform offset). For a source that doesn't start at
x = 0:
- The tile number only counts the superblocks of the source width, one
short when the source starts inside a superblock and ends inside
another one.
- The transform offset is the whole x position, truncated to the 8 bit
field, instead of the position in the 16 pixel wide superblock.
Count the superblock columns from the first one the source touches, and
take the x offset in the superblock, like the vendor kernel does for the
not mirrored cases. The X mirrored and rotated cases are left as they are.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 9ad138457204..dfb76bfbc358 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -486,7 +486,7 @@ static u32 vop2_afbc_transform_offset(struct drm_plane_state *pstate,
ty = act_yoffset;
break;
case DRM_MODE_REFLECT_Y:
- tx = act_xoffset;
+ tx = act_xoffset & 0xf;
ty = bottom_crop_line_num - act_yoffset;
break;
case DRM_MODE_ROTATE_90:
@@ -498,7 +498,7 @@ static u32 vop2_afbc_transform_offset(struct drm_plane_state *pstate,
ty = vir_width - width - act_xoffset;
break;
case 0:
- tx = act_xoffset;
+ tx = act_xoffset & 0xf;
ty = act_yoffset;
break;
}
@@ -1577,7 +1577,8 @@ static void vop2_plane_atomic_update(struct drm_plane *plane,
if (fb->modifier & AFBC_FORMAT_MOD_YTR)
afbc_format |= (1 << 4);
- afbc_tile_num = ALIGN(src_w, block_w) / block_w;
+ /* Superblock columns the source covers, the first one partly */
+ afbc_tile_num = ALIGN(src_w + (src->x1 >> 16) % block_w, block_w) / block_w;
/*
* AFBC pic_vir_width is count by pixel, this is different
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 8/8] drm/rockchip: vop2: Fix the AFBC offsets of a source that starts inside a superblock
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
The AFBC decoder of a window takes the position of the source in the
framebuffer (AFBC_PIC_OFFSET), the number of superblock columns to
decode (AFBC_TILE_NUM) and the offset of the first pixel inside its
superblock (the transform offset). For a source that doesn't start at
x = 0:
- The tile number only counts the superblocks of the source width, one
short when the source starts inside a superblock and ends inside
another one.
- The transform offset is the whole x position, truncated to the 8 bit
field, instead of the position in the 16 pixel wide superblock.
Count the superblock columns from the first one the source touches, and
take the x offset in the superblock, like the vendor kernel does for the
not mirrored cases. The X mirrored and rotated cases are left as they are.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 9ad138457204..dfb76bfbc358 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -486,7 +486,7 @@ static u32 vop2_afbc_transform_offset(struct drm_plane_state *pstate,
ty = act_yoffset;
break;
case DRM_MODE_REFLECT_Y:
- tx = act_xoffset;
+ tx = act_xoffset & 0xf;
ty = bottom_crop_line_num - act_yoffset;
break;
case DRM_MODE_ROTATE_90:
@@ -498,7 +498,7 @@ static u32 vop2_afbc_transform_offset(struct drm_plane_state *pstate,
ty = vir_width - width - act_xoffset;
break;
case 0:
- tx = act_xoffset;
+ tx = act_xoffset & 0xf;
ty = act_yoffset;
break;
}
@@ -1577,7 +1577,8 @@ static void vop2_plane_atomic_update(struct drm_plane *plane,
if (fb->modifier & AFBC_FORMAT_MOD_YTR)
afbc_format |= (1 << 4);
- afbc_tile_num = ALIGN(src_w, block_w) / block_w;
+ /* Superblock columns the source covers, the first one partly */
+ afbc_tile_num = ALIGN(src_w + (src->x1 >> 16) % block_w, block_w) / block_w;
/*
* AFBC pic_vir_width is count by pixel, this is different
--
2.55.0
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [PATCH 8/8] drm/rockchip: vop2: Fix the AFBC offsets of a source that starts inside a superblock
@ 2026-11-10 7:18 ` Arun Iyengar via B4 Relay
0 siblings, 0 replies; 40+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 7:18 UTC (permalink / raw)
To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer
Cc: dri-devel, linux-rockchip, linux-arm-kernel, linux-kernel
From: Arun Iyengar <aiyengar1990@gmail.com>
The AFBC decoder of a window takes the position of the source in the
framebuffer (AFBC_PIC_OFFSET), the number of superblock columns to
decode (AFBC_TILE_NUM) and the offset of the first pixel inside its
superblock (the transform offset). For a source that doesn't start at
x = 0:
- The tile number only counts the superblocks of the source width, one
short when the source starts inside a superblock and ends inside
another one.
- The transform offset is the whole x position, truncated to the 8 bit
field, instead of the position in the 16 pixel wide superblock.
Count the superblock columns from the first one the source touches, and
take the x offset in the superblock, like the vendor kernel does for the
not mirrored cases. The X mirrored and rotated cases are left as they are.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 9ad138457204..dfb76bfbc358 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -486,7 +486,7 @@ static u32 vop2_afbc_transform_offset(struct drm_plane_state *pstate,
ty = act_yoffset;
break;
case DRM_MODE_REFLECT_Y:
- tx = act_xoffset;
+ tx = act_xoffset & 0xf;
ty = bottom_crop_line_num - act_yoffset;
break;
case DRM_MODE_ROTATE_90:
@@ -498,7 +498,7 @@ static u32 vop2_afbc_transform_offset(struct drm_plane_state *pstate,
ty = vir_width - width - act_xoffset;
break;
case 0:
- tx = act_xoffset;
+ tx = act_xoffset & 0xf;
ty = act_yoffset;
break;
}
@@ -1577,7 +1577,8 @@ static void vop2_plane_atomic_update(struct drm_plane *plane,
if (fb->modifier & AFBC_FORMAT_MOD_YTR)
afbc_format |= (1 << 4);
- afbc_tile_num = ALIGN(src_w, block_w) / block_w;
+ /* Superblock columns the source covers, the first one partly */
+ afbc_tile_num = ALIGN(src_w + (src->x1 >> 16) % block_w, block_w) / block_w;
/*
* AFBC pic_vir_width is count by pixel, this is different
--
2.55.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 40+ messages in thread
* Re: [PATCH 4/8] drm/rockchip: vop2: Balance window fetches across both AXI ports on RK3588
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
(?)
(?)
@ 2026-11-10 7:58 ` sashiko-bot
-1 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-10 7:58 UTC (permalink / raw)
To: Arun Iyengar; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [Medium] drm/rockchip: vop2: plane commit order skews
vop2_win_get_axi_bus()
- [Medium] drm/rockchip: vop2: vop2_plane_atomic_update() omits UV plane
AXI load
- [Medium] drm/rockchip: vop2: premature AXI bus release in
vop2_win_put_axi_bus()
It also noticed 1 related pre-existing issue this patch does not
introduce:
- [High] drm/rockchip: vop2: wrong GEM object for UV plane in
vop2_plane_atomic_update() [1]
--
commit d091a328bc345e54d430e0882d6dfc1be033a2f5
Author: Arun Iyengar <aiyengar1990@gmail.com>
drm/rockchip: vop2: Balance window fetches across both AXI ports on RK3588
This commit dynamically assigns newly enabled VOP2 windows on RK3588 to the
least loaded of the two AXI read ports while avoiding AXI read ID collisions
with other active windows.
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> index 629ea7f112502..9aeb90c6aa4b1 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> @@ -1231,6 +1231,98 @@ static int vop2_plane_atomic_check(struct drm_plane *plane,
[ ... ]
> +static u8 vop2_win_get_axi_bus(struct vop2_win *win, u64 load)
> +{
> + struct vop2 *vop2 = win->vop2;
> + u64 bus_load[VOP2_SYS_AXI_BUS_NUM] = { };
> + bool clash[VOP2_SYS_AXI_BUS_NUM] = { };
> + u8 def = win->data->axi_bus_id;
> + unsigned long flags;
> + u8 bus = def;
> + int i;
> +
> + if (!(vop2->data->feature & VOP2_FEATURE_AXI_BALANCE))
> + return def;
> +
> + spin_lock_irqsave(&vop2->axi_lock, flags);
> +
> + if (win->axi_active) {
> + win->axi_load = load;
> + bus = win->axi_bus;
> + goto out;
> + }
> +
> + for (i = 0; i < vop2->registered_num_wins; i++) {
> + struct vop2_win *w = &vop2->win[i];
> +
> + if (w == win || !w->axi_active)
> + continue;
> +
> + bus_load[w->axi_bus] += w->axi_load;
> + if (vop2_win_axi_ids_clash(win, w))
> + clash[w->axi_bus] = true;
> + }
> +
> + if (!clash[def ^ 1] &&
> + (clash[def] || max(bus_load[def ^ 1] + load, bus_load[def]) <
> + max(bus_load[def] + load, bus_load[def ^ 1])))
> + bus = def ^ 1;
> +
> + WARN_ON_ONCE(clash[bus]);
> +
> + win->axi_bus = bus;
> + win->axi_load = load;
> + win->axi_active = true;
> +out:
> + spin_unlock_irqrestore(&vop2->axi_lock, flags);
> +
> + return bus;
> +}
[Severity: Medium]
Does vop2_win_get_axi_bus() see stale axi_active and axi_load state for
higher-indexed planes changed in the same atomic commit?
drm_atomic_helper_commit_planes() runs a single loop in ascending plane
index order, calling atomic_disable() or atomic_update() on each plane.
In rk3588_vop_win_data, all windows that default to AXI0 (Cluster0/1,
Esmart0/1) have lower indices than their AXI1 mirrors (Cluster2/3,
Esmart2/3). When a commit enables Esmart0 while disabling Esmart2 (or
another higher-indexed plane on AXI1), vop2_win_get_axi_bus() runs for
Esmart0 before vop2_plane_atomic_disable() clears Esmart2's axi_active.
It therefore sees Esmart2's old load and read-ID clash on AXI1 and pins
Esmart0 to AXI0 alongside Cluster0 for as long as Esmart0 remains enabled,
causing the POST_BUF_EMPTY underflows this patch aims to avoid.
> +
> +static void vop2_win_put_axi_bus(struct vop2_win *win)
> +{
> + struct vop2 *vop2 = win->vop2;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&vop2->axi_lock, flags);
> + win->axi_active = false;
> + win->axi_load = 0;
> + spin_unlock_irqrestore(&vop2->axi_lock, flags);
> +}
> +
> static void vop2_plane_atomic_disable(struct drm_plane *plane,
> struct drm_atomic_commit *state)
> {
> @@ -1241,6 +1333,8 @@ static void vop2_plane_atomic_disable(struct drm_plane *plane,
>
> drm_dbg(vop2->drm, "%s disable\n", win->data->name);
>
> + vop2_win_put_axi_bus(win);
> +
> if (state)
> old_pstate = drm_atomic_get_old_plane_state(state, plane);
[Severity: Medium]
Can clearing win->axi_active in vop2_plane_atomic_disable() cause an AXI
read ID collision before the disable is latched at VSYNC?
VOP2_WIN_ENABLE is a shadow register latched at the video port's next
VSYNC, so the disabled window keeps fetching over win->axi_bus until then.
If Esmart0 is on AXI1 on VP0 and is disabled while its mirror Esmart2
(which shares read IDs 0x0a and 0x0b) is enabled on VP1,
vop2_win_get_axi_bus() sees Esmart0->axi_active == false and assigns
Esmart2 to AXI1. If VP1's VSYNC fires before VP0's VSYNC, both windows
fetch on AXI1 with identical read IDs until VP0 latches the disable.
> @@ -1416,7 +1510,9 @@ static void vop2_plane_atomic_update(struct drm_plane *plane,
> afbc_en ? "AFBC" : "", &yrgb_mst);
>
> if (vop2->version > VOP_VERSION_RK3568) {
> - vop2_win_write(win, VOP2_WIN_AXI_BUS_ID, win->data->axi_bus_id);
> + u64 load = (u64)src_w * src_h * bpp / 8 * drm_mode_vrefresh(adjusted_mode);
> +
> + vop2_win_write(win, VOP2_WIN_AXI_BUS_ID, vop2_win_get_axi_bus(win, load));
> vop2_win_write(win, VOP2_WIN_AXI_YRGB_R_ID, win->data->axi_yrgb_r_id);
> vop2_win_write(win, VOP2_WIN_AXI_UV_R_ID, win->data->axi_uv_r_id);
> }
[Severity: Medium]
Does computing load from bpp omit the UV plane bandwidth for multi-planar
YUV formats?
bpp comes from vop2_get_bpp(fb->format), which falls back to
drm_format_info_bpp(format, 0) and returns only plane 0's bits per pixel:
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c:vop2_get_bpp() {
switch (format->format) {
case DRM_FORMAT_YUV420_8BIT:
return 12;
case DRM_FORMAT_YUV420_10BIT:
return 15;
case DRM_FORMAT_VUY101010:
return 30;
default:
return drm_format_info_bpp(format, 0);
}
}
For semi-planar formats such as NV12, NV16 or NV24, plane 1 is fetched
through the same AXI port via VOP2_WIN_AXI_UV_R_ID, but bpp only counts
the Y plane (8 or 10 bpp instead of 12 to 30 bpp), underestimating
win->axi_load by 33% to 67%.
[Severity: High]
This problem wasn't introduced by this patch, but earlier in
vop2_plane_atomic_update() the UV plane DMA address is computed from
fb->obj[0] instead of fb->obj[1] [1]:
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c:vop2_plane_atomic_update() {
...
rk_obj = to_rockchip_obj(fb->obj[0]);
yrgb_mst = rk_obj->dma_addr + offset + fb->offsets[0];
if (fb->format->is_yuv) {
...
rk_obj = to_rockchip_obj(fb->obj[0]);
uv_mst = rk_obj->dma_addr + offset + fb->offsets[1];
}
}
When a multi-planar YUV framebuffer has separate GEM objects for the Y and
UV planes, VOP2_WIN_UV_MST is programmed with plane 0's DMA base address
plus plane 1's offset, causing corrupted scanout or out-of-bounds DMA
reads past fb->obj[0].
[1] https://sashiko.dev/#/bug/linux-46433ab8-0e41-4994-af45-5dfb41ba7894
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261110-vop2-fixes-v1-0-d336e9dd90d3@gmail.com?part=4
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH 5/8] drm/rockchip: vop2: Report POST_BUF_EMPTY at most once per frame
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
(?)
(?)
@ 2026-11-10 8:11 ` sashiko-bot
-1 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-10 8:11 UTC (permalink / raw)
To: Arun Iyengar; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 4 potential
issues to consider:
- [Medium] drm/rockchip: vop2: stale FS status in
vop2_crtc_handle_post_buf_empty()
- [Medium] drm/rockchip: vop2: VP_INT_FS_FIELD left enabled across CRTC
disable
- [Low] drm/rockchip: vop2: vop2_crtc_enable_vblank() clears pending FS
irq
- [Low] drm/rockchip: vop2: missing Fixes trailer for POST_BUF_EMPTY irq
storm
--
commit 225fca8d582460d4af5df41d587daa2368d818f1
Author: Arun Iyengar <aiyengar1990@gmail.com>
> drm/rockchip: vop2: Report POST_BUF_EMPTY at most once per frame
>
> POST_BUF_EMPTY is raised for every line on which the post processing
> stage of a video port finds no data. The interrupt handler only
> rate-limits the error message and leaves the interrupt enabled, so an
> underflowing video port keeps the CPU in the interrupt handler 50000 to
> 300000 times per second, for as long as the configuration that causes
> the underflow stays in place (e.g. 120000 interrupts per second for
> 3840x2160@120 with two full screen layers on one AXI port).
>
> Mask POST_BUF_EMPTY when it fires and unmask it at the next frame start.
> The frame start (FS) interrupt is enabled for that frame even when
> vblank reporting is off, and disabled again once POST_BUF_EMPTY is
> re-armed. A new per video port lock serialises the interrupt enable
> bookkeeping between the interrupt handler and the vblank callbacks.
> The error is still reported, now at most once per frame and video port.
>
> Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
[Severity: Low]
This fixes a per-line interrupt storm on underflowing video ports. Should
this include:
Fixes: 604be85547ce ("drm/rockchip: Add VOP2 driver")
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> index 9aeb90c6aa4b1..3323e50224c55 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> @@ -841,6 +841,48 @@ static void vop2_crtc_disable_irq(struct vop2_video_port *vp, u32 irq)
> vop2_writel(vop2, RK3568_VP_INT_EN(vp->id), irq << 16);
> }
>
> +/*
> + * POST_BUF_EMPTY is raised for every line the post processing stage finds
> + * no data for, so an underflowing video port can raise it 100000 times per
> + * second. Report it at most once per frame: mask it when it fires and
> + * unmask it at the next frame start, using the FS interrupt even when
> + * vblank reporting is off.
> + */
> +static void vop2_crtc_enable_post_buf_empty_irq(struct vop2_video_port *vp)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&vp->irq_lock, flags);
> + vp->post_buf_empty_masked = false;
> + vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
> + spin_unlock_irqrestore(&vp->irq_lock, flags);
> +}
[Severity: Medium]
Can VP_INT_FS_FIELD stay enabled permanently after a CRTC disable and
re-enable cycle?
If VP_INT_POST_BUF_EMPTY fires during the last frame of a video port
(either before drm_crtc_vblank_off() or while vop2_crtc_atomic_disable()
waits on vp->dsp_hold_completion), vop2_crtc_handle_post_buf_empty() sets
vp->post_buf_empty_masked = true and enables VP_INT_FS_FIELD.
drm_crtc_vblank_off() -> vop2_crtc_disable_vblank() then sees
vp->post_buf_empty_masked == true and leaves VP_INT_FS_FIELD enabled in
RK3568_VP_INT_EN(vp->id) when the video port enters standby.
When the CRTC is later re-enabled (while another active video port kept
VOP2 powered on), vop2_crtc_enable_post_buf_empty_irq() resets
vp->post_buf_empty_masked = false without disabling VP_INT_FS_FIELD. Once
vblank turns off, the video port runs with both vp->vblank_enabled and
vp->post_buf_empty_masked false while VP_INT_FS_FIELD fires an unhandled
interrupt on every frame. In addition, vop2_crtc_atomic_disable() and the
err_put_enable_count error paths in vop2_crtc_atomic_enable() never disable
VP_INT_POST_BUF_EMPTY.
> +
> +static void vop2_crtc_handle_post_buf_empty(struct vop2_video_port *vp)
> +{
> + struct vop2 *vop2 = vp->vop2;
> +
> + spin_lock(&vp->irq_lock);
> + vop2_crtc_disable_irq(vp, VP_INT_POST_BUF_EMPTY);
> + vp->post_buf_empty_masked = true;
> + /* don't clear a pending FS, it may carry a vblank event */
> + vop2_writel(vop2, RK3568_VP_INT_EN(vp->id),
> + VP_INT_FS_FIELD << 16 | VP_INT_FS_FIELD);
> + spin_unlock(&vp->irq_lock);
> +}
[Severity: Medium]
When vp->vblank_enabled is false, does unmasking VP_INT_FS_FIELD without
clearing RK3568_VP_INT_CLR(vp->id) immediately trigger a spurious mid-frame
FS interrupt?
VOP2 latches raw interrupt status even while an interrupt is masked, and
vop2_isr() / rk3576_vp_isr() only clear bits returned by the masked status
register RK3568_VP_INT_STATUS(vp->id). While vblank reporting is disabled,
the raw VP_INT_FS_FIELD bit latched at the start of the current frame stays
set during active scanout. Writing RK3568_VP_INT_EN(vp->id) here without
clearing RK3568_VP_INT_CLR(vp->id) causes VP_INT_FS_FIELD to fire
immediately on that stale status and call vop2_crtc_rearm_post_buf_empty()
mid-frame, allowing VP_INT_POST_BUF_EMPTY to fire a second time in the same
frame.
When vp->vblank_enabled is true, VP_INT_FS_FIELD is already enabled; when
!vp->vblank_enabled, calling vop2_crtc_enable_irq(vp, VP_INT_FS_FIELD) will
clear the stale raw status before enabling it.
[ ... ]
> @@ -1668,8 +1710,12 @@ static const struct drm_plane_funcs vop2_plane_funcs = {
> static int vop2_crtc_enable_vblank(struct drm_crtc *crtc)
> {
> struct vop2_video_port *vp = to_vop2_video_port(crtc);
> + unsigned long flags;
>
> + spin_lock_irqsave(&vp->irq_lock, flags);
> + vp->vblank_enabled = true;
> vop2_crtc_enable_irq(vp, VP_INT_FS_FIELD);
> + spin_unlock_irqrestore(&vp->irq_lock, flags);
>
> return 0;
> }
[Severity: Low]
Can vop2_crtc_enable_vblank() clear a pending VP_INT_FS_FIELD interrupt if
vp->post_buf_empty_masked is already true?
When vp->post_buf_empty_masked is true, VP_INT_FS_FIELD is already enabled
in hardware. Calling vop2_crtc_enable_irq(vp, VP_INT_FS_FIELD)
unconditionally writes RK3568_VP_INT_CLR(vp->id), which can drop a pending
frame-start interrupt that raced with vop2_crtc_enable_vblank() and delay
both vblank handling and vop2_crtc_rearm_post_buf_empty() by a frame.
Guard the vop2_crtc_enable_irq() call with if (!vp->post_buf_empty_masked),
matching vop2_crtc_disable_vblank().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261110-vop2-fixes-v1-0-d336e9dd90d3@gmail.com?part=5
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH 6/8] drm/rockchip: vop2: Find the first mixer of a video port with Esmart3 too
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
(?)
(?)
@ 2026-11-10 8:16 ` sashiko-bot
-1 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-10 8:16 UTC (permalink / raw)
To: Arun Iyengar; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Low] drm/rockchip: missing Fixes trailer for
vop2_find_start_mixer_id_for_vp()
--
commit bef29062242358932c819e26dffcbfccfee3a800
Author: Arun Iyengar <aiyengar1990@gmail.com>
> drm/rockchip: vop2: Find the first mixer of a video port with Esmart3 too
>
> vop2_find_start_mixer_id_for_vp() looks for the first layer that holds a
> window of the video port, but for_each_set_bit() takes the number of bits
> to search, and ROCKCHIP_VOP2_ESMART3 is the highest window id, so Esmart3
> is never found. A video port whose bottom layer shows Esmart3 then starts
> its alpha setup at the mixer of a higher layer, or, if Esmart3 is its
> only window, past all layers, and programs mixers of other video ports.
>
> Search all window ids.
>
> Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
[Severity: Low]
This fixes the window bitmask search bound in
vop2_find_start_mixer_id_for_vp(). Should this include:
Fixes: d3fe9aa49585 ("drm/rockchip: vop2: Use OVL_LAYER_SEL configuration instead of use win_mask calculate used layers")
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261110-vop2-fixes-v1-0-d336e9dd90d3@gmail.com?part=6
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH 7/8] drm/rockchip: vop2: Keep the other LUT_PORT_SEL fields on gamma LUT updates
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
(?)
(?)
@ 2026-11-10 8:22 ` sashiko-bot
-1 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-10 8:22 UTC (permalink / raw)
To: Arun Iyengar; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Low] drm/rockchip: vop2: missing Fixes trailer for commit
4f537776340d
--
commit a8803c2d42085266d5dfa3113cf2bcad402ebf13
Author: Arun Iyengar <aiyengar1990@gmail.com>
> drm/rockchip: vop2: Keep the other LUT_PORT_SEL fields on gamma LUT updates
>
> On RK3588 the gamma LUT of a video port is written through a window that
> GAMMA_AHB_WRITE_SEL in SYS_CTRL_LUT_PORT_SEL selects. The register also
> holds the dclk source selection of video ports 0 and 2 and the enable of
> the video port 0/1 merge for modes wider than 4096 pixels, which writing
> the whole register clears.
>
> Only update the write select field.
>
> Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
[Severity: Low]
This fixes the full-register write to RK3568_LUT_PORT_SEL in
vop2_crtc_atomic_set_gamma_seamless(). Should this include:
Fixes: 4f537776340d ("rockchip/drm: vop2: add support for gamma LUT")
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261110-vop2-fixes-v1-0-d336e9dd90d3@gmail.com?part=7
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH 8/8] drm/rockchip: vop2: Fix the AFBC offsets of a source that starts inside a superblock
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
(?)
(?)
@ 2026-11-10 8:44 ` sashiko-bot
-1 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-10 8:44 UTC (permalink / raw)
To: Arun Iyengar; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] drm/rockchip: vop2: wrong 32x8 AFBC x mask in
vop2_afbc_transform_offset()
- [Low] drm/rockchip: vop2: missing Fixes trailer for commit
604be85547ce
--
commit 1aeff8145623ebb58cb0a14969b5391365ee2e1c
Author: Arun Iyengar <aiyengar1990@gmail.com>
> drm/rockchip: vop2: Fix the AFBC offsets of a source that starts inside a superblock
>
> The AFBC decoder of a window takes the position of the source in the
> framebuffer (AFBC_PIC_OFFSET), the number of superblock columns to
> decode (AFBC_TILE_NUM) and the offset of the first pixel inside its
> superblock (the transform offset). For a source that doesn't start at
> x = 0:
>
> - The tile number only counts the superblocks of the source width, one
> short when the source starts inside a superblock and ends inside
> another one.
> - The transform offset is the whole x position, truncated to the 8 bit
> field, instead of the position in the 16 pixel wide superblock.
>
> Count the superblock columns from the first one the source touches, and
> take the x offset in the superblock, like the vendor kernel does for the
> not mirrored cases. The X mirrored and rotated cases are left as they are.
>
> Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
[Severity: Low]
Since this fixes a bug introduced with the initial VOP2 driver, should it
include:
Fixes: 604be85547ce ("drm/rockchip: Add VOP2 driver")
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> index 9ad138457204f..dfb76bfbc358f 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> @@ -486,7 +486,7 @@ static u32 vop2_afbc_transform_offset(struct drm_plane_state *pstate,
> ty = act_yoffset;
> break;
> case DRM_MODE_REFLECT_Y:
> - tx = act_xoffset;
> + tx = act_xoffset & 0xf;
> ty = bottom_crop_line_num - act_yoffset;
> break;
> case DRM_MODE_ROTATE_90:
> @@ -498,7 +498,7 @@ static u32 vop2_afbc_transform_offset(struct drm_plane_state *pstate,
> ty = vir_width - width - act_xoffset;
> break;
> case 0:
> - tx = act_xoffset;
> + tx = act_xoffset & 0xf;
> ty = act_yoffset;
> break;
> }
[Severity: Medium]
Does masking act_xoffset with 0xf break 32x8 AFBC superblocks when
(act_xoffset % 32) is between 16 and 31?
In vop2_plane_atomic_update(), block_w is 32 when
AFBC_FORMAT_MOD_BLOCK_SIZE_32x8 is set (such as on RK3576 cluster windows),
and afbc_tile_num is computed using (src->x1 >> 16) % block_w. Hardcoding
0xf here truncates tx to 0..15 instead of 0..31, shifting the decoded
plane horizontally by 16 pixels whenever bit 4 of act_xoffset is set.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261110-vop2-fixes-v1-0-d336e9dd90d3@gmail.com?part=8
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH 0/8] drm/rockchip: vop2: RK3588 clocking, sync polarity, AXI port and window fixes
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
@ 2026-11-10 9:39 ` Chaoyi Chen
-1 siblings, 0 replies; 40+ messages in thread
From: Chaoyi Chen @ 2026-11-10 9:39 UTC (permalink / raw)
To: aiyengar1990
Cc: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer, dri-devel, linux-rockchip, linux-arm-kernel,
linux-kernel
Hi Arun,
On 11/10/2026 3:18 PM, Arun Iyengar via B4 Relay wrote:
> This series collects fixes for the RK3588 VOP2 found while bringing up
> HDMI and DisplayPort output on an Orange Pi 5 (RK3588S).
>
> Patches 1 and 2 fix the RK3588 interface setup: rk3588_calc_cru_cfg()
> leaves dividers it does not use uninitialised and the garbage ends up in
> VP_CLK_CTRL, and rk3588_get_hdmi_pol() decodes the POLFLAG bits it is
> handed as DRM mode flags, so the GRF sync polarity bits are wrong for
> most modes.
>
> Patch 3 stops the RK3588 Cluster windows from advertising AFBC with the
> lossless colour transform (YTR) for RGB565/BGR565, which the decoder
> cannot display (black picture plus POST_BUF_EMPTY).
>
> Patches 4 and 5 deal with POST_BUF_EMPTY underflows. The VOP2 fetches
> through two AXI read ports but the port of each window is fixed in the
> window table, so the usual primary plane plus overlay configuration of
> a video port puts both layers on one port while the other sits idle.
> One port cannot sustain two full screen 4K@120Hz layers (measured: the
> fetch rate collapses from ~6 GB/s to ~3 GB/s and about half of all
> lines underflow). Patch 4 assigns the port when a window starts
> fetching, balancing the estimated load; patch 5 rate-limits the
> POST_BUF_EMPTY interrupt to once per frame, since an underflowing port
> otherwise keeps the CPU in the interrupt handler 50000 to 300000 times
> per second.
>
Have you checked the aclk_vop rate?
> Patches 6 to 8 are smaller fixes: the start mixer search skipped
> Esmart3 (for_each_set_bit() was given the highest window id as the bit
> count), a gamma LUT update cleared the other fields of LUT_PORT_SEL
> (the VP0/VP2 dclk source selection and the VP0/VP1 merge enable), and
> the AFBC tile count and transform offset were wrong for a source that
> starts inside a 16 pixel superblock.
>
> Tested on an Orange Pi 5 (RK3588S) with an HDMI 2.1 TV and a
> DisplayPort monitor, pictures judged with a camera. Patches 1 and 2:
> VP0_CLK_CTRL reads back without the garbage dclk_out divider, the sync
> polarities seen by the HDMI controller (VIDEO_INTERFACE_STATUS0) match
> the mode flags for +/+ (1080p, 2160p) and -/- (720x480, 640x480) modes,
> and the picture is correct over TMDS and FRL modes from 27 to
> 1188 MHz. Patch 3: RGB565 is offered on the Cluster windows only
> without YTR, and those planes scan out correctly on three Cluster
> windows. Patches 4 and 5: 3840x2160@120 with a full screen overlay
> (previously ~120000 POST_BUF_EMPTY per second and a scrambled picture)
> and a dual-display session (HDMI 4K@60 plus DP 4K@30, two video players,
> ten minutes) run with zero underflows. Patches 6 to 8 were found while
> driving Esmart3 and partial AFBC overlays: an AFBC overlay placed at
> x=1001 shows correctly. All eight have been in the board's kernel for
> the display test runs since.
>
> This series was developed with Claude (Anthropic) as a coding assistant:
> it wrote the code and the commit messages, and I reviewed and tested
> every change as described above.
>
> Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
> ---
> Arun Iyengar (8):
> drm/rockchip: vop2: Don't use uninitialised dividers on RK3588
> drm/rockchip: vop2: Fix the HDMI sync polarity on RK3588
> drm/rockchip: vop2: Don't accept AFBC YTR for RGB565 on RK3588
> drm/rockchip: vop2: Balance window fetches across both AXI ports on RK3588
> drm/rockchip: vop2: Report POST_BUF_EMPTY at most once per frame
> drm/rockchip: vop2: Find the first mixer of a video port with Esmart3 too
> drm/rockchip: vop2: Keep the other LUT_PORT_SEL fields on gamma LUT updates
> drm/rockchip: vop2: Fix the AFBC offsets of a source that starts inside a superblock
>
> drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 186 +++++++++++++++++++++++++--
> drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 24 ++++
> drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 27 +++-
> 3 files changed, 223 insertions(+), 14 deletions(-)
> ---
> base-commit: 8deaade4a5a4ff635df91e246e42139ab2b33f6d
> change-id: 20261110-vop2-fixes-fa45142799c1
>
> Best regards,
> --
> Arun Iyengar <aiyengar1990@gmail.com>
--
Best,
Chaoyi
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH 0/8] drm/rockchip: vop2: RK3588 clocking, sync polarity, AXI port and window fixes
@ 2026-11-10 9:39 ` Chaoyi Chen
0 siblings, 0 replies; 40+ messages in thread
From: Chaoyi Chen @ 2026-11-10 9:39 UTC (permalink / raw)
To: aiyengar1990
Cc: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer, dri-devel, linux-rockchip, linux-arm-kernel,
linux-kernel
Hi Arun,
On 11/10/2026 3:18 PM, Arun Iyengar via B4 Relay wrote:
> This series collects fixes for the RK3588 VOP2 found while bringing up
> HDMI and DisplayPort output on an Orange Pi 5 (RK3588S).
>
> Patches 1 and 2 fix the RK3588 interface setup: rk3588_calc_cru_cfg()
> leaves dividers it does not use uninitialised and the garbage ends up in
> VP_CLK_CTRL, and rk3588_get_hdmi_pol() decodes the POLFLAG bits it is
> handed as DRM mode flags, so the GRF sync polarity bits are wrong for
> most modes.
>
> Patch 3 stops the RK3588 Cluster windows from advertising AFBC with the
> lossless colour transform (YTR) for RGB565/BGR565, which the decoder
> cannot display (black picture plus POST_BUF_EMPTY).
>
> Patches 4 and 5 deal with POST_BUF_EMPTY underflows. The VOP2 fetches
> through two AXI read ports but the port of each window is fixed in the
> window table, so the usual primary plane plus overlay configuration of
> a video port puts both layers on one port while the other sits idle.
> One port cannot sustain two full screen 4K@120Hz layers (measured: the
> fetch rate collapses from ~6 GB/s to ~3 GB/s and about half of all
> lines underflow). Patch 4 assigns the port when a window starts
> fetching, balancing the estimated load; patch 5 rate-limits the
> POST_BUF_EMPTY interrupt to once per frame, since an underflowing port
> otherwise keeps the CPU in the interrupt handler 50000 to 300000 times
> per second.
>
Have you checked the aclk_vop rate?
> Patches 6 to 8 are smaller fixes: the start mixer search skipped
> Esmart3 (for_each_set_bit() was given the highest window id as the bit
> count), a gamma LUT update cleared the other fields of LUT_PORT_SEL
> (the VP0/VP2 dclk source selection and the VP0/VP1 merge enable), and
> the AFBC tile count and transform offset were wrong for a source that
> starts inside a 16 pixel superblock.
>
> Tested on an Orange Pi 5 (RK3588S) with an HDMI 2.1 TV and a
> DisplayPort monitor, pictures judged with a camera. Patches 1 and 2:
> VP0_CLK_CTRL reads back without the garbage dclk_out divider, the sync
> polarities seen by the HDMI controller (VIDEO_INTERFACE_STATUS0) match
> the mode flags for +/+ (1080p, 2160p) and -/- (720x480, 640x480) modes,
> and the picture is correct over TMDS and FRL modes from 27 to
> 1188 MHz. Patch 3: RGB565 is offered on the Cluster windows only
> without YTR, and those planes scan out correctly on three Cluster
> windows. Patches 4 and 5: 3840x2160@120 with a full screen overlay
> (previously ~120000 POST_BUF_EMPTY per second and a scrambled picture)
> and a dual-display session (HDMI 4K@60 plus DP 4K@30, two video players,
> ten minutes) run with zero underflows. Patches 6 to 8 were found while
> driving Esmart3 and partial AFBC overlays: an AFBC overlay placed at
> x=1001 shows correctly. All eight have been in the board's kernel for
> the display test runs since.
>
> This series was developed with Claude (Anthropic) as a coding assistant:
> it wrote the code and the commit messages, and I reviewed and tested
> every change as described above.
>
> Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
> ---
> Arun Iyengar (8):
> drm/rockchip: vop2: Don't use uninitialised dividers on RK3588
> drm/rockchip: vop2: Fix the HDMI sync polarity on RK3588
> drm/rockchip: vop2: Don't accept AFBC YTR for RGB565 on RK3588
> drm/rockchip: vop2: Balance window fetches across both AXI ports on RK3588
> drm/rockchip: vop2: Report POST_BUF_EMPTY at most once per frame
> drm/rockchip: vop2: Find the first mixer of a video port with Esmart3 too
> drm/rockchip: vop2: Keep the other LUT_PORT_SEL fields on gamma LUT updates
> drm/rockchip: vop2: Fix the AFBC offsets of a source that starts inside a superblock
>
> drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 186 +++++++++++++++++++++++++--
> drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 24 ++++
> drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 27 +++-
> 3 files changed, 223 insertions(+), 14 deletions(-)
> ---
> base-commit: 8deaade4a5a4ff635df91e246e42139ab2b33f6d
> change-id: 20261110-vop2-fixes-fa45142799c1
>
> Best regards,
> --
> Arun Iyengar <aiyengar1990@gmail.com>
--
Best,
Chaoyi
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH 6/8] drm/rockchip: vop2: Find the first mixer of a video port with Esmart3 too
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
@ 2026-11-10 9:56 ` Chaoyi Chen
-1 siblings, 0 replies; 40+ messages in thread
From: Chaoyi Chen @ 2026-11-10 9:56 UTC (permalink / raw)
To: aiyengar1990
Cc: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer, dri-devel, linux-rockchip, linux-arm-kernel,
linux-kernel
Hi Arun,
On 11/10/2026 3:18 PM, Arun Iyengar via B4 Relay wrote:
> From: Arun Iyengar <aiyengar1990@gmail.com>
>
> vop2_find_start_mixer_id_for_vp() looks for the first layer that holds a
> window of the video port, but for_each_set_bit() takes the number of bits
> to search, and ROCKCHIP_VOP2_ESMART3 is the highest window id, so Esmart3
> is never found. A video port whose bottom layer shows Esmart3 then starts
> its alpha setup at the mixer of a higher layer, or, if Esmart3 is its
> only window, past all layers, and programs mixers of other video ports.
>
> Search all window ids.
>
> Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
> ---
> drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
> index 83203d18865b..2843bfb36341 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
> @@ -1889,7 +1889,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, ROCKCHIP_VOP2_ESMART3 + 1) {
It would be better to add `ROCKCHIP_VOP_MAX_LAYER_ID` after ROCKCHIP_VOP2_ESMART3.
> win = vop2_find_win_by_phys_id(vop2, phys_id);
> if (win->data->layer_sel_id[vp->id] == ((layer_sel >> i) & 0xf)) {
> match = true;
>
--
Best,
Chaoyi
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH 6/8] drm/rockchip: vop2: Find the first mixer of a video port with Esmart3 too
@ 2026-11-10 9:56 ` Chaoyi Chen
0 siblings, 0 replies; 40+ messages in thread
From: Chaoyi Chen @ 2026-11-10 9:56 UTC (permalink / raw)
To: aiyengar1990
Cc: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer, dri-devel, linux-rockchip, linux-arm-kernel,
linux-kernel
Hi Arun,
On 11/10/2026 3:18 PM, Arun Iyengar via B4 Relay wrote:
> From: Arun Iyengar <aiyengar1990@gmail.com>
>
> vop2_find_start_mixer_id_for_vp() looks for the first layer that holds a
> window of the video port, but for_each_set_bit() takes the number of bits
> to search, and ROCKCHIP_VOP2_ESMART3 is the highest window id, so Esmart3
> is never found. A video port whose bottom layer shows Esmart3 then starts
> its alpha setup at the mixer of a higher layer, or, if Esmart3 is its
> only window, past all layers, and programs mixers of other video ports.
>
> Search all window ids.
>
> Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
> ---
> drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
> index 83203d18865b..2843bfb36341 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
> @@ -1889,7 +1889,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, ROCKCHIP_VOP2_ESMART3 + 1) {
It would be better to add `ROCKCHIP_VOP_MAX_LAYER_ID` after ROCKCHIP_VOP2_ESMART3.
> win = vop2_find_win_by_phys_id(vop2, phys_id);
> if (win->data->layer_sel_id[vp->id] == ((layer_sel >> i) & 0xf)) {
> match = true;
>
--
Best,
Chaoyi
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH 7/8] drm/rockchip: vop2: Keep the other LUT_PORT_SEL fields on gamma LUT updates
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
@ 2026-11-10 9:59 ` Chaoyi Chen
-1 siblings, 0 replies; 40+ messages in thread
From: Chaoyi Chen @ 2026-11-10 9:59 UTC (permalink / raw)
To: aiyengar1990
Cc: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer, dri-devel, linux-rockchip, linux-arm-kernel,
linux-kernel
On 11/10/2026 3:18 PM, Arun Iyengar via B4 Relay wrote:
> From: Arun Iyengar <aiyengar1990@gmail.com>
>
> On RK3588 the gamma LUT of a video port is written through a window that
> GAMMA_AHB_WRITE_SEL in SYS_CTRL_LUT_PORT_SEL selects. The register also
> holds the dclk source selection of video ports 0 and 2 and the enable of
> the video port 0/1 merge for modes wider than 4096 pixels, which writing
> the whole register clears.
>
> Only update the write select field.
>
> Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
Reviewed-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
> ---
> drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 5 +++--
> 1 file changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> index 3323e50224c5..9ad138457204 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> @@ -1774,8 +1774,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);
>
--
Best,
Chaoyi
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH 7/8] drm/rockchip: vop2: Keep the other LUT_PORT_SEL fields on gamma LUT updates
@ 2026-11-10 9:59 ` Chaoyi Chen
0 siblings, 0 replies; 40+ messages in thread
From: Chaoyi Chen @ 2026-11-10 9:59 UTC (permalink / raw)
To: aiyengar1990
Cc: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer, dri-devel, linux-rockchip, linux-arm-kernel,
linux-kernel
On 11/10/2026 3:18 PM, Arun Iyengar via B4 Relay wrote:
> From: Arun Iyengar <aiyengar1990@gmail.com>
>
> On RK3588 the gamma LUT of a video port is written through a window that
> GAMMA_AHB_WRITE_SEL in SYS_CTRL_LUT_PORT_SEL selects. The register also
> holds the dclk source selection of video ports 0 and 2 and the enable of
> the video port 0/1 merge for modes wider than 4096 pixels, which writing
> the whole register clears.
>
> Only update the write select field.
>
> Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
Reviewed-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
> ---
> drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 5 +++--
> 1 file changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> index 3323e50224c5..9ad138457204 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> @@ -1774,8 +1774,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);
>
--
Best,
Chaoyi
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH 5/8] drm/rockchip: vop2: Report POST_BUF_EMPTY at most once per frame
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
@ 2026-11-10 10:54 ` Chaoyi Chen
-1 siblings, 0 replies; 40+ messages in thread
From: Chaoyi Chen @ 2026-11-10 10:54 UTC (permalink / raw)
To: aiyengar1990
Cc: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer, dri-devel, linux-rockchip, linux-arm-kernel,
linux-kernel
Hi Arun,
On 11/10/2026 3:18 PM, Arun Iyengar via B4 Relay wrote:
> From: Arun Iyengar <aiyengar1990@gmail.com>
>
> POST_BUF_EMPTY is raised for every line on which the post processing
> stage of a video port finds no data. The interrupt handler only
> rate-limits the error message and leaves the interrupt enabled, so an
> underflowing video port keeps the CPU in the interrupt handler 50000 to
> 300000 times per second, for as long as the configuration that causes
> the underflow stays in place (e.g. 120000 interrupts per second for
> 3840x2160@120 with two full screen layers on one AXI port).
>
> Mask POST_BUF_EMPTY when it fires and unmask it at the next frame start.
> The frame start (FS) interrupt is enabled for that frame even when
> vblank reporting is off, and disabled again once POST_BUF_EMPTY is
> re-armed. A new per video port lock serialises the interrupt enable
> bookkeeping between the interrupt handler and the vblank callbacks.
> The error is still reported, now at most once per frame and video port.
>
> Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
> ---
> drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 63 ++++++++++++++++++++++++++--
> drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 10 +++++
> 2 files changed, 70 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> index 9aeb90c6aa4b..3323e50224c5 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> @@ -841,6 +841,48 @@ static void vop2_crtc_disable_irq(struct vop2_video_port *vp, u32 irq)
> vop2_writel(vop2, RK3568_VP_INT_EN(vp->id), irq << 16);
> }
>
> +/*
> + * POST_BUF_EMPTY is raised for every line the post processing stage finds
> + * no data for, so an underflowing video port can raise it 100000 times per
> + * second. Report it at most once per frame: mask it when it fires and
> + * unmask it at the next frame start, using the FS interrupt even when
> + * vblank reporting is off.
> + */
> +static void vop2_crtc_enable_post_buf_empty_irq(struct vop2_video_port *vp)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&vp->irq_lock, flags);
> + vp->post_buf_empty_masked = false;
> + vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
> + spin_unlock_irqrestore(&vp->irq_lock, flags);
> +}
> +
> +static void vop2_crtc_handle_post_buf_empty(struct vop2_video_port *vp)
> +{
> + struct vop2 *vop2 = vp->vop2;
> +
> + spin_lock(&vp->irq_lock);
> + vop2_crtc_disable_irq(vp, VP_INT_POST_BUF_EMPTY);
> + vp->post_buf_empty_masked = true;
> + /* don't clear a pending FS, it may carry a vblank event */
> + vop2_writel(vop2, RK3568_VP_INT_EN(vp->id),
> + VP_INT_FS_FIELD << 16 | VP_INT_FS_FIELD);
Do we really need this? Disabling the POST_BUF_EMPTY IRQ should not
affect the FS_FIELD IRQ, because this register has write mask.
> + spin_unlock(&vp->irq_lock);
> +}
> +
> +static void vop2_crtc_rearm_post_buf_empty(struct vop2_video_port *vp)
> +{
> + spin_lock(&vp->irq_lock);
> + if (vp->post_buf_empty_masked) {
> + vp->post_buf_empty_masked = false;
> + vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
> + if (!vp->vblank_enabled)
> + vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
> + }
> + spin_unlock(&vp->irq_lock);
> +}
> +
> static int vop2_core_clks_prepare_enable(struct vop2 *vop2)
> {
> int ret;
> @@ -1668,8 +1710,12 @@ static const struct drm_plane_funcs vop2_plane_funcs = {
> static int vop2_crtc_enable_vblank(struct drm_crtc *crtc)
> {
> struct vop2_video_port *vp = to_vop2_video_port(crtc);
> + unsigned long flags;
>
> + spin_lock_irqsave(&vp->irq_lock, flags);
> + vp->vblank_enabled = true;
> vop2_crtc_enable_irq(vp, VP_INT_FS_FIELD);
> + spin_unlock_irqrestore(&vp->irq_lock, flags);
>
> return 0;
> }
> @@ -1677,8 +1723,13 @@ static int vop2_crtc_enable_vblank(struct drm_crtc *crtc)
> static void vop2_crtc_disable_vblank(struct drm_crtc *crtc)
> {
> struct vop2_video_port *vp = to_vop2_video_port(crtc);
> + unsigned long flags;
>
> - vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
> + spin_lock_irqsave(&vp->irq_lock, flags);
> + vp->vblank_enabled = false;
> + if (!vp->post_buf_empty_masked)
> + vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
> + spin_unlock_irqrestore(&vp->irq_lock, flags);
> }
>
> static enum drm_mode_status vop2_crtc_mode_valid(struct drm_crtc *crtc,
> @@ -1977,7 +2028,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
>
> vcstate->yuv_overlay = is_yuv_output(vcstate->bus_format);
>
> - vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
> + vop2_crtc_enable_post_buf_empty_irq(vp);
>
> polflags = 0;
> if (vcstate->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
> @@ -2535,6 +2586,7 @@ static irqreturn_t rk3576_vp_isr(int irq, void *data)
> }
>
> if (irqs & VP_INT_FS_FIELD) {
> + vop2_crtc_rearm_post_buf_empty(vp);
> drm_crtc_handle_vblank(crtc);
> spin_lock(&crtc->dev->event_lock);
> if (vp->event) {
> @@ -2552,6 +2604,7 @@ static irqreturn_t rk3576_vp_isr(int irq, void *data)
> }
>
> if (irqs & VP_INT_POST_BUF_EMPTY) {
> + vop2_crtc_handle_post_buf_empty(vp);
> drm_err_ratelimited(vop2->drm, "POST_BUF_EMPTY irq err at vp%d\n", vp->id);
> ret = IRQ_HANDLED;
> }
> @@ -2591,6 +2644,7 @@ static irqreturn_t vop2_isr(int irq, void *data)
> }
>
> if (irqs & VP_INT_FS_FIELD) {
> + vop2_crtc_rearm_post_buf_empty(vp);
> drm_crtc_handle_vblank(crtc);
> spin_lock(&crtc->dev->event_lock);
> if (vp->event) {
> @@ -2608,6 +2662,7 @@ static irqreturn_t vop2_isr(int irq, void *data)
> }
>
> if (irqs & VP_INT_POST_BUF_EMPTY) {
> + vop2_crtc_handle_post_buf_empty(vp);
> drm_err_ratelimited(vop2->drm,
> "POST_BUF_EMPTY irq err at vp%d\n",
> vp->id);
> @@ -2984,7 +3039,7 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
> struct vop2 *vop2;
> struct resource *res;
> size_t alloc_size;
> - int ret;
> + int ret, i;
>
> vop2_data = of_device_get_match_data(dev);
> if (!vop2_data)
> @@ -3102,6 +3157,8 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
> mutex_init(&vop2->vop2_lock);
> mutex_init(&vop2->ovl_lock);
> spin_lock_init(&vop2->axi_lock);
> + for (i = 0; i < ARRAY_SIZE(vop2->vps); i++)
> + spin_lock_init(&vop2->vps[i].irq_lock);
>
> ret = devm_request_irq(dev, vop2->irq, vop2_isr, IRQF_SHARED, dev_name(dev), vop2);
> if (ret)
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
> index f1e35967e67f..7de32bef8d7b 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
> @@ -272,6 +272,16 @@ struct vop2_video_port {
> struct drm_pending_vblank_event *event;
>
> unsigned int nlayers;
> +
> + /** @irq_lock: protects @vblank_enabled and @post_buf_empty_masked */
> + spinlock_t irq_lock;
> + /** @vblank_enabled: the FS interrupt is enabled for vblank reporting */
> + bool vblank_enabled;
> + /**
> + * @post_buf_empty_masked: POST_BUF_EMPTY is masked until the next
> + * frame start
> + */
> + bool post_buf_empty_masked;
> };
>
> /**
>
--
Best,
Chaoyi
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH 5/8] drm/rockchip: vop2: Report POST_BUF_EMPTY at most once per frame
@ 2026-11-10 10:54 ` Chaoyi Chen
0 siblings, 0 replies; 40+ messages in thread
From: Chaoyi Chen @ 2026-11-10 10:54 UTC (permalink / raw)
To: aiyengar1990
Cc: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sascha Hauer, dri-devel, linux-rockchip, linux-arm-kernel,
linux-kernel
Hi Arun,
On 11/10/2026 3:18 PM, Arun Iyengar via B4 Relay wrote:
> From: Arun Iyengar <aiyengar1990@gmail.com>
>
> POST_BUF_EMPTY is raised for every line on which the post processing
> stage of a video port finds no data. The interrupt handler only
> rate-limits the error message and leaves the interrupt enabled, so an
> underflowing video port keeps the CPU in the interrupt handler 50000 to
> 300000 times per second, for as long as the configuration that causes
> the underflow stays in place (e.g. 120000 interrupts per second for
> 3840x2160@120 with two full screen layers on one AXI port).
>
> Mask POST_BUF_EMPTY when it fires and unmask it at the next frame start.
> The frame start (FS) interrupt is enabled for that frame even when
> vblank reporting is off, and disabled again once POST_BUF_EMPTY is
> re-armed. A new per video port lock serialises the interrupt enable
> bookkeeping between the interrupt handler and the vblank callbacks.
> The error is still reported, now at most once per frame and video port.
>
> Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
> ---
> drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 63 ++++++++++++++++++++++++++--
> drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 10 +++++
> 2 files changed, 70 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> index 9aeb90c6aa4b..3323e50224c5 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
> @@ -841,6 +841,48 @@ static void vop2_crtc_disable_irq(struct vop2_video_port *vp, u32 irq)
> vop2_writel(vop2, RK3568_VP_INT_EN(vp->id), irq << 16);
> }
>
> +/*
> + * POST_BUF_EMPTY is raised for every line the post processing stage finds
> + * no data for, so an underflowing video port can raise it 100000 times per
> + * second. Report it at most once per frame: mask it when it fires and
> + * unmask it at the next frame start, using the FS interrupt even when
> + * vblank reporting is off.
> + */
> +static void vop2_crtc_enable_post_buf_empty_irq(struct vop2_video_port *vp)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&vp->irq_lock, flags);
> + vp->post_buf_empty_masked = false;
> + vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
> + spin_unlock_irqrestore(&vp->irq_lock, flags);
> +}
> +
> +static void vop2_crtc_handle_post_buf_empty(struct vop2_video_port *vp)
> +{
> + struct vop2 *vop2 = vp->vop2;
> +
> + spin_lock(&vp->irq_lock);
> + vop2_crtc_disable_irq(vp, VP_INT_POST_BUF_EMPTY);
> + vp->post_buf_empty_masked = true;
> + /* don't clear a pending FS, it may carry a vblank event */
> + vop2_writel(vop2, RK3568_VP_INT_EN(vp->id),
> + VP_INT_FS_FIELD << 16 | VP_INT_FS_FIELD);
Do we really need this? Disabling the POST_BUF_EMPTY IRQ should not
affect the FS_FIELD IRQ, because this register has write mask.
> + spin_unlock(&vp->irq_lock);
> +}
> +
> +static void vop2_crtc_rearm_post_buf_empty(struct vop2_video_port *vp)
> +{
> + spin_lock(&vp->irq_lock);
> + if (vp->post_buf_empty_masked) {
> + vp->post_buf_empty_masked = false;
> + vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
> + if (!vp->vblank_enabled)
> + vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
> + }
> + spin_unlock(&vp->irq_lock);
> +}
> +
> static int vop2_core_clks_prepare_enable(struct vop2 *vop2)
> {
> int ret;
> @@ -1668,8 +1710,12 @@ static const struct drm_plane_funcs vop2_plane_funcs = {
> static int vop2_crtc_enable_vblank(struct drm_crtc *crtc)
> {
> struct vop2_video_port *vp = to_vop2_video_port(crtc);
> + unsigned long flags;
>
> + spin_lock_irqsave(&vp->irq_lock, flags);
> + vp->vblank_enabled = true;
> vop2_crtc_enable_irq(vp, VP_INT_FS_FIELD);
> + spin_unlock_irqrestore(&vp->irq_lock, flags);
>
> return 0;
> }
> @@ -1677,8 +1723,13 @@ static int vop2_crtc_enable_vblank(struct drm_crtc *crtc)
> static void vop2_crtc_disable_vblank(struct drm_crtc *crtc)
> {
> struct vop2_video_port *vp = to_vop2_video_port(crtc);
> + unsigned long flags;
>
> - vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
> + spin_lock_irqsave(&vp->irq_lock, flags);
> + vp->vblank_enabled = false;
> + if (!vp->post_buf_empty_masked)
> + vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
> + spin_unlock_irqrestore(&vp->irq_lock, flags);
> }
>
> static enum drm_mode_status vop2_crtc_mode_valid(struct drm_crtc *crtc,
> @@ -1977,7 +2028,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
>
> vcstate->yuv_overlay = is_yuv_output(vcstate->bus_format);
>
> - vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
> + vop2_crtc_enable_post_buf_empty_irq(vp);
>
> polflags = 0;
> if (vcstate->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
> @@ -2535,6 +2586,7 @@ static irqreturn_t rk3576_vp_isr(int irq, void *data)
> }
>
> if (irqs & VP_INT_FS_FIELD) {
> + vop2_crtc_rearm_post_buf_empty(vp);
> drm_crtc_handle_vblank(crtc);
> spin_lock(&crtc->dev->event_lock);
> if (vp->event) {
> @@ -2552,6 +2604,7 @@ static irqreturn_t rk3576_vp_isr(int irq, void *data)
> }
>
> if (irqs & VP_INT_POST_BUF_EMPTY) {
> + vop2_crtc_handle_post_buf_empty(vp);
> drm_err_ratelimited(vop2->drm, "POST_BUF_EMPTY irq err at vp%d\n", vp->id);
> ret = IRQ_HANDLED;
> }
> @@ -2591,6 +2644,7 @@ static irqreturn_t vop2_isr(int irq, void *data)
> }
>
> if (irqs & VP_INT_FS_FIELD) {
> + vop2_crtc_rearm_post_buf_empty(vp);
> drm_crtc_handle_vblank(crtc);
> spin_lock(&crtc->dev->event_lock);
> if (vp->event) {
> @@ -2608,6 +2662,7 @@ static irqreturn_t vop2_isr(int irq, void *data)
> }
>
> if (irqs & VP_INT_POST_BUF_EMPTY) {
> + vop2_crtc_handle_post_buf_empty(vp);
> drm_err_ratelimited(vop2->drm,
> "POST_BUF_EMPTY irq err at vp%d\n",
> vp->id);
> @@ -2984,7 +3039,7 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
> struct vop2 *vop2;
> struct resource *res;
> size_t alloc_size;
> - int ret;
> + int ret, i;
>
> vop2_data = of_device_get_match_data(dev);
> if (!vop2_data)
> @@ -3102,6 +3157,8 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
> mutex_init(&vop2->vop2_lock);
> mutex_init(&vop2->ovl_lock);
> spin_lock_init(&vop2->axi_lock);
> + for (i = 0; i < ARRAY_SIZE(vop2->vps); i++)
> + spin_lock_init(&vop2->vps[i].irq_lock);
>
> ret = devm_request_irq(dev, vop2->irq, vop2_isr, IRQF_SHARED, dev_name(dev), vop2);
> if (ret)
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
> index f1e35967e67f..7de32bef8d7b 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
> @@ -272,6 +272,16 @@ struct vop2_video_port {
> struct drm_pending_vblank_event *event;
>
> unsigned int nlayers;
> +
> + /** @irq_lock: protects @vblank_enabled and @post_buf_empty_masked */
> + spinlock_t irq_lock;
> + /** @vblank_enabled: the FS interrupt is enabled for vblank reporting */
> + bool vblank_enabled;
> + /**
> + * @post_buf_empty_masked: POST_BUF_EMPTY is masked until the next
> + * frame start
> + */
> + bool post_buf_empty_masked;
> };
>
> /**
>
--
Best,
Chaoyi
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 40+ messages in thread
end of thread, other threads:[~2026-11-10 10:55 UTC | newest]
Thread overview: 40+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-10 7:18 [PATCH 0/8] drm/rockchip: vop2: RK3588 clocking, sync polarity, AXI port and window fixes Arun Iyengar
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:18 ` [PATCH 1/8] drm/rockchip: vop2: Don't use uninitialised dividers on RK3588 Arun Iyengar
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:18 ` [PATCH 2/8] drm/rockchip: vop2: Fix the HDMI sync polarity " Arun Iyengar
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:18 ` [PATCH 3/8] drm/rockchip: vop2: Don't accept AFBC YTR for RGB565 " Arun Iyengar
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:18 ` [PATCH 4/8] drm/rockchip: vop2: Balance window fetches across both AXI ports " Arun Iyengar
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:58 ` sashiko-bot
2026-11-10 7:18 ` [PATCH 5/8] drm/rockchip: vop2: Report POST_BUF_EMPTY at most once per frame Arun Iyengar
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 8:11 ` sashiko-bot
2026-11-10 10:54 ` Chaoyi Chen
2026-11-10 10:54 ` Chaoyi Chen
2026-11-10 7:18 ` [PATCH 6/8] drm/rockchip: vop2: Find the first mixer of a video port with Esmart3 too Arun Iyengar
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 8:16 ` sashiko-bot
2026-11-10 9:56 ` Chaoyi Chen
2026-11-10 9:56 ` Chaoyi Chen
2026-11-10 7:18 ` [PATCH 7/8] drm/rockchip: vop2: Keep the other LUT_PORT_SEL fields on gamma LUT updates Arun Iyengar
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 8:22 ` sashiko-bot
2026-11-10 9:59 ` Chaoyi Chen
2026-11-10 9:59 ` Chaoyi Chen
2026-11-10 7:18 ` [PATCH 8/8] drm/rockchip: vop2: Fix the AFBC offsets of a source that starts inside a superblock Arun Iyengar
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 7:18 ` Arun Iyengar via B4 Relay
2026-11-10 8:44 ` sashiko-bot
2026-11-10 9:39 ` [PATCH 0/8] drm/rockchip: vop2: RK3588 clocking, sync polarity, AXI port and window fixes Chaoyi Chen
2026-11-10 9:39 ` Chaoyi Chen
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.