18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Generic Loongson processor based LAPTOP/ALL-IN-ONE driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Jianmin Lv <lvjianmin@loongson.cn> 68c2ecf20Sopenharmony_ci * Huacai Chen <chenhuacai@loongson.cn> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or modify 98c2ecf20Sopenharmony_ci * it under the terms of the GNU General Public License as published by 108c2ecf20Sopenharmony_ci * the Free Software Foundation; either version 2 of the License, or 118c2ecf20Sopenharmony_ci * (at your option) any later version. 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * This program is distributed in the hope that it will be useful, 148c2ecf20Sopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of 158c2ecf20Sopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 168c2ecf20Sopenharmony_ci * GNU General Public License for more details. 178c2ecf20Sopenharmony_ci */ 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include <linux/init.h> 228c2ecf20Sopenharmony_ci#include <linux/kernel.h> 238c2ecf20Sopenharmony_ci#include <linux/module.h> 248c2ecf20Sopenharmony_ci#include <linux/acpi.h> 258c2ecf20Sopenharmony_ci#include <linux/backlight.h> 268c2ecf20Sopenharmony_ci#include <linux/device.h> 278c2ecf20Sopenharmony_ci#include <linux/input.h> 288c2ecf20Sopenharmony_ci#include <linux/input/sparse-keymap.h> 298c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 308c2ecf20Sopenharmony_ci#include <linux/string.h> 318c2ecf20Sopenharmony_ci#include <linux/types.h> 328c2ecf20Sopenharmony_ci#include <acpi/video.h> 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci/* 1. Driver-wide structs and misc. variables */ 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci/* ACPI HIDs */ 378c2ecf20Sopenharmony_ci#define LOONGSON_ACPI_EC_HID "PNP0C09" 388c2ecf20Sopenharmony_ci#define LOONGSON_ACPI_HKEY_HID "LOON0000" 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci#define ACPI_LAPTOP_NAME "loongson-laptop" 418c2ecf20Sopenharmony_ci#define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson" 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci#define MAX_ACPI_ARGS 3 448c2ecf20Sopenharmony_ci#define GENERIC_HOTKEY_MAP_MAX 64 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci#define GENERIC_EVENT_TYPE_OFF 12 478c2ecf20Sopenharmony_ci#define GENERIC_EVENT_TYPE_MASK 0xF000 488c2ecf20Sopenharmony_ci#define GENERIC_EVENT_CODE_MASK 0x0FFF 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistruct generic_sub_driver { 518c2ecf20Sopenharmony_ci u32 type; 528c2ecf20Sopenharmony_ci char *name; 538c2ecf20Sopenharmony_ci acpi_handle *handle; 548c2ecf20Sopenharmony_ci struct acpi_device *device; 558c2ecf20Sopenharmony_ci struct platform_driver *driver; 568c2ecf20Sopenharmony_ci int (*init)(struct generic_sub_driver *sub_driver); 578c2ecf20Sopenharmony_ci void (*notify)(struct generic_sub_driver *sub_driver, u32 event); 588c2ecf20Sopenharmony_ci u8 acpi_notify_installed; 598c2ecf20Sopenharmony_ci}; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic u32 input_device_registered; 628c2ecf20Sopenharmony_cistatic struct input_dev *generic_inputdev; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic acpi_handle hotkey_handle; 658c2ecf20Sopenharmony_cistatic struct key_entry hotkey_keycode_map[GENERIC_HOTKEY_MAP_MAX]; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ciint loongson_laptop_turn_on_backlight(void); 688c2ecf20Sopenharmony_ciint loongson_laptop_turn_off_backlight(void); 698c2ecf20Sopenharmony_cistatic int loongson_laptop_backlight_update(struct backlight_device *bd); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci/* 2. ACPI Helpers and device model */ 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic int acpi_evalf(acpi_handle handle, int *res, char *method, char *fmt, ...) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci char res_type; 768c2ecf20Sopenharmony_ci char *fmt0 = fmt; 778c2ecf20Sopenharmony_ci va_list ap; 788c2ecf20Sopenharmony_ci int success, quiet; 798c2ecf20Sopenharmony_ci acpi_status status; 808c2ecf20Sopenharmony_ci struct acpi_object_list params; 818c2ecf20Sopenharmony_ci struct acpi_buffer result, *resultp; 828c2ecf20Sopenharmony_ci union acpi_object in_objs[MAX_ACPI_ARGS], out_obj; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci if (!*fmt) { 858c2ecf20Sopenharmony_ci pr_err("acpi_evalf() called with empty format\n"); 868c2ecf20Sopenharmony_ci return 0; 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (*fmt == 'q') { 908c2ecf20Sopenharmony_ci quiet = 1; 918c2ecf20Sopenharmony_ci fmt++; 928c2ecf20Sopenharmony_ci } else 938c2ecf20Sopenharmony_ci quiet = 0; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci res_type = *(fmt++); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci params.count = 0; 988c2ecf20Sopenharmony_ci params.pointer = &in_objs[0]; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci va_start(ap, fmt); 1018c2ecf20Sopenharmony_ci while (*fmt) { 1028c2ecf20Sopenharmony_ci char c = *(fmt++); 1038c2ecf20Sopenharmony_ci switch (c) { 1048c2ecf20Sopenharmony_ci case 'd': /* int */ 1058c2ecf20Sopenharmony_ci in_objs[params.count].integer.value = va_arg(ap, int); 1068c2ecf20Sopenharmony_ci in_objs[params.count++].type = ACPI_TYPE_INTEGER; 1078c2ecf20Sopenharmony_ci break; 1088c2ecf20Sopenharmony_ci /* add more types as needed */ 1098c2ecf20Sopenharmony_ci default: 1108c2ecf20Sopenharmony_ci pr_err("acpi_evalf() called with invalid format character '%c'\n", c); 1118c2ecf20Sopenharmony_ci va_end(ap); 1128c2ecf20Sopenharmony_ci return 0; 1138c2ecf20Sopenharmony_ci } 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci va_end(ap); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci if (res_type != 'v') { 1188c2ecf20Sopenharmony_ci result.length = sizeof(out_obj); 1198c2ecf20Sopenharmony_ci result.pointer = &out_obj; 1208c2ecf20Sopenharmony_ci resultp = &result; 1218c2ecf20Sopenharmony_ci } else 1228c2ecf20Sopenharmony_ci resultp = NULL; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci status = acpi_evaluate_object(handle, method, ¶ms, resultp); 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci switch (res_type) { 1278c2ecf20Sopenharmony_ci case 'd': /* int */ 1288c2ecf20Sopenharmony_ci success = (status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER); 1298c2ecf20Sopenharmony_ci if (success && res) 1308c2ecf20Sopenharmony_ci *res = out_obj.integer.value; 1318c2ecf20Sopenharmony_ci break; 1328c2ecf20Sopenharmony_ci case 'v': /* void */ 1338c2ecf20Sopenharmony_ci success = status == AE_OK; 1348c2ecf20Sopenharmony_ci break; 1358c2ecf20Sopenharmony_ci /* add more types as needed */ 1368c2ecf20Sopenharmony_ci default: 1378c2ecf20Sopenharmony_ci pr_err("acpi_evalf() called with invalid format character '%c'\n", res_type); 1388c2ecf20Sopenharmony_ci return 0; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if (!success && !quiet) 1428c2ecf20Sopenharmony_ci pr_err("acpi_evalf(%s, %s, ...) failed: %s\n", 1438c2ecf20Sopenharmony_ci method, fmt0, acpi_format_exception(status)); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci return success; 1468c2ecf20Sopenharmony_ci} 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_cistatic int hotkey_status_get(int *status) 1498c2ecf20Sopenharmony_ci{ 1508c2ecf20Sopenharmony_ci if (!acpi_evalf(hotkey_handle, status, "GSWS", "d")) 1518c2ecf20Sopenharmony_ci return -EIO; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci return 0; 1548c2ecf20Sopenharmony_ci} 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_cistatic void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci struct generic_sub_driver *sub_driver = data; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci if (!sub_driver || !sub_driver->notify) 1618c2ecf20Sopenharmony_ci return; 1628c2ecf20Sopenharmony_ci sub_driver->notify(sub_driver, event); 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic int __init setup_acpi_notify(struct generic_sub_driver *sub_driver) 1668c2ecf20Sopenharmony_ci{ 1678c2ecf20Sopenharmony_ci int rc; 1688c2ecf20Sopenharmony_ci acpi_status status; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci if (!*sub_driver->handle) 1718c2ecf20Sopenharmony_ci return 0; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci rc = acpi_bus_get_device(*sub_driver->handle, &sub_driver->device); 1748c2ecf20Sopenharmony_ci if (rc < 0) { 1758c2ecf20Sopenharmony_ci pr_err("acpi_bus_get_device(%s) failed: %d\n", sub_driver->name, rc); 1768c2ecf20Sopenharmony_ci return -ENODEV; 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci sub_driver->device->driver_data = sub_driver; 1808c2ecf20Sopenharmony_ci sprintf(acpi_device_class(sub_driver->device), "%s/%s", 1818c2ecf20Sopenharmony_ci ACPI_LAPTOP_ACPI_EVENT_PREFIX, sub_driver->name); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci status = acpi_install_notify_handler(*sub_driver->handle, 1848c2ecf20Sopenharmony_ci sub_driver->type, dispatch_acpi_notify, sub_driver); 1858c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 1868c2ecf20Sopenharmony_ci if (status == AE_ALREADY_EXISTS) { 1878c2ecf20Sopenharmony_ci pr_notice("Another device driver is already " 1888c2ecf20Sopenharmony_ci "handling %s events\n", sub_driver->name); 1898c2ecf20Sopenharmony_ci } else { 1908c2ecf20Sopenharmony_ci pr_err("acpi_install_notify_handler(%s) failed: %s\n", 1918c2ecf20Sopenharmony_ci sub_driver->name, acpi_format_exception(status)); 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci return -ENODEV; 1948c2ecf20Sopenharmony_ci } 1958c2ecf20Sopenharmony_ci sub_driver->acpi_notify_installed = 1; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci return 0; 1988c2ecf20Sopenharmony_ci} 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 2018c2ecf20Sopenharmony_cistatic int loongson_hotkey_suspend(struct device *dev) 2028c2ecf20Sopenharmony_ci{ 2038c2ecf20Sopenharmony_ci return 0; 2048c2ecf20Sopenharmony_ci} 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cistatic int loongson_hotkey_resume(struct device *dev) 2078c2ecf20Sopenharmony_ci{ 2088c2ecf20Sopenharmony_ci int status = 0; 2098c2ecf20Sopenharmony_ci struct key_entry ke; 2108c2ecf20Sopenharmony_ci struct backlight_device *bd; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM); 2138c2ecf20Sopenharmony_ci if (bd) { 2148c2ecf20Sopenharmony_ci loongson_laptop_backlight_update(bd) ? 2158c2ecf20Sopenharmony_ci pr_warn("Loongson_backlight: resume brightness failed") : 2168c2ecf20Sopenharmony_ci pr_info("Loongson_backlight: resume brightness %d\n", bd->props.brightness); 2178c2ecf20Sopenharmony_ci } 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci /* 2208c2ecf20Sopenharmony_ci * Only if the firmware supports SW_LID event model, we can handle the 2218c2ecf20Sopenharmony_ci * event. This is for the consideration of development board without EC. 2228c2ecf20Sopenharmony_ci */ 2238c2ecf20Sopenharmony_ci if (test_bit(SW_LID, generic_inputdev->swbit)) { 2248c2ecf20Sopenharmony_ci if (hotkey_status_get(&status) < 0) 2258c2ecf20Sopenharmony_ci return -EIO; 2268c2ecf20Sopenharmony_ci /* 2278c2ecf20Sopenharmony_ci * The input device sw element records the last lid status. 2288c2ecf20Sopenharmony_ci * When the system is awakened by other wake-up sources, 2298c2ecf20Sopenharmony_ci * the lid event will also be reported. The judgment of 2308c2ecf20Sopenharmony_ci * adding SW_LID bit which in sw element can avoid this 2318c2ecf20Sopenharmony_ci * case. 2328c2ecf20Sopenharmony_ci * 2338c2ecf20Sopenharmony_ci * Input system will drop lid event when current lid event 2348c2ecf20Sopenharmony_ci * value and last lid status in the same. So laptop driver 2358c2ecf20Sopenharmony_ci * doesn't report repeated events. 2368c2ecf20Sopenharmony_ci * 2378c2ecf20Sopenharmony_ci * Lid status is generally 0, but hardware exception is 2388c2ecf20Sopenharmony_ci * considered. So add lid status confirmation. 2398c2ecf20Sopenharmony_ci */ 2408c2ecf20Sopenharmony_ci if (test_bit(SW_LID, generic_inputdev->sw) && !(status & (1 << SW_LID))) { 2418c2ecf20Sopenharmony_ci ke.type = KE_SW; 2428c2ecf20Sopenharmony_ci ke.sw.value = (u8)status; 2438c2ecf20Sopenharmony_ci ke.sw.code = SW_LID; 2448c2ecf20Sopenharmony_ci sparse_keymap_report_entry(generic_inputdev, &ke, 1, true); 2458c2ecf20Sopenharmony_ci } 2468c2ecf20Sopenharmony_ci } 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci return 0; 2498c2ecf20Sopenharmony_ci} 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(loongson_hotkey_pm, 2528c2ecf20Sopenharmony_ci loongson_hotkey_suspend, loongson_hotkey_resume); 2538c2ecf20Sopenharmony_ci#endif 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_cistatic int loongson_hotkey_probe(struct platform_device *pdev) 2568c2ecf20Sopenharmony_ci{ 2578c2ecf20Sopenharmony_ci hotkey_handle = ACPI_HANDLE(&pdev->dev); 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci if (!hotkey_handle) 2608c2ecf20Sopenharmony_ci return -ENODEV; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci return 0; 2638c2ecf20Sopenharmony_ci} 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_cistatic const struct acpi_device_id loongson_device_ids[] = { 2668c2ecf20Sopenharmony_ci {LOONGSON_ACPI_HKEY_HID, 0}, 2678c2ecf20Sopenharmony_ci {"", 0}, 2688c2ecf20Sopenharmony_ci}; 2698c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, loongson_device_ids); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_cistatic struct platform_driver loongson_hotkey_driver = { 2728c2ecf20Sopenharmony_ci .probe = loongson_hotkey_probe, 2738c2ecf20Sopenharmony_ci .driver = { 2748c2ecf20Sopenharmony_ci .name = "loongson-hotkey", 2758c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2768c2ecf20Sopenharmony_ci .pm = pm_ptr(&loongson_hotkey_pm), 2778c2ecf20Sopenharmony_ci .acpi_match_table = loongson_device_ids, 2788c2ecf20Sopenharmony_ci }, 2798c2ecf20Sopenharmony_ci}; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_cistatic int hotkey_map(void) 2828c2ecf20Sopenharmony_ci{ 2838c2ecf20Sopenharmony_ci u32 index; 2848c2ecf20Sopenharmony_ci acpi_status status; 2858c2ecf20Sopenharmony_ci struct acpi_buffer buf; 2868c2ecf20Sopenharmony_ci union acpi_object *pack; 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci buf.length = ACPI_ALLOCATE_BUFFER; 2898c2ecf20Sopenharmony_ci status = acpi_evaluate_object_typed(hotkey_handle, "KMAP", NULL, &buf, ACPI_TYPE_PACKAGE); 2908c2ecf20Sopenharmony_ci if (status != AE_OK) { 2918c2ecf20Sopenharmony_ci pr_err("ACPI exception: %s\n", acpi_format_exception(status)); 2928c2ecf20Sopenharmony_ci return -1; 2938c2ecf20Sopenharmony_ci } 2948c2ecf20Sopenharmony_ci pack = buf.pointer; 2958c2ecf20Sopenharmony_ci for (index = 0; index < pack->package.count; index++) { 2968c2ecf20Sopenharmony_ci union acpi_object *element, *sub_pack; 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci sub_pack = &pack->package.elements[index]; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci element = &sub_pack->package.elements[0]; 3018c2ecf20Sopenharmony_ci hotkey_keycode_map[index].type = element->integer.value; 3028c2ecf20Sopenharmony_ci element = &sub_pack->package.elements[1]; 3038c2ecf20Sopenharmony_ci hotkey_keycode_map[index].code = element->integer.value; 3048c2ecf20Sopenharmony_ci element = &sub_pack->package.elements[2]; 3058c2ecf20Sopenharmony_ci hotkey_keycode_map[index].keycode = element->integer.value; 3068c2ecf20Sopenharmony_ci } 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci return 0; 3098c2ecf20Sopenharmony_ci} 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_cistatic int hotkey_backlight_set(bool enable) 3128c2ecf20Sopenharmony_ci{ 3138c2ecf20Sopenharmony_ci if (!acpi_evalf(hotkey_handle, NULL, "VCBL", "vd", enable ? 1 : 0)) 3148c2ecf20Sopenharmony_ci return -EIO; 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci return 0; 3178c2ecf20Sopenharmony_ci} 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_cistatic int ec_get_brightness(void) 3208c2ecf20Sopenharmony_ci{ 3218c2ecf20Sopenharmony_ci int status = 0; 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ci if (!hotkey_handle) 3248c2ecf20Sopenharmony_ci return -ENXIO; 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci if (!acpi_evalf(hotkey_handle, &status, "ECBG", "d")) 3278c2ecf20Sopenharmony_ci return -EIO; 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci return status; 3308c2ecf20Sopenharmony_ci} 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_cistatic int ec_set_brightness(int level) 3338c2ecf20Sopenharmony_ci{ 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci int ret = 0; 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci if (!hotkey_handle) 3388c2ecf20Sopenharmony_ci return -ENXIO; 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci if (!acpi_evalf(hotkey_handle, NULL, "ECBS", "vd", level)) 3418c2ecf20Sopenharmony_ci ret = -EIO; 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci return ret; 3448c2ecf20Sopenharmony_ci} 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_cistatic int ec_backlight_level(u8 level) 3478c2ecf20Sopenharmony_ci{ 3488c2ecf20Sopenharmony_ci int status = 0; 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci if (!hotkey_handle) 3518c2ecf20Sopenharmony_ci return -ENXIO; 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d")) 3548c2ecf20Sopenharmony_ci return -EIO; 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci if ((status < 0) || (level > status)) 3578c2ecf20Sopenharmony_ci return status; 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci if (!acpi_evalf(hotkey_handle, &status, "ECSL", "d")) 3608c2ecf20Sopenharmony_ci return -EIO; 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci if ((status < 0) || (level < status)) 3638c2ecf20Sopenharmony_ci return status; 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ci return level; 3668c2ecf20Sopenharmony_ci} 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_cistatic int loongson_laptop_backlight_update(struct backlight_device *bd) 3698c2ecf20Sopenharmony_ci{ 3708c2ecf20Sopenharmony_ci int lvl = ec_backlight_level(bd->props.brightness); 3718c2ecf20Sopenharmony_ci if (lvl < 0) 3728c2ecf20Sopenharmony_ci return -EIO; 3738c2ecf20Sopenharmony_ci if (ec_set_brightness(lvl)) 3748c2ecf20Sopenharmony_ci return -EIO; 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_ci return 0; 3778c2ecf20Sopenharmony_ci} 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_cistatic int loongson_laptop_get_brightness(struct backlight_device *bd) 3808c2ecf20Sopenharmony_ci{ 3818c2ecf20Sopenharmony_ci int level; 3828c2ecf20Sopenharmony_ci 3838c2ecf20Sopenharmony_ci level = ec_get_brightness(); 3848c2ecf20Sopenharmony_ci if (level < 0) 3858c2ecf20Sopenharmony_ci return -EIO; 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_ci return level; 3888c2ecf20Sopenharmony_ci} 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_cistatic const struct backlight_ops backlight_laptop_ops = { 3918c2ecf20Sopenharmony_ci .update_status = loongson_laptop_backlight_update, 3928c2ecf20Sopenharmony_ci .get_brightness = loongson_laptop_get_brightness, 3938c2ecf20Sopenharmony_ci}; 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_cistatic int laptop_backlight_register(void) 3968c2ecf20Sopenharmony_ci{ 3978c2ecf20Sopenharmony_ci int status = 0; 3988c2ecf20Sopenharmony_ci struct backlight_properties props; 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_ci memset(&props, 0, sizeof(props)); 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ci if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d")) 4038c2ecf20Sopenharmony_ci return -EIO; 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ci props.brightness = 1; 4068c2ecf20Sopenharmony_ci props.max_brightness = status; 4078c2ecf20Sopenharmony_ci props.type = BACKLIGHT_PLATFORM; 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci backlight_device_register("loongson_laptop", 4108c2ecf20Sopenharmony_ci NULL, NULL, &backlight_laptop_ops, &props); 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_ci return 0; 4138c2ecf20Sopenharmony_ci} 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_ciint loongson_laptop_turn_on_backlight(void) 4168c2ecf20Sopenharmony_ci{ 4178c2ecf20Sopenharmony_ci int status; 4188c2ecf20Sopenharmony_ci union acpi_object arg0 = { ACPI_TYPE_INTEGER }; 4198c2ecf20Sopenharmony_ci struct acpi_object_list args = { 1, &arg0 }; 4208c2ecf20Sopenharmony_ci 4218c2ecf20Sopenharmony_ci arg0.integer.value = 1; 4228c2ecf20Sopenharmony_ci status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL); 4238c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 4248c2ecf20Sopenharmony_ci pr_info("Loongson lvds error:0x%x\n", status); 4258c2ecf20Sopenharmony_ci return -ENODEV; 4268c2ecf20Sopenharmony_ci } 4278c2ecf20Sopenharmony_ci return 0; 4288c2ecf20Sopenharmony_ci} 4298c2ecf20Sopenharmony_ci 4308c2ecf20Sopenharmony_ciint loongson_laptop_turn_off_backlight(void) 4318c2ecf20Sopenharmony_ci{ 4328c2ecf20Sopenharmony_ci int status; 4338c2ecf20Sopenharmony_ci union acpi_object arg0 = { ACPI_TYPE_INTEGER }; 4348c2ecf20Sopenharmony_ci struct acpi_object_list args = { 1, &arg0 }; 4358c2ecf20Sopenharmony_ci 4368c2ecf20Sopenharmony_ci arg0.integer.value = 0; 4378c2ecf20Sopenharmony_ci status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL); 4388c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 4398c2ecf20Sopenharmony_ci pr_info("Loongson lvds error:0x%x\n", status); 4408c2ecf20Sopenharmony_ci return -ENODEV; 4418c2ecf20Sopenharmony_ci } 4428c2ecf20Sopenharmony_ci return 0; 4438c2ecf20Sopenharmony_ci} 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_cistatic int __init event_init(struct generic_sub_driver *sub_driver) 4468c2ecf20Sopenharmony_ci{ 4478c2ecf20Sopenharmony_ci int ret; 4488c2ecf20Sopenharmony_ci 4498c2ecf20Sopenharmony_ci ret = hotkey_map(); 4508c2ecf20Sopenharmony_ci if (ret < 0) { 4518c2ecf20Sopenharmony_ci pr_err("Failed to parse keymap from DSDT\n"); 4528c2ecf20Sopenharmony_ci return ret; 4538c2ecf20Sopenharmony_ci } 4548c2ecf20Sopenharmony_ci 4558c2ecf20Sopenharmony_ci ret = sparse_keymap_setup(generic_inputdev, hotkey_keycode_map, NULL); 4568c2ecf20Sopenharmony_ci if (ret < 0) { 4578c2ecf20Sopenharmony_ci pr_err("Failed to setup input device keymap\n"); 4588c2ecf20Sopenharmony_ci input_free_device(generic_inputdev); 4598c2ecf20Sopenharmony_ci generic_inputdev = NULL; 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci return ret; 4628c2ecf20Sopenharmony_ci } 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci /* 4658c2ecf20Sopenharmony_ci * This hotkey driver handle backlight event when 4668c2ecf20Sopenharmony_ci * acpi_video_get_backlight_type() gets acpi_backlight_vendor 4678c2ecf20Sopenharmony_ci */ 4688c2ecf20Sopenharmony_ci if (acpi_video_get_backlight_type() == acpi_backlight_vendor) 4698c2ecf20Sopenharmony_ci hotkey_backlight_set(true); 4708c2ecf20Sopenharmony_ci else 4718c2ecf20Sopenharmony_ci hotkey_backlight_set(false); 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci pr_info("ACPI: enabling firmware HKEY event interface...\n"); 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci return ret; 4768c2ecf20Sopenharmony_ci} 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_cistatic void event_notify(struct generic_sub_driver *sub_driver, u32 event) 4798c2ecf20Sopenharmony_ci{ 4808c2ecf20Sopenharmony_ci int type, scan_code; 4818c2ecf20Sopenharmony_ci struct key_entry *ke = NULL; 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci scan_code = event & GENERIC_EVENT_CODE_MASK; 4848c2ecf20Sopenharmony_ci type = (event & GENERIC_EVENT_TYPE_MASK) >> GENERIC_EVENT_TYPE_OFF; 4858c2ecf20Sopenharmony_ci ke = sparse_keymap_entry_from_scancode(generic_inputdev, scan_code); 4868c2ecf20Sopenharmony_ci if (ke) { 4878c2ecf20Sopenharmony_ci if (type == KE_SW) { 4888c2ecf20Sopenharmony_ci int status = 0; 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci if (hotkey_status_get(&status) < 0) 4918c2ecf20Sopenharmony_ci return; 4928c2ecf20Sopenharmony_ci ke->sw.value = !!(status & (1 << ke->sw.code)); 4938c2ecf20Sopenharmony_ci } 4948c2ecf20Sopenharmony_ci sparse_keymap_report_entry(generic_inputdev, ke, 1, true); 4958c2ecf20Sopenharmony_ci } 4968c2ecf20Sopenharmony_ci} 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci/* 3. Infrastructure */ 4998c2ecf20Sopenharmony_ci 5008c2ecf20Sopenharmony_cistatic void generic_subdriver_exit(struct generic_sub_driver *sub_driver); 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_cistatic int __init generic_subdriver_init(struct generic_sub_driver *sub_driver) 5038c2ecf20Sopenharmony_ci{ 5048c2ecf20Sopenharmony_ci int ret; 5058c2ecf20Sopenharmony_ci 5068c2ecf20Sopenharmony_ci if (!sub_driver || !sub_driver->driver) 5078c2ecf20Sopenharmony_ci return -EINVAL; 5088c2ecf20Sopenharmony_ci 5098c2ecf20Sopenharmony_ci ret = platform_driver_register(sub_driver->driver); 5108c2ecf20Sopenharmony_ci if (ret) 5118c2ecf20Sopenharmony_ci return -EINVAL; 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_ci if (sub_driver->init) { 5148c2ecf20Sopenharmony_ci ret = sub_driver->init(sub_driver); 5158c2ecf20Sopenharmony_ci if (ret) 5168c2ecf20Sopenharmony_ci goto err_out; 5178c2ecf20Sopenharmony_ci } 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci if (sub_driver->notify) { 5208c2ecf20Sopenharmony_ci ret = setup_acpi_notify(sub_driver); 5218c2ecf20Sopenharmony_ci if (ret == -ENODEV) { 5228c2ecf20Sopenharmony_ci ret = 0; 5238c2ecf20Sopenharmony_ci goto err_out; 5248c2ecf20Sopenharmony_ci } 5258c2ecf20Sopenharmony_ci if (ret < 0) 5268c2ecf20Sopenharmony_ci goto err_out; 5278c2ecf20Sopenharmony_ci } 5288c2ecf20Sopenharmony_ci 5298c2ecf20Sopenharmony_ci return 0; 5308c2ecf20Sopenharmony_ci 5318c2ecf20Sopenharmony_cierr_out: 5328c2ecf20Sopenharmony_ci generic_subdriver_exit(sub_driver); 5338c2ecf20Sopenharmony_ci return ret; 5348c2ecf20Sopenharmony_ci} 5358c2ecf20Sopenharmony_ci 5368c2ecf20Sopenharmony_cistatic void generic_subdriver_exit(struct generic_sub_driver *sub_driver) 5378c2ecf20Sopenharmony_ci{ 5388c2ecf20Sopenharmony_ci 5398c2ecf20Sopenharmony_ci if (sub_driver->acpi_notify_installed) { 5408c2ecf20Sopenharmony_ci acpi_remove_notify_handler(*sub_driver->handle, 5418c2ecf20Sopenharmony_ci sub_driver->type, dispatch_acpi_notify); 5428c2ecf20Sopenharmony_ci sub_driver->acpi_notify_installed = 0; 5438c2ecf20Sopenharmony_ci } 5448c2ecf20Sopenharmony_ci platform_driver_unregister(sub_driver->driver); 5458c2ecf20Sopenharmony_ci} 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_cistatic struct generic_sub_driver generic_sub_drivers[] __refdata = { 5488c2ecf20Sopenharmony_ci { 5498c2ecf20Sopenharmony_ci .name = "hotkey", 5508c2ecf20Sopenharmony_ci .init = event_init, 5518c2ecf20Sopenharmony_ci .notify = event_notify, 5528c2ecf20Sopenharmony_ci .handle = &hotkey_handle, 5538c2ecf20Sopenharmony_ci .type = ACPI_DEVICE_NOTIFY, 5548c2ecf20Sopenharmony_ci .driver = &loongson_hotkey_driver, 5558c2ecf20Sopenharmony_ci }, 5568c2ecf20Sopenharmony_ci}; 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_cistatic int __init generic_acpi_laptop_init(void) 5598c2ecf20Sopenharmony_ci{ 5608c2ecf20Sopenharmony_ci bool ec_found; 5618c2ecf20Sopenharmony_ci int i, ret, status; 5628c2ecf20Sopenharmony_ci 5638c2ecf20Sopenharmony_ci if (acpi_disabled) 5648c2ecf20Sopenharmony_ci return -ENODEV; 5658c2ecf20Sopenharmony_ci 5668c2ecf20Sopenharmony_ci /* The EC device is required */ 5678c2ecf20Sopenharmony_ci ec_found = acpi_dev_found(LOONGSON_ACPI_EC_HID); 5688c2ecf20Sopenharmony_ci if (!ec_found) 5698c2ecf20Sopenharmony_ci return -ENODEV; 5708c2ecf20Sopenharmony_ci 5718c2ecf20Sopenharmony_ci /* Enable SCI for EC */ 5728c2ecf20Sopenharmony_ci acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1); 5738c2ecf20Sopenharmony_ci 5748c2ecf20Sopenharmony_ci generic_inputdev = input_allocate_device(); 5758c2ecf20Sopenharmony_ci if (!generic_inputdev) { 5768c2ecf20Sopenharmony_ci pr_err("Unable to allocate input device\n"); 5778c2ecf20Sopenharmony_ci return -ENOMEM; 5788c2ecf20Sopenharmony_ci } 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_ci /* Prepare input device, but don't register */ 5818c2ecf20Sopenharmony_ci generic_inputdev->name = 5828c2ecf20Sopenharmony_ci "Loongson Generic Laptop/All-in-One Extra Buttons"; 5838c2ecf20Sopenharmony_ci generic_inputdev->phys = ACPI_LAPTOP_NAME "/input0"; 5848c2ecf20Sopenharmony_ci generic_inputdev->id.bustype = BUS_HOST; 5858c2ecf20Sopenharmony_ci generic_inputdev->dev.parent = NULL; 5868c2ecf20Sopenharmony_ci 5878c2ecf20Sopenharmony_ci /* Init subdrivers */ 5888c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(generic_sub_drivers); i++) { 5898c2ecf20Sopenharmony_ci ret = generic_subdriver_init(&generic_sub_drivers[i]); 5908c2ecf20Sopenharmony_ci if (ret < 0) { 5918c2ecf20Sopenharmony_ci input_free_device(generic_inputdev); 5928c2ecf20Sopenharmony_ci while (--i >= 0) 5938c2ecf20Sopenharmony_ci generic_subdriver_exit(&generic_sub_drivers[i]); 5948c2ecf20Sopenharmony_ci return ret; 5958c2ecf20Sopenharmony_ci } 5968c2ecf20Sopenharmony_ci } 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_ci ret = input_register_device(generic_inputdev); 5998c2ecf20Sopenharmony_ci if (ret < 0) { 6008c2ecf20Sopenharmony_ci input_free_device(generic_inputdev); 6018c2ecf20Sopenharmony_ci while (--i >= 0) 6028c2ecf20Sopenharmony_ci generic_subdriver_exit(&generic_sub_drivers[i]); 6038c2ecf20Sopenharmony_ci pr_err("Unable to register input device\n"); 6048c2ecf20Sopenharmony_ci return ret; 6058c2ecf20Sopenharmony_ci } 6068c2ecf20Sopenharmony_ci 6078c2ecf20Sopenharmony_ci input_device_registered = 1; 6088c2ecf20Sopenharmony_ci 6098c2ecf20Sopenharmony_ci if (acpi_evalf(hotkey_handle, &status, "ECBG", "d")) { 6108c2ecf20Sopenharmony_ci pr_info("Loongson Laptop used, init brightness is 0x%x\n", status); 6118c2ecf20Sopenharmony_ci ret = laptop_backlight_register(); 6128c2ecf20Sopenharmony_ci if (ret < 0) 6138c2ecf20Sopenharmony_ci pr_err("Loongson Laptop: laptop-backlight device register failed\n"); 6148c2ecf20Sopenharmony_ci } 6158c2ecf20Sopenharmony_ci 6168c2ecf20Sopenharmony_ci return 0; 6178c2ecf20Sopenharmony_ci} 6188c2ecf20Sopenharmony_ci 6198c2ecf20Sopenharmony_cistatic void __exit generic_acpi_laptop_exit(void) 6208c2ecf20Sopenharmony_ci{ 6218c2ecf20Sopenharmony_ci if (generic_inputdev) { 6228c2ecf20Sopenharmony_ci if (input_device_registered) 6238c2ecf20Sopenharmony_ci input_unregister_device(generic_inputdev); 6248c2ecf20Sopenharmony_ci else 6258c2ecf20Sopenharmony_ci input_free_device(generic_inputdev); 6268c2ecf20Sopenharmony_ci } 6278c2ecf20Sopenharmony_ci} 6288c2ecf20Sopenharmony_ci 6298c2ecf20Sopenharmony_cimodule_init(generic_acpi_laptop_init); 6308c2ecf20Sopenharmony_cimodule_exit(generic_acpi_laptop_exit); 6318c2ecf20Sopenharmony_ci 6328c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jianmin Lv <lvjianmin@loongson.cn>"); 6338c2ecf20Sopenharmony_ciMODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>"); 6348c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Loongson Laptop/All-in-One ACPI Driver"); 6358c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 636