diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h index 910c55449ea2..532399d13b04 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h @@ -35,7 +35,7 @@ #define VOP_FEATURE_ALPHA_HDR10 BIT(3) #define VOP_FEATURE_ALPHA_NEXT_HDR BIT(4) /* a feature to splice two windows and two vps to support resolution > 4096 */ -#define VOP_FEATURE_SPLICE BIT(5) +#define VOP_FEATURE_SPLICE BIT(5) #define VOP_FEATURE_OUTPUT_10BIT VOP_FEATURE_OUTPUT_RGB10 @@ -97,6 +97,19 @@ enum vop2_win_dly_mode { VOP2_DLY_MODE_MAX, }; +/* + * vop2 internal power domain id, + * should be all none zero, 0 will be + * treat as invalid; + */ +#define VOP2_PD_CLUSTER0 BIT(0) +#define VOP2_PD_CLUSTER1 BIT(1) +#define VOP2_PD_CLUSTER2 BIT(2) +#define VOP2_PD_CLUSTER3 BIT(3) +#define VOP2_PD_ESMART0 BIT(4) +#define VOP2_PD_DSC_8K BIT(5) +#define VOP2_PD_DSC_4K BIT(6) + #define DSP_BG_SWAP 0x1 #define DSP_RB_SWAP 0x2 #define DSP_RG_SWAP 0x4 @@ -657,6 +670,11 @@ struct vop2_video_port_regs { struct vop_reg splice_en; }; +struct vop2_power_domain_regs { + struct vop_reg pd; + struct vop_reg status; +}; + struct vop2_dsc_regs { struct vop_reg rst_deassert; struct vop_reg port_mux; @@ -678,6 +696,12 @@ struct vop2_wb_regs { struct vop_reg axi_uv_id; }; +struct vop2_power_domain_data { + uint8_t id; + uint8_t parent_id; + const struct vop2_power_domain_regs *regs; +}; + /* * connector interface(RGB/HDMI/eDP/DP/MIPI) data */ @@ -698,6 +722,7 @@ struct vop2_win_data { const char *name; uint8_t phys_id; uint8_t splice_win_id; + uint8_t pd_id; uint32_t base; enum drm_plane_type type; @@ -731,6 +756,7 @@ struct vop2_win_data { struct vop2_dsc_data { char id; + uint8_t pd_id; const struct vop2_dsc_regs *regs; }; @@ -924,6 +950,8 @@ struct vop2_data { uint8_t nr_axi_intr; uint8_t nr_gammas; uint8_t nr_conns; + uint8_t nr_pds; + bool delayed_pd; const struct vop_intr *axi_intr; const struct vop2_ctrl *ctrl; const struct vop2_dsc_data *dsc; @@ -932,6 +960,7 @@ struct vop2_data { const struct vop2_connector_if_data *conn; const struct vop2_wb_data *wb; const struct vop2_layer_data *layer; + const struct vop2_power_domain_data *pd; const struct vop_csc_table *csc_table; const struct vop_hdr_table *hdr_table; const struct vop_grf_ctrl *grf_ctrl; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index b1c94458b05d..e7df129286e1 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -199,6 +199,26 @@ enum vop2_layer_phy_id { ROCKCHIP_VOP2_PHY_ID_INVALID = -1, }; +struct vop2_power_domain { + struct vop2_power_domain *parent; + struct vop2 *vop2; + /* + * @lock: protect power up/down procedure. + * power on take effect immediately, + * power down take effect by vsync. + * we must check power_domain_status register + * to make sure the power domain is down before + * send a power on request. + * + */ + struct mutex lock; + unsigned int ref_count; + bool on; + const struct vop2_power_domain_data *data; + struct list_head list; + struct delayed_work power_off_work; +}; + struct vop2_zpos { struct drm_plane *plane; int win_phys_id; @@ -308,6 +328,10 @@ struct vop2_win { uint8_t splice_win_id; + struct vop2_power_domain *pd; + + bool enabled; + /** * @phys_id: physical id for cluster0/1, esmart0/1, smart0/1 * Will be used as a identification for some register @@ -419,6 +443,7 @@ struct vop2_wb { struct vop2_dsc { uint8_t id; const struct vop2_dsc_regs *regs; + struct vop2_power_domain *pd; }; enum vop2_wb_format { @@ -643,6 +668,7 @@ struct vop2 { /* list_head of internal clk */ struct list_head clk_list_head; + struct list_head pd_list_head; struct vop2_layer layers[ROCKCHIP_MAX_LAYER]; /* must put at the end of the struct */ @@ -870,6 +896,18 @@ static struct vop2_win *vop2_find_win_by_phys_id(struct vop2 *vop2, uint8_t phys return NULL; } +static struct vop2_power_domain *vop2_find_pd_by_id(struct vop2 *vop2, uint8_t id) +{ + struct vop2_power_domain *pd, *n; + + list_for_each_entry_safe(pd, n, &vop2->pd_list_head, list) { + if (pd->data->id == id) + return pd; + } + + return NULL; +} + static const struct vop2_connector_if_data *vop2_find_connector_if_data(struct vop2 *vop2, int id) { const struct vop2_connector_if_data *if_data; @@ -1218,6 +1256,119 @@ static inline void vop2_cfg_done(struct drm_crtc *crtc) return rk3588_vop2_cfg_done(crtc); } +static uint32_t vop2_power_domain_status(struct vop2_power_domain *pd) +{ + struct vop2 *vop2 = pd->vop2; + + return vop2_read_reg(vop2, 0, &pd->data->regs->status); + +} + +static void vop2_wait_power_domain_off(struct vop2_power_domain *pd) +{ + struct vop2 *vop2 = pd->vop2; + int val; + int ret; + + ret = readx_poll_timeout(vop2_power_domain_status, pd, val, val, 100, 50 * 1000); + + if (ret) + DRM_DEV_ERROR(vop2->dev, "wait pd off timeout\n"); +} + +static void vop2_wait_power_domain_on(struct vop2_power_domain *pd) +{ + struct vop2 *vop2 = pd->vop2; + int val; + int ret; + + ret = readx_poll_timeout_atomic(vop2_power_domain_status, pd, val, !val, 0, 50 * 1000); + if (ret) + DRM_DEV_ERROR(vop2->dev, "wait pd on timeout\n"); +} + +/* + * Power domain on take effect immediately + */ +static void vop2_power_domain_on(struct vop2_power_domain *pd) +{ + struct vop2 *vop2 = pd->vop2; + + if (!pd->on) { + vop2_wait_power_domain_off(pd); + VOP_MODULE_SET(vop2, pd->data, pd, 0); + vop2_wait_power_domain_on(pd); + pd->on = true; + dev_dbg(vop2->dev, "pd%d on\n", pd->data->id); + } +} + +/* + * Power domain off take effect by vsync. + */ +static void vop2_power_domain_off(struct vop2_power_domain *pd) +{ + struct vop2 *vop2 = pd->vop2; + + dev_dbg(vop2->dev, "pd%d off\n", pd->data->id); + pd->on = false; + VOP_MODULE_SET(vop2, pd->data, pd, 1); +} + +static void vop2_power_domain_get(struct vop2_power_domain *pd) +{ + mutex_lock(&pd->lock); + if (pd->parent) + vop2_power_domain_get(pd->parent); + if (pd->ref_count == 0) { + if (pd->vop2->data->delayed_pd) + cancel_delayed_work(&pd->power_off_work); + vop2_power_domain_on(pd); + } + pd->ref_count++; + mutex_unlock(&pd->lock); +} + +static void vop2_power_domain_put(struct vop2_power_domain *pd) +{ + mutex_lock(&pd->lock); + if (--pd->ref_count == 0) { + if (pd->vop2->data->delayed_pd) + schedule_delayed_work(&pd->power_off_work, msecs_to_jiffies(2500)); + else + vop2_power_domain_off(pd); + } + + if (pd->parent) + vop2_power_domain_put(pd->parent); + mutex_unlock(&pd->lock); +} + +/* + * Called if the pd ref_count reach 0 after 2.5 + * seconds. + */ +static void vop2_power_domain_off_work(struct work_struct *work) +{ + struct vop2_power_domain *pd; + + pd = container_of(to_delayed_work(work), struct vop2_power_domain, power_off_work); + + mutex_lock(&pd->lock); + if (pd->ref_count == 0) + vop2_power_domain_off(pd); + mutex_unlock(&pd->lock); +} + +static void vop2_win_enable(struct vop2_win *win) +{ + if (!win->enabled) { + if (win->pd) + vop2_power_domain_get(win->pd); + win->enabled = true; + } +} + static void vop2_win_multi_area_disable(struct vop2_win *parent) { struct vop2 *vop2 = parent->vop2; @@ -1238,27 +1389,33 @@ static void vop2_win_disable(struct vop2_win *win) win->left_win = NULL; win->splice_win = NULL; win->splice_mode_right = false; - VOP_WIN_SET(vop2, win, enable, 0); - if (win->feature & WIN_FEATURE_CLUSTER_MAIN) { - struct vop2_win *sub_win; - int i = 0; - for (i = 0; i < vop2->registered_num_wins; i++) { - sub_win = &vop2->win[i]; + if (win->enabled) { + VOP_WIN_SET(vop2, win, enable, 0); + if (win->feature & WIN_FEATURE_CLUSTER_MAIN) { + struct vop2_win *sub_win; + int i = 0; - if ((sub_win->phys_id == win->phys_id) && - (sub_win->feature & WIN_FEATURE_CLUSTER_SUB)) - VOP_WIN_SET(vop2, sub_win, enable, 0); + for (i = 0; i < vop2->registered_num_wins; i++) { + sub_win = &vop2->win[i]; + + if ((sub_win->phys_id == win->phys_id) && + (sub_win->feature & WIN_FEATURE_CLUSTER_SUB)) + VOP_WIN_SET(vop2, sub_win, enable, 0); + } + + VOP_CLUSTER_SET(vop2, win, enable, 0); } - VOP_CLUSTER_SET(vop2, win, enable, 0); + /* + * disable all other multi area win if we want disable area0 here + */ + if (!win->parent && (win->feature & WIN_FEATURE_MULTI_AREA)) + vop2_win_multi_area_disable(win); + if (win->pd) + vop2_power_domain_put(win->pd); + win->enabled = false; } - - /* - * disable all other multi area win if we want disable area0 here - */ - if (!win->parent && (win->feature & WIN_FEATURE_MULTI_AREA)) - vop2_win_multi_area_disable(win); } static inline void vop2_write_lut(struct vop2 *vop2, uint32_t offset, uint32_t v) @@ -2760,8 +2917,6 @@ static void rk3588_vop2_regsbak(struct vop2 *vop2) uint32_t *base = vop2->regs; int i = 0; - vop2_writel(vop2, RK3568_SYS_PD_CTRL, 0); - /* * No need to backup DSC/GAMMA_LUT/BPP_LUT/MMU */ @@ -2769,6 +2924,15 @@ static void rk3588_vop2_regsbak(struct vop2 *vop2) vop2->regsbak[i] = base[i]; } +static void vop2_power_domain_init(struct vop2 *vop2) +{ + struct vop2_power_domain *pd, *n; + + list_for_each_entry_safe(pd, n, &vop2->pd_list_head, list) { + vop2_power_domain_on(pd); + } +} + static void vop2_initial(struct drm_crtc *crtc) { struct vop2_video_port *vp = to_vop2_video_port(crtc); @@ -2797,6 +2961,8 @@ static void vop2_initial(struct drm_crtc *crtc) if (vop2_soc_is_rk3566()) VOP_CTRL_SET(vop2, otp_en, 1); + vop2_power_domain_init(vop2); + /* dsc must deassert rst before its register can accessed */ for (i = 0; i < vop2_data->nr_dscs; i++) { dsc = &vop2->dscs[i]; @@ -3385,6 +3551,7 @@ static void vop2_win_atomic_update(struct vop2_win *win, struct drm_rect *src, s afbc_half_block_en = vop2_afbc_half_block_enable(vpstate); + vop2_win_enable(win); spin_lock(&vop2->reg_lock); DRM_DEV_DEBUG(vop2->dev, "vp%d update %s[%dx%d->%dx%d@%dx%d] fmt[%.4s_%s] addr[%pad]\n", vp->id, win->name, actual_w, actual_h, dsp_w, dsp_h, @@ -7085,6 +7252,37 @@ static void vop2_destroy_crtc(struct drm_crtc *crtc) drm_flip_work_cleanup(&vp->fb_unref_work); } +static int vop2_pd_data_init(struct vop2 *vop2) +{ + const struct vop2_data *vop2_data = vop2->data; + const struct vop2_power_domain_data *pd_data; + struct vop2_power_domain *pd; + int i; + + INIT_LIST_HEAD(&vop2->pd_list_head); + + for (i = 0; i < vop2_data->nr_pds; i++) { + pd_data = &vop2_data->pd[i]; + pd = devm_kzalloc(vop2->dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + pd->vop2 = vop2; + pd->data = pd_data; + mutex_init(&pd->lock); + list_add_tail(&pd->list, &vop2->pd_list_head); + INIT_DELAYED_WORK(&pd->power_off_work, vop2_power_domain_off_work); + if (pd_data->parent_id) { + pd->parent = vop2_find_pd_by_id(vop2, pd_data->parent_id); + if (!pd->parent) { + DRM_DEV_ERROR(vop2->dev, "no parent pd find for pd%d\n", pd->data->id); + return -EINVAL; + } + } + } + + return 0; +} + static void vop2_dsc_data_init(struct vop2 *vop2) { const struct vop2_data *vop2_data = vop2->data; @@ -7097,6 +7295,8 @@ static void vop2_dsc_data_init(struct vop2 *vop2) dsc_data = &vop2_data->dsc[i]; dsc->id = dsc_data->id; dsc->regs = dsc_data->regs; + if (dsc_data->pd_id) + dsc->pd = vop2_find_pd_by_id(vop2, dsc_data->pd_id); } } @@ -7141,6 +7341,9 @@ static int vop2_win_init(struct vop2 *vop2) win->zpos = i; win->vop2 = vop2; + if (win_data->pd_id) + win->pd = vop2_find_pd_by_id(vop2, win_data->pd_id); + num_wins++; if (!vop2->support_multi_area) @@ -7248,6 +7451,10 @@ static int vop2_bind(struct device *dev, struct device *master, void *data) vop2->support_multi_area = of_property_read_bool(dev->of_node, "support-multi-area"); vop2->disable_afbc_win = of_property_read_bool(dev->of_node, "disable-afbc-win"); + ret = vop2_pd_data_init(vop2); + if (ret) + return ret; + ret = vop2_win_init(vop2); if (ret) return ret; diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 411532a9d4fe..b3888c5de75b 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -464,10 +464,13 @@ static const struct vop2_dsc_data rk3588_vop_dsc_data[] = { { .id = 0, .regs = &rk3588_vop_dsc0_regs, + .pd_id = VOP2_PD_DSC_8K, }, + { .id = 1, .regs = &rk3588_vop_dsc1_regs, + .pd_id = VOP2_PD_DSC_4K, }, }; @@ -1783,6 +1786,90 @@ static const struct vop2_win_data rk3568_vop_win_data[] = { }, }; +const struct vop2_power_domain_regs rk3588_cluster0_pd_regs = { + .pd = VOP_REG(RK3568_SYS_PD_CTRL, 0x1, 0), + .status = VOP_REG(RK3568_SYS_STATUS0, 0x1, 8), +}; + +const struct vop2_power_domain_regs rk3588_cluster1_pd_regs = { + .pd = VOP_REG(RK3568_SYS_PD_CTRL, 0x1, 1), + .status = VOP_REG(RK3568_SYS_STATUS0, 0x1, 9), +}; + +const struct vop2_power_domain_regs rk3588_cluster2_pd_regs = { + .pd = VOP_REG(RK3568_SYS_PD_CTRL, 0x1, 2), + .status = VOP_REG(RK3568_SYS_STATUS0, 0x1, 10), +}; + +const struct vop2_power_domain_regs rk3588_cluster3_pd_regs = { + .pd = VOP_REG(RK3568_SYS_PD_CTRL, 0x1, 3), + .status = VOP_REG(RK3568_SYS_STATUS0, 0x1, 11), +}; + +const struct vop2_power_domain_regs rk3588_esmart_pd_regs = { + .pd = VOP_REG(RK3568_SYS_PD_CTRL, 0x1, 7), + .status = VOP_REG(RK3568_SYS_STATUS0, 0x1, 15), +}; + +const struct vop2_power_domain_regs rk3588_dsc_8k_pd_regs = { + .pd = VOP_REG(RK3568_SYS_PD_CTRL, 0x1, 5), + .status = VOP_REG(RK3568_SYS_STATUS0, 0x1, 13), +}; + +const struct vop2_power_domain_regs rk3588_dsc_4k_pd_regs = { + .pd = VOP_REG(RK3568_SYS_PD_CTRL, 0x1, 6), + .status = VOP_REG(RK3568_SYS_STATUS0, 0x1, 14), +}; + +/* + * There are 7 internal power domains on rk3588 vop, + * Cluster0/1/2/3 each have on pd, and PD_CLUSTER0 as parent, + * that means PD_CLUSTER0 should turn on first before + * PD_CLUSTER1/2/3 turn on. + * + * Esmart0/1/2/3 share one pd PD_ESMART0. + * DSC_8K/DSC_4K each have on pd. + */ +static const struct vop2_power_domain_data rk3588_vop_pd_data[] = { + { + .id = VOP2_PD_CLUSTER0, + .regs = &rk3588_cluster0_pd_regs, + }, + + { + .id = VOP2_PD_CLUSTER1, + .parent_id = VOP2_PD_CLUSTER0, + .regs = &rk3588_cluster1_pd_regs, + }, + + { + .id = VOP2_PD_CLUSTER2, + .parent_id = VOP2_PD_CLUSTER0, + .regs = &rk3588_cluster0_pd_regs, + }, + + { + .id = VOP2_PD_CLUSTER3, + .parent_id = VOP2_PD_CLUSTER0, + .regs = &rk3588_cluster1_pd_regs, + }, + + { + .id = VOP2_PD_ESMART0, + .regs = &rk3588_esmart_pd_regs, + }, + + { + .id = VOP2_PD_DSC_8K, + .regs = &rk3588_dsc_8k_pd_regs, + }, + + { + .id = VOP2_PD_DSC_4K, + .regs = &rk3588_dsc_4k_pd_regs, + }, +}; + /* * rk3588 vop with 4 cluster, 4 esmart win. * Every cluster can work as 4K win or split into two win. @@ -1817,6 +1904,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { .vsu_filter_mode = VOP2_SCALE_UP_BIL, .vsd_filter_mode = VOP2_SCALE_DOWN_BIL, .regs = &rk3568_cluster0_win_data, + .pd_id = VOP2_PD_CLUSTER0, .max_upscale_factor = 4, .max_downscale_factor = 4, .dly = { 4, 26, 29 }, @@ -1859,6 +1947,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { .vsu_filter_mode = VOP2_SCALE_UP_BIL, .vsd_filter_mode = VOP2_SCALE_DOWN_BIL, .regs = &rk3568_cluster1_win_data, + .pd_id = VOP2_PD_CLUSTER1, .type = DRM_PLANE_TYPE_OVERLAY, .max_upscale_factor = 4, .max_downscale_factor = 4, @@ -1889,6 +1978,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { { .name = "Cluster2-win0", .phys_id = ROCKCHIP_VOP2_CLUSTER2, + .pd_id = VOP2_PD_CLUSTER2, .splice_win_id = ROCKCHIP_VOP2_CLUSTER3, .base = 0x00, .formats = formats_win_full_10bit, @@ -1932,6 +2022,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { { .name = "Cluster3-win0", .phys_id = ROCKCHIP_VOP2_CLUSTER3, + .pd_id = VOP2_PD_CLUSTER3, .base = 0x00, .formats = formats_win_full_10bit, .nformats = ARRAY_SIZE(formats_win_full_10bit), @@ -1974,6 +2065,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { { .name = "Esmart0-win0", .phys_id = ROCKCHIP_VOP2_ESMART0, + .pd_id = VOP2_PD_ESMART0, .splice_win_id = ROCKCHIP_VOP2_ESMART1, .formats = formats_win_full_10bit, .nformats = ARRAY_SIZE(formats_win_full_10bit), @@ -1998,6 +2090,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { { .name = "Esmart2-win0", .phys_id = ROCKCHIP_VOP2_ESMART2, + .pd_id = VOP2_PD_ESMART0, .splice_win_id = ROCKCHIP_VOP2_ESMART3, .base = 0x400, .formats = formats_win_full_10bit, @@ -2022,6 +2115,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { { .name = "Esmart1-win0", .phys_id = ROCKCHIP_VOP2_ESMART1, + .pd_id = VOP2_PD_ESMART0, .formats = formats_win_full_10bit, .nformats = ARRAY_SIZE(formats_win_full_10bit), .format_modifiers = format_modifiers, @@ -2044,6 +2138,7 @@ static const struct vop2_win_data rk3588_vop_win_data[] = { { .name = "Esmart3-win0", .phys_id = ROCKCHIP_VOP2_ESMART3, + .pd_id = VOP2_PD_ESMART0, .formats = formats_win_full_10bit, .nformats = ARRAY_SIZE(formats_win_full_10bit), .format_modifiers = format_modifiers, @@ -2235,6 +2330,7 @@ static const struct vop2_data rk3588_vop = { .nr_vps = 4, .nr_mixers = 7, .nr_layers = 8, + .nr_pds = 7, .max_input = { 8192, 4320 }, .max_output = { 4096, 2304 }, .ctrl = &rk3588_vop_ctrl, @@ -2248,6 +2344,8 @@ static const struct vop2_data rk3588_vop = { .layer = rk3568_vop_layers, .win = rk3588_vop_win_data, .win_size = ARRAY_SIZE(rk3588_vop_win_data), + .pd = rk3588_vop_pd_data, + .nr_pds = ARRAY_SIZE(rk3588_vop_pd_data), }; static const struct of_device_id vop2_dt_match[] = { diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.h b/drivers/gpu/drm/rockchip/rockchip_vop_reg.h index f73857a52d97..3a28891fba34 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.h +++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.h @@ -1055,9 +1055,12 @@ #define RK3568_WB_XSCAL_FACTOR 0x44 #define RK3568_WB_YRGB_MST 0x48 #define RK3568_WB_CBR_MST 0x4C -#define RK3568_OTP_WIN_EN 0x050 -#define RK3568_LUT_PORT_SEL 0x058 -#define RK3568_SYS_STATUS0 0x060 +#define RK3568_OTP_WIN_EN 0x50 +#define RK3568_LUT_PORT_SEL 0x58 +#define RK3568_SYS_STATUS0 0x60 +#define RK3568_SYS_STATUS1 0x64 +#define RK3568_SYS_STATUS2 0x68 +#define RK3568_SYS_STATUS3 0x6C #define RK3568_VP0_LINE_FLAG 0x70 #define RK3568_VP1_LINE_FLAG 0x74 #define RK3568_VP2_LINE_FLAG 0x78