1 // SPDX-License-Identifier: GPL-2.0+
5 * Copyright (C) 2019 System76
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.
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>
20 struct 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
;
29 static const struct acpi_device_id device_ids
[] = {
33 MODULE_DEVICE_TABLE(acpi
, device_ids
);
35 // Array of keyboard LED brightness levels
36 static const enum led_brightness kb_levels
[] = {
45 // Array of keyboard LED colors in 24-bit RGB format
46 static const int kb_colors
[] = {
56 // Get a System76 ACPI device value by name
57 static int system76_get(struct system76_data
*data
, char *method
)
61 unsigned long long ret
= 0;
63 handle
= acpi_device_handle(data
->acpi_dev
);
64 status
= acpi_evaluate_integer(handle
, method
, NULL
, &ret
);
65 if (ACPI_SUCCESS(status
))
71 // Set a System76 ACPI device value by name
72 static int system76_set(struct system76_data
*data
, char *method
, int value
)
74 union acpi_object obj
;
75 struct acpi_object_list obj_list
;
79 obj
.type
= ACPI_TYPE_INTEGER
;
80 obj
.integer
.value
= value
;
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
))
91 // Get the airplane mode LED brightness
92 static enum led_brightness
ap_led_get(struct led_classdev
*led
)
94 struct system76_data
*data
;
97 data
= container_of(led
, struct system76_data
, ap_led
);
98 value
= system76_get(data
, "GAPL");
100 return (enum led_brightness
)value
;
105 // Set the airplane mode LED brightness
106 static void ap_led_set(struct led_classdev
*led
, enum led_brightness value
)
108 struct system76_data
*data
;
110 data
= container_of(led
, struct system76_data
, ap_led
);
111 system76_set(data
, "SAPL", value
== LED_OFF
? 0 : 1);
114 // Get the last set keyboard LED brightness
115 static enum led_brightness
kb_led_get(struct led_classdev
*led
)
117 struct system76_data
*data
;
119 data
= container_of(led
, struct system76_data
, kb_led
);
120 return data
->kb_brightness
;
123 // Set the keyboard LED brightness
124 static void kb_led_set(struct led_classdev
*led
, enum led_brightness value
)
126 struct system76_data
*data
;
128 data
= container_of(led
, struct system76_data
, kb_led
);
129 data
->kb_brightness
= value
;
130 system76_set(data
, "SKBL", (int)data
->kb_brightness
);
133 // Get the last set keyboard LED color
134 static ssize_t
kb_led_color_show(
136 struct device_attribute
*dev_attr
,
139 struct led_classdev
*led
;
140 struct system76_data
*data
;
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
);
147 // Set the keyboard LED color
148 static ssize_t
kb_led_color_store(
150 struct device_attribute
*dev_attr
,
154 struct led_classdev
*led
;
155 struct system76_data
*data
;
159 led
= (struct led_classdev
*)dev
->driver_data
;
160 data
= container_of(led
, struct system76_data
, kb_led
);
161 ret
= kstrtouint(buf
, 16, &val
);
166 data
->kb_color
= (int)val
;
167 system76_set(data
, "SKBC", data
->kb_color
);
172 static const struct device_attribute kb_led_color_dev_attr
= {
177 .show
= kb_led_color_show
,
178 .store
= kb_led_color_store
,
181 // Notify that the keyboard LED was changed by hardware
182 static void kb_led_notify(struct system76_data
*data
)
184 led_classdev_notify_brightness_hw_changed(
190 // Read keyboard LED brightness as set by hardware
191 static void kb_led_hotkey_hardware(struct system76_data
*data
)
195 value
= system76_get(data
, "GKBL");
198 data
->kb_brightness
= value
;
202 // Toggle the keyboard LED
203 static void kb_led_hotkey_toggle(struct system76_data
*data
)
205 if (data
->kb_brightness
> 0) {
206 data
->kb_toggle_brightness
= data
->kb_brightness
;
207 kb_led_set(&data
->kb_led
, 0);
209 kb_led_set(&data
->kb_led
, data
->kb_toggle_brightness
);
214 // Decrease the keyboard LED brightness
215 static void kb_led_hotkey_down(struct system76_data
*data
)
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]);
227 kb_led_set(&data
->kb_led
, data
->kb_toggle_brightness
);
232 // Increase the keyboard LED brightness
233 static void kb_led_hotkey_up(struct system76_data
*data
)
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
]);
245 kb_led_set(&data
->kb_led
, data
->kb_toggle_brightness
);
250 // Cycle the keyboard LED color
251 static void kb_led_hotkey_color(struct system76_data
*data
)
255 if (data
->kb_color
< 0)
257 if (data
->kb_brightness
> 0) {
258 for (i
= 0; i
< ARRAY_SIZE(kb_colors
); i
++) {
259 if (kb_colors
[i
] == data
->kb_color
)
263 if (i
>= ARRAY_SIZE(kb_colors
))
265 data
->kb_color
= kb_colors
[i
];
266 system76_set(data
, "SKBC", data
->kb_color
);
268 kb_led_set(&data
->kb_led
, data
->kb_toggle_brightness
);
273 // Handle ACPI notification
274 static void system76_notify(struct acpi_device
*acpi_dev
, u32 event
)
276 struct system76_data
*data
;
278 data
= acpi_driver_data(acpi_dev
);
281 kb_led_hotkey_hardware(data
);
284 kb_led_hotkey_toggle(data
);
287 kb_led_hotkey_down(data
);
290 kb_led_hotkey_up(data
);
293 kb_led_hotkey_color(data
);
298 // Add a System76 ACPI device
299 static int system76_add(struct acpi_device
*acpi_dev
)
301 struct system76_data
*data
;
304 data
= devm_kzalloc(&acpi_dev
->dev
, sizeof(*data
), GFP_KERNEL
);
307 acpi_dev
->driver_data
= data
;
308 data
->acpi_dev
= acpi_dev
;
310 err
= system76_get(data
, "INIT");
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
= 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
);
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
= 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
);
333 data
->kb_led
.max_brightness
= 5;
336 err
= devm_led_classdev_register(&acpi_dev
->dev
, &data
->kb_led
);
340 if (data
->kb_color
>= 0) {
341 err
= device_create_file(
343 &kb_led_color_dev_attr
352 // Remove a System76 ACPI device
353 static int system76_remove(struct acpi_device
*acpi_dev
)
355 struct system76_data
*data
;
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
);
361 devm_led_classdev_unregister(&acpi_dev
->dev
, &data
->ap_led
);
363 devm_led_classdev_unregister(&acpi_dev
->dev
, &data
->kb_led
);
365 system76_get(data
, "FINI");
370 static struct acpi_driver system76_driver
= {
371 .name
= "System76 ACPI Driver",
376 .remove
= system76_remove
,
377 .notify
= system76_notify
,
380 module_acpi_driver(system76_driver
);
382 MODULE_DESCRIPTION("System76 ACPI Driver");
383 MODULE_AUTHOR("Jeremy Soller <jeremy@system76.com>");
384 MODULE_LICENSE("GPL");