Hello, after a long time, but here is my second version of the LG laptop support patch. I tried to consider each point in the review, but some issues remain. > > Documentation/laptops/lg-laptop.txt | 71 +++ > This should be in reST file format. Done. > > drivers/platform/x86/lg-laptop.c | 724 > ++++++++++++++++++++++++++++++++++++ > 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 I tried to follow kernel coding style, and other drivers in this directory. The coding style is apparently not complete, and the different drivers have differing styles. > So, please, run checkpatch.pl and see if it complains about anything. Now it runs without errors or warnings. > Besides that, the approaches you choose might be old or racy: > - device attribute handling (use sysfs attribute group) I use attribute group now. > - btw, where is the ABI description? I added it. > Why did you choose so complex way to instantiate a driver? All those > ACPI handlers and additional burden for platform device... > Can it be simplified? Most keys are reported as WMI event, but one is reported only as an ACPI event. > Can you utilize WMI bus as much as possible. I tried the method suggested by Darren Hart, and every reasonable variation of it., but nothing appears to work. I will be grateful for any assistance on this issue. The dump of ACPI can be found at http://my.svgalib.org/acpi/ . From: Matan Ziv-Av <matan@xxxxxxxxxxx> Adds a driver for hotkeys, leds, battery charge limiter and fan mode on LG Gram laptops. Signed-off-by: Matan Ziv-Av <matan@xxxxxxxxxxx> --- Documentation/ABI/testing/sysfs-platform-lg-laptop | 27 Documentation/laptops/lg-laptop.rst | 72 ++ MAINTAINERS | 6 drivers/platform/x86/Kconfig | 14 drivers/platform/x86/Makefile | 1 drivers/platform/x86/lg-laptop.c | 662 +++++++++++++++++++++ 6 files changed, 782 insertions(+) diff -X dontdiff -urN a/Documentation/ABI/testing/sysfs-platform-lg-laptop linux-4.19-rc4/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.19-rc4/Documentation/ABI/testing/sysfs-platform-lg-laptop 2018-09-24 18:14:50.619235866 +0300 @@ -0,0 +1,27 @@ +What: /sys/devices/platform/lg-laptop/reader_mode +Date: September 2018 +KernelVersion: 4.19 +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: September 2018 +KernelVersion: 4.19 +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: September 2018 +KernelVersion: 4.19 +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: September 2018 +KernelVersion: 4.19 +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.rst linux-4.19-rc4/Documentation/laptops/lg-laptop.rst --- a/Documentation/laptops/lg-laptop.rst 1970-01-01 02:00:00.000000000 +0200 +++ linux-4.19-rc4/Documentation/laptops/lg-laptop.rst 2018-09-24 15:46:22.678017385 +0300 @@ -0,0 +1,72 @@ +.. 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. + + +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 -X dontdiff -urN a/MAINTAINERS linux-4.19-rc4/MAINTAINERS --- a/MAINTAINERS 2018-09-16 21:52:37.000000000 +0300 +++ linux-4.19-rc4/MAINTAINERS 2018-09-19 21:48:36.164398760 +0300 @@ -8242,6 +8242,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.19-rc4/drivers/platform/x86/Kconfig --- a/drivers/platform/x86/Kconfig 2018-09-16 21:52:37.000000000 +0300 +++ linux-4.19-rc4/drivers/platform/x86/Kconfig 2018-09-24 15:41:11.713009758 +0300 @@ -336,6 +336,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.19-rc4/drivers/platform/x86/Makefile --- a/drivers/platform/x86/Makefile 2018-09-16 21:52:37.000000000 +0300 +++ linux-4.19-rc4/drivers/platform/x86/Makefile 2018-09-19 21:48:36.164398760 +0300 @@ -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.19-rc4/drivers/platform/x86/lg-laptop.c --- a/drivers/platform/x86/lg-laptop.c 1970-01-01 02:00:00.000000000 +0200 +++ linux-4.19-rc4/drivers/platform/x86/lg-laptop.c 2018-09-24 18:00:39.025214980 +0300 @@ -0,0 +1,662 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 "\\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_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; + int ret; + + ret = kstrtoul(buffer, 10, &value); + if (!ret) + return ret; + + 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; + int ret; + + ret = kstrtoul(buffer, 10, &value); + if (!ret) + return ret; + + r = lg_wmab(WM_READER_MODE, WM_SET, !!value, NULL); + 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, 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; + int ret; + + ret = kstrtoul(buffer, 10, &value); + if (!ret) + return ret; + + r = lg_wmab(WM_FN_LOCK, WM_SET, !!value, NULL); + 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, 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; + 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, NULL); + 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, 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);