Commit | Line | Data |
---|---|---|
404d1a3e HG |
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> | |
e61a656f | 12 | #include <linux/module.h> |
404d1a3e | 13 | #include <drm/drm_connector.h> |
f6d25e1c | 14 | #include <drm/drm_utils.h> |
404d1a3e HG |
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 | ||
f2f2bb60 HG |
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 | ||
404d1a3e HG |
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 | ||
6dab9102 HG |
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 | ||
404d1a3e HG |
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 | ||
1f0eb8b8 HG |
72 | static const struct drm_dmi_panel_orientation_data gpd_win2 = { |
73 | .width = 720, | |
74 | .height = 1280, | |
75 | .bios_dates = (const char * const []){ | |
8817b44a | 76 | "12/07/2017", "05/24/2018", "06/29/2018", NULL }, |
1f0eb8b8 HG |
77 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, |
78 | }; | |
79 | ||
404d1a3e HG |
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 | ||
81ad7f9f JB |
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 | ||
dae1ccee HG |
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 | ||
f55826f0 | 100 | static const struct drm_dmi_panel_orientation_data lcd800x1280_rightside_up = { |
404d1a3e HG |
101 | .width = 800, |
102 | .height = 1280, | |
103 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, | |
104 | }; | |
105 | ||
c825dc23 DSR |
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 | ||
88fa1fde HG |
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 | ||
404d1a3e | 118 | static const struct dmi_system_id orientation_data[] = { |
0e8afefd HG |
119 | { /* Acer One 10 (S1003) */ |
120 | .matches = { | |
121 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Acer"), | |
122 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "One S1003"), | |
123 | }, | |
a05caf9e | 124 | .driver_data = (void *)&lcd800x1280_rightside_up, |
0e8afefd | 125 | }, { /* Asus T100HA */ |
404d1a3e HG |
126 | .matches = { |
127 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | |
128 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100HAN"), | |
129 | }, | |
130 | .driver_data = (void *)&asus_t100ha, | |
6c22bc18 HG |
131 | }, { /* Asus T101HA */ |
132 | .matches = { | |
133 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | |
134 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T101HA"), | |
135 | }, | |
136 | .driver_data = (void *)&lcd800x1280_rightside_up, | |
b5ac98cb MI |
137 | }, { /* Asus T103HAF */ |
138 | .matches = { | |
139 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | |
140 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T103HAF"), | |
141 | }, | |
142 | .driver_data = (void *)&lcd800x1280_rightside_up, | |
def0c369 BM |
143 | }, { /* AYA NEO 2021 */ |
144 | .matches = { | |
145 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYADEVICE"), | |
146 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AYA NEO 2021"), | |
147 | }, | |
148 | .driver_data = (void *)&lcd800x1280_rightside_up, | |
63a48815 HG |
149 | }, { /* Chuwi HiBook (CWI514) */ |
150 | .matches = { | |
151 | DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), | |
152 | DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), | |
153 | /* Above matches are too generic, add bios-date match */ | |
154 | DMI_MATCH(DMI_BIOS_DATE, "05/07/2016"), | |
155 | }, | |
156 | .driver_data = (void *)&lcd1200x1920_rightside_up, | |
072e70d5 HG |
157 | }, { /* Chuwi Hi10 Pro (CWI529) */ |
158 | .matches = { | |
159 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Hampoo"), | |
160 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Hi10 pro tablet"), | |
161 | }, | |
162 | .driver_data = (void *)&lcd1200x1920_rightside_up, | |
f2f2bb60 HG |
163 | }, { /* GPD MicroPC (generic strings, also match on bios date) */ |
164 | .matches = { | |
165 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"), | |
166 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), | |
167 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"), | |
168 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), | |
169 | }, | |
170 | .driver_data = (void *)&gpd_micropc, | |
dae1ccee HG |
171 | }, { /* GPD MicroPC (later BIOS versions with proper DMI strings) */ |
172 | .matches = { | |
173 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"), | |
174 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MicroPC"), | |
175 | }, | |
176 | .driver_data = (void *)&lcd720x1280_rightside_up, | |
404d1a3e HG |
177 | }, { /* |
178 | * GPD Pocket, note that the the DMI data is less generic then | |
179 | * it seems, devices with a board-vendor of "AMI Corporation" | |
180 | * are quite rare, as are devices which have both board- *and* | |
181 | * product-id set to "Default String" | |
182 | */ | |
183 | .matches = { | |
184 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), | |
185 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), | |
186 | DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"), | |
187 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), | |
188 | }, | |
189 | .driver_data = (void *)&gpd_pocket, | |
6dab9102 HG |
190 | }, { /* GPD Pocket 2 (generic strings, also match on bios date) */ |
191 | .matches = { | |
192 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"), | |
193 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), | |
194 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"), | |
195 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), | |
196 | }, | |
197 | .driver_data = (void *)&gpd_pocket2, | |
404d1a3e HG |
198 | }, { /* GPD Win (same note on DMI match as GPD Pocket) */ |
199 | .matches = { | |
200 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), | |
201 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), | |
202 | DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"), | |
203 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), | |
204 | }, | |
205 | .driver_data = (void *)&gpd_win, | |
1f0eb8b8 HG |
206 | }, { /* GPD Win 2 (too generic strings, also match on bios date) */ |
207 | .matches = { | |
208 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"), | |
209 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), | |
210 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"), | |
211 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), | |
212 | }, | |
213 | .driver_data = (void *)&gpd_win2, | |
61b1d445 M |
214 | }, { /* GPD Win 3 */ |
215 | .matches = { | |
216 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"), | |
217 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G1618-03") | |
218 | }, | |
219 | .driver_data = (void *)&lcd720x1280_rightside_up, | |
404d1a3e HG |
220 | }, { /* I.T.Works TW891 */ |
221 | .matches = { | |
222 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "To be filled by O.E.M."), | |
223 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TW891"), | |
224 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."), | |
225 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "TW891"), | |
226 | }, | |
227 | .driver_data = (void *)&itworks_tw891, | |
a53f1dd3 HG |
228 | }, { /* KD Kurio Smart C15200 2-in-1 */ |
229 | .matches = { | |
230 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "KD Interactive"), | |
231 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Kurio Smart"), | |
232 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "KDM960BCP"), | |
233 | }, | |
234 | .driver_data = (void *)&lcd800x1280_rightside_up, | |
068b01d8 HG |
235 | }, { /* |
236 | * Lenovo Ideapad Miix 310 laptop, only some production batches | |
237 | * have a portrait screen, the resolution checks makes the quirk | |
238 | * apply only to those batches. | |
239 | */ | |
240 | .matches = { | |
241 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
242 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80SG"), | |
243 | DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "MIIX 310-10ICR"), | |
244 | }, | |
f55826f0 HG |
245 | .driver_data = (void *)&lcd800x1280_rightside_up, |
246 | }, { /* Lenovo Ideapad Miix 320 */ | |
247 | .matches = { | |
248 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
249 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80XF"), | |
250 | DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 320-10ICR"), | |
251 | }, | |
252 | .driver_data = (void *)&lcd800x1280_rightside_up, | |
820a2ab2 HG |
253 | }, { /* Lenovo Ideapad D330-10IGM (HD) */ |
254 | .matches = { | |
255 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
256 | DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad D330-10IGM"), | |
257 | }, | |
258 | .driver_data = (void *)&lcd800x1280_rightside_up, | |
259 | }, { /* Lenovo Ideapad D330-10IGM (FHD) */ | |
c825dc23 DSR |
260 | .matches = { |
261 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
c825dc23 DSR |
262 | DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad D330-10IGM"), |
263 | }, | |
264 | .driver_data = (void *)&lcd1200x1920_rightside_up, | |
bc30c3b0 HG |
265 | }, { /* Lenovo Yoga Book X90F / X91F / X91L */ |
266 | .matches = { | |
267 | /* Non exact match to match all versions */ | |
268 | DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X9"), | |
269 | }, | |
270 | .driver_data = (void *)&lcd1200x1920_rightside_up, | |
81ad7f9f JB |
271 | }, { /* OneGX1 Pro */ |
272 | .matches = { | |
273 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SYSTEM_MANUFACTURER"), | |
274 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "SYSTEM_PRODUCT_NAME"), | |
275 | DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Default string"), | |
276 | }, | |
277 | .driver_data = (void *)&onegx1_pro, | |
88fa1fde HG |
278 | }, { /* Samsung GalaxyBook 10.6 */ |
279 | .matches = { | |
280 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
281 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Galaxy Book 10.6"), | |
282 | }, | |
283 | .driver_data = (void *)&lcd1280x1920_rightside_up, | |
9eeb7b4e SS |
284 | }, { /* Valve Steam Deck */ |
285 | .matches = { | |
286 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Valve"), | |
287 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jupiter"), | |
288 | DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "1"), | |
289 | }, | |
290 | .driver_data = (void *)&lcd800x1280_rightside_up, | |
404d1a3e HG |
291 | }, { /* VIOS LTH17 */ |
292 | .matches = { | |
293 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "VIOS"), | |
294 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LTH17"), | |
295 | }, | |
f55826f0 | 296 | .driver_data = (void *)&lcd800x1280_rightside_up, |
404d1a3e HG |
297 | }, |
298 | {} | |
299 | }; | |
300 | ||
301 | /** | |
302 | * drm_get_panel_orientation_quirk - Check for panel orientation quirks | |
303 | * @width: width in pixels of the panel | |
304 | * @height: height in pixels of the panel | |
305 | * | |
306 | * This function checks for platform specific (e.g. DMI based) quirks | |
307 | * providing info on panel_orientation for systems where this cannot be | |
308 | * probed from the hard-/firm-ware. To avoid false-positive this function | |
309 | * takes the panel resolution as argument and checks that against the | |
310 | * resolution expected by the quirk-table entry. | |
311 | * | |
312 | * Note this function is also used outside of the drm-subsys, by for example | |
313 | * the efifb code. Because of this this function gets compiled into its own | |
314 | * kernel-module when built as a module. | |
315 | * | |
316 | * Returns: | |
317 | * A DRM_MODE_PANEL_ORIENTATION_* value if there is a quirk for this system, | |
318 | * or DRM_MODE_PANEL_ORIENTATION_UNKNOWN if there is no quirk. | |
319 | */ | |
320 | int drm_get_panel_orientation_quirk(int width, int height) | |
321 | { | |
322 | const struct dmi_system_id *match; | |
323 | const struct drm_dmi_panel_orientation_data *data; | |
324 | const char *bios_date; | |
325 | int i; | |
326 | ||
327 | for (match = dmi_first_match(orientation_data); | |
328 | match; | |
329 | match = dmi_first_match(match + 1)) { | |
330 | data = match->driver_data; | |
331 | ||
332 | if (data->width != width || | |
333 | data->height != height) | |
334 | continue; | |
335 | ||
336 | if (!data->bios_dates) | |
337 | return data->orientation; | |
338 | ||
339 | bios_date = dmi_get_system_info(DMI_BIOS_DATE); | |
340 | if (!bios_date) | |
341 | continue; | |
342 | ||
818c05d8 AS |
343 | i = match_string(data->bios_dates, -1, bios_date); |
344 | if (i >= 0) | |
345 | return data->orientation; | |
404d1a3e HG |
346 | } |
347 | ||
348 | return DRM_MODE_PANEL_ORIENTATION_UNKNOWN; | |
349 | } | |
350 | EXPORT_SYMBOL(drm_get_panel_orientation_quirk); | |
351 | ||
352 | #else | |
353 | ||
354 | /* There are no quirks for non x86 devices yet */ | |
355 | int drm_get_panel_orientation_quirk(int width, int height) | |
356 | { | |
357 | return DRM_MODE_PANEL_ORIENTATION_UNKNOWN; | |
358 | } | |
359 | EXPORT_SYMBOL(drm_get_panel_orientation_quirk); | |
360 | ||
361 | #endif | |
e61a656f DL |
362 | |
363 | MODULE_LICENSE("Dual MIT/GPL"); |