1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * System76 ACPI Driver 4 * 5 * Copyright (C) 2019 System76 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 */ 11 12 #include <linux/acpi.h> 13 #include <linux/hwmon.h> 14 #include <linux/hwmon-sysfs.h> 15 #include <linux/init.h> 16 #include <linux/input.h> 17 #include <linux/kernel.h> 18 #include <linux/leds.h> 19 #include <linux/module.h> 20 #include <linux/pci_ids.h> 21 #include <linux/types.h> 22 23 struct system76_data { 24 struct acpi_device *acpi_dev; 25 struct led_classdev ap_led; 26 struct led_classdev kb_led; 27 enum led_brightness kb_brightness; 28 enum led_brightness kb_toggle_brightness; 29 int kb_color; 30 struct device *therm; 31 union acpi_object *nfan; 32 union acpi_object *ntmp; 33 struct input_dev *input; 34 }; 35 36 static const struct acpi_device_id device_ids[] = { 37 {"17761776", 0}, 38 {"", 0}, 39 }; 40 MODULE_DEVICE_TABLE(acpi, device_ids); 41 42 // Array of keyboard LED brightness levels 43 static const enum led_brightness kb_levels[] = { 44 48, 45 72, 46 96, 47 144, 48 192, 49 255 50 }; 51 52 // Array of keyboard LED colors in 24-bit RGB format 53 static const int kb_colors[] = { 54 0xFFFFFF, 55 0x0000FF, 56 0xFF0000, 57 0xFF00FF, 58 0x00FF00, 59 0x00FFFF, 60 0xFFFF00 61 }; 62 63 // Get a System76 ACPI device value by name 64 static int system76_get(struct system76_data *data, char *method) 65 { 66 acpi_handle handle; 67 acpi_status status; 68 unsigned long long ret = 0; 69 70 handle = acpi_device_handle(data->acpi_dev); 71 status = acpi_evaluate_integer(handle, method, NULL, &ret); 72 if (ACPI_SUCCESS(status)) 73 return ret; 74 return -ENODEV; 75 } 76 77 // Get a System76 ACPI device value by name with index 78 static int system76_get_index(struct system76_data *data, char *method, int index) 79 { 80 union acpi_object obj; 81 struct acpi_object_list obj_list; 82 acpi_handle handle; 83 acpi_status status; 84 unsigned long long ret = 0; 85 86 obj.type = ACPI_TYPE_INTEGER; 87 obj.integer.value = index; 88 obj_list.count = 1; 89 obj_list.pointer = &obj; 90 91 handle = acpi_device_handle(data->acpi_dev); 92 status = acpi_evaluate_integer(handle, method, &obj_list, &ret); 93 if (ACPI_SUCCESS(status)) 94 return ret; 95 return -ENODEV; 96 } 97 98 // Get a System76 ACPI device object by name 99 static int system76_get_object(struct system76_data *data, char *method, union acpi_object **obj) 100 { 101 acpi_handle handle; 102 acpi_status status; 103 struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; 104 105 handle = acpi_device_handle(data->acpi_dev); 106 status = acpi_evaluate_object(handle, method, NULL, &buf); 107 if (ACPI_SUCCESS(status)) { 108 *obj = buf.pointer; 109 return 0; 110 } 111 112 return -ENODEV; 113 } 114 115 // Get a name from a System76 ACPI device object 116 static char *system76_name(union acpi_object *obj, int index) 117 { 118 if (obj && obj->type == ACPI_TYPE_PACKAGE && index <= obj->package.count) { 119 if (obj->package.elements[index].type == ACPI_TYPE_STRING) 120 return obj->package.elements[index].string.pointer; 121 } 122 123 return NULL; 124 } 125 126 // Set a System76 ACPI device value by name 127 static int system76_set(struct system76_data *data, char *method, int value) 128 { 129 union acpi_object obj; 130 struct acpi_object_list obj_list; 131 acpi_handle handle; 132 acpi_status status; 133 134 obj.type = ACPI_TYPE_INTEGER; 135 obj.integer.value = value; 136 obj_list.count = 1; 137 obj_list.pointer = &obj; 138 handle = acpi_device_handle(data->acpi_dev); 139 status = acpi_evaluate_object(handle, method, &obj_list, NULL); 140 if (ACPI_SUCCESS(status)) 141 return 0; 142 else 143 return -1; 144 } 145 146 // Get the airplane mode LED brightness 147 static enum led_brightness ap_led_get(struct led_classdev *led) 148 { 149 struct system76_data *data; 150 int value; 151 152 data = container_of(led, struct system76_data, ap_led); 153 value = system76_get(data, "GAPL"); 154 if (value > 0) 155 return (enum led_brightness)value; 156 else 157 return LED_OFF; 158 } 159 160 // Set the airplane mode LED brightness 161 static int ap_led_set(struct led_classdev *led, enum led_brightness value) 162 { 163 struct system76_data *data; 164 165 data = container_of(led, struct system76_data, ap_led); 166 return system76_set(data, "SAPL", value == LED_OFF ? 0 : 1); 167 } 168 169 // Get the last set keyboard LED brightness 170 static enum led_brightness kb_led_get(struct led_classdev *led) 171 { 172 struct system76_data *data; 173 174 data = container_of(led, struct system76_data, kb_led); 175 return data->kb_brightness; 176 } 177 178 // Set the keyboard LED brightness 179 static int kb_led_set(struct led_classdev *led, enum led_brightness value) 180 { 181 struct system76_data *data; 182 183 data = container_of(led, struct system76_data, kb_led); 184 data->kb_brightness = value; 185 return system76_set(data, "SKBL", (int)data->kb_brightness); 186 } 187 188 // Get the last set keyboard LED color 189 static ssize_t kb_led_color_show( 190 struct device *dev, 191 struct device_attribute *dev_attr, 192 char *buf) 193 { 194 struct led_classdev *led; 195 struct system76_data *data; 196 197 led = (struct led_classdev *)dev->driver_data; 198 data = container_of(led, struct system76_data, kb_led); 199 return sprintf(buf, "%06X\n", data->kb_color); 200 } 201 202 // Set the keyboard LED color 203 static ssize_t kb_led_color_store( 204 struct device *dev, 205 struct device_attribute *dev_attr, 206 const char *buf, 207 size_t size) 208 { 209 struct led_classdev *led; 210 struct system76_data *data; 211 unsigned int val; 212 int ret; 213 214 led = (struct led_classdev *)dev->driver_data; 215 data = container_of(led, struct system76_data, kb_led); 216 ret = kstrtouint(buf, 16, &val); 217 if (ret) 218 return ret; 219 if (val > 0xFFFFFF) 220 return -EINVAL; 221 data->kb_color = (int)val; 222 system76_set(data, "SKBC", data->kb_color); 223 224 return size; 225 } 226 227 static const struct device_attribute kb_led_color_dev_attr = { 228 .attr = { 229 .name = "color", 230 .mode = 0644, 231 }, 232 .show = kb_led_color_show, 233 .store = kb_led_color_store, 234 }; 235 236 // Notify that the keyboard LED was changed by hardware 237 static void kb_led_notify(struct system76_data *data) 238 { 239 led_classdev_notify_brightness_hw_changed( 240 &data->kb_led, 241 data->kb_brightness 242 ); 243 } 244 245 // Read keyboard LED brightness as set by hardware 246 static void kb_led_hotkey_hardware(struct system76_data *data) 247 { 248 int value; 249 250 value = system76_get(data, "GKBL"); 251 if (value < 0) 252 return; 253 data->kb_brightness = value; 254 kb_led_notify(data); 255 } 256 257 // Toggle the keyboard LED 258 static void kb_led_hotkey_toggle(struct system76_data *data) 259 { 260 if (data->kb_brightness > 0) { 261 data->kb_toggle_brightness = data->kb_brightness; 262 kb_led_set(&data->kb_led, 0); 263 } else { 264 kb_led_set(&data->kb_led, data->kb_toggle_brightness); 265 } 266 kb_led_notify(data); 267 } 268 269 // Decrease the keyboard LED brightness 270 static void kb_led_hotkey_down(struct system76_data *data) 271 { 272 int i; 273 274 if (data->kb_brightness > 0) { 275 for (i = ARRAY_SIZE(kb_levels); i > 0; i--) { 276 if (kb_levels[i - 1] < data->kb_brightness) { 277 kb_led_set(&data->kb_led, kb_levels[i - 1]); 278 break; 279 } 280 } 281 } else { 282 kb_led_set(&data->kb_led, data->kb_toggle_brightness); 283 } 284 kb_led_notify(data); 285 } 286 287 // Increase the keyboard LED brightness 288 static void kb_led_hotkey_up(struct system76_data *data) 289 { 290 int i; 291 292 if (data->kb_brightness > 0) { 293 for (i = 0; i < ARRAY_SIZE(kb_levels); i++) { 294 if (kb_levels[i] > data->kb_brightness) { 295 kb_led_set(&data->kb_led, kb_levels[i]); 296 break; 297 } 298 } 299 } else { 300 kb_led_set(&data->kb_led, data->kb_toggle_brightness); 301 } 302 kb_led_notify(data); 303 } 304 305 // Cycle the keyboard LED color 306 static void kb_led_hotkey_color(struct system76_data *data) 307 { 308 int i; 309 310 if (data->kb_color < 0) 311 return; 312 if (data->kb_brightness > 0) { 313 for (i = 0; i < ARRAY_SIZE(kb_colors); i++) { 314 if (kb_colors[i] == data->kb_color) 315 break; 316 } 317 i += 1; 318 if (i >= ARRAY_SIZE(kb_colors)) 319 i = 0; 320 data->kb_color = kb_colors[i]; 321 system76_set(data, "SKBC", data->kb_color); 322 } else { 323 kb_led_set(&data->kb_led, data->kb_toggle_brightness); 324 } 325 kb_led_notify(data); 326 } 327 328 static umode_t thermal_is_visible(const void *drvdata, enum hwmon_sensor_types type, 329 u32 attr, int channel) 330 { 331 const struct system76_data *data = drvdata; 332 333 switch (type) { 334 case hwmon_fan: 335 case hwmon_pwm: 336 if (system76_name(data->nfan, channel)) 337 return 0444; 338 break; 339 340 case hwmon_temp: 341 if (system76_name(data->ntmp, channel)) 342 return 0444; 343 break; 344 345 default: 346 return 0; 347 } 348 349 return 0; 350 } 351 352 static int thermal_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, 353 int channel, long *val) 354 { 355 struct system76_data *data = dev_get_drvdata(dev); 356 int raw; 357 358 switch (type) { 359 case hwmon_fan: 360 if (attr == hwmon_fan_input) { 361 raw = system76_get_index(data, "GFAN", channel); 362 if (raw < 0) 363 return raw; 364 *val = (raw >> 8) & 0xFFFF; 365 return 0; 366 } 367 break; 368 369 case hwmon_pwm: 370 if (attr == hwmon_pwm_input) { 371 raw = system76_get_index(data, "GFAN", channel); 372 if (raw < 0) 373 return raw; 374 *val = raw & 0xFF; 375 return 0; 376 } 377 break; 378 379 case hwmon_temp: 380 if (attr == hwmon_temp_input) { 381 raw = system76_get_index(data, "GTMP", channel); 382 if (raw < 0) 383 return raw; 384 *val = raw * 1000; 385 return 0; 386 } 387 break; 388 389 default: 390 return -EOPNOTSUPP; 391 } 392 393 return -EOPNOTSUPP; 394 } 395 396 static int thermal_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, 397 int channel, const char **str) 398 { 399 struct system76_data *data = dev_get_drvdata(dev); 400 401 switch (type) { 402 case hwmon_fan: 403 if (attr == hwmon_fan_label) { 404 *str = system76_name(data->nfan, channel); 405 if (*str) 406 return 0; 407 } 408 break; 409 410 case hwmon_temp: 411 if (attr == hwmon_temp_label) { 412 *str = system76_name(data->ntmp, channel); 413 if (*str) 414 return 0; 415 } 416 break; 417 418 default: 419 return -EOPNOTSUPP; 420 } 421 422 return -EOPNOTSUPP; 423 } 424 425 static const struct hwmon_ops thermal_ops = { 426 .is_visible = thermal_is_visible, 427 .read = thermal_read, 428 .read_string = thermal_read_string, 429 }; 430 431 // Allocate up to 8 fans and temperatures 432 static const struct hwmon_channel_info *thermal_channel_info[] = { 433 HWMON_CHANNEL_INFO(fan, 434 HWMON_F_INPUT | HWMON_F_LABEL, 435 HWMON_F_INPUT | HWMON_F_LABEL, 436 HWMON_F_INPUT | HWMON_F_LABEL, 437 HWMON_F_INPUT | HWMON_F_LABEL, 438 HWMON_F_INPUT | HWMON_F_LABEL, 439 HWMON_F_INPUT | HWMON_F_LABEL, 440 HWMON_F_INPUT | HWMON_F_LABEL, 441 HWMON_F_INPUT | HWMON_F_LABEL), 442 HWMON_CHANNEL_INFO(pwm, 443 HWMON_PWM_INPUT, 444 HWMON_PWM_INPUT, 445 HWMON_PWM_INPUT, 446 HWMON_PWM_INPUT, 447 HWMON_PWM_INPUT, 448 HWMON_PWM_INPUT, 449 HWMON_PWM_INPUT, 450 HWMON_PWM_INPUT), 451 HWMON_CHANNEL_INFO(temp, 452 HWMON_T_INPUT | HWMON_T_LABEL, 453 HWMON_T_INPUT | HWMON_T_LABEL, 454 HWMON_T_INPUT | HWMON_T_LABEL, 455 HWMON_T_INPUT | HWMON_T_LABEL, 456 HWMON_T_INPUT | HWMON_T_LABEL, 457 HWMON_T_INPUT | HWMON_T_LABEL, 458 HWMON_T_INPUT | HWMON_T_LABEL, 459 HWMON_T_INPUT | HWMON_T_LABEL), 460 NULL 461 }; 462 463 static const struct hwmon_chip_info thermal_chip_info = { 464 .ops = &thermal_ops, 465 .info = thermal_channel_info, 466 }; 467 468 static void input_key(struct system76_data *data, unsigned int code) 469 { 470 input_report_key(data->input, code, 1); 471 input_sync(data->input); 472 473 input_report_key(data->input, code, 0); 474 input_sync(data->input); 475 } 476 477 // Handle ACPI notification 478 static void system76_notify(struct acpi_device *acpi_dev, u32 event) 479 { 480 struct system76_data *data; 481 482 data = acpi_driver_data(acpi_dev); 483 switch (event) { 484 case 0x80: 485 kb_led_hotkey_hardware(data); 486 break; 487 case 0x81: 488 kb_led_hotkey_toggle(data); 489 break; 490 case 0x82: 491 kb_led_hotkey_down(data); 492 break; 493 case 0x83: 494 kb_led_hotkey_up(data); 495 break; 496 case 0x84: 497 kb_led_hotkey_color(data); 498 break; 499 case 0x85: 500 input_key(data, KEY_SCREENLOCK); 501 break; 502 } 503 } 504 505 // Add a System76 ACPI device 506 static int system76_add(struct acpi_device *acpi_dev) 507 { 508 struct system76_data *data; 509 int err; 510 511 data = devm_kzalloc(&acpi_dev->dev, sizeof(*data), GFP_KERNEL); 512 if (!data) 513 return -ENOMEM; 514 acpi_dev->driver_data = data; 515 data->acpi_dev = acpi_dev; 516 517 err = system76_get(data, "INIT"); 518 if (err) 519 return err; 520 data->ap_led.name = "system76_acpi::airplane"; 521 data->ap_led.flags = LED_CORE_SUSPENDRESUME; 522 data->ap_led.brightness_get = ap_led_get; 523 data->ap_led.brightness_set_blocking = ap_led_set; 524 data->ap_led.max_brightness = 1; 525 data->ap_led.default_trigger = "rfkill-none"; 526 err = devm_led_classdev_register(&acpi_dev->dev, &data->ap_led); 527 if (err) 528 return err; 529 530 data->kb_led.name = "system76_acpi::kbd_backlight"; 531 data->kb_led.flags = LED_BRIGHT_HW_CHANGED | LED_CORE_SUSPENDRESUME; 532 data->kb_led.brightness_get = kb_led_get; 533 data->kb_led.brightness_set_blocking = kb_led_set; 534 if (acpi_has_method(acpi_device_handle(data->acpi_dev), "SKBC")) { 535 data->kb_led.max_brightness = 255; 536 data->kb_toggle_brightness = 72; 537 data->kb_color = 0xffffff; 538 system76_set(data, "SKBC", data->kb_color); 539 } else { 540 data->kb_led.max_brightness = 5; 541 data->kb_color = -1; 542 } 543 err = devm_led_classdev_register(&acpi_dev->dev, &data->kb_led); 544 if (err) 545 return err; 546 547 if (data->kb_color >= 0) { 548 err = device_create_file( 549 data->kb_led.dev, 550 &kb_led_color_dev_attr 551 ); 552 if (err) 553 return err; 554 } 555 556 data->input = devm_input_allocate_device(&acpi_dev->dev); 557 if (!data->input) 558 return -ENOMEM; 559 560 data->input->name = "System76 ACPI Hotkeys"; 561 data->input->phys = "system76_acpi/input0"; 562 data->input->id.bustype = BUS_HOST; 563 data->input->dev.parent = &acpi_dev->dev; 564 input_set_capability(data->input, EV_KEY, KEY_SCREENLOCK); 565 566 err = input_register_device(data->input); 567 if (err) 568 goto error; 569 570 err = system76_get_object(data, "NFAN", &data->nfan); 571 if (err) 572 goto error; 573 574 err = system76_get_object(data, "NTMP", &data->ntmp); 575 if (err) 576 goto error; 577 578 data->therm = devm_hwmon_device_register_with_info(&acpi_dev->dev, 579 "system76_acpi", data, &thermal_chip_info, NULL); 580 err = PTR_ERR_OR_ZERO(data->therm); 581 if (err) 582 goto error; 583 584 return 0; 585 586 error: 587 kfree(data->ntmp); 588 kfree(data->nfan); 589 input_free_device(data->input); 590 return err; 591 } 592 593 // Remove a System76 ACPI device 594 static int system76_remove(struct acpi_device *acpi_dev) 595 { 596 struct system76_data *data; 597 598 data = acpi_driver_data(acpi_dev); 599 if (data->kb_color >= 0) 600 device_remove_file(data->kb_led.dev, &kb_led_color_dev_attr); 601 602 devm_led_classdev_unregister(&acpi_dev->dev, &data->ap_led); 603 devm_led_classdev_unregister(&acpi_dev->dev, &data->kb_led); 604 605 kfree(data->nfan); 606 kfree(data->ntmp); 607 608 system76_get(data, "FINI"); 609 610 return 0; 611 } 612 613 static struct acpi_driver system76_driver = { 614 .name = "System76 ACPI Driver", 615 .class = "hotkey", 616 .ids = device_ids, 617 .ops = { 618 .add = system76_add, 619 .remove = system76_remove, 620 .notify = system76_notify, 621 }, 622 }; 623 module_acpi_driver(system76_driver); 624 625 MODULE_DESCRIPTION("System76 ACPI Driver"); 626 MODULE_AUTHOR("Jeremy Soller <jeremy@system76.com>"); 627 MODULE_LICENSE("GPL"); 628