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 int dmi_matched(const struct dmi_system_id *dmi) 115 { 116 pr_info("Identified laptop model '%s'\n", dmi->ident); 117 quirks = dmi->driver_data; 118 return 1; 119 } 120 121 static const struct dmi_system_id asus_quirks[] = { 122 { 123 .callback = dmi_matched, 124 .ident = "ASUSTeK COMPUTER INC. Q500A", 125 .matches = { 126 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 127 DMI_MATCH(DMI_PRODUCT_NAME, "Q500A"), 128 }, 129 .driver_data = &quirk_asus_q500a, 130 }, 131 { 132 .callback = dmi_matched, 133 .ident = "ASUSTeK COMPUTER INC. U32U", 134 .matches = { 135 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."), 136 DMI_MATCH(DMI_PRODUCT_NAME, "U32U"), 137 }, 138 /* 139 * Note this machine has a Brazos APU, and most Brazos Asus 140 * machines need quirk_asus_x55u / wmi_backlight_power but 141 * here acpi-video seems to work fine for backlight control. 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. X550LB", 373 .matches = { 374 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 375 DMI_MATCH(DMI_PRODUCT_NAME, "X550LB"), 376 }, 377 .driver_data = &quirk_asus_x550lb, 378 }, 379 {}, 380 }; 381 382 static void asus_nb_wmi_quirks(struct asus_wmi_driver *driver) 383 { 384 int ret; 385 386 quirks = &quirk_asus_unknown; 387 dmi_check_system(asus_quirks); 388 389 driver->quirks = quirks; 390 driver->panel_power = FB_BLANK_UNBLANK; 391 392 /* overwrite the wapf setting if the wapf paramater is specified */ 393 if (wapf != -1) 394 quirks->wapf = wapf; 395 else 396 wapf = quirks->wapf; 397 398 if (quirks->i8042_filter) { 399 ret = i8042_install_filter(quirks->i8042_filter); 400 if (ret) { 401 pr_warn("Unable to install key filter\n"); 402 return; 403 } 404 pr_info("Using i8042 filter function for receiving events\n"); 405 } 406 } 407 408 static const struct key_entry asus_nb_wmi_keymap[] = { 409 { KE_KEY, ASUS_WMI_BRN_DOWN, { KEY_BRIGHTNESSDOWN } }, 410 { KE_KEY, ASUS_WMI_BRN_UP, { KEY_BRIGHTNESSUP } }, 411 { KE_KEY, 0x30, { KEY_VOLUMEUP } }, 412 { KE_KEY, 0x31, { KEY_VOLUMEDOWN } }, 413 { KE_KEY, 0x32, { KEY_MUTE } }, 414 { KE_KEY, 0x33, { KEY_DISPLAYTOGGLE } }, /* LCD on */ 415 { KE_KEY, 0x34, { KEY_DISPLAY_OFF } }, /* LCD off */ 416 { KE_KEY, 0x40, { KEY_PREVIOUSSONG } }, 417 { KE_KEY, 0x41, { KEY_NEXTSONG } }, 418 { KE_KEY, 0x43, { KEY_STOPCD } }, /* Stop/Eject */ 419 { KE_KEY, 0x45, { KEY_PLAYPAUSE } }, 420 { KE_KEY, 0x4c, { KEY_MEDIA } }, /* WMP Key */ 421 { KE_KEY, 0x50, { KEY_EMAIL } }, 422 { KE_KEY, 0x51, { KEY_WWW } }, 423 { KE_KEY, 0x55, { KEY_CALC } }, 424 { KE_IGNORE, 0x57, }, /* Battery mode */ 425 { KE_IGNORE, 0x58, }, /* AC mode */ 426 { KE_KEY, 0x5C, { KEY_F15 } }, /* Power Gear key */ 427 { KE_KEY, 0x5D, { KEY_WLAN } }, /* Wireless console Toggle */ 428 { KE_KEY, 0x5E, { KEY_WLAN } }, /* Wireless console Enable */ 429 { KE_KEY, 0x5F, { KEY_WLAN } }, /* Wireless console Disable */ 430 { KE_KEY, 0x60, { KEY_TOUCHPAD_ON } }, 431 { KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD only */ 432 { KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT only */ 433 { KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT */ 434 { KE_KEY, 0x64, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV */ 435 { KE_KEY, 0x65, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV */ 436 { KE_KEY, 0x66, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV */ 437 { KE_KEY, 0x67, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV */ 438 { KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } }, 439 { KE_IGNORE, 0x6E, }, /* Low Battery notification */ 440 { KE_KEY, 0x7a, { KEY_ALS_TOGGLE } }, /* Ambient Light Sensor Toggle */ 441 { KE_KEY, 0x7D, { KEY_BLUETOOTH } }, /* Bluetooth Enable */ 442 { KE_KEY, 0x7E, { KEY_BLUETOOTH } }, /* Bluetooth Disable */ 443 { KE_KEY, 0x82, { KEY_CAMERA } }, 444 { KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */ 445 { KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */ 446 { KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */ 447 { KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */ 448 { KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */ 449 { KE_KEY, 0x8F, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + DVI */ 450 { KE_KEY, 0x90, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + DVI */ 451 { KE_KEY, 0x91, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + DVI */ 452 { KE_KEY, 0x92, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + DVI */ 453 { KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */ 454 { KE_KEY, 0x95, { KEY_MEDIA } }, 455 { KE_KEY, 0x99, { KEY_PHONE } }, 456 { KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */ 457 { KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */ 458 { KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */ 459 { KE_KEY, 0xA3, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + HDMI */ 460 { KE_KEY, 0xA4, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + HDMI */ 461 { KE_KEY, 0xA5, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + HDMI */ 462 { KE_KEY, 0xA6, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + HDMI */ 463 { KE_KEY, 0xA7, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + HDMI */ 464 { KE_KEY, 0xB5, { KEY_CALC } }, 465 { KE_KEY, 0xC4, { KEY_KBDILLUMUP } }, 466 { KE_KEY, 0xC5, { KEY_KBDILLUMDOWN } }, 467 { KE_IGNORE, 0xC6, }, /* Ambient Light Sensor notification */ 468 { KE_END, 0}, 469 }; 470 471 static struct asus_wmi_driver asus_nb_wmi_driver = { 472 .name = ASUS_NB_WMI_FILE, 473 .owner = THIS_MODULE, 474 .event_guid = ASUS_NB_WMI_EVENT_GUID, 475 .keymap = asus_nb_wmi_keymap, 476 .input_name = "Asus WMI hotkeys", 477 .input_phys = ASUS_NB_WMI_FILE "/input0", 478 .detect_quirks = asus_nb_wmi_quirks, 479 }; 480 481 482 static int __init asus_nb_wmi_init(void) 483 { 484 return asus_wmi_register_driver(&asus_nb_wmi_driver); 485 } 486 487 static void __exit asus_nb_wmi_exit(void) 488 { 489 asus_wmi_unregister_driver(&asus_nb_wmi_driver); 490 } 491 492 module_init(asus_nb_wmi_init); 493 module_exit(asus_nb_wmi_exit); 494