diff options
| -rw-r--r-- | drivers/platform/x86/dell-wmi.c | 129 |
1 files changed, 116 insertions, 13 deletions
diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 0f900cc9fa7a..67f3fe71c509 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c | |||
| @@ -31,6 +31,7 @@ | |||
| 31 | #include <acpi/acpi_drivers.h> | 31 | #include <acpi/acpi_drivers.h> |
| 32 | #include <linux/acpi.h> | 32 | #include <linux/acpi.h> |
| 33 | #include <linux/string.h> | 33 | #include <linux/string.h> |
| 34 | #include <linux/dmi.h> | ||
| 34 | 35 | ||
| 35 | MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); | 36 | MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); |
| 36 | MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); | 37 | MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); |
| @@ -38,6 +39,8 @@ MODULE_LICENSE("GPL"); | |||
| 38 | 39 | ||
| 39 | #define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" | 40 | #define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" |
| 40 | 41 | ||
| 42 | static int acpi_video; | ||
| 43 | |||
| 41 | MODULE_ALIAS("wmi:"DELL_EVENT_GUID); | 44 | MODULE_ALIAS("wmi:"DELL_EVENT_GUID); |
| 42 | 45 | ||
| 43 | struct key_entry { | 46 | struct key_entry { |
| @@ -54,7 +57,7 @@ enum { KE_KEY, KE_SW, KE_IGNORE, KE_END }; | |||
| 54 | * via the keyboard controller so should not be sent again. | 57 | * via the keyboard controller so should not be sent again. |
| 55 | */ | 58 | */ |
| 56 | 59 | ||
| 57 | static struct key_entry dell_wmi_keymap[] = { | 60 | static struct key_entry dell_legacy_wmi_keymap[] = { |
| 58 | {KE_KEY, 0xe045, KEY_PROG1}, | 61 | {KE_KEY, 0xe045, KEY_PROG1}, |
| 59 | {KE_KEY, 0xe009, KEY_EJECTCD}, | 62 | {KE_KEY, 0xe009, KEY_EJECTCD}, |
| 60 | 63 | ||
| @@ -72,7 +75,7 @@ static struct key_entry dell_wmi_keymap[] = { | |||
| 72 | 75 | ||
| 73 | /* The next device is at offset 6, the active devices are at | 76 | /* The next device is at offset 6, the active devices are at |
| 74 | offset 8 and the attached devices at offset 10 */ | 77 | offset 8 and the attached devices at offset 10 */ |
| 75 | {KE_KEY, 0xe00b, KEY_DISPLAYTOGGLE}, | 78 | {KE_KEY, 0xe00b, KEY_SWITCHVIDEOMODE}, |
| 76 | 79 | ||
| 77 | {KE_IGNORE, 0xe00c, KEY_KBDILLUMTOGGLE}, | 80 | {KE_IGNORE, 0xe00c, KEY_KBDILLUMTOGGLE}, |
| 78 | 81 | ||
| @@ -96,6 +99,47 @@ static struct key_entry dell_wmi_keymap[] = { | |||
| 96 | {KE_END, 0} | 99 | {KE_END, 0} |
| 97 | }; | 100 | }; |
| 98 | 101 | ||
| 102 | static bool dell_new_hk_type; | ||
| 103 | |||
| 104 | struct dell_new_keymap_entry { | ||
| 105 | u16 scancode; | ||
| 106 | u16 keycode; | ||
| 107 | }; | ||
| 108 | |||
| 109 | struct dell_hotkey_table { | ||
| 110 | struct dmi_header header; | ||
| 111 | struct dell_new_keymap_entry keymap[]; | ||
| 112 | |||
| 113 | }; | ||
| 114 | |||
| 115 | static struct key_entry *dell_new_wmi_keymap; | ||
| 116 | |||
| 117 | static u16 bios_to_linux_keycode[256] = { | ||
| 118 | |||
| 119 | KEY_MEDIA, KEY_NEXTSONG, KEY_PLAYPAUSE, KEY_PREVIOUSSONG, | ||
| 120 | KEY_STOPCD, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, | ||
| 121 | KEY_WWW, KEY_UNKNOWN, KEY_VOLUMEDOWN, KEY_MUTE, | ||
| 122 | KEY_VOLUMEUP, KEY_UNKNOWN, KEY_BATTERY, KEY_EJECTCD, | ||
| 123 | KEY_UNKNOWN, KEY_SLEEP, KEY_PROG1, KEY_BRIGHTNESSDOWN, | ||
| 124 | KEY_BRIGHTNESSUP, KEY_UNKNOWN, KEY_KBDILLUMTOGGLE, | ||
| 125 | KEY_UNKNOWN, KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, | ||
| 126 | KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, KEY_PROG2, | ||
| 127 | KEY_UNKNOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 128 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 129 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 130 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 131 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 132 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 133 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 134 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 135 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 136 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 137 | KEY_PROG3 | ||
| 138 | }; | ||
| 139 | |||
| 140 | |||
| 141 | static struct key_entry *dell_wmi_keymap = dell_legacy_wmi_keymap; | ||
| 142 | |||
| 99 | static struct input_dev *dell_wmi_input_dev; | 143 | static struct input_dev *dell_wmi_input_dev; |
| 100 | 144 | ||
| 101 | static struct key_entry *dell_wmi_get_entry_by_scancode(int code) | 145 | static struct key_entry *dell_wmi_get_entry_by_scancode(int code) |
| @@ -164,24 +208,78 @@ static void dell_wmi_notify(u32 value, void *context) | |||
| 164 | obj = (union acpi_object *)response.pointer; | 208 | obj = (union acpi_object *)response.pointer; |
| 165 | 209 | ||
| 166 | if (obj && obj->type == ACPI_TYPE_BUFFER) { | 210 | if (obj && obj->type == ACPI_TYPE_BUFFER) { |
| 167 | int *buffer = (int *)obj->buffer.pointer; | 211 | int reported_key; |
| 168 | /* | 212 | u16 *buffer_entry = (u16 *)obj->buffer.pointer; |
| 169 | * The upper bytes of the event may contain | 213 | if (dell_new_hk_type && (buffer_entry[1] != 0x10)) { |
| 170 | * additional information, so mask them off for the | 214 | printk(KERN_INFO "dell-wmi: Received unknown WMI event" |
| 171 | * scancode lookup | 215 | " (0x%x)\n", buffer_entry[1]); |
| 172 | */ | 216 | return; |
| 173 | key = dell_wmi_get_entry_by_scancode(buffer[1] & 0xFFFF); | 217 | } |
| 174 | if (key) { | 218 | |
| 219 | if (dell_new_hk_type) | ||
| 220 | reported_key = (int)buffer_entry[2]; | ||
| 221 | else | ||
| 222 | reported_key = (int)buffer_entry[1] & 0xffff; | ||
| 223 | |||
| 224 | key = dell_wmi_get_entry_by_scancode(reported_key); | ||
| 225 | |||
| 226 | if (!key) { | ||
| 227 | printk(KERN_INFO "dell-wmi: Unknown key %x pressed\n", | ||
| 228 | reported_key); | ||
| 229 | } else if ((key->keycode == KEY_BRIGHTNESSUP || | ||
| 230 | key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) { | ||
| 231 | /* Don't report brightness notifications that will also | ||
| 232 | * come via ACPI */ | ||
| 233 | return; | ||
| 234 | } else { | ||
| 175 | input_report_key(dell_wmi_input_dev, key->keycode, 1); | 235 | input_report_key(dell_wmi_input_dev, key->keycode, 1); |
| 176 | input_sync(dell_wmi_input_dev); | 236 | input_sync(dell_wmi_input_dev); |
| 177 | input_report_key(dell_wmi_input_dev, key->keycode, 0); | 237 | input_report_key(dell_wmi_input_dev, key->keycode, 0); |
| 178 | input_sync(dell_wmi_input_dev); | 238 | input_sync(dell_wmi_input_dev); |
| 179 | } else if (buffer[1] & 0xFFFF) | 239 | } |
| 180 | printk(KERN_INFO "dell-wmi: Unknown key %x pressed\n", | ||
| 181 | buffer[1] & 0xFFFF); | ||
| 182 | } | 240 | } |
| 183 | } | 241 | } |
| 184 | 242 | ||
| 243 | |||
| 244 | static void setup_new_hk_map(const struct dmi_header *dm) | ||
| 245 | { | ||
| 246 | |||
| 247 | int i; | ||
| 248 | int hotkey_num = (dm->length-4)/sizeof(struct dell_new_keymap_entry); | ||
| 249 | struct dell_hotkey_table *table = | ||
| 250 | container_of(dm, struct dell_hotkey_table, header); | ||
| 251 | |||
| 252 | dell_new_wmi_keymap = kzalloc((hotkey_num+1) * | ||
| 253 | sizeof(struct key_entry), GFP_KERNEL); | ||
| 254 | |||
| 255 | for (i = 0; i < hotkey_num; i++) { | ||
| 256 | dell_new_wmi_keymap[i].type = KE_KEY; | ||
| 257 | dell_new_wmi_keymap[i].code = table->keymap[i].scancode; | ||
| 258 | dell_new_wmi_keymap[i].keycode = | ||
| 259 | (table->keymap[i].keycode > 255) ? 0 : | ||
| 260 | bios_to_linux_keycode[table->keymap[i].keycode]; | ||
| 261 | } | ||
| 262 | |||
| 263 | dell_new_wmi_keymap[i].type = KE_END; | ||
| 264 | dell_new_wmi_keymap[i].code = 0; | ||
| 265 | dell_new_wmi_keymap[i].keycode = 0; | ||
| 266 | |||
| 267 | dell_wmi_keymap = dell_new_wmi_keymap; | ||
| 268 | |||
| 269 | } | ||
| 270 | |||
| 271 | |||
| 272 | static void find_hk_type(const struct dmi_header *dm, void *dummy) | ||
| 273 | { | ||
| 274 | |||
| 275 | if ((dm->type == 0xb2) && (dm->length > 6)) { | ||
| 276 | dell_new_hk_type = true; | ||
| 277 | setup_new_hk_map(dm); | ||
| 278 | } | ||
| 279 | |||
| 280 | } | ||
| 281 | |||
| 282 | |||
| 185 | static int __init dell_wmi_input_setup(void) | 283 | static int __init dell_wmi_input_setup(void) |
| 186 | { | 284 | { |
| 187 | struct key_entry *key; | 285 | struct key_entry *key; |
| @@ -226,6 +324,9 @@ static int __init dell_wmi_init(void) | |||
| 226 | int err; | 324 | int err; |
| 227 | 325 | ||
| 228 | if (wmi_has_guid(DELL_EVENT_GUID)) { | 326 | if (wmi_has_guid(DELL_EVENT_GUID)) { |
| 327 | |||
| 328 | dmi_walk(find_hk_type, NULL); | ||
| 329 | |||
| 229 | err = dell_wmi_input_setup(); | 330 | err = dell_wmi_input_setup(); |
| 230 | 331 | ||
| 231 | if (err) | 332 | if (err) |
| @@ -240,6 +341,8 @@ static int __init dell_wmi_init(void) | |||
| 240 | return err; | 341 | return err; |
| 241 | } | 342 | } |
| 242 | 343 | ||
| 344 | acpi_video = acpi_video_backlight_support(); | ||
| 345 | |||
| 243 | } else | 346 | } else |
| 244 | printk(KERN_WARNING "dell-wmi: No known WMI GUID found\n"); | 347 | printk(KERN_WARNING "dell-wmi: No known WMI GUID found\n"); |
| 245 | 348 | ||
