1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * System76 ACPI Driver |
4 | * |
5 | * Copyright (C) 2023 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/power_supply.h> |
22 | #include <linux/sysfs.h> |
23 | #include <linux/types.h> |
24 | |
25 | #include <acpi/battery.h> |
26 | |
27 | enum kbled_type { |
28 | KBLED_NONE, |
29 | KBLED_WHITE, |
30 | KBLED_RGB, |
31 | }; |
32 | |
33 | struct system76_data { |
34 | struct acpi_device *acpi_dev; |
35 | struct led_classdev ap_led; |
36 | struct led_classdev kb_led; |
37 | enum led_brightness kb_brightness; |
38 | enum led_brightness kb_toggle_brightness; |
39 | int kb_color; |
40 | struct device *therm; |
41 | union acpi_object *nfan; |
42 | union acpi_object *ntmp; |
43 | struct input_dev *input; |
44 | bool has_open_ec; |
45 | enum kbled_type kbled_type; |
46 | }; |
47 | |
48 | static const struct acpi_device_id device_ids[] = { |
49 | {"17761776" , 0}, |
50 | {"" , 0}, |
51 | }; |
52 | MODULE_DEVICE_TABLE(acpi, device_ids); |
53 | |
54 | // Array of keyboard LED brightness levels |
55 | static const enum led_brightness kb_levels[] = { |
56 | 48, |
57 | 72, |
58 | 96, |
59 | 144, |
60 | 192, |
61 | 255 |
62 | }; |
63 | |
64 | // Array of keyboard LED colors in 24-bit RGB format |
65 | static const int kb_colors[] = { |
66 | 0xFFFFFF, |
67 | 0x0000FF, |
68 | 0xFF0000, |
69 | 0xFF00FF, |
70 | 0x00FF00, |
71 | 0x00FFFF, |
72 | 0xFFFF00 |
73 | }; |
74 | |
75 | // Get a System76 ACPI device value by name |
76 | static int system76_get(struct system76_data *data, char *method) |
77 | { |
78 | acpi_handle handle; |
79 | acpi_status status; |
80 | unsigned long long ret = 0; |
81 | |
82 | handle = acpi_device_handle(adev: data->acpi_dev); |
83 | status = acpi_evaluate_integer(handle, pathname: method, NULL, data: &ret); |
84 | if (ACPI_SUCCESS(status)) |
85 | return ret; |
86 | return -ENODEV; |
87 | } |
88 | |
89 | // Get a System76 ACPI device value by name with index |
90 | static int system76_get_index(struct system76_data *data, char *method, int index) |
91 | { |
92 | union acpi_object obj; |
93 | struct acpi_object_list obj_list; |
94 | acpi_handle handle; |
95 | acpi_status status; |
96 | unsigned long long ret = 0; |
97 | |
98 | obj.type = ACPI_TYPE_INTEGER; |
99 | obj.integer.value = index; |
100 | obj_list.count = 1; |
101 | obj_list.pointer = &obj; |
102 | |
103 | handle = acpi_device_handle(adev: data->acpi_dev); |
104 | status = acpi_evaluate_integer(handle, pathname: method, arguments: &obj_list, data: &ret); |
105 | if (ACPI_SUCCESS(status)) |
106 | return ret; |
107 | return -ENODEV; |
108 | } |
109 | |
110 | // Get a System76 ACPI device object by name |
111 | static int system76_get_object(struct system76_data *data, char *method, union acpi_object **obj) |
112 | { |
113 | acpi_handle handle; |
114 | acpi_status status; |
115 | struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; |
116 | |
117 | handle = acpi_device_handle(adev: data->acpi_dev); |
118 | status = acpi_evaluate_object(object: handle, pathname: method, NULL, return_object_buffer: &buf); |
119 | if (ACPI_SUCCESS(status)) { |
120 | *obj = buf.pointer; |
121 | return 0; |
122 | } |
123 | |
124 | return -ENODEV; |
125 | } |
126 | |
127 | // Get a name from a System76 ACPI device object |
128 | static char *system76_name(union acpi_object *obj, int index) |
129 | { |
130 | if (obj && obj->type == ACPI_TYPE_PACKAGE && index <= obj->package.count) { |
131 | if (obj->package.elements[index].type == ACPI_TYPE_STRING) |
132 | return obj->package.elements[index].string.pointer; |
133 | } |
134 | |
135 | return NULL; |
136 | } |
137 | |
138 | // Set a System76 ACPI device value by name |
139 | static int system76_set(struct system76_data *data, char *method, int value) |
140 | { |
141 | union acpi_object obj; |
142 | struct acpi_object_list obj_list; |
143 | acpi_handle handle; |
144 | acpi_status status; |
145 | |
146 | obj.type = ACPI_TYPE_INTEGER; |
147 | obj.integer.value = value; |
148 | obj_list.count = 1; |
149 | obj_list.pointer = &obj; |
150 | handle = acpi_device_handle(adev: data->acpi_dev); |
151 | status = acpi_evaluate_object(object: handle, pathname: method, parameter_objects: &obj_list, NULL); |
152 | if (ACPI_SUCCESS(status)) |
153 | return 0; |
154 | else |
155 | return -1; |
156 | } |
157 | |
158 | #define BATTERY_THRESHOLD_INVALID 0xFF |
159 | |
160 | enum { |
161 | THRESHOLD_START, |
162 | THRESHOLD_END, |
163 | }; |
164 | |
165 | static ssize_t battery_get_threshold(int which, char *buf) |
166 | { |
167 | struct acpi_object_list input; |
168 | union acpi_object param; |
169 | acpi_handle handle; |
170 | acpi_status status; |
171 | unsigned long long ret = BATTERY_THRESHOLD_INVALID; |
172 | |
173 | handle = ec_get_handle(); |
174 | if (!handle) |
175 | return -ENODEV; |
176 | |
177 | input.count = 1; |
178 | input.pointer = ¶m; |
179 | // Start/stop selection |
180 | param.type = ACPI_TYPE_INTEGER; |
181 | param.integer.value = which; |
182 | |
183 | status = acpi_evaluate_integer(handle, pathname: "GBCT" , arguments: &input, data: &ret); |
184 | if (ACPI_FAILURE(status)) |
185 | return -EIO; |
186 | if (ret == BATTERY_THRESHOLD_INVALID) |
187 | return -EINVAL; |
188 | |
189 | return sysfs_emit(buf, fmt: "%d\n" , (int)ret); |
190 | } |
191 | |
192 | static ssize_t battery_set_threshold(int which, const char *buf, size_t count) |
193 | { |
194 | struct acpi_object_list input; |
195 | union acpi_object params[2]; |
196 | acpi_handle handle; |
197 | acpi_status status; |
198 | unsigned int value; |
199 | int ret; |
200 | |
201 | handle = ec_get_handle(); |
202 | if (!handle) |
203 | return -ENODEV; |
204 | |
205 | ret = kstrtouint(s: buf, base: 10, res: &value); |
206 | if (ret) |
207 | return ret; |
208 | |
209 | if (value > 100) |
210 | return -EINVAL; |
211 | |
212 | input.count = 2; |
213 | input.pointer = params; |
214 | // Start/stop selection |
215 | params[0].type = ACPI_TYPE_INTEGER; |
216 | params[0].integer.value = which; |
217 | // Threshold value |
218 | params[1].type = ACPI_TYPE_INTEGER; |
219 | params[1].integer.value = value; |
220 | |
221 | status = acpi_evaluate_object(object: handle, pathname: "SBCT" , parameter_objects: &input, NULL); |
222 | if (ACPI_FAILURE(status)) |
223 | return -EIO; |
224 | |
225 | return count; |
226 | } |
227 | |
228 | static ssize_t charge_control_start_threshold_show(struct device *dev, |
229 | struct device_attribute *attr, char *buf) |
230 | { |
231 | return battery_get_threshold(which: THRESHOLD_START, buf); |
232 | } |
233 | |
234 | static ssize_t charge_control_start_threshold_store(struct device *dev, |
235 | struct device_attribute *attr, const char *buf, size_t count) |
236 | { |
237 | return battery_set_threshold(which: THRESHOLD_START, buf, count); |
238 | } |
239 | |
240 | static DEVICE_ATTR_RW(charge_control_start_threshold); |
241 | |
242 | static ssize_t charge_control_end_threshold_show(struct device *dev, |
243 | struct device_attribute *attr, char *buf) |
244 | { |
245 | return battery_get_threshold(which: THRESHOLD_END, buf); |
246 | } |
247 | |
248 | static ssize_t charge_control_end_threshold_store(struct device *dev, |
249 | struct device_attribute *attr, const char *buf, size_t count) |
250 | { |
251 | return battery_set_threshold(which: THRESHOLD_END, buf, count); |
252 | } |
253 | |
254 | static DEVICE_ATTR_RW(charge_control_end_threshold); |
255 | |
256 | static struct attribute *system76_battery_attrs[] = { |
257 | &dev_attr_charge_control_start_threshold.attr, |
258 | &dev_attr_charge_control_end_threshold.attr, |
259 | NULL, |
260 | }; |
261 | |
262 | ATTRIBUTE_GROUPS(system76_battery); |
263 | |
264 | static int system76_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) |
265 | { |
266 | // System76 EC only supports 1 battery |
267 | if (strcmp(battery->desc->name, "BAT0" ) != 0) |
268 | return -ENODEV; |
269 | |
270 | if (device_add_groups(dev: &battery->dev, groups: system76_battery_groups)) |
271 | return -ENODEV; |
272 | |
273 | return 0; |
274 | } |
275 | |
276 | static int system76_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook) |
277 | { |
278 | device_remove_groups(dev: &battery->dev, groups: system76_battery_groups); |
279 | return 0; |
280 | } |
281 | |
282 | static struct acpi_battery_hook system76_battery_hook = { |
283 | .add_battery = system76_battery_add, |
284 | .remove_battery = system76_battery_remove, |
285 | .name = "System76 Battery Extension" , |
286 | }; |
287 | |
288 | static void system76_battery_init(void) |
289 | { |
290 | battery_hook_register(hook: &system76_battery_hook); |
291 | } |
292 | |
293 | static void system76_battery_exit(void) |
294 | { |
295 | battery_hook_unregister(hook: &system76_battery_hook); |
296 | } |
297 | |
298 | // Get the airplane mode LED brightness |
299 | static enum led_brightness ap_led_get(struct led_classdev *led) |
300 | { |
301 | struct system76_data *data; |
302 | int value; |
303 | |
304 | data = container_of(led, struct system76_data, ap_led); |
305 | value = system76_get(data, method: "GAPL" ); |
306 | if (value > 0) |
307 | return (enum led_brightness)value; |
308 | else |
309 | return LED_OFF; |
310 | } |
311 | |
312 | // Set the airplane mode LED brightness |
313 | static int ap_led_set(struct led_classdev *led, enum led_brightness value) |
314 | { |
315 | struct system76_data *data; |
316 | |
317 | data = container_of(led, struct system76_data, ap_led); |
318 | return system76_set(data, method: "SAPL" , value: value == LED_OFF ? 0 : 1); |
319 | } |
320 | |
321 | // Get the last set keyboard LED brightness |
322 | static enum led_brightness kb_led_get(struct led_classdev *led) |
323 | { |
324 | struct system76_data *data; |
325 | |
326 | data = container_of(led, struct system76_data, kb_led); |
327 | return data->kb_brightness; |
328 | } |
329 | |
330 | // Set the keyboard LED brightness |
331 | static int kb_led_set(struct led_classdev *led, enum led_brightness value) |
332 | { |
333 | struct system76_data *data; |
334 | |
335 | data = container_of(led, struct system76_data, kb_led); |
336 | data->kb_brightness = value; |
337 | if (acpi_has_method(handle: acpi_device_handle(adev: data->acpi_dev), name: "GKBK" )) { |
338 | return system76_set(data, method: "SKBB" , value: (int)data->kb_brightness); |
339 | } else { |
340 | return system76_set(data, method: "SKBL" , value: (int)data->kb_brightness); |
341 | } |
342 | } |
343 | |
344 | // Get the last set keyboard LED color |
345 | static ssize_t kb_led_color_show( |
346 | struct device *dev, |
347 | struct device_attribute *dev_attr, |
348 | char *buf) |
349 | { |
350 | struct led_classdev *led; |
351 | struct system76_data *data; |
352 | |
353 | led = dev_get_drvdata(dev); |
354 | data = container_of(led, struct system76_data, kb_led); |
355 | return sysfs_emit(buf, fmt: "%06X\n" , data->kb_color); |
356 | } |
357 | |
358 | // Set the keyboard LED color |
359 | static ssize_t kb_led_color_store( |
360 | struct device *dev, |
361 | struct device_attribute *dev_attr, |
362 | const char *buf, |
363 | size_t size) |
364 | { |
365 | struct led_classdev *led; |
366 | struct system76_data *data; |
367 | unsigned int val; |
368 | int ret; |
369 | |
370 | led = dev_get_drvdata(dev); |
371 | data = container_of(led, struct system76_data, kb_led); |
372 | ret = kstrtouint(s: buf, base: 16, res: &val); |
373 | if (ret) |
374 | return ret; |
375 | if (val > 0xFFFFFF) |
376 | return -EINVAL; |
377 | data->kb_color = (int)val; |
378 | system76_set(data, method: "SKBC" , value: data->kb_color); |
379 | |
380 | return size; |
381 | } |
382 | |
383 | static struct device_attribute dev_attr_kb_led_color = { |
384 | .attr = { |
385 | .name = "color" , |
386 | .mode = 0644, |
387 | }, |
388 | .show = kb_led_color_show, |
389 | .store = kb_led_color_store, |
390 | }; |
391 | |
392 | static struct attribute *system76_kb_led_color_attrs[] = { |
393 | &dev_attr_kb_led_color.attr, |
394 | NULL, |
395 | }; |
396 | |
397 | ATTRIBUTE_GROUPS(system76_kb_led_color); |
398 | |
399 | // Notify that the keyboard LED was changed by hardware |
400 | static void kb_led_notify(struct system76_data *data) |
401 | { |
402 | led_classdev_notify_brightness_hw_changed( |
403 | led_cdev: &data->kb_led, |
404 | brightness: data->kb_brightness |
405 | ); |
406 | } |
407 | |
408 | // Read keyboard LED brightness as set by hardware |
409 | static void kb_led_hotkey_hardware(struct system76_data *data) |
410 | { |
411 | int value; |
412 | |
413 | if (acpi_has_method(handle: acpi_device_handle(adev: data->acpi_dev), name: "GKBK" )) { |
414 | value = system76_get(data, method: "GKBB" ); |
415 | } else { |
416 | value = system76_get(data, method: "GKBL" ); |
417 | } |
418 | |
419 | if (value < 0) |
420 | return; |
421 | data->kb_brightness = value; |
422 | kb_led_notify(data); |
423 | } |
424 | |
425 | // Toggle the keyboard LED |
426 | static void kb_led_hotkey_toggle(struct system76_data *data) |
427 | { |
428 | if (data->kb_brightness > 0) { |
429 | data->kb_toggle_brightness = data->kb_brightness; |
430 | kb_led_set(led: &data->kb_led, value: 0); |
431 | } else { |
432 | kb_led_set(led: &data->kb_led, value: data->kb_toggle_brightness); |
433 | } |
434 | kb_led_notify(data); |
435 | } |
436 | |
437 | // Decrease the keyboard LED brightness |
438 | static void kb_led_hotkey_down(struct system76_data *data) |
439 | { |
440 | int i; |
441 | |
442 | if (data->kb_brightness > 0) { |
443 | for (i = ARRAY_SIZE(kb_levels); i > 0; i--) { |
444 | if (kb_levels[i - 1] < data->kb_brightness) { |
445 | kb_led_set(led: &data->kb_led, value: kb_levels[i - 1]); |
446 | break; |
447 | } |
448 | } |
449 | } else { |
450 | kb_led_set(led: &data->kb_led, value: data->kb_toggle_brightness); |
451 | } |
452 | kb_led_notify(data); |
453 | } |
454 | |
455 | // Increase the keyboard LED brightness |
456 | static void kb_led_hotkey_up(struct system76_data *data) |
457 | { |
458 | int i; |
459 | |
460 | if (data->kb_brightness > 0) { |
461 | for (i = 0; i < ARRAY_SIZE(kb_levels); i++) { |
462 | if (kb_levels[i] > data->kb_brightness) { |
463 | kb_led_set(led: &data->kb_led, value: kb_levels[i]); |
464 | break; |
465 | } |
466 | } |
467 | } else { |
468 | kb_led_set(led: &data->kb_led, value: data->kb_toggle_brightness); |
469 | } |
470 | kb_led_notify(data); |
471 | } |
472 | |
473 | // Cycle the keyboard LED color |
474 | static void kb_led_hotkey_color(struct system76_data *data) |
475 | { |
476 | int i; |
477 | |
478 | if (data->kbled_type != KBLED_RGB) |
479 | return; |
480 | |
481 | if (data->kb_brightness > 0) { |
482 | for (i = 0; i < ARRAY_SIZE(kb_colors); i++) { |
483 | if (kb_colors[i] == data->kb_color) |
484 | break; |
485 | } |
486 | i += 1; |
487 | if (i >= ARRAY_SIZE(kb_colors)) |
488 | i = 0; |
489 | data->kb_color = kb_colors[i]; |
490 | system76_set(data, method: "SKBC" , value: data->kb_color); |
491 | } else { |
492 | kb_led_set(led: &data->kb_led, value: data->kb_toggle_brightness); |
493 | } |
494 | kb_led_notify(data); |
495 | } |
496 | |
497 | static umode_t thermal_is_visible(const void *drvdata, enum hwmon_sensor_types type, |
498 | u32 attr, int channel) |
499 | { |
500 | const struct system76_data *data = drvdata; |
501 | |
502 | switch (type) { |
503 | case hwmon_fan: |
504 | case hwmon_pwm: |
505 | if (system76_name(obj: data->nfan, index: channel)) |
506 | return 0444; |
507 | break; |
508 | |
509 | case hwmon_temp: |
510 | if (system76_name(obj: data->ntmp, index: channel)) |
511 | return 0444; |
512 | break; |
513 | |
514 | default: |
515 | return 0; |
516 | } |
517 | |
518 | return 0; |
519 | } |
520 | |
521 | static int thermal_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, |
522 | int channel, long *val) |
523 | { |
524 | struct system76_data *data = dev_get_drvdata(dev); |
525 | int raw; |
526 | |
527 | switch (type) { |
528 | case hwmon_fan: |
529 | if (attr == hwmon_fan_input) { |
530 | raw = system76_get_index(data, method: "GFAN" , index: channel); |
531 | if (raw < 0) |
532 | return raw; |
533 | *val = (raw >> 8) & 0xFFFF; |
534 | return 0; |
535 | } |
536 | break; |
537 | |
538 | case hwmon_pwm: |
539 | if (attr == hwmon_pwm_input) { |
540 | raw = system76_get_index(data, method: "GFAN" , index: channel); |
541 | if (raw < 0) |
542 | return raw; |
543 | *val = raw & 0xFF; |
544 | return 0; |
545 | } |
546 | break; |
547 | |
548 | case hwmon_temp: |
549 | if (attr == hwmon_temp_input) { |
550 | raw = system76_get_index(data, method: "GTMP" , index: channel); |
551 | if (raw < 0) |
552 | return raw; |
553 | *val = raw * 1000; |
554 | return 0; |
555 | } |
556 | break; |
557 | |
558 | default: |
559 | return -EOPNOTSUPP; |
560 | } |
561 | |
562 | return -EOPNOTSUPP; |
563 | } |
564 | |
565 | static int thermal_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, |
566 | int channel, const char **str) |
567 | { |
568 | struct system76_data *data = dev_get_drvdata(dev); |
569 | |
570 | switch (type) { |
571 | case hwmon_fan: |
572 | if (attr == hwmon_fan_label) { |
573 | *str = system76_name(obj: data->nfan, index: channel); |
574 | if (*str) |
575 | return 0; |
576 | } |
577 | break; |
578 | |
579 | case hwmon_temp: |
580 | if (attr == hwmon_temp_label) { |
581 | *str = system76_name(obj: data->ntmp, index: channel); |
582 | if (*str) |
583 | return 0; |
584 | } |
585 | break; |
586 | |
587 | default: |
588 | return -EOPNOTSUPP; |
589 | } |
590 | |
591 | return -EOPNOTSUPP; |
592 | } |
593 | |
594 | static const struct hwmon_ops thermal_ops = { |
595 | .is_visible = thermal_is_visible, |
596 | .read = thermal_read, |
597 | .read_string = thermal_read_string, |
598 | }; |
599 | |
600 | // Allocate up to 8 fans and temperatures |
601 | static const struct hwmon_channel_info * const thermal_channel_info[] = { |
602 | HWMON_CHANNEL_INFO(fan, |
603 | HWMON_F_INPUT | HWMON_F_LABEL, |
604 | HWMON_F_INPUT | HWMON_F_LABEL, |
605 | HWMON_F_INPUT | HWMON_F_LABEL, |
606 | HWMON_F_INPUT | HWMON_F_LABEL, |
607 | HWMON_F_INPUT | HWMON_F_LABEL, |
608 | HWMON_F_INPUT | HWMON_F_LABEL, |
609 | HWMON_F_INPUT | HWMON_F_LABEL, |
610 | HWMON_F_INPUT | HWMON_F_LABEL), |
611 | HWMON_CHANNEL_INFO(pwm, |
612 | HWMON_PWM_INPUT, |
613 | HWMON_PWM_INPUT, |
614 | HWMON_PWM_INPUT, |
615 | HWMON_PWM_INPUT, |
616 | HWMON_PWM_INPUT, |
617 | HWMON_PWM_INPUT, |
618 | HWMON_PWM_INPUT, |
619 | HWMON_PWM_INPUT), |
620 | HWMON_CHANNEL_INFO(temp, |
621 | HWMON_T_INPUT | HWMON_T_LABEL, |
622 | HWMON_T_INPUT | HWMON_T_LABEL, |
623 | HWMON_T_INPUT | HWMON_T_LABEL, |
624 | HWMON_T_INPUT | HWMON_T_LABEL, |
625 | HWMON_T_INPUT | HWMON_T_LABEL, |
626 | HWMON_T_INPUT | HWMON_T_LABEL, |
627 | HWMON_T_INPUT | HWMON_T_LABEL, |
628 | HWMON_T_INPUT | HWMON_T_LABEL), |
629 | NULL |
630 | }; |
631 | |
632 | static const struct hwmon_chip_info thermal_chip_info = { |
633 | .ops = &thermal_ops, |
634 | .info = thermal_channel_info, |
635 | }; |
636 | |
637 | static void input_key(struct system76_data *data, unsigned int code) |
638 | { |
639 | input_report_key(dev: data->input, code, value: 1); |
640 | input_sync(dev: data->input); |
641 | |
642 | input_report_key(dev: data->input, code, value: 0); |
643 | input_sync(dev: data->input); |
644 | } |
645 | |
646 | // Handle ACPI notification |
647 | static void system76_notify(struct acpi_device *acpi_dev, u32 event) |
648 | { |
649 | struct system76_data *data; |
650 | |
651 | data = acpi_driver_data(d: acpi_dev); |
652 | switch (event) { |
653 | case 0x80: |
654 | kb_led_hotkey_hardware(data); |
655 | break; |
656 | case 0x81: |
657 | kb_led_hotkey_toggle(data); |
658 | break; |
659 | case 0x82: |
660 | kb_led_hotkey_down(data); |
661 | break; |
662 | case 0x83: |
663 | kb_led_hotkey_up(data); |
664 | break; |
665 | case 0x84: |
666 | kb_led_hotkey_color(data); |
667 | break; |
668 | case 0x85: |
669 | input_key(data, KEY_SCREENLOCK); |
670 | break; |
671 | } |
672 | } |
673 | |
674 | // Add a System76 ACPI device |
675 | static int system76_add(struct acpi_device *acpi_dev) |
676 | { |
677 | struct system76_data *data; |
678 | int err; |
679 | |
680 | data = devm_kzalloc(dev: &acpi_dev->dev, size: sizeof(*data), GFP_KERNEL); |
681 | if (!data) |
682 | return -ENOMEM; |
683 | acpi_dev->driver_data = data; |
684 | data->acpi_dev = acpi_dev; |
685 | |
686 | // Some models do not run open EC firmware. Check for an ACPI method |
687 | // that only exists on open EC to guard functionality specific to it. |
688 | data->has_open_ec = acpi_has_method(handle: acpi_device_handle(adev: data->acpi_dev), name: "NFAN" ); |
689 | |
690 | err = system76_get(data, method: "INIT" ); |
691 | if (err) |
692 | return err; |
693 | data->ap_led.name = "system76_acpi::airplane" ; |
694 | data->ap_led.flags = LED_CORE_SUSPENDRESUME; |
695 | data->ap_led.brightness_get = ap_led_get; |
696 | data->ap_led.brightness_set_blocking = ap_led_set; |
697 | data->ap_led.max_brightness = 1; |
698 | data->ap_led.default_trigger = "rfkill-none" ; |
699 | err = devm_led_classdev_register(parent: &acpi_dev->dev, led_cdev: &data->ap_led); |
700 | if (err) |
701 | return err; |
702 | |
703 | data->kb_led.name = "system76_acpi::kbd_backlight" ; |
704 | data->kb_led.flags = LED_BRIGHT_HW_CHANGED | LED_CORE_SUSPENDRESUME; |
705 | data->kb_led.brightness_get = kb_led_get; |
706 | data->kb_led.brightness_set_blocking = kb_led_set; |
707 | if (acpi_has_method(handle: acpi_device_handle(adev: data->acpi_dev), name: "GKBK" )) { |
708 | // Use the new ACPI methods |
709 | data->kbled_type = system76_get(data, method: "GKBK" ); |
710 | |
711 | switch (data->kbled_type) { |
712 | case KBLED_NONE: |
713 | // Nothing to do: Device will not be registered. |
714 | break; |
715 | case KBLED_WHITE: |
716 | data->kb_led.max_brightness = 255; |
717 | data->kb_toggle_brightness = 72; |
718 | break; |
719 | case KBLED_RGB: |
720 | data->kb_led.max_brightness = 255; |
721 | data->kb_led.groups = system76_kb_led_color_groups; |
722 | data->kb_toggle_brightness = 72; |
723 | data->kb_color = 0xffffff; |
724 | system76_set(data, method: "SKBC" , value: data->kb_color); |
725 | break; |
726 | } |
727 | } else { |
728 | // Use the old ACPI methods |
729 | if (acpi_has_method(handle: acpi_device_handle(adev: data->acpi_dev), name: "SKBC" )) { |
730 | data->kbled_type = KBLED_RGB; |
731 | data->kb_led.max_brightness = 255; |
732 | data->kb_led.groups = system76_kb_led_color_groups; |
733 | data->kb_toggle_brightness = 72; |
734 | data->kb_color = 0xffffff; |
735 | system76_set(data, method: "SKBC" , value: data->kb_color); |
736 | } else { |
737 | data->kbled_type = KBLED_WHITE; |
738 | data->kb_led.max_brightness = 5; |
739 | } |
740 | } |
741 | |
742 | if (data->kbled_type != KBLED_NONE) { |
743 | err = devm_led_classdev_register(parent: &acpi_dev->dev, led_cdev: &data->kb_led); |
744 | if (err) |
745 | return err; |
746 | } |
747 | |
748 | data->input = devm_input_allocate_device(&acpi_dev->dev); |
749 | if (!data->input) |
750 | return -ENOMEM; |
751 | |
752 | data->input->name = "System76 ACPI Hotkeys" ; |
753 | data->input->phys = "system76_acpi/input0" ; |
754 | data->input->id.bustype = BUS_HOST; |
755 | data->input->dev.parent = &acpi_dev->dev; |
756 | input_set_capability(dev: data->input, EV_KEY, KEY_SCREENLOCK); |
757 | |
758 | err = input_register_device(data->input); |
759 | if (err) |
760 | goto error; |
761 | |
762 | if (data->has_open_ec) { |
763 | err = system76_get_object(data, method: "NFAN" , obj: &data->nfan); |
764 | if (err) |
765 | goto error; |
766 | |
767 | err = system76_get_object(data, method: "NTMP" , obj: &data->ntmp); |
768 | if (err) |
769 | goto error; |
770 | |
771 | data->therm = devm_hwmon_device_register_with_info(dev: &acpi_dev->dev, |
772 | name: "system76_acpi" , drvdata: data, info: &thermal_chip_info, NULL); |
773 | err = PTR_ERR_OR_ZERO(ptr: data->therm); |
774 | if (err) |
775 | goto error; |
776 | |
777 | system76_battery_init(); |
778 | } |
779 | |
780 | return 0; |
781 | |
782 | error: |
783 | if (data->has_open_ec) { |
784 | kfree(objp: data->ntmp); |
785 | kfree(objp: data->nfan); |
786 | } |
787 | return err; |
788 | } |
789 | |
790 | // Remove a System76 ACPI device |
791 | static void system76_remove(struct acpi_device *acpi_dev) |
792 | { |
793 | struct system76_data *data; |
794 | |
795 | data = acpi_driver_data(d: acpi_dev); |
796 | |
797 | if (data->has_open_ec) { |
798 | system76_battery_exit(); |
799 | kfree(objp: data->nfan); |
800 | kfree(objp: data->ntmp); |
801 | } |
802 | |
803 | devm_led_classdev_unregister(parent: &acpi_dev->dev, led_cdev: &data->ap_led); |
804 | devm_led_classdev_unregister(parent: &acpi_dev->dev, led_cdev: &data->kb_led); |
805 | |
806 | system76_get(data, method: "FINI" ); |
807 | } |
808 | |
809 | static struct acpi_driver system76_driver = { |
810 | .name = "System76 ACPI Driver" , |
811 | .class = "hotkey" , |
812 | .ids = device_ids, |
813 | .ops = { |
814 | .add = system76_add, |
815 | .remove = system76_remove, |
816 | .notify = system76_notify, |
817 | }, |
818 | }; |
819 | module_acpi_driver(system76_driver); |
820 | |
821 | MODULE_DESCRIPTION("System76 ACPI Driver" ); |
822 | MODULE_AUTHOR("Jeremy Soller <jeremy@system76.com>" ); |
823 | MODULE_LICENSE("GPL" ); |
824 | |