18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * lg-laptop.c - LG Gram ACPI features and hotkeys Driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2018 Matan Ziv-Av <matan@svgalib.org> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/acpi.h> 118c2ecf20Sopenharmony_ci#include <linux/input.h> 128c2ecf20Sopenharmony_ci#include <linux/input/sparse-keymap.h> 138c2ecf20Sopenharmony_ci#include <linux/kernel.h> 148c2ecf20Sopenharmony_ci#include <linux/leds.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci#include <linux/types.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#define LED_DEVICE(_name, max) struct led_classdev _name = { \ 208c2ecf20Sopenharmony_ci .name = __stringify(_name), \ 218c2ecf20Sopenharmony_ci .max_brightness = max, \ 228c2ecf20Sopenharmony_ci .brightness_set = _name##_set, \ 238c2ecf20Sopenharmony_ci .brightness_get = _name##_get, \ 248c2ecf20Sopenharmony_ci} 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ciMODULE_AUTHOR("Matan Ziv-Av"); 278c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("LG WMI Hotkey Driver"); 288c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248" 318c2ecf20Sopenharmony_ci#define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2" 328c2ecf20Sopenharmony_ci#define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6" 338c2ecf20Sopenharmony_ci#define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B" 348c2ecf20Sopenharmony_ci#define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D" 358c2ecf20Sopenharmony_ci#define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210" 368c2ecf20Sopenharmony_ci#define WMI_EVENT_GUID WMI_EVENT_GUID0 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci#define WMAB_METHOD "\\XINI.WMAB" 398c2ecf20Sopenharmony_ci#define WMBB_METHOD "\\XINI.WMBB" 408c2ecf20Sopenharmony_ci#define SB_GGOV_METHOD "\\_SB.GGOV" 418c2ecf20Sopenharmony_ci#define GOV_TLED 0x2020008 428c2ecf20Sopenharmony_ci#define WM_GET 1 438c2ecf20Sopenharmony_ci#define WM_SET 2 448c2ecf20Sopenharmony_ci#define WM_KEY_LIGHT 0x400 458c2ecf20Sopenharmony_ci#define WM_TLED 0x404 468c2ecf20Sopenharmony_ci#define WM_FN_LOCK 0x407 478c2ecf20Sopenharmony_ci#define WM_BATT_LIMIT 0x61 488c2ecf20Sopenharmony_ci#define WM_READER_MODE 0xBF 498c2ecf20Sopenharmony_ci#define WM_FAN_MODE 0x33 508c2ecf20Sopenharmony_ci#define WMBB_USB_CHARGE 0x10B 518c2ecf20Sopenharmony_ci#define WMBB_BATT_LIMIT 0x10C 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci#define PLATFORM_NAME "lg-laptop" 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ciMODULE_ALIAS("wmi:" WMI_EVENT_GUID0); 568c2ecf20Sopenharmony_ciMODULE_ALIAS("wmi:" WMI_EVENT_GUID1); 578c2ecf20Sopenharmony_ciMODULE_ALIAS("wmi:" WMI_EVENT_GUID2); 588c2ecf20Sopenharmony_ciMODULE_ALIAS("wmi:" WMI_EVENT_GUID3); 598c2ecf20Sopenharmony_ciMODULE_ALIAS("wmi:" WMI_METHOD_WMAB); 608c2ecf20Sopenharmony_ciMODULE_ALIAS("wmi:" WMI_METHOD_WMBB); 618c2ecf20Sopenharmony_ciMODULE_ALIAS("acpi*:LGEX0815:*"); 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_cistatic struct platform_device *pf_device; 648c2ecf20Sopenharmony_cistatic struct input_dev *wmi_input_dev; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_cistatic u32 inited; 678c2ecf20Sopenharmony_ci#define INIT_INPUT_WMI_0 0x01 688c2ecf20Sopenharmony_ci#define INIT_INPUT_WMI_2 0x02 698c2ecf20Sopenharmony_ci#define INIT_INPUT_ACPI 0x04 708c2ecf20Sopenharmony_ci#define INIT_SPARSE_KEYMAP 0x80 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_cistatic const struct key_entry wmi_keymap[] = { 738c2ecf20Sopenharmony_ci {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */ 748c2ecf20Sopenharmony_ci {KE_KEY, 0x74, {KEY_F13} }, /* Touchpad toggle (F5) */ 758c2ecf20Sopenharmony_ci {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */ 768c2ecf20Sopenharmony_ci {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing 778c2ecf20Sopenharmony_ci * this key both sends an event and 788c2ecf20Sopenharmony_ci * changes backlight level. 798c2ecf20Sopenharmony_ci */ 808c2ecf20Sopenharmony_ci {KE_KEY, 0x80, {KEY_RFKILL} }, 818c2ecf20Sopenharmony_ci {KE_END, 0} 828c2ecf20Sopenharmony_ci}; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic int ggov(u32 arg0) 858c2ecf20Sopenharmony_ci{ 868c2ecf20Sopenharmony_ci union acpi_object args[1]; 878c2ecf20Sopenharmony_ci union acpi_object *r; 888c2ecf20Sopenharmony_ci acpi_status status; 898c2ecf20Sopenharmony_ci acpi_handle handle; 908c2ecf20Sopenharmony_ci struct acpi_object_list arg; 918c2ecf20Sopenharmony_ci struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 928c2ecf20Sopenharmony_ci int res; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci args[0].type = ACPI_TYPE_INTEGER; 958c2ecf20Sopenharmony_ci args[0].integer.value = arg0; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci status = acpi_get_handle(NULL, (acpi_string) SB_GGOV_METHOD, &handle); 988c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 998c2ecf20Sopenharmony_ci pr_err("Cannot get handle"); 1008c2ecf20Sopenharmony_ci return -ENODEV; 1018c2ecf20Sopenharmony_ci } 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci arg.count = 1; 1048c2ecf20Sopenharmony_ci arg.pointer = args; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci status = acpi_evaluate_object(handle, NULL, &arg, &buffer); 1078c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 1088c2ecf20Sopenharmony_ci acpi_handle_err(handle, "GGOV: call failed.\n"); 1098c2ecf20Sopenharmony_ci return -EINVAL; 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci r = buffer.pointer; 1138c2ecf20Sopenharmony_ci if (r->type != ACPI_TYPE_INTEGER) { 1148c2ecf20Sopenharmony_ci kfree(r); 1158c2ecf20Sopenharmony_ci return -EINVAL; 1168c2ecf20Sopenharmony_ci } 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci res = r->integer.value; 1198c2ecf20Sopenharmony_ci kfree(r); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci return res; 1228c2ecf20Sopenharmony_ci} 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic union acpi_object *lg_wmab(u32 method, u32 arg1, u32 arg2) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci union acpi_object args[3]; 1278c2ecf20Sopenharmony_ci acpi_status status; 1288c2ecf20Sopenharmony_ci acpi_handle handle; 1298c2ecf20Sopenharmony_ci struct acpi_object_list arg; 1308c2ecf20Sopenharmony_ci struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci args[0].type = ACPI_TYPE_INTEGER; 1338c2ecf20Sopenharmony_ci args[0].integer.value = method; 1348c2ecf20Sopenharmony_ci args[1].type = ACPI_TYPE_INTEGER; 1358c2ecf20Sopenharmony_ci args[1].integer.value = arg1; 1368c2ecf20Sopenharmony_ci args[2].type = ACPI_TYPE_INTEGER; 1378c2ecf20Sopenharmony_ci args[2].integer.value = arg2; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci status = acpi_get_handle(NULL, (acpi_string) WMAB_METHOD, &handle); 1408c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 1418c2ecf20Sopenharmony_ci pr_err("Cannot get handle"); 1428c2ecf20Sopenharmony_ci return NULL; 1438c2ecf20Sopenharmony_ci } 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci arg.count = 3; 1468c2ecf20Sopenharmony_ci arg.pointer = args; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci status = acpi_evaluate_object(handle, NULL, &arg, &buffer); 1498c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 1508c2ecf20Sopenharmony_ci acpi_handle_err(handle, "WMAB: call failed.\n"); 1518c2ecf20Sopenharmony_ci return NULL; 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci return buffer.pointer; 1558c2ecf20Sopenharmony_ci} 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic union acpi_object *lg_wmbb(u32 method_id, u32 arg1, u32 arg2) 1588c2ecf20Sopenharmony_ci{ 1598c2ecf20Sopenharmony_ci union acpi_object args[3]; 1608c2ecf20Sopenharmony_ci acpi_status status; 1618c2ecf20Sopenharmony_ci acpi_handle handle; 1628c2ecf20Sopenharmony_ci struct acpi_object_list arg; 1638c2ecf20Sopenharmony_ci struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 1648c2ecf20Sopenharmony_ci u8 buf[32]; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci *(u32 *)buf = method_id; 1678c2ecf20Sopenharmony_ci *(u32 *)(buf + 4) = arg1; 1688c2ecf20Sopenharmony_ci *(u32 *)(buf + 16) = arg2; 1698c2ecf20Sopenharmony_ci args[0].type = ACPI_TYPE_INTEGER; 1708c2ecf20Sopenharmony_ci args[0].integer.value = 0; /* ignored */ 1718c2ecf20Sopenharmony_ci args[1].type = ACPI_TYPE_INTEGER; 1728c2ecf20Sopenharmony_ci args[1].integer.value = 1; /* Must be 1 or 2. Does not matter which */ 1738c2ecf20Sopenharmony_ci args[2].type = ACPI_TYPE_BUFFER; 1748c2ecf20Sopenharmony_ci args[2].buffer.length = 32; 1758c2ecf20Sopenharmony_ci args[2].buffer.pointer = buf; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci status = acpi_get_handle(NULL, (acpi_string)WMBB_METHOD, &handle); 1788c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 1798c2ecf20Sopenharmony_ci pr_err("Cannot get handle"); 1808c2ecf20Sopenharmony_ci return NULL; 1818c2ecf20Sopenharmony_ci } 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci arg.count = 3; 1848c2ecf20Sopenharmony_ci arg.pointer = args; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci status = acpi_evaluate_object(handle, NULL, &arg, &buffer); 1878c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 1888c2ecf20Sopenharmony_ci acpi_handle_err(handle, "WMAB: call failed.\n"); 1898c2ecf20Sopenharmony_ci return NULL; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci return (union acpi_object *)buffer.pointer; 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic void wmi_notify(u32 value, void *context) 1968c2ecf20Sopenharmony_ci{ 1978c2ecf20Sopenharmony_ci struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; 1988c2ecf20Sopenharmony_ci union acpi_object *obj; 1998c2ecf20Sopenharmony_ci acpi_status status; 2008c2ecf20Sopenharmony_ci long data = (long)context; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci pr_debug("event guid %li\n", data); 2038c2ecf20Sopenharmony_ci status = wmi_get_event_data(value, &response); 2048c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 2058c2ecf20Sopenharmony_ci pr_err("Bad event status 0x%x\n", status); 2068c2ecf20Sopenharmony_ci return; 2078c2ecf20Sopenharmony_ci } 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci obj = (union acpi_object *)response.pointer; 2108c2ecf20Sopenharmony_ci if (!obj) 2118c2ecf20Sopenharmony_ci return; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci if (obj->type == ACPI_TYPE_INTEGER) { 2148c2ecf20Sopenharmony_ci int eventcode = obj->integer.value; 2158c2ecf20Sopenharmony_ci struct key_entry *key; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci key = 2188c2ecf20Sopenharmony_ci sparse_keymap_entry_from_scancode(wmi_input_dev, eventcode); 2198c2ecf20Sopenharmony_ci if (key && key->type == KE_KEY) 2208c2ecf20Sopenharmony_ci sparse_keymap_report_entry(wmi_input_dev, key, 1, true); 2218c2ecf20Sopenharmony_ci } 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type, 2248c2ecf20Sopenharmony_ci obj->integer.value); 2258c2ecf20Sopenharmony_ci kfree(response.pointer); 2268c2ecf20Sopenharmony_ci} 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_cistatic void wmi_input_setup(void) 2298c2ecf20Sopenharmony_ci{ 2308c2ecf20Sopenharmony_ci acpi_status status; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci wmi_input_dev = input_allocate_device(); 2338c2ecf20Sopenharmony_ci if (wmi_input_dev) { 2348c2ecf20Sopenharmony_ci wmi_input_dev->name = "LG WMI hotkeys"; 2358c2ecf20Sopenharmony_ci wmi_input_dev->phys = "wmi/input0"; 2368c2ecf20Sopenharmony_ci wmi_input_dev->id.bustype = BUS_HOST; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci if (sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL) || 2398c2ecf20Sopenharmony_ci input_register_device(wmi_input_dev)) { 2408c2ecf20Sopenharmony_ci pr_info("Cannot initialize input device"); 2418c2ecf20Sopenharmony_ci input_free_device(wmi_input_dev); 2428c2ecf20Sopenharmony_ci return; 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci inited |= INIT_SPARSE_KEYMAP; 2468c2ecf20Sopenharmony_ci status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify, 2478c2ecf20Sopenharmony_ci (void *)0); 2488c2ecf20Sopenharmony_ci if (ACPI_SUCCESS(status)) 2498c2ecf20Sopenharmony_ci inited |= INIT_INPUT_WMI_0; 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify, 2528c2ecf20Sopenharmony_ci (void *)2); 2538c2ecf20Sopenharmony_ci if (ACPI_SUCCESS(status)) 2548c2ecf20Sopenharmony_ci inited |= INIT_INPUT_WMI_2; 2558c2ecf20Sopenharmony_ci } else { 2568c2ecf20Sopenharmony_ci pr_info("Cannot allocate input device"); 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci} 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_cistatic void acpi_notify(struct acpi_device *device, u32 event) 2618c2ecf20Sopenharmony_ci{ 2628c2ecf20Sopenharmony_ci struct key_entry *key; 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci acpi_handle_debug(device->handle, "notify: %d\n", event); 2658c2ecf20Sopenharmony_ci if (inited & INIT_SPARSE_KEYMAP) { 2668c2ecf20Sopenharmony_ci key = sparse_keymap_entry_from_scancode(wmi_input_dev, 0x80); 2678c2ecf20Sopenharmony_ci if (key && key->type == KE_KEY) 2688c2ecf20Sopenharmony_ci sparse_keymap_report_entry(wmi_input_dev, key, 1, true); 2698c2ecf20Sopenharmony_ci } 2708c2ecf20Sopenharmony_ci} 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_cistatic ssize_t fan_mode_store(struct device *dev, 2738c2ecf20Sopenharmony_ci struct device_attribute *attr, 2748c2ecf20Sopenharmony_ci const char *buffer, size_t count) 2758c2ecf20Sopenharmony_ci{ 2768c2ecf20Sopenharmony_ci bool value; 2778c2ecf20Sopenharmony_ci union acpi_object *r; 2788c2ecf20Sopenharmony_ci u32 m; 2798c2ecf20Sopenharmony_ci int ret; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci ret = kstrtobool(buffer, &value); 2828c2ecf20Sopenharmony_ci if (ret) 2838c2ecf20Sopenharmony_ci return ret; 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci r = lg_wmab(WM_FAN_MODE, WM_GET, 0); 2868c2ecf20Sopenharmony_ci if (!r) 2878c2ecf20Sopenharmony_ci return -EIO; 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci if (r->type != ACPI_TYPE_INTEGER) { 2908c2ecf20Sopenharmony_ci kfree(r); 2918c2ecf20Sopenharmony_ci return -EIO; 2928c2ecf20Sopenharmony_ci } 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci m = r->integer.value; 2958c2ecf20Sopenharmony_ci kfree(r); 2968c2ecf20Sopenharmony_ci r = lg_wmab(WM_FAN_MODE, WM_SET, (m & 0xffffff0f) | (value << 4)); 2978c2ecf20Sopenharmony_ci kfree(r); 2988c2ecf20Sopenharmony_ci r = lg_wmab(WM_FAN_MODE, WM_SET, (m & 0xfffffff0) | value); 2998c2ecf20Sopenharmony_ci kfree(r); 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci return count; 3028c2ecf20Sopenharmony_ci} 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_cistatic ssize_t fan_mode_show(struct device *dev, 3058c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buffer) 3068c2ecf20Sopenharmony_ci{ 3078c2ecf20Sopenharmony_ci unsigned int status; 3088c2ecf20Sopenharmony_ci union acpi_object *r; 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci r = lg_wmab(WM_FAN_MODE, WM_GET, 0); 3118c2ecf20Sopenharmony_ci if (!r) 3128c2ecf20Sopenharmony_ci return -EIO; 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci if (r->type != ACPI_TYPE_INTEGER) { 3158c2ecf20Sopenharmony_ci kfree(r); 3168c2ecf20Sopenharmony_ci return -EIO; 3178c2ecf20Sopenharmony_ci } 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci status = r->integer.value & 0x01; 3208c2ecf20Sopenharmony_ci kfree(r); 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci return snprintf(buffer, PAGE_SIZE, "%d\n", status); 3238c2ecf20Sopenharmony_ci} 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_cistatic ssize_t usb_charge_store(struct device *dev, 3268c2ecf20Sopenharmony_ci struct device_attribute *attr, 3278c2ecf20Sopenharmony_ci const char *buffer, size_t count) 3288c2ecf20Sopenharmony_ci{ 3298c2ecf20Sopenharmony_ci bool value; 3308c2ecf20Sopenharmony_ci union acpi_object *r; 3318c2ecf20Sopenharmony_ci int ret; 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci ret = kstrtobool(buffer, &value); 3348c2ecf20Sopenharmony_ci if (ret) 3358c2ecf20Sopenharmony_ci return ret; 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci r = lg_wmbb(WMBB_USB_CHARGE, WM_SET, value); 3388c2ecf20Sopenharmony_ci if (!r) 3398c2ecf20Sopenharmony_ci return -EIO; 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci kfree(r); 3428c2ecf20Sopenharmony_ci return count; 3438c2ecf20Sopenharmony_ci} 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_cistatic ssize_t usb_charge_show(struct device *dev, 3468c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buffer) 3478c2ecf20Sopenharmony_ci{ 3488c2ecf20Sopenharmony_ci unsigned int status; 3498c2ecf20Sopenharmony_ci union acpi_object *r; 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci r = lg_wmbb(WMBB_USB_CHARGE, WM_GET, 0); 3528c2ecf20Sopenharmony_ci if (!r) 3538c2ecf20Sopenharmony_ci return -EIO; 3548c2ecf20Sopenharmony_ci 3558c2ecf20Sopenharmony_ci if (r->type != ACPI_TYPE_BUFFER) { 3568c2ecf20Sopenharmony_ci kfree(r); 3578c2ecf20Sopenharmony_ci return -EIO; 3588c2ecf20Sopenharmony_ci } 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_ci status = !!r->buffer.pointer[0x10]; 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci kfree(r); 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci return snprintf(buffer, PAGE_SIZE, "%d\n", status); 3658c2ecf20Sopenharmony_ci} 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_cistatic ssize_t reader_mode_store(struct device *dev, 3688c2ecf20Sopenharmony_ci struct device_attribute *attr, 3698c2ecf20Sopenharmony_ci const char *buffer, size_t count) 3708c2ecf20Sopenharmony_ci{ 3718c2ecf20Sopenharmony_ci bool value; 3728c2ecf20Sopenharmony_ci union acpi_object *r; 3738c2ecf20Sopenharmony_ci int ret; 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci ret = kstrtobool(buffer, &value); 3768c2ecf20Sopenharmony_ci if (ret) 3778c2ecf20Sopenharmony_ci return ret; 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_ci r = lg_wmab(WM_READER_MODE, WM_SET, value); 3808c2ecf20Sopenharmony_ci if (!r) 3818c2ecf20Sopenharmony_ci return -EIO; 3828c2ecf20Sopenharmony_ci 3838c2ecf20Sopenharmony_ci kfree(r); 3848c2ecf20Sopenharmony_ci return count; 3858c2ecf20Sopenharmony_ci} 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_cistatic ssize_t reader_mode_show(struct device *dev, 3888c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buffer) 3898c2ecf20Sopenharmony_ci{ 3908c2ecf20Sopenharmony_ci unsigned int status; 3918c2ecf20Sopenharmony_ci union acpi_object *r; 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_ci r = lg_wmab(WM_READER_MODE, WM_GET, 0); 3948c2ecf20Sopenharmony_ci if (!r) 3958c2ecf20Sopenharmony_ci return -EIO; 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci if (r->type != ACPI_TYPE_INTEGER) { 3988c2ecf20Sopenharmony_ci kfree(r); 3998c2ecf20Sopenharmony_ci return -EIO; 4008c2ecf20Sopenharmony_ci } 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ci status = !!r->integer.value; 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci kfree(r); 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_ci return snprintf(buffer, PAGE_SIZE, "%d\n", status); 4078c2ecf20Sopenharmony_ci} 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_cistatic ssize_t fn_lock_store(struct device *dev, 4108c2ecf20Sopenharmony_ci struct device_attribute *attr, 4118c2ecf20Sopenharmony_ci const char *buffer, size_t count) 4128c2ecf20Sopenharmony_ci{ 4138c2ecf20Sopenharmony_ci bool value; 4148c2ecf20Sopenharmony_ci union acpi_object *r; 4158c2ecf20Sopenharmony_ci int ret; 4168c2ecf20Sopenharmony_ci 4178c2ecf20Sopenharmony_ci ret = kstrtobool(buffer, &value); 4188c2ecf20Sopenharmony_ci if (ret) 4198c2ecf20Sopenharmony_ci return ret; 4208c2ecf20Sopenharmony_ci 4218c2ecf20Sopenharmony_ci r = lg_wmab(WM_FN_LOCK, WM_SET, value); 4228c2ecf20Sopenharmony_ci if (!r) 4238c2ecf20Sopenharmony_ci return -EIO; 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_ci kfree(r); 4268c2ecf20Sopenharmony_ci return count; 4278c2ecf20Sopenharmony_ci} 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_cistatic ssize_t fn_lock_show(struct device *dev, 4308c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buffer) 4318c2ecf20Sopenharmony_ci{ 4328c2ecf20Sopenharmony_ci unsigned int status; 4338c2ecf20Sopenharmony_ci union acpi_object *r; 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci r = lg_wmab(WM_FN_LOCK, WM_GET, 0); 4368c2ecf20Sopenharmony_ci if (!r) 4378c2ecf20Sopenharmony_ci return -EIO; 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci if (r->type != ACPI_TYPE_BUFFER) { 4408c2ecf20Sopenharmony_ci kfree(r); 4418c2ecf20Sopenharmony_ci return -EIO; 4428c2ecf20Sopenharmony_ci } 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_ci status = !!r->buffer.pointer[0]; 4458c2ecf20Sopenharmony_ci kfree(r); 4468c2ecf20Sopenharmony_ci 4478c2ecf20Sopenharmony_ci return snprintf(buffer, PAGE_SIZE, "%d\n", status); 4488c2ecf20Sopenharmony_ci} 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_cistatic ssize_t battery_care_limit_store(struct device *dev, 4518c2ecf20Sopenharmony_ci struct device_attribute *attr, 4528c2ecf20Sopenharmony_ci const char *buffer, size_t count) 4538c2ecf20Sopenharmony_ci{ 4548c2ecf20Sopenharmony_ci unsigned long value; 4558c2ecf20Sopenharmony_ci int ret; 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci ret = kstrtoul(buffer, 10, &value); 4588c2ecf20Sopenharmony_ci if (ret) 4598c2ecf20Sopenharmony_ci return ret; 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci if (value == 100 || value == 80) { 4628c2ecf20Sopenharmony_ci union acpi_object *r; 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci r = lg_wmab(WM_BATT_LIMIT, WM_SET, value); 4658c2ecf20Sopenharmony_ci if (!r) 4668c2ecf20Sopenharmony_ci return -EIO; 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_ci kfree(r); 4698c2ecf20Sopenharmony_ci return count; 4708c2ecf20Sopenharmony_ci } 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci return -EINVAL; 4738c2ecf20Sopenharmony_ci} 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_cistatic ssize_t battery_care_limit_show(struct device *dev, 4768c2ecf20Sopenharmony_ci struct device_attribute *attr, 4778c2ecf20Sopenharmony_ci char *buffer) 4788c2ecf20Sopenharmony_ci{ 4798c2ecf20Sopenharmony_ci unsigned int status; 4808c2ecf20Sopenharmony_ci union acpi_object *r; 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci r = lg_wmab(WM_BATT_LIMIT, WM_GET, 0); 4838c2ecf20Sopenharmony_ci if (!r) 4848c2ecf20Sopenharmony_ci return -EIO; 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_ci if (r->type != ACPI_TYPE_INTEGER) { 4878c2ecf20Sopenharmony_ci kfree(r); 4888c2ecf20Sopenharmony_ci return -EIO; 4898c2ecf20Sopenharmony_ci } 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_ci status = r->integer.value; 4928c2ecf20Sopenharmony_ci kfree(r); 4938c2ecf20Sopenharmony_ci if (status != 80 && status != 100) 4948c2ecf20Sopenharmony_ci status = 0; 4958c2ecf20Sopenharmony_ci 4968c2ecf20Sopenharmony_ci return snprintf(buffer, PAGE_SIZE, "%d\n", status); 4978c2ecf20Sopenharmony_ci} 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(fan_mode); 5008c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(usb_charge); 5018c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(reader_mode); 5028c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(fn_lock); 5038c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(battery_care_limit); 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_cistatic struct attribute *dev_attributes[] = { 5068c2ecf20Sopenharmony_ci &dev_attr_fan_mode.attr, 5078c2ecf20Sopenharmony_ci &dev_attr_usb_charge.attr, 5088c2ecf20Sopenharmony_ci &dev_attr_reader_mode.attr, 5098c2ecf20Sopenharmony_ci &dev_attr_fn_lock.attr, 5108c2ecf20Sopenharmony_ci &dev_attr_battery_care_limit.attr, 5118c2ecf20Sopenharmony_ci NULL 5128c2ecf20Sopenharmony_ci}; 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_cistatic const struct attribute_group dev_attribute_group = { 5158c2ecf20Sopenharmony_ci .attrs = dev_attributes, 5168c2ecf20Sopenharmony_ci}; 5178c2ecf20Sopenharmony_ci 5188c2ecf20Sopenharmony_cistatic void tpad_led_set(struct led_classdev *cdev, 5198c2ecf20Sopenharmony_ci enum led_brightness brightness) 5208c2ecf20Sopenharmony_ci{ 5218c2ecf20Sopenharmony_ci union acpi_object *r; 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_ci r = lg_wmab(WM_TLED, WM_SET, brightness > LED_OFF); 5248c2ecf20Sopenharmony_ci kfree(r); 5258c2ecf20Sopenharmony_ci} 5268c2ecf20Sopenharmony_ci 5278c2ecf20Sopenharmony_cistatic enum led_brightness tpad_led_get(struct led_classdev *cdev) 5288c2ecf20Sopenharmony_ci{ 5298c2ecf20Sopenharmony_ci return ggov(GOV_TLED) > 0 ? LED_ON : LED_OFF; 5308c2ecf20Sopenharmony_ci} 5318c2ecf20Sopenharmony_ci 5328c2ecf20Sopenharmony_cistatic LED_DEVICE(tpad_led, 1); 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_cistatic void kbd_backlight_set(struct led_classdev *cdev, 5358c2ecf20Sopenharmony_ci enum led_brightness brightness) 5368c2ecf20Sopenharmony_ci{ 5378c2ecf20Sopenharmony_ci u32 val; 5388c2ecf20Sopenharmony_ci union acpi_object *r; 5398c2ecf20Sopenharmony_ci 5408c2ecf20Sopenharmony_ci val = 0x22; 5418c2ecf20Sopenharmony_ci if (brightness <= LED_OFF) 5428c2ecf20Sopenharmony_ci val = 0; 5438c2ecf20Sopenharmony_ci if (brightness >= LED_FULL) 5448c2ecf20Sopenharmony_ci val = 0x24; 5458c2ecf20Sopenharmony_ci r = lg_wmab(WM_KEY_LIGHT, WM_SET, val); 5468c2ecf20Sopenharmony_ci kfree(r); 5478c2ecf20Sopenharmony_ci} 5488c2ecf20Sopenharmony_ci 5498c2ecf20Sopenharmony_cistatic enum led_brightness kbd_backlight_get(struct led_classdev *cdev) 5508c2ecf20Sopenharmony_ci{ 5518c2ecf20Sopenharmony_ci union acpi_object *r; 5528c2ecf20Sopenharmony_ci int val; 5538c2ecf20Sopenharmony_ci 5548c2ecf20Sopenharmony_ci r = lg_wmab(WM_KEY_LIGHT, WM_GET, 0); 5558c2ecf20Sopenharmony_ci 5568c2ecf20Sopenharmony_ci if (!r) 5578c2ecf20Sopenharmony_ci return LED_OFF; 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) { 5608c2ecf20Sopenharmony_ci kfree(r); 5618c2ecf20Sopenharmony_ci return LED_OFF; 5628c2ecf20Sopenharmony_ci } 5638c2ecf20Sopenharmony_ci 5648c2ecf20Sopenharmony_ci switch (r->buffer.pointer[0] & 0x27) { 5658c2ecf20Sopenharmony_ci case 0x24: 5668c2ecf20Sopenharmony_ci val = LED_FULL; 5678c2ecf20Sopenharmony_ci break; 5688c2ecf20Sopenharmony_ci case 0x22: 5698c2ecf20Sopenharmony_ci val = LED_HALF; 5708c2ecf20Sopenharmony_ci break; 5718c2ecf20Sopenharmony_ci default: 5728c2ecf20Sopenharmony_ci val = LED_OFF; 5738c2ecf20Sopenharmony_ci } 5748c2ecf20Sopenharmony_ci 5758c2ecf20Sopenharmony_ci kfree(r); 5768c2ecf20Sopenharmony_ci 5778c2ecf20Sopenharmony_ci return val; 5788c2ecf20Sopenharmony_ci} 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_cistatic LED_DEVICE(kbd_backlight, 255); 5818c2ecf20Sopenharmony_ci 5828c2ecf20Sopenharmony_cistatic void wmi_input_destroy(void) 5838c2ecf20Sopenharmony_ci{ 5848c2ecf20Sopenharmony_ci if (inited & INIT_INPUT_WMI_2) 5858c2ecf20Sopenharmony_ci wmi_remove_notify_handler(WMI_EVENT_GUID2); 5868c2ecf20Sopenharmony_ci 5878c2ecf20Sopenharmony_ci if (inited & INIT_INPUT_WMI_0) 5888c2ecf20Sopenharmony_ci wmi_remove_notify_handler(WMI_EVENT_GUID0); 5898c2ecf20Sopenharmony_ci 5908c2ecf20Sopenharmony_ci if (inited & INIT_SPARSE_KEYMAP) 5918c2ecf20Sopenharmony_ci input_unregister_device(wmi_input_dev); 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_ci inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP); 5948c2ecf20Sopenharmony_ci} 5958c2ecf20Sopenharmony_ci 5968c2ecf20Sopenharmony_cistatic struct platform_driver pf_driver = { 5978c2ecf20Sopenharmony_ci .driver = { 5988c2ecf20Sopenharmony_ci .name = PLATFORM_NAME, 5998c2ecf20Sopenharmony_ci } 6008c2ecf20Sopenharmony_ci}; 6018c2ecf20Sopenharmony_ci 6028c2ecf20Sopenharmony_cistatic int acpi_add(struct acpi_device *device) 6038c2ecf20Sopenharmony_ci{ 6048c2ecf20Sopenharmony_ci int ret; 6058c2ecf20Sopenharmony_ci 6068c2ecf20Sopenharmony_ci if (pf_device) 6078c2ecf20Sopenharmony_ci return 0; 6088c2ecf20Sopenharmony_ci 6098c2ecf20Sopenharmony_ci ret = platform_driver_register(&pf_driver); 6108c2ecf20Sopenharmony_ci if (ret) 6118c2ecf20Sopenharmony_ci return ret; 6128c2ecf20Sopenharmony_ci 6138c2ecf20Sopenharmony_ci pf_device = platform_device_register_simple(PLATFORM_NAME, 6148c2ecf20Sopenharmony_ci PLATFORM_DEVID_NONE, 6158c2ecf20Sopenharmony_ci NULL, 0); 6168c2ecf20Sopenharmony_ci if (IS_ERR(pf_device)) { 6178c2ecf20Sopenharmony_ci ret = PTR_ERR(pf_device); 6188c2ecf20Sopenharmony_ci pf_device = NULL; 6198c2ecf20Sopenharmony_ci pr_err("unable to register platform device\n"); 6208c2ecf20Sopenharmony_ci goto out_platform_registered; 6218c2ecf20Sopenharmony_ci } 6228c2ecf20Sopenharmony_ci 6238c2ecf20Sopenharmony_ci ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); 6248c2ecf20Sopenharmony_ci if (ret) 6258c2ecf20Sopenharmony_ci goto out_platform_device; 6268c2ecf20Sopenharmony_ci 6278c2ecf20Sopenharmony_ci /* LEDs are optional */ 6288c2ecf20Sopenharmony_ci led_classdev_register(&pf_device->dev, &kbd_backlight); 6298c2ecf20Sopenharmony_ci led_classdev_register(&pf_device->dev, &tpad_led); 6308c2ecf20Sopenharmony_ci 6318c2ecf20Sopenharmony_ci wmi_input_setup(); 6328c2ecf20Sopenharmony_ci 6338c2ecf20Sopenharmony_ci return 0; 6348c2ecf20Sopenharmony_ci 6358c2ecf20Sopenharmony_ciout_platform_device: 6368c2ecf20Sopenharmony_ci platform_device_unregister(pf_device); 6378c2ecf20Sopenharmony_ciout_platform_registered: 6388c2ecf20Sopenharmony_ci platform_driver_unregister(&pf_driver); 6398c2ecf20Sopenharmony_ci return ret; 6408c2ecf20Sopenharmony_ci} 6418c2ecf20Sopenharmony_ci 6428c2ecf20Sopenharmony_cistatic int acpi_remove(struct acpi_device *device) 6438c2ecf20Sopenharmony_ci{ 6448c2ecf20Sopenharmony_ci sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); 6458c2ecf20Sopenharmony_ci 6468c2ecf20Sopenharmony_ci led_classdev_unregister(&tpad_led); 6478c2ecf20Sopenharmony_ci led_classdev_unregister(&kbd_backlight); 6488c2ecf20Sopenharmony_ci 6498c2ecf20Sopenharmony_ci wmi_input_destroy(); 6508c2ecf20Sopenharmony_ci platform_device_unregister(pf_device); 6518c2ecf20Sopenharmony_ci pf_device = NULL; 6528c2ecf20Sopenharmony_ci platform_driver_unregister(&pf_driver); 6538c2ecf20Sopenharmony_ci 6548c2ecf20Sopenharmony_ci return 0; 6558c2ecf20Sopenharmony_ci} 6568c2ecf20Sopenharmony_ci 6578c2ecf20Sopenharmony_cistatic const struct acpi_device_id device_ids[] = { 6588c2ecf20Sopenharmony_ci {"LGEX0815", 0}, 6598c2ecf20Sopenharmony_ci {"", 0} 6608c2ecf20Sopenharmony_ci}; 6618c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, device_ids); 6628c2ecf20Sopenharmony_ci 6638c2ecf20Sopenharmony_cistatic struct acpi_driver acpi_driver = { 6648c2ecf20Sopenharmony_ci .name = "LG Gram Laptop Support", 6658c2ecf20Sopenharmony_ci .class = "lg-laptop", 6668c2ecf20Sopenharmony_ci .ids = device_ids, 6678c2ecf20Sopenharmony_ci .ops = { 6688c2ecf20Sopenharmony_ci .add = acpi_add, 6698c2ecf20Sopenharmony_ci .remove = acpi_remove, 6708c2ecf20Sopenharmony_ci .notify = acpi_notify, 6718c2ecf20Sopenharmony_ci }, 6728c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 6738c2ecf20Sopenharmony_ci}; 6748c2ecf20Sopenharmony_ci 6758c2ecf20Sopenharmony_cistatic int __init acpi_init(void) 6768c2ecf20Sopenharmony_ci{ 6778c2ecf20Sopenharmony_ci int result; 6788c2ecf20Sopenharmony_ci 6798c2ecf20Sopenharmony_ci result = acpi_bus_register_driver(&acpi_driver); 6808c2ecf20Sopenharmony_ci if (result < 0) { 6818c2ecf20Sopenharmony_ci ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error registering driver\n")); 6828c2ecf20Sopenharmony_ci return -ENODEV; 6838c2ecf20Sopenharmony_ci } 6848c2ecf20Sopenharmony_ci 6858c2ecf20Sopenharmony_ci return 0; 6868c2ecf20Sopenharmony_ci} 6878c2ecf20Sopenharmony_ci 6888c2ecf20Sopenharmony_cistatic void __exit acpi_exit(void) 6898c2ecf20Sopenharmony_ci{ 6908c2ecf20Sopenharmony_ci acpi_bus_unregister_driver(&acpi_driver); 6918c2ecf20Sopenharmony_ci} 6928c2ecf20Sopenharmony_ci 6938c2ecf20Sopenharmony_cimodule_init(acpi_init); 6948c2ecf20Sopenharmony_cimodule_exit(acpi_exit); 695