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