* [PATCH 00/10] HWMON support + DebugFS + Improvements
@ 2025-02-08 5:16 Kurt Borja
2025-02-08 5:16 ` [PATCH 01/10] platform/x86: alienware-wmi-wmax: Rename thermal related symbols Kurt Borja
` (9 more replies)
0 siblings, 10 replies; 25+ messages in thread
From: Kurt Borja @ 2025-02-08 5:16 UTC (permalink / raw)
To: Ilpo Järvinen, Armin Wolf
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Kurt Borja
Hi, all :)
Seeing that the alienware-wmi rework patchset is almost done I wanted to
get ahead on development and submit these series.
The main highlight is of course HWMON + manual fan control support
(patches 7 and 8) which is highly requested by the few users who
contacted me privately. I took a similar approach to the dell-wmi-ddv
driver (thanks Armin!) and cached sensor readings for better
performance.
As always, your feedback is very appreciated!
Based on pdx86/for-next, depends on [1].
~ Kurt
---
[1] https://lore.kernel.org/platform-driver-x86/20250207154610.13675-1-kuurtb@gmail.com/
Kurt Borja (10):
platform/x86: alienware-wmi-wmax: Rename thermal related symbols
platform/x86: alienware-wmi-wmax: Refactor is_awcc_thermal_mode()
platform/x86: alienware-wmi-wmax: Improve internal AWCC API
platform/x86: alienware-wmi-wmax: Modify supported_thermal_profiles[]
platform/x86: alienware-wmi-wmax: Improve platform profile probe
platform/x86: alienware-wmi-wmax: Add support for the "custom" thermal
profile
platform/x86: alienware-wmi-wmax: Add HWMON support
platform/x86: alienware-wmi-wmax: Add support for manual fan control
platform/x86: alienware-wmi-wmax: Add a DebugFS interface
platform/x86: alienware-wmi: Improve and update documentation
Documentation/wmi/devices/alienware-wmi.rst | 390 +++------
drivers/platform/x86/dell/Kconfig | 1 +
.../platform/x86/dell/alienware-wmi-wmax.c | 793 +++++++++++++++---
3 files changed, 792 insertions(+), 392 deletions(-)
base-commit: e75394bbf4838857f57b6c5d00f1e56c46cd6c11
prerequisite-patch-id: 2e54a1278aabd3810a2e501ab8f001af5f83e45c
prerequisite-patch-id: d9e78e1086bb0fc8df23678f91fcf2e219d73f81
prerequisite-patch-id: db9c0c4b1b0dacf365d9aa4644282e32a3e80634
prerequisite-patch-id: c9cf42516931054b8a292774b9c86416246da36c
prerequisite-patch-id: a047966c43a92c41a01858aed9b74470324f9e4f
prerequisite-patch-id: 32293f8c9ed39c076c30f7bcf9ab8105ff8dd4f4
prerequisite-patch-id: 100391541e9ca8c1a49b1e48a3f99128b0d14b5d
prerequisite-patch-id: e7cffaa05f5212b70520d3d80c23f1804007b349
prerequisite-patch-id: 4f9219d3b1f6281b1815f9ac5e823aa59e85f588
prerequisite-patch-id: c37756f4b1e7f61c0dac0f3cbe68b95838ecce84
prerequisite-patch-id: fdf58a28326cec768d5f45252dd7b1f189532623
prerequisite-patch-id: 0a0fd32bdc83a8676eeda30e25f93c2edd3b6d3c
prerequisite-patch-id: 4c85aa2b99a106907639075a2fc455f312a14b4f
prerequisite-patch-id: bb7c74dd242bf773f06e531090ec63cc17b1ac69
--
2.48.1
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH 01/10] platform/x86: alienware-wmi-wmax: Rename thermal related symbols
2025-02-08 5:16 [PATCH 00/10] HWMON support + DebugFS + Improvements Kurt Borja
@ 2025-02-08 5:16 ` Kurt Borja
2025-02-16 5:40 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 02/10] platform/x86: alienware-wmi-wmax: Refactor is_awcc_thermal_mode() Kurt Borja
` (8 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Kurt Borja @ 2025-02-08 5:16 UTC (permalink / raw)
To: Ilpo Järvinen, Armin Wolf
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Kurt Borja
The "thermal" features of the WMAX WMI device are only present on the
host device if the ACPI _UID is "AWCC". Replace WMAX prefixes with
"AWCC" to reflect this relationship.
Thermal profiles with WMAX_PROFILE_BASIC prefix are also renamed to
WMAX_PROFILE_LEGACY because they are only supported in older versions
of this WMI device.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
.../platform/x86/dell/alienware-wmi-wmax.c | 173 +++++++++---------
1 file changed, 87 insertions(+), 86 deletions(-)
diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
index 3d3014b5adf0..ed70e12d73d7 100644
--- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
+++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
@@ -24,16 +24,17 @@
#define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C
#define WMAX_METHOD_BRIGHTNESS 0x3
#define WMAX_METHOD_ZONE_CONTROL 0x4
-#define WMAX_METHOD_THERMAL_INFORMATION 0x14
-#define WMAX_METHOD_THERMAL_CONTROL 0x15
-#define WMAX_METHOD_GAME_SHIFT_STATUS 0x25
-#define WMAX_THERMAL_MODE_GMODE 0xAB
+#define AWCC_METHOD_THERMAL_INFORMATION 0x14
+#define AWCC_METHOD_THERMAL_CONTROL 0x15
+#define AWCC_METHOD_GAME_SHIFT_STATUS 0x25
-#define WMAX_FAILURE_CODE 0xFFFFFFFF
-#define WMAX_THERMAL_TABLE_MASK GENMASK(7, 4)
-#define WMAX_THERMAL_MODE_MASK GENMASK(3, 0)
-#define WMAX_SENSOR_ID_MASK BIT(8)
+#define AWCC_THERMAL_MODE_GMODE 0xAB
+
+#define AWCC_FAILURE_CODE 0xFFFFFFFF
+#define AWCC_THERMAL_TABLE_MASK GENMASK(7, 4)
+#define AWCC_THERMAL_MODE_MASK GENMASK(3, 0)
+#define AWCC_SENSOR_ID_MASK BIT(8)
static bool force_platform_profile;
module_param_unsafe(force_platform_profile, bool, 0);
@@ -151,38 +152,38 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = {
},
};
-enum WMAX_THERMAL_INFORMATION_OPERATIONS {
- WMAX_OPERATION_SYS_DESCRIPTION = 0x02,
- WMAX_OPERATION_LIST_IDS = 0x03,
- WMAX_OPERATION_CURRENT_PROFILE = 0x0B,
+enum AWCC_THERMAL_INFORMATION_OPERATIONS {
+ AWCC_OP_GET_SYSTEM_DESCRIPTION = 0x02,
+ AWCC_OP_GET_RESOURCE_ID = 0x03,
+ AWCC_OP_GET_CURRENT_PROFILE = 0x0B,
};
-enum WMAX_THERMAL_CONTROL_OPERATIONS {
- WMAX_OPERATION_ACTIVATE_PROFILE = 0x01,
+enum AWCC_THERMAL_CONTROL_OPERATIONS {
+ AWCC_OP_ACTIVATE_PROFILE = 0x01,
};
-enum WMAX_GAME_SHIFT_STATUS_OPERATIONS {
- WMAX_OPERATION_TOGGLE_GAME_SHIFT = 0x01,
- WMAX_OPERATION_GET_GAME_SHIFT_STATUS = 0x02,
+enum AWCC_GAME_SHIFT_STATUS_OPERATIONS {
+ AWCC_OP_TOGGLE_GAME_SHIFT = 0x01,
+ AWCC_OP_GET_GAME_SHIFT_STATUS = 0x02,
};
-enum WMAX_THERMAL_TABLES {
- WMAX_THERMAL_TABLE_BASIC = 0x90,
- WMAX_THERMAL_TABLE_USTT = 0xA0,
+enum AWCC_THERMAL_TABLES {
+ AWCC_THERMAL_TABLE_LEGACY = 0x90,
+ AWCC_THERMAL_TABLE_USTT = 0xA0,
};
-enum wmax_thermal_mode {
- THERMAL_MODE_USTT_BALANCED,
- THERMAL_MODE_USTT_BALANCED_PERFORMANCE,
- THERMAL_MODE_USTT_COOL,
- THERMAL_MODE_USTT_QUIET,
- THERMAL_MODE_USTT_PERFORMANCE,
- THERMAL_MODE_USTT_LOW_POWER,
- THERMAL_MODE_BASIC_QUIET,
- THERMAL_MODE_BASIC_BALANCED,
- THERMAL_MODE_BASIC_BALANCED_PERFORMANCE,
- THERMAL_MODE_BASIC_PERFORMANCE,
- THERMAL_MODE_LAST,
+enum awcc_thermal_profile {
+ AWCC_PROFILE_USTT_BALANCED,
+ AWCC_PROFILE_USTT_BALANCED_PERFORMANCE,
+ AWCC_PROFILE_USTT_COOL,
+ AWCC_PROFILE_USTT_QUIET,
+ AWCC_PROFILE_USTT_PERFORMANCE,
+ AWCC_PROFILE_USTT_LOW_POWER,
+ AWCC_PROFILE_LEGACY_QUIET,
+ AWCC_PROFILE_LEGACY_BALANCED,
+ AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE,
+ AWCC_PROFILE_LEGACY_PERFORMANCE,
+ AWCC_PROFILE_LAST,
};
struct wmax_led_args {
@@ -210,20 +211,20 @@ struct wmax_u32_args {
struct awcc_priv {
struct wmi_device *wdev;
struct device *ppdev;
- enum wmax_thermal_mode supported_thermal_profiles[PLATFORM_PROFILE_LAST];
+ enum awcc_thermal_profile supported_thermal_profiles[PLATFORM_PROFILE_LAST];
};
-static const enum platform_profile_option wmax_mode_to_platform_profile[THERMAL_MODE_LAST] = {
- [THERMAL_MODE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED,
- [THERMAL_MODE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
- [THERMAL_MODE_USTT_COOL] = PLATFORM_PROFILE_COOL,
- [THERMAL_MODE_USTT_QUIET] = PLATFORM_PROFILE_QUIET,
- [THERMAL_MODE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
- [THERMAL_MODE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER,
- [THERMAL_MODE_BASIC_QUIET] = PLATFORM_PROFILE_QUIET,
- [THERMAL_MODE_BASIC_BALANCED] = PLATFORM_PROFILE_BALANCED,
- [THERMAL_MODE_BASIC_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
- [THERMAL_MODE_BASIC_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
+static const enum platform_profile_option awcc_mode_to_platform_profile[AWCC_PROFILE_LAST] = {
+ [AWCC_PROFILE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED,
+ [AWCC_PROFILE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
+ [AWCC_PROFILE_USTT_COOL] = PLATFORM_PROFILE_COOL,
+ [AWCC_PROFILE_USTT_QUIET] = PLATFORM_PROFILE_QUIET,
+ [AWCC_PROFILE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
+ [AWCC_PROFILE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER,
+ [AWCC_PROFILE_LEGACY_QUIET] = PLATFORM_PROFILE_QUIET,
+ [AWCC_PROFILE_LEGACY_BALANCED] = PLATFORM_PROFILE_BALANCED,
+ [AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
+ [AWCC_PROFILE_LEGACY_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
};
static struct awcc_quirks *awcc;
@@ -444,26 +445,26 @@ const struct attribute_group wmax_deepsleep_attribute_group = {
* Thermal Profile control
* - Provides thermal profile control through the Platform Profile API
*/
-static bool is_wmax_thermal_code(u32 code)
+static bool is_awcc_thermal_mode(u32 code)
{
- if (code & WMAX_SENSOR_ID_MASK)
+ if (code & AWCC_SENSOR_ID_MASK)
return false;
- if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST)
+ if ((code & AWCC_THERMAL_MODE_MASK) >= AWCC_PROFILE_LAST)
return false;
- if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC &&
- (code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET)
+ if ((code & AWCC_THERMAL_TABLE_MASK) == AWCC_THERMAL_TABLE_LEGACY &&
+ (code & AWCC_THERMAL_MODE_MASK) >= AWCC_PROFILE_LEGACY_QUIET)
return true;
- if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT &&
- (code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER)
+ if ((code & AWCC_THERMAL_TABLE_MASK) == AWCC_THERMAL_TABLE_USTT &&
+ (code & AWCC_THERMAL_MODE_MASK) <= AWCC_PROFILE_USTT_LOW_POWER)
return true;
return false;
}
-static int wmax_thermal_information(struct wmi_device *wdev, u8 operation,
+static int awcc_thermal_information(struct wmi_device *wdev, u8 operation,
u8 arg, u32 *out_data)
{
struct wmax_u32_args in_args = {
@@ -474,21 +475,21 @@ static int wmax_thermal_information(struct wmi_device *wdev, u8 operation,
};
int ret;
- ret = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_INFORMATION,
+ ret = alienware_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION,
&in_args, sizeof(in_args), out_data);
if (ret < 0)
return ret;
- if (*out_data == WMAX_FAILURE_CODE)
+ if (*out_data == AWCC_FAILURE_CODE)
return -EBADRQC;
return 0;
}
-static int wmax_thermal_control(struct wmi_device *wdev, u8 profile)
+static int awcc_thermal_control(struct wmi_device *wdev, u8 profile)
{
struct wmax_u32_args in_args = {
- .operation = WMAX_OPERATION_ACTIVATE_PROFILE,
+ .operation = AWCC_OP_ACTIVATE_PROFILE,
.arg1 = profile,
.arg2 = 0,
.arg3 = 0,
@@ -496,18 +497,18 @@ static int wmax_thermal_control(struct wmi_device *wdev, u8 profile)
u32 out_data;
int ret;
- ret = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_CONTROL,
+ ret = alienware_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL,
&in_args, sizeof(in_args), &out_data);
if (ret)
return ret;
- if (out_data == WMAX_FAILURE_CODE)
+ if (out_data == AWCC_FAILURE_CODE)
return -EBADRQC;
return 0;
}
-static int wmax_game_shift_status(struct wmi_device *wdev, u8 operation,
+static int awcc_game_shift_status(struct wmi_device *wdev, u8 operation,
u32 *out_data)
{
struct wmax_u32_args in_args = {
@@ -518,46 +519,46 @@ static int wmax_game_shift_status(struct wmi_device *wdev, u8 operation,
};
int ret;
- ret = alienware_wmi_command(wdev, WMAX_METHOD_GAME_SHIFT_STATUS,
+ ret = alienware_wmi_command(wdev, AWCC_METHOD_GAME_SHIFT_STATUS,
&in_args, sizeof(in_args), out_data);
if (ret < 0)
return ret;
- if (*out_data == WMAX_FAILURE_CODE)
+ if (*out_data == AWCC_FAILURE_CODE)
return -EOPNOTSUPP;
return 0;
}
-static int thermal_profile_get(struct device *dev,
- enum platform_profile_option *profile)
+static int awcc_platform_profile_get(struct device *dev,
+ enum platform_profile_option *profile)
{
struct awcc_priv *priv = dev_get_drvdata(dev);
u32 out_data;
int ret;
- ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_CURRENT_PROFILE,
+ ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_CURRENT_PROFILE,
0, &out_data);
if (ret < 0)
return ret;
- if (out_data == WMAX_THERMAL_MODE_GMODE) {
+ if (out_data == AWCC_THERMAL_MODE_GMODE) {
*profile = PLATFORM_PROFILE_PERFORMANCE;
return 0;
}
- if (!is_wmax_thermal_code(out_data))
+ if (!is_awcc_thermal_mode(out_data))
return -ENODATA;
- out_data &= WMAX_THERMAL_MODE_MASK;
- *profile = wmax_mode_to_platform_profile[out_data];
+ out_data &= AWCC_THERMAL_MODE_MASK;
+ *profile = awcc_mode_to_platform_profile[out_data];
return 0;
}
-static int thermal_profile_set(struct device *dev,
- enum platform_profile_option profile)
+static int awcc_platform_profile_set(struct device *dev,
+ enum platform_profile_option profile)
{
struct awcc_priv *priv = dev_get_drvdata(dev);
@@ -565,8 +566,8 @@ static int thermal_profile_set(struct device *dev,
u32 gmode_status;
int ret;
- ret = wmax_game_shift_status(priv->wdev,
- WMAX_OPERATION_GET_GAME_SHIFT_STATUS,
+ ret = awcc_game_shift_status(priv->wdev,
+ AWCC_OP_GET_GAME_SHIFT_STATUS,
&gmode_status);
if (ret < 0)
@@ -574,8 +575,8 @@ static int thermal_profile_set(struct device *dev,
if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) ||
(profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) {
- ret = wmax_game_shift_status(priv->wdev,
- WMAX_OPERATION_TOGGLE_GAME_SHIFT,
+ ret = awcc_game_shift_status(priv->wdev,
+ AWCC_OP_TOGGLE_GAME_SHIFT,
&gmode_status);
if (ret < 0)
@@ -583,21 +584,21 @@ static int thermal_profile_set(struct device *dev,
}
}
- return wmax_thermal_control(priv->wdev,
+ return awcc_thermal_control(priv->wdev,
priv->supported_thermal_profiles[profile]);
}
-static int thermal_profile_probe(void *drvdata, unsigned long *choices)
+static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
{
enum platform_profile_option profile;
struct awcc_priv *priv = drvdata;
- enum wmax_thermal_mode mode;
+ enum awcc_thermal_profile mode;
u8 sys_desc[4];
u32 first_mode;
u32 out_data;
int ret;
- ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_SYS_DESCRIPTION,
+ ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_SYSTEM_DESCRIPTION,
0, (u32 *) &sys_desc);
if (ret < 0)
return ret;
@@ -605,7 +606,7 @@ static int thermal_profile_probe(void *drvdata, unsigned long *choices)
first_mode = sys_desc[0] + sys_desc[1];
for (u32 i = 0; i < sys_desc[3]; i++) {
- ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_LIST_IDS,
+ ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_RESOURCE_ID,
i + first_mode, &out_data);
if (ret == -EIO)
@@ -614,11 +615,11 @@ static int thermal_profile_probe(void *drvdata, unsigned long *choices)
if (ret == -EBADRQC)
break;
- if (!is_wmax_thermal_code(out_data))
+ if (!is_awcc_thermal_mode(out_data))
continue;
- mode = out_data & WMAX_THERMAL_MODE_MASK;
- profile = wmax_mode_to_platform_profile[mode];
+ mode = out_data & AWCC_THERMAL_MODE_MASK;
+ profile = awcc_mode_to_platform_profile[mode];
priv->supported_thermal_profiles[profile] = out_data;
set_bit(profile, choices);
@@ -629,7 +630,7 @@ static int thermal_profile_probe(void *drvdata, unsigned long *choices)
if (awcc->gmode) {
priv->supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] =
- WMAX_THERMAL_MODE_GMODE;
+ AWCC_THERMAL_MODE_GMODE;
set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
}
@@ -638,9 +639,9 @@ static int thermal_profile_probe(void *drvdata, unsigned long *choices)
}
static const struct platform_profile_ops awcc_platform_profile_ops = {
- .probe = thermal_profile_probe,
- .profile_get = thermal_profile_get,
- .profile_set = thermal_profile_set,
+ .probe = awcc_platform_profile_probe,
+ .profile_get = awcc_platform_profile_get,
+ .profile_set = awcc_platform_profile_set,
};
static int awcc_platform_profile_init(struct wmi_device *wdev)
--
2.48.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 02/10] platform/x86: alienware-wmi-wmax: Refactor is_awcc_thermal_mode()
2025-02-08 5:16 [PATCH 00/10] HWMON support + DebugFS + Improvements Kurt Borja
2025-02-08 5:16 ` [PATCH 01/10] platform/x86: alienware-wmi-wmax: Rename thermal related symbols Kurt Borja
@ 2025-02-08 5:16 ` Kurt Borja
2025-02-16 5:44 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 03/10] platform/x86: alienware-wmi-wmax: Improve internal AWCC API Kurt Borja
` (7 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Kurt Borja @ 2025-02-08 5:16 UTC (permalink / raw)
To: Ilpo Järvinen, Armin Wolf
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Kurt Borja
Refactor is_awcc_thermal_mode() to use FIELD_GET() instead of bitwise
operations. Drop the check for BIT(8) sensor flag and rename it to
is_awcc_thermal_profile_id().
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
.../platform/x86/dell/alienware-wmi-wmax.c | 31 ++++++++++---------
1 file changed, 16 insertions(+), 15 deletions(-)
diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
index ed70e12d73d7..7f0aa88221d6 100644
--- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
+++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
@@ -34,7 +34,7 @@
#define AWCC_FAILURE_CODE 0xFFFFFFFF
#define AWCC_THERMAL_TABLE_MASK GENMASK(7, 4)
#define AWCC_THERMAL_MODE_MASK GENMASK(3, 0)
-#define AWCC_SENSOR_ID_MASK BIT(8)
+#define AWCC_RESOURCE_ID_MASK GENMASK(7, 0)
static bool force_platform_profile;
module_param_unsafe(force_platform_profile, bool, 0);
@@ -168,8 +168,8 @@ enum AWCC_GAME_SHIFT_STATUS_OPERATIONS {
};
enum AWCC_THERMAL_TABLES {
- AWCC_THERMAL_TABLE_LEGACY = 0x90,
- AWCC_THERMAL_TABLE_USTT = 0xA0,
+ AWCC_THERMAL_TABLE_LEGACY = 0x9,
+ AWCC_THERMAL_TABLE_USTT = 0xA,
};
enum awcc_thermal_profile {
@@ -445,20 +445,18 @@ const struct attribute_group wmax_deepsleep_attribute_group = {
* Thermal Profile control
* - Provides thermal profile control through the Platform Profile API
*/
-static bool is_awcc_thermal_mode(u32 code)
+static bool is_awcc_thermal_profile_id(u8 code)
{
- if (code & AWCC_SENSOR_ID_MASK)
- return false;
+ u8 table = FIELD_GET(AWCC_THERMAL_TABLE_MASK, code);
+ u8 mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, code);
- if ((code & AWCC_THERMAL_MODE_MASK) >= AWCC_PROFILE_LAST)
+ if (mode >= AWCC_PROFILE_LAST)
return false;
- if ((code & AWCC_THERMAL_TABLE_MASK) == AWCC_THERMAL_TABLE_LEGACY &&
- (code & AWCC_THERMAL_MODE_MASK) >= AWCC_PROFILE_LEGACY_QUIET)
+ if (table == AWCC_THERMAL_TABLE_LEGACY && mode >= AWCC_PROFILE_LEGACY_QUIET)
return true;
- if ((code & AWCC_THERMAL_TABLE_MASK) == AWCC_THERMAL_TABLE_USTT &&
- (code & AWCC_THERMAL_MODE_MASK) <= AWCC_PROFILE_USTT_LOW_POWER)
+ if (table == AWCC_THERMAL_TABLE_USTT && mode <= AWCC_PROFILE_USTT_LOW_POWER)
return true;
return false;
@@ -548,7 +546,7 @@ static int awcc_platform_profile_get(struct device *dev,
return 0;
}
- if (!is_awcc_thermal_mode(out_data))
+ if (!is_awcc_thermal_profile_id(out_data))
return -ENODATA;
out_data &= AWCC_THERMAL_MODE_MASK;
@@ -597,6 +595,7 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
u32 first_mode;
u32 out_data;
int ret;
+ u8 id;
ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_SYSTEM_DESCRIPTION,
0, (u32 *) &sys_desc);
@@ -615,12 +614,14 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
if (ret == -EBADRQC)
break;
- if (!is_awcc_thermal_mode(out_data))
+ /* Some IDs have a BIT(8) flag that should be ignored */
+ id = FIELD_GET(AWCC_RESOURCE_ID_MASK, out_data);
+ if (!is_awcc_thermal_profile_id(id))
continue;
- mode = out_data & AWCC_THERMAL_MODE_MASK;
+ mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, id);
profile = awcc_mode_to_platform_profile[mode];
- priv->supported_thermal_profiles[profile] = out_data;
+ priv->supported_thermal_profiles[profile] = id;
set_bit(profile, choices);
}
--
2.48.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 03/10] platform/x86: alienware-wmi-wmax: Improve internal AWCC API
2025-02-08 5:16 [PATCH 00/10] HWMON support + DebugFS + Improvements Kurt Borja
2025-02-08 5:16 ` [PATCH 01/10] platform/x86: alienware-wmi-wmax: Rename thermal related symbols Kurt Borja
2025-02-08 5:16 ` [PATCH 02/10] platform/x86: alienware-wmi-wmax: Refactor is_awcc_thermal_mode() Kurt Borja
@ 2025-02-08 5:16 ` Kurt Borja
2025-02-16 5:48 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 04/10] platform/x86: alienware-wmi-wmax: Modify supported_thermal_profiles[] Kurt Borja
` (6 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Kurt Borja @ 2025-02-08 5:16 UTC (permalink / raw)
To: Ilpo Järvinen, Armin Wolf
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Kurt Borja
Inline all AWCC WMI helper methods and directly return the newly
introduced __awcc_wmi_command() to simplify implementation. Additionally
make wmax_u32_args __packed, additional failure code and add a helper
with documentation for AWCC_OP_GET_RESOURCE_ID.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
.../platform/x86/dell/alienware-wmi-wmax.c | 104 +++++++++++-------
1 file changed, 63 insertions(+), 41 deletions(-)
diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
index 7f0aa88221d6..57897a0f4296 100644
--- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
+++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
@@ -32,6 +32,7 @@
#define AWCC_THERMAL_MODE_GMODE 0xAB
#define AWCC_FAILURE_CODE 0xFFFFFFFF
+#define AWCC_FAILURE_CODE_2 0xFFFFFFFE
#define AWCC_THERMAL_TABLE_MASK GENMASK(7, 4)
#define AWCC_THERMAL_MODE_MASK GENMASK(3, 0)
#define AWCC_RESOURCE_ID_MASK GENMASK(7, 0)
@@ -206,7 +207,7 @@ struct wmax_u32_args {
u8 arg1;
u8 arg2;
u8 arg3;
-};
+} __packed;
struct awcc_priv {
struct wmi_device *wdev;
@@ -442,8 +443,7 @@ const struct attribute_group wmax_deepsleep_attribute_group = {
};
/*
- * Thermal Profile control
- * - Provides thermal profile control through the Platform Profile API
+ * AWCC Helpers
*/
static bool is_awcc_thermal_profile_id(u8 code)
{
@@ -462,72 +462,95 @@ static bool is_awcc_thermal_profile_id(u8 code)
return false;
}
-static int awcc_thermal_information(struct wmi_device *wdev, u8 operation,
- u8 arg, u32 *out_data)
+static int __awcc_wmi_command(struct wmi_device *wdev, u32 method_id,
+ struct wmax_u32_args *args, u32 *out)
{
- struct wmax_u32_args in_args = {
- .operation = operation,
- .arg1 = arg,
- .arg2 = 0,
- .arg3 = 0,
- };
int ret;
- ret = alienware_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION,
- &in_args, sizeof(in_args), out_data);
- if (ret < 0)
+ ret = alienware_wmi_command(wdev, method_id, args, sizeof(*args), out);
+ if (ret)
return ret;
- if (*out_data == AWCC_FAILURE_CODE)
+ if (*out == AWCC_FAILURE_CODE || *out == AWCC_FAILURE_CODE_2)
return -EBADRQC;
return 0;
}
-static int awcc_thermal_control(struct wmi_device *wdev, u8 profile)
+static inline int awcc_thermal_information(struct wmi_device *wdev, u8 operation,
+ u8 arg, u32 *out)
{
- struct wmax_u32_args in_args = {
- .operation = AWCC_OP_ACTIVATE_PROFILE,
- .arg1 = profile,
+ struct wmax_u32_args args = {
+ .operation = operation,
+ .arg1 = arg,
.arg2 = 0,
.arg3 = 0,
};
- u32 out_data;
- int ret;
- ret = alienware_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL,
- &in_args, sizeof(in_args), &out_data);
- if (ret)
- return ret;
+ return __awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
+}
- if (out_data == AWCC_FAILURE_CODE)
- return -EBADRQC;
+static inline int awcc_thermal_control(struct wmi_device *wdev, u8 profile)
+{
+ struct wmax_u32_args args = {
+ .operation = AWCC_OP_ACTIVATE_PROFILE,
+ .arg1 = profile,
+ .arg2 = 0,
+ .arg3 = 0,
+ };
+ u32 out;
- return 0;
+ return __awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL, &args, &out);
}
-static int awcc_game_shift_status(struct wmi_device *wdev, u8 operation,
- u32 *out_data)
+static inline int awcc_game_shift_status(struct wmi_device *wdev, u8 operation,
+ u32 *out)
{
- struct wmax_u32_args in_args = {
+ struct wmax_u32_args args = {
.operation = operation,
.arg1 = 0,
.arg2 = 0,
.arg3 = 0,
};
- int ret;
- ret = alienware_wmi_command(wdev, AWCC_METHOD_GAME_SHIFT_STATUS,
- &in_args, sizeof(in_args), out_data);
- if (ret < 0)
- return ret;
+ return __awcc_wmi_command(wdev, AWCC_METHOD_GAME_SHIFT_STATUS, &args, out);
+}
- if (*out_data == AWCC_FAILURE_CODE)
- return -EOPNOTSUPP;
+/**
+ * awcc_op_get_resource_id - Get the resource ID at a given index
+ * @wdev: AWCC WMI device
+ * @index: Index
+ * @out: Value returned by the WMI call
+ *
+ * Get the resource ID at a given index. Resource IDs are listed in the
+ * following order:
+ *
+ * - Fan IDs
+ * - Sensor IDs
+ * - Unknown IDs
+ * - Thermal Profile IDs
+ *
+ * The total number of IDs of a given type can be obtained with
+ * AWCC_OP_GET_SYSTEM_DESCRIPTION.
+ *
+ * Return: 0 on success, -errno on failure
+ */
+static inline int awcc_op_get_resource_id(struct wmi_device *wdev, u8 index, u32 *out)
+{
+ struct wmax_u32_args args = {
+ .operation = AWCC_OP_GET_RESOURCE_ID,
+ .arg1 = index,
+ .arg2 = 0,
+ .arg3 = 0,
+ };
- return 0;
+ return __awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
}
+/*
+ * Thermal Profile control
+ * - Provides thermal profile control through the Platform Profile API
+ */
static int awcc_platform_profile_get(struct device *dev,
enum platform_profile_option *profile)
{
@@ -605,8 +628,7 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
first_mode = sys_desc[0] + sys_desc[1];
for (u32 i = 0; i < sys_desc[3]; i++) {
- ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_RESOURCE_ID,
- i + first_mode, &out_data);
+ ret = awcc_op_get_resource_id(priv->wdev, i + first_mode, &out_data);
if (ret == -EIO)
return ret;
--
2.48.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 04/10] platform/x86: alienware-wmi-wmax: Modify supported_thermal_profiles[]
2025-02-08 5:16 [PATCH 00/10] HWMON support + DebugFS + Improvements Kurt Borja
` (2 preceding siblings ...)
2025-02-08 5:16 ` [PATCH 03/10] platform/x86: alienware-wmi-wmax: Improve internal AWCC API Kurt Borja
@ 2025-02-08 5:16 ` Kurt Borja
2025-02-16 5:49 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 05/10] platform/x86: alienware-wmi-wmax: Improve platform profile probe Kurt Borja
` (5 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Kurt Borja @ 2025-02-08 5:16 UTC (permalink / raw)
To: Ilpo Järvinen, Armin Wolf
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Kurt Borja
Rename supported_thermal_profiles[] -> supported_profiles[] and change
it's type to u8 because it stores AWCC thermal IDs.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/platform/x86/dell/alienware-wmi-wmax.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
index 57897a0f4296..4a8335d90b5d 100644
--- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
+++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
@@ -212,7 +212,7 @@ struct wmax_u32_args {
struct awcc_priv {
struct wmi_device *wdev;
struct device *ppdev;
- enum awcc_thermal_profile supported_thermal_profiles[PLATFORM_PROFILE_LAST];
+ u8 supported_profiles[PLATFORM_PROFILE_LAST];
};
static const enum platform_profile_option awcc_mode_to_platform_profile[AWCC_PROFILE_LAST] = {
@@ -606,7 +606,7 @@ static int awcc_platform_profile_set(struct device *dev,
}
return awcc_thermal_control(priv->wdev,
- priv->supported_thermal_profiles[profile]);
+ priv->supported_profiles[profile]);
}
static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
@@ -643,7 +643,7 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, id);
profile = awcc_mode_to_platform_profile[mode];
- priv->supported_thermal_profiles[profile] = id;
+ priv->supported_profiles[profile] = id;
set_bit(profile, choices);
}
@@ -652,7 +652,7 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
return -ENODEV;
if (awcc->gmode) {
- priv->supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] =
+ priv->supported_profiles[PLATFORM_PROFILE_PERFORMANCE] =
AWCC_THERMAL_MODE_GMODE;
set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
--
2.48.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 05/10] platform/x86: alienware-wmi-wmax: Improve platform profile probe
2025-02-08 5:16 [PATCH 00/10] HWMON support + DebugFS + Improvements Kurt Borja
` (3 preceding siblings ...)
2025-02-08 5:16 ` [PATCH 04/10] platform/x86: alienware-wmi-wmax: Modify supported_thermal_profiles[] Kurt Borja
@ 2025-02-08 5:16 ` Kurt Borja
2025-02-16 5:57 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 06/10] platform/x86: alienware-wmi-wmax: Add support for the "custom" thermal profile Kurt Borja
` (4 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Kurt Borja @ 2025-02-08 5:16 UTC (permalink / raw)
To: Ilpo Järvinen, Armin Wolf
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Kurt Borja
Get and store the AWCC system description in alienware_awcc_setup()
instead of awcc_platform_profile_probe() and add a check for integer
overflows to avoid misbehaviors.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
.../platform/x86/dell/alienware-wmi-wmax.c | 56 ++++++++++++++-----
1 file changed, 43 insertions(+), 13 deletions(-)
diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
index 4a8335d90b5d..e8fe16da036a 100644
--- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
+++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
@@ -12,6 +12,7 @@
#include <linux/bits.h>
#include <linux/dmi.h>
#include <linux/moduleparam.h>
+#include <linux/overflow.h>
#include <linux/platform_profile.h>
#include <linux/wmi.h>
#include "alienware-wmi.h"
@@ -211,6 +212,17 @@ struct wmax_u32_args {
struct awcc_priv {
struct wmi_device *wdev;
+ union {
+ u32 system_description;
+ struct {
+ u8 fan_count;
+ u8 temp_count;
+ u8 unknown_count;
+ u8 profile_count;
+ } __packed;
+ u8 res_count[4];
+ } __packed;
+
struct device *ppdev;
u8 supported_profiles[PLATFORM_PROFILE_LAST];
};
@@ -614,32 +626,35 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
enum platform_profile_option profile;
struct awcc_priv *priv = drvdata;
enum awcc_thermal_profile mode;
- u8 sys_desc[4];
- u32 first_mode;
+ u8 id, offset = 0;
u32 out_data;
int ret;
- u8 id;
-
- ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_SYSTEM_DESCRIPTION,
- 0, (u32 *) &sys_desc);
- if (ret < 0)
- return ret;
-
- first_mode = sys_desc[0] + sys_desc[1];
- for (u32 i = 0; i < sys_desc[3]; i++) {
- ret = awcc_op_get_resource_id(priv->wdev, i + first_mode, &out_data);
+ /*
+ * Thermal profile IDs are listed last at offset
+ * fan_count + temp_count + unknown_count
+ */
+ for (u32 i = 0; i < ARRAY_SIZE(priv->res_count) - 1; i++)
+ offset += priv->res_count[i];
+ for (u32 i = 0; i < priv->profile_count; i++) {
+ ret = awcc_op_get_resource_id(priv->wdev, i + offset, &out_data);
if (ret == -EIO)
return ret;
+ /*
+ * Some devices report an incorrect number of thermal profiles
+ * so the resource ID list may end prematurely
+ */
if (ret == -EBADRQC)
break;
/* Some IDs have a BIT(8) flag that should be ignored */
id = FIELD_GET(AWCC_RESOURCE_ID_MASK, out_data);
- if (!is_awcc_thermal_profile_id(id))
+ if (!is_awcc_thermal_profile_id(id)) {
+ dev_dbg(&priv->wdev->dev, "Unmapped thermal profile ID 0x%02x\n", id);
continue;
+ }
mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, id);
profile = awcc_mode_to_platform_profile[mode];
@@ -680,12 +695,27 @@ static int awcc_platform_profile_init(struct wmi_device *wdev)
static int alienware_awcc_setup(struct wmi_device *wdev)
{
struct awcc_priv *priv;
+ u8 tot = 0;
int ret;
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
+ ret = awcc_thermal_information(wdev, AWCC_OP_GET_SYSTEM_DESCRIPTION,
+ 0, &priv->system_description);
+ if (ret < 0)
+ return ret;
+
+ /* Sanity check */
+ for (u32 i = 0; i < ARRAY_SIZE(priv->res_count); i++) {
+ if (check_add_overflow(tot, priv->res_count[i], &tot)) {
+ dev_err(&wdev->dev, "Malformed system description: %u\n",
+ priv->system_description);
+ return -ENXIO;
+ }
+ }
+
priv->wdev = wdev;
dev_set_drvdata(&wdev->dev, priv);
--
2.48.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 06/10] platform/x86: alienware-wmi-wmax: Add support for the "custom" thermal profile
2025-02-08 5:16 [PATCH 00/10] HWMON support + DebugFS + Improvements Kurt Borja
` (4 preceding siblings ...)
2025-02-08 5:16 ` [PATCH 05/10] platform/x86: alienware-wmi-wmax: Improve platform profile probe Kurt Borja
@ 2025-02-08 5:16 ` Kurt Borja
2025-02-16 5:59 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 07/10] platform/x86: alienware-wmi-wmax: Add HWMON support Kurt Borja
` (3 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Kurt Borja @ 2025-02-08 5:16 UTC (permalink / raw)
To: Ilpo Järvinen, Armin Wolf
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Kurt Borja
All models with the "AWCC" WMAX device support a "custom" thermal
profile. In some models this profile signals user-space that the user
wants to manually control the fans, which are always unlocked. In other
models it actually unlocks manual fan control.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
.../platform/x86/dell/alienware-wmi-wmax.c | 23 +++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
index e8fe16da036a..0d31156f43bb 100644
--- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
+++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
@@ -30,8 +30,6 @@
#define AWCC_METHOD_THERMAL_CONTROL 0x15
#define AWCC_METHOD_GAME_SHIFT_STATUS 0x25
-#define AWCC_THERMAL_MODE_GMODE 0xAB
-
#define AWCC_FAILURE_CODE 0xFFFFFFFF
#define AWCC_FAILURE_CODE_2 0xFFFFFFFE
#define AWCC_THERMAL_TABLE_MASK GENMASK(7, 4)
@@ -174,6 +172,11 @@ enum AWCC_THERMAL_TABLES {
AWCC_THERMAL_TABLE_USTT = 0xA,
};
+enum AWCC_SPECIAL_THERMAL_CODES {
+ AWCC_SPECIAL_PROFILE_CUSTOM = 0x00,
+ AWCC_SPECIAL_PROFILE_GMODE = 0xAB,
+};
+
enum awcc_thermal_profile {
AWCC_PROFILE_USTT_BALANCED,
AWCC_PROFILE_USTT_BALANCED_PERFORMANCE,
@@ -576,9 +579,15 @@ static int awcc_platform_profile_get(struct device *dev,
if (ret < 0)
return ret;
- if (out_data == AWCC_THERMAL_MODE_GMODE) {
+ switch (out_data) {
+ case AWCC_SPECIAL_PROFILE_CUSTOM:
+ *profile = PLATFORM_PROFILE_CUSTOM;
+ return 0;
+ case AWCC_SPECIAL_PROFILE_GMODE:
*profile = PLATFORM_PROFILE_PERFORMANCE;
return 0;
+ default:
+ break;
}
if (!is_awcc_thermal_profile_id(out_data))
@@ -666,9 +675,15 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
if (bitmap_empty(choices, PLATFORM_PROFILE_LAST))
return -ENODEV;
+ /* Every model supports the "custom" profile */
+ priv->supported_profiles[PLATFORM_PROFILE_CUSTOM] =
+ AWCC_SPECIAL_PROFILE_CUSTOM;
+
+ set_bit(PLATFORM_PROFILE_CUSTOM, choices);
+
if (awcc->gmode) {
priv->supported_profiles[PLATFORM_PROFILE_PERFORMANCE] =
- AWCC_THERMAL_MODE_GMODE;
+ AWCC_SPECIAL_PROFILE_GMODE;
set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
}
--
2.48.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 07/10] platform/x86: alienware-wmi-wmax: Add HWMON support
2025-02-08 5:16 [PATCH 00/10] HWMON support + DebugFS + Improvements Kurt Borja
` (5 preceding siblings ...)
2025-02-08 5:16 ` [PATCH 06/10] platform/x86: alienware-wmi-wmax: Add support for the "custom" thermal profile Kurt Borja
@ 2025-02-08 5:16 ` Kurt Borja
2025-02-16 6:06 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 08/10] platform/x86: alienware-wmi-wmax: Add support for manual fan control Kurt Borja
` (2 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Kurt Borja @ 2025-02-08 5:16 UTC (permalink / raw)
To: Ilpo Järvinen, Armin Wolf
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Kurt Borja, Guenter Roeck
All models with the "AWCC" WMAX device support monitoring fan speed and
temperature sensors. Expose this feature through the HWMON interface.
Sensor readings are cached for 1 second before refreshing them to
mitigate the performance cost of calling WMI methods.
Cc: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/platform/x86/dell/Kconfig | 1 +
.../platform/x86/dell/alienware-wmi-wmax.c | 341 ++++++++++++++++++
2 files changed, 342 insertions(+)
diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig
index f8a0dffcaab7..85a57c01aaad 100644
--- a/drivers/platform/x86/dell/Kconfig
+++ b/drivers/platform/x86/dell/Kconfig
@@ -43,6 +43,7 @@ config ALIENWARE_WMI_WMAX
bool "Alienware WMAX WMI device driver"
default y
depends on ALIENWARE_WMI
+ depends on HWMON
select ACPI_PLATFORM_PROFILE
help
Alienware WMI driver with AlienFX LED, HDMI, amplifier, deep sleep and
diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
index 0d31156f43bb..5f02da7ff25f 100644
--- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
+++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
@@ -11,9 +11,13 @@
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/dmi.h>
+#include <linux/hwmon.h>
+#include <linux/jiffies.h>
#include <linux/moduleparam.h>
+#include <linux/mutex.h>
#include <linux/overflow.h>
#include <linux/platform_profile.h>
+#include <linux/units.h>
#include <linux/wmi.h>
#include "alienware-wmi.h"
@@ -26,6 +30,7 @@
#define WMAX_METHOD_BRIGHTNESS 0x3
#define WMAX_METHOD_ZONE_CONTROL 0x4
+#define AWCC_METHOD_GET_FAN_SENSORS 0x13
#define AWCC_METHOD_THERMAL_INFORMATION 0x14
#define AWCC_METHOD_THERMAL_CONTROL 0x15
#define AWCC_METHOD_GAME_SHIFT_STATUS 0x25
@@ -36,6 +41,10 @@
#define AWCC_THERMAL_MODE_MASK GENMASK(3, 0)
#define AWCC_RESOURCE_ID_MASK GENMASK(7, 0)
+static bool force_hwmon;
+module_param_unsafe(force_hwmon, bool, 0);
+MODULE_PARM_DESC(force_hwmon, "Force probing for HWMON support without checking if the WMI backend is available");
+
static bool force_platform_profile;
module_param_unsafe(force_platform_profile, bool, 0);
MODULE_PARM_DESC(force_platform_profile, "Forces auto-detecting thermal profiles without checking if WMI thermal backend is available");
@@ -45,16 +54,19 @@ module_param_unsafe(force_gmode, bool, 0);
MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected");
struct awcc_quirks {
+ bool hwmon;
bool pprof;
bool gmode;
};
static struct awcc_quirks g_series_quirks = {
+ .hwmon = true,
.pprof = true,
.gmode = true,
};
static struct awcc_quirks generic_quirks = {
+ .hwmon = true,
.pprof = true,
.gmode = false,
};
@@ -152,9 +164,17 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = {
},
};
+enum AWCC_GET_FAN_SENSORS_OPERATIONS {
+ AWCC_OP_GET_TEMP_SENSOR_ID = 0x02,
+};
+
enum AWCC_THERMAL_INFORMATION_OPERATIONS {
AWCC_OP_GET_SYSTEM_DESCRIPTION = 0x02,
AWCC_OP_GET_RESOURCE_ID = 0x03,
+ AWCC_OP_GET_TEMPERATURE = 0x04,
+ AWCC_OP_GET_CURRENT_RPM = 0x05,
+ AWCC_OP_GET_MIN_RPM = 0x08,
+ AWCC_OP_GET_MAX_RPM = 0x09,
AWCC_OP_GET_CURRENT_PROFILE = 0x0B,
};
@@ -177,6 +197,11 @@ enum AWCC_SPECIAL_THERMAL_CODES {
AWCC_SPECIAL_PROFILE_GMODE = 0xAB,
};
+enum AWCC_TEMP_SENSOR_TYPES {
+ AWCC_TEMP_SENSOR_CPU = 0x01,
+ AWCC_TEMP_SENSOR_GPU = 0x06,
+};
+
enum awcc_thermal_profile {
AWCC_PROFILE_USTT_BALANCED,
AWCC_PROFILE_USTT_BALANCED_PERFORMANCE,
@@ -213,6 +238,23 @@ struct wmax_u32_args {
u8 arg3;
} __packed;
+struct awcc_fan_channel_data {
+ u8 id;
+ u32 state;
+ u32 min_rpm;
+ u32 max_rpm;
+ u8 temp_sensor;
+ u64 timestamp;
+ struct mutex lock; /* protects state and timestamp */
+};
+
+struct awcc_temp_channel_data {
+ u8 id;
+ u32 state;
+ u64 timestamp;
+ struct mutex lock; /* protects state and timestamp */
+};
+
struct awcc_priv {
struct wmi_device *wdev;
union {
@@ -228,6 +270,10 @@ struct awcc_priv {
struct device *ppdev;
u8 supported_profiles[PLATFORM_PROFILE_LAST];
+
+ struct device *hwdev;
+ struct awcc_temp_channel_data *temp_data;
+ struct awcc_fan_channel_data *fan_data;
};
static const enum platform_profile_option awcc_mode_to_platform_profile[AWCC_PROFILE_LAST] = {
@@ -492,6 +538,18 @@ static int __awcc_wmi_command(struct wmi_device *wdev, u32 method_id,
return 0;
}
+static inline int awcc_get_fan_sensors(struct wmi_device *wdev, u8 fan_id, u32 *out)
+{
+ struct wmax_u32_args args = {
+ .operation = AWCC_OP_GET_TEMP_SENSOR_ID,
+ .arg1 = fan_id,
+ .arg2 = 0,
+ .arg3 = 0,
+ };
+
+ return __awcc_wmi_command(wdev, AWCC_METHOD_GET_FAN_SENSORS, &args, out);
+}
+
static inline int awcc_thermal_information(struct wmi_device *wdev, u8 operation,
u8 arg, u32 *out)
{
@@ -562,6 +620,276 @@ static inline int awcc_op_get_resource_id(struct wmi_device *wdev, u8 index, u32
return __awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
}
+/*
+ * HWMON
+ * - Provides temperature and fan speed monitoring as well as manual fan
+ * control
+ */
+static int awcc_hwmon_update_temp(struct wmi_device *wdev,
+ struct awcc_temp_channel_data *data)
+{
+ u32 temp;
+ int ret;
+
+ lockdep_assert_held(data->lock);
+
+ if (time_is_after_jiffies64(data->timestamp + secs_to_jiffies(1)))
+ return 0;
+
+ ret = awcc_thermal_information(wdev, AWCC_OP_GET_TEMPERATURE, data->id,
+ &temp);
+ if (ret)
+ return ret;
+
+ data->state = temp * MILLIDEGREE_PER_DEGREE;
+ data->timestamp = get_jiffies_64();
+
+ return 0;
+}
+
+static int awcc_hwmon_update_fan(struct wmi_device *wdev,
+ struct awcc_fan_channel_data *data)
+{
+ u32 rpm;
+ int ret;
+
+ lockdep_assert_held(data->lock);
+
+ if (time_is_after_jiffies64(data->timestamp + secs_to_jiffies(1)))
+ return 0;
+
+ ret = awcc_thermal_information(wdev, AWCC_OP_GET_CURRENT_RPM, data->id,
+ &rpm);
+ if (ret)
+ return ret;
+
+ data->state = rpm;
+ data->timestamp = get_jiffies_64();
+
+ return 0;
+}
+
+static umode_t awcc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct awcc_priv *priv = drvdata;
+
+ switch (type) {
+ case hwmon_temp:
+ if (channel < priv->temp_count)
+ return 0444;
+
+ break;
+ case hwmon_fan:
+ if (channel < priv->fan_count)
+ return 0444;
+
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int awcc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+ struct awcc_temp_channel_data *temp;
+ struct awcc_fan_channel_data *fan;
+ int ret;
+
+ switch (type) {
+ case hwmon_temp:
+ temp = &priv->temp_data[channel];
+
+ switch (attr) {
+ case hwmon_temp_input:
+ mutex_lock(&temp->lock);
+ ret = awcc_hwmon_update_temp(priv->wdev, temp);
+ mutex_unlock(&temp->lock);
+ if (ret)
+ return ret;
+
+ *val = temp->state;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ break;
+ case hwmon_fan:
+ fan = &priv->fan_data[channel];
+
+ switch (attr) {
+ case hwmon_fan_input:
+ mutex_lock(&fan->lock);
+ ret = awcc_hwmon_update_fan(priv->wdev, fan);
+ mutex_unlock(&fan->lock);
+ if (ret)
+ return ret;
+
+ *val = fan->state;
+ break;
+ case hwmon_fan_min:
+ *val = fan->min_rpm;
+ break;
+ case hwmon_fan_max:
+ *val = fan->max_rpm;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+ u8 temp_id;
+
+ switch (type) {
+ case hwmon_temp:
+ temp_id = priv->temp_data[channel].id;
+
+ switch (temp_id) {
+ case AWCC_TEMP_SENSOR_CPU:
+ *str = "CPU";
+ break;
+ case AWCC_TEMP_SENSOR_GPU:
+ *str = "GPU";
+ break;
+ default:
+ *str = "Unknown";
+ break;
+ }
+
+ break;
+ case hwmon_fan:
+ temp_id = priv->fan_data[channel].temp_sensor;
+
+ switch (temp_id) {
+ case AWCC_TEMP_SENSOR_CPU:
+ *str = "Processor Fan";
+ break;
+ case AWCC_TEMP_SENSOR_GPU:
+ *str = "Video Fan";
+ break;
+ default:
+ *str = "Unknown Fan";
+ break;
+ }
+
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_ops awcc_hwmon_ops = {
+ .is_visible = awcc_hwmon_is_visible,
+ .read = awcc_hwmon_read,
+ .read_string = awcc_hwmon_read_string,
+};
+
+static const struct hwmon_channel_info * const awcc_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_LABEL | HWMON_T_INPUT,
+ HWMON_T_LABEL | HWMON_T_INPUT,
+ HWMON_T_LABEL | HWMON_T_INPUT
+ ),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX
+ ),
+ NULL
+};
+
+static const struct hwmon_chip_info awcc_hwmon_chip_info = {
+ .ops = &awcc_hwmon_ops,
+ .info = awcc_hwmon_info,
+};
+
+static int awcc_hwmon_init(struct wmi_device *wdev)
+{
+ struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
+ u32 id, temp_sensor, min_rpm, max_rpm;
+ int ret;
+
+ priv->fan_data = devm_kcalloc(&wdev->dev, priv->fan_count,
+ sizeof(*priv->fan_data), GFP_KERNEL);
+ if (!priv->fan_data)
+ return -ENOMEM;
+
+ priv->temp_data = devm_kcalloc(&wdev->dev, priv->temp_count,
+ sizeof(*priv->temp_data), GFP_KERNEL);
+ if (!priv->temp_data)
+ return -ENOMEM;
+
+ for (u32 i = 0; i < priv->fan_count; i++) {
+ /*
+ * Fan IDs are listed first at offset 0
+ */
+ ret = awcc_op_get_resource_id(wdev, i, &id);
+ if (ret)
+ return ret;
+
+ ret = awcc_thermal_information(wdev, AWCC_OP_GET_MIN_RPM, id,
+ &min_rpm);
+ if (ret)
+ return ret;
+
+ ret = awcc_thermal_information(wdev, AWCC_OP_GET_MAX_RPM, id,
+ &max_rpm);
+ if (ret)
+ return ret;
+
+ ret = awcc_get_fan_sensors(wdev, id, &temp_sensor);
+ if (ret)
+ return ret;
+
+ priv->fan_data[i].id = FIELD_GET(AWCC_RESOURCE_ID_MASK, id);
+ priv->fan_data[i].min_rpm = min_rpm;
+ priv->fan_data[i].max_rpm = max_rpm;
+ priv->fan_data[i].temp_sensor = temp_sensor;
+ ret = devm_mutex_init(&wdev->dev, &priv->fan_data[i].lock);
+ if (ret)
+ return ret;
+ }
+
+ for (u32 i = 0; i < priv->temp_count; i++) {
+ /*
+ * Temperature sensors IDs are listed after the fan IDs at
+ * offset `fan_count`
+ */
+ ret = awcc_op_get_resource_id(wdev, i + priv->fan_count, &id);
+ if (ret)
+ return ret;
+
+ priv->temp_data[i].id = FIELD_GET(AWCC_RESOURCE_ID_MASK, id);
+ ret = devm_mutex_init(&wdev->dev, &priv->temp_data[i].lock);
+ if (ret)
+ return ret;
+ }
+
+ priv->hwdev = devm_hwmon_device_register_with_info(
+ &wdev->dev, "alienware_wmi", priv, &awcc_hwmon_chip_info, NULL);
+
+ return PTR_ERR_OR_ZERO(priv->hwdev);
+}
+
/*
* Thermal Profile control
* - Provides thermal profile control through the Platform Profile API
@@ -734,6 +1062,12 @@ static int alienware_awcc_setup(struct wmi_device *wdev)
priv->wdev = wdev;
dev_set_drvdata(&wdev->dev, priv);
+ if (awcc->hwmon) {
+ ret = awcc_hwmon_init(wdev);
+ if (ret)
+ return ret;
+ }
+
if (awcc->pprof) {
ret = awcc_platform_profile_init(wdev);
if (ret)
@@ -814,6 +1148,13 @@ int __init alienware_wmax_wmi_init(void)
if (id)
awcc = id->driver_data;
+ if (force_hwmon) {
+ if (!awcc)
+ awcc = &empty_quirks;
+
+ awcc->hwmon = true;
+ }
+
if (force_platform_profile) {
if (!awcc)
awcc = &empty_quirks;
--
2.48.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 08/10] platform/x86: alienware-wmi-wmax: Add support for manual fan control
2025-02-08 5:16 [PATCH 00/10] HWMON support + DebugFS + Improvements Kurt Borja
` (6 preceding siblings ...)
2025-02-08 5:16 ` [PATCH 07/10] platform/x86: alienware-wmi-wmax: Add HWMON support Kurt Borja
@ 2025-02-08 5:16 ` Kurt Borja
2025-02-16 6:12 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 09/10] platform/x86: alienware-wmi-wmax: Add a DebugFS interface Kurt Borja
2025-02-08 5:16 ` [PATCH 10/10] platform/x86: alienware-wmi: Improve and update documentation Kurt Borja
9 siblings, 1 reply; 25+ messages in thread
From: Kurt Borja @ 2025-02-08 5:16 UTC (permalink / raw)
To: Ilpo Järvinen, Armin Wolf
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Kurt Borja, Guenter Roeck
All models with the "AWCC" WMAX device support a way of manually
controlling fans.
The PWM duty cycle of a fan can't be controlled directly. Instead the
AWCC interface let's us tune a PWM `boost` value, which has the
following empirically discovered behavior over the PWM value:
pwm = pwm_base + (pwm_boost / 255) * (pwm_max - pwm_base)
Where the pwm_base is the locked PWM value controlled by the EC and
pwm_boost is a value between 0 and 255.
This pwm_boost knob is exposed as a standard `pwm` attribute.
Cc: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
.../platform/x86/dell/alienware-wmi-wmax.c | 55 +++++++++++++++++--
1 file changed, 49 insertions(+), 6 deletions(-)
diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
index 5f02da7ff25f..06d6f88ea54b 100644
--- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
+++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
@@ -13,6 +13,7 @@
#include <linux/dmi.h>
#include <linux/hwmon.h>
#include <linux/jiffies.h>
+#include <linux/minmax.h>
#include <linux/moduleparam.h>
#include <linux/mutex.h>
#include <linux/overflow.h>
@@ -176,10 +177,12 @@ enum AWCC_THERMAL_INFORMATION_OPERATIONS {
AWCC_OP_GET_MIN_RPM = 0x08,
AWCC_OP_GET_MAX_RPM = 0x09,
AWCC_OP_GET_CURRENT_PROFILE = 0x0B,
+ AWCC_OP_GET_FAN_BOOST = 0x0C,
};
enum AWCC_THERMAL_CONTROL_OPERATIONS {
AWCC_OP_ACTIVATE_PROFILE = 0x01,
+ AWCC_OP_SET_FAN_BOOST = 0x02,
};
enum AWCC_GAME_SHIFT_STATUS_OPERATIONS {
@@ -563,12 +566,13 @@ static inline int awcc_thermal_information(struct wmi_device *wdev, u8 operation
return __awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
}
-static inline int awcc_thermal_control(struct wmi_device *wdev, u8 profile)
+static inline int awcc_thermal_control(struct wmi_device *wdev, u8 operation,
+ u8 arg1, u8 arg2)
{
struct wmax_u32_args args = {
- .operation = AWCC_OP_ACTIVATE_PROFILE,
- .arg1 = profile,
- .arg2 = 0,
+ .operation = operation,
+ .arg1 = arg1,
+ .arg2 = arg2,
.arg3 = 0,
};
u32 out;
@@ -684,6 +688,11 @@ static umode_t awcc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_type
if (channel < priv->fan_count)
return 0444;
+ break;
+ case hwmon_pwm:
+ if (channel < priv->fan_count)
+ return 0644;
+
break;
default:
break;
@@ -698,6 +707,7 @@ static int awcc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
struct awcc_priv *priv = dev_get_drvdata(dev);
struct awcc_temp_channel_data *temp;
struct awcc_fan_channel_data *fan;
+ u32 fan_boost;
int ret;
switch (type) {
@@ -742,6 +752,16 @@ static int awcc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
return -EOPNOTSUPP;
}
+ break;
+ case hwmon_pwm:
+ fan = &priv->fan_data[channel];
+
+ ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_FAN_BOOST,
+ fan->id, &fan_boost);
+ if (ret)
+ return ret;
+
+ *val = fan_boost;
break;
default:
return -EOPNOTSUPP;
@@ -796,10 +816,27 @@ static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types ty
return 0;
}
+
+static int awcc_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+ u8 fan_id = priv->fan_data[channel].id;
+
+ switch (type) {
+ case hwmon_pwm:
+ return awcc_thermal_control(priv->wdev, AWCC_OP_SET_FAN_BOOST,
+ fan_id, (u8)clamp_val(val, 0, 255));
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
static const struct hwmon_ops awcc_hwmon_ops = {
.is_visible = awcc_hwmon_is_visible,
.read = awcc_hwmon_read,
.read_string = awcc_hwmon_read_string,
+ .write = awcc_hwmon_write,
};
static const struct hwmon_channel_info * const awcc_hwmon_info[] = {
@@ -814,6 +851,12 @@ static const struct hwmon_channel_info * const awcc_hwmon_info[] = {
HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX
),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT
+ ),
NULL
};
@@ -954,8 +997,8 @@ static int awcc_platform_profile_set(struct device *dev,
}
}
- return awcc_thermal_control(priv->wdev,
- priv->supported_profiles[profile]);
+ return awcc_thermal_control(priv->wdev, AWCC_OP_ACTIVATE_PROFILE,
+ priv->supported_profiles[profile], 0);
}
static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
--
2.48.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 09/10] platform/x86: alienware-wmi-wmax: Add a DebugFS interface
2025-02-08 5:16 [PATCH 00/10] HWMON support + DebugFS + Improvements Kurt Borja
` (7 preceding siblings ...)
2025-02-08 5:16 ` [PATCH 08/10] platform/x86: alienware-wmi-wmax: Add support for manual fan control Kurt Borja
@ 2025-02-08 5:16 ` Kurt Borja
2025-02-08 5:16 ` [PATCH 10/10] platform/x86: alienware-wmi: Improve and update documentation Kurt Borja
9 siblings, 0 replies; 25+ messages in thread
From: Kurt Borja @ 2025-02-08 5:16 UTC (permalink / raw)
To: Ilpo Järvinen, Armin Wolf
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Kurt Borja
Add a debugfs interface which exposes thermal private data.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
.../platform/x86/dell/alienware-wmi-wmax.c | 92 +++++++++++++++++++
1 file changed, 92 insertions(+)
diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
index 06d6f88ea54b..f20bd9a062a7 100644
--- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
+++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
@@ -10,6 +10,7 @@
#include <linux/bitfield.h>
#include <linux/bits.h>
+#include <linux/debugfs.h>
#include <linux/dmi.h>
#include <linux/hwmon.h>
#include <linux/jiffies.h>
@@ -18,6 +19,7 @@
#include <linux/mutex.h>
#include <linux/overflow.h>
#include <linux/platform_profile.h>
+#include <linux/seq_file.h>
#include <linux/units.h>
#include <linux/wmi.h>
#include "alienware-wmi.h"
@@ -1078,6 +1080,94 @@ static int awcc_platform_profile_init(struct wmi_device *wdev)
return PTR_ERR_OR_ZERO(priv->ppdev);
}
+/*
+ * DebugFS
+ */
+static int awcc_debugfs_system_description_read(struct seq_file *seq, void *data)
+{
+ struct device *dev = seq->private;
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+
+ seq_printf(seq, "0x%08x\n", priv->system_description);
+
+ return 0;
+}
+
+static int awcc_debugfs_hwmon_data_read(struct seq_file *seq, void *data)
+{
+ struct device *dev = seq->private;
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+ struct awcc_fan_channel_data *fan_data;
+ struct awcc_temp_channel_data *temp_data;
+
+ seq_printf(seq, "Number of fans: %u\n", priv->fan_count);
+ seq_printf(seq, "Number of temperature sensors: %u\n\n", priv->temp_count);
+
+ for (u32 i = 0; i < priv->fan_count; i++) {
+ fan_data = &priv->fan_data[i];
+
+ seq_printf(seq, "Fan %u:\n", i);
+ seq_printf(seq, " ID: 0x%02x\n", fan_data->id);
+ seq_printf(seq, " Temperature sensor: 0x%02x\n", fan_data->temp_sensor);
+ }
+
+ seq_puts(seq, "\n");
+
+ for (u32 i = 0; i < priv->temp_count; i++) {
+ temp_data = &priv->temp_data[i];
+
+ seq_printf(seq, "Temperature sensor %u:\n", i);
+ seq_printf(seq, " ID: 0x%02x\n", temp_data->id);
+ }
+
+ return 0;
+}
+
+static int awcc_debugfs_pprof_data_read(struct seq_file *seq, void *data)
+{
+ struct device *dev = seq->private;
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+
+ seq_printf(seq, "Number of thermal profiles: %u\n\n", priv->profile_count);
+
+ for (u32 i = 0; i < PLATFORM_PROFILE_LAST; i++) {
+ if (!priv->supported_profiles[i])
+ continue;
+
+ seq_printf(seq, "Platform profile %u:\n", i);
+ seq_printf(seq, " ID: 0x%02x\n", priv->supported_profiles[i]);
+ }
+
+ return 0;
+}
+
+static void awcc_debugfs_remove(void *data)
+{
+ struct dentry *root = data;
+
+ debugfs_remove(root);
+}
+
+static void awcc_debugfs_init(struct wmi_device *wdev)
+{
+ struct dentry *root;
+
+ root = debugfs_create_dir("alienware-wmi", NULL);
+
+ debugfs_create_devm_seqfile(&wdev->dev, "system_description", root,
+ awcc_debugfs_system_description_read);
+
+ if (awcc->hwmon)
+ debugfs_create_devm_seqfile(&wdev->dev, "hwmon_data", root,
+ awcc_debugfs_hwmon_data_read);
+
+ if (awcc->pprof)
+ debugfs_create_devm_seqfile(&wdev->dev, "pprof_data", root,
+ awcc_debugfs_pprof_data_read);
+
+ devm_add_action_or_reset(&wdev->dev, awcc_debugfs_remove, root);
+}
+
static int alienware_awcc_setup(struct wmi_device *wdev)
{
struct awcc_priv *priv;
@@ -1117,6 +1207,8 @@ static int alienware_awcc_setup(struct wmi_device *wdev)
return ret;
}
+ awcc_debugfs_init(wdev);
+
return 0;
}
--
2.48.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 10/10] platform/x86: alienware-wmi: Improve and update documentation
2025-02-08 5:16 [PATCH 00/10] HWMON support + DebugFS + Improvements Kurt Borja
` (8 preceding siblings ...)
2025-02-08 5:16 ` [PATCH 09/10] platform/x86: alienware-wmi-wmax: Add a DebugFS interface Kurt Borja
@ 2025-02-08 5:16 ` Kurt Borja
2025-02-16 6:22 ` Armin Wolf
9 siblings, 1 reply; 25+ messages in thread
From: Kurt Borja @ 2025-02-08 5:16 UTC (permalink / raw)
To: Ilpo Järvinen, Armin Wolf
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Kurt Borja
Use tables to describe method operations instead of using pseudo-code.
Drop unknown method descriptions to avoid redundancy. Drop GPIO section
as it is currently irrelevant to this driver. Update Thermal_Information
method documentation. Add one more helpful developer to the kudos section.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
Documentation/wmi/devices/alienware-wmi.rst | 390 ++++++--------------
1 file changed, 122 insertions(+), 268 deletions(-)
diff --git a/Documentation/wmi/devices/alienware-wmi.rst b/Documentation/wmi/devices/alienware-wmi.rst
index ddc5e561960e..2bb3abfd9cd7 100644
--- a/Documentation/wmi/devices/alienware-wmi.rst
+++ b/Documentation/wmi/devices/alienware-wmi.rst
@@ -11,7 +11,7 @@ The WMI device WMAX has been implemented for many Alienware and Dell's G-Series
models. Throughout these models, two implementations have been identified. The
first one, used by older systems, deals with HDMI, brightness, RGB, amplifier
and deep sleep control. The second one used by newer systems deals primarily
-with thermal, overclocking, and GPIO control.
+with thermal control and overclocking.
It is suspected that the latter is used by Alienware Command Center (AWCC) to
manage manufacturer predefined thermal profiles. The alienware-wmi driver
@@ -69,9 +69,6 @@ data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
[WmiMethodId(164), Implemented, read, write, Description("Tobii Camera Power Off.")] void TobiiCameraPowerOff([out] uint32 argr);
};
-Some of these methods get quite intricate so we will describe them using
-pseudo-code that vaguely resembles the original ASL code.
-
Methods not described in the following document have unknown behavior.
Argument Structure
@@ -87,175 +84,136 @@ ID 0xA0, the argument you would pass to the method is 0xA001.
Thermal Methods
===============
-WMI method Thermal_Information([in] uint32 arg2, [out] uint32 argr)
--------------------------------------------------------------------
-
-::
-
- if BYTE_0(arg2) == 0x01:
- argr = 1
-
- if BYTE_0(arg2) == 0x02:
- argr = SYSTEM_DESCRIPTION
-
- if BYTE_0(arg2) == 0x03:
- if BYTE_1(arg2) == 0x00:
- argr = FAN_ID_0
-
- if BYTE_1(arg2) == 0x01:
- argr = FAN_ID_1
-
- if BYTE_1(arg2) == 0x02:
- argr = FAN_ID_2
-
- if BYTE_1(arg2) == 0x03:
- argr = FAN_ID_3
-
- if BYTE_1(arg2) == 0x04:
- argr = SENSOR_ID_CPU | 0x0100
-
- if BYTE_1(arg2) == 0x05:
- argr = SENSOR_ID_GPU | 0x0100
-
- if BYTE_1(arg2) == 0x06:
- argr = THERMAL_MODE_QUIET_ID
-
- if BYTE_1(arg2) == 0x07:
- argr = THERMAL_MODE_BALANCED_ID
-
- if BYTE_1(arg2) == 0x08:
- argr = THERMAL_MODE_BALANCED_PERFORMANCE_ID
-
- if BYTE_1(arg2) == 0x09:
- argr = THERMAL_MODE_PERFORMANCE_ID
-
- if BYTE_1(arg2) == 0x0A:
- argr = THERMAL_MODE_LOW_POWER_ID
-
- if BYTE_1(arg2) == 0x0B:
- argr = THERMAL_MODE_GMODE_ID
-
- else:
- argr = 0xFFFFFFFF
-
- if BYTE_0(arg2) == 0x04:
- if is_valid_sensor(BYTE_1(arg2)):
- argr = SENSOR_TEMP_C
- else:
- argr = 0xFFFFFFFF
-
- if BYTE_0(arg2) == 0x05:
- if is_valid_fan(BYTE_1(arg2)):
- argr = FAN_RPM()
-
- if BYTE_0(arg2) == 0x06:
- skip
-
- if BYTE_0(arg2) == 0x07:
- argr = 0
-
- If BYTE_0(arg2) == 0x08:
- if is_valid_fan(BYTE_1(arg2)):
- argr = 0
- else:
- argr = 0xFFFFFFFF
-
- if BYTE_0(arg2) == 0x09:
- if is_valid_fan(BYTE_1(arg2)):
- argr = FAN_UNKNOWN_STAT_0()
-
- else:
- argr = 0xFFFFFFFF
-
- if BYTE_0(arg2) == 0x0A:
- argr = THERMAL_MODE_BALANCED_ID
-
- if BYTE_0(arg2) == 0x0B:
- argr = CURRENT_THERMAL_MODE()
-
- if BYTE_0(arg2) == 0x0C:
- if is_valid_fan(BYTE_1(arg2)):
- argr = FAN_UNKNOWN_STAT_1()
- else:
- argr = 0xFFFFFFFF
-
-Operation 0x02 returns a *system description* buffer with the following
-structure:
-
-::
-
- out[0] -> Number of fans
- out[1] -> Number of sensors
- out[2] -> 0x00
- out[3] -> Number of thermal modes
+WMI method GetFanSensors([in] uint32 arg2, [out] uint32 argr)
+-------------------------------------------------------------
-Operation 0x03 list all available fan IDs, sensor IDs and thermal profile
-codes in order, but different models may have different number of fans and
-thermal profiles. These are the known ranges:
++--------------------+------------------------------------+--------------------+
+| Operation (Byte 0) | Description | Arguments |
++====================+====================================+====================+
+| 0x01 | Get the number of fans for a given | - Byte 1: Fan ID |
+| | fan ID. | |
++--------------------+------------------------------------+--------------------+
+| 0x02 | Get the temperature sensor ID | - Byte 1: Fan ID |
+| | related to a fan sensor ID | |
++--------------------+------------------------------------+--------------------+
-* Fan IDs: from 2 up to 4
-* Sensor IDs: 2
-* Thermal profile codes: from 1 up to 7
+WMI method Thermal_Information([in] uint32 arg2, [out] uint32 argr)
+-------------------------------------------------------------------
-In total BYTE_1(ARG2) may range from 0x5 up to 0xD depending on the model.
++--------------------+------------------------------------+--------------------+
+| Operation (Byte 0) | Description | Arguments |
++====================+====================================+====================+
+| 0x01 | Unknown. | - None |
++--------------------+------------------------------------+--------------------+
+| 0x02 | Get system description number with | - None |
+| | the following structure: | |
+| | | |
+| | - Byte 0: Number of fans | |
+| | - Byte 1: Number of temperature | |
+| | sensors | |
+| | - Byte 2: Unknown | |
+| | - Byte 3: Number of thermal | |
+| | profiles | |
++--------------------+------------------------------------+--------------------+
+| 0x03 | List an ID or resource at a given | - Byte 1: Index |
+| | index. Fan IDs, temperature IDs, | |
+| | unknown IDs and thermal profile | |
+| | IDs are listed in that exact | |
+| | order. | |
+| | | |
+| | Operation 0x02 is used to know | |
+| | which indexes map to which | |
+| | resources. | |
+| | | |
+| | **Returns:** ID at a given index | |
++--------------------+------------------------------------+--------------------+
+| 0x04 | Get the current temperature for a | - Byte 1: Sensor |
+| | given temperature sensor. | ID |
++--------------------+------------------------------------+--------------------+
+| 0x05 | Get the current RPM for a given | - Byte 1: Fan ID |
+| | fan. | |
++--------------------+------------------------------------+--------------------+
+| 0x06 | Get fan speed percentage. (not | - Byte 1: Fan ID |
+| | implemented in every model) | |
++--------------------+------------------------------------+--------------------+
+| 0x07 | Unknown. | - Unknown |
++--------------------+------------------------------------+--------------------+
+| 0x08 | Get minimum RPM for a given FAN | - Byte 1: Fan ID |
+| | ID. | |
++--------------------+------------------------------------+--------------------+
+| 0x09 | Get maximum RPM for a given FAN | - Byte 1: Fan ID |
+| | ID. | |
++--------------------+------------------------------------+--------------------+
+| 0x09 | Get maximum RPM for a given FAN | - Byte 1: Fan ID |
+| | ID. | |
++--------------------+------------------------------------+--------------------+
+| 0x0A | Get balanced thermal profile ID. | - None |
++--------------------+------------------------------------+--------------------+
+| 0x0B | Get current thermal profile ID. | - None |
++--------------------+------------------------------------+--------------------+
+| 0x0C | Get current `boost` value for a | - Byte 1: Fan ID |
+| | given fan ID. | |
++--------------------+------------------------------------+--------------------+
WMI method Thermal_Control([in] uint32 arg2, [out] uint32 argr)
---------------------------------------------------------------
-::
-
- if BYTE_0(arg2) == 0x01:
- if is_valid_thermal_profile(BYTE_1(arg2)):
- SET_THERMAL_PROFILE(BYTE_1(arg2))
- argr = 0
-
- if BYTE_0(arg2) == 0x02:
- if is_valid_fan(BYTE_1(arg2)):
- SET_FAN_SPEED_MULTIPLIER(BYTE_2(arg2))
- argr = 0
- else:
- argr = 0xFFFFFFFF
-
-.. note::
- While you can manually change the fan speed multiplier with this method,
- Dell's BIOS tends to overwrite this changes anyway.
++--------------------+------------------------------------+--------------------+
+| Operation (Byte 0) | Description | Arguments |
++====================+====================================+====================+
+| 0x01 | Activate a given thermal profile. | - Byte 1: Thermal |
+| | | profile ID |
++--------------------+------------------------------------+--------------------+
+| 0x02 | Set a `boost` value for a given | - Byte 1: Fan ID |
+| | fan ID. | - Byte 2: Boost |
++--------------------+------------------------------------+--------------------+
These are the known thermal profile codes:
-::
-
- CUSTOM 0x00
-
- BALANCED_USTT 0xA0
- BALANCED_PERFORMANCE_USTT 0xA1
- COOL_USTT 0xA2
- QUIET_USTT 0xA3
- PERFORMANCE_USTT 0xA4
- LOW_POWER_USTT 0xA5
-
- QUIET 0x96
- BALANCED 0x97
- BALANCED_PERFORMANCE 0x98
- PERFORMANCE 0x99
-
- GMODE 0xAB
-
-Usually if a model doesn't support the first four profiles they will support
-the User Selectable Thermal Tables (USTT) profiles and vice-versa.
-
-GMODE replaces PERFORMANCE in G-Series laptops.
++------------------------------+----------+------+
+| Thermal Profile | Type | ID |
++==============================+==========+======+
+| Custom | Special | 0x00 |
++------------------------------+----------+------+
+| G-Mode | Special | 0xAB |
++------------------------------+----------+------+
+| Quiet | Legacy | 0x96 |
++------------------------------+----------+------+
+| Balanced | Legacy | 0x97 |
++------------------------------+----------+------+
+| Balanced Performance | Legacy | 0x98 |
++------------------------------+----------+------+
+| Performance | Legacy | 0x99 |
++------------------------------+----------+------+
+| Balanced | USTT | 0xA0 |
++------------------------------+----------+------+
+| Balanced Performance | USTT | 0xA1 |
++------------------------------+----------+------+
+| Cool | USTT | 0xA2 |
++------------------------------+----------+------+
+| Quiet | USTT | 0xA3 |
++------------------------------+----------+------+
+| Performance | USTT | 0xA4 |
++------------------------------+----------+------+
+| Low Power | USTT | 0xA5 |
++------------------------------+----------+------+
+
+If a model supports the User Selectable Thermal Tables (USTT) profiles, it will
+not support the Legacy profiles and vice-versa.
+
+Every model supports the CUSTOM (0x00) thermal profile. GMODE replaces
+PERFORMANCE in G-Series laptops.
WMI method GameShiftStatus([in] uint32 arg2, [out] uint32 argr)
---------------------------------------------------------------
-::
-
- if BYTE_0(arg2) == 0x1:
- TOGGLE_GAME_SHIFT()
- argr = GET_GAME_SHIFT_STATUS()
-
- if BYTE_0(arg2) == 0x2:
- argr = GET_GAME_SHIFT_STATUS()
++--------------------+------------------------------------+--------------------+
+| Operation (Byte 0) | Description | Arguments |
++====================+====================================+====================+
+| 0x01 | Toggle *Game Shift*. | - None |
++--------------------+------------------------------------+--------------------+
+| 0x02 | Get *Game Shift* status. | - None |
++--------------------+------------------------------------+--------------------+
Game Shift Status does not change the fan speed profile but it could be some
sort of CPU/GPU power profile. Benchmarks have not been done.
@@ -267,131 +225,27 @@ Thermal_Information does not list it.
G-key on Dell's G-Series laptops also changes Game Shift status, so both are
directly related.
-WMI method GetFanSensors([in] uint32 arg2, [out] uint32 argr)
--------------------------------------------------------------
-
-::
-
- if BYTE_0(arg2) == 0x1:
- if is_valid_fan(BYTE_1(arg2)):
- argr = 1
- else:
- argr = 0
-
- if BYTE_0(arg2) == 0x2:
- if is_valid_fan(BYTE_1(arg2)):
- if BYTE_2(arg2) == 0:
- argr == SENSOR_ID
- else
- argr == 0xFFFFFFFF
- else:
- argr = 0
-
Overclocking Methods
====================
-.. warning::
- These methods have not been tested and are only partially reverse
- engineered.
-
-WMI method Return_OverclockingReport([out] uint32 argr)
--------------------------------------------------------
-
-::
-
- CSMI (0xE3, 0x99)
- argr = 0
-
-CSMI is an unknown operation.
-
-WMI method Set_OCUIBIOSControl([in] uint32 arg2, [out] uint32 argr)
--------------------------------------------------------------------
-
-::
-
- CSMI (0xE3, 0x99)
- argr = 0
-
-CSMI is an unknown operation.
-
-WMI method Clear_OCFailSafeFlag([out] uint32 argr)
---------------------------------------------------
-
-::
-
- CSMI (0xE3, 0x99)
- argr = 0
-
-CSMI is an unknown operation.
-
-
WMI method MemoryOCControl([in] uint32 arg2, [out] uint32 argr)
---------------------------------------------------------------
AWCC supports memory overclocking, but this method is very intricate and has
not been deciphered yet.
-GPIO methods
-============
-
-These methods are probably related to some kind of firmware update system,
-through a GPIO device.
-
-.. warning::
- These methods have not been tested and are only partially reverse
- engineered.
-
-WMI method FWUpdateGPIOtoggle([in] uint32 arg2, [out] uint32 argr)
-------------------------------------------------------------------
-
-::
-
- if BYTE_0(arg2) == 0:
- if BYTE_1(arg2) == 1:
- SET_PIN_A_HIGH()
- else:
- SET_PIN_A_LOW()
-
- if BYTE_0(arg2) == 1:
- if BYTE_1(arg2) == 1:
- SET_PIN_B_HIGH()
-
- else:
- SET_PIN_B_LOW()
-
- else:
- argr = 1
-
-WMI method ReadTotalofGPIOs([out] uint32 argr)
-----------------------------------------------
-
-::
-
- argr = 0x02
-
-WMI method ReadGPIOpPinStatus([in] uint32 arg2, [out] uint32 argr)
-------------------------------------------------------------------
-
-::
-
- if BYTE_0(arg2) == 0:
- argr = PIN_A_STATUS
-
- if BYTE_0(arg2) == 1:
- argr = PIN_B_STATUS
-
Other information Methods
=========================
WMI method ReadChassisColor([out] uint32 argr)
----------------------------------------------
-::
-
- argr = CHASSIS_COLOR_ID
+Returns the chassis color internal ID.
Acknowledgements
================
-Kudos to `AlexIII <https://github.com/AlexIII/tcc-g15>`_ for documenting
-and testing available thermal profile codes.
+Kudos to `AlexIII <https://github.com/AlexIII/tcc-g15>`_ and
+`T-Troll <https://github.com/T-Troll/alienfx-tools/>`_ for documenting and
+testing some of this device's functionality, making it possible to generalize
+this driver.
--
2.48.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [PATCH 01/10] platform/x86: alienware-wmi-wmax: Rename thermal related symbols
2025-02-08 5:16 ` [PATCH 01/10] platform/x86: alienware-wmi-wmax: Rename thermal related symbols Kurt Borja
@ 2025-02-16 5:40 ` Armin Wolf
0 siblings, 0 replies; 25+ messages in thread
From: Armin Wolf @ 2025-02-16 5:40 UTC (permalink / raw)
To: Kurt Borja, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel
Am 08.02.25 um 06:16 schrieb Kurt Borja:
> The "thermal" features of the WMAX WMI device are only present on the
> host device if the ACPI _UID is "AWCC". Replace WMAX prefixes with
> "AWCC" to reflect this relationship.
>
> Thermal profiles with WMAX_PROFILE_BASIC prefix are also renamed to
> WMAX_PROFILE_LEGACY because they are only supported in older versions
> of this WMI device.
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> .../platform/x86/dell/alienware-wmi-wmax.c | 173 +++++++++---------
> 1 file changed, 87 insertions(+), 86 deletions(-)
>
> diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> index 3d3014b5adf0..ed70e12d73d7 100644
> --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
> +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> @@ -24,16 +24,17 @@
> #define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C
> #define WMAX_METHOD_BRIGHTNESS 0x3
> #define WMAX_METHOD_ZONE_CONTROL 0x4
> -#define WMAX_METHOD_THERMAL_INFORMATION 0x14
> -#define WMAX_METHOD_THERMAL_CONTROL 0x15
> -#define WMAX_METHOD_GAME_SHIFT_STATUS 0x25
>
> -#define WMAX_THERMAL_MODE_GMODE 0xAB
> +#define AWCC_METHOD_THERMAL_INFORMATION 0x14
> +#define AWCC_METHOD_THERMAL_CONTROL 0x15
> +#define AWCC_METHOD_GAME_SHIFT_STATUS 0x25
>
> -#define WMAX_FAILURE_CODE 0xFFFFFFFF
> -#define WMAX_THERMAL_TABLE_MASK GENMASK(7, 4)
> -#define WMAX_THERMAL_MODE_MASK GENMASK(3, 0)
> -#define WMAX_SENSOR_ID_MASK BIT(8)
> +#define AWCC_THERMAL_MODE_GMODE 0xAB
> +
> +#define AWCC_FAILURE_CODE 0xFFFFFFFF
> +#define AWCC_THERMAL_TABLE_MASK GENMASK(7, 4)
> +#define AWCC_THERMAL_MODE_MASK GENMASK(3, 0)
> +#define AWCC_SENSOR_ID_MASK BIT(8)
>
> static bool force_platform_profile;
> module_param_unsafe(force_platform_profile, bool, 0);
> @@ -151,38 +152,38 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = {
> },
> };
>
> -enum WMAX_THERMAL_INFORMATION_OPERATIONS {
> - WMAX_OPERATION_SYS_DESCRIPTION = 0x02,
> - WMAX_OPERATION_LIST_IDS = 0x03,
> - WMAX_OPERATION_CURRENT_PROFILE = 0x0B,
> +enum AWCC_THERMAL_INFORMATION_OPERATIONS {
> + AWCC_OP_GET_SYSTEM_DESCRIPTION = 0x02,
> + AWCC_OP_GET_RESOURCE_ID = 0x03,
> + AWCC_OP_GET_CURRENT_PROFILE = 0x0B,
> };
>
> -enum WMAX_THERMAL_CONTROL_OPERATIONS {
> - WMAX_OPERATION_ACTIVATE_PROFILE = 0x01,
> +enum AWCC_THERMAL_CONTROL_OPERATIONS {
> + AWCC_OP_ACTIVATE_PROFILE = 0x01,
> };
>
> -enum WMAX_GAME_SHIFT_STATUS_OPERATIONS {
> - WMAX_OPERATION_TOGGLE_GAME_SHIFT = 0x01,
> - WMAX_OPERATION_GET_GAME_SHIFT_STATUS = 0x02,
> +enum AWCC_GAME_SHIFT_STATUS_OPERATIONS {
> + AWCC_OP_TOGGLE_GAME_SHIFT = 0x01,
> + AWCC_OP_GET_GAME_SHIFT_STATUS = 0x02,
> };
>
> -enum WMAX_THERMAL_TABLES {
> - WMAX_THERMAL_TABLE_BASIC = 0x90,
> - WMAX_THERMAL_TABLE_USTT = 0xA0,
> +enum AWCC_THERMAL_TABLES {
> + AWCC_THERMAL_TABLE_LEGACY = 0x90,
> + AWCC_THERMAL_TABLE_USTT = 0xA0,
> };
>
> -enum wmax_thermal_mode {
> - THERMAL_MODE_USTT_BALANCED,
> - THERMAL_MODE_USTT_BALANCED_PERFORMANCE,
> - THERMAL_MODE_USTT_COOL,
> - THERMAL_MODE_USTT_QUIET,
> - THERMAL_MODE_USTT_PERFORMANCE,
> - THERMAL_MODE_USTT_LOW_POWER,
> - THERMAL_MODE_BASIC_QUIET,
> - THERMAL_MODE_BASIC_BALANCED,
> - THERMAL_MODE_BASIC_BALANCED_PERFORMANCE,
> - THERMAL_MODE_BASIC_PERFORMANCE,
> - THERMAL_MODE_LAST,
> +enum awcc_thermal_profile {
> + AWCC_PROFILE_USTT_BALANCED,
> + AWCC_PROFILE_USTT_BALANCED_PERFORMANCE,
> + AWCC_PROFILE_USTT_COOL,
> + AWCC_PROFILE_USTT_QUIET,
> + AWCC_PROFILE_USTT_PERFORMANCE,
> + AWCC_PROFILE_USTT_LOW_POWER,
> + AWCC_PROFILE_LEGACY_QUIET,
> + AWCC_PROFILE_LEGACY_BALANCED,
> + AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE,
> + AWCC_PROFILE_LEGACY_PERFORMANCE,
> + AWCC_PROFILE_LAST,
> };
>
> struct wmax_led_args {
> @@ -210,20 +211,20 @@ struct wmax_u32_args {
> struct awcc_priv {
> struct wmi_device *wdev;
> struct device *ppdev;
> - enum wmax_thermal_mode supported_thermal_profiles[PLATFORM_PROFILE_LAST];
> + enum awcc_thermal_profile supported_thermal_profiles[PLATFORM_PROFILE_LAST];
> };
>
> -static const enum platform_profile_option wmax_mode_to_platform_profile[THERMAL_MODE_LAST] = {
> - [THERMAL_MODE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED,
> - [THERMAL_MODE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
> - [THERMAL_MODE_USTT_COOL] = PLATFORM_PROFILE_COOL,
> - [THERMAL_MODE_USTT_QUIET] = PLATFORM_PROFILE_QUIET,
> - [THERMAL_MODE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
> - [THERMAL_MODE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER,
> - [THERMAL_MODE_BASIC_QUIET] = PLATFORM_PROFILE_QUIET,
> - [THERMAL_MODE_BASIC_BALANCED] = PLATFORM_PROFILE_BALANCED,
> - [THERMAL_MODE_BASIC_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
> - [THERMAL_MODE_BASIC_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
> +static const enum platform_profile_option awcc_mode_to_platform_profile[AWCC_PROFILE_LAST] = {
> + [AWCC_PROFILE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED,
> + [AWCC_PROFILE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
> + [AWCC_PROFILE_USTT_COOL] = PLATFORM_PROFILE_COOL,
> + [AWCC_PROFILE_USTT_QUIET] = PLATFORM_PROFILE_QUIET,
> + [AWCC_PROFILE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
> + [AWCC_PROFILE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER,
> + [AWCC_PROFILE_LEGACY_QUIET] = PLATFORM_PROFILE_QUIET,
> + [AWCC_PROFILE_LEGACY_BALANCED] = PLATFORM_PROFILE_BALANCED,
> + [AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
> + [AWCC_PROFILE_LEGACY_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
> };
>
> static struct awcc_quirks *awcc;
> @@ -444,26 +445,26 @@ const struct attribute_group wmax_deepsleep_attribute_group = {
> * Thermal Profile control
> * - Provides thermal profile control through the Platform Profile API
> */
> -static bool is_wmax_thermal_code(u32 code)
> +static bool is_awcc_thermal_mode(u32 code)
> {
> - if (code & WMAX_SENSOR_ID_MASK)
> + if (code & AWCC_SENSOR_ID_MASK)
> return false;
>
> - if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST)
> + if ((code & AWCC_THERMAL_MODE_MASK) >= AWCC_PROFILE_LAST)
> return false;
>
> - if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC &&
> - (code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET)
> + if ((code & AWCC_THERMAL_TABLE_MASK) == AWCC_THERMAL_TABLE_LEGACY &&
> + (code & AWCC_THERMAL_MODE_MASK) >= AWCC_PROFILE_LEGACY_QUIET)
> return true;
>
> - if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT &&
> - (code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER)
> + if ((code & AWCC_THERMAL_TABLE_MASK) == AWCC_THERMAL_TABLE_USTT &&
> + (code & AWCC_THERMAL_MODE_MASK) <= AWCC_PROFILE_USTT_LOW_POWER)
> return true;
>
> return false;
> }
>
> -static int wmax_thermal_information(struct wmi_device *wdev, u8 operation,
> +static int awcc_thermal_information(struct wmi_device *wdev, u8 operation,
> u8 arg, u32 *out_data)
> {
> struct wmax_u32_args in_args = {
> @@ -474,21 +475,21 @@ static int wmax_thermal_information(struct wmi_device *wdev, u8 operation,
> };
> int ret;
>
> - ret = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_INFORMATION,
> + ret = alienware_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION,
> &in_args, sizeof(in_args), out_data);
> if (ret < 0)
> return ret;
>
> - if (*out_data == WMAX_FAILURE_CODE)
> + if (*out_data == AWCC_FAILURE_CODE)
> return -EBADRQC;
>
> return 0;
> }
>
> -static int wmax_thermal_control(struct wmi_device *wdev, u8 profile)
> +static int awcc_thermal_control(struct wmi_device *wdev, u8 profile)
> {
> struct wmax_u32_args in_args = {
> - .operation = WMAX_OPERATION_ACTIVATE_PROFILE,
> + .operation = AWCC_OP_ACTIVATE_PROFILE,
> .arg1 = profile,
> .arg2 = 0,
> .arg3 = 0,
> @@ -496,18 +497,18 @@ static int wmax_thermal_control(struct wmi_device *wdev, u8 profile)
> u32 out_data;
> int ret;
>
> - ret = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_CONTROL,
> + ret = alienware_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL,
> &in_args, sizeof(in_args), &out_data);
> if (ret)
> return ret;
>
> - if (out_data == WMAX_FAILURE_CODE)
> + if (out_data == AWCC_FAILURE_CODE)
> return -EBADRQC;
>
> return 0;
> }
>
> -static int wmax_game_shift_status(struct wmi_device *wdev, u8 operation,
> +static int awcc_game_shift_status(struct wmi_device *wdev, u8 operation,
> u32 *out_data)
> {
> struct wmax_u32_args in_args = {
> @@ -518,46 +519,46 @@ static int wmax_game_shift_status(struct wmi_device *wdev, u8 operation,
> };
> int ret;
>
> - ret = alienware_wmi_command(wdev, WMAX_METHOD_GAME_SHIFT_STATUS,
> + ret = alienware_wmi_command(wdev, AWCC_METHOD_GAME_SHIFT_STATUS,
> &in_args, sizeof(in_args), out_data);
> if (ret < 0)
> return ret;
>
> - if (*out_data == WMAX_FAILURE_CODE)
> + if (*out_data == AWCC_FAILURE_CODE)
> return -EOPNOTSUPP;
>
> return 0;
> }
>
> -static int thermal_profile_get(struct device *dev,
> - enum platform_profile_option *profile)
> +static int awcc_platform_profile_get(struct device *dev,
> + enum platform_profile_option *profile)
> {
> struct awcc_priv *priv = dev_get_drvdata(dev);
> u32 out_data;
> int ret;
>
> - ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_CURRENT_PROFILE,
> + ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_CURRENT_PROFILE,
> 0, &out_data);
>
> if (ret < 0)
> return ret;
>
> - if (out_data == WMAX_THERMAL_MODE_GMODE) {
> + if (out_data == AWCC_THERMAL_MODE_GMODE) {
> *profile = PLATFORM_PROFILE_PERFORMANCE;
> return 0;
> }
>
> - if (!is_wmax_thermal_code(out_data))
> + if (!is_awcc_thermal_mode(out_data))
> return -ENODATA;
>
> - out_data &= WMAX_THERMAL_MODE_MASK;
> - *profile = wmax_mode_to_platform_profile[out_data];
> + out_data &= AWCC_THERMAL_MODE_MASK;
> + *profile = awcc_mode_to_platform_profile[out_data];
>
> return 0;
> }
>
> -static int thermal_profile_set(struct device *dev,
> - enum platform_profile_option profile)
> +static int awcc_platform_profile_set(struct device *dev,
> + enum platform_profile_option profile)
> {
> struct awcc_priv *priv = dev_get_drvdata(dev);
>
> @@ -565,8 +566,8 @@ static int thermal_profile_set(struct device *dev,
> u32 gmode_status;
> int ret;
>
> - ret = wmax_game_shift_status(priv->wdev,
> - WMAX_OPERATION_GET_GAME_SHIFT_STATUS,
> + ret = awcc_game_shift_status(priv->wdev,
> + AWCC_OP_GET_GAME_SHIFT_STATUS,
> &gmode_status);
>
> if (ret < 0)
> @@ -574,8 +575,8 @@ static int thermal_profile_set(struct device *dev,
>
> if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) ||
> (profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) {
> - ret = wmax_game_shift_status(priv->wdev,
> - WMAX_OPERATION_TOGGLE_GAME_SHIFT,
> + ret = awcc_game_shift_status(priv->wdev,
> + AWCC_OP_TOGGLE_GAME_SHIFT,
> &gmode_status);
>
> if (ret < 0)
> @@ -583,21 +584,21 @@ static int thermal_profile_set(struct device *dev,
> }
> }
>
> - return wmax_thermal_control(priv->wdev,
> + return awcc_thermal_control(priv->wdev,
> priv->supported_thermal_profiles[profile]);
> }
>
> -static int thermal_profile_probe(void *drvdata, unsigned long *choices)
> +static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
> {
> enum platform_profile_option profile;
> struct awcc_priv *priv = drvdata;
> - enum wmax_thermal_mode mode;
> + enum awcc_thermal_profile mode;
> u8 sys_desc[4];
> u32 first_mode;
> u32 out_data;
> int ret;
>
> - ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_SYS_DESCRIPTION,
> + ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_SYSTEM_DESCRIPTION,
> 0, (u32 *) &sys_desc);
> if (ret < 0)
> return ret;
> @@ -605,7 +606,7 @@ static int thermal_profile_probe(void *drvdata, unsigned long *choices)
> first_mode = sys_desc[0] + sys_desc[1];
>
> for (u32 i = 0; i < sys_desc[3]; i++) {
> - ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_LIST_IDS,
> + ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_RESOURCE_ID,
> i + first_mode, &out_data);
>
> if (ret == -EIO)
> @@ -614,11 +615,11 @@ static int thermal_profile_probe(void *drvdata, unsigned long *choices)
> if (ret == -EBADRQC)
> break;
>
> - if (!is_wmax_thermal_code(out_data))
> + if (!is_awcc_thermal_mode(out_data))
> continue;
>
> - mode = out_data & WMAX_THERMAL_MODE_MASK;
> - profile = wmax_mode_to_platform_profile[mode];
> + mode = out_data & AWCC_THERMAL_MODE_MASK;
> + profile = awcc_mode_to_platform_profile[mode];
> priv->supported_thermal_profiles[profile] = out_data;
>
> set_bit(profile, choices);
> @@ -629,7 +630,7 @@ static int thermal_profile_probe(void *drvdata, unsigned long *choices)
>
> if (awcc->gmode) {
> priv->supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] =
> - WMAX_THERMAL_MODE_GMODE;
> + AWCC_THERMAL_MODE_GMODE;
>
> set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> }
> @@ -638,9 +639,9 @@ static int thermal_profile_probe(void *drvdata, unsigned long *choices)
> }
>
> static const struct platform_profile_ops awcc_platform_profile_ops = {
> - .probe = thermal_profile_probe,
> - .profile_get = thermal_profile_get,
> - .profile_set = thermal_profile_set,
> + .probe = awcc_platform_profile_probe,
> + .profile_get = awcc_platform_profile_get,
> + .profile_set = awcc_platform_profile_set,
> };
>
> static int awcc_platform_profile_init(struct wmi_device *wdev)
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 02/10] platform/x86: alienware-wmi-wmax: Refactor is_awcc_thermal_mode()
2025-02-08 5:16 ` [PATCH 02/10] platform/x86: alienware-wmi-wmax: Refactor is_awcc_thermal_mode() Kurt Borja
@ 2025-02-16 5:44 ` Armin Wolf
0 siblings, 0 replies; 25+ messages in thread
From: Armin Wolf @ 2025-02-16 5:44 UTC (permalink / raw)
To: Kurt Borja, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel
Am 08.02.25 um 06:16 schrieb Kurt Borja:
> Refactor is_awcc_thermal_mode() to use FIELD_GET() instead of bitwise
> operations. Drop the check for BIT(8) sensor flag and rename it to
> is_awcc_thermal_profile_id().
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> .../platform/x86/dell/alienware-wmi-wmax.c | 31 ++++++++++---------
> 1 file changed, 16 insertions(+), 15 deletions(-)
>
> diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> index ed70e12d73d7..7f0aa88221d6 100644
> --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
> +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> @@ -34,7 +34,7 @@
> #define AWCC_FAILURE_CODE 0xFFFFFFFF
> #define AWCC_THERMAL_TABLE_MASK GENMASK(7, 4)
> #define AWCC_THERMAL_MODE_MASK GENMASK(3, 0)
> -#define AWCC_SENSOR_ID_MASK BIT(8)
> +#define AWCC_RESOURCE_ID_MASK GENMASK(7, 0)
>
> static bool force_platform_profile;
> module_param_unsafe(force_platform_profile, bool, 0);
> @@ -168,8 +168,8 @@ enum AWCC_GAME_SHIFT_STATUS_OPERATIONS {
> };
>
> enum AWCC_THERMAL_TABLES {
> - AWCC_THERMAL_TABLE_LEGACY = 0x90,
> - AWCC_THERMAL_TABLE_USTT = 0xA0,
> + AWCC_THERMAL_TABLE_LEGACY = 0x9,
> + AWCC_THERMAL_TABLE_USTT = 0xA,
> };
>
> enum awcc_thermal_profile {
> @@ -445,20 +445,18 @@ const struct attribute_group wmax_deepsleep_attribute_group = {
> * Thermal Profile control
> * - Provides thermal profile control through the Platform Profile API
> */
> -static bool is_awcc_thermal_mode(u32 code)
> +static bool is_awcc_thermal_profile_id(u8 code)
> {
> - if (code & AWCC_SENSOR_ID_MASK)
> - return false;
> + u8 table = FIELD_GET(AWCC_THERMAL_TABLE_MASK, code);
> + u8 mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, code);
>
> - if ((code & AWCC_THERMAL_MODE_MASK) >= AWCC_PROFILE_LAST)
> + if (mode >= AWCC_PROFILE_LAST)
> return false;
>
> - if ((code & AWCC_THERMAL_TABLE_MASK) == AWCC_THERMAL_TABLE_LEGACY &&
> - (code & AWCC_THERMAL_MODE_MASK) >= AWCC_PROFILE_LEGACY_QUIET)
> + if (table == AWCC_THERMAL_TABLE_LEGACY && mode >= AWCC_PROFILE_LEGACY_QUIET)
> return true;
>
> - if ((code & AWCC_THERMAL_TABLE_MASK) == AWCC_THERMAL_TABLE_USTT &&
> - (code & AWCC_THERMAL_MODE_MASK) <= AWCC_PROFILE_USTT_LOW_POWER)
> + if (table == AWCC_THERMAL_TABLE_USTT && mode <= AWCC_PROFILE_USTT_LOW_POWER)
> return true;
>
> return false;
> @@ -548,7 +546,7 @@ static int awcc_platform_profile_get(struct device *dev,
> return 0;
> }
>
> - if (!is_awcc_thermal_mode(out_data))
> + if (!is_awcc_thermal_profile_id(out_data))
> return -ENODATA;
>
> out_data &= AWCC_THERMAL_MODE_MASK;
> @@ -597,6 +595,7 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
> u32 first_mode;
> u32 out_data;
> int ret;
> + u8 id;
>
> ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_SYSTEM_DESCRIPTION,
> 0, (u32 *) &sys_desc);
> @@ -615,12 +614,14 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
> if (ret == -EBADRQC)
> break;
>
> - if (!is_awcc_thermal_mode(out_data))
> + /* Some IDs have a BIT(8) flag that should be ignored */
> + id = FIELD_GET(AWCC_RESOURCE_ID_MASK, out_data);
> + if (!is_awcc_thermal_profile_id(id))
> continue;
>
> - mode = out_data & AWCC_THERMAL_MODE_MASK;
> + mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, id);
> profile = awcc_mode_to_platform_profile[mode];
> - priv->supported_thermal_profiles[profile] = out_data;
> + priv->supported_thermal_profiles[profile] = id;
>
> set_bit(profile, choices);
> }
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 03/10] platform/x86: alienware-wmi-wmax: Improve internal AWCC API
2025-02-08 5:16 ` [PATCH 03/10] platform/x86: alienware-wmi-wmax: Improve internal AWCC API Kurt Borja
@ 2025-02-16 5:48 ` Armin Wolf
0 siblings, 0 replies; 25+ messages in thread
From: Armin Wolf @ 2025-02-16 5:48 UTC (permalink / raw)
To: Kurt Borja, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel
Am 08.02.25 um 06:16 schrieb Kurt Borja:
> Inline all AWCC WMI helper methods and directly return the newly
> introduced __awcc_wmi_command() to simplify implementation. Additionally
> make wmax_u32_args __packed, additional failure code and add a helper
> with documentation for AWCC_OP_GET_RESOURCE_ID.
"add a additional failure code"
With this being fixed:
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> .../platform/x86/dell/alienware-wmi-wmax.c | 104 +++++++++++-------
> 1 file changed, 63 insertions(+), 41 deletions(-)
>
> diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> index 7f0aa88221d6..57897a0f4296 100644
> --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
> +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> @@ -32,6 +32,7 @@
> #define AWCC_THERMAL_MODE_GMODE 0xAB
>
> #define AWCC_FAILURE_CODE 0xFFFFFFFF
> +#define AWCC_FAILURE_CODE_2 0xFFFFFFFE
> #define AWCC_THERMAL_TABLE_MASK GENMASK(7, 4)
> #define AWCC_THERMAL_MODE_MASK GENMASK(3, 0)
> #define AWCC_RESOURCE_ID_MASK GENMASK(7, 0)
> @@ -206,7 +207,7 @@ struct wmax_u32_args {
> u8 arg1;
> u8 arg2;
> u8 arg3;
> -};
> +} __packed;
>
> struct awcc_priv {
> struct wmi_device *wdev;
> @@ -442,8 +443,7 @@ const struct attribute_group wmax_deepsleep_attribute_group = {
> };
>
> /*
> - * Thermal Profile control
> - * - Provides thermal profile control through the Platform Profile API
> + * AWCC Helpers
> */
> static bool is_awcc_thermal_profile_id(u8 code)
> {
> @@ -462,72 +462,95 @@ static bool is_awcc_thermal_profile_id(u8 code)
> return false;
> }
>
> -static int awcc_thermal_information(struct wmi_device *wdev, u8 operation,
> - u8 arg, u32 *out_data)
> +static int __awcc_wmi_command(struct wmi_device *wdev, u32 method_id,
> + struct wmax_u32_args *args, u32 *out)
> {
> - struct wmax_u32_args in_args = {
> - .operation = operation,
> - .arg1 = arg,
> - .arg2 = 0,
> - .arg3 = 0,
> - };
> int ret;
>
> - ret = alienware_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION,
> - &in_args, sizeof(in_args), out_data);
> - if (ret < 0)
> + ret = alienware_wmi_command(wdev, method_id, args, sizeof(*args), out);
> + if (ret)
> return ret;
>
> - if (*out_data == AWCC_FAILURE_CODE)
> + if (*out == AWCC_FAILURE_CODE || *out == AWCC_FAILURE_CODE_2)
> return -EBADRQC;
>
> return 0;
> }
>
> -static int awcc_thermal_control(struct wmi_device *wdev, u8 profile)
> +static inline int awcc_thermal_information(struct wmi_device *wdev, u8 operation,
> + u8 arg, u32 *out)
> {
> - struct wmax_u32_args in_args = {
> - .operation = AWCC_OP_ACTIVATE_PROFILE,
> - .arg1 = profile,
> + struct wmax_u32_args args = {
> + .operation = operation,
> + .arg1 = arg,
> .arg2 = 0,
> .arg3 = 0,
> };
> - u32 out_data;
> - int ret;
>
> - ret = alienware_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL,
> - &in_args, sizeof(in_args), &out_data);
> - if (ret)
> - return ret;
> + return __awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
> +}
>
> - if (out_data == AWCC_FAILURE_CODE)
> - return -EBADRQC;
> +static inline int awcc_thermal_control(struct wmi_device *wdev, u8 profile)
> +{
> + struct wmax_u32_args args = {
> + .operation = AWCC_OP_ACTIVATE_PROFILE,
> + .arg1 = profile,
> + .arg2 = 0,
> + .arg3 = 0,
> + };
> + u32 out;
>
> - return 0;
> + return __awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL, &args, &out);
> }
>
> -static int awcc_game_shift_status(struct wmi_device *wdev, u8 operation,
> - u32 *out_data)
> +static inline int awcc_game_shift_status(struct wmi_device *wdev, u8 operation,
> + u32 *out)
> {
> - struct wmax_u32_args in_args = {
> + struct wmax_u32_args args = {
> .operation = operation,
> .arg1 = 0,
> .arg2 = 0,
> .arg3 = 0,
> };
> - int ret;
>
> - ret = alienware_wmi_command(wdev, AWCC_METHOD_GAME_SHIFT_STATUS,
> - &in_args, sizeof(in_args), out_data);
> - if (ret < 0)
> - return ret;
> + return __awcc_wmi_command(wdev, AWCC_METHOD_GAME_SHIFT_STATUS, &args, out);
> +}
>
> - if (*out_data == AWCC_FAILURE_CODE)
> - return -EOPNOTSUPP;
> +/**
> + * awcc_op_get_resource_id - Get the resource ID at a given index
> + * @wdev: AWCC WMI device
> + * @index: Index
> + * @out: Value returned by the WMI call
> + *
> + * Get the resource ID at a given index. Resource IDs are listed in the
> + * following order:
> + *
> + * - Fan IDs
> + * - Sensor IDs
> + * - Unknown IDs
> + * - Thermal Profile IDs
> + *
> + * The total number of IDs of a given type can be obtained with
> + * AWCC_OP_GET_SYSTEM_DESCRIPTION.
> + *
> + * Return: 0 on success, -errno on failure
> + */
> +static inline int awcc_op_get_resource_id(struct wmi_device *wdev, u8 index, u32 *out)
> +{
> + struct wmax_u32_args args = {
> + .operation = AWCC_OP_GET_RESOURCE_ID,
> + .arg1 = index,
> + .arg2 = 0,
> + .arg3 = 0,
> + };
>
> - return 0;
> + return __awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
> }
>
> +/*
> + * Thermal Profile control
> + * - Provides thermal profile control through the Platform Profile API
> + */
> static int awcc_platform_profile_get(struct device *dev,
> enum platform_profile_option *profile)
> {
> @@ -605,8 +628,7 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
> first_mode = sys_desc[0] + sys_desc[1];
>
> for (u32 i = 0; i < sys_desc[3]; i++) {
> - ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_RESOURCE_ID,
> - i + first_mode, &out_data);
> + ret = awcc_op_get_resource_id(priv->wdev, i + first_mode, &out_data);
>
> if (ret == -EIO)
> return ret;
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 04/10] platform/x86: alienware-wmi-wmax: Modify supported_thermal_profiles[]
2025-02-08 5:16 ` [PATCH 04/10] platform/x86: alienware-wmi-wmax: Modify supported_thermal_profiles[] Kurt Borja
@ 2025-02-16 5:49 ` Armin Wolf
0 siblings, 0 replies; 25+ messages in thread
From: Armin Wolf @ 2025-02-16 5:49 UTC (permalink / raw)
To: Kurt Borja, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel
Am 08.02.25 um 06:16 schrieb Kurt Borja:
> Rename supported_thermal_profiles[] -> supported_profiles[] and change
> it's type to u8 because it stores AWCC thermal IDs.
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> drivers/platform/x86/dell/alienware-wmi-wmax.c | 8 ++++----
> 1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> index 57897a0f4296..4a8335d90b5d 100644
> --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
> +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> @@ -212,7 +212,7 @@ struct wmax_u32_args {
> struct awcc_priv {
> struct wmi_device *wdev;
> struct device *ppdev;
> - enum awcc_thermal_profile supported_thermal_profiles[PLATFORM_PROFILE_LAST];
> + u8 supported_profiles[PLATFORM_PROFILE_LAST];
> };
>
> static const enum platform_profile_option awcc_mode_to_platform_profile[AWCC_PROFILE_LAST] = {
> @@ -606,7 +606,7 @@ static int awcc_platform_profile_set(struct device *dev,
> }
>
> return awcc_thermal_control(priv->wdev,
> - priv->supported_thermal_profiles[profile]);
> + priv->supported_profiles[profile]);
> }
>
> static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
> @@ -643,7 +643,7 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
>
> mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, id);
> profile = awcc_mode_to_platform_profile[mode];
> - priv->supported_thermal_profiles[profile] = id;
> + priv->supported_profiles[profile] = id;
>
> set_bit(profile, choices);
> }
> @@ -652,7 +652,7 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
> return -ENODEV;
>
> if (awcc->gmode) {
> - priv->supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] =
> + priv->supported_profiles[PLATFORM_PROFILE_PERFORMANCE] =
> AWCC_THERMAL_MODE_GMODE;
>
> set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 05/10] platform/x86: alienware-wmi-wmax: Improve platform profile probe
2025-02-08 5:16 ` [PATCH 05/10] platform/x86: alienware-wmi-wmax: Improve platform profile probe Kurt Borja
@ 2025-02-16 5:57 ` Armin Wolf
0 siblings, 0 replies; 25+ messages in thread
From: Armin Wolf @ 2025-02-16 5:57 UTC (permalink / raw)
To: Kurt Borja, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel
Am 08.02.25 um 06:16 schrieb Kurt Borja:
> Get and store the AWCC system description in alienware_awcc_setup()
> instead of awcc_platform_profile_probe() and add a check for integer
> overflows to avoid misbehaviors.
>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> .../platform/x86/dell/alienware-wmi-wmax.c | 56 ++++++++++++++-----
> 1 file changed, 43 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> index 4a8335d90b5d..e8fe16da036a 100644
> --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
> +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> @@ -12,6 +12,7 @@
> #include <linux/bits.h>
> #include <linux/dmi.h>
> #include <linux/moduleparam.h>
> +#include <linux/overflow.h>
> #include <linux/platform_profile.h>
> #include <linux/wmi.h>
> #include "alienware-wmi.h"
> @@ -211,6 +212,17 @@ struct wmax_u32_args {
>
> struct awcc_priv {
> struct wmi_device *wdev;
> + union {
> + u32 system_description;
> + struct {
> + u8 fan_count;
> + u8 temp_count;
> + u8 unknown_count;
> + u8 profile_count;
> + } __packed;
> + u8 res_count[4];
> + } __packed;
> +
> struct device *ppdev;
> u8 supported_profiles[PLATFORM_PROFILE_LAST];
> };
> @@ -614,32 +626,35 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
> enum platform_profile_option profile;
> struct awcc_priv *priv = drvdata;
> enum awcc_thermal_profile mode;
> - u8 sys_desc[4];
> - u32 first_mode;
> + u8 id, offset = 0;
> u32 out_data;
> int ret;
> - u8 id;
> -
> - ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_SYSTEM_DESCRIPTION,
> - 0, (u32 *) &sys_desc);
> - if (ret < 0)
> - return ret;
> -
> - first_mode = sys_desc[0] + sys_desc[1];
>
> - for (u32 i = 0; i < sys_desc[3]; i++) {
> - ret = awcc_op_get_resource_id(priv->wdev, i + first_mode, &out_data);
> + /*
> + * Thermal profile IDs are listed last at offset
> + * fan_count + temp_count + unknown_count
> + */
> + for (u32 i = 0; i < ARRAY_SIZE(priv->res_count) - 1; i++)
> + offset += priv->res_count[i];
>
> + for (u32 i = 0; i < priv->profile_count; i++) {
> + ret = awcc_op_get_resource_id(priv->wdev, i + offset, &out_data);
> if (ret == -EIO)
> return ret;
>
> + /*
> + * Some devices report an incorrect number of thermal profiles
> + * so the resource ID list may end prematurely
> + */
> if (ret == -EBADRQC)
> break;
>
> /* Some IDs have a BIT(8) flag that should be ignored */
> id = FIELD_GET(AWCC_RESOURCE_ID_MASK, out_data);
> - if (!is_awcc_thermal_profile_id(id))
> + if (!is_awcc_thermal_profile_id(id)) {
> + dev_dbg(&priv->wdev->dev, "Unmapped thermal profile ID 0x%02x\n", id);
> continue;
> + }
>
> mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, id);
> profile = awcc_mode_to_platform_profile[mode];
> @@ -680,12 +695,27 @@ static int awcc_platform_profile_init(struct wmi_device *wdev)
> static int alienware_awcc_setup(struct wmi_device *wdev)
> {
> struct awcc_priv *priv;
> + u8 tot = 0;
> int ret;
>
> priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> if (!priv)
> return -ENOMEM;
>
> + ret = awcc_thermal_information(wdev, AWCC_OP_GET_SYSTEM_DESCRIPTION,
> + 0, &priv->system_description);
> + if (ret < 0)
> + return ret;
> +
> + /* Sanity check */
> + for (u32 i = 0; i < ARRAY_SIZE(priv->res_count); i++) {
> + if (check_add_overflow(tot, priv->res_count[i], &tot)) {
I wonder if it would be easier to just use a int to accumulate all offsets and then check if the resulting value is greater than U8_MAX.
Other than that:
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> + dev_err(&wdev->dev, "Malformed system description: %u\n",
> + priv->system_description);
> + return -ENXIO;
> + }
> + }
> +
> priv->wdev = wdev;
> dev_set_drvdata(&wdev->dev, priv);
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 06/10] platform/x86: alienware-wmi-wmax: Add support for the "custom" thermal profile
2025-02-08 5:16 ` [PATCH 06/10] platform/x86: alienware-wmi-wmax: Add support for the "custom" thermal profile Kurt Borja
@ 2025-02-16 5:59 ` Armin Wolf
0 siblings, 0 replies; 25+ messages in thread
From: Armin Wolf @ 2025-02-16 5:59 UTC (permalink / raw)
To: Kurt Borja, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel
Am 08.02.25 um 06:16 schrieb Kurt Borja:
> All models with the "AWCC" WMAX device support a "custom" thermal
> profile. In some models this profile signals user-space that the user
> wants to manually control the fans, which are always unlocked. In other
> models it actually unlocks manual fan control.
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> .../platform/x86/dell/alienware-wmi-wmax.c | 23 +++++++++++++++----
> 1 file changed, 19 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> index e8fe16da036a..0d31156f43bb 100644
> --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
> +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> @@ -30,8 +30,6 @@
> #define AWCC_METHOD_THERMAL_CONTROL 0x15
> #define AWCC_METHOD_GAME_SHIFT_STATUS 0x25
>
> -#define AWCC_THERMAL_MODE_GMODE 0xAB
> -
> #define AWCC_FAILURE_CODE 0xFFFFFFFF
> #define AWCC_FAILURE_CODE_2 0xFFFFFFFE
> #define AWCC_THERMAL_TABLE_MASK GENMASK(7, 4)
> @@ -174,6 +172,11 @@ enum AWCC_THERMAL_TABLES {
> AWCC_THERMAL_TABLE_USTT = 0xA,
> };
>
> +enum AWCC_SPECIAL_THERMAL_CODES {
> + AWCC_SPECIAL_PROFILE_CUSTOM = 0x00,
> + AWCC_SPECIAL_PROFILE_GMODE = 0xAB,
> +};
> +
> enum awcc_thermal_profile {
> AWCC_PROFILE_USTT_BALANCED,
> AWCC_PROFILE_USTT_BALANCED_PERFORMANCE,
> @@ -576,9 +579,15 @@ static int awcc_platform_profile_get(struct device *dev,
> if (ret < 0)
> return ret;
>
> - if (out_data == AWCC_THERMAL_MODE_GMODE) {
> + switch (out_data) {
> + case AWCC_SPECIAL_PROFILE_CUSTOM:
> + *profile = PLATFORM_PROFILE_CUSTOM;
> + return 0;
> + case AWCC_SPECIAL_PROFILE_GMODE:
> *profile = PLATFORM_PROFILE_PERFORMANCE;
> return 0;
> + default:
> + break;
> }
>
> if (!is_awcc_thermal_profile_id(out_data))
> @@ -666,9 +675,15 @@ static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
> if (bitmap_empty(choices, PLATFORM_PROFILE_LAST))
> return -ENODEV;
>
> + /* Every model supports the "custom" profile */
> + priv->supported_profiles[PLATFORM_PROFILE_CUSTOM] =
> + AWCC_SPECIAL_PROFILE_CUSTOM;
> +
> + set_bit(PLATFORM_PROFILE_CUSTOM, choices);
> +
> if (awcc->gmode) {
> priv->supported_profiles[PLATFORM_PROFILE_PERFORMANCE] =
> - AWCC_THERMAL_MODE_GMODE;
> + AWCC_SPECIAL_PROFILE_GMODE;
>
> set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> }
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 07/10] platform/x86: alienware-wmi-wmax: Add HWMON support
2025-02-08 5:16 ` [PATCH 07/10] platform/x86: alienware-wmi-wmax: Add HWMON support Kurt Borja
@ 2025-02-16 6:06 ` Armin Wolf
2025-02-16 21:27 ` Kurt Borja
0 siblings, 1 reply; 25+ messages in thread
From: Armin Wolf @ 2025-02-16 6:06 UTC (permalink / raw)
To: Kurt Borja, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Guenter Roeck
Am 08.02.25 um 06:16 schrieb Kurt Borja:
> All models with the "AWCC" WMAX device support monitoring fan speed and
> temperature sensors. Expose this feature through the HWMON interface.
>
> Sensor readings are cached for 1 second before refreshing them to
> mitigate the performance cost of calling WMI methods.
Since each fan can be accessed independently, i do not think that the caching is actually
needed here.
The dell-wmi-ddv driver only uses caching because it can only read all fan sensors
at the same time, which means each call is quite expensive. The AWCC interface however
seems to allow access to each individual fan, so each call should be quite cheap (for
a WMI method call).
Do you know of a device which has such slow WMI calls? If not then i suggest you remove
the caching.
> Cc: Guenter Roeck <linux@roeck-us.net>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> drivers/platform/x86/dell/Kconfig | 1 +
> .../platform/x86/dell/alienware-wmi-wmax.c | 341 ++++++++++++++++++
> 2 files changed, 342 insertions(+)
>
> diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig
> index f8a0dffcaab7..85a57c01aaad 100644
> --- a/drivers/platform/x86/dell/Kconfig
> +++ b/drivers/platform/x86/dell/Kconfig
> @@ -43,6 +43,7 @@ config ALIENWARE_WMI_WMAX
> bool "Alienware WMAX WMI device driver"
> default y
> depends on ALIENWARE_WMI
> + depends on HWMON
> select ACPI_PLATFORM_PROFILE
> help
> Alienware WMI driver with AlienFX LED, HDMI, amplifier, deep sleep and
> diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> index 0d31156f43bb..5f02da7ff25f 100644
> --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
> +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> @@ -11,9 +11,13 @@
> #include <linux/bitfield.h>
> #include <linux/bits.h>
> #include <linux/dmi.h>
> +#include <linux/hwmon.h>
> +#include <linux/jiffies.h>
> #include <linux/moduleparam.h>
> +#include <linux/mutex.h>
> #include <linux/overflow.h>
> #include <linux/platform_profile.h>
> +#include <linux/units.h>
> #include <linux/wmi.h>
> #include "alienware-wmi.h"
>
> @@ -26,6 +30,7 @@
> #define WMAX_METHOD_BRIGHTNESS 0x3
> #define WMAX_METHOD_ZONE_CONTROL 0x4
>
> +#define AWCC_METHOD_GET_FAN_SENSORS 0x13
> #define AWCC_METHOD_THERMAL_INFORMATION 0x14
> #define AWCC_METHOD_THERMAL_CONTROL 0x15
> #define AWCC_METHOD_GAME_SHIFT_STATUS 0x25
> @@ -36,6 +41,10 @@
> #define AWCC_THERMAL_MODE_MASK GENMASK(3, 0)
> #define AWCC_RESOURCE_ID_MASK GENMASK(7, 0)
>
> +static bool force_hwmon;
> +module_param_unsafe(force_hwmon, bool, 0);
> +MODULE_PARM_DESC(force_hwmon, "Force probing for HWMON support without checking if the WMI backend is available");
> +
> static bool force_platform_profile;
> module_param_unsafe(force_platform_profile, bool, 0);
> MODULE_PARM_DESC(force_platform_profile, "Forces auto-detecting thermal profiles without checking if WMI thermal backend is available");
> @@ -45,16 +54,19 @@ module_param_unsafe(force_gmode, bool, 0);
> MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected");
>
> struct awcc_quirks {
> + bool hwmon;
> bool pprof;
> bool gmode;
> };
>
> static struct awcc_quirks g_series_quirks = {
> + .hwmon = true,
> .pprof = true,
> .gmode = true,
> };
>
> static struct awcc_quirks generic_quirks = {
> + .hwmon = true,
> .pprof = true,
> .gmode = false,
> };
> @@ -152,9 +164,17 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = {
> },
> };
>
> +enum AWCC_GET_FAN_SENSORS_OPERATIONS {
> + AWCC_OP_GET_TEMP_SENSOR_ID = 0x02,
> +};
> +
> enum AWCC_THERMAL_INFORMATION_OPERATIONS {
> AWCC_OP_GET_SYSTEM_DESCRIPTION = 0x02,
> AWCC_OP_GET_RESOURCE_ID = 0x03,
> + AWCC_OP_GET_TEMPERATURE = 0x04,
> + AWCC_OP_GET_CURRENT_RPM = 0x05,
> + AWCC_OP_GET_MIN_RPM = 0x08,
> + AWCC_OP_GET_MAX_RPM = 0x09,
> AWCC_OP_GET_CURRENT_PROFILE = 0x0B,
> };
>
> @@ -177,6 +197,11 @@ enum AWCC_SPECIAL_THERMAL_CODES {
> AWCC_SPECIAL_PROFILE_GMODE = 0xAB,
> };
>
> +enum AWCC_TEMP_SENSOR_TYPES {
> + AWCC_TEMP_SENSOR_CPU = 0x01,
> + AWCC_TEMP_SENSOR_GPU = 0x06,
> +};
> +
> enum awcc_thermal_profile {
> AWCC_PROFILE_USTT_BALANCED,
> AWCC_PROFILE_USTT_BALANCED_PERFORMANCE,
> @@ -213,6 +238,23 @@ struct wmax_u32_args {
> u8 arg3;
> } __packed;
>
> +struct awcc_fan_channel_data {
> + u8 id;
> + u32 state;
> + u32 min_rpm;
> + u32 max_rpm;
> + u8 temp_sensor;
> + u64 timestamp;
> + struct mutex lock; /* protects state and timestamp */
> +};
> +
> +struct awcc_temp_channel_data {
> + u8 id;
> + u32 state;
> + u64 timestamp;
> + struct mutex lock; /* protects state and timestamp */
> +};
> +
> struct awcc_priv {
> struct wmi_device *wdev;
> union {
> @@ -228,6 +270,10 @@ struct awcc_priv {
>
> struct device *ppdev;
> u8 supported_profiles[PLATFORM_PROFILE_LAST];
> +
> + struct device *hwdev;
> + struct awcc_temp_channel_data *temp_data;
> + struct awcc_fan_channel_data *fan_data;
> };
>
> static const enum platform_profile_option awcc_mode_to_platform_profile[AWCC_PROFILE_LAST] = {
> @@ -492,6 +538,18 @@ static int __awcc_wmi_command(struct wmi_device *wdev, u32 method_id,
> return 0;
> }
>
> +static inline int awcc_get_fan_sensors(struct wmi_device *wdev, u8 fan_id, u32 *out)
> +{
> + struct wmax_u32_args args = {
> + .operation = AWCC_OP_GET_TEMP_SENSOR_ID,
> + .arg1 = fan_id,
> + .arg2 = 0,
> + .arg3 = 0,
> + };
> +
> + return __awcc_wmi_command(wdev, AWCC_METHOD_GET_FAN_SENSORS, &args, out);
> +}
> +
> static inline int awcc_thermal_information(struct wmi_device *wdev, u8 operation,
> u8 arg, u32 *out)
> {
> @@ -562,6 +620,276 @@ static inline int awcc_op_get_resource_id(struct wmi_device *wdev, u8 index, u32
> return __awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
> }
>
> +/*
> + * HWMON
> + * - Provides temperature and fan speed monitoring as well as manual fan
> + * control
> + */
> +static int awcc_hwmon_update_temp(struct wmi_device *wdev,
> + struct awcc_temp_channel_data *data)
> +{
> + u32 temp;
> + int ret;
> +
> + lockdep_assert_held(data->lock);
> +
> + if (time_is_after_jiffies64(data->timestamp + secs_to_jiffies(1)))
> + return 0;
> +
> + ret = awcc_thermal_information(wdev, AWCC_OP_GET_TEMPERATURE, data->id,
> + &temp);
> + if (ret)
> + return ret;
> +
> + data->state = temp * MILLIDEGREE_PER_DEGREE;
> + data->timestamp = get_jiffies_64();
> +
> + return 0;
> +}
> +
> +static int awcc_hwmon_update_fan(struct wmi_device *wdev,
> + struct awcc_fan_channel_data *data)
> +{
> + u32 rpm;
> + int ret;
> +
> + lockdep_assert_held(data->lock);
> +
> + if (time_is_after_jiffies64(data->timestamp + secs_to_jiffies(1)))
> + return 0;
> +
> + ret = awcc_thermal_information(wdev, AWCC_OP_GET_CURRENT_RPM, data->id,
> + &rpm);
> + if (ret)
> + return ret;
> +
> + data->state = rpm;
> + data->timestamp = get_jiffies_64();
> +
> + return 0;
> +}
> +
> +static umode_t awcc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
> + u32 attr, int channel)
> +{
> + const struct awcc_priv *priv = drvdata;
> +
> + switch (type) {
> + case hwmon_temp:
> + if (channel < priv->temp_count)
> + return 0444;
> +
> + break;
> + case hwmon_fan:
> + if (channel < priv->fan_count)
> + return 0444;
> +
> + break;
> + default:
> + break;
> + }
> +
> + return 0;
> +}
> +
> +static int awcc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + struct awcc_priv *priv = dev_get_drvdata(dev);
> + struct awcc_temp_channel_data *temp;
> + struct awcc_fan_channel_data *fan;
> + int ret;
> +
> + switch (type) {
> + case hwmon_temp:
> + temp = &priv->temp_data[channel];
> +
> + switch (attr) {
> + case hwmon_temp_input:
> + mutex_lock(&temp->lock);
> + ret = awcc_hwmon_update_temp(priv->wdev, temp);
> + mutex_unlock(&temp->lock);
> + if (ret)
> + return ret;
> +
> + *val = temp->state;
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + break;
> + case hwmon_fan:
> + fan = &priv->fan_data[channel];
> +
> + switch (attr) {
> + case hwmon_fan_input:
> + mutex_lock(&fan->lock);
> + ret = awcc_hwmon_update_fan(priv->wdev, fan);
> + mutex_unlock(&fan->lock);
> + if (ret)
> + return ret;
> +
> + *val = fan->state;
> + break;
> + case hwmon_fan_min:
> + *val = fan->min_rpm;
> + break;
> + case hwmon_fan_max:
> + *val = fan->max_rpm;
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + return 0;
> +}
> +
> +static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, const char **str)
> +{
> + struct awcc_priv *priv = dev_get_drvdata(dev);
> + u8 temp_id;
> +
> + switch (type) {
> + case hwmon_temp:
> + temp_id = priv->temp_data[channel].id;
> +
> + switch (temp_id) {
> + case AWCC_TEMP_SENSOR_CPU:
> + *str = "CPU";
> + break;
> + case AWCC_TEMP_SENSOR_GPU:
> + *str = "GPU";
> + break;
> + default:
> + *str = "Unknown";
> + break;
> + }
> +
> + break;
> + case hwmon_fan:
> + temp_id = priv->fan_data[channel].temp_sensor;
> +
> + switch (temp_id) {
> + case AWCC_TEMP_SENSOR_CPU:
> + *str = "Processor Fan";
> + break;
> + case AWCC_TEMP_SENSOR_GPU:
> + *str = "Video Fan";
> + break;
> + default:
> + *str = "Unknown Fan";
> + break;
> + }
> +
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + return 0;
> +}
> +
> +static const struct hwmon_ops awcc_hwmon_ops = {
> + .is_visible = awcc_hwmon_is_visible,
> + .read = awcc_hwmon_read,
> + .read_string = awcc_hwmon_read_string,
> +};
> +
> +static const struct hwmon_channel_info * const awcc_hwmon_info[] = {
> + HWMON_CHANNEL_INFO(temp,
> + HWMON_T_LABEL | HWMON_T_INPUT,
> + HWMON_T_LABEL | HWMON_T_INPUT,
> + HWMON_T_LABEL | HWMON_T_INPUT
> + ),
> + HWMON_CHANNEL_INFO(fan,
> + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
> + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
> + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
> + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX
> + ),
> + NULL
> +};
> +
> +static const struct hwmon_chip_info awcc_hwmon_chip_info = {
> + .ops = &awcc_hwmon_ops,
> + .info = awcc_hwmon_info,
> +};
> +
> +static int awcc_hwmon_init(struct wmi_device *wdev)
> +{
> + struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
> + u32 id, temp_sensor, min_rpm, max_rpm;
> + int ret;
> +
> + priv->fan_data = devm_kcalloc(&wdev->dev, priv->fan_count,
> + sizeof(*priv->fan_data), GFP_KERNEL);
> + if (!priv->fan_data)
> + return -ENOMEM;
> +
> + priv->temp_data = devm_kcalloc(&wdev->dev, priv->temp_count,
> + sizeof(*priv->temp_data), GFP_KERNEL);
> + if (!priv->temp_data)
> + return -ENOMEM;
> +
> + for (u32 i = 0; i < priv->fan_count; i++) {
> + /*
> + * Fan IDs are listed first at offset 0
> + */
> + ret = awcc_op_get_resource_id(wdev, i, &id);
> + if (ret)
> + return ret;
> +
> + ret = awcc_thermal_information(wdev, AWCC_OP_GET_MIN_RPM, id,
> + &min_rpm);
> + if (ret)
> + return ret;
> +
> + ret = awcc_thermal_information(wdev, AWCC_OP_GET_MAX_RPM, id,
> + &max_rpm);
> + if (ret)
> + return ret;
> +
> + ret = awcc_get_fan_sensors(wdev, id, &temp_sensor);
> + if (ret)
> + return ret;
> +
> + priv->fan_data[i].id = FIELD_GET(AWCC_RESOURCE_ID_MASK, id);
> + priv->fan_data[i].min_rpm = min_rpm;
> + priv->fan_data[i].max_rpm = max_rpm;
> + priv->fan_data[i].temp_sensor = temp_sensor;
> + ret = devm_mutex_init(&wdev->dev, &priv->fan_data[i].lock);
> + if (ret)
> + return ret;
> + }
> +
> + for (u32 i = 0; i < priv->temp_count; i++) {
> + /*
> + * Temperature sensors IDs are listed after the fan IDs at
> + * offset `fan_count`
> + */
> + ret = awcc_op_get_resource_id(wdev, i + priv->fan_count, &id);
> + if (ret)
> + return ret;
> +
> + priv->temp_data[i].id = FIELD_GET(AWCC_RESOURCE_ID_MASK, id);
> + ret = devm_mutex_init(&wdev->dev, &priv->temp_data[i].lock);
> + if (ret)
> + return ret;
> + }
> +
> + priv->hwdev = devm_hwmon_device_register_with_info(
> + &wdev->dev, "alienware_wmi", priv, &awcc_hwmon_chip_info, NULL);
checkpatch says "CHECK: Lines should not end with a '('". Please fix this.
Other than that the code looks promising.
Thanks,
Armin Wolf
> +
> + return PTR_ERR_OR_ZERO(priv->hwdev);
> +}
> +
> /*
> * Thermal Profile control
> * - Provides thermal profile control through the Platform Profile API
> @@ -734,6 +1062,12 @@ static int alienware_awcc_setup(struct wmi_device *wdev)
> priv->wdev = wdev;
> dev_set_drvdata(&wdev->dev, priv);
>
> + if (awcc->hwmon) {
> + ret = awcc_hwmon_init(wdev);
> + if (ret)
> + return ret;
> + }
> +
> if (awcc->pprof) {
> ret = awcc_platform_profile_init(wdev);
> if (ret)
> @@ -814,6 +1148,13 @@ int __init alienware_wmax_wmi_init(void)
> if (id)
> awcc = id->driver_data;
>
> + if (force_hwmon) {
> + if (!awcc)
> + awcc = &empty_quirks;
> +
> + awcc->hwmon = true;
> + }
> +
> if (force_platform_profile) {
> if (!awcc)
> awcc = &empty_quirks;
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 08/10] platform/x86: alienware-wmi-wmax: Add support for manual fan control
2025-02-08 5:16 ` [PATCH 08/10] platform/x86: alienware-wmi-wmax: Add support for manual fan control Kurt Borja
@ 2025-02-16 6:12 ` Armin Wolf
2025-02-16 6:14 ` Armin Wolf
2025-02-16 21:25 ` Kurt Borja
0 siblings, 2 replies; 25+ messages in thread
From: Armin Wolf @ 2025-02-16 6:12 UTC (permalink / raw)
To: Kurt Borja, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Guenter Roeck
Am 08.02.25 um 06:16 schrieb Kurt Borja:
> All models with the "AWCC" WMAX device support a way of manually
> controlling fans.
>
> The PWM duty cycle of a fan can't be controlled directly. Instead the
> AWCC interface let's us tune a PWM `boost` value, which has the
> following empirically discovered behavior over the PWM value:
>
> pwm = pwm_base + (pwm_boost / 255) * (pwm_max - pwm_base)
>
> Where the pwm_base is the locked PWM value controlled by the EC and
> pwm_boost is a value between 0 and 255.
>
> This pwm_boost knob is exposed as a standard `pwm` attribute.
I am not sure if exposing this over the standard "pwm" attribute is correct here,
since userspace applications expect to have full access to the fan when using the
"pwm" attribute.
Maybe using a custom attribute like "fanX_boost" would make sense here? Either way
documenting this special behavior would be nice for future users, maybe you can write
this down under Documentation/admin-guide/laptops?
Thanks,
Armin Wolf
> Cc: Guenter Roeck <linux@roeck-us.net>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> .../platform/x86/dell/alienware-wmi-wmax.c | 55 +++++++++++++++++--
> 1 file changed, 49 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> index 5f02da7ff25f..06d6f88ea54b 100644
> --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
> +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
> @@ -13,6 +13,7 @@
> #include <linux/dmi.h>
> #include <linux/hwmon.h>
> #include <linux/jiffies.h>
> +#include <linux/minmax.h>
> #include <linux/moduleparam.h>
> #include <linux/mutex.h>
> #include <linux/overflow.h>
> @@ -176,10 +177,12 @@ enum AWCC_THERMAL_INFORMATION_OPERATIONS {
> AWCC_OP_GET_MIN_RPM = 0x08,
> AWCC_OP_GET_MAX_RPM = 0x09,
> AWCC_OP_GET_CURRENT_PROFILE = 0x0B,
> + AWCC_OP_GET_FAN_BOOST = 0x0C,
> };
>
> enum AWCC_THERMAL_CONTROL_OPERATIONS {
> AWCC_OP_ACTIVATE_PROFILE = 0x01,
> + AWCC_OP_SET_FAN_BOOST = 0x02,
> };
>
> enum AWCC_GAME_SHIFT_STATUS_OPERATIONS {
> @@ -563,12 +566,13 @@ static inline int awcc_thermal_information(struct wmi_device *wdev, u8 operation
> return __awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
> }
>
> -static inline int awcc_thermal_control(struct wmi_device *wdev, u8 profile)
> +static inline int awcc_thermal_control(struct wmi_device *wdev, u8 operation,
> + u8 arg1, u8 arg2)
> {
> struct wmax_u32_args args = {
> - .operation = AWCC_OP_ACTIVATE_PROFILE,
> - .arg1 = profile,
> - .arg2 = 0,
> + .operation = operation,
> + .arg1 = arg1,
> + .arg2 = arg2,
> .arg3 = 0,
> };
> u32 out;
> @@ -684,6 +688,11 @@ static umode_t awcc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_type
> if (channel < priv->fan_count)
> return 0444;
>
> + break;
> + case hwmon_pwm:
> + if (channel < priv->fan_count)
> + return 0644;
> +
> break;
> default:
> break;
> @@ -698,6 +707,7 @@ static int awcc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
> struct awcc_priv *priv = dev_get_drvdata(dev);
> struct awcc_temp_channel_data *temp;
> struct awcc_fan_channel_data *fan;
> + u32 fan_boost;
> int ret;
>
> switch (type) {
> @@ -742,6 +752,16 @@ static int awcc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
> return -EOPNOTSUPP;
> }
>
> + break;
> + case hwmon_pwm:
> + fan = &priv->fan_data[channel];
> +
> + ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_FAN_BOOST,
> + fan->id, &fan_boost);
> + if (ret)
> + return ret;
> +
> + *val = fan_boost;
> break;
> default:
> return -EOPNOTSUPP;
> @@ -796,10 +816,27 @@ static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types ty
> return 0;
> }
>
> +
> +static int awcc_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long val)
> +{
> + struct awcc_priv *priv = dev_get_drvdata(dev);
> + u8 fan_id = priv->fan_data[channel].id;
> +
> + switch (type) {
> + case hwmon_pwm:
> + return awcc_thermal_control(priv->wdev, AWCC_OP_SET_FAN_BOOST,
> + fan_id, (u8)clamp_val(val, 0, 255));
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> static const struct hwmon_ops awcc_hwmon_ops = {
> .is_visible = awcc_hwmon_is_visible,
> .read = awcc_hwmon_read,
> .read_string = awcc_hwmon_read_string,
> + .write = awcc_hwmon_write,
> };
>
> static const struct hwmon_channel_info * const awcc_hwmon_info[] = {
> @@ -814,6 +851,12 @@ static const struct hwmon_channel_info * const awcc_hwmon_info[] = {
> HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
> HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX
> ),
> + HWMON_CHANNEL_INFO(pwm,
> + HWMON_PWM_INPUT,
> + HWMON_PWM_INPUT,
> + HWMON_PWM_INPUT,
> + HWMON_PWM_INPUT
> + ),
> NULL
> };
>
> @@ -954,8 +997,8 @@ static int awcc_platform_profile_set(struct device *dev,
> }
> }
>
> - return awcc_thermal_control(priv->wdev,
> - priv->supported_profiles[profile]);
> + return awcc_thermal_control(priv->wdev, AWCC_OP_ACTIVATE_PROFILE,
> + priv->supported_profiles[profile], 0);
> }
>
> static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 08/10] platform/x86: alienware-wmi-wmax: Add support for manual fan control
2025-02-16 6:12 ` Armin Wolf
@ 2025-02-16 6:14 ` Armin Wolf
2025-02-16 21:25 ` Kurt Borja
1 sibling, 0 replies; 25+ messages in thread
From: Armin Wolf @ 2025-02-16 6:14 UTC (permalink / raw)
To: Kurt Borja, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Guenter Roeck
Am 16.02.25 um 07:12 schrieb Armin Wolf:
> Am 08.02.25 um 06:16 schrieb Kurt Borja:
>
>> All models with the "AWCC" WMAX device support a way of manually
>> controlling fans.
>>
>> The PWM duty cycle of a fan can't be controlled directly. Instead the
>> AWCC interface let's us tune a PWM `boost` value, which has the
>> following empirically discovered behavior over the PWM value:
>>
>> pwm = pwm_base + (pwm_boost / 255) * (pwm_max - pwm_base)
>>
>> Where the pwm_base is the locked PWM value controlled by the EC and
>> pwm_boost is a value between 0 and 255.
>>
>> This pwm_boost knob is exposed as a standard `pwm` attribute.
>
> I am not sure if exposing this over the standard "pwm" attribute is
> correct here,
> since userspace applications expect to have full access to the fan
> when using the
> "pwm" attribute.
>
> Maybe using a custom attribute like "fanX_boost" would make sense
> here? Either way
> documenting this special behavior would be nice for future users,
> maybe you can write
> this down under Documentation/admin-guide/laptops?
>
> Thanks,
> Armin Wolf
>
>> Cc: Guenter Roeck <linux@roeck-us.net>
>> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
>> ---
>> .../platform/x86/dell/alienware-wmi-wmax.c | 55 +++++++++++++++++--
>> 1 file changed, 49 insertions(+), 6 deletions(-)
>>
>> diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c
>> b/drivers/platform/x86/dell/alienware-wmi-wmax.c
>> index 5f02da7ff25f..06d6f88ea54b 100644
>> --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
>> +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
>> @@ -13,6 +13,7 @@
>> #include <linux/dmi.h>
>> #include <linux/hwmon.h>
>> #include <linux/jiffies.h>
>> +#include <linux/minmax.h>
>> #include <linux/moduleparam.h>
>> #include <linux/mutex.h>
>> #include <linux/overflow.h>
>> @@ -176,10 +177,12 @@ enum AWCC_THERMAL_INFORMATION_OPERATIONS {
>> AWCC_OP_GET_MIN_RPM = 0x08,
>> AWCC_OP_GET_MAX_RPM = 0x09,
>> AWCC_OP_GET_CURRENT_PROFILE = 0x0B,
>> + AWCC_OP_GET_FAN_BOOST = 0x0C,
>> };
>>
>> enum AWCC_THERMAL_CONTROL_OPERATIONS {
>> AWCC_OP_ACTIVATE_PROFILE = 0x01,
>> + AWCC_OP_SET_FAN_BOOST = 0x02,
>> };
>>
>> enum AWCC_GAME_SHIFT_STATUS_OPERATIONS {
>> @@ -563,12 +566,13 @@ static inline int
>> awcc_thermal_information(struct wmi_device *wdev, u8 operation
>> return __awcc_wmi_command(wdev,
>> AWCC_METHOD_THERMAL_INFORMATION, &args, out);
>> }
>>
>> -static inline int awcc_thermal_control(struct wmi_device *wdev, u8
>> profile)
>> +static inline int awcc_thermal_control(struct wmi_device *wdev, u8
>> operation,
>> + u8 arg1, u8 arg2)
>> {
>> struct wmax_u32_args args = {
>> - .operation = AWCC_OP_ACTIVATE_PROFILE,
>> - .arg1 = profile,
>> - .arg2 = 0,
>> + .operation = operation,
>> + .arg1 = arg1,
>> + .arg2 = arg2,
>> .arg3 = 0,
>> };
>> u32 out;
>> @@ -684,6 +688,11 @@ static umode_t awcc_hwmon_is_visible(const void
>> *drvdata, enum hwmon_sensor_type
>> if (channel < priv->fan_count)
>> return 0444;
>>
>> + break;
>> + case hwmon_pwm:
>> + if (channel < priv->fan_count)
>> + return 0644;
>> +
>> break;
>> default:
>> break;
>> @@ -698,6 +707,7 @@ static int awcc_hwmon_read(struct device *dev,
>> enum hwmon_sensor_types type,
>> struct awcc_priv *priv = dev_get_drvdata(dev);
>> struct awcc_temp_channel_data *temp;
>> struct awcc_fan_channel_data *fan;
>> + u32 fan_boost;
>> int ret;
>>
>> switch (type) {
>> @@ -742,6 +752,16 @@ static int awcc_hwmon_read(struct device *dev,
>> enum hwmon_sensor_types type,
>> return -EOPNOTSUPP;
>> }
>>
>> + break;
>> + case hwmon_pwm:
>> + fan = &priv->fan_data[channel];
>> +
>> + ret = awcc_thermal_information(priv->wdev,
>> AWCC_OP_GET_FAN_BOOST,
>> + fan->id, &fan_boost);
>> + if (ret)
>> + return ret;
>> +
>> + *val = fan_boost;
>> break;
>> default:
>> return -EOPNOTSUPP;
>> @@ -796,10 +816,27 @@ static int awcc_hwmon_read_string(struct device
>> *dev, enum hwmon_sensor_types ty
>> return 0;
>> }
>>
>> +
I nearly forgot: Please don't use multiple blank lines.
Thanks,
Armin Wolf
>>
>> +static int awcc_hwmon_write(struct device *dev, enum
>> hwmon_sensor_types type,
>> + u32 attr, int channel, long val)
>> +{
>> + struct awcc_priv *priv = dev_get_drvdata(dev);
>> + u8 fan_id = priv->fan_data[channel].id;
>> +
>> + switch (type) {
>> + case hwmon_pwm:
>> + return awcc_thermal_control(priv->wdev, AWCC_OP_SET_FAN_BOOST,
>> + fan_id, (u8)clamp_val(val, 0, 255));
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> +}
>> +
>> static const struct hwmon_ops awcc_hwmon_ops = {
>> .is_visible = awcc_hwmon_is_visible,
>> .read = awcc_hwmon_read,
>> .read_string = awcc_hwmon_read_string,
>> + .write = awcc_hwmon_write,
>> };
>>
>> static const struct hwmon_channel_info * const awcc_hwmon_info[] = {
>> @@ -814,6 +851,12 @@ static const struct hwmon_channel_info * const
>> awcc_hwmon_info[] = {
>> HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN |
>> HWMON_F_MAX,
>> HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN |
>> HWMON_F_MAX
>> ),
>> + HWMON_CHANNEL_INFO(pwm,
>> + HWMON_PWM_INPUT,
>> + HWMON_PWM_INPUT,
>> + HWMON_PWM_INPUT,
>> + HWMON_PWM_INPUT
>> + ),
>> NULL
>> };
>>
>> @@ -954,8 +997,8 @@ static int awcc_platform_profile_set(struct
>> device *dev,
>> }
>> }
>>
>> - return awcc_thermal_control(priv->wdev,
>> - priv->supported_profiles[profile]);
>> + return awcc_thermal_control(priv->wdev, AWCC_OP_ACTIVATE_PROFILE,
>> + priv->supported_profiles[profile], 0);
>> }
>>
>> static int awcc_platform_profile_probe(void *drvdata, unsigned long
>> *choices)
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 10/10] platform/x86: alienware-wmi: Improve and update documentation
2025-02-08 5:16 ` [PATCH 10/10] platform/x86: alienware-wmi: Improve and update documentation Kurt Borja
@ 2025-02-16 6:22 ` Armin Wolf
2025-02-16 20:51 ` Kurt Borja
0 siblings, 1 reply; 25+ messages in thread
From: Armin Wolf @ 2025-02-16 6:22 UTC (permalink / raw)
To: Kurt Borja, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel
Am 08.02.25 um 06:16 schrieb Kurt Borja:
> Use tables to describe method operations instead of using pseudo-code.
> Drop unknown method descriptions to avoid redundancy. Drop GPIO section
> as it is currently irrelevant to this driver. Update Thermal_Information
> method documentation. Add one more helpful developer to the kudos section.
>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> Documentation/wmi/devices/alienware-wmi.rst | 390 ++++++--------------
> 1 file changed, 122 insertions(+), 268 deletions(-)
>
> diff --git a/Documentation/wmi/devices/alienware-wmi.rst b/Documentation/wmi/devices/alienware-wmi.rst
> index ddc5e561960e..2bb3abfd9cd7 100644
> --- a/Documentation/wmi/devices/alienware-wmi.rst
> +++ b/Documentation/wmi/devices/alienware-wmi.rst
> @@ -11,7 +11,7 @@ The WMI device WMAX has been implemented for many Alienware and Dell's G-Series
> models. Throughout these models, two implementations have been identified. The
> first one, used by older systems, deals with HDMI, brightness, RGB, amplifier
> and deep sleep control. The second one used by newer systems deals primarily
> -with thermal, overclocking, and GPIO control.
> +with thermal control and overclocking.
>
> It is suspected that the latter is used by Alienware Command Center (AWCC) to
> manage manufacturer predefined thermal profiles. The alienware-wmi driver
> @@ -69,9 +69,6 @@ data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
> [WmiMethodId(164), Implemented, read, write, Description("Tobii Camera Power Off.")] void TobiiCameraPowerOff([out] uint32 argr);
> };
>
> -Some of these methods get quite intricate so we will describe them using
> -pseudo-code that vaguely resembles the original ASL code.
> -
> Methods not described in the following document have unknown behavior.
>
> Argument Structure
> @@ -87,175 +84,136 @@ ID 0xA0, the argument you would pass to the method is 0xA001.
> Thermal Methods
> ===============
>
> -WMI method Thermal_Information([in] uint32 arg2, [out] uint32 argr)
> --------------------------------------------------------------------
> -
> -::
> -
> - if BYTE_0(arg2) == 0x01:
> - argr = 1
> -
> - if BYTE_0(arg2) == 0x02:
> - argr = SYSTEM_DESCRIPTION
> -
> - if BYTE_0(arg2) == 0x03:
> - if BYTE_1(arg2) == 0x00:
> - argr = FAN_ID_0
> -
> - if BYTE_1(arg2) == 0x01:
> - argr = FAN_ID_1
> -
> - if BYTE_1(arg2) == 0x02:
> - argr = FAN_ID_2
> -
> - if BYTE_1(arg2) == 0x03:
> - argr = FAN_ID_3
> -
> - if BYTE_1(arg2) == 0x04:
> - argr = SENSOR_ID_CPU | 0x0100
> -
> - if BYTE_1(arg2) == 0x05:
> - argr = SENSOR_ID_GPU | 0x0100
> -
> - if BYTE_1(arg2) == 0x06:
> - argr = THERMAL_MODE_QUIET_ID
> -
> - if BYTE_1(arg2) == 0x07:
> - argr = THERMAL_MODE_BALANCED_ID
> -
> - if BYTE_1(arg2) == 0x08:
> - argr = THERMAL_MODE_BALANCED_PERFORMANCE_ID
> -
> - if BYTE_1(arg2) == 0x09:
> - argr = THERMAL_MODE_PERFORMANCE_ID
> -
> - if BYTE_1(arg2) == 0x0A:
> - argr = THERMAL_MODE_LOW_POWER_ID
> -
> - if BYTE_1(arg2) == 0x0B:
> - argr = THERMAL_MODE_GMODE_ID
> -
> - else:
> - argr = 0xFFFFFFFF
> -
> - if BYTE_0(arg2) == 0x04:
> - if is_valid_sensor(BYTE_1(arg2)):
> - argr = SENSOR_TEMP_C
> - else:
> - argr = 0xFFFFFFFF
> -
> - if BYTE_0(arg2) == 0x05:
> - if is_valid_fan(BYTE_1(arg2)):
> - argr = FAN_RPM()
> -
> - if BYTE_0(arg2) == 0x06:
> - skip
> -
> - if BYTE_0(arg2) == 0x07:
> - argr = 0
> -
> - If BYTE_0(arg2) == 0x08:
> - if is_valid_fan(BYTE_1(arg2)):
> - argr = 0
> - else:
> - argr = 0xFFFFFFFF
> -
> - if BYTE_0(arg2) == 0x09:
> - if is_valid_fan(BYTE_1(arg2)):
> - argr = FAN_UNKNOWN_STAT_0()
> -
> - else:
> - argr = 0xFFFFFFFF
> -
> - if BYTE_0(arg2) == 0x0A:
> - argr = THERMAL_MODE_BALANCED_ID
> -
> - if BYTE_0(arg2) == 0x0B:
> - argr = CURRENT_THERMAL_MODE()
> -
> - if BYTE_0(arg2) == 0x0C:
> - if is_valid_fan(BYTE_1(arg2)):
> - argr = FAN_UNKNOWN_STAT_1()
> - else:
> - argr = 0xFFFFFFFF
> -
> -Operation 0x02 returns a *system description* buffer with the following
> -structure:
> -
> -::
> -
> - out[0] -> Number of fans
> - out[1] -> Number of sensors
> - out[2] -> 0x00
> - out[3] -> Number of thermal modes
> +WMI method GetFanSensors([in] uint32 arg2, [out] uint32 argr)
> +-------------------------------------------------------------
>
> -Operation 0x03 list all available fan IDs, sensor IDs and thermal profile
> -codes in order, but different models may have different number of fans and
> -thermal profiles. These are the known ranges:
> ++--------------------+------------------------------------+--------------------+
> +| Operation (Byte 0) | Description | Arguments |
> ++====================+====================================+====================+
> +| 0x01 | Get the number of fans for a given | - Byte 1: Fan ID |
> +| | fan ID. | |
> ++--------------------+------------------------------------+--------------------+
> +| 0x02 | Get the temperature sensor ID | - Byte 1: Fan ID |
> +| | related to a fan sensor ID | |
> ++--------------------+------------------------------------+--------------------+
After looking at a acpidump from a alienware machine i discovered that operation 0x02
takes a second argument which acts like an index.
Could it be that operation 0x01 actually returns the number of temperature sensors
associated with a given fan which can then be queried using operation 0x02?
>
> -* Fan IDs: from 2 up to 4
> -* Sensor IDs: 2
> -* Thermal profile codes: from 1 up to 7
> +WMI method Thermal_Information([in] uint32 arg2, [out] uint32 argr)
> +-------------------------------------------------------------------
>
> -In total BYTE_1(ARG2) may range from 0x5 up to 0xD depending on the model.
> ++--------------------+------------------------------------+--------------------+
> +| Operation (Byte 0) | Description | Arguments |
> ++====================+====================================+====================+
> +| 0x01 | Unknown. | - None |
> ++--------------------+------------------------------------+--------------------+
> +| 0x02 | Get system description number with | - None |
> +| | the following structure: | |
> +| | | |
> +| | - Byte 0: Number of fans | |
> +| | - Byte 1: Number of temperature | |
> +| | sensors | |
> +| | - Byte 2: Unknown | |
> +| | - Byte 3: Number of thermal | |
> +| | profiles | |
> ++--------------------+------------------------------------+--------------------+
> +| 0x03 | List an ID or resource at a given | - Byte 1: Index |
> +| | index. Fan IDs, temperature IDs, | |
> +| | unknown IDs and thermal profile | |
> +| | IDs are listed in that exact | |
> +| | order. | |
> +| | | |
> +| | Operation 0x02 is used to know | |
> +| | which indexes map to which | |
> +| | resources. | |
> +| | | |
> +| | **Returns:** ID at a given index | |
> ++--------------------+------------------------------------+--------------------+
> +| 0x04 | Get the current temperature for a | - Byte 1: Sensor |
> +| | given temperature sensor. | ID |
> ++--------------------+------------------------------------+--------------------+
> +| 0x05 | Get the current RPM for a given | - Byte 1: Fan ID |
> +| | fan. | |
> ++--------------------+------------------------------------+--------------------+
> +| 0x06 | Get fan speed percentage. (not | - Byte 1: Fan ID |
> +| | implemented in every model) | |
> ++--------------------+------------------------------------+--------------------+
> +| 0x07 | Unknown. | - Unknown |
> ++--------------------+------------------------------------+--------------------+
> +| 0x08 | Get minimum RPM for a given FAN | - Byte 1: Fan ID |
> +| | ID. | |
> ++--------------------+------------------------------------+--------------------+
> +| 0x09 | Get maximum RPM for a given FAN | - Byte 1: Fan ID |
> +| | ID. | |
> ++--------------------+------------------------------------+--------------------+
> +| 0x09 | Get maximum RPM for a given FAN | - Byte 1: Fan ID |
> +| | ID. | |
> ++--------------------+------------------------------------+--------------------+
Duplicated entry here.
Other than that the new documentation looks quite good.
Thanks,
Armin Wolf
> +| 0x0A | Get balanced thermal profile ID. | - None |
> ++--------------------+------------------------------------+--------------------+
> +| 0x0B | Get current thermal profile ID. | - None |
> ++--------------------+------------------------------------+--------------------+
> +| 0x0C | Get current `boost` value for a | - Byte 1: Fan ID |
> +| | given fan ID. | |
> ++--------------------+------------------------------------+--------------------+
>
> WMI method Thermal_Control([in] uint32 arg2, [out] uint32 argr)
> ---------------------------------------------------------------
>
> -::
> -
> - if BYTE_0(arg2) == 0x01:
> - if is_valid_thermal_profile(BYTE_1(arg2)):
> - SET_THERMAL_PROFILE(BYTE_1(arg2))
> - argr = 0
> -
> - if BYTE_0(arg2) == 0x02:
> - if is_valid_fan(BYTE_1(arg2)):
> - SET_FAN_SPEED_MULTIPLIER(BYTE_2(arg2))
> - argr = 0
> - else:
> - argr = 0xFFFFFFFF
> -
> -.. note::
> - While you can manually change the fan speed multiplier with this method,
> - Dell's BIOS tends to overwrite this changes anyway.
> ++--------------------+------------------------------------+--------------------+
> +| Operation (Byte 0) | Description | Arguments |
> ++====================+====================================+====================+
> +| 0x01 | Activate a given thermal profile. | - Byte 1: Thermal |
> +| | | profile ID |
> ++--------------------+------------------------------------+--------------------+
> +| 0x02 | Set a `boost` value for a given | - Byte 1: Fan ID |
> +| | fan ID. | - Byte 2: Boost |
> ++--------------------+------------------------------------+--------------------+
>
> These are the known thermal profile codes:
>
> -::
> -
> - CUSTOM 0x00
> -
> - BALANCED_USTT 0xA0
> - BALANCED_PERFORMANCE_USTT 0xA1
> - COOL_USTT 0xA2
> - QUIET_USTT 0xA3
> - PERFORMANCE_USTT 0xA4
> - LOW_POWER_USTT 0xA5
> -
> - QUIET 0x96
> - BALANCED 0x97
> - BALANCED_PERFORMANCE 0x98
> - PERFORMANCE 0x99
> -
> - GMODE 0xAB
> -
> -Usually if a model doesn't support the first four profiles they will support
> -the User Selectable Thermal Tables (USTT) profiles and vice-versa.
> -
> -GMODE replaces PERFORMANCE in G-Series laptops.
> ++------------------------------+----------+------+
> +| Thermal Profile | Type | ID |
> ++==============================+==========+======+
> +| Custom | Special | 0x00 |
> ++------------------------------+----------+------+
> +| G-Mode | Special | 0xAB |
> ++------------------------------+----------+------+
> +| Quiet | Legacy | 0x96 |
> ++------------------------------+----------+------+
> +| Balanced | Legacy | 0x97 |
> ++------------------------------+----------+------+
> +| Balanced Performance | Legacy | 0x98 |
> ++------------------------------+----------+------+
> +| Performance | Legacy | 0x99 |
> ++------------------------------+----------+------+
> +| Balanced | USTT | 0xA0 |
> ++------------------------------+----------+------+
> +| Balanced Performance | USTT | 0xA1 |
> ++------------------------------+----------+------+
> +| Cool | USTT | 0xA2 |
> ++------------------------------+----------+------+
> +| Quiet | USTT | 0xA3 |
> ++------------------------------+----------+------+
> +| Performance | USTT | 0xA4 |
> ++------------------------------+----------+------+
> +| Low Power | USTT | 0xA5 |
> ++------------------------------+----------+------+
> +
> +If a model supports the User Selectable Thermal Tables (USTT) profiles, it will
> +not support the Legacy profiles and vice-versa.
> +
> +Every model supports the CUSTOM (0x00) thermal profile. GMODE replaces
> +PERFORMANCE in G-Series laptops.
>
> WMI method GameShiftStatus([in] uint32 arg2, [out] uint32 argr)
> ---------------------------------------------------------------
>
> -::
> -
> - if BYTE_0(arg2) == 0x1:
> - TOGGLE_GAME_SHIFT()
> - argr = GET_GAME_SHIFT_STATUS()
> -
> - if BYTE_0(arg2) == 0x2:
> - argr = GET_GAME_SHIFT_STATUS()
> ++--------------------+------------------------------------+--------------------+
> +| Operation (Byte 0) | Description | Arguments |
> ++====================+====================================+====================+
> +| 0x01 | Toggle *Game Shift*. | - None |
> ++--------------------+------------------------------------+--------------------+
> +| 0x02 | Get *Game Shift* status. | - None |
> ++--------------------+------------------------------------+--------------------+
>
> Game Shift Status does not change the fan speed profile but it could be some
> sort of CPU/GPU power profile. Benchmarks have not been done.
> @@ -267,131 +225,27 @@ Thermal_Information does not list it.
> G-key on Dell's G-Series laptops also changes Game Shift status, so both are
> directly related.
>
> -WMI method GetFanSensors([in] uint32 arg2, [out] uint32 argr)
> --------------------------------------------------------------
> -
> -::
> -
> - if BYTE_0(arg2) == 0x1:
> - if is_valid_fan(BYTE_1(arg2)):
> - argr = 1
> - else:
> - argr = 0
> -
> - if BYTE_0(arg2) == 0x2:
> - if is_valid_fan(BYTE_1(arg2)):
> - if BYTE_2(arg2) == 0:
> - argr == SENSOR_ID
> - else
> - argr == 0xFFFFFFFF
> - else:
> - argr = 0
> -
> Overclocking Methods
> ====================
>
> -.. warning::
> - These methods have not been tested and are only partially reverse
> - engineered.
> -
> -WMI method Return_OverclockingReport([out] uint32 argr)
> --------------------------------------------------------
> -
> -::
> -
> - CSMI (0xE3, 0x99)
> - argr = 0
> -
> -CSMI is an unknown operation.
> -
> -WMI method Set_OCUIBIOSControl([in] uint32 arg2, [out] uint32 argr)
> --------------------------------------------------------------------
> -
> -::
> -
> - CSMI (0xE3, 0x99)
> - argr = 0
> -
> -CSMI is an unknown operation.
> -
> -WMI method Clear_OCFailSafeFlag([out] uint32 argr)
> ---------------------------------------------------
> -
> -::
> -
> - CSMI (0xE3, 0x99)
> - argr = 0
> -
> -CSMI is an unknown operation.
> -
> -
> WMI method MemoryOCControl([in] uint32 arg2, [out] uint32 argr)
> ---------------------------------------------------------------
>
> AWCC supports memory overclocking, but this method is very intricate and has
> not been deciphered yet.
>
> -GPIO methods
> -============
> -
> -These methods are probably related to some kind of firmware update system,
> -through a GPIO device.
> -
> -.. warning::
> - These methods have not been tested and are only partially reverse
> - engineered.
> -
> -WMI method FWUpdateGPIOtoggle([in] uint32 arg2, [out] uint32 argr)
> -------------------------------------------------------------------
> -
> -::
> -
> - if BYTE_0(arg2) == 0:
> - if BYTE_1(arg2) == 1:
> - SET_PIN_A_HIGH()
> - else:
> - SET_PIN_A_LOW()
> -
> - if BYTE_0(arg2) == 1:
> - if BYTE_1(arg2) == 1:
> - SET_PIN_B_HIGH()
> -
> - else:
> - SET_PIN_B_LOW()
> -
> - else:
> - argr = 1
> -
> -WMI method ReadTotalofGPIOs([out] uint32 argr)
> -----------------------------------------------
> -
> -::
> -
> - argr = 0x02
> -
> -WMI method ReadGPIOpPinStatus([in] uint32 arg2, [out] uint32 argr)
> -------------------------------------------------------------------
> -
> -::
> -
> - if BYTE_0(arg2) == 0:
> - argr = PIN_A_STATUS
> -
> - if BYTE_0(arg2) == 1:
> - argr = PIN_B_STATUS
> -
> Other information Methods
> =========================
>
> WMI method ReadChassisColor([out] uint32 argr)
> ----------------------------------------------
>
> -::
> -
> - argr = CHASSIS_COLOR_ID
> +Returns the chassis color internal ID.
>
> Acknowledgements
> ================
>
> -Kudos to `AlexIII <https://github.com/AlexIII/tcc-g15>`_ for documenting
> -and testing available thermal profile codes.
> +Kudos to `AlexIII <https://github.com/AlexIII/tcc-g15>`_ and
> +`T-Troll <https://github.com/T-Troll/alienfx-tools/>`_ for documenting and
> +testing some of this device's functionality, making it possible to generalize
> +this driver.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 10/10] platform/x86: alienware-wmi: Improve and update documentation
2025-02-16 6:22 ` Armin Wolf
@ 2025-02-16 20:51 ` Kurt Borja
[not found] ` <198a578f-99d6-4d98-8186-eab141a2550d@gmx.de>
0 siblings, 1 reply; 25+ messages in thread
From: Kurt Borja @ 2025-02-16 20:51 UTC (permalink / raw)
To: Armin Wolf, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel
On Sun Feb 16, 2025 at 1:22 AM -05, Armin Wolf wrote:
> Am 08.02.25 um 06:16 schrieb Kurt Borja:
>
>> Use tables to describe method operations instead of using pseudo-code.
>> Drop unknown method descriptions to avoid redundancy. Drop GPIO section
>> as it is currently irrelevant to this driver. Update Thermal_Information
>> method documentation. Add one more helpful developer to the kudos section.
>>
>> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
>> ---
>> Documentation/wmi/devices/alienware-wmi.rst | 390 ++++++--------------
>> 1 file changed, 122 insertions(+), 268 deletions(-)
>>
>> diff --git a/Documentation/wmi/devices/alienware-wmi.rst b/Documentation/wmi/devices/alienware-wmi.rst
>> index ddc5e561960e..2bb3abfd9cd7 100644
>> --- a/Documentation/wmi/devices/alienware-wmi.rst
>> +++ b/Documentation/wmi/devices/alienware-wmi.rst
>> @@ -11,7 +11,7 @@ The WMI device WMAX has been implemented for many Alienware and Dell's G-Series
>> models. Throughout these models, two implementations have been identified. The
>> first one, used by older systems, deals with HDMI, brightness, RGB, amplifier
>> and deep sleep control. The second one used by newer systems deals primarily
>> -with thermal, overclocking, and GPIO control.
>> +with thermal control and overclocking.
>>
>> It is suspected that the latter is used by Alienware Command Center (AWCC) to
>> manage manufacturer predefined thermal profiles. The alienware-wmi driver
>> @@ -69,9 +69,6 @@ data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
>> [WmiMethodId(164), Implemented, read, write, Description("Tobii Camera Power Off.")] void TobiiCameraPowerOff([out] uint32 argr);
>> };
>>
>> -Some of these methods get quite intricate so we will describe them using
>> -pseudo-code that vaguely resembles the original ASL code.
>> -
>> Methods not described in the following document have unknown behavior.
>>
>> Argument Structure
>> @@ -87,175 +84,136 @@ ID 0xA0, the argument you would pass to the method is 0xA001.
>> Thermal Methods
>> ===============
>>
>> -WMI method Thermal_Information([in] uint32 arg2, [out] uint32 argr)
>> --------------------------------------------------------------------
>> -
>> -::
>> -
>> - if BYTE_0(arg2) == 0x01:
>> - argr = 1
>> -
>> - if BYTE_0(arg2) == 0x02:
>> - argr = SYSTEM_DESCRIPTION
>> -
>> - if BYTE_0(arg2) == 0x03:
>> - if BYTE_1(arg2) == 0x00:
>> - argr = FAN_ID_0
>> -
>> - if BYTE_1(arg2) == 0x01:
>> - argr = FAN_ID_1
>> -
>> - if BYTE_1(arg2) == 0x02:
>> - argr = FAN_ID_2
>> -
>> - if BYTE_1(arg2) == 0x03:
>> - argr = FAN_ID_3
>> -
>> - if BYTE_1(arg2) == 0x04:
>> - argr = SENSOR_ID_CPU | 0x0100
>> -
>> - if BYTE_1(arg2) == 0x05:
>> - argr = SENSOR_ID_GPU | 0x0100
>> -
>> - if BYTE_1(arg2) == 0x06:
>> - argr = THERMAL_MODE_QUIET_ID
>> -
>> - if BYTE_1(arg2) == 0x07:
>> - argr = THERMAL_MODE_BALANCED_ID
>> -
>> - if BYTE_1(arg2) == 0x08:
>> - argr = THERMAL_MODE_BALANCED_PERFORMANCE_ID
>> -
>> - if BYTE_1(arg2) == 0x09:
>> - argr = THERMAL_MODE_PERFORMANCE_ID
>> -
>> - if BYTE_1(arg2) == 0x0A:
>> - argr = THERMAL_MODE_LOW_POWER_ID
>> -
>> - if BYTE_1(arg2) == 0x0B:
>> - argr = THERMAL_MODE_GMODE_ID
>> -
>> - else:
>> - argr = 0xFFFFFFFF
>> -
>> - if BYTE_0(arg2) == 0x04:
>> - if is_valid_sensor(BYTE_1(arg2)):
>> - argr = SENSOR_TEMP_C
>> - else:
>> - argr = 0xFFFFFFFF
>> -
>> - if BYTE_0(arg2) == 0x05:
>> - if is_valid_fan(BYTE_1(arg2)):
>> - argr = FAN_RPM()
>> -
>> - if BYTE_0(arg2) == 0x06:
>> - skip
>> -
>> - if BYTE_0(arg2) == 0x07:
>> - argr = 0
>> -
>> - If BYTE_0(arg2) == 0x08:
>> - if is_valid_fan(BYTE_1(arg2)):
>> - argr = 0
>> - else:
>> - argr = 0xFFFFFFFF
>> -
>> - if BYTE_0(arg2) == 0x09:
>> - if is_valid_fan(BYTE_1(arg2)):
>> - argr = FAN_UNKNOWN_STAT_0()
>> -
>> - else:
>> - argr = 0xFFFFFFFF
>> -
>> - if BYTE_0(arg2) == 0x0A:
>> - argr = THERMAL_MODE_BALANCED_ID
>> -
>> - if BYTE_0(arg2) == 0x0B:
>> - argr = CURRENT_THERMAL_MODE()
>> -
>> - if BYTE_0(arg2) == 0x0C:
>> - if is_valid_fan(BYTE_1(arg2)):
>> - argr = FAN_UNKNOWN_STAT_1()
>> - else:
>> - argr = 0xFFFFFFFF
>> -
>> -Operation 0x02 returns a *system description* buffer with the following
>> -structure:
>> -
>> -::
>> -
>> - out[0] -> Number of fans
>> - out[1] -> Number of sensors
>> - out[2] -> 0x00
>> - out[3] -> Number of thermal modes
>> +WMI method GetFanSensors([in] uint32 arg2, [out] uint32 argr)
>> +-------------------------------------------------------------
>>
>> -Operation 0x03 list all available fan IDs, sensor IDs and thermal profile
>> -codes in order, but different models may have different number of fans and
>> -thermal profiles. These are the known ranges:
>> ++--------------------+------------------------------------+--------------------+
>> +| Operation (Byte 0) | Description | Arguments |
>> ++====================+====================================+====================+
>> +| 0x01 | Get the number of fans for a given | - Byte 1: Fan ID |
>> +| | fan ID. | |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x02 | Get the temperature sensor ID | - Byte 1: Fan ID |
>> +| | related to a fan sensor ID | |
>> ++--------------------+------------------------------------+--------------------+
>
> After looking at a acpidump from a alienware machine i discovered that operation 0x02
> takes a second argument which acts like an index.
>
> Could it be that operation 0x01 actually returns the number of temperature sensors
> associated with a given fan which can then be queried using operation 0x02?
After reading a couple of acpidumps, I think you might be right.
It's hard to know because in all laptops I've seen, operation 0x01
returns 1 for all fans. The only exception is the Alienware Aurora
series, where it seems your hypothesis holds true. However even in this
model, op 0x02 list all sensors for almost all fans so I don't know how
to interpret that.
Anyway, I'm going with your hypothesis because 0x01 does give the
correct upper bound for op 0x02 index. I'll think how to reconcile this
with my approach for fan labels.
Btw, what is the model of the acpidump you mentioned? Can you please
send it to me? I keep a private stash of acpidumps for alienware
devices.
>
>>
>> -* Fan IDs: from 2 up to 4
>> -* Sensor IDs: 2
>> -* Thermal profile codes: from 1 up to 7
>> +WMI method Thermal_Information([in] uint32 arg2, [out] uint32 argr)
>> +-------------------------------------------------------------------
>>
>> -In total BYTE_1(ARG2) may range from 0x5 up to 0xD depending on the model.
>> ++--------------------+------------------------------------+--------------------+
>> +| Operation (Byte 0) | Description | Arguments |
>> ++====================+====================================+====================+
>> +| 0x01 | Unknown. | - None |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x02 | Get system description number with | - None |
>> +| | the following structure: | |
>> +| | | |
>> +| | - Byte 0: Number of fans | |
>> +| | - Byte 1: Number of temperature | |
>> +| | sensors | |
>> +| | - Byte 2: Unknown | |
>> +| | - Byte 3: Number of thermal | |
>> +| | profiles | |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x03 | List an ID or resource at a given | - Byte 1: Index |
>> +| | index. Fan IDs, temperature IDs, | |
>> +| | unknown IDs and thermal profile | |
>> +| | IDs are listed in that exact | |
>> +| | order. | |
>> +| | | |
>> +| | Operation 0x02 is used to know | |
>> +| | which indexes map to which | |
>> +| | resources. | |
>> +| | | |
>> +| | **Returns:** ID at a given index | |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x04 | Get the current temperature for a | - Byte 1: Sensor |
>> +| | given temperature sensor. | ID |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x05 | Get the current RPM for a given | - Byte 1: Fan ID |
>> +| | fan. | |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x06 | Get fan speed percentage. (not | - Byte 1: Fan ID |
>> +| | implemented in every model) | |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x07 | Unknown. | - Unknown |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x08 | Get minimum RPM for a given FAN | - Byte 1: Fan ID |
>> +| | ID. | |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x09 | Get maximum RPM for a given FAN | - Byte 1: Fan ID |
>> +| | ID. | |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x09 | Get maximum RPM for a given FAN | - Byte 1: Fan ID |
>> +| | ID. | |
>> ++--------------------+------------------------------------+--------------------+
>
> Duplicated entry here.
Thanks!
>
> Other than that the new documentation looks quite good.
The old one was quite ugly so I'm happy with this too :p
--
~ Kurt
>
> Thanks,
> Armin Wolf
>
>> +| 0x0A | Get balanced thermal profile ID. | - None |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x0B | Get current thermal profile ID. | - None |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x0C | Get current `boost` value for a | - Byte 1: Fan ID |
>> +| | given fan ID. | |
>> ++--------------------+------------------------------------+--------------------+
>>
>> WMI method Thermal_Control([in] uint32 arg2, [out] uint32 argr)
>> ---------------------------------------------------------------
>>
>> -::
>> -
>> - if BYTE_0(arg2) == 0x01:
>> - if is_valid_thermal_profile(BYTE_1(arg2)):
>> - SET_THERMAL_PROFILE(BYTE_1(arg2))
>> - argr = 0
>> -
>> - if BYTE_0(arg2) == 0x02:
>> - if is_valid_fan(BYTE_1(arg2)):
>> - SET_FAN_SPEED_MULTIPLIER(BYTE_2(arg2))
>> - argr = 0
>> - else:
>> - argr = 0xFFFFFFFF
>> -
>> -.. note::
>> - While you can manually change the fan speed multiplier with this method,
>> - Dell's BIOS tends to overwrite this changes anyway.
>> ++--------------------+------------------------------------+--------------------+
>> +| Operation (Byte 0) | Description | Arguments |
>> ++====================+====================================+====================+
>> +| 0x01 | Activate a given thermal profile. | - Byte 1: Thermal |
>> +| | | profile ID |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x02 | Set a `boost` value for a given | - Byte 1: Fan ID |
>> +| | fan ID. | - Byte 2: Boost |
>> ++--------------------+------------------------------------+--------------------+
>>
>> These are the known thermal profile codes:
>>
>> -::
>> -
>> - CUSTOM 0x00
>> -
>> - BALANCED_USTT 0xA0
>> - BALANCED_PERFORMANCE_USTT 0xA1
>> - COOL_USTT 0xA2
>> - QUIET_USTT 0xA3
>> - PERFORMANCE_USTT 0xA4
>> - LOW_POWER_USTT 0xA5
>> -
>> - QUIET 0x96
>> - BALANCED 0x97
>> - BALANCED_PERFORMANCE 0x98
>> - PERFORMANCE 0x99
>> -
>> - GMODE 0xAB
>> -
>> -Usually if a model doesn't support the first four profiles they will support
>> -the User Selectable Thermal Tables (USTT) profiles and vice-versa.
>> -
>> -GMODE replaces PERFORMANCE in G-Series laptops.
>> ++------------------------------+----------+------+
>> +| Thermal Profile | Type | ID |
>> ++==============================+==========+======+
>> +| Custom | Special | 0x00 |
>> ++------------------------------+----------+------+
>> +| G-Mode | Special | 0xAB |
>> ++------------------------------+----------+------+
>> +| Quiet | Legacy | 0x96 |
>> ++------------------------------+----------+------+
>> +| Balanced | Legacy | 0x97 |
>> ++------------------------------+----------+------+
>> +| Balanced Performance | Legacy | 0x98 |
>> ++------------------------------+----------+------+
>> +| Performance | Legacy | 0x99 |
>> ++------------------------------+----------+------+
>> +| Balanced | USTT | 0xA0 |
>> ++------------------------------+----------+------+
>> +| Balanced Performance | USTT | 0xA1 |
>> ++------------------------------+----------+------+
>> +| Cool | USTT | 0xA2 |
>> ++------------------------------+----------+------+
>> +| Quiet | USTT | 0xA3 |
>> ++------------------------------+----------+------+
>> +| Performance | USTT | 0xA4 |
>> ++------------------------------+----------+------+
>> +| Low Power | USTT | 0xA5 |
>> ++------------------------------+----------+------+
>> +
>> +If a model supports the User Selectable Thermal Tables (USTT) profiles, it will
>> +not support the Legacy profiles and vice-versa.
>> +
>> +Every model supports the CUSTOM (0x00) thermal profile. GMODE replaces
>> +PERFORMANCE in G-Series laptops.
>>
>> WMI method GameShiftStatus([in] uint32 arg2, [out] uint32 argr)
>> ---------------------------------------------------------------
>>
>> -::
>> -
>> - if BYTE_0(arg2) == 0x1:
>> - TOGGLE_GAME_SHIFT()
>> - argr = GET_GAME_SHIFT_STATUS()
>> -
>> - if BYTE_0(arg2) == 0x2:
>> - argr = GET_GAME_SHIFT_STATUS()
>> ++--------------------+------------------------------------+--------------------+
>> +| Operation (Byte 0) | Description | Arguments |
>> ++====================+====================================+====================+
>> +| 0x01 | Toggle *Game Shift*. | - None |
>> ++--------------------+------------------------------------+--------------------+
>> +| 0x02 | Get *Game Shift* status. | - None |
>> ++--------------------+------------------------------------+--------------------+
>>
>> Game Shift Status does not change the fan speed profile but it could be some
>> sort of CPU/GPU power profile. Benchmarks have not been done.
>> @@ -267,131 +225,27 @@ Thermal_Information does not list it.
>> G-key on Dell's G-Series laptops also changes Game Shift status, so both are
>> directly related.
>>
>> -WMI method GetFanSensors([in] uint32 arg2, [out] uint32 argr)
>> --------------------------------------------------------------
>> -
>> -::
>> -
>> - if BYTE_0(arg2) == 0x1:
>> - if is_valid_fan(BYTE_1(arg2)):
>> - argr = 1
>> - else:
>> - argr = 0
>> -
>> - if BYTE_0(arg2) == 0x2:
>> - if is_valid_fan(BYTE_1(arg2)):
>> - if BYTE_2(arg2) == 0:
>> - argr == SENSOR_ID
>> - else
>> - argr == 0xFFFFFFFF
>> - else:
>> - argr = 0
>> -
>> Overclocking Methods
>> ====================
>>
>> -.. warning::
>> - These methods have not been tested and are only partially reverse
>> - engineered.
>> -
>> -WMI method Return_OverclockingReport([out] uint32 argr)
>> --------------------------------------------------------
>> -
>> -::
>> -
>> - CSMI (0xE3, 0x99)
>> - argr = 0
>> -
>> -CSMI is an unknown operation.
>> -
>> -WMI method Set_OCUIBIOSControl([in] uint32 arg2, [out] uint32 argr)
>> --------------------------------------------------------------------
>> -
>> -::
>> -
>> - CSMI (0xE3, 0x99)
>> - argr = 0
>> -
>> -CSMI is an unknown operation.
>> -
>> -WMI method Clear_OCFailSafeFlag([out] uint32 argr)
>> ---------------------------------------------------
>> -
>> -::
>> -
>> - CSMI (0xE3, 0x99)
>> - argr = 0
>> -
>> -CSMI is an unknown operation.
>> -
>> -
>> WMI method MemoryOCControl([in] uint32 arg2, [out] uint32 argr)
>> ---------------------------------------------------------------
>>
>> AWCC supports memory overclocking, but this method is very intricate and has
>> not been deciphered yet.
>>
>> -GPIO methods
>> -============
>> -
>> -These methods are probably related to some kind of firmware update system,
>> -through a GPIO device.
>> -
>> -.. warning::
>> - These methods have not been tested and are only partially reverse
>> - engineered.
>> -
>> -WMI method FWUpdateGPIOtoggle([in] uint32 arg2, [out] uint32 argr)
>> -------------------------------------------------------------------
>> -
>> -::
>> -
>> - if BYTE_0(arg2) == 0:
>> - if BYTE_1(arg2) == 1:
>> - SET_PIN_A_HIGH()
>> - else:
>> - SET_PIN_A_LOW()
>> -
>> - if BYTE_0(arg2) == 1:
>> - if BYTE_1(arg2) == 1:
>> - SET_PIN_B_HIGH()
>> -
>> - else:
>> - SET_PIN_B_LOW()
>> -
>> - else:
>> - argr = 1
>> -
>> -WMI method ReadTotalofGPIOs([out] uint32 argr)
>> -----------------------------------------------
>> -
>> -::
>> -
>> - argr = 0x02
>> -
>> -WMI method ReadGPIOpPinStatus([in] uint32 arg2, [out] uint32 argr)
>> -------------------------------------------------------------------
>> -
>> -::
>> -
>> - if BYTE_0(arg2) == 0:
>> - argr = PIN_A_STATUS
>> -
>> - if BYTE_0(arg2) == 1:
>> - argr = PIN_B_STATUS
>> -
>> Other information Methods
>> =========================
>>
>> WMI method ReadChassisColor([out] uint32 argr)
>> ----------------------------------------------
>>
>> -::
>> -
>> - argr = CHASSIS_COLOR_ID
>> +Returns the chassis color internal ID.
>>
>> Acknowledgements
>> ================
>>
>> -Kudos to `AlexIII <https://github.com/AlexIII/tcc-g15>`_ for documenting
>> -and testing available thermal profile codes.
>> +Kudos to `AlexIII <https://github.com/AlexIII/tcc-g15>`_ and
>> +`T-Troll <https://github.com/T-Troll/alienfx-tools/>`_ for documenting and
>> +testing some of this device's functionality, making it possible to generalize
>> +this driver.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 08/10] platform/x86: alienware-wmi-wmax: Add support for manual fan control
2025-02-16 6:12 ` Armin Wolf
2025-02-16 6:14 ` Armin Wolf
@ 2025-02-16 21:25 ` Kurt Borja
1 sibling, 0 replies; 25+ messages in thread
From: Kurt Borja @ 2025-02-16 21:25 UTC (permalink / raw)
To: Armin Wolf, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Guenter Roeck
On Sun Feb 16, 2025 at 1:12 AM -05, Armin Wolf wrote:
> Am 08.02.25 um 06:16 schrieb Kurt Borja:
>
>> All models with the "AWCC" WMAX device support a way of manually
>> controlling fans.
>>
>> The PWM duty cycle of a fan can't be controlled directly. Instead the
>> AWCC interface let's us tune a PWM `boost` value, which has the
>> following empirically discovered behavior over the PWM value:
>>
>> pwm = pwm_base + (pwm_boost / 255) * (pwm_max - pwm_base)
>>
>> Where the pwm_base is the locked PWM value controlled by the EC and
>> pwm_boost is a value between 0 and 255.
>>
>> This pwm_boost knob is exposed as a standard `pwm` attribute.
>
> I am not sure if exposing this over the standard "pwm" attribute is correct here,
> since userspace applications expect to have full access to the fan when using the
> "pwm" attribute.
>
> Maybe using a custom attribute like "fanX_boost" would make sense here? Either way
Actually, in the first draft of this patch I actually did this. I
exposed it as pwmX_boost because it behaved like a "percentage" kind of
thing. I'm ok with fanX_boost tho, it's more explicit in it's intention.
> documenting this special behavior would be nice for future users, maybe you can write
> this down under Documentation/admin-guide/laptops?
Sure! I will. I also want to document the legacy driver features, just
like an historical archive, although some people still use these
features.
--
~ Kurt
>
> Thanks,
> Armin Wolf
>
>> Cc: Guenter Roeck <linux@roeck-us.net>
>> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
>> ---
>> .../platform/x86/dell/alienware-wmi-wmax.c | 55 +++++++++++++++++--
>> 1 file changed, 49 insertions(+), 6 deletions(-)
>>
>> diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
>> index 5f02da7ff25f..06d6f88ea54b 100644
>> --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
>> +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
>> @@ -13,6 +13,7 @@
>> #include <linux/dmi.h>
>> #include <linux/hwmon.h>
>> #include <linux/jiffies.h>
>> +#include <linux/minmax.h>
>> #include <linux/moduleparam.h>
>> #include <linux/mutex.h>
>> #include <linux/overflow.h>
>> @@ -176,10 +177,12 @@ enum AWCC_THERMAL_INFORMATION_OPERATIONS {
>> AWCC_OP_GET_MIN_RPM = 0x08,
>> AWCC_OP_GET_MAX_RPM = 0x09,
>> AWCC_OP_GET_CURRENT_PROFILE = 0x0B,
>> + AWCC_OP_GET_FAN_BOOST = 0x0C,
>> };
>>
>> enum AWCC_THERMAL_CONTROL_OPERATIONS {
>> AWCC_OP_ACTIVATE_PROFILE = 0x01,
>> + AWCC_OP_SET_FAN_BOOST = 0x02,
>> };
>>
>> enum AWCC_GAME_SHIFT_STATUS_OPERATIONS {
>> @@ -563,12 +566,13 @@ static inline int awcc_thermal_information(struct wmi_device *wdev, u8 operation
>> return __awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
>> }
>>
>> -static inline int awcc_thermal_control(struct wmi_device *wdev, u8 profile)
>> +static inline int awcc_thermal_control(struct wmi_device *wdev, u8 operation,
>> + u8 arg1, u8 arg2)
>> {
>> struct wmax_u32_args args = {
>> - .operation = AWCC_OP_ACTIVATE_PROFILE,
>> - .arg1 = profile,
>> - .arg2 = 0,
>> + .operation = operation,
>> + .arg1 = arg1,
>> + .arg2 = arg2,
>> .arg3 = 0,
>> };
>> u32 out;
>> @@ -684,6 +688,11 @@ static umode_t awcc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_type
>> if (channel < priv->fan_count)
>> return 0444;
>>
>> + break;
>> + case hwmon_pwm:
>> + if (channel < priv->fan_count)
>> + return 0644;
>> +
>> break;
>> default:
>> break;
>> @@ -698,6 +707,7 @@ static int awcc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
>> struct awcc_priv *priv = dev_get_drvdata(dev);
>> struct awcc_temp_channel_data *temp;
>> struct awcc_fan_channel_data *fan;
>> + u32 fan_boost;
>> int ret;
>>
>> switch (type) {
>> @@ -742,6 +752,16 @@ static int awcc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
>> return -EOPNOTSUPP;
>> }
>>
>> + break;
>> + case hwmon_pwm:
>> + fan = &priv->fan_data[channel];
>> +
>> + ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_FAN_BOOST,
>> + fan->id, &fan_boost);
>> + if (ret)
>> + return ret;
>> +
>> + *val = fan_boost;
>> break;
>> default:
>> return -EOPNOTSUPP;
>> @@ -796,10 +816,27 @@ static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types ty
>> return 0;
>> }
>>
>> +
>> +static int awcc_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
>> + u32 attr, int channel, long val)
>> +{
>> + struct awcc_priv *priv = dev_get_drvdata(dev);
>> + u8 fan_id = priv->fan_data[channel].id;
>> +
>> + switch (type) {
>> + case hwmon_pwm:
>> + return awcc_thermal_control(priv->wdev, AWCC_OP_SET_FAN_BOOST,
>> + fan_id, (u8)clamp_val(val, 0, 255));
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> +}
>> +
>> static const struct hwmon_ops awcc_hwmon_ops = {
>> .is_visible = awcc_hwmon_is_visible,
>> .read = awcc_hwmon_read,
>> .read_string = awcc_hwmon_read_string,
>> + .write = awcc_hwmon_write,
>> };
>>
>> static const struct hwmon_channel_info * const awcc_hwmon_info[] = {
>> @@ -814,6 +851,12 @@ static const struct hwmon_channel_info * const awcc_hwmon_info[] = {
>> HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
>> HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX
>> ),
>> + HWMON_CHANNEL_INFO(pwm,
>> + HWMON_PWM_INPUT,
>> + HWMON_PWM_INPUT,
>> + HWMON_PWM_INPUT,
>> + HWMON_PWM_INPUT
>> + ),
>> NULL
>> };
>>
>> @@ -954,8 +997,8 @@ static int awcc_platform_profile_set(struct device *dev,
>> }
>> }
>>
>> - return awcc_thermal_control(priv->wdev,
>> - priv->supported_profiles[profile]);
>> + return awcc_thermal_control(priv->wdev, AWCC_OP_ACTIVATE_PROFILE,
>> + priv->supported_profiles[profile], 0);
>> }
>>
>> static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 07/10] platform/x86: alienware-wmi-wmax: Add HWMON support
2025-02-16 6:06 ` Armin Wolf
@ 2025-02-16 21:27 ` Kurt Borja
0 siblings, 0 replies; 25+ messages in thread
From: Kurt Borja @ 2025-02-16 21:27 UTC (permalink / raw)
To: Armin Wolf, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel, Guenter Roeck
On Sun Feb 16, 2025 at 1:06 AM -05, Armin Wolf wrote:
> Am 08.02.25 um 06:16 schrieb Kurt Borja:
>
>> All models with the "AWCC" WMAX device support monitoring fan speed and
>> temperature sensors. Expose this feature through the HWMON interface.
>>
>> Sensor readings are cached for 1 second before refreshing them to
>> mitigate the performance cost of calling WMI methods.
>
> Since each fan can be accessed independently, i do not think that the caching is actually
> needed here.
>
> The dell-wmi-ddv driver only uses caching because it can only read all fan sensors
> at the same time, which means each call is quite expensive. The AWCC interface however
> seems to allow access to each individual fan, so each call should be quite cheap (for
> a WMI method call).
>
> Do you know of a device which has such slow WMI calls? If not then i suggest you remove
> the caching.
I was under the impression that all WMI calls are a bit "expensive" in
the performance sense. Also the AWCC OEM windows app slows down your
computer a LOT, actually unusable while gaming and I figured this had
something to do with it.
I'm going to do some tests. If the performance cost is not noticeable I
will drop it.
--
~ Kurt
>
>> Cc: Guenter Roeck <linux@roeck-us.net>
>> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
>> ---
>> drivers/platform/x86/dell/Kconfig | 1 +
>> .../platform/x86/dell/alienware-wmi-wmax.c | 341 ++++++++++++++++++
>> 2 files changed, 342 insertions(+)
>>
>> diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig
>> index f8a0dffcaab7..85a57c01aaad 100644
>> --- a/drivers/platform/x86/dell/Kconfig
>> +++ b/drivers/platform/x86/dell/Kconfig
>> @@ -43,6 +43,7 @@ config ALIENWARE_WMI_WMAX
>> bool "Alienware WMAX WMI device driver"
>> default y
>> depends on ALIENWARE_WMI
>> + depends on HWMON
>> select ACPI_PLATFORM_PROFILE
>> help
>> Alienware WMI driver with AlienFX LED, HDMI, amplifier, deep sleep and
>> diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
>> index 0d31156f43bb..5f02da7ff25f 100644
>> --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
>> +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
>> @@ -11,9 +11,13 @@
>> #include <linux/bitfield.h>
>> #include <linux/bits.h>
>> #include <linux/dmi.h>
>> +#include <linux/hwmon.h>
>> +#include <linux/jiffies.h>
>> #include <linux/moduleparam.h>
>> +#include <linux/mutex.h>
>> #include <linux/overflow.h>
>> #include <linux/platform_profile.h>
>> +#include <linux/units.h>
>> #include <linux/wmi.h>
>> #include "alienware-wmi.h"
>>
>> @@ -26,6 +30,7 @@
>> #define WMAX_METHOD_BRIGHTNESS 0x3
>> #define WMAX_METHOD_ZONE_CONTROL 0x4
>>
>> +#define AWCC_METHOD_GET_FAN_SENSORS 0x13
>> #define AWCC_METHOD_THERMAL_INFORMATION 0x14
>> #define AWCC_METHOD_THERMAL_CONTROL 0x15
>> #define AWCC_METHOD_GAME_SHIFT_STATUS 0x25
>> @@ -36,6 +41,10 @@
>> #define AWCC_THERMAL_MODE_MASK GENMASK(3, 0)
>> #define AWCC_RESOURCE_ID_MASK GENMASK(7, 0)
>>
>> +static bool force_hwmon;
>> +module_param_unsafe(force_hwmon, bool, 0);
>> +MODULE_PARM_DESC(force_hwmon, "Force probing for HWMON support without checking if the WMI backend is available");
>> +
>> static bool force_platform_profile;
>> module_param_unsafe(force_platform_profile, bool, 0);
>> MODULE_PARM_DESC(force_platform_profile, "Forces auto-detecting thermal profiles without checking if WMI thermal backend is available");
>> @@ -45,16 +54,19 @@ module_param_unsafe(force_gmode, bool, 0);
>> MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected");
>>
>> struct awcc_quirks {
>> + bool hwmon;
>> bool pprof;
>> bool gmode;
>> };
>>
>> static struct awcc_quirks g_series_quirks = {
>> + .hwmon = true,
>> .pprof = true,
>> .gmode = true,
>> };
>>
>> static struct awcc_quirks generic_quirks = {
>> + .hwmon = true,
>> .pprof = true,
>> .gmode = false,
>> };
>> @@ -152,9 +164,17 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = {
>> },
>> };
>>
>> +enum AWCC_GET_FAN_SENSORS_OPERATIONS {
>> + AWCC_OP_GET_TEMP_SENSOR_ID = 0x02,
>> +};
>> +
>> enum AWCC_THERMAL_INFORMATION_OPERATIONS {
>> AWCC_OP_GET_SYSTEM_DESCRIPTION = 0x02,
>> AWCC_OP_GET_RESOURCE_ID = 0x03,
>> + AWCC_OP_GET_TEMPERATURE = 0x04,
>> + AWCC_OP_GET_CURRENT_RPM = 0x05,
>> + AWCC_OP_GET_MIN_RPM = 0x08,
>> + AWCC_OP_GET_MAX_RPM = 0x09,
>> AWCC_OP_GET_CURRENT_PROFILE = 0x0B,
>> };
>>
>> @@ -177,6 +197,11 @@ enum AWCC_SPECIAL_THERMAL_CODES {
>> AWCC_SPECIAL_PROFILE_GMODE = 0xAB,
>> };
>>
>> +enum AWCC_TEMP_SENSOR_TYPES {
>> + AWCC_TEMP_SENSOR_CPU = 0x01,
>> + AWCC_TEMP_SENSOR_GPU = 0x06,
>> +};
>> +
>> enum awcc_thermal_profile {
>> AWCC_PROFILE_USTT_BALANCED,
>> AWCC_PROFILE_USTT_BALANCED_PERFORMANCE,
>> @@ -213,6 +238,23 @@ struct wmax_u32_args {
>> u8 arg3;
>> } __packed;
>>
>> +struct awcc_fan_channel_data {
>> + u8 id;
>> + u32 state;
>> + u32 min_rpm;
>> + u32 max_rpm;
>> + u8 temp_sensor;
>> + u64 timestamp;
>> + struct mutex lock; /* protects state and timestamp */
>> +};
>> +
>> +struct awcc_temp_channel_data {
>> + u8 id;
>> + u32 state;
>> + u64 timestamp;
>> + struct mutex lock; /* protects state and timestamp */
>> +};
>> +
>> struct awcc_priv {
>> struct wmi_device *wdev;
>> union {
>> @@ -228,6 +270,10 @@ struct awcc_priv {
>>
>> struct device *ppdev;
>> u8 supported_profiles[PLATFORM_PROFILE_LAST];
>> +
>> + struct device *hwdev;
>> + struct awcc_temp_channel_data *temp_data;
>> + struct awcc_fan_channel_data *fan_data;
>> };
>>
>> static const enum platform_profile_option awcc_mode_to_platform_profile[AWCC_PROFILE_LAST] = {
>> @@ -492,6 +538,18 @@ static int __awcc_wmi_command(struct wmi_device *wdev, u32 method_id,
>> return 0;
>> }
>>
>> +static inline int awcc_get_fan_sensors(struct wmi_device *wdev, u8 fan_id, u32 *out)
>> +{
>> + struct wmax_u32_args args = {
>> + .operation = AWCC_OP_GET_TEMP_SENSOR_ID,
>> + .arg1 = fan_id,
>> + .arg2 = 0,
>> + .arg3 = 0,
>> + };
>> +
>> + return __awcc_wmi_command(wdev, AWCC_METHOD_GET_FAN_SENSORS, &args, out);
>> +}
>> +
>> static inline int awcc_thermal_information(struct wmi_device *wdev, u8 operation,
>> u8 arg, u32 *out)
>> {
>> @@ -562,6 +620,276 @@ static inline int awcc_op_get_resource_id(struct wmi_device *wdev, u8 index, u32
>> return __awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
>> }
>>
>> +/*
>> + * HWMON
>> + * - Provides temperature and fan speed monitoring as well as manual fan
>> + * control
>> + */
>> +static int awcc_hwmon_update_temp(struct wmi_device *wdev,
>> + struct awcc_temp_channel_data *data)
>> +{
>> + u32 temp;
>> + int ret;
>> +
>> + lockdep_assert_held(data->lock);
>> +
>> + if (time_is_after_jiffies64(data->timestamp + secs_to_jiffies(1)))
>> + return 0;
>> +
>> + ret = awcc_thermal_information(wdev, AWCC_OP_GET_TEMPERATURE, data->id,
>> + &temp);
>> + if (ret)
>> + return ret;
>> +
>> + data->state = temp * MILLIDEGREE_PER_DEGREE;
>> + data->timestamp = get_jiffies_64();
>> +
>> + return 0;
>> +}
>> +
>> +static int awcc_hwmon_update_fan(struct wmi_device *wdev,
>> + struct awcc_fan_channel_data *data)
>> +{
>> + u32 rpm;
>> + int ret;
>> +
>> + lockdep_assert_held(data->lock);
>> +
>> + if (time_is_after_jiffies64(data->timestamp + secs_to_jiffies(1)))
>> + return 0;
>> +
>> + ret = awcc_thermal_information(wdev, AWCC_OP_GET_CURRENT_RPM, data->id,
>> + &rpm);
>> + if (ret)
>> + return ret;
>> +
>> + data->state = rpm;
>> + data->timestamp = get_jiffies_64();
>> +
>> + return 0;
>> +}
>> +
>> +static umode_t awcc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
>> + u32 attr, int channel)
>> +{
>> + const struct awcc_priv *priv = drvdata;
>> +
>> + switch (type) {
>> + case hwmon_temp:
>> + if (channel < priv->temp_count)
>> + return 0444;
>> +
>> + break;
>> + case hwmon_fan:
>> + if (channel < priv->fan_count)
>> + return 0444;
>> +
>> + break;
>> + default:
>> + break;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int awcc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
>> + u32 attr, int channel, long *val)
>> +{
>> + struct awcc_priv *priv = dev_get_drvdata(dev);
>> + struct awcc_temp_channel_data *temp;
>> + struct awcc_fan_channel_data *fan;
>> + int ret;
>> +
>> + switch (type) {
>> + case hwmon_temp:
>> + temp = &priv->temp_data[channel];
>> +
>> + switch (attr) {
>> + case hwmon_temp_input:
>> + mutex_lock(&temp->lock);
>> + ret = awcc_hwmon_update_temp(priv->wdev, temp);
>> + mutex_unlock(&temp->lock);
>> + if (ret)
>> + return ret;
>> +
>> + *val = temp->state;
>> + break;
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> +
>> + break;
>> + case hwmon_fan:
>> + fan = &priv->fan_data[channel];
>> +
>> + switch (attr) {
>> + case hwmon_fan_input:
>> + mutex_lock(&fan->lock);
>> + ret = awcc_hwmon_update_fan(priv->wdev, fan);
>> + mutex_unlock(&fan->lock);
>> + if (ret)
>> + return ret;
>> +
>> + *val = fan->state;
>> + break;
>> + case hwmon_fan_min:
>> + *val = fan->min_rpm;
>> + break;
>> + case hwmon_fan_max:
>> + *val = fan->max_rpm;
>> + break;
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> +
>> + break;
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
>> + u32 attr, int channel, const char **str)
>> +{
>> + struct awcc_priv *priv = dev_get_drvdata(dev);
>> + u8 temp_id;
>> +
>> + switch (type) {
>> + case hwmon_temp:
>> + temp_id = priv->temp_data[channel].id;
>> +
>> + switch (temp_id) {
>> + case AWCC_TEMP_SENSOR_CPU:
>> + *str = "CPU";
>> + break;
>> + case AWCC_TEMP_SENSOR_GPU:
>> + *str = "GPU";
>> + break;
>> + default:
>> + *str = "Unknown";
>> + break;
>> + }
>> +
>> + break;
>> + case hwmon_fan:
>> + temp_id = priv->fan_data[channel].temp_sensor;
>> +
>> + switch (temp_id) {
>> + case AWCC_TEMP_SENSOR_CPU:
>> + *str = "Processor Fan";
>> + break;
>> + case AWCC_TEMP_SENSOR_GPU:
>> + *str = "Video Fan";
>> + break;
>> + default:
>> + *str = "Unknown Fan";
>> + break;
>> + }
>> +
>> + break;
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static const struct hwmon_ops awcc_hwmon_ops = {
>> + .is_visible = awcc_hwmon_is_visible,
>> + .read = awcc_hwmon_read,
>> + .read_string = awcc_hwmon_read_string,
>> +};
>> +
>> +static const struct hwmon_channel_info * const awcc_hwmon_info[] = {
>> + HWMON_CHANNEL_INFO(temp,
>> + HWMON_T_LABEL | HWMON_T_INPUT,
>> + HWMON_T_LABEL | HWMON_T_INPUT,
>> + HWMON_T_LABEL | HWMON_T_INPUT
>> + ),
>> + HWMON_CHANNEL_INFO(fan,
>> + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
>> + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
>> + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
>> + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX
>> + ),
>> + NULL
>> +};
>> +
>> +static const struct hwmon_chip_info awcc_hwmon_chip_info = {
>> + .ops = &awcc_hwmon_ops,
>> + .info = awcc_hwmon_info,
>> +};
>> +
>> +static int awcc_hwmon_init(struct wmi_device *wdev)
>> +{
>> + struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
>> + u32 id, temp_sensor, min_rpm, max_rpm;
>> + int ret;
>> +
>> + priv->fan_data = devm_kcalloc(&wdev->dev, priv->fan_count,
>> + sizeof(*priv->fan_data), GFP_KERNEL);
>> + if (!priv->fan_data)
>> + return -ENOMEM;
>> +
>> + priv->temp_data = devm_kcalloc(&wdev->dev, priv->temp_count,
>> + sizeof(*priv->temp_data), GFP_KERNEL);
>> + if (!priv->temp_data)
>> + return -ENOMEM;
>> +
>> + for (u32 i = 0; i < priv->fan_count; i++) {
>> + /*
>> + * Fan IDs are listed first at offset 0
>> + */
>> + ret = awcc_op_get_resource_id(wdev, i, &id);
>> + if (ret)
>> + return ret;
>> +
>> + ret = awcc_thermal_information(wdev, AWCC_OP_GET_MIN_RPM, id,
>> + &min_rpm);
>> + if (ret)
>> + return ret;
>> +
>> + ret = awcc_thermal_information(wdev, AWCC_OP_GET_MAX_RPM, id,
>> + &max_rpm);
>> + if (ret)
>> + return ret;
>> +
>> + ret = awcc_get_fan_sensors(wdev, id, &temp_sensor);
>> + if (ret)
>> + return ret;
>> +
>> + priv->fan_data[i].id = FIELD_GET(AWCC_RESOURCE_ID_MASK, id);
>> + priv->fan_data[i].min_rpm = min_rpm;
>> + priv->fan_data[i].max_rpm = max_rpm;
>> + priv->fan_data[i].temp_sensor = temp_sensor;
>> + ret = devm_mutex_init(&wdev->dev, &priv->fan_data[i].lock);
>> + if (ret)
>> + return ret;
>> + }
>> +
>> + for (u32 i = 0; i < priv->temp_count; i++) {
>> + /*
>> + * Temperature sensors IDs are listed after the fan IDs at
>> + * offset `fan_count`
>> + */
>> + ret = awcc_op_get_resource_id(wdev, i + priv->fan_count, &id);
>> + if (ret)
>> + return ret;
>> +
>> + priv->temp_data[i].id = FIELD_GET(AWCC_RESOURCE_ID_MASK, id);
>> + ret = devm_mutex_init(&wdev->dev, &priv->temp_data[i].lock);
>> + if (ret)
>> + return ret;
>> + }
>> +
>> + priv->hwdev = devm_hwmon_device_register_with_info(
>> + &wdev->dev, "alienware_wmi", priv, &awcc_hwmon_chip_info, NULL);
>
> checkpatch says "CHECK: Lines should not end with a '('". Please fix this.
>
> Other than that the code looks promising.
>
> Thanks,
> Armin Wolf
>
>> +
>> + return PTR_ERR_OR_ZERO(priv->hwdev);
>> +}
>> +
>> /*
>> * Thermal Profile control
>> * - Provides thermal profile control through the Platform Profile API
>> @@ -734,6 +1062,12 @@ static int alienware_awcc_setup(struct wmi_device *wdev)
>> priv->wdev = wdev;
>> dev_set_drvdata(&wdev->dev, priv);
>>
>> + if (awcc->hwmon) {
>> + ret = awcc_hwmon_init(wdev);
>> + if (ret)
>> + return ret;
>> + }
>> +
>> if (awcc->pprof) {
>> ret = awcc_platform_profile_init(wdev);
>> if (ret)
>> @@ -814,6 +1148,13 @@ int __init alienware_wmax_wmi_init(void)
>> if (id)
>> awcc = id->driver_data;
>>
>> + if (force_hwmon) {
>> + if (!awcc)
>> + awcc = &empty_quirks;
>> +
>> + awcc->hwmon = true;
>> + }
>> +
>> if (force_platform_profile) {
>> if (!awcc)
>> awcc = &empty_quirks;
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 10/10] platform/x86: alienware-wmi: Improve and update documentation
[not found] ` <198a578f-99d6-4d98-8186-eab141a2550d@gmx.de>
@ 2025-02-16 21:38 ` Kurt Borja
0 siblings, 0 replies; 25+ messages in thread
From: Kurt Borja @ 2025-02-16 21:38 UTC (permalink / raw)
To: Armin Wolf, Ilpo Järvinen
Cc: platform-driver-x86, Hans de Goede, Dell.Client.Kernel,
linux-kernel
On Sun Feb 16, 2025 at 4:29 PM -05, Armin Wolf wrote:
> Maybe you can use the "pwmX_auto_channels_temp" attribute to expose this information
> to userspace?
I like this approach.
>
> Anyway i attached the acpidump you requested. Sadly i cannot remember from which device
> i got it from.
Thanks!
Also thank you for reviewing this set :) I'll implement the changes and
send a new version by tomorrow.
>
> Thanks,
> Armin Wolf
>
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2025-02-16 21:38 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-02-08 5:16 [PATCH 00/10] HWMON support + DebugFS + Improvements Kurt Borja
2025-02-08 5:16 ` [PATCH 01/10] platform/x86: alienware-wmi-wmax: Rename thermal related symbols Kurt Borja
2025-02-16 5:40 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 02/10] platform/x86: alienware-wmi-wmax: Refactor is_awcc_thermal_mode() Kurt Borja
2025-02-16 5:44 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 03/10] platform/x86: alienware-wmi-wmax: Improve internal AWCC API Kurt Borja
2025-02-16 5:48 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 04/10] platform/x86: alienware-wmi-wmax: Modify supported_thermal_profiles[] Kurt Borja
2025-02-16 5:49 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 05/10] platform/x86: alienware-wmi-wmax: Improve platform profile probe Kurt Borja
2025-02-16 5:57 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 06/10] platform/x86: alienware-wmi-wmax: Add support for the "custom" thermal profile Kurt Borja
2025-02-16 5:59 ` Armin Wolf
2025-02-08 5:16 ` [PATCH 07/10] platform/x86: alienware-wmi-wmax: Add HWMON support Kurt Borja
2025-02-16 6:06 ` Armin Wolf
2025-02-16 21:27 ` Kurt Borja
2025-02-08 5:16 ` [PATCH 08/10] platform/x86: alienware-wmi-wmax: Add support for manual fan control Kurt Borja
2025-02-16 6:12 ` Armin Wolf
2025-02-16 6:14 ` Armin Wolf
2025-02-16 21:25 ` Kurt Borja
2025-02-08 5:16 ` [PATCH 09/10] platform/x86: alienware-wmi-wmax: Add a DebugFS interface Kurt Borja
2025-02-08 5:16 ` [PATCH 10/10] platform/x86: alienware-wmi: Improve and update documentation Kurt Borja
2025-02-16 6:22 ` Armin Wolf
2025-02-16 20:51 ` Kurt Borja
[not found] ` <198a578f-99d6-4d98-8186-eab141a2550d@gmx.de>
2025-02-16 21:38 ` Kurt Borja
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox