1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * lg-laptop.c - LG Gram ACPI features and hotkeys Driver |
4 | * |
5 | * Copyright (C) 2018 Matan Ziv-Av <matan@svgalib.org> |
6 | */ |
7 | |
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
9 | |
10 | #include <linux/acpi.h> |
11 | #include <linux/dmi.h> |
12 | #include <linux/input.h> |
13 | #include <linux/input/sparse-keymap.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/leds.h> |
16 | #include <linux/module.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/types.h> |
19 | |
20 | #include <acpi/battery.h> |
21 | |
22 | #define LED_DEVICE(_name, max, flag) struct led_classdev _name = { \ |
23 | .name = __stringify(_name), \ |
24 | .max_brightness = max, \ |
25 | .brightness_set = _name##_set, \ |
26 | .brightness_get = _name##_get, \ |
27 | .flags = flag, \ |
28 | } |
29 | |
30 | MODULE_AUTHOR("Matan Ziv-Av" ); |
31 | MODULE_DESCRIPTION("LG WMI Hotkey Driver" ); |
32 | MODULE_LICENSE("GPL" ); |
33 | |
34 | #define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248" |
35 | #define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2" |
36 | #define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6" |
37 | #define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B" |
38 | #define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D" |
39 | #define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210" |
40 | #define WMI_EVENT_GUID WMI_EVENT_GUID0 |
41 | |
42 | #define WMAB_METHOD "\\XINI.WMAB" |
43 | #define WMBB_METHOD "\\XINI.WMBB" |
44 | #define SB_GGOV_METHOD "\\_SB.GGOV" |
45 | #define GOV_TLED 0x2020008 |
46 | #define WM_GET 1 |
47 | #define WM_SET 2 |
48 | #define WM_KEY_LIGHT 0x400 |
49 | #define WM_TLED 0x404 |
50 | #define WM_FN_LOCK 0x407 |
51 | #define WM_BATT_LIMIT 0x61 |
52 | #define WM_READER_MODE 0xBF |
53 | #define WM_FAN_MODE 0x33 |
54 | #define WMBB_USB_CHARGE 0x10B |
55 | #define WMBB_BATT_LIMIT 0x10C |
56 | |
57 | #define PLATFORM_NAME "lg-laptop" |
58 | |
59 | MODULE_ALIAS("wmi:" WMI_EVENT_GUID0); |
60 | MODULE_ALIAS("wmi:" WMI_EVENT_GUID1); |
61 | MODULE_ALIAS("wmi:" WMI_EVENT_GUID2); |
62 | MODULE_ALIAS("wmi:" WMI_EVENT_GUID3); |
63 | MODULE_ALIAS("wmi:" WMI_METHOD_WMAB); |
64 | MODULE_ALIAS("wmi:" WMI_METHOD_WMBB); |
65 | |
66 | static struct platform_device *pf_device; |
67 | static struct input_dev *wmi_input_dev; |
68 | |
69 | static u32 inited; |
70 | #define INIT_INPUT_WMI_0 0x01 |
71 | #define INIT_INPUT_WMI_2 0x02 |
72 | #define INIT_INPUT_ACPI 0x04 |
73 | #define INIT_SPARSE_KEYMAP 0x80 |
74 | |
75 | static int battery_limit_use_wmbb; |
76 | static struct led_classdev kbd_backlight; |
77 | static enum led_brightness get_kbd_backlight_level(void); |
78 | |
79 | static const struct key_entry wmi_keymap[] = { |
80 | {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */ |
81 | {KE_KEY, 0x74, {KEY_F21} }, /* Touchpad toggle (F5) */ |
82 | {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */ |
83 | {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing |
84 | * this key both sends an event and |
85 | * changes backlight level. |
86 | */ |
87 | {KE_KEY, 0x80, {KEY_RFKILL} }, |
88 | {KE_END, 0} |
89 | }; |
90 | |
91 | static int ggov(u32 arg0) |
92 | { |
93 | union acpi_object args[1]; |
94 | union acpi_object *r; |
95 | acpi_status status; |
96 | acpi_handle handle; |
97 | struct acpi_object_list arg; |
98 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; |
99 | int res; |
100 | |
101 | args[0].type = ACPI_TYPE_INTEGER; |
102 | args[0].integer.value = arg0; |
103 | |
104 | status = acpi_get_handle(NULL, pathname: (acpi_string) SB_GGOV_METHOD, ret_handle: &handle); |
105 | if (ACPI_FAILURE(status)) { |
106 | pr_err("Cannot get handle" ); |
107 | return -ENODEV; |
108 | } |
109 | |
110 | arg.count = 1; |
111 | arg.pointer = args; |
112 | |
113 | status = acpi_evaluate_object(object: handle, NULL, parameter_objects: &arg, return_object_buffer: &buffer); |
114 | if (ACPI_FAILURE(status)) { |
115 | acpi_handle_err(handle, "GGOV: call failed.\n" ); |
116 | return -EINVAL; |
117 | } |
118 | |
119 | r = buffer.pointer; |
120 | if (r->type != ACPI_TYPE_INTEGER) { |
121 | kfree(objp: r); |
122 | return -EINVAL; |
123 | } |
124 | |
125 | res = r->integer.value; |
126 | kfree(objp: r); |
127 | |
128 | return res; |
129 | } |
130 | |
131 | static union acpi_object *lg_wmab(u32 method, u32 arg1, u32 arg2) |
132 | { |
133 | union acpi_object args[3]; |
134 | acpi_status status; |
135 | acpi_handle handle; |
136 | struct acpi_object_list arg; |
137 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; |
138 | |
139 | args[0].type = ACPI_TYPE_INTEGER; |
140 | args[0].integer.value = method; |
141 | args[1].type = ACPI_TYPE_INTEGER; |
142 | args[1].integer.value = arg1; |
143 | args[2].type = ACPI_TYPE_INTEGER; |
144 | args[2].integer.value = arg2; |
145 | |
146 | status = acpi_get_handle(NULL, pathname: (acpi_string) WMAB_METHOD, ret_handle: &handle); |
147 | if (ACPI_FAILURE(status)) { |
148 | pr_err("Cannot get handle" ); |
149 | return NULL; |
150 | } |
151 | |
152 | arg.count = 3; |
153 | arg.pointer = args; |
154 | |
155 | status = acpi_evaluate_object(object: handle, NULL, parameter_objects: &arg, return_object_buffer: &buffer); |
156 | if (ACPI_FAILURE(status)) { |
157 | acpi_handle_err(handle, "WMAB: call failed.\n" ); |
158 | return NULL; |
159 | } |
160 | |
161 | return buffer.pointer; |
162 | } |
163 | |
164 | static union acpi_object *lg_wmbb(u32 method_id, u32 arg1, u32 arg2) |
165 | { |
166 | union acpi_object args[3]; |
167 | acpi_status status; |
168 | acpi_handle handle; |
169 | struct acpi_object_list arg; |
170 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; |
171 | u8 buf[32]; |
172 | |
173 | *(u32 *)buf = method_id; |
174 | *(u32 *)(buf + 4) = arg1; |
175 | *(u32 *)(buf + 16) = arg2; |
176 | args[0].type = ACPI_TYPE_INTEGER; |
177 | args[0].integer.value = 0; /* ignored */ |
178 | args[1].type = ACPI_TYPE_INTEGER; |
179 | args[1].integer.value = 1; /* Must be 1 or 2. Does not matter which */ |
180 | args[2].type = ACPI_TYPE_BUFFER; |
181 | args[2].buffer.length = 32; |
182 | args[2].buffer.pointer = buf; |
183 | |
184 | status = acpi_get_handle(NULL, pathname: (acpi_string)WMBB_METHOD, ret_handle: &handle); |
185 | if (ACPI_FAILURE(status)) { |
186 | pr_err("Cannot get handle" ); |
187 | return NULL; |
188 | } |
189 | |
190 | arg.count = 3; |
191 | arg.pointer = args; |
192 | |
193 | status = acpi_evaluate_object(object: handle, NULL, parameter_objects: &arg, return_object_buffer: &buffer); |
194 | if (ACPI_FAILURE(status)) { |
195 | acpi_handle_err(handle, "WMAB: call failed.\n" ); |
196 | return NULL; |
197 | } |
198 | |
199 | return (union acpi_object *)buffer.pointer; |
200 | } |
201 | |
202 | static void wmi_notify(u32 value, void *context) |
203 | { |
204 | struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; |
205 | union acpi_object *obj; |
206 | acpi_status status; |
207 | long data = (long)context; |
208 | |
209 | pr_debug("event guid %li\n" , data); |
210 | status = wmi_get_event_data(event: value, out: &response); |
211 | if (ACPI_FAILURE(status)) { |
212 | pr_err("Bad event status 0x%x\n" , status); |
213 | return; |
214 | } |
215 | |
216 | obj = (union acpi_object *)response.pointer; |
217 | if (!obj) |
218 | return; |
219 | |
220 | if (obj->type == ACPI_TYPE_INTEGER) { |
221 | int eventcode = obj->integer.value; |
222 | struct key_entry *key; |
223 | |
224 | if (eventcode == 0x10000000) { |
225 | led_classdev_notify_brightness_hw_changed( |
226 | led_cdev: &kbd_backlight, brightness: get_kbd_backlight_level()); |
227 | } else { |
228 | key = sparse_keymap_entry_from_scancode( |
229 | dev: wmi_input_dev, code: eventcode); |
230 | if (key && key->type == KE_KEY) |
231 | sparse_keymap_report_entry(dev: wmi_input_dev, |
232 | ke: key, value: 1, autorelease: true); |
233 | } |
234 | } |
235 | |
236 | pr_debug("Type: %i Eventcode: 0x%llx\n" , obj->type, |
237 | obj->integer.value); |
238 | kfree(objp: response.pointer); |
239 | } |
240 | |
241 | static void wmi_input_setup(void) |
242 | { |
243 | acpi_status status; |
244 | |
245 | wmi_input_dev = input_allocate_device(); |
246 | if (wmi_input_dev) { |
247 | wmi_input_dev->name = "LG WMI hotkeys" ; |
248 | wmi_input_dev->phys = "wmi/input0" ; |
249 | wmi_input_dev->id.bustype = BUS_HOST; |
250 | |
251 | if (sparse_keymap_setup(dev: wmi_input_dev, keymap: wmi_keymap, NULL) || |
252 | input_register_device(wmi_input_dev)) { |
253 | pr_info("Cannot initialize input device" ); |
254 | input_free_device(dev: wmi_input_dev); |
255 | return; |
256 | } |
257 | |
258 | inited |= INIT_SPARSE_KEYMAP; |
259 | status = wmi_install_notify_handler(WMI_EVENT_GUID0, handler: wmi_notify, |
260 | data: (void *)0); |
261 | if (ACPI_SUCCESS(status)) |
262 | inited |= INIT_INPUT_WMI_0; |
263 | |
264 | status = wmi_install_notify_handler(WMI_EVENT_GUID2, handler: wmi_notify, |
265 | data: (void *)2); |
266 | if (ACPI_SUCCESS(status)) |
267 | inited |= INIT_INPUT_WMI_2; |
268 | } else { |
269 | pr_info("Cannot allocate input device" ); |
270 | } |
271 | } |
272 | |
273 | static void acpi_notify(struct acpi_device *device, u32 event) |
274 | { |
275 | struct key_entry *key; |
276 | |
277 | acpi_handle_debug(device->handle, "notify: %d\n" , event); |
278 | if (inited & INIT_SPARSE_KEYMAP) { |
279 | key = sparse_keymap_entry_from_scancode(dev: wmi_input_dev, code: 0x80); |
280 | if (key && key->type == KE_KEY) |
281 | sparse_keymap_report_entry(dev: wmi_input_dev, ke: key, value: 1, autorelease: true); |
282 | } |
283 | } |
284 | |
285 | static ssize_t fan_mode_store(struct device *dev, |
286 | struct device_attribute *attr, |
287 | const char *buffer, size_t count) |
288 | { |
289 | bool value; |
290 | union acpi_object *r; |
291 | u32 m; |
292 | int ret; |
293 | |
294 | ret = kstrtobool(s: buffer, res: &value); |
295 | if (ret) |
296 | return ret; |
297 | |
298 | r = lg_wmab(WM_FAN_MODE, WM_GET, arg2: 0); |
299 | if (!r) |
300 | return -EIO; |
301 | |
302 | if (r->type != ACPI_TYPE_INTEGER) { |
303 | kfree(objp: r); |
304 | return -EIO; |
305 | } |
306 | |
307 | m = r->integer.value; |
308 | kfree(objp: r); |
309 | r = lg_wmab(WM_FAN_MODE, WM_SET, arg2: (m & 0xffffff0f) | (value << 4)); |
310 | kfree(objp: r); |
311 | r = lg_wmab(WM_FAN_MODE, WM_SET, arg2: (m & 0xfffffff0) | value); |
312 | kfree(objp: r); |
313 | |
314 | return count; |
315 | } |
316 | |
317 | static ssize_t fan_mode_show(struct device *dev, |
318 | struct device_attribute *attr, char *buffer) |
319 | { |
320 | unsigned int status; |
321 | union acpi_object *r; |
322 | |
323 | r = lg_wmab(WM_FAN_MODE, WM_GET, arg2: 0); |
324 | if (!r) |
325 | return -EIO; |
326 | |
327 | if (r->type != ACPI_TYPE_INTEGER) { |
328 | kfree(objp: r); |
329 | return -EIO; |
330 | } |
331 | |
332 | status = r->integer.value & 0x01; |
333 | kfree(objp: r); |
334 | |
335 | return sysfs_emit(buf: buffer, fmt: "%d\n" , status); |
336 | } |
337 | |
338 | static ssize_t usb_charge_store(struct device *dev, |
339 | struct device_attribute *attr, |
340 | const char *buffer, size_t count) |
341 | { |
342 | bool value; |
343 | union acpi_object *r; |
344 | int ret; |
345 | |
346 | ret = kstrtobool(s: buffer, res: &value); |
347 | if (ret) |
348 | return ret; |
349 | |
350 | r = lg_wmbb(WMBB_USB_CHARGE, WM_SET, arg2: value); |
351 | if (!r) |
352 | return -EIO; |
353 | |
354 | kfree(objp: r); |
355 | return count; |
356 | } |
357 | |
358 | static ssize_t usb_charge_show(struct device *dev, |
359 | struct device_attribute *attr, char *buffer) |
360 | { |
361 | unsigned int status; |
362 | union acpi_object *r; |
363 | |
364 | r = lg_wmbb(WMBB_USB_CHARGE, WM_GET, arg2: 0); |
365 | if (!r) |
366 | return -EIO; |
367 | |
368 | if (r->type != ACPI_TYPE_BUFFER) { |
369 | kfree(objp: r); |
370 | return -EIO; |
371 | } |
372 | |
373 | status = !!r->buffer.pointer[0x10]; |
374 | |
375 | kfree(objp: r); |
376 | |
377 | return sysfs_emit(buf: buffer, fmt: "%d\n" , status); |
378 | } |
379 | |
380 | static ssize_t reader_mode_store(struct device *dev, |
381 | struct device_attribute *attr, |
382 | const char *buffer, size_t count) |
383 | { |
384 | bool value; |
385 | union acpi_object *r; |
386 | int ret; |
387 | |
388 | ret = kstrtobool(s: buffer, res: &value); |
389 | if (ret) |
390 | return ret; |
391 | |
392 | r = lg_wmab(WM_READER_MODE, WM_SET, arg2: value); |
393 | if (!r) |
394 | return -EIO; |
395 | |
396 | kfree(objp: r); |
397 | return count; |
398 | } |
399 | |
400 | static ssize_t reader_mode_show(struct device *dev, |
401 | struct device_attribute *attr, char *buffer) |
402 | { |
403 | unsigned int status; |
404 | union acpi_object *r; |
405 | |
406 | r = lg_wmab(WM_READER_MODE, WM_GET, arg2: 0); |
407 | if (!r) |
408 | return -EIO; |
409 | |
410 | if (r->type != ACPI_TYPE_INTEGER) { |
411 | kfree(objp: r); |
412 | return -EIO; |
413 | } |
414 | |
415 | status = !!r->integer.value; |
416 | |
417 | kfree(objp: r); |
418 | |
419 | return sysfs_emit(buf: buffer, fmt: "%d\n" , status); |
420 | } |
421 | |
422 | static ssize_t fn_lock_store(struct device *dev, |
423 | struct device_attribute *attr, |
424 | const char *buffer, size_t count) |
425 | { |
426 | bool value; |
427 | union acpi_object *r; |
428 | int ret; |
429 | |
430 | ret = kstrtobool(s: buffer, res: &value); |
431 | if (ret) |
432 | return ret; |
433 | |
434 | r = lg_wmab(WM_FN_LOCK, WM_SET, arg2: value); |
435 | if (!r) |
436 | return -EIO; |
437 | |
438 | kfree(objp: r); |
439 | return count; |
440 | } |
441 | |
442 | static ssize_t fn_lock_show(struct device *dev, |
443 | struct device_attribute *attr, char *buffer) |
444 | { |
445 | unsigned int status; |
446 | union acpi_object *r; |
447 | |
448 | r = lg_wmab(WM_FN_LOCK, WM_GET, arg2: 0); |
449 | if (!r) |
450 | return -EIO; |
451 | |
452 | if (r->type != ACPI_TYPE_BUFFER) { |
453 | kfree(objp: r); |
454 | return -EIO; |
455 | } |
456 | |
457 | status = !!r->buffer.pointer[0]; |
458 | kfree(objp: r); |
459 | |
460 | return sysfs_emit(buf: buffer, fmt: "%d\n" , status); |
461 | } |
462 | |
463 | static ssize_t charge_control_end_threshold_store(struct device *dev, |
464 | struct device_attribute *attr, |
465 | const char *buf, size_t count) |
466 | { |
467 | unsigned long value; |
468 | int ret; |
469 | |
470 | ret = kstrtoul(s: buf, base: 10, res: &value); |
471 | if (ret) |
472 | return ret; |
473 | |
474 | if (value == 100 || value == 80) { |
475 | union acpi_object *r; |
476 | |
477 | if (battery_limit_use_wmbb) |
478 | r = lg_wmbb(WMBB_BATT_LIMIT, WM_SET, arg2: value); |
479 | else |
480 | r = lg_wmab(WM_BATT_LIMIT, WM_SET, arg2: value); |
481 | if (!r) |
482 | return -EIO; |
483 | |
484 | kfree(objp: r); |
485 | return count; |
486 | } |
487 | |
488 | return -EINVAL; |
489 | } |
490 | |
491 | static ssize_t charge_control_end_threshold_show(struct device *device, |
492 | struct device_attribute *attr, |
493 | char *buf) |
494 | { |
495 | unsigned int status; |
496 | union acpi_object *r; |
497 | |
498 | if (battery_limit_use_wmbb) { |
499 | r = lg_wmbb(WMBB_BATT_LIMIT, WM_GET, arg2: 0); |
500 | if (!r) |
501 | return -EIO; |
502 | |
503 | if (r->type != ACPI_TYPE_BUFFER) { |
504 | kfree(objp: r); |
505 | return -EIO; |
506 | } |
507 | |
508 | status = r->buffer.pointer[0x10]; |
509 | } else { |
510 | r = lg_wmab(WM_BATT_LIMIT, WM_GET, arg2: 0); |
511 | if (!r) |
512 | return -EIO; |
513 | |
514 | if (r->type != ACPI_TYPE_INTEGER) { |
515 | kfree(objp: r); |
516 | return -EIO; |
517 | } |
518 | |
519 | status = r->integer.value; |
520 | } |
521 | kfree(objp: r); |
522 | if (status != 80 && status != 100) |
523 | status = 0; |
524 | |
525 | return sysfs_emit(buf, fmt: "%d\n" , status); |
526 | } |
527 | |
528 | static ssize_t battery_care_limit_show(struct device *dev, |
529 | struct device_attribute *attr, |
530 | char *buffer) |
531 | { |
532 | return charge_control_end_threshold_show(device: dev, attr, buf: buffer); |
533 | } |
534 | |
535 | static ssize_t battery_care_limit_store(struct device *dev, |
536 | struct device_attribute *attr, |
537 | const char *buffer, size_t count) |
538 | { |
539 | return charge_control_end_threshold_store(dev, attr, buf: buffer, count); |
540 | } |
541 | |
542 | static DEVICE_ATTR_RW(fan_mode); |
543 | static DEVICE_ATTR_RW(usb_charge); |
544 | static DEVICE_ATTR_RW(reader_mode); |
545 | static DEVICE_ATTR_RW(fn_lock); |
546 | static DEVICE_ATTR_RW(charge_control_end_threshold); |
547 | static DEVICE_ATTR_RW(battery_care_limit); |
548 | |
549 | static int lg_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) |
550 | { |
551 | if (device_create_file(device: &battery->dev, |
552 | entry: &dev_attr_charge_control_end_threshold)) |
553 | return -ENODEV; |
554 | |
555 | return 0; |
556 | } |
557 | |
558 | static int lg_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook) |
559 | { |
560 | device_remove_file(dev: &battery->dev, |
561 | attr: &dev_attr_charge_control_end_threshold); |
562 | return 0; |
563 | } |
564 | |
565 | static struct acpi_battery_hook battery_hook = { |
566 | .add_battery = lg_battery_add, |
567 | .remove_battery = lg_battery_remove, |
568 | .name = "LG Battery Extension" , |
569 | }; |
570 | |
571 | static struct attribute *dev_attributes[] = { |
572 | &dev_attr_fan_mode.attr, |
573 | &dev_attr_usb_charge.attr, |
574 | &dev_attr_reader_mode.attr, |
575 | &dev_attr_fn_lock.attr, |
576 | &dev_attr_battery_care_limit.attr, |
577 | NULL |
578 | }; |
579 | |
580 | static const struct attribute_group dev_attribute_group = { |
581 | .attrs = dev_attributes, |
582 | }; |
583 | |
584 | static void tpad_led_set(struct led_classdev *cdev, |
585 | enum led_brightness brightness) |
586 | { |
587 | union acpi_object *r; |
588 | |
589 | r = lg_wmab(WM_TLED, WM_SET, arg2: brightness > LED_OFF); |
590 | kfree(objp: r); |
591 | } |
592 | |
593 | static enum led_brightness tpad_led_get(struct led_classdev *cdev) |
594 | { |
595 | return ggov(GOV_TLED) > 0 ? LED_ON : LED_OFF; |
596 | } |
597 | |
598 | static LED_DEVICE(tpad_led, 1, 0); |
599 | |
600 | static void kbd_backlight_set(struct led_classdev *cdev, |
601 | enum led_brightness brightness) |
602 | { |
603 | u32 val; |
604 | union acpi_object *r; |
605 | |
606 | val = 0x22; |
607 | if (brightness <= LED_OFF) |
608 | val = 0; |
609 | if (brightness >= LED_FULL) |
610 | val = 0x24; |
611 | r = lg_wmab(WM_KEY_LIGHT, WM_SET, arg2: val); |
612 | kfree(objp: r); |
613 | } |
614 | |
615 | static enum led_brightness get_kbd_backlight_level(void) |
616 | { |
617 | union acpi_object *r; |
618 | int val; |
619 | |
620 | r = lg_wmab(WM_KEY_LIGHT, WM_GET, arg2: 0); |
621 | |
622 | if (!r) |
623 | return LED_OFF; |
624 | |
625 | if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) { |
626 | kfree(objp: r); |
627 | return LED_OFF; |
628 | } |
629 | |
630 | switch (r->buffer.pointer[0] & 0x27) { |
631 | case 0x24: |
632 | val = LED_FULL; |
633 | break; |
634 | case 0x22: |
635 | val = LED_HALF; |
636 | break; |
637 | default: |
638 | val = LED_OFF; |
639 | } |
640 | |
641 | kfree(objp: r); |
642 | |
643 | return val; |
644 | } |
645 | |
646 | static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) |
647 | { |
648 | return get_kbd_backlight_level(); |
649 | } |
650 | |
651 | static LED_DEVICE(kbd_backlight, 255, LED_BRIGHT_HW_CHANGED); |
652 | |
653 | static void wmi_input_destroy(void) |
654 | { |
655 | if (inited & INIT_INPUT_WMI_2) |
656 | wmi_remove_notify_handler(WMI_EVENT_GUID2); |
657 | |
658 | if (inited & INIT_INPUT_WMI_0) |
659 | wmi_remove_notify_handler(WMI_EVENT_GUID0); |
660 | |
661 | if (inited & INIT_SPARSE_KEYMAP) |
662 | input_unregister_device(wmi_input_dev); |
663 | |
664 | inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP); |
665 | } |
666 | |
667 | static struct platform_driver pf_driver = { |
668 | .driver = { |
669 | .name = PLATFORM_NAME, |
670 | } |
671 | }; |
672 | |
673 | static int acpi_add(struct acpi_device *device) |
674 | { |
675 | int ret; |
676 | const char *product; |
677 | int year = 2017; |
678 | |
679 | if (pf_device) |
680 | return 0; |
681 | |
682 | ret = platform_driver_register(&pf_driver); |
683 | if (ret) |
684 | return ret; |
685 | |
686 | pf_device = platform_device_register_simple(PLATFORM_NAME, |
687 | PLATFORM_DEVID_NONE, |
688 | NULL, num: 0); |
689 | if (IS_ERR(ptr: pf_device)) { |
690 | ret = PTR_ERR(ptr: pf_device); |
691 | pf_device = NULL; |
692 | pr_err("unable to register platform device\n" ); |
693 | goto out_platform_registered; |
694 | } |
695 | product = dmi_get_system_info(field: DMI_PRODUCT_NAME); |
696 | if (product && strlen(product) > 4) |
697 | switch (product[4]) { |
698 | case '5': |
699 | if (strlen(product) > 5) |
700 | switch (product[5]) { |
701 | case 'N': |
702 | year = 2021; |
703 | break; |
704 | case '0': |
705 | year = 2016; |
706 | break; |
707 | default: |
708 | year = 2022; |
709 | } |
710 | break; |
711 | case '6': |
712 | year = 2016; |
713 | break; |
714 | case '7': |
715 | year = 2017; |
716 | break; |
717 | case '8': |
718 | year = 2018; |
719 | break; |
720 | case '9': |
721 | year = 2019; |
722 | break; |
723 | case '0': |
724 | if (strlen(product) > 5) |
725 | switch (product[5]) { |
726 | case 'N': |
727 | year = 2020; |
728 | break; |
729 | case 'P': |
730 | year = 2021; |
731 | break; |
732 | default: |
733 | year = 2022; |
734 | } |
735 | break; |
736 | default: |
737 | year = 2019; |
738 | } |
739 | pr_info("product: %s year: %d\n" , product, year); |
740 | |
741 | if (year >= 2019) |
742 | battery_limit_use_wmbb = 1; |
743 | |
744 | ret = sysfs_create_group(kobj: &pf_device->dev.kobj, grp: &dev_attribute_group); |
745 | if (ret) |
746 | goto out_platform_device; |
747 | |
748 | /* LEDs are optional */ |
749 | led_classdev_register(parent: &pf_device->dev, led_cdev: &kbd_backlight); |
750 | led_classdev_register(parent: &pf_device->dev, led_cdev: &tpad_led); |
751 | |
752 | wmi_input_setup(); |
753 | battery_hook_register(hook: &battery_hook); |
754 | |
755 | return 0; |
756 | |
757 | out_platform_device: |
758 | platform_device_unregister(pf_device); |
759 | out_platform_registered: |
760 | platform_driver_unregister(&pf_driver); |
761 | return ret; |
762 | } |
763 | |
764 | static void acpi_remove(struct acpi_device *device) |
765 | { |
766 | sysfs_remove_group(kobj: &pf_device->dev.kobj, grp: &dev_attribute_group); |
767 | |
768 | led_classdev_unregister(led_cdev: &tpad_led); |
769 | led_classdev_unregister(led_cdev: &kbd_backlight); |
770 | |
771 | battery_hook_unregister(hook: &battery_hook); |
772 | wmi_input_destroy(); |
773 | platform_device_unregister(pf_device); |
774 | pf_device = NULL; |
775 | platform_driver_unregister(&pf_driver); |
776 | } |
777 | |
778 | static const struct acpi_device_id device_ids[] = { |
779 | {"LGEX0815" , 0}, |
780 | {"" , 0} |
781 | }; |
782 | MODULE_DEVICE_TABLE(acpi, device_ids); |
783 | |
784 | static struct acpi_driver acpi_driver = { |
785 | .name = "LG Gram Laptop Support" , |
786 | .class = "lg-laptop" , |
787 | .ids = device_ids, |
788 | .ops = { |
789 | .add = acpi_add, |
790 | .remove = acpi_remove, |
791 | .notify = acpi_notify, |
792 | }, |
793 | .owner = THIS_MODULE, |
794 | }; |
795 | |
796 | static int __init acpi_init(void) |
797 | { |
798 | int result; |
799 | |
800 | result = acpi_bus_register_driver(driver: &acpi_driver); |
801 | if (result < 0) { |
802 | pr_debug("Error registering driver\n" ); |
803 | return -ENODEV; |
804 | } |
805 | |
806 | return 0; |
807 | } |
808 | |
809 | static void __exit acpi_exit(void) |
810 | { |
811 | acpi_bus_unregister_driver(driver: &acpi_driver); |
812 | } |
813 | |
814 | module_init(acpi_init); |
815 | module_exit(acpi_exit); |
816 | |