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