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