1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Alienware AlienFX control
5 * Copyright (C) 2014 Dell Inc <mario_limonciello@dell.com>
8 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
10 #include <linux/acpi.h>
11 #include <linux/module.h>
12 #include <linux/platform_device.h>
13 #include <linux/dmi.h>
14 #include <linux/leds.h>
16 #define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492"
17 #define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
18 #define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492"
20 #define WMAX_METHOD_HDMI_SOURCE 0x1
21 #define WMAX_METHOD_HDMI_STATUS 0x2
22 #define WMAX_METHOD_BRIGHTNESS 0x3
23 #define WMAX_METHOD_ZONE_CONTROL 0x4
24 #define WMAX_METHOD_HDMI_CABLE 0x5
25 #define WMAX_METHOD_AMPLIFIER_CABLE 0x6
26 #define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B
27 #define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C
29 MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>");
30 MODULE_DESCRIPTION("Alienware special feature control");
31 MODULE_LICENSE("GPL");
32 MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID
);
33 MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID
);
35 enum INTERFACE_FLAGS
{
40 enum LEGACY_CONTROL_STATES
{
46 enum WMAX_CONTROL_STATES
{
59 static struct quirk_entry
*quirks
;
62 static struct quirk_entry quirk_inspiron5675
= {
69 static struct quirk_entry quirk_unknown
= {
76 static struct quirk_entry quirk_x51_r1_r2
= {
83 static struct quirk_entry quirk_x51_r3
= {
90 static struct quirk_entry quirk_asm100
= {
97 static struct quirk_entry quirk_asm200
= {
104 static struct quirk_entry quirk_asm201
= {
111 static int __init
dmi_matched(const struct dmi_system_id
*dmi
)
113 quirks
= dmi
->driver_data
;
117 static const struct dmi_system_id alienware_quirks
[] __initconst
= {
119 .callback
= dmi_matched
,
120 .ident
= "Alienware X51 R3",
122 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
123 DMI_MATCH(DMI_PRODUCT_NAME
, "Alienware X51 R3"),
125 .driver_data
= &quirk_x51_r3
,
128 .callback
= dmi_matched
,
129 .ident
= "Alienware X51 R2",
131 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
132 DMI_MATCH(DMI_PRODUCT_NAME
, "Alienware X51 R2"),
134 .driver_data
= &quirk_x51_r1_r2
,
137 .callback
= dmi_matched
,
138 .ident
= "Alienware X51 R1",
140 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
141 DMI_MATCH(DMI_PRODUCT_NAME
, "Alienware X51"),
143 .driver_data
= &quirk_x51_r1_r2
,
146 .callback
= dmi_matched
,
147 .ident
= "Alienware ASM100",
149 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
150 DMI_MATCH(DMI_PRODUCT_NAME
, "ASM100"),
152 .driver_data
= &quirk_asm100
,
155 .callback
= dmi_matched
,
156 .ident
= "Alienware ASM200",
158 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
159 DMI_MATCH(DMI_PRODUCT_NAME
, "ASM200"),
161 .driver_data
= &quirk_asm200
,
164 .callback
= dmi_matched
,
165 .ident
= "Alienware ASM201",
167 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
168 DMI_MATCH(DMI_PRODUCT_NAME
, "ASM201"),
170 .driver_data
= &quirk_asm201
,
173 .callback
= dmi_matched
,
174 .ident
= "Dell Inc. Inspiron 5675",
176 DMI_MATCH(DMI_SYS_VENDOR
, "Dell Inc."),
177 DMI_MATCH(DMI_PRODUCT_NAME
, "Inspiron 5675"),
179 .driver_data
= &quirk_inspiron5675
,
184 struct color_platform
{
190 struct platform_zone
{
192 struct device_attribute
*attr
;
193 struct color_platform colors
;
196 struct wmax_brightness_args
{
201 struct wmax_basic_args
{
205 struct legacy_led_args
{
206 struct color_platform colors
;
211 struct wmax_led_args
{
213 struct color_platform colors
;
217 static struct platform_device
*platform_device
;
218 static struct device_attribute
*zone_dev_attrs
;
219 static struct attribute
**zone_attrs
;
220 static struct platform_zone
*zone_data
;
222 static struct platform_driver platform_driver
= {
224 .name
= "alienware-wmi",
228 static struct attribute_group zone_attribute_group
= {
233 static u8 lighting_control_state
;
234 static u8 global_brightness
;
237 * Helpers used for zone control
239 static int parse_rgb(const char *buf
, struct platform_zone
*zone
)
241 long unsigned int rgb
;
244 struct color_platform cp
;
248 ret
= kstrtoul(buf
, 16, &rgb
);
252 /* RGB triplet notation is 24-bit hexadecimal */
256 repackager
.package
= rgb
& 0x0f0f0f0f;
257 pr_debug("alienware-wmi: r: %d g:%d b: %d\n",
258 repackager
.cp
.red
, repackager
.cp
.green
, repackager
.cp
.blue
);
259 zone
->colors
= repackager
.cp
;
263 static struct platform_zone
*match_zone(struct device_attribute
*attr
)
267 for (zone
= 0; zone
< quirks
->num_zones
; zone
++) {
268 if ((struct device_attribute
*)zone_data
[zone
].attr
== attr
) {
269 pr_debug("alienware-wmi: matched zone location: %d\n",
270 zone_data
[zone
].location
);
271 return &zone_data
[zone
];
278 * Individual RGB zone control
280 static int alienware_update_led(struct platform_zone
*zone
)
285 struct acpi_buffer input
;
286 struct legacy_led_args legacy_args
;
287 struct wmax_led_args wmax_basic_args
;
288 if (interface
== WMAX
) {
289 wmax_basic_args
.led_mask
= 1 << zone
->location
;
290 wmax_basic_args
.colors
= zone
->colors
;
291 wmax_basic_args
.state
= lighting_control_state
;
292 guid
= WMAX_CONTROL_GUID
;
293 method_id
= WMAX_METHOD_ZONE_CONTROL
;
295 input
.length
= (acpi_size
) sizeof(wmax_basic_args
);
296 input
.pointer
= &wmax_basic_args
;
298 legacy_args
.colors
= zone
->colors
;
299 legacy_args
.brightness
= global_brightness
;
300 legacy_args
.state
= 0;
301 if (lighting_control_state
== LEGACY_BOOTING
||
302 lighting_control_state
== LEGACY_SUSPEND
) {
303 guid
= LEGACY_POWER_CONTROL_GUID
;
304 legacy_args
.state
= lighting_control_state
;
306 guid
= LEGACY_CONTROL_GUID
;
307 method_id
= zone
->location
+ 1;
309 input
.length
= (acpi_size
) sizeof(legacy_args
);
310 input
.pointer
= &legacy_args
;
312 pr_debug("alienware-wmi: guid %s method %d\n", guid
, method_id
);
314 status
= wmi_evaluate_method(guid
, 0, method_id
, &input
, NULL
);
315 if (ACPI_FAILURE(status
))
316 pr_err("alienware-wmi: zone set failure: %u\n", status
);
317 return ACPI_FAILURE(status
);
320 static ssize_t
zone_show(struct device
*dev
, struct device_attribute
*attr
,
323 struct platform_zone
*target_zone
;
324 target_zone
= match_zone(attr
);
325 if (target_zone
== NULL
)
326 return sprintf(buf
, "red: -1, green: -1, blue: -1\n");
327 return sprintf(buf
, "red: %d, green: %d, blue: %d\n",
328 target_zone
->colors
.red
,
329 target_zone
->colors
.green
, target_zone
->colors
.blue
);
333 static ssize_t
zone_set(struct device
*dev
, struct device_attribute
*attr
,
334 const char *buf
, size_t count
)
336 struct platform_zone
*target_zone
;
338 target_zone
= match_zone(attr
);
339 if (target_zone
== NULL
) {
340 pr_err("alienware-wmi: invalid target zone\n");
343 ret
= parse_rgb(buf
, target_zone
);
346 ret
= alienware_update_led(target_zone
);
347 return ret
? ret
: count
;
351 * LED Brightness (Global)
353 static int wmax_brightness(int brightness
)
356 struct acpi_buffer input
;
357 struct wmax_brightness_args args
= {
359 .percentage
= brightness
,
361 input
.length
= (acpi_size
) sizeof(args
);
362 input
.pointer
= &args
;
363 status
= wmi_evaluate_method(WMAX_CONTROL_GUID
, 0,
364 WMAX_METHOD_BRIGHTNESS
, &input
, NULL
);
365 if (ACPI_FAILURE(status
))
366 pr_err("alienware-wmi: brightness set failure: %u\n", status
);
367 return ACPI_FAILURE(status
);
370 static void global_led_set(struct led_classdev
*led_cdev
,
371 enum led_brightness brightness
)
374 global_brightness
= brightness
;
375 if (interface
== WMAX
)
376 ret
= wmax_brightness(brightness
);
378 ret
= alienware_update_led(&zone_data
[0]);
380 pr_err("LED brightness update failed\n");
383 static enum led_brightness
global_led_get(struct led_classdev
*led_cdev
)
385 return global_brightness
;
388 static struct led_classdev global_led
= {
389 .brightness_set
= global_led_set
,
390 .brightness_get
= global_led_get
,
391 .name
= "alienware::global_brightness",
395 * Lighting control state device attribute (Global)
397 static ssize_t
show_control_state(struct device
*dev
,
398 struct device_attribute
*attr
, char *buf
)
400 if (lighting_control_state
== LEGACY_BOOTING
)
401 return scnprintf(buf
, PAGE_SIZE
, "[booting] running suspend\n");
402 else if (lighting_control_state
== LEGACY_SUSPEND
)
403 return scnprintf(buf
, PAGE_SIZE
, "booting running [suspend]\n");
404 return scnprintf(buf
, PAGE_SIZE
, "booting [running] suspend\n");
407 static ssize_t
store_control_state(struct device
*dev
,
408 struct device_attribute
*attr
,
409 const char *buf
, size_t count
)
411 long unsigned int val
;
412 if (strcmp(buf
, "booting\n") == 0)
413 val
= LEGACY_BOOTING
;
414 else if (strcmp(buf
, "suspend\n") == 0)
415 val
= LEGACY_SUSPEND
;
416 else if (interface
== LEGACY
)
417 val
= LEGACY_RUNNING
;
420 lighting_control_state
= val
;
421 pr_debug("alienware-wmi: updated control state to %d\n",
422 lighting_control_state
);
426 static DEVICE_ATTR(lighting_control_state
, 0644, show_control_state
,
427 store_control_state
);
429 static int alienware_zone_init(struct platform_device
*dev
)
435 if (interface
== WMAX
) {
436 lighting_control_state
= WMAX_RUNNING
;
437 } else if (interface
== LEGACY
) {
438 lighting_control_state
= LEGACY_RUNNING
;
440 global_led
.max_brightness
= 0x0F;
441 global_brightness
= global_led
.max_brightness
;
444 * - zone_dev_attrs num_zones + 1 is for individual zones and then
446 * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs +
447 * the lighting control + null terminated
448 * - zone_data num_zones is for the distinct zones
451 kcalloc(quirks
->num_zones
+ 1, sizeof(struct device_attribute
),
457 kcalloc(quirks
->num_zones
+ 2, sizeof(struct attribute
*),
463 kcalloc(quirks
->num_zones
, sizeof(struct platform_zone
),
468 for (zone
= 0; zone
< quirks
->num_zones
; zone
++) {
469 sprintf(buffer
, "zone%02hhX", zone
);
470 name
= kstrdup(buffer
, GFP_KERNEL
);
473 sysfs_attr_init(&zone_dev_attrs
[zone
].attr
);
474 zone_dev_attrs
[zone
].attr
.name
= name
;
475 zone_dev_attrs
[zone
].attr
.mode
= 0644;
476 zone_dev_attrs
[zone
].show
= zone_show
;
477 zone_dev_attrs
[zone
].store
= zone_set
;
478 zone_data
[zone
].location
= zone
;
479 zone_attrs
[zone
] = &zone_dev_attrs
[zone
].attr
;
480 zone_data
[zone
].attr
= &zone_dev_attrs
[zone
];
482 zone_attrs
[quirks
->num_zones
] = &dev_attr_lighting_control_state
.attr
;
483 zone_attribute_group
.attrs
= zone_attrs
;
485 led_classdev_register(&dev
->dev
, &global_led
);
487 return sysfs_create_group(&dev
->dev
.kobj
, &zone_attribute_group
);
490 static void alienware_zone_exit(struct platform_device
*dev
)
494 sysfs_remove_group(&dev
->dev
.kobj
, &zone_attribute_group
);
495 led_classdev_unregister(&global_led
);
496 if (zone_dev_attrs
) {
497 for (zone
= 0; zone
< quirks
->num_zones
; zone
++)
498 kfree(zone_dev_attrs
[zone
].attr
.name
);
500 kfree(zone_dev_attrs
);
505 static acpi_status
alienware_wmax_command(struct wmax_basic_args
*in_args
,
506 u32 command
, int *out_data
)
509 union acpi_object
*obj
;
510 struct acpi_buffer input
;
511 struct acpi_buffer output
;
513 input
.length
= (acpi_size
) sizeof(*in_args
);
514 input
.pointer
= in_args
;
516 output
.length
= ACPI_ALLOCATE_BUFFER
;
517 output
.pointer
= NULL
;
518 status
= wmi_evaluate_method(WMAX_CONTROL_GUID
, 0,
519 command
, &input
, &output
);
520 if (ACPI_SUCCESS(status
)) {
521 obj
= (union acpi_object
*)output
.pointer
;
522 if (obj
&& obj
->type
== ACPI_TYPE_INTEGER
)
523 *out_data
= (u32
)obj
->integer
.value
;
525 kfree(output
.pointer
);
527 status
= wmi_evaluate_method(WMAX_CONTROL_GUID
, 0,
528 command
, &input
, NULL
);
534 * The HDMI mux sysfs node indicates the status of the HDMI input mux.
535 * It can toggle between standard system GPU output and HDMI input.
537 static ssize_t
show_hdmi_cable(struct device
*dev
,
538 struct device_attribute
*attr
, char *buf
)
542 struct wmax_basic_args in_args
= {
546 alienware_wmax_command(&in_args
, WMAX_METHOD_HDMI_CABLE
,
548 if (ACPI_SUCCESS(status
)) {
550 return scnprintf(buf
, PAGE_SIZE
,
551 "[unconnected] connected unknown\n");
552 else if (out_data
== 1)
553 return scnprintf(buf
, PAGE_SIZE
,
554 "unconnected [connected] unknown\n");
556 pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status
);
557 return scnprintf(buf
, PAGE_SIZE
, "unconnected connected [unknown]\n");
560 static ssize_t
show_hdmi_source(struct device
*dev
,
561 struct device_attribute
*attr
, char *buf
)
565 struct wmax_basic_args in_args
= {
569 alienware_wmax_command(&in_args
, WMAX_METHOD_HDMI_STATUS
,
572 if (ACPI_SUCCESS(status
)) {
574 return scnprintf(buf
, PAGE_SIZE
,
575 "[input] gpu unknown\n");
576 else if (out_data
== 2)
577 return scnprintf(buf
, PAGE_SIZE
,
578 "input [gpu] unknown\n");
580 pr_err("alienware-wmi: unknown HDMI source status: %u\n", status
);
581 return scnprintf(buf
, PAGE_SIZE
, "input gpu [unknown]\n");
584 static ssize_t
toggle_hdmi_source(struct device
*dev
,
585 struct device_attribute
*attr
,
586 const char *buf
, size_t count
)
589 struct wmax_basic_args args
;
590 if (strcmp(buf
, "gpu\n") == 0)
592 else if (strcmp(buf
, "input\n") == 0)
596 pr_debug("alienware-wmi: setting hdmi to %d : %s", args
.arg
, buf
);
598 status
= alienware_wmax_command(&args
, WMAX_METHOD_HDMI_SOURCE
, NULL
);
600 if (ACPI_FAILURE(status
))
601 pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
606 static DEVICE_ATTR(cable
, S_IRUGO
, show_hdmi_cable
, NULL
);
607 static DEVICE_ATTR(source
, S_IRUGO
| S_IWUSR
, show_hdmi_source
,
610 static struct attribute
*hdmi_attrs
[] = {
611 &dev_attr_cable
.attr
,
612 &dev_attr_source
.attr
,
616 static const struct attribute_group hdmi_attribute_group
= {
621 static void remove_hdmi(struct platform_device
*dev
)
623 if (quirks
->hdmi_mux
> 0)
624 sysfs_remove_group(&dev
->dev
.kobj
, &hdmi_attribute_group
);
627 static int create_hdmi(struct platform_device
*dev
)
631 ret
= sysfs_create_group(&dev
->dev
.kobj
, &hdmi_attribute_group
);
638 * Alienware GFX amplifier support
639 * - Currently supports reading cable status
640 * - Leaving expansion room to possibly support dock/undock events later
642 static ssize_t
show_amplifier_status(struct device
*dev
,
643 struct device_attribute
*attr
, char *buf
)
647 struct wmax_basic_args in_args
= {
651 alienware_wmax_command(&in_args
, WMAX_METHOD_AMPLIFIER_CABLE
,
653 if (ACPI_SUCCESS(status
)) {
655 return scnprintf(buf
, PAGE_SIZE
,
656 "[unconnected] connected unknown\n");
657 else if (out_data
== 1)
658 return scnprintf(buf
, PAGE_SIZE
,
659 "unconnected [connected] unknown\n");
661 pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status
);
662 return scnprintf(buf
, PAGE_SIZE
, "unconnected connected [unknown]\n");
665 static DEVICE_ATTR(status
, S_IRUGO
, show_amplifier_status
, NULL
);
667 static struct attribute
*amplifier_attrs
[] = {
668 &dev_attr_status
.attr
,
672 static const struct attribute_group amplifier_attribute_group
= {
674 .attrs
= amplifier_attrs
,
677 static void remove_amplifier(struct platform_device
*dev
)
679 if (quirks
->amplifier
> 0)
680 sysfs_remove_group(&dev
->dev
.kobj
, &lifier_attribute_group
);
683 static int create_amplifier(struct platform_device
*dev
)
687 ret
= sysfs_create_group(&dev
->dev
.kobj
, &lifier_attribute_group
);
689 remove_amplifier(dev
);
694 * Deep Sleep Control support
695 * - Modifies BIOS setting for deep sleep control allowing extra wakeup events
697 static ssize_t
show_deepsleep_status(struct device
*dev
,
698 struct device_attribute
*attr
, char *buf
)
702 struct wmax_basic_args in_args
= {
705 status
= alienware_wmax_command(&in_args
, WMAX_METHOD_DEEP_SLEEP_STATUS
,
707 if (ACPI_SUCCESS(status
)) {
709 return scnprintf(buf
, PAGE_SIZE
,
710 "[disabled] s5 s5_s4\n");
711 else if (out_data
== 1)
712 return scnprintf(buf
, PAGE_SIZE
,
713 "disabled [s5] s5_s4\n");
714 else if (out_data
== 2)
715 return scnprintf(buf
, PAGE_SIZE
,
716 "disabled s5 [s5_s4]\n");
718 pr_err("alienware-wmi: unknown deep sleep status: %d\n", status
);
719 return scnprintf(buf
, PAGE_SIZE
, "disabled s5 s5_s4 [unknown]\n");
722 static ssize_t
toggle_deepsleep(struct device
*dev
,
723 struct device_attribute
*attr
,
724 const char *buf
, size_t count
)
727 struct wmax_basic_args args
;
729 if (strcmp(buf
, "disabled\n") == 0)
731 else if (strcmp(buf
, "s5\n") == 0)
735 pr_debug("alienware-wmi: setting deep sleep to %d : %s", args
.arg
, buf
);
737 status
= alienware_wmax_command(&args
, WMAX_METHOD_DEEP_SLEEP_CONTROL
,
740 if (ACPI_FAILURE(status
))
741 pr_err("alienware-wmi: deep sleep control failed: results: %u\n",
746 static DEVICE_ATTR(deepsleep
, S_IRUGO
| S_IWUSR
, show_deepsleep_status
, toggle_deepsleep
);
748 static struct attribute
*deepsleep_attrs
[] = {
749 &dev_attr_deepsleep
.attr
,
753 static const struct attribute_group deepsleep_attribute_group
= {
755 .attrs
= deepsleep_attrs
,
758 static void remove_deepsleep(struct platform_device
*dev
)
760 if (quirks
->deepslp
> 0)
761 sysfs_remove_group(&dev
->dev
.kobj
, &deepsleep_attribute_group
);
764 static int create_deepsleep(struct platform_device
*dev
)
768 ret
= sysfs_create_group(&dev
->dev
.kobj
, &deepsleep_attribute_group
);
770 remove_deepsleep(dev
);
774 static int __init
alienware_wmi_init(void)
778 if (wmi_has_guid(LEGACY_CONTROL_GUID
))
780 else if (wmi_has_guid(WMAX_CONTROL_GUID
))
783 pr_warn("alienware-wmi: No known WMI GUID found\n");
787 dmi_check_system(alienware_quirks
);
789 quirks
= &quirk_unknown
;
791 ret
= platform_driver_register(&platform_driver
);
793 goto fail_platform_driver
;
794 platform_device
= platform_device_alloc("alienware-wmi", -1);
795 if (!platform_device
) {
797 goto fail_platform_device1
;
799 ret
= platform_device_add(platform_device
);
801 goto fail_platform_device2
;
803 if (quirks
->hdmi_mux
> 0) {
804 ret
= create_hdmi(platform_device
);
809 if (quirks
->amplifier
> 0) {
810 ret
= create_amplifier(platform_device
);
812 goto fail_prep_amplifier
;
815 if (quirks
->deepslp
> 0) {
816 ret
= create_deepsleep(platform_device
);
818 goto fail_prep_deepsleep
;
821 ret
= alienware_zone_init(platform_device
);
823 goto fail_prep_zones
;
828 alienware_zone_exit(platform_device
);
832 platform_device_del(platform_device
);
833 fail_platform_device2
:
834 platform_device_put(platform_device
);
835 fail_platform_device1
:
836 platform_driver_unregister(&platform_driver
);
837 fail_platform_driver
:
841 module_init(alienware_wmi_init
);
843 static void __exit
alienware_wmi_exit(void)
845 if (platform_device
) {
846 alienware_zone_exit(platform_device
);
847 remove_hdmi(platform_device
);
848 platform_device_unregister(platform_device
);
849 platform_driver_unregister(&platform_driver
);
853 module_exit(alienware_wmi_exit
);