linux/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
   4 */
   5
   6#define pr_fmt(fmt)     "[drm:%s] " fmt, __func__
   7#include "dpu_kms.h"
   8#include "dpu_hw_lm.h"
   9#include "dpu_hw_ctl.h"
  10#include "dpu_hw_pingpong.h"
  11#include "dpu_hw_intf.h"
  12#include "dpu_encoder.h"
  13#include "dpu_trace.h"
  14
  15#define RESERVED_BY_OTHER(h, r)  \
  16                ((h)->enc_id && (h)->enc_id != r)
  17
  18/**
  19 * struct dpu_rm_requirements - Reservation requirements parameter bundle
  20 * @topology:  selected topology for the display
  21 * @hw_res:        Hardware resources required as reported by the encoders
  22 */
  23struct dpu_rm_requirements {
  24        struct msm_display_topology topology;
  25        struct dpu_encoder_hw_resources hw_res;
  26};
  27
  28
  29/**
  30 * struct dpu_rm_hw_blk - hardware block tracking list member
  31 * @list:       List head for list of all hardware blocks tracking items
  32 * @id:         Hardware ID number, within it's own space, ie. LM_X
  33 * @enc_id:     Encoder id to which this blk is binded
  34 * @hw:         Pointer to the hardware register access object for this block
  35 */
  36struct dpu_rm_hw_blk {
  37        struct list_head list;
  38        uint32_t id;
  39        uint32_t enc_id;
  40        struct dpu_hw_blk *hw;
  41};
  42
  43void dpu_rm_init_hw_iter(
  44                struct dpu_rm_hw_iter *iter,
  45                uint32_t enc_id,
  46                enum dpu_hw_blk_type type)
  47{
  48        memset(iter, 0, sizeof(*iter));
  49        iter->enc_id = enc_id;
  50        iter->type = type;
  51}
  52
  53static bool _dpu_rm_get_hw_locked(struct dpu_rm *rm, struct dpu_rm_hw_iter *i)
  54{
  55        struct list_head *blk_list;
  56
  57        if (!rm || !i || i->type >= DPU_HW_BLK_MAX) {
  58                DPU_ERROR("invalid rm\n");
  59                return false;
  60        }
  61
  62        i->hw = NULL;
  63        blk_list = &rm->hw_blks[i->type];
  64
  65        if (i->blk && (&i->blk->list == blk_list)) {
  66                DPU_DEBUG("attempt resume iteration past last\n");
  67                return false;
  68        }
  69
  70        i->blk = list_prepare_entry(i->blk, blk_list, list);
  71
  72        list_for_each_entry_continue(i->blk, blk_list, list) {
  73                if (i->enc_id == i->blk->enc_id) {
  74                        i->hw = i->blk->hw;
  75                        DPU_DEBUG("found type %d id %d for enc %d\n",
  76                                        i->type, i->blk->id, i->enc_id);
  77                        return true;
  78                }
  79        }
  80
  81        DPU_DEBUG("no match, type %d for enc %d\n", i->type, i->enc_id);
  82
  83        return false;
  84}
  85
  86bool dpu_rm_get_hw(struct dpu_rm *rm, struct dpu_rm_hw_iter *i)
  87{
  88        bool ret;
  89
  90        mutex_lock(&rm->rm_lock);
  91        ret = _dpu_rm_get_hw_locked(rm, i);
  92        mutex_unlock(&rm->rm_lock);
  93
  94        return ret;
  95}
  96
  97static void _dpu_rm_hw_destroy(enum dpu_hw_blk_type type, void *hw)
  98{
  99        switch (type) {
 100        case DPU_HW_BLK_LM:
 101                dpu_hw_lm_destroy(hw);
 102                break;
 103        case DPU_HW_BLK_CTL:
 104                dpu_hw_ctl_destroy(hw);
 105                break;
 106        case DPU_HW_BLK_PINGPONG:
 107                dpu_hw_pingpong_destroy(hw);
 108                break;
 109        case DPU_HW_BLK_INTF:
 110                dpu_hw_intf_destroy(hw);
 111                break;
 112        case DPU_HW_BLK_SSPP:
 113                /* SSPPs are not managed by the resource manager */
 114        case DPU_HW_BLK_TOP:
 115                /* Top is a singleton, not managed in hw_blks list */
 116        case DPU_HW_BLK_MAX:
 117        default:
 118                DPU_ERROR("unsupported block type %d\n", type);
 119                break;
 120        }
 121}
 122
 123int dpu_rm_destroy(struct dpu_rm *rm)
 124{
 125        struct dpu_rm_hw_blk *hw_cur, *hw_nxt;
 126        enum dpu_hw_blk_type type;
 127
 128        for (type = 0; type < DPU_HW_BLK_MAX; type++) {
 129                list_for_each_entry_safe(hw_cur, hw_nxt, &rm->hw_blks[type],
 130                                list) {
 131                        list_del(&hw_cur->list);
 132                        _dpu_rm_hw_destroy(type, hw_cur->hw);
 133                        kfree(hw_cur);
 134                }
 135        }
 136
 137        mutex_destroy(&rm->rm_lock);
 138
 139        return 0;
 140}
 141
 142static int _dpu_rm_hw_blk_create(
 143                struct dpu_rm *rm,
 144                struct dpu_mdss_cfg *cat,
 145                void __iomem *mmio,
 146                enum dpu_hw_blk_type type,
 147                uint32_t id,
 148                void *hw_catalog_info)
 149{
 150        struct dpu_rm_hw_blk *blk;
 151        void *hw;
 152
 153        switch (type) {
 154        case DPU_HW_BLK_LM:
 155                hw = dpu_hw_lm_init(id, mmio, cat);
 156                break;
 157        case DPU_HW_BLK_CTL:
 158                hw = dpu_hw_ctl_init(id, mmio, cat);
 159                break;
 160        case DPU_HW_BLK_PINGPONG:
 161                hw = dpu_hw_pingpong_init(id, mmio, cat);
 162                break;
 163        case DPU_HW_BLK_INTF:
 164                hw = dpu_hw_intf_init(id, mmio, cat);
 165                break;
 166        case DPU_HW_BLK_SSPP:
 167                /* SSPPs are not managed by the resource manager */
 168        case DPU_HW_BLK_TOP:
 169                /* Top is a singleton, not managed in hw_blks list */
 170        case DPU_HW_BLK_MAX:
 171        default:
 172                DPU_ERROR("unsupported block type %d\n", type);
 173                return -EINVAL;
 174        }
 175
 176        if (IS_ERR_OR_NULL(hw)) {
 177                DPU_ERROR("failed hw object creation: type %d, err %ld\n",
 178                                type, PTR_ERR(hw));
 179                return -EFAULT;
 180        }
 181
 182        blk = kzalloc(sizeof(*blk), GFP_KERNEL);
 183        if (!blk) {
 184                _dpu_rm_hw_destroy(type, hw);
 185                return -ENOMEM;
 186        }
 187
 188        blk->id = id;
 189        blk->hw = hw;
 190        blk->enc_id = 0;
 191        list_add_tail(&blk->list, &rm->hw_blks[type]);
 192
 193        return 0;
 194}
 195
 196int dpu_rm_init(struct dpu_rm *rm,
 197                struct dpu_mdss_cfg *cat,
 198                void __iomem *mmio)
 199{
 200        int rc, i;
 201        enum dpu_hw_blk_type type;
 202
 203        if (!rm || !cat || !mmio) {
 204                DPU_ERROR("invalid kms\n");
 205                return -EINVAL;
 206        }
 207
 208        /* Clear, setup lists */
 209        memset(rm, 0, sizeof(*rm));
 210
 211        mutex_init(&rm->rm_lock);
 212
 213        for (type = 0; type < DPU_HW_BLK_MAX; type++)
 214                INIT_LIST_HEAD(&rm->hw_blks[type]);
 215
 216        /* Interrogate HW catalog and create tracking items for hw blocks */
 217        for (i = 0; i < cat->mixer_count; i++) {
 218                struct dpu_lm_cfg *lm = &cat->mixer[i];
 219
 220                if (lm->pingpong == PINGPONG_MAX) {
 221                        DPU_DEBUG("skip mixer %d without pingpong\n", lm->id);
 222                        continue;
 223                }
 224
 225                rc = _dpu_rm_hw_blk_create(rm, cat, mmio, DPU_HW_BLK_LM,
 226                                cat->mixer[i].id, &cat->mixer[i]);
 227                if (rc) {
 228                        DPU_ERROR("failed: lm hw not available\n");
 229                        goto fail;
 230                }
 231
 232                if (!rm->lm_max_width) {
 233                        rm->lm_max_width = lm->sblk->maxwidth;
 234                } else if (rm->lm_max_width != lm->sblk->maxwidth) {
 235                        /*
 236                         * Don't expect to have hw where lm max widths differ.
 237                         * If found, take the min.
 238                         */
 239                        DPU_ERROR("unsupported: lm maxwidth differs\n");
 240                        if (rm->lm_max_width > lm->sblk->maxwidth)
 241                                rm->lm_max_width = lm->sblk->maxwidth;
 242                }
 243        }
 244
 245        for (i = 0; i < cat->pingpong_count; i++) {
 246                rc = _dpu_rm_hw_blk_create(rm, cat, mmio, DPU_HW_BLK_PINGPONG,
 247                                cat->pingpong[i].id, &cat->pingpong[i]);
 248                if (rc) {
 249                        DPU_ERROR("failed: pp hw not available\n");
 250                        goto fail;
 251                }
 252        }
 253
 254        for (i = 0; i < cat->intf_count; i++) {
 255                if (cat->intf[i].type == INTF_NONE) {
 256                        DPU_DEBUG("skip intf %d with type none\n", i);
 257                        continue;
 258                }
 259
 260                rc = _dpu_rm_hw_blk_create(rm, cat, mmio, DPU_HW_BLK_INTF,
 261                                cat->intf[i].id, &cat->intf[i]);
 262                if (rc) {
 263                        DPU_ERROR("failed: intf hw not available\n");
 264                        goto fail;
 265                }
 266        }
 267
 268        for (i = 0; i < cat->ctl_count; i++) {
 269                rc = _dpu_rm_hw_blk_create(rm, cat, mmio, DPU_HW_BLK_CTL,
 270                                cat->ctl[i].id, &cat->ctl[i]);
 271                if (rc) {
 272                        DPU_ERROR("failed: ctl hw not available\n");
 273                        goto fail;
 274                }
 275        }
 276
 277        return 0;
 278
 279fail:
 280        dpu_rm_destroy(rm);
 281
 282        return rc;
 283}
 284
 285static bool _dpu_rm_needs_split_display(const struct msm_display_topology *top)
 286{
 287        return top->num_intf > 1;
 288}
 289
 290/**
 291 * _dpu_rm_check_lm_and_get_connected_blks - check if proposed layer mixer meets
 292 *      proposed use case requirements, incl. hardwired dependent blocks like
 293 *      pingpong
 294 * @rm: dpu resource manager handle
 295 * @enc_id: encoder id requesting for allocation
 296 * @reqs: proposed use case requirements
 297 * @lm: proposed layer mixer, function checks if lm, and all other hardwired
 298 *      blocks connected to the lm (pp) is available and appropriate
 299 * @pp: output parameter, pingpong block attached to the layer mixer.
 300 *      NULL if pp was not available, or not matching requirements.
 301 * @primary_lm: if non-null, this function check if lm is compatible primary_lm
 302 *              as well as satisfying all other requirements
 303 * @Return: true if lm matches all requirements, false otherwise
 304 */
 305static bool _dpu_rm_check_lm_and_get_connected_blks(
 306                struct dpu_rm *rm,
 307                uint32_t enc_id,
 308                struct dpu_rm_requirements *reqs,
 309                struct dpu_rm_hw_blk *lm,
 310                struct dpu_rm_hw_blk **pp,
 311                struct dpu_rm_hw_blk *primary_lm)
 312{
 313        const struct dpu_lm_cfg *lm_cfg = to_dpu_hw_mixer(lm->hw)->cap;
 314        struct dpu_rm_hw_iter iter;
 315
 316        *pp = NULL;
 317
 318        DPU_DEBUG("check lm %d pp %d\n",
 319                           lm_cfg->id, lm_cfg->pingpong);
 320
 321        /* Check if this layer mixer is a peer of the proposed primary LM */
 322        if (primary_lm) {
 323                const struct dpu_lm_cfg *prim_lm_cfg =
 324                                to_dpu_hw_mixer(primary_lm->hw)->cap;
 325
 326                if (!test_bit(lm_cfg->id, &prim_lm_cfg->lm_pair_mask)) {
 327                        DPU_DEBUG("lm %d not peer of lm %d\n", lm_cfg->id,
 328                                        prim_lm_cfg->id);
 329                        return false;
 330                }
 331        }
 332
 333        /* Already reserved? */
 334        if (RESERVED_BY_OTHER(lm, enc_id)) {
 335                DPU_DEBUG("lm %d already reserved\n", lm_cfg->id);
 336                return false;
 337        }
 338
 339        dpu_rm_init_hw_iter(&iter, 0, DPU_HW_BLK_PINGPONG);
 340        while (_dpu_rm_get_hw_locked(rm, &iter)) {
 341                if (iter.blk->id == lm_cfg->pingpong) {
 342                        *pp = iter.blk;
 343                        break;
 344                }
 345        }
 346
 347        if (!*pp) {
 348                DPU_ERROR("failed to get pp on lm %d\n", lm_cfg->pingpong);
 349                return false;
 350        }
 351
 352        if (RESERVED_BY_OTHER(*pp, enc_id)) {
 353                DPU_DEBUG("lm %d pp %d already reserved\n", lm->id,
 354                                (*pp)->id);
 355                return false;
 356        }
 357
 358        return true;
 359}
 360
 361static int _dpu_rm_reserve_lms(struct dpu_rm *rm, uint32_t enc_id,
 362                               struct dpu_rm_requirements *reqs)
 363
 364{
 365        struct dpu_rm_hw_blk *lm[MAX_BLOCKS];
 366        struct dpu_rm_hw_blk *pp[MAX_BLOCKS];
 367        struct dpu_rm_hw_iter iter_i, iter_j;
 368        int lm_count = 0;
 369        int i, rc = 0;
 370
 371        if (!reqs->topology.num_lm) {
 372                DPU_ERROR("invalid number of lm: %d\n", reqs->topology.num_lm);
 373                return -EINVAL;
 374        }
 375
 376        /* Find a primary mixer */
 377        dpu_rm_init_hw_iter(&iter_i, 0, DPU_HW_BLK_LM);
 378        while (lm_count != reqs->topology.num_lm &&
 379                        _dpu_rm_get_hw_locked(rm, &iter_i)) {
 380                memset(&lm, 0, sizeof(lm));
 381                memset(&pp, 0, sizeof(pp));
 382
 383                lm_count = 0;
 384                lm[lm_count] = iter_i.blk;
 385
 386                if (!_dpu_rm_check_lm_and_get_connected_blks(
 387                                rm, enc_id, reqs, lm[lm_count],
 388                                &pp[lm_count], NULL))
 389                        continue;
 390
 391                ++lm_count;
 392
 393                /* Valid primary mixer found, find matching peers */
 394                dpu_rm_init_hw_iter(&iter_j, 0, DPU_HW_BLK_LM);
 395
 396                while (lm_count != reqs->topology.num_lm &&
 397                                _dpu_rm_get_hw_locked(rm, &iter_j)) {
 398                        if (iter_i.blk == iter_j.blk)
 399                                continue;
 400
 401                        if (!_dpu_rm_check_lm_and_get_connected_blks(
 402                                        rm, enc_id, reqs, iter_j.blk,
 403                                        &pp[lm_count], iter_i.blk))
 404                                continue;
 405
 406                        lm[lm_count] = iter_j.blk;
 407                        ++lm_count;
 408                }
 409        }
 410
 411        if (lm_count != reqs->topology.num_lm) {
 412                DPU_DEBUG("unable to find appropriate mixers\n");
 413                return -ENAVAIL;
 414        }
 415
 416        for (i = 0; i < ARRAY_SIZE(lm); i++) {
 417                if (!lm[i])
 418                        break;
 419
 420                lm[i]->enc_id = enc_id;
 421                pp[i]->enc_id = enc_id;
 422
 423                trace_dpu_rm_reserve_lms(lm[i]->id, enc_id, pp[i]->id);
 424        }
 425
 426        return rc;
 427}
 428
 429static int _dpu_rm_reserve_ctls(
 430                struct dpu_rm *rm,
 431                uint32_t enc_id,
 432                const struct msm_display_topology *top)
 433{
 434        struct dpu_rm_hw_blk *ctls[MAX_BLOCKS];
 435        struct dpu_rm_hw_iter iter;
 436        int i = 0, num_ctls = 0;
 437        bool needs_split_display = false;
 438
 439        memset(&ctls, 0, sizeof(ctls));
 440
 441        /* each hw_intf needs its own hw_ctrl to program its control path */
 442        num_ctls = top->num_intf;
 443
 444        needs_split_display = _dpu_rm_needs_split_display(top);
 445
 446        dpu_rm_init_hw_iter(&iter, 0, DPU_HW_BLK_CTL);
 447        while (_dpu_rm_get_hw_locked(rm, &iter)) {
 448                const struct dpu_hw_ctl *ctl = to_dpu_hw_ctl(iter.blk->hw);
 449                unsigned long features = ctl->caps->features;
 450                bool has_split_display;
 451
 452                if (RESERVED_BY_OTHER(iter.blk, enc_id))
 453                        continue;
 454
 455                has_split_display = BIT(DPU_CTL_SPLIT_DISPLAY) & features;
 456
 457                DPU_DEBUG("ctl %d caps 0x%lX\n", iter.blk->id, features);
 458
 459                if (needs_split_display != has_split_display)
 460                        continue;
 461
 462                ctls[i] = iter.blk;
 463                DPU_DEBUG("ctl %d match\n", iter.blk->id);
 464
 465                if (++i == num_ctls)
 466                        break;
 467        }
 468
 469        if (i != num_ctls)
 470                return -ENAVAIL;
 471
 472        for (i = 0; i < ARRAY_SIZE(ctls) && i < num_ctls; i++) {
 473                ctls[i]->enc_id = enc_id;
 474                trace_dpu_rm_reserve_ctls(ctls[i]->id, enc_id);
 475        }
 476
 477        return 0;
 478}
 479
 480static int _dpu_rm_reserve_intf(
 481                struct dpu_rm *rm,
 482                uint32_t enc_id,
 483                uint32_t id,
 484                enum dpu_hw_blk_type type)
 485{
 486        struct dpu_rm_hw_iter iter;
 487        int ret = 0;
 488
 489        /* Find the block entry in the rm, and note the reservation */
 490        dpu_rm_init_hw_iter(&iter, 0, type);
 491        while (_dpu_rm_get_hw_locked(rm, &iter)) {
 492                if (iter.blk->id != id)
 493                        continue;
 494
 495                if (RESERVED_BY_OTHER(iter.blk, enc_id)) {
 496                        DPU_ERROR("type %d id %d already reserved\n", type, id);
 497                        return -ENAVAIL;
 498                }
 499
 500                iter.blk->enc_id = enc_id;
 501                trace_dpu_rm_reserve_intf(iter.blk->id, enc_id);
 502                break;
 503        }
 504
 505        /* Shouldn't happen since intfs are fixed at probe */
 506        if (!iter.hw) {
 507                DPU_ERROR("couldn't find type %d id %d\n", type, id);
 508                return -EINVAL;
 509        }
 510
 511        return ret;
 512}
 513
 514static int _dpu_rm_reserve_intf_related_hw(
 515                struct dpu_rm *rm,
 516                uint32_t enc_id,
 517                struct dpu_encoder_hw_resources *hw_res)
 518{
 519        int i, ret = 0;
 520        u32 id;
 521
 522        for (i = 0; i < ARRAY_SIZE(hw_res->intfs); i++) {
 523                if (hw_res->intfs[i] == INTF_MODE_NONE)
 524                        continue;
 525                id = i + INTF_0;
 526                ret = _dpu_rm_reserve_intf(rm, enc_id, id,
 527                                DPU_HW_BLK_INTF);
 528                if (ret)
 529                        return ret;
 530        }
 531
 532        return ret;
 533}
 534
 535static int _dpu_rm_make_reservation(
 536                struct dpu_rm *rm,
 537                struct drm_encoder *enc,
 538                struct drm_crtc_state *crtc_state,
 539                struct dpu_rm_requirements *reqs)
 540{
 541        int ret;
 542
 543        ret = _dpu_rm_reserve_lms(rm, enc->base.id, reqs);
 544        if (ret) {
 545                DPU_ERROR("unable to find appropriate mixers\n");
 546                return ret;
 547        }
 548
 549        ret = _dpu_rm_reserve_ctls(rm, enc->base.id, &reqs->topology);
 550        if (ret) {
 551                DPU_ERROR("unable to find appropriate CTL\n");
 552                return ret;
 553        }
 554
 555        ret = _dpu_rm_reserve_intf_related_hw(rm, enc->base.id, &reqs->hw_res);
 556        if (ret)
 557                return ret;
 558
 559        return ret;
 560}
 561
 562static int _dpu_rm_populate_requirements(
 563                struct dpu_rm *rm,
 564                struct drm_encoder *enc,
 565                struct drm_crtc_state *crtc_state,
 566                struct dpu_rm_requirements *reqs,
 567                struct msm_display_topology req_topology)
 568{
 569        dpu_encoder_get_hw_resources(enc, &reqs->hw_res);
 570
 571        reqs->topology = req_topology;
 572
 573        DRM_DEBUG_KMS("num_lm: %d num_enc: %d num_intf: %d\n",
 574                      reqs->topology.num_lm, reqs->topology.num_enc,
 575                      reqs->topology.num_intf);
 576
 577        return 0;
 578}
 579
 580static void _dpu_rm_release_reservation(struct dpu_rm *rm, uint32_t enc_id)
 581{
 582        struct dpu_rm_hw_blk *blk;
 583        enum dpu_hw_blk_type type;
 584
 585        for (type = 0; type < DPU_HW_BLK_MAX; type++) {
 586                list_for_each_entry(blk, &rm->hw_blks[type], list) {
 587                        if (blk->enc_id == enc_id) {
 588                                blk->enc_id = 0;
 589                                DPU_DEBUG("rel enc %d %d %d\n", enc_id,
 590                                          type, blk->id);
 591                        }
 592                }
 593        }
 594}
 595
 596void dpu_rm_release(struct dpu_rm *rm, struct drm_encoder *enc)
 597{
 598        mutex_lock(&rm->rm_lock);
 599
 600        _dpu_rm_release_reservation(rm, enc->base.id);
 601
 602        mutex_unlock(&rm->rm_lock);
 603}
 604
 605int dpu_rm_reserve(
 606                struct dpu_rm *rm,
 607                struct drm_encoder *enc,
 608                struct drm_crtc_state *crtc_state,
 609                struct msm_display_topology topology,
 610                bool test_only)
 611{
 612        struct dpu_rm_requirements reqs;
 613        int ret;
 614
 615        /* Check if this is just a page-flip */
 616        if (!drm_atomic_crtc_needs_modeset(crtc_state))
 617                return 0;
 618
 619        DRM_DEBUG_KMS("reserving hw for enc %d crtc %d test_only %d\n",
 620                      enc->base.id, crtc_state->crtc->base.id, test_only);
 621
 622        mutex_lock(&rm->rm_lock);
 623
 624        ret = _dpu_rm_populate_requirements(rm, enc, crtc_state, &reqs,
 625                                            topology);
 626        if (ret) {
 627                DPU_ERROR("failed to populate hw requirements\n");
 628                goto end;
 629        }
 630
 631        ret = _dpu_rm_make_reservation(rm, enc, crtc_state, &reqs);
 632        if (ret) {
 633                DPU_ERROR("failed to reserve hw resources: %d\n", ret);
 634                _dpu_rm_release_reservation(rm, enc->base.id);
 635        } else if (test_only) {
 636                 /* test_only: test the reservation and then undo */
 637                DPU_DEBUG("test_only: discard test [enc: %d]\n",
 638                                enc->base.id);
 639                _dpu_rm_release_reservation(rm, enc->base.id);
 640        }
 641
 642end:
 643        mutex_unlock(&rm->rm_lock);
 644
 645        return ret;
 646}
 647