1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Alienware AlienFX control |
4 | * |
5 | * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com> |
6 | */ |
7 | |
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
9 | |
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> |
15 | |
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" |
19 | |
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 |
28 | |
29 | MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.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); |
34 | |
35 | enum INTERFACE_FLAGS { |
36 | LEGACY, |
37 | WMAX, |
38 | }; |
39 | |
40 | enum LEGACY_CONTROL_STATES { |
41 | LEGACY_RUNNING = 1, |
42 | LEGACY_BOOTING = 0, |
43 | LEGACY_SUSPEND = 3, |
44 | }; |
45 | |
46 | enum WMAX_CONTROL_STATES { |
47 | WMAX_RUNNING = 0xFF, |
48 | WMAX_BOOTING = 0, |
49 | WMAX_SUSPEND = 3, |
50 | }; |
51 | |
52 | struct quirk_entry { |
53 | u8 num_zones; |
54 | u8 hdmi_mux; |
55 | u8 amplifier; |
56 | u8 deepslp; |
57 | }; |
58 | |
59 | static struct quirk_entry *quirks; |
60 | |
61 | |
62 | static struct quirk_entry quirk_inspiron5675 = { |
63 | .num_zones = 2, |
64 | .hdmi_mux = 0, |
65 | .amplifier = 0, |
66 | .deepslp = 0, |
67 | }; |
68 | |
69 | static struct quirk_entry quirk_unknown = { |
70 | .num_zones = 2, |
71 | .hdmi_mux = 0, |
72 | .amplifier = 0, |
73 | .deepslp = 0, |
74 | }; |
75 | |
76 | static struct quirk_entry quirk_x51_r1_r2 = { |
77 | .num_zones = 3, |
78 | .hdmi_mux = 0, |
79 | .amplifier = 0, |
80 | .deepslp = 0, |
81 | }; |
82 | |
83 | static struct quirk_entry quirk_x51_r3 = { |
84 | .num_zones = 4, |
85 | .hdmi_mux = 0, |
86 | .amplifier = 1, |
87 | .deepslp = 0, |
88 | }; |
89 | |
90 | static struct quirk_entry quirk_asm100 = { |
91 | .num_zones = 2, |
92 | .hdmi_mux = 1, |
93 | .amplifier = 0, |
94 | .deepslp = 0, |
95 | }; |
96 | |
97 | static struct quirk_entry quirk_asm200 = { |
98 | .num_zones = 2, |
99 | .hdmi_mux = 1, |
100 | .amplifier = 0, |
101 | .deepslp = 1, |
102 | }; |
103 | |
104 | static struct quirk_entry quirk_asm201 = { |
105 | .num_zones = 2, |
106 | .hdmi_mux = 1, |
107 | .amplifier = 1, |
108 | .deepslp = 1, |
109 | }; |
110 | |
111 | static int __init dmi_matched(const struct dmi_system_id *dmi) |
112 | { |
113 | quirks = dmi->driver_data; |
114 | return 1; |
115 | } |
116 | |
117 | static const struct dmi_system_id alienware_quirks[] __initconst = { |
118 | { |
119 | .callback = dmi_matched, |
120 | .ident = "Alienware X51 R3" , |
121 | .matches = { |
122 | DMI_MATCH(DMI_SYS_VENDOR, "Alienware" ), |
123 | DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3" ), |
124 | }, |
125 | .driver_data = &quirk_x51_r3, |
126 | }, |
127 | { |
128 | .callback = dmi_matched, |
129 | .ident = "Alienware X51 R2" , |
130 | .matches = { |
131 | DMI_MATCH(DMI_SYS_VENDOR, "Alienware" ), |
132 | DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2" ), |
133 | }, |
134 | .driver_data = &quirk_x51_r1_r2, |
135 | }, |
136 | { |
137 | .callback = dmi_matched, |
138 | .ident = "Alienware X51 R1" , |
139 | .matches = { |
140 | DMI_MATCH(DMI_SYS_VENDOR, "Alienware" ), |
141 | DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51" ), |
142 | }, |
143 | .driver_data = &quirk_x51_r1_r2, |
144 | }, |
145 | { |
146 | .callback = dmi_matched, |
147 | .ident = "Alienware ASM100" , |
148 | .matches = { |
149 | DMI_MATCH(DMI_SYS_VENDOR, "Alienware" ), |
150 | DMI_MATCH(DMI_PRODUCT_NAME, "ASM100" ), |
151 | }, |
152 | .driver_data = &quirk_asm100, |
153 | }, |
154 | { |
155 | .callback = dmi_matched, |
156 | .ident = "Alienware ASM200" , |
157 | .matches = { |
158 | DMI_MATCH(DMI_SYS_VENDOR, "Alienware" ), |
159 | DMI_MATCH(DMI_PRODUCT_NAME, "ASM200" ), |
160 | }, |
161 | .driver_data = &quirk_asm200, |
162 | }, |
163 | { |
164 | .callback = dmi_matched, |
165 | .ident = "Alienware ASM201" , |
166 | .matches = { |
167 | DMI_MATCH(DMI_SYS_VENDOR, "Alienware" ), |
168 | DMI_MATCH(DMI_PRODUCT_NAME, "ASM201" ), |
169 | }, |
170 | .driver_data = &quirk_asm201, |
171 | }, |
172 | { |
173 | .callback = dmi_matched, |
174 | .ident = "Dell Inc. Inspiron 5675" , |
175 | .matches = { |
176 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc." ), |
177 | DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675" ), |
178 | }, |
179 | .driver_data = &quirk_inspiron5675, |
180 | }, |
181 | {} |
182 | }; |
183 | |
184 | struct color_platform { |
185 | u8 blue; |
186 | u8 green; |
187 | u8 red; |
188 | } __packed; |
189 | |
190 | struct platform_zone { |
191 | u8 location; |
192 | struct device_attribute *attr; |
193 | struct color_platform colors; |
194 | }; |
195 | |
196 | struct wmax_brightness_args { |
197 | u32 led_mask; |
198 | u32 percentage; |
199 | }; |
200 | |
201 | struct wmax_basic_args { |
202 | u8 arg; |
203 | }; |
204 | |
205 | struct legacy_led_args { |
206 | struct color_platform colors; |
207 | u8 brightness; |
208 | u8 state; |
209 | } __packed; |
210 | |
211 | struct wmax_led_args { |
212 | u32 led_mask; |
213 | struct color_platform colors; |
214 | u8 state; |
215 | } __packed; |
216 | |
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; |
221 | |
222 | static struct platform_driver platform_driver = { |
223 | .driver = { |
224 | .name = "alienware-wmi" , |
225 | } |
226 | }; |
227 | |
228 | static struct attribute_group zone_attribute_group = { |
229 | .name = "rgb_zones" , |
230 | }; |
231 | |
232 | static u8 interface; |
233 | static u8 lighting_control_state; |
234 | static u8 global_brightness; |
235 | |
236 | /* |
237 | * Helpers used for zone control |
238 | */ |
239 | static int parse_rgb(const char *buf, struct platform_zone *zone) |
240 | { |
241 | long unsigned int rgb; |
242 | int ret; |
243 | union color_union { |
244 | struct color_platform cp; |
245 | int package; |
246 | } repackager; |
247 | |
248 | ret = kstrtoul(s: buf, base: 16, res: &rgb); |
249 | if (ret) |
250 | return ret; |
251 | |
252 | /* RGB triplet notation is 24-bit hexadecimal */ |
253 | if (rgb > 0xFFFFFF) |
254 | return -EINVAL; |
255 | |
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; |
260 | return 0; |
261 | } |
262 | |
263 | static struct platform_zone *match_zone(struct device_attribute *attr) |
264 | { |
265 | u8 zone; |
266 | |
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]; |
272 | } |
273 | } |
274 | return NULL; |
275 | } |
276 | |
277 | /* |
278 | * Individual RGB zone control |
279 | */ |
280 | static int alienware_update_led(struct platform_zone *zone) |
281 | { |
282 | int method_id; |
283 | acpi_status status; |
284 | char *guid; |
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; |
294 | |
295 | input.length = (acpi_size) sizeof(wmax_basic_args); |
296 | input.pointer = &wmax_basic_args; |
297 | } else { |
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; |
305 | } else |
306 | guid = LEGACY_CONTROL_GUID; |
307 | method_id = zone->location + 1; |
308 | |
309 | input.length = (acpi_size) sizeof(legacy_args); |
310 | input.pointer = &legacy_args; |
311 | } |
312 | pr_debug("alienware-wmi: guid %s method %d\n" , guid, method_id); |
313 | |
314 | status = wmi_evaluate_method(guid, instance: 0, method_id, in: &input, NULL); |
315 | if (ACPI_FAILURE(status)) |
316 | pr_err("alienware-wmi: zone set failure: %u\n" , status); |
317 | return ACPI_FAILURE(status); |
318 | } |
319 | |
320 | static ssize_t zone_show(struct device *dev, struct device_attribute *attr, |
321 | char *buf) |
322 | { |
323 | struct platform_zone *target_zone; |
324 | target_zone = match_zone(attr); |
325 | if (target_zone == NULL) |
326 | return sprintf(buf, fmt: "red: -1, green: -1, blue: -1\n" ); |
327 | return sprintf(buf, fmt: "red: %d, green: %d, blue: %d\n" , |
328 | target_zone->colors.red, |
329 | target_zone->colors.green, target_zone->colors.blue); |
330 | |
331 | } |
332 | |
333 | static ssize_t zone_set(struct device *dev, struct device_attribute *attr, |
334 | const char *buf, size_t count) |
335 | { |
336 | struct platform_zone *target_zone; |
337 | int ret; |
338 | target_zone = match_zone(attr); |
339 | if (target_zone == NULL) { |
340 | pr_err("alienware-wmi: invalid target zone\n" ); |
341 | return 1; |
342 | } |
343 | ret = parse_rgb(buf, zone: target_zone); |
344 | if (ret) |
345 | return ret; |
346 | ret = alienware_update_led(zone: target_zone); |
347 | return ret ? ret : count; |
348 | } |
349 | |
350 | /* |
351 | * LED Brightness (Global) |
352 | */ |
353 | static int wmax_brightness(int brightness) |
354 | { |
355 | acpi_status status; |
356 | struct acpi_buffer input; |
357 | struct wmax_brightness_args args = { |
358 | .led_mask = 0xFF, |
359 | .percentage = brightness, |
360 | }; |
361 | input.length = (acpi_size) sizeof(args); |
362 | input.pointer = &args; |
363 | status = wmi_evaluate_method(WMAX_CONTROL_GUID, instance: 0, |
364 | WMAX_METHOD_BRIGHTNESS, in: &input, NULL); |
365 | if (ACPI_FAILURE(status)) |
366 | pr_err("alienware-wmi: brightness set failure: %u\n" , status); |
367 | return ACPI_FAILURE(status); |
368 | } |
369 | |
370 | static void global_led_set(struct led_classdev *led_cdev, |
371 | enum led_brightness brightness) |
372 | { |
373 | int ret; |
374 | global_brightness = brightness; |
375 | if (interface == WMAX) |
376 | ret = wmax_brightness(brightness); |
377 | else |
378 | ret = alienware_update_led(zone: &zone_data[0]); |
379 | if (ret) |
380 | pr_err("LED brightness update failed\n" ); |
381 | } |
382 | |
383 | static enum led_brightness global_led_get(struct led_classdev *led_cdev) |
384 | { |
385 | return global_brightness; |
386 | } |
387 | |
388 | static struct led_classdev global_led = { |
389 | .brightness_set = global_led_set, |
390 | .brightness_get = global_led_get, |
391 | .name = "alienware::global_brightness" , |
392 | }; |
393 | |
394 | /* |
395 | * Lighting control state device attribute (Global) |
396 | */ |
397 | static ssize_t show_control_state(struct device *dev, |
398 | struct device_attribute *attr, char *buf) |
399 | { |
400 | if (lighting_control_state == LEGACY_BOOTING) |
401 | return sysfs_emit(buf, fmt: "[booting] running suspend\n" ); |
402 | else if (lighting_control_state == LEGACY_SUSPEND) |
403 | return sysfs_emit(buf, fmt: "booting running [suspend]\n" ); |
404 | return sysfs_emit(buf, fmt: "booting [running] suspend\n" ); |
405 | } |
406 | |
407 | static ssize_t store_control_state(struct device *dev, |
408 | struct device_attribute *attr, |
409 | const char *buf, size_t count) |
410 | { |
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; |
418 | else |
419 | val = WMAX_RUNNING; |
420 | lighting_control_state = val; |
421 | pr_debug("alienware-wmi: updated control state to %d\n" , |
422 | lighting_control_state); |
423 | return count; |
424 | } |
425 | |
426 | static DEVICE_ATTR(lighting_control_state, 0644, show_control_state, |
427 | store_control_state); |
428 | |
429 | static int alienware_zone_init(struct platform_device *dev) |
430 | { |
431 | u8 zone; |
432 | char *name; |
433 | |
434 | if (interface == WMAX) { |
435 | lighting_control_state = WMAX_RUNNING; |
436 | } else if (interface == LEGACY) { |
437 | lighting_control_state = LEGACY_RUNNING; |
438 | } |
439 | global_led.max_brightness = 0x0F; |
440 | global_brightness = global_led.max_brightness; |
441 | |
442 | /* |
443 | * - zone_dev_attrs num_zones + 1 is for individual zones and then |
444 | * null terminated |
445 | * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs + |
446 | * the lighting control + null terminated |
447 | * - zone_data num_zones is for the distinct zones |
448 | */ |
449 | zone_dev_attrs = |
450 | kcalloc(n: quirks->num_zones + 1, size: sizeof(struct device_attribute), |
451 | GFP_KERNEL); |
452 | if (!zone_dev_attrs) |
453 | return -ENOMEM; |
454 | |
455 | zone_attrs = |
456 | kcalloc(n: quirks->num_zones + 2, size: sizeof(struct attribute *), |
457 | GFP_KERNEL); |
458 | if (!zone_attrs) |
459 | return -ENOMEM; |
460 | |
461 | zone_data = |
462 | kcalloc(n: quirks->num_zones, size: sizeof(struct platform_zone), |
463 | GFP_KERNEL); |
464 | if (!zone_data) |
465 | return -ENOMEM; |
466 | |
467 | for (zone = 0; zone < quirks->num_zones; zone++) { |
468 | name = kasprintf(GFP_KERNEL, fmt: "zone%02hhX" , zone); |
469 | if (name == NULL) |
470 | return 1; |
471 | sysfs_attr_init(&zone_dev_attrs[zone].attr); |
472 | zone_dev_attrs[zone].attr.name = name; |
473 | zone_dev_attrs[zone].attr.mode = 0644; |
474 | zone_dev_attrs[zone].show = zone_show; |
475 | zone_dev_attrs[zone].store = zone_set; |
476 | zone_data[zone].location = zone; |
477 | zone_attrs[zone] = &zone_dev_attrs[zone].attr; |
478 | zone_data[zone].attr = &zone_dev_attrs[zone]; |
479 | } |
480 | zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr; |
481 | zone_attribute_group.attrs = zone_attrs; |
482 | |
483 | led_classdev_register(parent: &dev->dev, led_cdev: &global_led); |
484 | |
485 | return sysfs_create_group(kobj: &dev->dev.kobj, grp: &zone_attribute_group); |
486 | } |
487 | |
488 | static void alienware_zone_exit(struct platform_device *dev) |
489 | { |
490 | u8 zone; |
491 | |
492 | sysfs_remove_group(kobj: &dev->dev.kobj, grp: &zone_attribute_group); |
493 | led_classdev_unregister(led_cdev: &global_led); |
494 | if (zone_dev_attrs) { |
495 | for (zone = 0; zone < quirks->num_zones; zone++) |
496 | kfree(objp: zone_dev_attrs[zone].attr.name); |
497 | } |
498 | kfree(objp: zone_dev_attrs); |
499 | kfree(objp: zone_data); |
500 | kfree(objp: zone_attrs); |
501 | } |
502 | |
503 | static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args, |
504 | u32 command, int *out_data) |
505 | { |
506 | acpi_status status; |
507 | union acpi_object *obj; |
508 | struct acpi_buffer input; |
509 | struct acpi_buffer output; |
510 | |
511 | input.length = (acpi_size) sizeof(*in_args); |
512 | input.pointer = in_args; |
513 | if (out_data) { |
514 | output.length = ACPI_ALLOCATE_BUFFER; |
515 | output.pointer = NULL; |
516 | status = wmi_evaluate_method(WMAX_CONTROL_GUID, instance: 0, |
517 | method_id: command, in: &input, out: &output); |
518 | if (ACPI_SUCCESS(status)) { |
519 | obj = (union acpi_object *)output.pointer; |
520 | if (obj && obj->type == ACPI_TYPE_INTEGER) |
521 | *out_data = (u32)obj->integer.value; |
522 | } |
523 | kfree(objp: output.pointer); |
524 | } else { |
525 | status = wmi_evaluate_method(WMAX_CONTROL_GUID, instance: 0, |
526 | method_id: command, in: &input, NULL); |
527 | } |
528 | return status; |
529 | } |
530 | |
531 | /* |
532 | * The HDMI mux sysfs node indicates the status of the HDMI input mux. |
533 | * It can toggle between standard system GPU output and HDMI input. |
534 | */ |
535 | static ssize_t show_hdmi_cable(struct device *dev, |
536 | struct device_attribute *attr, char *buf) |
537 | { |
538 | acpi_status status; |
539 | u32 out_data; |
540 | struct wmax_basic_args in_args = { |
541 | .arg = 0, |
542 | }; |
543 | status = |
544 | alienware_wmax_command(in_args: &in_args, WMAX_METHOD_HDMI_CABLE, |
545 | out_data: (u32 *) &out_data); |
546 | if (ACPI_SUCCESS(status)) { |
547 | if (out_data == 0) |
548 | return sysfs_emit(buf, fmt: "[unconnected] connected unknown\n" ); |
549 | else if (out_data == 1) |
550 | return sysfs_emit(buf, fmt: "unconnected [connected] unknown\n" ); |
551 | } |
552 | pr_err("alienware-wmi: unknown HDMI cable status: %d\n" , status); |
553 | return sysfs_emit(buf, fmt: "unconnected connected [unknown]\n" ); |
554 | } |
555 | |
556 | static ssize_t show_hdmi_source(struct device *dev, |
557 | struct device_attribute *attr, char *buf) |
558 | { |
559 | acpi_status status; |
560 | u32 out_data; |
561 | struct wmax_basic_args in_args = { |
562 | .arg = 0, |
563 | }; |
564 | status = |
565 | alienware_wmax_command(in_args: &in_args, WMAX_METHOD_HDMI_STATUS, |
566 | out_data: (u32 *) &out_data); |
567 | |
568 | if (ACPI_SUCCESS(status)) { |
569 | if (out_data == 1) |
570 | return sysfs_emit(buf, fmt: "[input] gpu unknown\n" ); |
571 | else if (out_data == 2) |
572 | return sysfs_emit(buf, fmt: "input [gpu] unknown\n" ); |
573 | } |
574 | pr_err("alienware-wmi: unknown HDMI source status: %u\n" , status); |
575 | return sysfs_emit(buf, fmt: "input gpu [unknown]\n" ); |
576 | } |
577 | |
578 | static ssize_t toggle_hdmi_source(struct device *dev, |
579 | struct device_attribute *attr, |
580 | const char *buf, size_t count) |
581 | { |
582 | acpi_status status; |
583 | struct wmax_basic_args args; |
584 | if (strcmp(buf, "gpu\n" ) == 0) |
585 | args.arg = 1; |
586 | else if (strcmp(buf, "input\n" ) == 0) |
587 | args.arg = 2; |
588 | else |
589 | args.arg = 3; |
590 | pr_debug("alienware-wmi: setting hdmi to %d : %s" , args.arg, buf); |
591 | |
592 | status = alienware_wmax_command(in_args: &args, WMAX_METHOD_HDMI_SOURCE, NULL); |
593 | |
594 | if (ACPI_FAILURE(status)) |
595 | pr_err("alienware-wmi: HDMI toggle failed: results: %u\n" , |
596 | status); |
597 | return count; |
598 | } |
599 | |
600 | static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL); |
601 | static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source, |
602 | toggle_hdmi_source); |
603 | |
604 | static struct attribute *hdmi_attrs[] = { |
605 | &dev_attr_cable.attr, |
606 | &dev_attr_source.attr, |
607 | NULL, |
608 | }; |
609 | |
610 | static const struct attribute_group hdmi_attribute_group = { |
611 | .name = "hdmi" , |
612 | .attrs = hdmi_attrs, |
613 | }; |
614 | |
615 | static void remove_hdmi(struct platform_device *dev) |
616 | { |
617 | if (quirks->hdmi_mux > 0) |
618 | sysfs_remove_group(kobj: &dev->dev.kobj, grp: &hdmi_attribute_group); |
619 | } |
620 | |
621 | static int create_hdmi(struct platform_device *dev) |
622 | { |
623 | int ret; |
624 | |
625 | ret = sysfs_create_group(kobj: &dev->dev.kobj, grp: &hdmi_attribute_group); |
626 | if (ret) |
627 | remove_hdmi(dev); |
628 | return ret; |
629 | } |
630 | |
631 | /* |
632 | * Alienware GFX amplifier support |
633 | * - Currently supports reading cable status |
634 | * - Leaving expansion room to possibly support dock/undock events later |
635 | */ |
636 | static ssize_t show_amplifier_status(struct device *dev, |
637 | struct device_attribute *attr, char *buf) |
638 | { |
639 | acpi_status status; |
640 | u32 out_data; |
641 | struct wmax_basic_args in_args = { |
642 | .arg = 0, |
643 | }; |
644 | status = |
645 | alienware_wmax_command(in_args: &in_args, WMAX_METHOD_AMPLIFIER_CABLE, |
646 | out_data: (u32 *) &out_data); |
647 | if (ACPI_SUCCESS(status)) { |
648 | if (out_data == 0) |
649 | return sysfs_emit(buf, fmt: "[unconnected] connected unknown\n" ); |
650 | else if (out_data == 1) |
651 | return sysfs_emit(buf, fmt: "unconnected [connected] unknown\n" ); |
652 | } |
653 | pr_err("alienware-wmi: unknown amplifier cable status: %d\n" , status); |
654 | return sysfs_emit(buf, fmt: "unconnected connected [unknown]\n" ); |
655 | } |
656 | |
657 | static DEVICE_ATTR(status, S_IRUGO, show_amplifier_status, NULL); |
658 | |
659 | static struct attribute *amplifier_attrs[] = { |
660 | &dev_attr_status.attr, |
661 | NULL, |
662 | }; |
663 | |
664 | static const struct attribute_group amplifier_attribute_group = { |
665 | .name = "amplifier" , |
666 | .attrs = amplifier_attrs, |
667 | }; |
668 | |
669 | static void remove_amplifier(struct platform_device *dev) |
670 | { |
671 | if (quirks->amplifier > 0) |
672 | sysfs_remove_group(kobj: &dev->dev.kobj, grp: &lifier_attribute_group); |
673 | } |
674 | |
675 | static int create_amplifier(struct platform_device *dev) |
676 | { |
677 | int ret; |
678 | |
679 | ret = sysfs_create_group(kobj: &dev->dev.kobj, grp: &lifier_attribute_group); |
680 | if (ret) |
681 | remove_amplifier(dev); |
682 | return ret; |
683 | } |
684 | |
685 | /* |
686 | * Deep Sleep Control support |
687 | * - Modifies BIOS setting for deep sleep control allowing extra wakeup events |
688 | */ |
689 | static ssize_t show_deepsleep_status(struct device *dev, |
690 | struct device_attribute *attr, char *buf) |
691 | { |
692 | acpi_status status; |
693 | u32 out_data; |
694 | struct wmax_basic_args in_args = { |
695 | .arg = 0, |
696 | }; |
697 | status = alienware_wmax_command(in_args: &in_args, WMAX_METHOD_DEEP_SLEEP_STATUS, |
698 | out_data: (u32 *) &out_data); |
699 | if (ACPI_SUCCESS(status)) { |
700 | if (out_data == 0) |
701 | return sysfs_emit(buf, fmt: "[disabled] s5 s5_s4\n" ); |
702 | else if (out_data == 1) |
703 | return sysfs_emit(buf, fmt: "disabled [s5] s5_s4\n" ); |
704 | else if (out_data == 2) |
705 | return sysfs_emit(buf, fmt: "disabled s5 [s5_s4]\n" ); |
706 | } |
707 | pr_err("alienware-wmi: unknown deep sleep status: %d\n" , status); |
708 | return sysfs_emit(buf, fmt: "disabled s5 s5_s4 [unknown]\n" ); |
709 | } |
710 | |
711 | static ssize_t toggle_deepsleep(struct device *dev, |
712 | struct device_attribute *attr, |
713 | const char *buf, size_t count) |
714 | { |
715 | acpi_status status; |
716 | struct wmax_basic_args args; |
717 | |
718 | if (strcmp(buf, "disabled\n" ) == 0) |
719 | args.arg = 0; |
720 | else if (strcmp(buf, "s5\n" ) == 0) |
721 | args.arg = 1; |
722 | else |
723 | args.arg = 2; |
724 | pr_debug("alienware-wmi: setting deep sleep to %d : %s" , args.arg, buf); |
725 | |
726 | status = alienware_wmax_command(in_args: &args, WMAX_METHOD_DEEP_SLEEP_CONTROL, |
727 | NULL); |
728 | |
729 | if (ACPI_FAILURE(status)) |
730 | pr_err("alienware-wmi: deep sleep control failed: results: %u\n" , |
731 | status); |
732 | return count; |
733 | } |
734 | |
735 | static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep); |
736 | |
737 | static struct attribute *deepsleep_attrs[] = { |
738 | &dev_attr_deepsleep.attr, |
739 | NULL, |
740 | }; |
741 | |
742 | static const struct attribute_group deepsleep_attribute_group = { |
743 | .name = "deepsleep" , |
744 | .attrs = deepsleep_attrs, |
745 | }; |
746 | |
747 | static void remove_deepsleep(struct platform_device *dev) |
748 | { |
749 | if (quirks->deepslp > 0) |
750 | sysfs_remove_group(kobj: &dev->dev.kobj, grp: &deepsleep_attribute_group); |
751 | } |
752 | |
753 | static int create_deepsleep(struct platform_device *dev) |
754 | { |
755 | int ret; |
756 | |
757 | ret = sysfs_create_group(kobj: &dev->dev.kobj, grp: &deepsleep_attribute_group); |
758 | if (ret) |
759 | remove_deepsleep(dev); |
760 | return ret; |
761 | } |
762 | |
763 | static int __init alienware_wmi_init(void) |
764 | { |
765 | int ret; |
766 | |
767 | if (wmi_has_guid(LEGACY_CONTROL_GUID)) |
768 | interface = LEGACY; |
769 | else if (wmi_has_guid(WMAX_CONTROL_GUID)) |
770 | interface = WMAX; |
771 | else { |
772 | pr_warn("alienware-wmi: No known WMI GUID found\n" ); |
773 | return -ENODEV; |
774 | } |
775 | |
776 | dmi_check_system(list: alienware_quirks); |
777 | if (quirks == NULL) |
778 | quirks = &quirk_unknown; |
779 | |
780 | ret = platform_driver_register(&platform_driver); |
781 | if (ret) |
782 | goto fail_platform_driver; |
783 | platform_device = platform_device_alloc(name: "alienware-wmi" , PLATFORM_DEVID_NONE); |
784 | if (!platform_device) { |
785 | ret = -ENOMEM; |
786 | goto fail_platform_device1; |
787 | } |
788 | ret = platform_device_add(pdev: platform_device); |
789 | if (ret) |
790 | goto fail_platform_device2; |
791 | |
792 | if (quirks->hdmi_mux > 0) { |
793 | ret = create_hdmi(dev: platform_device); |
794 | if (ret) |
795 | goto fail_prep_hdmi; |
796 | } |
797 | |
798 | if (quirks->amplifier > 0) { |
799 | ret = create_amplifier(dev: platform_device); |
800 | if (ret) |
801 | goto fail_prep_amplifier; |
802 | } |
803 | |
804 | if (quirks->deepslp > 0) { |
805 | ret = create_deepsleep(dev: platform_device); |
806 | if (ret) |
807 | goto fail_prep_deepsleep; |
808 | } |
809 | |
810 | ret = alienware_zone_init(dev: platform_device); |
811 | if (ret) |
812 | goto fail_prep_zones; |
813 | |
814 | return 0; |
815 | |
816 | fail_prep_zones: |
817 | alienware_zone_exit(dev: platform_device); |
818 | fail_prep_deepsleep: |
819 | fail_prep_amplifier: |
820 | fail_prep_hdmi: |
821 | platform_device_del(pdev: platform_device); |
822 | fail_platform_device2: |
823 | platform_device_put(pdev: platform_device); |
824 | fail_platform_device1: |
825 | platform_driver_unregister(&platform_driver); |
826 | fail_platform_driver: |
827 | return ret; |
828 | } |
829 | |
830 | module_init(alienware_wmi_init); |
831 | |
832 | static void __exit alienware_wmi_exit(void) |
833 | { |
834 | if (platform_device) { |
835 | alienware_zone_exit(dev: platform_device); |
836 | remove_hdmi(dev: platform_device); |
837 | platform_device_unregister(platform_device); |
838 | platform_driver_unregister(&platform_driver); |
839 | } |
840 | } |
841 | |
842 | module_exit(alienware_wmi_exit); |
843 | |