diff options
Diffstat (limited to 'drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c')
| -rw-r--r-- | drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c | 1447 |
1 files changed, 807 insertions, 640 deletions
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c index bdb117709674..f6568ed8375f 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c @@ -1,408 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * + * Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved. */ #define pr_fmt(fmt) "[drm:%s] " fmt, __func__ #include "dpu_kms.h" #include "dpu_hw_lm.h" #include "dpu_hw_ctl.h" +#include "dpu_hw_cdm.h" +#include "dpu_hw_cwb.h" #include "dpu_hw_pingpong.h" +#include "dpu_hw_sspp.h" #include "dpu_hw_intf.h" +#include "dpu_hw_wb.h" +#include "dpu_hw_dspp.h" +#include "dpu_hw_merge3d.h" +#include "dpu_hw_dsc.h" #include "dpu_encoder.h" #include "dpu_trace.h" -#define RESERVED_BY_OTHER(h, r) \ - ((h)->rsvp && ((h)->rsvp->enc_id != (r)->enc_id)) - -/** - * struct dpu_rm_requirements - Reservation requirements parameter bundle - * @topology: selected topology for the display - * @hw_res: Hardware resources required as reported by the encoders - */ -struct dpu_rm_requirements { - struct msm_display_topology topology; - struct dpu_encoder_hw_resources hw_res; -}; - -/** - * struct dpu_rm_rsvp - Use Case Reservation tagging structure - * Used to tag HW blocks as reserved by a CRTC->Encoder->Connector chain - * By using as a tag, rather than lists of pointers to HW blocks used - * we can avoid some list management since we don't know how many blocks - * of each type a given use case may require. - * @list: List head for list of all reservations - * @seq: Global RSVP sequence number for debugging, especially for - * differentiating differenct allocations for same encoder. - * @enc_id: Reservations are tracked by Encoder DRM object ID. - * CRTCs may be connected to multiple Encoders. - * An encoder or connector id identifies the display path. - */ -struct dpu_rm_rsvp { - struct list_head list; - uint32_t seq; - uint32_t enc_id; -}; -/** - * struct dpu_rm_hw_blk - hardware block tracking list member - * @list: List head for list of all hardware blocks tracking items - * @rsvp: Pointer to use case reservation if reserved by a client - * @rsvp_nxt: Temporary pointer used during reservation to the incoming - * request. Will be swapped into rsvp if proposal is accepted - * @type: Type of hardware block this structure tracks - * @id: Hardware ID number, within it's own space, ie. LM_X - * @catalog: Pointer to the hardware catalog entry for this block - * @hw: Pointer to the hardware register access object for this block - */ -struct dpu_rm_hw_blk { - struct list_head list; - struct dpu_rm_rsvp *rsvp; - struct dpu_rm_rsvp *rsvp_nxt; - enum dpu_hw_blk_type type; - uint32_t id; - struct dpu_hw_blk *hw; -}; +static inline bool reserved_by_other(uint32_t *res_map, int idx, + uint32_t crtc_id) +{ + return res_map[idx] && res_map[idx] != crtc_id; +} /** - * dpu_rm_dbg_rsvp_stage - enum of steps in making reservation for event logging + * dpu_rm_init - Read hardware catalog and create reservation tracking objects + * for all HW blocks. + * @dev: Corresponding device for devres management + * @rm: DPU Resource Manager handle + * @cat: Pointer to hardware catalog + * @mdss_data: Pointer to MDSS / UBWC configuration + * @mmio: mapped register io address of MDP + * @return: 0 on Success otherwise -ERROR */ -enum dpu_rm_dbg_rsvp_stage { - DPU_RM_STAGE_BEGIN, - DPU_RM_STAGE_AFTER_CLEAR, - DPU_RM_STAGE_AFTER_RSVPNEXT, - DPU_RM_STAGE_FINAL -}; - -static void _dpu_rm_print_rsvps( +int dpu_rm_init(struct drm_device *dev, struct dpu_rm *rm, - enum dpu_rm_dbg_rsvp_stage stage) + const struct dpu_mdss_cfg *cat, + const struct qcom_ubwc_cfg_data *mdss_data, + void __iomem *mmio) { - struct dpu_rm_rsvp *rsvp; - struct dpu_rm_hw_blk *blk; - enum dpu_hw_blk_type type; - - DPU_DEBUG("%d\n", stage); - - list_for_each_entry(rsvp, &rm->rsvps, list) { - DRM_DEBUG_KMS("%d rsvp[s%ue%u]\n", stage, rsvp->seq, - rsvp->enc_id); - } - - for (type = 0; type < DPU_HW_BLK_MAX; type++) { - list_for_each_entry(blk, &rm->hw_blks[type], list) { - if (!blk->rsvp && !blk->rsvp_nxt) - continue; + int rc, i; - DRM_DEBUG_KMS("%d rsvp[s%ue%u->s%ue%u] %d %d\n", stage, - (blk->rsvp) ? blk->rsvp->seq : 0, - (blk->rsvp) ? blk->rsvp->enc_id : 0, - (blk->rsvp_nxt) ? blk->rsvp_nxt->seq : 0, - (blk->rsvp_nxt) ? blk->rsvp_nxt->enc_id : 0, - blk->type, blk->id); - } + if (!rm || !cat || !mmio) { + DPU_ERROR("invalid kms\n"); + return -EINVAL; } -} - -struct dpu_hw_mdp *dpu_rm_get_mdp(struct dpu_rm *rm) -{ - return rm->hw_mdp; -} - -void dpu_rm_init_hw_iter( - struct dpu_rm_hw_iter *iter, - uint32_t enc_id, - enum dpu_hw_blk_type type) -{ - memset(iter, 0, sizeof(*iter)); - iter->enc_id = enc_id; - iter->type = type; -} -static bool _dpu_rm_get_hw_locked(struct dpu_rm *rm, struct dpu_rm_hw_iter *i) -{ - struct list_head *blk_list; + /* Clear, setup lists */ + memset(rm, 0, sizeof(*rm)); - if (!rm || !i || i->type >= DPU_HW_BLK_MAX) { - DPU_ERROR("invalid rm\n"); - return false; - } + rm->has_legacy_ctls = (cat->mdss_ver->core_major_ver < 5); - i->hw = NULL; - blk_list = &rm->hw_blks[i->type]; + /* Interrogate HW catalog and create tracking items for hw blocks */ + for (i = 0; i < cat->mixer_count; i++) { + struct dpu_hw_mixer *hw; + const struct dpu_lm_cfg *lm = &cat->mixer[i]; - if (i->blk && (&i->blk->list == blk_list)) { - DPU_DEBUG("attempt resume iteration past last\n"); - return false; + hw = dpu_hw_lm_init(dev, lm, mmio, cat->mdss_ver); + if (IS_ERR(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed lm object creation: err %d\n", rc); + goto fail; + } + rm->mixer_blks[lm->id - LM_0] = &hw->base; } - i->blk = list_prepare_entry(i->blk, blk_list, list); + for (i = 0; i < cat->merge_3d_count; i++) { + struct dpu_hw_merge_3d *hw; + const struct dpu_merge_3d_cfg *merge_3d = &cat->merge_3d[i]; - list_for_each_entry_continue(i->blk, blk_list, list) { - struct dpu_rm_rsvp *rsvp = i->blk->rsvp; - - if (i->blk->type != i->type) { - DPU_ERROR("found incorrect block type %d on %d list\n", - i->blk->type, i->type); - return false; + hw = dpu_hw_merge_3d_init(dev, merge_3d, mmio); + if (IS_ERR(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed merge_3d object creation: err %d\n", + rc); + goto fail; } + rm->merge_3d_blks[merge_3d->id - MERGE_3D_0] = &hw->base; + } - if ((i->enc_id == 0) || (rsvp && rsvp->enc_id == i->enc_id)) { - i->hw = i->blk->hw; - DPU_DEBUG("found type %d id %d for enc %d\n", - i->type, i->blk->id, i->enc_id); - return true; + for (i = 0; i < cat->pingpong_count; i++) { + struct dpu_hw_pingpong *hw; + const struct dpu_pingpong_cfg *pp = &cat->pingpong[i]; + + hw = dpu_hw_pingpong_init(dev, pp, mmio, cat->mdss_ver); + if (IS_ERR(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed pingpong object creation: err %d\n", + rc); + goto fail; } + if (pp->merge_3d && pp->merge_3d < MERGE_3D_MAX) + hw->merge_3d = to_dpu_hw_merge_3d(rm->merge_3d_blks[pp->merge_3d - MERGE_3D_0]); + rm->pingpong_blks[pp->id - PINGPONG_0] = &hw->base; } - DPU_DEBUG("no match, type %d for enc %d\n", i->type, i->enc_id); - - return false; -} - -bool dpu_rm_get_hw(struct dpu_rm *rm, struct dpu_rm_hw_iter *i) -{ - bool ret; + for (i = 0; i < cat->intf_count; i++) { + struct dpu_hw_intf *hw; + const struct dpu_intf_cfg *intf = &cat->intf[i]; - mutex_lock(&rm->rm_lock); - ret = _dpu_rm_get_hw_locked(rm, i); - mutex_unlock(&rm->rm_lock); + hw = dpu_hw_intf_init(dev, intf, mmio, cat->mdss_ver); + if (IS_ERR(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed intf object creation: err %d\n", rc); + goto fail; + } + rm->hw_intf[intf->id - INTF_0] = hw; + } - return ret; -} + for (i = 0; i < cat->wb_count; i++) { + struct dpu_hw_wb *hw; + const struct dpu_wb_cfg *wb = &cat->wb[i]; -static void _dpu_rm_hw_destroy(enum dpu_hw_blk_type type, void *hw) -{ - switch (type) { - case DPU_HW_BLK_LM: - dpu_hw_lm_destroy(hw); - break; - case DPU_HW_BLK_CTL: - dpu_hw_ctl_destroy(hw); - break; - case DPU_HW_BLK_PINGPONG: - dpu_hw_pingpong_destroy(hw); - break; - case DPU_HW_BLK_INTF: - dpu_hw_intf_destroy(hw); - break; - case DPU_HW_BLK_SSPP: - /* SSPPs are not managed by the resource manager */ - case DPU_HW_BLK_TOP: - /* Top is a singleton, not managed in hw_blks list */ - case DPU_HW_BLK_MAX: - default: - DPU_ERROR("unsupported block type %d\n", type); - break; + hw = dpu_hw_wb_init(dev, wb, mmio, cat->mdss_ver); + if (IS_ERR(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed wb object creation: err %d\n", rc); + goto fail; + } + rm->hw_wb[wb->id - WB_0] = hw; } -} - -int dpu_rm_destroy(struct dpu_rm *rm) -{ - struct dpu_rm_rsvp *rsvp_cur, *rsvp_nxt; - struct dpu_rm_hw_blk *hw_cur, *hw_nxt; - enum dpu_hw_blk_type type; + for (i = 0; i < cat->cwb_count; i++) { + struct dpu_hw_cwb *hw; + const struct dpu_cwb_cfg *cwb = &cat->cwb[i]; - if (!rm) { - DPU_ERROR("invalid rm\n"); - return -EINVAL; + hw = dpu_hw_cwb_init(dev, cwb, mmio); + if (IS_ERR(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed cwb object creation: err %d\n", rc); + goto fail; + } + rm->cwb_blks[cwb->id - CWB_0] = &hw->base; } - list_for_each_entry_safe(rsvp_cur, rsvp_nxt, &rm->rsvps, list) { - list_del(&rsvp_cur->list); - kfree(rsvp_cur); + for (i = 0; i < cat->ctl_count; i++) { + struct dpu_hw_ctl *hw; + const struct dpu_ctl_cfg *ctl = &cat->ctl[i]; + + hw = dpu_hw_ctl_init(dev, ctl, mmio, cat->mdss_ver, cat->mixer_count, cat->mixer); + if (IS_ERR(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed ctl object creation: err %d\n", rc); + goto fail; + } + rm->ctl_blks[ctl->id - CTL_0] = &hw->base; } + for (i = 0; i < cat->dspp_count; i++) { + struct dpu_hw_dspp *hw; + const struct dpu_dspp_cfg *dspp = &cat->dspp[i]; - for (type = 0; type < DPU_HW_BLK_MAX; type++) { - list_for_each_entry_safe(hw_cur, hw_nxt, &rm->hw_blks[type], - list) { - list_del(&hw_cur->list); - _dpu_rm_hw_destroy(hw_cur->type, hw_cur->hw); - kfree(hw_cur); + hw = dpu_hw_dspp_init(dev, dspp, mmio); + if (IS_ERR(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed dspp object creation: err %d\n", rc); + goto fail; } + rm->dspp_blks[dspp->id - DSPP_0] = &hw->base; } - dpu_hw_mdp_destroy(rm->hw_mdp); - rm->hw_mdp = NULL; + for (i = 0; i < cat->dsc_count; i++) { + struct dpu_hw_dsc *hw; + const struct dpu_dsc_cfg *dsc = &cat->dsc[i]; - mutex_destroy(&rm->rm_lock); + if (cat->mdss_ver->core_major_ver >= 7) + hw = dpu_hw_dsc_init_1_2(dev, dsc, mmio); + else + hw = dpu_hw_dsc_init(dev, dsc, mmio, cat->mdss_ver); - return 0; -} - -static int _dpu_rm_hw_blk_create( - struct dpu_rm *rm, - struct dpu_mdss_cfg *cat, - void __iomem *mmio, - enum dpu_hw_blk_type type, - uint32_t id, - void *hw_catalog_info) -{ - struct dpu_rm_hw_blk *blk; - struct dpu_hw_mdp *hw_mdp; - void *hw; + if (IS_ERR(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed dsc object creation: err %d\n", rc); + goto fail; + } + rm->dsc_blks[dsc->id - DSC_0] = &hw->base; + } - hw_mdp = rm->hw_mdp; + for (i = 0; i < cat->sspp_count; i++) { + struct dpu_hw_sspp *hw; + const struct dpu_sspp_cfg *sspp = &cat->sspp[i]; - switch (type) { - case DPU_HW_BLK_LM: - hw = dpu_hw_lm_init(id, mmio, cat); - break; - case DPU_HW_BLK_CTL: - hw = dpu_hw_ctl_init(id, mmio, cat); - break; - case DPU_HW_BLK_PINGPONG: - hw = dpu_hw_pingpong_init(id, mmio, cat); - break; - case DPU_HW_BLK_INTF: - hw = dpu_hw_intf_init(id, mmio, cat); - break; - case DPU_HW_BLK_SSPP: - /* SSPPs are not managed by the resource manager */ - case DPU_HW_BLK_TOP: - /* Top is a singleton, not managed in hw_blks list */ - case DPU_HW_BLK_MAX: - default: - DPU_ERROR("unsupported block type %d\n", type); - return -EINVAL; + hw = dpu_hw_sspp_init(dev, sspp, mmio, mdss_data, cat->mdss_ver); + if (IS_ERR(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed sspp object creation: err %d\n", rc); + goto fail; + } + rm->hw_sspp[sspp->id - SSPP_NONE] = hw; } - if (IS_ERR_OR_NULL(hw)) { - DPU_ERROR("failed hw object creation: type %d, err %ld\n", - type, PTR_ERR(hw)); - return -EFAULT; - } + if (cat->cdm) { + struct dpu_hw_cdm *hw; - blk = kzalloc(sizeof(*blk), GFP_KERNEL); - if (!blk) { - _dpu_rm_hw_destroy(type, hw); - return -ENOMEM; + hw = dpu_hw_cdm_init(dev, cat->cdm, mmio, cat->mdss_ver); + if (IS_ERR(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed cdm object creation: err %d\n", rc); + goto fail; + } + rm->cdm_blk = &hw->base; } - blk->type = type; - blk->id = id; - blk->hw = hw; - list_add_tail(&blk->list, &rm->hw_blks[type]); - return 0; + +fail: + return rc ? rc : -EFAULT; } -int dpu_rm_init(struct dpu_rm *rm, - struct dpu_mdss_cfg *cat, - void __iomem *mmio, - struct drm_device *dev) +static bool _dpu_rm_needs_split_display(const struct msm_display_topology *top) { - int rc, i; - enum dpu_hw_blk_type type; - - if (!rm || !cat || !mmio || !dev) { - DPU_ERROR("invalid kms\n"); - return -EINVAL; - } - - /* Clear, setup lists */ - memset(rm, 0, sizeof(*rm)); - - mutex_init(&rm->rm_lock); - - INIT_LIST_HEAD(&rm->rsvps); - for (type = 0; type < DPU_HW_BLK_MAX; type++) - INIT_LIST_HEAD(&rm->hw_blks[type]); + return top->num_intf > 1; +} - rm->dev = dev; +/** + * _dpu_rm_get_lm_peer - get the id of a mixer which is a peer of the primary + * @rm: dpu resource manager handle + * @primary_idx: index of primary mixer in rm->mixer_blks[] + * + * Returns: lm peer mixed id on success or %-EINVAL on error + */ +static int _dpu_rm_get_lm_peer(struct dpu_rm *rm, int primary_idx) +{ + const struct dpu_lm_cfg *prim_lm_cfg; - /* Some of the sub-blocks require an mdptop to be created */ - rm->hw_mdp = dpu_hw_mdptop_init(MDP_TOP, mmio, cat); - if (IS_ERR_OR_NULL(rm->hw_mdp)) { - rc = PTR_ERR(rm->hw_mdp); - rm->hw_mdp = NULL; - DPU_ERROR("failed: mdp hw not available\n"); - goto fail; - } + prim_lm_cfg = to_dpu_hw_mixer(rm->mixer_blks[primary_idx])->cap; - /* Interrogate HW catalog and create tracking items for hw blocks */ - for (i = 0; i < cat->mixer_count; i++) { - struct dpu_lm_cfg *lm = &cat->mixer[i]; - - if (lm->pingpong == PINGPONG_MAX) { - DPU_DEBUG("skip mixer %d without pingpong\n", lm->id); - continue; - } + if (prim_lm_cfg->lm_pair >= LM_0 && prim_lm_cfg->lm_pair < LM_MAX) + return prim_lm_cfg->lm_pair - LM_0; + return -EINVAL; +} - rc = _dpu_rm_hw_blk_create(rm, cat, mmio, DPU_HW_BLK_LM, - cat->mixer[i].id, &cat->mixer[i]); - if (rc) { - DPU_ERROR("failed: lm hw not available\n"); - goto fail; - } +static int _dpu_rm_reserve_cwb_mux_and_pingpongs(struct dpu_rm *rm, + struct dpu_global_state *global_state, + uint32_t crtc_id, + struct msm_display_topology *topology) +{ + int num_cwb_mux = topology->num_lm, cwb_mux_count = 0; + int cwb_pp_start_idx = PINGPONG_CWB_0 - PINGPONG_0; + int cwb_pp_idx[MAX_BLOCKS]; + int cwb_mux_idx[MAX_BLOCKS]; - if (!rm->lm_max_width) { - rm->lm_max_width = lm->sblk->maxwidth; - } else if (rm->lm_max_width != lm->sblk->maxwidth) { + /* + * Reserve additional dedicated CWB PINGPONG blocks and muxes for each + * mixer + * + * TODO: add support reserving resources for platforms with no + * PINGPONG_CWB + */ + for (int i = 0; i < ARRAY_SIZE(rm->mixer_blks) && + cwb_mux_count < num_cwb_mux; i++) { + for (int j = 0; j < ARRAY_SIZE(rm->cwb_blks); j++) { /* - * Don't expect to have hw where lm max widths differ. - * If found, take the min. + * Odd LMs must be assigned to odd CWB muxes and even + * LMs with even CWB muxes. + * + * Since the RM HW block array index is based on the HW + * block ids, we can also use the array index to enforce + * the odd/even rule. See dpu_rm_init() for more + * information */ - DPU_ERROR("unsupported: lm maxwidth differs\n"); - if (rm->lm_max_width > lm->sblk->maxwidth) - rm->lm_max_width = lm->sblk->maxwidth; - } - } + if (reserved_by_other(global_state->cwb_to_crtc_id, j, crtc_id) || + i % 2 != j % 2) + continue; - for (i = 0; i < cat->pingpong_count; i++) { - rc = _dpu_rm_hw_blk_create(rm, cat, mmio, DPU_HW_BLK_PINGPONG, - cat->pingpong[i].id, &cat->pingpong[i]); - if (rc) { - DPU_ERROR("failed: pp hw not available\n"); - goto fail; + cwb_mux_idx[cwb_mux_count] = j; + cwb_pp_idx[cwb_mux_count] = j + cwb_pp_start_idx; + cwb_mux_count++; + break; } } - for (i = 0; i < cat->intf_count; i++) { - if (cat->intf[i].type == INTF_NONE) { - DPU_DEBUG("skip intf %d with type none\n", i); - continue; - } - - rc = _dpu_rm_hw_blk_create(rm, cat, mmio, DPU_HW_BLK_INTF, - cat->intf[i].id, &cat->intf[i]); - if (rc) { - DPU_ERROR("failed: intf hw not available\n"); - goto fail; - } + if (cwb_mux_count != num_cwb_mux) { + DPU_ERROR("Unable to reserve all CWB PINGPONGs\n"); + return -ENAVAIL; } - for (i = 0; i < cat->ctl_count; i++) { - rc = _dpu_rm_hw_blk_create(rm, cat, mmio, DPU_HW_BLK_CTL, - cat->ctl[i].id, &cat->ctl[i]); - if (rc) { - DPU_ERROR("failed: ctl hw not available\n"); - goto fail; - } + for (int i = 0; i < cwb_mux_count; i++) { + global_state->pingpong_to_crtc_id[cwb_pp_idx[i]] = crtc_id; + global_state->cwb_to_crtc_id[cwb_mux_idx[i]] = crtc_id; } return 0; - -fail: - dpu_rm_destroy(rm); - - return rc; -} - -static bool _dpu_rm_needs_split_display(const struct msm_display_topology *top) -{ - return top->num_intf > 1; } /** @@ -410,479 +293,763 @@ static bool _dpu_rm_needs_split_display(const struct msm_display_topology *top) * proposed use case requirements, incl. hardwired dependent blocks like * pingpong * @rm: dpu resource manager handle - * @rsvp: reservation currently being created - * @reqs: proposed use case requirements - * @lm: proposed layer mixer, function checks if lm, and all other hardwired - * blocks connected to the lm (pp) is available and appropriate - * @pp: output parameter, pingpong block attached to the layer mixer. - * NULL if pp was not available, or not matching requirements. - * @primary_lm: if non-null, this function check if lm is compatible primary_lm - * as well as satisfying all other requirements - * @Return: true if lm matches all requirements, false otherwise + * @global_state: resources shared across multiple kms objects + * @crtc_id: crtc id requesting for allocation + * @lm_idx: index of proposed layer mixer in rm->mixer_blks[], function checks + * if lm, and all other hardwired blocks connected to the lm (pp) is + * available and appropriate + * @pp_idx: output parameter, index of pingpong block attached to the layer + * mixer in rm->pingpong_blks[]. + * @dspp_idx: output parameter, index of dspp block attached to the layer + * mixer in rm->dspp_blks[]. + * @topology: selected topology for the display + * Return: true if lm matches all requirements, false otherwise */ -static bool _dpu_rm_check_lm_and_get_connected_blks( - struct dpu_rm *rm, - struct dpu_rm_rsvp *rsvp, - struct dpu_rm_requirements *reqs, - struct dpu_rm_hw_blk *lm, - struct dpu_rm_hw_blk **pp, - struct dpu_rm_hw_blk *primary_lm) +static bool _dpu_rm_check_lm_and_get_connected_blks(struct dpu_rm *rm, + struct dpu_global_state *global_state, + uint32_t crtc_id, int lm_idx, int *pp_idx, int *dspp_idx, + struct msm_display_topology *topology) { - const struct dpu_lm_cfg *lm_cfg = to_dpu_hw_mixer(lm->hw)->cap; - struct dpu_rm_hw_iter iter; + const struct dpu_lm_cfg *lm_cfg; + int idx; - *pp = NULL; - - DPU_DEBUG("check lm %d pp %d\n", - lm_cfg->id, lm_cfg->pingpong); - - /* Check if this layer mixer is a peer of the proposed primary LM */ - if (primary_lm) { - const struct dpu_lm_cfg *prim_lm_cfg = - to_dpu_hw_mixer(primary_lm->hw)->cap; - - if (!test_bit(lm_cfg->id, &prim_lm_cfg->lm_pair_mask)) { - DPU_DEBUG("lm %d not peer of lm %d\n", lm_cfg->id, - prim_lm_cfg->id); - return false; - } + /* Already reserved? */ + if (reserved_by_other(global_state->mixer_to_crtc_id, lm_idx, crtc_id)) { + DPU_DEBUG("lm %d already reserved\n", lm_idx + LM_0); + return false; } - /* Already reserved? */ - if (RESERVED_BY_OTHER(lm, rsvp)) { - DPU_DEBUG("lm %d already reserved\n", lm_cfg->id); + lm_cfg = to_dpu_hw_mixer(rm->mixer_blks[lm_idx])->cap; + idx = lm_cfg->pingpong - PINGPONG_0; + if (idx < 0 || idx >= ARRAY_SIZE(rm->pingpong_blks)) { + DPU_ERROR("failed to get pp on lm %d\n", lm_cfg->pingpong); return false; } - dpu_rm_init_hw_iter(&iter, 0, DPU_HW_BLK_PINGPONG); - while (_dpu_rm_get_hw_locked(rm, &iter)) { - if (iter.blk->id == lm_cfg->pingpong) { - *pp = iter.blk; - break; - } + if (reserved_by_other(global_state->pingpong_to_crtc_id, idx, crtc_id)) { + DPU_DEBUG("lm %d pp %d already reserved\n", lm_cfg->id, + lm_cfg->pingpong); + return false; } + *pp_idx = idx; - if (!*pp) { - DPU_ERROR("failed to get pp on lm %d\n", lm_cfg->pingpong); + if (!topology->num_dspp) + return true; + + idx = lm_cfg->dspp - DSPP_0; + if (idx < 0 || idx >= ARRAY_SIZE(rm->dspp_blks)) { + DPU_ERROR("failed to get dspp on lm %d\n", lm_cfg->dspp); return false; } - if (RESERVED_BY_OTHER(*pp, rsvp)) { - DPU_DEBUG("lm %d pp %d already reserved\n", lm->id, - (*pp)->id); + if (reserved_by_other(global_state->dspp_to_crtc_id, idx, crtc_id)) { + DPU_DEBUG("lm %d dspp %d already reserved\n", lm_cfg->id, + lm_cfg->dspp); return false; } + *dspp_idx = idx; return true; } -static int _dpu_rm_reserve_lms( - struct dpu_rm *rm, - struct dpu_rm_rsvp *rsvp, - struct dpu_rm_requirements *reqs) +static int _dpu_rm_reserve_lms(struct dpu_rm *rm, + struct dpu_global_state *global_state, + uint32_t crtc_id, + struct msm_display_topology *topology) { - struct dpu_rm_hw_blk *lm[MAX_BLOCKS]; - struct dpu_rm_hw_blk *pp[MAX_BLOCKS]; - struct dpu_rm_hw_iter iter_i, iter_j; - int lm_count = 0; - int i, rc = 0; - - if (!reqs->topology.num_lm) { - DPU_ERROR("invalid number of lm: %d\n", reqs->topology.num_lm); + int lm_idx[MAX_BLOCKS]; + int pp_idx[MAX_BLOCKS]; + int dspp_idx[MAX_BLOCKS] = {0}; + int i, lm_count = 0; + + if (!topology->num_lm) { + DPU_ERROR("invalid number of lm: %d\n", topology->num_lm); return -EINVAL; } /* Find a primary mixer */ - dpu_rm_init_hw_iter(&iter_i, 0, DPU_HW_BLK_LM); - while (lm_count != reqs->topology.num_lm && - _dpu_rm_get_hw_locked(rm, &iter_i)) { - memset(&lm, 0, sizeof(lm)); - memset(&pp, 0, sizeof(pp)); - - lm_count = 0; - lm[lm_count] = iter_i.blk; - - if (!_dpu_rm_check_lm_and_get_connected_blks( - rm, rsvp, reqs, lm[lm_count], - &pp[lm_count], NULL)) + for (i = 0; i < ARRAY_SIZE(rm->mixer_blks) && + lm_count < topology->num_lm; i++) { + if (!rm->mixer_blks[i]) continue; + /* + * Reset lm_count to an even index. This will drop the previous + * primary mixer if failed to find its peer. + */ + lm_count &= ~1; + lm_idx[lm_count] = i; + + if (!_dpu_rm_check_lm_and_get_connected_blks(rm, global_state, + crtc_id, i, &pp_idx[lm_count], + &dspp_idx[lm_count], topology)) { + continue; + } + ++lm_count; /* Valid primary mixer found, find matching peers */ - dpu_rm_init_hw_iter(&iter_j, 0, DPU_HW_BLK_LM); + if (lm_count < topology->num_lm) { + int j = _dpu_rm_get_lm_peer(rm, i); + + /* ignore the peer if there is an error or if the peer was already processed */ + if (j < 0 || j < i) + continue; - while (lm_count != reqs->topology.num_lm && - _dpu_rm_get_hw_locked(rm, &iter_j)) { - if (iter_i.blk == iter_j.blk) + if (!rm->mixer_blks[j]) continue; - if (!_dpu_rm_check_lm_and_get_connected_blks( - rm, rsvp, reqs, iter_j.blk, - &pp[lm_count], iter_i.blk)) + if (!_dpu_rm_check_lm_and_get_connected_blks(rm, + global_state, crtc_id, j, + &pp_idx[lm_count], &dspp_idx[lm_count], + topology)) { continue; + } - lm[lm_count] = iter_j.blk; + lm_idx[lm_count] = j; ++lm_count; } } - if (lm_count != reqs->topology.num_lm) { + if (lm_count != topology->num_lm) { DPU_DEBUG("unable to find appropriate mixers\n"); return -ENAVAIL; } - for (i = 0; i < ARRAY_SIZE(lm); i++) { - if (!lm[i]) - break; - - lm[i]->rsvp_nxt = rsvp; - pp[i]->rsvp_nxt = rsvp; + for (i = 0; i < lm_count; i++) { + global_state->mixer_to_crtc_id[lm_idx[i]] = crtc_id; + global_state->pingpong_to_crtc_id[pp_idx[i]] = crtc_id; + global_state->dspp_to_crtc_id[dspp_idx[i]] = + topology->num_dspp ? crtc_id : 0; - trace_dpu_rm_reserve_lms(lm[i]->id, lm[i]->type, rsvp->enc_id, - pp[i]->id); + trace_dpu_rm_reserve_lms(lm_idx[i] + LM_0, crtc_id, + pp_idx[i] + PINGPONG_0); } - return rc; + return 0; } static int _dpu_rm_reserve_ctls( struct dpu_rm *rm, - struct dpu_rm_rsvp *rsvp, + struct dpu_global_state *global_state, + uint32_t crtc_id, const struct msm_display_topology *top) { - struct dpu_rm_hw_blk *ctls[MAX_BLOCKS]; - struct dpu_rm_hw_iter iter; - int i = 0, num_ctls = 0; - bool needs_split_display = false; - - memset(&ctls, 0, sizeof(ctls)); + int ctl_idx[MAX_BLOCKS]; + int i = 0, j, num_ctls; + bool needs_split_display; - /* each hw_intf needs its own hw_ctrl to program its control path */ - num_ctls = top->num_intf; + if (rm->has_legacy_ctls) { + /* + * TODO: check if there is a need for special handling if + * DPU < 5.0 get CWB support. + */ + num_ctls = top->num_intf; - needs_split_display = _dpu_rm_needs_split_display(top); + needs_split_display = _dpu_rm_needs_split_display(top); + } else { + /* use single CTL */ + num_ctls = 1; + needs_split_display = false; + } - dpu_rm_init_hw_iter(&iter, 0, DPU_HW_BLK_CTL); - while (_dpu_rm_get_hw_locked(rm, &iter)) { - const struct dpu_hw_ctl *ctl = to_dpu_hw_ctl(iter.blk->hw); - unsigned long features = ctl->caps->features; + for (j = 0; j < ARRAY_SIZE(rm->ctl_blks); j++) { + const struct dpu_hw_ctl *ctl; + unsigned long features; bool has_split_display; - if (RESERVED_BY_OTHER(iter.blk, rsvp)) + if (!rm->ctl_blks[j]) + continue; + if (reserved_by_other(global_state->ctl_to_crtc_id, j, crtc_id)) continue; + ctl = to_dpu_hw_ctl(rm->ctl_blks[j]); + features = ctl->caps->features; has_split_display = BIT(DPU_CTL_SPLIT_DISPLAY) & features; - DPU_DEBUG("ctl %d caps 0x%lX\n", iter.blk->id, features); + DPU_DEBUG("ctl %d caps 0x%lX\n", j + CTL_0, features); if (needs_split_display != has_split_display) continue; - ctls[i] = iter.blk; - DPU_DEBUG("ctl %d match\n", iter.blk->id); + ctl_idx[i] = j; + DPU_DEBUG("ctl %d match\n", j + CTL_0); if (++i == num_ctls) break; + } if (i != num_ctls) return -ENAVAIL; - for (i = 0; i < ARRAY_SIZE(ctls) && i < num_ctls; i++) { - ctls[i]->rsvp_nxt = rsvp; - trace_dpu_rm_reserve_ctls(ctls[i]->id, ctls[i]->type, - rsvp->enc_id); + for (i = 0; i < ARRAY_SIZE(ctl_idx) && i < num_ctls; i++) { + global_state->ctl_to_crtc_id[ctl_idx[i]] = crtc_id; + trace_dpu_rm_reserve_ctls(i + CTL_0, crtc_id); } return 0; } -static int _dpu_rm_reserve_intf( - struct dpu_rm *rm, - struct dpu_rm_rsvp *rsvp, - uint32_t id, - enum dpu_hw_blk_type type) +static int _dpu_rm_pingpong_next_index(struct dpu_global_state *global_state, + int start, + uint32_t crtc_id) +{ + int i; + + for (i = start; i < (PINGPONG_MAX - PINGPONG_0); i++) { + if (global_state->pingpong_to_crtc_id[i] == crtc_id) + return i; + } + + return -ENAVAIL; +} + +static int _dpu_rm_pingpong_dsc_check(int dsc_idx, int pp_idx) +{ + /* + * DSC with even index must be used with the PINGPONG with even index + * DSC with odd index must be used with the PINGPONG with odd index + */ + if ((dsc_idx & 0x01) != (pp_idx & 0x01)) + return -ENAVAIL; + + return 0; +} + +static int _dpu_rm_dsc_alloc(struct dpu_rm *rm, + struct dpu_global_state *global_state, + uint32_t crtc_id, + const struct msm_display_topology *top) { - struct dpu_rm_hw_iter iter; - int ret = 0; + int num_dsc = 0; + int pp_idx = 0; + int dsc_idx; + int ret; + + for (dsc_idx = 0; dsc_idx < ARRAY_SIZE(rm->dsc_blks) && + num_dsc < top->num_dsc; dsc_idx++) { + if (!rm->dsc_blks[dsc_idx]) + continue; - /* Find the block entry in the rm, and note the reservation */ - dpu_rm_init_hw_iter(&iter, 0, type); - while (_dpu_rm_get_hw_locked(rm, &iter)) { - if (iter.blk->id != id) + if (reserved_by_other(global_state->dsc_to_crtc_id, dsc_idx, crtc_id)) continue; - if (RESERVED_BY_OTHER(iter.blk, rsvp)) { - DPU_ERROR("type %d id %d already reserved\n", type, id); + pp_idx = _dpu_rm_pingpong_next_index(global_state, pp_idx, crtc_id); + if (pp_idx < 0) return -ENAVAIL; - } - iter.blk->rsvp_nxt = rsvp; - trace_dpu_rm_reserve_intf(iter.blk->id, iter.blk->type, - rsvp->enc_id); - break; + ret = _dpu_rm_pingpong_dsc_check(dsc_idx, pp_idx); + if (ret) + return -ENAVAIL; + + global_state->dsc_to_crtc_id[dsc_idx] = crtc_id; + num_dsc++; + pp_idx++; } - /* Shouldn't happen since intfs are fixed at probe */ - if (!iter.hw) { - DPU_ERROR("couldn't find type %d id %d\n", type, id); - return -EINVAL; + if (num_dsc < top->num_dsc) { + DPU_ERROR("DSC allocation failed num_dsc=%d required=%d\n", + num_dsc, top->num_dsc); + return -ENAVAIL; } - return ret; + return 0; } -static int _dpu_rm_reserve_intf_related_hw( - struct dpu_rm *rm, - struct dpu_rm_rsvp *rsvp, - struct dpu_encoder_hw_resources *hw_res) +static int _dpu_rm_dsc_alloc_pair(struct dpu_rm *rm, + struct dpu_global_state *global_state, + uint32_t crtc_id, + const struct msm_display_topology *top) { - int i, ret = 0; - u32 id; + int num_dsc = 0; + int dsc_idx, pp_idx = 0; + int ret; - for (i = 0; i < ARRAY_SIZE(hw_res->intfs); i++) { - if (hw_res->intfs[i] == INTF_MODE_NONE) + /* only start from even dsc index */ + for (dsc_idx = 0; dsc_idx < ARRAY_SIZE(rm->dsc_blks) && + num_dsc < top->num_dsc; dsc_idx += 2) { + if (!rm->dsc_blks[dsc_idx] || + !rm->dsc_blks[dsc_idx + 1]) continue; - id = i + INTF_0; - ret = _dpu_rm_reserve_intf(rm, rsvp, id, - DPU_HW_BLK_INTF); - if (ret) - return ret; + + /* consective dsc index to be paired */ + if (reserved_by_other(global_state->dsc_to_crtc_id, dsc_idx, crtc_id) || + reserved_by_other(global_state->dsc_to_crtc_id, dsc_idx + 1, crtc_id)) + continue; + + pp_idx = _dpu_rm_pingpong_next_index(global_state, pp_idx, crtc_id); + if (pp_idx < 0) + return -ENAVAIL; + + ret = _dpu_rm_pingpong_dsc_check(dsc_idx, pp_idx); + if (ret) { + pp_idx = 0; + continue; + } + + pp_idx = _dpu_rm_pingpong_next_index(global_state, pp_idx + 1, crtc_id); + if (pp_idx < 0) + return -ENAVAIL; + + ret = _dpu_rm_pingpong_dsc_check(dsc_idx + 1, pp_idx); + if (ret) { + pp_idx = 0; + continue; + } + + global_state->dsc_to_crtc_id[dsc_idx] = crtc_id; + global_state->dsc_to_crtc_id[dsc_idx + 1] = crtc_id; + num_dsc += 2; + pp_idx++; /* start for next pair */ } - return ret; + if (num_dsc < top->num_dsc) { + DPU_ERROR("DSC allocation failed num_dsc=%d required=%d\n", + num_dsc, top->num_dsc); + return -ENAVAIL; + } + + return 0; +} + +static int _dpu_rm_reserve_dsc(struct dpu_rm *rm, + struct dpu_global_state *global_state, + uint32_t crtc_id, + const struct msm_display_topology *top) +{ + if (!top->num_dsc || !top->num_intf) + return 0; + + /* + * Facts: + * 1) no pingpong split (two layer mixers shared one pingpong) + * 2) DSC pair starts from even index, such as index(0,1), (2,3), etc + * 3) even PINGPONG connects to even DSC + * 4) odd PINGPONG connects to odd DSC + * 5) pair: encoder +--> pp_idx_0 --> dsc_idx_0 + * +--> pp_idx_1 --> dsc_idx_1 + */ + + /* num_dsc should be either 1, 2 or 4 */ + if (top->num_dsc > top->num_intf) /* merge mode */ + return _dpu_rm_dsc_alloc_pair(rm, global_state, crtc_id, top); + else + return _dpu_rm_dsc_alloc(rm, global_state, crtc_id, top); + + return 0; } -static int _dpu_rm_make_next_rsvp( +static int _dpu_rm_reserve_cdm(struct dpu_rm *rm, + struct dpu_global_state *global_state, + uint32_t crtc_id, + int num_cdm) +{ + /* try allocating only one CDM block */ + if (!rm->cdm_blk) { + DPU_ERROR("CDM block does not exist\n"); + return -EIO; + } + + if (num_cdm > 1) { + DPU_ERROR("More than 1 INTF requesting CDM\n"); + return -EINVAL; + } + + if (global_state->cdm_to_crtc_id) { + DPU_ERROR("CDM_0 is already allocated\n"); + return -EIO; + } + + global_state->cdm_to_crtc_id = crtc_id; + + return 0; +} + +static int _dpu_rm_make_reservation( struct dpu_rm *rm, - struct drm_encoder *enc, - struct drm_crtc_state *crtc_state, - struct dpu_rm_rsvp *rsvp, - struct dpu_rm_requirements *reqs) + struct dpu_global_state *global_state, + uint32_t crtc_id, + struct msm_display_topology *topology) { int ret; - /* Create reservation info, tag reserved blocks with it as we go */ - rsvp->seq = ++rm->rsvp_next_seq; - rsvp->enc_id = enc->base.id; - list_add_tail(&rsvp->list, &rm->rsvps); - - ret = _dpu_rm_reserve_lms(rm, rsvp, reqs); + ret = _dpu_rm_reserve_lms(rm, global_state, crtc_id, topology); if (ret) { DPU_ERROR("unable to find appropriate mixers\n"); return ret; } - ret = _dpu_rm_reserve_ctls(rm, rsvp, &reqs->topology); + if (topology->cwb_enabled) { + ret = _dpu_rm_reserve_cwb_mux_and_pingpongs(rm, global_state, + crtc_id, topology); + if (ret) + return ret; + } + + ret = _dpu_rm_reserve_ctls(rm, global_state, crtc_id, + topology); if (ret) { DPU_ERROR("unable to find appropriate CTL\n"); return ret; } - ret = _dpu_rm_reserve_intf_related_hw(rm, rsvp, &reqs->hw_res); + ret = _dpu_rm_reserve_dsc(rm, global_state, crtc_id, topology); if (ret) return ret; + if (topology->num_cdm > 0) { + ret = _dpu_rm_reserve_cdm(rm, global_state, crtc_id, topology->num_cdm); + if (ret) { + DPU_ERROR("unable to find CDM blk\n"); + return ret; + } + } + return ret; } -static int _dpu_rm_populate_requirements( - struct dpu_rm *rm, - struct drm_encoder *enc, - struct drm_crtc_state *crtc_state, - struct dpu_rm_requirements *reqs, - struct msm_display_topology req_topology) +static void _dpu_rm_clear_mapping(uint32_t *res_mapping, int cnt, + uint32_t crtc_id) { - dpu_encoder_get_hw_resources(enc, &reqs->hw_res); + int i; - reqs->topology = req_topology; - - DRM_DEBUG_KMS("num_lm: %d num_enc: %d num_intf: %d\n", - reqs->topology.num_lm, reqs->topology.num_enc, - reqs->topology.num_intf); + for (i = 0; i < cnt; i++) { + if (res_mapping[i] == crtc_id) + res_mapping[i] = 0; + } +} - return 0; +/** + * dpu_rm_release - Given the encoder for the display chain, release any + * HW blocks previously reserved for that use case. + * @global_state: resources shared across multiple kms objects + * @crtc: DRM CRTC handle + * @return: 0 on Success otherwise -ERROR + */ +void dpu_rm_release(struct dpu_global_state *global_state, + struct drm_crtc *crtc) +{ + uint32_t crtc_id = crtc->base.id; + + _dpu_rm_clear_mapping(global_state->pingpong_to_crtc_id, + ARRAY_SIZE(global_state->pingpong_to_crtc_id), crtc_id); + _dpu_rm_clear_mapping(global_state->mixer_to_crtc_id, + ARRAY_SIZE(global_state->mixer_to_crtc_id), crtc_id); + _dpu_rm_clear_mapping(global_state->ctl_to_crtc_id, + ARRAY_SIZE(global_state->ctl_to_crtc_id), crtc_id); + _dpu_rm_clear_mapping(global_state->dsc_to_crtc_id, + ARRAY_SIZE(global_state->dsc_to_crtc_id), crtc_id); + _dpu_rm_clear_mapping(global_state->dspp_to_crtc_id, + ARRAY_SIZE(global_state->dspp_to_crtc_id), crtc_id); + _dpu_rm_clear_mapping(&global_state->cdm_to_crtc_id, 1, crtc_id); + _dpu_rm_clear_mapping(global_state->cwb_to_crtc_id, + ARRAY_SIZE(global_state->cwb_to_crtc_id), crtc_id); } -static struct dpu_rm_rsvp *_dpu_rm_get_rsvp( +/** + * dpu_rm_reserve - Given a CRTC->Encoder->Connector display chain, analyze + * the use connections and user requirements, specified through related + * topology control properties, and reserve hardware blocks to that + * display chain. + * HW blocks can then be accessed through dpu_rm_get_* functions. + * HW Reservations should be released via dpu_rm_release_hw. + * @rm: DPU Resource Manager handle + * @global_state: resources shared across multiple kms objects + * @crtc: DRM CRTC handle + * @topology: Pointer to topology info for the display + * @return: 0 on Success otherwise -ERROR + */ +int dpu_rm_reserve( struct dpu_rm *rm, - struct drm_encoder *enc) + struct dpu_global_state *global_state, + struct drm_crtc *crtc, + struct msm_display_topology *topology) { - struct dpu_rm_rsvp *i; + int ret; - if (!rm || !enc) { - DPU_ERROR("invalid params\n"); - return NULL; + if (IS_ERR(global_state)) { + DPU_ERROR("failed to global state\n"); + return PTR_ERR(global_state); } - if (list_empty(&rm->rsvps)) - return NULL; + DRM_DEBUG_KMS("reserving hw for crtc %d\n", crtc->base.id); - list_for_each_entry(i, &rm->rsvps, list) - if (i->enc_id == enc->base.id) - return i; + DRM_DEBUG_KMS("num_lm: %d num_dsc: %d num_intf: %d\n", + topology->num_lm, topology->num_dsc, + topology->num_intf); - return NULL; + ret = _dpu_rm_make_reservation(rm, global_state, crtc->base.id, topology); + if (ret) + DPU_ERROR("failed to reserve hw resources: %d\n", ret); + + return ret; } -/** - * _dpu_rm_release_rsvp - release resources and release a reservation - * @rm: KMS handle - * @rsvp: RSVP pointer to release and release resources for - */ -static void _dpu_rm_release_rsvp(struct dpu_rm *rm, struct dpu_rm_rsvp *rsvp) +static struct dpu_hw_sspp *dpu_rm_try_sspp(struct dpu_rm *rm, + struct dpu_global_state *global_state, + struct drm_crtc *crtc, + struct dpu_rm_sspp_requirements *reqs, + unsigned int type) { - struct dpu_rm_rsvp *rsvp_c, *rsvp_n; - struct dpu_rm_hw_blk *blk; - enum dpu_hw_blk_type type; + uint32_t crtc_id = crtc->base.id; + struct dpu_hw_sspp *hw_sspp; + int i; - if (!rsvp) - return; + for (i = 0; i < ARRAY_SIZE(rm->hw_sspp); i++) { + if (!rm->hw_sspp[i]) + continue; - DPU_DEBUG("rel rsvp %d enc %d\n", rsvp->seq, rsvp->enc_id); + if (global_state->sspp_to_crtc_id[i]) + continue; - list_for_each_entry_safe(rsvp_c, rsvp_n, &rm->rsvps, list) { - if (rsvp == rsvp_c) { - list_del(&rsvp_c->list); - break; - } - } + hw_sspp = rm->hw_sspp[i]; - for (type = 0; type < DPU_HW_BLK_MAX; type++) { - list_for_each_entry(blk, &rm->hw_blks[type], list) { - if (blk->rsvp == rsvp) { - blk->rsvp = NULL; - DPU_DEBUG("rel rsvp %d enc %d %d %d\n", - rsvp->seq, rsvp->enc_id, - blk->type, blk->id); - } - if (blk->rsvp_nxt == rsvp) { - blk->rsvp_nxt = NULL; - DPU_DEBUG("rel rsvp_nxt %d enc %d %d %d\n", - rsvp->seq, rsvp->enc_id, - blk->type, blk->id); - } - } - } + if (hw_sspp->cap->type != type) + continue; - kfree(rsvp); -} + if (reqs->scale && !hw_sspp->cap->sblk->scaler_blk.len) + continue; -void dpu_rm_release(struct dpu_rm *rm, struct drm_encoder *enc) -{ - struct dpu_rm_rsvp *rsvp; + // TODO: QSEED2 and RGB scalers are not yet supported + if (reqs->scale && !hw_sspp->ops.setup_scaler) + continue; - if (!rm || !enc) { - DPU_ERROR("invalid params\n"); - return; - } + if (reqs->yuv && !hw_sspp->cap->sblk->csc_blk.len) + continue; - mutex_lock(&rm->rm_lock); + if (reqs->rot90 && !(hw_sspp->cap->features & DPU_SSPP_INLINE_ROTATION)) + continue; + + global_state->sspp_to_crtc_id[i] = crtc_id; - rsvp = _dpu_rm_get_rsvp(rm, enc); - if (!rsvp) { - DPU_ERROR("failed to find rsvp for enc %d\n", enc->base.id); - goto end; + return rm->hw_sspp[i]; } - _dpu_rm_release_rsvp(rm, rsvp); -end: - mutex_unlock(&rm->rm_lock); + return NULL; } -static void _dpu_rm_commit_rsvp(struct dpu_rm *rm, struct dpu_rm_rsvp *rsvp) +/** + * dpu_rm_reserve_sspp - Reserve the required SSPP for the provided CRTC + * @rm: DPU Resource Manager handle + * @global_state: private global state + * @crtc: DRM CRTC handle + * @reqs: SSPP required features + */ +struct dpu_hw_sspp *dpu_rm_reserve_sspp(struct dpu_rm *rm, + struct dpu_global_state *global_state, + struct drm_crtc *crtc, + struct dpu_rm_sspp_requirements *reqs) { - struct dpu_rm_hw_blk *blk; - enum dpu_hw_blk_type type; - - /* Swap next rsvp to be the active */ - for (type = 0; type < DPU_HW_BLK_MAX; type++) { - list_for_each_entry(blk, &rm->hw_blks[type], list) { - if (blk->rsvp_nxt) { - blk->rsvp = blk->rsvp_nxt; - blk->rsvp_nxt = NULL; - } - } - } + struct dpu_hw_sspp *hw_sspp = NULL; + + if (!reqs->scale && !reqs->yuv) + hw_sspp = dpu_rm_try_sspp(rm, global_state, crtc, reqs, SSPP_TYPE_DMA); + if (!hw_sspp && !reqs->yuv) + hw_sspp = dpu_rm_try_sspp(rm, global_state, crtc, reqs, SSPP_TYPE_RGB); + if (!hw_sspp) + hw_sspp = dpu_rm_try_sspp(rm, global_state, crtc, reqs, SSPP_TYPE_VIG); + + return hw_sspp; } -int dpu_rm_reserve( - struct dpu_rm *rm, - struct drm_encoder *enc, - struct drm_crtc_state *crtc_state, - struct msm_display_topology topology, - bool test_only) +/** + * dpu_rm_release_all_sspp - Given the CRTC, release all SSPP + * blocks previously reserved for that use case. + * @global_state: resources shared across multiple kms objects + * @crtc: DRM CRTC handle + */ +void dpu_rm_release_all_sspp(struct dpu_global_state *global_state, + struct drm_crtc *crtc) { - struct dpu_rm_rsvp *rsvp_cur, *rsvp_nxt; - struct dpu_rm_requirements reqs; - int ret; - - /* Check if this is just a page-flip */ - if (!drm_atomic_crtc_needs_modeset(crtc_state)) - return 0; + uint32_t crtc_id = crtc->base.id; - DRM_DEBUG_KMS("reserving hw for enc %d crtc %d test_only %d\n", - enc->base.id, crtc_state->crtc->base.id, test_only); + _dpu_rm_clear_mapping(global_state->sspp_to_crtc_id, + ARRAY_SIZE(global_state->sspp_to_crtc_id), crtc_id); +} - mutex_lock(&rm->rm_lock); +static char *dpu_hw_blk_type_name[] = { + [DPU_HW_BLK_TOP] = "TOP", + [DPU_HW_BLK_SSPP] = "SSPP", + [DPU_HW_BLK_LM] = "LM", + [DPU_HW_BLK_CTL] = "CTL", + [DPU_HW_BLK_PINGPONG] = "pingpong", + [DPU_HW_BLK_INTF] = "INTF", + [DPU_HW_BLK_WB] = "WB", + [DPU_HW_BLK_DSPP] = "DSPP", + [DPU_HW_BLK_MERGE_3D] = "merge_3d", + [DPU_HW_BLK_DSC] = "DSC", + [DPU_HW_BLK_CDM] = "CDM", + [DPU_HW_BLK_MAX] = "unknown", +}; - _dpu_rm_print_rsvps(rm, DPU_RM_STAGE_BEGIN); +/** + * dpu_rm_get_assigned_resources - Get hw resources of the given type that are + * assigned to this encoder + * @rm: DPU Resource Manager handle + * @global_state: resources shared across multiple kms objects + * @crtc: DRM CRTC handle + * @type: resource type to return data for + * @blks: pointer to the array to be filled by HW resources + * @blks_size: size of the @blks array + */ +int dpu_rm_get_assigned_resources(struct dpu_rm *rm, + struct dpu_global_state *global_state, struct drm_crtc *crtc, + enum dpu_hw_blk_type type, struct dpu_hw_blk **blks, int blks_size) +{ + uint32_t crtc_id = crtc->base.id; + struct dpu_hw_blk **hw_blks; + uint32_t *hw_to_crtc_id; + int i, num_blks, max_blks; - ret = _dpu_rm_populate_requirements(rm, enc, crtc_state, &reqs, - topology); - if (ret) { - DPU_ERROR("failed to populate hw requirements\n"); - goto end; + switch (type) { + case DPU_HW_BLK_PINGPONG: + case DPU_HW_BLK_DCWB_PINGPONG: + hw_blks = rm->pingpong_blks; + hw_to_crtc_id = global_state->pingpong_to_crtc_id; + max_blks = ARRAY_SIZE(rm->pingpong_blks); + break; + case DPU_HW_BLK_LM: + hw_blks = rm->mixer_blks; + hw_to_crtc_id = global_state->mixer_to_crtc_id; + max_blks = ARRAY_SIZE(rm->mixer_blks); + break; + case DPU_HW_BLK_CTL: + hw_blks = rm->ctl_blks; + hw_to_crtc_id = global_state->ctl_to_crtc_id; + max_blks = ARRAY_SIZE(rm->ctl_blks); + break; + case DPU_HW_BLK_DSPP: + hw_blks = rm->dspp_blks; + hw_to_crtc_id = global_state->dspp_to_crtc_id; + max_blks = ARRAY_SIZE(rm->dspp_blks); + break; + case DPU_HW_BLK_DSC: + hw_blks = rm->dsc_blks; + hw_to_crtc_id = global_state->dsc_to_crtc_id; + max_blks = ARRAY_SIZE(rm->dsc_blks); + break; + case DPU_HW_BLK_CDM: + hw_blks = &rm->cdm_blk; + hw_to_crtc_id = &global_state->cdm_to_crtc_id; + max_blks = 1; + break; + case DPU_HW_BLK_CWB: + hw_blks = rm->cwb_blks; + hw_to_crtc_id = global_state->cwb_to_crtc_id; + max_blks = ARRAY_SIZE(rm->cwb_blks); + break; + default: + DPU_ERROR("blk type %d not managed by rm\n", type); + return 0; } - /* - * We only support one active reservation per-hw-block. But to implement - * transactional semantics for test-only, and for allowing failure while - * modifying your existing reservation, over the course of this - * function we can have two reservations: - * Current: Existing reservation - * Next: Proposed reservation. The proposed reservation may fail, or may - * be discarded if in test-only mode. - * If reservation is successful, and we're not in test-only, then we - * replace the current with the next. - */ - rsvp_nxt = kzalloc(sizeof(*rsvp_nxt), GFP_KERNEL); - if (!rsvp_nxt) { - ret = -ENOMEM; - goto end; - } + num_blks = 0; + for (i = 0; i < max_blks; i++) { + if (hw_to_crtc_id[i] != crtc_id) + continue; - rsvp_cur = _dpu_rm_get_rsvp(rm, enc); + if (type == DPU_HW_BLK_PINGPONG) { + struct dpu_hw_pingpong *pp = to_dpu_hw_pingpong(hw_blks[i]); - /* Check the proposed reservation, store it in hw's "next" field */ - ret = _dpu_rm_make_next_rsvp(rm, enc, crtc_state, rsvp_nxt, &reqs); + if (pp->idx >= PINGPONG_CWB_0) + continue; + } - _dpu_rm_print_rsvps(rm, DPU_RM_STAGE_AFTER_RSVPNEXT); + if (type == DPU_HW_BLK_DCWB_PINGPONG) { + struct dpu_hw_pingpong *pp = to_dpu_hw_pingpong(hw_blks[i]); - if (ret) { - DPU_ERROR("failed to reserve hw resources: %d\n", ret); - _dpu_rm_release_rsvp(rm, rsvp_nxt); - } else if (test_only) { - /* - * Normally, if test_only, test the reservation and then undo - * However, if the user requests LOCK, then keep the reservation - * made during the atomic_check phase. - */ - DPU_DEBUG("test_only: discard test rsvp[s%de%d]\n", - rsvp_nxt->seq, rsvp_nxt->enc_id); - _dpu_rm_release_rsvp(rm, rsvp_nxt); - } else { - _dpu_rm_release_rsvp(rm, rsvp_cur); + if (pp->idx < PINGPONG_CWB_0) + continue; + } - _dpu_rm_commit_rsvp(rm, rsvp_nxt); + if (num_blks == blks_size) { + DPU_ERROR("More than %d %s assigned to crtc %d\n", + blks_size, dpu_hw_blk_type_name[type], crtc_id); + break; + } + if (!hw_blks[i]) { + DPU_ERROR("%s unavailable to assign to crtc %d\n", + dpu_hw_blk_type_name[type], crtc_id); + break; + } + blks[num_blks++] = hw_blks[i]; } - _dpu_rm_print_rsvps(rm, DPU_RM_STAGE_FINAL); + return num_blks; +} -end: - mutex_unlock(&rm->rm_lock); +static void dpu_rm_print_state_helper(struct drm_printer *p, + struct dpu_hw_blk *blk, + uint32_t mapping) +{ + if (!blk) + drm_puts(p, "- "); + else if (!mapping) + drm_puts(p, "# "); + else + drm_printf(p, "%d ", mapping); +} - return ret; + +/** + * dpu_rm_print_state - output the RM private state + * @p: DRM printer + * @global_state: global state + */ +void dpu_rm_print_state(struct drm_printer *p, + const struct dpu_global_state *global_state) +{ + const struct dpu_rm *rm = global_state->rm; + int i; + + drm_puts(p, "resource mapping:\n"); + drm_puts(p, "\tpingpong="); + for (i = 0; i < ARRAY_SIZE(global_state->pingpong_to_crtc_id); i++) + dpu_rm_print_state_helper(p, rm->pingpong_blks[i], + global_state->pingpong_to_crtc_id[i]); + drm_puts(p, "\n"); + + drm_puts(p, "\tmixer="); + for (i = 0; i < ARRAY_SIZE(global_state->mixer_to_crtc_id); i++) + dpu_rm_print_state_helper(p, rm->mixer_blks[i], + global_state->mixer_to_crtc_id[i]); + drm_puts(p, "\n"); + + drm_puts(p, "\tctl="); + for (i = 0; i < ARRAY_SIZE(global_state->ctl_to_crtc_id); i++) + dpu_rm_print_state_helper(p, rm->ctl_blks[i], + global_state->ctl_to_crtc_id[i]); + drm_puts(p, "\n"); + + drm_puts(p, "\tdspp="); + for (i = 0; i < ARRAY_SIZE(global_state->dspp_to_crtc_id); i++) + dpu_rm_print_state_helper(p, rm->dspp_blks[i], + global_state->dspp_to_crtc_id[i]); + drm_puts(p, "\n"); + + drm_puts(p, "\tdsc="); + for (i = 0; i < ARRAY_SIZE(global_state->dsc_to_crtc_id); i++) + dpu_rm_print_state_helper(p, rm->dsc_blks[i], + global_state->dsc_to_crtc_id[i]); + drm_puts(p, "\n"); + + drm_puts(p, "\tcdm="); + dpu_rm_print_state_helper(p, rm->cdm_blk, + global_state->cdm_to_crtc_id); + drm_puts(p, "\n"); + + drm_puts(p, "\tsspp="); + /* skip SSPP_NONE and start from the next index */ + for (i = SSPP_NONE + 1; i < ARRAY_SIZE(global_state->sspp_to_crtc_id); i++) + dpu_rm_print_state_helper(p, rm->hw_sspp[i] ? &rm->hw_sspp[i]->base : NULL, + global_state->sspp_to_crtc_id[i]); + drm_puts(p, "\n"); + + drm_puts(p, "\tcwb="); + for (i = 0; i < ARRAY_SIZE(global_state->cwb_to_crtc_id); i++) + dpu_rm_print_state_helper(p, rm->cwb_blks[i], + global_state->cwb_to_crtc_id[i]); + drm_puts(p, "\n"); } |
