blob: acaf58fb3b720397d6dbf60b5356ae35ec283234 [file] [log] [blame]
Daniel Stonefbe6c1d2019-06-17 16:04:26 +01001/*
2 * Copyright © 2008-2011 Kristian Høgsberg
3 * Copyright © 2011 Intel Corporation
4 * Copyright © 2017, 2018 Collabora, Ltd.
5 * Copyright © 2017, 2018 General Electric Company
6 * Copyright (c) 2018 DisplayLink (UK) Ltd.
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining
9 * a copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice (including the
17 * next paragraph) shall be included in all copies or substantial
18 * portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
24 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
25 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 * SOFTWARE.
28 */
29
30#include "config.h"
31
32#include <xf86drm.h>
33#include <xf86drmMode.h>
Daniel Stonefbe6c1d2019-06-17 16:04:26 +010034
35#include "drm-internal.h"
Pekka Paalanen4b301fe2021-02-04 17:39:45 +020036#include "shared/weston-drm-fourcc.h"
leng.fang32af9fc2024-06-13 11:22:15 +080037#ifdef ENABLE_DRM_HELP
38#include "compositor-drm-help.h"
39#endif
leng.fang2c0999b2024-06-27 11:39:24 +080040#include "aml-weston/aml-backend.h"
Daniel Stonefbe6c1d2019-06-17 16:04:26 +010041
42static const char *const aspect_ratio_as_string[] = {
43 [WESTON_MODE_PIC_AR_NONE] = "",
44 [WESTON_MODE_PIC_AR_4_3] = " 4:3",
45 [WESTON_MODE_PIC_AR_16_9] = " 16:9",
46 [WESTON_MODE_PIC_AR_64_27] = " 64:27",
47 [WESTON_MODE_PIC_AR_256_135] = " 256:135",
48};
49
50/*
51 * Get the aspect-ratio from drmModeModeInfo mode flags.
52 *
53 * @param drm_mode_flags- flags from drmModeModeInfo structure.
54 * @returns aspect-ratio as encoded in enum 'weston_mode_aspect_ratio'.
55 */
56static enum weston_mode_aspect_ratio
57drm_to_weston_mode_aspect_ratio(uint32_t drm_mode_flags)
58{
Daniel Stone60937722019-11-26 10:48:12 +000059 switch (drm_mode_flags & DRM_MODE_FLAG_PIC_AR_MASK) {
60 case DRM_MODE_FLAG_PIC_AR_4_3:
61 return WESTON_MODE_PIC_AR_4_3;
62 case DRM_MODE_FLAG_PIC_AR_16_9:
63 return WESTON_MODE_PIC_AR_16_9;
64 case DRM_MODE_FLAG_PIC_AR_64_27:
65 return WESTON_MODE_PIC_AR_64_27;
66 case DRM_MODE_FLAG_PIC_AR_256_135:
67 return WESTON_MODE_PIC_AR_256_135;
68 case DRM_MODE_FLAG_PIC_AR_NONE:
69 default:
70 return WESTON_MODE_PIC_AR_NONE;
71 }
Daniel Stonefbe6c1d2019-06-17 16:04:26 +010072}
73
74static const char *
75aspect_ratio_to_string(enum weston_mode_aspect_ratio ratio)
76{
77 if (ratio < 0 || ratio >= ARRAY_LENGTH(aspect_ratio_as_string) ||
78 !aspect_ratio_as_string[ratio])
79 return " (unknown aspect ratio)";
80
81 return aspect_ratio_as_string[ratio];
82}
83
84static int
85drm_subpixel_to_wayland(int drm_value)
86{
87 switch (drm_value) {
88 default:
89 case DRM_MODE_SUBPIXEL_UNKNOWN:
90 return WL_OUTPUT_SUBPIXEL_UNKNOWN;
91 case DRM_MODE_SUBPIXEL_NONE:
92 return WL_OUTPUT_SUBPIXEL_NONE;
93 case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
94 return WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB;
95 case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
96 return WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR;
97 case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
98 return WL_OUTPUT_SUBPIXEL_VERTICAL_RGB;
99 case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
100 return WL_OUTPUT_SUBPIXEL_VERTICAL_BGR;
101 }
102}
103
104int
105drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode)
106{
107 int ret;
108
109 if (mode->blob_id)
110 return 0;
111
112 ret = drmModeCreatePropertyBlob(backend->drm.fd,
113 &mode->mode_info,
114 sizeof(mode->mode_info),
115 &mode->blob_id);
116 if (ret != 0)
117 weston_log("failed to create mode property blob: %s\n",
118 strerror(errno));
119
120 drm_debug(backend, "\t\t\t[atomic] created new mode blob %lu for %s\n",
121 (unsigned long) mode->blob_id, mode->mode_info.name);
122
123 return ret;
124}
125
126static bool
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300127check_non_desktop(struct drm_connector *connector, drmModeObjectPropertiesPtr props)
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100128{
129 struct drm_property_info *non_desktop_info =
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300130 &connector->props[WDRM_CONNECTOR_NON_DESKTOP];
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100131
132 return drm_property_get_value(non_desktop_info, props, 0);
133}
134
Lucas Stach72e7a1e2019-11-25 23:31:57 +0000135static uint32_t
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300136get_panel_orientation(struct drm_connector *connector, drmModeObjectPropertiesPtr props)
Lucas Stach72e7a1e2019-11-25 23:31:57 +0000137{
138 struct drm_property_info *orientation =
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300139 &connector->props[WDRM_CONNECTOR_PANEL_ORIENTATION];
Lucas Stach72e7a1e2019-11-25 23:31:57 +0000140 uint64_t kms_val =
141 drm_property_get_value(orientation, props,
142 WDRM_PANEL_ORIENTATION_NORMAL);
143
144 switch (kms_val) {
145 case WDRM_PANEL_ORIENTATION_NORMAL:
146 return WL_OUTPUT_TRANSFORM_NORMAL;
147 case WDRM_PANEL_ORIENTATION_UPSIDE_DOWN:
148 return WL_OUTPUT_TRANSFORM_180;
149 case WDRM_PANEL_ORIENTATION_LEFT_SIDE_UP:
150 return WL_OUTPUT_TRANSFORM_90;
151 case WDRM_PANEL_ORIENTATION_RIGHT_SIDE_UP:
152 return WL_OUTPUT_TRANSFORM_270;
153 default:
154 assert(!"unknown property value in get_panel_orientation");
155 }
156}
157
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100158static int
159parse_modeline(const char *s, drmModeModeInfo *mode)
160{
161 char hsync[16];
162 char vsync[16];
163 float fclock;
164
165 memset(mode, 0, sizeof *mode);
166
167 mode->type = DRM_MODE_TYPE_USERDEF;
168 mode->hskew = 0;
169 mode->vscan = 0;
170 mode->vrefresh = 0;
171 mode->flags = 0;
172
173 if (sscanf(s, "%f %hd %hd %hd %hd %hd %hd %hd %hd %15s %15s",
174 &fclock,
175 &mode->hdisplay,
176 &mode->hsync_start,
177 &mode->hsync_end,
178 &mode->htotal,
179 &mode->vdisplay,
180 &mode->vsync_start,
181 &mode->vsync_end,
182 &mode->vtotal, hsync, vsync) != 11)
183 return -1;
184
185 mode->clock = fclock * 1000;
186 if (strcasecmp(hsync, "+hsync") == 0)
187 mode->flags |= DRM_MODE_FLAG_PHSYNC;
188 else if (strcasecmp(hsync, "-hsync") == 0)
189 mode->flags |= DRM_MODE_FLAG_NHSYNC;
190 else
191 return -1;
192
193 if (strcasecmp(vsync, "+vsync") == 0)
194 mode->flags |= DRM_MODE_FLAG_PVSYNC;
195 else if (strcasecmp(vsync, "-vsync") == 0)
196 mode->flags |= DRM_MODE_FLAG_NVSYNC;
197 else
198 return -1;
199
200 snprintf(mode->name, sizeof mode->name, "%dx%d@%.3f",
201 mode->hdisplay, mode->vdisplay, fclock);
202
203 return 0;
204}
205
206static void
207edid_parse_string(const uint8_t *data, char text[])
208{
209 int i;
210 int replaced = 0;
211
212 /* this is always 12 bytes, but we can't guarantee it's null
213 * terminated or not junk. */
214 strncpy(text, (const char *) data, 12);
215
216 /* guarantee our new string is null-terminated */
217 text[12] = '\0';
218
219 /* remove insane chars */
220 for (i = 0; text[i] != '\0'; i++) {
221 if (text[i] == '\n' ||
222 text[i] == '\r') {
223 text[i] = '\0';
224 break;
225 }
226 }
227
228 /* ensure string is printable */
229 for (i = 0; text[i] != '\0'; i++) {
230 if (!isprint(text[i])) {
231 text[i] = '-';
232 replaced++;
233 }
234 }
235
236 /* if the string is random junk, ignore the string */
237 if (replaced > 4)
238 text[0] = '\0';
239}
240
241#define EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING 0xfe
242#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME 0xfc
243#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER 0xff
244#define EDID_OFFSET_DATA_BLOCKS 0x36
245#define EDID_OFFSET_LAST_BLOCK 0x6c
246#define EDID_OFFSET_PNPID 0x08
247#define EDID_OFFSET_SERIAL 0x0c
248
249static int
250edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length)
251{
252 int i;
253 uint32_t serial_number;
254
255 /* check header */
256 if (length < 128)
257 return -1;
258 if (data[0] != 0x00 || data[1] != 0xff)
259 return -1;
260
261 /* decode the PNP ID from three 5 bit words packed into 2 bytes
262 * /--08--\/--09--\
263 * 7654321076543210
264 * |\---/\---/\---/
265 * R C1 C2 C3 */
266 edid->pnp_id[0] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x7c) / 4) - 1;
267 edid->pnp_id[1] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x3) * 8) + ((data[EDID_OFFSET_PNPID + 1] & 0xe0) / 32) - 1;
268 edid->pnp_id[2] = 'A' + (data[EDID_OFFSET_PNPID + 1] & 0x1f) - 1;
269 edid->pnp_id[3] = '\0';
270
271 /* maybe there isn't a ASCII serial number descriptor, so use this instead */
272 serial_number = (uint32_t) data[EDID_OFFSET_SERIAL + 0];
273 serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 1] * 0x100;
274 serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 2] * 0x10000;
275 serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 3] * 0x1000000;
276 if (serial_number > 0)
277 sprintf(edid->serial_number, "%lu", (unsigned long) serial_number);
278
279 /* parse EDID data */
280 for (i = EDID_OFFSET_DATA_BLOCKS;
281 i <= EDID_OFFSET_LAST_BLOCK;
282 i += 18) {
283 /* ignore pixel clock data */
284 if (data[i] != 0)
285 continue;
286 if (data[i+2] != 0)
287 continue;
288
289 /* any useful blocks? */
290 if (data[i+3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME) {
291 edid_parse_string(&data[i+5],
292 edid->monitor_name);
293 } else if (data[i+3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER) {
294 edid_parse_string(&data[i+5],
295 edid->serial_number);
296 } else if (data[i+3] == EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING) {
297 edid_parse_string(&data[i+5],
298 edid->eisa_id);
299 }
300 }
301 return 0;
302}
303
304/** Parse monitor make, model and serial from EDID
305 *
306 * \param head The head whose \c drm_edid to fill in.
307 * \param props The DRM connector properties to get the EDID from.
308 * \param[out] make The monitor make (PNP ID).
309 * \param[out] model The monitor model (name).
310 * \param[out] serial_number The monitor serial number.
311 *
312 * Each of \c *make, \c *model and \c *serial_number are set only if the
313 * information is found in the EDID. The pointers they are set to must not
314 * be free()'d explicitly, instead they get implicitly freed when the
315 * \c drm_head is destroyed.
316 */
317static void
318find_and_parse_output_edid(struct drm_head *head,
319 drmModeObjectPropertiesPtr props,
320 const char **make,
321 const char **model,
322 const char **serial_number)
323{
324 drmModePropertyBlobPtr edid_blob = NULL;
325 uint32_t blob_id;
326 int rc;
327
328 blob_id =
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300329 drm_property_get_value(
330 &head->connector.props[WDRM_CONNECTOR_EDID],
331 props, 0);
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100332 if (!blob_id)
333 return;
334
335 edid_blob = drmModeGetPropertyBlob(head->backend->drm.fd, blob_id);
336 if (!edid_blob)
337 return;
338
339 rc = edid_parse(&head->edid,
340 edid_blob->data,
341 edid_blob->length);
342 if (!rc) {
343 if (head->edid.pnp_id[0] != '\0')
344 *make = head->edid.pnp_id;
345 if (head->edid.monitor_name[0] != '\0')
346 *model = head->edid.monitor_name;
347 if (head->edid.serial_number[0] != '\0')
348 *serial_number = head->edid.serial_number;
349 }
350 drmModeFreePropertyBlob(edid_blob);
351}
352
353static uint32_t
354drm_refresh_rate_mHz(const drmModeModeInfo *info)
355{
356 uint64_t refresh;
357
358 /* Calculate higher precision (mHz) refresh rate */
359 refresh = (info->clock * 1000000LL / info->htotal +
360 info->vtotal / 2) / info->vtotal;
361
362 if (info->flags & DRM_MODE_FLAG_INTERLACE)
363 refresh *= 2;
364 if (info->flags & DRM_MODE_FLAG_DBLSCAN)
365 refresh /= 2;
366 if (info->vscan > 1)
367 refresh /= info->vscan;
368
369 return refresh;
370}
371
372/**
373 * Add a mode to output's mode list
374 *
375 * Copy the supplied DRM mode into a Weston mode structure, and add it to the
376 * output's mode list.
377 *
378 * @param output DRM output to add mode to
379 * @param info DRM mode structure to add
380 * @returns Newly-allocated Weston/DRM mode structure
381 */
382static struct drm_mode *
383drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info)
384{
385 struct drm_mode *mode;
386
387 mode = malloc(sizeof *mode);
388 if (mode == NULL)
389 return NULL;
390
391 mode->base.flags = 0;
392 mode->base.width = info->hdisplay;
393 mode->base.height = info->vdisplay;
394
395 mode->base.refresh = drm_refresh_rate_mHz(info);
396 mode->mode_info = *info;
397 mode->blob_id = 0;
398
399 if (info->type & DRM_MODE_TYPE_PREFERRED)
400 mode->base.flags |= WL_OUTPUT_MODE_PREFERRED;
401
402 mode->base.aspect_ratio = drm_to_weston_mode_aspect_ratio(info->flags);
403
404 wl_list_insert(output->base.mode_list.prev, &mode->base.link);
405
406 return mode;
407}
408
409/**
410 * Destroys a mode, and removes it from the list.
411 */
412static void
413drm_output_destroy_mode(struct drm_backend *backend, struct drm_mode *mode)
414{
415 if (mode->blob_id)
416 drmModeDestroyPropertyBlob(backend->drm.fd, mode->blob_id);
417 wl_list_remove(&mode->base.link);
418 free(mode);
419}
420
421/** Destroy a list of drm_modes
422 *
423 * @param backend The backend for releasing mode property blobs.
424 * @param mode_list The list linked by drm_mode::base.link.
425 */
426void
427drm_mode_list_destroy(struct drm_backend *backend, struct wl_list *mode_list)
428{
429 struct drm_mode *mode, *next;
430
431 wl_list_for_each_safe(mode, next, mode_list, base.link)
432 drm_output_destroy_mode(backend, mode);
433}
434
435void
436drm_output_print_modes(struct drm_output *output)
437{
438 struct weston_mode *m;
439 struct drm_mode *dm;
440 const char *aspect_ratio;
441
442 wl_list_for_each(m, &output->base.mode_list, link) {
443 dm = to_drm_mode(m);
444
445 aspect_ratio = aspect_ratio_to_string(m->aspect_ratio);
leng.fang32af9fc2024-06-13 11:22:15 +0800446#ifdef MESON_DRM_FIX_UI_SIZE
447 if (m->flags & WL_OUTPUT_MODE_CURRENT)
448 weston_log_continue(STAMP_SPACE "%dx%d@%.1f%s%s%s, %.1f MHz(MESON_DRM_FIX_UI_SIZE)\n",
449 output->display_size.width, output->display_size.height, m->refresh / 1000.0,
450 aspect_ratio,
451 m->flags & WL_OUTPUT_MODE_PREFERRED ?
452 ", preferred" : "",
453 m->flags & WL_OUTPUT_MODE_CURRENT ?
454 ", current" : "",
455 dm->mode_info.clock / 1000.0);
456 else
457#endif
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100458 weston_log_continue(STAMP_SPACE "%dx%d@%.1f%s%s%s, %.1f MHz\n",
459 m->width, m->height, m->refresh / 1000.0,
460 aspect_ratio,
461 m->flags & WL_OUTPUT_MODE_PREFERRED ?
462 ", preferred" : "",
463 m->flags & WL_OUTPUT_MODE_CURRENT ?
464 ", current" : "",
465 dm->mode_info.clock / 1000.0);
466 }
467}
468
469
470/**
471 * Find the closest-matching mode for a given target
472 *
473 * Given a target mode, find the most suitable mode amongst the output's
474 * current mode list to use, preferring the current mode if possible, to
475 * avoid an expensive mode switch.
476 *
477 * @param output DRM output
478 * @param target_mode Mode to attempt to match
479 * @returns Pointer to a mode from the output's mode list
480 */
481struct drm_mode *
482drm_output_choose_mode(struct drm_output *output,
483 struct weston_mode *target_mode)
484{
485 struct drm_mode *tmp_mode = NULL, *mode_fall_back = NULL, *mode;
486 enum weston_mode_aspect_ratio src_aspect = WESTON_MODE_PIC_AR_NONE;
487 enum weston_mode_aspect_ratio target_aspect = WESTON_MODE_PIC_AR_NONE;
488 struct drm_backend *b;
489
490 b = to_drm_backend(output->base.compositor);
491 target_aspect = target_mode->aspect_ratio;
492 src_aspect = output->base.current_mode->aspect_ratio;
leng.fang32af9fc2024-06-13 11:22:15 +0800493
494 if (
495#ifdef MESON_DRM_FIX_UI_SIZE
496 output->display_size.width == target_mode->width &&
497 output->display_size.height == target_mode->height &&
498#else
499 output->base.current_mode->width == target_mode->width &&
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100500 output->base.current_mode->height == target_mode->height &&
leng.fang32af9fc2024-06-13 11:22:15 +0800501#endif
502 output->base.current_mode->flags == target_mode->flags &&
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100503 (output->base.current_mode->refresh == target_mode->refresh ||
504 target_mode->refresh == 0)) {
505 if (!b->aspect_ratio_supported || src_aspect == target_aspect)
506 return to_drm_mode(output->base.current_mode);
507 }
508
509 wl_list_for_each(mode, &output->base.mode_list, base.link) {
510
511 src_aspect = mode->base.aspect_ratio;
512 if (mode->mode_info.hdisplay == target_mode->width &&
leng.fang32af9fc2024-06-13 11:22:15 +0800513 mode->mode_info.vdisplay == target_mode->height &&
514 ((mode->mode_info.flags & DRM_MODE_FLAG_INTERLACE) == (target_mode->flags & DRM_MODE_FLAG_INTERLACE))
515 ) {
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100516 if (mode->base.refresh == target_mode->refresh ||
517 target_mode->refresh == 0) {
518 if (!b->aspect_ratio_supported ||
519 src_aspect == target_aspect)
520 return mode;
521 else if (!mode_fall_back)
522 mode_fall_back = mode;
523 } else if (!tmp_mode) {
524 tmp_mode = mode;
525 }
526 }
527 }
528
529 if (mode_fall_back)
530 return mode_fall_back;
531
532 return tmp_mode;
533}
534
535void
Leandro Ribeiro702fbf72020-08-18 17:35:05 -0300536update_head_from_connector(struct drm_head *head)
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100537{
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300538 struct drm_connector *connector = &head->connector;
Leandro Ribeiro702fbf72020-08-18 17:35:05 -0300539 drmModeObjectProperties *props = connector->props_drm;
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300540 drmModeConnector *conn = connector->conn;
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100541 const char *make = "unknown";
542 const char *model = "unknown";
543 const char *serial_number = "unknown";
544
545 find_and_parse_output_edid(head, props, &make, &model, &serial_number);
546 weston_head_set_monitor_strings(&head->base, make, model, serial_number);
547 weston_head_set_non_desktop(&head->base,
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300548 check_non_desktop(connector, props));
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100549 weston_head_set_subpixel(&head->base,
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300550 drm_subpixel_to_wayland(conn->subpixel));
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100551
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300552 weston_head_set_physical_size(&head->base, conn->mmWidth, conn->mmHeight);
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100553
Lucas Stach72e7a1e2019-11-25 23:31:57 +0000554 weston_head_set_transform(&head->base,
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300555 get_panel_orientation(connector, props));
Lucas Stach72e7a1e2019-11-25 23:31:57 +0000556
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100557 /* Unknown connection status is assumed disconnected. */
558 weston_head_set_connection_status(&head->base,
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300559 conn->connection == DRM_MODE_CONNECTED);
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100560}
561
562/**
563 * Choose suitable mode for an output
564 *
565 * Find the most suitable mode to use for initial setup (or reconfiguration on
566 * hotplug etc) for a DRM output.
567 *
568 * @param backend the DRM backend
569 * @param output DRM output to choose mode for
570 * @param mode Strategy and preference to use when choosing mode
571 * @param modeline Manually-entered mode (may be NULL)
572 * @param current_mode Mode currently being displayed on this output
573 * @returns A mode from the output's mode list, or NULL if none available
574 */
575static struct drm_mode *
576drm_output_choose_initial_mode(struct drm_backend *backend,
577 struct drm_output *output,
578 enum weston_drm_backend_output_mode mode,
579 const char *modeline,
580 const drmModeModeInfo *current_mode)
581{
582 struct drm_mode *preferred = NULL;
583 struct drm_mode *current = NULL;
584 struct drm_mode *configured = NULL;
585 struct drm_mode *config_fall_back = NULL;
586 struct drm_mode *best = NULL;
587 struct drm_mode *drm_mode;
588 drmModeModeInfo drm_modeline;
589 int32_t width = 0;
590 int32_t height = 0;
591 uint32_t refresh = 0;
leng.fangf80d5b92024-08-05 10:05:32 +0800592 uint32_t flags = 0;
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100593 uint32_t aspect_width = 0;
594 uint32_t aspect_height = 0;
595 enum weston_mode_aspect_ratio aspect_ratio = WESTON_MODE_PIC_AR_NONE;
596 int n;
597
598 if (mode == WESTON_DRM_BACKEND_OUTPUT_PREFERRED && modeline) {
leng.fangf80d5b92024-08-05 10:05:32 +0800599 n = drm_parse_modeline(modeline, &width, &height, &refresh,
600 &aspect_width, &aspect_height, &flags);
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100601 if (backend->aspect_ratio_supported && n == 5) {
602 if (aspect_width == 4 && aspect_height == 3)
603 aspect_ratio = WESTON_MODE_PIC_AR_4_3;
604 else if (aspect_width == 16 && aspect_height == 9)
605 aspect_ratio = WESTON_MODE_PIC_AR_16_9;
606 else if (aspect_width == 64 && aspect_height == 27)
607 aspect_ratio = WESTON_MODE_PIC_AR_64_27;
608 else if (aspect_width == 256 && aspect_height == 135)
609 aspect_ratio = WESTON_MODE_PIC_AR_256_135;
610 else
611 weston_log("Invalid modeline \"%s\" for output %s\n",
612 modeline, output->base.name);
613 }
614 if (n != 2 && n != 3 && n != 5) {
615 width = -1;
616
617 if (parse_modeline(modeline, &drm_modeline) == 0) {
618 configured = drm_output_add_mode(output, &drm_modeline);
619 if (!configured)
620 return NULL;
621 } else {
622 weston_log("Invalid modeline \"%s\" for output %s\n",
623 modeline, output->base.name);
624 }
625 }
626 }
627
628 wl_list_for_each_reverse(drm_mode, &output->base.mode_list, base.link) {
629 if (width == drm_mode->base.width &&
630 height == drm_mode->base.height &&
leng.fangf80d5b92024-08-05 10:05:32 +0800631 (refresh == 0 || refresh == drm_mode->mode_info.vrefresh) &&
632 (drm_mode->mode_info.flags & DRM_MODE_FLAG_INTERLACE) == (flags & DRM_MODE_FLAG_INTERLACE)) {
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100633 if (!backend->aspect_ratio_supported ||
634 aspect_ratio == drm_mode->base.aspect_ratio)
635 configured = drm_mode;
636 else
637 config_fall_back = drm_mode;
638 }
639
640 if (memcmp(current_mode, &drm_mode->mode_info,
641 sizeof *current_mode) == 0)
642 current = drm_mode;
643
644 if (drm_mode->base.flags & WL_OUTPUT_MODE_PREFERRED)
645 preferred = drm_mode;
646
647 best = drm_mode;
648 }
649
650 if (current == NULL && current_mode->clock != 0) {
651 current = drm_output_add_mode(output, current_mode);
652 if (!current)
653 return NULL;
654 }
655
656 if (mode == WESTON_DRM_BACKEND_OUTPUT_CURRENT)
657 configured = current;
658
659 if (configured)
660 return configured;
661
662 if (config_fall_back)
663 return config_fall_back;
664
665 if (preferred)
666 return preferred;
667
668 if (current)
669 return current;
670
671 if (best)
672 return best;
673
674 weston_log("no available modes for %s\n", output->base.name);
675 return NULL;
676}
677
678static uint32_t
679u32distance(uint32_t a, uint32_t b)
680{
681 if (a < b)
682 return b - a;
683 else
684 return a - b;
685}
686
687/** Choose equivalent mode
688 *
689 * If the two modes are not equivalent, return NULL.
690 * Otherwise return the mode that is more likely to work in place of both.
691 *
692 * None of the fuzzy matching criteria in this function have any justification.
693 *
694 * typedef struct _drmModeModeInfo {
695 * uint32_t clock;
696 * uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew;
697 * uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan;
698 *
699 * uint32_t vrefresh;
700 *
701 * uint32_t flags;
702 * uint32_t type;
703 * char name[DRM_DISPLAY_MODE_LEN];
704 * } drmModeModeInfo, *drmModeModeInfoPtr;
705 */
706static const drmModeModeInfo *
707drm_mode_pick_equivalent(const drmModeModeInfo *a, const drmModeModeInfo *b)
708{
709 uint32_t refresh_a, refresh_b;
710
711 if (a->hdisplay != b->hdisplay || a->vdisplay != b->vdisplay)
712 return NULL;
713
714 if (a->flags != b->flags)
715 return NULL;
716
717 /* kHz */
718 if (u32distance(a->clock, b->clock) > 500)
719 return NULL;
720
721 refresh_a = drm_refresh_rate_mHz(a);
722 refresh_b = drm_refresh_rate_mHz(b);
723 if (u32distance(refresh_a, refresh_b) > 50)
724 return NULL;
725
726 if ((a->type ^ b->type) & DRM_MODE_TYPE_PREFERRED) {
727 if (a->type & DRM_MODE_TYPE_PREFERRED)
728 return a;
729 else
730 return b;
731 }
732
733 return a;
734}
735
736/* If the given mode info is not already in the list, add it.
737 * If it is in the list, either keep the existing or replace it,
738 * depending on which one is "better".
739 */
740static int
741drm_output_try_add_mode(struct drm_output *output, const drmModeModeInfo *info)
742{
743 struct weston_mode *base;
Scott Anderson60b65722020-01-28 17:18:33 +1300744 struct drm_mode *mode = NULL;
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100745 struct drm_backend *backend;
746 const drmModeModeInfo *chosen = NULL;
747
748 assert(info);
749
750 wl_list_for_each(base, &output->base.mode_list, link) {
751 mode = to_drm_mode(base);
752 chosen = drm_mode_pick_equivalent(&mode->mode_info, info);
753 if (chosen)
754 break;
755 }
756
757 if (chosen == info) {
Scott Anderson60b65722020-01-28 17:18:33 +1300758 assert(mode);
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100759 backend = to_drm_backend(output->base.compositor);
760 drm_output_destroy_mode(backend, mode);
761 chosen = NULL;
762 }
763
764 if (!chosen) {
765 mode = drm_output_add_mode(output, info);
766 if (!mode)
767 return -1;
768 }
769 /* else { the equivalent mode is already in the list } */
770
771 return 0;
772}
773
774/** Rewrite the output's mode list
775 *
776 * @param output The output.
777 * @return 0 on success, -1 on failure.
778 *
779 * Destroy all existing modes in the list, and reconstruct a new list from
780 * scratch, based on the currently attached heads.
781 *
782 * On failure the output's mode list may contain some modes.
783 */
784static int
785drm_output_update_modelist_from_heads(struct drm_output *output)
786{
787 struct drm_backend *backend = to_drm_backend(output->base.compositor);
788 struct weston_head *head_base;
789 struct drm_head *head;
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300790 drmModeConnector *conn;
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100791 int i;
792 int ret;
793
794 assert(!output->base.enabled);
795
796 drm_mode_list_destroy(backend, &output->base.mode_list);
797
798 wl_list_for_each(head_base, &output->base.head_list, output_link) {
799 head = to_drm_head(head_base);
Leandro Ribeiroe6369902020-06-17 11:09:47 -0300800 conn = head->connector.conn;
801 for (i = 0; i < conn->count_modes; i++) {
802 ret = drm_output_try_add_mode(output, &conn->modes[i]);
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100803 if (ret < 0)
804 return -1;
805 }
806 }
807
808 return 0;
809}
810
811int
812drm_output_set_mode(struct weston_output *base,
813 enum weston_drm_backend_output_mode mode,
814 const char *modeline)
815{
816 struct drm_output *output = to_drm_output(base);
817 struct drm_backend *b = to_drm_backend(base->compositor);
818 struct drm_head *head = to_drm_head(weston_output_get_first_head(base));
819
820 struct drm_mode *current;
leng.fang91856072024-06-07 14:12:54 +0800821 struct weston_mode *wstmode;
822 char wstmodeline[32] = { 0 };
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100823
824 if (output->virtual)
825 return -1;
826
827 if (drm_output_update_modelist_from_heads(output) < 0)
828 return -1;
829
leng.fang91856072024-06-07 14:12:54 +0800830 strcpy(wstmodeline, modeline);
831#ifdef ENABLE_MODE_POLICY
leng.fang3e8a4b52024-07-24 14:51:30 +0800832 mode_policy_set_head(&head->base);
leng.fang91856072024-06-07 14:12:54 +0800833 wstmode = mode_policy_choose_mode(NULL);
834 if (wstmode)
leng.fangf80d5b92024-08-05 10:05:32 +0800835 sprintf(wstmodeline, "%dx%d%s@%d %u:%u",
leng.fang91856072024-06-07 14:12:54 +0800836 wstmode->width, wstmode->height,
leng.fangf80d5b92024-08-05 10:05:32 +0800837 wstmode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "",
leng.fang91856072024-06-07 14:12:54 +0800838 wstmode->refresh / 1000, 0, 0);
839#endif
840
841 current = drm_output_choose_initial_mode(b, output, mode, wstmodeline,
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100842 &head->inherited_mode);
843 if (!current)
844 return -1;
845
846 output->base.current_mode = &current->base;
847 output->base.current_mode->flags |= WL_OUTPUT_MODE_CURRENT;
848
849 /* Set native_ fields, so weston_output_mode_switch_to_native() works */
850 output->base.native_mode = output->base.current_mode;
851 output->base.native_scale = output->base.current_scale;
852
leng.fang32af9fc2024-06-13 11:22:15 +0800853 drm_set_mode_update_aml_info(head, output, current);
854
Daniel Stonefbe6c1d2019-06-17 16:04:26 +0100855 return 0;
856}