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