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