aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatan Ziv-Av <matan@svgalib.org>2018-09-28 10:34:06 -0400
committerAndy Shevchenko <andriy.shevchenko@linux.intel.com>2018-10-19 12:21:57 -0400
commitdbf0c5a6b1f8e7bec5e17baa60a1e04c28d90f9b (patch)
treebeb9d919d4707d7c7fe3cbbea34aae20b7fbd651
parent9fe44fc98ce45fe42004be9fd282775030c6b147 (diff)
platform/x86: Add LG Gram laptop special features driver
A driver for LG Gram laptop supporting features not available through the standard interfaces: - Support for the 5 Fn keys that generate ACPI or WMI events. - Two software controlled LEDs: keyboard backlight (also controlled by hardware) and touchpad LED. - Extra features: reader mode, Fn lock, cooling mode, USB charge mode, and maximal battery charging level. Signed-off-by: Matan Ziv-Av <matan@svgalib.org> Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
-rw-r--r--Documentation/ABI/testing/sysfs-platform-lg-laptop35
-rw-r--r--Documentation/laptops/lg-laptop.rst81
-rw-r--r--MAINTAINERS8
-rw-r--r--drivers/platform/x86/Kconfig14
-rw-r--r--drivers/platform/x86/Makefile1
-rw-r--r--drivers/platform/x86/lg-laptop.c700
6 files changed, 839 insertions, 0 deletions
diff --git a/Documentation/ABI/testing/sysfs-platform-lg-laptop b/Documentation/ABI/testing/sysfs-platform-lg-laptop
new file mode 100644
index 000000000000..cf47749b19df
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-lg-laptop
@@ -0,0 +1,35 @@
1What: /sys/devices/platform/lg-laptop/reader_mode
2Date: October 2018
3KernelVersion: 4.20
4Contact: "Matan Ziv-Av <matan@svgalib.org>
5Description:
6 Control reader mode. 1 means on, 0 means off.
7
8What: /sys/devices/platform/lg-laptop/fn_lock
9Date: October 2018
10KernelVersion: 4.20
11Contact: "Matan Ziv-Av <matan@svgalib.org>
12Description:
13 Control FN lock mode. 1 means on, 0 means off.
14
15What: /sys/devices/platform/lg-laptop/battery_care_limit
16Date: October 2018
17KernelVersion: 4.20
18Contact: "Matan Ziv-Av <matan@svgalib.org>
19Description:
20 Maximal battery charge level. Accepted values are 80 or 100.
21
22What: /sys/devices/platform/lg-laptop/fan_mode
23Date: October 2018
24KernelVersion: 4.20
25Contact: "Matan Ziv-Av <matan@svgalib.org>
26Description:
27 Control fan mode. 1 for performance mode, 0 for silent mode.
28
29What: /sys/devices/platform/lg-laptop/usb_charge
30Date: October 2018
31KernelVersion: 4.20
32Contact: "Matan Ziv-Av <matan@svgalib.org>
33Description:
34 Control USB port charging when device is turned off.
35 1 means on, 0 means off.
diff --git a/Documentation/laptops/lg-laptop.rst b/Documentation/laptops/lg-laptop.rst
new file mode 100644
index 000000000000..e486fe7ddc35
--- /dev/null
+++ b/Documentation/laptops/lg-laptop.rst
@@ -0,0 +1,81 @@
1.. SPDX-License-Identifier: GPL-2.0+
2LG Gram laptop extra features
3=============================
4
5By Matan Ziv-Av <matan@svgalib.org>
6
7
8Hotkeys
9-------
10
11The following FN keys are ignored by the kernel without this driver:
12- FN-F1 (LG control panel) - Generates F15
13- FN-F5 (Touchpad toggle) - Generates F13
14- FN-F6 (Airplane mode) - Generates RFKILL
15- FN-F8 (Keyboard backlight) - Generates F16.
16 This key also changes keyboard backlight mode.
17- FN-F9 (Reader mode) - Generates F14
18
19The rest of the FN key work without a need for a special driver.
20
21
22Reader mode
23-----------
24
25Writing 0/1 to /sys/devices/platform/lg-laptop/reader_mode disables/enables
26reader mode. In this mode the screen colors change (blue color reduced),
27and the reader mode indicator LED (on F9 key) turns on.
28
29
30FN Lock
31-------
32
33Writing 0/1 to /sys/devices/platform/lg-laptop/fn_lock disables/enables
34FN lock.
35
36
37Battery care limit
38------------------
39
40Writing 80/100 to /sys/devices/platform/lg-laptop/battery_care_limit
41sets the maximum capacity to charge the battery. Limiting the charge
42reduces battery capacity loss over time.
43
44This value is reset to 100 when the kernel boots.
45
46
47Fan mode
48--------
49
50Writing 1/0 to /sys/devices/platform/lg-laptop/fan_mode disables/enables
51the fan silent mode.
52
53
54USB charge
55----------
56
57Writing 0/1 to /sys/devices/platform/lg-laptop/usb_charge disables/enables
58charging another device from the USB port while the device is turned off.
59
60This value is reset to 0 when the kernel boots.
61
62
63LEDs
64~~~~
65
66The are two LED devices supported by the driver:
67
68Keyboard backlight
69------------------
70
71A led device named kbd_led controls the keyboard backlight. There are three
72lighting level: off (0), low (127) and high (255).
73
74The keyboard backlight is also controlled by the key combination FN-F8
75which cycles through those levels.
76
77
78Touchpad indicator LED
79----------------------
80
81On the F5 key. Controlled by led device names tpad_led.
diff --git a/MAINTAINERS b/MAINTAINERS
index 257f0aa8af45..09d697430c8b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8231,6 +8231,14 @@ W: http://legousb.sourceforge.net/
8231S: Maintained 8231S: Maintained
8232F: drivers/usb/misc/legousbtower.c 8232F: drivers/usb/misc/legousbtower.c
8233 8233
8234LG LAPTOP EXTRAS
8235M: Matan Ziv-Av <matan@svgalib.org>
8236L: platform-driver-x86@vger.kernel.org
8237S: Maintained
8238F: Documentation/ABI/testing/sysfs-platform-lg-laptop
8239F: Documentation/laptops/lg-laptop.rst
8240F: drivers/platform/x86/lg-laptop.c
8241
8234LG2160 MEDIA DRIVER 8242LG2160 MEDIA DRIVER
8235M: Michael Krufky <mkrufky@linuxtv.org> 8243M: Michael Krufky <mkrufky@linuxtv.org>
8236L: linux-media@vger.kernel.org 8244L: linux-media@vger.kernel.org
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 4283239115ca..1991608549d5 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -367,6 +367,20 @@ config HP_WMI
367 To compile this driver as a module, choose M here: the module will 367 To compile this driver as a module, choose M here: the module will
368 be called hp-wmi. 368 be called hp-wmi.
369 369
370config LG_LAPTOP
371 tristate "LG Laptop Extras"
372 depends on ACPI
373 depends on ACPI_WMI
374 depends on INPUT
375 select INPUT_SPARSEKMAP
376 select LEDS_CLASS
377 help
378 This driver adds support for hotkeys as well as control of keyboard
379 backlight, battery maximum charge level and various other ACPI
380 features.
381
382 If you have an LG Gram laptop, say Y or M here.
383
370config MSI_LAPTOP 384config MSI_LAPTOP
371 tristate "MSI Laptop Extras" 385 tristate "MSI Laptop Extras"
372 depends on ACPI 386 depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 4e2712c9c0b0..395c8ff12bd1 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_ASUS_NB_WMI) += asus-nb-wmi.o
9obj-$(CONFIG_ASUS_WIRELESS) += asus-wireless.o 9obj-$(CONFIG_ASUS_WIRELESS) += asus-wireless.o
10obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o 10obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o
11obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o 11obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o
12obj-$(CONFIG_LG_LAPTOP) += lg-laptop.o
12obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o 13obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
13obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o 14obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o
14obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o 15obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o
diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c
new file mode 100644
index 000000000000..c0bb1f864dfe
--- /dev/null
+++ b/drivers/platform/x86/lg-laptop.c
@@ -0,0 +1,700 @@
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/input.h>
12#include <linux/input/sparse-keymap.h>
13#include <linux/kernel.h>
14#include <linux/leds.h>
15#include <linux/module.h>
16#include <linux/platform_device.h>
17#include <linux/types.h>
18
19#define LED_DEVICE(_name, max) struct led_classdev _name = { \
20 .name = __stringify(_name), \
21 .max_brightness = max, \
22 .brightness_set = _name##_set, \
23 .brightness_get = _name##_get, \
24}
25
26MODULE_AUTHOR("Matan Ziv-Av");
27MODULE_DESCRIPTION("LG WMI Hotkey Driver");
28MODULE_LICENSE("GPL");
29
30#define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248"
31#define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2"
32#define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6"
33#define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B"
34#define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D"
35#define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210"
36#define WMI_EVENT_GUID WMI_EVENT_GUID0
37
38#define WMAB_METHOD "\\XINI.WMAB"
39#define WMBB_METHOD "\\XINI.WMBB"
40#define SB_GGOV_METHOD "\\_SB.GGOV"
41#define GOV_TLED 0x2020008
42#define WM_GET 1
43#define WM_SET 2
44#define WM_KEY_LIGHT 0x400
45#define WM_TLED 0x404
46#define WM_FN_LOCK 0x407
47#define WM_BATT_LIMIT 0x61
48#define WM_READER_MODE 0xBF
49#define WM_FAN_MODE 0x33
50#define WMBB_USB_CHARGE 0x10B
51#define WMBB_BATT_LIMIT 0x10C
52
53#define PLATFORM_NAME "lg-laptop"
54
55MODULE_ALIAS("wmi:" WMI_EVENT_GUID0);
56MODULE_ALIAS("wmi:" WMI_EVENT_GUID1);
57MODULE_ALIAS("wmi:" WMI_EVENT_GUID2);
58MODULE_ALIAS("wmi:" WMI_EVENT_GUID3);
59MODULE_ALIAS("wmi:" WMI_METHOD_WMAB);
60MODULE_ALIAS("wmi:" WMI_METHOD_WMBB);
61MODULE_ALIAS("acpi*:LGEX0815:*");
62
63static struct platform_device *pf_device;
64static struct input_dev *wmi_input_dev;
65
66static u32 inited;
67#define INIT_INPUT_WMI_0 0x01
68#define INIT_INPUT_WMI_2 0x02
69#define INIT_INPUT_ACPI 0x04
70#define INIT_TPAD_LED 0x08
71#define INIT_KBD_LED 0x10
72#define INIT_SPARSE_KEYMAP 0x80
73
74static const struct key_entry wmi_keymap[] = {
75 {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */
76 {KE_KEY, 0x74, {KEY_F13} }, /* Touchpad toggle (F5) */
77 {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */
78 {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing
79 * this key both sends an event and
80 * changes backlight level.
81 */
82 {KE_KEY, 0x80, {KEY_RFKILL} },
83 {KE_END, 0}
84};
85
86static int ggov(u32 arg0)
87{
88 union acpi_object args[1];
89 union acpi_object *r;
90 acpi_status status;
91 acpi_handle handle;
92 struct acpi_object_list arg;
93 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
94 int res;
95
96 args[0].type = ACPI_TYPE_INTEGER;
97 args[0].integer.value = arg0;
98
99 status = acpi_get_handle(NULL, (acpi_string) SB_GGOV_METHOD, &handle);
100 if (ACPI_FAILURE(status)) {
101 pr_err("Cannot get handle");
102 return -ENODEV;
103 }
104
105 arg.count = 1;
106 arg.pointer = args;
107
108 status = acpi_evaluate_object(handle, NULL, &arg, &buffer);
109 if (ACPI_FAILURE(status)) {
110 acpi_handle_err(handle, "GGOV: call failed.\n");
111 return -EINVAL;
112 }
113
114 r = buffer.pointer;
115 if (r->type != ACPI_TYPE_INTEGER) {
116 kfree(r);
117 return -EINVAL;
118 }
119
120 res = r->integer.value;
121 kfree(r);
122
123 return res;
124}
125
126static union acpi_object *lg_wmab(u32 method, u32 arg1, u32 arg2)
127{
128 union acpi_object args[3];
129 acpi_status status;
130 acpi_handle handle;
131 struct acpi_object_list arg;
132 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
133
134 args[0].type = ACPI_TYPE_INTEGER;
135 args[0].integer.value = method;
136 args[1].type = ACPI_TYPE_INTEGER;
137 args[1].integer.value = arg1;
138 args[2].type = ACPI_TYPE_INTEGER;
139 args[2].integer.value = arg2;
140
141 status = acpi_get_handle(NULL, (acpi_string) WMAB_METHOD, &handle);
142 if (ACPI_FAILURE(status)) {
143 pr_err("Cannot get handle");
144 return NULL;
145 }
146
147 arg.count = 3;
148 arg.pointer = args;
149
150 status = acpi_evaluate_object(handle, NULL, &arg, &buffer);
151 if (ACPI_FAILURE(status)) {
152 acpi_handle_err(handle, "WMAB: call failed.\n");
153 return NULL;
154 }
155
156 return buffer.pointer;
157}
158
159static union acpi_object *lg_wmbb(u32 method_id, u32 arg1, u32 arg2)
160{
161 union acpi_object args[3];
162 acpi_status status;
163 acpi_handle handle;
164 struct acpi_object_list arg;
165 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
166 u8 buf[32];
167
168 *(u32 *)buf = method_id;
169 *(u32 *)(buf + 4) = arg1;
170 *(u32 *)(buf + 16) = arg2;
171 args[0].type = ACPI_TYPE_INTEGER;
172 args[0].integer.value = 0; /* ignored */
173 args[1].type = ACPI_TYPE_INTEGER;
174 args[1].integer.value = 1; /* Must be 1 or 2. Does not matter which */
175 args[2].type = ACPI_TYPE_BUFFER;
176 args[2].buffer.length = 32;
177 args[2].buffer.pointer = buf;
178
179 status = acpi_get_handle(NULL, (acpi_string)WMBB_METHOD, &handle);
180 if (ACPI_FAILURE(status)) {
181 pr_err("Cannot get handle");
182 return NULL;
183 }
184
185 arg.count = 3;
186 arg.pointer = args;
187
188 status = acpi_evaluate_object(handle, NULL, &arg, &buffer);
189 if (ACPI_FAILURE(status)) {
190 acpi_handle_err(handle, "WMAB: call failed.\n");
191 return NULL;
192 }
193
194 return (union acpi_object *)buffer.pointer;
195}
196
197static void wmi_notify(u32 value, void *context)
198{
199 struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
200 union acpi_object *obj;
201 acpi_status status;
202 long data = (long)context;
203
204 pr_debug("event guid %li\n", data);
205 status = wmi_get_event_data(value, &response);
206 if (ACPI_FAILURE(status)) {
207 pr_err("Bad event status 0x%x\n", status);
208 return;
209 }
210
211 obj = (union acpi_object *)response.pointer;
212 if (!obj)
213 return;
214
215 if (obj->type == ACPI_TYPE_INTEGER) {
216 int eventcode = obj->integer.value;
217 struct key_entry *key;
218
219 key =
220 sparse_keymap_entry_from_scancode(wmi_input_dev, eventcode);
221 if (key && key->type == KE_KEY)
222 sparse_keymap_report_entry(wmi_input_dev, key, 1, true);
223 }
224
225 pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type,
226 obj->integer.value);
227 kfree(response.pointer);
228}
229
230static void wmi_input_setup(void)
231{
232 acpi_status status;
233
234 wmi_input_dev = input_allocate_device();
235 if (wmi_input_dev) {
236 wmi_input_dev->name = "LG WMI hotkeys";
237 wmi_input_dev->phys = "wmi/input0";
238 wmi_input_dev->id.bustype = BUS_HOST;
239
240 if (sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL) ||
241 input_register_device(wmi_input_dev)) {
242 pr_info("Cannot initialize input device");
243 input_free_device(wmi_input_dev);
244 return;
245 }
246
247 inited |= INIT_SPARSE_KEYMAP;
248 status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify,
249 (void *)0);
250 if (ACPI_SUCCESS(status))
251 inited |= INIT_INPUT_WMI_0;
252
253 status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify,
254 (void *)2);
255 if (ACPI_SUCCESS(status))
256 inited |= INIT_INPUT_WMI_2;
257 } else {
258 pr_info("Cannot allocate input device");
259 }
260}
261
262static void acpi_notify(struct acpi_device *device, u32 event)
263{
264 struct key_entry *key;
265
266 acpi_handle_debug(device->handle, "notify: %d\n", event);
267 if (inited & INIT_SPARSE_KEYMAP) {
268 key = sparse_keymap_entry_from_scancode(wmi_input_dev, 0x80);
269 if (key && key->type == KE_KEY)
270 sparse_keymap_report_entry(wmi_input_dev, key, 1, true);
271 }
272}
273
274static ssize_t fan_mode_store(struct device *dev,
275 struct device_attribute *attr,
276 const char *buffer, size_t count)
277{
278 bool value;
279 union acpi_object *r;
280 u32 m;
281 int ret;
282
283 ret = kstrtobool(buffer, &value);
284 if (ret)
285 return ret;
286
287 r = lg_wmab(WM_FAN_MODE, WM_GET, 0);
288 if (!r)
289 return -EIO;
290
291 if (r->type != ACPI_TYPE_INTEGER) {
292 kfree(r);
293 return -EIO;
294 }
295
296 m = r->integer.value;
297 kfree(r);
298 r = lg_wmab(WM_FAN_MODE, WM_SET, (m & 0xffffff0f) | (value << 4));
299 kfree(r);
300 r = lg_wmab(WM_FAN_MODE, WM_SET, (m & 0xfffffff0) | value);
301 kfree(r);
302
303 return count;
304}
305
306static ssize_t fan_mode_show(struct device *dev,
307 struct device_attribute *attr, char *buffer)
308{
309 unsigned int status;
310 union acpi_object *r;
311
312 r = lg_wmab(WM_FAN_MODE, WM_GET, 0);
313 if (!r)
314 return -EIO;
315
316 if (r->type != ACPI_TYPE_INTEGER) {
317 kfree(r);
318 return -EIO;
319 }
320
321 status = r->integer.value & 0x01;
322 kfree(r);
323
324 return snprintf(buffer, PAGE_SIZE, "%d\n", status);
325}
326
327static ssize_t usb_charge_store(struct device *dev,
328 struct device_attribute *attr,
329 const char *buffer, size_t count)
330{
331 bool value;
332 union acpi_object *r;
333 int ret;
334
335 ret = kstrtobool(buffer, &value);
336 if (ret)
337 return ret;
338
339 r = lg_wmbb(WMBB_USB_CHARGE, WM_SET, value);
340 if (!r)
341 return -EIO;
342
343 kfree(r);
344 return count;
345}
346
347static ssize_t usb_charge_show(struct device *dev,
348 struct device_attribute *attr, char *buffer)
349{
350 unsigned int status;
351 union acpi_object *r;
352
353 r = lg_wmbb(WMBB_USB_CHARGE, WM_GET, 0);
354 if (!r)
355 return -EIO;
356
357 if (r->type != ACPI_TYPE_BUFFER) {
358 kfree(r);
359 return -EIO;
360 }
361
362 status = !!r->buffer.pointer[0x10];
363
364 kfree(r);
365
366 return snprintf(buffer, PAGE_SIZE, "%d\n", status);
367}
368
369static ssize_t reader_mode_store(struct device *dev,
370 struct device_attribute *attr,
371 const char *buffer, size_t count)
372{
373 bool value;
374 union acpi_object *r;
375 int ret;
376
377 ret = kstrtobool(buffer, &value);
378 if (ret)
379 return ret;
380
381 r = lg_wmab(WM_READER_MODE, WM_SET, value);
382 if (!r)
383 return -EIO;
384
385 kfree(r);
386 return count;
387}
388
389static ssize_t reader_mode_show(struct device *dev,
390 struct device_attribute *attr, char *buffer)
391{
392 unsigned int status;
393 union acpi_object *r;
394
395 r = lg_wmab(WM_READER_MODE, WM_GET, 0);
396 if (!r)
397 return -EIO;
398
399 if (r->type != ACPI_TYPE_INTEGER) {
400 kfree(r);
401 return -EIO;
402 }
403
404 status = !!r->integer.value;
405
406 kfree(r);
407
408 return snprintf(buffer, PAGE_SIZE, "%d\n", status);
409}
410
411static ssize_t fn_lock_store(struct device *dev,
412 struct device_attribute *attr,
413 const char *buffer, size_t count)
414{
415 bool value;
416 union acpi_object *r;
417 int ret;
418
419 ret = kstrtobool(buffer, &value);
420 if (ret)
421 return ret;
422
423 r = lg_wmab(WM_FN_LOCK, WM_SET, value);
424 if (!r)
425 return -EIO;
426
427 kfree(r);
428 return count;
429}
430
431static ssize_t fn_lock_show(struct device *dev,
432 struct device_attribute *attr, char *buffer)
433{
434 unsigned int status;
435 union acpi_object *r;
436
437 r = lg_wmab(WM_FN_LOCK, WM_GET, 0);
438 if (!r)
439 return -EIO;
440
441 if (r->type != ACPI_TYPE_BUFFER) {
442 kfree(r);
443 return -EIO;
444 }
445
446 status = !!r->buffer.pointer[0];
447 kfree(r);
448
449 return snprintf(buffer, PAGE_SIZE, "%d\n", status);
450}
451
452static ssize_t battery_care_limit_store(struct device *dev,
453 struct device_attribute *attr,
454 const char *buffer, size_t count)
455{
456 unsigned long value;
457 int ret;
458
459 ret = kstrtoul(buffer, 10, &value);
460 if (ret)
461 return ret;
462
463 if (value == 100 || value == 80) {
464 union acpi_object *r;
465
466 r = lg_wmab(WM_BATT_LIMIT, WM_SET, value);
467 if (!r)
468 return -EIO;
469
470 kfree(r);
471 return count;
472 }
473
474 return -EINVAL;
475}
476
477static ssize_t battery_care_limit_show(struct device *dev,
478 struct device_attribute *attr,
479 char *buffer)
480{
481 unsigned int status;
482 union acpi_object *r;
483
484 r = lg_wmab(WM_BATT_LIMIT, WM_GET, 0);
485 if (!r)
486 return -EIO;
487
488 if (r->type != ACPI_TYPE_INTEGER) {
489 kfree(r);
490 return -EIO;
491 }
492
493 status = r->integer.value;
494 kfree(r);
495 if (status != 80 && status != 100)
496 status = 0;
497
498 return snprintf(buffer, PAGE_SIZE, "%d\n", status);
499}
500
501static DEVICE_ATTR_RW(fan_mode);
502static DEVICE_ATTR_RW(usb_charge);
503static DEVICE_ATTR_RW(reader_mode);
504static DEVICE_ATTR_RW(fn_lock);
505static DEVICE_ATTR_RW(battery_care_limit);
506
507static struct attribute *dev_attributes[] = {
508 &dev_attr_fan_mode.attr,
509 &dev_attr_usb_charge.attr,
510 &dev_attr_reader_mode.attr,
511 &dev_attr_fn_lock.attr,
512 &dev_attr_battery_care_limit.attr,
513 NULL
514};
515
516static const struct attribute_group dev_attribute_group = {
517 .attrs = dev_attributes,
518};
519
520static void tpad_led_set(struct led_classdev *cdev,
521 enum led_brightness brightness)
522{
523 union acpi_object *r;
524
525 r = lg_wmab(WM_TLED, WM_SET, brightness > LED_OFF);
526 kfree(r);
527}
528
529static enum led_brightness tpad_led_get(struct led_classdev *cdev)
530{
531 return ggov(GOV_TLED) > 0 ? LED_ON : LED_OFF;
532}
533
534static LED_DEVICE(tpad_led, 1);
535
536static void kbd_backlight_set(struct led_classdev *cdev,
537 enum led_brightness brightness)
538{
539 u32 val;
540 union acpi_object *r;
541
542 val = 0x22;
543 if (brightness <= LED_OFF)
544 val = 0;
545 if (brightness >= LED_FULL)
546 val = 0x24;
547 r = lg_wmab(WM_KEY_LIGHT, WM_SET, val);
548 kfree(r);
549}
550
551static enum led_brightness kbd_backlight_get(struct led_classdev *cdev)
552{
553 union acpi_object *r;
554 int val;
555
556 r = lg_wmab(WM_KEY_LIGHT, WM_GET, 0);
557
558 if (!r)
559 return LED_OFF;
560
561 if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) {
562 kfree(r);
563 return LED_OFF;
564 }
565
566 switch (r->buffer.pointer[0] & 0x27) {
567 case 0x24:
568 val = LED_FULL;
569 break;
570 case 0x22:
571 val = LED_HALF;
572 break;
573 default:
574 val = LED_OFF;
575 }
576
577 kfree(r);
578
579 return val;
580}
581
582static LED_DEVICE(kbd_backlight, 255);
583
584static void wmi_input_destroy(void)
585{
586 if (inited & INIT_INPUT_WMI_2)
587 wmi_remove_notify_handler(WMI_EVENT_GUID2);
588
589 if (inited & INIT_INPUT_WMI_0)
590 wmi_remove_notify_handler(WMI_EVENT_GUID0);
591
592 if (inited & INIT_SPARSE_KEYMAP)
593 input_unregister_device(wmi_input_dev);
594
595 inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP);
596}
597
598static struct platform_driver pf_driver = {
599 .driver = {
600 .name = PLATFORM_NAME,
601 }
602};
603
604static int acpi_add(struct acpi_device *device)
605{
606 int ret;
607
608 if (pf_device)
609 return 0;
610
611 ret = platform_driver_register(&pf_driver);
612 if (ret)
613 return ret;
614
615 pf_device = platform_device_register_simple(PLATFORM_NAME,
616 PLATFORM_DEVID_NONE,
617 NULL, 0);
618 if (IS_ERR(pf_device)) {
619 ret = PTR_ERR(pf_device);
620 pf_device = NULL;
621 pr_err("unable to register platform device\n");
622 goto out_platform_registered;
623 }
624
625 ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group);
626 if (ret)
627 goto out_platform_device;
628
629 if (!led_classdev_register(&pf_device->dev, &kbd_backlight))
630 inited |= INIT_KBD_LED;
631
632 if (!led_classdev_register(&pf_device->dev, &tpad_led))
633 inited |= INIT_TPAD_LED;
634
635 wmi_input_setup();
636
637 return 0;
638
639out_platform_device:
640 platform_device_unregister(pf_device);
641out_platform_registered:
642 platform_driver_unregister(&pf_driver);
643 return ret;
644}
645
646static int acpi_remove(struct acpi_device *device)
647{
648 sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group);
649 if (inited & INIT_KBD_LED)
650 led_classdev_unregister(&kbd_backlight);
651
652 if (inited & INIT_TPAD_LED)
653 led_classdev_unregister(&tpad_led);
654
655 wmi_input_destroy();
656 platform_device_unregister(pf_device);
657 pf_device = NULL;
658 platform_driver_unregister(&pf_driver);
659
660 return 0;
661}
662
663static const struct acpi_device_id device_ids[] = {
664 {"LGEX0815", 0},
665 {"", 0}
666};
667MODULE_DEVICE_TABLE(acpi, device_ids);
668
669static struct acpi_driver acpi_driver = {
670 .name = "LG Gram Laptop Support",
671 .class = "lg-laptop",
672 .ids = device_ids,
673 .ops = {
674 .add = acpi_add,
675 .remove = acpi_remove,
676 .notify = acpi_notify,
677 },
678 .owner = THIS_MODULE,
679};
680
681static int __init acpi_init(void)
682{
683 int result;
684
685 result = acpi_bus_register_driver(&acpi_driver);
686 if (result < 0) {
687 ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error registering driver\n"));
688 return -ENODEV;
689 }
690
691 return 0;
692}
693
694static void __exit acpi_exit(void)
695{
696 acpi_bus_unregister_driver(&acpi_driver);
697}
698
699module_init(acpi_init);
700module_exit(acpi_exit);