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