1 | // SPDX-License-Identifier: MIT |
2 | /* |
3 | * Copyright 2018 Noralf Trønnes |
4 | * Copyright (c) 2006-2009 Red Hat Inc. |
5 | * Copyright (c) 2006-2008 Intel Corporation |
6 | * Jesse Barnes <jesse.barnes@intel.com> |
7 | * Copyright (c) 2007 Dave Airlie <airlied@linux.ie> |
8 | */ |
9 | |
10 | #include "drm/drm_modeset_lock.h" |
11 | #include <linux/module.h> |
12 | #include <linux/mutex.h> |
13 | #include <linux/slab.h> |
14 | #include <linux/string_helpers.h> |
15 | |
16 | #include <drm/drm_atomic.h> |
17 | #include <drm/drm_client.h> |
18 | #include <drm/drm_connector.h> |
19 | #include <drm/drm_crtc.h> |
20 | #include <drm/drm_device.h> |
21 | #include <drm/drm_drv.h> |
22 | #include <drm/drm_edid.h> |
23 | #include <drm/drm_encoder.h> |
24 | #include <drm/drm_print.h> |
25 | |
26 | #include "drm_crtc_internal.h" |
27 | #include "drm_internal.h" |
28 | |
29 | #define DRM_CLIENT_MAX_CLONED_CONNECTORS 8 |
30 | |
31 | struct drm_client_offset { |
32 | int x, y; |
33 | }; |
34 | |
35 | int drm_client_modeset_create(struct drm_client_dev *client) |
36 | { |
37 | struct drm_device *dev = client->dev; |
38 | unsigned int num_crtc = dev->mode_config.num_crtc; |
39 | unsigned int max_connector_count = 1; |
40 | struct drm_mode_set *modeset; |
41 | struct drm_crtc *crtc; |
42 | unsigned int i = 0; |
43 | |
44 | /* Add terminating zero entry to enable index less iteration */ |
45 | client->modesets = kcalloc(n: num_crtc + 1, size: sizeof(*client->modesets), GFP_KERNEL); |
46 | if (!client->modesets) |
47 | return -ENOMEM; |
48 | |
49 | mutex_init(&client->modeset_mutex); |
50 | |
51 | drm_for_each_crtc(crtc, dev) |
52 | client->modesets[i++].crtc = crtc; |
53 | |
54 | /* Cloning is only supported in the single crtc case. */ |
55 | if (num_crtc == 1) |
56 | max_connector_count = DRM_CLIENT_MAX_CLONED_CONNECTORS; |
57 | |
58 | for (modeset = client->modesets; modeset->crtc; modeset++) { |
59 | modeset->connectors = kcalloc(n: max_connector_count, |
60 | size: sizeof(*modeset->connectors), GFP_KERNEL); |
61 | if (!modeset->connectors) |
62 | goto err_free; |
63 | } |
64 | |
65 | return 0; |
66 | |
67 | err_free: |
68 | drm_client_modeset_free(client); |
69 | |
70 | return -ENOMEM; |
71 | } |
72 | |
73 | static void drm_client_modeset_release(struct drm_client_dev *client) |
74 | { |
75 | struct drm_mode_set *modeset; |
76 | unsigned int i; |
77 | |
78 | drm_client_for_each_modeset(modeset, client) { |
79 | drm_mode_destroy(dev: client->dev, mode: modeset->mode); |
80 | modeset->mode = NULL; |
81 | modeset->fb = NULL; |
82 | |
83 | for (i = 0; i < modeset->num_connectors; i++) { |
84 | drm_connector_put(connector: modeset->connectors[i]); |
85 | modeset->connectors[i] = NULL; |
86 | } |
87 | modeset->num_connectors = 0; |
88 | } |
89 | } |
90 | |
91 | void drm_client_modeset_free(struct drm_client_dev *client) |
92 | { |
93 | struct drm_mode_set *modeset; |
94 | |
95 | mutex_lock(&client->modeset_mutex); |
96 | |
97 | drm_client_modeset_release(client); |
98 | |
99 | drm_client_for_each_modeset(modeset, client) |
100 | kfree(objp: modeset->connectors); |
101 | |
102 | mutex_unlock(lock: &client->modeset_mutex); |
103 | |
104 | mutex_destroy(lock: &client->modeset_mutex); |
105 | kfree(objp: client->modesets); |
106 | } |
107 | |
108 | static struct drm_mode_set * |
109 | drm_client_find_modeset(struct drm_client_dev *client, struct drm_crtc *crtc) |
110 | { |
111 | struct drm_mode_set *modeset; |
112 | |
113 | drm_client_for_each_modeset(modeset, client) |
114 | if (modeset->crtc == crtc) |
115 | return modeset; |
116 | |
117 | return NULL; |
118 | } |
119 | |
120 | static struct drm_display_mode * |
121 | drm_connector_get_tiled_mode(struct drm_connector *connector) |
122 | { |
123 | struct drm_display_mode *mode; |
124 | |
125 | list_for_each_entry(mode, &connector->modes, head) { |
126 | if (mode->hdisplay == connector->tile_h_size && |
127 | mode->vdisplay == connector->tile_v_size) |
128 | return mode; |
129 | } |
130 | return NULL; |
131 | } |
132 | |
133 | static struct drm_display_mode * |
134 | drm_connector_fallback_non_tiled_mode(struct drm_connector *connector) |
135 | { |
136 | struct drm_display_mode *mode; |
137 | |
138 | list_for_each_entry(mode, &connector->modes, head) { |
139 | if (mode->hdisplay == connector->tile_h_size && |
140 | mode->vdisplay == connector->tile_v_size) |
141 | continue; |
142 | return mode; |
143 | } |
144 | return NULL; |
145 | } |
146 | |
147 | static struct drm_display_mode * |
148 | drm_connector_has_preferred_mode(struct drm_connector *connector, int width, int height) |
149 | { |
150 | struct drm_display_mode *mode; |
151 | |
152 | list_for_each_entry(mode, &connector->modes, head) { |
153 | if (mode->hdisplay > width || |
154 | mode->vdisplay > height) |
155 | continue; |
156 | if (mode->type & DRM_MODE_TYPE_PREFERRED) |
157 | return mode; |
158 | } |
159 | return NULL; |
160 | } |
161 | |
162 | static struct drm_display_mode *drm_connector_pick_cmdline_mode(struct drm_connector *connector) |
163 | { |
164 | struct drm_cmdline_mode *cmdline_mode; |
165 | struct drm_display_mode *mode; |
166 | bool prefer_non_interlace; |
167 | |
168 | /* |
169 | * Find a user-defined mode. If the user gave us a valid |
170 | * mode on the kernel command line, it will show up in this |
171 | * list. |
172 | */ |
173 | |
174 | list_for_each_entry(mode, &connector->modes, head) { |
175 | if (mode->type & DRM_MODE_TYPE_USERDEF) |
176 | return mode; |
177 | } |
178 | |
179 | cmdline_mode = &connector->cmdline_mode; |
180 | if (cmdline_mode->specified == false) |
181 | return NULL; |
182 | |
183 | /* |
184 | * Attempt to find a matching mode in the list of modes we |
185 | * have gotten so far. |
186 | */ |
187 | |
188 | prefer_non_interlace = !cmdline_mode->interlace; |
189 | again: |
190 | list_for_each_entry(mode, &connector->modes, head) { |
191 | /* check width/height */ |
192 | if (mode->hdisplay != cmdline_mode->xres || |
193 | mode->vdisplay != cmdline_mode->yres) |
194 | continue; |
195 | |
196 | if (cmdline_mode->refresh_specified) { |
197 | if (drm_mode_vrefresh(mode) != cmdline_mode->refresh) |
198 | continue; |
199 | } |
200 | |
201 | if (cmdline_mode->interlace) { |
202 | if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) |
203 | continue; |
204 | } else if (prefer_non_interlace) { |
205 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) |
206 | continue; |
207 | } |
208 | return mode; |
209 | } |
210 | |
211 | if (prefer_non_interlace) { |
212 | prefer_non_interlace = false; |
213 | goto again; |
214 | } |
215 | |
216 | return NULL; |
217 | } |
218 | |
219 | static bool drm_connector_enabled(struct drm_connector *connector, bool strict) |
220 | { |
221 | bool enable; |
222 | |
223 | if (connector->display_info.non_desktop) |
224 | return false; |
225 | |
226 | if (strict) |
227 | enable = connector->status == connector_status_connected; |
228 | else |
229 | enable = connector->status != connector_status_disconnected; |
230 | |
231 | return enable; |
232 | } |
233 | |
234 | static void drm_client_connectors_enabled(struct drm_connector **connectors, |
235 | unsigned int connector_count, |
236 | bool *enabled) |
237 | { |
238 | bool any_enabled = false; |
239 | struct drm_connector *connector; |
240 | int i = 0; |
241 | |
242 | for (i = 0; i < connector_count; i++) { |
243 | connector = connectors[i]; |
244 | enabled[i] = drm_connector_enabled(connector, strict: true); |
245 | DRM_DEBUG_KMS("connector %d enabled? %s\n" , connector->base.id, |
246 | connector->display_info.non_desktop ? "non desktop" : str_yes_no(enabled[i])); |
247 | |
248 | any_enabled |= enabled[i]; |
249 | } |
250 | |
251 | if (any_enabled) |
252 | return; |
253 | |
254 | for (i = 0; i < connector_count; i++) |
255 | enabled[i] = drm_connector_enabled(connector: connectors[i], strict: false); |
256 | } |
257 | |
258 | static bool drm_client_target_cloned(struct drm_device *dev, |
259 | struct drm_connector **connectors, |
260 | unsigned int connector_count, |
261 | struct drm_display_mode **modes, |
262 | struct drm_client_offset *offsets, |
263 | bool *enabled, int width, int height) |
264 | { |
265 | int count, i, j; |
266 | bool can_clone = false; |
267 | struct drm_display_mode *dmt_mode, *mode; |
268 | |
269 | /* only contemplate cloning in the single crtc case */ |
270 | if (dev->mode_config.num_crtc > 1) |
271 | return false; |
272 | |
273 | count = 0; |
274 | for (i = 0; i < connector_count; i++) { |
275 | if (enabled[i]) |
276 | count++; |
277 | } |
278 | |
279 | /* only contemplate cloning if more than one connector is enabled */ |
280 | if (count <= 1) |
281 | return false; |
282 | |
283 | /* check the command line or if nothing common pick 1024x768 */ |
284 | can_clone = true; |
285 | for (i = 0; i < connector_count; i++) { |
286 | if (!enabled[i]) |
287 | continue; |
288 | modes[i] = drm_connector_pick_cmdline_mode(connector: connectors[i]); |
289 | if (!modes[i]) { |
290 | can_clone = false; |
291 | break; |
292 | } |
293 | for (j = 0; j < i; j++) { |
294 | if (!enabled[j]) |
295 | continue; |
296 | if (!drm_mode_match(mode1: modes[j], mode2: modes[i], |
297 | DRM_MODE_MATCH_TIMINGS | |
298 | DRM_MODE_MATCH_CLOCK | |
299 | DRM_MODE_MATCH_FLAGS | |
300 | DRM_MODE_MATCH_3D_FLAGS)) |
301 | can_clone = false; |
302 | } |
303 | } |
304 | |
305 | if (can_clone) { |
306 | DRM_DEBUG_KMS("can clone using command line\n" ); |
307 | return true; |
308 | } |
309 | |
310 | /* try and find a 1024x768 mode on each connector */ |
311 | can_clone = true; |
312 | dmt_mode = drm_mode_find_dmt(dev, hsize: 1024, vsize: 768, fresh: 60, rb: false); |
313 | |
314 | if (!dmt_mode) |
315 | goto fail; |
316 | |
317 | for (i = 0; i < connector_count; i++) { |
318 | if (!enabled[i]) |
319 | continue; |
320 | |
321 | list_for_each_entry(mode, &connectors[i]->modes, head) { |
322 | if (drm_mode_match(mode1: mode, mode2: dmt_mode, |
323 | DRM_MODE_MATCH_TIMINGS | |
324 | DRM_MODE_MATCH_CLOCK | |
325 | DRM_MODE_MATCH_FLAGS | |
326 | DRM_MODE_MATCH_3D_FLAGS)) |
327 | modes[i] = mode; |
328 | } |
329 | if (!modes[i]) |
330 | can_clone = false; |
331 | } |
332 | kfree(objp: dmt_mode); |
333 | |
334 | if (can_clone) { |
335 | DRM_DEBUG_KMS("can clone using 1024x768\n" ); |
336 | return true; |
337 | } |
338 | fail: |
339 | DRM_INFO("kms: can't enable cloning when we probably wanted to.\n" ); |
340 | return false; |
341 | } |
342 | |
343 | static int drm_client_get_tile_offsets(struct drm_connector **connectors, |
344 | unsigned int connector_count, |
345 | struct drm_display_mode **modes, |
346 | struct drm_client_offset *offsets, |
347 | int idx, |
348 | int h_idx, int v_idx) |
349 | { |
350 | struct drm_connector *connector; |
351 | int i; |
352 | int hoffset = 0, voffset = 0; |
353 | |
354 | for (i = 0; i < connector_count; i++) { |
355 | connector = connectors[i]; |
356 | if (!connector->has_tile) |
357 | continue; |
358 | |
359 | if (!modes[i] && (h_idx || v_idx)) { |
360 | DRM_DEBUG_KMS("no modes for connector tiled %d %d\n" , i, |
361 | connector->base.id); |
362 | continue; |
363 | } |
364 | if (connector->tile_h_loc < h_idx) |
365 | hoffset += modes[i]->hdisplay; |
366 | |
367 | if (connector->tile_v_loc < v_idx) |
368 | voffset += modes[i]->vdisplay; |
369 | } |
370 | offsets[idx].x = hoffset; |
371 | offsets[idx].y = voffset; |
372 | DRM_DEBUG_KMS("returned %d %d for %d %d\n" , hoffset, voffset, h_idx, v_idx); |
373 | return 0; |
374 | } |
375 | |
376 | static bool drm_client_target_preferred(struct drm_connector **connectors, |
377 | unsigned int connector_count, |
378 | struct drm_display_mode **modes, |
379 | struct drm_client_offset *offsets, |
380 | bool *enabled, int width, int height) |
381 | { |
382 | const u64 mask = BIT_ULL(connector_count) - 1; |
383 | struct drm_connector *connector; |
384 | u64 conn_configured = 0; |
385 | int tile_pass = 0; |
386 | int num_tiled_conns = 0; |
387 | int i; |
388 | |
389 | for (i = 0; i < connector_count; i++) { |
390 | if (connectors[i]->has_tile && |
391 | connectors[i]->status == connector_status_connected) |
392 | num_tiled_conns++; |
393 | } |
394 | |
395 | retry: |
396 | for (i = 0; i < connector_count; i++) { |
397 | connector = connectors[i]; |
398 | |
399 | if (conn_configured & BIT_ULL(i)) |
400 | continue; |
401 | |
402 | if (enabled[i] == false) { |
403 | conn_configured |= BIT_ULL(i); |
404 | continue; |
405 | } |
406 | |
407 | /* first pass over all the untiled connectors */ |
408 | if (tile_pass == 0 && connector->has_tile) |
409 | continue; |
410 | |
411 | if (tile_pass == 1) { |
412 | if (connector->tile_h_loc != 0 || |
413 | connector->tile_v_loc != 0) |
414 | continue; |
415 | |
416 | } else { |
417 | if (connector->tile_h_loc != tile_pass - 1 && |
418 | connector->tile_v_loc != tile_pass - 1) |
419 | /* if this tile_pass doesn't cover any of the tiles - keep going */ |
420 | continue; |
421 | |
422 | /* |
423 | * find the tile offsets for this pass - need to find |
424 | * all tiles left and above |
425 | */ |
426 | drm_client_get_tile_offsets(connectors, connector_count, modes, offsets, idx: i, |
427 | h_idx: connector->tile_h_loc, v_idx: connector->tile_v_loc); |
428 | } |
429 | DRM_DEBUG_KMS("looking for cmdline mode on connector %d\n" , |
430 | connector->base.id); |
431 | |
432 | /* got for command line mode first */ |
433 | modes[i] = drm_connector_pick_cmdline_mode(connector); |
434 | if (!modes[i]) { |
435 | DRM_DEBUG_KMS("looking for preferred mode on connector %d %d\n" , |
436 | connector->base.id, connector->tile_group ? connector->tile_group->id : 0); |
437 | modes[i] = drm_connector_has_preferred_mode(connector, width, height); |
438 | } |
439 | /* No preferred modes, pick one off the list */ |
440 | if (!modes[i] && !list_empty(head: &connector->modes)) { |
441 | list_for_each_entry(modes[i], &connector->modes, head) |
442 | break; |
443 | } |
444 | /* |
445 | * In case of tiled mode if all tiles not present fallback to |
446 | * first available non tiled mode. |
447 | * After all tiles are present, try to find the tiled mode |
448 | * for all and if tiled mode not present due to fbcon size |
449 | * limitations, use first non tiled mode only for |
450 | * tile 0,0 and set to no mode for all other tiles. |
451 | */ |
452 | if (connector->has_tile) { |
453 | if (num_tiled_conns < |
454 | connector->num_h_tile * connector->num_v_tile || |
455 | (connector->tile_h_loc == 0 && |
456 | connector->tile_v_loc == 0 && |
457 | !drm_connector_get_tiled_mode(connector))) { |
458 | DRM_DEBUG_KMS("Falling back to non tiled mode on Connector %d\n" , |
459 | connector->base.id); |
460 | modes[i] = drm_connector_fallback_non_tiled_mode(connector); |
461 | } else { |
462 | modes[i] = drm_connector_get_tiled_mode(connector); |
463 | } |
464 | } |
465 | |
466 | DRM_DEBUG_KMS("found mode %s\n" , modes[i] ? modes[i]->name : |
467 | "none" ); |
468 | conn_configured |= BIT_ULL(i); |
469 | } |
470 | |
471 | if ((conn_configured & mask) != mask) { |
472 | tile_pass++; |
473 | goto retry; |
474 | } |
475 | return true; |
476 | } |
477 | |
478 | static bool connector_has_possible_crtc(struct drm_connector *connector, |
479 | struct drm_crtc *crtc) |
480 | { |
481 | struct drm_encoder *encoder; |
482 | |
483 | drm_connector_for_each_possible_encoder(connector, encoder) { |
484 | if (encoder->possible_crtcs & drm_crtc_mask(crtc)) |
485 | return true; |
486 | } |
487 | |
488 | return false; |
489 | } |
490 | |
491 | static int drm_client_pick_crtcs(struct drm_client_dev *client, |
492 | struct drm_connector **connectors, |
493 | unsigned int connector_count, |
494 | struct drm_crtc **best_crtcs, |
495 | struct drm_display_mode **modes, |
496 | int n, int width, int height) |
497 | { |
498 | struct drm_device *dev = client->dev; |
499 | struct drm_connector *connector; |
500 | int my_score, best_score, score; |
501 | struct drm_crtc **crtcs, *crtc; |
502 | struct drm_mode_set *modeset; |
503 | int o; |
504 | |
505 | if (n == connector_count) |
506 | return 0; |
507 | |
508 | connector = connectors[n]; |
509 | |
510 | best_crtcs[n] = NULL; |
511 | best_score = drm_client_pick_crtcs(client, connectors, connector_count, |
512 | best_crtcs, modes, n: n + 1, width, height); |
513 | if (modes[n] == NULL) |
514 | return best_score; |
515 | |
516 | crtcs = kcalloc(n: connector_count, size: sizeof(*crtcs), GFP_KERNEL); |
517 | if (!crtcs) |
518 | return best_score; |
519 | |
520 | my_score = 1; |
521 | if (connector->status == connector_status_connected) |
522 | my_score++; |
523 | if (connector->cmdline_mode.specified) |
524 | my_score++; |
525 | if (drm_connector_has_preferred_mode(connector, width, height)) |
526 | my_score++; |
527 | |
528 | /* |
529 | * select a crtc for this connector and then attempt to configure |
530 | * remaining connectors |
531 | */ |
532 | drm_client_for_each_modeset(modeset, client) { |
533 | crtc = modeset->crtc; |
534 | |
535 | if (!connector_has_possible_crtc(connector, crtc)) |
536 | continue; |
537 | |
538 | for (o = 0; o < n; o++) |
539 | if (best_crtcs[o] == crtc) |
540 | break; |
541 | |
542 | if (o < n) { |
543 | /* ignore cloning unless only a single crtc */ |
544 | if (dev->mode_config.num_crtc > 1) |
545 | continue; |
546 | |
547 | if (!drm_mode_equal(mode1: modes[o], mode2: modes[n])) |
548 | continue; |
549 | } |
550 | |
551 | crtcs[n] = crtc; |
552 | memcpy(crtcs, best_crtcs, n * sizeof(*crtcs)); |
553 | score = my_score + drm_client_pick_crtcs(client, connectors, connector_count, |
554 | best_crtcs: crtcs, modes, n: n + 1, width, height); |
555 | if (score > best_score) { |
556 | best_score = score; |
557 | memcpy(best_crtcs, crtcs, connector_count * sizeof(*crtcs)); |
558 | } |
559 | } |
560 | |
561 | kfree(objp: crtcs); |
562 | return best_score; |
563 | } |
564 | |
565 | /* Try to read the BIOS display configuration and use it for the initial config */ |
566 | static bool drm_client_firmware_config(struct drm_client_dev *client, |
567 | struct drm_connector **connectors, |
568 | unsigned int connector_count, |
569 | struct drm_crtc **crtcs, |
570 | struct drm_display_mode **modes, |
571 | struct drm_client_offset *offsets, |
572 | bool *enabled, int width, int height) |
573 | { |
574 | const int count = min_t(unsigned int, connector_count, BITS_PER_LONG); |
575 | unsigned long conn_configured, conn_seq, mask; |
576 | struct drm_device *dev = client->dev; |
577 | int i, j; |
578 | bool *save_enabled; |
579 | bool fallback = true, ret = true; |
580 | int num_connectors_enabled = 0; |
581 | int num_connectors_detected = 0; |
582 | int num_tiled_conns = 0; |
583 | struct drm_modeset_acquire_ctx ctx; |
584 | |
585 | if (!drm_drv_uses_atomic_modeset(dev)) |
586 | return false; |
587 | |
588 | if (WARN_ON(count <= 0)) |
589 | return false; |
590 | |
591 | save_enabled = kcalloc(n: count, size: sizeof(bool), GFP_KERNEL); |
592 | if (!save_enabled) |
593 | return false; |
594 | |
595 | drm_modeset_acquire_init(ctx: &ctx, flags: 0); |
596 | |
597 | while (drm_modeset_lock_all_ctx(dev, ctx: &ctx) != 0) |
598 | drm_modeset_backoff(ctx: &ctx); |
599 | |
600 | memcpy(save_enabled, enabled, count); |
601 | mask = GENMASK(count - 1, 0); |
602 | conn_configured = 0; |
603 | for (i = 0; i < count; i++) { |
604 | if (connectors[i]->has_tile && |
605 | connectors[i]->status == connector_status_connected) |
606 | num_tiled_conns++; |
607 | } |
608 | retry: |
609 | conn_seq = conn_configured; |
610 | for (i = 0; i < count; i++) { |
611 | struct drm_connector *connector; |
612 | struct drm_encoder *encoder; |
613 | struct drm_crtc *new_crtc; |
614 | |
615 | connector = connectors[i]; |
616 | |
617 | if (conn_configured & BIT(i)) |
618 | continue; |
619 | |
620 | if (conn_seq == 0 && !connector->has_tile) |
621 | continue; |
622 | |
623 | if (connector->status == connector_status_connected) |
624 | num_connectors_detected++; |
625 | |
626 | if (!enabled[i]) { |
627 | DRM_DEBUG_KMS("connector %s not enabled, skipping\n" , |
628 | connector->name); |
629 | conn_configured |= BIT(i); |
630 | continue; |
631 | } |
632 | |
633 | if (connector->force == DRM_FORCE_OFF) { |
634 | DRM_DEBUG_KMS("connector %s is disabled by user, skipping\n" , |
635 | connector->name); |
636 | enabled[i] = false; |
637 | continue; |
638 | } |
639 | |
640 | encoder = connector->state->best_encoder; |
641 | if (!encoder || WARN_ON(!connector->state->crtc)) { |
642 | if (connector->force > DRM_FORCE_OFF) |
643 | goto bail; |
644 | |
645 | DRM_DEBUG_KMS("connector %s has no encoder or crtc, skipping\n" , |
646 | connector->name); |
647 | enabled[i] = false; |
648 | conn_configured |= BIT(i); |
649 | continue; |
650 | } |
651 | |
652 | num_connectors_enabled++; |
653 | |
654 | new_crtc = connector->state->crtc; |
655 | |
656 | /* |
657 | * Make sure we're not trying to drive multiple connectors |
658 | * with a single CRTC, since our cloning support may not |
659 | * match the BIOS. |
660 | */ |
661 | for (j = 0; j < count; j++) { |
662 | if (crtcs[j] == new_crtc) { |
663 | DRM_DEBUG_KMS("fallback: cloned configuration\n" ); |
664 | goto bail; |
665 | } |
666 | } |
667 | |
668 | DRM_DEBUG_KMS("looking for cmdline mode on connector %s\n" , |
669 | connector->name); |
670 | |
671 | /* go for command line mode first */ |
672 | modes[i] = drm_connector_pick_cmdline_mode(connector); |
673 | |
674 | /* try for preferred next */ |
675 | if (!modes[i]) { |
676 | DRM_DEBUG_KMS("looking for preferred mode on connector %s %d\n" , |
677 | connector->name, connector->has_tile); |
678 | modes[i] = drm_connector_has_preferred_mode(connector, width, height); |
679 | } |
680 | |
681 | /* No preferred mode marked by the EDID? Are there any modes? */ |
682 | if (!modes[i] && !list_empty(head: &connector->modes)) { |
683 | DRM_DEBUG_KMS("using first mode listed on connector %s\n" , |
684 | connector->name); |
685 | modes[i] = list_first_entry(&connector->modes, |
686 | struct drm_display_mode, |
687 | head); |
688 | } |
689 | |
690 | /* last resort: use current mode */ |
691 | if (!modes[i]) { |
692 | /* |
693 | * IMPORTANT: We want to use the adjusted mode (i.e. |
694 | * after the panel fitter upscaling) as the initial |
695 | * config, not the input mode, which is what crtc->mode |
696 | * usually contains. But since our current |
697 | * code puts a mode derived from the post-pfit timings |
698 | * into crtc->mode this works out correctly. |
699 | * |
700 | * This is crtc->mode and not crtc->state->mode for the |
701 | * fastboot check to work correctly. |
702 | */ |
703 | DRM_DEBUG_KMS("looking for current mode on connector %s\n" , |
704 | connector->name); |
705 | modes[i] = &connector->state->crtc->mode; |
706 | } |
707 | /* |
708 | * In case of tiled modes, if all tiles are not present |
709 | * then fallback to a non tiled mode. |
710 | */ |
711 | if (connector->has_tile && |
712 | num_tiled_conns < connector->num_h_tile * connector->num_v_tile) { |
713 | DRM_DEBUG_KMS("Falling back to non tiled mode on Connector %d\n" , |
714 | connector->base.id); |
715 | modes[i] = drm_connector_fallback_non_tiled_mode(connector); |
716 | } |
717 | crtcs[i] = new_crtc; |
718 | |
719 | DRM_DEBUG_KMS("connector %s on [CRTC:%d:%s]: %dx%d%s\n" , |
720 | connector->name, |
721 | connector->state->crtc->base.id, |
722 | connector->state->crtc->name, |
723 | modes[i]->hdisplay, modes[i]->vdisplay, |
724 | modes[i]->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "" ); |
725 | |
726 | fallback = false; |
727 | conn_configured |= BIT(i); |
728 | } |
729 | |
730 | if ((conn_configured & mask) != mask && conn_configured != conn_seq) |
731 | goto retry; |
732 | |
733 | /* |
734 | * If the BIOS didn't enable everything it could, fall back to have the |
735 | * same user experiencing of lighting up as much as possible like the |
736 | * fbdev helper library. |
737 | */ |
738 | if (num_connectors_enabled != num_connectors_detected && |
739 | num_connectors_enabled < dev->mode_config.num_crtc) { |
740 | DRM_DEBUG_KMS("fallback: Not all outputs enabled\n" ); |
741 | DRM_DEBUG_KMS("Enabled: %i, detected: %i\n" , num_connectors_enabled, |
742 | num_connectors_detected); |
743 | fallback = true; |
744 | } |
745 | |
746 | if (fallback) { |
747 | bail: |
748 | DRM_DEBUG_KMS("Not using firmware configuration\n" ); |
749 | memcpy(enabled, save_enabled, count); |
750 | ret = false; |
751 | } |
752 | |
753 | drm_modeset_drop_locks(ctx: &ctx); |
754 | drm_modeset_acquire_fini(ctx: &ctx); |
755 | |
756 | kfree(objp: save_enabled); |
757 | return ret; |
758 | } |
759 | |
760 | /** |
761 | * drm_client_modeset_probe() - Probe for displays |
762 | * @client: DRM client |
763 | * @width: Maximum display mode width (optional) |
764 | * @height: Maximum display mode height (optional) |
765 | * |
766 | * This function sets up display pipelines for enabled connectors and stores the |
767 | * config in the client's modeset array. |
768 | * |
769 | * Returns: |
770 | * Zero on success or negative error code on failure. |
771 | */ |
772 | int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height) |
773 | { |
774 | struct drm_connector *connector, **connectors = NULL; |
775 | struct drm_connector_list_iter conn_iter; |
776 | struct drm_device *dev = client->dev; |
777 | unsigned int total_modes_count = 0; |
778 | struct drm_client_offset *offsets; |
779 | unsigned int connector_count = 0; |
780 | struct drm_display_mode **modes; |
781 | struct drm_crtc **crtcs; |
782 | int i, ret = 0; |
783 | bool *enabled; |
784 | |
785 | DRM_DEBUG_KMS("\n" ); |
786 | |
787 | if (!width) |
788 | width = dev->mode_config.max_width; |
789 | if (!height) |
790 | height = dev->mode_config.max_height; |
791 | |
792 | drm_connector_list_iter_begin(dev, iter: &conn_iter); |
793 | drm_client_for_each_connector_iter(connector, &conn_iter) { |
794 | struct drm_connector **tmp; |
795 | |
796 | tmp = krealloc(objp: connectors, new_size: (connector_count + 1) * sizeof(*connectors), GFP_KERNEL); |
797 | if (!tmp) { |
798 | ret = -ENOMEM; |
799 | goto free_connectors; |
800 | } |
801 | |
802 | connectors = tmp; |
803 | drm_connector_get(connector); |
804 | connectors[connector_count++] = connector; |
805 | } |
806 | drm_connector_list_iter_end(iter: &conn_iter); |
807 | |
808 | if (!connector_count) |
809 | return 0; |
810 | |
811 | crtcs = kcalloc(n: connector_count, size: sizeof(*crtcs), GFP_KERNEL); |
812 | modes = kcalloc(n: connector_count, size: sizeof(*modes), GFP_KERNEL); |
813 | offsets = kcalloc(n: connector_count, size: sizeof(*offsets), GFP_KERNEL); |
814 | enabled = kcalloc(n: connector_count, size: sizeof(bool), GFP_KERNEL); |
815 | if (!crtcs || !modes || !enabled || !offsets) { |
816 | DRM_ERROR("Memory allocation failed\n" ); |
817 | ret = -ENOMEM; |
818 | goto out; |
819 | } |
820 | |
821 | mutex_lock(&client->modeset_mutex); |
822 | |
823 | mutex_lock(&dev->mode_config.mutex); |
824 | for (i = 0; i < connector_count; i++) |
825 | total_modes_count += connectors[i]->funcs->fill_modes(connectors[i], width, height); |
826 | if (!total_modes_count) |
827 | DRM_DEBUG_KMS("No connectors reported connected with modes\n" ); |
828 | drm_client_connectors_enabled(connectors, connector_count, enabled); |
829 | |
830 | if (!drm_client_firmware_config(client, connectors, connector_count, crtcs, |
831 | modes, offsets, enabled, width, height)) { |
832 | memset(modes, 0, connector_count * sizeof(*modes)); |
833 | memset(crtcs, 0, connector_count * sizeof(*crtcs)); |
834 | memset(offsets, 0, connector_count * sizeof(*offsets)); |
835 | |
836 | if (!drm_client_target_cloned(dev, connectors, connector_count, modes, |
837 | offsets, enabled, width, height) && |
838 | !drm_client_target_preferred(connectors, connector_count, modes, |
839 | offsets, enabled, width, height)) |
840 | DRM_ERROR("Unable to find initial modes\n" ); |
841 | |
842 | DRM_DEBUG_KMS("picking CRTCs for %dx%d config\n" , |
843 | width, height); |
844 | |
845 | drm_client_pick_crtcs(client, connectors, connector_count, |
846 | best_crtcs: crtcs, modes, n: 0, width, height); |
847 | } |
848 | mutex_unlock(lock: &dev->mode_config.mutex); |
849 | |
850 | drm_client_modeset_release(client); |
851 | |
852 | for (i = 0; i < connector_count; i++) { |
853 | struct drm_display_mode *mode = modes[i]; |
854 | struct drm_crtc *crtc = crtcs[i]; |
855 | struct drm_client_offset *offset = &offsets[i]; |
856 | |
857 | if (mode && crtc) { |
858 | struct drm_mode_set *modeset = drm_client_find_modeset(client, crtc); |
859 | struct drm_connector *connector = connectors[i]; |
860 | |
861 | DRM_DEBUG_KMS("desired mode %s set on crtc %d (%d,%d)\n" , |
862 | mode->name, crtc->base.id, offset->x, offset->y); |
863 | |
864 | if (WARN_ON_ONCE(modeset->num_connectors == DRM_CLIENT_MAX_CLONED_CONNECTORS || |
865 | (dev->mode_config.num_crtc > 1 && modeset->num_connectors == 1))) { |
866 | ret = -EINVAL; |
867 | break; |
868 | } |
869 | |
870 | kfree(objp: modeset->mode); |
871 | modeset->mode = drm_mode_duplicate(dev, mode); |
872 | drm_connector_get(connector); |
873 | modeset->connectors[modeset->num_connectors++] = connector; |
874 | modeset->x = offset->x; |
875 | modeset->y = offset->y; |
876 | } |
877 | } |
878 | |
879 | mutex_unlock(lock: &client->modeset_mutex); |
880 | out: |
881 | kfree(objp: crtcs); |
882 | kfree(objp: modes); |
883 | kfree(objp: offsets); |
884 | kfree(objp: enabled); |
885 | free_connectors: |
886 | for (i = 0; i < connector_count; i++) |
887 | drm_connector_put(connector: connectors[i]); |
888 | kfree(objp: connectors); |
889 | |
890 | return ret; |
891 | } |
892 | EXPORT_SYMBOL(drm_client_modeset_probe); |
893 | |
894 | /** |
895 | * drm_client_rotation() - Check the initial rotation value |
896 | * @modeset: DRM modeset |
897 | * @rotation: Returned rotation value |
898 | * |
899 | * This function checks if the primary plane in @modeset can hw rotate |
900 | * to match the rotation needed on its connector. |
901 | * |
902 | * Note: Currently only 0 and 180 degrees are supported. |
903 | * |
904 | * Return: |
905 | * True if the plane can do the rotation, false otherwise. |
906 | */ |
907 | bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation) |
908 | { |
909 | struct drm_connector *connector = modeset->connectors[0]; |
910 | struct drm_plane *plane = modeset->crtc->primary; |
911 | struct drm_cmdline_mode *cmdline; |
912 | u64 valid_mask = 0; |
913 | unsigned int i; |
914 | |
915 | if (!modeset->num_connectors) |
916 | return false; |
917 | |
918 | switch (connector->display_info.panel_orientation) { |
919 | case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP: |
920 | *rotation = DRM_MODE_ROTATE_180; |
921 | break; |
922 | case DRM_MODE_PANEL_ORIENTATION_LEFT_UP: |
923 | *rotation = DRM_MODE_ROTATE_90; |
924 | break; |
925 | case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP: |
926 | *rotation = DRM_MODE_ROTATE_270; |
927 | break; |
928 | default: |
929 | *rotation = DRM_MODE_ROTATE_0; |
930 | } |
931 | |
932 | /** |
933 | * The panel already defined the default rotation |
934 | * through its orientation. Whatever has been provided |
935 | * on the command line needs to be added to that. |
936 | * |
937 | * Unfortunately, the rotations are at different bit |
938 | * indices, so the math to add them up are not as |
939 | * trivial as they could. |
940 | * |
941 | * Reflections on the other hand are pretty trivial to deal with, a |
942 | * simple XOR between the two handle the addition nicely. |
943 | */ |
944 | cmdline = &connector->cmdline_mode; |
945 | if (cmdline->specified && cmdline->rotation_reflection) { |
946 | unsigned int cmdline_rest, panel_rest; |
947 | unsigned int cmdline_rot, panel_rot; |
948 | unsigned int sum_rot, sum_rest; |
949 | |
950 | panel_rot = ilog2(*rotation & DRM_MODE_ROTATE_MASK); |
951 | cmdline_rot = ilog2(cmdline->rotation_reflection & DRM_MODE_ROTATE_MASK); |
952 | sum_rot = (panel_rot + cmdline_rot) % 4; |
953 | |
954 | panel_rest = *rotation & ~DRM_MODE_ROTATE_MASK; |
955 | cmdline_rest = cmdline->rotation_reflection & ~DRM_MODE_ROTATE_MASK; |
956 | sum_rest = panel_rest ^ cmdline_rest; |
957 | |
958 | *rotation = (1 << sum_rot) | sum_rest; |
959 | } |
960 | |
961 | /* |
962 | * TODO: support 90 / 270 degree hardware rotation, |
963 | * depending on the hardware this may require the framebuffer |
964 | * to be in a specific tiling format. |
965 | */ |
966 | if (((*rotation & DRM_MODE_ROTATE_MASK) != DRM_MODE_ROTATE_0 && |
967 | (*rotation & DRM_MODE_ROTATE_MASK) != DRM_MODE_ROTATE_180) || |
968 | !plane->rotation_property) |
969 | return false; |
970 | |
971 | for (i = 0; i < plane->rotation_property->num_values; i++) |
972 | valid_mask |= (1ULL << plane->rotation_property->values[i]); |
973 | |
974 | if (!(*rotation & valid_mask)) |
975 | return false; |
976 | |
977 | return true; |
978 | } |
979 | EXPORT_SYMBOL(drm_client_rotation); |
980 | |
981 | static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active, bool check) |
982 | { |
983 | struct drm_device *dev = client->dev; |
984 | struct drm_plane *plane; |
985 | struct drm_atomic_state *state; |
986 | struct drm_modeset_acquire_ctx ctx; |
987 | struct drm_mode_set *mode_set; |
988 | int ret; |
989 | |
990 | drm_modeset_acquire_init(ctx: &ctx, flags: 0); |
991 | |
992 | state = drm_atomic_state_alloc(dev); |
993 | if (!state) { |
994 | ret = -ENOMEM; |
995 | goto out_ctx; |
996 | } |
997 | |
998 | state->acquire_ctx = &ctx; |
999 | retry: |
1000 | drm_for_each_plane(plane, dev) { |
1001 | struct drm_plane_state *plane_state; |
1002 | |
1003 | plane_state = drm_atomic_get_plane_state(state, plane); |
1004 | if (IS_ERR(ptr: plane_state)) { |
1005 | ret = PTR_ERR(ptr: plane_state); |
1006 | goto out_state; |
1007 | } |
1008 | |
1009 | plane_state->rotation = DRM_MODE_ROTATE_0; |
1010 | |
1011 | /* disable non-primary: */ |
1012 | if (plane->type == DRM_PLANE_TYPE_PRIMARY) |
1013 | continue; |
1014 | |
1015 | ret = __drm_atomic_helper_disable_plane(plane, plane_state); |
1016 | if (ret != 0) |
1017 | goto out_state; |
1018 | } |
1019 | |
1020 | drm_client_for_each_modeset(mode_set, client) { |
1021 | struct drm_plane *primary = mode_set->crtc->primary; |
1022 | unsigned int rotation; |
1023 | |
1024 | if (drm_client_rotation(mode_set, &rotation)) { |
1025 | struct drm_plane_state *plane_state; |
1026 | |
1027 | /* Cannot fail as we've already gotten the plane state above */ |
1028 | plane_state = drm_atomic_get_new_plane_state(state, plane: primary); |
1029 | plane_state->rotation = rotation; |
1030 | } |
1031 | |
1032 | ret = __drm_atomic_helper_set_config(set: mode_set, state); |
1033 | if (ret != 0) |
1034 | goto out_state; |
1035 | |
1036 | /* |
1037 | * __drm_atomic_helper_set_config() sets active when a |
1038 | * mode is set, unconditionally clear it if we force DPMS off |
1039 | */ |
1040 | if (!active) { |
1041 | struct drm_crtc *crtc = mode_set->crtc; |
1042 | struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); |
1043 | |
1044 | crtc_state->active = false; |
1045 | } |
1046 | } |
1047 | |
1048 | if (check) |
1049 | ret = drm_atomic_check_only(state); |
1050 | else |
1051 | ret = drm_atomic_commit(state); |
1052 | |
1053 | out_state: |
1054 | if (ret == -EDEADLK) |
1055 | goto backoff; |
1056 | |
1057 | drm_atomic_state_put(state); |
1058 | out_ctx: |
1059 | drm_modeset_drop_locks(ctx: &ctx); |
1060 | drm_modeset_acquire_fini(ctx: &ctx); |
1061 | |
1062 | return ret; |
1063 | |
1064 | backoff: |
1065 | drm_atomic_state_clear(state); |
1066 | drm_modeset_backoff(ctx: &ctx); |
1067 | |
1068 | goto retry; |
1069 | } |
1070 | |
1071 | static int drm_client_modeset_commit_legacy(struct drm_client_dev *client) |
1072 | { |
1073 | struct drm_device *dev = client->dev; |
1074 | struct drm_mode_set *mode_set; |
1075 | struct drm_plane *plane; |
1076 | int ret = 0; |
1077 | |
1078 | drm_modeset_lock_all(dev); |
1079 | drm_for_each_plane(plane, dev) { |
1080 | if (plane->type != DRM_PLANE_TYPE_PRIMARY) |
1081 | drm_plane_force_disable(plane); |
1082 | |
1083 | if (plane->rotation_property) |
1084 | drm_mode_plane_set_obj_prop(plane, |
1085 | property: plane->rotation_property, |
1086 | DRM_MODE_ROTATE_0); |
1087 | } |
1088 | |
1089 | drm_client_for_each_modeset(mode_set, client) { |
1090 | struct drm_crtc *crtc = mode_set->crtc; |
1091 | |
1092 | if (crtc->funcs->cursor_set2) { |
1093 | ret = crtc->funcs->cursor_set2(crtc, NULL, 0, 0, 0, 0, 0); |
1094 | if (ret) |
1095 | goto out; |
1096 | } else if (crtc->funcs->cursor_set) { |
1097 | ret = crtc->funcs->cursor_set(crtc, NULL, 0, 0, 0); |
1098 | if (ret) |
1099 | goto out; |
1100 | } |
1101 | |
1102 | ret = drm_mode_set_config_internal(set: mode_set); |
1103 | if (ret) |
1104 | goto out; |
1105 | } |
1106 | out: |
1107 | drm_modeset_unlock_all(dev); |
1108 | |
1109 | return ret; |
1110 | } |
1111 | |
1112 | /** |
1113 | * drm_client_modeset_check() - Check modeset configuration |
1114 | * @client: DRM client |
1115 | * |
1116 | * Check modeset configuration. |
1117 | * |
1118 | * Returns: |
1119 | * Zero on success or negative error code on failure. |
1120 | */ |
1121 | int drm_client_modeset_check(struct drm_client_dev *client) |
1122 | { |
1123 | int ret; |
1124 | |
1125 | if (!drm_drv_uses_atomic_modeset(dev: client->dev)) |
1126 | return 0; |
1127 | |
1128 | mutex_lock(&client->modeset_mutex); |
1129 | ret = drm_client_modeset_commit_atomic(client, active: true, check: true); |
1130 | mutex_unlock(lock: &client->modeset_mutex); |
1131 | |
1132 | return ret; |
1133 | } |
1134 | EXPORT_SYMBOL(drm_client_modeset_check); |
1135 | |
1136 | /** |
1137 | * drm_client_modeset_commit_locked() - Force commit CRTC configuration |
1138 | * @client: DRM client |
1139 | * |
1140 | * Commit modeset configuration to crtcs without checking if there is a DRM |
1141 | * master. The assumption is that the caller already holds an internal DRM |
1142 | * master reference acquired with drm_master_internal_acquire(). |
1143 | * |
1144 | * Returns: |
1145 | * Zero on success or negative error code on failure. |
1146 | */ |
1147 | int drm_client_modeset_commit_locked(struct drm_client_dev *client) |
1148 | { |
1149 | struct drm_device *dev = client->dev; |
1150 | int ret; |
1151 | |
1152 | mutex_lock(&client->modeset_mutex); |
1153 | if (drm_drv_uses_atomic_modeset(dev)) |
1154 | ret = drm_client_modeset_commit_atomic(client, active: true, check: false); |
1155 | else |
1156 | ret = drm_client_modeset_commit_legacy(client); |
1157 | mutex_unlock(lock: &client->modeset_mutex); |
1158 | |
1159 | return ret; |
1160 | } |
1161 | EXPORT_SYMBOL(drm_client_modeset_commit_locked); |
1162 | |
1163 | /** |
1164 | * drm_client_modeset_commit() - Commit CRTC configuration |
1165 | * @client: DRM client |
1166 | * |
1167 | * Commit modeset configuration to crtcs. |
1168 | * |
1169 | * Returns: |
1170 | * Zero on success or negative error code on failure. |
1171 | */ |
1172 | int drm_client_modeset_commit(struct drm_client_dev *client) |
1173 | { |
1174 | struct drm_device *dev = client->dev; |
1175 | int ret; |
1176 | |
1177 | if (!drm_master_internal_acquire(dev)) |
1178 | return -EBUSY; |
1179 | |
1180 | ret = drm_client_modeset_commit_locked(client); |
1181 | |
1182 | drm_master_internal_release(dev); |
1183 | |
1184 | return ret; |
1185 | } |
1186 | EXPORT_SYMBOL(drm_client_modeset_commit); |
1187 | |
1188 | static void drm_client_modeset_dpms_legacy(struct drm_client_dev *client, int dpms_mode) |
1189 | { |
1190 | struct drm_device *dev = client->dev; |
1191 | struct drm_connector *connector; |
1192 | struct drm_mode_set *modeset; |
1193 | struct drm_modeset_acquire_ctx ctx; |
1194 | int j; |
1195 | int ret; |
1196 | |
1197 | DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret); |
1198 | drm_client_for_each_modeset(modeset, client) { |
1199 | if (!modeset->crtc->enabled) |
1200 | continue; |
1201 | |
1202 | for (j = 0; j < modeset->num_connectors; j++) { |
1203 | connector = modeset->connectors[j]; |
1204 | connector->funcs->dpms(connector, dpms_mode); |
1205 | drm_object_property_set_value(obj: &connector->base, |
1206 | property: dev->mode_config.dpms_property, val: dpms_mode); |
1207 | } |
1208 | } |
1209 | DRM_MODESET_LOCK_ALL_END(dev, ctx, ret); |
1210 | } |
1211 | |
1212 | /** |
1213 | * drm_client_modeset_dpms() - Set DPMS mode |
1214 | * @client: DRM client |
1215 | * @mode: DPMS mode |
1216 | * |
1217 | * Note: For atomic drivers @mode is reduced to on/off. |
1218 | * |
1219 | * Returns: |
1220 | * Zero on success or negative error code on failure. |
1221 | */ |
1222 | int drm_client_modeset_dpms(struct drm_client_dev *client, int mode) |
1223 | { |
1224 | struct drm_device *dev = client->dev; |
1225 | int ret = 0; |
1226 | |
1227 | if (!drm_master_internal_acquire(dev)) |
1228 | return -EBUSY; |
1229 | |
1230 | mutex_lock(&client->modeset_mutex); |
1231 | if (drm_drv_uses_atomic_modeset(dev)) |
1232 | ret = drm_client_modeset_commit_atomic(client, active: mode == DRM_MODE_DPMS_ON, check: false); |
1233 | else |
1234 | drm_client_modeset_dpms_legacy(client, dpms_mode: mode); |
1235 | mutex_unlock(lock: &client->modeset_mutex); |
1236 | |
1237 | drm_master_internal_release(dev); |
1238 | |
1239 | return ret; |
1240 | } |
1241 | EXPORT_SYMBOL(drm_client_modeset_dpms); |
1242 | |
1243 | #ifdef CONFIG_DRM_KUNIT_TEST |
1244 | #include "tests/drm_client_modeset_test.c" |
1245 | #endif |
1246 | |