[PATCH v2] platform/x86: Add driver for LG Gram laptop extra features

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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);




[Index of Archives]     [Linux Kernel Development]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux