2 * Intel NUC LED Control Driver
4 * Copyright (C) 2017 Miles Peterson
6 * Portions based on asus-wmi.c:
7 * Copyright (C) 2010 Intel Corporation.
8 * Copyright (C) 2010-2011 Corentin Chary <corentin.chary@gmail.com>
10 * Portions based on acpi_call.c:
11 * Copyright (C) 2010: Michal Kottman
13 * Based on Intel Article ID 000023426
14 * http://www.intel.com/content/www/us/en/support/boards-and-kits/intel-nuc-kits/000023426.html
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
33 #include <linux/module.h>
34 #include <linux/kernel.h>
35 #include <linux/types.h>
36 #include <linux/proc_fs.h>
37 #include <linux/acpi.h>
38 #include <linux/vmalloc.h>
39 #include <linux/uaccess.h>
41 MODULE_AUTHOR("Miles Peterson");
42 MODULE_DESCRIPTION("Intel NUC LED Control WMI Driver");
43 MODULE_LICENSE("GPL");
45 /* Intel NUC WMI GUID */
46 #define NUCLED_WMI_MGMT_GUID "8C5DA44C-CDC3-46b3-8619-4E26D34390B7"
47 MODULE_ALIAS("wmi:" NUCLED_WMI_MGMT_GUID);
49 /* LED Control Method ID */
50 #define NUCLED_WMI_METHODID_GETSTATE 0x01
51 #define NUCLED_WMI_METHODID_SETSTATE 0x02
54 #define NUCLED_WMI_POWER_LED_ID 0x01
55 #define NUCLED_WMI_RING_LED_ID 0x02
58 #define NUCLED_WMI_RETURN_SUCCESS 0x00
59 #define NUCLED_WMI_RETURN_NOSUPPORT 0xE1
60 #define NUCLED_WMI_RETURN_UNDEFINED 0xE2
61 #define NUCLED_WMI_RETURN_NORESPONSE 0xE3
62 #define NUCLED_WMI_RETURN_BADPARAM 0xE4
63 #define NUCLED_WMI_RETURN_UNEXPECTED 0xEF
66 #define NUCLED_WMI_BLINK_1HZ 0x01
67 #define NUCLED_WMI_BLINK_0_25HZ 0x02
68 #define NUCLED_WMI_FADE_1HZ 0x03
69 #define NUCLED_WMI_ALWAYS_ON 0x04
70 #define NUCLED_WMI_BLINK_0_5HZ 0x05
71 #define NUCLED_WMI_FADE_0_25HZ 0x06
72 #define NUCLED_WMI_FADE_0_5HZ 0x07
74 /* Power button colors */
75 #define NUCLED_WMI_POWER_COLOR_DISABLE 0x00
76 #define NUCLED_WMI_POWER_COLOR_BLUE 0x01
77 #define NUCLED_WMI_POWER_COLOR_AMBER 0x02
80 #define NUCLED_WMI_RING_COLOR_DISABLE 0x00
81 #define NUCLED_WMI_RING_COLOR_CYAN 0x01
82 #define NUCLED_WMI_RING_COLOR_PINK 0x02
83 #define NUCLED_WMI_RING_COLOR_YELLOW 0x03
84 #define NUCLED_WMI_RING_COLOR_BLUE 0x04
85 #define NUCLED_WMI_RING_COLOR_RED 0x05
86 #define NUCLED_WMI_RING_COLOR_GREEN 0x06
87 #define NUCLED_WMI_RING_COLOR_WHITE 0x07
89 extern struct proc_dir_entry *acpi_root_dir;
91 struct led_get_state_args {
95 struct led_get_state_return {
102 struct led_set_state_args {
109 struct led_set_state_return {
110 u32 brightness_return;
111 u32 blink_fade_return;
115 #define BUFFER_SIZE 512
116 static char result_buffer[BUFFER_SIZE];
117 static char *get_buffer_end(void) {
118 return result_buffer + strlen(result_buffer);
121 /* Convert blink/fade value to text */
122 static const char* const blink_fade_text[] = { "Off", "1Hz Blink", "0.25Hz Blink", "1Hz Fade", "Always On", "0.5Hz Blink", "0.25Hz Fade", "0.5Hz Fade" };
124 /* Convert color value to text */
125 static const char* const pwrcolor_text[] = { "Off", "Blue", "Amber" };
126 static const char* const ringcolor_text[] = { "Off", "Cyan", "Pink", "Yellow", "Blue", "Red", "Green", "White" };
129 static int nuc_led_get_state(u32 led, struct led_get_state_return *state)
131 struct led_get_state_args args = {
134 struct acpi_buffer input;
135 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
137 union acpi_object *obj;
139 input.length = (acpi_size) sizeof(args);
140 input.pointer = &args;
142 status = wmi_evaluate_method(NUCLED_WMI_MGMT_GUID, 1, NUCLED_WMI_METHODID_GETSTATE,
145 if (ACPI_FAILURE(status))
148 // Always returns a buffer
149 obj = (union acpi_object *)output.pointer;
152 state->return_code = obj->buffer.pointer[0];
153 state->brightness = obj->buffer.pointer[1];
154 state->blink_fade = obj->buffer.pointer[2];
155 state->color_state = obj->buffer.pointer[3];
164 static int nuc_led_set_state(u32 led, u32 brightness, u32 blink_fade, u32 color_state,
165 struct led_set_state_return *retval)
167 struct led_set_state_args args = {
169 .brightness = brightness,
170 .blink_fade = blink_fade,
171 .color_state = color_state
174 struct acpi_buffer input;
175 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
177 union acpi_object *obj;
179 input.length = (acpi_size) sizeof(args);
180 input.pointer = &args;
182 status = wmi_evaluate_method(NUCLED_WMI_MGMT_GUID, 1, NUCLED_WMI_METHODID_SETSTATE,
185 if (ACPI_FAILURE(status))
188 // Always returns a buffer
189 obj = (union acpi_object *)output.pointer;
192 retval->brightness_return = obj->buffer.pointer[0];
193 retval->blink_fade_return = obj->buffer.pointer[1];
194 retval->color_return = obj->buffer.pointer[2];
202 static ssize_t acpi_proc_write(struct file *filp, const char __user *buff,
203 size_t len, loff_t *data)
208 static int status = 0;
209 struct led_set_state_return retval;
210 u32 led, brightness, blink_fade, color_state;
212 // Move buffer from user space to kernel space
213 input = vmalloc(len);
217 if (copy_from_user(input, buff, len))
222 if (input[len - 1] == '\n')
223 input[len - 1] = '\0';
225 // Parse input string
226 while ((arg = strsep(&input, ",")) && *arg)
228 if (i == 0) // First arg: LED ("power" or "ring")
230 if (!strcmp(arg, "power"))
231 led = NUCLED_WMI_POWER_LED_ID;
232 else if (!strcmp(arg, "ring"))
233 led = NUCLED_WMI_RING_LED_ID;
237 else if (i == 1) // Second arg: brightness (0 - 100)
241 if (kstrtol(arg, 0, &val))
247 if (val < 0 || val > 100)
253 else if (i == 2) // Third arg: fade/brightness (text values)
255 if (!strcmp(arg, "none"))
256 blink_fade = NUCLED_WMI_ALWAYS_ON;
257 else if (!strcmp(arg, "blink_fast"))
258 blink_fade = NUCLED_WMI_BLINK_1HZ;
259 else if (!strcmp(arg, "blink_medium"))
260 blink_fade = NUCLED_WMI_BLINK_0_5HZ;
261 else if (!strcmp(arg, "blink_slow"))
262 blink_fade = NUCLED_WMI_BLINK_0_25HZ;
263 else if (!strcmp(arg, "fade_fast"))
264 blink_fade = NUCLED_WMI_FADE_1HZ;
265 else if (!strcmp(arg, "fade_medium"))
266 blink_fade = NUCLED_WMI_FADE_0_5HZ;
267 else if (!strcmp(arg, "fade_slow"))
268 blink_fade = NUCLED_WMI_FADE_0_25HZ;
272 else if (i == 3) // Fourth arg: color (text values)
274 if (led == NUCLED_WMI_POWER_LED_ID)
276 if (!strcmp(arg, "off"))
277 color_state = NUCLED_WMI_POWER_COLOR_DISABLE;
278 else if (!strcmp(arg, "blue"))
279 color_state = NUCLED_WMI_POWER_COLOR_BLUE;
280 else if (!strcmp(arg, "amber"))
281 color_state = NUCLED_WMI_POWER_COLOR_AMBER;
285 else if (led == NUCLED_WMI_RING_LED_ID)
287 if (!strcmp(arg, "off"))
288 color_state = NUCLED_WMI_RING_COLOR_DISABLE;
289 else if (!strcmp(arg, "cyan"))
290 color_state = NUCLED_WMI_RING_COLOR_CYAN;
291 else if (!strcmp(arg, "pink"))
292 color_state = NUCLED_WMI_RING_COLOR_PINK;
293 else if (!strcmp(arg, "yellow"))
294 color_state = NUCLED_WMI_RING_COLOR_YELLOW;
295 else if (!strcmp(arg, "blue"))
296 color_state = NUCLED_WMI_RING_COLOR_BLUE;
297 else if (!strcmp(arg, "red"))
298 color_state = NUCLED_WMI_RING_COLOR_RED;
299 else if (!strcmp(arg, "green"))
300 color_state = NUCLED_WMI_RING_COLOR_GREEN;
301 else if (!strcmp(arg, "white"))
302 color_state = NUCLED_WMI_RING_COLOR_WHITE;
307 else // Too many args!
316 if (ret == -EOVERFLOW)
318 pr_warn("Too many arguments while setting NUC LED state\n");
322 pr_warn("Too few arguments while setting NUC LED state\n");
324 else if (ret == -EINVAL)
326 pr_warn("Invalid argument while setting NUC LED state\n");
330 status = nuc_led_set_state(led, brightness, blink_fade, color_state, &retval);
333 pr_warn("Unable to set NUC LED state: WMI call failed\n");
337 if (retval.brightness_return == NUCLED_WMI_RETURN_UNDEFINED)
339 if (led == NUCLED_WMI_POWER_LED_ID)
340 pr_warn("Unable set NUC power LED state: not set for SW control\n");
341 else if (led == NUCLED_WMI_RING_LED_ID)
342 pr_warn("Unable set NUC ring LED state: not set for SW control\n");
344 else if (retval.brightness_return == NUCLED_WMI_RETURN_BADPARAM || retval.blink_fade_return == NUCLED_WMI_RETURN_BADPARAM ||
345 retval.color_return == NUCLED_WMI_RETURN_BADPARAM)
347 pr_warn("Unable to set NUC LED state: invalid parameter\n");
349 else if (retval.brightness_return != NUCLED_WMI_RETURN_SUCCESS)
351 pr_warn("Unable to set NUC LED state: WMI call returned error\n");
359 static ssize_t acpi_proc_read(struct file *filp, char __user *buff,
360 size_t count, loff_t *off)
363 static int status_pwr = 0;
364 static int status_ring = 0;
365 struct led_get_state_return power_led;
366 struct led_get_state_return ring_led;
369 // Get statuses from WMI interface
370 status_pwr = nuc_led_get_state(NUCLED_WMI_POWER_LED_ID, &power_led);
372 pr_warn("Unable to get NUC power LED state\n");
374 status_ring = nuc_led_get_state(NUCLED_WMI_RING_LED_ID, &ring_led);
376 pr_warn("Unable to get NUC ring LED state\n");
379 memset(result_buffer, 0, BUFFER_SIZE);
381 // Process state for power LED
384 sprintf(get_buffer_end(), "Power LED state could not be determined: WMI call failed\n\n");
388 if (power_led.return_code == NUCLED_WMI_RETURN_SUCCESS)
389 sprintf(get_buffer_end(), "Power LED Brightness: %d%%\nPower LED Blink/Fade: %s (0x%02x)\nPower LED Color: %s (0x%02x)\n\n",
390 power_led.brightness,
391 blink_fade_text[power_led.blink_fade], power_led.blink_fade,
392 pwrcolor_text[power_led.color_state], power_led.color_state);
393 else if (power_led.return_code == NUCLED_WMI_RETURN_UNDEFINED)
394 sprintf(get_buffer_end(), "Power LED not set for software control\n\n");
396 sprintf(get_buffer_end(), "Power LED state could not be determined: WMI call returned error\n\n");
399 // Process state for ring LED
402 sprintf(get_buffer_end(), "Ring LED state could not be determined: WMI call failed\n\n");
406 if (ring_led.return_code == NUCLED_WMI_RETURN_SUCCESS)
407 sprintf(get_buffer_end(), "Ring LED Brightness: %d%%\nRing LED Blink/Fade: %s (0x%02x)\nRing LED Color: %s (0x%02x)\n\n",
409 blink_fade_text[ring_led.blink_fade], ring_led.blink_fade,
410 ringcolor_text[ring_led.color_state], ring_led.color_state);
411 else if (power_led.return_code == NUCLED_WMI_RETURN_UNDEFINED)
412 sprintf(get_buffer_end(), "Ring LED not set for software control\n\n");
414 sprintf(get_buffer_end(), "Ring LED state could not be determined: WMI call returned error\n\n");
417 // Return buffer via proc
418 len = strlen(result_buffer);
419 ret = simple_read_from_buffer(buff, count, off, result_buffer, len + 1);
424 static struct file_operations proc_acpi_operations = {
425 .owner = THIS_MODULE,
426 .read = acpi_proc_read,
427 .write = acpi_proc_write,
431 static int __init init_nuc_led(void)
433 struct proc_dir_entry *acpi_entry;
435 // Make sure LED control WMI GUID exists
436 if (!wmi_has_guid(NUCLED_WMI_MGMT_GUID)) {
437 pr_warn("Intel NUC LED WMI GUID not found\n");
441 // Create nuc_led ACPI proc entry
442 acpi_entry = proc_create("nuc_led", 0664, acpi_root_dir, &proc_acpi_operations);
444 if (acpi_entry == NULL) {
445 pr_warn("Intel NUC LED control driver could not create proc entry\n");
449 pr_info("Intel NUC LED control driver loaded\n");
454 static void __exit unload_nuc_led(void)
456 remove_proc_entry("nuc_led", acpi_root_dir);
457 pr_info("Intel NUC LED control driver unloaded\n");
460 module_init(init_nuc_led);
461 module_exit(unload_nuc_led);