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.dev = dev; |
314 | huawei->cdev.flags = LED_CORE_SUSPENDRESUME; |
315 | |
316 | devm_led_classdev_register(parent: dev, led_cdev: &huawei->cdev); |
317 | } |
318 | |
319 | /* Battery protection */ |
320 | |
321 | static int huawei_wmi_battery_get(int *start, int *end) |
322 | { |
323 | u8 ret[0x100]; |
324 | int err, i; |
325 | |
326 | err = huawei_wmi_cmd(arg: BATTERY_THRESH_GET, buf: ret, buflen: sizeof(ret)); |
327 | if (err) |
328 | return err; |
329 | |
330 | /* Find the last two non-zero values. Return status is ignored. */ |
331 | i = ARRAY_SIZE(ret) - 1; |
332 | do { |
333 | if (start) |
334 | *start = ret[i-1]; |
335 | if (end) |
336 | *end = ret[i]; |
337 | } while (i > 2 && !ret[i--]); |
338 | |
339 | return 0; |
340 | } |
341 | |
342 | static int huawei_wmi_battery_set(int start, int end) |
343 | { |
344 | union hwmi_arg arg; |
345 | int err; |
346 | |
347 | if (start < 0 || end < 0 || start > 100 || end > 100) |
348 | return -EINVAL; |
349 | |
350 | arg.cmd = BATTERY_THRESH_SET; |
351 | arg.args[2] = start; |
352 | arg.args[3] = end; |
353 | |
354 | /* This is an edge case were some models turn battery protection |
355 | * off without changing their thresholds values. We clear the |
356 | * values before turning off protection. Sometimes we need a sleep delay to |
357 | * make sure these values make their way to EC memory. |
358 | */ |
359 | if (quirks && quirks->battery_reset && start == 0 && end == 100) { |
360 | err = huawei_wmi_battery_set(start: 0, end: 0); |
361 | if (err) |
362 | return err; |
363 | |
364 | msleep(msecs: 1000); |
365 | } |
366 | |
367 | err = huawei_wmi_cmd(arg: arg.cmd, NULL, buflen: 0); |
368 | |
369 | return err; |
370 | } |
371 | |
372 | static ssize_t charge_control_start_threshold_show(struct device *dev, |
373 | struct device_attribute *attr, |
374 | char *buf) |
375 | { |
376 | int err, start; |
377 | |
378 | err = huawei_wmi_battery_get(start: &start, NULL); |
379 | if (err) |
380 | return err; |
381 | |
382 | return sprintf(buf, fmt: "%d\n" , start); |
383 | } |
384 | |
385 | static ssize_t charge_control_end_threshold_show(struct device *dev, |
386 | struct device_attribute *attr, |
387 | char *buf) |
388 | { |
389 | int err, end; |
390 | |
391 | err = huawei_wmi_battery_get(NULL, end: &end); |
392 | if (err) |
393 | return err; |
394 | |
395 | return sprintf(buf, fmt: "%d\n" , end); |
396 | } |
397 | |
398 | static ssize_t charge_control_thresholds_show(struct device *dev, |
399 | struct device_attribute *attr, |
400 | char *buf) |
401 | { |
402 | int err, start, end; |
403 | |
404 | err = huawei_wmi_battery_get(start: &start, end: &end); |
405 | if (err) |
406 | return err; |
407 | |
408 | return sprintf(buf, fmt: "%d %d\n" , start, end); |
409 | } |
410 | |
411 | static ssize_t charge_control_start_threshold_store(struct device *dev, |
412 | struct device_attribute *attr, |
413 | const char *buf, size_t size) |
414 | { |
415 | int err, start, end; |
416 | |
417 | err = huawei_wmi_battery_get(NULL, end: &end); |
418 | if (err) |
419 | return err; |
420 | |
421 | if (sscanf(buf, "%d" , &start) != 1) |
422 | return -EINVAL; |
423 | |
424 | err = huawei_wmi_battery_set(start, end); |
425 | if (err) |
426 | return err; |
427 | |
428 | return size; |
429 | } |
430 | |
431 | static ssize_t charge_control_end_threshold_store(struct device *dev, |
432 | struct device_attribute *attr, |
433 | const char *buf, size_t size) |
434 | { |
435 | int err, start, end; |
436 | |
437 | err = huawei_wmi_battery_get(start: &start, NULL); |
438 | if (err) |
439 | return err; |
440 | |
441 | if (sscanf(buf, "%d" , &end) != 1) |
442 | return -EINVAL; |
443 | |
444 | err = huawei_wmi_battery_set(start, end); |
445 | if (err) |
446 | return err; |
447 | |
448 | return size; |
449 | } |
450 | |
451 | static ssize_t charge_control_thresholds_store(struct device *dev, |
452 | struct device_attribute *attr, |
453 | const char *buf, size_t size) |
454 | { |
455 | int err, start, end; |
456 | |
457 | if (sscanf(buf, "%d %d" , &start, &end) != 2) |
458 | return -EINVAL; |
459 | |
460 | err = huawei_wmi_battery_set(start, end); |
461 | if (err) |
462 | return err; |
463 | |
464 | return size; |
465 | } |
466 | |
467 | static DEVICE_ATTR_RW(charge_control_start_threshold); |
468 | static DEVICE_ATTR_RW(charge_control_end_threshold); |
469 | static DEVICE_ATTR_RW(charge_control_thresholds); |
470 | |
471 | static int huawei_wmi_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) |
472 | { |
473 | int err = 0; |
474 | |
475 | err = device_create_file(device: &battery->dev, entry: &dev_attr_charge_control_start_threshold); |
476 | if (err) |
477 | return err; |
478 | |
479 | err = device_create_file(device: &battery->dev, entry: &dev_attr_charge_control_end_threshold); |
480 | if (err) |
481 | device_remove_file(dev: &battery->dev, attr: &dev_attr_charge_control_start_threshold); |
482 | |
483 | return err; |
484 | } |
485 | |
486 | static int huawei_wmi_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook) |
487 | { |
488 | device_remove_file(dev: &battery->dev, attr: &dev_attr_charge_control_start_threshold); |
489 | device_remove_file(dev: &battery->dev, attr: &dev_attr_charge_control_end_threshold); |
490 | |
491 | return 0; |
492 | } |
493 | |
494 | static struct acpi_battery_hook huawei_wmi_battery_hook = { |
495 | .add_battery = huawei_wmi_battery_add, |
496 | .remove_battery = huawei_wmi_battery_remove, |
497 | .name = "Huawei Battery Extension" |
498 | }; |
499 | |
500 | static void huawei_wmi_battery_setup(struct device *dev) |
501 | { |
502 | struct huawei_wmi *huawei = dev_get_drvdata(dev); |
503 | |
504 | huawei->battery_available = true; |
505 | if (huawei_wmi_battery_get(NULL, NULL)) { |
506 | huawei->battery_available = false; |
507 | return; |
508 | } |
509 | |
510 | battery_hook_register(hook: &huawei_wmi_battery_hook); |
511 | device_create_file(device: dev, entry: &dev_attr_charge_control_thresholds); |
512 | } |
513 | |
514 | static void huawei_wmi_battery_exit(struct device *dev) |
515 | { |
516 | struct huawei_wmi *huawei = dev_get_drvdata(dev); |
517 | |
518 | if (huawei->battery_available) { |
519 | battery_hook_unregister(hook: &huawei_wmi_battery_hook); |
520 | device_remove_file(dev, attr: &dev_attr_charge_control_thresholds); |
521 | } |
522 | } |
523 | |
524 | /* Fn lock */ |
525 | |
526 | static int huawei_wmi_fn_lock_get(int *on) |
527 | { |
528 | u8 ret[0x100] = { 0 }; |
529 | int err, i; |
530 | |
531 | err = huawei_wmi_cmd(arg: FN_LOCK_GET, buf: ret, buflen: 0x100); |
532 | if (err) |
533 | return err; |
534 | |
535 | /* Find the first non-zero value. Return status is ignored. */ |
536 | i = 1; |
537 | do { |
538 | if (on) |
539 | *on = ret[i] - 1; // -1 undefined, 0 off, 1 on. |
540 | } while (i < 0xff && !ret[i++]); |
541 | |
542 | return 0; |
543 | } |
544 | |
545 | static int huawei_wmi_fn_lock_set(int on) |
546 | { |
547 | union hwmi_arg arg; |
548 | |
549 | arg.cmd = FN_LOCK_SET; |
550 | arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on. |
551 | |
552 | return huawei_wmi_cmd(arg: arg.cmd, NULL, buflen: 0); |
553 | } |
554 | |
555 | static ssize_t fn_lock_state_show(struct device *dev, |
556 | struct device_attribute *attr, |
557 | char *buf) |
558 | { |
559 | int err, on; |
560 | |
561 | err = huawei_wmi_fn_lock_get(on: &on); |
562 | if (err) |
563 | return err; |
564 | |
565 | return sprintf(buf, fmt: "%d\n" , on); |
566 | } |
567 | |
568 | static ssize_t fn_lock_state_store(struct device *dev, |
569 | struct device_attribute *attr, |
570 | const char *buf, size_t size) |
571 | { |
572 | int on, err; |
573 | |
574 | if (kstrtoint(s: buf, base: 10, res: &on) || |
575 | on < 0 || on > 1) |
576 | return -EINVAL; |
577 | |
578 | err = huawei_wmi_fn_lock_set(on); |
579 | if (err) |
580 | return err; |
581 | |
582 | return size; |
583 | } |
584 | |
585 | static DEVICE_ATTR_RW(fn_lock_state); |
586 | |
587 | static void huawei_wmi_fn_lock_setup(struct device *dev) |
588 | { |
589 | struct huawei_wmi *huawei = dev_get_drvdata(dev); |
590 | |
591 | huawei->fn_lock_available = true; |
592 | if (huawei_wmi_fn_lock_get(NULL)) { |
593 | huawei->fn_lock_available = false; |
594 | return; |
595 | } |
596 | |
597 | device_create_file(device: dev, entry: &dev_attr_fn_lock_state); |
598 | } |
599 | |
600 | static void huawei_wmi_fn_lock_exit(struct device *dev) |
601 | { |
602 | struct huawei_wmi *huawei = dev_get_drvdata(dev); |
603 | |
604 | if (huawei->fn_lock_available) |
605 | device_remove_file(dev, attr: &dev_attr_fn_lock_state); |
606 | } |
607 | |
608 | /* debugfs */ |
609 | |
610 | static void huawei_wmi_debugfs_call_dump(struct seq_file *m, void *data, |
611 | union acpi_object *obj) |
612 | { |
613 | struct huawei_wmi *huawei = m->private; |
614 | int i; |
615 | |
616 | switch (obj->type) { |
617 | case ACPI_TYPE_INTEGER: |
618 | seq_printf(m, fmt: "0x%llx" , obj->integer.value); |
619 | break; |
620 | case ACPI_TYPE_STRING: |
621 | seq_printf(m, fmt: "\"%.*s\"" , obj->string.length, obj->string.pointer); |
622 | break; |
623 | case ACPI_TYPE_BUFFER: |
624 | seq_puts(m, s: "{" ); |
625 | for (i = 0; i < obj->buffer.length; i++) { |
626 | seq_printf(m, fmt: "0x%02x" , obj->buffer.pointer[i]); |
627 | if (i < obj->buffer.length - 1) |
628 | seq_puts(m, s: "," ); |
629 | } |
630 | seq_puts(m, s: "}" ); |
631 | break; |
632 | case ACPI_TYPE_PACKAGE: |
633 | seq_puts(m, s: "[" ); |
634 | for (i = 0; i < obj->package.count; i++) { |
635 | huawei_wmi_debugfs_call_dump(m, data: huawei, obj: &obj->package.elements[i]); |
636 | if (i < obj->package.count - 1) |
637 | seq_puts(m, s: "," ); |
638 | } |
639 | seq_puts(m, s: "]" ); |
640 | break; |
641 | default: |
642 | dev_err(huawei->dev, "Unexpected obj type, got %d\n" , obj->type); |
643 | return; |
644 | } |
645 | } |
646 | |
647 | static int huawei_wmi_debugfs_call_show(struct seq_file *m, void *data) |
648 | { |
649 | struct huawei_wmi *huawei = m->private; |
650 | struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; |
651 | struct acpi_buffer in; |
652 | union acpi_object *obj; |
653 | int err; |
654 | |
655 | in.length = sizeof(u64); |
656 | in.pointer = &huawei->debug.arg; |
657 | |
658 | err = huawei_wmi_call(huawei, in: &in, out: &out); |
659 | if (err) |
660 | return err; |
661 | |
662 | obj = out.pointer; |
663 | if (!obj) { |
664 | err = -EIO; |
665 | goto fail_debugfs_call; |
666 | } |
667 | |
668 | huawei_wmi_debugfs_call_dump(m, data: huawei, obj); |
669 | |
670 | fail_debugfs_call: |
671 | kfree(objp: out.pointer); |
672 | return err; |
673 | } |
674 | |
675 | DEFINE_SHOW_ATTRIBUTE(huawei_wmi_debugfs_call); |
676 | |
677 | static void huawei_wmi_debugfs_setup(struct device *dev) |
678 | { |
679 | struct huawei_wmi *huawei = dev_get_drvdata(dev); |
680 | |
681 | huawei->debug.root = debugfs_create_dir(name: "huawei-wmi" , NULL); |
682 | |
683 | debugfs_create_x64(name: "arg" , mode: 0644, parent: huawei->debug.root, |
684 | value: &huawei->debug.arg); |
685 | debugfs_create_file(name: "call" , mode: 0400, |
686 | parent: huawei->debug.root, data: huawei, fops: &huawei_wmi_debugfs_call_fops); |
687 | } |
688 | |
689 | static void huawei_wmi_debugfs_exit(struct device *dev) |
690 | { |
691 | struct huawei_wmi *huawei = dev_get_drvdata(dev); |
692 | |
693 | debugfs_remove_recursive(dentry: huawei->debug.root); |
694 | } |
695 | |
696 | /* Input */ |
697 | |
698 | static void huawei_wmi_process_key(struct input_dev *idev, int code) |
699 | { |
700 | const struct key_entry *key; |
701 | |
702 | /* |
703 | * WMI0 uses code 0x80 to indicate a hotkey event. |
704 | * The actual key is fetched from the method WQ00 |
705 | * using WMI0_EXPENSIVE_GUID. |
706 | */ |
707 | if (code == 0x80) { |
708 | struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; |
709 | union acpi_object *obj; |
710 | acpi_status status; |
711 | |
712 | status = wmi_query_block(WMI0_EXPENSIVE_GUID, instance: 0, out: &response); |
713 | if (ACPI_FAILURE(status)) |
714 | return; |
715 | |
716 | obj = (union acpi_object *)response.pointer; |
717 | if (obj && obj->type == ACPI_TYPE_INTEGER) |
718 | code = obj->integer.value; |
719 | |
720 | kfree(objp: response.pointer); |
721 | } |
722 | |
723 | key = sparse_keymap_entry_from_scancode(dev: idev, code); |
724 | if (!key) { |
725 | dev_info(&idev->dev, "Unknown key pressed, code: 0x%04x\n" , code); |
726 | return; |
727 | } |
728 | |
729 | if (quirks && !quirks->report_brightness && |
730 | (key->sw.code == KEY_BRIGHTNESSDOWN || |
731 | key->sw.code == KEY_BRIGHTNESSUP)) |
732 | return; |
733 | |
734 | sparse_keymap_report_entry(dev: idev, ke: key, value: 1, autorelease: true); |
735 | } |
736 | |
737 | static void huawei_wmi_input_notify(u32 value, void *context) |
738 | { |
739 | struct input_dev *idev = (struct input_dev *)context; |
740 | struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; |
741 | union acpi_object *obj; |
742 | acpi_status status; |
743 | |
744 | status = wmi_get_event_data(event: value, out: &response); |
745 | if (ACPI_FAILURE(status)) { |
746 | dev_err(&idev->dev, "Unable to get event data\n" ); |
747 | return; |
748 | } |
749 | |
750 | obj = (union acpi_object *)response.pointer; |
751 | if (obj && obj->type == ACPI_TYPE_INTEGER) |
752 | huawei_wmi_process_key(idev, code: obj->integer.value); |
753 | else |
754 | dev_err(&idev->dev, "Bad response type\n" ); |
755 | |
756 | kfree(objp: response.pointer); |
757 | } |
758 | |
759 | static int huawei_wmi_input_setup(struct device *dev, const char *guid) |
760 | { |
761 | struct input_dev *idev; |
762 | acpi_status status; |
763 | int err; |
764 | |
765 | idev = devm_input_allocate_device(dev); |
766 | if (!idev) |
767 | return -ENOMEM; |
768 | |
769 | idev->name = "Huawei WMI hotkeys" ; |
770 | idev->phys = "wmi/input0" ; |
771 | idev->id.bustype = BUS_HOST; |
772 | idev->dev.parent = dev; |
773 | |
774 | err = sparse_keymap_setup(dev: idev, keymap: huawei_wmi_keymap, NULL); |
775 | if (err) |
776 | return err; |
777 | |
778 | err = input_register_device(idev); |
779 | if (err) |
780 | return err; |
781 | |
782 | status = wmi_install_notify_handler(guid, handler: huawei_wmi_input_notify, data: idev); |
783 | if (ACPI_FAILURE(status)) |
784 | return -EIO; |
785 | |
786 | return 0; |
787 | } |
788 | |
789 | static void huawei_wmi_input_exit(struct device *dev, const char *guid) |
790 | { |
791 | wmi_remove_notify_handler(guid); |
792 | } |
793 | |
794 | /* Huawei driver */ |
795 | |
796 | static const struct wmi_device_id huawei_wmi_events_id_table[] = { |
797 | { .guid_string = WMI0_EVENT_GUID }, |
798 | { .guid_string = HWMI_EVENT_GUID }, |
799 | { } |
800 | }; |
801 | |
802 | static int huawei_wmi_probe(struct platform_device *pdev) |
803 | { |
804 | const struct wmi_device_id *guid = huawei_wmi_events_id_table; |
805 | int err; |
806 | |
807 | platform_set_drvdata(pdev, data: huawei_wmi); |
808 | huawei_wmi->dev = &pdev->dev; |
809 | |
810 | while (*guid->guid_string) { |
811 | if (wmi_has_guid(guid: guid->guid_string)) { |
812 | err = huawei_wmi_input_setup(dev: &pdev->dev, guid: guid->guid_string); |
813 | if (err) { |
814 | dev_err(&pdev->dev, "Failed to setup input on %s\n" , guid->guid_string); |
815 | return err; |
816 | } |
817 | } |
818 | |
819 | guid++; |
820 | } |
821 | |
822 | if (wmi_has_guid(HWMI_METHOD_GUID)) { |
823 | mutex_init(&huawei_wmi->wmi_lock); |
824 | |
825 | huawei_wmi_leds_setup(dev: &pdev->dev); |
826 | huawei_wmi_fn_lock_setup(dev: &pdev->dev); |
827 | huawei_wmi_battery_setup(dev: &pdev->dev); |
828 | huawei_wmi_debugfs_setup(dev: &pdev->dev); |
829 | } |
830 | |
831 | return 0; |
832 | } |
833 | |
834 | static void huawei_wmi_remove(struct platform_device *pdev) |
835 | { |
836 | const struct wmi_device_id *guid = huawei_wmi_events_id_table; |
837 | |
838 | while (*guid->guid_string) { |
839 | if (wmi_has_guid(guid: guid->guid_string)) |
840 | huawei_wmi_input_exit(dev: &pdev->dev, guid: guid->guid_string); |
841 | |
842 | guid++; |
843 | } |
844 | |
845 | if (wmi_has_guid(HWMI_METHOD_GUID)) { |
846 | huawei_wmi_debugfs_exit(dev: &pdev->dev); |
847 | huawei_wmi_battery_exit(dev: &pdev->dev); |
848 | huawei_wmi_fn_lock_exit(dev: &pdev->dev); |
849 | } |
850 | } |
851 | |
852 | static struct platform_driver huawei_wmi_driver = { |
853 | .driver = { |
854 | .name = "huawei-wmi" , |
855 | }, |
856 | .probe = huawei_wmi_probe, |
857 | .remove_new = huawei_wmi_remove, |
858 | }; |
859 | |
860 | static __init int huawei_wmi_init(void) |
861 | { |
862 | struct platform_device *pdev; |
863 | int err; |
864 | |
865 | huawei_wmi = kzalloc(size: sizeof(struct huawei_wmi), GFP_KERNEL); |
866 | if (!huawei_wmi) |
867 | return -ENOMEM; |
868 | |
869 | quirks = &quirk_unknown; |
870 | dmi_check_system(list: huawei_quirks); |
871 | if (battery_reset != -1) |
872 | quirks->battery_reset = battery_reset; |
873 | if (report_brightness != -1) |
874 | quirks->report_brightness = report_brightness; |
875 | |
876 | err = platform_driver_register(&huawei_wmi_driver); |
877 | if (err) |
878 | goto pdrv_err; |
879 | |
880 | pdev = platform_device_register_simple(name: "huawei-wmi" , PLATFORM_DEVID_NONE, NULL, num: 0); |
881 | if (IS_ERR(ptr: pdev)) { |
882 | err = PTR_ERR(ptr: pdev); |
883 | goto pdev_err; |
884 | } |
885 | |
886 | return 0; |
887 | |
888 | pdev_err: |
889 | platform_driver_unregister(&huawei_wmi_driver); |
890 | pdrv_err: |
891 | kfree(objp: huawei_wmi); |
892 | return err; |
893 | } |
894 | |
895 | static __exit void huawei_wmi_exit(void) |
896 | { |
897 | struct platform_device *pdev = to_platform_device(huawei_wmi->dev); |
898 | |
899 | platform_device_unregister(pdev); |
900 | platform_driver_unregister(&huawei_wmi_driver); |
901 | |
902 | kfree(objp: huawei_wmi); |
903 | } |
904 | |
905 | module_init(huawei_wmi_init); |
906 | module_exit(huawei_wmi_exit); |
907 | |
908 | MODULE_ALIAS("wmi:" HWMI_METHOD_GUID); |
909 | MODULE_DEVICE_TABLE(wmi, huawei_wmi_events_id_table); |
910 | MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@gmail.com>" ); |
911 | MODULE_DESCRIPTION("Huawei WMI laptop extras driver" ); |
912 | MODULE_LICENSE("GPL v2" ); |
913 | |