919 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * lg-laptop.c - LG Gram ACPI features and hotkeys Driver4 *5 * Copyright (C) 2018 Matan Ziv-Av <matan@svgalib.org>6 */7 8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt9 10#include <linux/acpi.h>11#include <linux/bits.h>12#include <linux/device.h>13#include <linux/dev_printk.h>14#include <linux/dmi.h>15#include <linux/input.h>16#include <linux/input/sparse-keymap.h>17#include <linux/kernel.h>18#include <linux/leds.h>19#include <linux/module.h>20#include <linux/platform_device.h>21#include <linux/types.h>22 23#include <acpi/battery.h>24 25#define LED_DEVICE(_name, max, flag) struct led_classdev _name = { \26 .name = __stringify(_name), \27 .max_brightness = max, \28 .brightness_set = _name##_set, \29 .brightness_get = _name##_get, \30 .flags = flag, \31}32 33MODULE_AUTHOR("Matan Ziv-Av");34MODULE_DESCRIPTION("LG WMI Hotkey Driver");35MODULE_LICENSE("GPL");36 37static bool fw_debug;38module_param(fw_debug, bool, 0);39MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages");40 41#define LG_ADDRESS_SPACE_ID 0x8F42 43#define LG_ADDRESS_SPACE_DEBUG_FLAG_ADR 0x0044#define LG_ADDRESS_SPACE_FAN_MODE_ADR 0x0345 46#define LG_ADDRESS_SPACE_DTTM_FLAG_ADR 0x2047#define LG_ADDRESS_SPACE_CPU_TEMP_ADR 0x2148#define LG_ADDRESS_SPACE_CPU_TRIP_LOW_ADR 0x2249#define LG_ADDRESS_SPACE_CPU_TRIP_HIGH_ADR 0x2350#define LG_ADDRESS_SPACE_MB_TEMP_ADR 0x2451#define LG_ADDRESS_SPACE_MB_TRIP_LOW_ADR 0x2552#define LG_ADDRESS_SPACE_MB_TRIP_HIGH_ADR 0x2653 54#define LG_ADDRESS_SPACE_DEBUG_MSG_START_ADR 0x3E855#define LG_ADDRESS_SPACE_DEBUG_MSG_END_ADR 0x5E856 57#define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248"58#define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2"59#define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6"60#define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B"61#define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D"62#define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210"63#define WMI_EVENT_GUID WMI_EVENT_GUID064 65#define SB_GGOV_METHOD "\\_SB.GGOV"66#define GOV_TLED 0x202000867#define WM_GET 168#define WM_SET 269#define WM_KEY_LIGHT 0x40070#define WM_TLED 0x40471#define WM_FN_LOCK 0x40772#define WM_BATT_LIMIT 0x6173#define WM_READER_MODE 0xBF74#define WM_FAN_MODE 0x3375#define WMBB_USB_CHARGE 0x10B76#define WMBB_BATT_LIMIT 0x10C77 78#define PLATFORM_NAME "lg-laptop"79 80MODULE_ALIAS("wmi:" WMI_EVENT_GUID0);81MODULE_ALIAS("wmi:" WMI_EVENT_GUID1);82MODULE_ALIAS("wmi:" WMI_EVENT_GUID2);83MODULE_ALIAS("wmi:" WMI_EVENT_GUID3);84MODULE_ALIAS("wmi:" WMI_METHOD_WMAB);85MODULE_ALIAS("wmi:" WMI_METHOD_WMBB);86 87static struct platform_device *pf_device;88static struct input_dev *wmi_input_dev;89 90static u32 inited;91#define INIT_INPUT_WMI_0 0x0192#define INIT_INPUT_WMI_2 0x0293#define INIT_INPUT_ACPI 0x0494#define INIT_SPARSE_KEYMAP 0x8095 96static int battery_limit_use_wmbb;97static struct led_classdev kbd_backlight;98static enum led_brightness get_kbd_backlight_level(struct device *dev);99 100static const struct key_entry wmi_keymap[] = {101 {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */102 {KE_KEY, 0x74, {KEY_F21} }, /* Touchpad toggle (F5) */103 {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */104 {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing105 * this key both sends an event and106 * changes backlight level.107 */108 {KE_END, 0}109};110 111static int ggov(u32 arg0)112{113 union acpi_object args[1];114 union acpi_object *r;115 acpi_status status;116 acpi_handle handle;117 struct acpi_object_list arg;118 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };119 int res;120 121 args[0].type = ACPI_TYPE_INTEGER;122 args[0].integer.value = arg0;123 124 status = acpi_get_handle(NULL, (acpi_string) SB_GGOV_METHOD, &handle);125 if (ACPI_FAILURE(status)) {126 pr_err("Cannot get handle");127 return -ENODEV;128 }129 130 arg.count = 1;131 arg.pointer = args;132 133 status = acpi_evaluate_object(handle, NULL, &arg, &buffer);134 if (ACPI_FAILURE(status)) {135 acpi_handle_err(handle, "GGOV: call failed.\n");136 return -EINVAL;137 }138 139 r = buffer.pointer;140 if (r->type != ACPI_TYPE_INTEGER) {141 kfree(r);142 return -EINVAL;143 }144 145 res = r->integer.value;146 kfree(r);147 148 return res;149}150 151static union acpi_object *lg_wmab(struct device *dev, u32 method, u32 arg1, u32 arg2)152{153 union acpi_object args[3];154 acpi_status status;155 struct acpi_object_list arg;156 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };157 158 args[0].type = ACPI_TYPE_INTEGER;159 args[0].integer.value = method;160 args[1].type = ACPI_TYPE_INTEGER;161 args[1].integer.value = arg1;162 args[2].type = ACPI_TYPE_INTEGER;163 args[2].integer.value = arg2;164 165 arg.count = 3;166 arg.pointer = args;167 168 status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &arg, &buffer);169 if (ACPI_FAILURE(status)) {170 dev_err(dev, "WMAB: call failed.\n");171 return NULL;172 }173 174 return buffer.pointer;175}176 177static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u32 arg2)178{179 union acpi_object args[3];180 acpi_status status;181 struct acpi_object_list arg;182 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };183 u8 buf[32];184 185 *(u32 *)buf = method_id;186 *(u32 *)(buf + 4) = arg1;187 *(u32 *)(buf + 16) = arg2;188 args[0].type = ACPI_TYPE_INTEGER;189 args[0].integer.value = 0; /* ignored */190 args[1].type = ACPI_TYPE_INTEGER;191 args[1].integer.value = 1; /* Must be 1 or 2. Does not matter which */192 args[2].type = ACPI_TYPE_BUFFER;193 args[2].buffer.length = 32;194 args[2].buffer.pointer = buf;195 196 arg.count = 3;197 arg.pointer = args;198 199 status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMBB", &arg, &buffer);200 if (ACPI_FAILURE(status)) {201 dev_err(dev, "WMBB: call failed.\n");202 return NULL;203 }204 205 return (union acpi_object *)buffer.pointer;206}207 208static void wmi_notify(union acpi_object *obj, void *context)209{210 long data = (long)context;211 212 pr_debug("event guid %li\n", data);213 if (!obj)214 return;215 216 if (obj->type == ACPI_TYPE_INTEGER) {217 int eventcode = obj->integer.value;218 struct key_entry *key;219 220 if (eventcode == 0x10000000) {221 led_classdev_notify_brightness_hw_changed(222 &kbd_backlight, get_kbd_backlight_level(kbd_backlight.dev->parent));223 } else {224 key = sparse_keymap_entry_from_scancode(225 wmi_input_dev, eventcode);226 if (key && key->type == KE_KEY)227 sparse_keymap_report_entry(wmi_input_dev,228 key, 1, true);229 }230 }231 232 pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type,233 obj->integer.value);234}235 236static void wmi_input_setup(void)237{238 acpi_status status;239 240 wmi_input_dev = input_allocate_device();241 if (wmi_input_dev) {242 wmi_input_dev->name = "LG WMI hotkeys";243 wmi_input_dev->phys = "wmi/input0";244 wmi_input_dev->id.bustype = BUS_HOST;245 246 if (sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL) ||247 input_register_device(wmi_input_dev)) {248 pr_info("Cannot initialize input device");249 input_free_device(wmi_input_dev);250 return;251 }252 253 inited |= INIT_SPARSE_KEYMAP;254 status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify,255 (void *)0);256 if (ACPI_SUCCESS(status))257 inited |= INIT_INPUT_WMI_0;258 259 status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify,260 (void *)2);261 if (ACPI_SUCCESS(status))262 inited |= INIT_INPUT_WMI_2;263 } else {264 pr_info("Cannot allocate input device");265 }266}267 268static void acpi_notify(struct acpi_device *device, u32 event)269{270 acpi_handle_debug(device->handle, "notify: %d\n", event);271}272 273static ssize_t fan_mode_store(struct device *dev,274 struct device_attribute *attr,275 const char *buffer, size_t count)276{277 bool value;278 union acpi_object *r;279 u32 m;280 int ret;281 282 ret = kstrtobool(buffer, &value);283 if (ret)284 return ret;285 286 r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0);287 if (!r)288 return -EIO;289 290 if (r->type != ACPI_TYPE_INTEGER) {291 kfree(r);292 return -EIO;293 }294 295 m = r->integer.value;296 kfree(r);297 r = lg_wmab(dev, WM_FAN_MODE, WM_SET, (m & 0xffffff0f) | (value << 4));298 kfree(r);299 r = lg_wmab(dev, WM_FAN_MODE, WM_SET, (m & 0xfffffff0) | value);300 kfree(r);301 302 return count;303}304 305static ssize_t fan_mode_show(struct device *dev,306 struct device_attribute *attr, char *buffer)307{308 unsigned int status;309 union acpi_object *r;310 311 r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0);312 if (!r)313 return -EIO;314 315 if (r->type != ACPI_TYPE_INTEGER) {316 kfree(r);317 return -EIO;318 }319 320 status = r->integer.value & 0x01;321 kfree(r);322 323 return sysfs_emit(buffer, "%d\n", status);324}325 326static ssize_t usb_charge_store(struct device *dev,327 struct device_attribute *attr,328 const char *buffer, size_t count)329{330 bool value;331 union acpi_object *r;332 int ret;333 334 ret = kstrtobool(buffer, &value);335 if (ret)336 return ret;337 338 r = lg_wmbb(dev, WMBB_USB_CHARGE, WM_SET, value);339 if (!r)340 return -EIO;341 342 kfree(r);343 return count;344}345 346static ssize_t usb_charge_show(struct device *dev,347 struct device_attribute *attr, char *buffer)348{349 unsigned int status;350 union acpi_object *r;351 352 r = lg_wmbb(dev, WMBB_USB_CHARGE, WM_GET, 0);353 if (!r)354 return -EIO;355 356 if (r->type != ACPI_TYPE_BUFFER) {357 kfree(r);358 return -EIO;359 }360 361 status = !!r->buffer.pointer[0x10];362 363 kfree(r);364 365 return sysfs_emit(buffer, "%d\n", status);366}367 368static ssize_t reader_mode_store(struct device *dev,369 struct device_attribute *attr,370 const char *buffer, size_t count)371{372 bool value;373 union acpi_object *r;374 int ret;375 376 ret = kstrtobool(buffer, &value);377 if (ret)378 return ret;379 380 r = lg_wmab(dev, WM_READER_MODE, WM_SET, value);381 if (!r)382 return -EIO;383 384 kfree(r);385 return count;386}387 388static ssize_t reader_mode_show(struct device *dev,389 struct device_attribute *attr, char *buffer)390{391 unsigned int status;392 union acpi_object *r;393 394 r = lg_wmab(dev, WM_READER_MODE, WM_GET, 0);395 if (!r)396 return -EIO;397 398 if (r->type != ACPI_TYPE_INTEGER) {399 kfree(r);400 return -EIO;401 }402 403 status = !!r->integer.value;404 405 kfree(r);406 407 return sysfs_emit(buffer, "%d\n", status);408}409 410static ssize_t fn_lock_store(struct device *dev,411 struct device_attribute *attr,412 const char *buffer, size_t count)413{414 bool value;415 union acpi_object *r;416 int ret;417 418 ret = kstrtobool(buffer, &value);419 if (ret)420 return ret;421 422 r = lg_wmab(dev, WM_FN_LOCK, WM_SET, value);423 if (!r)424 return -EIO;425 426 kfree(r);427 return count;428}429 430static ssize_t fn_lock_show(struct device *dev,431 struct device_attribute *attr, char *buffer)432{433 unsigned int status;434 union acpi_object *r;435 436 r = lg_wmab(dev, WM_FN_LOCK, WM_GET, 0);437 if (!r)438 return -EIO;439 440 if (r->type != ACPI_TYPE_BUFFER) {441 kfree(r);442 return -EIO;443 }444 445 status = !!r->buffer.pointer[0];446 kfree(r);447 448 return sysfs_emit(buffer, "%d\n", status);449}450 451static ssize_t charge_control_end_threshold_store(struct device *dev,452 struct device_attribute *attr,453 const char *buf, size_t count)454{455 unsigned long value;456 int ret;457 458 ret = kstrtoul(buf, 10, &value);459 if (ret)460 return ret;461 462 if (value == 100 || value == 80) {463 union acpi_object *r;464 465 if (battery_limit_use_wmbb)466 r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_SET, value);467 else468 r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_SET, value);469 if (!r)470 return -EIO;471 472 kfree(r);473 return count;474 }475 476 return -EINVAL;477}478 479static ssize_t charge_control_end_threshold_show(struct device *device,480 struct device_attribute *attr,481 char *buf)482{483 unsigned int status;484 union acpi_object *r;485 486 if (battery_limit_use_wmbb) {487 r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_GET, 0);488 if (!r)489 return -EIO;490 491 if (r->type != ACPI_TYPE_BUFFER) {492 kfree(r);493 return -EIO;494 }495 496 status = r->buffer.pointer[0x10];497 } else {498 r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_GET, 0);499 if (!r)500 return -EIO;501 502 if (r->type != ACPI_TYPE_INTEGER) {503 kfree(r);504 return -EIO;505 }506 507 status = r->integer.value;508 }509 kfree(r);510 if (status != 80 && status != 100)511 status = 0;512 513 return sysfs_emit(buf, "%d\n", status);514}515 516static ssize_t battery_care_limit_show(struct device *dev,517 struct device_attribute *attr,518 char *buffer)519{520 return charge_control_end_threshold_show(dev, attr, buffer);521}522 523static ssize_t battery_care_limit_store(struct device *dev,524 struct device_attribute *attr,525 const char *buffer, size_t count)526{527 return charge_control_end_threshold_store(dev, attr, buffer, count);528}529 530static DEVICE_ATTR_RW(fan_mode);531static DEVICE_ATTR_RW(usb_charge);532static DEVICE_ATTR_RW(reader_mode);533static DEVICE_ATTR_RW(fn_lock);534static DEVICE_ATTR_RW(charge_control_end_threshold);535static DEVICE_ATTR_RW(battery_care_limit);536 537static int lg_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)538{539 if (device_create_file(&battery->dev,540 &dev_attr_charge_control_end_threshold))541 return -ENODEV;542 543 return 0;544}545 546static int lg_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook)547{548 device_remove_file(&battery->dev,549 &dev_attr_charge_control_end_threshold);550 return 0;551}552 553static struct acpi_battery_hook battery_hook = {554 .add_battery = lg_battery_add,555 .remove_battery = lg_battery_remove,556 .name = "LG Battery Extension",557};558 559static struct attribute *dev_attributes[] = {560 &dev_attr_fan_mode.attr,561 &dev_attr_usb_charge.attr,562 &dev_attr_reader_mode.attr,563 &dev_attr_fn_lock.attr,564 &dev_attr_battery_care_limit.attr,565 NULL566};567 568static const struct attribute_group dev_attribute_group = {569 .attrs = dev_attributes,570};571 572static void tpad_led_set(struct led_classdev *cdev,573 enum led_brightness brightness)574{575 union acpi_object *r;576 577 r = lg_wmab(cdev->dev->parent, WM_TLED, WM_SET, brightness > LED_OFF);578 kfree(r);579}580 581static enum led_brightness tpad_led_get(struct led_classdev *cdev)582{583 return ggov(GOV_TLED) > 0 ? LED_ON : LED_OFF;584}585 586static LED_DEVICE(tpad_led, 1, 0);587 588static void kbd_backlight_set(struct led_classdev *cdev,589 enum led_brightness brightness)590{591 u32 val;592 union acpi_object *r;593 594 val = 0x22;595 if (brightness <= LED_OFF)596 val = 0;597 if (brightness >= LED_FULL)598 val = 0x24;599 r = lg_wmab(cdev->dev->parent, WM_KEY_LIGHT, WM_SET, val);600 kfree(r);601}602 603static enum led_brightness get_kbd_backlight_level(struct device *dev)604{605 union acpi_object *r;606 int val;607 608 r = lg_wmab(dev, WM_KEY_LIGHT, WM_GET, 0);609 610 if (!r)611 return LED_OFF;612 613 if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) {614 kfree(r);615 return LED_OFF;616 }617 618 switch (r->buffer.pointer[0] & 0x27) {619 case 0x24:620 val = LED_FULL;621 break;622 case 0x22:623 val = LED_HALF;624 break;625 default:626 val = LED_OFF;627 }628 629 kfree(r);630 631 return val;632}633 634static enum led_brightness kbd_backlight_get(struct led_classdev *cdev)635{636 return get_kbd_backlight_level(cdev->dev->parent);637}638 639static LED_DEVICE(kbd_backlight, 255, LED_BRIGHT_HW_CHANGED);640 641static void wmi_input_destroy(void)642{643 if (inited & INIT_INPUT_WMI_2)644 wmi_remove_notify_handler(WMI_EVENT_GUID2);645 646 if (inited & INIT_INPUT_WMI_0)647 wmi_remove_notify_handler(WMI_EVENT_GUID0);648 649 if (inited & INIT_SPARSE_KEYMAP)650 input_unregister_device(wmi_input_dev);651 652 inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP);653}654 655static struct platform_driver pf_driver = {656 .driver = {657 .name = PLATFORM_NAME,658 }659};660 661static acpi_status lg_laptop_address_space_write(struct device *dev, acpi_physical_address address,662 size_t size, u64 value)663{664 u8 byte;665 666 /* Ignore any debug messages */667 if (address >= LG_ADDRESS_SPACE_DEBUG_MSG_START_ADR &&668 address <= LG_ADDRESS_SPACE_DEBUG_MSG_END_ADR)669 return AE_OK;670 671 if (size != sizeof(byte))672 return AE_BAD_PARAMETER;673 674 byte = value & 0xFF;675 676 switch (address) {677 case LG_ADDRESS_SPACE_FAN_MODE_ADR:678 /*679 * The fan mode field is not affected by the DTTM flag, so we680 * have to manually check fw_debug.681 */682 if (fw_debug)683 dev_dbg(dev, "Fan mode set to mode %u\n", byte);684 685 return AE_OK;686 case LG_ADDRESS_SPACE_CPU_TEMP_ADR:687 dev_dbg(dev, "CPU temperature is %u °C\n", byte);688 return AE_OK;689 case LG_ADDRESS_SPACE_CPU_TRIP_LOW_ADR:690 dev_dbg(dev, "CPU lower trip point set to %u °C\n", byte);691 return AE_OK;692 case LG_ADDRESS_SPACE_CPU_TRIP_HIGH_ADR:693 dev_dbg(dev, "CPU higher trip point set to %u °C\n", byte);694 return AE_OK;695 case LG_ADDRESS_SPACE_MB_TEMP_ADR:696 dev_dbg(dev, "Motherboard temperature is %u °C\n", byte);697 return AE_OK;698 case LG_ADDRESS_SPACE_MB_TRIP_LOW_ADR:699 dev_dbg(dev, "Motherboard lower trip point set to %u °C\n", byte);700 return AE_OK;701 case LG_ADDRESS_SPACE_MB_TRIP_HIGH_ADR:702 dev_dbg(dev, "Motherboard higher trip point set to %u °C\n", byte);703 return AE_OK;704 default:705 dev_notice_ratelimited(dev, "Ignoring write to unknown opregion address %llu\n",706 address);707 return AE_OK;708 }709}710 711static acpi_status lg_laptop_address_space_read(struct device *dev, acpi_physical_address address,712 size_t size, u64 *value)713{714 if (size != 1)715 return AE_BAD_PARAMETER;716 717 switch (address) {718 case LG_ADDRESS_SPACE_DEBUG_FLAG_ADR:719 /* Debug messages are already printed using the standard ACPI Debug object */720 *value = 0x00;721 return AE_OK;722 case LG_ADDRESS_SPACE_DTTM_FLAG_ADR:723 *value = fw_debug;724 return AE_OK;725 default:726 dev_notice_ratelimited(dev, "Attempt to read unknown opregion address %llu\n",727 address);728 return AE_BAD_PARAMETER;729 }730}731 732static acpi_status lg_laptop_address_space_handler(u32 function, acpi_physical_address address,733 u32 bits, u64 *value, void *handler_context,734 void *region_context)735{736 struct device *dev = handler_context;737 size_t size;738 739 if (bits % BITS_PER_BYTE)740 return AE_BAD_PARAMETER;741 742 size = bits / BITS_PER_BYTE;743 744 switch (function) {745 case ACPI_READ:746 return lg_laptop_address_space_read(dev, address, size, value);747 case ACPI_WRITE:748 return lg_laptop_address_space_write(dev, address, size, *value);749 default:750 return AE_BAD_PARAMETER;751 }752}753 754static void lg_laptop_remove_address_space_handler(void *data)755{756 struct acpi_device *device = data;757 758 acpi_remove_address_space_handler(device->handle, LG_ADDRESS_SPACE_ID,759 &lg_laptop_address_space_handler);760}761 762static int acpi_add(struct acpi_device *device)763{764 struct platform_device_info pdev_info = {765 .fwnode = acpi_fwnode_handle(device),766 .name = PLATFORM_NAME,767 .id = PLATFORM_DEVID_NONE,768 };769 acpi_status status;770 int ret;771 const char *product;772 int year = 2017;773 774 if (pf_device)775 return 0;776 777 status = acpi_install_address_space_handler(device->handle, LG_ADDRESS_SPACE_ID,778 &lg_laptop_address_space_handler,779 NULL, &device->dev);780 if (ACPI_FAILURE(status))781 return -ENODEV;782 783 ret = devm_add_action_or_reset(&device->dev, lg_laptop_remove_address_space_handler,784 device);785 if (ret < 0)786 return ret;787 788 ret = platform_driver_register(&pf_driver);789 if (ret)790 return ret;791 792 pf_device = platform_device_register_full(&pdev_info);793 if (IS_ERR(pf_device)) {794 ret = PTR_ERR(pf_device);795 pf_device = NULL;796 pr_err("unable to register platform device\n");797 goto out_platform_registered;798 }799 product = dmi_get_system_info(DMI_PRODUCT_NAME);800 if (product && strlen(product) > 4)801 switch (product[4]) {802 case '5':803 if (strlen(product) > 5)804 switch (product[5]) {805 case 'N':806 year = 2021;807 break;808 case '0':809 year = 2016;810 break;811 default:812 year = 2022;813 }814 break;815 case '6':816 year = 2016;817 break;818 case '7':819 year = 2017;820 break;821 case '8':822 year = 2018;823 break;824 case '9':825 year = 2019;826 break;827 case '0':828 if (strlen(product) > 5)829 switch (product[5]) {830 case 'N':831 year = 2020;832 break;833 case 'P':834 year = 2021;835 break;836 default:837 year = 2022;838 }839 break;840 default:841 year = 2019;842 }843 pr_info("product: %s year: %d\n", product ?: "unknown", year);844 845 if (year >= 2019)846 battery_limit_use_wmbb = 1;847 848 ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group);849 if (ret)850 goto out_platform_device;851 852 /* LEDs are optional */853 led_classdev_register(&pf_device->dev, &kbd_backlight);854 led_classdev_register(&pf_device->dev, &tpad_led);855 856 wmi_input_setup();857 battery_hook_register(&battery_hook);858 859 return 0;860 861out_platform_device:862 platform_device_unregister(pf_device);863out_platform_registered:864 platform_driver_unregister(&pf_driver);865 return ret;866}867 868static void acpi_remove(struct acpi_device *device)869{870 sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group);871 872 led_classdev_unregister(&tpad_led);873 led_classdev_unregister(&kbd_backlight);874 875 battery_hook_unregister(&battery_hook);876 wmi_input_destroy();877 platform_device_unregister(pf_device);878 pf_device = NULL;879 platform_driver_unregister(&pf_driver);880}881 882static const struct acpi_device_id device_ids[] = {883 {"LGEX0820", 0},884 {"", 0}885};886MODULE_DEVICE_TABLE(acpi, device_ids);887 888static struct acpi_driver acpi_driver = {889 .name = "LG Gram Laptop Support",890 .class = "lg-laptop",891 .ids = device_ids,892 .ops = {893 .add = acpi_add,894 .remove = acpi_remove,895 .notify = acpi_notify,896 },897};898 899static int __init acpi_init(void)900{901 int result;902 903 result = acpi_bus_register_driver(&acpi_driver);904 if (result < 0) {905 pr_debug("Error registering driver\n");906 return -ENODEV;907 }908 909 return 0;910}911 912static void __exit acpi_exit(void)913{914 acpi_bus_unregister_driver(&acpi_driver);915}916 917module_init(acpi_init);918module_exit(acpi_exit);919