linux/drivers/platform/x86/lg-laptop.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * lg-laptop.c - LG Gram ACPI features and hotkeys Driver
   4 *
   5 * Copyright (C) 2018 Matan Ziv-Av <matan@svgalib.org>
   6 */
   7
   8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
   9
  10#include <linux/acpi.h>
  11#include <linux/input.h>
  12#include <linux/input/sparse-keymap.h>
  13#include <linux/kernel.h>
  14#include <linux/leds.h>
  15#include <linux/module.h>
  16#include <linux/platform_device.h>
  17#include <linux/types.h>
  18
  19#define LED_DEVICE(_name, max) struct led_classdev _name = { \
  20        .name           = __stringify(_name),   \
  21        .max_brightness = max,                  \
  22        .brightness_set = _name##_set,          \
  23        .brightness_get = _name##_get,          \
  24}
  25
  26MODULE_AUTHOR("Matan Ziv-Av");
  27MODULE_DESCRIPTION("LG WMI Hotkey Driver");
  28MODULE_LICENSE("GPL");
  29
  30#define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248"
  31#define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2"
  32#define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6"
  33#define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B"
  34#define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D"
  35#define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210"
  36#define WMI_EVENT_GUID  WMI_EVENT_GUID0
  37
  38#define WMAB_METHOD     "\\XINI.WMAB"
  39#define WMBB_METHOD     "\\XINI.WMBB"
  40#define SB_GGOV_METHOD  "\\_SB.GGOV"
  41#define GOV_TLED        0x2020008
  42#define WM_GET          1
  43#define WM_SET          2
  44#define WM_KEY_LIGHT    0x400
  45#define WM_TLED         0x404
  46#define WM_FN_LOCK      0x407
  47#define WM_BATT_LIMIT   0x61
  48#define WM_READER_MODE  0xBF
  49#define WM_FAN_MODE     0x33
  50#define WMBB_USB_CHARGE 0x10B
  51#define WMBB_BATT_LIMIT 0x10C
  52
  53#define PLATFORM_NAME   "lg-laptop"
  54
  55MODULE_ALIAS("wmi:" WMI_EVENT_GUID0);
  56MODULE_ALIAS("wmi:" WMI_EVENT_GUID1);
  57MODULE_ALIAS("wmi:" WMI_EVENT_GUID2);
  58MODULE_ALIAS("wmi:" WMI_EVENT_GUID3);
  59MODULE_ALIAS("wmi:" WMI_METHOD_WMAB);
  60MODULE_ALIAS("wmi:" WMI_METHOD_WMBB);
  61MODULE_ALIAS("acpi*:LGEX0815:*");
  62
  63static struct platform_device *pf_device;
  64static struct input_dev *wmi_input_dev;
  65
  66static u32 inited;
  67#define INIT_INPUT_WMI_0        0x01
  68#define INIT_INPUT_WMI_2        0x02
  69#define INIT_INPUT_ACPI         0x04
  70#define INIT_SPARSE_KEYMAP      0x80
  71
  72static const struct key_entry wmi_keymap[] = {
  73        {KE_KEY, 0x70, {KEY_F15} },      /* LG control panel (F1) */
  74        {KE_KEY, 0x74, {KEY_F13} },      /* Touchpad toggle (F5) */
  75        {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */
  76        {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing
  77                                          * this key both sends an event and
  78                                          * changes backlight level.
  79                                          */
  80        {KE_KEY, 0x80, {KEY_RFKILL} },
  81        {KE_END, 0}
  82};
  83
  84static int ggov(u32 arg0)
  85{
  86        union acpi_object args[1];
  87        union acpi_object *r;
  88        acpi_status status;
  89        acpi_handle handle;
  90        struct acpi_object_list arg;
  91        struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
  92        int res;
  93
  94        args[0].type = ACPI_TYPE_INTEGER;
  95        args[0].integer.value = arg0;
  96
  97        status = acpi_get_handle(NULL, (acpi_string) SB_GGOV_METHOD, &handle);
  98        if (ACPI_FAILURE(status)) {
  99                pr_err("Cannot get handle");
 100                return -ENODEV;
 101        }
 102
 103        arg.count = 1;
 104        arg.pointer = args;
 105
 106        status = acpi_evaluate_object(handle, NULL, &arg, &buffer);
 107        if (ACPI_FAILURE(status)) {
 108                acpi_handle_err(handle, "GGOV: call failed.\n");
 109                return -EINVAL;
 110        }
 111
 112        r = buffer.pointer;
 113        if (r->type != ACPI_TYPE_INTEGER) {
 114                kfree(r);
 115                return -EINVAL;
 116        }
 117
 118        res = r->integer.value;
 119        kfree(r);
 120
 121        return res;
 122}
 123
 124static union acpi_object *lg_wmab(u32 method, u32 arg1, u32 arg2)
 125{
 126        union acpi_object args[3];
 127        acpi_status status;
 128        acpi_handle handle;
 129        struct acpi_object_list arg;
 130        struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
 131
 132        args[0].type = ACPI_TYPE_INTEGER;
 133        args[0].integer.value = method;
 134        args[1].type = ACPI_TYPE_INTEGER;
 135        args[1].integer.value = arg1;
 136        args[2].type = ACPI_TYPE_INTEGER;
 137        args[2].integer.value = arg2;
 138
 139        status = acpi_get_handle(NULL, (acpi_string) WMAB_METHOD, &handle);
 140        if (ACPI_FAILURE(status)) {
 141                pr_err("Cannot get handle");
 142                return NULL;
 143        }
 144
 145        arg.count = 3;
 146        arg.pointer = args;
 147
 148        status = acpi_evaluate_object(handle, NULL, &arg, &buffer);
 149        if (ACPI_FAILURE(status)) {
 150                acpi_handle_err(handle, "WMAB: call failed.\n");
 151                return NULL;
 152        }
 153
 154        return buffer.pointer;
 155}
 156
 157static union acpi_object *lg_wmbb(u32 method_id, u32 arg1, u32 arg2)
 158{
 159        union acpi_object args[3];
 160        acpi_status status;
 161        acpi_handle handle;
 162        struct acpi_object_list arg;
 163        struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
 164        u8 buf[32];
 165
 166        *(u32 *)buf = method_id;
 167        *(u32 *)(buf + 4) = arg1;
 168        *(u32 *)(buf + 16) = arg2;
 169        args[0].type = ACPI_TYPE_INTEGER;
 170        args[0].integer.value = 0; /* ignored */
 171        args[1].type = ACPI_TYPE_INTEGER;
 172        args[1].integer.value = 1; /* Must be 1 or 2. Does not matter which */
 173        args[2].type = ACPI_TYPE_BUFFER;
 174        args[2].buffer.length = 32;
 175        args[2].buffer.pointer = buf;
 176
 177        status = acpi_get_handle(NULL, (acpi_string)WMBB_METHOD, &handle);
 178        if (ACPI_FAILURE(status)) {
 179                pr_err("Cannot get handle");
 180                return NULL;
 181        }
 182
 183        arg.count = 3;
 184        arg.pointer = args;
 185
 186        status = acpi_evaluate_object(handle, NULL, &arg, &buffer);
 187        if (ACPI_FAILURE(status)) {
 188                acpi_handle_err(handle, "WMAB: call failed.\n");
 189                return NULL;
 190        }
 191
 192        return (union acpi_object *)buffer.pointer;
 193}
 194
 195static void wmi_notify(u32 value, void *context)
 196{
 197        struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
 198        union acpi_object *obj;
 199        acpi_status status;
 200        long data = (long)context;
 201
 202        pr_debug("event guid %li\n", data);
 203        status = wmi_get_event_data(value, &response);
 204        if (ACPI_FAILURE(status)) {
 205                pr_err("Bad event status 0x%x\n", status);
 206                return;
 207        }
 208
 209        obj = (union acpi_object *)response.pointer;
 210        if (!obj)
 211                return;
 212
 213        if (obj->type == ACPI_TYPE_INTEGER) {
 214                int eventcode = obj->integer.value;
 215                struct key_entry *key;
 216
 217                key =
 218                    sparse_keymap_entry_from_scancode(wmi_input_dev, eventcode);
 219                if (key && key->type == KE_KEY)
 220                        sparse_keymap_report_entry(wmi_input_dev, key, 1, true);
 221        }
 222
 223        pr_debug("Type: %i    Eventcode: 0x%llx\n", obj->type,
 224                 obj->integer.value);
 225        kfree(response.pointer);
 226}
 227
 228static void wmi_input_setup(void)
 229{
 230        acpi_status status;
 231
 232        wmi_input_dev = input_allocate_device();
 233        if (wmi_input_dev) {
 234                wmi_input_dev->name = "LG WMI hotkeys";
 235                wmi_input_dev->phys = "wmi/input0";
 236                wmi_input_dev->id.bustype = BUS_HOST;
 237
 238                if (sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL) ||
 239                    input_register_device(wmi_input_dev)) {
 240                        pr_info("Cannot initialize input device");
 241                        input_free_device(wmi_input_dev);
 242                        return;
 243                }
 244
 245                inited |= INIT_SPARSE_KEYMAP;
 246                status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify,
 247                                                    (void *)0);
 248                if (ACPI_SUCCESS(status))
 249                        inited |= INIT_INPUT_WMI_0;
 250
 251                status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify,
 252                                                    (void *)2);
 253                if (ACPI_SUCCESS(status))
 254                        inited |= INIT_INPUT_WMI_2;
 255        } else {
 256                pr_info("Cannot allocate input device");
 257        }
 258}
 259
 260static void acpi_notify(struct acpi_device *device, u32 event)
 261{
 262        struct key_entry *key;
 263
 264        acpi_handle_debug(device->handle, "notify: %d\n", event);
 265        if (inited & INIT_SPARSE_KEYMAP) {
 266                key = sparse_keymap_entry_from_scancode(wmi_input_dev, 0x80);
 267                if (key && key->type == KE_KEY)
 268                        sparse_keymap_report_entry(wmi_input_dev, key, 1, true);
 269        }
 270}
 271
 272static ssize_t fan_mode_store(struct device *dev,
 273                              struct device_attribute *attr,
 274                              const char *buffer, size_t count)
 275{
 276        bool value;
 277        union acpi_object *r;
 278        u32 m;
 279        int ret;
 280
 281        ret = kstrtobool(buffer, &value);
 282        if (ret)
 283                return ret;
 284
 285        r = lg_wmab(WM_FAN_MODE, WM_GET, 0);
 286        if (!r)
 287                return -EIO;
 288
 289        if (r->type != ACPI_TYPE_INTEGER) {
 290                kfree(r);
 291                return -EIO;
 292        }
 293
 294        m = r->integer.value;
 295        kfree(r);
 296        r = lg_wmab(WM_FAN_MODE, WM_SET, (m & 0xffffff0f) | (value << 4));
 297        kfree(r);
 298        r = lg_wmab(WM_FAN_MODE, WM_SET, (m & 0xfffffff0) | value);
 299        kfree(r);
 300
 301        return count;
 302}
 303
 304static ssize_t fan_mode_show(struct device *dev,
 305                             struct device_attribute *attr, char *buffer)
 306{
 307        unsigned int status;
 308        union acpi_object *r;
 309
 310        r = lg_wmab(WM_FAN_MODE, WM_GET, 0);
 311        if (!r)
 312                return -EIO;
 313
 314        if (r->type != ACPI_TYPE_INTEGER) {
 315                kfree(r);
 316                return -EIO;
 317        }
 318
 319        status = r->integer.value & 0x01;
 320        kfree(r);
 321
 322        return snprintf(buffer, PAGE_SIZE, "%d\n", status);
 323}
 324
 325static ssize_t usb_charge_store(struct device *dev,
 326                                struct device_attribute *attr,
 327                                const char *buffer, size_t count)
 328{
 329        bool value;
 330        union acpi_object *r;
 331        int ret;
 332
 333        ret = kstrtobool(buffer, &value);
 334        if (ret)
 335                return ret;
 336
 337        r = lg_wmbb(WMBB_USB_CHARGE, WM_SET, value);
 338        if (!r)
 339                return -EIO;
 340
 341        kfree(r);
 342        return count;
 343}
 344
 345static ssize_t usb_charge_show(struct device *dev,
 346                               struct device_attribute *attr, char *buffer)
 347{
 348        unsigned int status;
 349        union acpi_object *r;
 350
 351        r = lg_wmbb(WMBB_USB_CHARGE, WM_GET, 0);
 352        if (!r)
 353                return -EIO;
 354
 355        if (r->type != ACPI_TYPE_BUFFER) {
 356                kfree(r);
 357                return -EIO;
 358        }
 359
 360        status = !!r->buffer.pointer[0x10];
 361
 362        kfree(r);
 363
 364        return snprintf(buffer, PAGE_SIZE, "%d\n", status);
 365}
 366
 367static ssize_t reader_mode_store(struct device *dev,
 368                                 struct device_attribute *attr,
 369                                 const char *buffer, size_t count)
 370{
 371        bool value;
 372        union acpi_object *r;
 373        int ret;
 374
 375        ret = kstrtobool(buffer, &value);
 376        if (ret)
 377                return ret;
 378
 379        r = lg_wmab(WM_READER_MODE, WM_SET, value);
 380        if (!r)
 381                return -EIO;
 382
 383        kfree(r);
 384        return count;
 385}
 386
 387static ssize_t reader_mode_show(struct device *dev,
 388                                struct device_attribute *attr, char *buffer)
 389{
 390        unsigned int status;
 391        union acpi_object *r;
 392
 393        r = lg_wmab(WM_READER_MODE, WM_GET, 0);
 394        if (!r)
 395                return -EIO;
 396
 397        if (r->type != ACPI_TYPE_INTEGER) {
 398                kfree(r);
 399                return -EIO;
 400        }
 401
 402        status = !!r->integer.value;
 403
 404        kfree(r);
 405
 406        return snprintf(buffer, PAGE_SIZE, "%d\n", status);
 407}
 408
 409static ssize_t fn_lock_store(struct device *dev,
 410                             struct device_attribute *attr,
 411                             const char *buffer, size_t count)
 412{
 413        bool value;
 414        union acpi_object *r;
 415        int ret;
 416
 417        ret = kstrtobool(buffer, &value);
 418        if (ret)
 419                return ret;
 420
 421        r = lg_wmab(WM_FN_LOCK, WM_SET, value);
 422        if (!r)
 423                return -EIO;
 424
 425        kfree(r);
 426        return count;
 427}
 428
 429static ssize_t fn_lock_show(struct device *dev,
 430                            struct device_attribute *attr, char *buffer)
 431{
 432        unsigned int status;
 433        union acpi_object *r;
 434
 435        r = lg_wmab(WM_FN_LOCK, WM_GET, 0);
 436        if (!r)
 437                return -EIO;
 438
 439        if (r->type != ACPI_TYPE_BUFFER) {
 440                kfree(r);
 441                return -EIO;
 442        }
 443
 444        status = !!r->buffer.pointer[0];
 445        kfree(r);
 446
 447        return snprintf(buffer, PAGE_SIZE, "%d\n", status);
 448}
 449
 450static ssize_t battery_care_limit_store(struct device *dev,
 451                                        struct device_attribute *attr,
 452                                        const char *buffer, size_t count)
 453{
 454        unsigned long value;
 455        int ret;
 456
 457        ret = kstrtoul(buffer, 10, &value);
 458        if (ret)
 459                return ret;
 460
 461        if (value == 100 || value == 80) {
 462                union acpi_object *r;
 463
 464                r = lg_wmab(WM_BATT_LIMIT, WM_SET, value);
 465                if (!r)
 466                        return -EIO;
 467
 468                kfree(r);
 469                return count;
 470        }
 471
 472        return -EINVAL;
 473}
 474
 475static ssize_t battery_care_limit_show(struct device *dev,
 476                                       struct device_attribute *attr,
 477                                       char *buffer)
 478{
 479        unsigned int status;
 480        union acpi_object *r;
 481
 482        r = lg_wmab(WM_BATT_LIMIT, WM_GET, 0);
 483        if (!r)
 484                return -EIO;
 485
 486        if (r->type != ACPI_TYPE_INTEGER) {
 487                kfree(r);
 488                return -EIO;
 489        }
 490
 491        status = r->integer.value;
 492        kfree(r);
 493        if (status != 80 && status != 100)
 494                status = 0;
 495
 496        return snprintf(buffer, PAGE_SIZE, "%d\n", status);
 497}
 498
 499static DEVICE_ATTR_RW(fan_mode);
 500static DEVICE_ATTR_RW(usb_charge);
 501static DEVICE_ATTR_RW(reader_mode);
 502static DEVICE_ATTR_RW(fn_lock);
 503static DEVICE_ATTR_RW(battery_care_limit);
 504
 505static struct attribute *dev_attributes[] = {
 506        &dev_attr_fan_mode.attr,
 507        &dev_attr_usb_charge.attr,
 508        &dev_attr_reader_mode.attr,
 509        &dev_attr_fn_lock.attr,
 510        &dev_attr_battery_care_limit.attr,
 511        NULL
 512};
 513
 514static const struct attribute_group dev_attribute_group = {
 515        .attrs = dev_attributes,
 516};
 517
 518static void tpad_led_set(struct led_classdev *cdev,
 519                         enum led_brightness brightness)
 520{
 521        union acpi_object *r;
 522
 523        r = lg_wmab(WM_TLED, WM_SET, brightness > LED_OFF);
 524        kfree(r);
 525}
 526
 527static enum led_brightness tpad_led_get(struct led_classdev *cdev)
 528{
 529        return ggov(GOV_TLED) > 0 ? LED_ON : LED_OFF;
 530}
 531
 532static LED_DEVICE(tpad_led, 1);
 533
 534static void kbd_backlight_set(struct led_classdev *cdev,
 535                              enum led_brightness brightness)
 536{
 537        u32 val;
 538        union acpi_object *r;
 539
 540        val = 0x22;
 541        if (brightness <= LED_OFF)
 542                val = 0;
 543        if (brightness >= LED_FULL)
 544                val = 0x24;
 545        r = lg_wmab(WM_KEY_LIGHT, WM_SET, val);
 546        kfree(r);
 547}
 548
 549static enum led_brightness kbd_backlight_get(struct led_classdev *cdev)
 550{
 551        union acpi_object *r;
 552        int val;
 553
 554        r = lg_wmab(WM_KEY_LIGHT, WM_GET, 0);
 555
 556        if (!r)
 557                return LED_OFF;
 558
 559        if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) {
 560                kfree(r);
 561                return LED_OFF;
 562        }
 563
 564        switch (r->buffer.pointer[0] & 0x27) {
 565        case 0x24:
 566                val = LED_FULL;
 567                break;
 568        case 0x22:
 569                val = LED_HALF;
 570                break;
 571        default:
 572                val = LED_OFF;
 573        }
 574
 575        kfree(r);
 576
 577        return val;
 578}
 579
 580static LED_DEVICE(kbd_backlight, 255);
 581
 582static void wmi_input_destroy(void)
 583{
 584        if (inited & INIT_INPUT_WMI_2)
 585                wmi_remove_notify_handler(WMI_EVENT_GUID2);
 586
 587        if (inited & INIT_INPUT_WMI_0)
 588                wmi_remove_notify_handler(WMI_EVENT_GUID0);
 589
 590        if (inited & INIT_SPARSE_KEYMAP)
 591                input_unregister_device(wmi_input_dev);
 592
 593        inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP);
 594}
 595
 596static struct platform_driver pf_driver = {
 597        .driver = {
 598                   .name = PLATFORM_NAME,
 599        }
 600};
 601
 602static int acpi_add(struct acpi_device *device)
 603{
 604        int ret;
 605
 606        if (pf_device)
 607                return 0;
 608
 609        ret = platform_driver_register(&pf_driver);
 610        if (ret)
 611                return ret;
 612
 613        pf_device = platform_device_register_simple(PLATFORM_NAME,
 614                                                    PLATFORM_DEVID_NONE,
 615                                                    NULL, 0);
 616        if (IS_ERR(pf_device)) {
 617                ret = PTR_ERR(pf_device);
 618                pf_device = NULL;
 619                pr_err("unable to register platform device\n");
 620                goto out_platform_registered;
 621        }
 622
 623        ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group);
 624        if (ret)
 625                goto out_platform_device;
 626
 627        /* LEDs are optional */
 628        led_classdev_register(&pf_device->dev, &kbd_backlight);
 629        led_classdev_register(&pf_device->dev, &tpad_led);
 630
 631        wmi_input_setup();
 632
 633        return 0;
 634
 635out_platform_device:
 636        platform_device_unregister(pf_device);
 637out_platform_registered:
 638        platform_driver_unregister(&pf_driver);
 639        return ret;
 640}
 641
 642static int acpi_remove(struct acpi_device *device)
 643{
 644        sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group);
 645
 646        led_classdev_unregister(&tpad_led);
 647        led_classdev_unregister(&kbd_backlight);
 648
 649        wmi_input_destroy();
 650        platform_device_unregister(pf_device);
 651        pf_device = NULL;
 652        platform_driver_unregister(&pf_driver);
 653
 654        return 0;
 655}
 656
 657static const struct acpi_device_id device_ids[] = {
 658        {"LGEX0815", 0},
 659        {"", 0}
 660};
 661MODULE_DEVICE_TABLE(acpi, device_ids);
 662
 663static struct acpi_driver acpi_driver = {
 664        .name = "LG Gram Laptop Support",
 665        .class = "lg-laptop",
 666        .ids = device_ids,
 667        .ops = {
 668                .add = acpi_add,
 669                .remove = acpi_remove,
 670                .notify = acpi_notify,
 671                },
 672        .owner = THIS_MODULE,
 673};
 674
 675static int __init acpi_init(void)
 676{
 677        int result;
 678
 679        result = acpi_bus_register_driver(&acpi_driver);
 680        if (result < 0) {
 681                pr_debug("Error registering driver\n");
 682                return -ENODEV;
 683        }
 684
 685        return 0;
 686}
 687
 688static void __exit acpi_exit(void)
 689{
 690        acpi_bus_unregister_driver(&acpi_driver);
 691}
 692
 693module_init(acpi_init);
 694module_exit(acpi_exit);
 695