]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
drm/rockchip: vop2: Fix the update of LAYER/PORT select registers when there are...
authorAndy Yan <andy.yan@rock-chips.com>
Mon, 21 Apr 2025 10:21:54 +0000 (18:21 +0800)
committerHeiko Stuebner <heiko@sntech.de>
Tue, 8 Jul 2025 15:44:08 +0000 (17:44 +0200)
The all video ports of rk3568/rk3588 share the same OVL_LAYER_SEL
and OVL_PORT_SEL registers, and the configuration of these two registers
can be set to take effect when the vsync signal arrives at a certain Video
Port.

If two threads for two display output choose to update these two registers
simultaneously to meet their own plane adjustment requirements(change plane
zpos or switch plane from one crtc to another), then no matter which Video
Port'svsync signal we choose to follow for these two registers, the display
output of the other Video Port will be abnormal.
This is because the configuration of this Video Port does not take
effect at the right time (its configuration should take effect when its
VSYNC signal arrives).

In order to solve this problem, when performing plane migration or
change the zpos of planes, there are two things to be observed and
followed:

1. When a plane is migrated from one VP to another, the configuration of
   the layer can only take effect after the Port mux configuration is
   enabled.

2. When change the zpos of planes, we must ensure that the change for
   the previous VP takes effect before we proceed to change the next VP.
   Otherwise, the new configuration might overwrite the previous one for
   the previous VP, or it could lead to the configuration of the previous
   VP being take effect along with the VSYNC of the new VP.

This issue only occurs in scenarios where multi-display output is enabled.

Fixes: c5996e4ab109 ("drm/rockchip: vop2: Make overlay layer select register configuration take effect by vsync")
Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
Signed-off-by: Heiko Stuebner <heiko@sntech.de>
Link: https://lore.kernel.org/r/20250421102156.424480-1-andyshrk@163.com
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c

index 6b37ce3ee60b82cf7173f328c90e3da60ef51741..186f6452a7d359f079662bc580850929632ea8fe 100644 (file)
@@ -146,25 +146,6 @@ static void vop2_unlock(struct vop2 *vop2)
        mutex_unlock(&vop2->vop2_lock);
 }
 
-/*
- * Note:
- * The write mask function is documented but missing on rk3566/8, writes
- * to these bits have no effect. For newer soc(rk3588 and following) the
- * write mask is needed for register writes.
- *
- * GLB_CFG_DONE_EN has no write mask bit.
- *
- */
-static void vop2_cfg_done(struct vop2_video_port *vp)
-{
-       struct vop2 *vop2 = vp->vop2;
-       u32 val = RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN;
-
-       val |= BIT(vp->id) | (BIT(vp->id) << 16);
-
-       regmap_set_bits(vop2->map, RK3568_REG_CFG_DONE, val);
-}
-
 static void vop2_win_disable(struct vop2_win *win)
 {
        vop2_win_write(win, VOP2_WIN_ENABLE, 0);
@@ -854,6 +835,11 @@ static void vop2_enable(struct vop2 *vop2)
        if (vop2->version == VOP_VERSION_RK3588)
                rk3588_vop2_power_domain_enable_all(vop2);
 
+       if (vop2->version <= VOP_VERSION_RK3588) {
+               vop2->old_layer_sel = vop2_readl(vop2, RK3568_OVL_LAYER_SEL);
+               vop2->old_port_sel = vop2_readl(vop2, RK3568_OVL_PORT_SEL);
+       }
+
        vop2_writel(vop2, RK3568_REG_CFG_DONE, RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN);
 
        /*
@@ -2728,6 +2714,7 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
                return dev_err_probe(drm->dev, vop2->irq, "cannot find irq for vop2\n");
 
        mutex_init(&vop2->vop2_lock);
+       mutex_init(&vop2->ovl_lock);
 
        ret = devm_request_irq(dev, vop2->irq, vop2_isr, IRQF_SHARED, dev_name(dev), vop2);
        if (ret)
index fc3ecb9fcd9576d20c0fdfa8df469dfbff6605da..fa5c56f16047e3493e82fbedaced221459696dcc 100644 (file)
@@ -334,6 +334,19 @@ struct vop2 {
        /* optional internal rgb encoder */
        struct rockchip_rgb *rgb;
 
+       /*
+        * Used to record layer selection configuration on rk356x/rk3588
+        * as register RK3568_OVL_LAYER_SEL and RK3568_OVL_PORT_SEL are
+        * shared for all the Video Ports.
+        */
+       u32 old_layer_sel;
+       u32 old_port_sel;
+       /*
+        * Ensure that the updates to these two registers(RKK3568_OVL_LAYER_SEL/RK3568_OVL_PORT_SEL)
+        * take effect in sequence.
+        */
+       struct mutex ovl_lock;
+
        /* must be put at the end of the struct */
        struct vop2_win win[];
 };
