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 | ||
dae1ccee HG |
87 | static const struct drm_dmi_panel_orientation_data lcd720x1280_rightside_up = { |
88 | .width = 720, | |
89 | .height = 1280, | |
90 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, | |
91 | }; | |
92 | ||
f55826f0 | 93 | static const struct drm_dmi_panel_orientation_data lcd800x1280_rightside_up = { |
404d1a3e HG |
94 | .width = 800, |
95 | .height = 1280, | |
96 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, | |
97 | }; | |
98 | ||
c825dc23 DSR |
99 | static const struct drm_dmi_panel_orientation_data lcd1200x1920_rightside_up = { |
100 | .width = 1200, | |
101 | .height = 1920, | |
102 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, | |
103 | }; | |
104 | ||
404d1a3e | 105 | static const struct dmi_system_id orientation_data[] = { |
0e8afefd HG |
106 | { /* Acer One 10 (S1003) */ |
107 | .matches = { | |
108 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Acer"), | |
109 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "One S1003"), | |
110 | }, | |
a05caf9e | 111 | .driver_data = (void *)&lcd800x1280_rightside_up, |
0e8afefd | 112 | }, { /* Asus T100HA */ |
404d1a3e HG |
113 | .matches = { |
114 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | |
115 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100HAN"), | |
116 | }, | |
117 | .driver_data = (void *)&asus_t100ha, | |
6c22bc18 HG |
118 | }, { /* Asus T101HA */ |
119 | .matches = { | |
120 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | |
121 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T101HA"), | |
122 | }, | |
123 | .driver_data = (void *)&lcd800x1280_rightside_up, | |
b5ac98cb MI |
124 | }, { /* Asus T103HAF */ |
125 | .matches = { | |
126 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | |
127 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T103HAF"), | |
128 | }, | |
129 | .driver_data = (void *)&lcd800x1280_rightside_up, | |
f2f2bb60 HG |
130 | }, { /* GPD MicroPC (generic strings, also match on bios date) */ |
131 | .matches = { | |
132 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"), | |
133 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), | |
134 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"), | |
135 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), | |
136 | }, | |
137 | .driver_data = (void *)&gpd_micropc, | |
dae1ccee HG |
138 | }, { /* GPD MicroPC (later BIOS versions with proper DMI strings) */ |
139 | .matches = { | |
140 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"), | |
141 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MicroPC"), | |
142 | }, | |
143 | .driver_data = (void *)&lcd720x1280_rightside_up, | |
404d1a3e HG |
144 | }, { /* |
145 | * GPD Pocket, note that the the DMI data is less generic then | |
146 | * it seems, devices with a board-vendor of "AMI Corporation" | |
147 | * are quite rare, as are devices which have both board- *and* | |
148 | * product-id set to "Default String" | |
149 | */ | |
150 | .matches = { | |
151 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), | |
152 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), | |
153 | DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"), | |
154 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), | |
155 | }, | |
156 | .driver_data = (void *)&gpd_pocket, | |
6dab9102 HG |
157 | }, { /* GPD Pocket 2 (generic strings, also match on bios date) */ |
158 | .matches = { | |
159 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"), | |
160 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), | |
161 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"), | |
162 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), | |
163 | }, | |
164 | .driver_data = (void *)&gpd_pocket2, | |
404d1a3e HG |
165 | }, { /* GPD Win (same note on DMI match as GPD Pocket) */ |
166 | .matches = { | |
167 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), | |
168 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), | |
169 | DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"), | |
170 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), | |
171 | }, | |
172 | .driver_data = (void *)&gpd_win, | |
1f0eb8b8 HG |
173 | }, { /* GPD Win 2 (too generic strings, also match on bios date) */ |
174 | .matches = { | |
175 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"), | |
176 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), | |
177 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"), | |
178 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), | |
179 | }, | |
180 | .driver_data = (void *)&gpd_win2, | |
404d1a3e HG |
181 | }, { /* I.T.Works TW891 */ |
182 | .matches = { | |
183 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "To be filled by O.E.M."), | |
184 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TW891"), | |
185 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."), | |
186 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "TW891"), | |
187 | }, | |
188 | .driver_data = (void *)&itworks_tw891, | |
068b01d8 HG |
189 | }, { /* |
190 | * Lenovo Ideapad Miix 310 laptop, only some production batches | |
191 | * have a portrait screen, the resolution checks makes the quirk | |
192 | * apply only to those batches. | |
193 | */ | |
194 | .matches = { | |
195 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
196 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80SG"), | |
197 | DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "MIIX 310-10ICR"), | |
198 | }, | |
f55826f0 HG |
199 | .driver_data = (void *)&lcd800x1280_rightside_up, |
200 | }, { /* Lenovo Ideapad Miix 320 */ | |
201 | .matches = { | |
202 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
203 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80XF"), | |
204 | DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 320-10ICR"), | |
205 | }, | |
206 | .driver_data = (void *)&lcd800x1280_rightside_up, | |
c825dc23 DSR |
207 | }, { /* Lenovo Ideapad D330 */ |
208 | .matches = { | |
209 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), | |
210 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "81H3"), | |
211 | DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad D330-10IGM"), | |
212 | }, | |
213 | .driver_data = (void *)&lcd1200x1920_rightside_up, | |
404d1a3e HG |
214 | }, { /* VIOS LTH17 */ |
215 | .matches = { | |
216 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "VIOS"), | |
217 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LTH17"), | |
218 | }, | |
f55826f0 | 219 | .driver_data = (void *)&lcd800x1280_rightside_up, |
404d1a3e HG |
220 | }, |
221 | {} | |
222 | }; | |
223 | ||
224 | /** | |
225 | * drm_get_panel_orientation_quirk - Check for panel orientation quirks | |
226 | * @width: width in pixels of the panel | |
227 | * @height: height in pixels of the panel | |
228 | * | |
229 | * This function checks for platform specific (e.g. DMI based) quirks | |
230 | * providing info on panel_orientation for systems where this cannot be | |
231 | * probed from the hard-/firm-ware. To avoid false-positive this function | |
232 | * takes the panel resolution as argument and checks that against the | |
233 | * resolution expected by the quirk-table entry. | |
234 | * | |
235 | * Note this function is also used outside of the drm-subsys, by for example | |
236 | * the efifb code. Because of this this function gets compiled into its own | |
237 | * kernel-module when built as a module. | |
238 | * | |
239 | * Returns: | |
240 | * A DRM_MODE_PANEL_ORIENTATION_* value if there is a quirk for this system, | |
241 | * or DRM_MODE_PANEL_ORIENTATION_UNKNOWN if there is no quirk. | |
242 | */ | |
243 | int drm_get_panel_orientation_quirk(int width, int height) | |
244 | { | |
245 | const struct dmi_system_id *match; | |
246 | const struct drm_dmi_panel_orientation_data *data; | |
247 | const char *bios_date; | |
248 | int i; | |
249 | ||
250 | for (match = dmi_first_match(orientation_data); | |
251 | match; | |
252 | match = dmi_first_match(match + 1)) { | |
253 | data = match->driver_data; | |
254 | ||
255 | if (data->width != width || | |
256 | data->height != height) | |
257 | continue; | |
258 | ||
259 | if (!data->bios_dates) | |
260 | return data->orientation; | |
261 | ||
262 | bios_date = dmi_get_system_info(DMI_BIOS_DATE); | |
263 | if (!bios_date) | |
264 | continue; | |
265 | ||
818c05d8 AS |
266 | i = match_string(data->bios_dates, -1, bios_date); |
267 | if (i >= 0) | |
268 | return data->orientation; | |
404d1a3e HG |
269 | } |
270 | ||
271 | return DRM_MODE_PANEL_ORIENTATION_UNKNOWN; | |
272 | } | |
273 | EXPORT_SYMBOL(drm_get_panel_orientation_quirk); | |
274 | ||
275 | #else | |
276 | ||
277 | /* There are no quirks for non x86 devices yet */ | |
278 | int drm_get_panel_orientation_quirk(int width, int height) | |
279 | { | |
280 | return DRM_MODE_PANEL_ORIENTATION_UNKNOWN; | |
281 | } | |
282 | EXPORT_SYMBOL(drm_get_panel_orientation_quirk); | |
283 | ||
284 | #endif | |
e61a656f DL |
285 | |
286 | MODULE_LICENSE("Dual MIT/GPL"); |