Line data Source code
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 :
26 : struct drm_dmi_panel_orientation_data {
27 : int width;
28 : int height;
29 : const char * const *bios_dates;
30 : int orientation;
31 : };
32 :
33 : static const struct drm_dmi_panel_orientation_data asus_t100ha = {
34 : .width = 800,
35 : .height = 1280,
36 : .orientation = DRM_MODE_PANEL_ORIENTATION_LEFT_UP,
37 : };
38 :
39 : static const struct drm_dmi_panel_orientation_data gpd_micropc = {
40 : .width = 720,
41 : .height = 1280,
42 : .bios_dates = (const char * const []){ "04/26/2019",
43 : NULL },
44 : .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
45 : };
46 :
47 : static const struct drm_dmi_panel_orientation_data gpd_pocket = {
48 : .width = 1200,
49 : .height = 1920,
50 : .bios_dates = (const char * const []){ "05/26/2017", "06/28/2017",
51 : "07/05/2017", "08/07/2017", NULL },
52 : .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
53 : };
54 :
55 : static const struct drm_dmi_panel_orientation_data gpd_pocket2 = {
56 : .width = 1200,
57 : .height = 1920,
58 : .bios_dates = (const char * const []){ "06/28/2018", "08/28/2018",
59 : "12/07/2018", NULL },
60 : .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
61 : };
62 :
63 : static const struct drm_dmi_panel_orientation_data gpd_win = {
64 : .width = 720,
65 : .height = 1280,
66 : .bios_dates = (const char * const []){
67 : "10/25/2016", "11/18/2016", "12/23/2016", "12/26/2016",
68 : "02/21/2017", "03/20/2017", "05/25/2017", NULL },
69 : .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
70 : };
71 :
72 : static const struct drm_dmi_panel_orientation_data gpd_win2 = {
73 : .width = 720,
74 : .height = 1280,
75 : .bios_dates = (const char * const []){
76 : "12/07/2017", "05/24/2018", "06/29/2018", NULL },
77 : .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
78 : };
79 :
80 : static const struct drm_dmi_panel_orientation_data itworks_tw891 = {
81 : .width = 800,
82 : .height = 1280,
83 : .bios_dates = (const char * const []){ "10/16/2015", NULL },
84 : .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
85 : };
86 :
87 : static const struct drm_dmi_panel_orientation_data onegx1_pro = {
88 : .width = 1200,
89 : .height = 1920,
90 : .bios_dates = (const char * const []){ "12/17/2020", NULL },
91 : .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
92 : };
93 :
94 : static const struct drm_dmi_panel_orientation_data lcd720x1280_rightside_up = {
95 : .width = 720,
96 : .height = 1280,
97 : .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
98 : };
99 :
100 : static 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 :
106 : static const struct drm_dmi_panel_orientation_data lcd1200x1920_rightside_up = {
107 : .width = 1200,
108 : .height = 1920,
109 : .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
110 : };
111 :
112 : static const struct drm_dmi_panel_orientation_data lcd1280x1920_rightside_up = {
113 : .width = 1280,
114 : .height = 1920,
115 : .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
116 : };
117 :
118 : static const struct drm_dmi_panel_orientation_data lcd1600x2560_leftside_up = {
119 : .width = 1600,
120 : .height = 2560,
121 : .orientation = DRM_MODE_PANEL_ORIENTATION_LEFT_UP,
122 : };
123 :
124 : static const struct dmi_system_id orientation_data[] = {
125 : { /* Acer One 10 (S1003) */
126 : .matches = {
127 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Acer"),
128 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "One S1003"),
129 : },
130 : .driver_data = (void *)&lcd800x1280_rightside_up,
131 : }, { /* Asus T100HA */
132 : .matches = {
133 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
134 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100HAN"),
135 : },
136 : .driver_data = (void *)&asus_t100ha,
137 : }, { /* Asus T101HA */
138 : .matches = {
139 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
140 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T101HA"),
141 : },
142 : .driver_data = (void *)&lcd800x1280_rightside_up,
143 : }, { /* Asus T103HAF */
144 : .matches = {
145 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
146 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T103HAF"),
147 : },
148 : .driver_data = (void *)&lcd800x1280_rightside_up,
149 : }, { /* AYA NEO 2021 */
150 : .matches = {
151 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYADEVICE"),
152 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AYA NEO 2021"),
153 : },
154 : .driver_data = (void *)&lcd800x1280_rightside_up,
155 : }, { /* Chuwi HiBook (CWI514) */
156 : .matches = {
157 : DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"),
158 : DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"),
159 : /* Above matches are too generic, add bios-date match */
160 : DMI_MATCH(DMI_BIOS_DATE, "05/07/2016"),
161 : },
162 : .driver_data = (void *)&lcd1200x1920_rightside_up,
163 : }, { /* Chuwi Hi10 Pro (CWI529) */
164 : .matches = {
165 : DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Hampoo"),
166 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Hi10 pro tablet"),
167 : },
168 : .driver_data = (void *)&lcd1200x1920_rightside_up,
169 : }, { /* GPD MicroPC (generic strings, also match on bios date) */
170 : .matches = {
171 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"),
172 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
173 : DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"),
174 : DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
175 : },
176 : .driver_data = (void *)&gpd_micropc,
177 : }, { /* GPD MicroPC (later BIOS versions with proper DMI strings) */
178 : .matches = {
179 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"),
180 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MicroPC"),
181 : },
182 : .driver_data = (void *)&lcd720x1280_rightside_up,
183 : }, { /* GPD Win Max */
184 : .matches = {
185 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"),
186 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G1619-01"),
187 : },
188 : .driver_data = (void *)&lcd800x1280_rightside_up,
189 : }, { /*
190 : * GPD Pocket, note that the the DMI data is less generic then
191 : * it seems, devices with a board-vendor of "AMI Corporation"
192 : * are quite rare, as are devices which have both board- *and*
193 : * product-id set to "Default String"
194 : */
195 : .matches = {
196 : DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
197 : DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
198 : DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"),
199 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
200 : },
201 : .driver_data = (void *)&gpd_pocket,
202 : }, { /* GPD Pocket 2 (generic strings, also match on bios date) */
203 : .matches = {
204 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"),
205 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
206 : DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"),
207 : DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
208 : },
209 : .driver_data = (void *)&gpd_pocket2,
210 : }, { /* GPD Win (same note on DMI match as GPD Pocket) */
211 : .matches = {
212 : DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
213 : DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
214 : DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"),
215 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
216 : },
217 : .driver_data = (void *)&gpd_win,
218 : }, { /* GPD Win 2 (too generic strings, also match on bios date) */
219 : .matches = {
220 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"),
221 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
222 : DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"),
223 : DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
224 : },
225 : .driver_data = (void *)&gpd_win2,
226 : }, { /* GPD Win 3 */
227 : .matches = {
228 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"),
229 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G1618-03")
230 : },
231 : .driver_data = (void *)&lcd720x1280_rightside_up,
232 : }, { /* I.T.Works TW891 */
233 : .matches = {
234 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "To be filled by O.E.M."),
235 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TW891"),
236 : DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."),
237 : DMI_EXACT_MATCH(DMI_BOARD_NAME, "TW891"),
238 : },
239 : .driver_data = (void *)&itworks_tw891,
240 : }, { /* KD Kurio Smart C15200 2-in-1 */
241 : .matches = {
242 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "KD Interactive"),
243 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Kurio Smart"),
244 : DMI_EXACT_MATCH(DMI_BOARD_NAME, "KDM960BCP"),
245 : },
246 : .driver_data = (void *)&lcd800x1280_rightside_up,
247 : }, { /*
248 : * Lenovo Ideapad Miix 310 laptop, only some production batches
249 : * have a portrait screen, the resolution checks makes the quirk
250 : * apply only to those batches.
251 : */
252 : .matches = {
253 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
254 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80SG"),
255 : DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "MIIX 310-10ICR"),
256 : },
257 : .driver_data = (void *)&lcd800x1280_rightside_up,
258 : }, { /* Lenovo Ideapad Miix 320 */
259 : .matches = {
260 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
261 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80XF"),
262 : DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 320-10ICR"),
263 : },
264 : .driver_data = (void *)&lcd800x1280_rightside_up,
265 : }, { /* Lenovo Ideapad D330-10IGM (HD) */
266 : .matches = {
267 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
268 : DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad D330-10IGM"),
269 : },
270 : .driver_data = (void *)&lcd800x1280_rightside_up,
271 : }, { /* Lenovo Ideapad D330-10IGM (FHD) */
272 : .matches = {
273 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
274 : DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad D330-10IGM"),
275 : },
276 : .driver_data = (void *)&lcd1200x1920_rightside_up,
277 : }, { /* Lenovo Yoga Book X90F / X91F / X91L */
278 : .matches = {
279 : /* Non exact match to match all versions */
280 : DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X9"),
281 : },
282 : .driver_data = (void *)&lcd1200x1920_rightside_up,
283 : }, { /* OneGX1 Pro */
284 : .matches = {
285 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SYSTEM_MANUFACTURER"),
286 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "SYSTEM_PRODUCT_NAME"),
287 : DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Default string"),
288 : },
289 : .driver_data = (void *)&onegx1_pro,
290 : }, { /* OneXPlayer */
291 : .matches = {
292 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK TECHNOLOGY CO., LTD."),
293 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONE XPLAYER"),
294 : },
295 : .driver_data = (void *)&lcd1600x2560_leftside_up,
296 : }, { /* Samsung GalaxyBook 10.6 */
297 : .matches = {
298 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
299 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Galaxy Book 10.6"),
300 : },
301 : .driver_data = (void *)&lcd1280x1920_rightside_up,
302 : }, { /* Valve Steam Deck */
303 : .matches = {
304 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Valve"),
305 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jupiter"),
306 : DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "1"),
307 : },
308 : .driver_data = (void *)&lcd800x1280_rightside_up,
309 : }, { /* VIOS LTH17 */
310 : .matches = {
311 : DMI_EXACT_MATCH(DMI_SYS_VENDOR, "VIOS"),
312 : DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LTH17"),
313 : },
314 : .driver_data = (void *)&lcd800x1280_rightside_up,
315 : },
316 : {}
317 : };
318 :
319 : /**
320 : * drm_get_panel_orientation_quirk - Check for panel orientation quirks
321 : * @width: width in pixels of the panel
322 : * @height: height in pixels of the panel
323 : *
324 : * This function checks for platform specific (e.g. DMI based) quirks
325 : * providing info on panel_orientation for systems where this cannot be
326 : * probed from the hard-/firm-ware. To avoid false-positive this function
327 : * takes the panel resolution as argument and checks that against the
328 : * resolution expected by the quirk-table entry.
329 : *
330 : * Note this function is also used outside of the drm-subsys, by for example
331 : * the efifb code. Because of this this function gets compiled into its own
332 : * kernel-module when built as a module.
333 : *
334 : * Returns:
335 : * A DRM_MODE_PANEL_ORIENTATION_* value if there is a quirk for this system,
336 : * or DRM_MODE_PANEL_ORIENTATION_UNKNOWN if there is no quirk.
337 : */
338 : int drm_get_panel_orientation_quirk(int width, int height)
339 : {
340 : const struct dmi_system_id *match;
341 : const struct drm_dmi_panel_orientation_data *data;
342 : const char *bios_date;
343 : int i;
344 :
345 : for (match = dmi_first_match(orientation_data);
346 : match;
347 : match = dmi_first_match(match + 1)) {
348 : data = match->driver_data;
349 :
350 : if (data->width != width ||
351 : data->height != height)
352 : continue;
353 :
354 : if (!data->bios_dates)
355 : return data->orientation;
356 :
357 : bios_date = dmi_get_system_info(DMI_BIOS_DATE);
358 : if (!bios_date)
359 : continue;
360 :
361 : i = match_string(data->bios_dates, -1, bios_date);
362 : if (i >= 0)
363 : return data->orientation;
364 : }
365 :
366 : return DRM_MODE_PANEL_ORIENTATION_UNKNOWN;
367 : }
368 : EXPORT_SYMBOL(drm_get_panel_orientation_quirk);
369 :
370 : #else
371 :
372 : /* There are no quirks for non x86 devices yet */
373 0 : int drm_get_panel_orientation_quirk(int width, int height)
374 : {
375 0 : return DRM_MODE_PANEL_ORIENTATION_UNKNOWN;
376 : }
377 : EXPORT_SYMBOL(drm_get_panel_orientation_quirk);
378 :
379 : #endif
380 :
381 : MODULE_LICENSE("Dual MIT/GPL");
|