1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Alienware AlienFX control
4 *
5 * Copyright (C) 2014 Dell Inc <[email protected]>
6 */
7
8 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9
10 #include <linux/acpi.h>
11 #include <linux/bitfield.h>
12 #include <linux/bits.h>
13 #include <linux/module.h>
14 #include <linux/platform_device.h>
15 #include <linux/platform_profile.h>
16 #include <linux/dmi.h>
17 #include <linux/leds.h>
18
19 #define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492"
20 #define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
21 #define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492"
22
23 #define WMAX_METHOD_HDMI_SOURCE 0x1
24 #define WMAX_METHOD_HDMI_STATUS 0x2
25 #define WMAX_METHOD_BRIGHTNESS 0x3
26 #define WMAX_METHOD_ZONE_CONTROL 0x4
27 #define WMAX_METHOD_HDMI_CABLE 0x5
28 #define WMAX_METHOD_AMPLIFIER_CABLE 0x6
29 #define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B
30 #define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C
31 #define WMAX_METHOD_THERMAL_INFORMATION 0x14
32 #define WMAX_METHOD_THERMAL_CONTROL 0x15
33 #define WMAX_METHOD_GAME_SHIFT_STATUS 0x25
34
35 #define WMAX_THERMAL_MODE_GMODE 0xAB
36
37 #define WMAX_FAILURE_CODE 0xFFFFFFFF
38
39 MODULE_AUTHOR("Mario Limonciello <[email protected]>");
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);
44
45 static bool force_platform_profile;
46 module_param_unsafe(force_platform_profile, bool, 0);
47 MODULE_PARM_DESC(force_platform_profile, "Forces auto-detecting thermal profiles without checking if WMI thermal backend is available");
48
49 static bool force_gmode;
50 module_param_unsafe(force_gmode, bool, 0);
51 MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected");
52
53 enum INTERFACE_FLAGS {
54 LEGACY,
55 WMAX,
56 };
57
58 enum LEGACY_CONTROL_STATES {
59 LEGACY_RUNNING = 1,
60 LEGACY_BOOTING = 0,
61 LEGACY_SUSPEND = 3,
62 };
63
64 enum WMAX_CONTROL_STATES {
65 WMAX_RUNNING = 0xFF,
66 WMAX_BOOTING = 0,
67 WMAX_SUSPEND = 3,
68 };
69
70 enum WMAX_THERMAL_INFORMATION_OPERATIONS {
71 WMAX_OPERATION_SYS_DESCRIPTION = 0x02,
72 WMAX_OPERATION_LIST_IDS = 0x03,
73 WMAX_OPERATION_CURRENT_PROFILE = 0x0B,
74 };
75
76 enum WMAX_THERMAL_CONTROL_OPERATIONS {
77 WMAX_OPERATION_ACTIVATE_PROFILE = 0x01,
78 };
79
80 enum WMAX_GAME_SHIFT_STATUS_OPERATIONS {
81 WMAX_OPERATION_TOGGLE_GAME_SHIFT = 0x01,
82 WMAX_OPERATION_GET_GAME_SHIFT_STATUS = 0x02,
83 };
84
85 enum WMAX_THERMAL_TABLES {
86 WMAX_THERMAL_TABLE_BASIC = 0x90,
87 WMAX_THERMAL_TABLE_USTT = 0xA0,
88 };
89
90 enum wmax_thermal_mode {
91 THERMAL_MODE_USTT_BALANCED,
92 THERMAL_MODE_USTT_BALANCED_PERFORMANCE,
93 THERMAL_MODE_USTT_COOL,
94 THERMAL_MODE_USTT_QUIET,
95 THERMAL_MODE_USTT_PERFORMANCE,
96 THERMAL_MODE_USTT_LOW_POWER,
97 THERMAL_MODE_BASIC_QUIET,
98 THERMAL_MODE_BASIC_BALANCED,
99 THERMAL_MODE_BASIC_BALANCED_PERFORMANCE,
100 THERMAL_MODE_BASIC_PERFORMANCE,
101 THERMAL_MODE_LAST,
102 };
103
104 static const enum platform_profile_option wmax_mode_to_platform_profile[THERMAL_MODE_LAST] = {
105 [THERMAL_MODE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED,
106 [THERMAL_MODE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
107 [THERMAL_MODE_USTT_COOL] = PLATFORM_PROFILE_COOL,
108 [THERMAL_MODE_USTT_QUIET] = PLATFORM_PROFILE_QUIET,
109 [THERMAL_MODE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
110 [THERMAL_MODE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER,
111 [THERMAL_MODE_BASIC_QUIET] = PLATFORM_PROFILE_QUIET,
112 [THERMAL_MODE_BASIC_BALANCED] = PLATFORM_PROFILE_BALANCED,
113 [THERMAL_MODE_BASIC_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
114 [THERMAL_MODE_BASIC_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
115 };
116
117 struct quirk_entry {
118 u8 num_zones;
119 u8 hdmi_mux;
120 u8 amplifier;
121 u8 deepslp;
122 bool thermal;
123 bool gmode;
124 };
125
126 static struct quirk_entry *quirks;
127
128
129 static struct quirk_entry quirk_inspiron5675 = {
130 .num_zones = 2,
131 .hdmi_mux = 0,
132 .amplifier = 0,
133 .deepslp = 0,
134 .thermal = false,
135 .gmode = false,
136 };
137
138 static struct quirk_entry quirk_unknown = {
139 .num_zones = 2,
140 .hdmi_mux = 0,
141 .amplifier = 0,
142 .deepslp = 0,
143 .thermal = false,
144 .gmode = false,
145 };
146
147 static struct quirk_entry quirk_x51_r1_r2 = {
148 .num_zones = 3,
149 .hdmi_mux = 0,
150 .amplifier = 0,
151 .deepslp = 0,
152 .thermal = false,
153 .gmode = false,
154 };
155
156 static struct quirk_entry quirk_x51_r3 = {
157 .num_zones = 4,
158 .hdmi_mux = 0,
159 .amplifier = 1,
160 .deepslp = 0,
161 .thermal = false,
162 .gmode = false,
163 };
164
165 static struct quirk_entry quirk_asm100 = {
166 .num_zones = 2,
167 .hdmi_mux = 1,
168 .amplifier = 0,
169 .deepslp = 0,
170 .thermal = false,
171 .gmode = false,
172 };
173
174 static struct quirk_entry quirk_asm200 = {
175 .num_zones = 2,
176 .hdmi_mux = 1,
177 .amplifier = 0,
178 .deepslp = 1,
179 .thermal = false,
180 .gmode = false,
181 };
182
183 static struct quirk_entry quirk_asm201 = {
184 .num_zones = 2,
185 .hdmi_mux = 1,
186 .amplifier = 1,
187 .deepslp = 1,
188 .thermal = false,
189 .gmode = false,
190 };
191
192 static struct quirk_entry quirk_g_series = {
193 .num_zones = 0,
194 .hdmi_mux = 0,
195 .amplifier = 0,
196 .deepslp = 0,
197 .thermal = true,
198 .gmode = true,
199 };
200
201 static struct quirk_entry quirk_x_series = {
202 .num_zones = 0,
203 .hdmi_mux = 0,
204 .amplifier = 0,
205 .deepslp = 0,
206 .thermal = true,
207 .gmode = false,
208 };
209
dmi_matched(const struct dmi_system_id * dmi)210 static int __init dmi_matched(const struct dmi_system_id *dmi)
211 {
212 quirks = dmi->driver_data;
213 return 1;
214 }
215
216 static const struct dmi_system_id alienware_quirks[] __initconst = {
217 {
218 .callback = dmi_matched,
219 .ident = "Alienware Area-51m R2",
220 .matches = {
221 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
222 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware Area-51m R2"),
223 },
224 .driver_data = &quirk_x_series,
225 },
226 {
227 .callback = dmi_matched,
228 .ident = "Alienware ASM100",
229 .matches = {
230 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
231 DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"),
232 },
233 .driver_data = &quirk_asm100,
234 },
235 {
236 .callback = dmi_matched,
237 .ident = "Alienware ASM200",
238 .matches = {
239 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
240 DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"),
241 },
242 .driver_data = &quirk_asm200,
243 },
244 {
245 .callback = dmi_matched,
246 .ident = "Alienware ASM201",
247 .matches = {
248 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
249 DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"),
250 },
251 .driver_data = &quirk_asm201,
252 },
253 {
254 .callback = dmi_matched,
255 .ident = "Alienware m16 R1",
256 .matches = {
257 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
258 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R1"),
259 },
260 .driver_data = &quirk_g_series,
261 },
262 {
263 .callback = dmi_matched,
264 .ident = "Alienware m16 R1 AMD",
265 .matches = {
266 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
267 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R1 AMD"),
268 },
269 .driver_data = &quirk_g_series,
270 },
271 {
272 .callback = dmi_matched,
273 .ident = "Alienware m17 R5",
274 .matches = {
275 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
276 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"),
277 },
278 .driver_data = &quirk_x_series,
279 },
280 {
281 .callback = dmi_matched,
282 .ident = "Alienware m16 R2",
283 .matches = {
284 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
285 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R2"),
286 },
287 .driver_data = &quirk_x_series,
288 },
289 {
290 .callback = dmi_matched,
291 .ident = "Alienware m18 R2",
292 .matches = {
293 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
294 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R2"),
295 },
296 .driver_data = &quirk_x_series,
297 },
298 {
299 .callback = dmi_matched,
300 .ident = "Alienware x15 R1",
301 .matches = {
302 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
303 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R1"),
304 },
305 .driver_data = &quirk_x_series,
306 },
307 {
308 .callback = dmi_matched,
309 .ident = "Alienware x15 R2",
310 .matches = {
311 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
312 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R2"),
313 },
314 .driver_data = &quirk_x_series,
315 },
316 {
317 .callback = dmi_matched,
318 .ident = "Alienware x17 R2",
319 .matches = {
320 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
321 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x17 R2"),
322 },
323 .driver_data = &quirk_x_series,
324 },
325 {
326 .callback = dmi_matched,
327 .ident = "Alienware X51 R1",
328 .matches = {
329 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
330 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
331 },
332 .driver_data = &quirk_x51_r1_r2,
333 },
334 {
335 .callback = dmi_matched,
336 .ident = "Alienware X51 R2",
337 .matches = {
338 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
339 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),
340 },
341 .driver_data = &quirk_x51_r1_r2,
342 },
343 {
344 .callback = dmi_matched,
345 .ident = "Alienware X51 R3",
346 .matches = {
347 DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
348 DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"),
349 },
350 .driver_data = &quirk_x51_r3,
351 },
352 {
353 .callback = dmi_matched,
354 .ident = "Dell Inc. G15 5510",
355 .matches = {
356 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
357 DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5510"),
358 },
359 .driver_data = &quirk_g_series,
360 },
361 {
362 .callback = dmi_matched,
363 .ident = "Dell Inc. G15 5511",
364 .matches = {
365 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
366 DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"),
367 },
368 .driver_data = &quirk_g_series,
369 },
370 {
371 .callback = dmi_matched,
372 .ident = "Dell Inc. G15 5515",
373 .matches = {
374 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
375 DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5515"),
376 },
377 .driver_data = &quirk_g_series,
378 },
379 {
380 .callback = dmi_matched,
381 .ident = "Dell Inc. G16 7630",
382 .matches = {
383 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
384 DMI_MATCH(DMI_PRODUCT_NAME, "Dell G16 7630"),
385 },
386 .driver_data = &quirk_g_series,
387 },
388 {
389 .callback = dmi_matched,
390 .ident = "Dell Inc. G3 3500",
391 .matches = {
392 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
393 DMI_MATCH(DMI_PRODUCT_NAME, "G3 3500"),
394 },
395 .driver_data = &quirk_g_series,
396 },
397 {
398 .callback = dmi_matched,
399 .ident = "Dell Inc. G3 3590",
400 .matches = {
401 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
402 DMI_MATCH(DMI_PRODUCT_NAME, "G3 3590"),
403 },
404 .driver_data = &quirk_g_series,
405 },
406 {
407 .callback = dmi_matched,
408 .ident = "Dell Inc. G5 5500",
409 .matches = {
410 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
411 DMI_MATCH(DMI_PRODUCT_NAME, "G5 5500"),
412 },
413 .driver_data = &quirk_g_series,
414 },
415 {
416 .callback = dmi_matched,
417 .ident = "Dell Inc. G5 5505",
418 .matches = {
419 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
420 DMI_MATCH(DMI_PRODUCT_NAME, "G5 5505"),
421 },
422 .driver_data = &quirk_g_series,
423 },
424 {
425 .callback = dmi_matched,
426 .ident = "Dell Inc. Inspiron 5675",
427 .matches = {
428 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
429 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"),
430 },
431 .driver_data = &quirk_inspiron5675,
432 },
433 {}
434 };
435
436 struct color_platform {
437 u8 blue;
438 u8 green;
439 u8 red;
440 } __packed;
441
442 struct wmax_brightness_args {
443 u32 led_mask;
444 u32 percentage;
445 };
446
447 struct wmax_basic_args {
448 u8 arg;
449 };
450
451 struct legacy_led_args {
452 struct color_platform colors;
453 u8 brightness;
454 u8 state;
455 } __packed;
456
457 struct wmax_led_args {
458 u32 led_mask;
459 struct color_platform colors;
460 u8 state;
461 } __packed;
462
463 struct wmax_u32_args {
464 u8 operation;
465 u8 arg1;
466 u8 arg2;
467 u8 arg3;
468 };
469
470 static struct platform_device *platform_device;
471 static struct color_platform colors[4];
472 static enum wmax_thermal_mode supported_thermal_profiles[PLATFORM_PROFILE_LAST];
473
474 static u8 interface;
475 static u8 lighting_control_state;
476 static u8 global_brightness;
477
478 /*
479 * Helpers used for zone control
480 */
parse_rgb(const char * buf,struct color_platform * colors)481 static int parse_rgb(const char *buf, struct color_platform *colors)
482 {
483 long unsigned int rgb;
484 int ret;
485 union color_union {
486 struct color_platform cp;
487 int package;
488 } repackager;
489
490 ret = kstrtoul(buf, 16, &rgb);
491 if (ret)
492 return ret;
493
494 /* RGB triplet notation is 24-bit hexadecimal */
495 if (rgb > 0xFFFFFF)
496 return -EINVAL;
497
498 repackager.package = rgb & 0x0f0f0f0f;
499 pr_debug("alienware-wmi: r: %d g:%d b: %d\n",
500 repackager.cp.red, repackager.cp.green, repackager.cp.blue);
501 *colors = repackager.cp;
502 return 0;
503 }
504
505 /*
506 * Individual RGB zone control
507 */
alienware_update_led(u8 location)508 static int alienware_update_led(u8 location)
509 {
510 int method_id;
511 acpi_status status;
512 char *guid;
513 struct acpi_buffer input;
514 struct legacy_led_args legacy_args;
515 struct wmax_led_args wmax_basic_args;
516 if (interface == WMAX) {
517 wmax_basic_args.led_mask = 1 << location;
518 wmax_basic_args.colors = colors[location];
519 wmax_basic_args.state = lighting_control_state;
520 guid = WMAX_CONTROL_GUID;
521 method_id = WMAX_METHOD_ZONE_CONTROL;
522
523 input.length = sizeof(wmax_basic_args);
524 input.pointer = &wmax_basic_args;
525 } else {
526 legacy_args.colors = colors[location];
527 legacy_args.brightness = global_brightness;
528 legacy_args.state = 0;
529 if (lighting_control_state == LEGACY_BOOTING ||
530 lighting_control_state == LEGACY_SUSPEND) {
531 guid = LEGACY_POWER_CONTROL_GUID;
532 legacy_args.state = lighting_control_state;
533 } else
534 guid = LEGACY_CONTROL_GUID;
535 method_id = location + 1;
536
537 input.length = sizeof(legacy_args);
538 input.pointer = &legacy_args;
539 }
540 pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id);
541
542 status = wmi_evaluate_method(guid, 0, method_id, &input, NULL);
543 if (ACPI_FAILURE(status))
544 pr_err("alienware-wmi: zone set failure: %u\n", status);
545 return ACPI_FAILURE(status);
546 }
547
zone_show(struct device * dev,struct device_attribute * attr,char * buf,u8 location)548 static ssize_t zone_show(struct device *dev, struct device_attribute *attr,
549 char *buf, u8 location)
550 {
551 return sprintf(buf, "red: %d, green: %d, blue: %d\n",
552 colors[location].red, colors[location].green,
553 colors[location].blue);
554
555 }
556
zone_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count,u8 location)557 static ssize_t zone_store(struct device *dev, struct device_attribute *attr,
558 const char *buf, size_t count, u8 location)
559 {
560 int ret;
561
562 ret = parse_rgb(buf, &colors[location]);
563 if (ret)
564 return ret;
565
566 ret = alienware_update_led(location);
567
568 return ret ? ret : count;
569 }
570
zone00_show(struct device * dev,struct device_attribute * attr,char * buf)571 static ssize_t zone00_show(struct device *dev, struct device_attribute *attr,
572 char *buf)
573 {
574 return zone_show(dev, attr, buf, 0);
575 }
576
zone00_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)577 static ssize_t zone00_store(struct device *dev, struct device_attribute *attr,
578 const char *buf, size_t count)
579 {
580 return zone_store(dev, attr, buf, count, 0);
581 }
582
583 static DEVICE_ATTR_RW(zone00);
584
zone01_show(struct device * dev,struct device_attribute * attr,char * buf)585 static ssize_t zone01_show(struct device *dev, struct device_attribute *attr,
586 char *buf)
587 {
588 return zone_show(dev, attr, buf, 1);
589 }
590
zone01_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)591 static ssize_t zone01_store(struct device *dev, struct device_attribute *attr,
592 const char *buf, size_t count)
593 {
594 return zone_store(dev, attr, buf, count, 1);
595 }
596
597 static DEVICE_ATTR_RW(zone01);
598
zone02_show(struct device * dev,struct device_attribute * attr,char * buf)599 static ssize_t zone02_show(struct device *dev, struct device_attribute *attr,
600 char *buf)
601 {
602 return zone_show(dev, attr, buf, 2);
603 }
604
zone02_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)605 static ssize_t zone02_store(struct device *dev, struct device_attribute *attr,
606 const char *buf, size_t count)
607 {
608 return zone_store(dev, attr, buf, count, 2);
609 }
610
611 static DEVICE_ATTR_RW(zone02);
612
zone03_show(struct device * dev,struct device_attribute * attr,char * buf)613 static ssize_t zone03_show(struct device *dev, struct device_attribute *attr,
614 char *buf)
615 {
616 return zone_show(dev, attr, buf, 3);
617 }
618
zone03_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)619 static ssize_t zone03_store(struct device *dev, struct device_attribute *attr,
620 const char *buf, size_t count)
621 {
622 return zone_store(dev, attr, buf, count, 3);
623 }
624
625 static DEVICE_ATTR_RW(zone03);
626
627 /*
628 * Lighting control state device attribute (Global)
629 */
lighting_control_state_show(struct device * dev,struct device_attribute * attr,char * buf)630 static ssize_t lighting_control_state_show(struct device *dev,
631 struct device_attribute *attr,
632 char *buf)
633 {
634 if (lighting_control_state == LEGACY_BOOTING)
635 return sysfs_emit(buf, "[booting] running suspend\n");
636 else if (lighting_control_state == LEGACY_SUSPEND)
637 return sysfs_emit(buf, "booting running [suspend]\n");
638
639 return sysfs_emit(buf, "booting [running] suspend\n");
640 }
641
lighting_control_state_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)642 static ssize_t lighting_control_state_store(struct device *dev,
643 struct device_attribute *attr,
644 const char *buf, size_t count)
645 {
646 u8 val;
647
648 if (strcmp(buf, "booting\n") == 0)
649 val = LEGACY_BOOTING;
650 else if (strcmp(buf, "suspend\n") == 0)
651 val = LEGACY_SUSPEND;
652 else if (interface == LEGACY)
653 val = LEGACY_RUNNING;
654 else
655 val = WMAX_RUNNING;
656
657 lighting_control_state = val;
658 pr_debug("alienware-wmi: updated control state to %d\n",
659 lighting_control_state);
660
661 return count;
662 }
663
664 static DEVICE_ATTR_RW(lighting_control_state);
665
zone_attr_visible(struct kobject * kobj,struct attribute * attr,int n)666 static umode_t zone_attr_visible(struct kobject *kobj,
667 struct attribute *attr, int n)
668 {
669 if (n < quirks->num_zones + 1)
670 return attr->mode;
671
672 return 0;
673 }
674
zone_group_visible(struct kobject * kobj)675 static bool zone_group_visible(struct kobject *kobj)
676 {
677 return quirks->num_zones > 0;
678 }
679 DEFINE_SYSFS_GROUP_VISIBLE(zone);
680
681 static struct attribute *zone_attrs[] = {
682 &dev_attr_lighting_control_state.attr,
683 &dev_attr_zone00.attr,
684 &dev_attr_zone01.attr,
685 &dev_attr_zone02.attr,
686 &dev_attr_zone03.attr,
687 NULL
688 };
689
690 static struct attribute_group zone_attribute_group = {
691 .name = "rgb_zones",
692 .is_visible = SYSFS_GROUP_VISIBLE(zone),
693 .attrs = zone_attrs,
694 };
695
696 /*
697 * LED Brightness (Global)
698 */
wmax_brightness(int brightness)699 static int wmax_brightness(int brightness)
700 {
701 acpi_status status;
702 struct acpi_buffer input;
703 struct wmax_brightness_args args = {
704 .led_mask = 0xFF,
705 .percentage = brightness,
706 };
707 input.length = sizeof(args);
708 input.pointer = &args;
709 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,
710 WMAX_METHOD_BRIGHTNESS, &input, NULL);
711 if (ACPI_FAILURE(status))
712 pr_err("alienware-wmi: brightness set failure: %u\n", status);
713 return ACPI_FAILURE(status);
714 }
715
global_led_set(struct led_classdev * led_cdev,enum led_brightness brightness)716 static void global_led_set(struct led_classdev *led_cdev,
717 enum led_brightness brightness)
718 {
719 int ret;
720 global_brightness = brightness;
721 if (interface == WMAX)
722 ret = wmax_brightness(brightness);
723 else
724 ret = alienware_update_led(0);
725 if (ret)
726 pr_err("LED brightness update failed\n");
727 }
728
global_led_get(struct led_classdev * led_cdev)729 static enum led_brightness global_led_get(struct led_classdev *led_cdev)
730 {
731 return global_brightness;
732 }
733
734 static struct led_classdev global_led = {
735 .brightness_set = global_led_set,
736 .brightness_get = global_led_get,
737 .name = "alienware::global_brightness",
738 };
739
alienware_zone_init(struct platform_device * dev)740 static int alienware_zone_init(struct platform_device *dev)
741 {
742 if (interface == WMAX) {
743 lighting_control_state = WMAX_RUNNING;
744 } else if (interface == LEGACY) {
745 lighting_control_state = LEGACY_RUNNING;
746 }
747 global_led.max_brightness = 0x0F;
748 global_brightness = global_led.max_brightness;
749
750 return led_classdev_register(&dev->dev, &global_led);
751 }
752
alienware_zone_exit(struct platform_device * dev)753 static void alienware_zone_exit(struct platform_device *dev)
754 {
755 if (!quirks->num_zones)
756 return;
757
758 led_classdev_unregister(&global_led);
759 }
760
alienware_wmax_command(void * in_args,size_t in_size,u32 command,u32 * out_data)761 static acpi_status alienware_wmax_command(void *in_args, size_t in_size,
762 u32 command, u32 *out_data)
763 {
764 acpi_status status;
765 union acpi_object *obj;
766 struct acpi_buffer input;
767 struct acpi_buffer output;
768
769 input.length = in_size;
770 input.pointer = in_args;
771 if (out_data) {
772 output.length = ACPI_ALLOCATE_BUFFER;
773 output.pointer = NULL;
774 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,
775 command, &input, &output);
776 if (ACPI_SUCCESS(status)) {
777 obj = (union acpi_object *)output.pointer;
778 if (obj && obj->type == ACPI_TYPE_INTEGER)
779 *out_data = (u32)obj->integer.value;
780 }
781 kfree(output.pointer);
782 } else {
783 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,
784 command, &input, NULL);
785 }
786 return status;
787 }
788
789 /*
790 * The HDMI mux sysfs node indicates the status of the HDMI input mux.
791 * It can toggle between standard system GPU output and HDMI input.
792 */
cable_show(struct device * dev,struct device_attribute * attr,char * buf)793 static ssize_t cable_show(struct device *dev, struct device_attribute *attr,
794 char *buf)
795 {
796 struct wmax_basic_args in_args = {
797 .arg = 0,
798 };
799 acpi_status status;
800 u32 out_data;
801
802 status =
803 alienware_wmax_command(&in_args, sizeof(in_args),
804 WMAX_METHOD_HDMI_CABLE, &out_data);
805 if (ACPI_SUCCESS(status)) {
806 if (out_data == 0)
807 return sysfs_emit(buf, "[unconnected] connected unknown\n");
808 else if (out_data == 1)
809 return sysfs_emit(buf, "unconnected [connected] unknown\n");
810 }
811 pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status);
812 return sysfs_emit(buf, "unconnected connected [unknown]\n");
813 }
814
source_show(struct device * dev,struct device_attribute * attr,char * buf)815 static ssize_t source_show(struct device *dev, struct device_attribute *attr,
816 char *buf)
817 {
818 struct wmax_basic_args in_args = {
819 .arg = 0,
820 };
821 acpi_status status;
822 u32 out_data;
823
824 status =
825 alienware_wmax_command(&in_args, sizeof(in_args),
826 WMAX_METHOD_HDMI_STATUS, &out_data);
827
828 if (ACPI_SUCCESS(status)) {
829 if (out_data == 1)
830 return sysfs_emit(buf, "[input] gpu unknown\n");
831 else if (out_data == 2)
832 return sysfs_emit(buf, "input [gpu] unknown\n");
833 }
834 pr_err("alienware-wmi: unknown HDMI source status: %u\n", status);
835 return sysfs_emit(buf, "input gpu [unknown]\n");
836 }
837
source_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)838 static ssize_t source_store(struct device *dev, struct device_attribute *attr,
839 const char *buf, size_t count)
840 {
841 struct wmax_basic_args args;
842 acpi_status status;
843
844 if (strcmp(buf, "gpu\n") == 0)
845 args.arg = 1;
846 else if (strcmp(buf, "input\n") == 0)
847 args.arg = 2;
848 else
849 args.arg = 3;
850 pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);
851
852 status = alienware_wmax_command(&args, sizeof(args),
853 WMAX_METHOD_HDMI_SOURCE, NULL);
854
855 if (ACPI_FAILURE(status))
856 pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
857 status);
858 return count;
859 }
860
861 static DEVICE_ATTR_RO(cable);
862 static DEVICE_ATTR_RW(source);
863
hdmi_group_visible(struct kobject * kobj)864 static bool hdmi_group_visible(struct kobject *kobj)
865 {
866 return quirks->hdmi_mux;
867 }
868 DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(hdmi);
869
870 static struct attribute *hdmi_attrs[] = {
871 &dev_attr_cable.attr,
872 &dev_attr_source.attr,
873 NULL,
874 };
875
876 static const struct attribute_group hdmi_attribute_group = {
877 .name = "hdmi",
878 .is_visible = SYSFS_GROUP_VISIBLE(hdmi),
879 .attrs = hdmi_attrs,
880 };
881
882 /*
883 * Alienware GFX amplifier support
884 * - Currently supports reading cable status
885 * - Leaving expansion room to possibly support dock/undock events later
886 */
status_show(struct device * dev,struct device_attribute * attr,char * buf)887 static ssize_t status_show(struct device *dev, struct device_attribute *attr,
888 char *buf)
889 {
890 struct wmax_basic_args in_args = {
891 .arg = 0,
892 };
893 acpi_status status;
894 u32 out_data;
895
896 status =
897 alienware_wmax_command(&in_args, sizeof(in_args),
898 WMAX_METHOD_AMPLIFIER_CABLE, &out_data);
899 if (ACPI_SUCCESS(status)) {
900 if (out_data == 0)
901 return sysfs_emit(buf, "[unconnected] connected unknown\n");
902 else if (out_data == 1)
903 return sysfs_emit(buf, "unconnected [connected] unknown\n");
904 }
905 pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status);
906 return sysfs_emit(buf, "unconnected connected [unknown]\n");
907 }
908
909 static DEVICE_ATTR_RO(status);
910
amplifier_group_visible(struct kobject * kobj)911 static bool amplifier_group_visible(struct kobject *kobj)
912 {
913 return quirks->amplifier;
914 }
915 DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(amplifier);
916
917 static struct attribute *amplifier_attrs[] = {
918 &dev_attr_status.attr,
919 NULL,
920 };
921
922 static const struct attribute_group amplifier_attribute_group = {
923 .name = "amplifier",
924 .is_visible = SYSFS_GROUP_VISIBLE(amplifier),
925 .attrs = amplifier_attrs,
926 };
927
928 /*
929 * Deep Sleep Control support
930 * - Modifies BIOS setting for deep sleep control allowing extra wakeup events
931 */
deepsleep_show(struct device * dev,struct device_attribute * attr,char * buf)932 static ssize_t deepsleep_show(struct device *dev, struct device_attribute *attr,
933 char *buf)
934 {
935 struct wmax_basic_args in_args = {
936 .arg = 0,
937 };
938 acpi_status status;
939 u32 out_data;
940
941 status = alienware_wmax_command(&in_args, sizeof(in_args),
942 WMAX_METHOD_DEEP_SLEEP_STATUS, &out_data);
943 if (ACPI_SUCCESS(status)) {
944 if (out_data == 0)
945 return sysfs_emit(buf, "[disabled] s5 s5_s4\n");
946 else if (out_data == 1)
947 return sysfs_emit(buf, "disabled [s5] s5_s4\n");
948 else if (out_data == 2)
949 return sysfs_emit(buf, "disabled s5 [s5_s4]\n");
950 }
951 pr_err("alienware-wmi: unknown deep sleep status: %d\n", status);
952 return sysfs_emit(buf, "disabled s5 s5_s4 [unknown]\n");
953 }
954
deepsleep_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)955 static ssize_t deepsleep_store(struct device *dev, struct device_attribute *attr,
956 const char *buf, size_t count)
957 {
958 struct wmax_basic_args args;
959 acpi_status status;
960
961 if (strcmp(buf, "disabled\n") == 0)
962 args.arg = 0;
963 else if (strcmp(buf, "s5\n") == 0)
964 args.arg = 1;
965 else
966 args.arg = 2;
967 pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf);
968
969 status = alienware_wmax_command(&args, sizeof(args),
970 WMAX_METHOD_DEEP_SLEEP_CONTROL, NULL);
971
972 if (ACPI_FAILURE(status))
973 pr_err("alienware-wmi: deep sleep control failed: results: %u\n",
974 status);
975 return count;
976 }
977
978 static DEVICE_ATTR_RW(deepsleep);
979
deepsleep_group_visible(struct kobject * kobj)980 static bool deepsleep_group_visible(struct kobject *kobj)
981 {
982 return quirks->deepslp;
983 }
984 DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(deepsleep);
985
986 static struct attribute *deepsleep_attrs[] = {
987 &dev_attr_deepsleep.attr,
988 NULL,
989 };
990
991 static const struct attribute_group deepsleep_attribute_group = {
992 .name = "deepsleep",
993 .is_visible = SYSFS_GROUP_VISIBLE(deepsleep),
994 .attrs = deepsleep_attrs,
995 };
996
997 /*
998 * Thermal Profile control
999 * - Provides thermal profile control through the Platform Profile API
1000 */
1001 #define WMAX_THERMAL_TABLE_MASK GENMASK(7, 4)
1002 #define WMAX_THERMAL_MODE_MASK GENMASK(3, 0)
1003 #define WMAX_SENSOR_ID_MASK BIT(8)
1004
is_wmax_thermal_code(u32 code)1005 static bool is_wmax_thermal_code(u32 code)
1006 {
1007 if (code & WMAX_SENSOR_ID_MASK)
1008 return false;
1009
1010 if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST)
1011 return false;
1012
1013 if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC &&
1014 (code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET)
1015 return true;
1016
1017 if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT &&
1018 (code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER)
1019 return true;
1020
1021 return false;
1022 }
1023
wmax_thermal_information(u8 operation,u8 arg,u32 * out_data)1024 static int wmax_thermal_information(u8 operation, u8 arg, u32 *out_data)
1025 {
1026 struct wmax_u32_args in_args = {
1027 .operation = operation,
1028 .arg1 = arg,
1029 .arg2 = 0,
1030 .arg3 = 0,
1031 };
1032 acpi_status status;
1033
1034 status = alienware_wmax_command(&in_args, sizeof(in_args),
1035 WMAX_METHOD_THERMAL_INFORMATION,
1036 out_data);
1037
1038 if (ACPI_FAILURE(status))
1039 return -EIO;
1040
1041 if (*out_data == WMAX_FAILURE_CODE)
1042 return -EBADRQC;
1043
1044 return 0;
1045 }
1046
wmax_thermal_control(u8 profile)1047 static int wmax_thermal_control(u8 profile)
1048 {
1049 struct wmax_u32_args in_args = {
1050 .operation = WMAX_OPERATION_ACTIVATE_PROFILE,
1051 .arg1 = profile,
1052 .arg2 = 0,
1053 .arg3 = 0,
1054 };
1055 acpi_status status;
1056 u32 out_data;
1057
1058 status = alienware_wmax_command(&in_args, sizeof(in_args),
1059 WMAX_METHOD_THERMAL_CONTROL,
1060 &out_data);
1061
1062 if (ACPI_FAILURE(status))
1063 return -EIO;
1064
1065 if (out_data == WMAX_FAILURE_CODE)
1066 return -EBADRQC;
1067
1068 return 0;
1069 }
1070
wmax_game_shift_status(u8 operation,u32 * out_data)1071 static int wmax_game_shift_status(u8 operation, u32 *out_data)
1072 {
1073 struct wmax_u32_args in_args = {
1074 .operation = operation,
1075 .arg1 = 0,
1076 .arg2 = 0,
1077 .arg3 = 0,
1078 };
1079 acpi_status status;
1080
1081 status = alienware_wmax_command(&in_args, sizeof(in_args),
1082 WMAX_METHOD_GAME_SHIFT_STATUS,
1083 out_data);
1084
1085 if (ACPI_FAILURE(status))
1086 return -EIO;
1087
1088 if (*out_data == WMAX_FAILURE_CODE)
1089 return -EOPNOTSUPP;
1090
1091 return 0;
1092 }
1093
thermal_profile_get(struct device * dev,enum platform_profile_option * profile)1094 static int thermal_profile_get(struct device *dev,
1095 enum platform_profile_option *profile)
1096 {
1097 u32 out_data;
1098 int ret;
1099
1100 ret = wmax_thermal_information(WMAX_OPERATION_CURRENT_PROFILE,
1101 0, &out_data);
1102
1103 if (ret < 0)
1104 return ret;
1105
1106 if (out_data == WMAX_THERMAL_MODE_GMODE) {
1107 *profile = PLATFORM_PROFILE_PERFORMANCE;
1108 return 0;
1109 }
1110
1111 if (!is_wmax_thermal_code(out_data))
1112 return -ENODATA;
1113
1114 out_data &= WMAX_THERMAL_MODE_MASK;
1115 *profile = wmax_mode_to_platform_profile[out_data];
1116
1117 return 0;
1118 }
1119
thermal_profile_set(struct device * dev,enum platform_profile_option profile)1120 static int thermal_profile_set(struct device *dev,
1121 enum platform_profile_option profile)
1122 {
1123 if (quirks->gmode) {
1124 u32 gmode_status;
1125 int ret;
1126
1127 ret = wmax_game_shift_status(WMAX_OPERATION_GET_GAME_SHIFT_STATUS,
1128 &gmode_status);
1129
1130 if (ret < 0)
1131 return ret;
1132
1133 if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) ||
1134 (profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) {
1135 ret = wmax_game_shift_status(WMAX_OPERATION_TOGGLE_GAME_SHIFT,
1136 &gmode_status);
1137
1138 if (ret < 0)
1139 return ret;
1140 }
1141 }
1142
1143 return wmax_thermal_control(supported_thermal_profiles[profile]);
1144 }
1145
thermal_profile_probe(void * drvdata,unsigned long * choices)1146 static int thermal_profile_probe(void *drvdata, unsigned long *choices)
1147 {
1148 enum platform_profile_option profile;
1149 enum wmax_thermal_mode mode;
1150 u8 sys_desc[4];
1151 u32 first_mode;
1152 u32 out_data;
1153 int ret;
1154
1155 ret = wmax_thermal_information(WMAX_OPERATION_SYS_DESCRIPTION,
1156 0, (u32 *) &sys_desc);
1157 if (ret < 0)
1158 return ret;
1159
1160 first_mode = sys_desc[0] + sys_desc[1];
1161
1162 for (u32 i = 0; i < sys_desc[3]; i++) {
1163 ret = wmax_thermal_information(WMAX_OPERATION_LIST_IDS,
1164 i + first_mode, &out_data);
1165
1166 if (ret == -EIO)
1167 return ret;
1168
1169 if (ret == -EBADRQC)
1170 break;
1171
1172 if (!is_wmax_thermal_code(out_data))
1173 continue;
1174
1175 mode = out_data & WMAX_THERMAL_MODE_MASK;
1176 profile = wmax_mode_to_platform_profile[mode];
1177 supported_thermal_profiles[profile] = out_data;
1178
1179 set_bit(profile, choices);
1180 }
1181
1182 if (bitmap_empty(choices, PLATFORM_PROFILE_LAST))
1183 return -ENODEV;
1184
1185 if (quirks->gmode) {
1186 supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] =
1187 WMAX_THERMAL_MODE_GMODE;
1188
1189 set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
1190 }
1191
1192 return 0;
1193 }
1194
1195 static const struct platform_profile_ops awcc_platform_profile_ops = {
1196 .probe = thermal_profile_probe,
1197 .profile_get = thermal_profile_get,
1198 .profile_set = thermal_profile_set,
1199 };
1200
create_thermal_profile(struct platform_device * platform_device)1201 static int create_thermal_profile(struct platform_device *platform_device)
1202 {
1203 struct device *ppdev;
1204
1205 ppdev = devm_platform_profile_register(&platform_device->dev, "alienware-wmi",
1206 NULL, &awcc_platform_profile_ops);
1207
1208 return PTR_ERR_OR_ZERO(ppdev);
1209 }
1210
1211 /*
1212 * Platform Driver
1213 */
1214 static const struct attribute_group *alienfx_groups[] = {
1215 &zone_attribute_group,
1216 &hdmi_attribute_group,
1217 &lifier_attribute_group,
1218 &deepsleep_attribute_group,
1219 NULL
1220 };
1221
1222 static struct platform_driver platform_driver = {
1223 .driver = {
1224 .name = "alienware-wmi",
1225 .dev_groups = alienfx_groups,
1226 },
1227 };
1228
alienware_wmi_init(void)1229 static int __init alienware_wmi_init(void)
1230 {
1231 int ret;
1232
1233 if (wmi_has_guid(LEGACY_CONTROL_GUID))
1234 interface = LEGACY;
1235 else if (wmi_has_guid(WMAX_CONTROL_GUID))
1236 interface = WMAX;
1237 else {
1238 pr_warn("alienware-wmi: No known WMI GUID found\n");
1239 return -ENODEV;
1240 }
1241
1242 dmi_check_system(alienware_quirks);
1243 if (quirks == NULL)
1244 quirks = &quirk_unknown;
1245
1246 if (force_platform_profile)
1247 quirks->thermal = true;
1248
1249 if (force_gmode) {
1250 if (quirks->thermal)
1251 quirks->gmode = true;
1252 else
1253 pr_warn("force_gmode requires platform profile support\n");
1254 }
1255
1256 ret = platform_driver_register(&platform_driver);
1257 if (ret)
1258 goto fail_platform_driver;
1259 platform_device = platform_device_alloc("alienware-wmi", PLATFORM_DEVID_NONE);
1260 if (!platform_device) {
1261 ret = -ENOMEM;
1262 goto fail_platform_device1;
1263 }
1264 ret = platform_device_add(platform_device);
1265 if (ret)
1266 goto fail_platform_device2;
1267
1268 if (quirks->thermal) {
1269 ret = create_thermal_profile(platform_device);
1270 if (ret)
1271 goto fail_prep_thermal_profile;
1272 }
1273
1274 if (quirks->num_zones > 0) {
1275 ret = alienware_zone_init(platform_device);
1276 if (ret)
1277 goto fail_prep_zones;
1278 }
1279
1280 return 0;
1281
1282 fail_prep_zones:
1283 alienware_zone_exit(platform_device);
1284 fail_prep_thermal_profile:
1285 platform_device_del(platform_device);
1286 fail_platform_device2:
1287 platform_device_put(platform_device);
1288 fail_platform_device1:
1289 platform_driver_unregister(&platform_driver);
1290 fail_platform_driver:
1291 return ret;
1292 }
1293
1294 module_init(alienware_wmi_init);
1295
alienware_wmi_exit(void)1296 static void __exit alienware_wmi_exit(void)
1297 {
1298 alienware_zone_exit(platform_device);
1299 platform_device_unregister(platform_device);
1300 platform_driver_unregister(&platform_driver);
1301 }
1302
1303 module_exit(alienware_wmi_exit);
1304