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