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 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 41 static 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 49 static 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 57 static 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 66 static 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 74 static 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 81 static 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 88 static 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 94 static 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 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 lcd1080x1920_leftside_up = { 107 .width = 1080, 108 .height = 1920, 109 .orientation = DRM_MODE_PANEL_ORIENTATION_LEFT_UP, 110 }; 111 112 static 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 118 static 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 124 static 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 130 static 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 136 static 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 }, { /* KD Kurio Smart C15200 2-in-1 */ 283 .matches = { 284 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "KD Interactive"), 285 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Kurio Smart"), 286 DMI_EXACT_MATCH(DMI_BOARD_NAME, "KDM960BCP"), 287 }, 288 .driver_data = (void *)&lcd800x1280_rightside_up, 289 }, { /* 290 * Lenovo Ideapad Miix 310 laptop, only some production batches 291 * have a portrait screen, the resolution checks makes the quirk 292 * apply only to those batches. 293 */ 294 .matches = { 295 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), 296 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80SG"), 297 DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "MIIX 310-10ICR"), 298 }, 299 .driver_data = (void *)&lcd800x1280_rightside_up, 300 }, { /* Lenovo Ideapad Miix 320 */ 301 .matches = { 302 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), 303 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80XF"), 304 DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 320-10ICR"), 305 }, 306 .driver_data = (void *)&lcd800x1280_rightside_up, 307 }, { /* Lenovo Ideapad D330-10IGM (HD) */ 308 .matches = { 309 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), 310 DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad D330-10IGM"), 311 }, 312 .driver_data = (void *)&lcd800x1280_rightside_up, 313 }, { /* Lenovo Ideapad D330-10IGM (FHD) */ 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 *)&lcd1200x1920_rightside_up, 319 }, { /* Lenovo Ideapad D330-10IGL (HD) */ 320 .matches = { 321 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), 322 DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad D330-10IGL"), 323 }, 324 .driver_data = (void *)&lcd800x1280_rightside_up, 325 }, { /* Lenovo Yoga Book X90F / X91F / X91L */ 326 .matches = { 327 /* Non exact match to match all versions */ 328 DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X9"), 329 }, 330 .driver_data = (void *)&lcd1200x1920_rightside_up, 331 }, { /* Lenovo Yoga Tablet 2 830F / 830L */ 332 .matches = { 333 /* 334 * Note this also matches the Lenovo Yoga Tablet 2 1050F/L 335 * since that uses the same mainboard. The resolution match 336 * will limit this to only matching on the 830F/L. Neither has 337 * any external video outputs so those are not a concern. 338 */ 339 DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp."), 340 DMI_MATCH(DMI_PRODUCT_NAME, "VALLEYVIEW C0 PLATFORM"), 341 DMI_MATCH(DMI_BOARD_NAME, "BYT-T FFD8"), 342 /* Partial match on beginning of BIOS version */ 343 DMI_MATCH(DMI_BIOS_VERSION, "BLADE_21"), 344 }, 345 .driver_data = (void *)&lcd1200x1920_rightside_up, 346 }, { /* Lenovo Yoga Tab 3 X90F */ 347 .matches = { 348 DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), 349 DMI_MATCH(DMI_PRODUCT_NAME, "CHERRYVIEW D1 PLATFORM"), 350 DMI_MATCH(DMI_PRODUCT_VERSION, "Blade3-10A-001"), 351 }, 352 .driver_data = (void *)&lcd1600x2560_rightside_up, 353 }, { /* Nanote UMPC-01 */ 354 .matches = { 355 DMI_MATCH(DMI_SYS_VENDOR, "RWC CO.,LTD"), 356 DMI_MATCH(DMI_PRODUCT_NAME, "UMPC-01"), 357 }, 358 .driver_data = (void *)&lcd1200x1920_rightside_up, 359 }, { /* OneGX1 Pro */ 360 .matches = { 361 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SYSTEM_MANUFACTURER"), 362 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "SYSTEM_PRODUCT_NAME"), 363 DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Default string"), 364 }, 365 .driver_data = (void *)&onegx1_pro, 366 }, { /* OneXPlayer */ 367 .matches = { 368 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK TECHNOLOGY CO., LTD."), 369 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONE XPLAYER"), 370 }, 371 .driver_data = (void *)&lcd1600x2560_leftside_up, 372 }, { /* Samsung GalaxyBook 10.6 */ 373 .matches = { 374 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), 375 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Galaxy Book 10.6"), 376 }, 377 .driver_data = (void *)&lcd1280x1920_rightside_up, 378 }, { /* Valve Steam Deck */ 379 .matches = { 380 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Valve"), 381 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jupiter"), 382 DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "1"), 383 }, 384 .driver_data = (void *)&lcd800x1280_rightside_up, 385 }, { /* VIOS LTH17 */ 386 .matches = { 387 DMI_EXACT_MATCH(DMI_SYS_VENDOR, "VIOS"), 388 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LTH17"), 389 }, 390 .driver_data = (void *)&lcd800x1280_rightside_up, 391 }, 392 {} 393 }; 394 395 /** 396 * drm_get_panel_orientation_quirk - Check for panel orientation quirks 397 * @width: width in pixels of the panel 398 * @height: height in pixels of the panel 399 * 400 * This function checks for platform specific (e.g. DMI based) quirks 401 * providing info on panel_orientation for systems where this cannot be 402 * probed from the hard-/firm-ware. To avoid false-positive this function 403 * takes the panel resolution as argument and checks that against the 404 * resolution expected by the quirk-table entry. 405 * 406 * Note this function is also used outside of the drm-subsys, by for example 407 * the efifb code. Because of this this function gets compiled into its own 408 * kernel-module when built as a module. 409 * 410 * Returns: 411 * A DRM_MODE_PANEL_ORIENTATION_* value if there is a quirk for this system, 412 * or DRM_MODE_PANEL_ORIENTATION_UNKNOWN if there is no quirk. 413 */ 414 int drm_get_panel_orientation_quirk(int width, int height) 415 { 416 const struct dmi_system_id *match; 417 const struct drm_dmi_panel_orientation_data *data; 418 const char *bios_date; 419 int i; 420 421 for (match = dmi_first_match(orientation_data); 422 match; 423 match = dmi_first_match(match + 1)) { 424 data = match->driver_data; 425 426 if (data->width != width || 427 data->height != height) 428 continue; 429 430 if (!data->bios_dates) 431 return data->orientation; 432 433 bios_date = dmi_get_system_info(DMI_BIOS_DATE); 434 if (!bios_date) 435 continue; 436 437 i = match_string(data->bios_dates, -1, bios_date); 438 if (i >= 0) 439 return data->orientation; 440 } 441 442 return DRM_MODE_PANEL_ORIENTATION_UNKNOWN; 443 } 444 EXPORT_SYMBOL(drm_get_panel_orientation_quirk); 445 446 #else 447 448 /* There are no quirks for non x86 devices yet */ 449 int drm_get_panel_orientation_quirk(int width, int height) 450 { 451 return DRM_MODE_PANEL_ORIENTATION_UNKNOWN; 452 } 453 EXPORT_SYMBOL(drm_get_panel_orientation_quirk); 454 455 #endif 456 457 MODULE_LICENSE("Dual MIT/GPL"); 458