Skip to content

Instantly share code, notes, and snippets.

@iamahuman
Last active February 24, 2017 01:37
Show Gist options
  • Save iamahuman/8908e20fc6e0f540f447f5eb3107959e to your computer and use it in GitHub Desktop.
Save iamahuman/8908e20fc6e0f540f447f5eb3107959e to your computer and use it in GitHub Desktop.
LGE HotKey driver
#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/err.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/leds.h>
#include <linux/acpi.h>
#define LGE_WMI_KEY_EVENT_GUID "E4FB94F9-7F2B-4173-AD1A-CD1D95086248"
#define LGE_WMI_TMP_EVENT_GUID "023B133E-49D1-4E10-B313-698220140DC2"
#define LGE_WMI_MIS_EVENT_GUID "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6"
#define LGE_WMI_GUID1 "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D"
#define LGE_WMI_GUID2 "4E5C4404-3CED-4A5E-8C7A-1BA875D00A43"
#define LGE_WMI_GUID3 "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210"
#define LGE_WMI_FILE "lge-wmi"
#define LGE_LAPTOP_NAME "LG Electronics Laptop Support"
#define LGE_LAPTOP_CLASS "LGE"
#define LGE_LAPTOP_FILE "lge-laptop"
#define LGE_AIRP_NAME "LGE Airplane Key driver"
#define LGE_AIRP_FILE "lge-airp"
#define LGE_WMI_GETULONG_METHODID 0x01
#define LGE_WMI_SETULONG_METHODID 0x02
#define LGE_WMI_FIREULONG_METHODID 0x03 /* unused */
#define LGE_WMI_IOULONG_METHODID 0x04 /* unused */
#define LGE_WMI_ERROR_INSTANCE_NOT_FOUND 0x80000001
#define LGE_WMI_ERROR_UNSUPPORTED_OPERATION 0x80000002
#define LGE_WMI_ERROR_INVALID_VALUE 0x80000003
#define LGE_EVBR 0x0140
#define LGE_EVWL 0x0136
#define LGE_EVFN 0x013B
#define LGE_EVDK 0x0148
#define LGE_EVPB 0x014B
MODULE_ALIAS("wmi:" LGE_WMI_KEY_EVENT_GUID);
MODULE_ALIAS("wmi:" LGE_WMI_TMP_EVENT_GUID);
MODULE_ALIAS("wmi:" LGE_WMI_MIS_EVENT_GUID);
static struct platform_device *lge_platform_device;
static struct input_dev *lge_wmi_inputdev;
static struct input_dev *lge_airp_inputdev;
static struct workqueue_struct *lge_touchpad_led_wq;
static struct work_struct lge_touchpad_led_work;
static int lge_touchpad_led_state;
static const struct key_entry lge_wmi_keymap[] = {
{ KE_KEY, 112, { KEY_F13 } },
{ KE_KEY, 116, { KEY_F21 } },
{ KE_KEY, 151, { KEY_F14 } },
{ KE_END, 0},
};
struct wmbb_buf_header {
u32 req1;
u16 req2;
u16 unused;
u32 result;
};
static int lge_wmab_execute(u8 instance, u32 method_id, u32 param, u32 *result)
{
u32 tmp;
struct acpi_buffer input = { sizeof(u32), &param };
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
acpi_status status;
union acpi_object *obj;
int retval = -EIO;
status = wmi_evaluate_method(LGE_WMI_GUID1, instance, method_id, &input, &output);
if (ACPI_FAILURE(status)) {
printk(KERN_ERR pr_fmt("wmab execute failed: %x\n"), status);
return retval;
}
obj = (union acpi_object *)output.pointer;
if (!obj || obj->type != ACPI_TYPE_INTEGER)
goto exit;
tmp = (u32) obj->integer.value;
if (tmp >= 0x80000000) {
printk(KERN_ERR pr_fmt("wmab returned %x\n"), tmp);
retval = -EIO;
if (tmp == LGE_WMI_ERROR_INSTANCE_NOT_FOUND)
retval = -ENXIO;
if (tmp == LGE_WMI_ERROR_UNSUPPORTED_OPERATION)
retval = -EPERM;
if (tmp == LGE_WMI_ERROR_INVALID_VALUE)
retval = -EINVAL;
} else {
retval = 0;
if (result)
*result = tmp;
}
exit:
kfree(obj);
return retval;
}
static inline int lge_wmi_get_param(u8 instance, u32 *result) {
return lge_wmab_execute(instance, LGE_WMI_GETULONG_METHODID, 0, result);
}
static inline int lge_wmi_set_param(u8 instance, u32 value) {
return lge_wmab_execute(instance, LGE_WMI_SETULONG_METHODID, value, NULL);
}
static void lge_wmi_hotkey_notify(u32 event, void *context)
{
struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *obj;
acpi_status status;
u32 code;
status = wmi_get_event_data(event, &response);
if (ACPI_FAILURE(status)) {
pr_err("bad event status %#x\n", status);
return;
}
obj = (union acpi_object *)response.pointer;
if (!obj || obj->type != ACPI_TYPE_INTEGER)
goto exit;
code = obj->integer.value;
if (code <= 0xff) { /* FN key event (EVKY) */
if (!sparse_keymap_report_event(lge_wmi_inputdev,
code, 1, true))
pr_info("Unknown key %x pressed\n", code);
goto exit;
}
switch (code & 0xffff) {
case LGE_EVBR:
case LGE_EVWL:
case LGE_EVFN:
case LGE_EVDK:
case LGE_EVPB:
default:
pr_info("event %x occured\n", code);
}
exit:
kfree(obj);
}
static enum led_brightness lge_wmi_touchpad_led_get(struct led_classdev *cdev)
{
u32 res = 0xf;
lge_wmi_get_param(0x30, &res);
return (res & 0x2) ? LED_OFF : 1;
}
static void lge_wmi_touchpad_led_update(struct work_struct *work)
{
u32 tmp;
acpi_status status;
status = lge_wmi_get_param(0x30, &tmp);
if (ACPI_FAILURE(status))
return;
if (!(tmp & 0x2) != !!lge_touchpad_led_state)
lge_wmi_set_param(0x30, tmp ^ 0x2);
}
static void lge_wmi_touchpad_led_set(struct led_classdev *cdev,
enum led_brightness value)
{
lge_touchpad_led_state = (value > 0);
queue_work(lge_touchpad_led_wq, &lge_touchpad_led_work);
}
static struct led_classdev touchpad_led = {
.name = "lge-wmi::touchpad",
.flags = 0,
.max_brightness = 1,
.brightness_get = lge_wmi_touchpad_led_get,
.brightness_set = lge_wmi_touchpad_led_set,
};
static struct attribute *lge_attributes[] = {
NULL
};
static const struct attribute_group lge_attr_group = {
.is_visible = NULL,
.attrs = lge_attributes,
};
static int lge_wmi_input_setup(struct platform_device *device)
{
struct input_dev *inputdev;
acpi_status status;
int err;
inputdev = input_allocate_device();
if (!inputdev)
return -ENOMEM;
inputdev->name = "LG Electronics Laptop WMI hotkeys";
inputdev->phys = LGE_WMI_FILE "/input0";
inputdev->id.bustype = BUS_HOST;
inputdev->dev.parent = &device->dev;
err = sparse_keymap_setup(inputdev, lge_wmi_keymap, NULL);
if (err)
goto err_free_dev;
status = wmi_install_notify_handler(LGE_WMI_KEY_EVENT_GUID,
lge_wmi_hotkey_notify, NULL);
if (ACPI_FAILURE(status)) {
err = -EIO;
goto err_free_keymap;
}
err = input_register_device(inputdev);
if (err)
goto err_uninstall_notifier;
lge_wmi_inputdev = inputdev;
return 0;
err_uninstall_notifier:
wmi_remove_notify_handler(LGE_WMI_KEY_EVENT_GUID);
err_free_keymap:
sparse_keymap_free(inputdev);
err_free_dev:
input_free_device(inputdev);
return err;
}
static void lge_wmi_input_destroy(void)
{
if (lge_wmi_inputdev) {
wmi_remove_notify_handler(LGE_WMI_KEY_EVENT_GUID);
sparse_keymap_free(lge_wmi_inputdev);
input_unregister_device(lge_wmi_inputdev);
}
lge_wmi_inputdev = NULL;
}
static int lge_airp_input_setup(struct platform_device *device)
{
int err;
lge_airp_inputdev = input_allocate_device();
if (!lge_airp_inputdev)
return -ENOMEM;
lge_airp_inputdev->name = LGE_AIRP_NAME;
lge_airp_inputdev->phys = LGE_AIRP_FILE "/input0";
lge_airp_inputdev->id.bustype = BUS_HOST;
lge_airp_inputdev->dev.parent = &device->dev;
set_bit(EV_KEY, lge_airp_inputdev->evbit);
set_bit(KEY_RFKILL, lge_airp_inputdev->keybit);
err = input_register_device(lge_airp_inputdev);
if (err) {
input_free_device(lge_airp_inputdev);
return err;
}
return 0;
}
static int lge_airp_input_destroy(struct platform_device *device)
{
input_unregister_device(lge_airp_inputdev);
return 0;
}
static void lge_airp_notify_handler(acpi_handle handle, u32 event, void *context)
{
struct platform_device *device = context;
if (event != 0x80) {
dev_warn(&device->dev, "unknown event: %#x\n", event);
return;
}
input_report_key(lge_airp_inputdev, KEY_RFKILL, 1);
input_report_key(lge_airp_inputdev, KEY_RFKILL, 0);
input_sync(lge_airp_inputdev);
}
static int lge_airp_probe(struct platform_device *device)
{
acpi_handle handle = ACPI_HANDLE(&device->dev);
acpi_status result;
u64 dev_status;
int err;
result = acpi_evaluate_integer(handle, "_STA", NULL, &dev_status);
if (!(result == AE_NOT_FOUND || (ACPI_SUCCESS(result) && (dev_status & 0x1f) != 0))) {
dev_warn(&device->dev, "Either LGE Airplane Key is not present or OSI configuration is inappropriate\n");
return -ENODEV;
}
err = lge_airp_input_setup(device);
if (err) {
pr_err("Failed to set up LGE Airplane Key\n");
return err;
}
result = acpi_install_notify_handler(handle,
ACPI_DEVICE_NOTIFY,
lge_airp_notify_handler,
device);
if (ACPI_FAILURE(result)) {
err = -EBUSY;
goto err_remove_input;
}
return 0;
err_remove_input:
lge_airp_input_destroy(device);
return err;
}
static int lge_airp_remove(struct platform_device *device)
{
acpi_handle handle = ACPI_HANDLE(&device->dev);
lge_airp_input_destroy(device);
acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, lge_airp_notify_handler);
return 0;
}
static const struct acpi_device_id lge_airp_ids[] = {
{"LGEX0815", 0},
{"", 0},
};
static struct platform_driver lge_airp_pl_driver = {
.driver = {
.name = "lge-airp",
.acpi_match_table = lge_airp_ids,
},
.probe = lge_airp_probe,
.remove = lge_airp_remove,
};
MODULE_DEVICE_TABLE(acpi, lge_airp_ids);
static int lge_wmi_platform_probe(struct platform_device *device)
{
int err;
if (wmi_has_guid(LGE_WMI_KEY_EVENT_GUID)) {
err = lge_wmi_input_setup(device);
if (err)
goto fail_input_setup;
}
if (wmi_has_guid(LGE_WMI_GUID1)) {
lge_touchpad_led_wq =
create_singlethread_workqueue("lge_touchpad_workqueue");
if (!lge_touchpad_led_wq) {
err = -ENOMEM;
goto fail_create_touchpad_wq;
}
INIT_WORK(&lge_touchpad_led_work, lge_wmi_touchpad_led_update);
touchpad_led.brightness = 1;
err = devm_led_classdev_register(&device->dev, &touchpad_led);
if (err)
goto fail_create_touchpad_led;
}
err = sysfs_create_group(&device->dev.kobj, &lge_attr_group);
if (err)
goto fail_sysfs;
return 0;
fail_sysfs:
devm_led_classdev_unregister(&device->dev, &touchpad_led);
fail_create_touchpad_led:
destroy_workqueue(lge_touchpad_led_wq);
fail_create_touchpad_wq:
lge_wmi_input_destroy();
fail_input_setup:
return err;
}
static int lge_wmi_platform_remove(struct platform_device *device)
{
sysfs_remove_group(&device->dev.kobj, &lge_attr_group);
devm_led_classdev_unregister(&device->dev, &touchpad_led);
destroy_workqueue(lge_touchpad_led_wq);
lge_wmi_input_destroy();
return 0;
}
static struct platform_driver lge_platform_driver = {
.driver = {
.name = LGE_LAPTOP_FILE,
},
.remove = lge_wmi_platform_remove,
};
static acpi_status __init
check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
{
const struct acpi_device_id *ids = context;
struct acpi_device *dev;
if (acpi_bus_get_device(handle, &dev) != 0)
return AE_OK;
if (acpi_match_device_ids(dev, ids) == 0)
if (acpi_create_platform_device(dev, NULL))
dev_info(&dev->dev, "created platform device");
return AE_OK;
}
static int lge_wmi_enabled = 0, lge_airp_enabled = 0;
static int __init lge_wmi_init(void)
{
if (!wmi_has_guid(LGE_WMI_GUID1)) {
return -ENODEV;
}
acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, check_acpi_dev, NULL,
(void *)lge_airp_ids, NULL);
lge_platform_device = platform_create_bundle(&lge_platform_driver,
lge_wmi_platform_probe,
NULL, 0, NULL, 0);
lge_wmi_enabled = !IS_ERR(lge_platform_device);
lge_airp_enabled = !platform_driver_register(&lge_airp_pl_driver);
if (!lge_wmi_enabled && !lge_airp_enabled)
return -ENODEV;
return 0;
}
void lge_wmi_exit(void)
{
if (lge_airp_enabled)
platform_driver_unregister(&lge_airp_pl_driver);
if (lge_wmi_enabled) {
platform_device_unregister(lge_platform_device);
platform_driver_unregister(&lge_platform_driver);
}
}
module_init(lge_wmi_init);
module_exit(lge_wmi_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jin-oh Kang <jinoh.kang.kr@gmail.com>");
MODULE_DESCRIPTION("some LG laptop stuff");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment