Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 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> |
David Lechner | e61a656 | 2017-12-21 12:46:19 -0600 | [diff] [blame] | 12 | #include <linux/module.h> |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 13 | #include <drm/drm_connector.h> |
Ville Syrjälä | f6d25e1c | 2018-02-26 16:24:23 +0200 | [diff] [blame] | 14 | #include <drm/drm_utils.h> |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 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 | |
Hans de Goede | 0e8afefd5 | 2018-10-12 12:16:10 +0200 | [diff] [blame] | 33 | static const struct drm_dmi_panel_orientation_data acer_s1003 = { |
| 34 | .width = 800, |
| 35 | .height = 1280, |
| 36 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, |
| 37 | }; |
| 38 | |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 39 | static const struct drm_dmi_panel_orientation_data asus_t100ha = { |
| 40 | .width = 800, |
| 41 | .height = 1280, |
| 42 | .orientation = DRM_MODE_PANEL_ORIENTATION_LEFT_UP, |
| 43 | }; |
| 44 | |
Hans de Goede | f2f2bb6 | 2019-05-24 14:57:59 +0200 | [diff] [blame] | 45 | static const struct drm_dmi_panel_orientation_data gpd_micropc = { |
| 46 | .width = 720, |
| 47 | .height = 1280, |
| 48 | .bios_dates = (const char * const []){ "04/26/2019", |
| 49 | NULL }, |
| 50 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, |
| 51 | }; |
| 52 | |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 53 | static const struct drm_dmi_panel_orientation_data gpd_pocket = { |
| 54 | .width = 1200, |
| 55 | .height = 1920, |
| 56 | .bios_dates = (const char * const []){ "05/26/2017", "06/28/2017", |
| 57 | "07/05/2017", "08/07/2017", NULL }, |
| 58 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, |
| 59 | }; |
| 60 | |
Hans de Goede | 6dab910 | 2019-05-24 14:57:58 +0200 | [diff] [blame] | 61 | static const struct drm_dmi_panel_orientation_data gpd_pocket2 = { |
| 62 | .width = 1200, |
| 63 | .height = 1920, |
| 64 | .bios_dates = (const char * const []){ "06/28/2018", "08/28/2018", |
| 65 | "12/07/2018", NULL }, |
| 66 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, |
| 67 | }; |
| 68 | |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 69 | static const struct drm_dmi_panel_orientation_data gpd_win = { |
| 70 | .width = 720, |
| 71 | .height = 1280, |
| 72 | .bios_dates = (const char * const []){ |
| 73 | "10/25/2016", "11/18/2016", "12/23/2016", "12/26/2016", |
| 74 | "02/21/2017", "03/20/2017", "05/25/2017", NULL }, |
| 75 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, |
| 76 | }; |
| 77 | |
Hans de Goede | 1f0eb8b | 2018-09-09 15:34:57 +0200 | [diff] [blame] | 78 | static const struct drm_dmi_panel_orientation_data gpd_win2 = { |
| 79 | .width = 720, |
| 80 | .height = 1280, |
| 81 | .bios_dates = (const char * const []){ |
Gabriel Krisman Bertazi | 8817b44 | 2018-10-18 15:31:36 -0400 | [diff] [blame] | 82 | "12/07/2017", "05/24/2018", "06/29/2018", NULL }, |
Hans de Goede | 1f0eb8b | 2018-09-09 15:34:57 +0200 | [diff] [blame] | 83 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, |
| 84 | }; |
| 85 | |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 86 | static const struct drm_dmi_panel_orientation_data itworks_tw891 = { |
| 87 | .width = 800, |
| 88 | .height = 1280, |
| 89 | .bios_dates = (const char * const []){ "10/16/2015", NULL }, |
| 90 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, |
| 91 | }; |
| 92 | |
Hans de Goede | dae1cce | 2019-06-24 17:40:14 +0200 | [diff] [blame] | 93 | static const struct drm_dmi_panel_orientation_data lcd720x1280_rightside_up = { |
| 94 | .width = 720, |
| 95 | .height = 1280, |
| 96 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, |
| 97 | }; |
| 98 | |
Hans de Goede | f55826f | 2018-04-18 14:36:42 +0200 | [diff] [blame] | 99 | static const struct drm_dmi_panel_orientation_data lcd800x1280_rightside_up = { |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 100 | .width = 800, |
| 101 | .height = 1280, |
| 102 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, |
| 103 | }; |
| 104 | |
David Santamaría Rogado | c825dc2 | 2019-02-23 22:19:28 +0100 | [diff] [blame] | 105 | static const struct drm_dmi_panel_orientation_data lcd1200x1920_rightside_up = { |
| 106 | .width = 1200, |
| 107 | .height = 1920, |
| 108 | .orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, |
| 109 | }; |
| 110 | |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 111 | static const struct dmi_system_id orientation_data[] = { |
Hans de Goede | 0e8afefd5 | 2018-10-12 12:16:10 +0200 | [diff] [blame] | 112 | { /* Acer One 10 (S1003) */ |
| 113 | .matches = { |
| 114 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Acer"), |
| 115 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "One S1003"), |
| 116 | }, |
| 117 | .driver_data = (void *)&acer_s1003, |
| 118 | }, { /* Asus T100HA */ |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 119 | .matches = { |
| 120 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), |
| 121 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100HAN"), |
| 122 | }, |
| 123 | .driver_data = (void *)&asus_t100ha, |
Hans de Goede | f2f2bb6 | 2019-05-24 14:57:59 +0200 | [diff] [blame] | 124 | }, { /* GPD MicroPC (generic strings, also match on bios date) */ |
| 125 | .matches = { |
| 126 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"), |
| 127 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), |
| 128 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"), |
| 129 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), |
| 130 | }, |
| 131 | .driver_data = (void *)&gpd_micropc, |
Hans de Goede | dae1cce | 2019-06-24 17:40:14 +0200 | [diff] [blame] | 132 | }, { /* GPD MicroPC (later BIOS versions with proper DMI strings) */ |
| 133 | .matches = { |
| 134 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"), |
| 135 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MicroPC"), |
| 136 | }, |
| 137 | .driver_data = (void *)&lcd720x1280_rightside_up, |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 138 | }, { /* |
| 139 | * GPD Pocket, note that the the DMI data is less generic then |
| 140 | * it seems, devices with a board-vendor of "AMI Corporation" |
| 141 | * are quite rare, as are devices which have both board- *and* |
| 142 | * product-id set to "Default String" |
| 143 | */ |
| 144 | .matches = { |
| 145 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), |
| 146 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), |
| 147 | DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"), |
| 148 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), |
| 149 | }, |
| 150 | .driver_data = (void *)&gpd_pocket, |
Hans de Goede | 6dab910 | 2019-05-24 14:57:58 +0200 | [diff] [blame] | 151 | }, { /* GPD Pocket 2 (generic strings, also match on bios date) */ |
| 152 | .matches = { |
| 153 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"), |
| 154 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), |
| 155 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"), |
| 156 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), |
| 157 | }, |
| 158 | .driver_data = (void *)&gpd_pocket2, |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 159 | }, { /* GPD Win (same note on DMI match as GPD Pocket) */ |
| 160 | .matches = { |
| 161 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), |
| 162 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), |
| 163 | DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"), |
| 164 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), |
| 165 | }, |
| 166 | .driver_data = (void *)&gpd_win, |
Hans de Goede | 1f0eb8b | 2018-09-09 15:34:57 +0200 | [diff] [blame] | 167 | }, { /* GPD Win 2 (too generic strings, also match on bios date) */ |
| 168 | .matches = { |
| 169 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Default string"), |
| 170 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), |
| 171 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"), |
| 172 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), |
| 173 | }, |
| 174 | .driver_data = (void *)&gpd_win2, |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 175 | }, { /* I.T.Works TW891 */ |
| 176 | .matches = { |
| 177 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "To be filled by O.E.M."), |
| 178 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TW891"), |
| 179 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."), |
| 180 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "TW891"), |
| 181 | }, |
| 182 | .driver_data = (void *)&itworks_tw891, |
Hans de Goede | 068b01d | 2018-04-18 14:36:41 +0200 | [diff] [blame] | 183 | }, { /* |
| 184 | * Lenovo Ideapad Miix 310 laptop, only some production batches |
| 185 | * have a portrait screen, the resolution checks makes the quirk |
| 186 | * apply only to those batches. |
| 187 | */ |
| 188 | .matches = { |
| 189 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), |
| 190 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80SG"), |
| 191 | DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "MIIX 310-10ICR"), |
| 192 | }, |
Hans de Goede | f55826f | 2018-04-18 14:36:42 +0200 | [diff] [blame] | 193 | .driver_data = (void *)&lcd800x1280_rightside_up, |
| 194 | }, { /* Lenovo Ideapad Miix 320 */ |
| 195 | .matches = { |
| 196 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), |
| 197 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80XF"), |
| 198 | DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 320-10ICR"), |
| 199 | }, |
| 200 | .driver_data = (void *)&lcd800x1280_rightside_up, |
David Santamaría Rogado | c825dc2 | 2019-02-23 22:19:28 +0100 | [diff] [blame] | 201 | }, { /* Lenovo Ideapad D330 */ |
| 202 | .matches = { |
| 203 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), |
| 204 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "81H3"), |
| 205 | DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad D330-10IGM"), |
| 206 | }, |
| 207 | .driver_data = (void *)&lcd1200x1920_rightside_up, |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 208 | }, { /* VIOS LTH17 */ |
| 209 | .matches = { |
| 210 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "VIOS"), |
| 211 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LTH17"), |
| 212 | }, |
Hans de Goede | f55826f | 2018-04-18 14:36:42 +0200 | [diff] [blame] | 213 | .driver_data = (void *)&lcd800x1280_rightside_up, |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 214 | }, |
| 215 | {} |
| 216 | }; |
| 217 | |
| 218 | /** |
| 219 | * drm_get_panel_orientation_quirk - Check for panel orientation quirks |
| 220 | * @width: width in pixels of the panel |
| 221 | * @height: height in pixels of the panel |
| 222 | * |
| 223 | * This function checks for platform specific (e.g. DMI based) quirks |
| 224 | * providing info on panel_orientation for systems where this cannot be |
| 225 | * probed from the hard-/firm-ware. To avoid false-positive this function |
| 226 | * takes the panel resolution as argument and checks that against the |
| 227 | * resolution expected by the quirk-table entry. |
| 228 | * |
| 229 | * Note this function is also used outside of the drm-subsys, by for example |
| 230 | * the efifb code. Because of this this function gets compiled into its own |
| 231 | * kernel-module when built as a module. |
| 232 | * |
| 233 | * Returns: |
| 234 | * A DRM_MODE_PANEL_ORIENTATION_* value if there is a quirk for this system, |
| 235 | * or DRM_MODE_PANEL_ORIENTATION_UNKNOWN if there is no quirk. |
| 236 | */ |
| 237 | int drm_get_panel_orientation_quirk(int width, int height) |
| 238 | { |
| 239 | const struct dmi_system_id *match; |
| 240 | const struct drm_dmi_panel_orientation_data *data; |
| 241 | const char *bios_date; |
| 242 | int i; |
| 243 | |
| 244 | for (match = dmi_first_match(orientation_data); |
| 245 | match; |
| 246 | match = dmi_first_match(match + 1)) { |
| 247 | data = match->driver_data; |
| 248 | |
| 249 | if (data->width != width || |
| 250 | data->height != height) |
| 251 | continue; |
| 252 | |
| 253 | if (!data->bios_dates) |
| 254 | return data->orientation; |
| 255 | |
| 256 | bios_date = dmi_get_system_info(DMI_BIOS_DATE); |
| 257 | if (!bios_date) |
| 258 | continue; |
| 259 | |
Andy Shevchenko | 818c05d | 2018-05-03 21:41:19 +0300 | [diff] [blame] | 260 | i = match_string(data->bios_dates, -1, bios_date); |
| 261 | if (i >= 0) |
| 262 | return data->orientation; |
Hans de Goede | 404d1a3 | 2017-11-25 20:35:48 +0100 | [diff] [blame] | 263 | } |
| 264 | |
| 265 | return DRM_MODE_PANEL_ORIENTATION_UNKNOWN; |
| 266 | } |
| 267 | EXPORT_SYMBOL(drm_get_panel_orientation_quirk); |
| 268 | |
| 269 | #else |
| 270 | |
| 271 | /* There are no quirks for non x86 devices yet */ |
| 272 | int drm_get_panel_orientation_quirk(int width, int height) |
| 273 | { |
| 274 | return DRM_MODE_PANEL_ORIENTATION_UNKNOWN; |
| 275 | } |
| 276 | EXPORT_SYMBOL(drm_get_panel_orientation_quirk); |
| 277 | |
| 278 | #endif |
David Lechner | e61a656 | 2017-12-21 12:46:19 -0600 | [diff] [blame] | 279 | |
| 280 | MODULE_LICENSE("Dual MIT/GPL"); |