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