]> Git Repo - linux.git/blob - drivers/platform/x86/dell/alienware-wmi.c
Linux 6.14-rc3
[linux.git] / drivers / platform / x86 / dell / alienware-wmi.c
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
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 ASM100",
220                 .matches = {
221                         DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
222                         DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"),
223                 },
224                 .driver_data = &quirk_asm100,
225         },
226         {
227                 .callback = dmi_matched,
228                 .ident = "Alienware ASM200",
229                 .matches = {
230                         DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
231                         DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"),
232                 },
233                 .driver_data = &quirk_asm200,
234         },
235         {
236                 .callback = dmi_matched,
237                 .ident = "Alienware ASM201",
238                 .matches = {
239                         DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
240                         DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"),
241                 },
242                 .driver_data = &quirk_asm201,
243         },
244         {
245                 .callback = dmi_matched,
246                 .ident = "Alienware m16 R1 AMD",
247                 .matches = {
248                         DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
249                         DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R1 AMD"),
250                 },
251                 .driver_data = &quirk_x_series,
252         },
253         {
254                 .callback = dmi_matched,
255                 .ident = "Alienware m17 R5",
256                 .matches = {
257                         DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
258                         DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"),
259                 },
260                 .driver_data = &quirk_x_series,
261         },
262         {
263                 .callback = dmi_matched,
264                 .ident = "Alienware m18 R2",
265                 .matches = {
266                         DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
267                         DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R2"),
268                 },
269                 .driver_data = &quirk_x_series,
270         },
271         {
272                 .callback = dmi_matched,
273                 .ident = "Alienware x15 R1",
274                 .matches = {
275                         DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
276                         DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R1"),
277                 },
278                 .driver_data = &quirk_x_series,
279         },
280         {
281                 .callback = dmi_matched,
282                 .ident = "Alienware x17 R2",
283                 .matches = {
284                         DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
285                         DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x17 R2"),
286                 },
287                 .driver_data = &quirk_x_series,
288         },
289         {
290                 .callback = dmi_matched,
291                 .ident = "Alienware X51 R1",
292                 .matches = {
293                         DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
294                         DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
295                 },
296                 .driver_data = &quirk_x51_r1_r2,
297         },
298         {
299                 .callback = dmi_matched,
300                 .ident = "Alienware X51 R2",
301                 .matches = {
302                         DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
303                         DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),
304                 },
305                 .driver_data = &quirk_x51_r1_r2,
306         },
307         {
308                 .callback = dmi_matched,
309                 .ident = "Alienware X51 R3",
310                 .matches = {
311                         DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
312                         DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"),
313                 },
314                 .driver_data = &quirk_x51_r3,
315         },
316         {
317                 .callback = dmi_matched,
318                 .ident = "Dell Inc. G15 5510",
319                 .matches = {
320                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
321                         DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5510"),
322                 },
323                 .driver_data = &quirk_g_series,
324         },
325         {
326                 .callback = dmi_matched,
327                 .ident = "Dell Inc. G15 5511",
328                 .matches = {
329                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
330                         DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"),
331                 },
332                 .driver_data = &quirk_g_series,
333         },
334         {
335                 .callback = dmi_matched,
336                 .ident = "Dell Inc. G15 5515",
337                 .matches = {
338                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
339                         DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5515"),
340                 },
341                 .driver_data = &quirk_g_series,
342         },
343         {
344                 .callback = dmi_matched,
345                 .ident = "Dell Inc. G3 3500",
346                 .matches = {
347                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
348                         DMI_MATCH(DMI_PRODUCT_NAME, "G3 3500"),
349                 },
350                 .driver_data = &quirk_g_series,
351         },
352         {
353                 .callback = dmi_matched,
354                 .ident = "Dell Inc. G3 3590",
355                 .matches = {
356                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
357                         DMI_MATCH(DMI_PRODUCT_NAME, "G3 3590"),
358                 },
359                 .driver_data = &quirk_g_series,
360         },
361         {
362                 .callback = dmi_matched,
363                 .ident = "Dell Inc. G5 5500",
364                 .matches = {
365                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
366                         DMI_MATCH(DMI_PRODUCT_NAME, "G5 5500"),
367                 },
368                 .driver_data = &quirk_g_series,
369         },
370         {
371                 .callback = dmi_matched,
372                 .ident = "Dell Inc. Inspiron 5675",
373                 .matches = {
374                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
375                         DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"),
376                 },
377                 .driver_data = &quirk_inspiron5675,
378         },
379         {}
380 };
381
382 struct color_platform {
383         u8 blue;
384         u8 green;
385         u8 red;
386 } __packed;
387
388 struct wmax_brightness_args {
389         u32 led_mask;
390         u32 percentage;
391 };
392
393 struct wmax_basic_args {
394         u8 arg;
395 };
396
397 struct legacy_led_args {
398         struct color_platform colors;
399         u8 brightness;
400         u8 state;
401 } __packed;
402
403 struct wmax_led_args {
404         u32 led_mask;
405         struct color_platform colors;
406         u8 state;
407 } __packed;
408
409 struct wmax_u32_args {
410         u8 operation;
411         u8 arg1;
412         u8 arg2;
413         u8 arg3;
414 };
415
416 static struct platform_device *platform_device;
417 static struct color_platform colors[4];
418 static enum wmax_thermal_mode supported_thermal_profiles[PLATFORM_PROFILE_LAST];
419
420 static u8 interface;
421 static u8 lighting_control_state;
422 static u8 global_brightness;
423
424 /*
425  * Helpers used for zone control
426  */
427 static int parse_rgb(const char *buf, struct color_platform *colors)
428 {
429         long unsigned int rgb;
430         int ret;
431         union color_union {
432                 struct color_platform cp;
433                 int package;
434         } repackager;
435
436         ret = kstrtoul(buf, 16, &rgb);
437         if (ret)
438                 return ret;
439
440         /* RGB triplet notation is 24-bit hexadecimal */
441         if (rgb > 0xFFFFFF)
442                 return -EINVAL;
443
444         repackager.package = rgb & 0x0f0f0f0f;
445         pr_debug("alienware-wmi: r: %d g:%d b: %d\n",
446                  repackager.cp.red, repackager.cp.green, repackager.cp.blue);
447         *colors = repackager.cp;
448         return 0;
449 }
450
451 /*
452  * Individual RGB zone control
453  */
454 static int alienware_update_led(u8 location)
455 {
456         int method_id;
457         acpi_status status;
458         char *guid;
459         struct acpi_buffer input;
460         struct legacy_led_args legacy_args;
461         struct wmax_led_args wmax_basic_args;
462         if (interface == WMAX) {
463                 wmax_basic_args.led_mask = 1 << location;
464                 wmax_basic_args.colors = colors[location];
465                 wmax_basic_args.state = lighting_control_state;
466                 guid = WMAX_CONTROL_GUID;
467                 method_id = WMAX_METHOD_ZONE_CONTROL;
468
469                 input.length = sizeof(wmax_basic_args);
470                 input.pointer = &wmax_basic_args;
471         } else {
472                 legacy_args.colors = colors[location];
473                 legacy_args.brightness = global_brightness;
474                 legacy_args.state = 0;
475                 if (lighting_control_state == LEGACY_BOOTING ||
476                     lighting_control_state == LEGACY_SUSPEND) {
477                         guid = LEGACY_POWER_CONTROL_GUID;
478                         legacy_args.state = lighting_control_state;
479                 } else
480                         guid = LEGACY_CONTROL_GUID;
481                 method_id = location + 1;
482
483                 input.length = sizeof(legacy_args);
484                 input.pointer = &legacy_args;
485         }
486         pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id);
487
488         status = wmi_evaluate_method(guid, 0, method_id, &input, NULL);
489         if (ACPI_FAILURE(status))
490                 pr_err("alienware-wmi: zone set failure: %u\n", status);
491         return ACPI_FAILURE(status);
492 }
493
494 static ssize_t zone_show(struct device *dev, struct device_attribute *attr,
495                          char *buf, u8 location)
496 {
497         return sprintf(buf, "red: %d, green: %d, blue: %d\n",
498                        colors[location].red, colors[location].green,
499                        colors[location].blue);
500
501 }
502
503 static ssize_t zone_store(struct device *dev, struct device_attribute *attr,
504                           const char *buf, size_t count, u8 location)
505 {
506         int ret;
507
508         ret = parse_rgb(buf, &colors[location]);
509         if (ret)
510                 return ret;
511
512         ret = alienware_update_led(location);
513
514         return ret ? ret : count;
515 }
516
517 static ssize_t zone00_show(struct device *dev, struct device_attribute *attr,
518                            char *buf)
519 {
520         return zone_show(dev, attr, buf, 0);
521 }
522
523 static ssize_t zone00_store(struct device *dev, struct device_attribute *attr,
524                             const char *buf, size_t count)
525 {
526         return zone_store(dev, attr, buf, count, 0);
527 }
528
529 static DEVICE_ATTR_RW(zone00);
530
531 static ssize_t zone01_show(struct device *dev, struct device_attribute *attr,
532                            char *buf)
533 {
534         return zone_show(dev, attr, buf, 1);
535 }
536
537 static ssize_t zone01_store(struct device *dev, struct device_attribute *attr,
538                             const char *buf, size_t count)
539 {
540         return zone_store(dev, attr, buf, count, 1);
541 }
542
543 static DEVICE_ATTR_RW(zone01);
544
545 static ssize_t zone02_show(struct device *dev, struct device_attribute *attr,
546                            char *buf)
547 {
548         return zone_show(dev, attr, buf, 2);
549 }
550
551 static ssize_t zone02_store(struct device *dev, struct device_attribute *attr,
552                             const char *buf, size_t count)
553 {
554         return zone_store(dev, attr, buf, count, 2);
555 }
556
557 static DEVICE_ATTR_RW(zone02);
558
559 static ssize_t zone03_show(struct device *dev, struct device_attribute *attr,
560                            char *buf)
561 {
562         return zone_show(dev, attr, buf, 3);
563 }
564
565 static ssize_t zone03_store(struct device *dev, struct device_attribute *attr,
566                             const char *buf, size_t count)
567 {
568         return zone_store(dev, attr, buf, count, 3);
569 }
570
571 static DEVICE_ATTR_RW(zone03);
572
573 /*
574  * Lighting control state device attribute (Global)
575  */
576 static ssize_t lighting_control_state_show(struct device *dev,
577                                            struct device_attribute *attr,
578                                            char *buf)
579 {
580         if (lighting_control_state == LEGACY_BOOTING)
581                 return sysfs_emit(buf, "[booting] running suspend\n");
582         else if (lighting_control_state == LEGACY_SUSPEND)
583                 return sysfs_emit(buf, "booting running [suspend]\n");
584
585         return sysfs_emit(buf, "booting [running] suspend\n");
586 }
587
588 static ssize_t lighting_control_state_store(struct device *dev,
589                                             struct device_attribute *attr,
590                                             const char *buf, size_t count)
591 {
592         u8 val;
593
594         if (strcmp(buf, "booting\n") == 0)
595                 val = LEGACY_BOOTING;
596         else if (strcmp(buf, "suspend\n") == 0)
597                 val = LEGACY_SUSPEND;
598         else if (interface == LEGACY)
599                 val = LEGACY_RUNNING;
600         else
601                 val = WMAX_RUNNING;
602
603         lighting_control_state = val;
604         pr_debug("alienware-wmi: updated control state to %d\n",
605                  lighting_control_state);
606
607         return count;
608 }
609
610 static DEVICE_ATTR_RW(lighting_control_state);
611
612 static umode_t zone_attr_visible(struct kobject *kobj,
613                                  struct attribute *attr, int n)
614 {
615         if (n < quirks->num_zones + 1)
616                 return attr->mode;
617
618         return 0;
619 }
620
621 static bool zone_group_visible(struct kobject *kobj)
622 {
623         return quirks->num_zones > 0;
624 }
625 DEFINE_SYSFS_GROUP_VISIBLE(zone);
626
627 static struct attribute *zone_attrs[] = {
628         &dev_attr_lighting_control_state.attr,
629         &dev_attr_zone00.attr,
630         &dev_attr_zone01.attr,
631         &dev_attr_zone02.attr,
632         &dev_attr_zone03.attr,
633         NULL
634 };
635
636 static struct attribute_group zone_attribute_group = {
637         .name = "rgb_zones",
638         .is_visible = SYSFS_GROUP_VISIBLE(zone),
639         .attrs = zone_attrs,
640 };
641
642 /*
643  * LED Brightness (Global)
644  */
645 static int wmax_brightness(int brightness)
646 {
647         acpi_status status;
648         struct acpi_buffer input;
649         struct wmax_brightness_args args = {
650                 .led_mask = 0xFF,
651                 .percentage = brightness,
652         };
653         input.length = sizeof(args);
654         input.pointer = &args;
655         status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,
656                                      WMAX_METHOD_BRIGHTNESS, &input, NULL);
657         if (ACPI_FAILURE(status))
658                 pr_err("alienware-wmi: brightness set failure: %u\n", status);
659         return ACPI_FAILURE(status);
660 }
661
662 static void global_led_set(struct led_classdev *led_cdev,
663                            enum led_brightness brightness)
664 {
665         int ret;
666         global_brightness = brightness;
667         if (interface == WMAX)
668                 ret = wmax_brightness(brightness);
669         else
670                 ret = alienware_update_led(0);
671         if (ret)
672                 pr_err("LED brightness update failed\n");
673 }
674
675 static enum led_brightness global_led_get(struct led_classdev *led_cdev)
676 {
677         return global_brightness;
678 }
679
680 static struct led_classdev global_led = {
681         .brightness_set = global_led_set,
682         .brightness_get = global_led_get,
683         .name = "alienware::global_brightness",
684 };
685
686 static int alienware_zone_init(struct platform_device *dev)
687 {
688         if (interface == WMAX) {
689                 lighting_control_state = WMAX_RUNNING;
690         } else if (interface == LEGACY) {
691                 lighting_control_state = LEGACY_RUNNING;
692         }
693         global_led.max_brightness = 0x0F;
694         global_brightness = global_led.max_brightness;
695
696         return led_classdev_register(&dev->dev, &global_led);
697 }
698
699 static void alienware_zone_exit(struct platform_device *dev)
700 {
701         if (!quirks->num_zones)
702                 return;
703
704         led_classdev_unregister(&global_led);
705 }
706
707 static acpi_status alienware_wmax_command(void *in_args, size_t in_size,
708                                           u32 command, u32 *out_data)
709 {
710         acpi_status status;
711         union acpi_object *obj;
712         struct acpi_buffer input;
713         struct acpi_buffer output;
714
715         input.length = in_size;
716         input.pointer = in_args;
717         if (out_data) {
718                 output.length = ACPI_ALLOCATE_BUFFER;
719                 output.pointer = NULL;
720                 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,
721                                              command, &input, &output);
722                 if (ACPI_SUCCESS(status)) {
723                         obj = (union acpi_object *)output.pointer;
724                         if (obj && obj->type == ACPI_TYPE_INTEGER)
725                                 *out_data = (u32)obj->integer.value;
726                 }
727                 kfree(output.pointer);
728         } else {
729                 status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,
730                                              command, &input, NULL);
731         }
732         return status;
733 }
734
735 /*
736  *      The HDMI mux sysfs node indicates the status of the HDMI input mux.
737  *      It can toggle between standard system GPU output and HDMI input.
738  */
739 static ssize_t cable_show(struct device *dev, struct device_attribute *attr,
740                           char *buf)
741 {
742         struct wmax_basic_args in_args = {
743                 .arg = 0,
744         };
745         acpi_status status;
746         u32 out_data;
747
748         status =
749             alienware_wmax_command(&in_args, sizeof(in_args),
750                                    WMAX_METHOD_HDMI_CABLE, &out_data);
751         if (ACPI_SUCCESS(status)) {
752                 if (out_data == 0)
753                         return sysfs_emit(buf, "[unconnected] connected unknown\n");
754                 else if (out_data == 1)
755                         return sysfs_emit(buf, "unconnected [connected] unknown\n");
756         }
757         pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status);
758         return sysfs_emit(buf, "unconnected connected [unknown]\n");
759 }
760
761 static ssize_t source_show(struct device *dev, struct device_attribute *attr,
762                            char *buf)
763 {
764         struct wmax_basic_args in_args = {
765                 .arg = 0,
766         };
767         acpi_status status;
768         u32 out_data;
769
770         status =
771             alienware_wmax_command(&in_args, sizeof(in_args),
772                                    WMAX_METHOD_HDMI_STATUS, &out_data);
773
774         if (ACPI_SUCCESS(status)) {
775                 if (out_data == 1)
776                         return sysfs_emit(buf, "[input] gpu unknown\n");
777                 else if (out_data == 2)
778                         return sysfs_emit(buf, "input [gpu] unknown\n");
779         }
780         pr_err("alienware-wmi: unknown HDMI source status: %u\n", status);
781         return sysfs_emit(buf, "input gpu [unknown]\n");
782 }
783
784 static ssize_t source_store(struct device *dev, struct device_attribute *attr,
785                             const char *buf, size_t count)
786 {
787         struct wmax_basic_args args;
788         acpi_status status;
789
790         if (strcmp(buf, "gpu\n") == 0)
791                 args.arg = 1;
792         else if (strcmp(buf, "input\n") == 0)
793                 args.arg = 2;
794         else
795                 args.arg = 3;
796         pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);
797
798         status = alienware_wmax_command(&args, sizeof(args),
799                                         WMAX_METHOD_HDMI_SOURCE, NULL);
800
801         if (ACPI_FAILURE(status))
802                 pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
803                        status);
804         return count;
805 }
806
807 static DEVICE_ATTR_RO(cable);
808 static DEVICE_ATTR_RW(source);
809
810 static bool hdmi_group_visible(struct kobject *kobj)
811 {
812         return quirks->hdmi_mux;
813 }
814 DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(hdmi);
815
816 static struct attribute *hdmi_attrs[] = {
817         &dev_attr_cable.attr,
818         &dev_attr_source.attr,
819         NULL,
820 };
821
822 static const struct attribute_group hdmi_attribute_group = {
823         .name = "hdmi",
824         .is_visible = SYSFS_GROUP_VISIBLE(hdmi),
825         .attrs = hdmi_attrs,
826 };
827
828 /*
829  * Alienware GFX amplifier support
830  * - Currently supports reading cable status
831  * - Leaving expansion room to possibly support dock/undock events later
832  */
833 static ssize_t status_show(struct device *dev, struct device_attribute *attr,
834                            char *buf)
835 {
836         struct wmax_basic_args in_args = {
837                 .arg = 0,
838         };
839         acpi_status status;
840         u32 out_data;
841
842         status =
843             alienware_wmax_command(&in_args, sizeof(in_args),
844                                    WMAX_METHOD_AMPLIFIER_CABLE, &out_data);
845         if (ACPI_SUCCESS(status)) {
846                 if (out_data == 0)
847                         return sysfs_emit(buf, "[unconnected] connected unknown\n");
848                 else if (out_data == 1)
849                         return sysfs_emit(buf, "unconnected [connected] unknown\n");
850         }
851         pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status);
852         return sysfs_emit(buf, "unconnected connected [unknown]\n");
853 }
854
855 static DEVICE_ATTR_RO(status);
856
857 static bool amplifier_group_visible(struct kobject *kobj)
858 {
859         return quirks->amplifier;
860 }
861 DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(amplifier);
862
863 static struct attribute *amplifier_attrs[] = {
864         &dev_attr_status.attr,
865         NULL,
866 };
867
868 static const struct attribute_group amplifier_attribute_group = {
869         .name = "amplifier",
870         .is_visible = SYSFS_GROUP_VISIBLE(amplifier),
871         .attrs = amplifier_attrs,
872 };
873
874 /*
875  * Deep Sleep Control support
876  * - Modifies BIOS setting for deep sleep control allowing extra wakeup events
877  */
878 static ssize_t deepsleep_show(struct device *dev, struct device_attribute *attr,
879                               char *buf)
880 {
881         struct wmax_basic_args in_args = {
882                 .arg = 0,
883         };
884         acpi_status status;
885         u32 out_data;
886
887         status = alienware_wmax_command(&in_args, sizeof(in_args),
888                                         WMAX_METHOD_DEEP_SLEEP_STATUS, &out_data);
889         if (ACPI_SUCCESS(status)) {
890                 if (out_data == 0)
891                         return sysfs_emit(buf, "[disabled] s5 s5_s4\n");
892                 else if (out_data == 1)
893                         return sysfs_emit(buf, "disabled [s5] s5_s4\n");
894                 else if (out_data == 2)
895                         return sysfs_emit(buf, "disabled s5 [s5_s4]\n");
896         }
897         pr_err("alienware-wmi: unknown deep sleep status: %d\n", status);
898         return sysfs_emit(buf, "disabled s5 s5_s4 [unknown]\n");
899 }
900
901 static ssize_t deepsleep_store(struct device *dev, struct device_attribute *attr,
902                                const char *buf, size_t count)
903 {
904         struct wmax_basic_args args;
905         acpi_status status;
906
907         if (strcmp(buf, "disabled\n") == 0)
908                 args.arg = 0;
909         else if (strcmp(buf, "s5\n") == 0)
910                 args.arg = 1;
911         else
912                 args.arg = 2;
913         pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf);
914
915         status = alienware_wmax_command(&args, sizeof(args),
916                                         WMAX_METHOD_DEEP_SLEEP_CONTROL, NULL);
917
918         if (ACPI_FAILURE(status))
919                 pr_err("alienware-wmi: deep sleep control failed: results: %u\n",
920                         status);
921         return count;
922 }
923
924 static DEVICE_ATTR_RW(deepsleep);
925
926 static bool deepsleep_group_visible(struct kobject *kobj)
927 {
928         return quirks->deepslp;
929 }
930 DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(deepsleep);
931
932 static struct attribute *deepsleep_attrs[] = {
933         &dev_attr_deepsleep.attr,
934         NULL,
935 };
936
937 static const struct attribute_group deepsleep_attribute_group = {
938         .name = "deepsleep",
939         .is_visible = SYSFS_GROUP_VISIBLE(deepsleep),
940         .attrs = deepsleep_attrs,
941 };
942
943 /*
944  * Thermal Profile control
945  *  - Provides thermal profile control through the Platform Profile API
946  */
947 #define WMAX_THERMAL_TABLE_MASK         GENMASK(7, 4)
948 #define WMAX_THERMAL_MODE_MASK          GENMASK(3, 0)
949 #define WMAX_SENSOR_ID_MASK             BIT(8)
950
951 static bool is_wmax_thermal_code(u32 code)
952 {
953         if (code & WMAX_SENSOR_ID_MASK)
954                 return false;
955
956         if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST)
957                 return false;
958
959         if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC &&
960             (code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET)
961                 return true;
962
963         if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT &&
964             (code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER)
965                 return true;
966
967         return false;
968 }
969
970 static int wmax_thermal_information(u8 operation, u8 arg, u32 *out_data)
971 {
972         struct wmax_u32_args in_args = {
973                 .operation = operation,
974                 .arg1 = arg,
975                 .arg2 = 0,
976                 .arg3 = 0,
977         };
978         acpi_status status;
979
980         status = alienware_wmax_command(&in_args, sizeof(in_args),
981                                         WMAX_METHOD_THERMAL_INFORMATION,
982                                         out_data);
983
984         if (ACPI_FAILURE(status))
985                 return -EIO;
986
987         if (*out_data == WMAX_FAILURE_CODE)
988                 return -EBADRQC;
989
990         return 0;
991 }
992
993 static int wmax_thermal_control(u8 profile)
994 {
995         struct wmax_u32_args in_args = {
996                 .operation = WMAX_OPERATION_ACTIVATE_PROFILE,
997                 .arg1 = profile,
998                 .arg2 = 0,
999                 .arg3 = 0,
1000         };
1001         acpi_status status;
1002         u32 out_data;
1003
1004         status = alienware_wmax_command(&in_args, sizeof(in_args),
1005                                         WMAX_METHOD_THERMAL_CONTROL,
1006                                         &out_data);
1007
1008         if (ACPI_FAILURE(status))
1009                 return -EIO;
1010
1011         if (out_data == WMAX_FAILURE_CODE)
1012                 return -EBADRQC;
1013
1014         return 0;
1015 }
1016
1017 static int wmax_game_shift_status(u8 operation, u32 *out_data)
1018 {
1019         struct wmax_u32_args in_args = {
1020                 .operation = operation,
1021                 .arg1 = 0,
1022                 .arg2 = 0,
1023                 .arg3 = 0,
1024         };
1025         acpi_status status;
1026
1027         status = alienware_wmax_command(&in_args, sizeof(in_args),
1028                                         WMAX_METHOD_GAME_SHIFT_STATUS,
1029                                         out_data);
1030
1031         if (ACPI_FAILURE(status))
1032                 return -EIO;
1033
1034         if (*out_data == WMAX_FAILURE_CODE)
1035                 return -EOPNOTSUPP;
1036
1037         return 0;
1038 }
1039
1040 static int thermal_profile_get(struct device *dev,
1041                                enum platform_profile_option *profile)
1042 {
1043         u32 out_data;
1044         int ret;
1045
1046         ret = wmax_thermal_information(WMAX_OPERATION_CURRENT_PROFILE,
1047                                        0, &out_data);
1048
1049         if (ret < 0)
1050                 return ret;
1051
1052         if (out_data == WMAX_THERMAL_MODE_GMODE) {
1053                 *profile = PLATFORM_PROFILE_PERFORMANCE;
1054                 return 0;
1055         }
1056
1057         if (!is_wmax_thermal_code(out_data))
1058                 return -ENODATA;
1059
1060         out_data &= WMAX_THERMAL_MODE_MASK;
1061         *profile = wmax_mode_to_platform_profile[out_data];
1062
1063         return 0;
1064 }
1065
1066 static int thermal_profile_set(struct device *dev,
1067                                enum platform_profile_option profile)
1068 {
1069         if (quirks->gmode) {
1070                 u32 gmode_status;
1071                 int ret;
1072
1073                 ret = wmax_game_shift_status(WMAX_OPERATION_GET_GAME_SHIFT_STATUS,
1074                                              &gmode_status);
1075
1076                 if (ret < 0)
1077                         return ret;
1078
1079                 if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) ||
1080                     (profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) {
1081                         ret = wmax_game_shift_status(WMAX_OPERATION_TOGGLE_GAME_SHIFT,
1082                                                      &gmode_status);
1083
1084                         if (ret < 0)
1085                                 return ret;
1086                 }
1087         }
1088
1089         return wmax_thermal_control(supported_thermal_profiles[profile]);
1090 }
1091
1092 static int thermal_profile_probe(void *drvdata, unsigned long *choices)
1093 {
1094         enum platform_profile_option profile;
1095         enum wmax_thermal_mode mode;
1096         u8 sys_desc[4];
1097         u32 first_mode;
1098         u32 out_data;
1099         int ret;
1100
1101         ret = wmax_thermal_information(WMAX_OPERATION_SYS_DESCRIPTION,
1102                                        0, (u32 *) &sys_desc);
1103         if (ret < 0)
1104                 return ret;
1105
1106         first_mode = sys_desc[0] + sys_desc[1];
1107
1108         for (u32 i = 0; i < sys_desc[3]; i++) {
1109                 ret = wmax_thermal_information(WMAX_OPERATION_LIST_IDS,
1110                                                i + first_mode, &out_data);
1111
1112                 if (ret == -EIO)
1113                         return ret;
1114
1115                 if (ret == -EBADRQC)
1116                         break;
1117
1118                 if (!is_wmax_thermal_code(out_data))
1119                         continue;
1120
1121                 mode = out_data & WMAX_THERMAL_MODE_MASK;
1122                 profile = wmax_mode_to_platform_profile[mode];
1123                 supported_thermal_profiles[profile] = out_data;
1124
1125                 set_bit(profile, choices);
1126         }
1127
1128         if (bitmap_empty(choices, PLATFORM_PROFILE_LAST))
1129                 return -ENODEV;
1130
1131         if (quirks->gmode) {
1132                 supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] =
1133                         WMAX_THERMAL_MODE_GMODE;
1134
1135                 set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
1136         }
1137
1138         return 0;
1139 }
1140
1141 static const struct platform_profile_ops awcc_platform_profile_ops = {
1142         .probe = thermal_profile_probe,
1143         .profile_get = thermal_profile_get,
1144         .profile_set = thermal_profile_set,
1145 };
1146
1147 static int create_thermal_profile(struct platform_device *platform_device)
1148 {
1149         struct device *ppdev;
1150
1151         ppdev = devm_platform_profile_register(&platform_device->dev, "alienware-wmi",
1152                                                NULL, &awcc_platform_profile_ops);
1153
1154         return PTR_ERR_OR_ZERO(ppdev);
1155 }
1156
1157 /*
1158  * Platform Driver
1159  */
1160 static const struct attribute_group *alienfx_groups[] = {
1161         &zone_attribute_group,
1162         &hdmi_attribute_group,
1163         &amplifier_attribute_group,
1164         &deepsleep_attribute_group,
1165         NULL
1166 };
1167
1168 static struct platform_driver platform_driver = {
1169         .driver = {
1170                 .name = "alienware-wmi",
1171                 .dev_groups = alienfx_groups,
1172         },
1173 };
1174
1175 static int __init alienware_wmi_init(void)
1176 {
1177         int ret;
1178
1179         if (wmi_has_guid(LEGACY_CONTROL_GUID))
1180                 interface = LEGACY;
1181         else if (wmi_has_guid(WMAX_CONTROL_GUID))
1182                 interface = WMAX;
1183         else {
1184                 pr_warn("alienware-wmi: No known WMI GUID found\n");
1185                 return -ENODEV;
1186         }
1187
1188         dmi_check_system(alienware_quirks);
1189         if (quirks == NULL)
1190                 quirks = &quirk_unknown;
1191
1192         if (force_platform_profile)
1193                 quirks->thermal = true;
1194
1195         if (force_gmode) {
1196                 if (quirks->thermal)
1197                         quirks->gmode = true;
1198                 else
1199                         pr_warn("force_gmode requires platform profile support\n");
1200         }
1201
1202         ret = platform_driver_register(&platform_driver);
1203         if (ret)
1204                 goto fail_platform_driver;
1205         platform_device = platform_device_alloc("alienware-wmi", PLATFORM_DEVID_NONE);
1206         if (!platform_device) {
1207                 ret = -ENOMEM;
1208                 goto fail_platform_device1;
1209         }
1210         ret = platform_device_add(platform_device);
1211         if (ret)
1212                 goto fail_platform_device2;
1213
1214         if (quirks->thermal) {
1215                 ret = create_thermal_profile(platform_device);
1216                 if (ret)
1217                         goto fail_prep_thermal_profile;
1218         }
1219
1220         if (quirks->num_zones > 0) {
1221                 ret = alienware_zone_init(platform_device);
1222                 if (ret)
1223                         goto fail_prep_zones;
1224         }
1225
1226         return 0;
1227
1228 fail_prep_zones:
1229         alienware_zone_exit(platform_device);
1230 fail_prep_thermal_profile:
1231         platform_device_del(platform_device);
1232 fail_platform_device2:
1233         platform_device_put(platform_device);
1234 fail_platform_device1:
1235         platform_driver_unregister(&platform_driver);
1236 fail_platform_driver:
1237         return ret;
1238 }
1239
1240 module_init(alienware_wmi_init);
1241
1242 static void __exit alienware_wmi_exit(void)
1243 {
1244         alienware_zone_exit(platform_device);
1245         platform_device_unregister(platform_device);
1246         platform_driver_unregister(&platform_driver);
1247 }
1248
1249 module_exit(alienware_wmi_exit);
This page took 0.102674 seconds and 4 git commands to generate.