@@ -727,6 +740,7 @@ enum dst_factor_mode {
 #define RK3588_OVL_PORT_SEL__CLUSTER2                  GENMASK(21, 20)
 #define RK3568_OVL_PORT_SEL__CLUSTER1                  GENMASK(19, 18)
 #define RK3568_OVL_PORT_SEL__CLUSTER0                  GENMASK(17, 16)
+#define RK3588_OVL_PORT_SET__PORT3_MUX                 GENMASK(15, 12)
 #define RK3568_OVL_PORT_SET__PORT2_MUX                 GENMASK(11, 8)
 #define RK3568_OVL_PORT_SET__PORT1_MUX                 GENMASK(7, 4)
 #define RK3568_OVL_PORT_SET__PORT0_MUX                 GENMASK(3, 0)
@@ -831,4 +845,23 @@ static inline struct vop2_win *to_vop2_win(struct drm_plane *p)
        return container_of(p, struct vop2_win, base);
 }
 
+/*
+ * Note:
+ * The write mask function is documented but missing on rk3566/8, writes
+ * to these bits have no effect. For newer soc(rk3588 and following) the
+ * write mask is needed for register writes.
+ *
+ * GLB_CFG_DONE_EN has no write mask bit.
+ *
+ */
+static inline void vop2_cfg_done(struct vop2_video_port *vp)
+{
+       struct vop2 *vop2 = vp->vop2;
+       u32 val = RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN;
+
+       val |= BIT(vp->id) | (BIT(vp->id) << 16);
+
+       regmap_set_bits(vop2->map, RK3568_REG_CFG_DONE, val);
+}
+
 #endif /* _ROCKCHIP_DRM_VOP2_H */
index 32c4ed6857395a953bef8cd800b510fbdf7d9cec..45c5e39878133163ee05c25a6a99004313f5ed19 100644 (file)
@@ -2052,12 +2052,55 @@ static void vop2_setup_alpha(struct vop2_video_port *vp)
        }
 }
 
+static u32 rk3568_vop2_read_port_mux(struct vop2 *vop2)
+{
+       return vop2_readl(vop2, RK3568_OVL_PORT_SEL);
+}
+
+static void rk3568_vop2_wait_for_port_mux_done(struct vop2 *vop2)
+{
+       u32 port_mux_sel;
+       int ret;
+
+       /*
+        * Spin until the previous port_mux figuration is done.
+        */
+       ret = readx_poll_timeout_atomic(rk3568_vop2_read_port_mux, vop2, port_mux_sel,
+                                       port_mux_sel == vop2->old_port_sel, 0, 50 * 1000);
+       if (ret)
+               DRM_DEV_ERROR(vop2->dev, "wait port_mux done timeout: 0x%x--0x%x\n",
+                             port_mux_sel, vop2->old_port_sel);
+}
+
+static u32 rk3568_vop2_read_layer_cfg(struct vop2 *vop2)
+{
+       return vop2_readl(vop2, RK3568_OVL_LAYER_SEL);
+}
+
+static void rk3568_vop2_wait_for_layer_cfg_done(struct vop2 *vop2, u32 cfg)
+{
+       u32 atv_layer_cfg;
+       int ret;
+
+       /*
+        * Spin until the previous layer configuration is done.
+        */
+       ret = readx_poll_timeout_atomic(rk3568_vop2_read_layer_cfg, vop2, atv_layer_cfg,
+                                       atv_layer_cfg == cfg, 0, 50 * 1000);
+       if (ret)
+               DRM_DEV_ERROR(vop2->dev, "wait layer cfg done timeout: 0x%x--0x%x\n",
+                             atv_layer_cfg, cfg);
+}
+
 static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp)
 {
        struct vop2 *vop2 = vp->vop2;
        struct drm_plane *plane;
        u32 layer_sel = 0;
        u32 port_sel;
+       u32 old_layer_sel = 0;
+       u32 atv_layer_sel = 0;
+       u32 old_port_sel = 0;
        u8 layer_id;
        u8 old_layer_id;
        u8 layer_sel_id;
@@ -2069,19 +2112,18 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp)
        struct vop2_video_port *vp2 = &vop2->vps[2];
        struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(vp->crtc.state);
 
