From 8ebfee0d1b7ba26f176c76d9f8c701784d581ac7 Mon Sep 17 00:00:00 2001 From: Zhang Yubing Date: Mon, 8 Jan 2024 20:06:22 +0800 Subject: [PATCH] drm/rockchip: vop2: support vrr by changing dclk and hfp Change-Id: Ib4237693add871c9fe9f5a848cffcfaee242b69e Signed-off-by: Zhang Yubing --- drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 7 + drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 1 + drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 162 +++++++++++++++++-- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 2 + 4 files changed, 162 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index 0e48f22e9c4a..998b6afa962f 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -134,6 +134,12 @@ enum rockchip_drm_vop_aclk_mode { ROCKCHIP_VOP_ACLK_MAX_MODE, }; +enum rockchip_drm_vrr_type { + ROCKCHIP_VRR_VFP_MODE = 0, + ROCKCHIP_VRR_HFP_MODE = 1, + ROCKCHIP_VRR_DCLK_MODE = 2, +}; + struct rockchip_drm_sub_dev { struct list_head list; struct drm_connector *connector; @@ -347,6 +353,7 @@ struct rockchip_crtc_state { int min_refresh_rate; int shift_x; int shift_y; + int vrr_type; }; #define to_rockchip_crtc_state(s) \ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h index e3e5c972bee1..e3c3c54b69b1 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h @@ -1315,6 +1315,7 @@ struct vop2_video_port_data { char id; uint8_t splice_vp_id; uint16_t lut_dma_rid; + uint32_t dclk_switch_id; uint32_t feature; uint64_t soc_id[VOP2_SOC_VARIANT]; uint16_t gamma_lut_len; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 7fea1044df54..1de363172a04 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -582,6 +582,7 @@ struct vop2_video_port { struct vop2 *vop2; struct reset_control *dclk_rst; struct clk *dclk; + struct clk *dclk_switch; struct clk *dclk_parent; uint8_t id; bool layer_sel_update; @@ -631,6 +632,12 @@ struct vop2_video_port { */ bool has_dci_enabled_win; + /** + * @dclk_switch_enabled: a new refresh rate is config by userspace and the + * vrr type is switch dclk. + */ + bool dclk_switch_enabled; + /** * @bg_ovl_dly: The timing delay from background layer * to overlay module. @@ -5179,6 +5186,10 @@ static void vop2_disable(struct drm_crtc *crtc) struct vop2 *vop2 = vp->vop2; clk_disable_unprepare(vp->dclk); + if (vp->dclk_switch_enabled) { + vp->dclk_switch_enabled = false; + clk_disable_unprepare(vp->dclk_switch); + } if (--vop2->enable_count > 0) return; @@ -11983,28 +11994,127 @@ out: kfree(vop2_zpos_splice_hdr); } -static void vop2_crtc_update_vrr(struct drm_crtc *crtc) +static struct clk *vop2_get_switch_dclk(struct vop2_video_port *vp) +{ + struct vop2 *vop2 = vp->vop2; + const struct vop2_data *vop2_data = vop2->data; + const struct vop2_video_port_data *vp_data = &vop2_data->vp[vp->id]; + struct vop2_video_port *dclk_switch_vp; + uint32_t dclk_switch_id; + + if (!vp_data->dclk_switch_id) + return NULL; + + dclk_switch_id = ffs(vp_data->dclk_switch_id) - 1; + if (dclk_switch_id >= vop2_data->nr_vps || dclk_switch_id >= ARRAY_SIZE(vop2->vps)) + return NULL; + dclk_switch_vp = &vop2->vps[dclk_switch_id]; + + return dclk_switch_vp->dclk; +} + +static void vop2_crtc_dclk_seamless_switch(struct drm_crtc *crtc) { struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state); struct vop2_video_port *vp = to_vop2_video_port(crtc); struct vop2 *vop2 = vp->vop2; struct drm_display_mode *adjust_mode = &crtc->state->adjusted_mode; - + struct vop2_clk *dclk; + char clk_name[32]; + unsigned int dclk_src; unsigned int vrefresh; - unsigned int new_vtotal, vfp, new_vfp; + u64 dclk_rate; + int ret; - if (!vp->refresh_rate_change) - return; + DRM_DEV_INFO(vop2->dev, "change refresh rate by changing dclk\n"); - if (!vcstate->min_refresh_rate || !vcstate->max_refresh_rate) - return; + if (!vp->dclk_switch) { + vp->dclk_switch = vop2_get_switch_dclk(vp); + if (!vp->dclk_switch) + return; + } - if (vcstate->request_refresh_rate < vcstate->min_refresh_rate || - vcstate->request_refresh_rate > vcstate->max_refresh_rate) { - DRM_ERROR("invalid rate:%d\n", vcstate->request_refresh_rate); + dclk_src = VOP_MODULE_GET(vop2, vp, dclk_src_sel); + + snprintf(clk_name, sizeof(clk_name), "dclk%d", vp->id); + dclk = vop2_clk_get(vop2, clk_name); + if (!dclk) { + DRM_DEV_INFO(vop2->dev, "can't find dclk%d\n", vp->id); return; } + vrefresh = drm_mode_vrefresh(adjust_mode); + DRM_DEV_INFO(vop2->dev, "origin fresh rate:%u, request fresh rate:%u\n", + vrefresh, vcstate->request_refresh_rate); + + if (vp->loader_protect && !dclk->rate) + dclk->rate = clk_get_rate(vp->dclk); + + if (!vp->dclk_switch_enabled) { + vp->dclk_switch_enabled = true; + ret = clk_prepare_enable(vp->dclk_switch); + if (ret < 0) + DRM_DEV_ERROR(vop2->dev, "failed to enable dclk switch %d\n", ret); + } + + dclk_rate = (unsigned long long)dclk->rate * vcstate->request_refresh_rate; + do_div(dclk_rate, vrefresh); + + DRM_DEV_INFO(vop2->dev, "origin dclk rate:%lu, request dclk rate:%llu\n", + dclk->rate, dclk_rate); + if (dclk_rate > (VOP2_MAX_DCLK_RATE * 1000)) + return; + + if (dclk_src) { + rockchip_drm_dclk_set_rate(vop2->version, vp->dclk, dclk_rate); + DRM_DEV_INFO(vop2->dev, "set %s to %llu, get %lu\n", + __clk_get_name(vp->dclk), dclk_rate, clk_get_rate(vp->dclk)); + } else { + rockchip_drm_dclk_set_rate(vop2->version, vp->dclk_switch, dclk_rate); + DRM_DEV_INFO(vop2->dev, "set %s to %llu, get %lu\n", + __clk_get_name(vp->dclk_switch), dclk_rate, + clk_get_rate(vp->dclk_switch)); + } + + VOP_MODULE_SET(vop2, vp, dclk_src_sel, dclk_src ? 0 : 1); +} + +static void vop2_crtc_hfp_seamless_switch(struct drm_crtc *crtc) +{ + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state); + struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct vop2 *vop2 = vp->vop2; + struct drm_display_mode *adjust_mode = &crtc->state->adjusted_mode; + u32 hsync_len; + unsigned int vrefresh; + unsigned int new_htotal, hfp, new_hfp; + + DRM_DEV_INFO(vop2->dev, "change refresh rate by changing hfp\n"); + vrefresh = drm_mode_vrefresh(adjust_mode); + + /* calculate new hfp for new refresh rate */ + new_htotal = adjust_mode->htotal * vrefresh / vcstate->request_refresh_rate; + hfp = adjust_mode->hsync_start - adjust_mode->hdisplay; + new_hfp = hfp + new_htotal - adjust_mode->htotal; + + DRM_DEV_INFO(vop2->dev, "origin fresh rate:%u, request fresh rate:%u\n", + vrefresh, vcstate->request_refresh_rate); + DRM_DEV_INFO(vop2->dev, "origin hfp:%u, request hfp:%u\n", hfp, new_hfp); + /* config vop2 htotal register */ + hsync_len = VOP_MODULE_GET(vop2, vp, htotal_pw) & 0xffff; + VOP_MODULE_SET(vop2, vp, htotal_pw, (new_htotal << 16) | hsync_len); +} + +static void vop2_crtc_vfp_seamless_switch(struct drm_crtc *crtc) +{ + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state); + struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct vop2 *vop2 = vp->vop2; + struct drm_display_mode *adjust_mode = &crtc->state->adjusted_mode; + unsigned int vrefresh; + unsigned int new_vtotal, vfp, new_vfp; + + DRM_DEV_INFO(vop2->dev, "change refresh rate by changing vfp\n"); vrefresh = drm_mode_vrefresh(adjust_mode); /* calculate new vfp for new refresh rate */ @@ -12032,6 +12142,38 @@ static void vop2_crtc_update_vrr(struct drm_crtc *crtc) rockchip_connector_update_vfp_for_vrr(crtc, adjust_mode, new_vfp); } +static void vop2_crtc_update_vrr(struct drm_crtc *crtc) +{ + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state); + struct vop2_video_port *vp = to_vop2_video_port(crtc); + + if (!vp->refresh_rate_change) + return; + + if (!vcstate->min_refresh_rate || !vcstate->max_refresh_rate) + return; + + if (vcstate->request_refresh_rate < vcstate->min_refresh_rate || + vcstate->request_refresh_rate > vcstate->max_refresh_rate) { + DRM_ERROR("invalid rate:%d\n", vcstate->request_refresh_rate); + return; + } + + switch (vcstate->vrr_type) { + case ROCKCHIP_VRR_DCLK_MODE: + vop2_crtc_dclk_seamless_switch(crtc); + break; + case ROCKCHIP_VRR_HFP_MODE: + vop2_crtc_hfp_seamless_switch(crtc); + break; + case ROCKCHIP_VRR_VFP_MODE: + vop2_crtc_vfp_seamless_switch(crtc); + fallthrough; + default: + break; + } +} + static bool post_sharp_enabled(struct drm_crtc *crtc) { struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state); diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 6ddf4fe013cd..9eafe51e72cc 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -2256,6 +2256,7 @@ static const struct vop2_video_port_data rk3588_vop_video_ports[] = { { .id = 0, .splice_vp_id = 1, + .dclk_switch_id = BIT(ROCKCHIP_VOP_VP1), .lut_dma_rid = 0xd, .soc_id = { 0x3588, 0x3588 }, .feature = VOP_FEATURE_OUTPUT_10BIT | VOP_FEATURE_ALPHA_SCALE | @@ -2287,6 +2288,7 @@ static const struct vop2_video_port_data rk3588_vop_video_ports[] = { }, { .id = 2, + .dclk_switch_id = BIT(ROCKCHIP_VOP_VP1), .lut_dma_rid = 0xe, .soc_id = { 0x3588, 0x3588 }, .feature = VOP_FEATURE_OUTPUT_10BIT | VOP_FEATURE_ALPHA_SCALE,