xref: /linux/drivers/platform/x86/dell/alienware-wmi-wmax.c (revision f694f30e81c4ade358eb8c75273bac1a48f0cb8f)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Alienware WMAX WMI device driver
4  *
5  * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com>
6  * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com>
7  */
8 
9 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
10 
11 #include <linux/bitfield.h>
12 #include <linux/bits.h>
13 #include <linux/dmi.h>
14 #include <linux/moduleparam.h>
15 #include <linux/platform_profile.h>
16 #include <linux/wmi.h>
17 #include "alienware-wmi.h"
18 
19 #define WMAX_METHOD_HDMI_SOURCE			0x1
20 #define WMAX_METHOD_HDMI_STATUS			0x2
21 #define WMAX_METHOD_HDMI_CABLE			0x5
22 #define WMAX_METHOD_AMPLIFIER_CABLE		0x6
23 #define WMAX_METHOD_DEEP_SLEEP_CONTROL		0x0B
24 #define WMAX_METHOD_DEEP_SLEEP_STATUS		0x0C
25 #define WMAX_METHOD_BRIGHTNESS			0x3
26 #define WMAX_METHOD_ZONE_CONTROL		0x4
27 #define WMAX_METHOD_THERMAL_INFORMATION		0x14
28 #define WMAX_METHOD_THERMAL_CONTROL		0x15
29 #define WMAX_METHOD_GAME_SHIFT_STATUS		0x25
30 
31 #define WMAX_THERMAL_MODE_GMODE			0xAB
32 
33 #define WMAX_FAILURE_CODE			0xFFFFFFFF
34 #define WMAX_THERMAL_TABLE_MASK			GENMASK(7, 4)
35 #define WMAX_THERMAL_MODE_MASK			GENMASK(3, 0)
36 #define WMAX_SENSOR_ID_MASK			BIT(8)
37 
38 static bool force_platform_profile;
39 module_param_unsafe(force_platform_profile, bool, 0);
40 MODULE_PARM_DESC(force_platform_profile, "Forces auto-detecting thermal profiles without checking if WMI thermal backend is available");
41 
42 static bool force_gmode;
43 module_param_unsafe(force_gmode, bool, 0);
44 MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected");
45 
46 struct awcc_quirks {
47 	bool pprof;
48 	bool gmode;
49 };
50 
51 static struct awcc_quirks g_series_quirks = {
52 	.pprof = true,
53 	.gmode = true,
54 };
55 
56 static struct awcc_quirks generic_quirks = {
57 	.pprof = true,
58 	.gmode = false,
59 };
60 
61 static struct awcc_quirks empty_quirks;
62 
63 static const struct dmi_system_id awcc_dmi_table[] __initconst = {
64 	{
65 		.ident = "Alienware m16 R1 AMD",
66 		.matches = {
67 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
68 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R1 AMD"),
69 		},
70 		.driver_data = &generic_quirks,
71 	},
72 	{
73 		.ident = "Alienware m17 R5",
74 		.matches = {
75 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
76 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"),
77 		},
78 		.driver_data = &generic_quirks,
79 	},
80 	{
81 		.ident = "Alienware m18 R2",
82 		.matches = {
83 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
84 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R2"),
85 		},
86 		.driver_data = &generic_quirks,
87 	},
88 	{
89 		.ident = "Alienware x15 R1",
90 		.matches = {
91 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
92 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R1"),
93 		},
94 		.driver_data = &generic_quirks,
95 	},
96 	{
97 		.ident = "Alienware x17 R2",
98 		.matches = {
99 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
100 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x17 R2"),
101 		},
102 		.driver_data = &generic_quirks,
103 	},
104 	{
105 		.ident = "Dell Inc. G15 5510",
106 		.matches = {
107 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
108 			DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5510"),
109 		},
110 		.driver_data = &g_series_quirks,
111 	},
112 	{
113 		.ident = "Dell Inc. G15 5511",
114 		.matches = {
115 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
116 			DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"),
117 		},
118 		.driver_data = &g_series_quirks,
119 	},
120 	{
121 		.ident = "Dell Inc. G15 5515",
122 		.matches = {
123 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
124 			DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5515"),
125 		},
126 		.driver_data = &g_series_quirks,
127 	},
128 	{
129 		.ident = "Dell Inc. G3 3500",
130 		.matches = {
131 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
132 			DMI_MATCH(DMI_PRODUCT_NAME, "G3 3500"),
133 		},
134 		.driver_data = &g_series_quirks,
135 	},
136 	{
137 		.ident = "Dell Inc. G3 3590",
138 		.matches = {
139 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
140 			DMI_MATCH(DMI_PRODUCT_NAME, "G3 3590"),
141 		},
142 		.driver_data = &g_series_quirks,
143 	},
144 	{
145 		.ident = "Dell Inc. G5 5500",
146 		.matches = {
147 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
148 			DMI_MATCH(DMI_PRODUCT_NAME, "G5 5500"),
149 		},
150 		.driver_data = &g_series_quirks,
151 	},
152 };
153 
154 enum WMAX_THERMAL_INFORMATION_OPERATIONS {
155 	WMAX_OPERATION_SYS_DESCRIPTION		= 0x02,
156 	WMAX_OPERATION_LIST_IDS			= 0x03,
157 	WMAX_OPERATION_CURRENT_PROFILE		= 0x0B,
158 };
159 
160 enum WMAX_THERMAL_CONTROL_OPERATIONS {
161 	WMAX_OPERATION_ACTIVATE_PROFILE		= 0x01,
162 };
163 
164 enum WMAX_GAME_SHIFT_STATUS_OPERATIONS {
165 	WMAX_OPERATION_TOGGLE_GAME_SHIFT	= 0x01,
166 	WMAX_OPERATION_GET_GAME_SHIFT_STATUS	= 0x02,
167 };
168 
169 enum WMAX_THERMAL_TABLES {
170 	WMAX_THERMAL_TABLE_BASIC		= 0x90,
171 	WMAX_THERMAL_TABLE_USTT			= 0xA0,
172 };
173 
174 enum wmax_thermal_mode {
175 	THERMAL_MODE_USTT_BALANCED,
176 	THERMAL_MODE_USTT_BALANCED_PERFORMANCE,
177 	THERMAL_MODE_USTT_COOL,
178 	THERMAL_MODE_USTT_QUIET,
179 	THERMAL_MODE_USTT_PERFORMANCE,
180 	THERMAL_MODE_USTT_LOW_POWER,
181 	THERMAL_MODE_BASIC_QUIET,
182 	THERMAL_MODE_BASIC_BALANCED,
183 	THERMAL_MODE_BASIC_BALANCED_PERFORMANCE,
184 	THERMAL_MODE_BASIC_PERFORMANCE,
185 	THERMAL_MODE_LAST,
186 };
187 
188 struct wmax_led_args {
189 	u32 led_mask;
190 	struct color_platform colors;
191 	u8 state;
192 } __packed;
193 
194 struct wmax_brightness_args {
195 	u32 led_mask;
196 	u32 percentage;
197 };
198 
199 struct wmax_basic_args {
200 	u8 arg;
201 };
202 
203 struct wmax_u32_args {
204 	u8 operation;
205 	u8 arg1;
206 	u8 arg2;
207 	u8 arg3;
208 };
209 
210 struct awcc_priv {
211 	struct wmi_device *wdev;
212 	struct device *ppdev;
213 	enum wmax_thermal_mode supported_thermal_profiles[PLATFORM_PROFILE_LAST];
214 };
215 
216 static const enum platform_profile_option wmax_mode_to_platform_profile[THERMAL_MODE_LAST] = {
217 	[THERMAL_MODE_USTT_BALANCED]			= PLATFORM_PROFILE_BALANCED,
218 	[THERMAL_MODE_USTT_BALANCED_PERFORMANCE]	= PLATFORM_PROFILE_BALANCED_PERFORMANCE,
219 	[THERMAL_MODE_USTT_COOL]			= PLATFORM_PROFILE_COOL,
220 	[THERMAL_MODE_USTT_QUIET]			= PLATFORM_PROFILE_QUIET,
221 	[THERMAL_MODE_USTT_PERFORMANCE]			= PLATFORM_PROFILE_PERFORMANCE,
222 	[THERMAL_MODE_USTT_LOW_POWER]			= PLATFORM_PROFILE_LOW_POWER,
223 	[THERMAL_MODE_BASIC_QUIET]			= PLATFORM_PROFILE_QUIET,
224 	[THERMAL_MODE_BASIC_BALANCED]			= PLATFORM_PROFILE_BALANCED,
225 	[THERMAL_MODE_BASIC_BALANCED_PERFORMANCE]	= PLATFORM_PROFILE_BALANCED_PERFORMANCE,
226 	[THERMAL_MODE_BASIC_PERFORMANCE]		= PLATFORM_PROFILE_PERFORMANCE,
227 };
228 
229 static struct awcc_quirks *awcc;
230 
231 /*
232  *	The HDMI mux sysfs node indicates the status of the HDMI input mux.
233  *	It can toggle between standard system GPU output and HDMI input.
234  */
235 static ssize_t cable_show(struct device *dev, struct device_attribute *attr,
236 			  char *buf)
237 {
238 	struct alienfx_platdata *pdata = dev_get_platdata(dev);
239 	struct wmax_basic_args in_args = {
240 		.arg = 0,
241 	};
242 	u32 out_data;
243 	int ret;
244 
245 	ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_HDMI_CABLE,
246 				    &in_args, sizeof(in_args), &out_data);
247 	if (!ret) {
248 		if (out_data == 0)
249 			return sysfs_emit(buf, "[unconnected] connected unknown\n");
250 		else if (out_data == 1)
251 			return sysfs_emit(buf, "unconnected [connected] unknown\n");
252 	}
253 
254 	pr_err("alienware-wmi: unknown HDMI cable status: %d\n", ret);
255 	return sysfs_emit(buf, "unconnected connected [unknown]\n");
256 }
257 
258 static ssize_t source_show(struct device *dev, struct device_attribute *attr,
259 			   char *buf)
260 {
261 	struct alienfx_platdata *pdata = dev_get_platdata(dev);
262 	struct wmax_basic_args in_args = {
263 		.arg = 0,
264 	};
265 	u32 out_data;
266 	int ret;
267 
268 	ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_HDMI_STATUS,
269 				    &in_args, sizeof(in_args), &out_data);
270 	if (!ret) {
271 		if (out_data == 1)
272 			return sysfs_emit(buf, "[input] gpu unknown\n");
273 		else if (out_data == 2)
274 			return sysfs_emit(buf, "input [gpu] unknown\n");
275 	}
276 
277 	pr_err("alienware-wmi: unknown HDMI source status: %u\n", ret);
278 	return sysfs_emit(buf, "input gpu [unknown]\n");
279 }
280 
281 static ssize_t source_store(struct device *dev, struct device_attribute *attr,
282 			    const char *buf, size_t count)
283 {
284 	struct alienfx_platdata *pdata = dev_get_platdata(dev);
285 	struct wmax_basic_args args;
286 	int ret;
287 
288 	if (strcmp(buf, "gpu\n") == 0)
289 		args.arg = 1;
290 	else if (strcmp(buf, "input\n") == 0)
291 		args.arg = 2;
292 	else
293 		args.arg = 3;
294 	pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);
295 
296 	ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_HDMI_SOURCE, &args,
297 				    sizeof(args), NULL);
298 	if (ret < 0)
299 		pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", ret);
300 
301 	return count;
302 }
303 
304 static DEVICE_ATTR_RO(cable);
305 static DEVICE_ATTR_RW(source);
306 
307 static bool hdmi_group_visible(struct kobject *kobj)
308 {
309 	return alienware_interface == WMAX && alienfx->hdmi_mux;
310 }
311 DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(hdmi);
312 
313 static struct attribute *hdmi_attrs[] = {
314 	&dev_attr_cable.attr,
315 	&dev_attr_source.attr,
316 	NULL,
317 };
318 
319 const struct attribute_group wmax_hdmi_attribute_group = {
320 	.name = "hdmi",
321 	.is_visible = SYSFS_GROUP_VISIBLE(hdmi),
322 	.attrs = hdmi_attrs,
323 };
324 
325 /*
326  * Alienware GFX amplifier support
327  * - Currently supports reading cable status
328  * - Leaving expansion room to possibly support dock/undock events later
329  */
330 static ssize_t status_show(struct device *dev, struct device_attribute *attr,
331 			   char *buf)
332 {
333 	struct alienfx_platdata *pdata = dev_get_platdata(dev);
334 	struct wmax_basic_args in_args = {
335 		.arg = 0,
336 	};
337 	u32 out_data;
338 	int ret;
339 
340 	ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_AMPLIFIER_CABLE,
341 				    &in_args, sizeof(in_args), &out_data);
342 	if (!ret) {
343 		if (out_data == 0)
344 			return sysfs_emit(buf, "[unconnected] connected unknown\n");
345 		else if (out_data == 1)
346 			return sysfs_emit(buf, "unconnected [connected] unknown\n");
347 	}
348 
349 	pr_err("alienware-wmi: unknown amplifier cable status: %d\n", ret);
350 	return sysfs_emit(buf, "unconnected connected [unknown]\n");
351 }
352 
353 static DEVICE_ATTR_RO(status);
354 
355 static bool amplifier_group_visible(struct kobject *kobj)
356 {
357 	return alienware_interface == WMAX && alienfx->amplifier;
358 }
359 DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(amplifier);
360 
361 static struct attribute *amplifier_attrs[] = {
362 	&dev_attr_status.attr,
363 	NULL,
364 };
365 
366 const struct attribute_group wmax_amplifier_attribute_group = {
367 	.name = "amplifier",
368 	.is_visible = SYSFS_GROUP_VISIBLE(amplifier),
369 	.attrs = amplifier_attrs,
370 };
371 
372 /*
373  * Deep Sleep Control support
374  * - Modifies BIOS setting for deep sleep control allowing extra wakeup events
375  */
376 static ssize_t deepsleep_show(struct device *dev, struct device_attribute *attr,
377 			      char *buf)
378 {
379 	struct alienfx_platdata *pdata = dev_get_platdata(dev);
380 	struct wmax_basic_args in_args = {
381 		.arg = 0,
382 	};
383 	u32 out_data;
384 	int ret;
385 
386 	ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_DEEP_SLEEP_STATUS,
387 				    &in_args, sizeof(in_args), &out_data);
388 	if (!ret) {
389 		if (out_data == 0)
390 			return sysfs_emit(buf, "[disabled] s5 s5_s4\n");
391 		else if (out_data == 1)
392 			return sysfs_emit(buf, "disabled [s5] s5_s4\n");
393 		else if (out_data == 2)
394 			return sysfs_emit(buf, "disabled s5 [s5_s4]\n");
395 	}
396 
397 	pr_err("alienware-wmi: unknown deep sleep status: %d\n", ret);
398 	return sysfs_emit(buf, "disabled s5 s5_s4 [unknown]\n");
399 }
400 
401 static ssize_t deepsleep_store(struct device *dev, struct device_attribute *attr,
402 			       const char *buf, size_t count)
403 {
404 	struct alienfx_platdata *pdata = dev_get_platdata(dev);
405 	struct wmax_basic_args args;
406 	int ret;
407 
408 	if (strcmp(buf, "disabled\n") == 0)
409 		args.arg = 0;
410 	else if (strcmp(buf, "s5\n") == 0)
411 		args.arg = 1;
412 	else
413 		args.arg = 2;
414 	pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf);
415 
416 	ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_DEEP_SLEEP_CONTROL,
417 				    &args, sizeof(args), NULL);
418 	if (!ret)
419 		pr_err("alienware-wmi: deep sleep control failed: results: %u\n", ret);
420 
421 	return count;
422 }
423 
424 static DEVICE_ATTR_RW(deepsleep);
425 
426 static bool deepsleep_group_visible(struct kobject *kobj)
427 {
428 	return alienware_interface == WMAX && alienfx->deepslp;
429 }
430 DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(deepsleep);
431 
432 static struct attribute *deepsleep_attrs[] = {
433 	&dev_attr_deepsleep.attr,
434 	NULL,
435 };
436 
437 const struct attribute_group wmax_deepsleep_attribute_group = {
438 	.name = "deepsleep",
439 	.is_visible = SYSFS_GROUP_VISIBLE(deepsleep),
440 	.attrs = deepsleep_attrs,
441 };
442 
443 /*
444  * Thermal Profile control
445  *  - Provides thermal profile control through the Platform Profile API
446  */
447 static bool is_wmax_thermal_code(u32 code)
448 {
449 	if (code & WMAX_SENSOR_ID_MASK)
450 		return false;
451 
452 	if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST)
453 		return false;
454 
455 	if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC &&
456 	    (code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET)
457 		return true;
458 
459 	if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT &&
460 	    (code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER)
461 		return true;
462 
463 	return false;
464 }
465 
466 static int wmax_thermal_information(struct wmi_device *wdev, u8 operation,
467 				    u8 arg, u32 *out_data)
468 {
469 	struct wmax_u32_args in_args = {
470 		.operation = operation,
471 		.arg1 = arg,
472 		.arg2 = 0,
473 		.arg3 = 0,
474 	};
475 	int ret;
476 
477 	ret = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_INFORMATION,
478 				    &in_args, sizeof(in_args), out_data);
479 	if (ret < 0)
480 		return ret;
481 
482 	if (*out_data == WMAX_FAILURE_CODE)
483 		return -EBADRQC;
484 
485 	return 0;
486 }
487 
488 static int wmax_thermal_control(struct wmi_device *wdev, u8 profile)
489 {
490 	struct wmax_u32_args in_args = {
491 		.operation = WMAX_OPERATION_ACTIVATE_PROFILE,
492 		.arg1 = profile,
493 		.arg2 = 0,
494 		.arg3 = 0,
495 	};
496 	u32 out_data;
497 	int ret;
498 
499 	ret = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_CONTROL,
500 				    &in_args, sizeof(in_args), &out_data);
501 	if (ret)
502 		return ret;
503 
504 	if (out_data == WMAX_FAILURE_CODE)
505 		return -EBADRQC;
506 
507 	return 0;
508 }
509 
510 static int wmax_game_shift_status(struct wmi_device *wdev, u8 operation,
511 				  u32 *out_data)
512 {
513 	struct wmax_u32_args in_args = {
514 		.operation = operation,
515 		.arg1 = 0,
516 		.arg2 = 0,
517 		.arg3 = 0,
518 	};
519 	int ret;
520 
521 	ret = alienware_wmi_command(wdev, WMAX_METHOD_GAME_SHIFT_STATUS,
522 				    &in_args, sizeof(in_args), out_data);
523 	if (ret < 0)
524 		return ret;
525 
526 	if (*out_data == WMAX_FAILURE_CODE)
527 		return -EOPNOTSUPP;
528 
529 	return 0;
530 }
531 
532 static int thermal_profile_get(struct device *dev,
533 			       enum platform_profile_option *profile)
534 {
535 	struct awcc_priv *priv = dev_get_drvdata(dev);
536 	u32 out_data;
537 	int ret;
538 
539 	ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_CURRENT_PROFILE,
540 				       0, &out_data);
541 
542 	if (ret < 0)
543 		return ret;
544 
545 	if (out_data == WMAX_THERMAL_MODE_GMODE) {
546 		*profile = PLATFORM_PROFILE_PERFORMANCE;
547 		return 0;
548 	}
549 
550 	if (!is_wmax_thermal_code(out_data))
551 		return -ENODATA;
552 
553 	out_data &= WMAX_THERMAL_MODE_MASK;
554 	*profile = wmax_mode_to_platform_profile[out_data];
555 
556 	return 0;
557 }
558 
559 static int thermal_profile_set(struct device *dev,
560 			       enum platform_profile_option profile)
561 {
562 	struct awcc_priv *priv = dev_get_drvdata(dev);
563 
564 	if (awcc->gmode) {
565 		u32 gmode_status;
566 		int ret;
567 
568 		ret = wmax_game_shift_status(priv->wdev,
569 					     WMAX_OPERATION_GET_GAME_SHIFT_STATUS,
570 					     &gmode_status);
571 
572 		if (ret < 0)
573 			return ret;
574 
575 		if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) ||
576 		    (profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) {
577 			ret = wmax_game_shift_status(priv->wdev,
578 						     WMAX_OPERATION_TOGGLE_GAME_SHIFT,
579 						     &gmode_status);
580 
581 			if (ret < 0)
582 				return ret;
583 		}
584 	}
585 
586 	return wmax_thermal_control(priv->wdev,
587 				    priv->supported_thermal_profiles[profile]);
588 }
589 
590 static int thermal_profile_probe(void *drvdata, unsigned long *choices)
591 {
592 	enum platform_profile_option profile;
593 	struct awcc_priv *priv = drvdata;
594 	enum wmax_thermal_mode mode;
595 	u8 sys_desc[4];
596 	u32 first_mode;
597 	u32 out_data;
598 	int ret;
599 
600 	ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_SYS_DESCRIPTION,
601 				       0, (u32 *) &sys_desc);
602 	if (ret < 0)
603 		return ret;
604 
605 	first_mode = sys_desc[0] + sys_desc[1];
606 
607 	for (u32 i = 0; i < sys_desc[3]; i++) {
608 		ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_LIST_IDS,
609 					       i + first_mode, &out_data);
610 
611 		if (ret == -EIO)
612 			return ret;
613 
614 		if (ret == -EBADRQC)
615 			break;
616 
617 		if (!is_wmax_thermal_code(out_data))
618 			continue;
619 
620 		mode = out_data & WMAX_THERMAL_MODE_MASK;
621 		profile = wmax_mode_to_platform_profile[mode];
622 		priv->supported_thermal_profiles[profile] = out_data;
623 
624 		set_bit(profile, choices);
625 	}
626 
627 	if (bitmap_empty(choices, PLATFORM_PROFILE_LAST))
628 		return -ENODEV;
629 
630 	if (awcc->gmode) {
631 		priv->supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] =
632 			WMAX_THERMAL_MODE_GMODE;
633 
634 		set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
635 	}
636 
637 	return 0;
638 }
639 
640 static const struct platform_profile_ops awcc_platform_profile_ops = {
641 	.probe = thermal_profile_probe,
642 	.profile_get = thermal_profile_get,
643 	.profile_set = thermal_profile_set,
644 };
645 
646 static int awcc_platform_profile_init(struct wmi_device *wdev)
647 {
648 	struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
649 
650 	priv->ppdev = devm_platform_profile_register(&wdev->dev, "alienware-wmi",
651 						     priv, &awcc_platform_profile_ops);
652 
653 	return PTR_ERR_OR_ZERO(priv->ppdev);
654 }
655 
656 static int alienware_awcc_setup(struct wmi_device *wdev)
657 {
658 	struct awcc_priv *priv;
659 	int ret;
660 
661 	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
662 	if (!priv)
663 		return -ENOMEM;
664 
665 	priv->wdev = wdev;
666 	dev_set_drvdata(&wdev->dev, priv);
667 
668 	if (awcc->pprof) {
669 		ret = awcc_platform_profile_init(wdev);
670 		if (ret)
671 			return ret;
672 	}
673 
674 	return 0;
675 }
676 
677 /*
678  * WMAX WMI driver
679  */
680 static int wmax_wmi_update_led(struct alienfx_priv *priv,
681 			       struct wmi_device *wdev, u8 location)
682 {
683 	struct wmax_led_args in_args = {
684 		.led_mask = 1 << location,
685 		.colors = priv->colors[location],
686 		.state = priv->lighting_control_state,
687 	};
688 
689 	return alienware_wmi_command(wdev, WMAX_METHOD_ZONE_CONTROL, &in_args,
690 				     sizeof(in_args), NULL);
691 }
692 
693 static int wmax_wmi_update_brightness(struct alienfx_priv *priv,
694 				      struct wmi_device *wdev, u8 brightness)
695 {
696 	struct wmax_brightness_args in_args = {
697 		.led_mask = 0xFF,
698 		.percentage = brightness,
699 	};
700 
701 	return alienware_wmi_command(wdev, WMAX_METHOD_BRIGHTNESS, &in_args,
702 				     sizeof(in_args), NULL);
703 }
704 
705 static int wmax_wmi_probe(struct wmi_device *wdev, const void *context)
706 {
707 	struct alienfx_platdata pdata = {
708 		.wdev = wdev,
709 		.ops = {
710 			.upd_led = wmax_wmi_update_led,
711 			.upd_brightness = wmax_wmi_update_brightness,
712 		},
713 	};
714 	int ret;
715 
716 	if (awcc)
717 		ret = alienware_awcc_setup(wdev);
718 	else
719 		ret = alienware_alienfx_setup(&pdata);
720 
721 	return ret;
722 }
723 
724 static const struct wmi_device_id alienware_wmax_device_id_table[] = {
725 	{ WMAX_CONTROL_GUID, NULL },
726 	{ },
727 };
728 MODULE_DEVICE_TABLE(wmi, alienware_wmax_device_id_table);
729 
730 static struct wmi_driver alienware_wmax_wmi_driver = {
731 	.driver = {
732 		.name = "alienware-wmi-wmax",
733 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
734 	},
735 	.id_table = alienware_wmax_device_id_table,
736 	.probe = wmax_wmi_probe,
737 	.no_singleton = true,
738 };
739 
740 int __init alienware_wmax_wmi_init(void)
741 {
742 	const struct dmi_system_id *id;
743 
744 	id = dmi_first_match(awcc_dmi_table);
745 	if (id)
746 		awcc = id->driver_data;
747 
748 	if (force_platform_profile) {
749 		if (!awcc)
750 			awcc = &empty_quirks;
751 
752 		awcc->pprof = true;
753 	}
754 
755 	if (force_gmode) {
756 		if (awcc)
757 			awcc->gmode = true;
758 		else
759 			pr_warn("force_gmode requires platform profile support\n");
760 	}
761 
762 	return wmi_driver_register(&alienware_wmax_wmi_driver);
763 }
764 
765 void __exit alienware_wmax_wmi_exit(void)
766 {
767 	wmi_driver_unregister(&alienware_wmax_wmi_driver);
768 }
769