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/acpi.h>
25 #include <linux/leds.h>
27 #define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492"
28 #define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
29 #define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492"
31 #define WMAX_METHOD_HDMI_SOURCE 0x1
32 #define WMAX_METHOD_HDMI_STATUS 0x2
33 #define WMAX_METHOD_BRIGHTNESS 0x3
34 #define WMAX_METHOD_ZONE_CONTROL 0x4
35 #define WMAX_METHOD_HDMI_CABLE 0x5
36 #define WMAX_METHOD_AMPLIFIER_CABLE 0x6
38 MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>");
39 MODULE_DESCRIPTION("Alienware special feature control");
40 MODULE_LICENSE("GPL");
41 MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID
);
42 MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID
);
44 enum INTERFACE_FLAGS
{
49 enum LEGACY_CONTROL_STATES
{
55 enum WMAX_CONTROL_STATES
{
67 static struct quirk_entry
*quirks
;
69 static struct quirk_entry quirk_unknown
= {
75 static struct quirk_entry quirk_x51_r1_r2
= {
81 static struct quirk_entry quirk_x51_r3
= {
87 static struct quirk_entry quirk_asm100
= {
93 static int __init
dmi_matched(const struct dmi_system_id
*dmi
)
95 quirks
= dmi
->driver_data
;
99 static const struct dmi_system_id alienware_quirks
[] __initconst
= {
101 .callback
= dmi_matched
,
102 .ident
= "Alienware X51 R3",
104 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
105 DMI_MATCH(DMI_PRODUCT_NAME
, "Alienware X51 R3"),
107 .driver_data
= &quirk_x51_r3
,
110 .callback
= dmi_matched
,
111 .ident
= "Alienware X51 R2",
113 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
114 DMI_MATCH(DMI_PRODUCT_NAME
, "Alienware X51 R2"),
116 .driver_data
= &quirk_x51_r1_r2
,
119 .callback
= dmi_matched
,
120 .ident
= "Alienware X51 R1",
122 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
123 DMI_MATCH(DMI_PRODUCT_NAME
, "Alienware X51"),
125 .driver_data
= &quirk_x51_r1_r2
,
128 .callback
= dmi_matched
,
129 .ident
= "Alienware ASM100",
131 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
132 DMI_MATCH(DMI_PRODUCT_NAME
, "ASM100"),
134 .driver_data
= &quirk_asm100
,
139 struct color_platform
{
145 struct platform_zone
{
147 struct device_attribute
*attr
;
148 struct color_platform colors
;
151 struct wmax_brightness_args
{
156 struct wmax_basic_args
{
160 struct legacy_led_args
{
161 struct color_platform colors
;
166 struct wmax_led_args
{
168 struct color_platform colors
;
172 static struct platform_device
*platform_device
;
173 static struct device_attribute
*zone_dev_attrs
;
174 static struct attribute
**zone_attrs
;
175 static struct platform_zone
*zone_data
;
177 static struct platform_driver platform_driver
= {
179 .name
= "alienware-wmi",
183 static struct attribute_group zone_attribute_group
= {
188 static u8 lighting_control_state
;
189 static u8 global_brightness
;
192 * Helpers used for zone control
194 static int parse_rgb(const char *buf
, struct platform_zone
*zone
)
196 long unsigned int rgb
;
199 struct color_platform cp
;
203 ret
= kstrtoul(buf
, 16, &rgb
);
207 /* RGB triplet notation is 24-bit hexadecimal */
211 repackager
.package
= rgb
& 0x0f0f0f0f;
212 pr_debug("alienware-wmi: r: %d g:%d b: %d\n",
213 repackager
.cp
.red
, repackager
.cp
.green
, repackager
.cp
.blue
);
214 zone
->colors
= repackager
.cp
;
218 static struct platform_zone
*match_zone(struct device_attribute
*attr
)
221 for (i
= 0; i
< quirks
->num_zones
; i
++) {
222 if ((struct device_attribute
*)zone_data
[i
].attr
== attr
) {
223 pr_debug("alienware-wmi: matched zone location: %d\n",
224 zone_data
[i
].location
);
225 return &zone_data
[i
];
232 * Individual RGB zone control
234 static int alienware_update_led(struct platform_zone
*zone
)
239 struct acpi_buffer input
;
240 struct legacy_led_args legacy_args
;
241 struct wmax_led_args wmax_basic_args
;
242 if (interface
== WMAX
) {
243 wmax_basic_args
.led_mask
= 1 << zone
->location
;
244 wmax_basic_args
.colors
= zone
->colors
;
245 wmax_basic_args
.state
= lighting_control_state
;
246 guid
= WMAX_CONTROL_GUID
;
247 method_id
= WMAX_METHOD_ZONE_CONTROL
;
249 input
.length
= (acpi_size
) sizeof(wmax_basic_args
);
250 input
.pointer
= &wmax_basic_args
;
252 legacy_args
.colors
= zone
->colors
;
253 legacy_args
.brightness
= global_brightness
;
254 legacy_args
.state
= 0;
255 if (lighting_control_state
== LEGACY_BOOTING
||
256 lighting_control_state
== LEGACY_SUSPEND
) {
257 guid
= LEGACY_POWER_CONTROL_GUID
;
258 legacy_args
.state
= lighting_control_state
;
260 guid
= LEGACY_CONTROL_GUID
;
261 method_id
= zone
->location
+ 1;
263 input
.length
= (acpi_size
) sizeof(legacy_args
);
264 input
.pointer
= &legacy_args
;
266 pr_debug("alienware-wmi: guid %s method %d\n", guid
, method_id
);
268 status
= wmi_evaluate_method(guid
, 1, method_id
, &input
, NULL
);
269 if (ACPI_FAILURE(status
))
270 pr_err("alienware-wmi: zone set failure: %u\n", status
);
271 return ACPI_FAILURE(status
);
274 static ssize_t
zone_show(struct device
*dev
, struct device_attribute
*attr
,
277 struct platform_zone
*target_zone
;
278 target_zone
= match_zone(attr
);
279 if (target_zone
== NULL
)
280 return sprintf(buf
, "red: -1, green: -1, blue: -1\n");
281 return sprintf(buf
, "red: %d, green: %d, blue: %d\n",
282 target_zone
->colors
.red
,
283 target_zone
->colors
.green
, target_zone
->colors
.blue
);
287 static ssize_t
zone_set(struct device
*dev
, struct device_attribute
*attr
,
288 const char *buf
, size_t count
)
290 struct platform_zone
*target_zone
;
292 target_zone
= match_zone(attr
);
293 if (target_zone
== NULL
) {
294 pr_err("alienware-wmi: invalid target zone\n");
297 ret
= parse_rgb(buf
, target_zone
);
300 ret
= alienware_update_led(target_zone
);
301 return ret
? ret
: count
;
305 * LED Brightness (Global)
307 static int wmax_brightness(int brightness
)
310 struct acpi_buffer input
;
311 struct wmax_brightness_args args
= {
313 .percentage
= brightness
,
315 input
.length
= (acpi_size
) sizeof(args
);
316 input
.pointer
= &args
;
317 status
= wmi_evaluate_method(WMAX_CONTROL_GUID
, 1,
318 WMAX_METHOD_BRIGHTNESS
, &input
, NULL
);
319 if (ACPI_FAILURE(status
))
320 pr_err("alienware-wmi: brightness set failure: %u\n", status
);
321 return ACPI_FAILURE(status
);
324 static void global_led_set(struct led_classdev
*led_cdev
,
325 enum led_brightness brightness
)
328 global_brightness
= brightness
;
329 if (interface
== WMAX
)
330 ret
= wmax_brightness(brightness
);
332 ret
= alienware_update_led(&zone_data
[0]);
334 pr_err("LED brightness update failed\n");
337 static enum led_brightness
global_led_get(struct led_classdev
*led_cdev
)
339 return global_brightness
;
342 static struct led_classdev global_led
= {
343 .brightness_set
= global_led_set
,
344 .brightness_get
= global_led_get
,
345 .name
= "alienware::global_brightness",
349 * Lighting control state device attribute (Global)
351 static ssize_t
show_control_state(struct device
*dev
,
352 struct device_attribute
*attr
, char *buf
)
354 if (lighting_control_state
== LEGACY_BOOTING
)
355 return scnprintf(buf
, PAGE_SIZE
, "[booting] running suspend\n");
356 else if (lighting_control_state
== LEGACY_SUSPEND
)
357 return scnprintf(buf
, PAGE_SIZE
, "booting running [suspend]\n");
358 return scnprintf(buf
, PAGE_SIZE
, "booting [running] suspend\n");
361 static ssize_t
store_control_state(struct device
*dev
,
362 struct device_attribute
*attr
,
363 const char *buf
, size_t count
)
365 long unsigned int val
;
366 if (strcmp(buf
, "booting\n") == 0)
367 val
= LEGACY_BOOTING
;
368 else if (strcmp(buf
, "suspend\n") == 0)
369 val
= LEGACY_SUSPEND
;
370 else if (interface
== LEGACY
)
371 val
= LEGACY_RUNNING
;
374 lighting_control_state
= val
;
375 pr_debug("alienware-wmi: updated control state to %d\n",
376 lighting_control_state
);
380 static DEVICE_ATTR(lighting_control_state
, 0644, show_control_state
,
381 store_control_state
);
383 static int alienware_zone_init(struct platform_device
*dev
)
389 if (interface
== WMAX
) {
390 lighting_control_state
= WMAX_RUNNING
;
391 } else if (interface
== LEGACY
) {
392 lighting_control_state
= LEGACY_RUNNING
;
394 global_led
.max_brightness
= 0x0F;
395 global_brightness
= global_led
.max_brightness
;
398 * - zone_dev_attrs num_zones + 1 is for individual zones and then
400 * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs +
401 * the lighting control + null terminated
402 * - zone_data num_zones is for the distinct zones
405 kzalloc(sizeof(struct device_attribute
) * (quirks
->num_zones
+ 1),
411 kzalloc(sizeof(struct attribute
*) * (quirks
->num_zones
+ 2),
417 kzalloc(sizeof(struct platform_zone
) * (quirks
->num_zones
),
422 for (i
= 0; i
< quirks
->num_zones
; i
++) {
423 sprintf(buffer
, "zone%02X", i
);
424 name
= kstrdup(buffer
, GFP_KERNEL
);
427 sysfs_attr_init(&zone_dev_attrs
[i
].attr
);
428 zone_dev_attrs
[i
].attr
.name
= name
;
429 zone_dev_attrs
[i
].attr
.mode
= 0644;
430 zone_dev_attrs
[i
].show
= zone_show
;
431 zone_dev_attrs
[i
].store
= zone_set
;
432 zone_data
[i
].location
= i
;
433 zone_attrs
[i
] = &zone_dev_attrs
[i
].attr
;
434 zone_data
[i
].attr
= &zone_dev_attrs
[i
];
436 zone_attrs
[quirks
->num_zones
] = &dev_attr_lighting_control_state
.attr
;
437 zone_attribute_group
.attrs
= zone_attrs
;
439 led_classdev_register(&dev
->dev
, &global_led
);
441 return sysfs_create_group(&dev
->dev
.kobj
, &zone_attribute_group
);
444 static void alienware_zone_exit(struct platform_device
*dev
)
446 sysfs_remove_group(&dev
->dev
.kobj
, &zone_attribute_group
);
447 led_classdev_unregister(&global_led
);
448 if (zone_dev_attrs
) {
450 for (i
= 0; i
< quirks
->num_zones
; i
++)
451 kfree(zone_dev_attrs
[i
].attr
.name
);
453 kfree(zone_dev_attrs
);
458 static acpi_status
alienware_wmax_command(struct wmax_basic_args
*in_args
,
459 u32 command
, int *out_data
)
462 union acpi_object
*obj
;
463 struct acpi_buffer input
;
464 struct acpi_buffer output
;
466 input
.length
= (acpi_size
) sizeof(*in_args
);
467 input
.pointer
= in_args
;
468 if (out_data
!= NULL
) {
469 output
.length
= ACPI_ALLOCATE_BUFFER
;
470 output
.pointer
= NULL
;
471 status
= wmi_evaluate_method(WMAX_CONTROL_GUID
, 1,
472 command
, &input
, &output
);
474 status
= wmi_evaluate_method(WMAX_CONTROL_GUID
, 1,
475 command
, &input
, NULL
);
477 if (ACPI_SUCCESS(status
) && out_data
!= NULL
) {
478 obj
= (union acpi_object
*)output
.pointer
;
479 if (obj
&& obj
->type
== ACPI_TYPE_INTEGER
)
480 *out_data
= (u32
) obj
->integer
.value
;
487 * The HDMI mux sysfs node indicates the status of the HDMI input mux.
488 * It can toggle between standard system GPU output and HDMI input.
490 static ssize_t
show_hdmi_cable(struct device
*dev
,
491 struct device_attribute
*attr
, char *buf
)
495 struct wmax_basic_args in_args
= {
499 alienware_wmax_command(&in_args
, WMAX_METHOD_HDMI_CABLE
,
501 if (ACPI_SUCCESS(status
)) {
503 return scnprintf(buf
, PAGE_SIZE
,
504 "[unconnected] connected unknown\n");
505 else if (out_data
== 1)
506 return scnprintf(buf
, PAGE_SIZE
,
507 "unconnected [connected] unknown\n");
509 pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status
);
510 return scnprintf(buf
, PAGE_SIZE
, "unconnected connected [unknown]\n");
513 static ssize_t
show_hdmi_source(struct device
*dev
,
514 struct device_attribute
*attr
, char *buf
)
518 struct wmax_basic_args in_args
= {
522 alienware_wmax_command(&in_args
, WMAX_METHOD_HDMI_STATUS
,
525 if (ACPI_SUCCESS(status
)) {
527 return scnprintf(buf
, PAGE_SIZE
,
528 "[input] gpu unknown\n");
529 else if (out_data
== 2)
530 return scnprintf(buf
, PAGE_SIZE
,
531 "input [gpu] unknown\n");
533 pr_err("alienware-wmi: unknown HDMI source status: %d\n", out_data
);
534 return scnprintf(buf
, PAGE_SIZE
, "input gpu [unknown]\n");
537 static ssize_t
toggle_hdmi_source(struct device
*dev
,
538 struct device_attribute
*attr
,
539 const char *buf
, size_t count
)
542 struct wmax_basic_args args
;
543 if (strcmp(buf
, "gpu\n") == 0)
545 else if (strcmp(buf
, "input\n") == 0)
549 pr_debug("alienware-wmi: setting hdmi to %d : %s", args
.arg
, buf
);
551 status
= alienware_wmax_command(&args
, WMAX_METHOD_HDMI_SOURCE
, NULL
);
553 if (ACPI_FAILURE(status
))
554 pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
559 static DEVICE_ATTR(cable
, S_IRUGO
, show_hdmi_cable
, NULL
);
560 static DEVICE_ATTR(source
, S_IRUGO
| S_IWUSR
, show_hdmi_source
,
563 static struct attribute
*hdmi_attrs
[] = {
564 &dev_attr_cable
.attr
,
565 &dev_attr_source
.attr
,
569 static struct attribute_group hdmi_attribute_group
= {
574 static void remove_hdmi(struct platform_device
*dev
)
576 if (quirks
->hdmi_mux
> 0)
577 sysfs_remove_group(&dev
->dev
.kobj
, &hdmi_attribute_group
);
580 static int create_hdmi(struct platform_device
*dev
)
584 ret
= sysfs_create_group(&dev
->dev
.kobj
, &hdmi_attribute_group
);
586 goto error_create_hdmi
;
595 * Alienware GFX amplifier support
596 * - Currently supports reading cable status
597 * - Leaving expansion room to possibly support dock/undock events later
599 static ssize_t
show_amplifier_status(struct device
*dev
,
600 struct device_attribute
*attr
, char *buf
)
604 struct wmax_basic_args in_args
= {
608 alienware_wmax_command(&in_args
, WMAX_METHOD_AMPLIFIER_CABLE
,
610 if (ACPI_SUCCESS(status
)) {
612 return scnprintf(buf
, PAGE_SIZE
,
613 "[unconnected] connected unknown\n");
614 else if (out_data
== 1)
615 return scnprintf(buf
, PAGE_SIZE
,
616 "unconnected [connected] unknown\n");
618 pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status
);
619 return scnprintf(buf
, PAGE_SIZE
, "unconnected connected [unknown]\n");
622 static DEVICE_ATTR(status
, S_IRUGO
, show_amplifier_status
, NULL
);
624 static struct attribute
*amplifier_attrs
[] = {
625 &dev_attr_status
.attr
,
629 static struct attribute_group amplifier_attribute_group
= {
631 .attrs
= amplifier_attrs
,
634 static void remove_amplifier(struct platform_device
*dev
)
636 if (quirks
->amplifier
> 0)
637 sysfs_remove_group(&dev
->dev
.kobj
, &lifier_attribute_group
);
640 static int create_amplifier(struct platform_device
*dev
)
644 ret
= sysfs_create_group(&dev
->dev
.kobj
, &lifier_attribute_group
);
646 remove_amplifier(dev
);
650 static int __init
alienware_wmi_init(void)
654 if (wmi_has_guid(LEGACY_CONTROL_GUID
))
656 else if (wmi_has_guid(WMAX_CONTROL_GUID
))
659 pr_warn("alienware-wmi: No known WMI GUID found\n");
663 dmi_check_system(alienware_quirks
);
665 quirks
= &quirk_unknown
;
667 ret
= platform_driver_register(&platform_driver
);
669 goto fail_platform_driver
;
670 platform_device
= platform_device_alloc("alienware-wmi", -1);
671 if (!platform_device
) {
673 goto fail_platform_device1
;
675 ret
= platform_device_add(platform_device
);
677 goto fail_platform_device2
;
679 if (quirks
->hdmi_mux
> 0) {
680 ret
= create_hdmi(platform_device
);
685 if (quirks
->amplifier
> 0) {
686 ret
= create_amplifier(platform_device
);
688 goto fail_prep_amplifier
;
691 ret
= alienware_zone_init(platform_device
);
693 goto fail_prep_zones
;
698 alienware_zone_exit(platform_device
);
701 platform_device_del(platform_device
);
702 fail_platform_device2
:
703 platform_device_put(platform_device
);
704 fail_platform_device1
:
705 platform_driver_unregister(&platform_driver
);
706 fail_platform_driver
:
710 module_init(alienware_wmi_init
);
712 static void __exit
alienware_wmi_exit(void)
714 if (platform_device
) {
715 alienware_zone_exit(platform_device
);
716 remove_hdmi(platform_device
);
717 platform_device_unregister(platform_device
);
718 platform_driver_unregister(&platform_driver
);
722 module_exit(alienware_wmi_exit
);