1// SPDX-License-Identifier: GPL-2.0 2/* 3 * Generic Loongson processor based LAPTOP/ALL-IN-ONE driver 4 * 5 * Jianmin Lv <lvjianmin@loongson.cn> 6 * Huacai Chen <chenhuacai@loongson.cn> 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 */ 18 19#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 20 21#include <linux/init.h> 22#include <linux/kernel.h> 23#include <linux/module.h> 24#include <linux/acpi.h> 25#include <linux/backlight.h> 26#include <linux/device.h> 27#include <linux/input.h> 28#include <linux/input/sparse-keymap.h> 29#include <linux/platform_device.h> 30#include <linux/string.h> 31#include <linux/types.h> 32#include <acpi/video.h> 33 34/* 1. Driver-wide structs and misc. variables */ 35 36/* ACPI HIDs */ 37#define LOONGSON_ACPI_EC_HID "PNP0C09" 38#define LOONGSON_ACPI_HKEY_HID "LOON0000" 39 40#define ACPI_LAPTOP_NAME "loongson-laptop" 41#define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson" 42 43#define MAX_ACPI_ARGS 3 44#define GENERIC_HOTKEY_MAP_MAX 64 45 46#define GENERIC_EVENT_TYPE_OFF 12 47#define GENERIC_EVENT_TYPE_MASK 0xF000 48#define GENERIC_EVENT_CODE_MASK 0x0FFF 49 50struct generic_sub_driver { 51 u32 type; 52 char *name; 53 acpi_handle *handle; 54 struct acpi_device *device; 55 struct platform_driver *driver; 56 int (*init)(struct generic_sub_driver *sub_driver); 57 void (*notify)(struct generic_sub_driver *sub_driver, u32 event); 58 u8 acpi_notify_installed; 59}; 60 61static u32 input_device_registered; 62static struct input_dev *generic_inputdev; 63 64static acpi_handle hotkey_handle; 65static struct key_entry hotkey_keycode_map[GENERIC_HOTKEY_MAP_MAX]; 66 67int loongson_laptop_turn_on_backlight(void); 68int loongson_laptop_turn_off_backlight(void); 69static int loongson_laptop_backlight_update(struct backlight_device *bd); 70 71/* 2. ACPI Helpers and device model */ 72 73static int acpi_evalf(acpi_handle handle, int *res, char *method, char *fmt, ...) 74{ 75 char res_type; 76 char *fmt0 = fmt; 77 va_list ap; 78 int success, quiet; 79 acpi_status status; 80 struct acpi_object_list params; 81 struct acpi_buffer result, *resultp; 82 union acpi_object in_objs[MAX_ACPI_ARGS], out_obj; 83 84 if (!*fmt) { 85 pr_err("acpi_evalf() called with empty format\n"); 86 return 0; 87 } 88 89 if (*fmt == 'q') { 90 quiet = 1; 91 fmt++; 92 } else 93 quiet = 0; 94 95 res_type = *(fmt++); 96 97 params.count = 0; 98 params.pointer = &in_objs[0]; 99 100 va_start(ap, fmt); 101 while (*fmt) { 102 char c = *(fmt++); 103 switch (c) { 104 case 'd': /* int */ 105 in_objs[params.count].integer.value = va_arg(ap, int); 106 in_objs[params.count++].type = ACPI_TYPE_INTEGER; 107 break; 108 /* add more types as needed */ 109 default: 110 pr_err("acpi_evalf() called with invalid format character '%c'\n", c); 111 va_end(ap); 112 return 0; 113 } 114 } 115 va_end(ap); 116 117 if (res_type != 'v') { 118 result.length = sizeof(out_obj); 119 result.pointer = &out_obj; 120 resultp = &result; 121 } else 122 resultp = NULL; 123 124 status = acpi_evaluate_object(handle, method, ¶ms, resultp); 125 126 switch (res_type) { 127 case 'd': /* int */ 128 success = (status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER); 129 if (success && res) 130 *res = out_obj.integer.value; 131 break; 132 case 'v': /* void */ 133 success = status == AE_OK; 134 break; 135 /* add more types as needed */ 136 default: 137 pr_err("acpi_evalf() called with invalid format character '%c'\n", res_type); 138 return 0; 139 } 140 141 if (!success && !quiet) 142 pr_err("acpi_evalf(%s, %s, ...) failed: %s\n", 143 method, fmt0, acpi_format_exception(status)); 144 145 return success; 146} 147 148static int hotkey_status_get(int *status) 149{ 150 if (!acpi_evalf(hotkey_handle, status, "GSWS", "d")) 151 return -EIO; 152 153 return 0; 154} 155 156static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data) 157{ 158 struct generic_sub_driver *sub_driver = data; 159 160 if (!sub_driver || !sub_driver->notify) 161 return; 162 sub_driver->notify(sub_driver, event); 163} 164 165static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver) 166{ 167 int rc; 168 acpi_status status; 169 170 if (!*sub_driver->handle) 171 return 0; 172 173 rc = acpi_bus_get_device(*sub_driver->handle, &sub_driver->device); 174 if (rc < 0) { 175 pr_err("acpi_bus_get_device(%s) failed: %d\n", sub_driver->name, rc); 176 return -ENODEV; 177 } 178 179 sub_driver->device->driver_data = sub_driver; 180 sprintf(acpi_device_class(sub_driver->device), "%s/%s", 181 ACPI_LAPTOP_ACPI_EVENT_PREFIX, sub_driver->name); 182 183 status = acpi_install_notify_handler(*sub_driver->handle, 184 sub_driver->type, dispatch_acpi_notify, sub_driver); 185 if (ACPI_FAILURE(status)) { 186 if (status == AE_ALREADY_EXISTS) { 187 pr_notice("Another device driver is already " 188 "handling %s events\n", sub_driver->name); 189 } else { 190 pr_err("acpi_install_notify_handler(%s) failed: %s\n", 191 sub_driver->name, acpi_format_exception(status)); 192 } 193 return -ENODEV; 194 } 195 sub_driver->acpi_notify_installed = 1; 196 197 return 0; 198} 199 200#ifdef CONFIG_PM 201static int loongson_hotkey_suspend(struct device *dev) 202{ 203 return 0; 204} 205 206static int loongson_hotkey_resume(struct device *dev) 207{ 208 int status = 0; 209 struct key_entry ke; 210 struct backlight_device *bd; 211 212 bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM); 213 if (bd) { 214 loongson_laptop_backlight_update(bd) ? 215 pr_warn("Loongson_backlight: resume brightness failed") : 216 pr_info("Loongson_backlight: resume brightness %d\n", bd->props.brightness); 217 } 218 219 /* 220 * Only if the firmware supports SW_LID event model, we can handle the 221 * event. This is for the consideration of development board without EC. 222 */ 223 if (test_bit(SW_LID, generic_inputdev->swbit)) { 224 if (hotkey_status_get(&status) < 0) 225 return -EIO; 226 /* 227 * The input device sw element records the last lid status. 228 * When the system is awakened by other wake-up sources, 229 * the lid event will also be reported. The judgment of 230 * adding SW_LID bit which in sw element can avoid this 231 * case. 232 * 233 * Input system will drop lid event when current lid event 234 * value and last lid status in the same. So laptop driver 235 * doesn't report repeated events. 236 * 237 * Lid status is generally 0, but hardware exception is 238 * considered. So add lid status confirmation. 239 */ 240 if (test_bit(SW_LID, generic_inputdev->sw) && !(status & (1 << SW_LID))) { 241 ke.type = KE_SW; 242 ke.sw.value = (u8)status; 243 ke.sw.code = SW_LID; 244 sparse_keymap_report_entry(generic_inputdev, &ke, 1, true); 245 } 246 } 247 248 return 0; 249} 250 251static SIMPLE_DEV_PM_OPS(loongson_hotkey_pm, 252 loongson_hotkey_suspend, loongson_hotkey_resume); 253#endif 254 255static int loongson_hotkey_probe(struct platform_device *pdev) 256{ 257 hotkey_handle = ACPI_HANDLE(&pdev->dev); 258 259 if (!hotkey_handle) 260 return -ENODEV; 261 262 return 0; 263} 264 265static const struct acpi_device_id loongson_device_ids[] = { 266 {LOONGSON_ACPI_HKEY_HID, 0}, 267 {"", 0}, 268}; 269MODULE_DEVICE_TABLE(acpi, loongson_device_ids); 270 271static struct platform_driver loongson_hotkey_driver = { 272 .probe = loongson_hotkey_probe, 273 .driver = { 274 .name = "loongson-hotkey", 275 .owner = THIS_MODULE, 276 .pm = pm_ptr(&loongson_hotkey_pm), 277 .acpi_match_table = loongson_device_ids, 278 }, 279}; 280 281static int hotkey_map(void) 282{ 283 u32 index; 284 acpi_status status; 285 struct acpi_buffer buf; 286 union acpi_object *pack; 287 288 buf.length = ACPI_ALLOCATE_BUFFER; 289 status = acpi_evaluate_object_typed(hotkey_handle, "KMAP", NULL, &buf, ACPI_TYPE_PACKAGE); 290 if (status != AE_OK) { 291 pr_err("ACPI exception: %s\n", acpi_format_exception(status)); 292 return -1; 293 } 294 pack = buf.pointer; 295 for (index = 0; index < pack->package.count; index++) { 296 union acpi_object *element, *sub_pack; 297 298 sub_pack = &pack->package.elements[index]; 299 300 element = &sub_pack->package.elements[0]; 301 hotkey_keycode_map[index].type = element->integer.value; 302 element = &sub_pack->package.elements[1]; 303 hotkey_keycode_map[index].code = element->integer.value; 304 element = &sub_pack->package.elements[2]; 305 hotkey_keycode_map[index].keycode = element->integer.value; 306 } 307 308 return 0; 309} 310 311static int hotkey_backlight_set(bool enable) 312{ 313 if (!acpi_evalf(hotkey_handle, NULL, "VCBL", "vd", enable ? 1 : 0)) 314 return -EIO; 315 316 return 0; 317} 318 319static int ec_get_brightness(void) 320{ 321 int status = 0; 322 323 if (!hotkey_handle) 324 return -ENXIO; 325 326 if (!acpi_evalf(hotkey_handle, &status, "ECBG", "d")) 327 return -EIO; 328 329 return status; 330} 331 332static int ec_set_brightness(int level) 333{ 334 335 int ret = 0; 336 337 if (!hotkey_handle) 338 return -ENXIO; 339 340 if (!acpi_evalf(hotkey_handle, NULL, "ECBS", "vd", level)) 341 ret = -EIO; 342 343 return ret; 344} 345 346static int ec_backlight_level(u8 level) 347{ 348 int status = 0; 349 350 if (!hotkey_handle) 351 return -ENXIO; 352 353 if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d")) 354 return -EIO; 355 356 if ((status < 0) || (level > status)) 357 return status; 358 359 if (!acpi_evalf(hotkey_handle, &status, "ECSL", "d")) 360 return -EIO; 361 362 if ((status < 0) || (level < status)) 363 return status; 364 365 return level; 366} 367 368static int loongson_laptop_backlight_update(struct backlight_device *bd) 369{ 370 int lvl = ec_backlight_level(bd->props.brightness); 371 if (lvl < 0) 372 return -EIO; 373 if (ec_set_brightness(lvl)) 374 return -EIO; 375 376 return 0; 377} 378 379static int loongson_laptop_get_brightness(struct backlight_device *bd) 380{ 381 int level; 382 383 level = ec_get_brightness(); 384 if (level < 0) 385 return -EIO; 386 387 return level; 388} 389 390static const struct backlight_ops backlight_laptop_ops = { 391 .update_status = loongson_laptop_backlight_update, 392 .get_brightness = loongson_laptop_get_brightness, 393}; 394 395static int laptop_backlight_register(void) 396{ 397 int status = 0; 398 struct backlight_properties props; 399 400 memset(&props, 0, sizeof(props)); 401 402 if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d")) 403 return -EIO; 404 405 props.brightness = 1; 406 props.max_brightness = status; 407 props.type = BACKLIGHT_PLATFORM; 408 409 backlight_device_register("loongson_laptop", 410 NULL, NULL, &backlight_laptop_ops, &props); 411 412 return 0; 413} 414 415int loongson_laptop_turn_on_backlight(void) 416{ 417 int status; 418 union acpi_object arg0 = { ACPI_TYPE_INTEGER }; 419 struct acpi_object_list args = { 1, &arg0 }; 420 421 arg0.integer.value = 1; 422 status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL); 423 if (ACPI_FAILURE(status)) { 424 pr_info("Loongson lvds error:0x%x\n", status); 425 return -ENODEV; 426 } 427 return 0; 428} 429 430int loongson_laptop_turn_off_backlight(void) 431{ 432 int status; 433 union acpi_object arg0 = { ACPI_TYPE_INTEGER }; 434 struct acpi_object_list args = { 1, &arg0 }; 435 436 arg0.integer.value = 0; 437 status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL); 438 if (ACPI_FAILURE(status)) { 439 pr_info("Loongson lvds error:0x%x\n", status); 440 return -ENODEV; 441 } 442 return 0; 443} 444 445static int __init event_init(struct generic_sub_driver *sub_driver) 446{ 447 int ret; 448 449 ret = hotkey_map(); 450 if (ret < 0) { 451 pr_err("Failed to parse keymap from DSDT\n"); 452 return ret; 453 } 454 455 ret = sparse_keymap_setup(generic_inputdev, hotkey_keycode_map, NULL); 456 if (ret < 0) { 457 pr_err("Failed to setup input device keymap\n"); 458 input_free_device(generic_inputdev); 459 generic_inputdev = NULL; 460 461 return ret; 462 } 463 464 /* 465 * This hotkey driver handle backlight event when 466 * acpi_video_get_backlight_type() gets acpi_backlight_vendor 467 */ 468 if (acpi_video_get_backlight_type() == acpi_backlight_vendor) 469 hotkey_backlight_set(true); 470 else 471 hotkey_backlight_set(false); 472 473 pr_info("ACPI: enabling firmware HKEY event interface...\n"); 474 475 return ret; 476} 477 478static void event_notify(struct generic_sub_driver *sub_driver, u32 event) 479{ 480 int type, scan_code; 481 struct key_entry *ke = NULL; 482 483 scan_code = event & GENERIC_EVENT_CODE_MASK; 484 type = (event & GENERIC_EVENT_TYPE_MASK) >> GENERIC_EVENT_TYPE_OFF; 485 ke = sparse_keymap_entry_from_scancode(generic_inputdev, scan_code); 486 if (ke) { 487 if (type == KE_SW) { 488 int status = 0; 489 490 if (hotkey_status_get(&status) < 0) 491 return; 492 ke->sw.value = !!(status & (1 << ke->sw.code)); 493 } 494 sparse_keymap_report_entry(generic_inputdev, ke, 1, true); 495 } 496} 497 498/* 3. Infrastructure */ 499 500static void generic_subdriver_exit(struct generic_sub_driver *sub_driver); 501 502static int __init generic_subdriver_init(struct generic_sub_driver *sub_driver) 503{ 504 int ret; 505 506 if (!sub_driver || !sub_driver->driver) 507 return -EINVAL; 508 509 ret = platform_driver_register(sub_driver->driver); 510 if (ret) 511 return -EINVAL; 512 513 if (sub_driver->init) { 514 ret = sub_driver->init(sub_driver); 515 if (ret) 516 goto err_out; 517 } 518 519 if (sub_driver->notify) { 520 ret = setup_acpi_notify(sub_driver); 521 if (ret == -ENODEV) { 522 ret = 0; 523 goto err_out; 524 } 525 if (ret < 0) 526 goto err_out; 527 } 528 529 return 0; 530 531err_out: 532 generic_subdriver_exit(sub_driver); 533 return ret; 534} 535 536static void generic_subdriver_exit(struct generic_sub_driver *sub_driver) 537{ 538 539 if (sub_driver->acpi_notify_installed) { 540 acpi_remove_notify_handler(*sub_driver->handle, 541 sub_driver->type, dispatch_acpi_notify); 542 sub_driver->acpi_notify_installed = 0; 543 } 544 platform_driver_unregister(sub_driver->driver); 545} 546 547static struct generic_sub_driver generic_sub_drivers[] __refdata = { 548 { 549 .name = "hotkey", 550 .init = event_init, 551 .notify = event_notify, 552 .handle = &hotkey_handle, 553 .type = ACPI_DEVICE_NOTIFY, 554 .driver = &loongson_hotkey_driver, 555 }, 556}; 557 558static int __init generic_acpi_laptop_init(void) 559{ 560 bool ec_found; 561 int i, ret, status; 562 563 if (acpi_disabled) 564 return -ENODEV; 565 566 /* The EC device is required */ 567 ec_found = acpi_dev_found(LOONGSON_ACPI_EC_HID); 568 if (!ec_found) 569 return -ENODEV; 570 571 /* Enable SCI for EC */ 572 acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1); 573 574 generic_inputdev = input_allocate_device(); 575 if (!generic_inputdev) { 576 pr_err("Unable to allocate input device\n"); 577 return -ENOMEM; 578 } 579 580 /* Prepare input device, but don't register */ 581 generic_inputdev->name = 582 "Loongson Generic Laptop/All-in-One Extra Buttons"; 583 generic_inputdev->phys = ACPI_LAPTOP_NAME "/input0"; 584 generic_inputdev->id.bustype = BUS_HOST; 585 generic_inputdev->dev.parent = NULL; 586 587 /* Init subdrivers */ 588 for (i = 0; i < ARRAY_SIZE(generic_sub_drivers); i++) { 589 ret = generic_subdriver_init(&generic_sub_drivers[i]); 590 if (ret < 0) { 591 input_free_device(generic_inputdev); 592 while (--i >= 0) 593 generic_subdriver_exit(&generic_sub_drivers[i]); 594 return ret; 595 } 596 } 597 598 ret = input_register_device(generic_inputdev); 599 if (ret < 0) { 600 input_free_device(generic_inputdev); 601 while (--i >= 0) 602 generic_subdriver_exit(&generic_sub_drivers[i]); 603 pr_err("Unable to register input device\n"); 604 return ret; 605 } 606 607 input_device_registered = 1; 608 609 if (acpi_evalf(hotkey_handle, &status, "ECBG", "d")) { 610 pr_info("Loongson Laptop used, init brightness is 0x%x\n", status); 611 ret = laptop_backlight_register(); 612 if (ret < 0) 613 pr_err("Loongson Laptop: laptop-backlight device register failed\n"); 614 } 615 616 return 0; 617} 618 619static void __exit generic_acpi_laptop_exit(void) 620{ 621 if (generic_inputdev) { 622 if (input_device_registered) 623 input_unregister_device(generic_inputdev); 624 else 625 input_free_device(generic_inputdev); 626 } 627} 628 629module_init(generic_acpi_laptop_init); 630module_exit(generic_acpi_laptop_exit); 631 632MODULE_AUTHOR("Jianmin Lv <lvjianmin@loongson.cn>"); 633MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>"); 634MODULE_DESCRIPTION("Loongson Laptop/All-in-One ACPI Driver"); 635MODULE_LICENSE("GPL"); 636