1fd13c862SJeremy Soller // SPDX-License-Identifier: GPL-2.0+ 2fd13c862SJeremy Soller /* 3fd13c862SJeremy Soller * System76 ACPI Driver 4fd13c862SJeremy Soller * 5fd13c862SJeremy Soller * Copyright (C) 2019 System76 6fd13c862SJeremy Soller * 7fd13c862SJeremy Soller * This program is free software; you can redistribute it and/or modify 8fd13c862SJeremy Soller * it under the terms of the GNU General Public License version 2 as 9fd13c862SJeremy Soller * published by the Free Software Foundation. 10fd13c862SJeremy Soller */ 11fd13c862SJeremy Soller 12fd13c862SJeremy Soller #include <linux/acpi.h> 13fd13c862SJeremy Soller #include <linux/init.h> 14fd13c862SJeremy Soller #include <linux/kernel.h> 15fd13c862SJeremy Soller #include <linux/leds.h> 16fd13c862SJeremy Soller #include <linux/module.h> 17fd13c862SJeremy Soller #include <linux/pci_ids.h> 18fd13c862SJeremy Soller #include <linux/types.h> 19fd13c862SJeremy Soller 20fd13c862SJeremy Soller struct system76_data { 21fd13c862SJeremy Soller struct acpi_device *acpi_dev; 22fd13c862SJeremy Soller struct led_classdev ap_led; 23fd13c862SJeremy Soller struct led_classdev kb_led; 24fd13c862SJeremy Soller enum led_brightness kb_brightness; 25fd13c862SJeremy Soller enum led_brightness kb_toggle_brightness; 26fd13c862SJeremy Soller int kb_color; 27fd13c862SJeremy Soller }; 28fd13c862SJeremy Soller 29fd13c862SJeremy Soller static const struct acpi_device_id device_ids[] = { 30fd13c862SJeremy Soller {"17761776", 0}, 31fd13c862SJeremy Soller {"", 0}, 32fd13c862SJeremy Soller }; 33fd13c862SJeremy Soller MODULE_DEVICE_TABLE(acpi, device_ids); 34fd13c862SJeremy Soller 35fd13c862SJeremy Soller // Array of keyboard LED brightness levels 36fd13c862SJeremy Soller static const enum led_brightness kb_levels[] = { 37fd13c862SJeremy Soller 48, 38fd13c862SJeremy Soller 72, 39fd13c862SJeremy Soller 96, 40fd13c862SJeremy Soller 144, 41fd13c862SJeremy Soller 192, 42fd13c862SJeremy Soller 255 43fd13c862SJeremy Soller }; 44fd13c862SJeremy Soller 45fd13c862SJeremy Soller // Array of keyboard LED colors in 24-bit RGB format 46fd13c862SJeremy Soller static const int kb_colors[] = { 47fd13c862SJeremy Soller 0xFFFFFF, 48fd13c862SJeremy Soller 0x0000FF, 49fd13c862SJeremy Soller 0xFF0000, 50fd13c862SJeremy Soller 0xFF00FF, 51fd13c862SJeremy Soller 0x00FF00, 52fd13c862SJeremy Soller 0x00FFFF, 53fd13c862SJeremy Soller 0xFFFF00 54fd13c862SJeremy Soller }; 55fd13c862SJeremy Soller 56fd13c862SJeremy Soller // Get a System76 ACPI device value by name 57fd13c862SJeremy Soller static int system76_get(struct system76_data *data, char *method) 58fd13c862SJeremy Soller { 59fd13c862SJeremy Soller acpi_handle handle; 60fd13c862SJeremy Soller acpi_status status; 61fd13c862SJeremy Soller unsigned long long ret = 0; 62fd13c862SJeremy Soller 63fd13c862SJeremy Soller handle = acpi_device_handle(data->acpi_dev); 64fd13c862SJeremy Soller status = acpi_evaluate_integer(handle, method, NULL, &ret); 65fd13c862SJeremy Soller if (ACPI_SUCCESS(status)) 66fd13c862SJeremy Soller return (int)ret; 67fd13c862SJeremy Soller else 68fd13c862SJeremy Soller return -1; 69fd13c862SJeremy Soller } 70fd13c862SJeremy Soller 71fd13c862SJeremy Soller // Set a System76 ACPI device value by name 72fd13c862SJeremy Soller static int system76_set(struct system76_data *data, char *method, int value) 73fd13c862SJeremy Soller { 74fd13c862SJeremy Soller union acpi_object obj; 75fd13c862SJeremy Soller struct acpi_object_list obj_list; 76fd13c862SJeremy Soller acpi_handle handle; 77fd13c862SJeremy Soller acpi_status status; 78fd13c862SJeremy Soller 79fd13c862SJeremy Soller obj.type = ACPI_TYPE_INTEGER; 80fd13c862SJeremy Soller obj.integer.value = value; 81fd13c862SJeremy Soller obj_list.count = 1; 82fd13c862SJeremy Soller obj_list.pointer = &obj; 83fd13c862SJeremy Soller handle = acpi_device_handle(data->acpi_dev); 84fd13c862SJeremy Soller status = acpi_evaluate_object(handle, method, &obj_list, NULL); 85fd13c862SJeremy Soller if (ACPI_SUCCESS(status)) 86fd13c862SJeremy Soller return 0; 87fd13c862SJeremy Soller else 88fd13c862SJeremy Soller return -1; 89fd13c862SJeremy Soller } 90fd13c862SJeremy Soller 91fd13c862SJeremy Soller // Get the airplane mode LED brightness 92fd13c862SJeremy Soller static enum led_brightness ap_led_get(struct led_classdev *led) 93fd13c862SJeremy Soller { 94fd13c862SJeremy Soller struct system76_data *data; 95fd13c862SJeremy Soller int value; 96fd13c862SJeremy Soller 97fd13c862SJeremy Soller data = container_of(led, struct system76_data, ap_led); 98fd13c862SJeremy Soller value = system76_get(data, "GAPL"); 99fd13c862SJeremy Soller if (value > 0) 100fd13c862SJeremy Soller return (enum led_brightness)value; 101fd13c862SJeremy Soller else 102fd13c862SJeremy Soller return LED_OFF; 103fd13c862SJeremy Soller } 104fd13c862SJeremy Soller 105fd13c862SJeremy Soller // Set the airplane mode LED brightness 106fd13c862SJeremy Soller static void ap_led_set(struct led_classdev *led, enum led_brightness value) 107fd13c862SJeremy Soller { 108fd13c862SJeremy Soller struct system76_data *data; 109fd13c862SJeremy Soller 110fd13c862SJeremy Soller data = container_of(led, struct system76_data, ap_led); 111fd13c862SJeremy Soller system76_set(data, "SAPL", value == LED_OFF ? 0 : 1); 112fd13c862SJeremy Soller } 113fd13c862SJeremy Soller 114fd13c862SJeremy Soller // Get the last set keyboard LED brightness 115fd13c862SJeremy Soller static enum led_brightness kb_led_get(struct led_classdev *led) 116fd13c862SJeremy Soller { 117fd13c862SJeremy Soller struct system76_data *data; 118fd13c862SJeremy Soller 119fd13c862SJeremy Soller data = container_of(led, struct system76_data, kb_led); 120fd13c862SJeremy Soller return data->kb_brightness; 121fd13c862SJeremy Soller } 122fd13c862SJeremy Soller 123fd13c862SJeremy Soller // Set the keyboard LED brightness 124fd13c862SJeremy Soller static void kb_led_set(struct led_classdev *led, enum led_brightness value) 125fd13c862SJeremy Soller { 126fd13c862SJeremy Soller struct system76_data *data; 127fd13c862SJeremy Soller 128fd13c862SJeremy Soller data = container_of(led, struct system76_data, kb_led); 129fd13c862SJeremy Soller data->kb_brightness = value; 130fd13c862SJeremy Soller system76_set(data, "SKBL", (int)data->kb_brightness); 131fd13c862SJeremy Soller } 132fd13c862SJeremy Soller 133fd13c862SJeremy Soller // Get the last set keyboard LED color 134fd13c862SJeremy Soller static ssize_t kb_led_color_show( 135fd13c862SJeremy Soller struct device *dev, 136fd13c862SJeremy Soller struct device_attribute *dev_attr, 137fd13c862SJeremy Soller char *buf) 138fd13c862SJeremy Soller { 139fd13c862SJeremy Soller struct led_classdev *led; 140fd13c862SJeremy Soller struct system76_data *data; 141fd13c862SJeremy Soller 142fd13c862SJeremy Soller led = (struct led_classdev *)dev->driver_data; 143fd13c862SJeremy Soller data = container_of(led, struct system76_data, kb_led); 144fd13c862SJeremy Soller return sprintf(buf, "%06X\n", data->kb_color); 145fd13c862SJeremy Soller } 146fd13c862SJeremy Soller 147fd13c862SJeremy Soller // Set the keyboard LED color 148fd13c862SJeremy Soller static ssize_t kb_led_color_store( 149fd13c862SJeremy Soller struct device *dev, 150fd13c862SJeremy Soller struct device_attribute *dev_attr, 151fd13c862SJeremy Soller const char *buf, 152fd13c862SJeremy Soller size_t size) 153fd13c862SJeremy Soller { 154fd13c862SJeremy Soller struct led_classdev *led; 155fd13c862SJeremy Soller struct system76_data *data; 156fd13c862SJeremy Soller unsigned int val; 157fd13c862SJeremy Soller int ret; 158fd13c862SJeremy Soller 159fd13c862SJeremy Soller led = (struct led_classdev *)dev->driver_data; 160fd13c862SJeremy Soller data = container_of(led, struct system76_data, kb_led); 161fd13c862SJeremy Soller ret = kstrtouint(buf, 16, &val); 162fd13c862SJeremy Soller if (ret) 163fd13c862SJeremy Soller return ret; 164fd13c862SJeremy Soller if (val > 0xFFFFFF) 165fd13c862SJeremy Soller return -EINVAL; 166fd13c862SJeremy Soller data->kb_color = (int)val; 167fd13c862SJeremy Soller system76_set(data, "SKBC", data->kb_color); 168fd13c862SJeremy Soller 169fd13c862SJeremy Soller return size; 170fd13c862SJeremy Soller } 171fd13c862SJeremy Soller 172fd13c862SJeremy Soller static const struct device_attribute kb_led_color_dev_attr = { 173fd13c862SJeremy Soller .attr = { 174fd13c862SJeremy Soller .name = "color", 175fd13c862SJeremy Soller .mode = 0644, 176fd13c862SJeremy Soller }, 177fd13c862SJeremy Soller .show = kb_led_color_show, 178fd13c862SJeremy Soller .store = kb_led_color_store, 179fd13c862SJeremy Soller }; 180fd13c862SJeremy Soller 181fd13c862SJeremy Soller // Notify that the keyboard LED was changed by hardware 182fd13c862SJeremy Soller static void kb_led_notify(struct system76_data *data) 183fd13c862SJeremy Soller { 184fd13c862SJeremy Soller led_classdev_notify_brightness_hw_changed( 185fd13c862SJeremy Soller &data->kb_led, 186fd13c862SJeremy Soller data->kb_brightness 187fd13c862SJeremy Soller ); 188fd13c862SJeremy Soller } 189fd13c862SJeremy Soller 190fd13c862SJeremy Soller // Read keyboard LED brightness as set by hardware 191fd13c862SJeremy Soller static void kb_led_hotkey_hardware(struct system76_data *data) 192fd13c862SJeremy Soller { 193fd13c862SJeremy Soller int value; 194fd13c862SJeremy Soller 195fd13c862SJeremy Soller value = system76_get(data, "GKBL"); 196fd13c862SJeremy Soller if (value < 0) 197fd13c862SJeremy Soller return; 198fd13c862SJeremy Soller data->kb_brightness = value; 199fd13c862SJeremy Soller kb_led_notify(data); 200fd13c862SJeremy Soller } 201fd13c862SJeremy Soller 202fd13c862SJeremy Soller // Toggle the keyboard LED 203fd13c862SJeremy Soller static void kb_led_hotkey_toggle(struct system76_data *data) 204fd13c862SJeremy Soller { 205fd13c862SJeremy Soller if (data->kb_brightness > 0) { 206fd13c862SJeremy Soller data->kb_toggle_brightness = data->kb_brightness; 207fd13c862SJeremy Soller kb_led_set(&data->kb_led, 0); 208fd13c862SJeremy Soller } else { 209fd13c862SJeremy Soller kb_led_set(&data->kb_led, data->kb_toggle_brightness); 210fd13c862SJeremy Soller } 211fd13c862SJeremy Soller kb_led_notify(data); 212fd13c862SJeremy Soller } 213fd13c862SJeremy Soller 214fd13c862SJeremy Soller // Decrease the keyboard LED brightness 215fd13c862SJeremy Soller static void kb_led_hotkey_down(struct system76_data *data) 216fd13c862SJeremy Soller { 217fd13c862SJeremy Soller int i; 218fd13c862SJeremy Soller 219fd13c862SJeremy Soller if (data->kb_brightness > 0) { 220fd13c862SJeremy Soller for (i = ARRAY_SIZE(kb_levels); i > 0; i--) { 221fd13c862SJeremy Soller if (kb_levels[i - 1] < data->kb_brightness) { 222fd13c862SJeremy Soller kb_led_set(&data->kb_led, kb_levels[i - 1]); 223fd13c862SJeremy Soller break; 224fd13c862SJeremy Soller } 225fd13c862SJeremy Soller } 226fd13c862SJeremy Soller } else { 227fd13c862SJeremy Soller kb_led_set(&data->kb_led, data->kb_toggle_brightness); 228fd13c862SJeremy Soller } 229fd13c862SJeremy Soller kb_led_notify(data); 230fd13c862SJeremy Soller } 231fd13c862SJeremy Soller 232fd13c862SJeremy Soller // Increase the keyboard LED brightness 233fd13c862SJeremy Soller static void kb_led_hotkey_up(struct system76_data *data) 234fd13c862SJeremy Soller { 235fd13c862SJeremy Soller int i; 236fd13c862SJeremy Soller 237fd13c862SJeremy Soller if (data->kb_brightness > 0) { 238fd13c862SJeremy Soller for (i = 0; i < ARRAY_SIZE(kb_levels); i++) { 239fd13c862SJeremy Soller if (kb_levels[i] > data->kb_brightness) { 240fd13c862SJeremy Soller kb_led_set(&data->kb_led, kb_levels[i]); 241fd13c862SJeremy Soller break; 242fd13c862SJeremy Soller } 243fd13c862SJeremy Soller } 244fd13c862SJeremy Soller } else { 245fd13c862SJeremy Soller kb_led_set(&data->kb_led, data->kb_toggle_brightness); 246fd13c862SJeremy Soller } 247fd13c862SJeremy Soller kb_led_notify(data); 248fd13c862SJeremy Soller } 249fd13c862SJeremy Soller 250fd13c862SJeremy Soller // Cycle the keyboard LED color 251fd13c862SJeremy Soller static void kb_led_hotkey_color(struct system76_data *data) 252fd13c862SJeremy Soller { 253fd13c862SJeremy Soller int i; 254fd13c862SJeremy Soller 255fd13c862SJeremy Soller if (data->kb_color < 0) 256fd13c862SJeremy Soller return; 257fd13c862SJeremy Soller if (data->kb_brightness > 0) { 258fd13c862SJeremy Soller for (i = 0; i < ARRAY_SIZE(kb_colors); i++) { 259fd13c862SJeremy Soller if (kb_colors[i] == data->kb_color) 260fd13c862SJeremy Soller break; 261fd13c862SJeremy Soller } 262fd13c862SJeremy Soller i += 1; 263fd13c862SJeremy Soller if (i >= ARRAY_SIZE(kb_colors)) 264fd13c862SJeremy Soller i = 0; 265fd13c862SJeremy Soller data->kb_color = kb_colors[i]; 266fd13c862SJeremy Soller system76_set(data, "SKBC", data->kb_color); 267fd13c862SJeremy Soller } else { 268fd13c862SJeremy Soller kb_led_set(&data->kb_led, data->kb_toggle_brightness); 269fd13c862SJeremy Soller } 270fd13c862SJeremy Soller kb_led_notify(data); 271fd13c862SJeremy Soller } 272fd13c862SJeremy Soller 273fd13c862SJeremy Soller // Handle ACPI notification 274fd13c862SJeremy Soller static void system76_notify(struct acpi_device *acpi_dev, u32 event) 275fd13c862SJeremy Soller { 276fd13c862SJeremy Soller struct system76_data *data; 277fd13c862SJeremy Soller 278fd13c862SJeremy Soller data = acpi_driver_data(acpi_dev); 279fd13c862SJeremy Soller switch (event) { 280fd13c862SJeremy Soller case 0x80: 281fd13c862SJeremy Soller kb_led_hotkey_hardware(data); 282fd13c862SJeremy Soller break; 283fd13c862SJeremy Soller case 0x81: 284fd13c862SJeremy Soller kb_led_hotkey_toggle(data); 285fd13c862SJeremy Soller break; 286fd13c862SJeremy Soller case 0x82: 287fd13c862SJeremy Soller kb_led_hotkey_down(data); 288fd13c862SJeremy Soller break; 289fd13c862SJeremy Soller case 0x83: 290fd13c862SJeremy Soller kb_led_hotkey_up(data); 291fd13c862SJeremy Soller break; 292fd13c862SJeremy Soller case 0x84: 293fd13c862SJeremy Soller kb_led_hotkey_color(data); 294fd13c862SJeremy Soller break; 295fd13c862SJeremy Soller } 296fd13c862SJeremy Soller } 297fd13c862SJeremy Soller 298fd13c862SJeremy Soller // Add a System76 ACPI device 299fd13c862SJeremy Soller static int system76_add(struct acpi_device *acpi_dev) 300fd13c862SJeremy Soller { 301fd13c862SJeremy Soller struct system76_data *data; 302fd13c862SJeremy Soller int err; 303fd13c862SJeremy Soller 304fd13c862SJeremy Soller data = devm_kzalloc(&acpi_dev->dev, sizeof(*data), GFP_KERNEL); 305fd13c862SJeremy Soller if (!data) 306fd13c862SJeremy Soller return -ENOMEM; 307fd13c862SJeremy Soller acpi_dev->driver_data = data; 308fd13c862SJeremy Soller data->acpi_dev = acpi_dev; 309fd13c862SJeremy Soller 310fd13c862SJeremy Soller err = system76_get(data, "INIT"); 311fd13c862SJeremy Soller if (err) 312fd13c862SJeremy Soller return err; 313fd13c862SJeremy Soller data->ap_led.name = "system76_acpi::airplane"; 314fd13c862SJeremy Soller data->ap_led.flags = LED_CORE_SUSPENDRESUME; 315fd13c862SJeremy Soller data->ap_led.brightness_get = ap_led_get; 316fd13c862SJeremy Soller data->ap_led.brightness_set = ap_led_set; 317fd13c862SJeremy Soller data->ap_led.max_brightness = 1; 318fd13c862SJeremy Soller data->ap_led.default_trigger = "rfkill-none"; 319fd13c862SJeremy Soller err = devm_led_classdev_register(&acpi_dev->dev, &data->ap_led); 320fd13c862SJeremy Soller if (err) 321fd13c862SJeremy Soller return err; 322fd13c862SJeremy Soller 323fd13c862SJeremy Soller data->kb_led.name = "system76_acpi::kbd_backlight"; 324fd13c862SJeremy Soller data->kb_led.flags = LED_BRIGHT_HW_CHANGED | LED_CORE_SUSPENDRESUME; 325fd13c862SJeremy Soller data->kb_led.brightness_get = kb_led_get; 326fd13c862SJeremy Soller data->kb_led.brightness_set = kb_led_set; 327fd13c862SJeremy Soller if (acpi_has_method(acpi_device_handle(data->acpi_dev), "SKBC")) { 328fd13c862SJeremy Soller data->kb_led.max_brightness = 255; 329fd13c862SJeremy Soller data->kb_toggle_brightness = 72; 330fd13c862SJeremy Soller data->kb_color = 0xffffff; 331fd13c862SJeremy Soller system76_set(data, "SKBC", data->kb_color); 332fd13c862SJeremy Soller } else { 333fd13c862SJeremy Soller data->kb_led.max_brightness = 5; 334fd13c862SJeremy Soller data->kb_color = -1; 335fd13c862SJeremy Soller } 336fd13c862SJeremy Soller err = devm_led_classdev_register(&acpi_dev->dev, &data->kb_led); 337fd13c862SJeremy Soller if (err) 338fd13c862SJeremy Soller return err; 339fd13c862SJeremy Soller 340fd13c862SJeremy Soller if (data->kb_color >= 0) { 341fd13c862SJeremy Soller err = device_create_file( 342fd13c862SJeremy Soller data->kb_led.dev, 343fd13c862SJeremy Soller &kb_led_color_dev_attr 344fd13c862SJeremy Soller ); 345fd13c862SJeremy Soller if (err) 346fd13c862SJeremy Soller return err; 347fd13c862SJeremy Soller } 348fd13c862SJeremy Soller 349fd13c862SJeremy Soller return 0; 350fd13c862SJeremy Soller } 351fd13c862SJeremy Soller 352fd13c862SJeremy Soller // Remove a System76 ACPI device 353fd13c862SJeremy Soller static int system76_remove(struct acpi_device *acpi_dev) 354fd13c862SJeremy Soller { 355fd13c862SJeremy Soller struct system76_data *data; 356fd13c862SJeremy Soller 357fd13c862SJeremy Soller data = acpi_driver_data(acpi_dev); 358fd13c862SJeremy Soller if (data->kb_color >= 0) 359fd13c862SJeremy Soller device_remove_file(data->kb_led.dev, &kb_led_color_dev_attr); 360fd13c862SJeremy Soller 361fd13c862SJeremy Soller devm_led_classdev_unregister(&acpi_dev->dev, &data->ap_led); 362fd13c862SJeremy Soller 363fd13c862SJeremy Soller devm_led_classdev_unregister(&acpi_dev->dev, &data->kb_led); 364fd13c862SJeremy Soller 365fd13c862SJeremy Soller system76_get(data, "FINI"); 366fd13c862SJeremy Soller 367fd13c862SJeremy Soller return 0; 368fd13c862SJeremy Soller } 369fd13c862SJeremy Soller 370fd13c862SJeremy Soller static struct acpi_driver system76_driver = { 371fd13c862SJeremy Soller .name = "System76 ACPI Driver", 372fd13c862SJeremy Soller .class = "hotkey", 373fd13c862SJeremy Soller .ids = device_ids, 374fd13c862SJeremy Soller .ops = { 375fd13c862SJeremy Soller .add = system76_add, 376fd13c862SJeremy Soller .remove = system76_remove, 377fd13c862SJeremy Soller .notify = system76_notify, 378fd13c862SJeremy Soller }, 379fd13c862SJeremy Soller }; 380fd13c862SJeremy Soller module_acpi_driver(system76_driver); 381fd13c862SJeremy Soller 382fd13c862SJeremy Soller MODULE_DESCRIPTION("System76 ACPI Driver"); 383fd13c862SJeremy Soller MODULE_AUTHOR("Jeremy Soller <jeremy@system76.com>"); 384fd13c862SJeremy Soller MODULE_LICENSE("GPL"); 385