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 	&amplifier_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