On Mon, 2 Apr 2018, Andy Shevchenko wrote:
> > You have too many subtle issues, such as:
> > - redundant empty lines
> > - missed empty lines
> > - redundant conditionals (before kstrtox() call)
> > - shadowing returned error codes (kstrtox() usage)
> > - etc, I even would not like to look at
> >
> > So, please, run checkpatch.pl and see if it complains about anything.
I fixed some of the above issues, and it passes checkpatch.pl.
> >
> > Besides that, the approaches you choose might be old or racy:
> > - device attribute handling (use sysfs attribute group)
I use those now.
> > - btw, where is the ABI description?
Added.
> > Why did you choose so complex way to instantiate a driver? All those
I guess that this question is rhetorical, but if not, I wrote this
driver mainly by cutting and pasting from other drivers, and layer by
layer (hot keys, then leds, then control devices).
> > ACPI handlers and additional burden for platform device...
> > Can it be simplified?
I simplified it a little.
> > Can you utilize WMI bus as much as possible.
Of the five hot keys, four are reported as WMI events, but one only as
an ACPI event, so I don't think it is possible to improve there.
The method used for control (WMAB), has three integer inputs, but as far
as I understand wmi_evaluate_method(), the third parameter there is only
a buffer or a string, so I cannot use WMI to call this method.
The touchpas LED status is not available by any method call, but only by
directly reading the corresponding register, so there it cannot be
improved either.
> Matan, do you have any updates on the driver?
I am sorry if I do not follow the exact protocol of this list.
I attach a patch representing my current tree. I do not post a new
version of the patch, as I did not address all comments yet (mainly
simplifying and converting documentation to RST format).
--
Matan.
diff -X dontdiff -urN a/Documentation/ABI/testing/sysfs-platform-lg-laptop linux-4.16-rc5/Documentation/ABI/testing/sysfs-platform-lg-laptop
--- a/Documentation/ABI/testing/sysfs-platform-lg-laptop 1970-01-01 02:00:00.000000000 +0200
+++ linux-4.16-rc5/Documentation/ABI/testing/sysfs-platform-lg-laptop 2018-04-03 14:19:53.700915068 +0300
@@ -0,0 +1,27 @@
+What: /sys/devices/platform/lg-laptop/reader_mode
+Date: April 2018
+KernelVersion: 4.16
+Contact: "Matan Ziv-Av <matan@xxxxxxxxxxx>
+Description:
+ Control reader mode. 1 means on, 0 means off.
+
+What: /sys/devices/platform/lg-laptop/fn_lock
+Date: April 2018
+KernelVersion: 4.16
+Contact: "Matan Ziv-Av <matan@xxxxxxxxxxx>
+Description:
+ Control FN lock mode. 1 means on, 0 means off.
+
+What: /sys/devices/platform/lg-laptop/battery_care_limit
+Date: April 2018
+KernelVersion: 4.16
+Contact: "Matan Ziv-Av <matan@xxxxxxxxxxx>
+Description:
+ Maximal battery charge level. Accepted values are 80 or 100.
+
+What: /sys/devices/platform/lg-laptop/fan_mode
+Date: April 2018
+KernelVersion: 4.16
+Contact: "Matan Ziv-Av <matan@xxxxxxxxxxx>
+Description:
+ Control fan mode. 1 for performance mode, 0 for silent mode.
diff -X dontdiff -urN a/Documentation/laptops/lg-laptop.txt linux-4.16-rc5/Documentation/laptops/lg-laptop.txt
--- a/Documentation/laptops/lg-laptop.txt 1970-01-01 02:00:00.000000000 +0200
+++ linux-4.16-rc5/Documentation/laptops/lg-laptop.txt 2018-04-03 16:03:40.955067802 +0300
@@ -0,0 +1,69 @@
+LG Gram laptop extra features
+=============================
+
+By Matan Ziv-Av <matan@xxxxxxxxxxx>
+
+
+Hotkeys
+-------
+
+The following FN keys are ignored by the kernel without this driver:
+ FN-F1 (LG control panel) - Generates F15
+ FN-F5 (Touchpad toggle) - Generates F13
+ FN-F6 (Airplane mode) - Generates RFKILL
+ FN-F8 (Keyboard backlight) - Generates F16. This key also changes keyboard
+ backlight mode.
+ FN-F9 (Reader mode) - Generates F14
+
+The rest of the FN key work without a need for a special driver.
+
+
+Reader mode
+-----------
+
+Writing 0/1 to /sys/devices/platform/lg-laptop/reader_mode disables/enables
+reader mode. In this mode the screen colors change (blue color reduced),
+and the reader mode indicator LED (on F9 key) turns on.
+
+
+FN Lock
+-------
+
+Writing 0/1 to /sys/devices/platform/lg-laptop/fn_lock disables/enables
+FN lock.
+
+
+Battery care limit
+------------------
+
+Writing 80/100 to /sys/devices/platform/lg-laptop/battery_care_limit
+sets the maximum capacity to charge the battery. Limiting the charge
+reduces battery capacity loss over time.
+
+This value is reset to 100 when the kernel boots.
+
+
+Fan mode
+--------
+
+Writing 1/0 to /sys/devices/platform/lg-laptop/fan_mode disables/enables
+the fan silent mode.
+
+
+
+The are two LED devices supported by the driver:
+
+Keyboard backlight
+------------------
+
+A led device named kbd_led controls the keyboard backlight. There are three
+lighting level: off (0), low (127) and high (255).
+
+The keyboard backlight is also controlled by the key combination FN-F8
+which cycles through those levels.
+
+
+Touchpad indicator LED
+----------------------
+
+On the F5 key. Controlled by led device names tpad_led.
diff -X dontdiff -urN a/MAINTAINERS linux-4.16-rc5/MAINTAINERS
--- a/MAINTAINERS 2018-03-12 02:25:09.000000000 +0200
+++ linux-4.16-rc5/MAINTAINERS 2018-03-12 17:54:40.374725379 +0200
@@ -7935,6 +7935,12 @@
S: Maintained
F: drivers/usb/misc/legousbtower.c
+LG LAPTOP EXTRAS
+M: Matan Ziv-Av <matan@xxxxxxxxxxx>
+L: platform-driver-x86@xxxxxxxxxxxxxxx
+S: Maintained
+F: drivers/platform/x86/lg-laptop.c
+
LG2160 MEDIA DRIVER
M: Michael Krufky <mkrufky@xxxxxxxxxxx>
L: linux-media@xxxxxxxxxxxxxxx
diff -X dontdiff -urN a/drivers/platform/x86/Kconfig linux-4.16-rc5/drivers/platform/x86/Kconfig
--- a/drivers/platform/x86/Kconfig 2018-03-12 02:25:09.000000000 +0200
+++ linux-4.16-rc5/drivers/platform/x86/Kconfig 2018-03-17 18:32:17.067534620 +0200
@@ -335,6 +335,20 @@
To compile this driver as a module, choose M here: the module will
be called hp-wmi.
+config LG_LAPTOP
+ tristate "LG Laptop Extras"
+ depends on ACPI
+ depends on ACPI_WMI
+ depends on INPUT
+ select INPUT_SPARSEKMAP
+ select LEDS_CLASS
+ ---help---
+ This driver adds support for hotkeys as well as control of keyboard
+ backlight, battery maximum charge level and various other ACPI
+ features.
+
+ If you have an LG Gram laptop, say Y or M here.
+
config MSI_LAPTOP
tristate "MSI Laptop Extras"
depends on ACPI
diff -X dontdiff -urN a/drivers/platform/x86/Makefile linux-4.16-rc5/drivers/platform/x86/Makefile
--- a/drivers/platform/x86/Makefile 2018-03-12 02:25:09.000000000 +0200
+++ linux-4.16-rc5/drivers/platform/x86/Makefile 2018-03-12 14:41:32.025441156 +0200
@@ -9,6 +9,7 @@
obj-$(CONFIG_ASUS_WIRELESS) += asus-wireless.o
obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o
obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o
+obj-$(CONFIG_LG_LAPTOP) += lg-laptop.o
obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o
obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o
diff -X dontdiff -urN a/drivers/platform/x86/lg-laptop.c linux-4.16-rc5/drivers/platform/x86/lg-laptop.c
--- a/drivers/platform/x86/lg-laptop.c 1970-01-01 02:00:00.000000000 +0200
+++ linux-4.16-rc5/drivers/platform/x86/lg-laptop.c 2018-04-03 16:01:29.181064570 +0300
@@ -0,0 +1,660 @@
+/*
+ * lg_wmi.c - LG Gram ACPI features and hotkeys Driver
+ *
+ * Copyright (C) 2018 Matan Ziv-Av <matan@xxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+#include <linux/leds.h>
+
+#define LED_DEVICE(_name, max) struct led_classdev _name = { \
+ .name = __stringify(_name), \
+ .max_brightness = max, \
+ .brightness_set = _name##_set, \
+ .brightness_get = _name##_get, \
+}
+
+MODULE_AUTHOR("Matan Ziv-Av");
+MODULE_DESCRIPTION("LG WMI Hotkey Driver");
+MODULE_LICENSE("GPL");
+
+#define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248"
+#define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2"
+#define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6"
+#define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B"
+#define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D"
+#define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210"
+#define WMI_EVENT_GUID WMI_EVENT_GUID0
+
+#define WMAB_METHOD "\\_SB.PCI0.LPCB.H_EC.MAP1.WMAB"
+#define WMBB_METHOD "\\_SB.PCI0.LPCB.H_EC.MAP1.WMBB"
+#define SB_GGOV_METHOD "\\_SB.GGOV"
+#define GOV_TLED 0x2020008
+#define WM_GET 1
+#define WM_SET 2
+#define WM_KEY_LIGHT 0x400
+#define WM_TLED 0x404
+#define WM_FN_LOCK 0x407
+#define WM_BATT_LIMIT 0x61
+#define WM_READER_MODE 0xBF
+#define WM_FAN_MODE 0x33
+#define WMBB_BATT_LIMIT 0x10C
+
+#define PLATFORM_NAME "lg-laptop"
+
+MODULE_ALIAS("wmi:" WMI_EVENT_GUID0);
+MODULE_ALIAS("wmi:" WMI_EVENT_GUID1);
+MODULE_ALIAS("wmi:" WMI_EVENT_GUID2);
+MODULE_ALIAS("wmi:" WMI_EVENT_GUID3);
+MODULE_ALIAS("wmi:" WMI_METHOD_WMAB);
+MODULE_ALIAS("wmi:" WMI_METHOD_WMBB);
+MODULE_ALIAS("acpi*:LGEX0815:*");
+
+static struct platform_device *pf_device;
+static struct input_dev *wmi_input_dev;
+
+static int wmi_inited;
+
+static const struct key_entry wmi_keymap[] __initconst = {
+ /* TODO: Add keymap values once found... */
+ {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */
+ {KE_KEY, 0x74, {KEY_F13} }, /* Touchpad toggle (F5) */
+ {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */
+ {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - changes
+ * backlight and sends an event
+ */
+ {KE_KEY, 0x80, {KEY_RFKILL} },
+ {KE_END, 0}
+};
+
+static int ggov(u32 arg0)
+{
+ union acpi_object args[1];
+ union acpi_object *r;
+ acpi_status status;
+ acpi_handle handle;
+ struct acpi_object_list arg;
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ int res;
+
+ args[0].type = ACPI_TYPE_INTEGER;
+ args[0].integer.value = arg0;
+
+ status = acpi_get_handle(NULL, (acpi_string) SB_GGOV_METHOD, &handle);
+
+ if (ACPI_FAILURE(status)) {
+ pr_err("lg_wmi: Cannot get handle");
+ return -1;
+ }
+
+ arg.count = 1;
+ arg.pointer = args;
+
+ status = acpi_evaluate_object(handle, NULL, &arg, &buffer);
+ if (ACPI_FAILURE(status)) {
+ pr_err("lg_wmi: Call failed.\n");
+ return -2;
+ }
+ r = buffer.pointer;
+
+ if (r->type != ACPI_TYPE_INTEGER) {
+ kfree(r);
+ return -2;
+ }
+
+ res = r->integer.value;
+ kfree(r);
+
+ return res;
+}
+
+static union acpi_object *lg_wmab(u32 method, u32 arg1, u32 arg2,
+ u32 *retval)
+{
+ union acpi_object args[3];
+ acpi_status status;
+ acpi_handle handle;
+ struct acpi_object_list arg;
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+
+ args[0].type = ACPI_TYPE_INTEGER;
+ args[0].integer.value = method;
+ args[1].type = ACPI_TYPE_INTEGER;
+ args[1].integer.value = arg1;
+ args[2].type = ACPI_TYPE_INTEGER;
+ args[2].integer.value = arg2;
+
+ status = acpi_get_handle(NULL, (acpi_string) WMAB_METHOD, &handle);
+
+ if (ACPI_FAILURE(status)) {
+ pr_err("lg_wmi: Cannot get handle");
+ return NULL;
+ }
+
+ arg.count = 3;
+ arg.pointer = args;
+
+ status = acpi_evaluate_object(handle, NULL, &arg, &buffer);
+ if (ACPI_FAILURE(status)) {
+ pr_err("lg_wmi: Call failed.\n");
+ return NULL;
+ }
+
+ return buffer.pointer;
+}
+
+// ***************************************************************************
+
+static void wmi_notify(u32 value, void *context)
+{
+ struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+ long data = (long) context;
+
+ pr_debug("event guid %li\n", data);
+ status = wmi_get_event_data(value, &response);
+ if (ACPI_FAILURE(status)) {
+ pr_err("Bad event status 0x%x\n", status);
+ return;
+ }
+
+ obj = (union acpi_object *)response.pointer;
+ if (!obj)
+ return;
+
+ if (obj->type == ACPI_TYPE_INTEGER) {
+ int eventcode = obj->integer.value;
+ struct key_entry *key;
+
+ key =
+ sparse_keymap_entry_from_scancode(wmi_input_dev, eventcode);
+ if (key && key->type == KE_KEY)
+ sparse_keymap_report_entry(wmi_input_dev, key, 1, true);
+ }
+
+ pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type,
+ obj->integer.value);
+
+ kfree(response.pointer);
+}
+
+static int __init wmi_input_setup(void)
+{
+ acpi_status status;
+ int err;
+
+ wmi_input_dev = input_allocate_device();
+ if (!wmi_input_dev)
+ return -ENOMEM;
+
+ wmi_input_dev->name = "LG WMI hotkeys";
+ wmi_input_dev->phys = "wmi/input0";
+ wmi_input_dev->id.bustype = BUS_HOST;
+
+ err = sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL);
+ if (err)
+ goto err_free_dev;
+
+ status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify,
+ (void *)0);
+ if (ACPI_FAILURE(status)) {
+ err = -EIO;
+ goto err_free_dev;
+ }
+
+ status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify,
+ (void *)2);
+ if (ACPI_FAILURE(status)) {
+ err = -EIO;
+ goto err_remove_notifier_0;
+ }
+
+ err = input_register_device(wmi_input_dev);
+ if (err)
+ goto err_remove_notifier_2;
+
+ return 0;
+
+err_remove_notifier_2:
+ wmi_remove_notify_handler(WMI_EVENT_GUID2);
+err_remove_notifier_0:
+ wmi_remove_notify_handler(WMI_EVENT_GUID0);
+err_free_dev:
+ input_free_device(wmi_input_dev);
+ return err;
+}
+
+static void acpi_notify(struct acpi_device *device, u32 event)
+{
+ struct key_entry *key;
+
+ pr_debug("LGEX0815 notify: %d\n", event);
+ if (wmi_input_dev) {
+ key = sparse_keymap_entry_from_scancode(wmi_input_dev, 0x80);
+ if (key && key->type == KE_KEY)
+ sparse_keymap_report_entry(wmi_input_dev, key, 1, true);
+ }
+
+}
+
+// ***************************************************************************
+
+static ssize_t fan_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ unsigned long value;
+ union acpi_object *r;
+ u32 m, v1, v2;
+
+ if (kstrtoul(buffer, 10, &value))
+ return -EINVAL;
+
+ if ((value != 0) && (value != 1))
+ return -EINVAL;
+
+ r = lg_wmab(WM_FAN_MODE, WM_GET, 0, NULL);
+
+ if (!r)
+ return -EIO;
+
+ if (r->type != ACPI_TYPE_INTEGER) {
+ kfree(r);
+ return -EIO;
+ }
+
+ m = r->integer.value;
+
+ kfree(r);
+
+ v1 = (m&0xffffff0f) | (value<<4);
+ v2 = (m&0xfffffff0) | value;
+
+ r = lg_wmab(WM_FAN_MODE, WM_SET, v1, NULL);
+ kfree(r);
+ r = lg_wmab(WM_FAN_MODE, WM_SET, v2, NULL);
+ kfree(r);
+
+ return count;
+}
+
+static ssize_t fan_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ unsigned int status;
+
+ union acpi_object *r;
+
+ r = lg_wmab(WM_FAN_MODE, WM_GET, 0, NULL);
+
+ if (!r)
+ return -EIO;
+
+ if (r->type != ACPI_TYPE_INTEGER) {
+ kfree(r);
+ return -EIO;
+ }
+
+ status = r->integer.value & 0x01;
+
+ kfree(r);
+
+ return snprintf(buffer, PAGE_SIZE, "%d\n", status);
+}
+
+static ssize_t reader_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ unsigned long value;
+ union acpi_object *r;
+
+ if (kstrtoul(buffer, 10, &value))
+ return -EINVAL;
+
+ r = lg_wmab(WM_READER_MODE, WM_SET, !!value, NULL);
+ kfree(r);
+ return count;
+}
+
+static ssize_t reader_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ unsigned int status;
+ union acpi_object *r;
+
+ r = lg_wmab(WM_READER_MODE, WM_GET, 0, NULL);
+
+ if (!r)
+ return -EIO;
+
+ if (r->type != ACPI_TYPE_INTEGER) {
+ kfree(r);
+ return -EIO;
+ }
+
+ status = !!r->integer.value;
+
+ kfree(r);
+
+ return snprintf(buffer, PAGE_SIZE, "%d\n", status);
+}
+
+static ssize_t fn_lock_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ unsigned long value;
+ union acpi_object *r;
+
+ if (kstrtoul(buffer, 10, &value))
+ return -EINVAL;
+
+ r = lg_wmab(WM_FN_LOCK, WM_SET, !!value, NULL);
+ kfree(r);
+ return count;
+
+ return -EINVAL;
+}
+
+static ssize_t fn_lock_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ unsigned int status;
+ union acpi_object *r;
+
+ r = lg_wmab(WM_FN_LOCK, WM_GET, 0, NULL);
+
+ if (!r)
+ return -EIO;
+
+ if (r->type != ACPI_TYPE_BUFFER) {
+ kfree(r);
+ return -EIO;
+ }
+
+ status = !!r->buffer.pointer[0];
+
+ kfree(r);
+
+ return snprintf(buffer, PAGE_SIZE, "%d\n", status);
+}
+
+static ssize_t battery_care_limit_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ unsigned long value;
+
+ if (kstrtoul(buffer, 10, &value))
+ return -EINVAL;
+
+ if ((value == 100) || (value == 80)) {
+ union acpi_object *r;
+
+ r = lg_wmab(WM_BATT_LIMIT, WM_SET, value, NULL);
+ kfree(r);
+ return count;
+ }
+
+ return -EINVAL;
+}
+
+static ssize_t battery_care_limit_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buffer)
+{
+ unsigned int status;
+
+ union acpi_object *r;
+
+ r = lg_wmab(WM_BATT_LIMIT, WM_GET, 0, NULL);
+
+ if (!r)
+ return -EIO;
+
+ if (r->type != ACPI_TYPE_INTEGER) {
+ kfree(r);
+ return -EIO;
+ }
+ status = r->integer.value;
+
+ kfree(r);
+
+ if ((status != 80) && (status != 100))
+ status = 0;
+
+ return snprintf(buffer, PAGE_SIZE, "%d\n", status);
+}
+
+static DEVICE_ATTR_RW(fan_mode);
+static DEVICE_ATTR_RW(reader_mode);
+static DEVICE_ATTR_RW(fn_lock);
+static DEVICE_ATTR_RW(battery_care_limit);
+
+static struct attribute *dev_attributes[] = {
+ &dev_attr_fan_mode.attr,
+ &dev_attr_reader_mode.attr,
+ &dev_attr_fn_lock.attr,
+ &dev_attr_battery_care_limit.attr,
+ NULL
+};
+
+static const struct attribute_group dev_attribute_group = {
+ .attrs = dev_attributes
+};
+
+// *****************************************************************
+
+static int tpad_led_registered;
+
+static void tpad_led_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ union acpi_object *r;
+
+ r = lg_wmab(WM_TLED, WM_SET, brightness > LED_OFF, NULL);
+ kfree(r);
+}
+
+static enum led_brightness tpad_led_get(struct led_classdev *cdev)
+{
+ int r;
+
+ r = ggov(GOV_TLED);
+ return r > 0 ? LED_ON : LED_OFF;
+}
+
+static LED_DEVICE(tpad_led, 1);
+
+static int kbd_backlight_registered;
+
+static void kbd_backlight_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ u32 val;
+ union acpi_object *r;
+
+ val = 0x22;
+ if (brightness <= LED_OFF)
+ val = 0;
+ if (brightness >= LED_FULL)
+ val = 0x24;
+ r = lg_wmab(WM_KEY_LIGHT, WM_SET, val, NULL);
+ kfree(r);
+}
+
+static enum led_brightness kbd_backlight_get(struct led_classdev *cdev)
+{
+ union acpi_object *r;
+ int val;
+
+ r = lg_wmab(WM_KEY_LIGHT, WM_GET, 0, NULL);
+
+ if (!r)
+ return LED_OFF;
+
+ if ((r->type != ACPI_TYPE_BUFFER) || (r->buffer.pointer[1] != 0x05)) {
+ kfree(r);
+ return LED_OFF;
+ }
+
+ switch (r->buffer.pointer[0] & 0x27) {
+ case 0x24:
+ val = LED_FULL;
+ break;
+ case 0x22:
+ val = LED_HALF;
+ break;
+ default:
+ val = LED_OFF;
+ }
+
+ kfree(r);
+
+ return val;
+}
+
+static LED_DEVICE(kbd_backlight, 255);
+
+// *****************************************************************
+
+static void wmi_input_destroy(void)
+{
+ wmi_remove_notify_handler(WMI_EVENT_GUID0);
+ wmi_remove_notify_handler(WMI_EVENT_GUID2);
+ input_unregister_device(wmi_input_dev);
+}
+
+static atomic_t pf_users = ATOMIC_INIT(0);
+static struct platform_driver pf_driver = {
+ .driver = {
+ .name = PLATFORM_NAME,
+ }
+};
+
+static int acpi_add(struct acpi_device *device)
+{
+ int ret = 0;
+
+ /* don't run again if already initialized */
+ if (atomic_add_return(1, &pf_users) > 1)
+ return 0;
+
+ ret = platform_driver_register(&pf_driver);
+ if (ret)
+ goto out;
+
+ pf_device = platform_device_register_simple(PLATFORM_NAME,
+ -1, NULL, 0);
+ if (IS_ERR(pf_device)) {
+ ret = PTR_ERR(pf_device);
+ pf_device = NULL;
+ pr_err("unable to register platform device\n");
+ goto out_platform_device;
+ }
+
+ ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group);
+ if (ret)
+ goto out_platform_registered;
+
+ kbd_backlight_registered = !led_classdev_register(&pf_device->dev,
+ &kbd_backlight);
+ tpad_led_registered =
+ !led_classdev_register(&pf_device->dev, &tpad_led);
+
+ return 0;
+
+out_platform_device:
+ platform_device_unregister(pf_device);
+out_platform_registered:
+ platform_driver_unregister(&pf_driver);
+out:
+ atomic_dec(&pf_users);
+ return ret;
+}
+
+static int acpi_remove(struct acpi_device *device)
+{
+ /* deregister only after the last user has gone */
+ if (!atomic_dec_and_test(&pf_users))
+ return -EBUSY;
+
+ sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group);
+ if (kbd_backlight_registered)
+ led_classdev_unregister(&kbd_backlight);
+ if (tpad_led_registered)
+ led_classdev_unregister(&tpad_led);
+
+ platform_device_unregister(pf_device);
+ platform_driver_unregister(&pf_driver);
+
+ return 0;
+}
+
+static const struct acpi_device_id device_ids[] = {
+ {"LGEX0815", 0},
+ {"", 0},
+};
+
+MODULE_DEVICE_TABLE(acpi, device_ids);
+
+static struct acpi_driver acpi_driver = {
+ .name = "LG",
+ .class = "LG",
+ .ids = device_ids,
+ .ops = {
+ .add = acpi_add,
+ .remove = acpi_remove,
+ .notify = acpi_notify,
+ },
+ .owner = THIS_MODULE,
+};
+
+static int __init acpi_init(void)
+{
+ int result = 0;
+
+ result = acpi_bus_register_driver(&acpi_driver);
+ if (result < 0) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error registering driver\n"));
+ return -ENODEV;
+ }
+
+ wmi_inited = !wmi_input_setup();
+
+ return 0;
+}
+
+static void __exit acpi_exit(void)
+{
+ acpi_bus_unregister_driver(&acpi_driver);
+ if (wmi_inited)
+ wmi_input_destroy();
+}
+
+module_init(acpi_init);
+module_exit(acpi_exit);