diff options
author | Jeykumar Sankaran <jsanka@codeaurora.org> | 2018-06-27 15:26:09 -0400 |
---|---|---|
committer | Sean Paul <seanpaul@chromium.org> | 2018-07-26 10:45:04 -0400 |
commit | 25fdd5933e4c0f5fe2ea5cd59994f8ac5fbe90ef (patch) | |
tree | 0da1ad5999deaee810a28b4bfa47d60d4822e00c /drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c | |
parent | 036bfeb33b89578ab924fa198213260659e08dc8 (diff) | |
download | blackbird-obmc-linux-25fdd5933e4c0f5fe2ea5cd59994f8ac5fbe90ef.tar.gz blackbird-obmc-linux-25fdd5933e4c0f5fe2ea5cd59994f8ac5fbe90ef.zip |
drm/msm: Add SDM845 DPU support
SDM845 SoC includes the Mobile Display Sub System (MDSS) which is a
top level wrapper consisting of Display Processing Unit (DPU) and
display peripheral modules such as Display Serial Interface (DSI)
and DisplayPort (DP).
MDSS functions essentially as a back-end composition engine. It blends
video and graphic images stored in the frame buffers and scans out the
composed image to a display sink (over DSI/DP).
The following diagram represents hardware blocks for a simple pipeline
(two planes are present on a given crtc which is connected to a DSI
connector):
MDSS
+---------------------------------+
| +-----------------------------+ |
| | DPU | |
| | +--------+ +--------+ | |
| | | SSPP | | SSPP | | |
| | +----+---+ +----+---+ | |
| | | | | |
| | +----v-----------v---+ | |
| | | Layer Mixer (LM) | | |
| | +--------------------+ | |
| | +--------------------+ | |
| | | PingPong (PP) | | |
| | +--------------------+ | |
| | +--------------------+ | |
| | | INTERFACE (VIDEO) | | |
| | +---+----------------+ | |
| +------|----------------------+ |
| | |
| +------|---------------------+ |
| | | DISPLAY PERIPHERALS | |
| | +---v-+ +-----+ | |
| | | DSI | | DP | | |
| | +-----+ +-----+ | |
| +----------------------------+ |
+---------------------------------+
The number of DPU sub-blocks (i.e. SSPPs, LMs, PP blocks and INTFs)
depends on SoC capabilities.
Overview of DPU sub-blocks:
---------------------------
* Source Surface Processor (SSPP):
Refers to any of hardware pipes like ViG, DMA etc. Only ViG pipes are
capable of performing format conversion, scaling and quality improvement
for source surfaces.
* Layer Mixer (LM):
Blend source surfaces together (in requested zorder)
* PingPong (PP):
This block controls frame done interrupt output, EOL and EOF generation,
overflow/underflow control.
* Display interface (INTF):
Timing generator and interface connecting the display peripherals.
DRM components mapping to DPU architecture:
------------------------------------------
PLANEs maps to SSPPs
CRTC maps to LMs
Encoder maps to PPs, INTFs
Data flow setup:
---------------
MDSS hardware can support various data flows (e.g.):
- Dual pipe: Output from two LMs combined to single display.
- Split display: Output from two LMs connected to two separate
interfaces.
The hardware capabilities determine the number of concurrent data paths
possible. Any control path (i.e. pipeline w/i DPU) can be routed to any
of the hardware data paths. A given control path can be triggered,
flushed and controlled independently.
Changes in v3:
- Move msm_media_info.h from uapi to dpu/ subdir
- Remove preclose callback dpu (it's handled in core)
- Fix kbuild warnings with parent_ops
- Remove unused functions from dpu_core_irq
- Rename mdss_phys to mdss
- Rename mdp_phys address space to mdp
- Drop _phys from vbif and regdma binding names
Signed-off-by: Abhinav Kumar <abhinavk@codeaurora.org>
Signed-off-by: Archit Taneja <architt@codeaurora.org>
Signed-off-by: Chandan Uddaraju <chandanu@codeaurora.org>
Signed-off-by: Jeykumar Sankaran <jsanka@codeaurora.org>
Signed-off-by: Jordan Crouse <jcrouse@codeaurora.org>
Signed-off-by: Rajesh Yadav <ryadav@codeaurora.org>
Signed-off-by: Sravanthi Kollukuduru <skolluku@codeaurora.org>
Signed-off-by: Sean Paul <seanpaul@chromium.org>
[robclark minor rebase]
Signed-off-by: Rob Clark <robdclark@gmail.com>
Diffstat (limited to 'drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c')
-rw-r--r-- | drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c | 1079 |
1 files changed, 1079 insertions, 0 deletions
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c new file mode 100644 index 000000000000..13c0a36d4ef9 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c @@ -0,0 +1,1079 @@ +/* + * 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. + * + */ + +#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_pingpong.h" +#include "dpu_hw_intf.h" +#include "dpu_encoder.h" +#include "dpu_trace.h" + +#define RESERVED_BY_OTHER(h, r) \ + ((h)->rsvp && ((h)->rsvp->enc_id != (r)->enc_id)) + +#define RM_RQ_LOCK(r) ((r)->top_ctrl & BIT(DPU_RM_TOPCTL_RESERVE_LOCK)) +#define RM_RQ_CLEAR(r) ((r)->top_ctrl & BIT(DPU_RM_TOPCTL_RESERVE_CLEAR)) +#define RM_RQ_DS(r) ((r)->top_ctrl & BIT(DPU_RM_TOPCTL_DS)) +#define RM_IS_TOPOLOGY_MATCH(t, r) ((t).num_lm == (r).num_lm && \ + (t).num_comp_enc == (r).num_enc && \ + (t).num_intf == (r).num_intf) + +struct dpu_rm_topology_def { + enum dpu_rm_topology_name top_name; + int num_lm; + int num_comp_enc; + int num_intf; + int num_ctl; + int needs_split_display; +}; + +static const struct dpu_rm_topology_def g_top_table[] = { + { DPU_RM_TOPOLOGY_NONE, 0, 0, 0, 0, false }, + { DPU_RM_TOPOLOGY_SINGLEPIPE, 1, 0, 1, 1, false }, + { DPU_RM_TOPOLOGY_DUALPIPE, 2, 0, 2, 2, true }, + { DPU_RM_TOPOLOGY_DUALPIPE_3DMERGE, 2, 0, 1, 1, false }, +}; + +/** + * struct dpu_rm_requirements - Reservation requirements parameter bundle + * @top_ctrl: topology control preference from kernel client + * @top: selected topology for the display + * @hw_res: Hardware resources required as reported by the encoders + */ +struct dpu_rm_requirements { + uint64_t top_ctrl; + const struct dpu_rm_topology_def *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. + * @topology DRM<->HW topology use case + */ +struct dpu_rm_rsvp { + struct list_head list; + uint32_t seq; + uint32_t enc_id; + enum dpu_rm_topology_name topology; +}; + +/** + * 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; +}; + +/** + * dpu_rm_dbg_rsvp_stage - enum of steps in making reservation for event logging + */ +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( + struct dpu_rm *rm, + enum dpu_rm_dbg_rsvp_stage stage) +{ + 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] topology %d\n", stage, rsvp->seq, + rsvp->enc_id, rsvp->topology); + } + + 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; + + 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); + } + } +} + +struct dpu_hw_mdp *dpu_rm_get_mdp(struct dpu_rm *rm) +{ + return rm->hw_mdp; +} + +enum dpu_rm_topology_name +dpu_rm_get_topology_name(struct msm_display_topology topology) +{ + int i; + + for (i = 0; i < DPU_RM_TOPOLOGY_MAX; i++) + if (RM_IS_TOPOLOGY_MATCH(g_top_table[i], topology)) + return g_top_table[i].top_name; + + return DPU_RM_TOPOLOGY_NONE; +} + +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; + + if (!rm || !i || i->type >= DPU_HW_BLK_MAX) { + DPU_ERROR("invalid rm\n"); + return false; + } + + i->hw = NULL; + blk_list = &rm->hw_blks[i->type]; + + if (i->blk && (&i->blk->list == blk_list)) { + DPU_DEBUG("attempt resume iteration past last\n"); + return false; + } + + i->blk = list_prepare_entry(i->blk, blk_list, list); + + 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; + } + + 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; + } + } + + 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; + + mutex_lock(&rm->rm_lock); + ret = _dpu_rm_get_hw_locked(rm, i); + mutex_unlock(&rm->rm_lock); + + return ret; +} + +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_CDM: + dpu_hw_cdm_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; + } +} + +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; + + if (!rm) { + DPU_ERROR("invalid rm\n"); + return -EINVAL; + } + + list_for_each_entry_safe(rsvp_cur, rsvp_nxt, &rm->rsvps, list) { + list_del(&rsvp_cur->list); + kfree(rsvp_cur); + } + + + 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); + } + } + + dpu_hw_mdp_destroy(rm->hw_mdp); + rm->hw_mdp = NULL; + + mutex_destroy(&rm->rm_lock); + + 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; + + hw_mdp = rm->hw_mdp; + + 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_CDM: + hw = dpu_hw_cdm_init(id, mmio, cat, hw_mdp); + 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; + } + + if (IS_ERR_OR_NULL(hw)) { + DPU_ERROR("failed hw object creation: type %d, err %ld\n", + type, PTR_ERR(hw)); + return -EFAULT; + } + + blk = kzalloc(sizeof(*blk), GFP_KERNEL); + if (!blk) { + _dpu_rm_hw_destroy(type, hw); + return -ENOMEM; + } + + blk->type = type; + blk->id = id; + blk->hw = hw; + list_add_tail(&blk->list, &rm->hw_blks[type]); + + return 0; +} + +int dpu_rm_init(struct dpu_rm *rm, + struct dpu_mdss_cfg *cat, + void __iomem *mmio, + struct drm_device *dev) +{ + 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]); + + rm->dev = dev; + + /* 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; + } + + /* 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; + } + + 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; + } + + if (!rm->lm_max_width) { + rm->lm_max_width = lm->sblk->maxwidth; + } else if (rm->lm_max_width != lm->sblk->maxwidth) { + /* + * Don't expect to have hw where lm max widths differ. + * If found, take the min. + */ + DPU_ERROR("unsupported: lm maxwidth differs\n"); + if (rm->lm_max_width > lm->sblk->maxwidth) + rm->lm_max_width = lm->sblk->maxwidth; + } + } + + 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; + } + } + + 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; + } + } + + 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 (i = 0; i < cat->cdm_count; i++) { + rc = _dpu_rm_hw_blk_create(rm, cat, mmio, DPU_HW_BLK_CDM, + cat->cdm[i].id, &cat->cdm[i]); + if (rc) { + DPU_ERROR("failed: cdm hw not available\n"); + goto fail; + } + } + + return 0; + +fail: + dpu_rm_destroy(rm); + + return rc; +} + +/** + * _dpu_rm_check_lm_and_get_connected_blks - check if proposed layer mixer meets + * 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 + */ +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) +{ + const struct dpu_lm_cfg *lm_cfg = to_dpu_hw_mixer(lm->hw)->cap; + struct dpu_rm_hw_iter iter; + + *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(lm, rsvp)) { + DPU_DEBUG("lm %d already reserved\n", lm_cfg->id); + 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 (!*pp) { + DPU_ERROR("failed to get pp on lm %d\n", lm_cfg->pingpong); + return false; + } + + if (RESERVED_BY_OTHER(*pp, rsvp)) { + DPU_DEBUG("lm %d pp %d already reserved\n", lm->id, + (*pp)->id); + return false; + } + + return true; +} + +static int _dpu_rm_reserve_lms( + struct dpu_rm *rm, + struct dpu_rm_rsvp *rsvp, + struct dpu_rm_requirements *reqs) + +{ + 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); + 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)) + continue; + + ++lm_count; + + /* Valid primary mixer found, find matching peers */ + dpu_rm_init_hw_iter(&iter_j, 0, DPU_HW_BLK_LM); + + while (lm_count != reqs->topology->num_lm && + _dpu_rm_get_hw_locked(rm, &iter_j)) { + if (iter_i.blk == iter_j.blk) + continue; + + if (!_dpu_rm_check_lm_and_get_connected_blks( + rm, rsvp, reqs, iter_j.blk, + &pp[lm_count], iter_i.blk)) + continue; + + lm[lm_count] = iter_j.blk; + ++lm_count; + } + } + + if (lm_count != reqs->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; + + trace_dpu_rm_reserve_lms(lm[i]->id, lm[i]->type, rsvp->enc_id, + pp[i]->id); + } + + return rc; +} + +static int _dpu_rm_reserve_ctls( + struct dpu_rm *rm, + struct dpu_rm_rsvp *rsvp, + const struct dpu_rm_topology_def *top) +{ + struct dpu_rm_hw_blk *ctls[MAX_BLOCKS]; + struct dpu_rm_hw_iter iter; + int i = 0; + + memset(&ctls, 0, sizeof(ctls)); + + 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; + bool has_split_display; + + if (RESERVED_BY_OTHER(iter.blk, rsvp)) + continue; + + has_split_display = BIT(DPU_CTL_SPLIT_DISPLAY) & features; + + DPU_DEBUG("ctl %d caps 0x%lX\n", iter.blk->id, features); + + if (top->needs_split_display != has_split_display) + continue; + + ctls[i] = iter.blk; + DPU_DEBUG("ctl %d match\n", iter.blk->id); + + if (++i == top->num_ctl) + break; + } + + if (i != top->num_ctl) + return -ENAVAIL; + + for (i = 0; i < ARRAY_SIZE(ctls) && i < top->num_ctl; i++) { + ctls[i]->rsvp_nxt = rsvp; + trace_dpu_rm_reserve_ctls(ctls[i]->id, ctls[i]->type, + rsvp->enc_id); + } + + return 0; +} + +static int _dpu_rm_reserve_cdm( + struct dpu_rm *rm, + struct dpu_rm_rsvp *rsvp, + uint32_t id, + enum dpu_hw_blk_type type) +{ + struct dpu_rm_hw_iter iter; + + DRM_DEBUG_KMS("type %d id %d\n", type, id); + + dpu_rm_init_hw_iter(&iter, 0, DPU_HW_BLK_CDM); + while (_dpu_rm_get_hw_locked(rm, &iter)) { + const struct dpu_hw_cdm *cdm = to_dpu_hw_cdm(iter.blk->hw); + const struct dpu_cdm_cfg *caps = cdm->caps; + bool match = false; + + if (RESERVED_BY_OTHER(iter.blk, rsvp)) + continue; + + if (type == DPU_HW_BLK_INTF && id != INTF_MAX) + match = test_bit(id, &caps->intf_connect); + + DRM_DEBUG_KMS("iter: type:%d id:%d enc:%d cdm:%lu match:%d\n", + iter.blk->type, iter.blk->id, rsvp->enc_id, + caps->intf_connect, match); + + if (!match) + continue; + + trace_dpu_rm_reserve_cdm(iter.blk->id, iter.blk->type, + rsvp->enc_id); + iter.blk->rsvp_nxt = rsvp; + break; + } + + if (!iter.hw) { + DPU_ERROR("couldn't reserve cdm for type %d id %d\n", type, id); + return -ENAVAIL; + } + + 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, + bool needs_cdm) +{ + struct dpu_rm_hw_iter iter; + int ret = 0; + + /* 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) + continue; + + if (RESERVED_BY_OTHER(iter.blk, rsvp)) { + DPU_ERROR("type %d id %d already reserved\n", type, id); + return -ENAVAIL; + } + + iter.blk->rsvp_nxt = rsvp; + trace_dpu_rm_reserve_intf(iter.blk->id, iter.blk->type, + rsvp->enc_id); + break; + } + + /* 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 (needs_cdm) + ret = _dpu_rm_reserve_cdm(rm, rsvp, id, type); + + return ret; +} + +static int _dpu_rm_reserve_intf_related_hw( + struct dpu_rm *rm, + struct dpu_rm_rsvp *rsvp, + struct dpu_encoder_hw_resources *hw_res) +{ + int i, ret = 0; + u32 id; + + for (i = 0; i < ARRAY_SIZE(hw_res->intfs); i++) { + if (hw_res->intfs[i] == INTF_MODE_NONE) + continue; + id = i + INTF_0; + ret = _dpu_rm_reserve_intf(rm, rsvp, id, + DPU_HW_BLK_INTF, hw_res->needs_cdm); + if (ret) + return ret; + } + + return ret; +} + +static int _dpu_rm_make_next_rsvp( + struct dpu_rm *rm, + struct drm_encoder *enc, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + struct dpu_rm_rsvp *rsvp, + struct dpu_rm_requirements *reqs) +{ + int ret; + struct dpu_rm_topology_def topology; + + /* Create reservation info, tag reserved blocks with it as we go */ + rsvp->seq = ++rm->rsvp_next_seq; + rsvp->enc_id = enc->base.id; + rsvp->topology = reqs->topology->top_name; + list_add_tail(&rsvp->list, &rm->rsvps); + + ret = _dpu_rm_reserve_lms(rm, rsvp, reqs); + if (ret) { + DPU_ERROR("unable to find appropriate mixers\n"); + return ret; + } + + /* + * Do assignment preferring to give away low-resource CTLs first: + * - Check mixers without Split Display + * - Only then allow to grab from CTLs with split display capability + */ + _dpu_rm_reserve_ctls(rm, rsvp, reqs->topology); + if (ret && !reqs->topology->needs_split_display) { + memcpy(&topology, reqs->topology, sizeof(topology)); + topology.needs_split_display = true; + _dpu_rm_reserve_ctls(rm, rsvp, &topology); + } + if (ret) { + DPU_ERROR("unable to find appropriate CTL\n"); + return ret; + } + + /* Assign INTFs and blks whose usage is tied to them: CTL & CDM */ + ret = _dpu_rm_reserve_intf_related_hw(rm, rsvp, &reqs->hw_res); + if (ret) + 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 drm_connector_state *conn_state, + struct dpu_rm_requirements *reqs, + struct msm_display_topology req_topology) +{ + int i; + + memset(reqs, 0, sizeof(*reqs)); + + dpu_encoder_get_hw_resources(enc, &reqs->hw_res, conn_state); + + for (i = 0; i < DPU_RM_TOPOLOGY_MAX; i++) { + if (RM_IS_TOPOLOGY_MATCH(g_top_table[i], + req_topology)) { + reqs->topology = &g_top_table[i]; + break; + } + } + + if (!reqs->topology) { + DPU_ERROR("invalid topology for the display\n"); + return -EINVAL; + } + + /** + * Set the requirement based on caps if not set from user space + * This will ensure to select LM tied with DS blocks + * Currently, DS blocks are tied with LM 0 and LM 1 (primary display) + */ + if (!RM_RQ_DS(reqs) && rm->hw_mdp->caps->has_dest_scaler && + conn_state->connector->connector_type == DRM_MODE_CONNECTOR_DSI) + reqs->top_ctrl |= BIT(DPU_RM_TOPCTL_DS); + + DRM_DEBUG_KMS("top_ctrl: 0x%llX num_h_tiles: %d\n", reqs->top_ctrl, + reqs->hw_res.display_num_of_h_tiles); + DRM_DEBUG_KMS("num_lm: %d num_ctl: %d topology: %d split_display: %d\n", + reqs->topology->num_lm, reqs->topology->num_ctl, + reqs->topology->top_name, + reqs->topology->needs_split_display); + + return 0; +} + +static struct dpu_rm_rsvp *_dpu_rm_get_rsvp( + struct dpu_rm *rm, + struct drm_encoder *enc) +{ + struct dpu_rm_rsvp *i; + + if (!rm || !enc) { + DPU_ERROR("invalid params\n"); + return NULL; + } + + if (list_empty(&rm->rsvps)) + return NULL; + + list_for_each_entry(i, &rm->rsvps, list) + if (i->enc_id == enc->base.id) + return i; + + return NULL; +} + +static struct drm_connector *_dpu_rm_get_connector( + struct drm_encoder *enc) +{ + struct drm_connector *conn = NULL; + struct list_head *connector_list = + &enc->dev->mode_config.connector_list; + + list_for_each_entry(conn, connector_list, head) + if (conn->encoder == enc) + return conn; + + return NULL; +} + +/** + * _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, + struct drm_connector *conn) +{ + struct dpu_rm_rsvp *rsvp_c, *rsvp_n; + struct dpu_rm_hw_blk *blk; + enum dpu_hw_blk_type type; + + if (!rsvp) + return; + + DPU_DEBUG("rel rsvp %d enc %d\n", rsvp->seq, rsvp->enc_id); + + list_for_each_entry_safe(rsvp_c, rsvp_n, &rm->rsvps, list) { + if (rsvp == rsvp_c) { + list_del(&rsvp_c->list); + break; + } + } + + 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); + } + } + } + + kfree(rsvp); +} + +void dpu_rm_release(struct dpu_rm *rm, struct drm_encoder *enc) +{ + struct dpu_rm_rsvp *rsvp; + struct drm_connector *conn; + + if (!rm || !enc) { + DPU_ERROR("invalid params\n"); + return; + } + + mutex_lock(&rm->rm_lock); + + rsvp = _dpu_rm_get_rsvp(rm, enc); + if (!rsvp) { + DPU_ERROR("failed to find rsvp for enc %d\n", enc->base.id); + goto end; + } + + conn = _dpu_rm_get_connector(enc); + if (!conn) { + DPU_ERROR("failed to get connector for enc %d\n", enc->base.id); + goto end; + } + + _dpu_rm_release_rsvp(rm, rsvp, conn); +end: + mutex_unlock(&rm->rm_lock); +} + +static int _dpu_rm_commit_rsvp( + struct dpu_rm *rm, + struct dpu_rm_rsvp *rsvp, + struct drm_connector_state *conn_state) +{ + struct dpu_rm_hw_blk *blk; + enum dpu_hw_blk_type type; + int ret = 0; + + /* 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; + } + } + } + + if (!ret) + DRM_DEBUG_KMS("rsrv enc %d topology %d\n", rsvp->enc_id, + rsvp->topology); + + return ret; +} + +int dpu_rm_reserve( + struct dpu_rm *rm, + struct drm_encoder *enc, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + struct msm_display_topology topology, + bool test_only) +{ + struct dpu_rm_rsvp *rsvp_cur, *rsvp_nxt; + struct dpu_rm_requirements reqs; + int ret; + + if (!rm || !enc || !crtc_state || !conn_state) { + DPU_ERROR("invalid arguments\n"); + return -EINVAL; + } + + /* Check if this is just a page-flip */ + if (!drm_atomic_crtc_needs_modeset(crtc_state)) + return 0; + + DRM_DEBUG_KMS("reserving hw for conn %d enc %d crtc %d test_only %d\n", + conn_state->connector->base.id, enc->base.id, + crtc_state->crtc->base.id, test_only); + + mutex_lock(&rm->rm_lock); + + _dpu_rm_print_rsvps(rm, DPU_RM_STAGE_BEGIN); + + ret = _dpu_rm_populate_requirements(rm, enc, crtc_state, + conn_state, &reqs, topology); + if (ret) { + DPU_ERROR("failed to populate hw requirements\n"); + goto end; + } + + /* + * 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; + } + + rsvp_cur = _dpu_rm_get_rsvp(rm, enc); + + /* + * User can request that we clear out any reservation during the + * atomic_check phase by using this CLEAR bit + */ + if (rsvp_cur && test_only && RM_RQ_CLEAR(&reqs)) { + DPU_DEBUG("test_only & CLEAR: clear rsvp[s%de%d]\n", + rsvp_cur->seq, rsvp_cur->enc_id); + _dpu_rm_release_rsvp(rm, rsvp_cur, conn_state->connector); + rsvp_cur = NULL; + _dpu_rm_print_rsvps(rm, DPU_RM_STAGE_AFTER_CLEAR); + } + + /* Check the proposed reservation, store it in hw's "next" field */ + ret = _dpu_rm_make_next_rsvp(rm, enc, crtc_state, conn_state, + rsvp_nxt, &reqs); + + _dpu_rm_print_rsvps(rm, DPU_RM_STAGE_AFTER_RSVPNEXT); + + if (ret) { + DPU_ERROR("failed to reserve hw resources: %d\n", ret); + _dpu_rm_release_rsvp(rm, rsvp_nxt, conn_state->connector); + } else if (test_only && !RM_RQ_LOCK(&reqs)) { + /* + * 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, conn_state->connector); + } else { + if (test_only && RM_RQ_LOCK(&reqs)) + DPU_DEBUG("test_only & LOCK: lock rsvp[s%de%d]\n", + rsvp_nxt->seq, rsvp_nxt->enc_id); + + _dpu_rm_release_rsvp(rm, rsvp_cur, conn_state->connector); + + ret = _dpu_rm_commit_rsvp(rm, rsvp_nxt, conn_state); + } + + _dpu_rm_print_rsvps(rm, DPU_RM_STAGE_FINAL); + +end: + mutex_unlock(&rm->rm_lock); + + return ret; +} |