1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Asus Notebooks WMI hotkey driver 4 * 5 * Copyright(C) 2010 Corentin Chary <corentin.chary@gmail.com> 6 */ 7 8 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 9 10 #include <linux/kernel.h> 11 #include <linux/module.h> 12 #include <linux/init.h> 13 #include <linux/input.h> 14 #include <linux/input/sparse-keymap.h> 15 #include <linux/fb.h> 16 #include <linux/dmi.h> 17 #include <linux/i8042.h> 18 19 #include "asus-wmi.h" 20 21 #define ASUS_NB_WMI_FILE "asus-nb-wmi" 22 23 MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>"); 24 MODULE_DESCRIPTION("Asus Notebooks WMI Hotkey Driver"); 25 MODULE_LICENSE("GPL"); 26 27 #define ASUS_NB_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C" 28 29 MODULE_ALIAS("wmi:"ASUS_NB_WMI_EVENT_GUID); 30 31 /* 32 * WAPF defines the behavior of the Fn+Fx wlan key 33 * The significance of values is yet to be found, but 34 * most of the time: 35 * Bit | Bluetooth | WLAN 36 * 0 | Hardware | Hardware 37 * 1 | Hardware | Software 38 * 4 | Software | Software 39 */ 40 static int wapf = -1; 41 module_param(wapf, uint, 0444); 42 MODULE_PARM_DESC(wapf, "WAPF value"); 43 44 static struct quirk_entry *quirks; 45 46 static bool asus_q500a_i8042_filter(unsigned char data, unsigned char str, 47 struct serio *port) 48 { 49 static bool extended; 50 bool ret = false; 51 52 if (str & I8042_STR_AUXDATA) 53 return false; 54 55 if (unlikely(data == 0xe1)) { 56 extended = true; 57 ret = true; 58 } else if (unlikely(extended)) { 59 extended = false; 60 ret = true; 61 } 62 63 return ret; 64 } 65 66 static struct quirk_entry quirk_asus_unknown = { 67 .wapf = 0, 68 }; 69 70 static struct quirk_entry quirk_asus_q500a = { 71 .i8042_filter = asus_q500a_i8042_filter, 72 }; 73 74 /* 75 * For those machines that need software to control bt/wifi status 76 * and can't adjust brightness through ACPI interface 77 * and have duplicate events(ACPI and WMI) for display toggle 78 */ 79 static struct quirk_entry quirk_asus_x55u = { 80 .wapf = 4, 81 .wmi_backlight_power = true, 82 .no_display_toggle = true, 83 }; 84 85 static struct quirk_entry quirk_asus_wapf4 = { 86 .wapf = 4, 87 }; 88 89 static struct quirk_entry quirk_asus_x200ca = { 90 .wapf = 2, 91 }; 92 93 static struct quirk_entry quirk_asus_ux303ub = { 94 .wmi_backlight_native = true, 95 }; 96 97 static struct quirk_entry quirk_asus_x550lb = { 98 .xusb2pr = 0x01D9, 99 }; 100 101 static struct quirk_entry quirk_asus_forceals = { 102 .wmi_force_als_set = true, 103 }; 104 105 static int dmi_matched(const struct dmi_system_id *dmi) 106 { 107 pr_info("Identified laptop model '%s'\n", dmi->ident); 108 quirks = dmi->driver_data; 109 return 1; 110 } 111 112 static const struct dmi_system_id asus_quirks[] = { 113 { 114 .callback = dmi_matched, 115 .ident = "ASUSTeK COMPUTER INC. Q500A", 116 .matches = { 117 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 118 DMI_MATCH(DMI_PRODUCT_NAME, "Q500A"), 119 }, 120 .driver_data = &quirk_asus_q500a, 121 }, 122 { 123 .callback = dmi_matched, 124 .ident = "ASUSTeK COMPUTER INC. U32U", 125 .matches = { 126 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."), 127 DMI_MATCH(DMI_PRODUCT_NAME, "U32U"), 128 }, 129 /* 130 * Note this machine has a Brazos APU, and most Brazos Asus 131 * machines need quirk_asus_x55u / wmi_backlight_power but 132 * here acpi-video seems to work fine for backlight control. 133 */ 134 .driver_data = &quirk_asus_wapf4, 135 }, 136 { 137 .callback = dmi_matched, 138 .ident = "ASUSTeK COMPUTER INC. X302UA", 139 .matches = { 140 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 141 DMI_MATCH(DMI_PRODUCT_NAME, "X302UA"), 142 }, 143 .driver_data = &quirk_asus_wapf4, 144 }, 145 { 146 .callback = dmi_matched, 147 .ident = "ASUSTeK COMPUTER INC. X401U", 148 .matches = { 149 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 150 DMI_MATCH(DMI_PRODUCT_NAME, "X401U"), 151 }, 152 .driver_data = &quirk_asus_x55u, 153 }, 154 { 155 .callback = dmi_matched, 156 .ident = "ASUSTeK COMPUTER INC. X401A", 157 .matches = { 158 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 159 DMI_MATCH(DMI_PRODUCT_NAME, "X401A"), 160 }, 161 .driver_data = &quirk_asus_wapf4, 162 }, 163 { 164 .callback = dmi_matched, 165 .ident = "ASUSTeK COMPUTER INC. X401A1", 166 .matches = { 167 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 168 DMI_MATCH(DMI_PRODUCT_NAME, "X401A1"), 169 }, 170 .driver_data = &quirk_asus_wapf4, 171 }, 172 { 173 .callback = dmi_matched, 174 .ident = "ASUSTeK COMPUTER INC. X45U", 175 .matches = { 176 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 177 DMI_MATCH(DMI_PRODUCT_NAME, "X45U"), 178 }, 179 .driver_data = &quirk_asus_wapf4, 180 }, 181 { 182 .callback = dmi_matched, 183 .ident = "ASUSTeK COMPUTER INC. X456UA", 184 .matches = { 185 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 186 DMI_MATCH(DMI_PRODUCT_NAME, "X456UA"), 187 }, 188 .driver_data = &quirk_asus_wapf4, 189 }, 190 { 191 .callback = dmi_matched, 192 .ident = "ASUSTeK COMPUTER INC. X456UF", 193 .matches = { 194 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 195 DMI_MATCH(DMI_PRODUCT_NAME, "X456UF"), 196 }, 197 .driver_data = &quirk_asus_wapf4, 198 }, 199 { 200 .callback = dmi_matched, 201 .ident = "ASUSTeK COMPUTER INC. X501U", 202 .matches = { 203 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 204 DMI_MATCH(DMI_PRODUCT_NAME, "X501U"), 205 }, 206 .driver_data = &quirk_asus_x55u, 207 }, 208 { 209 .callback = dmi_matched, 210 .ident = "ASUSTeK COMPUTER INC. X501A", 211 .matches = { 212 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 213 DMI_MATCH(DMI_PRODUCT_NAME, "X501A"), 214 }, 215 .driver_data = &quirk_asus_wapf4, 216 }, 217 { 218 .callback = dmi_matched, 219 .ident = "ASUSTeK COMPUTER INC. X501A1", 220 .matches = { 221 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 222 DMI_MATCH(DMI_PRODUCT_NAME, "X501A1"), 223 }, 224 .driver_data = &quirk_asus_wapf4, 225 }, 226 { 227 .callback = dmi_matched, 228 .ident = "ASUSTeK COMPUTER INC. X550CA", 229 .matches = { 230 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 231 DMI_MATCH(DMI_PRODUCT_NAME, "X550CA"), 232 }, 233 .driver_data = &quirk_asus_wapf4, 234 }, 235 { 236 .callback = dmi_matched, 237 .ident = "ASUSTeK COMPUTER INC. X550CC", 238 .matches = { 239 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 240 DMI_MATCH(DMI_PRODUCT_NAME, "X550CC"), 241 }, 242 .driver_data = &quirk_asus_wapf4, 243 }, 244 { 245 .callback = dmi_matched, 246 .ident = "ASUSTeK COMPUTER INC. X550CL", 247 .matches = { 248 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 249 DMI_MATCH(DMI_PRODUCT_NAME, "X550CL"), 250 }, 251 .driver_data = &quirk_asus_wapf4, 252 }, 253 { 254 .callback = dmi_matched, 255 .ident = "ASUSTeK COMPUTER INC. X550VB", 256 .matches = { 257 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 258 DMI_MATCH(DMI_PRODUCT_NAME, "X550VB"), 259 }, 260 .driver_data = &quirk_asus_wapf4, 261 }, 262 { 263 .callback = dmi_matched, 264 .ident = "ASUSTeK COMPUTER INC. X551CA", 265 .matches = { 266 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 267 DMI_MATCH(DMI_PRODUCT_NAME, "X551CA"), 268 }, 269 .driver_data = &quirk_asus_wapf4, 270 }, 271 { 272 .callback = dmi_matched, 273 .ident = "ASUSTeK COMPUTER INC. X55A", 274 .matches = { 275 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 276 DMI_MATCH(DMI_PRODUCT_NAME, "X55A"), 277 }, 278 .driver_data = &quirk_asus_wapf4, 279 }, 280 { 281 .callback = dmi_matched, 282 .ident = "ASUSTeK COMPUTER INC. X55C", 283 .matches = { 284 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 285 DMI_MATCH(DMI_PRODUCT_NAME, "X55C"), 286 }, 287 .driver_data = &quirk_asus_wapf4, 288 }, 289 { 290 .callback = dmi_matched, 291 .ident = "ASUSTeK COMPUTER INC. X55U", 292 .matches = { 293 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 294 DMI_MATCH(DMI_PRODUCT_NAME, "X55U"), 295 }, 296 .driver_data = &quirk_asus_x55u, 297 }, 298 { 299 .callback = dmi_matched, 300 .ident = "ASUSTeK COMPUTER INC. X55VD", 301 .matches = { 302 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 303 DMI_MATCH(DMI_PRODUCT_NAME, "X55VD"), 304 }, 305 .driver_data = &quirk_asus_wapf4, 306 }, 307 { 308 .callback = dmi_matched, 309 .ident = "ASUSTeK COMPUTER INC. X75A", 310 .matches = { 311 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 312 DMI_MATCH(DMI_PRODUCT_NAME, "X75A"), 313 }, 314 .driver_data = &quirk_asus_wapf4, 315 }, 316 { 317 .callback = dmi_matched, 318 .ident = "ASUSTeK COMPUTER INC. X75VBP", 319 .matches = { 320 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 321 DMI_MATCH(DMI_PRODUCT_NAME, "X75VBP"), 322 }, 323 .driver_data = &quirk_asus_wapf4, 324 }, 325 { 326 .callback = dmi_matched, 327 .ident = "ASUSTeK COMPUTER INC. X75VD", 328 .matches = { 329 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 330 DMI_MATCH(DMI_PRODUCT_NAME, "X75VD"), 331 }, 332 .driver_data = &quirk_asus_wapf4, 333 }, 334 { 335 .callback = dmi_matched, 336 .ident = "ASUSTeK COMPUTER INC. 1015E", 337 .matches = { 338 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 339 DMI_MATCH(DMI_PRODUCT_NAME, "1015E"), 340 }, 341 .driver_data = &quirk_asus_wapf4, 342 }, 343 { 344 .callback = dmi_matched, 345 .ident = "ASUSTeK COMPUTER INC. 1015U", 346 .matches = { 347 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 348 DMI_MATCH(DMI_PRODUCT_NAME, "1015U"), 349 }, 350 .driver_data = &quirk_asus_wapf4, 351 }, 352 { 353 .callback = dmi_matched, 354 .ident = "ASUSTeK COMPUTER INC. X200CA", 355 .matches = { 356 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 357 DMI_MATCH(DMI_PRODUCT_NAME, "X200CA"), 358 }, 359 .driver_data = &quirk_asus_x200ca, 360 }, 361 { 362 .callback = dmi_matched, 363 .ident = "ASUSTeK COMPUTER INC. UX303UB", 364 .matches = { 365 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 366 DMI_MATCH(DMI_PRODUCT_NAME, "UX303UB"), 367 }, 368 .driver_data = &quirk_asus_ux303ub, 369 }, 370 { 371 .callback = dmi_matched, 372 .ident = "ASUSTeK COMPUTER INC. UX330UAK", 373 .matches = { 374 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 375 DMI_MATCH(DMI_PRODUCT_NAME, "UX330UAK"), 376 }, 377 .driver_data = &quirk_asus_forceals, 378 }, 379 { 380 .callback = dmi_matched, 381 .ident = "ASUSTeK COMPUTER INC. X550LB", 382 .matches = { 383 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 384 DMI_MATCH(DMI_PRODUCT_NAME, "X550LB"), 385 }, 386 .driver_data = &quirk_asus_x550lb, 387 }, 388 { 389 .callback = dmi_matched, 390 .ident = "ASUSTeK COMPUTER INC. UX430UQ", 391 .matches = { 392 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 393 DMI_MATCH(DMI_PRODUCT_NAME, "UX430UQ"), 394 }, 395 .driver_data = &quirk_asus_forceals, 396 }, 397 {}, 398 }; 399 400 static void asus_nb_wmi_quirks(struct asus_wmi_driver *driver) 401 { 402 int ret; 403 404 quirks = &quirk_asus_unknown; 405 dmi_check_system(asus_quirks); 406 407 driver->quirks = quirks; 408 driver->panel_power = FB_BLANK_UNBLANK; 409 410 /* overwrite the wapf setting if the wapf paramater is specified */ 411 if (wapf != -1) 412 quirks->wapf = wapf; 413 else 414 wapf = quirks->wapf; 415 416 if (quirks->i8042_filter) { 417 ret = i8042_install_filter(quirks->i8042_filter); 418 if (ret) { 419 pr_warn("Unable to install key filter\n"); 420 return; 421 } 422 pr_info("Using i8042 filter function for receiving events\n"); 423 } 424 } 425 426 static const struct key_entry asus_nb_wmi_keymap[] = { 427 { KE_KEY, ASUS_WMI_BRN_DOWN, { KEY_BRIGHTNESSDOWN } }, 428 { KE_KEY, ASUS_WMI_BRN_UP, { KEY_BRIGHTNESSUP } }, 429 { KE_KEY, 0x30, { KEY_VOLUMEUP } }, 430 { KE_KEY, 0x31, { KEY_VOLUMEDOWN } }, 431 { KE_KEY, 0x32, { KEY_MUTE } }, 432 { KE_KEY, 0x35, { KEY_SCREENLOCK } }, 433 { KE_KEY, 0x40, { KEY_PREVIOUSSONG } }, 434 { KE_KEY, 0x41, { KEY_NEXTSONG } }, 435 { KE_KEY, 0x43, { KEY_STOPCD } }, /* Stop/Eject */ 436 { KE_KEY, 0x45, { KEY_PLAYPAUSE } }, 437 { KE_KEY, 0x4c, { KEY_MEDIA } }, /* WMP Key */ 438 { KE_KEY, 0x50, { KEY_EMAIL } }, 439 { KE_KEY, 0x51, { KEY_WWW } }, 440 { KE_KEY, 0x55, { KEY_CALC } }, 441 { KE_IGNORE, 0x57, }, /* Battery mode */ 442 { KE_IGNORE, 0x58, }, /* AC mode */ 443 { KE_KEY, 0x5C, { KEY_F15 } }, /* Power Gear key */ 444 { KE_KEY, 0x5D, { KEY_WLAN } }, /* Wireless console Toggle */ 445 { KE_KEY, 0x5E, { KEY_WLAN } }, /* Wireless console Enable */ 446 { KE_KEY, 0x5F, { KEY_WLAN } }, /* Wireless console Disable */ 447 { KE_KEY, 0x60, { KEY_TOUCHPAD_ON } }, 448 { KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD only */ 449 { KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT only */ 450 { KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT */ 451 { KE_KEY, 0x64, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV */ 452 { KE_KEY, 0x65, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV */ 453 { KE_KEY, 0x66, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV */ 454 { KE_KEY, 0x67, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV */ 455 { KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } }, 456 { KE_IGNORE, 0x6E, }, /* Low Battery notification */ 457 { KE_KEY, 0x7a, { KEY_ALS_TOGGLE } }, /* Ambient Light Sensor Toggle */ 458 { KE_KEY, 0x7D, { KEY_BLUETOOTH } }, /* Bluetooth Enable */ 459 { KE_KEY, 0x7E, { KEY_BLUETOOTH } }, /* Bluetooth Disable */ 460 { KE_KEY, 0x82, { KEY_CAMERA } }, 461 { KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */ 462 { KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */ 463 { KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */ 464 { KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */ 465 { KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */ 466 { KE_KEY, 0x8F, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + DVI */ 467 { KE_KEY, 0x90, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + DVI */ 468 { KE_KEY, 0x91, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + DVI */ 469 { KE_KEY, 0x92, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + DVI */ 470 { KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */ 471 { KE_KEY, 0x95, { KEY_MEDIA } }, 472 { KE_KEY, 0x99, { KEY_PHONE } }, 473 { KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */ 474 { KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */ 475 { KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */ 476 { KE_KEY, 0xA3, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + HDMI */ 477 { KE_KEY, 0xA4, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + HDMI */ 478 { KE_KEY, 0xA5, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + HDMI */ 479 { KE_KEY, 0xA6, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + HDMI */ 480 { KE_KEY, 0xA7, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + HDMI */ 481 { KE_KEY, 0xB5, { KEY_CALC } }, 482 { KE_KEY, 0xC4, { KEY_KBDILLUMUP } }, 483 { KE_KEY, 0xC5, { KEY_KBDILLUMDOWN } }, 484 { KE_IGNORE, 0xC6, }, /* Ambient Light Sensor notification */ 485 { KE_KEY, 0xFA, { KEY_PROG2 } }, /* Lid flip action */ 486 { KE_END, 0}, 487 }; 488 489 static struct asus_wmi_driver asus_nb_wmi_driver = { 490 .name = ASUS_NB_WMI_FILE, 491 .owner = THIS_MODULE, 492 .event_guid = ASUS_NB_WMI_EVENT_GUID, 493 .keymap = asus_nb_wmi_keymap, 494 .input_name = "Asus WMI hotkeys", 495 .input_phys = ASUS_NB_WMI_FILE "/input0", 496 .detect_quirks = asus_nb_wmi_quirks, 497 }; 498 499 500 static int __init asus_nb_wmi_init(void) 501 { 502 return asus_wmi_register_driver(&asus_nb_wmi_driver); 503 } 504 505 static void __exit asus_nb_wmi_exit(void) 506 { 507 asus_wmi_unregister_driver(&asus_nb_wmi_driver); 508 } 509 510 module_init(asus_nb_wmi_init); 511 module_exit(asus_nb_wmi_exit); 512