2 * Alienware AlienFX control
4 * Copyright (C) 2014 Dell Inc <mario_limonciello@dell.com>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
18 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
20 #include <linux/acpi.h>
21 #include <linux/module.h>
22 #include <linux/platform_device.h>
23 #include <linux/dmi.h>
24 #include <linux/leds.h>
26 #define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492"
27 #define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
28 #define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492"
30 #define WMAX_METHOD_HDMI_SOURCE 0x1
31 #define WMAX_METHOD_HDMI_STATUS 0x2
32 #define WMAX_METHOD_BRIGHTNESS 0x3
33 #define WMAX_METHOD_ZONE_CONTROL 0x4
34 #define WMAX_METHOD_HDMI_CABLE 0x5
35 #define WMAX_METHOD_AMPLIFIER_CABLE 0x6
36 #define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B
37 #define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C
39 MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>");
40 MODULE_DESCRIPTION("Alienware special feature control");
41 MODULE_LICENSE("GPL");
42 MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID
);
43 MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID
);
45 enum INTERFACE_FLAGS
{
50 enum LEGACY_CONTROL_STATES
{
56 enum WMAX_CONTROL_STATES
{
69 static struct quirk_entry
*quirks
;
72 static struct quirk_entry quirk_inspiron5675
= {
79 static struct quirk_entry quirk_unknown
= {
86 static struct quirk_entry quirk_x51_r1_r2
= {
93 static struct quirk_entry quirk_x51_r3
= {
100 static struct quirk_entry quirk_asm100
= {
107 static struct quirk_entry quirk_asm200
= {
114 static struct quirk_entry quirk_asm201
= {
121 static int __init
dmi_matched(const struct dmi_system_id
*dmi
)
123 quirks
= dmi
->driver_data
;
127 static const struct dmi_system_id alienware_quirks
[] __initconst
= {
129 .callback
= dmi_matched
,
130 .ident
= "Alienware X51 R3",
132 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
133 DMI_MATCH(DMI_PRODUCT_NAME
, "Alienware X51 R3"),
135 .driver_data
= &quirk_x51_r3
,
138 .callback
= dmi_matched
,
139 .ident
= "Alienware X51 R2",
141 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
142 DMI_MATCH(DMI_PRODUCT_NAME
, "Alienware X51 R2"),
144 .driver_data
= &quirk_x51_r1_r2
,
147 .callback
= dmi_matched
,
148 .ident
= "Alienware X51 R1",
150 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
151 DMI_MATCH(DMI_PRODUCT_NAME
, "Alienware X51"),
153 .driver_data
= &quirk_x51_r1_r2
,
156 .callback
= dmi_matched
,
157 .ident
= "Alienware ASM100",
159 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
160 DMI_MATCH(DMI_PRODUCT_NAME
, "ASM100"),
162 .driver_data
= &quirk_asm100
,
165 .callback
= dmi_matched
,
166 .ident
= "Alienware ASM200",
168 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
169 DMI_MATCH(DMI_PRODUCT_NAME
, "ASM200"),
171 .driver_data
= &quirk_asm200
,
174 .callback
= dmi_matched
,
175 .ident
= "Alienware ASM201",
177 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
178 DMI_MATCH(DMI_PRODUCT_NAME
, "ASM201"),
180 .driver_data
= &quirk_asm201
,
183 .callback
= dmi_matched
,
184 .ident
= "Dell Inc. Inspiron 5675",
186 DMI_MATCH(DMI_SYS_VENDOR
, "Dell Inc."),
187 DMI_MATCH(DMI_PRODUCT_NAME
, "Inspiron 5675"),
189 .driver_data
= &quirk_inspiron5675
,
194 struct color_platform
{
200 struct platform_zone
{
202 struct device_attribute
*attr
;
203 struct color_platform colors
;
206 struct wmax_brightness_args
{
211 struct wmax_basic_args
{
215 struct legacy_led_args
{
216 struct color_platform colors
;
221 struct wmax_led_args
{
223 struct color_platform colors
;
227 static struct platform_device
*platform_device
;
228 static struct device_attribute
*zone_dev_attrs
;
229 static struct attribute
**zone_attrs
;
230 static struct platform_zone
*zone_data
;
232 static struct platform_driver platform_driver
= {
234 .name
= "alienware-wmi",
238 static struct attribute_group zone_attribute_group
= {
243 static u8 lighting_control_state
;
244 static u8 global_brightness
;
247 * Helpers used for zone control
249 static int parse_rgb(const char *buf
, struct platform_zone
*zone
)
251 long unsigned int rgb
;
254 struct color_platform cp
;
258 ret
= kstrtoul(buf
, 16, &rgb
);
262 /* RGB triplet notation is 24-bit hexadecimal */
266 repackager
.package
= rgb
& 0x0f0f0f0f;
267 pr_debug("alienware-wmi: r: %d g:%d b: %d\n",
268 repackager
.cp
.red
, repackager
.cp
.green
, repackager
.cp
.blue
);
269 zone
->colors
= repackager
.cp
;
273 static struct platform_zone
*match_zone(struct device_attribute
*attr
)
277 for (zone
= 0; zone
< quirks
->num_zones
; zone
++) {
278 if ((struct device_attribute
*)zone_data
[zone
].attr
== attr
) {
279 pr_debug("alienware-wmi: matched zone location: %d\n",
280 zone_data
[zone
].location
);
281 return &zone_data
[zone
];
288 * Individual RGB zone control
290 static int alienware_update_led(struct platform_zone
*zone
)
295 struct acpi_buffer input
;
296 struct legacy_led_args legacy_args
;
297 struct wmax_led_args wmax_basic_args
;
298 if (interface
== WMAX
) {
299 wmax_basic_args
.led_mask
= 1 << zone
->location
;
300 wmax_basic_args
.colors
= zone
->colors
;
301 wmax_basic_args
.state
= lighting_control_state
;
302 guid
= WMAX_CONTROL_GUID
;
303 method_id
= WMAX_METHOD_ZONE_CONTROL
;
305 input
.length
= (acpi_size
) sizeof(wmax_basic_args
);
306 input
.pointer
= &wmax_basic_args
;
308 legacy_args
.colors
= zone
->colors
;
309 legacy_args
.brightness
= global_brightness
;
310 legacy_args
.state
= 0;
311 if (lighting_control_state
== LEGACY_BOOTING
||
312 lighting_control_state
== LEGACY_SUSPEND
) {
313 guid
= LEGACY_POWER_CONTROL_GUID
;
314 legacy_args
.state
= lighting_control_state
;
316 guid
= LEGACY_CONTROL_GUID
;
317 method_id
= zone
->location
+ 1;
319 input
.length
= (acpi_size
) sizeof(legacy_args
);
320 input
.pointer
= &legacy_args
;
322 pr_debug("alienware-wmi: guid %s method %d\n", guid
, method_id
);
324 status
= wmi_evaluate_method(guid
, 0, method_id
, &input
, NULL
);
325 if (ACPI_FAILURE(status
))
326 pr_err("alienware-wmi: zone set failure: %u\n", status
);
327 return ACPI_FAILURE(status
);
330 static ssize_t
zone_show(struct device
*dev
, struct device_attribute
*attr
,
333 struct platform_zone
*target_zone
;
334 target_zone
= match_zone(attr
);
335 if (target_zone
== NULL
)
336 return sprintf(buf
, "red: -1, green: -1, blue: -1\n");
337 return sprintf(buf
, "red: %d, green: %d, blue: %d\n",
338 target_zone
->colors
.red
,
339 target_zone
->colors
.green
, target_zone
->colors
.blue
);
343 static ssize_t
zone_set(struct device
*dev
, struct device_attribute
*attr
,
344 const char *buf
, size_t count
)
346 struct platform_zone
*target_zone
;
348 target_zone
= match_zone(attr
);
349 if (target_zone
== NULL
) {
350 pr_err("alienware-wmi: invalid target zone\n");
353 ret
= parse_rgb(buf
, target_zone
);
356 ret
= alienware_update_led(target_zone
);
357 return ret
? ret
: count
;
361 * LED Brightness (Global)
363 static int wmax_brightness(int brightness
)
366 struct acpi_buffer input
;
367 struct wmax_brightness_args args
= {
369 .percentage
= brightness
,
371 input
.length
= (acpi_size
) sizeof(args
);
372 input
.pointer
= &args
;
373 status
= wmi_evaluate_method(WMAX_CONTROL_GUID
, 0,
374 WMAX_METHOD_BRIGHTNESS
, &input
, NULL
);
375 if (ACPI_FAILURE(status
))
376 pr_err("alienware-wmi: brightness set failure: %u\n", status
);
377 return ACPI_FAILURE(status
);
380 static void global_led_set(struct led_classdev
*led_cdev
,
381 enum led_brightness brightness
)
384 global_brightness
= brightness
;
385 if (interface
== WMAX
)
386 ret
= wmax_brightness(brightness
);
388 ret
= alienware_update_led(&zone_data
[0]);
390 pr_err("LED brightness update failed\n");
393 static enum led_brightness
global_led_get(struct led_classdev
*led_cdev
)
395 return global_brightness
;
398 static struct led_classdev global_led
= {
399 .brightness_set
= global_led_set
,
400 .brightness_get
= global_led_get
,
401 .name
= "alienware::global_brightness",
405 * Lighting control state device attribute (Global)
407 static ssize_t
show_control_state(struct device
*dev
,
408 struct device_attribute
*attr
, char *buf
)
410 if (lighting_control_state
== LEGACY_BOOTING
)
411 return scnprintf(buf
, PAGE_SIZE
, "[booting] running suspend\n");
412 else if (lighting_control_state
== LEGACY_SUSPEND
)
413 return scnprintf(buf
, PAGE_SIZE
, "booting running [suspend]\n");
414 return scnprintf(buf
, PAGE_SIZE
, "booting [running] suspend\n");
417 static ssize_t
store_control_state(struct device
*dev
,
418 struct device_attribute
*attr
,
419 const char *buf
, size_t count
)
421 long unsigned int val
;
422 if (strcmp(buf
, "booting\n") == 0)
423 val
= LEGACY_BOOTING
;
424 else if (strcmp(buf
, "suspend\n") == 0)
425 val
= LEGACY_SUSPEND
;
426 else if (interface
== LEGACY
)
427 val
= LEGACY_RUNNING
;
430 lighting_control_state
= val
;
431 pr_debug("alienware-wmi: updated control state to %d\n",
432 lighting_control_state
);
436 static DEVICE_ATTR(lighting_control_state
, 0644, show_control_state
,
437 store_control_state
);
439 static int alienware_zone_init(struct platform_device
*dev
)
445 if (interface
== WMAX
) {
446 lighting_control_state
= WMAX_RUNNING
;
447 } else if (interface
== LEGACY
) {
448 lighting_control_state
= LEGACY_RUNNING
;
450 global_led
.max_brightness
= 0x0F;
451 global_brightness
= global_led
.max_brightness
;
454 * - zone_dev_attrs num_zones + 1 is for individual zones and then
456 * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs +
457 * the lighting control + null terminated
458 * - zone_data num_zones is for the distinct zones
461 kcalloc(quirks
->num_zones
+ 1, sizeof(struct device_attribute
),
467 kcalloc(quirks
->num_zones
+ 2, sizeof(struct attribute
*),
473 kcalloc(quirks
->num_zones
, sizeof(struct platform_zone
),
478 for (zone
= 0; zone
< quirks
->num_zones
; zone
++) {
479 sprintf(buffer
, "zone%02hhX", zone
);
480 name
= kstrdup(buffer
, GFP_KERNEL
);
483 sysfs_attr_init(&zone_dev_attrs
[zone
].attr
);
484 zone_dev_attrs
[zone
].attr
.name
= name
;
485 zone_dev_attrs
[zone
].attr
.mode
= 0644;
486 zone_dev_attrs
[zone
].show
= zone_show
;
487 zone_dev_attrs
[zone
].store
= zone_set
;
488 zone_data
[zone
].location
= zone
;
489 zone_attrs
[zone
] = &zone_dev_attrs
[zone
].attr
;
490 zone_data
[zone
].attr
= &zone_dev_attrs
[zone
];
492 zone_attrs
[quirks
->num_zones
] = &dev_attr_lighting_control_state
.attr
;
493 zone_attribute_group
.attrs
= zone_attrs
;
495 led_classdev_register(&dev
->dev
, &global_led
);
497 return sysfs_create_group(&dev
->dev
.kobj
, &zone_attribute_group
);
500 static void alienware_zone_exit(struct platform_device
*dev
)
504 sysfs_remove_group(&dev
->dev
.kobj
, &zone_attribute_group
);
505 led_classdev_unregister(&global_led
);
506 if (zone_dev_attrs
) {
507 for (zone
= 0; zone
< quirks
->num_zones
; zone
++)
508 kfree(zone_dev_attrs
[zone
].attr
.name
);
510 kfree(zone_dev_attrs
);
515 static acpi_status
alienware_wmax_command(struct wmax_basic_args
*in_args
,
516 u32 command
, int *out_data
)
519 union acpi_object
*obj
;
520 struct acpi_buffer input
;
521 struct acpi_buffer output
;
523 input
.length
= (acpi_size
) sizeof(*in_args
);
524 input
.pointer
= in_args
;
525 if (out_data
!= NULL
) {
526 output
.length
= ACPI_ALLOCATE_BUFFER
;
527 output
.pointer
= NULL
;
528 status
= wmi_evaluate_method(WMAX_CONTROL_GUID
, 0,
529 command
, &input
, &output
);
531 status
= wmi_evaluate_method(WMAX_CONTROL_GUID
, 0,
532 command
, &input
, NULL
);
534 if (ACPI_SUCCESS(status
) && out_data
!= NULL
) {
535 obj
= (union acpi_object
*)output
.pointer
;
536 if (obj
&& obj
->type
== ACPI_TYPE_INTEGER
)
537 *out_data
= (u32
) obj
->integer
.value
;
539 kfree(output
.pointer
);
545 * The HDMI mux sysfs node indicates the status of the HDMI input mux.
546 * It can toggle between standard system GPU output and HDMI input.
548 static ssize_t
show_hdmi_cable(struct device
*dev
,
549 struct device_attribute
*attr
, char *buf
)
553 struct wmax_basic_args in_args
= {
557 alienware_wmax_command(&in_args
, WMAX_METHOD_HDMI_CABLE
,
559 if (ACPI_SUCCESS(status
)) {
561 return scnprintf(buf
, PAGE_SIZE
,
562 "[unconnected] connected unknown\n");
563 else if (out_data
== 1)
564 return scnprintf(buf
, PAGE_SIZE
,
565 "unconnected [connected] unknown\n");
567 pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status
);
568 return scnprintf(buf
, PAGE_SIZE
, "unconnected connected [unknown]\n");
571 static ssize_t
show_hdmi_source(struct device
*dev
,
572 struct device_attribute
*attr
, char *buf
)
576 struct wmax_basic_args in_args
= {
580 alienware_wmax_command(&in_args
, WMAX_METHOD_HDMI_STATUS
,
583 if (ACPI_SUCCESS(status
)) {
585 return scnprintf(buf
, PAGE_SIZE
,
586 "[input] gpu unknown\n");
587 else if (out_data
== 2)
588 return scnprintf(buf
, PAGE_SIZE
,
589 "input [gpu] unknown\n");
591 pr_err("alienware-wmi: unknown HDMI source status: %d\n", out_data
);
592 return scnprintf(buf
, PAGE_SIZE
, "input gpu [unknown]\n");
595 static ssize_t
toggle_hdmi_source(struct device
*dev
,
596 struct device_attribute
*attr
,
597 const char *buf
, size_t count
)
600 struct wmax_basic_args args
;
601 if (strcmp(buf
, "gpu\n") == 0)
603 else if (strcmp(buf
, "input\n") == 0)
607 pr_debug("alienware-wmi: setting hdmi to %d : %s", args
.arg
, buf
);
609 status
= alienware_wmax_command(&args
, WMAX_METHOD_HDMI_SOURCE
, NULL
);
611 if (ACPI_FAILURE(status
))
612 pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
617 static DEVICE_ATTR(cable
, S_IRUGO
, show_hdmi_cable
, NULL
);
618 static DEVICE_ATTR(source
, S_IRUGO
| S_IWUSR
, show_hdmi_source
,
621 static struct attribute
*hdmi_attrs
[] = {
622 &dev_attr_cable
.attr
,
623 &dev_attr_source
.attr
,
627 static const struct attribute_group hdmi_attribute_group
= {
632 static void remove_hdmi(struct platform_device
*dev
)
634 if (quirks
->hdmi_mux
> 0)
635 sysfs_remove_group(&dev
->dev
.kobj
, &hdmi_attribute_group
);
638 static int create_hdmi(struct platform_device
*dev
)
642 ret
= sysfs_create_group(&dev
->dev
.kobj
, &hdmi_attribute_group
);
649 * Alienware GFX amplifier support
650 * - Currently supports reading cable status
651 * - Leaving expansion room to possibly support dock/undock events later
653 static ssize_t
show_amplifier_status(struct device
*dev
,
654 struct device_attribute
*attr
, char *buf
)
658 struct wmax_basic_args in_args
= {
662 alienware_wmax_command(&in_args
, WMAX_METHOD_AMPLIFIER_CABLE
,
664 if (ACPI_SUCCESS(status
)) {
666 return scnprintf(buf
, PAGE_SIZE
,
667 "[unconnected] connected unknown\n");
668 else if (out_data
== 1)
669 return scnprintf(buf
, PAGE_SIZE
,
670 "unconnected [connected] unknown\n");
672 pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status
);
673 return scnprintf(buf
, PAGE_SIZE
, "unconnected connected [unknown]\n");
676 static DEVICE_ATTR(status
, S_IRUGO
, show_amplifier_status
, NULL
);
678 static struct attribute
*amplifier_attrs
[] = {
679 &dev_attr_status
.attr
,
683 static const struct attribute_group amplifier_attribute_group
= {
685 .attrs
= amplifier_attrs
,
688 static void remove_amplifier(struct platform_device
*dev
)
690 if (quirks
->amplifier
> 0)
691 sysfs_remove_group(&dev
->dev
.kobj
, &lifier_attribute_group
);
694 static int create_amplifier(struct platform_device
*dev
)
698 ret
= sysfs_create_group(&dev
->dev
.kobj
, &lifier_attribute_group
);
700 remove_amplifier(dev
);
705 * Deep Sleep Control support
706 * - Modifies BIOS setting for deep sleep control allowing extra wakeup events
708 static ssize_t
show_deepsleep_status(struct device
*dev
,
709 struct device_attribute
*attr
, char *buf
)
713 struct wmax_basic_args in_args
= {
716 status
= alienware_wmax_command(&in_args
, WMAX_METHOD_DEEP_SLEEP_STATUS
,
718 if (ACPI_SUCCESS(status
)) {
720 return scnprintf(buf
, PAGE_SIZE
,
721 "[disabled] s5 s5_s4\n");
722 else if (out_data
== 1)
723 return scnprintf(buf
, PAGE_SIZE
,
724 "disabled [s5] s5_s4\n");
725 else if (out_data
== 2)
726 return scnprintf(buf
, PAGE_SIZE
,
727 "disabled s5 [s5_s4]\n");
729 pr_err("alienware-wmi: unknown deep sleep status: %d\n", status
);
730 return scnprintf(buf
, PAGE_SIZE
, "disabled s5 s5_s4 [unknown]\n");
733 static ssize_t
toggle_deepsleep(struct device
*dev
,
734 struct device_attribute
*attr
,
735 const char *buf
, size_t count
)
738 struct wmax_basic_args args
;
740 if (strcmp(buf
, "disabled\n") == 0)
742 else if (strcmp(buf
, "s5\n") == 0)
746 pr_debug("alienware-wmi: setting deep sleep to %d : %s", args
.arg
, buf
);
748 status
= alienware_wmax_command(&args
, WMAX_METHOD_DEEP_SLEEP_CONTROL
,
751 if (ACPI_FAILURE(status
))
752 pr_err("alienware-wmi: deep sleep control failed: results: %u\n",
757 static DEVICE_ATTR(deepsleep
, S_IRUGO
| S_IWUSR
, show_deepsleep_status
, toggle_deepsleep
);
759 static struct attribute
*deepsleep_attrs
[] = {
760 &dev_attr_deepsleep
.attr
,
764 static const struct attribute_group deepsleep_attribute_group
= {
766 .attrs
= deepsleep_attrs
,
769 static void remove_deepsleep(struct platform_device
*dev
)
771 if (quirks
->deepslp
> 0)
772 sysfs_remove_group(&dev
->dev
.kobj
, &deepsleep_attribute_group
);
775 static int create_deepsleep(struct platform_device
*dev
)
779 ret
= sysfs_create_group(&dev
->dev
.kobj
, &deepsleep_attribute_group
);
781 remove_deepsleep(dev
);
785 static int __init
alienware_wmi_init(void)
789 if (wmi_has_guid(LEGACY_CONTROL_GUID
))
791 else if (wmi_has_guid(WMAX_CONTROL_GUID
))
794 pr_warn("alienware-wmi: No known WMI GUID found\n");
798 dmi_check_system(alienware_quirks
);
800 quirks
= &quirk_unknown
;
802 ret
= platform_driver_register(&platform_driver
);
804 goto fail_platform_driver
;
805 platform_device
= platform_device_alloc("alienware-wmi", -1);
806 if (!platform_device
) {
808 goto fail_platform_device1
;
810 ret
= platform_device_add(platform_device
);
812 goto fail_platform_device2
;
814 if (quirks
->hdmi_mux
> 0) {
815 ret
= create_hdmi(platform_device
);
820 if (quirks
->amplifier
> 0) {
821 ret
= create_amplifier(platform_device
);
823 goto fail_prep_amplifier
;
826 if (quirks
->deepslp
> 0) {
827 ret
= create_deepsleep(platform_device
);
829 goto fail_prep_deepsleep
;
832 ret
= alienware_zone_init(platform_device
);
834 goto fail_prep_zones
;
839 alienware_zone_exit(platform_device
);
843 platform_device_del(platform_device
);
844 fail_platform_device2
:
845 platform_device_put(platform_device
);
846 fail_platform_device1
:
847 platform_driver_unregister(&platform_driver
);
848 fail_platform_driver
:
852 module_init(alienware_wmi_init
);
854 static void __exit
alienware_wmi_exit(void)
856 if (platform_device
) {
857 alienware_zone_exit(platform_device
);
858 remove_hdmi(platform_device
);
859 platform_device_unregister(platform_device
);
860 platform_driver_unregister(&platform_driver
);
864 module_exit(alienware_wmi_exit
);