]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blob - drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c
treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 284
[mirror_ubuntu-jammy-kernel.git] / drivers / gpu / drm / msm / disp / dpu1 / dpu_rm.c
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 */
23 struct 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 */
36 struct 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
43 void 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
53 static 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
86 bool 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
97 static 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
123 int 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
142 static 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
196 int 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
279 fail:
280 dpu_rm_destroy(rm);
281
282 return rc;
283 }
284
285 static 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 */
305 static 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
361 static 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
429 static 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
480 static 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
514 static 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
535 static 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
562 static 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
580 static 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
596 void 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
605 int 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
642 end:
643 mutex_unlock(&rm->rm_lock);
644
645 return ret;
646 }