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