You forgot Darren in Cc list. Added. Also proper prefix is missed in Subject line. On Fri, Sep 28, 2018 at 5:34 PM Matan Ziv-Av <matan@xxxxxxxxxxx> wrote: > > > 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. Thanks for an update. I still see room for improvement, but overall seems good enough. So, I'm going to push this as is for now to make a progress. > > Signed-off-by: Matan Ziv-Av <matan@xxxxxxxxxxx> > --- > Documentation/ABI/testing/sysfs-platform-lg-laptop | 35 ++ > Documentation/laptops/lg-laptop.rst | 81 +++ > MAINTAINERS | 6 + > drivers/platform/x86/Kconfig | 14 + > drivers/platform/x86/Makefile | 1 + > drivers/platform/x86/lg-laptop.c | 700 +++++++++++++++++++++ > 6 files changed, 837 insertions(+) > create mode 100644 Documentation/ABI/testing/sysfs-platform-lg-laptop > create mode 100644 Documentation/laptops/lg-laptop.rst > create mode 100644 drivers/platform/x86/lg-laptop.c > > 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 @@ > +What: /sys/devices/platform/lg-laptop/reader_mode > +Date: October 2018 > +KernelVersion: 4.20 > +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: October 2018 > +KernelVersion: 4.20 > +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: October 2018 > +KernelVersion: 4.20 > +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: October 2018 > +KernelVersion: 4.20 > +Contact: "Matan Ziv-Av <matan@xxxxxxxxxxx> > +Description: > + Control fan mode. 1 for performance mode, 0 for silent mode. > + > +What: /sys/devices/platform/lg-laptop/usb_charge > +Date: October 2018 > +KernelVersion: 4.20 > +Contact: "Matan Ziv-Av <matan@xxxxxxxxxxx> > +Description: > + Control USB port charging when device is turned off. > + 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 @@ > +.. SPDX-License-Identifier: GPL-2.0+ > +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. > + > + > +USB charge > +---------- > + > +Writing 0/1 to /sys/devices/platform/lg-laptop/usb_charge disables/enables > +charging another device from the USB port while the device is turned off. > + > +This value is reset to 0 when the kernel boots. > + > + > +LEDs > +~~~~ > + > +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 --git a/MAINTAINERS b/MAINTAINERS > index 02a39617ec82..8cf6e6b1e2d9 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -8242,6 +8242,12 @@ W: http://legousb.sourceforge.net/ > 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 --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig > index 0c1aa6c314f5..158588c7f4a9 100644 > --- a/drivers/platform/x86/Kconfig > +++ b/drivers/platform/x86/Kconfig > @@ -336,6 +336,20 @@ config HP_WMI > 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 --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile > index e6d1becf81ce..4d5cd84d90fd 100644 > --- a/drivers/platform/x86/Makefile > +++ b/drivers/platform/x86/Makefile > @@ -9,6 +9,7 @@ obj-$(CONFIG_ASUS_NB_WMI) += asus-nb-wmi.o > 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 --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 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * lg-laptop.c - LG Gram ACPI features and hotkeys Driver > + * > + * Copyright (C) 2018 Matan Ziv-Av <matan@xxxxxxxxxxx> > + */ > + > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt > + > +#include <linux/acpi.h> > +#include <linux/input.h> > +#include <linux/input/sparse-keymap.h> > +#include <linux/kernel.h> > +#include <linux/leds.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/types.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 "\\XINI.WMAB" > +#define WMBB_METHOD "\\XINI.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_USB_CHARGE 0x10B > +#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 u32 inited; > +#define INIT_INPUT_WMI_0 0x01 > +#define INIT_INPUT_WMI_2 0x02 > +#define INIT_INPUT_ACPI 0x04 > +#define INIT_TPAD_LED 0x08 > +#define INIT_KBD_LED 0x10 > +#define INIT_SPARSE_KEYMAP 0x80 > + > +static const struct key_entry wmi_keymap[] = { > + {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) - pressing > + * this key both sends an event and > + * changes backlight level. > + */ > + {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("Cannot get handle"); > + return -ENODEV; > + } > + > + arg.count = 1; > + arg.pointer = args; > + > + status = acpi_evaluate_object(handle, NULL, &arg, &buffer); > + if (ACPI_FAILURE(status)) { > + acpi_handle_err(handle, "GGOV: call failed.\n"); > + return -EINVAL; > + } > + > + r = buffer.pointer; > + if (r->type != ACPI_TYPE_INTEGER) { > + kfree(r); > + return -EINVAL; > + } > + > + res = r->integer.value; > + kfree(r); > + > + return res; > +} > + > +static union acpi_object *lg_wmab(u32 method, u32 arg1, u32 arg2) > +{ > + 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("Cannot get handle"); > + return NULL; > + } > + > + arg.count = 3; > + arg.pointer = args; > + > + status = acpi_evaluate_object(handle, NULL, &arg, &buffer); > + if (ACPI_FAILURE(status)) { > + acpi_handle_err(handle, "WMAB: call failed.\n"); > + return NULL; > + } > + > + return buffer.pointer; > +} > + > +static union acpi_object *lg_wmbb(u32 method_id, u32 arg1, u32 arg2) > +{ > + union acpi_object args[3]; > + acpi_status status; > + acpi_handle handle; > + struct acpi_object_list arg; > + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; > + u8 buf[32]; > + > + *(u32 *)buf = method_id; > + *(u32 *)(buf + 4) = arg1; > + *(u32 *)(buf + 16) = arg2; > + args[0].type = ACPI_TYPE_INTEGER; > + args[0].integer.value = 0; /* ignored */ > + args[1].type = ACPI_TYPE_INTEGER; > + args[1].integer.value = 1; /* Must be 1 or 2. Does not matter which */ > + args[2].type = ACPI_TYPE_BUFFER; > + args[2].buffer.length = 32; > + args[2].buffer.pointer = buf; > + > + status = acpi_get_handle(NULL, (acpi_string)WMBB_METHOD, &handle); > + if (ACPI_FAILURE(status)) { > + pr_err("Cannot get handle"); > + return NULL; > + } > + > + arg.count = 3; > + arg.pointer = args; > + > + status = acpi_evaluate_object(handle, NULL, &arg, &buffer); > + if (ACPI_FAILURE(status)) { > + acpi_handle_err(handle, "WMAB: call failed.\n"); > + return NULL; > + } > + > + return (union acpi_object *)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 void wmi_input_setup(void) > +{ > + acpi_status status; > + > + wmi_input_dev = input_allocate_device(); > + if (wmi_input_dev) { > + wmi_input_dev->name = "LG WMI hotkeys"; > + wmi_input_dev->phys = "wmi/input0"; > + wmi_input_dev->id.bustype = BUS_HOST; > + > + if (sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL) || > + input_register_device(wmi_input_dev)) { > + pr_info("Cannot initialize input device"); > + input_free_device(wmi_input_dev); > + return; > + } > + > + inited |= INIT_SPARSE_KEYMAP; > + status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify, > + (void *)0); > + if (ACPI_SUCCESS(status)) > + inited |= INIT_INPUT_WMI_0; > + > + status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify, > + (void *)2); > + if (ACPI_SUCCESS(status)) > + inited |= INIT_INPUT_WMI_2; > + } else { > + pr_info("Cannot allocate input device"); > + } > +} > + > +static void acpi_notify(struct acpi_device *device, u32 event) > +{ > + struct key_entry *key; > + > + acpi_handle_debug(device->handle, "notify: %d\n", event); > + if (inited & INIT_SPARSE_KEYMAP) { > + 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) > +{ > + bool value; > + union acpi_object *r; > + u32 m; > + int ret; > + > + ret = kstrtobool(buffer, &value); > + if (ret) > + return ret; > + > + r = lg_wmab(WM_FAN_MODE, WM_GET, 0); > + if (!r) > + return -EIO; > + > + if (r->type != ACPI_TYPE_INTEGER) { > + kfree(r); > + return -EIO; > + } > + > + m = r->integer.value; > + kfree(r); > + r = lg_wmab(WM_FAN_MODE, WM_SET, (m & 0xffffff0f) | (value << 4)); > + kfree(r); > + r = lg_wmab(WM_FAN_MODE, WM_SET, (m & 0xfffffff0) | value); > + 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); > + 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 usb_charge_store(struct device *dev, > + struct device_attribute *attr, > + const char *buffer, size_t count) > +{ > + bool value; > + union acpi_object *r; > + int ret; > + > + ret = kstrtobool(buffer, &value); > + if (ret) > + return ret; > + > + r = lg_wmbb(WMBB_USB_CHARGE, WM_SET, value); > + if (!r) > + return -EIO; > + > + kfree(r); > + return count; > +} > + > +static ssize_t usb_charge_show(struct device *dev, > + struct device_attribute *attr, char *buffer) > +{ > + unsigned int status; > + union acpi_object *r; > + > + r = lg_wmbb(WMBB_USB_CHARGE, WM_GET, 0); > + if (!r) > + return -EIO; > + > + if (r->type != ACPI_TYPE_BUFFER) { > + kfree(r); > + return -EIO; > + } > + > + status = !!r->buffer.pointer[0x10]; > + > + 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) > +{ > + bool value; > + union acpi_object *r; > + int ret; > + > + ret = kstrtobool(buffer, &value); > + if (ret) > + return ret; > + > + r = lg_wmab(WM_READER_MODE, WM_SET, value); > + if (!r) > + return -EIO; > + > + 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); > + 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) > +{ > + bool value; > + union acpi_object *r; > + int ret; > + > + ret = kstrtobool(buffer, &value); > + if (ret) > + return ret; > + > + r = lg_wmab(WM_FN_LOCK, WM_SET, value); > + if (!r) > + return -EIO; > + > + kfree(r); > + return count; > +} > + > +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); > + 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; > + int ret; > + > + ret = kstrtoul(buffer, 10, &value); > + if (ret) > + return ret; > + > + if (value == 100 || value == 80) { > + union acpi_object *r; > + > + r = lg_wmab(WM_BATT_LIMIT, WM_SET, value); > + if (!r) > + return -EIO; > + > + 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); > + 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(usb_charge); > +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_usb_charge.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 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); > + kfree(r); > +} > + > +static enum led_brightness tpad_led_get(struct led_classdev *cdev) > +{ > + return ggov(GOV_TLED) > 0 ? LED_ON : LED_OFF; > +} > + > +static LED_DEVICE(tpad_led, 1); > + > +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); > + 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); > + > + 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) > +{ > + if (inited & INIT_INPUT_WMI_2) > + wmi_remove_notify_handler(WMI_EVENT_GUID2); > + > + if (inited & INIT_INPUT_WMI_0) > + wmi_remove_notify_handler(WMI_EVENT_GUID0); > + > + if (inited & INIT_SPARSE_KEYMAP) > + input_unregister_device(wmi_input_dev); > + > + inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP); > +} > + > +static struct platform_driver pf_driver = { > + .driver = { > + .name = PLATFORM_NAME, > + } > +}; > + > +static int acpi_add(struct acpi_device *device) > +{ > + int ret; > + > + if (pf_device) > + return 0; > + > + ret = platform_driver_register(&pf_driver); > + if (ret) > + return ret; > + > + pf_device = platform_device_register_simple(PLATFORM_NAME, > + PLATFORM_DEVID_NONE, > + 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_registered; > + } > + > + ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); > + if (ret) > + goto out_platform_device; > + > + if (!led_classdev_register(&pf_device->dev, &kbd_backlight)) > + inited |= INIT_KBD_LED; > + > + if (!led_classdev_register(&pf_device->dev, &tpad_led)) > + inited |= INIT_TPAD_LED; > + > + wmi_input_setup(); > + > + return 0; > + > +out_platform_device: > + platform_device_unregister(pf_device); > +out_platform_registered: > + platform_driver_unregister(&pf_driver); > + return ret; > +} > + > +static int acpi_remove(struct acpi_device *device) > +{ > + sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); > + if (inited & INIT_KBD_LED) > + led_classdev_unregister(&kbd_backlight); > + > + if (inited & INIT_TPAD_LED) > + led_classdev_unregister(&tpad_led); > + > + wmi_input_destroy(); > + platform_device_unregister(pf_device); > + pf_device = NULL; > + 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 Gram Laptop Support", > + .class = "lg-laptop", > + .ids = device_ids, > + .ops = { > + .add = acpi_add, > + .remove = acpi_remove, > + .notify = acpi_notify, > + }, > + .owner = THIS_MODULE, > +}; > + > +static int __init acpi_init(void) > +{ > + int result; > + > + result = acpi_bus_register_driver(&acpi_driver); > + if (result < 0) { > + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error registering driver\n")); > + return -ENODEV; > + } > + > + return 0; > +} > + > +static void __exit acpi_exit(void) > +{ > + acpi_bus_unregister_driver(&acpi_driver); > +} > + > +module_init(acpi_init); > +module_exit(acpi_exit); > -- > 2.14.4 > > > -- With Best Regards, Andy Shevchenko