1/* 2 * MSI WMI hotkeys 3 * 4 * Copyright (C) 2009 Novell <trenn@suse.de> 5 * 6 * Most stuff taken over from hp-wmi 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 */ 22 23#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 24 25#include <linux/kernel.h> 26#include <linux/input.h> 27#include <linux/input/sparse-keymap.h> 28#include <linux/acpi.h> 29#include <linux/backlight.h> 30#include <linux/slab.h> 31#include <linux/module.h> 32 33MODULE_AUTHOR("Thomas Renninger <trenn@suse.de>"); 34MODULE_DESCRIPTION("MSI laptop WMI hotkeys driver"); 35MODULE_LICENSE("GPL"); 36 37#define DRV_NAME "msi-wmi" 38 39#define MSIWMI_BIOS_GUID "551A1F84-FBDD-4125-91DB-3EA8F44F1D45" 40#define MSIWMI_MSI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2" 41#define MSIWMI_WIND_EVENT_GUID "5B3CC38A-40D9-7245-8AE6-1145B751BE3F" 42 43MODULE_ALIAS("wmi:" MSIWMI_BIOS_GUID); 44MODULE_ALIAS("wmi:" MSIWMI_MSI_EVENT_GUID); 45MODULE_ALIAS("wmi:" MSIWMI_WIND_EVENT_GUID); 46 47enum msi_scancodes { 48 /* Generic MSI keys (not present on MSI Wind) */ 49 MSI_KEY_BRIGHTNESSUP = 0xD0, 50 MSI_KEY_BRIGHTNESSDOWN, 51 MSI_KEY_VOLUMEUP, 52 MSI_KEY_VOLUMEDOWN, 53 MSI_KEY_MUTE, 54 /* MSI Wind keys */ 55 WIND_KEY_TOUCHPAD = 0x08, /* Fn+F3 touchpad toggle */ 56 WIND_KEY_BLUETOOTH = 0x56, /* Fn+F11 Bluetooth toggle */ 57 WIND_KEY_CAMERA, /* Fn+F6 webcam toggle */ 58 WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ 59 WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */ 60 WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ 61}; 62static struct key_entry msi_wmi_keymap[] = { 63 { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, 64 { KE_KEY, MSI_KEY_BRIGHTNESSDOWN, {KEY_BRIGHTNESSDOWN} }, 65 { KE_KEY, MSI_KEY_VOLUMEUP, {KEY_VOLUMEUP} }, 66 { KE_KEY, MSI_KEY_VOLUMEDOWN, {KEY_VOLUMEDOWN} }, 67 { KE_KEY, MSI_KEY_MUTE, {KEY_MUTE} }, 68 69 /* These keys work without WMI. Ignore them to avoid double keycodes */ 70 { KE_IGNORE, WIND_KEY_TOUCHPAD, {KEY_TOUCHPAD_TOGGLE} }, 71 { KE_IGNORE, WIND_KEY_BLUETOOTH, {KEY_BLUETOOTH} }, 72 { KE_IGNORE, WIND_KEY_CAMERA, {KEY_CAMERA} }, 73 { KE_IGNORE, WIND_KEY_WLAN, {KEY_WLAN} }, 74 75 /* These are unknown WMI events found on MSI Wind */ 76 { KE_IGNORE, 0x00 }, 77 { KE_IGNORE, 0x62 }, 78 { KE_IGNORE, 0x63 }, 79 80 /* These are MSI Wind keys that should be handled via WMI */ 81 { KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} }, 82 { KE_KEY, WIND_KEY_ECO, {KEY_PROG2} }, 83 84 { KE_END, 0 } 85}; 86 87static ktime_t last_pressed; 88 89static const struct { 90 const char *guid; 91 bool quirk_last_pressed; 92} *event_wmi, event_wmis[] = { 93 { MSIWMI_MSI_EVENT_GUID, true }, 94 { MSIWMI_WIND_EVENT_GUID, false }, 95}; 96 97static struct backlight_device *backlight; 98 99static int backlight_map[] = { 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF }; 100 101static struct input_dev *msi_wmi_input_dev; 102 103static int msi_wmi_query_block(int instance, int *ret) 104{ 105 acpi_status status; 106 union acpi_object *obj; 107 108 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 109 110 status = wmi_query_block(MSIWMI_BIOS_GUID, instance, &output); 111 112 obj = output.pointer; 113 114 if (!obj || obj->type != ACPI_TYPE_INTEGER) { 115 if (obj) { 116 pr_err("query block returned object " 117 "type: %d - buffer length:%d\n", obj->type, 118 obj->type == ACPI_TYPE_BUFFER ? 119 obj->buffer.length : 0); 120 } 121 kfree(obj); 122 return -EINVAL; 123 } 124 *ret = obj->integer.value; 125 kfree(obj); 126 return 0; 127} 128 129static int msi_wmi_set_block(int instance, int value) 130{ 131 acpi_status status; 132 133 struct acpi_buffer input = { sizeof(int), &value }; 134 135 pr_debug("Going to set block of instance: %d - value: %d\n", 136 instance, value); 137 138 status = wmi_set_block(MSIWMI_BIOS_GUID, instance, &input); 139 140 return ACPI_SUCCESS(status) ? 0 : 1; 141} 142 143static int bl_get(struct backlight_device *bd) 144{ 145 int level, err, ret; 146 147 /* Instance 1 is "get backlight", cmp with DSDT */ 148 err = msi_wmi_query_block(1, &ret); 149 if (err) { 150 pr_err("Could not query backlight: %d\n", err); 151 return -EINVAL; 152 } 153 pr_debug("Get: Query block returned: %d\n", ret); 154 for (level = 0; level < ARRAY_SIZE(backlight_map); level++) { 155 if (backlight_map[level] == ret) { 156 pr_debug("Current backlight level: 0x%X - index: %d\n", 157 backlight_map[level], level); 158 break; 159 } 160 } 161 if (level == ARRAY_SIZE(backlight_map)) { 162 pr_err("get: Invalid brightness value: 0x%X\n", ret); 163 return -EINVAL; 164 } 165 return level; 166} 167 168static int bl_set_status(struct backlight_device *bd) 169{ 170 int bright = bd->props.brightness; 171 if (bright >= ARRAY_SIZE(backlight_map) || bright < 0) 172 return -EINVAL; 173 174 /* Instance 0 is "set backlight" */ 175 return msi_wmi_set_block(0, backlight_map[bright]); 176} 177 178static const struct backlight_ops msi_backlight_ops = { 179 .get_brightness = bl_get, 180 .update_status = bl_set_status, 181}; 182 183static void msi_wmi_notify(u32 value, void *context) 184{ 185 struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; 186 static struct key_entry *key; 187 union acpi_object *obj; 188 acpi_status status; 189 190 status = wmi_get_event_data(value, &response); 191 if (status != AE_OK) { 192 pr_info("bad event status 0x%x\n", status); 193 return; 194 } 195 196 obj = (union acpi_object *)response.pointer; 197 198 if (obj && obj->type == ACPI_TYPE_INTEGER) { 199 int eventcode = obj->integer.value; 200 pr_debug("Eventcode: 0x%x\n", eventcode); 201 key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, 202 eventcode); 203 if (!key) { 204 pr_info("Unknown key pressed - %x\n", eventcode); 205 goto msi_wmi_notify_exit; 206 } 207 208 if (event_wmi->quirk_last_pressed) { 209 ktime_t cur = ktime_get_real(); 210 ktime_t diff = ktime_sub(cur, last_pressed); 211 /* Ignore event if any event happened in a 50 ms 212 timeframe -> Key press may result in 10-20 GPEs */ 213 if (ktime_to_us(diff) < 1000 * 50) { 214 pr_debug("Suppressed key event 0x%X - " 215 "Last press was %lld us ago\n", 216 key->code, ktime_to_us(diff)); 217 goto msi_wmi_notify_exit; 218 } 219 last_pressed = cur; 220 } 221 222 if (key->type == KE_KEY && 223 /* Brightness is served via acpi video driver */ 224 (backlight || 225 (key->code != MSI_KEY_BRIGHTNESSUP && 226 key->code != MSI_KEY_BRIGHTNESSDOWN))) { 227 pr_debug("Send key: 0x%X - Input layer keycode: %d\n", 228 key->code, key->keycode); 229 sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, 230 true); 231 } 232 } else 233 pr_info("Unknown event received\n"); 234 235msi_wmi_notify_exit: 236 kfree(response.pointer); 237} 238 239static int __init msi_wmi_backlight_setup(void) 240{ 241 int err; 242 struct backlight_properties props; 243 244 memset(&props, 0, sizeof(struct backlight_properties)); 245 props.type = BACKLIGHT_PLATFORM; 246 props.max_brightness = ARRAY_SIZE(backlight_map) - 1; 247 backlight = backlight_device_register(DRV_NAME, NULL, NULL, 248 &msi_backlight_ops, 249 &props); 250 if (IS_ERR(backlight)) 251 return PTR_ERR(backlight); 252 253 err = bl_get(NULL); 254 if (err < 0) { 255 backlight_device_unregister(backlight); 256 return err; 257 } 258 259 backlight->props.brightness = err; 260 261 return 0; 262} 263 264static int __init msi_wmi_input_setup(void) 265{ 266 int err; 267 268 msi_wmi_input_dev = input_allocate_device(); 269 if (!msi_wmi_input_dev) 270 return -ENOMEM; 271 272 msi_wmi_input_dev->name = "MSI WMI hotkeys"; 273 msi_wmi_input_dev->phys = "wmi/input0"; 274 msi_wmi_input_dev->id.bustype = BUS_HOST; 275 276 err = sparse_keymap_setup(msi_wmi_input_dev, msi_wmi_keymap, NULL); 277 if (err) 278 goto err_free_dev; 279 280 err = input_register_device(msi_wmi_input_dev); 281 282 if (err) 283 goto err_free_keymap; 284 285 last_pressed = ktime_set(0, 0); 286 287 return 0; 288 289err_free_keymap: 290 sparse_keymap_free(msi_wmi_input_dev); 291err_free_dev: 292 input_free_device(msi_wmi_input_dev); 293 return err; 294} 295 296static int __init msi_wmi_init(void) 297{ 298 int err; 299 int i; 300 301 for (i = 0; i < ARRAY_SIZE(event_wmis); i++) { 302 if (!wmi_has_guid(event_wmis[i].guid)) 303 continue; 304 305 err = msi_wmi_input_setup(); 306 if (err) { 307 pr_err("Unable to setup input device\n"); 308 return err; 309 } 310 311 err = wmi_install_notify_handler(event_wmis[i].guid, 312 msi_wmi_notify, NULL); 313 if (ACPI_FAILURE(err)) { 314 pr_err("Unable to setup WMI notify handler\n"); 315 goto err_free_input; 316 } 317 318 pr_debug("Event handler installed\n"); 319 event_wmi = &event_wmis[i]; 320 break; 321 } 322 323 if (wmi_has_guid(MSIWMI_BIOS_GUID) && !acpi_video_backlight_support()) { 324 err = msi_wmi_backlight_setup(); 325 if (err) { 326 pr_err("Unable to setup backlight device\n"); 327 goto err_uninstall_handler; 328 } 329 pr_debug("Backlight device created\n"); 330 } 331 332 if (!event_wmi && !backlight) { 333 pr_err("This machine doesn't have neither MSI-hotkeys nor backlight through WMI\n"); 334 return -ENODEV; 335 } 336 337 return 0; 338 339err_uninstall_handler: 340 if (event_wmi) 341 wmi_remove_notify_handler(event_wmi->guid); 342err_free_input: 343 if (event_wmi) { 344 sparse_keymap_free(msi_wmi_input_dev); 345 input_unregister_device(msi_wmi_input_dev); 346 } 347 return err; 348} 349 350static void __exit msi_wmi_exit(void) 351{ 352 if (event_wmi) { 353 wmi_remove_notify_handler(event_wmi->guid); 354 sparse_keymap_free(msi_wmi_input_dev); 355 input_unregister_device(msi_wmi_input_dev); 356 } 357 backlight_device_unregister(backlight); 358} 359 360module_init(msi_wmi_init); 361module_exit(msi_wmi_exit); 362