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