msi-wmi.c revision fedda8e7385f5fb01acb8897beca90b6256fc7bd
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_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2" 41 42MODULE_ALIAS("wmi:" MSIWMI_BIOS_GUID); 43MODULE_ALIAS("wmi:" MSIWMI_EVENT_GUID); 44 45enum msi_scancodes { 46 MSI_KEY_BRIGHTNESSUP = 0xD0, 47 MSI_KEY_BRIGHTNESSDOWN, 48 MSI_KEY_VOLUMEUP, 49 MSI_KEY_VOLUMEDOWN, 50 MSI_KEY_MUTE, 51}; 52static struct key_entry msi_wmi_keymap[] = { 53 { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, 54 { KE_KEY, MSI_KEY_BRIGHTNESSDOWN, {KEY_BRIGHTNESSDOWN} }, 55 { KE_KEY, MSI_KEY_VOLUMEUP, {KEY_VOLUMEUP} }, 56 { KE_KEY, MSI_KEY_VOLUMEDOWN, {KEY_VOLUMEDOWN} }, 57 { KE_KEY, MSI_KEY_MUTE, {KEY_MUTE} }, 58 { KE_END, 0 } 59}; 60 61static ktime_t last_pressed; 62static bool quirk_last_pressed; 63 64static const char *event_wmi_guid; 65 66static struct backlight_device *backlight; 67 68static int backlight_map[] = { 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF }; 69 70static struct input_dev *msi_wmi_input_dev; 71 72static int msi_wmi_query_block(int instance, int *ret) 73{ 74 acpi_status status; 75 union acpi_object *obj; 76 77 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 78 79 status = wmi_query_block(MSIWMI_BIOS_GUID, instance, &output); 80 81 obj = output.pointer; 82 83 if (!obj || obj->type != ACPI_TYPE_INTEGER) { 84 if (obj) { 85 pr_err("query block returned object " 86 "type: %d - buffer length:%d\n", obj->type, 87 obj->type == ACPI_TYPE_BUFFER ? 88 obj->buffer.length : 0); 89 } 90 kfree(obj); 91 return -EINVAL; 92 } 93 *ret = obj->integer.value; 94 kfree(obj); 95 return 0; 96} 97 98static int msi_wmi_set_block(int instance, int value) 99{ 100 acpi_status status; 101 102 struct acpi_buffer input = { sizeof(int), &value }; 103 104 pr_debug("Going to set block of instance: %d - value: %d\n", 105 instance, value); 106 107 status = wmi_set_block(MSIWMI_BIOS_GUID, instance, &input); 108 109 return ACPI_SUCCESS(status) ? 0 : 1; 110} 111 112static int bl_get(struct backlight_device *bd) 113{ 114 int level, err, ret; 115 116 /* Instance 1 is "get backlight", cmp with DSDT */ 117 err = msi_wmi_query_block(1, &ret); 118 if (err) { 119 pr_err("Could not query backlight: %d\n", err); 120 return -EINVAL; 121 } 122 pr_debug("Get: Query block returned: %d\n", ret); 123 for (level = 0; level < ARRAY_SIZE(backlight_map); level++) { 124 if (backlight_map[level] == ret) { 125 pr_debug("Current backlight level: 0x%X - index: %d\n", 126 backlight_map[level], level); 127 break; 128 } 129 } 130 if (level == ARRAY_SIZE(backlight_map)) { 131 pr_err("get: Invalid brightness value: 0x%X\n", ret); 132 return -EINVAL; 133 } 134 return level; 135} 136 137static int bl_set_status(struct backlight_device *bd) 138{ 139 int bright = bd->props.brightness; 140 if (bright >= ARRAY_SIZE(backlight_map) || bright < 0) 141 return -EINVAL; 142 143 /* Instance 0 is "set backlight" */ 144 return msi_wmi_set_block(0, backlight_map[bright]); 145} 146 147static const struct backlight_ops msi_backlight_ops = { 148 .get_brightness = bl_get, 149 .update_status = bl_set_status, 150}; 151 152static void msi_wmi_notify(u32 value, void *context) 153{ 154 struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; 155 static struct key_entry *key; 156 union acpi_object *obj; 157 acpi_status status; 158 159 status = wmi_get_event_data(value, &response); 160 if (status != AE_OK) { 161 pr_info("bad event status 0x%x\n", status); 162 return; 163 } 164 165 obj = (union acpi_object *)response.pointer; 166 167 if (obj && obj->type == ACPI_TYPE_INTEGER) { 168 int eventcode = obj->integer.value; 169 pr_debug("Eventcode: 0x%x\n", eventcode); 170 key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, 171 eventcode); 172 if (!key) { 173 pr_info("Unknown key pressed - %x\n", eventcode); 174 goto msi_wmi_notify_exit; 175 } 176 177 if (quirk_last_pressed) { 178 ktime_t cur = ktime_get_real(); 179 ktime_t diff = ktime_sub(cur, last_pressed); 180 /* Ignore event if any event happened in a 50 ms 181 timeframe -> Key press may result in 10-20 GPEs */ 182 if (ktime_to_us(diff) < 1000 * 50) { 183 pr_debug("Suppressed key event 0x%X - " 184 "Last press was %lld us ago\n", 185 key->code, ktime_to_us(diff)); 186 goto msi_wmi_notify_exit; 187 } 188 last_pressed = cur; 189 } 190 191 if (key->type == KE_KEY && 192 /* Brightness is served via acpi video driver */ 193 (backlight || 194 (key->code != MSI_KEY_BRIGHTNESSUP && 195 key->code != MSI_KEY_BRIGHTNESSDOWN))) { 196 pr_debug("Send key: 0x%X - Input layer keycode: %d\n", 197 key->code, key->keycode); 198 sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, 199 true); 200 } 201 } else 202 pr_info("Unknown event received\n"); 203 204msi_wmi_notify_exit: 205 kfree(response.pointer); 206} 207 208static int __init msi_wmi_backlight_setup(void) 209{ 210 int err; 211 struct backlight_properties props; 212 213 memset(&props, 0, sizeof(struct backlight_properties)); 214 props.type = BACKLIGHT_PLATFORM; 215 props.max_brightness = ARRAY_SIZE(backlight_map) - 1; 216 backlight = backlight_device_register(DRV_NAME, NULL, NULL, 217 &msi_backlight_ops, 218 &props); 219 if (IS_ERR(backlight)) 220 return PTR_ERR(backlight); 221 222 err = bl_get(NULL); 223 if (err < 0) { 224 backlight_device_unregister(backlight); 225 return err; 226 } 227 228 backlight->props.brightness = err; 229 230 return 0; 231} 232 233static int __init msi_wmi_input_setup(void) 234{ 235 int err; 236 237 msi_wmi_input_dev = input_allocate_device(); 238 if (!msi_wmi_input_dev) 239 return -ENOMEM; 240 241 msi_wmi_input_dev->name = "MSI WMI hotkeys"; 242 msi_wmi_input_dev->phys = "wmi/input0"; 243 msi_wmi_input_dev->id.bustype = BUS_HOST; 244 245 err = sparse_keymap_setup(msi_wmi_input_dev, msi_wmi_keymap, NULL); 246 if (err) 247 goto err_free_dev; 248 249 err = input_register_device(msi_wmi_input_dev); 250 251 if (err) 252 goto err_free_keymap; 253 254 last_pressed = ktime_set(0, 0); 255 256 return 0; 257 258err_free_keymap: 259 sparse_keymap_free(msi_wmi_input_dev); 260err_free_dev: 261 input_free_device(msi_wmi_input_dev); 262 return err; 263} 264 265static int __init msi_wmi_init(void) 266{ 267 int err; 268 269 if (wmi_has_guid(MSIWMI_EVENT_GUID)) { 270 err = msi_wmi_input_setup(); 271 if (err) { 272 pr_err("Unable to setup input device\n"); 273 return err; 274 } 275 276 err = wmi_install_notify_handler(MSIWMI_EVENT_GUID, 277 msi_wmi_notify, NULL); 278 if (ACPI_FAILURE(err)) { 279 pr_err("Unable to setup WMI notify handler\n"); 280 goto err_free_input; 281 } 282 283 pr_debug("Event handler installed\n"); 284 event_wmi_guid = MSIWMI_EVENT_GUID; 285 quirk_last_pressed = true; 286 } 287 288 if (wmi_has_guid(MSIWMI_BIOS_GUID) && !acpi_video_backlight_support()) { 289 err = msi_wmi_backlight_setup(); 290 if (err) { 291 pr_err("Unable to setup backlight device\n"); 292 goto err_uninstall_handler; 293 } 294 pr_debug("Backlight device created\n"); 295 } 296 297 if (!event_wmi_guid && !backlight) { 298 pr_err("This machine doesn't have neither MSI-hotkeys nor backlight through WMI\n"); 299 return -ENODEV; 300 } 301 302 return 0; 303 304err_uninstall_handler: 305 if (event_wmi_guid) 306 wmi_remove_notify_handler(event_wmi_guid); 307err_free_input: 308 if (event_wmi_guid) { 309 sparse_keymap_free(msi_wmi_input_dev); 310 input_unregister_device(msi_wmi_input_dev); 311 } 312 return err; 313} 314 315static void __exit msi_wmi_exit(void) 316{ 317 if (event_wmi_guid) { 318 wmi_remove_notify_handler(event_wmi_guid); 319 sparse_keymap_free(msi_wmi_input_dev); 320 input_unregister_device(msi_wmi_input_dev); 321 } 322 if (backlight) 323 backlight_device_unregister(backlight); 324} 325 326module_init(msi_wmi_init); 327module_exit(msi_wmi_exit); 328