xref: /linux/drivers/platform/x86/dell/alienware-wmi-wmax.c (revision 4663747812d1a272312d1b95cbd128f0cdb329f2)
18cc2c415SKurt Borja // SPDX-License-Identifier: GPL-2.0-or-later
28cc2c415SKurt Borja /*
38cc2c415SKurt Borja  * Alienware WMAX WMI device driver
48cc2c415SKurt Borja  *
58cc2c415SKurt Borja  * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com>
68cc2c415SKurt Borja  * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com>
78cc2c415SKurt Borja  */
88cc2c415SKurt Borja 
98cc2c415SKurt Borja #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
108cc2c415SKurt Borja 
1132b6372dSKurt Borja #include <linux/array_size.h>
128cc2c415SKurt Borja #include <linux/bitfield.h>
13d6999078SKurt Borja #include <linux/bitmap.h>
148cc2c415SKurt Borja #include <linux/bits.h>
15b028fb49SKurt Borja #include <linux/debugfs.h>
168cc2c415SKurt Borja #include <linux/dmi.h>
17d6999078SKurt Borja #include <linux/hwmon.h>
1807ac2759SKurt Borja #include <linux/hwmon-sysfs.h>
1907ac2759SKurt Borja #include <linux/kstrtox.h>
2007ac2759SKurt Borja #include <linux/minmax.h>
218cc2c415SKurt Borja #include <linux/moduleparam.h>
228cc2c415SKurt Borja #include <linux/platform_profile.h>
2307ac2759SKurt Borja #include <linux/pm.h>
24b028fb49SKurt Borja #include <linux/seq_file.h>
25d6999078SKurt Borja #include <linux/units.h>
268cc2c415SKurt Borja #include <linux/wmi.h>
278cc2c415SKurt Borja #include "alienware-wmi.h"
288cc2c415SKurt Borja 
298cc2c415SKurt Borja #define WMAX_METHOD_HDMI_SOURCE			0x1
308cc2c415SKurt Borja #define WMAX_METHOD_HDMI_STATUS			0x2
318cc2c415SKurt Borja #define WMAX_METHOD_HDMI_CABLE			0x5
328cc2c415SKurt Borja #define WMAX_METHOD_AMPLIFIER_CABLE		0x6
338cc2c415SKurt Borja #define WMAX_METHOD_DEEP_SLEEP_CONTROL		0x0B
348cc2c415SKurt Borja #define WMAX_METHOD_DEEP_SLEEP_STATUS		0x0C
358cc2c415SKurt Borja #define WMAX_METHOD_BRIGHTNESS			0x3
368cc2c415SKurt Borja #define WMAX_METHOD_ZONE_CONTROL		0x4
378cc2c415SKurt Borja 
38d6999078SKurt Borja #define AWCC_METHOD_GET_FAN_SENSORS		0x13
398a1a0fb5SKurt Borja #define AWCC_METHOD_THERMAL_INFORMATION		0x14
408a1a0fb5SKurt Borja #define AWCC_METHOD_THERMAL_CONTROL		0x15
41aee5cf93SKurt Borja #define AWCC_METHOD_FWUP_GPIO_CONTROL		0x20
42aee5cf93SKurt Borja #define AWCC_METHOD_READ_TOTAL_GPIOS		0x21
43aee5cf93SKurt Borja #define AWCC_METHOD_READ_GPIO_STATUS		0x22
448a1a0fb5SKurt Borja #define AWCC_METHOD_GAME_SHIFT_STATUS		0x25
458cc2c415SKurt Borja 
468a1a0fb5SKurt Borja #define AWCC_FAILURE_CODE			0xFFFFFFFF
4745983d19SKurt Borja #define AWCC_FAILURE_CODE_2			0xFFFFFFFE
48a000da9dSKurt Borja 
49a000da9dSKurt Borja #define AWCC_SENSOR_ID_FLAG			BIT(8)
508a1a0fb5SKurt Borja #define AWCC_THERMAL_MODE_MASK			GENMASK(3, 0)
51a000da9dSKurt Borja #define AWCC_THERMAL_TABLE_MASK			GENMASK(7, 4)
52a000da9dSKurt Borja #define AWCC_RESOURCE_ID_MASK			GENMASK(7, 0)
538cc2c415SKurt Borja 
5432b6372dSKurt Borja /* Arbitrary limit based on supported models */
5532b6372dSKurt Borja #define AWCC_MAX_RES_COUNT			16
56d6999078SKurt Borja #define AWCC_ID_BITMAP_SIZE			(U8_MAX + 1)
57d6999078SKurt Borja #define AWCC_ID_BITMAP_LONGS			BITS_TO_LONGS(AWCC_ID_BITMAP_SIZE)
58d6999078SKurt Borja 
59d6999078SKurt Borja static bool force_hwmon;
60d6999078SKurt Borja module_param_unsafe(force_hwmon, bool, 0);
61d6999078SKurt Borja MODULE_PARM_DESC(force_hwmon, "Force probing for HWMON support without checking if the WMI backend is available");
6232b6372dSKurt Borja 
638cc2c415SKurt Borja static bool force_platform_profile;
648cc2c415SKurt Borja module_param_unsafe(force_platform_profile, bool, 0);
658cc2c415SKurt Borja MODULE_PARM_DESC(force_platform_profile, "Forces auto-detecting thermal profiles without checking if WMI thermal backend is available");
668cc2c415SKurt Borja 
678cc2c415SKurt Borja static bool force_gmode;
688cc2c415SKurt Borja module_param_unsafe(force_gmode, bool, 0);
698cc2c415SKurt Borja MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected");
708cc2c415SKurt Borja 
718cc2c415SKurt Borja struct awcc_quirks {
72d6999078SKurt Borja 	bool hwmon;
738cc2c415SKurt Borja 	bool pprof;
748cc2c415SKurt Borja 	bool gmode;
758cc2c415SKurt Borja };
768cc2c415SKurt Borja 
778cc2c415SKurt Borja static struct awcc_quirks g_series_quirks = {
78d6999078SKurt Borja 	.hwmon = true,
798cc2c415SKurt Borja 	.pprof = true,
808cc2c415SKurt Borja 	.gmode = true,
818cc2c415SKurt Borja };
828cc2c415SKurt Borja 
838cc2c415SKurt Borja static struct awcc_quirks generic_quirks = {
84d6999078SKurt Borja 	.hwmon = true,
858cc2c415SKurt Borja 	.pprof = true,
868cc2c415SKurt Borja 	.gmode = false,
878cc2c415SKurt Borja };
888cc2c415SKurt Borja 
898cc2c415SKurt Borja static struct awcc_quirks empty_quirks;
908cc2c415SKurt Borja 
918cc2c415SKurt Borja static const struct dmi_system_id awcc_dmi_table[] __initconst = {
928cc2c415SKurt Borja 	{
93202a8612SKurt Borja 		.ident = "Alienware Area-51m R2",
94202a8612SKurt Borja 		.matches = {
95202a8612SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
96202a8612SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware Area-51m R2"),
97202a8612SKurt Borja 		},
98202a8612SKurt Borja 		.driver_data = &generic_quirks,
99202a8612SKurt Borja 	},
100202a8612SKurt Borja 	{
101246f9bb6SKurt Borja 		.ident = "Alienware m15 R7",
102246f9bb6SKurt Borja 		.matches = {
103246f9bb6SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
104246f9bb6SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m15 R7"),
105246f9bb6SKurt Borja 		},
106246f9bb6SKurt Borja 		.driver_data = &generic_quirks,
107246f9bb6SKurt Borja 	},
108246f9bb6SKurt Borja 	{
109202a8612SKurt Borja 		.ident = "Alienware m16 R1",
110202a8612SKurt Borja 		.matches = {
111202a8612SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
112202a8612SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R1"),
113202a8612SKurt Borja 		},
114202a8612SKurt Borja 		.driver_data = &g_series_quirks,
115202a8612SKurt Borja 	},
116202a8612SKurt Borja 	{
1178cc2c415SKurt Borja 		.ident = "Alienware m16 R1 AMD",
1188cc2c415SKurt Borja 		.matches = {
1198cc2c415SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
1208cc2c415SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R1 AMD"),
1218cc2c415SKurt Borja 		},
122*e2468dc7SKurt Borja 		.driver_data = &generic_quirks,
1238cc2c415SKurt Borja 	},
1248cc2c415SKurt Borja 	{
125202a8612SKurt Borja 		.ident = "Alienware m16 R2",
126202a8612SKurt Borja 		.matches = {
127202a8612SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
128202a8612SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R2"),
129202a8612SKurt Borja 		},
1308cc2c415SKurt Borja 		.driver_data = &generic_quirks,
1318cc2c415SKurt Borja 	},
1328cc2c415SKurt Borja 	{
1338cc2c415SKurt Borja 		.ident = "Alienware m17 R5",
1348cc2c415SKurt Borja 		.matches = {
1358cc2c415SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
1368cc2c415SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"),
1378cc2c415SKurt Borja 		},
1388cc2c415SKurt Borja 		.driver_data = &generic_quirks,
1398cc2c415SKurt Borja 	},
1408cc2c415SKurt Borja 	{
1418cc2c415SKurt Borja 		.ident = "Alienware m18 R2",
1428cc2c415SKurt Borja 		.matches = {
1438cc2c415SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
1448cc2c415SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R2"),
1458cc2c415SKurt Borja 		},
1468cc2c415SKurt Borja 		.driver_data = &generic_quirks,
1478cc2c415SKurt Borja 	},
1488cc2c415SKurt Borja 	{
1498cc2c415SKurt Borja 		.ident = "Alienware x15 R1",
1508cc2c415SKurt Borja 		.matches = {
1518cc2c415SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
1528cc2c415SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R1"),
1538cc2c415SKurt Borja 		},
1548cc2c415SKurt Borja 		.driver_data = &generic_quirks,
1558cc2c415SKurt Borja 	},
1568cc2c415SKurt Borja 	{
157202a8612SKurt Borja 		.ident = "Alienware x15 R2",
158202a8612SKurt Borja 		.matches = {
159202a8612SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
160202a8612SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R2"),
161202a8612SKurt Borja 		},
162202a8612SKurt Borja 		.driver_data = &generic_quirks,
163202a8612SKurt Borja 	},
164202a8612SKurt Borja 	{
1658cc2c415SKurt Borja 		.ident = "Alienware x17 R2",
1668cc2c415SKurt Borja 		.matches = {
1678cc2c415SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
1688cc2c415SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x17 R2"),
1698cc2c415SKurt Borja 		},
1708cc2c415SKurt Borja 		.driver_data = &generic_quirks,
1718cc2c415SKurt Borja 	},
1728cc2c415SKurt Borja 	{
1738cc2c415SKurt Borja 		.ident = "Dell Inc. G15 5510",
1748cc2c415SKurt Borja 		.matches = {
1758cc2c415SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1768cc2c415SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5510"),
1778cc2c415SKurt Borja 		},
1788cc2c415SKurt Borja 		.driver_data = &g_series_quirks,
1798cc2c415SKurt Borja 	},
1808cc2c415SKurt Borja 	{
1818cc2c415SKurt Borja 		.ident = "Dell Inc. G15 5511",
1828cc2c415SKurt Borja 		.matches = {
1838cc2c415SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1848cc2c415SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"),
1858cc2c415SKurt Borja 		},
1868cc2c415SKurt Borja 		.driver_data = &g_series_quirks,
1878cc2c415SKurt Borja 	},
1888cc2c415SKurt Borja 	{
1898cc2c415SKurt Borja 		.ident = "Dell Inc. G15 5515",
1908cc2c415SKurt Borja 		.matches = {
1918cc2c415SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1928cc2c415SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5515"),
1938cc2c415SKurt Borja 		},
1948cc2c415SKurt Borja 		.driver_data = &g_series_quirks,
1958cc2c415SKurt Borja 	},
1968cc2c415SKurt Borja 	{
197202a8612SKurt Borja 		.ident = "Dell Inc. G16 7630",
198202a8612SKurt Borja 		.matches = {
199202a8612SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
200202a8612SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "Dell G16 7630"),
201202a8612SKurt Borja 		},
202202a8612SKurt Borja 		.driver_data = &g_series_quirks,
203202a8612SKurt Borja 	},
204202a8612SKurt Borja 	{
2058cc2c415SKurt Borja 		.ident = "Dell Inc. G3 3500",
2068cc2c415SKurt Borja 		.matches = {
2078cc2c415SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
2088cc2c415SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "G3 3500"),
2098cc2c415SKurt Borja 		},
2108cc2c415SKurt Borja 		.driver_data = &g_series_quirks,
2118cc2c415SKurt Borja 	},
2128cc2c415SKurt Borja 	{
2138cc2c415SKurt Borja 		.ident = "Dell Inc. G3 3590",
2148cc2c415SKurt Borja 		.matches = {
2158cc2c415SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
2168cc2c415SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "G3 3590"),
2178cc2c415SKurt Borja 		},
2188cc2c415SKurt Borja 		.driver_data = &g_series_quirks,
2198cc2c415SKurt Borja 	},
2208cc2c415SKurt Borja 	{
2218cc2c415SKurt Borja 		.ident = "Dell Inc. G5 5500",
2228cc2c415SKurt Borja 		.matches = {
2238cc2c415SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
2248cc2c415SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "G5 5500"),
2258cc2c415SKurt Borja 		},
2268cc2c415SKurt Borja 		.driver_data = &g_series_quirks,
2278cc2c415SKurt Borja 	},
228202a8612SKurt Borja 	{
229202a8612SKurt Borja 		.ident = "Dell Inc. G5 5505",
230202a8612SKurt Borja 		.matches = {
231202a8612SKurt Borja 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
232202a8612SKurt Borja 			DMI_MATCH(DMI_PRODUCT_NAME, "G5 5505"),
233202a8612SKurt Borja 		},
234202a8612SKurt Borja 		.driver_data = &g_series_quirks,
235202a8612SKurt Borja 	},
2368cc2c415SKurt Borja };
2378cc2c415SKurt Borja 
238d6999078SKurt Borja enum AWCC_GET_FAN_SENSORS_OPERATIONS {
239d6999078SKurt Borja 	AWCC_OP_GET_TOTAL_FAN_TEMPS		= 0x01,
240d6999078SKurt Borja 	AWCC_OP_GET_FAN_TEMP_ID			= 0x02,
241d6999078SKurt Borja };
242d6999078SKurt Borja 
2438a1a0fb5SKurt Borja enum AWCC_THERMAL_INFORMATION_OPERATIONS {
2448a1a0fb5SKurt Borja 	AWCC_OP_GET_SYSTEM_DESCRIPTION		= 0x02,
2458a1a0fb5SKurt Borja 	AWCC_OP_GET_RESOURCE_ID			= 0x03,
246d6999078SKurt Borja 	AWCC_OP_GET_TEMPERATURE			= 0x04,
247d6999078SKurt Borja 	AWCC_OP_GET_FAN_RPM			= 0x05,
248d6999078SKurt Borja 	AWCC_OP_GET_FAN_MIN_RPM			= 0x08,
249d6999078SKurt Borja 	AWCC_OP_GET_FAN_MAX_RPM			= 0x09,
2508a1a0fb5SKurt Borja 	AWCC_OP_GET_CURRENT_PROFILE		= 0x0B,
25107ac2759SKurt Borja 	AWCC_OP_GET_FAN_BOOST			= 0x0C,
2528cc2c415SKurt Borja };
2538cc2c415SKurt Borja 
2548a1a0fb5SKurt Borja enum AWCC_THERMAL_CONTROL_OPERATIONS {
2558a1a0fb5SKurt Borja 	AWCC_OP_ACTIVATE_PROFILE		= 0x01,
25607ac2759SKurt Borja 	AWCC_OP_SET_FAN_BOOST			= 0x02,
2578cc2c415SKurt Borja };
2588cc2c415SKurt Borja 
2598a1a0fb5SKurt Borja enum AWCC_GAME_SHIFT_STATUS_OPERATIONS {
2608a1a0fb5SKurt Borja 	AWCC_OP_TOGGLE_GAME_SHIFT		= 0x01,
2618a1a0fb5SKurt Borja 	AWCC_OP_GET_GAME_SHIFT_STATUS		= 0x02,
2628cc2c415SKurt Borja };
2638cc2c415SKurt Borja 
2648a1a0fb5SKurt Borja enum AWCC_THERMAL_TABLES {
265a000da9dSKurt Borja 	AWCC_THERMAL_TABLE_LEGACY		= 0x9,
266a000da9dSKurt Borja 	AWCC_THERMAL_TABLE_USTT			= 0xA,
2678cc2c415SKurt Borja };
2688cc2c415SKurt Borja 
2693dde0ae1SKurt Borja enum AWCC_SPECIAL_THERMAL_CODES {
2703dde0ae1SKurt Borja 	AWCC_SPECIAL_PROFILE_CUSTOM		= 0x00,
2713dde0ae1SKurt Borja 	AWCC_SPECIAL_PROFILE_GMODE		= 0xAB,
2723dde0ae1SKurt Borja };
2733dde0ae1SKurt Borja 
274d6999078SKurt Borja enum AWCC_TEMP_SENSOR_TYPES {
275d6999078SKurt Borja 	AWCC_TEMP_SENSOR_CPU			= 0x01,
276d6999078SKurt Borja 	AWCC_TEMP_SENSOR_GPU			= 0x06,
277d6999078SKurt Borja };
278d6999078SKurt Borja 
2798a1a0fb5SKurt Borja enum awcc_thermal_profile {
2808a1a0fb5SKurt Borja 	AWCC_PROFILE_USTT_BALANCED,
2818a1a0fb5SKurt Borja 	AWCC_PROFILE_USTT_BALANCED_PERFORMANCE,
2828a1a0fb5SKurt Borja 	AWCC_PROFILE_USTT_COOL,
2838a1a0fb5SKurt Borja 	AWCC_PROFILE_USTT_QUIET,
2848a1a0fb5SKurt Borja 	AWCC_PROFILE_USTT_PERFORMANCE,
2858a1a0fb5SKurt Borja 	AWCC_PROFILE_USTT_LOW_POWER,
2868a1a0fb5SKurt Borja 	AWCC_PROFILE_LEGACY_QUIET,
2878a1a0fb5SKurt Borja 	AWCC_PROFILE_LEGACY_BALANCED,
2888a1a0fb5SKurt Borja 	AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE,
2898a1a0fb5SKurt Borja 	AWCC_PROFILE_LEGACY_PERFORMANCE,
2908a1a0fb5SKurt Borja 	AWCC_PROFILE_LAST,
2918cc2c415SKurt Borja };
2928cc2c415SKurt Borja 
2938cc2c415SKurt Borja struct wmax_led_args {
2948cc2c415SKurt Borja 	u32 led_mask;
2958cc2c415SKurt Borja 	struct color_platform colors;
2968cc2c415SKurt Borja 	u8 state;
2978cc2c415SKurt Borja } __packed;
2988cc2c415SKurt Borja 
2998cc2c415SKurt Borja struct wmax_brightness_args {
3008cc2c415SKurt Borja 	u32 led_mask;
3018cc2c415SKurt Borja 	u32 percentage;
3028cc2c415SKurt Borja };
3038cc2c415SKurt Borja 
3048cc2c415SKurt Borja struct wmax_basic_args {
3058cc2c415SKurt Borja 	u8 arg;
3068cc2c415SKurt Borja };
3078cc2c415SKurt Borja 
3088cc2c415SKurt Borja struct wmax_u32_args {
3098cc2c415SKurt Borja 	u8 operation;
3108cc2c415SKurt Borja 	u8 arg1;
3118cc2c415SKurt Borja 	u8 arg2;
3128cc2c415SKurt Borja 	u8 arg3;
3138cc2c415SKurt Borja };
3148cc2c415SKurt Borja 
315d6999078SKurt Borja struct awcc_fan_data {
316d6999078SKurt Borja 	unsigned long auto_channels_temp;
317d6999078SKurt Borja 	const char *label;
318d6999078SKurt Borja 	u32 min_rpm;
319d6999078SKurt Borja 	u32 max_rpm;
32007ac2759SKurt Borja 	u8 suspend_cache;
321d6999078SKurt Borja 	u8 id;
322d6999078SKurt Borja };
323d6999078SKurt Borja 
3248cc2c415SKurt Borja struct awcc_priv {
3258cc2c415SKurt Borja 	struct wmi_device *wdev;
32632b6372dSKurt Borja 	union {
32732b6372dSKurt Borja 		u32 system_description;
32832b6372dSKurt Borja 		struct {
32932b6372dSKurt Borja 			u8 fan_count;
33032b6372dSKurt Borja 			u8 temp_count;
33132b6372dSKurt Borja 			u8 unknown_count;
33232b6372dSKurt Borja 			u8 profile_count;
33332b6372dSKurt Borja 		};
33432b6372dSKurt Borja 		u8 res_count[4];
33532b6372dSKurt Borja 	};
33632b6372dSKurt Borja 
3378cc2c415SKurt Borja 	struct device *ppdev;
33877bb2ec5SKurt Borja 	u8 supported_profiles[PLATFORM_PROFILE_LAST];
339d6999078SKurt Borja 
340d6999078SKurt Borja 	struct device *hwdev;
341d6999078SKurt Borja 	struct awcc_fan_data **fan_data;
342d6999078SKurt Borja 	unsigned long temp_sensors[AWCC_ID_BITMAP_LONGS];
343aee5cf93SKurt Borja 
344aee5cf93SKurt Borja 	u32 gpio_count;
3458cc2c415SKurt Borja };
3468cc2c415SKurt Borja 
3478a1a0fb5SKurt Borja static const enum platform_profile_option awcc_mode_to_platform_profile[AWCC_PROFILE_LAST] = {
3488a1a0fb5SKurt Borja 	[AWCC_PROFILE_USTT_BALANCED]			= PLATFORM_PROFILE_BALANCED,
3498a1a0fb5SKurt Borja 	[AWCC_PROFILE_USTT_BALANCED_PERFORMANCE]	= PLATFORM_PROFILE_BALANCED_PERFORMANCE,
3508a1a0fb5SKurt Borja 	[AWCC_PROFILE_USTT_COOL]			= PLATFORM_PROFILE_COOL,
3518a1a0fb5SKurt Borja 	[AWCC_PROFILE_USTT_QUIET]			= PLATFORM_PROFILE_QUIET,
3528a1a0fb5SKurt Borja 	[AWCC_PROFILE_USTT_PERFORMANCE]			= PLATFORM_PROFILE_PERFORMANCE,
3538a1a0fb5SKurt Borja 	[AWCC_PROFILE_USTT_LOW_POWER]			= PLATFORM_PROFILE_LOW_POWER,
3548a1a0fb5SKurt Borja 	[AWCC_PROFILE_LEGACY_QUIET]			= PLATFORM_PROFILE_QUIET,
3558a1a0fb5SKurt Borja 	[AWCC_PROFILE_LEGACY_BALANCED]			= PLATFORM_PROFILE_BALANCED,
3568a1a0fb5SKurt Borja 	[AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE]	= PLATFORM_PROFILE_BALANCED_PERFORMANCE,
3578a1a0fb5SKurt Borja 	[AWCC_PROFILE_LEGACY_PERFORMANCE]		= PLATFORM_PROFILE_PERFORMANCE,
3588cc2c415SKurt Borja };
3598cc2c415SKurt Borja 
3608cc2c415SKurt Borja static struct awcc_quirks *awcc;
3618cc2c415SKurt Borja 
3628cc2c415SKurt Borja /*
3638cc2c415SKurt Borja  *	The HDMI mux sysfs node indicates the status of the HDMI input mux.
3648cc2c415SKurt Borja  *	It can toggle between standard system GPU output and HDMI input.
3658cc2c415SKurt Borja  */
cable_show(struct device * dev,struct device_attribute * attr,char * buf)3668cc2c415SKurt Borja static ssize_t cable_show(struct device *dev, struct device_attribute *attr,
3678cc2c415SKurt Borja 			  char *buf)
3688cc2c415SKurt Borja {
3698cc2c415SKurt Borja 	struct alienfx_platdata *pdata = dev_get_platdata(dev);
3708cc2c415SKurt Borja 	struct wmax_basic_args in_args = {
3718cc2c415SKurt Borja 		.arg = 0,
3728cc2c415SKurt Borja 	};
3738cc2c415SKurt Borja 	u32 out_data;
3748cc2c415SKurt Borja 	int ret;
3758cc2c415SKurt Borja 
3768cc2c415SKurt Borja 	ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_HDMI_CABLE,
3778cc2c415SKurt Borja 				    &in_args, sizeof(in_args), &out_data);
3788cc2c415SKurt Borja 	if (!ret) {
3798cc2c415SKurt Borja 		if (out_data == 0)
3808cc2c415SKurt Borja 			return sysfs_emit(buf, "[unconnected] connected unknown\n");
3818cc2c415SKurt Borja 		else if (out_data == 1)
3828cc2c415SKurt Borja 			return sysfs_emit(buf, "unconnected [connected] unknown\n");
3838cc2c415SKurt Borja 	}
3848cc2c415SKurt Borja 
3858cc2c415SKurt Borja 	pr_err("alienware-wmi: unknown HDMI cable status: %d\n", ret);
3868cc2c415SKurt Borja 	return sysfs_emit(buf, "unconnected connected [unknown]\n");
3878cc2c415SKurt Borja }
3888cc2c415SKurt Borja 
source_show(struct device * dev,struct device_attribute * attr,char * buf)3898cc2c415SKurt Borja static ssize_t source_show(struct device *dev, struct device_attribute *attr,
3908cc2c415SKurt Borja 			   char *buf)
3918cc2c415SKurt Borja {
3928cc2c415SKurt Borja 	struct alienfx_platdata *pdata = dev_get_platdata(dev);
3938cc2c415SKurt Borja 	struct wmax_basic_args in_args = {
3948cc2c415SKurt Borja 		.arg = 0,
3958cc2c415SKurt Borja 	};
3968cc2c415SKurt Borja 	u32 out_data;
3978cc2c415SKurt Borja 	int ret;
3988cc2c415SKurt Borja 
3998cc2c415SKurt Borja 	ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_HDMI_STATUS,
4008cc2c415SKurt Borja 				    &in_args, sizeof(in_args), &out_data);
4018cc2c415SKurt Borja 	if (!ret) {
4028cc2c415SKurt Borja 		if (out_data == 1)
4038cc2c415SKurt Borja 			return sysfs_emit(buf, "[input] gpu unknown\n");
4048cc2c415SKurt Borja 		else if (out_data == 2)
4058cc2c415SKurt Borja 			return sysfs_emit(buf, "input [gpu] unknown\n");
4068cc2c415SKurt Borja 	}
4078cc2c415SKurt Borja 
4088cc2c415SKurt Borja 	pr_err("alienware-wmi: unknown HDMI source status: %u\n", ret);
4098cc2c415SKurt Borja 	return sysfs_emit(buf, "input gpu [unknown]\n");
4108cc2c415SKurt Borja }
4118cc2c415SKurt Borja 
source_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)4128cc2c415SKurt Borja static ssize_t source_store(struct device *dev, struct device_attribute *attr,
4138cc2c415SKurt Borja 			    const char *buf, size_t count)
4148cc2c415SKurt Borja {
4158cc2c415SKurt Borja 	struct alienfx_platdata *pdata = dev_get_platdata(dev);
4168cc2c415SKurt Borja 	struct wmax_basic_args args;
4178cc2c415SKurt Borja 	int ret;
4188cc2c415SKurt Borja 
4198cc2c415SKurt Borja 	if (strcmp(buf, "gpu\n") == 0)
4208cc2c415SKurt Borja 		args.arg = 1;
4218cc2c415SKurt Borja 	else if (strcmp(buf, "input\n") == 0)
4228cc2c415SKurt Borja 		args.arg = 2;
4238cc2c415SKurt Borja 	else
4248cc2c415SKurt Borja 		args.arg = 3;
4258cc2c415SKurt Borja 	pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);
4268cc2c415SKurt Borja 
4278cc2c415SKurt Borja 	ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_HDMI_SOURCE, &args,
4288cc2c415SKurt Borja 				    sizeof(args), NULL);
4298cc2c415SKurt Borja 	if (ret < 0)
4308cc2c415SKurt Borja 		pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", ret);
4318cc2c415SKurt Borja 
4328cc2c415SKurt Borja 	return count;
4338cc2c415SKurt Borja }
4348cc2c415SKurt Borja 
4358cc2c415SKurt Borja static DEVICE_ATTR_RO(cable);
4368cc2c415SKurt Borja static DEVICE_ATTR_RW(source);
4378cc2c415SKurt Borja 
hdmi_group_visible(struct kobject * kobj)4388cc2c415SKurt Borja static bool hdmi_group_visible(struct kobject *kobj)
4398cc2c415SKurt Borja {
4408cc2c415SKurt Borja 	return alienware_interface == WMAX && alienfx->hdmi_mux;
4418cc2c415SKurt Borja }
4428cc2c415SKurt Borja DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(hdmi);
4438cc2c415SKurt Borja 
4448cc2c415SKurt Borja static struct attribute *hdmi_attrs[] = {
4458cc2c415SKurt Borja 	&dev_attr_cable.attr,
4468cc2c415SKurt Borja 	&dev_attr_source.attr,
4478cc2c415SKurt Borja 	NULL,
4488cc2c415SKurt Borja };
4498cc2c415SKurt Borja 
4508cc2c415SKurt Borja const struct attribute_group wmax_hdmi_attribute_group = {
4518cc2c415SKurt Borja 	.name = "hdmi",
4528cc2c415SKurt Borja 	.is_visible = SYSFS_GROUP_VISIBLE(hdmi),
4538cc2c415SKurt Borja 	.attrs = hdmi_attrs,
4548cc2c415SKurt Borja };
4558cc2c415SKurt Borja 
4568cc2c415SKurt Borja /*
4578cc2c415SKurt Borja  * Alienware GFX amplifier support
4588cc2c415SKurt Borja  * - Currently supports reading cable status
4598cc2c415SKurt Borja  * - Leaving expansion room to possibly support dock/undock events later
4608cc2c415SKurt Borja  */
status_show(struct device * dev,struct device_attribute * attr,char * buf)4618cc2c415SKurt Borja static ssize_t status_show(struct device *dev, struct device_attribute *attr,
4628cc2c415SKurt Borja 			   char *buf)
4638cc2c415SKurt Borja {
4648cc2c415SKurt Borja 	struct alienfx_platdata *pdata = dev_get_platdata(dev);
4658cc2c415SKurt Borja 	struct wmax_basic_args in_args = {
4668cc2c415SKurt Borja 		.arg = 0,
4678cc2c415SKurt Borja 	};
4688cc2c415SKurt Borja 	u32 out_data;
4698cc2c415SKurt Borja 	int ret;
4708cc2c415SKurt Borja 
4718cc2c415SKurt Borja 	ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_AMPLIFIER_CABLE,
4728cc2c415SKurt Borja 				    &in_args, sizeof(in_args), &out_data);
4738cc2c415SKurt Borja 	if (!ret) {
4748cc2c415SKurt Borja 		if (out_data == 0)
4758cc2c415SKurt Borja 			return sysfs_emit(buf, "[unconnected] connected unknown\n");
4768cc2c415SKurt Borja 		else if (out_data == 1)
4778cc2c415SKurt Borja 			return sysfs_emit(buf, "unconnected [connected] unknown\n");
4788cc2c415SKurt Borja 	}
4798cc2c415SKurt Borja 
4808cc2c415SKurt Borja 	pr_err("alienware-wmi: unknown amplifier cable status: %d\n", ret);
4818cc2c415SKurt Borja 	return sysfs_emit(buf, "unconnected connected [unknown]\n");
4828cc2c415SKurt Borja }
4838cc2c415SKurt Borja 
4848cc2c415SKurt Borja static DEVICE_ATTR_RO(status);
4858cc2c415SKurt Borja 
amplifier_group_visible(struct kobject * kobj)4868cc2c415SKurt Borja static bool amplifier_group_visible(struct kobject *kobj)
4878cc2c415SKurt Borja {
4888cc2c415SKurt Borja 	return alienware_interface == WMAX && alienfx->amplifier;
4898cc2c415SKurt Borja }
4908cc2c415SKurt Borja DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(amplifier);
4918cc2c415SKurt Borja 
4928cc2c415SKurt Borja static struct attribute *amplifier_attrs[] = {
4938cc2c415SKurt Borja 	&dev_attr_status.attr,
4948cc2c415SKurt Borja 	NULL,
4958cc2c415SKurt Borja };
4968cc2c415SKurt Borja 
4978cc2c415SKurt Borja const struct attribute_group wmax_amplifier_attribute_group = {
4988cc2c415SKurt Borja 	.name = "amplifier",
4998cc2c415SKurt Borja 	.is_visible = SYSFS_GROUP_VISIBLE(amplifier),
5008cc2c415SKurt Borja 	.attrs = amplifier_attrs,
5018cc2c415SKurt Borja };
5028cc2c415SKurt Borja 
5038cc2c415SKurt Borja /*
5048cc2c415SKurt Borja  * Deep Sleep Control support
5058cc2c415SKurt Borja  * - Modifies BIOS setting for deep sleep control allowing extra wakeup events
5068cc2c415SKurt Borja  */
deepsleep_show(struct device * dev,struct device_attribute * attr,char * buf)5078cc2c415SKurt Borja static ssize_t deepsleep_show(struct device *dev, struct device_attribute *attr,
5088cc2c415SKurt Borja 			      char *buf)
5098cc2c415SKurt Borja {
5108cc2c415SKurt Borja 	struct alienfx_platdata *pdata = dev_get_platdata(dev);
5118cc2c415SKurt Borja 	struct wmax_basic_args in_args = {
5128cc2c415SKurt Borja 		.arg = 0,
5138cc2c415SKurt Borja 	};
5148cc2c415SKurt Borja 	u32 out_data;
5158cc2c415SKurt Borja 	int ret;
5168cc2c415SKurt Borja 
5178cc2c415SKurt Borja 	ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_DEEP_SLEEP_STATUS,
5188cc2c415SKurt Borja 				    &in_args, sizeof(in_args), &out_data);
5198cc2c415SKurt Borja 	if (!ret) {
5208cc2c415SKurt Borja 		if (out_data == 0)
5218cc2c415SKurt Borja 			return sysfs_emit(buf, "[disabled] s5 s5_s4\n");
5228cc2c415SKurt Borja 		else if (out_data == 1)
5238cc2c415SKurt Borja 			return sysfs_emit(buf, "disabled [s5] s5_s4\n");
5248cc2c415SKurt Borja 		else if (out_data == 2)
5258cc2c415SKurt Borja 			return sysfs_emit(buf, "disabled s5 [s5_s4]\n");
5268cc2c415SKurt Borja 	}
5278cc2c415SKurt Borja 
5288cc2c415SKurt Borja 	pr_err("alienware-wmi: unknown deep sleep status: %d\n", ret);
5298cc2c415SKurt Borja 	return sysfs_emit(buf, "disabled s5 s5_s4 [unknown]\n");
5308cc2c415SKurt Borja }
5318cc2c415SKurt Borja 
deepsleep_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)5328cc2c415SKurt Borja static ssize_t deepsleep_store(struct device *dev, struct device_attribute *attr,
5338cc2c415SKurt Borja 			       const char *buf, size_t count)
5348cc2c415SKurt Borja {
5358cc2c415SKurt Borja 	struct alienfx_platdata *pdata = dev_get_platdata(dev);
5368cc2c415SKurt Borja 	struct wmax_basic_args args;
5378cc2c415SKurt Borja 	int ret;
5388cc2c415SKurt Borja 
5398cc2c415SKurt Borja 	if (strcmp(buf, "disabled\n") == 0)
5408cc2c415SKurt Borja 		args.arg = 0;
5418cc2c415SKurt Borja 	else if (strcmp(buf, "s5\n") == 0)
5428cc2c415SKurt Borja 		args.arg = 1;
5438cc2c415SKurt Borja 	else
5448cc2c415SKurt Borja 		args.arg = 2;
5458cc2c415SKurt Borja 	pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf);
5468cc2c415SKurt Borja 
5478cc2c415SKurt Borja 	ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_DEEP_SLEEP_CONTROL,
5488cc2c415SKurt Borja 				    &args, sizeof(args), NULL);
5498cc2c415SKurt Borja 	if (!ret)
5508cc2c415SKurt Borja 		pr_err("alienware-wmi: deep sleep control failed: results: %u\n", ret);
5518cc2c415SKurt Borja 
5528cc2c415SKurt Borja 	return count;
5538cc2c415SKurt Borja }
5548cc2c415SKurt Borja 
5558cc2c415SKurt Borja static DEVICE_ATTR_RW(deepsleep);
5568cc2c415SKurt Borja 
deepsleep_group_visible(struct kobject * kobj)5578cc2c415SKurt Borja static bool deepsleep_group_visible(struct kobject *kobj)
5588cc2c415SKurt Borja {
5598cc2c415SKurt Borja 	return alienware_interface == WMAX && alienfx->deepslp;
5608cc2c415SKurt Borja }
5618cc2c415SKurt Borja DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(deepsleep);
5628cc2c415SKurt Borja 
5638cc2c415SKurt Borja static struct attribute *deepsleep_attrs[] = {
5648cc2c415SKurt Borja 	&dev_attr_deepsleep.attr,
5658cc2c415SKurt Borja 	NULL,
5668cc2c415SKurt Borja };
5678cc2c415SKurt Borja 
5688cc2c415SKurt Borja const struct attribute_group wmax_deepsleep_attribute_group = {
5698cc2c415SKurt Borja 	.name = "deepsleep",
5708cc2c415SKurt Borja 	.is_visible = SYSFS_GROUP_VISIBLE(deepsleep),
5718cc2c415SKurt Borja 	.attrs = deepsleep_attrs,
5728cc2c415SKurt Borja };
5738cc2c415SKurt Borja 
5748cc2c415SKurt Borja /*
57545983d19SKurt Borja  * AWCC Helpers
5768cc2c415SKurt Borja  */
is_awcc_thermal_profile_id(u8 code)577a000da9dSKurt Borja static bool is_awcc_thermal_profile_id(u8 code)
5788cc2c415SKurt Borja {
579a000da9dSKurt Borja 	u8 table = FIELD_GET(AWCC_THERMAL_TABLE_MASK, code);
580a000da9dSKurt Borja 	u8 mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, code);
581a000da9dSKurt Borja 
582a000da9dSKurt Borja 	if (mode >= AWCC_PROFILE_LAST)
5838cc2c415SKurt Borja 		return false;
5848cc2c415SKurt Borja 
585a000da9dSKurt Borja 	if (table == AWCC_THERMAL_TABLE_LEGACY && mode >= AWCC_PROFILE_LEGACY_QUIET)
5868cc2c415SKurt Borja 		return true;
5878cc2c415SKurt Borja 
588a000da9dSKurt Borja 	if (table == AWCC_THERMAL_TABLE_USTT && mode <= AWCC_PROFILE_USTT_LOW_POWER)
5898cc2c415SKurt Borja 		return true;
5908cc2c415SKurt Borja 
5918cc2c415SKurt Borja 	return false;
5928cc2c415SKurt Borja }
5938cc2c415SKurt Borja 
awcc_wmi_command(struct wmi_device * wdev,u32 method_id,struct wmax_u32_args * args,u32 * out)59445983d19SKurt Borja static int awcc_wmi_command(struct wmi_device *wdev, u32 method_id,
59545983d19SKurt Borja 			    struct wmax_u32_args *args, u32 *out)
5968cc2c415SKurt Borja {
5978cc2c415SKurt Borja 	int ret;
5988cc2c415SKurt Borja 
59945983d19SKurt Borja 	ret = alienware_wmi_command(wdev, method_id, args, sizeof(*args), out);
60045983d19SKurt Borja 	if (ret)
6018cc2c415SKurt Borja 		return ret;
6028cc2c415SKurt Borja 
60345983d19SKurt Borja 	if (*out == AWCC_FAILURE_CODE || *out == AWCC_FAILURE_CODE_2)
6048cc2c415SKurt Borja 		return -EBADRQC;
6058cc2c415SKurt Borja 
6068cc2c415SKurt Borja 	return 0;
6078cc2c415SKurt Borja }
6088cc2c415SKurt Borja 
awcc_get_fan_sensors(struct wmi_device * wdev,u8 operation,u8 fan_id,u8 index,u32 * out)609d6999078SKurt Borja static int awcc_get_fan_sensors(struct wmi_device *wdev, u8 operation,
610d6999078SKurt Borja 				u8 fan_id, u8 index, u32 *out)
611d6999078SKurt Borja {
612d6999078SKurt Borja 	struct wmax_u32_args args = {
613d6999078SKurt Borja 		.operation = operation,
614d6999078SKurt Borja 		.arg1 = fan_id,
615d6999078SKurt Borja 		.arg2 = index,
616d6999078SKurt Borja 		.arg3 = 0,
617d6999078SKurt Borja 	};
618d6999078SKurt Borja 
619d6999078SKurt Borja 	return awcc_wmi_command(wdev, AWCC_METHOD_GET_FAN_SENSORS, &args, out);
620d6999078SKurt Borja }
621d6999078SKurt Borja 
awcc_thermal_information(struct wmi_device * wdev,u8 operation,u8 arg,u32 * out)62245983d19SKurt Borja static int awcc_thermal_information(struct wmi_device *wdev, u8 operation, u8 arg,
62345983d19SKurt Borja 				    u32 *out)
6248cc2c415SKurt Borja {
62545983d19SKurt Borja 	struct wmax_u32_args args = {
62645983d19SKurt Borja 		.operation = operation,
62745983d19SKurt Borja 		.arg1 = arg,
62845983d19SKurt Borja 		.arg2 = 0,
62945983d19SKurt Borja 		.arg3 = 0,
63045983d19SKurt Borja 	};
63145983d19SKurt Borja 
63245983d19SKurt Borja 	return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
63345983d19SKurt Borja }
63445983d19SKurt Borja 
awcc_fwup_gpio_control(struct wmi_device * wdev,u8 pin,u8 status)635aee5cf93SKurt Borja static int awcc_fwup_gpio_control(struct wmi_device *wdev, u8 pin, u8 status)
636aee5cf93SKurt Borja {
637aee5cf93SKurt Borja 	struct wmax_u32_args args = {
638aee5cf93SKurt Borja 		.operation = pin,
639aee5cf93SKurt Borja 		.arg1 = status,
640aee5cf93SKurt Borja 		.arg2 = 0,
641aee5cf93SKurt Borja 		.arg3 = 0,
642aee5cf93SKurt Borja 	};
643aee5cf93SKurt Borja 	u32 out;
644aee5cf93SKurt Borja 
645aee5cf93SKurt Borja 	return awcc_wmi_command(wdev, AWCC_METHOD_FWUP_GPIO_CONTROL, &args, &out);
646aee5cf93SKurt Borja }
647aee5cf93SKurt Borja 
awcc_read_total_gpios(struct wmi_device * wdev,u32 * count)648aee5cf93SKurt Borja static int awcc_read_total_gpios(struct wmi_device *wdev, u32 *count)
649aee5cf93SKurt Borja {
650aee5cf93SKurt Borja 	struct wmax_u32_args args = {};
651aee5cf93SKurt Borja 
652aee5cf93SKurt Borja 	return awcc_wmi_command(wdev, AWCC_METHOD_READ_TOTAL_GPIOS, &args, count);
653aee5cf93SKurt Borja }
654aee5cf93SKurt Borja 
awcc_read_gpio_status(struct wmi_device * wdev,u8 pin,u32 * status)655aee5cf93SKurt Borja static int awcc_read_gpio_status(struct wmi_device *wdev, u8 pin, u32 *status)
656aee5cf93SKurt Borja {
657aee5cf93SKurt Borja 	struct wmax_u32_args args = {
658aee5cf93SKurt Borja 		.operation = pin,
659aee5cf93SKurt Borja 		.arg1 = 0,
660aee5cf93SKurt Borja 		.arg2 = 0,
661aee5cf93SKurt Borja 		.arg3 = 0,
662aee5cf93SKurt Borja 	};
663aee5cf93SKurt Borja 
664aee5cf93SKurt Borja 	return awcc_wmi_command(wdev, AWCC_METHOD_READ_GPIO_STATUS, &args, status);
665aee5cf93SKurt Borja }
666aee5cf93SKurt Borja 
awcc_game_shift_status(struct wmi_device * wdev,u8 operation,u32 * out)66745983d19SKurt Borja static int awcc_game_shift_status(struct wmi_device *wdev, u8 operation,
66845983d19SKurt Borja 				  u32 *out)
66945983d19SKurt Borja {
67045983d19SKurt Borja 	struct wmax_u32_args args = {
67145983d19SKurt Borja 		.operation = operation,
67245983d19SKurt Borja 		.arg1 = 0,
67345983d19SKurt Borja 		.arg2 = 0,
67445983d19SKurt Borja 		.arg3 = 0,
67545983d19SKurt Borja 	};
67645983d19SKurt Borja 
67745983d19SKurt Borja 	return awcc_wmi_command(wdev, AWCC_METHOD_GAME_SHIFT_STATUS, &args, out);
67845983d19SKurt Borja }
67945983d19SKurt Borja 
68045983d19SKurt Borja /**
68145983d19SKurt Borja  * awcc_op_get_resource_id - Get the resource ID at a given index
68245983d19SKurt Borja  * @wdev: AWCC WMI device
68345983d19SKurt Borja  * @index: Index
68445983d19SKurt Borja  * @out: Value returned by the WMI call
68545983d19SKurt Borja  *
68645983d19SKurt Borja  * Get the resource ID at a given @index. Resource IDs are listed in the
68745983d19SKurt Borja  * following order:
68845983d19SKurt Borja  *
68945983d19SKurt Borja  *	- Fan IDs
69045983d19SKurt Borja  *	- Sensor IDs
69145983d19SKurt Borja  *	- Unknown IDs
69245983d19SKurt Borja  *	- Thermal Profile IDs
69345983d19SKurt Borja  *
69445983d19SKurt Borja  * The total number of IDs of a given type can be obtained with
69545983d19SKurt Borja  * AWCC_OP_GET_SYSTEM_DESCRIPTION.
69645983d19SKurt Borja  *
69745983d19SKurt Borja  * Return: 0 on success, -errno on failure
69845983d19SKurt Borja  */
awcc_op_get_resource_id(struct wmi_device * wdev,u8 index,u8 * out)69945983d19SKurt Borja static int awcc_op_get_resource_id(struct wmi_device *wdev, u8 index, u8 *out)
70045983d19SKurt Borja {
70145983d19SKurt Borja 	struct wmax_u32_args args = {
70245983d19SKurt Borja 		.operation = AWCC_OP_GET_RESOURCE_ID,
70345983d19SKurt Borja 		.arg1 = index,
7048cc2c415SKurt Borja 		.arg2 = 0,
7058cc2c415SKurt Borja 		.arg3 = 0,
7068cc2c415SKurt Borja 	};
7078cc2c415SKurt Borja 	u32 out_data;
7088cc2c415SKurt Borja 	int ret;
7098cc2c415SKurt Borja 
71045983d19SKurt Borja 	ret = awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, &out_data);
7118cc2c415SKurt Borja 	if (ret)
7128cc2c415SKurt Borja 		return ret;
7138cc2c415SKurt Borja 
71445983d19SKurt Borja 	*out = FIELD_GET(AWCC_RESOURCE_ID_MASK, out_data);
7158cc2c415SKurt Borja 
7168cc2c415SKurt Borja 	return 0;
7178cc2c415SKurt Borja }
7188cc2c415SKurt Borja 
awcc_op_get_fan_rpm(struct wmi_device * wdev,u8 fan_id,u32 * out)719d6999078SKurt Borja static int awcc_op_get_fan_rpm(struct wmi_device *wdev, u8 fan_id, u32 *out)
720d6999078SKurt Borja {
721d6999078SKurt Borja 	struct wmax_u32_args args = {
722d6999078SKurt Borja 		.operation = AWCC_OP_GET_FAN_RPM,
723d6999078SKurt Borja 		.arg1 = fan_id,
724d6999078SKurt Borja 		.arg2 = 0,
725d6999078SKurt Borja 		.arg3 = 0,
726d6999078SKurt Borja 	};
727d6999078SKurt Borja 
728d6999078SKurt Borja 	return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
729d6999078SKurt Borja }
730d6999078SKurt Borja 
awcc_op_get_temperature(struct wmi_device * wdev,u8 temp_id,u32 * out)731d6999078SKurt Borja static int awcc_op_get_temperature(struct wmi_device *wdev, u8 temp_id, u32 *out)
732d6999078SKurt Borja {
733d6999078SKurt Borja 	struct wmax_u32_args args = {
734d6999078SKurt Borja 		.operation = AWCC_OP_GET_TEMPERATURE,
735d6999078SKurt Borja 		.arg1 = temp_id,
736d6999078SKurt Borja 		.arg2 = 0,
737d6999078SKurt Borja 		.arg3 = 0,
738d6999078SKurt Borja 	};
739d6999078SKurt Borja 
740d6999078SKurt Borja 	return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
741d6999078SKurt Borja }
742d6999078SKurt Borja 
awcc_op_get_fan_boost(struct wmi_device * wdev,u8 fan_id,u32 * out)74307ac2759SKurt Borja static int awcc_op_get_fan_boost(struct wmi_device *wdev, u8 fan_id, u32 *out)
74407ac2759SKurt Borja {
74507ac2759SKurt Borja 	struct wmax_u32_args args = {
74607ac2759SKurt Borja 		.operation = AWCC_OP_GET_FAN_BOOST,
74707ac2759SKurt Borja 		.arg1 = fan_id,
74807ac2759SKurt Borja 		.arg2 = 0,
74907ac2759SKurt Borja 		.arg3 = 0,
75007ac2759SKurt Borja 	};
75107ac2759SKurt Borja 
75207ac2759SKurt Borja 	return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
75307ac2759SKurt Borja }
75407ac2759SKurt Borja 
awcc_op_get_current_profile(struct wmi_device * wdev,u32 * out)75545983d19SKurt Borja static int awcc_op_get_current_profile(struct wmi_device *wdev, u32 *out)
7568cc2c415SKurt Borja {
75745983d19SKurt Borja 	struct wmax_u32_args args = {
75845983d19SKurt Borja 		.operation = AWCC_OP_GET_CURRENT_PROFILE,
7598cc2c415SKurt Borja 		.arg1 = 0,
7608cc2c415SKurt Borja 		.arg2 = 0,
7618cc2c415SKurt Borja 		.arg3 = 0,
7628cc2c415SKurt Borja 	};
7638cc2c415SKurt Borja 
76445983d19SKurt Borja 	return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
7658cc2c415SKurt Borja }
7668cc2c415SKurt Borja 
awcc_op_activate_profile(struct wmi_device * wdev,u8 profile)76745983d19SKurt Borja static int awcc_op_activate_profile(struct wmi_device *wdev, u8 profile)
76845983d19SKurt Borja {
76945983d19SKurt Borja 	struct wmax_u32_args args = {
77045983d19SKurt Borja 		.operation = AWCC_OP_ACTIVATE_PROFILE,
77145983d19SKurt Borja 		.arg1 = profile,
77245983d19SKurt Borja 		.arg2 = 0,
77345983d19SKurt Borja 		.arg3 = 0,
77445983d19SKurt Borja 	};
77545983d19SKurt Borja 	u32 out;
77645983d19SKurt Borja 
77745983d19SKurt Borja 	return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL, &args, &out);
77845983d19SKurt Borja }
77945983d19SKurt Borja 
awcc_op_set_fan_boost(struct wmi_device * wdev,u8 fan_id,u8 boost)78007ac2759SKurt Borja static int awcc_op_set_fan_boost(struct wmi_device *wdev, u8 fan_id, u8 boost)
78107ac2759SKurt Borja {
78207ac2759SKurt Borja 	struct wmax_u32_args args = {
78307ac2759SKurt Borja 		.operation = AWCC_OP_SET_FAN_BOOST,
78407ac2759SKurt Borja 		.arg1 = fan_id,
78507ac2759SKurt Borja 		.arg2 = boost,
78607ac2759SKurt Borja 		.arg3 = 0,
78707ac2759SKurt Borja 	};
78807ac2759SKurt Borja 	u32 out;
78907ac2759SKurt Borja 
79007ac2759SKurt Borja 	return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL, &args, &out);
79107ac2759SKurt Borja }
79207ac2759SKurt Borja 
79345983d19SKurt Borja /*
794d6999078SKurt Borja  * HWMON
795d6999078SKurt Borja  *  - Provides temperature and fan speed monitoring as well as manual fan
796d6999078SKurt Borja  *    control
797d6999078SKurt Borja  */
awcc_hwmon_is_visible(const void * drvdata,enum hwmon_sensor_types type,u32 attr,int channel)798d6999078SKurt Borja static umode_t awcc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
799d6999078SKurt Borja 				     u32 attr, int channel)
800d6999078SKurt Borja {
801d6999078SKurt Borja 	const struct awcc_priv *priv = drvdata;
802d6999078SKurt Borja 	unsigned int temp_count;
803d6999078SKurt Borja 
804d6999078SKurt Borja 	switch (type) {
805d6999078SKurt Borja 	case hwmon_temp:
806d6999078SKurt Borja 		temp_count = bitmap_weight(priv->temp_sensors, AWCC_ID_BITMAP_SIZE);
807d6999078SKurt Borja 
808d6999078SKurt Borja 		return channel < temp_count ? 0444 : 0;
809d6999078SKurt Borja 	case hwmon_fan:
810d6999078SKurt Borja 		return channel < priv->fan_count ? 0444 : 0;
811d6999078SKurt Borja 	case hwmon_pwm:
812d6999078SKurt Borja 		return channel < priv->fan_count ? 0444 : 0;
813d6999078SKurt Borja 	default:
814d6999078SKurt Borja 		return 0;
815d6999078SKurt Borja 	}
816d6999078SKurt Borja }
817d6999078SKurt Borja 
awcc_hwmon_read(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long * val)818d6999078SKurt Borja static int awcc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
819d6999078SKurt Borja 			   u32 attr, int channel, long *val)
820d6999078SKurt Borja {
821d6999078SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(dev);
822d6999078SKurt Borja 	const struct awcc_fan_data *fan;
823d6999078SKurt Borja 	u32 state;
824d6999078SKurt Borja 	int ret;
825d6999078SKurt Borja 	u8 temp;
826d6999078SKurt Borja 
827d6999078SKurt Borja 	switch (type) {
828d6999078SKurt Borja 	case hwmon_temp:
829d6999078SKurt Borja 		temp = find_nth_bit(priv->temp_sensors, AWCC_ID_BITMAP_SIZE, channel);
830d6999078SKurt Borja 
831d6999078SKurt Borja 		switch (attr) {
832d6999078SKurt Borja 		case hwmon_temp_input:
833d6999078SKurt Borja 			ret = awcc_op_get_temperature(priv->wdev, temp, &state);
834d6999078SKurt Borja 			if (ret)
835d6999078SKurt Borja 				return ret;
836d6999078SKurt Borja 
837d6999078SKurt Borja 			*val = state * MILLIDEGREE_PER_DEGREE;
838d6999078SKurt Borja 			break;
839d6999078SKurt Borja 		default:
840d6999078SKurt Borja 			return -EOPNOTSUPP;
841d6999078SKurt Borja 		}
842d6999078SKurt Borja 
843d6999078SKurt Borja 		break;
844d6999078SKurt Borja 	case hwmon_fan:
845d6999078SKurt Borja 		fan = priv->fan_data[channel];
846d6999078SKurt Borja 
847d6999078SKurt Borja 		switch (attr) {
848d6999078SKurt Borja 		case hwmon_fan_input:
849d6999078SKurt Borja 			ret = awcc_op_get_fan_rpm(priv->wdev, fan->id, &state);
850d6999078SKurt Borja 			if (ret)
851d6999078SKurt Borja 				return ret;
852d6999078SKurt Borja 
853d6999078SKurt Borja 			*val = state;
854d6999078SKurt Borja 			break;
855d6999078SKurt Borja 		case hwmon_fan_min:
856d6999078SKurt Borja 			*val = fan->min_rpm;
857d6999078SKurt Borja 			break;
858d6999078SKurt Borja 		case hwmon_fan_max:
859d6999078SKurt Borja 			*val = fan->max_rpm;
860d6999078SKurt Borja 			break;
861d6999078SKurt Borja 		default:
862d6999078SKurt Borja 			return -EOPNOTSUPP;
863d6999078SKurt Borja 		}
864d6999078SKurt Borja 
865d6999078SKurt Borja 		break;
866d6999078SKurt Borja 	case hwmon_pwm:
867d6999078SKurt Borja 		fan = priv->fan_data[channel];
868d6999078SKurt Borja 
869d6999078SKurt Borja 		switch (attr) {
870d6999078SKurt Borja 		case hwmon_pwm_auto_channels_temp:
871d6999078SKurt Borja 			*val = fan->auto_channels_temp;
872d6999078SKurt Borja 			break;
873d6999078SKurt Borja 		default:
874d6999078SKurt Borja 			return -EOPNOTSUPP;
875d6999078SKurt Borja 		}
876d6999078SKurt Borja 
877d6999078SKurt Borja 		break;
878d6999078SKurt Borja 	default:
879d6999078SKurt Borja 		return -EOPNOTSUPP;
880d6999078SKurt Borja 	}
881d6999078SKurt Borja 
882d6999078SKurt Borja 	return 0;
883d6999078SKurt Borja }
884d6999078SKurt Borja 
awcc_hwmon_read_string(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,const char ** str)885d6999078SKurt Borja static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
886d6999078SKurt Borja 				  u32 attr, int channel, const char **str)
887d6999078SKurt Borja {
888d6999078SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(dev);
889d6999078SKurt Borja 	u8 temp;
890d6999078SKurt Borja 
891d6999078SKurt Borja 	switch (type) {
892d6999078SKurt Borja 	case hwmon_temp:
893d6999078SKurt Borja 		temp = find_nth_bit(priv->temp_sensors, AWCC_ID_BITMAP_SIZE, channel);
894d6999078SKurt Borja 
895d6999078SKurt Borja 		switch (temp) {
896d6999078SKurt Borja 		case AWCC_TEMP_SENSOR_CPU:
897d6999078SKurt Borja 			*str = "CPU";
898d6999078SKurt Borja 			break;
899d6999078SKurt Borja 		case AWCC_TEMP_SENSOR_GPU:
900d6999078SKurt Borja 			*str = "GPU";
901d6999078SKurt Borja 			break;
902d6999078SKurt Borja 		default:
903d6999078SKurt Borja 			*str = "Unknown";
904d6999078SKurt Borja 			break;
905d6999078SKurt Borja 		}
906d6999078SKurt Borja 
907d6999078SKurt Borja 		break;
908d6999078SKurt Borja 	case hwmon_fan:
909d6999078SKurt Borja 		*str = priv->fan_data[channel]->label;
910d6999078SKurt Borja 		break;
911d6999078SKurt Borja 	default:
912d6999078SKurt Borja 		return -EOPNOTSUPP;
913d6999078SKurt Borja 	}
914d6999078SKurt Borja 
915d6999078SKurt Borja 	return 0;
916d6999078SKurt Borja }
917d6999078SKurt Borja 
918d6999078SKurt Borja static const struct hwmon_ops awcc_hwmon_ops = {
919d6999078SKurt Borja 	.is_visible = awcc_hwmon_is_visible,
920d6999078SKurt Borja 	.read = awcc_hwmon_read,
921d6999078SKurt Borja 	.read_string = awcc_hwmon_read_string,
922d6999078SKurt Borja };
923d6999078SKurt Borja 
924d6999078SKurt Borja static const struct hwmon_channel_info * const awcc_hwmon_info[] = {
925d6999078SKurt Borja 	HWMON_CHANNEL_INFO(temp,
926d6999078SKurt Borja 			   HWMON_T_LABEL | HWMON_T_INPUT,
927d6999078SKurt Borja 			   HWMON_T_LABEL | HWMON_T_INPUT,
928d6999078SKurt Borja 			   HWMON_T_LABEL | HWMON_T_INPUT,
929d6999078SKurt Borja 			   HWMON_T_LABEL | HWMON_T_INPUT,
930d6999078SKurt Borja 			   HWMON_T_LABEL | HWMON_T_INPUT,
931d6999078SKurt Borja 			   HWMON_T_LABEL | HWMON_T_INPUT
932d6999078SKurt Borja 			   ),
933d6999078SKurt Borja 	HWMON_CHANNEL_INFO(fan,
934d6999078SKurt Borja 			   HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
935d6999078SKurt Borja 			   HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
936d6999078SKurt Borja 			   HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
937d6999078SKurt Borja 			   HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
938d6999078SKurt Borja 			   HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
939d6999078SKurt Borja 			   HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX
940d6999078SKurt Borja 			   ),
941d6999078SKurt Borja 	HWMON_CHANNEL_INFO(pwm,
942d6999078SKurt Borja 			   HWMON_PWM_AUTO_CHANNELS_TEMP,
943d6999078SKurt Borja 			   HWMON_PWM_AUTO_CHANNELS_TEMP,
944d6999078SKurt Borja 			   HWMON_PWM_AUTO_CHANNELS_TEMP,
945d6999078SKurt Borja 			   HWMON_PWM_AUTO_CHANNELS_TEMP,
946d6999078SKurt Borja 			   HWMON_PWM_AUTO_CHANNELS_TEMP,
947d6999078SKurt Borja 			   HWMON_PWM_AUTO_CHANNELS_TEMP
948d6999078SKurt Borja 			   ),
949d6999078SKurt Borja 	NULL
950d6999078SKurt Borja };
951d6999078SKurt Borja 
952d6999078SKurt Borja static const struct hwmon_chip_info awcc_hwmon_chip_info = {
953d6999078SKurt Borja 	.ops = &awcc_hwmon_ops,
954d6999078SKurt Borja 	.info = awcc_hwmon_info,
955d6999078SKurt Borja };
956d6999078SKurt Borja 
fan_boost_show(struct device * dev,struct device_attribute * attr,char * buf)95707ac2759SKurt Borja static ssize_t fan_boost_show(struct device *dev, struct device_attribute *attr,
95807ac2759SKurt Borja 			      char *buf)
95907ac2759SKurt Borja {
96007ac2759SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(dev);
96107ac2759SKurt Borja 	int index = to_sensor_dev_attr(attr)->index;
96207ac2759SKurt Borja 	struct awcc_fan_data *fan = priv->fan_data[index];
96307ac2759SKurt Borja 	u32 boost;
96407ac2759SKurt Borja 	int ret;
96507ac2759SKurt Borja 
96607ac2759SKurt Borja 	ret = awcc_op_get_fan_boost(priv->wdev, fan->id, &boost);
96707ac2759SKurt Borja 	if (ret)
96807ac2759SKurt Borja 		return ret;
96907ac2759SKurt Borja 
97007ac2759SKurt Borja 	return sysfs_emit(buf, "%u\n", boost);
97107ac2759SKurt Borja }
97207ac2759SKurt Borja 
fan_boost_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)97307ac2759SKurt Borja static ssize_t fan_boost_store(struct device *dev, struct device_attribute *attr,
97407ac2759SKurt Borja 			       const char *buf, size_t count)
97507ac2759SKurt Borja {
97607ac2759SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(dev);
97707ac2759SKurt Borja 	int index = to_sensor_dev_attr(attr)->index;
97807ac2759SKurt Borja 	struct awcc_fan_data *fan = priv->fan_data[index];
97907ac2759SKurt Borja 	unsigned long val;
98007ac2759SKurt Borja 	int ret;
98107ac2759SKurt Borja 
98207ac2759SKurt Borja 	ret = kstrtoul(buf, 0, &val);
98307ac2759SKurt Borja 	if (ret)
98407ac2759SKurt Borja 		return ret;
98507ac2759SKurt Borja 
98607ac2759SKurt Borja 	ret = awcc_op_set_fan_boost(priv->wdev, fan->id, clamp_val(val, 0, 255));
98707ac2759SKurt Borja 
98807ac2759SKurt Borja 	return ret ? ret : count;
98907ac2759SKurt Borja }
99007ac2759SKurt Borja 
99107ac2759SKurt Borja static SENSOR_DEVICE_ATTR_RW(fan1_boost, fan_boost, 0);
99207ac2759SKurt Borja static SENSOR_DEVICE_ATTR_RW(fan2_boost, fan_boost, 1);
99307ac2759SKurt Borja static SENSOR_DEVICE_ATTR_RW(fan3_boost, fan_boost, 2);
99407ac2759SKurt Borja static SENSOR_DEVICE_ATTR_RW(fan4_boost, fan_boost, 3);
99507ac2759SKurt Borja static SENSOR_DEVICE_ATTR_RW(fan5_boost, fan_boost, 4);
99607ac2759SKurt Borja static SENSOR_DEVICE_ATTR_RW(fan6_boost, fan_boost, 5);
99707ac2759SKurt Borja 
fan_boost_attr_visible(struct kobject * kobj,struct attribute * attr,int n)99807ac2759SKurt Borja static umode_t fan_boost_attr_visible(struct kobject *kobj, struct attribute *attr, int n)
99907ac2759SKurt Borja {
100007ac2759SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(kobj_to_dev(kobj));
100107ac2759SKurt Borja 
100207ac2759SKurt Borja 	return n < priv->fan_count ? attr->mode : 0;
100307ac2759SKurt Borja }
100407ac2759SKurt Borja 
fan_boost_group_visible(struct kobject * kobj)100507ac2759SKurt Borja static bool fan_boost_group_visible(struct kobject *kobj)
100607ac2759SKurt Borja {
100707ac2759SKurt Borja 	return true;
100807ac2759SKurt Borja }
100907ac2759SKurt Borja 
101007ac2759SKurt Borja DEFINE_SYSFS_GROUP_VISIBLE(fan_boost);
101107ac2759SKurt Borja 
101207ac2759SKurt Borja static struct attribute *fan_boost_attrs[] = {
101307ac2759SKurt Borja 	&sensor_dev_attr_fan1_boost.dev_attr.attr,
101407ac2759SKurt Borja 	&sensor_dev_attr_fan2_boost.dev_attr.attr,
101507ac2759SKurt Borja 	&sensor_dev_attr_fan3_boost.dev_attr.attr,
101607ac2759SKurt Borja 	&sensor_dev_attr_fan4_boost.dev_attr.attr,
101707ac2759SKurt Borja 	&sensor_dev_attr_fan5_boost.dev_attr.attr,
101807ac2759SKurt Borja 	&sensor_dev_attr_fan6_boost.dev_attr.attr,
101907ac2759SKurt Borja 	NULL
102007ac2759SKurt Borja };
102107ac2759SKurt Borja 
102207ac2759SKurt Borja static const struct attribute_group fan_boost_group = {
102307ac2759SKurt Borja 	.attrs = fan_boost_attrs,
102407ac2759SKurt Borja 	.is_visible = SYSFS_GROUP_VISIBLE(fan_boost),
102507ac2759SKurt Borja };
102607ac2759SKurt Borja 
102707ac2759SKurt Borja static const struct attribute_group *awcc_hwmon_groups[] = {
102807ac2759SKurt Borja 	&fan_boost_group,
102907ac2759SKurt Borja 	NULL
103007ac2759SKurt Borja };
103107ac2759SKurt Borja 
awcc_hwmon_temps_init(struct wmi_device * wdev)1032d6999078SKurt Borja static int awcc_hwmon_temps_init(struct wmi_device *wdev)
1033d6999078SKurt Borja {
1034d6999078SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
1035d6999078SKurt Borja 	unsigned int i;
1036d6999078SKurt Borja 	int ret;
1037d6999078SKurt Borja 	u8 id;
1038d6999078SKurt Borja 
1039d6999078SKurt Borja 	for (i = 0; i < priv->temp_count; i++) {
1040d6999078SKurt Borja 		/*
1041d6999078SKurt Borja 		 * Temperature sensors IDs are listed after the fan IDs at
1042d6999078SKurt Borja 		 * offset `fan_count`
1043d6999078SKurt Borja 		 */
1044d6999078SKurt Borja 		ret = awcc_op_get_resource_id(wdev, i + priv->fan_count, &id);
1045d6999078SKurt Borja 		if (ret)
1046d6999078SKurt Borja 			return ret;
1047d6999078SKurt Borja 
1048d6999078SKurt Borja 		__set_bit(id, priv->temp_sensors);
1049d6999078SKurt Borja 	}
1050d6999078SKurt Borja 
1051d6999078SKurt Borja 	return 0;
1052d6999078SKurt Borja }
1053d6999078SKurt Borja 
awcc_get_fan_label(unsigned long * fan_temps)10544b4da10bSKurt Borja static char *awcc_get_fan_label(unsigned long *fan_temps)
1055d6999078SKurt Borja {
10564b4da10bSKurt Borja 	unsigned int temp_count = bitmap_weight(fan_temps, AWCC_ID_BITMAP_SIZE);
1057d6999078SKurt Borja 	char *label;
10584b4da10bSKurt Borja 	u8 temp_id;
1059d6999078SKurt Borja 
1060d6999078SKurt Borja 	switch (temp_count) {
1061d6999078SKurt Borja 	case 0:
1062d6999078SKurt Borja 		label = "Independent Fan";
1063d6999078SKurt Borja 		break;
1064d6999078SKurt Borja 	case 1:
10654b4da10bSKurt Borja 		temp_id = find_first_bit(fan_temps, AWCC_ID_BITMAP_SIZE);
10664b4da10bSKurt Borja 
1067d6999078SKurt Borja 		switch (temp_id) {
1068d6999078SKurt Borja 		case AWCC_TEMP_SENSOR_CPU:
1069d6999078SKurt Borja 			label = "Processor Fan";
1070d6999078SKurt Borja 			break;
1071d6999078SKurt Borja 		case AWCC_TEMP_SENSOR_GPU:
1072d6999078SKurt Borja 			label = "Video Fan";
1073d6999078SKurt Borja 			break;
1074d6999078SKurt Borja 		default:
1075d6999078SKurt Borja 			label = "Unknown Fan";
1076d6999078SKurt Borja 			break;
1077d6999078SKurt Borja 		}
1078d6999078SKurt Borja 
1079d6999078SKurt Borja 		break;
1080d6999078SKurt Borja 	default:
1081d6999078SKurt Borja 		label = "Shared Fan";
1082d6999078SKurt Borja 		break;
1083d6999078SKurt Borja 	}
1084d6999078SKurt Borja 
1085d6999078SKurt Borja 	return label;
1086d6999078SKurt Borja }
1087d6999078SKurt Borja 
awcc_hwmon_fans_init(struct wmi_device * wdev)1088d6999078SKurt Borja static int awcc_hwmon_fans_init(struct wmi_device *wdev)
1089d6999078SKurt Borja {
1090d6999078SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
1091d6999078SKurt Borja 	unsigned long fan_temps[AWCC_ID_BITMAP_LONGS];
1092d6999078SKurt Borja 	unsigned long gather[AWCC_ID_BITMAP_LONGS];
1093d6999078SKurt Borja 	u32 min_rpm, max_rpm, temp_count, temp_id;
1094d6999078SKurt Borja 	struct awcc_fan_data *fan_data;
1095d6999078SKurt Borja 	unsigned int i, j;
1096d6999078SKurt Borja 	int ret;
1097d6999078SKurt Borja 	u8 id;
1098d6999078SKurt Borja 
1099d6999078SKurt Borja 	for (i = 0; i < priv->fan_count; i++) {
1100d6999078SKurt Borja 		fan_data = devm_kzalloc(&wdev->dev, sizeof(*fan_data), GFP_KERNEL);
1101d6999078SKurt Borja 		if (!fan_data)
1102d6999078SKurt Borja 			return -ENOMEM;
1103d6999078SKurt Borja 
1104d6999078SKurt Borja 		/*
1105d6999078SKurt Borja 		 * Fan IDs are listed first at offset 0
1106d6999078SKurt Borja 		 */
1107d6999078SKurt Borja 		ret = awcc_op_get_resource_id(wdev, i, &id);
1108d6999078SKurt Borja 		if (ret)
1109d6999078SKurt Borja 			return ret;
1110d6999078SKurt Borja 
1111d6999078SKurt Borja 		ret = awcc_thermal_information(wdev, AWCC_OP_GET_FAN_MIN_RPM, id,
1112d6999078SKurt Borja 					       &min_rpm);
1113d6999078SKurt Borja 		if (ret)
1114d6999078SKurt Borja 			return ret;
1115d6999078SKurt Borja 
1116d6999078SKurt Borja 		ret = awcc_thermal_information(wdev, AWCC_OP_GET_FAN_MAX_RPM, id,
1117d6999078SKurt Borja 					       &max_rpm);
1118d6999078SKurt Borja 		if (ret)
1119d6999078SKurt Borja 			return ret;
1120d6999078SKurt Borja 
1121d6999078SKurt Borja 		ret = awcc_get_fan_sensors(wdev, AWCC_OP_GET_TOTAL_FAN_TEMPS, id,
1122d6999078SKurt Borja 					   0, &temp_count);
1123d6999078SKurt Borja 		if (ret)
1124d6999078SKurt Borja 			return ret;
1125d6999078SKurt Borja 
11261fe9596aSKurt Borja 		bitmap_zero(fan_temps, AWCC_ID_BITMAP_SIZE);
11271fe9596aSKurt Borja 
1128d6999078SKurt Borja 		for (j = 0; j < temp_count; j++) {
1129d6999078SKurt Borja 			ret = awcc_get_fan_sensors(wdev, AWCC_OP_GET_FAN_TEMP_ID,
1130d6999078SKurt Borja 						   id, j, &temp_id);
1131d6999078SKurt Borja 			if (ret)
1132d6999078SKurt Borja 				break;
1133d6999078SKurt Borja 
1134d6999078SKurt Borja 			temp_id = FIELD_GET(AWCC_RESOURCE_ID_MASK, temp_id);
1135d6999078SKurt Borja 			__set_bit(temp_id, fan_temps);
1136d6999078SKurt Borja 		}
1137d6999078SKurt Borja 
1138d6999078SKurt Borja 		fan_data->id = id;
1139d6999078SKurt Borja 		fan_data->min_rpm = min_rpm;
1140d6999078SKurt Borja 		fan_data->max_rpm = max_rpm;
11414b4da10bSKurt Borja 		fan_data->label = awcc_get_fan_label(fan_temps);
1142d6999078SKurt Borja 		bitmap_gather(gather, fan_temps, priv->temp_sensors, AWCC_ID_BITMAP_SIZE);
1143d6999078SKurt Borja 		bitmap_copy(&fan_data->auto_channels_temp, gather, BITS_PER_LONG);
1144d6999078SKurt Borja 		priv->fan_data[i] = fan_data;
1145d6999078SKurt Borja 	}
1146d6999078SKurt Borja 
1147d6999078SKurt Borja 	return 0;
1148d6999078SKurt Borja }
1149d6999078SKurt Borja 
awcc_hwmon_init(struct wmi_device * wdev)1150d6999078SKurt Borja static int awcc_hwmon_init(struct wmi_device *wdev)
1151d6999078SKurt Borja {
1152d6999078SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
1153d6999078SKurt Borja 	int ret;
1154d6999078SKurt Borja 
1155d6999078SKurt Borja 	priv->fan_data = devm_kcalloc(&wdev->dev, priv->fan_count,
1156d6999078SKurt Borja 				      sizeof(*priv->fan_data), GFP_KERNEL);
1157d6999078SKurt Borja 	if (!priv->fan_data)
1158d6999078SKurt Borja 		return -ENOMEM;
1159d6999078SKurt Borja 
1160d6999078SKurt Borja 	ret = awcc_hwmon_temps_init(wdev);
1161d6999078SKurt Borja 	if (ret)
1162d6999078SKurt Borja 		return ret;
1163d6999078SKurt Borja 
1164d6999078SKurt Borja 	ret = awcc_hwmon_fans_init(wdev);
1165d6999078SKurt Borja 	if (ret)
1166d6999078SKurt Borja 		return ret;
1167d6999078SKurt Borja 
116807ac2759SKurt Borja 	priv->hwdev = devm_hwmon_device_register_with_info(&wdev->dev, "alienware_wmi",
116907ac2759SKurt Borja 							   priv, &awcc_hwmon_chip_info,
117007ac2759SKurt Borja 							   awcc_hwmon_groups);
1171d6999078SKurt Borja 
1172d6999078SKurt Borja 	return PTR_ERR_OR_ZERO(priv->hwdev);
1173d6999078SKurt Borja }
1174d6999078SKurt Borja 
awcc_hwmon_suspend(struct device * dev)117507ac2759SKurt Borja static void awcc_hwmon_suspend(struct device *dev)
117607ac2759SKurt Borja {
117707ac2759SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(dev);
117807ac2759SKurt Borja 	struct awcc_fan_data *fan;
117907ac2759SKurt Borja 	unsigned int i;
118007ac2759SKurt Borja 	u32 boost;
118107ac2759SKurt Borja 	int ret;
118207ac2759SKurt Borja 
118307ac2759SKurt Borja 	for (i = 0; i < priv->fan_count; i++) {
118407ac2759SKurt Borja 		fan = priv->fan_data[i];
118507ac2759SKurt Borja 
118607ac2759SKurt Borja 		ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_FAN_BOOST,
118707ac2759SKurt Borja 					       fan->id, &boost);
118807ac2759SKurt Borja 		if (ret)
118907ac2759SKurt Borja 			dev_err(dev, "Failed to store Fan %u boost while suspending\n", i);
119007ac2759SKurt Borja 
119107ac2759SKurt Borja 		fan->suspend_cache = ret ? 0 : clamp_val(boost, 0, 255);
119207ac2759SKurt Borja 
119307ac2759SKurt Borja 		awcc_op_set_fan_boost(priv->wdev, fan->id, 0);
119407ac2759SKurt Borja 		if (ret)
119507ac2759SKurt Borja 			dev_err(dev, "Failed to set Fan %u boost to 0 while suspending\n", i);
119607ac2759SKurt Borja 	}
119707ac2759SKurt Borja }
119807ac2759SKurt Borja 
awcc_hwmon_resume(struct device * dev)119907ac2759SKurt Borja static void awcc_hwmon_resume(struct device *dev)
120007ac2759SKurt Borja {
120107ac2759SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(dev);
120207ac2759SKurt Borja 	struct awcc_fan_data *fan;
120307ac2759SKurt Borja 	unsigned int i;
120407ac2759SKurt Borja 	int ret;
120507ac2759SKurt Borja 
120607ac2759SKurt Borja 	for (i = 0; i < priv->fan_count; i++) {
120707ac2759SKurt Borja 		fan = priv->fan_data[i];
120807ac2759SKurt Borja 
120907ac2759SKurt Borja 		if (!fan->suspend_cache)
121007ac2759SKurt Borja 			continue;
121107ac2759SKurt Borja 
121207ac2759SKurt Borja 		ret = awcc_op_set_fan_boost(priv->wdev, fan->id, fan->suspend_cache);
121307ac2759SKurt Borja 		if (ret)
121407ac2759SKurt Borja 			dev_err(dev, "Failed to restore Fan %u boost while resuming\n", i);
121507ac2759SKurt Borja 	}
121607ac2759SKurt Borja }
121707ac2759SKurt Borja 
1218d6999078SKurt Borja /*
121945983d19SKurt Borja  * Thermal Profile control
122045983d19SKurt Borja  *  - Provides thermal profile control through the Platform Profile API
122145983d19SKurt Borja  */
awcc_platform_profile_get(struct device * dev,enum platform_profile_option * profile)12228a1a0fb5SKurt Borja static int awcc_platform_profile_get(struct device *dev,
12238cc2c415SKurt Borja 				     enum platform_profile_option *profile)
12248cc2c415SKurt Borja {
12258cc2c415SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(dev);
12268cc2c415SKurt Borja 	u32 out_data;
12278cc2c415SKurt Borja 	int ret;
12288cc2c415SKurt Borja 
122945983d19SKurt Borja 	ret = awcc_op_get_current_profile(priv->wdev, &out_data);
123045983d19SKurt Borja 	if (ret)
12318cc2c415SKurt Borja 		return ret;
12328cc2c415SKurt Borja 
12333dde0ae1SKurt Borja 	switch (out_data) {
12343dde0ae1SKurt Borja 	case AWCC_SPECIAL_PROFILE_CUSTOM:
12353dde0ae1SKurt Borja 		*profile = PLATFORM_PROFILE_CUSTOM;
12363dde0ae1SKurt Borja 		return 0;
12373dde0ae1SKurt Borja 	case AWCC_SPECIAL_PROFILE_GMODE:
12388cc2c415SKurt Borja 		*profile = PLATFORM_PROFILE_PERFORMANCE;
12398cc2c415SKurt Borja 		return 0;
12403dde0ae1SKurt Borja 	default:
12413dde0ae1SKurt Borja 		break;
12428cc2c415SKurt Borja 	}
12438cc2c415SKurt Borja 
1244a000da9dSKurt Borja 	if (!is_awcc_thermal_profile_id(out_data))
12458cc2c415SKurt Borja 		return -ENODATA;
12468cc2c415SKurt Borja 
1247a000da9dSKurt Borja 	out_data = FIELD_GET(AWCC_THERMAL_MODE_MASK, out_data);
12488a1a0fb5SKurt Borja 	*profile = awcc_mode_to_platform_profile[out_data];
12498cc2c415SKurt Borja 
12508cc2c415SKurt Borja 	return 0;
12518cc2c415SKurt Borja }
12528cc2c415SKurt Borja 
awcc_platform_profile_set(struct device * dev,enum platform_profile_option profile)12538a1a0fb5SKurt Borja static int awcc_platform_profile_set(struct device *dev,
12548cc2c415SKurt Borja 				     enum platform_profile_option profile)
12558cc2c415SKurt Borja {
12568cc2c415SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(dev);
12578cc2c415SKurt Borja 
12588cc2c415SKurt Borja 	if (awcc->gmode) {
12598cc2c415SKurt Borja 		u32 gmode_status;
12608cc2c415SKurt Borja 		int ret;
12618cc2c415SKurt Borja 
12628a1a0fb5SKurt Borja 		ret = awcc_game_shift_status(priv->wdev,
12638a1a0fb5SKurt Borja 					     AWCC_OP_GET_GAME_SHIFT_STATUS,
12648cc2c415SKurt Borja 					     &gmode_status);
12658cc2c415SKurt Borja 
12668cc2c415SKurt Borja 		if (ret < 0)
12678cc2c415SKurt Borja 			return ret;
12688cc2c415SKurt Borja 
12698cc2c415SKurt Borja 		if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) ||
12708cc2c415SKurt Borja 		    (profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) {
12718a1a0fb5SKurt Borja 			ret = awcc_game_shift_status(priv->wdev,
12728a1a0fb5SKurt Borja 						     AWCC_OP_TOGGLE_GAME_SHIFT,
12738cc2c415SKurt Borja 						     &gmode_status);
12748cc2c415SKurt Borja 
12758cc2c415SKurt Borja 			if (ret < 0)
12768cc2c415SKurt Borja 				return ret;
12778cc2c415SKurt Borja 		}
12788cc2c415SKurt Borja 	}
12798cc2c415SKurt Borja 
128077bb2ec5SKurt Borja 	return awcc_op_activate_profile(priv->wdev, priv->supported_profiles[profile]);
12818cc2c415SKurt Borja }
12828cc2c415SKurt Borja 
awcc_platform_profile_probe(void * drvdata,unsigned long * choices)12838a1a0fb5SKurt Borja static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
12848cc2c415SKurt Borja {
12858cc2c415SKurt Borja 	enum platform_profile_option profile;
12868cc2c415SKurt Borja 	struct awcc_priv *priv = drvdata;
12878a1a0fb5SKurt Borja 	enum awcc_thermal_profile mode;
128832b6372dSKurt Borja 	u8 id, offset = 0;
12898cc2c415SKurt Borja 	int ret;
12908cc2c415SKurt Borja 
129132b6372dSKurt Borja 	/*
129232b6372dSKurt Borja 	 * Thermal profile IDs are listed last at offset
129332b6372dSKurt Borja 	 *	fan_count + temp_count + unknown_count
129432b6372dSKurt Borja 	 */
129532b6372dSKurt Borja 	for (unsigned int i = 0; i < ARRAY_SIZE(priv->res_count) - 1; i++)
129632b6372dSKurt Borja 		offset += priv->res_count[i];
12978cc2c415SKurt Borja 
129832b6372dSKurt Borja 	for (unsigned int i = 0; i < priv->profile_count; i++) {
129932b6372dSKurt Borja 		ret = awcc_op_get_resource_id(priv->wdev, i + offset, &id);
130032b6372dSKurt Borja 		/*
130132b6372dSKurt Borja 		 * Some devices report an incorrect number of thermal profiles
130232b6372dSKurt Borja 		 * so the resource ID list may end prematurely
130332b6372dSKurt Borja 		 */
13048cc2c415SKurt Borja 		if (ret == -EBADRQC)
13058cc2c415SKurt Borja 			break;
13064a8e04e2SKurt Borja 		if (ret)
13074a8e04e2SKurt Borja 			return ret;
13088cc2c415SKurt Borja 
130932b6372dSKurt Borja 		if (!is_awcc_thermal_profile_id(id)) {
131032b6372dSKurt Borja 			dev_dbg(&priv->wdev->dev, "Unmapped thermal profile ID 0x%02x\n", id);
13118cc2c415SKurt Borja 			continue;
131232b6372dSKurt Borja 		}
13138cc2c415SKurt Borja 
1314a000da9dSKurt Borja 		mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, id);
13158a1a0fb5SKurt Borja 		profile = awcc_mode_to_platform_profile[mode];
131677bb2ec5SKurt Borja 		priv->supported_profiles[profile] = id;
13178cc2c415SKurt Borja 
131832b6372dSKurt Borja 		__set_bit(profile, choices);
13198cc2c415SKurt Borja 	}
13208cc2c415SKurt Borja 
13218cc2c415SKurt Borja 	if (bitmap_empty(choices, PLATFORM_PROFILE_LAST))
13228cc2c415SKurt Borja 		return -ENODEV;
13238cc2c415SKurt Borja 
13248cc2c415SKurt Borja 	if (awcc->gmode) {
132577bb2ec5SKurt Borja 		priv->supported_profiles[PLATFORM_PROFILE_PERFORMANCE] =
13263dde0ae1SKurt Borja 			AWCC_SPECIAL_PROFILE_GMODE;
13278cc2c415SKurt Borja 
132832b6372dSKurt Borja 		__set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
13298cc2c415SKurt Borja 	}
13308cc2c415SKurt Borja 
13313dde0ae1SKurt Borja 	/* Every model supports the "custom" profile */
13323dde0ae1SKurt Borja 	priv->supported_profiles[PLATFORM_PROFILE_CUSTOM] =
13333dde0ae1SKurt Borja 		AWCC_SPECIAL_PROFILE_CUSTOM;
13343dde0ae1SKurt Borja 
13353dde0ae1SKurt Borja 	__set_bit(PLATFORM_PROFILE_CUSTOM, choices);
13363dde0ae1SKurt Borja 
13378cc2c415SKurt Borja 	return 0;
13388cc2c415SKurt Borja }
13398cc2c415SKurt Borja 
13408cc2c415SKurt Borja static const struct platform_profile_ops awcc_platform_profile_ops = {
13418a1a0fb5SKurt Borja 	.probe = awcc_platform_profile_probe,
13428a1a0fb5SKurt Borja 	.profile_get = awcc_platform_profile_get,
13438a1a0fb5SKurt Borja 	.profile_set = awcc_platform_profile_set,
13448cc2c415SKurt Borja };
13458cc2c415SKurt Borja 
awcc_platform_profile_init(struct wmi_device * wdev)13468cc2c415SKurt Borja static int awcc_platform_profile_init(struct wmi_device *wdev)
13478cc2c415SKurt Borja {
13488cc2c415SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
13498cc2c415SKurt Borja 
13508cc2c415SKurt Borja 	priv->ppdev = devm_platform_profile_register(&wdev->dev, "alienware-wmi",
13518cc2c415SKurt Borja 						     priv, &awcc_platform_profile_ops);
13528cc2c415SKurt Borja 
13538cc2c415SKurt Borja 	return PTR_ERR_OR_ZERO(priv->ppdev);
13548cc2c415SKurt Borja }
13558cc2c415SKurt Borja 
1356b028fb49SKurt Borja /*
1357b028fb49SKurt Borja  * DebugFS
1358b028fb49SKurt Borja  */
awcc_debugfs_system_description_read(struct seq_file * seq,void * data)1359b028fb49SKurt Borja static int awcc_debugfs_system_description_read(struct seq_file *seq, void *data)
1360b028fb49SKurt Borja {
1361b028fb49SKurt Borja 	struct device *dev = seq->private;
1362b028fb49SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(dev);
1363b028fb49SKurt Borja 
1364b028fb49SKurt Borja 	seq_printf(seq, "0x%08x\n", priv->system_description);
1365b028fb49SKurt Borja 
1366b028fb49SKurt Borja 	return 0;
1367b028fb49SKurt Borja }
1368b028fb49SKurt Borja 
awcc_debugfs_hwmon_data_read(struct seq_file * seq,void * data)1369b028fb49SKurt Borja static int awcc_debugfs_hwmon_data_read(struct seq_file *seq, void *data)
1370b028fb49SKurt Borja {
1371b028fb49SKurt Borja 	struct device *dev = seq->private;
1372b028fb49SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(dev);
1373b028fb49SKurt Borja 	const struct awcc_fan_data *fan;
1374b028fb49SKurt Borja 	unsigned int bit;
1375b028fb49SKurt Borja 
1376b028fb49SKurt Borja 	seq_printf(seq, "Number of fans: %u\n", priv->fan_count);
1377b028fb49SKurt Borja 	seq_printf(seq, "Number of temperature sensors: %u\n\n", priv->temp_count);
1378b028fb49SKurt Borja 
1379b028fb49SKurt Borja 	for (u32 i = 0; i < priv->fan_count; i++) {
1380b028fb49SKurt Borja 		fan = priv->fan_data[i];
1381b028fb49SKurt Borja 
1382b028fb49SKurt Borja 		seq_printf(seq, "Fan %u:\n", i);
1383b028fb49SKurt Borja 		seq_printf(seq, "  ID: 0x%02x\n", fan->id);
1384b028fb49SKurt Borja 		seq_printf(seq, "  Related temperature sensors bitmap: %lu\n",
1385b028fb49SKurt Borja 			   fan->auto_channels_temp);
1386b028fb49SKurt Borja 	}
1387b028fb49SKurt Borja 
1388b028fb49SKurt Borja 	seq_puts(seq, "\nTemperature sensor IDs:\n");
1389b028fb49SKurt Borja 	for_each_set_bit(bit, priv->temp_sensors, AWCC_ID_BITMAP_SIZE)
1390b028fb49SKurt Borja 		seq_printf(seq, "  0x%02x\n", bit);
1391b028fb49SKurt Borja 
1392b028fb49SKurt Borja 	return 0;
1393b028fb49SKurt Borja }
1394b028fb49SKurt Borja 
awcc_debugfs_pprof_data_read(struct seq_file * seq,void * data)1395b028fb49SKurt Borja static int awcc_debugfs_pprof_data_read(struct seq_file *seq, void *data)
1396b028fb49SKurt Borja {
1397b028fb49SKurt Borja 	struct device *dev = seq->private;
1398b028fb49SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(dev);
1399b028fb49SKurt Borja 
1400b028fb49SKurt Borja 	seq_printf(seq, "Number of thermal profiles: %u\n\n", priv->profile_count);
1401b028fb49SKurt Borja 
1402b028fb49SKurt Borja 	for (u32 i = 0; i < PLATFORM_PROFILE_LAST; i++) {
1403b028fb49SKurt Borja 		if (!priv->supported_profiles[i])
1404b028fb49SKurt Borja 			continue;
1405b028fb49SKurt Borja 
1406b028fb49SKurt Borja 		seq_printf(seq, "Platform profile %u:\n", i);
1407b028fb49SKurt Borja 		seq_printf(seq, "  ID: 0x%02x\n", priv->supported_profiles[i]);
1408b028fb49SKurt Borja 	}
1409b028fb49SKurt Borja 
1410b028fb49SKurt Borja 	return 0;
1411b028fb49SKurt Borja }
1412b028fb49SKurt Borja 
awcc_gpio_pin_show(struct seq_file * seq,void * data)1413aee5cf93SKurt Borja static int awcc_gpio_pin_show(struct seq_file *seq, void *data)
1414aee5cf93SKurt Borja {
1415aee5cf93SKurt Borja 	unsigned long pin = debugfs_get_aux_num(seq->file);
1416aee5cf93SKurt Borja 	struct wmi_device *wdev = seq->private;
1417aee5cf93SKurt Borja 	u32 status;
1418aee5cf93SKurt Borja 	int ret;
1419aee5cf93SKurt Borja 
1420aee5cf93SKurt Borja 	ret = awcc_read_gpio_status(wdev, pin, &status);
1421aee5cf93SKurt Borja 	if (ret)
1422aee5cf93SKurt Borja 		return ret;
1423aee5cf93SKurt Borja 
1424aee5cf93SKurt Borja 	seq_printf(seq, "%u\n", status);
1425aee5cf93SKurt Borja 
1426aee5cf93SKurt Borja 	return 0;
1427aee5cf93SKurt Borja }
1428aee5cf93SKurt Borja 
awcc_gpio_pin_write(struct file * file,const char __user * buf,size_t count,loff_t * ppos)1429aee5cf93SKurt Borja static ssize_t awcc_gpio_pin_write(struct file *file, const char __user *buf,
1430aee5cf93SKurt Borja 				   size_t count, loff_t *ppos)
1431aee5cf93SKurt Borja {
1432aee5cf93SKurt Borja 	unsigned long pin = debugfs_get_aux_num(file);
1433aee5cf93SKurt Borja 	struct seq_file *seq = file->private_data;
1434aee5cf93SKurt Borja 	struct wmi_device *wdev = seq->private;
1435aee5cf93SKurt Borja 	bool status;
1436aee5cf93SKurt Borja 	int ret;
1437aee5cf93SKurt Borja 
1438aee5cf93SKurt Borja 	if (!ppos || *ppos)
1439aee5cf93SKurt Borja 		return -EINVAL;
1440aee5cf93SKurt Borja 
1441aee5cf93SKurt Borja 	ret = kstrtobool_from_user(buf, count, &status);
1442aee5cf93SKurt Borja 	if (ret)
1443aee5cf93SKurt Borja 		return ret;
1444aee5cf93SKurt Borja 
1445aee5cf93SKurt Borja 	ret = awcc_fwup_gpio_control(wdev, pin, status);
1446aee5cf93SKurt Borja 	if (ret)
1447aee5cf93SKurt Borja 		return ret;
1448aee5cf93SKurt Borja 
1449aee5cf93SKurt Borja 	return count;
1450aee5cf93SKurt Borja }
1451aee5cf93SKurt Borja 
1452aee5cf93SKurt Borja DEFINE_SHOW_STORE_ATTRIBUTE(awcc_gpio_pin);
1453aee5cf93SKurt Borja 
awcc_debugfs_remove(void * data)1454b028fb49SKurt Borja static void awcc_debugfs_remove(void *data)
1455b028fb49SKurt Borja {
1456b028fb49SKurt Borja 	struct dentry *root = data;
1457b028fb49SKurt Borja 
1458b028fb49SKurt Borja 	debugfs_remove(root);
1459b028fb49SKurt Borja }
1460b028fb49SKurt Borja 
awcc_debugfs_init(struct wmi_device * wdev)1461b028fb49SKurt Borja static void awcc_debugfs_init(struct wmi_device *wdev)
1462b028fb49SKurt Borja {
1463aee5cf93SKurt Borja 	struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
1464aee5cf93SKurt Borja 	struct dentry *root, *gpio_ctl;
1465aee5cf93SKurt Borja 	u32 gpio_count;
1466b028fb49SKurt Borja 	char name[64];
1467aee5cf93SKurt Borja 	int ret;
1468b028fb49SKurt Borja 
1469b028fb49SKurt Borja 	scnprintf(name, sizeof(name), "%s-%s", "alienware-wmi", dev_name(&wdev->dev));
1470b028fb49SKurt Borja 	root = debugfs_create_dir(name, NULL);
1471b028fb49SKurt Borja 
1472b028fb49SKurt Borja 	debugfs_create_devm_seqfile(&wdev->dev, "system_description", root,
1473b028fb49SKurt Borja 				    awcc_debugfs_system_description_read);
1474b028fb49SKurt Borja 
1475b028fb49SKurt Borja 	if (awcc->hwmon)
1476b028fb49SKurt Borja 		debugfs_create_devm_seqfile(&wdev->dev, "hwmon_data", root,
1477b028fb49SKurt Borja 					    awcc_debugfs_hwmon_data_read);
1478b028fb49SKurt Borja 
1479b028fb49SKurt Borja 	if (awcc->pprof)
1480b028fb49SKurt Borja 		debugfs_create_devm_seqfile(&wdev->dev, "pprof_data", root,
1481b028fb49SKurt Borja 					    awcc_debugfs_pprof_data_read);
1482b028fb49SKurt Borja 
1483aee5cf93SKurt Borja 	ret = awcc_read_total_gpios(wdev, &gpio_count);
1484aee5cf93SKurt Borja 	if (ret) {
1485aee5cf93SKurt Borja 		dev_dbg(&wdev->dev, "Failed to get total GPIO Pin count\n");
1486aee5cf93SKurt Borja 		goto out_add_action;
1487aee5cf93SKurt Borja 	} else if (gpio_count > AWCC_MAX_RES_COUNT) {
1488aee5cf93SKurt Borja 		dev_dbg(&wdev->dev, "Reported GPIO Pin count may be incorrect: %u\n", gpio_count);
1489aee5cf93SKurt Borja 		goto out_add_action;
1490aee5cf93SKurt Borja 	}
1491aee5cf93SKurt Borja 
1492aee5cf93SKurt Borja 	gpio_ctl = debugfs_create_dir("gpio_ctl", root);
1493aee5cf93SKurt Borja 
1494aee5cf93SKurt Borja 	priv->gpio_count = gpio_count;
1495aee5cf93SKurt Borja 	debugfs_create_u32("total_gpios", 0444, gpio_ctl, &priv->gpio_count);
1496aee5cf93SKurt Borja 
1497aee5cf93SKurt Borja 	for (unsigned int i = 0; i < gpio_count; i++) {
1498aee5cf93SKurt Borja 		scnprintf(name, sizeof(name), "pin%u", i);
1499aee5cf93SKurt Borja 		debugfs_create_file_aux_num(name, 0644, gpio_ctl, wdev, i,
1500aee5cf93SKurt Borja 					    &awcc_gpio_pin_fops);
1501aee5cf93SKurt Borja 	}
1502aee5cf93SKurt Borja 
1503aee5cf93SKurt Borja out_add_action:
1504b028fb49SKurt Borja 	devm_add_action_or_reset(&wdev->dev, awcc_debugfs_remove, root);
1505b028fb49SKurt Borja }
1506b028fb49SKurt Borja 
alienware_awcc_setup(struct wmi_device * wdev)15078cc2c415SKurt Borja static int alienware_awcc_setup(struct wmi_device *wdev)
15088cc2c415SKurt Borja {
15098cc2c415SKurt Borja 	struct awcc_priv *priv;
15108cc2c415SKurt Borja 	int ret;
15118cc2c415SKurt Borja 
15128cc2c415SKurt Borja 	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
15138cc2c415SKurt Borja 	if (!priv)
15148cc2c415SKurt Borja 		return -ENOMEM;
15158cc2c415SKurt Borja 
151632b6372dSKurt Borja 	ret = awcc_thermal_information(wdev, AWCC_OP_GET_SYSTEM_DESCRIPTION,
151732b6372dSKurt Borja 				       0, &priv->system_description);
151832b6372dSKurt Borja 	if (ret < 0)
151932b6372dSKurt Borja 		return ret;
152032b6372dSKurt Borja 
152132b6372dSKurt Borja 	/* Sanity check */
152232b6372dSKurt Borja 	for (unsigned int i = 0; i < ARRAY_SIZE(priv->res_count); i++) {
152332b6372dSKurt Borja 		if (priv->res_count[i] > AWCC_MAX_RES_COUNT) {
152432b6372dSKurt Borja 			dev_err(&wdev->dev, "Malformed system description: 0x%08x\n",
152532b6372dSKurt Borja 				priv->system_description);
152632b6372dSKurt Borja 			return -ENXIO;
152732b6372dSKurt Borja 		}
152832b6372dSKurt Borja 	}
152932b6372dSKurt Borja 
15308cc2c415SKurt Borja 	priv->wdev = wdev;
15318cc2c415SKurt Borja 	dev_set_drvdata(&wdev->dev, priv);
15328cc2c415SKurt Borja 
1533d6999078SKurt Borja 	if (awcc->hwmon) {
1534d6999078SKurt Borja 		ret = awcc_hwmon_init(wdev);
1535d6999078SKurt Borja 		if (ret)
1536d6999078SKurt Borja 			return ret;
1537d6999078SKurt Borja 	}
1538d6999078SKurt Borja 
15398cc2c415SKurt Borja 	if (awcc->pprof) {
15408cc2c415SKurt Borja 		ret = awcc_platform_profile_init(wdev);
15418cc2c415SKurt Borja 		if (ret)
15428cc2c415SKurt Borja 			return ret;
15438cc2c415SKurt Borja 	}
15448cc2c415SKurt Borja 
1545b028fb49SKurt Borja 	awcc_debugfs_init(wdev);
1546b028fb49SKurt Borja 
15478cc2c415SKurt Borja 	return 0;
15488cc2c415SKurt Borja }
15498cc2c415SKurt Borja 
15508cc2c415SKurt Borja /*
15518cc2c415SKurt Borja  * WMAX WMI driver
15528cc2c415SKurt Borja  */
wmax_wmi_update_led(struct alienfx_priv * priv,struct wmi_device * wdev,u8 location)15538cc2c415SKurt Borja static int wmax_wmi_update_led(struct alienfx_priv *priv,
15548cc2c415SKurt Borja 			       struct wmi_device *wdev, u8 location)
15558cc2c415SKurt Borja {
15568cc2c415SKurt Borja 	struct wmax_led_args in_args = {
15578cc2c415SKurt Borja 		.led_mask = 1 << location,
15588cc2c415SKurt Borja 		.colors = priv->colors[location],
15598cc2c415SKurt Borja 		.state = priv->lighting_control_state,
15608cc2c415SKurt Borja 	};
15618cc2c415SKurt Borja 
15628cc2c415SKurt Borja 	return alienware_wmi_command(wdev, WMAX_METHOD_ZONE_CONTROL, &in_args,
15638cc2c415SKurt Borja 				     sizeof(in_args), NULL);
15648cc2c415SKurt Borja }
15658cc2c415SKurt Borja 
wmax_wmi_update_brightness(struct alienfx_priv * priv,struct wmi_device * wdev,u8 brightness)15668cc2c415SKurt Borja static int wmax_wmi_update_brightness(struct alienfx_priv *priv,
15678cc2c415SKurt Borja 				      struct wmi_device *wdev, u8 brightness)
15688cc2c415SKurt Borja {
15698cc2c415SKurt Borja 	struct wmax_brightness_args in_args = {
15708cc2c415SKurt Borja 		.led_mask = 0xFF,
15718cc2c415SKurt Borja 		.percentage = brightness,
15728cc2c415SKurt Borja 	};
15738cc2c415SKurt Borja 
15748cc2c415SKurt Borja 	return alienware_wmi_command(wdev, WMAX_METHOD_BRIGHTNESS, &in_args,
15758cc2c415SKurt Borja 				     sizeof(in_args), NULL);
15768cc2c415SKurt Borja }
15778cc2c415SKurt Borja 
wmax_wmi_probe(struct wmi_device * wdev,const void * context)15788cc2c415SKurt Borja static int wmax_wmi_probe(struct wmi_device *wdev, const void *context)
15798cc2c415SKurt Borja {
15808cc2c415SKurt Borja 	struct alienfx_platdata pdata = {
15818cc2c415SKurt Borja 		.wdev = wdev,
15828cc2c415SKurt Borja 		.ops = {
15838cc2c415SKurt Borja 			.upd_led = wmax_wmi_update_led,
15848cc2c415SKurt Borja 			.upd_brightness = wmax_wmi_update_brightness,
15858cc2c415SKurt Borja 		},
15868cc2c415SKurt Borja 	};
15878cc2c415SKurt Borja 	int ret;
15888cc2c415SKurt Borja 
15898cc2c415SKurt Borja 	if (awcc)
15908cc2c415SKurt Borja 		ret = alienware_awcc_setup(wdev);
15918cc2c415SKurt Borja 	else
15928cc2c415SKurt Borja 		ret = alienware_alienfx_setup(&pdata);
15938cc2c415SKurt Borja 
15948cc2c415SKurt Borja 	return ret;
15958cc2c415SKurt Borja }
15968cc2c415SKurt Borja 
wmax_wmi_suspend(struct device * dev)159707ac2759SKurt Borja static int wmax_wmi_suspend(struct device *dev)
159807ac2759SKurt Borja {
159907ac2759SKurt Borja 	if (awcc->hwmon)
160007ac2759SKurt Borja 		awcc_hwmon_suspend(dev);
160107ac2759SKurt Borja 
160207ac2759SKurt Borja 	return 0;
160307ac2759SKurt Borja }
160407ac2759SKurt Borja 
wmax_wmi_resume(struct device * dev)160507ac2759SKurt Borja static int wmax_wmi_resume(struct device *dev)
160607ac2759SKurt Borja {
160707ac2759SKurt Borja 	if (awcc->hwmon)
160807ac2759SKurt Borja 		awcc_hwmon_resume(dev);
160907ac2759SKurt Borja 
161007ac2759SKurt Borja 	return 0;
161107ac2759SKurt Borja }
161207ac2759SKurt Borja 
161307ac2759SKurt Borja static DEFINE_SIMPLE_DEV_PM_OPS(wmax_wmi_pm_ops, wmax_wmi_suspend, wmax_wmi_resume);
161407ac2759SKurt Borja 
16158cc2c415SKurt Borja static const struct wmi_device_id alienware_wmax_device_id_table[] = {
16168cc2c415SKurt Borja 	{ WMAX_CONTROL_GUID, NULL },
16178cc2c415SKurt Borja 	{ },
16188cc2c415SKurt Borja };
16198cc2c415SKurt Borja MODULE_DEVICE_TABLE(wmi, alienware_wmax_device_id_table);
16208cc2c415SKurt Borja 
16218cc2c415SKurt Borja static struct wmi_driver alienware_wmax_wmi_driver = {
16228cc2c415SKurt Borja 	.driver = {
16238cc2c415SKurt Borja 		.name = "alienware-wmi-wmax",
16248cc2c415SKurt Borja 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
162507ac2759SKurt Borja 		.pm = pm_sleep_ptr(&wmax_wmi_pm_ops),
16268cc2c415SKurt Borja 	},
16278cc2c415SKurt Borja 	.id_table = alienware_wmax_device_id_table,
16288cc2c415SKurt Borja 	.probe = wmax_wmi_probe,
16298cc2c415SKurt Borja 	.no_singleton = true,
16308cc2c415SKurt Borja };
16318cc2c415SKurt Borja 
alienware_wmax_wmi_init(void)16328cc2c415SKurt Borja int __init alienware_wmax_wmi_init(void)
16338cc2c415SKurt Borja {
16348cc2c415SKurt Borja 	const struct dmi_system_id *id;
16358cc2c415SKurt Borja 
16368cc2c415SKurt Borja 	id = dmi_first_match(awcc_dmi_table);
16378cc2c415SKurt Borja 	if (id)
16388cc2c415SKurt Borja 		awcc = id->driver_data;
16398cc2c415SKurt Borja 
1640d6999078SKurt Borja 	if (force_hwmon) {
1641d6999078SKurt Borja 		if (!awcc)
1642d6999078SKurt Borja 			awcc = &empty_quirks;
1643d6999078SKurt Borja 
1644d6999078SKurt Borja 		awcc->hwmon = true;
1645d6999078SKurt Borja 	}
1646d6999078SKurt Borja 
16478cc2c415SKurt Borja 	if (force_platform_profile) {
16488cc2c415SKurt Borja 		if (!awcc)
16498cc2c415SKurt Borja 			awcc = &empty_quirks;
16508cc2c415SKurt Borja 
16518cc2c415SKurt Borja 		awcc->pprof = true;
16528cc2c415SKurt Borja 	}
16538cc2c415SKurt Borja 
16548cc2c415SKurt Borja 	if (force_gmode) {
16558cc2c415SKurt Borja 		if (awcc)
16568cc2c415SKurt Borja 			awcc->gmode = true;
16578cc2c415SKurt Borja 		else
16588cc2c415SKurt Borja 			pr_warn("force_gmode requires platform profile support\n");
16598cc2c415SKurt Borja 	}
16608cc2c415SKurt Borja 
16618cc2c415SKurt Borja 	return wmi_driver_register(&alienware_wmax_wmi_driver);
16628cc2c415SKurt Borja }
16638cc2c415SKurt Borja 
alienware_wmax_wmi_exit(void)16648cc2c415SKurt Borja void __exit alienware_wmax_wmi_exit(void)
16658cc2c415SKurt Borja {
16668cc2c415SKurt Borja 	wmi_driver_unregister(&alienware_wmax_wmi_driver);
16678cc2c415SKurt Borja }
1668