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