drm_panel_orientation_quirks.c revision 1.26
1/* SPDX-License-Identifier: MIT */
2/*
3 * drm_panel_orientation_quirks.c -- Quirks for non-normal panel orientation
4 *
5 * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
6 *
7 * Note the quirks in this file are shared with fbdev/efifb and as such
8 * must not depend on other drm code.
9 */
10
11#include <linux/dmi.h>
12#include <linux/module.h>
13#include <drm/drm_connector.h>
14#include <drm/drm_utils.h>
15
16#ifdef CONFIG_DMI
17
18/*
19 * Some x86 clamshell design devices use portrait tablet screens and a display
20 * engine which cannot rotate in hardware, so we need to rotate the fbcon to
21 * compensate. Unfortunately these (cheap) devices also typically have quite
22 * generic DMI data, so we match on a combination of DMI data, screen resolution
23 * and a list of known BIOS dates to avoid false positives.
24 */
25
26struct drm_dmi_panel_orientation_data {
27	int width;
28	int height;
29	const char * const *bios_dates;
30	int orientation;
31};
32
33static const struct drm_dmi_panel_orientation_data gpd_micropc = {
34	.width = 720,
35	.height = 1280,
36	.bios_dates = (const char * const []){ "04/26/2019",
37		NULL },
38	.orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
39};
40
41static const struct drm_dmi_panel_orientation_data gpd_pocket = {
42	.width = 1200,
43	.height = 1920,
44	.bios_dates = (const char * const []){ "05/26/2017", "06/28/2017",
45		"07/05/2017", "08/07/2017", NULL },
46	.orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
47};
48
49static const struct drm_dmi_panel_orientation_data gpd_pocket2 = {
50	.width = 1200,
51	.height = 1920,
52	.bios_dates = (const char * const []){ "06/28/2018", "08/28/2018",
53		"12/07/2018", NULL },
54	.orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
55};
56
57static const struct drm_dmi_panel_orientation_data gpd_win = {
58	.width = 720,
59	.height = 1280,
60	.bios_dates = (const char * const []){
61		"10/25/2016", "11/18/2016", "12/23/2016", "12/26/2016",
62		"02/21/2017", "03/20/2017", "05/25/2017", NULL },
63	.orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
64};
65
66static const struct drm_dmi_panel_orientation_data gpd_win2 = {
67	.width = 720,
68	.height = 1280,
69	.bios_dates = (const char * const []){
70		"12/07/2017", "05/24/2018", "06/29/2018", NULL },
71	.orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
72};
73
74static const struct drm_dmi_panel_orientation_data itworks_tw891 = {
75	.width = 800,
76	.height = 1280,
77	.bios_dates = (const char * const []){ "10/16/2015", NULL },
78	.orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
79};
80
81static const struct drm_dmi_panel_orientation_data onegx1_pro = {
82	.width = 1200,
83	.height = 1920,
84	.bios_dates = (const char * const []){ "12/17/2020", NULL },
85	.orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
86};
87
88static const struct drm_dmi_panel_orientation_data lcd720x1280_rightside_up = {
89	.width = 720,
90	.height = 1280,
91	.orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
92};
93
94static const struct drm_dmi_panel_orientation_data lcd800x1280_leftside_up = {
95	.width = 800,
96	.height = 1280,
97	.orientation = DRM_MODE_PANEL_ORIENTATION_LEFT_UP,
98};
99
100static const struct drm_dmi_panel_orientation_data lcd800x1280_rightside_up = {
101	.width = 800,
102	.height = 1280,
103	.orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
104};
105
106static const struct drm_dmi_panel_orientation_data lcd1080x1920_leftside_up = {
107	.width = 1080,
108	.height = 1920,
109	.orientation = DRM_MODE_PANEL_ORIENTATION_LEFT_UP,
110};
111
112static const struct drm_dmi_panel_orientation_data lcd1200x1920_rightside_up = {
113	.width = 1200,
114	.height = 1920,
115	.orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
116};
117
118static const struct drm_dmi_panel_orientation_data lcd1280x1920_rightside_up = {
119	.width = 1280,
120	.height = 1920,
121	.orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
122};
123
124static const struct drm_dmi_panel_orientation_data lcd1600x2560_leftside_up = {
125	.width = 1600,
126	.height = 2560,
127	.orientation = DRM_MODE_PANEL_ORIENTATION_LEFT_UP,
128};
129
130static const struct drm_dmi_panel_orientation_data lcd1600x2560_rightside_up = {
131	.width = 1600,
132	.height = 2560,
133	.orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
134};
135
136static const struct dmi_system_id orientation_data[] = {
137	{	/* Acer One 10 (S1003) */
138		.matches = {
139		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Acer"),
140		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "One S1003"),
141		},
142		.driver_data = (void *)&lcd800x1280_rightside_up,
143	}, {	/* Acer Switch V 10 (SW5-017) */
144		.matches = {
145		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Acer"),
146		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "SW5-017"),
147		},
148		.driver_data = (void *)&lcd800x1280_rightside_up,
149	}, {	/* Anbernic Win600 */
150		.matches = {
151		  DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Anbernic"),
152		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Win600"),
153		},
154		.driver_data = (void *)&lcd720x1280_rightside_up,
155	}, {	/* Asus T100HA */
156		.matches = {
157		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
158		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100HAN"),
159		},
160		.driver_data = (void *)&lcd800x1280_leftside_up,
161	}, {	/* Asus T101HA */
162		.matches = {
163		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
164		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T101HA"),
165		},
166		.driver_data = (void *)&lcd800x1280_rightside_up,
167	}, {	/* Asus T103HAF */
168		.matches = {
169		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
170		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T103HAF"),
171		},
172		.driver_data = (void *)&lcd800x1280_rightside_up,
173	}, {	/* AYA NEO 2021 */
174		.matches = {
175		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYADEVICE"),
176		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AYA NEO 2021"),
177		},
178		.driver_data = (void *)&lcd800x1280_rightside_up,
179	}, {	/* AYA NEO AIR */
180		.matches = {
181		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYANEO"),
182		  DMI_MATCH(DMI_BOARD_NAME, "AIR"),
183		},
184		.driver_data = (void *)&lcd1080x1920_leftside_up,
185	}, {	/* AYA NEO NEXT */
186		.matches = {
187		  DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
188		  DMI_MATCH(DMI_BOARD_NAME, "NEXT"),
189		},
190		.driver_data = (void *)&lcd800x1280_rightside_up,
191	}, {	/* Chuwi HiBook (CWI514) */
192		.matches = {
193			DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"),
194			DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"),
195			/* Above matches are too generic, add bios-date match */
196			DMI_MATCH(DMI_BIOS_DATE, "05/07/2016"),
197		},
198		.driver_data = (void *)&lcd1200x1920_rightside_up,
199	}, {	/* Chuwi Hi10 Pro (CWI529) */
200		.matches = {
201		  DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Hampoo"),
202		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Hi10 pro tablet"),
203		},
204		.driver_data = (void *)&lcd1200x1920_rightside_up,
205	}, {	/* Dynabook K50 */
206		.matches = {
207		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dynabook Inc."),
208		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "dynabook K50/FR"),
209		},
210		.driver_data = (void *)&lcd800x1280_leftside_up,
211	}, {	/* GPD MicroPC (generic strings, also match on bios date) */
212		.matches = {
213		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"),
214		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
215		  DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"),
216		  DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
217		},
218		.driver_data = (void *)&gpd_micropc,
219	}, {	/* GPD MicroPC (later BIOS versions with proper DMI strings) */
220		.matches = {
221		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"),
222		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MicroPC"),
223		},
224		.driver_data = (void *)&lcd720x1280_rightside_up,
225	}, {	/* GPD Win Max */
226		.matches = {
227		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"),
228		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G1619-01"),
229		},
230		.driver_data = (void *)&lcd800x1280_rightside_up,
231	}, {	/*
232		 * GPD Pocket, note that the DMI data is less generic then
233		 * it seems, devices with a board-vendor of "AMI Corporation"
234		 * are quite rare, as are devices which have both board- *and*
235		 * product-id set to "Default String"
236		 */
237		.matches = {
238		  DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
239		  DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
240		  DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"),
241		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
242		},
243		.driver_data = (void *)&gpd_pocket,
244	}, {	/* GPD Pocket 2 (generic strings, also match on bios date) */
245		.matches = {
246		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"),
247		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
248		  DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"),
249		  DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
250		},
251		.driver_data = (void *)&gpd_pocket2,
252	}, {	/* GPD Win (same note on DMI match as GPD Pocket) */
253		.matches = {
254		  DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
255		  DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
256		  DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"),
257		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
258		},
259		.driver_data = (void *)&gpd_win,
260	}, {	/* GPD Win 2 (too generic strings, also match on bios date) */
261		.matches = {
262		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"),
263		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
264		  DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"),
265		  DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
266		},
267		.driver_data = (void *)&gpd_win2,
268	}, {	/* GPD Win 3 */
269		.matches = {
270		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"),
271		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G1618-03")
272		},
273		.driver_data = (void *)&lcd720x1280_rightside_up,
274	}, {	/* I.T.Works TW891 */
275		.matches = {
276		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "To be filled by O.E.M."),
277		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TW891"),
278		  DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."),
279		  DMI_EXACT_MATCH(DMI_BOARD_NAME, "TW891"),
280		},
281		.driver_data = (void *)&itworks_tw891,
282	}, {	/* Kogan Atlas 2-in-1 D300 */
283		.matches = {
284		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Kogan"),
285		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "KALAP10D300EA"),
286		},
287		.driver_data = (void *)&lcd800x1280_rightside_up,
288	}, {	/* KD Kurio Smart C15200 2-in-1 */
289		.matches = {
290		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "KD Interactive"),
291		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Kurio Smart"),
292		  DMI_EXACT_MATCH(DMI_BOARD_NAME, "KDM960BCP"),
293		},
294		.driver_data = (void *)&lcd800x1280_rightside_up,
295	}, {	/*
296		 * Lenovo Ideapad Miix 310 laptop, only some production batches
297		 * have a portrait screen, the resolution checks makes the quirk
298		 * apply only to those batches.
299		 */
300		.matches = {
301		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
302		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80SG"),
303		  DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "MIIX 310-10ICR"),
304		},
305		.driver_data = (void *)&lcd800x1280_rightside_up,
306	}, {	/* Lenovo Ideapad Miix 320 */
307		.matches = {
308		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
309		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80XF"),
310		  DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 320-10ICR"),
311		},
312		.driver_data = (void *)&lcd800x1280_rightside_up,
313	}, {	/* Lenovo Ideapad D330-10IGM (HD) */
314		.matches = {
315		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
316		  DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad D330-10IGM"),
317		},
318		.driver_data = (void *)&lcd800x1280_rightside_up,
319	}, {	/* Lenovo Ideapad D330-10IGM (FHD) */
320		.matches = {
321		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
322		  DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad D330-10IGM"),
323		},
324		.driver_data = (void *)&lcd1200x1920_rightside_up,
325	}, {	/* Lenovo Ideapad D330-10IGL (HD) */
326		.matches = {
327		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
328		  DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad D330-10IGL"),
329		},
330		.driver_data = (void *)&lcd800x1280_rightside_up,
331	}, {	/* Lenovo IdeaPad Duet 3 10IGL5 */
332		.matches = {
333		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
334		  DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "IdeaPad Duet 3 10IGL5"),
335		},
336		.driver_data = (void *)&lcd1200x1920_rightside_up,
337	}, {	/* Lenovo Yoga Book X90F / X90L */
338		.matches = {
339		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
340		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "CHERRYVIEW D1 PLATFORM"),
341		  DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "YETI-11"),
342		},
343		.driver_data = (void *)&lcd1200x1920_rightside_up,
344	}, {	/* Lenovo Yoga Book X91F / X91L */
345		.matches = {
346		  /* Non exact match to match F + L versions */
347		  DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X91"),
348		},
349		.driver_data = (void *)&lcd1200x1920_rightside_up,
350	}, {	/* Lenovo Yoga Tablet 2 830F / 830L */
351		.matches = {
352		 /*
353		  * Note this also matches the Lenovo Yoga Tablet 2 1050F/L
354		  * since that uses the same mainboard. The resolution match
355		  * will limit this to only matching on the 830F/L. Neither has
356		  * any external video outputs so those are not a concern.
357		  */
358		 DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp."),
359		 DMI_MATCH(DMI_PRODUCT_NAME, "VALLEYVIEW C0 PLATFORM"),
360		 DMI_MATCH(DMI_BOARD_NAME, "BYT-T FFD8"),
361		 /* Partial match on beginning of BIOS version */
362		 DMI_MATCH(DMI_BIOS_VERSION, "BLADE_21"),
363		},
364		.driver_data = (void *)&lcd1200x1920_rightside_up,
365	}, {	/* Lenovo Yoga Tab 3 X90F */
366		.matches = {
367		 DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
368		 DMI_MATCH(DMI_PRODUCT_NAME, "CHERRYVIEW D1 PLATFORM"),
369		 DMI_MATCH(DMI_PRODUCT_VERSION, "Blade3-10A-001"),
370		},
371		.driver_data = (void *)&lcd1600x2560_rightside_up,
372	}, {	/* Nanote UMPC-01 */
373		.matches = {
374		 DMI_MATCH(DMI_SYS_VENDOR, "RWC CO.,LTD"),
375		 DMI_MATCH(DMI_PRODUCT_NAME, "UMPC-01"),
376		},
377		.driver_data = (void *)&lcd1200x1920_rightside_up,
378	}, {	/* OneGX1 Pro */
379		.matches = {
380		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SYSTEM_MANUFACTURER"),
381		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "SYSTEM_PRODUCT_NAME"),
382		  DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Default string"),
383		},
384		.driver_data = (void *)&onegx1_pro,
385	}, {	/* OneXPlayer */
386		.matches = {
387		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK TECHNOLOGY CO., LTD."),
388		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONE XPLAYER"),
389		},
390		.driver_data = (void *)&lcd1600x2560_leftside_up,
391	}, {	/* Samsung GalaxyBook 10.6 */
392		.matches = {
393		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
394		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Galaxy Book 10.6"),
395		},
396		.driver_data = (void *)&lcd1280x1920_rightside_up,
397	}, {	/* Valve Steam Deck */
398		.matches = {
399		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Valve"),
400		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jupiter"),
401		  DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "1"),
402		},
403		.driver_data = (void *)&lcd800x1280_rightside_up,
404	}, {	/* VIOS LTH17 */
405		.matches = {
406		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "VIOS"),
407		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LTH17"),
408		},
409		.driver_data = (void *)&lcd800x1280_rightside_up,
410	},
411	{}
412};
413
414/**
415 * drm_get_panel_orientation_quirk - Check for panel orientation quirks
416 * @width: width in pixels of the panel
417 * @height: height in pixels of the panel
418 *
419 * This function checks for platform specific (e.g. DMI based) quirks
420 * providing info on panel_orientation for systems where this cannot be
421 * probed from the hard-/firm-ware. To avoid false-positive this function
422 * takes the panel resolution as argument and checks that against the
423 * resolution expected by the quirk-table entry.
424 *
425 * Note this function is also used outside of the drm-subsys, by for example
426 * the efifb code. Because of this this function gets compiled into its own
427 * kernel-module when built as a module.
428 *
429 * Returns:
430 * A DRM_MODE_PANEL_ORIENTATION_* value if there is a quirk for this system,
431 * or DRM_MODE_PANEL_ORIENTATION_UNKNOWN if there is no quirk.
432 */
433int drm_get_panel_orientation_quirk(int width, int height)
434{
435	const struct dmi_system_id *match;
436	const struct drm_dmi_panel_orientation_data *data;
437	const char *bios_date;
438	int i;
439
440	for (match = dmi_first_match(orientation_data);
441	     match;
442	     match = dmi_first_match(match + 1)) {
443		data = match->driver_data;
444
445		if (data->width != width ||
446		    data->height != height)
447			continue;
448
449		if (!data->bios_dates)
450			return data->orientation;
451
452		bios_date = dmi_get_system_info(DMI_BIOS_DATE);
453		if (!bios_date)
454			continue;
455
456		i = match_string(data->bios_dates, -1, bios_date);
457		if (i >= 0)
458			return data->orientation;
459	}
460
461	return DRM_MODE_PANEL_ORIENTATION_UNKNOWN;
462}
463EXPORT_SYMBOL(drm_get_panel_orientation_quirk);
464
465#else
466
467/* There are no quirks for non x86 devices yet */
468int drm_get_panel_orientation_quirk(int width, int height)
469{
470	return DRM_MODE_PANEL_ORIENTATION_UNKNOWN;
471}
472EXPORT_SYMBOL(drm_get_panel_orientation_quirk);
473
474#endif
475
476MODULE_LICENSE("Dual MIT/GPL");
477