diff options
Diffstat (limited to 'drivers/platform/x86/dell-wmi.c')
-rw-r--r-- | drivers/platform/x86/dell-wmi.c | 189 |
1 files changed, 148 insertions, 41 deletions
diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 0f900cc9fa7a..66f53c3c35e8 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c | |||
@@ -26,11 +26,13 @@ | |||
26 | #include <linux/kernel.h> | 26 | #include <linux/kernel.h> |
27 | #include <linux/module.h> | 27 | #include <linux/module.h> |
28 | #include <linux/init.h> | 28 | #include <linux/init.h> |
29 | #include <linux/slab.h> | ||
29 | #include <linux/types.h> | 30 | #include <linux/types.h> |
30 | #include <linux/input.h> | 31 | #include <linux/input.h> |
31 | #include <acpi/acpi_drivers.h> | 32 | #include <acpi/acpi_drivers.h> |
32 | #include <linux/acpi.h> | 33 | #include <linux/acpi.h> |
33 | #include <linux/string.h> | 34 | #include <linux/string.h> |
35 | #include <linux/dmi.h> | ||
34 | 36 | ||
35 | MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); | 37 | MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); |
36 | MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); | 38 | MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); |
@@ -38,6 +40,8 @@ MODULE_LICENSE("GPL"); | |||
38 | 40 | ||
39 | #define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" | 41 | #define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" |
40 | 42 | ||
43 | static int acpi_video; | ||
44 | |||
41 | MODULE_ALIAS("wmi:"DELL_EVENT_GUID); | 45 | MODULE_ALIAS("wmi:"DELL_EVENT_GUID); |
42 | 46 | ||
43 | struct key_entry { | 47 | struct key_entry { |
@@ -54,7 +58,7 @@ enum { KE_KEY, KE_SW, KE_IGNORE, KE_END }; | |||
54 | * via the keyboard controller so should not be sent again. | 58 | * via the keyboard controller so should not be sent again. |
55 | */ | 59 | */ |
56 | 60 | ||
57 | static struct key_entry dell_wmi_keymap[] = { | 61 | static struct key_entry dell_legacy_wmi_keymap[] = { |
58 | {KE_KEY, 0xe045, KEY_PROG1}, | 62 | {KE_KEY, 0xe045, KEY_PROG1}, |
59 | {KE_KEY, 0xe009, KEY_EJECTCD}, | 63 | {KE_KEY, 0xe009, KEY_EJECTCD}, |
60 | 64 | ||
@@ -72,7 +76,7 @@ static struct key_entry dell_wmi_keymap[] = { | |||
72 | 76 | ||
73 | /* The next device is at offset 6, the active devices are at | 77 | /* The next device is at offset 6, the active devices are at |
74 | offset 8 and the attached devices at offset 10 */ | 78 | offset 8 and the attached devices at offset 10 */ |
75 | {KE_KEY, 0xe00b, KEY_DISPLAYTOGGLE}, | 79 | {KE_KEY, 0xe00b, KEY_SWITCHVIDEOMODE}, |
76 | 80 | ||
77 | {KE_IGNORE, 0xe00c, KEY_KBDILLUMTOGGLE}, | 81 | {KE_IGNORE, 0xe00c, KEY_KBDILLUMTOGGLE}, |
78 | 82 | ||
@@ -96,9 +100,50 @@ static struct key_entry dell_wmi_keymap[] = { | |||
96 | {KE_END, 0} | 100 | {KE_END, 0} |
97 | }; | 101 | }; |
98 | 102 | ||
103 | static bool dell_new_hk_type; | ||
104 | |||
105 | struct dell_new_keymap_entry { | ||
106 | u16 scancode; | ||
107 | u16 keycode; | ||
108 | }; | ||
109 | |||
110 | struct dell_hotkey_table { | ||
111 | struct dmi_header header; | ||
112 | struct dell_new_keymap_entry keymap[]; | ||
113 | |||
114 | }; | ||
115 | |||
116 | static struct key_entry *dell_new_wmi_keymap; | ||
117 | |||
118 | static u16 bios_to_linux_keycode[256] = { | ||
119 | |||
120 | KEY_MEDIA, KEY_NEXTSONG, KEY_PLAYPAUSE, KEY_PREVIOUSSONG, | ||
121 | KEY_STOPCD, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, | ||
122 | KEY_WWW, KEY_UNKNOWN, KEY_VOLUMEDOWN, KEY_MUTE, | ||
123 | KEY_VOLUMEUP, KEY_UNKNOWN, KEY_BATTERY, KEY_EJECTCD, | ||
124 | KEY_UNKNOWN, KEY_SLEEP, KEY_PROG1, KEY_BRIGHTNESSDOWN, | ||
125 | KEY_BRIGHTNESSUP, KEY_UNKNOWN, KEY_KBDILLUMTOGGLE, | ||
126 | KEY_UNKNOWN, KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, | ||
127 | KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, KEY_PROG2, | ||
128 | KEY_UNKNOWN, 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, 0, 0, | ||
137 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
138 | KEY_PROG3 | ||
139 | }; | ||
140 | |||
141 | |||
142 | static struct key_entry *dell_wmi_keymap = dell_legacy_wmi_keymap; | ||
143 | |||
99 | static struct input_dev *dell_wmi_input_dev; | 144 | static struct input_dev *dell_wmi_input_dev; |
100 | 145 | ||
101 | static struct key_entry *dell_wmi_get_entry_by_scancode(int code) | 146 | static struct key_entry *dell_wmi_get_entry_by_scancode(unsigned int code) |
102 | { | 147 | { |
103 | struct key_entry *key; | 148 | struct key_entry *key; |
104 | 149 | ||
@@ -109,7 +154,7 @@ static struct key_entry *dell_wmi_get_entry_by_scancode(int code) | |||
109 | return NULL; | 154 | return NULL; |
110 | } | 155 | } |
111 | 156 | ||
112 | static struct key_entry *dell_wmi_get_entry_by_keycode(int keycode) | 157 | static struct key_entry *dell_wmi_get_entry_by_keycode(unsigned int keycode) |
113 | { | 158 | { |
114 | struct key_entry *key; | 159 | struct key_entry *key; |
115 | 160 | ||
@@ -120,8 +165,8 @@ static struct key_entry *dell_wmi_get_entry_by_keycode(int keycode) | |||
120 | return NULL; | 165 | return NULL; |
121 | } | 166 | } |
122 | 167 | ||
123 | static int dell_wmi_getkeycode(struct input_dev *dev, int scancode, | 168 | static int dell_wmi_getkeycode(struct input_dev *dev, |
124 | int *keycode) | 169 | unsigned int scancode, unsigned int *keycode) |
125 | { | 170 | { |
126 | struct key_entry *key = dell_wmi_get_entry_by_scancode(scancode); | 171 | struct key_entry *key = dell_wmi_get_entry_by_scancode(scancode); |
127 | 172 | ||
@@ -133,13 +178,11 @@ static int dell_wmi_getkeycode(struct input_dev *dev, int scancode, | |||
133 | return -EINVAL; | 178 | return -EINVAL; |
134 | } | 179 | } |
135 | 180 | ||
136 | static int dell_wmi_setkeycode(struct input_dev *dev, int scancode, int keycode) | 181 | static int dell_wmi_setkeycode(struct input_dev *dev, |
182 | unsigned int scancode, unsigned int keycode) | ||
137 | { | 183 | { |
138 | struct key_entry *key; | 184 | struct key_entry *key; |
139 | int old_keycode; | 185 | unsigned int old_keycode; |
140 | |||
141 | if (keycode < 0 || keycode > KEY_MAX) | ||
142 | return -EINVAL; | ||
143 | 186 | ||
144 | key = dell_wmi_get_entry_by_scancode(scancode); | 187 | key = dell_wmi_get_entry_by_scancode(scancode); |
145 | if (key && key->type == KE_KEY) { | 188 | if (key && key->type == KE_KEY) { |
@@ -158,30 +201,91 @@ static void dell_wmi_notify(u32 value, void *context) | |||
158 | struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; | 201 | struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; |
159 | static struct key_entry *key; | 202 | static struct key_entry *key; |
160 | union acpi_object *obj; | 203 | union acpi_object *obj; |
204 | acpi_status status; | ||
161 | 205 | ||
162 | wmi_get_event_data(value, &response); | 206 | status = wmi_get_event_data(value, &response); |
207 | if (status != AE_OK) { | ||
208 | printk(KERN_INFO "dell-wmi: bad event status 0x%x\n", status); | ||
209 | return; | ||
210 | } | ||
163 | 211 | ||
164 | obj = (union acpi_object *)response.pointer; | 212 | obj = (union acpi_object *)response.pointer; |
165 | 213 | ||
166 | if (obj && obj->type == ACPI_TYPE_BUFFER) { | 214 | if (obj && obj->type == ACPI_TYPE_BUFFER) { |
167 | int *buffer = (int *)obj->buffer.pointer; | 215 | int reported_key; |
168 | /* | 216 | u16 *buffer_entry = (u16 *)obj->buffer.pointer; |
169 | * The upper bytes of the event may contain | 217 | if (dell_new_hk_type && (buffer_entry[1] != 0x10)) { |
170 | * additional information, so mask them off for the | 218 | printk(KERN_INFO "dell-wmi: Received unknown WMI event" |
171 | * scancode lookup | 219 | " (0x%x)\n", buffer_entry[1]); |
172 | */ | 220 | kfree(obj); |
173 | key = dell_wmi_get_entry_by_scancode(buffer[1] & 0xFFFF); | 221 | return; |
174 | if (key) { | 222 | } |
223 | |||
224 | if (dell_new_hk_type) | ||
225 | reported_key = (int)buffer_entry[2]; | ||
226 | else | ||
227 | reported_key = (int)buffer_entry[1] & 0xffff; | ||
228 | |||
229 | key = dell_wmi_get_entry_by_scancode(reported_key); | ||
230 | |||
231 | if (!key) { | ||
232 | printk(KERN_INFO "dell-wmi: Unknown key %x pressed\n", | ||
233 | reported_key); | ||
234 | } else if ((key->keycode == KEY_BRIGHTNESSUP || | ||
235 | key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) { | ||
236 | /* Don't report brightness notifications that will also | ||
237 | * come via ACPI */ | ||
238 | ; | ||
239 | } else { | ||
175 | input_report_key(dell_wmi_input_dev, key->keycode, 1); | 240 | input_report_key(dell_wmi_input_dev, key->keycode, 1); |
176 | input_sync(dell_wmi_input_dev); | 241 | input_sync(dell_wmi_input_dev); |
177 | input_report_key(dell_wmi_input_dev, key->keycode, 0); | 242 | input_report_key(dell_wmi_input_dev, key->keycode, 0); |
178 | input_sync(dell_wmi_input_dev); | 243 | input_sync(dell_wmi_input_dev); |
179 | } else if (buffer[1] & 0xFFFF) | 244 | } |
180 | printk(KERN_INFO "dell-wmi: Unknown key %x pressed\n", | ||
181 | buffer[1] & 0xFFFF); | ||
182 | } | 245 | } |
246 | kfree(obj); | ||
183 | } | 247 | } |
184 | 248 | ||
249 | |||
250 | static void setup_new_hk_map(const struct dmi_header *dm) | ||
251 | { | ||
252 | |||
253 | int i; | ||
254 | int hotkey_num = (dm->length-4)/sizeof(struct dell_new_keymap_entry); | ||
255 | struct dell_hotkey_table *table = | ||
256 | container_of(dm, struct dell_hotkey_table, header); | ||
257 | |||
258 | dell_new_wmi_keymap = kzalloc((hotkey_num+1) * | ||
259 | sizeof(struct key_entry), GFP_KERNEL); | ||
260 | |||
261 | for (i = 0; i < hotkey_num; i++) { | ||
262 | dell_new_wmi_keymap[i].type = KE_KEY; | ||
263 | dell_new_wmi_keymap[i].code = table->keymap[i].scancode; | ||
264 | dell_new_wmi_keymap[i].keycode = | ||
265 | (table->keymap[i].keycode > 255) ? 0 : | ||
266 | bios_to_linux_keycode[table->keymap[i].keycode]; | ||
267 | } | ||
268 | |||
269 | dell_new_wmi_keymap[i].type = KE_END; | ||
270 | dell_new_wmi_keymap[i].code = 0; | ||
271 | dell_new_wmi_keymap[i].keycode = 0; | ||
272 | |||
273 | dell_wmi_keymap = dell_new_wmi_keymap; | ||
274 | |||
275 | } | ||
276 | |||
277 | |||
278 | static void find_hk_type(const struct dmi_header *dm, void *dummy) | ||
279 | { | ||
280 | |||
281 | if ((dm->type == 0xb2) && (dm->length > 6)) { | ||
282 | dell_new_hk_type = true; | ||
283 | setup_new_hk_map(dm); | ||
284 | } | ||
285 | |||
286 | } | ||
287 | |||
288 | |||
185 | static int __init dell_wmi_input_setup(void) | 289 | static int __init dell_wmi_input_setup(void) |
186 | { | 290 | { |
187 | struct key_entry *key; | 291 | struct key_entry *key; |
@@ -224,34 +328,37 @@ static int __init dell_wmi_input_setup(void) | |||
224 | static int __init dell_wmi_init(void) | 328 | static int __init dell_wmi_init(void) |
225 | { | 329 | { |
226 | int err; | 330 | int err; |
331 | acpi_status status; | ||
227 | 332 | ||
228 | if (wmi_has_guid(DELL_EVENT_GUID)) { | 333 | if (!wmi_has_guid(DELL_EVENT_GUID)) { |
229 | err = dell_wmi_input_setup(); | 334 | printk(KERN_WARNING "dell-wmi: No known WMI GUID found\n"); |
335 | return -ENODEV; | ||
336 | } | ||
230 | 337 | ||
231 | if (err) | 338 | dmi_walk(find_hk_type, NULL); |
232 | return err; | 339 | acpi_video = acpi_video_backlight_support(); |
233 | 340 | ||
234 | err = wmi_install_notify_handler(DELL_EVENT_GUID, | 341 | err = dell_wmi_input_setup(); |
235 | dell_wmi_notify, NULL); | 342 | if (err) |
236 | if (err) { | 343 | return err; |
237 | input_unregister_device(dell_wmi_input_dev); | ||
238 | printk(KERN_ERR "dell-wmi: Unable to register" | ||
239 | " notify handler - %d\n", err); | ||
240 | return err; | ||
241 | } | ||
242 | 344 | ||
243 | } else | 345 | status = wmi_install_notify_handler(DELL_EVENT_GUID, |
244 | printk(KERN_WARNING "dell-wmi: No known WMI GUID found\n"); | 346 | dell_wmi_notify, NULL); |
347 | if (ACPI_FAILURE(status)) { | ||
348 | input_unregister_device(dell_wmi_input_dev); | ||
349 | printk(KERN_ERR | ||
350 | "dell-wmi: Unable to register notify handler - %d\n", | ||
351 | status); | ||
352 | return -ENODEV; | ||
353 | } | ||
245 | 354 | ||
246 | return 0; | 355 | return 0; |
247 | } | 356 | } |
248 | 357 | ||
249 | static void __exit dell_wmi_exit(void) | 358 | static void __exit dell_wmi_exit(void) |
250 | { | 359 | { |
251 | if (wmi_has_guid(DELL_EVENT_GUID)) { | 360 | wmi_remove_notify_handler(DELL_EVENT_GUID); |
252 | wmi_remove_notify_handler(DELL_EVENT_GUID); | 361 | input_unregister_device(dell_wmi_input_dev); |
253 | input_unregister_device(dell_wmi_input_dev); | ||
254 | } | ||
255 | } | 362 | } |
256 | 363 | ||
257 | module_init(dell_wmi_init); | 364 | module_init(dell_wmi_init); |