+       mutex_lock(&vop2->ovl_lock);
        ovl_ctrl = vop2_readl(vop2, RK3568_OVL_CTRL);
        ovl_ctrl &= ~RK3568_OVL_CTRL__LAYERSEL_REGDONE_IMD;
        ovl_ctrl &= ~RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL;
-       ovl_ctrl |= FIELD_PREP(RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL, vp->id);
 
        if (vcstate->yuv_overlay)
                ovl_ctrl |= RK3568_OVL_CTRL__YUV_MODE(vp->id);
        else
                ovl_ctrl &= ~RK3568_OVL_CTRL__YUV_MODE(vp->id);
 
-       vop2_writel(vop2, RK3568_OVL_CTRL, ovl_ctrl);
-
-       port_sel = vop2_readl(vop2, RK3568_OVL_PORT_SEL);
+       old_port_sel = vop2->old_port_sel;
+       port_sel = old_port_sel;
        port_sel &= RK3568_OVL_PORT_SEL__SEL_PORT;
 
        if (vp0->nlayers)
@@ -2102,7 +2144,13 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp)
        else
                port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT2_MUX, 8);
 
-       layer_sel = vop2_readl(vop2, RK3568_OVL_LAYER_SEL);
+       /* Fixed value for rk3588 */
+       if (vop2->version == VOP_VERSION_RK3588)
+               port_sel |= FIELD_PREP(RK3588_OVL_PORT_SET__PORT3_MUX, 7);
+
+       atv_layer_sel = vop2_readl(vop2, RK3568_OVL_LAYER_SEL);
+       old_layer_sel = vop2->old_layer_sel;
+       layer_sel = old_layer_sel;
 
        ofs = 0;
        for (i = 0; i < vp->id; i++)
@@ -2186,8 +2234,37 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp)
                             old_win->data->layer_sel_id[vp->id]);
        }
 
+       vop2->old_layer_sel = layer_sel;
+       vop2->old_port_sel = port_sel;
+       /*
+        * As the RK3568_OVL_LAYER_SEL and RK3568_OVL_PORT_SEL are shared by all Video Ports,
+        * and the configuration take effect by one Video Port's vsync.
+        * When performing layer migration or change the zpos of layers, there are two things
+        * to be observed and followed:
+        * 1. When a layer is migrated from one VP to another, the configuration of the layer
+        *    can only take effect after the Port mux configuration is enabled.
+        *
+        * 2. When we change the zpos of layers, we must ensure that the change for the previous
+        *    VP takes effect before we proceed to change the next VP. Otherwise, the new
+        *    configuration might overwrite the previous one for the previous VP, or it could
+        *    lead to the configuration of the previous VP being take effect along with the VSYNC
+        *    of the new VP.
+        */
+       if (layer_sel != old_layer_sel || port_sel != old_port_sel)
+               ovl_ctrl |= FIELD_PREP(RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL, vp->id);
+       vop2_writel(vop2, RK3568_OVL_CTRL, ovl_ctrl);
+
+       if (port_sel != old_port_sel) {
+               vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel);
+               vop2_cfg_done(vp);
+               rk3568_vop2_wait_for_port_mux_done(vop2);
+       }
+
+       if (layer_sel != old_layer_sel && atv_layer_sel != old_layer_sel)
+               rk3568_vop2_wait_for_layer_cfg_done(vop2, vop2->old_layer_sel);
+
        vop2_writel(vop2, RK3568_OVL_LAYER_SEL, layer_sel);
-       vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel);
+       mutex_unlock(&vop2->ovl_lock);
 }
 
 static void rk3568_vop2_setup_dly_for_windows(struct vop2_video_port *vp)