From aacefe9c743c70f66834d916bd50a05ebbb6682e Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Fri, 25 Feb 2022 20:31:08 +0800 Subject: [PATCH] drm/rockchip: vop2: A workaround for PD_CLUSTER0 off The internal PD of VOP2 on rk3588 take effect immediately for power up and take effect by vsync for power down. And the PD_CLUSTER0 is a parent PD of PD_CLUSTER1/2/3, we may have this use case: Cluster0 is attached to VP0 for HDMI output, Cluster1 is attached to VP1 for MIPI DSI, When we enable Cluster1 on VP1, we should enable PD_CLUSTER0 as it is the parent PD, event though HDMI is plugout, VP1 is disabled, the PD of Cluster0 should keep power on. When system go to suspend: (1) Power down PD of Cluster1 before VP1 standby(the power down is take effect by vsync) (2) Power down PD of Cluster0 But we have problem at step (2), Cluster0 is attached to VP0. bus VP0 is in standby mode, as it is never used or hdmi plugout. So there is no vsync, the power down will never take effect. According to IC designer: We must power down all internal PD of VOP before we power down the global PD_VOP. So we get this workaround: We we found a VP is in standby mode when we want power down a PD is attached to it, we release the VP from standby mode, than it will run a default timing and generate vsync. Than we can power down the PD by this vsync. After all this is done, we standby the VP at last. Signed-off-by: Andy Yan Change-Id: Ib9be8628f07d783c6bc3b7678c5eebfc63aabe1c --- drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 12 ++ drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 116 ++++++++++++++----- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 13 ++- 3 files changed, 113 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h index 71dc623324b9..0ee0a030d2d1 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h @@ -102,6 +102,12 @@ enum vop2_win_dly_mode { VOP2_DLY_MODE_MAX, }; +/* + * vop2 dsc id + */ +#define ROCKCHIP_VOP2_DSC_8K 0 +#define ROCKCHIP_VOP2_DSC_4K 1 + /* * vop2 internal power domain id, * should be all none zero, 0 will be @@ -772,6 +778,12 @@ struct vop2_wb_regs { struct vop2_power_domain_data { uint8_t id; uint8_t parent_id; + /* + * @module_id_mask: module id of which module this power domain is belongs to. + * PD_CLUSTER0,1,2,3 only belongs to CLUSTER0/1/2/3, PD_Esmart0 shared by Esmart1/2/3 + */ + uint32_t module_id_mask; + const struct vop2_power_domain_regs *regs; }; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 9ac8bba84366..d6700487b99d 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -231,6 +231,7 @@ struct vop2_power_domain { * If the module powered by this power domain was enabled. */ bool module_on; + const struct vop2_power_domain_data *data; struct list_head list; struct delayed_work power_off_work; @@ -1435,7 +1436,8 @@ static void vop2_wait_power_domain_off(struct vop2_power_domain *pd) ret = readx_poll_timeout_atomic(vop2_power_domain_status, pd, val, !val, 0, 50 * 1000); if (ret) - DRM_DEV_ERROR(vop2->dev, "wait pd%d off timeout\n", ffs(pd->data->id) - 1); + DRM_DEV_ERROR(vop2->dev, "wait pd%d off timeout power_ctrl: 0x%x\n", + ffs(pd->data->id) - 1, vop2_readl(vop2, 0x34)); } static void vop2_wait_power_domain_on(struct vop2_power_domain *pd) @@ -1446,7 +1448,8 @@ static void vop2_wait_power_domain_on(struct vop2_power_domain *pd) ret = readx_poll_timeout_atomic(vop2_power_domain_status, pd, val, val, 0, 50 * 1000); if (ret) - DRM_DEV_ERROR(vop2->dev, "wait pd%d on timeout\n", ffs(pd->data->id) - 1); + DRM_DEV_ERROR(vop2->dev, "wait pd%d on timeout power_ctrl: 0x%x\n", + ffs(pd->data->id) - 1, vop2_readl(vop2, 0x34)); } /* @@ -1498,16 +1501,15 @@ static void vop2_power_domain_put(struct vop2_power_domain *pd) /* * For a nested power domain(PD_Cluster0 is the parent of PD_CLuster1/2/3) - * the parent powe domain must be enabled before child power domain + * the parent power domain must be enabled before child power domain * is on. * - * So we may met this condition: Cluster0 is not enabled, but PD_Cluster0 - * must enabled as one of the child PD_CLUSTER1/2/3 is enabled. + * So we may met this condition: Cluster0 is not on a activated VP, + * but PD_Cluster0 must enabled as one of the child PD_CLUSTER1/2/3 is enabled. * when all child PD is disabled, we want disable the parent - * PD(PD_CLUSTER0), but as module CLUSTER0 is not enabled, - * the turn down configuration will never take effect. - * so we will see a "wait pd0 off timeout" log when we - * turn on PD_CLUSTER0 next time. + * PD(PD_CLUSTER0), but as module CLUSTER0 is not attcthed on a activated VP, + * the turn down operation(which is take effect by vsync) will never take effect. + * so we will see a "wait pd0 off timeout" log when we turn on PD_CLUSTER0 next time. * * So don't try to turn off a power domain when the module is not * enabled. @@ -3243,18 +3245,6 @@ static void vop2_initial(struct drm_crtc *crtc) return; } - /* - * we should rest axi to clear register state - * when set pd_off_imd in vop2_power_off_all_pd, - * otherwise the bit0/1/2 of POWER_CTRL register - * will auto cleared after we clear pd_off_imd - * in the following. - */ - if (vop2->version == VOP_VERSION_RK3588) { - if (!vp->loader_protect) - vop2_clk_reset(vop2->axi_rst); - } - if (vop2_soc_is_rk3566()) VOP_CTRL_SET(vop2, otp_en, 1); @@ -3266,7 +3256,6 @@ static void vop2_initial(struct drm_crtc *crtc) else memcpy(vop2->regsbak, vop2->regs, vop2->len); - VOP_CTRL_SET(vop2, pd_off_imd, 0); VOP_MODULE_SET(vop2, wb, axi_yrgb_id, 0xd); VOP_MODULE_SET(vop2, wb, axi_uv_id, 0xe); vop2_wb_cfg_done(vp); @@ -3307,14 +3296,89 @@ static void vop2_initial(struct drm_crtc *crtc) vp->id, ret); } +/* + * The internal PD of VOP2 on rk3588 take effect immediately + * for power up and take effect by vsync for power down. + * + * And the PD_CLUSTER0 is a parent PD of PD_CLUSTER1/2/3, + * we may have this use case: + * Cluster0 is attached to VP0 for HDMI output, + * Cluster1 is attached to VP1 for MIPI DSI, + + * When we enable Cluster1 on VP1, we should enable PD_CLUSTER0 as + * it is the parent PD, event though HDMI is plugout, VP1 is disabled, + * the PD of Cluster0 should keep power on. + + * When system go to suspend: + * (1) Power down PD of Cluster1 before VP1 standby(the power down is take + * effect by vsync) + * (2) Power down PD of Cluster0 + * + * But we have problem at step (2), Cluster0 is attached to VP0. but VP0 + * is in standby mode, as it is never used or hdmi plugout. So there is + * no vsync, the power down will never take effect. + + * According to IC designer: We must power down all internal PD of VOP + * before we power down the global PD_VOP. + + * So we get this workaround: + * We we found a VP is in standby mode when we want power down a PD is + * attached to it, we release the VP from standby mode, than it will + * run a default timing and generate vsync. Than we can power down the + * PD by this vsync. After all this is done, we standby the VP at last. + */ +static void vop2_power_domain_off_by_disabled_vp(struct vop2_power_domain *pd) +{ + struct vop2_video_port *vp = NULL; + struct vop2 *vop2 = pd->vop2; + struct vop2_win *win; + struct drm_crtc *crtc; + uint32_t vp_id; + uint8_t phys_id; + int ret; + + if (pd->data->id == VOP2_PD_CLUSTER0 || pd->data->id == VOP2_PD_CLUSTER1 || + pd->data->id == VOP2_PD_CLUSTER2 || pd->data->id == VOP2_PD_CLUSTER3) { + phys_id = ffs(pd->data->module_id_mask) - 1; + win = vop2_find_win_by_phys_id(vop2, phys_id); + vp_id = ffs(win->vp_mask) - 1; + vp = &vop2->vps[vp_id]; + } else { + DRM_DEV_ERROR(vop2->dev, "unexpected power on pd%d\n", ffs(pd->data->id) - 1); + } + + if (vp) { + ret = clk_prepare_enable(vp->dclk); + if (ret < 0) + DRM_DEV_ERROR(vop2->dev, "failed to enable dclk for video port%d - %d\n", + vp->id, ret); + crtc = &vp->rockchip_crtc.crtc; + VOP_MODULE_SET(vop2, vp, aclk_en, 1); + VOP_MODULE_SET(vop2, vp, standby, 0); + vop2_power_domain_off(pd); + vop2_cfg_done(crtc); + vop2_wait_power_domain_off(pd); + + reinit_completion(&vp->dsp_hold_completion); + vop2_dsp_hold_valid_irq_enable(crtc); + VOP_MODULE_SET(vop2, vp, standby, 1); + ret = wait_for_completion_timeout(&vp->dsp_hold_completion, msecs_to_jiffies(50)); + if (!ret) + DRM_DEV_INFO(vop2->dev, "wait for vp%d dsp_hold timeout\n", vp->id); + + vop2_dsp_hold_valid_irq_disable(crtc); + VOP_MODULE_SET(vop2, vp, aclk_en, 0); + clk_disable_unprepare(vp->dclk); + } +} + static void vop2_power_off_all_pd(struct vop2 *vop2) { struct vop2_power_domain *pd, *n; - VOP_CTRL_SET(vop2, pd_off_imd, 1); list_for_each_entry_safe_reverse(pd, n, &vop2->pd_list_head, list) { - VOP_MODULE_SET(vop2, pd->data, pd, 1); - vop2_wait_power_domain_off(pd); + if (vop2_power_domain_status(pd)) + vop2_power_domain_off_by_disabled_vp(pd); pd->on = false; pd->module_on = false; } @@ -3330,7 +3394,6 @@ static void vop2_disable(struct drm_crtc *crtc) if (--vop2->enable_count > 0) return; - vop2->is_enabled = false; if (vop2->is_iommu_enabled) { /* * vop2 standby complete, so iommu detach is safe. @@ -3342,6 +3405,7 @@ static void vop2_disable(struct drm_crtc *crtc) if (vop2->version == VOP_VERSION_RK3588) vop2_power_off_all_pd(vop2); + vop2->is_enabled = false; pm_runtime_put_sync(vop2->dev); clk_disable_unprepare(vop2->pclk); diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 22bb89bc2137..7d7aa3930885 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -607,7 +607,7 @@ static const struct dsc_error_info dsc_buffer_flow[] = { static const struct vop2_dsc_data rk3588_vop_dsc_data[] = { { - .id = 0, + .id = ROCKCHIP_VOP2_DSC_8K, .pd_id = VOP2_PD_DSC_8K, .max_slice_num = 8, .max_linebuf_depth = 11, @@ -620,7 +620,7 @@ static const struct vop2_dsc_data rk3588_vop_dsc_data[] = { }, { - .id = 1, + .id = ROCKCHIP_VOP2_DSC_4K, .pd_id = VOP2_PD_DSC_4K, .max_slice_num = 2, .max_linebuf_depth = 11, @@ -2066,39 +2066,48 @@ const struct vop2_power_domain_regs rk3588_dsc_4k_pd_regs = { static const struct vop2_power_domain_data rk3588_vop_pd_data[] = { { .id = VOP2_PD_CLUSTER0, + .module_id_mask = BIT(ROCKCHIP_VOP2_CLUSTER0), .regs = &rk3588_cluster0_pd_regs, }, { .id = VOP2_PD_CLUSTER1, + .module_id_mask = BIT(ROCKCHIP_VOP2_CLUSTER1), .parent_id = VOP2_PD_CLUSTER0, .regs = &rk3588_cluster1_pd_regs, }, { .id = VOP2_PD_CLUSTER2, + .module_id_mask = BIT(ROCKCHIP_VOP2_CLUSTER2), .parent_id = VOP2_PD_CLUSTER0, .regs = &rk3588_cluster2_pd_regs, }, { .id = VOP2_PD_CLUSTER3, + .module_id_mask = BIT(ROCKCHIP_VOP2_CLUSTER3), .parent_id = VOP2_PD_CLUSTER0, .regs = &rk3588_cluster3_pd_regs, }, { .id = VOP2_PD_ESMART, + .module_id_mask = BIT(ROCKCHIP_VOP2_ESMART1) | + BIT(ROCKCHIP_VOP2_ESMART2) | + BIT(ROCKCHIP_VOP2_ESMART3), .regs = &rk3588_esmart_pd_regs, }, { .id = VOP2_PD_DSC_8K, + .module_id_mask = BIT(ROCKCHIP_VOP2_DSC_8K), .regs = &rk3588_dsc_8k_pd_regs, }, { .id = VOP2_PD_DSC_4K, + .module_id_mask = BIT(ROCKCHIP_VOP2_DSC_4K), .regs = &rk3588_dsc_4k_pd_regs, }, };