1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Huawei WMI laptop extras driver |
4 | * |
5 | * Copyright (C) 2018 Ayman Bagabas <ayman.bagabas@gmail.com> |
6 | */ |
7 | |
8 | #include <linux/acpi.h> |
9 | #include <linux/debugfs.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/dmi.h> |
12 | #include <linux/input.h> |
13 | #include <linux/input/sparse-keymap.h> |
14 | #include <linux/leds.h> |
15 | #include <linux/module.h> |
16 | #include <linux/mutex.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/power_supply.h> |
19 | #include <linux/sysfs.h> |
20 | #include <linux/wmi.h> |
21 | #include <acpi/battery.h> |
22 | |
23 | /* |
24 | * Huawei WMI GUIDs |
25 | */ |
26 | #define HWMI_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C90629100000" |
27 | #define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000" |
28 | |
29 | /* Legacy GUIDs */ |
30 | #define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100" |
31 | #define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" |
32 | |
33 | /* HWMI commands */ |
34 | |
35 | enum { |
36 | BATTERY_THRESH_GET = 0x00001103, /* \GBTT */ |
37 | BATTERY_THRESH_SET = 0x00001003, /* \SBTT */ |
38 | FN_LOCK_GET = 0x00000604, /* \GFRS */ |
39 | FN_LOCK_SET = 0x00000704, /* \SFRS */ |
40 | MICMUTE_LED_SET = 0x00000b04, /* \SMLS */ |
41 | }; |
42 | |
43 | union hwmi_arg { |
44 | u64 cmd; |
45 | u8 args[8]; |
46 | }; |
47 | |
48 | struct quirk_entry { |
49 | bool battery_reset; |
50 | bool ec_micmute; |
51 | bool report_brightness; |
52 | }; |
53 | |
54 | static struct quirk_entry *quirks; |
55 | |
56 | struct huawei_wmi_debug { |
57 | struct dentry *root; |
58 | u64 arg; |
59 | }; |
60 | |
61 | struct huawei_wmi { |
62 | bool battery_available; |
63 | bool fn_lock_available; |
64 | |
65 | struct huawei_wmi_debug debug; |
66 | struct led_classdev cdev; |
67 | struct device *dev; |
68 | |
69 | struct mutex wmi_lock; |
70 | }; |
71 | |
72 | static struct huawei_wmi *huawei_wmi; |
73 | |
74 | static const struct key_entry huawei_wmi_keymap[] = { |
75 | { KE_KEY, 0x281, { KEY_BRIGHTNESSDOWN } }, |
76 | { KE_KEY, 0x282, { KEY_BRIGHTNESSUP } }, |
77 | { KE_KEY, 0x284, { KEY_MUTE } }, |
78 | { KE_KEY, 0x285, { KEY_VOLUMEDOWN } }, |
79 | { KE_KEY, 0x286, { KEY_VOLUMEUP } }, |
80 | { KE_KEY, 0x287, { KEY_MICMUTE } }, |
81 | { KE_KEY, 0x289, { KEY_WLAN } }, |
82 | // Huawei |M| key |
83 | { KE_KEY, 0x28a, { KEY_CONFIG } }, |
84 | // Keyboard backlit |
85 | { KE_IGNORE, 0x293, { KEY_KBDILLUMTOGGLE } }, |
86 | { KE_IGNORE, 0x294, { KEY_KBDILLUMUP } }, |
87 | { KE_IGNORE, 0x295, { KEY_KBDILLUMUP } }, |
88 | // Ignore Ambient Light Sensoring |
89 | { KE_KEY, 0x2c1, { KEY_RESERVED } }, |
90 | { KE_END, 0 } |
91 | }; |
92 | |
93 | static int battery_reset = -1; |
94 | static int report_brightness = -1; |
95 | |
96 | module_param(battery_reset, bint, 0444); |
97 | MODULE_PARM_DESC(battery_reset, |
98 | "Reset battery charge values to (0-0) before disabling it using (0-100)" ); |
99 | module_param(report_brightness, bint, 0444); |
100 | MODULE_PARM_DESC(report_brightness, |
101 | "Report brightness keys." ); |
102 | |
103 | /* Quirks */ |
104 | |
105 | static int __init dmi_matched(const struct dmi_system_id *dmi) |
106 | { |
107 | quirks = dmi->driver_data; |
108 | return 1; |
109 | } |
110 | |
111 | static struct quirk_entry quirk_unknown = { |
112 | }; |
113 | |
114 | static struct quirk_entry quirk_battery_reset = { |
115 | .battery_reset = true, |
116 | }; |
117 | |
118 | static struct quirk_entry quirk_matebook_x = { |
119 | .ec_micmute = true, |
120 | .report_brightness = true, |
121 | }; |
122 | |
123 | static const struct dmi_system_id huawei_quirks[] = { |
124 | { |
125 | .callback = dmi_matched, |
126 | .ident = "Huawei MACH-WX9" , |
127 | .matches = { |
128 | DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI" ), |
129 | DMI_MATCH(DMI_PRODUCT_NAME, "MACH-WX9" ), |
130 | }, |
131 | .driver_data = &quirk_battery_reset |
132 | }, |
133 | { |
134 | .callback = dmi_matched, |
135 | .ident = "Huawei MateBook X" , |
136 | .matches = { |
137 | DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI" ), |
138 | DMI_MATCH(DMI_PRODUCT_NAME, "HUAWEI MateBook X" ) |
139 | }, |
140 | .driver_data = &quirk_matebook_x |
141 | }, |
142 | { } |
143 | }; |
144 | |
145 | /* Utils */ |
146 | |
147 | static int huawei_wmi_call(struct huawei_wmi *huawei, |
148 | struct acpi_buffer *in, struct acpi_buffer *out) |
149 | { |
150 | acpi_status status; |
151 | |
152 | mutex_lock(&huawei->wmi_lock); |
153 | status = wmi_evaluate_method(HWMI_METHOD_GUID, instance: 0, method_id: 1, in, out); |
154 | mutex_unlock(lock: &huawei->wmi_lock); |
155 | if (ACPI_FAILURE(status)) { |
156 | dev_err(huawei->dev, "Failed to evaluate wmi method\n" ); |
157 | return -ENODEV; |
158 | } |
159 | |
160 | return 0; |
161 | } |
162 | |
163 | /* HWMI takes a 64 bit input and returns either a package with 2 buffers, one of |
164 | * 4 bytes and the other of 256 bytes, or one buffer of size 0x104 (260) bytes. |
165 | * The first 4 bytes are ignored, we ignore the first 4 bytes buffer if we got a |
166 | * package, or skip the first 4 if a buffer of 0x104 is used. The first byte of |
167 | * the remaining 0x100 sized buffer has the return status of every call. In case |
168 | * the return status is non-zero, we return -ENODEV but still copy the returned |
169 | * buffer to the given buffer parameter (buf). |
170 | */ |
171 | static int huawei_wmi_cmd(u64 arg, u8 *buf, size_t buflen) |
172 | { |
173 | struct huawei_wmi *huawei = huawei_wmi; |
174 | struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; |
175 | struct acpi_buffer in; |
176 | union acpi_object *obj; |
177 | size_t len; |
178 | int err, i; |
179 | |
180 | in.length = sizeof(arg); |
181 | in.pointer = &arg; |
182 | |
183 | /* Some models require calling HWMI twice to execute a command. We evaluate |
184 | * HWMI and if we get a non-zero return status we evaluate it again. |
185 | */ |
186 | for (i = 0; i < 2; i++) { |
187 | err = huawei_wmi_call(huawei, in: &in, out: &out); |
188 | if (err) |
189 | goto fail_cmd; |
190 | |
191 | obj = out.pointer; |
192 | if (!obj) { |
193 | err = -EIO; |
194 | goto fail_cmd; |
195 | } |
196 | |
197 | switch (obj->type) { |
198 | /* Models that implement both "legacy" and HWMI tend to return a 0x104 |
199 | * sized buffer instead of a package of 0x4 and 0x100 buffers. |
200 | */ |
201 | case ACPI_TYPE_BUFFER: |
202 | if (obj->buffer.length == 0x104) { |
203 | // Skip the first 4 bytes. |
204 | obj->buffer.pointer += 4; |
205 | len = 0x100; |
206 | } else { |
207 | dev_err(huawei->dev, "Bad buffer length, got %d\n" , obj->buffer.length); |
208 | err = -EIO; |
209 | goto fail_cmd; |
210 | } |
211 | |
212 | break; |
213 | /* HWMI returns a package with 2 buffer elements, one of 4 bytes and the |
214 | * other is 256 bytes. |
215 | */ |
216 | case ACPI_TYPE_PACKAGE: |
217 | if (obj->package.count != 2) { |
218 | dev_err(huawei->dev, "Bad package count, got %d\n" , obj->package.count); |
219 | err = -EIO; |
220 | goto fail_cmd; |
221 | } |
222 | |
223 | obj = &obj->package.elements[1]; |
224 | if (obj->type != ACPI_TYPE_BUFFER) { |
225 | dev_err(huawei->dev, "Bad package element type, got %d\n" , obj->type); |
226 | err = -EIO; |
227 | goto fail_cmd; |
228 | } |
229 | len = obj->buffer.length; |
230 | |
231 | break; |
232 | /* Shouldn't get here! */ |
233 | default: |
234 | dev_err(huawei->dev, "Unexpected obj type, got: %d\n" , obj->type); |
235 | err = -EIO; |
236 | goto fail_cmd; |
237 | } |
238 | |
239 | if (!*obj->buffer.pointer) |
240 | break; |
241 | } |
242 | |
243 | err = (*obj->buffer.pointer) ? -ENODEV : 0; |
244 | |
245 | if (buf) { |
246 | len = min(buflen, len); |
247 | memcpy(buf, obj->buffer.pointer, len); |
248 | } |
249 | |
250 | fail_cmd: |
251 | kfree(objp: out.pointer); |
252 | return err; |
253 | } |
254 | |
255 | /* LEDs */ |
256 | |
257 | static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, |
258 | enum led_brightness brightness) |
259 | { |
260 | /* This is a workaround until the "legacy" interface is implemented. */ |
261 | if (quirks && quirks->ec_micmute) { |
262 | char *acpi_method; |
263 | acpi_handle handle; |
264 | acpi_status status; |
265 | union acpi_object args[3]; |
266 | struct acpi_object_list arg_list = { |
267 | .pointer = args, |
268 | .count = ARRAY_SIZE(args), |
269 | }; |
270 | |
271 | handle = ec_get_handle(); |
272 | if (!handle) |
273 | return -ENODEV; |
274 | |
275 | args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER; |
276 | args[1].integer.value = 0x04; |
277 | |
278 | if (acpi_has_method(handle, name: "SPIN" )) { |
279 | acpi_method = "SPIN" ; |
280 | args[0].integer.value = 0; |
281 | args[2].integer.value = brightness ? 1 : 0; |
282 | } else if (acpi_has_method(handle, name: "WPIN" )) { |
283 | acpi_method = "WPIN" ; |
284 | args[0].integer.value = 1; |
285 | args[2].integer.value = brightness ? 0 : 1; |
286 | } else { |
287 | return -ENODEV; |
288 | } |
289 | |
290 | status = acpi_evaluate_object(object: handle, pathname: acpi_method, parameter_objects: &arg_list, NULL); |
291 | if (ACPI_FAILURE(status)) |
292 | return -ENODEV; |
293 | |
294 | return 0; |
295 | } else { |
296 | union hwmi_arg arg; |
297 | |
298 | arg.cmd = MICMUTE_LED_SET; |
299 | arg.args[2] = brightness; |
300 | |
301 | return huawei_wmi_cmd(arg: arg.cmd, NULL, buflen: 0); |
302 | } |
303 | } |
304 | |
305 | static void huawei_wmi_leds_setup(struct device *dev) |
306 | { |
307 | struct huawei_wmi *huawei = dev_get_drvdata(dev); |
308 | |
309 | huawei->cdev.name = "platform::micmute" ; |
310 | huawei->cdev.max_brightness = 1; |
311 | huawei->cdev.brightness_set_blocking = &huawei_wmi_micmute_led_set; |
312 | huawei->cdev.default_trigger = "audio-micmute" ; |
313 | huawei->cdev.brightness = ledtrig_audio_get(type: LED_AUDIO_MICMUTE); |
314 | huawei->cdev.dev = dev; |
315 | huawei->cdev.flags = LED_CORE_SUSPENDRESUME; |
316 | |
317 | devm_led_classdev_register(parent: dev, led_cdev: &huawei->cdev); |
318 | } |
319 | |
320 | /* Battery protection */ |
321 | |
322 | static int huawei_wmi_battery_get(int *start, int *end) |
323 | { |
324 | u8 ret[0x100]; |
325 | int err, i; |
326 | |
327 | err = huawei_wmi_cmd(arg: BATTERY_THRESH_GET, buf: ret, buflen: sizeof(ret)); |
328 | if (err) |
329 | return err; |
330 | |
331 | /* Find the last two non-zero values. Return status is ignored. */ |
332 | i = ARRAY_SIZE(ret) - 1; |
333 | do { |
334 | if (start) |
335 | *start = ret[i-1]; |
336 | if (end) |
337 | *end = ret[i]; |
338 | } while (i > 2 && !ret[i--]); |
339 | |
340 | return 0; |
341 | } |
342 | |
343 | static int huawei_wmi_battery_set(int start, int end) |
344 | { |
345 | union hwmi_arg arg; |
346 | int err; |
347 | |
348 | if (start < 0 || end < 0 || start > 100 || end > 100) |
349 | return -EINVAL; |
350 | |
351 | arg.cmd = BATTERY_THRESH_SET; |
352 | arg.args[2] = start; |
353 | arg.args[3] = end; |
354 | |
355 | /* This is an edge case were some models turn battery protection |
356 | * off without changing their thresholds values. We clear the |
357 | * values before turning off protection. Sometimes we need a sleep delay to |
358 | * make sure these values make their way to EC memory. |
359 | */ |
360 | if (quirks && quirks->battery_reset && start == 0 && end == 100) { |
361 | err = huawei_wmi_battery_set(start: 0, end: 0); |
362 | if (err) |
363 | return err; |
364 | |
365 | msleep(msecs: 1000); |
366 | } |
367 | |
368 | err = huawei_wmi_cmd(arg: arg.cmd, NULL, buflen: 0); |
369 | |
370 | return err; |
371 | } |
372 | |
373 | static ssize_t charge_control_start_threshold_show(struct device *dev, |
374 | struct device_attribute *attr, |
375 | char *buf) |
376 | { |
377 | int err, start; |
378 | |
379 | err = huawei_wmi_battery_get(start: &start, NULL); |
380 | if (err) |
381 | return err; |
382 | |
383 | return sprintf(buf, fmt: "%d\n" , start); |
384 | } |
385 | |
386 | static ssize_t charge_control_end_threshold_show(struct device *dev, |
387 | struct device_attribute *attr, |
388 | char *buf) |
389 | { |
390 | int err, end; |
391 | |
392 | err = huawei_wmi_battery_get(NULL, end: &end); |
393 | if (err) |
394 | return err; |
395 | |
396 | return sprintf(buf, fmt: "%d\n" , end); |
397 | } |
398 | |
399 | static ssize_t charge_control_thresholds_show(struct device *dev, |
400 | struct device_attribute *attr, |
401 | char *buf) |
402 | { |
403 | int err, start, end; |
404 | |
405 | err = huawei_wmi_battery_get(start: &start, end: &end); |
406 | if (err) |
407 | return err; |
408 | |
409 | return sprintf(buf, fmt: "%d %d\n" , start, end); |
410 | } |
411 | |
412 | static ssize_t charge_control_start_threshold_store(struct device *dev, |
413 | struct device_attribute *attr, |
414 | const char *buf, size_t size) |
415 | { |
416 | int err, start, end; |
417 | |
418 | err = huawei_wmi_battery_get(NULL, end: &end); |
419 | if (err) |
420 | return err; |
421 | |
422 | if (sscanf(buf, "%d" , &start) != 1) |
423 | return -EINVAL; |
424 | |
425 | err = huawei_wmi_battery_set(start, end); |
426 | if (err) |
427 | return err; |
428 | |
429 | return size; |
430 | } |
431 | |
432 | static ssize_t charge_control_end_threshold_store(struct device *dev, |
433 | struct device_attribute *attr, |
434 | const char *buf, size_t size) |
435 | { |
436 | int err, start, end; |
437 | |
438 | err = huawei_wmi_battery_get(start: &start, NULL); |
439 | if (err) |
440 | return err; |
441 | |
442 | if (sscanf(buf, "%d" , &end) != 1) |
443 | return -EINVAL; |
444 | |
445 | err = huawei_wmi_battery_set(start, end); |
446 | if (err) |
447 | return err; |
448 | |
449 | return size; |
450 | } |
451 | |
452 | static ssize_t charge_control_thresholds_store(struct device *dev, |
453 | struct device_attribute *attr, |
454 | const char *buf, size_t size) |
455 | { |
456 | int err, start, end; |
457 | |
458 | if (sscanf(buf, "%d %d" , &start, &end) != 2) |
459 | return -EINVAL; |
460 | |
461 | err = huawei_wmi_battery_set(start, end); |
462 | if (err) |
463 | return err; |
464 | |
465 | return size; |
466 | } |
467 | |
468 | static DEVICE_ATTR_RW(charge_control_start_threshold); |
469 | static DEVICE_ATTR_RW(charge_control_end_threshold); |
470 | static DEVICE_ATTR_RW(charge_control_thresholds); |
471 | |
472 | static int huawei_wmi_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) |
473 | { |
474 | int err = 0; |
475 | |
476 | err = device_create_file(device: &battery->dev, entry: &dev_attr_charge_control_start_threshold); |
477 | if (err) |
478 | return err; |
479 | |
480 | err = device_create_file(device: &battery->dev, entry: &dev_attr_charge_control_end_threshold); |
481 | if (err) |
482 | device_remove_file(dev: &battery->dev, attr: &dev_attr_charge_control_start_threshold); |
483 | |
484 | return err; |
485 | } |
486 | |
487 | static int huawei_wmi_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook) |
488 | { |
489 | device_remove_file(dev: &battery->dev, attr: &dev_attr_charge_control_start_threshold); |
490 | device_remove_file(dev: &battery->dev, attr: &dev_attr_charge_control_end_threshold); |
491 | |
492 | return 0; |
493 | } |
494 | |
495 | static struct acpi_battery_hook huawei_wmi_battery_hook = { |
496 | .add_battery = huawei_wmi_battery_add, |
497 | .remove_battery = huawei_wmi_battery_remove, |
498 | .name = "Huawei Battery Extension" |
499 | }; |
500 | |
501 | static void huawei_wmi_battery_setup(struct device *dev) |
502 | { |
503 | struct huawei_wmi *huawei = dev_get_drvdata(dev); |
504 | |
505 | huawei->battery_available = true; |
506 | if (huawei_wmi_battery_get(NULL, NULL)) { |
507 | huawei->battery_available = false; |
508 | return; |
509 | } |
510 | |
511 | battery_hook_register(hook: &huawei_wmi_battery_hook); |
512 | device_create_file(device: dev, entry: &dev_attr_charge_control_thresholds); |
513 | } |
514 | |
515 | static void huawei_wmi_battery_exit(struct device *dev) |
516 | { |
517 | struct huawei_wmi *huawei = dev_get_drvdata(dev); |
518 | |
519 | if (huawei->battery_available) { |
520 | battery_hook_unregister(hook: &huawei_wmi_battery_hook); |
521 | device_remove_file(dev, attr: &dev_attr_charge_control_thresholds); |
522 | } |
523 | } |
524 | |
525 | /* Fn lock */ |
526 | |
527 | static int huawei_wmi_fn_lock_get(int *on) |
528 | { |
529 | u8 ret[0x100] = { 0 }; |
530 | int err, i; |
531 | |
532 | err = huawei_wmi_cmd(arg: FN_LOCK_GET, buf: ret, buflen: 0x100); |
533 | if (err) |
534 | return err; |
535 | |
536 | /* Find the first non-zero value. Return status is ignored. */ |
537 | i = 1; |
538 | do { |
539 | if (on) |
540 | *on = ret[i] - 1; // -1 undefined, 0 off, 1 on. |
541 | } while (i < 0xff && !ret[i++]); |
542 | |
543 | return 0; |
544 | } |
545 | |
546 | static int huawei_wmi_fn_lock_set(int on) |
547 | { |
548 | union hwmi_arg arg; |
549 | |
550 | arg.cmd = FN_LOCK_SET; |
551 | arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on. |
552 | |
553 | return huawei_wmi_cmd(arg: arg.cmd, NULL, buflen: 0); |
554 | } |
555 | |
556 | static ssize_t fn_lock_state_show(struct device *dev, |
557 | struct device_attribute *attr, |
558 | char *buf) |
559 | { |
560 | int err, on; |
561 | |
562 | err = huawei_wmi_fn_lock_get(on: &on); |
563 | if (err) |
564 | return err; |
565 | |
566 | return sprintf(buf, fmt: "%d\n" , on); |
567 | } |
568 | |
569 | static ssize_t fn_lock_state_store(struct device *dev, |
570 | struct device_attribute *attr, |
571 | const char *buf, size_t size) |
572 | { |
573 | int on, err; |
574 | |
575 | if (kstrtoint(s: buf, base: 10, res: &on) || |
576 | on < 0 || on > 1) |
577 | return -EINVAL; |
578 | |
579 | err = huawei_wmi_fn_lock_set(on); |
580 | if (err) |
581 | return err; |
582 | |
583 | return size; |
584 | } |
585 | |
586 | static DEVICE_ATTR_RW(fn_lock_state); |
587 | |
588 | static void huawei_wmi_fn_lock_setup(struct device *dev) |
589 | { |
590 | struct huawei_wmi *huawei = dev_get_drvdata(dev); |
591 | |
592 | huawei->fn_lock_available = true; |
593 | if (huawei_wmi_fn_lock_get(NULL)) { |
594 | huawei->fn_lock_available = false; |
595 | return; |
596 | } |
597 | |
598 | device_create_file(device: dev, entry: &dev_attr_fn_lock_state); |
599 | } |
600 | |
601 | static void huawei_wmi_fn_lock_exit(struct device *dev) |
602 | { |
603 | struct huawei_wmi *huawei = dev_get_drvdata(dev); |
604 | |
605 | if (huawei->fn_lock_available) |
606 | device_remove_file(dev, attr: &dev_attr_fn_lock_state); |
607 | } |
608 | |
609 | /* debugfs */ |
610 | |
611 | static void huawei_wmi_debugfs_call_dump(struct seq_file *m, void *data, |
612 | union acpi_object *obj) |
613 | { |
614 | struct huawei_wmi *huawei = m->private; |
615 | int i; |
616 | |
617 | switch (obj->type) { |
618 | case ACPI_TYPE_INTEGER: |
619 | seq_printf(m, fmt: "0x%llx" , obj->integer.value); |
620 | break; |
621 | case ACPI_TYPE_STRING: |
622 | seq_printf(m, fmt: "\"%.*s\"" , obj->string.length, obj->string.pointer); |
623 | break; |
624 | case ACPI_TYPE_BUFFER: |
625 | seq_puts(m, s: "{" ); |
626 | for (i = 0; i < obj->buffer.length; i++) { |
627 | seq_printf(m, fmt: "0x%02x" , obj->buffer.pointer[i]); |
628 | if (i < obj->buffer.length - 1) |
629 | seq_puts(m, s: "," ); |
630 | } |
631 | seq_puts(m, s: "}" ); |
632 | break; |
633 | case ACPI_TYPE_PACKAGE: |
634 | seq_puts(m, s: "[" ); |
635 | for (i = 0; i < obj->package.count; i++) { |
636 | huawei_wmi_debugfs_call_dump(m, data: huawei, obj: &obj->package.elements[i]); |
637 | if (i < obj->package.count - 1) |
638 | seq_puts(m, s: "," ); |
639 | } |
640 | seq_puts(m, s: "]" ); |
641 | break; |
642 | default: |
643 | dev_err(huawei->dev, "Unexpected obj type, got %d\n" , obj->type); |
644 | return; |
645 | } |
646 | } |
647 | |
648 | static int huawei_wmi_debugfs_call_show(struct seq_file *m, void *data) |
649 | { |
650 | struct huawei_wmi *huawei = m->private; |
651 | struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; |
652 | struct acpi_buffer in; |
653 | union acpi_object *obj; |
654 | int err; |
655 | |
656 | in.length = sizeof(u64); |
657 | in.pointer = &huawei->debug.arg; |
658 | |
659 | err = huawei_wmi_call(huawei, in: &in, out: &out); |
660 | if (err) |
661 | return err; |
662 | |
663 | obj = out.pointer; |
664 | if (!obj) { |
665 | err = -EIO; |
666 | goto fail_debugfs_call; |
667 | } |
668 | |
669 | huawei_wmi_debugfs_call_dump(m, data: huawei, obj); |
670 | |
671 | fail_debugfs_call: |
672 | kfree(objp: out.pointer); |
673 | return err; |
674 | } |
675 | |
676 | DEFINE_SHOW_ATTRIBUTE(huawei_wmi_debugfs_call); |
677 | |
678 | static void huawei_wmi_debugfs_setup(struct device *dev) |
679 | { |
680 | struct huawei_wmi *huawei = dev_get_drvdata(dev); |
681 | |
682 | huawei->debug.root = debugfs_create_dir(name: "huawei-wmi" , NULL); |
683 | |
684 | debugfs_create_x64(name: "arg" , mode: 0644, parent: huawei->debug.root, |
685 | value: &huawei->debug.arg); |
686 | debugfs_create_file(name: "call" , mode: 0400, |
687 | parent: huawei->debug.root, data: huawei, fops: &huawei_wmi_debugfs_call_fops); |
688 | } |
689 | |
690 | static void huawei_wmi_debugfs_exit(struct device *dev) |
691 | { |
692 | struct huawei_wmi *huawei = dev_get_drvdata(dev); |
693 | |
694 | debugfs_remove_recursive(dentry: huawei->debug.root); |
695 | } |
696 | |
697 | /* Input */ |
698 | |
699 | static void huawei_wmi_process_key(struct input_dev *idev, int code) |
700 | { |
701 | const struct key_entry *key; |
702 | |
703 | /* |
704 | * WMI0 uses code 0x80 to indicate a hotkey event. |
705 | * The actual key is fetched from the method WQ00 |
706 | * using WMI0_EXPENSIVE_GUID. |
707 | */ |
708 | if (code == 0x80) { |
709 | struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; |
710 | union acpi_object *obj; |
711 | acpi_status status; |
712 | |
713 | status = wmi_query_block(WMI0_EXPENSIVE_GUID, instance: 0, out: &response); |
714 | if (ACPI_FAILURE(status)) |
715 | return; |
716 | |
717 | obj = (union acpi_object *)response.pointer; |
718 | if (obj && obj->type == ACPI_TYPE_INTEGER) |
719 | code = obj->integer.value; |
720 | |
721 | kfree(objp: response.pointer); |
722 | } |
723 | |
724 | key = sparse_keymap_entry_from_scancode(dev: idev, code); |
725 | if (!key) { |
726 | dev_info(&idev->dev, "Unknown key pressed, code: 0x%04x\n" , code); |
727 | return; |
728 | } |
729 | |
730 | if (quirks && !quirks->report_brightness && |
731 | (key->sw.code == KEY_BRIGHTNESSDOWN || |
732 | key->sw.code == KEY_BRIGHTNESSUP)) |
733 | return; |
734 | |
735 | sparse_keymap_report_entry(dev: idev, ke: key, value: 1, autorelease: true); |
736 | } |
737 | |
738 | static void huawei_wmi_input_notify(u32 value, void *context) |
739 | { |
740 | struct input_dev *idev = (struct input_dev *)context; |
741 | struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; |
742 | union acpi_object *obj; |
743 | acpi_status status; |
744 | |
745 | status = wmi_get_event_data(event: value, out: &response); |
746 | if (ACPI_FAILURE(status)) { |
747 | dev_err(&idev->dev, "Unable to get event data\n" ); |
748 | return; |
749 | } |
750 | |
751 | obj = (union acpi_object *)response.pointer; |
752 | if (obj && obj->type == ACPI_TYPE_INTEGER) |
753 | huawei_wmi_process_key(idev, code: obj->integer.value); |
754 | else |
755 | dev_err(&idev->dev, "Bad response type\n" ); |
756 | |
757 | kfree(objp: response.pointer); |
758 | } |
759 | |
760 | static int huawei_wmi_input_setup(struct device *dev, const char *guid) |
761 | { |
762 | struct input_dev *idev; |
763 | acpi_status status; |
764 | int err; |
765 | |
766 | idev = devm_input_allocate_device(dev); |
767 | if (!idev) |
768 | return -ENOMEM; |
769 | |
770 | idev->name = "Huawei WMI hotkeys" ; |
771 | idev->phys = "wmi/input0" ; |
772 | idev->id.bustype = BUS_HOST; |
773 | idev->dev.parent = dev; |
774 | |
775 | err = sparse_keymap_setup(dev: idev, keymap: huawei_wmi_keymap, NULL); |
776 | if (err) |
777 | return err; |
778 | |
779 | err = input_register_device(idev); |
780 | if (err) |
781 | return err; |
782 | |
783 | status = wmi_install_notify_handler(guid, handler: huawei_wmi_input_notify, data: idev); |
784 | if (ACPI_FAILURE(status)) |
785 | return -EIO; |
786 | |
787 | return 0; |
788 | } |
789 | |
790 | static void huawei_wmi_input_exit(struct device *dev, const char *guid) |
791 | { |
792 | wmi_remove_notify_handler(guid); |
793 | } |
794 | |
795 | /* Huawei driver */ |
796 | |
797 | static const struct wmi_device_id huawei_wmi_events_id_table[] = { |
798 | { .guid_string = WMI0_EVENT_GUID }, |
799 | { .guid_string = HWMI_EVENT_GUID }, |
800 | { } |
801 | }; |
802 | |
803 | static int huawei_wmi_probe(struct platform_device *pdev) |
804 | { |
805 | const struct wmi_device_id *guid = huawei_wmi_events_id_table; |
806 | int err; |
807 | |
808 | platform_set_drvdata(pdev, data: huawei_wmi); |
809 | huawei_wmi->dev = &pdev->dev; |
810 | |
811 | while (*guid->guid_string) { |
812 | if (wmi_has_guid(guid: guid->guid_string)) { |
813 | err = huawei_wmi_input_setup(dev: &pdev->dev, guid: guid->guid_string); |
814 | if (err) { |
815 | dev_err(&pdev->dev, "Failed to setup input on %s\n" , guid->guid_string); |
816 | return err; |
817 | } |
818 | } |
819 | |
820 | guid++; |
821 | } |
822 | |
823 | if (wmi_has_guid(HWMI_METHOD_GUID)) { |
824 | mutex_init(&huawei_wmi->wmi_lock); |
825 | |
826 | huawei_wmi_leds_setup(dev: &pdev->dev); |
827 | huawei_wmi_fn_lock_setup(dev: &pdev->dev); |
828 | huawei_wmi_battery_setup(dev: &pdev->dev); |
829 | huawei_wmi_debugfs_setup(dev: &pdev->dev); |
830 | } |
831 | |
832 | return 0; |
833 | } |
834 | |
835 | static void huawei_wmi_remove(struct platform_device *pdev) |
836 | { |
837 | const struct wmi_device_id *guid = huawei_wmi_events_id_table; |
838 | |
839 | while (*guid->guid_string) { |
840 | if (wmi_has_guid(guid: guid->guid_string)) |
841 | huawei_wmi_input_exit(dev: &pdev->dev, guid: guid->guid_string); |
842 | |
843 | guid++; |
844 | } |
845 | |
846 | if (wmi_has_guid(HWMI_METHOD_GUID)) { |
847 | huawei_wmi_debugfs_exit(dev: &pdev->dev); |
848 | huawei_wmi_battery_exit(dev: &pdev->dev); |
849 | huawei_wmi_fn_lock_exit(dev: &pdev->dev); |
850 | } |
851 | } |
852 | |
853 | static struct platform_driver huawei_wmi_driver = { |
854 | .driver = { |
855 | .name = "huawei-wmi" , |
856 | }, |
857 | .probe = huawei_wmi_probe, |
858 | .remove_new = huawei_wmi_remove, |
859 | }; |
860 | |
861 | static __init int huawei_wmi_init(void) |
862 | { |
863 | struct platform_device *pdev; |
864 | int err; |
865 | |
866 | huawei_wmi = kzalloc(size: sizeof(struct huawei_wmi), GFP_KERNEL); |
867 | if (!huawei_wmi) |
868 | return -ENOMEM; |
869 | |
870 | quirks = &quirk_unknown; |
871 | dmi_check_system(list: huawei_quirks); |
872 | if (battery_reset != -1) |
873 | quirks->battery_reset = battery_reset; |
874 | if (report_brightness != -1) |
875 | quirks->report_brightness = report_brightness; |
876 | |
877 | err = platform_driver_register(&huawei_wmi_driver); |
878 | if (err) |
879 | goto pdrv_err; |
880 | |
881 | pdev = platform_device_register_simple(name: "huawei-wmi" , PLATFORM_DEVID_NONE, NULL, num: 0); |
882 | if (IS_ERR(ptr: pdev)) { |
883 | err = PTR_ERR(ptr: pdev); |
884 | goto pdev_err; |
885 | } |
886 | |
887 | return 0; |
888 | |
889 | pdev_err: |
890 | platform_driver_unregister(&huawei_wmi_driver); |
891 | pdrv_err: |
892 | kfree(objp: huawei_wmi); |
893 | return err; |
894 | } |
895 | |
896 | static __exit void huawei_wmi_exit(void) |
897 | { |
898 | struct platform_device *pdev = to_platform_device(huawei_wmi->dev); |
899 | |
900 | platform_device_unregister(pdev); |
901 | platform_driver_unregister(&huawei_wmi_driver); |
902 | |
903 | kfree(objp: huawei_wmi); |
904 | } |
905 | |
906 | module_init(huawei_wmi_init); |
907 | module_exit(huawei_wmi_exit); |
908 | |
909 | MODULE_ALIAS("wmi:" HWMI_METHOD_GUID); |
910 | MODULE_DEVICE_TABLE(wmi, huawei_wmi_events_id_table); |
911 | MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@gmail.com>" ); |
912 | MODULE_DESCRIPTION("Huawei WMI laptop extras driver" ); |
913 | MODULE_LICENSE("GPL v2" ); |
914 | |