Re: [PATCH v3] LG Gram laptop special features driver

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

 



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



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

  Powered by Linux