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

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

 



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/laptops/lg-laptop.txt |   71 +++
 MAINTAINERS                         |    6 
 drivers/platform/x86/Kconfig        |   14 
 drivers/platform/x86/Makefile       |    1 
 drivers/platform/x86/lg-laptop.c    |  724 ++++++++++++++++++++++++++++++++++++
 5 files changed, 816 insertions(+)

diff -X a/.gitignore -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-03-12 17:08:40.365657685 +0200
@@ -0,0 +1,71 @@
+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 0/1 to /sys/devices/platform/lg-laptop/fan_mode disables/enables
+the fan silent mod.
+
+
+
+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 a/.gitignore -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 a/.gitignore -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-12 14:41:01.242440401 +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 a/.gitignore -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 a/.gitignore -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-03-12 17:26:06.806683351 +0200
@@ -0,0 +1,724 @@
+/*
+ * 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 <linux/dmi.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
+
+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) -
+					   it 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)) {
+		printk(KERN_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)) {
+		printk(KERN_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)) {
+		printk(KERN_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)) {
+		printk(KERN_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 const struct dmi_system_id wmi_dmi_table[] __initconst = {
+	{
+	 .ident = "LG laptop",
+	 .matches = {
+		     DMI_MATCH(DMI_SYS_VENDOR, "LG Electronics"),
+		     },
+	 },
+	{}
+};
+
+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 (count > 31)
+		return -EINVAL;
+
+	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);
+	if (r)
+		kfree(r);
+	r = lg_wmab(WM_FAN_MODE, WM_SET, v2, NULL);
+	if (r)
+		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 DEVICE_ATTR_RW(fan_mode);
+
+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 (count > 31)
+		return -EINVAL;
+
+	if (kstrtoul(buffer, 10, &value))
+		return -EINVAL;
+
+	r = lg_wmab(WM_READER_MODE, WM_SET, ! !value, NULL);
+	if (r)
+		kfree(r);
+	return count;
+
+	return -EINVAL;
+}
+
+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_BUFFER) {
+		kfree(r);
+		return -EIO;
+	}
+
+	status = ! !r->buffer.pointer[0];
+
+	kfree(r);
+
+	return snprintf(buffer, PAGE_SIZE, "%d\n", status);
+}
+
+static DEVICE_ATTR_RW(reader_mode);
+
+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 (count > 31)
+		return -EINVAL;
+
+	if (kstrtoul(buffer, 10, &value))
+		return -EINVAL;
+
+	r = lg_wmab(WM_FN_LOCK, WM_SET, ! !value, NULL);
+	if (r)
+		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 DEVICE_ATTR_RW(fn_lock);
+
+static ssize_t battery_care_limit_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buffer, size_t count)
+{
+	unsigned long value;
+
+	if (count > 31)
+		return -EINVAL;
+
+	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);
+		if (r)
+			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(battery_care_limit);
+
+// *****************************************************************
+
+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);
+	if (r)
+		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);
+
+	if (r)
+		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 int __init wmi_init(void)
+{
+	int ret;
+
+	if (!wmi_has_guid(WMI_EVENT_GUID) || !dmi_check_system(wmi_dmi_table))
+		return -ENODEV;
+
+	ret = wmi_input_setup();
+	if (ret) {
+		pr_err("Failed to setup input device\n");
+		return ret;
+	}
+
+	pr_debug("LG WMI Hotkey Driver\n");
+
+	return 0;
+}
+
+static void __exit wmi_exit(void)
+{
+	if (wmi_has_guid(WMI_EVENT_GUID))
+		wmi_input_destroy();
+}
+
+static atomic_t pf_users = ATOMIC_INIT(0);
+static struct platform_driver pf_driver = {
+	.driver = {
+		   .name = "lg-laptop",
+		   }
+};
+
+static int pf_add(void)
+{
+	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_alloc("lg-laptop", -1);
+	if (!pf_device) {
+		ret = -ENOMEM;
+		goto out_platform_registered;
+	}
+
+	ret = platform_device_add(pf_device);
+	if (ret)
+		goto out_platform_alloced;
+
+	ret = device_create_file(&pf_device->dev, &dev_attr_battery_care_limit);
+	ret = device_create_file(&pf_device->dev, &dev_attr_fn_lock);
+	ret = device_create_file(&pf_device->dev, &dev_attr_reader_mode);
+	ret = device_create_file(&pf_device->dev, &dev_attr_fan_mode);
+	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_alloced:
+	platform_device_put(pf_device);
+	pf_device = NULL;
+out_platform_registered:
+	platform_driver_unregister(&pf_driver);
+out:
+	atomic_dec(&pf_users);
+	return ret;
+}
+
+static void pf_remove(void)
+{
+	/* deregister only after the last user has gone */
+	if (!atomic_dec_and_test(&pf_users))
+		return;
+
+	device_remove_file(&pf_device->dev, &dev_attr_battery_care_limit);
+	device_remove_file(&pf_device->dev, &dev_attr_fn_lock);
+	device_remove_file(&pf_device->dev, &dev_attr_reader_mode);
+	device_remove_file(&pf_device->dev, &dev_attr_fan_mode);
+	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);
+}
+
+
+static int acpi_add(struct acpi_device *device)
+{
+	int result;
+	result = pf_add();
+	return result;
+}
+
+static int acpi_remove(struct acpi_device *device)
+{
+	pf_remove();
+	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_init();
+
+	return 0;
+}
+
+static void __exit acpi_exit(void)
+{
+	acpi_bus_unregister_driver(&acpi_driver);
+	if (wmi_inited)
+		wmi_exit();
+}
+
+module_init(acpi_init);
+module_exit(acpi_exit);


-- 
Matan.





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

  Powered by Linux