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
36 MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>");
37 MODULE_DESCRIPTION("Alienware special feature control");
38 MODULE_LICENSE("GPL");
39 MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID
);
40 MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID
);
42 enum INTERFACE_FLAGS
{
47 enum LEGACY_CONTROL_STATES
{
53 enum WMAX_CONTROL_STATES
{
63 static struct quirk_entry
*quirks
;
65 static struct quirk_entry quirk_unknown
= {
69 static struct quirk_entry quirk_x51_family
= {
73 static int dmi_matched(const struct dmi_system_id
*dmi
)
75 quirks
= dmi
->driver_data
;
79 static struct dmi_system_id alienware_quirks
[] = {
81 .callback
= dmi_matched
,
82 .ident
= "Alienware X51 R1",
84 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
85 DMI_MATCH(DMI_PRODUCT_NAME
, "Alienware X51"),
87 .driver_data
= &quirk_x51_family
,
90 .callback
= dmi_matched
,
91 .ident
= "Alienware X51 R2",
93 DMI_MATCH(DMI_SYS_VENDOR
, "Alienware"),
94 DMI_MATCH(DMI_PRODUCT_NAME
, "Alienware X51 R2"),
96 .driver_data
= &quirk_x51_family
,
101 struct color_platform
{
107 struct platform_zone
{
109 struct device_attribute
*attr
;
110 struct color_platform colors
;
113 struct wmax_brightness_args
{
122 struct legacy_led_args
{
123 struct color_platform colors
;
128 struct wmax_led_args
{
130 struct color_platform colors
;
134 static struct platform_device
*platform_device
;
135 static struct device_attribute
*zone_dev_attrs
;
136 static struct attribute
**zone_attrs
;
137 static struct platform_zone
*zone_data
;
139 static struct platform_driver platform_driver
= {
141 .name
= "alienware-wmi",
142 .owner
= THIS_MODULE
,
146 static struct attribute_group zone_attribute_group
= {
151 static u8 lighting_control_state
;
152 static u8 global_brightness
;
155 * Helpers used for zone control
157 static int parse_rgb(const char *buf
, struct platform_zone
*zone
)
159 long unsigned int rgb
;
162 struct color_platform cp
;
166 ret
= kstrtoul(buf
, 16, &rgb
);
170 /* RGB triplet notation is 24-bit hexadecimal */
174 repackager
.package
= rgb
& 0x0f0f0f0f;
175 pr_debug("alienware-wmi: r: %d g:%d b: %d\n",
176 repackager
.cp
.red
, repackager
.cp
.green
, repackager
.cp
.blue
);
177 zone
->colors
= repackager
.cp
;
181 static struct platform_zone
*match_zone(struct device_attribute
*attr
)
184 for (i
= 0; i
< quirks
->num_zones
; i
++) {
185 if ((struct device_attribute
*)zone_data
[i
].attr
== attr
) {
186 pr_debug("alienware-wmi: matched zone location: %d\n",
187 zone_data
[i
].location
);
188 return &zone_data
[i
];
195 * Individual RGB zone control
197 static int alienware_update_led(struct platform_zone
*zone
)
202 struct acpi_buffer input
;
203 struct legacy_led_args legacy_args
;
204 struct wmax_led_args wmax_args
;
205 if (interface
== WMAX
) {
206 wmax_args
.led_mask
= 1 << zone
->location
;
207 wmax_args
.colors
= zone
->colors
;
208 wmax_args
.state
= lighting_control_state
;
209 guid
= WMAX_CONTROL_GUID
;
210 method_id
= WMAX_METHOD_ZONE_CONTROL
;
212 input
.length
= (acpi_size
) sizeof(wmax_args
);
213 input
.pointer
= &wmax_args
;
215 legacy_args
.colors
= zone
->colors
;
216 legacy_args
.brightness
= global_brightness
;
217 legacy_args
.state
= 0;
218 if (lighting_control_state
== LEGACY_BOOTING
||
219 lighting_control_state
== LEGACY_SUSPEND
) {
220 guid
= LEGACY_POWER_CONTROL_GUID
;
221 legacy_args
.state
= lighting_control_state
;
223 guid
= LEGACY_CONTROL_GUID
;
224 method_id
= zone
->location
+ 1;
226 input
.length
= (acpi_size
) sizeof(legacy_args
);
227 input
.pointer
= &legacy_args
;
229 pr_debug("alienware-wmi: guid %s method %d\n", guid
, method_id
);
231 status
= wmi_evaluate_method(guid
, 1, method_id
, &input
, NULL
);
232 if (ACPI_FAILURE(status
))
233 pr_err("alienware-wmi: zone set failure: %u\n", status
);
234 return ACPI_FAILURE(status
);
237 static ssize_t
zone_show(struct device
*dev
, struct device_attribute
*attr
,
240 struct platform_zone
*target_zone
;
241 target_zone
= match_zone(attr
);
242 if (target_zone
== NULL
)
243 return sprintf(buf
, "red: -1, green: -1, blue: -1\n");
244 return sprintf(buf
, "red: %d, green: %d, blue: %d\n",
245 target_zone
->colors
.red
,
246 target_zone
->colors
.green
, target_zone
->colors
.blue
);
250 static ssize_t
zone_set(struct device
*dev
, struct device_attribute
*attr
,
251 const char *buf
, size_t count
)
253 struct platform_zone
*target_zone
;
255 target_zone
= match_zone(attr
);
256 if (target_zone
== NULL
) {
257 pr_err("alienware-wmi: invalid target zone\n");
260 ret
= parse_rgb(buf
, target_zone
);
263 ret
= alienware_update_led(target_zone
);
264 return ret
? ret
: count
;
268 * LED Brightness (Global)
270 static int wmax_brightness(int brightness
)
273 struct acpi_buffer input
;
274 struct wmax_brightness_args args
= {
276 .percentage
= brightness
,
278 input
.length
= (acpi_size
) sizeof(args
);
279 input
.pointer
= &args
;
280 status
= wmi_evaluate_method(WMAX_CONTROL_GUID
, 1,
281 WMAX_METHOD_BRIGHTNESS
, &input
, NULL
);
282 if (ACPI_FAILURE(status
))
283 pr_err("alienware-wmi: brightness set failure: %u\n", status
);
284 return ACPI_FAILURE(status
);
287 static void global_led_set(struct led_classdev
*led_cdev
,
288 enum led_brightness brightness
)
291 global_brightness
= brightness
;
292 if (interface
== WMAX
)
293 ret
= wmax_brightness(brightness
);
295 ret
= alienware_update_led(&zone_data
[0]);
297 pr_err("LED brightness update failed\n");
300 static enum led_brightness
global_led_get(struct led_classdev
*led_cdev
)
302 return global_brightness
;
305 static struct led_classdev global_led
= {
306 .brightness_set
= global_led_set
,
307 .brightness_get
= global_led_get
,
308 .name
= "alienware::global_brightness",
312 * Lighting control state device attribute (Global)
314 static ssize_t
show_control_state(struct device
*dev
,
315 struct device_attribute
*attr
, char *buf
)
317 if (lighting_control_state
== LEGACY_BOOTING
)
318 return scnprintf(buf
, PAGE_SIZE
, "[booting] running suspend\n");
319 else if (lighting_control_state
== LEGACY_SUSPEND
)
320 return scnprintf(buf
, PAGE_SIZE
, "booting running [suspend]\n");
321 return scnprintf(buf
, PAGE_SIZE
, "booting [running] suspend\n");
324 static ssize_t
store_control_state(struct device
*dev
,
325 struct device_attribute
*attr
,
326 const char *buf
, size_t count
)
328 long unsigned int val
;
329 if (strcmp(buf
, "booting\n") == 0)
330 val
= LEGACY_BOOTING
;
331 else if (strcmp(buf
, "suspend\n") == 0)
332 val
= LEGACY_SUSPEND
;
333 else if (interface
== LEGACY
)
334 val
= LEGACY_RUNNING
;
337 lighting_control_state
= val
;
338 pr_debug("alienware-wmi: updated control state to %d\n",
339 lighting_control_state
);
343 static DEVICE_ATTR(lighting_control_state
, 0644, show_control_state
,
344 store_control_state
);
346 static int alienware_zone_init(struct platform_device
*dev
)
352 if (interface
== WMAX
) {
353 global_led
.max_brightness
= 100;
354 lighting_control_state
= WMAX_RUNNING
;
355 } else if (interface
== LEGACY
) {
356 global_led
.max_brightness
= 0x0F;
357 lighting_control_state
= LEGACY_RUNNING
;
359 global_brightness
= global_led
.max_brightness
;
362 * - zone_dev_attrs num_zones + 1 is for individual zones and then
364 * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs +
365 * the lighting control + null terminated
366 * - zone_data num_zones is for the distinct zones
369 kzalloc(sizeof(struct device_attribute
) * (quirks
->num_zones
+ 1),
375 kzalloc(sizeof(struct attribute
*) * (quirks
->num_zones
+ 2),
381 kzalloc(sizeof(struct platform_zone
) * (quirks
->num_zones
),
386 for (i
= 0; i
< quirks
->num_zones
; i
++) {
387 sprintf(buffer
, "zone%02X", i
);
388 name
= kstrdup(buffer
, GFP_KERNEL
);
391 sysfs_attr_init(&zone_dev_attrs
[i
].attr
);
392 zone_dev_attrs
[i
].attr
.name
= name
;
393 zone_dev_attrs
[i
].attr
.mode
= 0644;
394 zone_dev_attrs
[i
].show
= zone_show
;
395 zone_dev_attrs
[i
].store
= zone_set
;
396 zone_data
[i
].location
= i
;
397 zone_attrs
[i
] = &zone_dev_attrs
[i
].attr
;
398 zone_data
[i
].attr
= &zone_dev_attrs
[i
];
400 zone_attrs
[quirks
->num_zones
] = &dev_attr_lighting_control_state
.attr
;
401 zone_attribute_group
.attrs
= zone_attrs
;
403 led_classdev_register(&dev
->dev
, &global_led
);
405 return sysfs_create_group(&dev
->dev
.kobj
, &zone_attribute_group
);
408 static void alienware_zone_exit(struct platform_device
*dev
)
410 sysfs_remove_group(&dev
->dev
.kobj
, &zone_attribute_group
);
411 led_classdev_unregister(&global_led
);
412 if (zone_dev_attrs
) {
414 for (i
= 0; i
< quirks
->num_zones
; i
++)
415 kfree(zone_dev_attrs
[i
].attr
.name
);
417 kfree(zone_dev_attrs
);
423 The HDMI mux sysfs node indicates the status of the HDMI input mux.
424 It can toggle between standard system GPU output and HDMI input.
426 static ssize_t
show_hdmi(struct device
*dev
, struct device_attribute
*attr
,
430 struct acpi_buffer input
;
431 union acpi_object
*obj
;
433 struct acpi_buffer output
= { ACPI_ALLOCATE_BUFFER
, NULL
};
434 struct hdmi_args in_args
= {
437 input
.length
= (acpi_size
) sizeof(in_args
);
438 input
.pointer
= &in_args
;
439 status
= wmi_evaluate_method(WMAX_CONTROL_GUID
, 1,
440 WMAX_METHOD_HDMI_STATUS
, &input
, &output
);
442 if (ACPI_SUCCESS(status
)) {
443 obj
= (union acpi_object
*)output
.pointer
;
444 if (obj
&& obj
->type
== ACPI_TYPE_INTEGER
)
445 tmp
= (u32
) obj
->integer
.value
;
447 return scnprintf(buf
, PAGE_SIZE
,
448 "[input] gpu unknown\n");
450 return scnprintf(buf
, PAGE_SIZE
,
451 "input [gpu] unknown\n");
453 pr_err("alienware-wmi: unknown HDMI status: %d\n", status
);
454 return scnprintf(buf
, PAGE_SIZE
, "input gpu [unknown]\n");
457 static ssize_t
toggle_hdmi(struct device
*dev
, struct device_attribute
*attr
,
458 const char *buf
, size_t count
)
460 struct acpi_buffer input
;
462 struct hdmi_args args
;
463 if (strcmp(buf
, "gpu\n") == 0)
465 else if (strcmp(buf
, "input\n") == 0)
469 pr_debug("alienware-wmi: setting hdmi to %d : %s", args
.arg
, buf
);
470 input
.length
= (acpi_size
) sizeof(args
);
471 input
.pointer
= &args
;
472 status
= wmi_evaluate_method(WMAX_CONTROL_GUID
, 1,
473 WMAX_METHOD_HDMI_SOURCE
, &input
, NULL
);
474 if (ACPI_FAILURE(status
))
475 pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
480 static DEVICE_ATTR(hdmi
, S_IRUGO
| S_IWUSR
, show_hdmi
, toggle_hdmi
);
482 static void remove_hdmi(struct platform_device
*device
)
484 device_remove_file(&device
->dev
, &dev_attr_hdmi
);
487 static int create_hdmi(void)
490 ret
= device_create_file(&platform_device
->dev
, &dev_attr_hdmi
);
492 goto error_create_hdmi
;
496 remove_hdmi(platform_device
);
500 static int __init
alienware_wmi_init(void)
504 if (wmi_has_guid(LEGACY_CONTROL_GUID
))
506 else if (wmi_has_guid(WMAX_CONTROL_GUID
))
509 pr_warn("alienware-wmi: No known WMI GUID found\n");
513 dmi_check_system(alienware_quirks
);
515 quirks
= &quirk_unknown
;
517 ret
= platform_driver_register(&platform_driver
);
519 goto fail_platform_driver
;
520 platform_device
= platform_device_alloc("alienware-wmi", -1);
521 if (!platform_device
) {
523 goto fail_platform_device1
;
525 ret
= platform_device_add(platform_device
);
527 goto fail_platform_device2
;
529 if (interface
== WMAX
) {
535 ret
= alienware_zone_init(platform_device
);
537 goto fail_prep_zones
;
542 alienware_zone_exit(platform_device
);
544 platform_device_del(platform_device
);
545 fail_platform_device2
:
546 platform_device_put(platform_device
);
547 fail_platform_device1
:
548 platform_driver_unregister(&platform_driver
);
549 fail_platform_driver
:
553 module_init(alienware_wmi_init
);
555 static void __exit
alienware_wmi_exit(void)
557 if (platform_device
) {
558 alienware_zone_exit(platform_device
);
559 remove_hdmi(platform_device
);
560 platform_device_unregister(platform_device
);
561 platform_driver_unregister(&platform_driver
);
565 module_exit(alienware_wmi_exit
);