Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  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");