* [PATCH v1 01/10] platform/x86: msi-wmi-platform: Use input buffer for returning result
2025-05-11 20:44 [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Antheas Kapenekakis
@ 2025-05-11 20:44 ` Antheas Kapenekakis
2025-05-11 23:31 ` Kurt Borja
2025-05-11 20:44 ` [PATCH v1 02/10] platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query Antheas Kapenekakis
` (10 subsequent siblings)
11 siblings, 1 reply; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-11 20:44 UTC (permalink / raw)
To: platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, Kurt Borja, linux-doc, linux-kernel,
linux-hwmon, Antheas Kapenekakis
From: Armin Wolf <W_Armin@gmx.de>
Modify msi_wmi_platform_query() to reuse the input buffer for
returning the result of a WMI method call. Using a separate output
buffer to return the result is unnecessary because the WMI interface
requires both buffers to have the same length anyway.
Co-developed-by: Antheas Kapenekakis <lkml@antheas.dev>
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
Signed-off-by: Armin Wolf <W_Armin@gmx.de>
---
drivers/platform/x86/msi-wmi-platform.c | 53 ++++++++++++-------------
1 file changed, 26 insertions(+), 27 deletions(-)
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
index dc5e9878cb682..41218a9d6e35d 100644
--- a/drivers/platform/x86/msi-wmi-platform.c
+++ b/drivers/platform/x86/msi-wmi-platform.c
@@ -21,6 +21,7 @@
#include <linux/mutex.h>
#include <linux/printk.h>
#include <linux/rwsem.h>
+#include <linux/string.h>
#include <linux/types.h>
#include <linux/wmi.h>
@@ -140,19 +141,19 @@ static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, siz
}
static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
- enum msi_wmi_platform_method method, u8 *input,
- size_t input_length, u8 *output, size_t output_length)
+ enum msi_wmi_platform_method method, u8 *buffer,
+ size_t length)
{
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_buffer in = {
- .length = input_length,
- .pointer = input
+ .length = length,
+ .pointer = buffer
};
union acpi_object *obj;
acpi_status status;
int ret;
- if (!input_length || !output_length)
+ if (!length)
return -EINVAL;
/*
@@ -169,7 +170,7 @@ static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
if (!obj)
return -ENODATA;
- ret = msi_wmi_platform_parse_buffer(obj, output, output_length);
+ ret = msi_wmi_platform_parse_buffer(obj, buffer, length);
kfree(obj);
return ret;
@@ -185,17 +186,15 @@ static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types typ
int channel, long *val)
{
struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
- u8 input[32] = { 0 };
- u8 output[32];
+ u8 buffer[32] = { 0 };
u16 value;
int ret;
- ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, input, sizeof(input), output,
- sizeof(output));
+ ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buf, sizeof(buf));
if (ret < 0)
return ret;
- value = get_unaligned_be16(&output[channel * 2 + 1]);
+ value = get_unaligned_be16(&buffer[channel * 2 + 1]);
if (!value)
*val = 0;
else
@@ -245,13 +244,17 @@ static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input,
return ret;
down_write(&data->buffer_lock);
- ret = msi_wmi_platform_query(data->data, data->method, payload, data->length, data->buffer,
+ ret = msi_wmi_platform_query(data->data, data->method, data->buffer,
data->length);
up_write(&data->buffer_lock);
if (ret < 0)
return ret;
+ down_write(&data->buffer_lock);
+ memcpy(data->buffer, payload, data->length);
+ up_write(&data->buffer_lock);
+
return length;
}
@@ -348,23 +351,21 @@ static int msi_wmi_platform_hwmon_init(struct msi_wmi_platform_data *data)
static int msi_wmi_platform_ec_init(struct msi_wmi_platform_data *data)
{
- u8 input[32] = { 0 };
- u8 output[32];
+ u8 buffer[32] = { 0 };
u8 flags;
int ret;
- ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_EC, input, sizeof(input), output,
- sizeof(output));
+ ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_EC, buffer, sizeof(buffer));
if (ret < 0)
return ret;
- flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET];
+ flags = buffer[MSI_PLATFORM_EC_FLAGS_OFFSET];
dev_dbg(&data->wdev->dev, "EC RAM version %lu.%lu\n",
FIELD_GET(MSI_PLATFORM_EC_MAJOR_MASK, flags),
FIELD_GET(MSI_PLATFORM_EC_MINOR_MASK, flags));
dev_dbg(&data->wdev->dev, "EC firmware version %.28s\n",
- &output[MSI_PLATFORM_EC_VERSION_OFFSET]);
+ &buffer[MSI_PLATFORM_EC_VERSION_OFFSET]);
if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) {
if (!force)
@@ -378,27 +379,25 @@ static int msi_wmi_platform_ec_init(struct msi_wmi_platform_data *data)
static int msi_wmi_platform_init(struct msi_wmi_platform_data *data)
{
- u8 input[32] = { 0 };
- u8 output[32];
+ u8 buffer[32] = { 0 };
int ret;
- ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_WMI, input, sizeof(input), output,
- sizeof(output));
+ ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_WMI, buffer, sizeof(buffer));
if (ret < 0)
return ret;
dev_dbg(&data->wdev->dev, "WMI interface version %u.%u\n",
- output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
- output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
+ buffer[MSI_PLATFORM_WMI_MAJOR_OFFSET],
+ buffer[MSI_PLATFORM_WMI_MINOR_OFFSET]);
- if (output[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) {
+ if (buffer[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) {
if (!force)
return -ENODEV;
dev_warn(&data->wdev->dev,
"Loading despite unsupported WMI interface version (%u.%u)\n",
- output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
- output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
+ buffer[MSI_PLATFORM_WMI_MAJOR_OFFSET],
+ buffer[MSI_PLATFORM_WMI_MINOR_OFFSET]);
}
return 0;
--
2.49.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v1 01/10] platform/x86: msi-wmi-platform: Use input buffer for returning result
2025-05-11 20:44 ` [PATCH v1 01/10] platform/x86: msi-wmi-platform: Use input buffer for returning result Antheas Kapenekakis
@ 2025-05-11 23:31 ` Kurt Borja
2025-05-13 19:42 ` Armin Wolf
0 siblings, 1 reply; 45+ messages in thread
From: Kurt Borja @ 2025-05-11 23:31 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, linux-doc, linux-kernel, linux-hwmon
[-- Attachment #1: Type: text/plain, Size: 6113 bytes --]
On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> From: Armin Wolf <W_Armin@gmx.de>
>
> Modify msi_wmi_platform_query() to reuse the input buffer for
> returning the result of a WMI method call. Using a separate output
> buffer to return the result is unnecessary because the WMI interface
> requires both buffers to have the same length anyway.
>
> Co-developed-by: Antheas Kapenekakis <lkml@antheas.dev>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> Signed-off-by: Armin Wolf <W_Armin@gmx.de>
> ---
> drivers/platform/x86/msi-wmi-platform.c | 53 ++++++++++++-------------
> 1 file changed, 26 insertions(+), 27 deletions(-)
>
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index dc5e9878cb682..41218a9d6e35d 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -21,6 +21,7 @@
> #include <linux/mutex.h>
> #include <linux/printk.h>
> #include <linux/rwsem.h>
> +#include <linux/string.h>
> #include <linux/types.h>
> #include <linux/wmi.h>
>
> @@ -140,19 +141,19 @@ static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, siz
> }
>
> static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
> - enum msi_wmi_platform_method method, u8 *input,
> - size_t input_length, u8 *output, size_t output_length)
> + enum msi_wmi_platform_method method, u8 *buffer,
> + size_t length)
> {
> struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
> struct acpi_buffer in = {
> - .length = input_length,
> - .pointer = input
> + .length = length,
> + .pointer = buffer
> };
> union acpi_object *obj;
> acpi_status status;
> int ret;
>
> - if (!input_length || !output_length)
> + if (!length)
> return -EINVAL;
>
> /*
> @@ -169,7 +170,7 @@ static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
> if (!obj)
> return -ENODATA;
>
> - ret = msi_wmi_platform_parse_buffer(obj, output, output_length);
> + ret = msi_wmi_platform_parse_buffer(obj, buffer, length);
> kfree(obj);
>
> return ret;
> @@ -185,17 +186,15 @@ static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types typ
> int channel, long *val)
> {
> struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> - u8 input[32] = { 0 };
> - u8 output[32];
> + u8 buffer[32] = { 0 };
> u16 value;
> int ret;
>
> - ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, input, sizeof(input), output,
> - sizeof(output));
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buf, sizeof(buf));
s/buf/buffer/
> if (ret < 0)
> return ret;
>
> - value = get_unaligned_be16(&output[channel * 2 + 1]);
> + value = get_unaligned_be16(&buffer[channel * 2 + 1]);
> if (!value)
> *val = 0;
> else
> @@ -245,13 +244,17 @@ static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input,
> return ret;
>
> down_write(&data->buffer_lock);
> - ret = msi_wmi_platform_query(data->data, data->method, payload, data->length, data->buffer,
> + ret = msi_wmi_platform_query(data->data, data->method, data->buffer,
Is this logic right? Shouldn't we pass payload instead of data->buffer?
Better yet, I think we should write the payload directly to
data->buffer and drop the memcpy hunk bellow
--
~ Kurt
> data->length);
> up_write(&data->buffer_lock);
>
> if (ret < 0)
> return ret;
>
> + down_write(&data->buffer_lock);
> + memcpy(data->buffer, payload, data->length);
> + up_write(&data->buffer_lock);
> +
> return length;
> }
>
> @@ -348,23 +351,21 @@ static int msi_wmi_platform_hwmon_init(struct msi_wmi_platform_data *data)
>
> static int msi_wmi_platform_ec_init(struct msi_wmi_platform_data *data)
> {
> - u8 input[32] = { 0 };
> - u8 output[32];
> + u8 buffer[32] = { 0 };
> u8 flags;
> int ret;
>
> - ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_EC, input, sizeof(input), output,
> - sizeof(output));
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_EC, buffer, sizeof(buffer));
> if (ret < 0)
> return ret;
>
> - flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET];
> + flags = buffer[MSI_PLATFORM_EC_FLAGS_OFFSET];
>
> dev_dbg(&data->wdev->dev, "EC RAM version %lu.%lu\n",
> FIELD_GET(MSI_PLATFORM_EC_MAJOR_MASK, flags),
> FIELD_GET(MSI_PLATFORM_EC_MINOR_MASK, flags));
> dev_dbg(&data->wdev->dev, "EC firmware version %.28s\n",
> - &output[MSI_PLATFORM_EC_VERSION_OFFSET]);
> + &buffer[MSI_PLATFORM_EC_VERSION_OFFSET]);
>
> if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) {
> if (!force)
> @@ -378,27 +379,25 @@ static int msi_wmi_platform_ec_init(struct msi_wmi_platform_data *data)
>
> static int msi_wmi_platform_init(struct msi_wmi_platform_data *data)
> {
> - u8 input[32] = { 0 };
> - u8 output[32];
> + u8 buffer[32] = { 0 };
> int ret;
>
> - ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_WMI, input, sizeof(input), output,
> - sizeof(output));
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_WMI, buffer, sizeof(buffer));
> if (ret < 0)
> return ret;
>
> dev_dbg(&data->wdev->dev, "WMI interface version %u.%u\n",
> - output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
> - output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
> + buffer[MSI_PLATFORM_WMI_MAJOR_OFFSET],
> + buffer[MSI_PLATFORM_WMI_MINOR_OFFSET]);
>
> - if (output[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) {
> + if (buffer[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) {
> if (!force)
> return -ENODEV;
>
> dev_warn(&data->wdev->dev,
> "Loading despite unsupported WMI interface version (%u.%u)\n",
> - output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
> - output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
> + buffer[MSI_PLATFORM_WMI_MAJOR_OFFSET],
> + buffer[MSI_PLATFORM_WMI_MINOR_OFFSET]);
> }
>
> return 0;
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 01/10] platform/x86: msi-wmi-platform: Use input buffer for returning result
2025-05-11 23:31 ` Kurt Borja
@ 2025-05-13 19:42 ` Armin Wolf
0 siblings, 0 replies; 45+ messages in thread
From: Armin Wolf @ 2025-05-13 19:42 UTC (permalink / raw)
To: Kurt Borja, Antheas Kapenekakis, platform-driver-x86
Cc: Jonathan Corbet, Hans de Goede, Ilpo Järvinen, Jean Delvare,
Guenter Roeck, linux-doc, linux-kernel, linux-hwmon
Am 12.05.25 um 01:31 schrieb Kurt Borja:
> On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
>> From: Armin Wolf <W_Armin@gmx.de>
>>
>> Modify msi_wmi_platform_query() to reuse the input buffer for
>> returning the result of a WMI method call. Using a separate output
>> buffer to return the result is unnecessary because the WMI interface
>> requires both buffers to have the same length anyway.
>>
>> Co-developed-by: Antheas Kapenekakis <lkml@antheas.dev>
>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>> Signed-off-by: Armin Wolf <W_Armin@gmx.de>
>> ---
>> drivers/platform/x86/msi-wmi-platform.c | 53 ++++++++++++-------------
>> 1 file changed, 26 insertions(+), 27 deletions(-)
>>
>> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
>> index dc5e9878cb682..41218a9d6e35d 100644
>> --- a/drivers/platform/x86/msi-wmi-platform.c
>> +++ b/drivers/platform/x86/msi-wmi-platform.c
>> @@ -21,6 +21,7 @@
>> #include <linux/mutex.h>
>> #include <linux/printk.h>
>> #include <linux/rwsem.h>
>> +#include <linux/string.h>
>> #include <linux/types.h>
>> #include <linux/wmi.h>
>>
>> @@ -140,19 +141,19 @@ static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, siz
>> }
>>
>> static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
>> - enum msi_wmi_platform_method method, u8 *input,
>> - size_t input_length, u8 *output, size_t output_length)
>> + enum msi_wmi_platform_method method, u8 *buffer,
>> + size_t length)
>> {
>> struct acpi_buffer out = ACPI_ALLOCATE_BUFFER, NULL };
>> struct acpi_buffer in =
>> - .length = nput_length,
>> - .pointer =nput
>> + .length =ength,
>> + .pointer =uffer
>> };
>> union acpi_object *obj;
>> acpi_status status;
>> int ret;
>>
>> - if (!input_length || !output_length)
>> + if (!length)
>> return -EINVAL;
>>
>> /*
>> @@ -169,7 +170,7 @@ static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
>> if (!obj)
>> return -ENODATA;
>>
>> - ret =si_wmi_platform_parse_buffer(obj, output, output_length);
>> + ret =si_wmi_platform_parse_buffer(obj, buffer, length);
>> kfree(obj);
>>
>> return ret;
>> @@ -185,17 +186,15 @@ static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types typ
>> int channel, long *val)
>> {
>> struct msi_wmi_platform_data *data =ev_get_drvdata(dev);
>> - u8 input[32] = 0 };
>> - u8 output[32];
>> + u8 buffer[32] = 0 };
>> u16 value;
>> int ret;
>>
>> - ret =si_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, input, sizeof(input), output,
>> - sizeof(output));
>> + ret =si_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buf, sizeof(buf));
> s/buf/buffer/
>
>> if (ret < 0)
>> return ret;
>>
>> - value =et_unaligned_be16(&output[channel * 2 + 1]);
>> + value =et_unaligned_be16(&buffer[channel * 2 + 1]);
>> if (!value)
>> *val =;
>> else
>> @@ -245,13 +244,17 @@ static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input,
>> return ret;
>>
>> down_write(&data->buffer_lock);
>> - ret =si_wmi_platform_query(data->data, data->method, payload, data->length, data->buffer,
>> + ret =si_wmi_platform_query(data->data, data->method, data->buffer,
> Is this logic right? Shouldn't we pass payload instead of data->buffer?
>
> Better yet, I think we should write the payload directly to
> data->buffer and drop the memcpy hunk bellow
>
You are right that we indeed pass the wrong buffer here, but we should only update data->buffer
if msi_wmi_platform_query() was successful. That why we have the call to memcpy().
Thanks,
Armin Wolf
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v1 02/10] platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
2025-05-11 20:44 [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Antheas Kapenekakis
2025-05-11 20:44 ` [PATCH v1 01/10] platform/x86: msi-wmi-platform: Use input buffer for returning result Antheas Kapenekakis
@ 2025-05-11 20:44 ` Antheas Kapenekakis
2025-05-12 19:21 ` Kurt Borja
2025-05-13 19:47 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 03/10] platform/x86: msi-wmi-platform: Add quirk system Antheas Kapenekakis
` (9 subsequent siblings)
11 siblings, 2 replies; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-11 20:44 UTC (permalink / raw)
To: platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, Kurt Borja, linux-doc, linux-kernel,
linux-hwmon, Antheas Kapenekakis
This driver requires to be able to handle transactions that perform
multiple WMI actions at a time. Therefore, it needs to be able to
lock the wmi_lock mutex for multiple operations.
Add msi_wmi_platform_query_unlocked() to allow the caller to
perform the WMI query without locking the wmi_lock mutex, by
renaming the existing function and adding a new one that only
locks the mutex.
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
drivers/platform/x86/msi-wmi-platform.c | 27 ++++++++++++++++---------
1 file changed, 17 insertions(+), 10 deletions(-)
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
index 41218a9d6e35d..f0d1b8e1a2fec 100644
--- a/drivers/platform/x86/msi-wmi-platform.c
+++ b/drivers/platform/x86/msi-wmi-platform.c
@@ -140,7 +140,7 @@ static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, siz
return 0;
}
-static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
+static int msi_wmi_platform_query_unlocked(struct msi_wmi_platform_data *data,
enum msi_wmi_platform_method method, u8 *buffer,
size_t length)
{
@@ -156,15 +156,9 @@ static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
if (!length)
return -EINVAL;
- /*
- * The ACPI control method responsible for handling the WMI method calls
- * is not thread-safe. Because of this we have to do the locking ourself.
- */
- scoped_guard(mutex, &data->wmi_lock) {
- status = wmidev_evaluate_method(data->wdev, 0x0, method, &in, &out);
- if (ACPI_FAILURE(status))
- return -EIO;
- }
+ status = wmidev_evaluate_method(data->wdev, 0x0, method, &in, &out);
+ if (ACPI_FAILURE(status))
+ return -EIO;
obj = out.pointer;
if (!obj)
@@ -176,6 +170,19 @@ static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
return ret;
}
+static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
+ enum msi_wmi_platform_method method, u8 *buffer,
+ size_t length)
+{
+ /*
+ * The ACPI control method responsible for handling the WMI method calls
+ * is not thread-safe. Because of this we have to do the locking ourself.
+ */
+ scoped_guard(mutex, &data->wmi_lock) {
+ return msi_wmi_platform_query_unlocked(data, method, buffer, length);
+ }
+}
+
static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type,
u32 attr, int channel)
{
--
2.49.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v1 02/10] platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
2025-05-11 20:44 ` [PATCH v1 02/10] platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query Antheas Kapenekakis
@ 2025-05-12 19:21 ` Kurt Borja
2025-05-12 20:51 ` Antheas Kapenekakis
2025-05-13 19:45 ` Armin Wolf
2025-05-13 19:47 ` Armin Wolf
1 sibling, 2 replies; 45+ messages in thread
From: Kurt Borja @ 2025-05-12 19:21 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, linux-doc, linux-kernel, linux-hwmon
[-- Attachment #1: Type: text/plain, Size: 890 bytes --]
On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> This driver requires to be able to handle transactions that perform
> multiple WMI actions at a time. Therefore, it needs to be able to
> lock the wmi_lock mutex for multiple operations.
>
> Add msi_wmi_platform_query_unlocked() to allow the caller to
> perform the WMI query without locking the wmi_lock mutex, by
> renaming the existing function and adding a new one that only
> locks the mutex.
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
You only use msi_wmi_platform_query_unlocked() to protect the
fan_curve/AP state right?
If that's the case I think we don't need it. AFAIK sysfs reads/writes
are already synchronized/locked, and as I mentioned in Patch 10, I don't
think you need this variant in probe/remove either.
I'd like to hear more opinions on this though.
--
~ Kurt
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 02/10] platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
2025-05-12 19:21 ` Kurt Borja
@ 2025-05-12 20:51 ` Antheas Kapenekakis
2025-05-12 21:23 ` Kurt Borja
2025-05-13 19:45 ` Armin Wolf
1 sibling, 1 reply; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-12 20:51 UTC (permalink / raw)
To: Kurt Borja
Cc: platform-driver-x86, Armin Wolf, Jonathan Corbet, Hans de Goede,
Ilpo Järvinen, Jean Delvare, Guenter Roeck, linux-doc,
linux-kernel, linux-hwmon
On Mon, 12 May 2025 at 21:21, Kurt Borja <kuurtb@gmail.com> wrote:
>
> On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> > This driver requires to be able to handle transactions that perform
> > multiple WMI actions at a time. Therefore, it needs to be able to
> > lock the wmi_lock mutex for multiple operations.
> >
> > Add msi_wmi_platform_query_unlocked() to allow the caller to
> > perform the WMI query without locking the wmi_lock mutex, by
> > renaming the existing function and adding a new one that only
> > locks the mutex.
> >
> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>
> You only use msi_wmi_platform_query_unlocked() to protect the
> fan_curve/AP state right?
>
> If that's the case I think we don't need it. AFAIK sysfs reads/writes
> are already synchronized/locked, and as I mentioned in Patch 10, I don't
> think you need this variant in probe/remove either.
>
> I'd like to hear more opinions on this though.
Are sysfs reads/writes between different files of the same driver
synced? If not, it is better to lock.
I want a second opinion here too.
You are correct on probe/remove.
Antheas
> --
> ~ Kurt
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 02/10] platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
2025-05-12 20:51 ` Antheas Kapenekakis
@ 2025-05-12 21:23 ` Kurt Borja
2025-05-12 21:51 ` Antheas Kapenekakis
0 siblings, 1 reply; 45+ messages in thread
From: Kurt Borja @ 2025-05-12 21:23 UTC (permalink / raw)
To: Antheas Kapenekakis
Cc: platform-driver-x86, Armin Wolf, Jonathan Corbet, Hans de Goede,
Ilpo Järvinen, Jean Delvare, Guenter Roeck, linux-doc,
linux-kernel, linux-hwmon
[-- Attachment #1: Type: text/plain, Size: 1794 bytes --]
On Mon May 12, 2025 at 5:51 PM -03, Antheas Kapenekakis wrote:
> On Mon, 12 May 2025 at 21:21, Kurt Borja <kuurtb@gmail.com> wrote:
>>
>> On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
>> > This driver requires to be able to handle transactions that perform
>> > multiple WMI actions at a time. Therefore, it needs to be able to
>> > lock the wmi_lock mutex for multiple operations.
>> >
>> > Add msi_wmi_platform_query_unlocked() to allow the caller to
>> > perform the WMI query without locking the wmi_lock mutex, by
>> > renaming the existing function and adding a new one that only
>> > locks the mutex.
>> >
>> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>>
>> You only use msi_wmi_platform_query_unlocked() to protect the
>> fan_curve/AP state right?
>>
>> If that's the case I think we don't need it. AFAIK sysfs reads/writes
>> are already synchronized/locked, and as I mentioned in Patch 10, I don't
>> think you need this variant in probe/remove either.
>>
>> I'd like to hear more opinions on this though.
>
> Are sysfs reads/writes between different files of the same driver
> synced? If not, it is better to lock.
You are right, you definitely need locking there.
However, what do you think about introducing a new lock specifically for
this state?
IMO locks should never be multi-function and I don't see why all WMI
calls have to contest the same lock that we use for fan stuff. This
would eliminate the need for this extra function.
Also keep in mind that by introducing this patch you are also extending
the time the lock is held per WMI call, which is also unnecessary.
--
~ Kurt
>
> I want a second opinion here too.
>
> You are correct on probe/remove.
>
> Antheas
>
>> --
>> ~ Kurt
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 02/10] platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
2025-05-12 21:23 ` Kurt Borja
@ 2025-05-12 21:51 ` Antheas Kapenekakis
0 siblings, 0 replies; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-12 21:51 UTC (permalink / raw)
To: Kurt Borja
Cc: platform-driver-x86, Armin Wolf, Jonathan Corbet, Hans de Goede,
Ilpo Järvinen, Jean Delvare, Guenter Roeck, linux-doc,
linux-kernel, linux-hwmon
On Mon, 12 May 2025 at 23:24, Kurt Borja <kuurtb@gmail.com> wrote:
>
> On Mon May 12, 2025 at 5:51 PM -03, Antheas Kapenekakis wrote:
> > On Mon, 12 May 2025 at 21:21, Kurt Borja <kuurtb@gmail.com> wrote:
> >>
> >> On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> >> > This driver requires to be able to handle transactions that perform
> >> > multiple WMI actions at a time. Therefore, it needs to be able to
> >> > lock the wmi_lock mutex for multiple operations.
> >> >
> >> > Add msi_wmi_platform_query_unlocked() to allow the caller to
> >> > perform the WMI query without locking the wmi_lock mutex, by
> >> > renaming the existing function and adding a new one that only
> >> > locks the mutex.
> >> >
> >> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> >>
> >> You only use msi_wmi_platform_query_unlocked() to protect the
> >> fan_curve/AP state right?
> >>
> >> If that's the case I think we don't need it. AFAIK sysfs reads/writes
> >> are already synchronized/locked, and as I mentioned in Patch 10, I don't
> >> think you need this variant in probe/remove either.
> >>
> >> I'd like to hear more opinions on this though.
> >
> > Are sysfs reads/writes between different files of the same driver
> > synced? If not, it is better to lock.
>
> You are right, you definitely need locking there.
>
> However, what do you think about introducing a new lock specifically for
> this state?
>
> IMO locks should never be multi-function and I don't see why all WMI
> calls have to contest the same lock that we use for fan stuff. This
> would eliminate the need for this extra function.
The _unlocked variant was meant to be used for operations that read,
mutate, then write the same value. Therefore, we should ensure that it
is not possible to call WMI functions in-between that could
potentially cause that value to be overwritten.
Theoretically, at least. Although after I simplified shift mode and
the battery threshold, I ended up doing only writes for them.
Off the top of my head I think that both shift mode and Set_AP (used
for enabling the custom fan curve) touch the same register (Set_AP
touches 3 registers and one of them is shift mode[1]). So it is safer
to use a single lock and avoid edge cases like this one.
> Also keep in mind that by introducing this patch you are also extending
> the time the lock is held per WMI call, which is also unnecessary.
I would say marginally, sure, although the operations we extend the
lock outside of are trivial. A case could be made that locking
multiple times hurts performance more, as if we introduced a fan curve
lock, each fan operation would have to lock at least three times
instead of one (e.g., disabling a custom fan curve would take 6
locks).
Antheas
[1] https://github.com/hhd-dev/hwinfo/blob/master/devices/msi_claw8/acpi/decoded/dsdt.dsl
> --
> ~ Kurt
>
> >
> > I want a second opinion here too.
> >
> > You are correct on probe/remove.
> >
> > Antheas
> >
> >> --
> >> ~ Kurt
>
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 02/10] platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
2025-05-12 19:21 ` Kurt Borja
2025-05-12 20:51 ` Antheas Kapenekakis
@ 2025-05-13 19:45 ` Armin Wolf
1 sibling, 0 replies; 45+ messages in thread
From: Armin Wolf @ 2025-05-13 19:45 UTC (permalink / raw)
To: Kurt Borja, Antheas Kapenekakis, platform-driver-x86
Cc: Jonathan Corbet, Hans de Goede, Ilpo Järvinen, Jean Delvare,
Guenter Roeck, linux-doc, linux-kernel, linux-hwmon
Am 12.05.25 um 21:21 schrieb Kurt Borja:
> On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
>> This driver requires to be able to handle transactions that perform
>> multiple WMI actions at a time. Therefore, it needs to be able to
>> lock the wmi_lock mutex for multiple operations.
>>
>> Add msi_wmi_platform_query_unlocked() to allow the caller to
>> perform the WMI query without locking the wmi_lock mutex, by
>> renaming the existing function and adding a new one that only
>> locks the mutex.
>>
>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> You only use msi_wmi_platform_query_unlocked() to protect the
> fan_curve/AP state right?
>
> If that's the case I think we don't need it. AFAIK sysfs reads/writes
> are already synchronized/locked, and as I mentioned in Patch 10, I don't
> think you need this variant in probe/remove either.
>
> I'd like to hear more opinions on this though.
>
The reason why we want such a function is that sometimes we need to perform
read-modify-write operations over the WMI interface, forcing us to prevent
other clients from calling a WMI method.
Thanks,
Armin Wolf
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 02/10] platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
2025-05-11 20:44 ` [PATCH v1 02/10] platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query Antheas Kapenekakis
2025-05-12 19:21 ` Kurt Borja
@ 2025-05-13 19:47 ` Armin Wolf
1 sibling, 0 replies; 45+ messages in thread
From: Armin Wolf @ 2025-05-13 19:47 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Jonathan Corbet, Hans de Goede, Ilpo Järvinen, Jean Delvare,
Guenter Roeck, Kurt Borja, linux-doc, linux-kernel, linux-hwmon
Am 11.05.25 um 22:44 schrieb Antheas Kapenekakis:
> This driver requires to be able to handle transactions that perform
> multiple WMI actions at a time. Therefore, it needs to be able to
> lock the wmi_lock mutex for multiple operations.
>
> Add msi_wmi_platform_query_unlocked() to allow the caller to
> perform the WMI query without locking the wmi_lock mutex, by
> renaming the existing function and adding a new one that only
> locks the mutex.
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/msi-wmi-platform.c | 27 ++++++++++++++++---------
> 1 file changed, 17 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index 41218a9d6e35d..f0d1b8e1a2fec 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -140,7 +140,7 @@ static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, siz
> return 0;
> }
>
> -static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
> +static int msi_wmi_platform_query_unlocked(struct msi_wmi_platform_data *data,
> enum msi_wmi_platform_method method, u8 *buffer,
> size_t length)
> {
> @@ -156,15 +156,9 @@ static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
> if (!length)
> return -EINVAL;
>
> - /*
> - * The ACPI control method responsible for handling the WMI method calls
> - * is not thread-safe. Because of this we have to do the locking ourself.
> - */
> - scoped_guard(mutex, &data->wmi_lock) {
> - status = wmidev_evaluate_method(data->wdev, 0x0, method, &in, &out);
> - if (ACPI_FAILURE(status))
> - return -EIO;
> - }
> + status = wmidev_evaluate_method(data->wdev, 0x0, method, &in, &out);
> + if (ACPI_FAILURE(status))
> + return -EIO;
>
> obj = out.pointer;
> if (!obj)
> @@ -176,6 +170,19 @@ static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
> return ret;
> }
>
> +static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
> + enum msi_wmi_platform_method method, u8 *buffer,
> + size_t length)
> +{
> + /*
> + * The ACPI control method responsible for handling the WMI method calls
> + * is not thread-safe. Because of this we have to do the locking ourself.
> + */
> + scoped_guard(mutex, &data->wmi_lock) {
> + return msi_wmi_platform_query_unlocked(data, method, buffer, length);
> + }
Please just use guard() here.
Thanks,
Armin Wolf
> +}
> +
> static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type,
> u32 attr, int channel)
> {
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v1 03/10] platform/x86: msi-wmi-platform: Add quirk system
2025-05-11 20:44 [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Antheas Kapenekakis
2025-05-11 20:44 ` [PATCH v1 01/10] platform/x86: msi-wmi-platform: Use input buffer for returning result Antheas Kapenekakis
2025-05-11 20:44 ` [PATCH v1 02/10] platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query Antheas Kapenekakis
@ 2025-05-11 20:44 ` Antheas Kapenekakis
2025-05-11 23:32 ` Kurt Borja
2025-05-13 20:43 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 04/10] platform/x86: msi-wmi-platform: Add support for fan control Antheas Kapenekakis
` (8 subsequent siblings)
11 siblings, 2 replies; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-11 20:44 UTC (permalink / raw)
To: platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, Kurt Borja, linux-doc, linux-kernel,
linux-hwmon, Antheas Kapenekakis
MSI uses the WMI interface as a passthrough for writes to the EC
and uses a board name match and a quirk table from userspace on
Windows. Therefore, there is no auto-detection functionality and
we have to fallback to a quirk table.
Introduce it here, prior to starting to add features to it.
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
drivers/platform/x86/msi-wmi-platform.c | 45 +++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
index f0d1b8e1a2fec..408d42ab19e20 100644
--- a/drivers/platform/x86/msi-wmi-platform.c
+++ b/drivers/platform/x86/msi-wmi-platform.c
@@ -14,6 +14,7 @@
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/device/driver.h>
+#include <linux/dmi.h>
#include <linux/errno.h>
#include <linux/hwmon.h>
#include <linux/kernel.h>
@@ -79,8 +80,12 @@ enum msi_wmi_platform_method {
MSI_PLATFORM_GET_WMI = 0x1d,
};
+struct msi_wmi_platform_quirk {
+};
+
struct msi_wmi_platform_data {
struct wmi_device *wdev;
+ struct msi_wmi_platform_quirk *quirks;
struct mutex wmi_lock; /* Necessary when calling WMI methods */
};
@@ -124,6 +129,39 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
"get_wmi"
};
+static struct msi_wmi_platform_quirk quirk_default = {};
+static struct msi_wmi_platform_quirk quirk_gen1 = {
+};
+static struct msi_wmi_platform_quirk quirk_gen2 = {
+};
+
+static const struct dmi_system_id msi_quirks[] = {
+ {
+ .ident = "MSI Claw (gen 1)",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-1T41"),
+ },
+ .driver_data = &quirk_gen1,
+ },
+ {
+ .ident = "MSI Claw AI+ 7",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-1T42"),
+ },
+ .driver_data = &quirk_gen2,
+ },
+ {
+ .ident = "MSI Claw AI+ 8",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-1T52"),
+ },
+ .driver_data = &quirk_gen2,
+ },
+};
+
static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, size_t length)
{
if (obj->type != ACPI_TYPE_BUFFER)
@@ -413,6 +451,7 @@ static int msi_wmi_platform_init(struct msi_wmi_platform_data *data)
static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
{
struct msi_wmi_platform_data *data;
+ const struct dmi_system_id *dmi_id;
int ret;
data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
@@ -422,6 +461,12 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
data->wdev = wdev;
dev_set_drvdata(&wdev->dev, data);
+ dmi_id = dmi_first_match(msi_quirks);
+ if (dmi_id)
+ data->quirks = dmi_id->driver_data;
+ else
+ data->quirks = &quirk_default;
+
ret = devm_mutex_init(&wdev->dev, &data->wmi_lock);
if (ret < 0)
return ret;
--
2.49.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v1 03/10] platform/x86: msi-wmi-platform: Add quirk system
2025-05-11 20:44 ` [PATCH v1 03/10] platform/x86: msi-wmi-platform: Add quirk system Antheas Kapenekakis
@ 2025-05-11 23:32 ` Kurt Borja
2025-05-13 20:43 ` Armin Wolf
1 sibling, 0 replies; 45+ messages in thread
From: Kurt Borja @ 2025-05-11 23:32 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, linux-doc, linux-kernel, linux-hwmon
[-- Attachment #1: Type: text/plain, Size: 3617 bytes --]
On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> MSI uses the WMI interface as a passthrough for writes to the EC
> and uses a board name match and a quirk table from userspace on
> Windows. Therefore, there is no auto-detection functionality and
> we have to fallback to a quirk table.
>
> Introduce it here, prior to starting to add features to it.
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/msi-wmi-platform.c | 45 +++++++++++++++++++++++++
> 1 file changed, 45 insertions(+)
>
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index f0d1b8e1a2fec..408d42ab19e20 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -14,6 +14,7 @@
> #include <linux/debugfs.h>
> #include <linux/device.h>
> #include <linux/device/driver.h>
> +#include <linux/dmi.h>
> #include <linux/errno.h>
> #include <linux/hwmon.h>
> #include <linux/kernel.h>
> @@ -79,8 +80,12 @@ enum msi_wmi_platform_method {
> MSI_PLATFORM_GET_WMI = 0x1d,
> };
>
> +struct msi_wmi_platform_quirk {
> +};
> +
> struct msi_wmi_platform_data {
> struct wmi_device *wdev;
> + struct msi_wmi_platform_quirk *quirks;
> struct mutex wmi_lock; /* Necessary when calling WMI methods */
> };
>
> @@ -124,6 +129,39 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
> "get_wmi"
> };
>
> +static struct msi_wmi_platform_quirk quirk_default = {};
This is static, you can drop the `= {}`.
> +static struct msi_wmi_platform_quirk quirk_gen1 = {
> +};
> +static struct msi_wmi_platform_quirk quirk_gen2 = {
> +};
> +
> +static const struct dmi_system_id msi_quirks[] = {
This should be moved to an .init section, i.e. marked as __initconst.
To achieve this, you have to move DMI matching to module_init and
*quirks most be static.
This way this memory can be freed after init.
--
~ Kurt
> + {
> + .ident = "MSI Claw (gen 1)",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
> + DMI_MATCH(DMI_BOARD_NAME, "MS-1T41"),
> + },
> + .driver_data = &quirk_gen1,
> + },
> + {
> + .ident = "MSI Claw AI+ 7",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
> + DMI_MATCH(DMI_BOARD_NAME, "MS-1T42"),
> + },
> + .driver_data = &quirk_gen2,
> + },
> + {
> + .ident = "MSI Claw AI+ 8",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
> + DMI_MATCH(DMI_BOARD_NAME, "MS-1T52"),
> + },
> + .driver_data = &quirk_gen2,
> + },
> +};
> +
> static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, size_t length)
> {
> if (obj->type != ACPI_TYPE_BUFFER)
> @@ -413,6 +451,7 @@ static int msi_wmi_platform_init(struct msi_wmi_platform_data *data)
> static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> {
> struct msi_wmi_platform_data *data;
> + const struct dmi_system_id *dmi_id;
> int ret;
>
> data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
> @@ -422,6 +461,12 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> data->wdev = wdev;
> dev_set_drvdata(&wdev->dev, data);
>
> + dmi_id = dmi_first_match(msi_quirks);
> + if (dmi_id)
> + data->quirks = dmi_id->driver_data;
> + else
> + data->quirks = &quirk_default;
> +
> ret = devm_mutex_init(&wdev->dev, &data->wmi_lock);
> if (ret < 0)
> return ret;
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 03/10] platform/x86: msi-wmi-platform: Add quirk system
2025-05-11 20:44 ` [PATCH v1 03/10] platform/x86: msi-wmi-platform: Add quirk system Antheas Kapenekakis
2025-05-11 23:32 ` Kurt Borja
@ 2025-05-13 20:43 ` Armin Wolf
1 sibling, 0 replies; 45+ messages in thread
From: Armin Wolf @ 2025-05-13 20:43 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Jonathan Corbet, Hans de Goede, Ilpo Järvinen, Jean Delvare,
Guenter Roeck, Kurt Borja, linux-doc, linux-kernel, linux-hwmon
Am 11.05.25 um 22:44 schrieb Antheas Kapenekakis:
> MSI uses the WMI interface as a passthrough for writes to the EC
> and uses a board name match and a quirk table from userspace on
> Windows. Therefore, there is no auto-detection functionality and
> we have to fallback to a quirk table.
>
> Introduce it here, prior to starting to add features to it.
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/msi-wmi-platform.c | 45 +++++++++++++++++++++++++
> 1 file changed, 45 insertions(+)
>
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index f0d1b8e1a2fec..408d42ab19e20 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -14,6 +14,7 @@
> #include <linux/debugfs.h>
> #include <linux/device.h>
> #include <linux/device/driver.h>
> +#include <linux/dmi.h>
> #include <linux/errno.h>
> #include <linux/hwmon.h>
> #include <linux/kernel.h>
> @@ -79,8 +80,12 @@ enum msi_wmi_platform_method {
> MSI_PLATFORM_GET_WMI = 0x1d,
> };
>
> +struct msi_wmi_platform_quirk {
> +};
> +
> struct msi_wmi_platform_data {
> struct wmi_device *wdev;
> + struct msi_wmi_platform_quirk *quirks;
> struct mutex wmi_lock; /* Necessary when calling WMI methods */
> };
>
> @@ -124,6 +129,39 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
> "get_wmi"
> };
>
> +static struct msi_wmi_platform_quirk quirk_default = {};
> +static struct msi_wmi_platform_quirk quirk_gen1 = {
> +};
> +static struct msi_wmi_platform_quirk quirk_gen2 = {
> +};
> +
> +static const struct dmi_system_id msi_quirks[] = {
> + {
> + .ident = "MSI Claw (gen 1)",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
> + DMI_MATCH(DMI_BOARD_NAME, "MS-1T41"),
> + },
> + .driver_data = &quirk_gen1,
> + },
> + {
> + .ident = "MSI Claw AI+ 7",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
> + DMI_MATCH(DMI_BOARD_NAME, "MS-1T42"),
> + },
> + .driver_data = &quirk_gen2,
> + },
> + {
> + .ident = "MSI Claw AI+ 8",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
> + DMI_MATCH(DMI_BOARD_NAME, "MS-1T52"),
> + },
> + .driver_data = &quirk_gen2,
> + },
> +};
I would prefer if we rely on the model code reported by the embedded controller instead of
the SMBIOS board name.
The model code is contained inside the firmware string returned by the Get_EC() WMI method,
see https://github.com/BeardOverflow/msi-ec/blob/main/docs/device_support_guide.md (section "Additional Info") for details.
This way support for devices sharing a model code can be implemented more efficiently.
I suggest we extract this model code inside msi_wmi_platform_ec_init() and perform the quirk matching there.
Thanks,
Armin Wolf
> +
> static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, size_t length)
> {
> if (obj->type != ACPI_TYPE_BUFFER)
> @@ -413,6 +451,7 @@ static int msi_wmi_platform_init(struct msi_wmi_platform_data *data)
> static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> {
> struct msi_wmi_platform_data *data;
> + const struct dmi_system_id *dmi_id;
> int ret;
>
> data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
> @@ -422,6 +461,12 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> data->wdev = wdev;
> dev_set_drvdata(&wdev->dev, data);
>
> + dmi_id = dmi_first_match(msi_quirks);
> + if (dmi_id)
> + data->quirks = dmi_id->driver_data;
> + else
> + data->quirks = &quirk_default;
> +
> ret = devm_mutex_init(&wdev->dev, &data->wmi_lock);
> if (ret < 0)
> return ret;
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v1 04/10] platform/x86: msi-wmi-platform: Add support for fan control
2025-05-11 20:44 [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Antheas Kapenekakis
` (2 preceding siblings ...)
2025-05-11 20:44 ` [PATCH v1 03/10] platform/x86: msi-wmi-platform: Add quirk system Antheas Kapenekakis
@ 2025-05-11 20:44 ` Antheas Kapenekakis
2025-05-11 23:32 ` Kurt Borja
2025-05-13 20:58 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 05/10] platform/x86: msi-wmi-platform: Add platform profile through shift mode Antheas Kapenekakis
` (7 subsequent siblings)
11 siblings, 2 replies; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-11 20:44 UTC (permalink / raw)
To: platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, Kurt Borja, linux-doc, linux-kernel,
linux-hwmon, Antheas Kapenekakis
From: Armin Wolf <W_Armin@gmx.de>
Adds fan curve support for the MSI platform. These devices contain
support for two fans, where they are named CPU and GPU but in the
case of the Claw series just map to left and right fan.
Co-developed-by: Antheas Kapenekakis <lkml@antheas.dev>
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
Signed-off-by: Armin Wolf <W_Armin@gmx.de>
---
.../wmi/devices/msi-wmi-platform.rst | 26 ++
drivers/platform/x86/msi-wmi-platform.c | 328 +++++++++++++++++-
2 files changed, 337 insertions(+), 17 deletions(-)
diff --git a/Documentation/wmi/devices/msi-wmi-platform.rst b/Documentation/wmi/devices/msi-wmi-platform.rst
index 73197b31926a5..704bfdac5203e 100644
--- a/Documentation/wmi/devices/msi-wmi-platform.rst
+++ b/Documentation/wmi/devices/msi-wmi-platform.rst
@@ -169,6 +169,32 @@ The fan RPM readings can be calculated with the following formula:
If the fan speed reading is zero, then the fan RPM is zero too.
+The subfeature ``0x01`` is used to retrieve the fan speed table for the CPU fan. The output
+data contains the fan speed table and two bytes with unknown data. The fan speed table
+consists of six 8-bit entries, each containing a fan speed value in percent.
+
+The subfeature ``0x02`` is used tho retrieve the same data for the GPU fan.
+
+WMI method Set_Fan()
+--------------------
+
+The fan speed tables can be accessed using subfeature ``0x01`` (CPU fan) and subfeature ``0x02``
+(GPU fan). The input data has the same format as the output data of the ``Get_Fan`` WMI method.
+
+WMI method Get_AP()
+-------------------
+
+The current fan mode can be accessed using subfeature ``0x01``. The output data contains a flag
+byte and two bytes of unknown data. If the 7th bit inside the flag byte is cleared then all fans
+are operating in automatic mode, otherwise the fans operate based on the fan speed tables
+accessible thru the ``Get_Fan``/``Set_Fan`` WMI methods.
+
+WMI method Set_AP()
+-------------------
+
+The current fan mode can be changed using subfeature ``0x01``. The input data has the same format
+as the output data of the ``Get_AP`` WMI method.
+
WMI method Get_WMI()
--------------------
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
index 408d42ab19e20..9ac3c6f1b3f1d 100644
--- a/drivers/platform/x86/msi-wmi-platform.c
+++ b/drivers/platform/x86/msi-wmi-platform.c
@@ -16,13 +16,18 @@
#include <linux/device/driver.h>
#include <linux/dmi.h>
#include <linux/errno.h>
+#include <linux/fixp-arith.h>
#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
#include <linux/kernel.h>
+#include <linux/kstrtox.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/printk.h>
#include <linux/rwsem.h>
#include <linux/string.h>
+#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/wmi.h>
@@ -34,9 +39,11 @@
#define MSI_WMI_PLATFORM_INTERFACE_VERSION 2
+/* Get_WMI() WMI method */
#define MSI_PLATFORM_WMI_MAJOR_OFFSET 1
#define MSI_PLATFORM_WMI_MINOR_OFFSET 2
+/* Get_EC() and Set_EC() WMI methods */
#define MSI_PLATFORM_EC_FLAGS_OFFSET 1
#define MSI_PLATFORM_EC_MINOR_MASK GENMASK(3, 0)
#define MSI_PLATFORM_EC_MAJOR_MASK GENMASK(5, 4)
@@ -44,6 +51,18 @@
#define MSI_PLATFORM_EC_IS_TIGERLAKE BIT(7)
#define MSI_PLATFORM_EC_VERSION_OFFSET 2
+/* Get_Fan() and Set_Fan() WMI methods */
+#define MSI_PLATFORM_FAN_SUBFEATURE_FAN_SPEED 0x0
+#define MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE 0x1
+#define MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE 0x2
+#define MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE 0x1
+#define MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE 0x2
+
+/* Get_AP() and Set_AP() WMI methods */
+#define MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE 0x1
+#define MSI_PLATFORM_AP_FAN_FLAGS_OFFSET 1
+#define MSI_PLATFORM_AP_ENABLE_FAN_TABLES BIT(7)
+
static bool force;
module_param_unsafe(force, bool, 0);
MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
@@ -221,9 +240,201 @@ static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
}
}
+static ssize_t msi_wmi_platform_fan_table_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+ struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+ u8 buffer[32] = { sattr->nr };
+ u8 fan_percent;
+ int ret;
+
+ ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buffer, sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ fan_percent = buffer[sattr->index + 1];
+ if (fan_percent > 100)
+ return -EIO;
+
+ return sysfs_emit(buf, "%d\n", fixp_linear_interpolate(0, 0, 100, 255, fan_percent));
+}
+
+static ssize_t msi_wmi_platform_fan_table_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+ struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+ u8 buffer[32] = { sattr->nr };
+ long speed;
+ int ret;
+
+ ret = kstrtol(buf, 10, &speed);
+ if (ret < 0)
+ return ret;
+
+ speed = clamp_val(speed, 0, 255);
+
+ guard(mutex)(&data->wmi_lock);
+
+ ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_GET_FAN,
+ buffer, sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ buffer[0] = sattr->nr;
+ buffer[sattr->index + 1] = fixp_linear_interpolate(0, 0, 255, 100, speed);
+
+ ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_FAN,
+ buffer, sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t msi_wmi_platform_temp_table_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+ struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+ u8 buffer[32] = { sattr->nr };
+ u8 temp_c;
+ int ret;
+
+ ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_TEMPERATURE,
+ buffer, sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ temp_c = buffer[sattr->index + 1];
+
+ return sysfs_emit(buf, "%d\n", temp_c);
+}
+
+static ssize_t msi_wmi_platform_temp_table_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+ struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+ u8 buffer[32] = { sattr->nr };
+ long temp_c;
+ int ret;
+
+ ret = kstrtol(buf, 10, &temp_c);
+ if (ret < 0)
+ return ret;
+
+ temp_c = clamp_val(temp_c, 0, 255);
+
+ guard(mutex)(&data->wmi_lock);
+
+ ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_GET_TEMPERATURE,
+ buffer, sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ buffer[0] = sattr->nr;
+ buffer[sattr->index + 1] = temp_c;
+
+ ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_TEMPERATURE,
+ buffer, sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_temp, msi_wmi_platform_temp_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x0);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_temp, msi_wmi_platform_temp_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x3);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_temp, msi_wmi_platform_temp_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x4);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_temp, msi_wmi_platform_temp_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x5);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_temp, msi_wmi_platform_temp_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x6);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_temp, msi_wmi_platform_temp_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x7);
+
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_pwm, msi_wmi_platform_fan_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x1);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_pwm, msi_wmi_platform_fan_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x2);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_pwm, msi_wmi_platform_fan_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x3);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_pwm, msi_wmi_platform_fan_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x4);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_pwm, msi_wmi_platform_fan_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x5);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_pwm, msi_wmi_platform_fan_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x6);
+
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_temp, msi_wmi_platform_temp_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x0);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_temp, msi_wmi_platform_temp_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x3);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_temp, msi_wmi_platform_temp_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x4);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_temp, msi_wmi_platform_temp_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x5);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_temp, msi_wmi_platform_temp_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x6);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_temp, msi_wmi_platform_temp_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x7);
+
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_pwm, msi_wmi_platform_fan_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x1);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_pwm, msi_wmi_platform_fan_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x2);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_pwm, msi_wmi_platform_fan_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x3);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_pwm, msi_wmi_platform_fan_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x4);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_pwm, msi_wmi_platform_fan_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x5);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_pwm, msi_wmi_platform_fan_table,
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x6);
+
+static struct attribute *msi_wmi_platform_hwmon_attrs[] = {
+ &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr,
+
+ &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr,
+
+ &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm2_auto_point5_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm2_auto_point6_temp.dev_attr.attr,
+
+ &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm2_auto_point5_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm2_auto_point6_pwm.dev_attr.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(msi_wmi_platform_hwmon);
+
static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type,
u32 attr, int channel)
{
+ if (type == hwmon_pwm && attr == hwmon_pwm_enable)
+ return 0644;
+
return 0444;
}
@@ -233,24 +444,102 @@ static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types typ
struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
u8 buffer[32] = { 0 };
u16 value;
+ u8 flags;
int ret;
- ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buf, sizeof(buf));
- if (ret < 0)
- return ret;
+ switch (type) {
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_input:
+ buffer[0] = MSI_PLATFORM_FAN_SUBFEATURE_FAN_SPEED;
+ ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buffer,
+ sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ value = get_unaligned_be16(&buffer[channel * 2 + 1]);
+ if (!value)
+ *val = 0;
+ else
+ *val = 480000 / value;
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_enable:
+ buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
+ ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_AP, buffer,
+ sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ flags = buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET];
+ if (flags & MSI_PLATFORM_AP_ENABLE_FAN_TABLES)
+ *val = 1;
+ else
+ *val = 2;
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
- value = get_unaligned_be16(&buffer[channel * 2 + 1]);
- if (!value)
- *val = 0;
- else
- *val = 480000 / value;
+static int msi_wmi_platform_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, long val)
+{
+ struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+ u8 buffer[32] = { };
+ int ret;
- return 0;
+ switch (type) {
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_enable:
+ guard(mutex)(&data->wmi_lock);
+
+ buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
+ ret = msi_wmi_platform_query_unlocked(
+ data, MSI_PLATFORM_GET_AP, buffer,
+ sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
+ switch (val) {
+ case 1:
+ buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET] |=
+ MSI_PLATFORM_AP_ENABLE_FAN_TABLES;
+ break;
+ case 2:
+ buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET] &=
+ ~MSI_PLATFORM_AP_ENABLE_FAN_TABLES;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return msi_wmi_platform_query_unlocked(
+ data, MSI_PLATFORM_SET_AP, buffer,
+ sizeof(buffer));
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
}
static const struct hwmon_ops msi_wmi_platform_ops = {
.is_visible = msi_wmi_platform_is_visible,
.read = msi_wmi_platform_read,
+ .write = msi_wmi_platform_write,
};
static const struct hwmon_channel_info * const msi_wmi_platform_info[] = {
@@ -260,6 +549,10 @@ static const struct hwmon_channel_info * const msi_wmi_platform_info[] = {
HWMON_F_INPUT,
HWMON_F_INPUT
),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_ENABLE,
+ HWMON_PWM_ENABLE
+ ),
NULL
};
@@ -268,8 +561,8 @@ static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
.info = msi_wmi_platform_info,
};
-static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input, size_t length,
- loff_t *offset)
+static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
+ size_t length, loff_t *offset)
{
struct seq_file *seq = fp->private_data;
struct msi_wmi_platform_debugfs_data *data = seq->private;
@@ -303,7 +596,7 @@ static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input,
return length;
}
-static int msi_wmi_platform_show(struct seq_file *seq, void *p)
+static int msi_wmi_platform_debugfs_show(struct seq_file *seq, void *p)
{
struct msi_wmi_platform_debugfs_data *data = seq->private;
int ret;
@@ -315,19 +608,19 @@ static int msi_wmi_platform_show(struct seq_file *seq, void *p)
return ret;
}
-static int msi_wmi_platform_open(struct inode *inode, struct file *fp)
+static int msi_wmi_platform_debugfs_open(struct inode *inode, struct file *fp)
{
struct msi_wmi_platform_debugfs_data *data = inode->i_private;
/* The seq_file uses the last byte of the buffer for detecting buffer overflows */
- return single_open_size(fp, msi_wmi_platform_show, data, data->length + 1);
+ return single_open_size(fp, msi_wmi_platform_debugfs_show, data, data->length + 1);
}
static const struct file_operations msi_wmi_platform_debugfs_fops = {
.owner = THIS_MODULE,
- .open = msi_wmi_platform_open,
+ .open = msi_wmi_platform_debugfs_open,
.read = seq_read,
- .write = msi_wmi_platform_write,
+ .write = msi_wmi_platform_debugfs_write,
.llseek = seq_lseek,
.release = single_release,
};
@@ -389,7 +682,8 @@ static int msi_wmi_platform_hwmon_init(struct msi_wmi_platform_data *data)
struct device *hdev;
hdev = devm_hwmon_device_register_with_info(&data->wdev->dev, "msi_wmi_platform", data,
- &msi_wmi_platform_chip_info, NULL);
+ &msi_wmi_platform_chip_info,
+ msi_wmi_platform_hwmon_groups);
return PTR_ERR_OR_ZERO(hdev);
}
--
2.49.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v1 04/10] platform/x86: msi-wmi-platform: Add support for fan control
2025-05-11 20:44 ` [PATCH v1 04/10] platform/x86: msi-wmi-platform: Add support for fan control Antheas Kapenekakis
@ 2025-05-11 23:32 ` Kurt Borja
2025-05-13 20:58 ` Armin Wolf
1 sibling, 0 replies; 45+ messages in thread
From: Kurt Borja @ 2025-05-11 23:32 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, linux-doc, linux-kernel, linux-hwmon
[-- Attachment #1: Type: text/plain, Size: 19114 bytes --]
On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> From: Armin Wolf <W_Armin@gmx.de>
>
> Adds fan curve support for the MSI platform. These devices contain
> support for two fans, where they are named CPU and GPU but in the
> case of the Claw series just map to left and right fan.
>
> Co-developed-by: Antheas Kapenekakis <lkml@antheas.dev>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> Signed-off-by: Armin Wolf <W_Armin@gmx.de>
> ---
> .../wmi/devices/msi-wmi-platform.rst | 26 ++
> drivers/platform/x86/msi-wmi-platform.c | 328 +++++++++++++++++-
> 2 files changed, 337 insertions(+), 17 deletions(-)
>
> diff --git a/Documentation/wmi/devices/msi-wmi-platform.rst b/Documentation/wmi/devices/msi-wmi-platform.rst
> index 73197b31926a5..704bfdac5203e 100644
> --- a/Documentation/wmi/devices/msi-wmi-platform.rst
> +++ b/Documentation/wmi/devices/msi-wmi-platform.rst
> @@ -169,6 +169,32 @@ The fan RPM readings can be calculated with the following formula:
>
> If the fan speed reading is zero, then the fan RPM is zero too.
>
> +The subfeature ``0x01`` is used to retrieve the fan speed table for the CPU fan. The output
> +data contains the fan speed table and two bytes with unknown data. The fan speed table
> +consists of six 8-bit entries, each containing a fan speed value in percent.
> +
> +The subfeature ``0x02`` is used tho retrieve the same data for the GPU fan.
> +
> +WMI method Set_Fan()
> +--------------------
> +
> +The fan speed tables can be accessed using subfeature ``0x01`` (CPU fan) and subfeature ``0x02``
> +(GPU fan). The input data has the same format as the output data of the ``Get_Fan`` WMI method.
> +
> +WMI method Get_AP()
> +-------------------
> +
> +The current fan mode can be accessed using subfeature ``0x01``. The output data contains a flag
> +byte and two bytes of unknown data. If the 7th bit inside the flag byte is cleared then all fans
> +are operating in automatic mode, otherwise the fans operate based on the fan speed tables
> +accessible thru the ``Get_Fan``/``Set_Fan`` WMI methods.
> +
> +WMI method Set_AP()
> +-------------------
> +
> +The current fan mode can be changed using subfeature ``0x01``. The input data has the same format
> +as the output data of the ``Get_AP`` WMI method.
> +
> WMI method Get_WMI()
> --------------------
>
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index 408d42ab19e20..9ac3c6f1b3f1d 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -16,13 +16,18 @@
> #include <linux/device/driver.h>
> #include <linux/dmi.h>
> #include <linux/errno.h>
> +#include <linux/fixp-arith.h>
> #include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> #include <linux/kernel.h>
> +#include <linux/kstrtox.h>
> +#include <linux/minmax.h>
> #include <linux/module.h>
> #include <linux/mutex.h>
> #include <linux/printk.h>
> #include <linux/rwsem.h>
> #include <linux/string.h>
> +#include <linux/sysfs.h>
> #include <linux/types.h>
> #include <linux/wmi.h>
>
> @@ -34,9 +39,11 @@
>
> #define MSI_WMI_PLATFORM_INTERFACE_VERSION 2
>
> +/* Get_WMI() WMI method */
> #define MSI_PLATFORM_WMI_MAJOR_OFFSET 1
> #define MSI_PLATFORM_WMI_MINOR_OFFSET 2
>
> +/* Get_EC() and Set_EC() WMI methods */
> #define MSI_PLATFORM_EC_FLAGS_OFFSET 1
> #define MSI_PLATFORM_EC_MINOR_MASK GENMASK(3, 0)
> #define MSI_PLATFORM_EC_MAJOR_MASK GENMASK(5, 4)
> @@ -44,6 +51,18 @@
> #define MSI_PLATFORM_EC_IS_TIGERLAKE BIT(7)
> #define MSI_PLATFORM_EC_VERSION_OFFSET 2
>
> +/* Get_Fan() and Set_Fan() WMI methods */
> +#define MSI_PLATFORM_FAN_SUBFEATURE_FAN_SPEED 0x0
> +#define MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE 0x1
> +#define MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE 0x2
> +#define MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE 0x1
> +#define MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE 0x2
Would be nice to have all these subfeatures described in docs.
> +
> +/* Get_AP() and Set_AP() WMI methods */
> +#define MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE 0x1
> +#define MSI_PLATFORM_AP_FAN_FLAGS_OFFSET 1
> +#define MSI_PLATFORM_AP_ENABLE_FAN_TABLES BIT(7)
> +
> static bool force;
> module_param_unsafe(force, bool, 0);
> MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
> @@ -221,9 +240,201 @@ static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
> }
> }
>
> +static ssize_t msi_wmi_platform_fan_table_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + u8 buffer[32] = { sattr->nr };
> + u8 fan_percent;
> + int ret;
> +
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + fan_percent = buffer[sattr->index + 1];
> + if (fan_percent > 100)
> + return -EIO;
> +
> + return sysfs_emit(buf, "%d\n", fixp_linear_interpolate(0, 0, 100, 255, fan_percent));
> +}
> +
> +static ssize_t msi_wmi_platform_fan_table_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + u8 buffer[32] = { sattr->nr };
> + long speed;
> + int ret;
> +
> + ret = kstrtol(buf, 10, &speed);
> + if (ret < 0)
> + return ret;
> +
> + speed = clamp_val(speed, 0, 255);
> +
> + guard(mutex)(&data->wmi_lock);
> +
> + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_GET_FAN,
> + buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + buffer[0] = sattr->nr;
> + buffer[sattr->index + 1] = fixp_linear_interpolate(0, 0, 255, 100, speed);
> +
> + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_FAN,
> + buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + return count;
> +}
> +
> +static ssize_t msi_wmi_platform_temp_table_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + u8 buffer[32] = { sattr->nr };
> + u8 temp_c;
> + int ret;
> +
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_TEMPERATURE,
> + buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + temp_c = buffer[sattr->index + 1];
HWMON ABI specifies temps are given in millidegrees for tempX_*
attributes.
It does not specify anything for `auto_point` attributes, but I think
this should be millidegrees, for consistency.
> +
> + return sysfs_emit(buf, "%d\n", temp_c);
> +}
> +
> +static ssize_t msi_wmi_platform_temp_table_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + u8 buffer[32] = { sattr->nr };
> + long temp_c;
> + int ret;
> +
> + ret = kstrtol(buf, 10, &temp_c);
> + if (ret < 0)
> + return ret;
> +
> + temp_c = clamp_val(temp_c, 0, 255);
> +
> + guard(mutex)(&data->wmi_lock);
> +
> + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_GET_TEMPERATURE,
> + buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + buffer[0] = sattr->nr;
> + buffer[sattr->index + 1] = temp_c;
> +
> + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_TEMPERATURE,
> + buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + return count;
> +}
> +
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x0);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x3);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x4);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x5);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x6);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x7);
> +
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x1);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x2);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x3);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x4);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x5);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x6);
> +
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x0);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x3);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x4);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x5);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x6);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x7);
> +
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x1);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x2);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x3);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x4);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x5);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x6);
> +
> +static struct attribute *msi_wmi_platform_hwmon_attrs[] = {
> + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr,
> +
> + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr,
> +
> + &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point5_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point6_temp.dev_attr.attr,
> +
> + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point5_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point6_pwm.dev_attr.attr,
> + NULL
> +};
> +ATTRIBUTE_GROUPS(msi_wmi_platform_hwmon);
> +
> static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type,
> u32 attr, int channel)
> {
> + if (type == hwmon_pwm && attr == hwmon_pwm_enable)
> + return 0644;
> +
> return 0444;
> }
>
> @@ -233,24 +444,102 @@ static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types typ
> struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> u8 buffer[32] = { 0 };
> u16 value;
> + u8 flags;
> int ret;
>
> - ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buf, sizeof(buf));
> - if (ret < 0)
> - return ret;
> + switch (type) {
> + case hwmon_fan:
> + switch (attr) {
> + case hwmon_fan_input:
> + buffer[0] = MSI_PLATFORM_FAN_SUBFEATURE_FAN_SPEED;
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buffer,
> + sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + value = get_unaligned_be16(&buffer[channel * 2 + 1]);
> + if (!value)
> + *val = 0;
> + else
> + *val = 480000 / value;
> +
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> + case hwmon_pwm:
> + switch (attr) {
> + case hwmon_pwm_enable:
> + buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_AP, buffer,
> + sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + flags = buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET];
> + if (flags & MSI_PLATFORM_AP_ENABLE_FAN_TABLES)
> + *val = 1;
> + else
> + *val = 2;
> +
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
>
> - value = get_unaligned_be16(&buffer[channel * 2 + 1]);
> - if (!value)
> - *val = 0;
> - else
> - *val = 480000 / value;
> +static int msi_wmi_platform_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> + int channel, long val)
> +{
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + u8 buffer[32] = { };
> + int ret;
>
> - return 0;
> + switch (type) {
> + case hwmon_pwm:
> + switch (attr) {
> + case hwmon_pwm_enable:
> + guard(mutex)(&data->wmi_lock);
> +
> + buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
> + ret = msi_wmi_platform_query_unlocked(
> + data, MSI_PLATFORM_GET_AP, buffer,
> + sizeof(buffer));
Broken format on these calls.
> + if (ret < 0)
> + return ret;
> +
> + buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
> + switch (val) {
> + case 1:
> + buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET] |=
> + MSI_PLATFORM_AP_ENABLE_FAN_TABLES;
> + break;
> + case 2:
> + buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET] &=
> + ~MSI_PLATFORM_AP_ENABLE_FAN_TABLES;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return msi_wmi_platform_query_unlocked(
> + data, MSI_PLATFORM_SET_AP, buffer,
> + sizeof(buffer));
> + default:
> + return -EOPNOTSUPP;
> + }
> + default:
> + return -EOPNOTSUPP;
> + }
> }
>
> static const struct hwmon_ops msi_wmi_platform_ops = {
> .is_visible = msi_wmi_platform_is_visible,
> .read = msi_wmi_platform_read,
> + .write = msi_wmi_platform_write,
> };
>
> static const struct hwmon_channel_info * const msi_wmi_platform_info[] = {
> @@ -260,6 +549,10 @@ static const struct hwmon_channel_info * const msi_wmi_platform_info[] = {
> HWMON_F_INPUT,
> HWMON_F_INPUT
> ),
> + HWMON_CHANNEL_INFO(pwm,
> + HWMON_PWM_ENABLE,
> + HWMON_PWM_ENABLE
> + ),
> NULL
> };
>
> @@ -268,8 +561,8 @@ static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
> .info = msi_wmi_platform_info,
> };
>
> -static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input, size_t length,
> - loff_t *offset)
> +static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
Maybe mention this renames in commit messsage?
--
~ Kurt
> + size_t length, loff_t *offset)
> {
> struct seq_file *seq = fp->private_data;
> struct msi_wmi_platform_debugfs_data *data = seq->private;
> @@ -303,7 +596,7 @@ static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input,
> return length;
> }
>
> -static int msi_wmi_platform_show(struct seq_file *seq, void *p)
> +static int msi_wmi_platform_debugfs_show(struct seq_file *seq, void *p)
> {
> struct msi_wmi_platform_debugfs_data *data = seq->private;
> int ret;
> @@ -315,19 +608,19 @@ static int msi_wmi_platform_show(struct seq_file *seq, void *p)
> return ret;
> }
>
> -static int msi_wmi_platform_open(struct inode *inode, struct file *fp)
> +static int msi_wmi_platform_debugfs_open(struct inode *inode, struct file *fp)
> {
> struct msi_wmi_platform_debugfs_data *data = inode->i_private;
>
> /* The seq_file uses the last byte of the buffer for detecting buffer overflows */
> - return single_open_size(fp, msi_wmi_platform_show, data, data->length + 1);
> + return single_open_size(fp, msi_wmi_platform_debugfs_show, data, data->length + 1);
> }
>
> static const struct file_operations msi_wmi_platform_debugfs_fops = {
> .owner = THIS_MODULE,
> - .open = msi_wmi_platform_open,
> + .open = msi_wmi_platform_debugfs_open,
> .read = seq_read,
> - .write = msi_wmi_platform_write,
> + .write = msi_wmi_platform_debugfs_write,
> .llseek = seq_lseek,
> .release = single_release,
> };
> @@ -389,7 +682,8 @@ static int msi_wmi_platform_hwmon_init(struct msi_wmi_platform_data *data)
> struct device *hdev;
>
> hdev = devm_hwmon_device_register_with_info(&data->wdev->dev, "msi_wmi_platform", data,
> - &msi_wmi_platform_chip_info, NULL);
> + &msi_wmi_platform_chip_info,
> + msi_wmi_platform_hwmon_groups);
>
> return PTR_ERR_OR_ZERO(hdev);
> }
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 04/10] platform/x86: msi-wmi-platform: Add support for fan control
2025-05-11 20:44 ` [PATCH v1 04/10] platform/x86: msi-wmi-platform: Add support for fan control Antheas Kapenekakis
2025-05-11 23:32 ` Kurt Borja
@ 2025-05-13 20:58 ` Armin Wolf
2025-05-19 1:35 ` Armin Wolf
1 sibling, 1 reply; 45+ messages in thread
From: Armin Wolf @ 2025-05-13 20:58 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Jonathan Corbet, Hans de Goede, Ilpo Järvinen, Jean Delvare,
Guenter Roeck, Kurt Borja, linux-doc, linux-kernel, linux-hwmon
Am 11.05.25 um 22:44 schrieb Antheas Kapenekakis:
> From: Armin Wolf <W_Armin@gmx.de>
>
> Adds fan curve support for the MSI platform. These devices contain
> support for two fans, where they are named CPU and GPU but in the
> case of the Claw series just map to left and right fan.
I assume that not all devices support this feature? If so then please hide it behind
a quirk.
Thanks,
Armin Wolf
> Co-developed-by: Antheas Kapenekakis <lkml@antheas.dev>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> Signed-off-by: Armin Wolf <W_Armin@gmx.de>
> ---
> .../wmi/devices/msi-wmi-platform.rst | 26 ++
> drivers/platform/x86/msi-wmi-platform.c | 328 +++++++++++++++++-
> 2 files changed, 337 insertions(+), 17 deletions(-)
>
> diff --git a/Documentation/wmi/devices/msi-wmi-platform.rst b/Documentation/wmi/devices/msi-wmi-platform.rst
> index 73197b31926a5..704bfdac5203e 100644
> --- a/Documentation/wmi/devices/msi-wmi-platform.rst
> +++ b/Documentation/wmi/devices/msi-wmi-platform.rst
> @@ -169,6 +169,32 @@ The fan RPM readings can be calculated with the following formula:
>
> If the fan speed reading is zero, then the fan RPM is zero too.
>
> +The subfeature ``0x01`` is used to retrieve the fan speed table for the CPU fan. The output
> +data contains the fan speed table and two bytes with unknown data. The fan speed table
> +consists of six 8-bit entries, each containing a fan speed value in percent.
> +
> +The subfeature ``0x02`` is used tho retrieve the same data for the GPU fan.
> +
> +WMI method Set_Fan()
> +--------------------
> +
> +The fan speed tables can be accessed using subfeature ``0x01`` (CPU fan) and subfeature ``0x02``
> +(GPU fan). The input data has the same format as the output data of the ``Get_Fan`` WMI method.
> +
> +WMI method Get_AP()
> +-------------------
> +
> +The current fan mode can be accessed using subfeature ``0x01``. The output data contains a flag
> +byte and two bytes of unknown data. If the 7th bit inside the flag byte is cleared then all fans
> +are operating in automatic mode, otherwise the fans operate based on the fan speed tables
> +accessible thru the ``Get_Fan``/``Set_Fan`` WMI methods.
> +
> +WMI method Set_AP()
> +-------------------
> +
> +The current fan mode can be changed using subfeature ``0x01``. The input data has the same format
> +as the output data of the ``Get_AP`` WMI method.
> +
> WMI method Get_WMI()
> --------------------
>
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index 408d42ab19e20..9ac3c6f1b3f1d 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -16,13 +16,18 @@
> #include <linux/device/driver.h>
> #include <linux/dmi.h>
> #include <linux/errno.h>
> +#include <linux/fixp-arith.h>
> #include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> #include <linux/kernel.h>
> +#include <linux/kstrtox.h>
> +#include <linux/minmax.h>
> #include <linux/module.h>
> #include <linux/mutex.h>
> #include <linux/printk.h>
> #include <linux/rwsem.h>
> #include <linux/string.h>
> +#include <linux/sysfs.h>
> #include <linux/types.h>
> #include <linux/wmi.h>
>
> @@ -34,9 +39,11 @@
>
> #define MSI_WMI_PLATFORM_INTERFACE_VERSION 2
>
> +/* Get_WMI() WMI method */
> #define MSI_PLATFORM_WMI_MAJOR_OFFSET 1
> #define MSI_PLATFORM_WMI_MINOR_OFFSET 2
>
> +/* Get_EC() and Set_EC() WMI methods */
> #define MSI_PLATFORM_EC_FLAGS_OFFSET 1
> #define MSI_PLATFORM_EC_MINOR_MASK GENMASK(3, 0)
> #define MSI_PLATFORM_EC_MAJOR_MASK GENMASK(5, 4)
> @@ -44,6 +51,18 @@
> #define MSI_PLATFORM_EC_IS_TIGERLAKE BIT(7)
> #define MSI_PLATFORM_EC_VERSION_OFFSET 2
>
> +/* Get_Fan() and Set_Fan() WMI methods */
> +#define MSI_PLATFORM_FAN_SUBFEATURE_FAN_SPEED 0x0
> +#define MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE 0x1
> +#define MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE 0x2
> +#define MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE 0x1
> +#define MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE 0x2
> +
> +/* Get_AP() and Set_AP() WMI methods */
> +#define MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE 0x1
> +#define MSI_PLATFORM_AP_FAN_FLAGS_OFFSET 1
> +#define MSI_PLATFORM_AP_ENABLE_FAN_TABLES BIT(7)
> +
> static bool force;
> module_param_unsafe(force, bool, 0);
> MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
> @@ -221,9 +240,201 @@ static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
> }
> }
>
> +static ssize_t msi_wmi_platform_fan_table_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + u8 buffer[32] = { sattr->nr };
> + u8 fan_percent;
> + int ret;
> +
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + fan_percent = buffer[sattr->index + 1];
> + if (fan_percent > 100)
> + return -EIO;
> +
> + return sysfs_emit(buf, "%d\n", fixp_linear_interpolate(0, 0, 100, 255, fan_percent));
> +}
> +
> +static ssize_t msi_wmi_platform_fan_table_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + u8 buffer[32] = { sattr->nr };
> + long speed;
> + int ret;
> +
> + ret = kstrtol(buf, 10, &speed);
> + if (ret < 0)
> + return ret;
> +
> + speed = clamp_val(speed, 0, 255);
> +
> + guard(mutex)(&data->wmi_lock);
> +
> + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_GET_FAN,
> + buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + buffer[0] = sattr->nr;
> + buffer[sattr->index + 1] = fixp_linear_interpolate(0, 0, 255, 100, speed);
> +
> + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_FAN,
> + buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + return count;
> +}
> +
> +static ssize_t msi_wmi_platform_temp_table_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + u8 buffer[32] = { sattr->nr };
> + u8 temp_c;
> + int ret;
> +
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_TEMPERATURE,
> + buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + temp_c = buffer[sattr->index + 1];
> +
> + return sysfs_emit(buf, "%d\n", temp_c);
> +}
> +
> +static ssize_t msi_wmi_platform_temp_table_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + u8 buffer[32] = { sattr->nr };
> + long temp_c;
> + int ret;
> +
> + ret = kstrtol(buf, 10, &temp_c);
> + if (ret < 0)
> + return ret;
> +
> + temp_c = clamp_val(temp_c, 0, 255);
> +
> + guard(mutex)(&data->wmi_lock);
> +
> + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_GET_TEMPERATURE,
> + buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + buffer[0] = sattr->nr;
> + buffer[sattr->index + 1] = temp_c;
> +
> + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_TEMPERATURE,
> + buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + return count;
> +}
> +
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x0);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x3);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x4);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x5);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x6);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x7);
> +
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x1);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x2);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x3);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x4);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x5);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x6);
> +
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x0);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x3);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x4);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x5);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x6);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_temp, msi_wmi_platform_temp_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x7);
> +
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x1);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x2);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x3);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x4);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x5);
> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_pwm, msi_wmi_platform_fan_table,
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x6);
> +
> +static struct attribute *msi_wmi_platform_hwmon_attrs[] = {
> + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr,
> +
> + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr,
> +
> + &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point5_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point6_temp.dev_attr.attr,
> +
> + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point5_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm2_auto_point6_pwm.dev_attr.attr,
> + NULL
> +};
> +ATTRIBUTE_GROUPS(msi_wmi_platform_hwmon);
> +
> static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type,
> u32 attr, int channel)
> {
> + if (type == hwmon_pwm && attr == hwmon_pwm_enable)
> + return 0644;
> +
> return 0444;
> }
>
> @@ -233,24 +444,102 @@ static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types typ
> struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> u8 buffer[32] = { 0 };
> u16 value;
> + u8 flags;
> int ret;
>
> - ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buf, sizeof(buf));
> - if (ret < 0)
> - return ret;
> + switch (type) {
> + case hwmon_fan:
> + switch (attr) {
> + case hwmon_fan_input:
> + buffer[0] = MSI_PLATFORM_FAN_SUBFEATURE_FAN_SPEED;
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buffer,
> + sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + value = get_unaligned_be16(&buffer[channel * 2 + 1]);
> + if (!value)
> + *val = 0;
> + else
> + *val = 480000 / value;
> +
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> + case hwmon_pwm:
> + switch (attr) {
> + case hwmon_pwm_enable:
> + buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_AP, buffer,
> + sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + flags = buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET];
> + if (flags & MSI_PLATFORM_AP_ENABLE_FAN_TABLES)
> + *val = 1;
> + else
> + *val = 2;
> +
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
>
> - value = get_unaligned_be16(&buffer[channel * 2 + 1]);
> - if (!value)
> - *val = 0;
> - else
> - *val = 480000 / value;
> +static int msi_wmi_platform_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> + int channel, long val)
> +{
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + u8 buffer[32] = { };
> + int ret;
>
> - return 0;
> + switch (type) {
> + case hwmon_pwm:
> + switch (attr) {
> + case hwmon_pwm_enable:
> + guard(mutex)(&data->wmi_lock);
> +
> + buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
> + ret = msi_wmi_platform_query_unlocked(
> + data, MSI_PLATFORM_GET_AP, buffer,
> + sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
> + switch (val) {
> + case 1:
> + buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET] |=
> + MSI_PLATFORM_AP_ENABLE_FAN_TABLES;
> + break;
> + case 2:
> + buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET] &=
> + ~MSI_PLATFORM_AP_ENABLE_FAN_TABLES;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return msi_wmi_platform_query_unlocked(
> + data, MSI_PLATFORM_SET_AP, buffer,
> + sizeof(buffer));
> + default:
> + return -EOPNOTSUPP;
> + }
> + default:
> + return -EOPNOTSUPP;
> + }
> }
>
> static const struct hwmon_ops msi_wmi_platform_ops = {
> .is_visible = msi_wmi_platform_is_visible,
> .read = msi_wmi_platform_read,
> + .write = msi_wmi_platform_write,
> };
>
> static const struct hwmon_channel_info * const msi_wmi_platform_info[] = {
> @@ -260,6 +549,10 @@ static const struct hwmon_channel_info * const msi_wmi_platform_info[] = {
> HWMON_F_INPUT,
> HWMON_F_INPUT
> ),
> + HWMON_CHANNEL_INFO(pwm,
> + HWMON_PWM_ENABLE,
> + HWMON_PWM_ENABLE
> + ),
> NULL
> };
>
> @@ -268,8 +561,8 @@ static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
> .info = msi_wmi_platform_info,
> };
>
> -static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input, size_t length,
> - loff_t *offset)
> +static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
> + size_t length, loff_t *offset)
> {
> struct seq_file *seq = fp->private_data;
> struct msi_wmi_platform_debugfs_data *data = seq->private;
> @@ -303,7 +596,7 @@ static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input,
> return length;
> }
>
> -static int msi_wmi_platform_show(struct seq_file *seq, void *p)
> +static int msi_wmi_platform_debugfs_show(struct seq_file *seq, void *p)
> {
> struct msi_wmi_platform_debugfs_data *data = seq->private;
> int ret;
> @@ -315,19 +608,19 @@ static int msi_wmi_platform_show(struct seq_file *seq, void *p)
> return ret;
> }
>
> -static int msi_wmi_platform_open(struct inode *inode, struct file *fp)
> +static int msi_wmi_platform_debugfs_open(struct inode *inode, struct file *fp)
> {
> struct msi_wmi_platform_debugfs_data *data = inode->i_private;
>
> /* The seq_file uses the last byte of the buffer for detecting buffer overflows */
> - return single_open_size(fp, msi_wmi_platform_show, data, data->length + 1);
> + return single_open_size(fp, msi_wmi_platform_debugfs_show, data, data->length + 1);
> }
>
> static const struct file_operations msi_wmi_platform_debugfs_fops = {
> .owner = THIS_MODULE,
> - .open = msi_wmi_platform_open,
> + .open = msi_wmi_platform_debugfs_open,
> .read = seq_read,
> - .write = msi_wmi_platform_write,
> + .write = msi_wmi_platform_debugfs_write,
> .llseek = seq_lseek,
> .release = single_release,
> };
> @@ -389,7 +682,8 @@ static int msi_wmi_platform_hwmon_init(struct msi_wmi_platform_data *data)
> struct device *hdev;
>
> hdev = devm_hwmon_device_register_with_info(&data->wdev->dev, "msi_wmi_platform", data,
> - &msi_wmi_platform_chip_info, NULL);
> + &msi_wmi_platform_chip_info,
> + msi_wmi_platform_hwmon_groups);
>
> return PTR_ERR_OR_ZERO(hdev);
> }
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 04/10] platform/x86: msi-wmi-platform: Add support for fan control
2025-05-13 20:58 ` Armin Wolf
@ 2025-05-19 1:35 ` Armin Wolf
0 siblings, 0 replies; 45+ messages in thread
From: Armin Wolf @ 2025-05-19 1:35 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Jonathan Corbet, Hans de Goede, Ilpo Järvinen, Jean Delvare,
Guenter Roeck, Kurt Borja, linux-doc, linux-kernel, linux-hwmon
Am 13.05.25 um 22:58 schrieb Armin Wolf:
> Am 11.05.25 um 22:44 schrieb Antheas Kapenekakis:
>
>> From: Armin Wolf <W_Armin@gmx.de>
>>
>> Adds fan curve support for the MSI platform. These devices contain
>> support for two fans, where they are named CPU and GPU but in the
>> case of the Claw series just map to left and right fan.
>
> I assume that not all devices support this feature? If so then please
> hide it behind
> a quirk.
>
> Thanks,
> Armin Wolf
>
>> Co-developed-by: Antheas Kapenekakis <lkml@antheas.dev>
>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>> Signed-off-by: Armin Wolf <W_Armin@gmx.de>
>> ---
>> .../wmi/devices/msi-wmi-platform.rst | 26 ++
>> drivers/platform/x86/msi-wmi-platform.c | 328 +++++++++++++++++-
>> 2 files changed, 337 insertions(+), 17 deletions(-)
>>
>> diff --git a/Documentation/wmi/devices/msi-wmi-platform.rst
>> b/Documentation/wmi/devices/msi-wmi-platform.rst
>> index 73197b31926a5..704bfdac5203e 100644
>> --- a/Documentation/wmi/devices/msi-wmi-platform.rst
>> +++ b/Documentation/wmi/devices/msi-wmi-platform.rst
>> @@ -169,6 +169,32 @@ The fan RPM readings can be calculated with the
>> following formula:
>> If the fan speed reading is zero, then the fan RPM is zero too.
>> +The subfeature ``0x01`` is used to retrieve the fan speed table
>> for the CPU fan. The output
>> +data contains the fan speed table and two bytes with unknown data.
>> The fan speed table
>> +consists of six 8-bit entries, each containing a fan speed value in
>> percent.
>> +
>> +The subfeature ``0x02`` is used tho retrieve the same data for the
>> GPU fan.
>> +
>> +WMI method Set_Fan()
>> +--------------------
>> +
>> +The fan speed tables can be accessed using subfeature ``0x01`` (CPU
>> fan) and subfeature ``0x02``
>> +(GPU fan). The input data has the same format as the output data of
>> the ``Get_Fan`` WMI method.
>> +
>> +WMI method Get_AP()
>> +-------------------
>> +
>> +The current fan mode can be accessed using subfeature ``0x01``. The
>> output data contains a flag
>> +byte and two bytes of unknown data. If the 7th bit inside the flag
>> byte is cleared then all fans
>> +are operating in automatic mode, otherwise the fans operate based on
>> the fan speed tables
>> +accessible thru the ``Get_Fan``/``Set_Fan`` WMI methods.
>> +
>> +WMI method Set_AP()
>> +-------------------
>> +
>> +The current fan mode can be changed using subfeature ``0x01``. The
>> input data has the same format
>> +as the output data of the ``Get_AP`` WMI method.
>> +
>> WMI method Get_WMI()
>> --------------------
>> diff --git a/drivers/platform/x86/msi-wmi-platform.c
>> b/drivers/platform/x86/msi-wmi-platform.c
>> index 408d42ab19e20..9ac3c6f1b3f1d 100644
>> --- a/drivers/platform/x86/msi-wmi-platform.c
>> +++ b/drivers/platform/x86/msi-wmi-platform.c
>> @@ -16,13 +16,18 @@
>> #include <linux/device/driver.h>
>> #include <linux/dmi.h>
>> #include <linux/errno.h>
>> +#include <linux/fixp-arith.h>
>> #include <linux/hwmon.h>
>> +#include <linux/hwmon-sysfs.h>
>> #include <linux/kernel.h>
>> +#include <linux/kstrtox.h>
>> +#include <linux/minmax.h>
>> #include <linux/module.h>
>> #include <linux/mutex.h>
>> #include <linux/printk.h>
>> #include <linux/rwsem.h>
>> #include <linux/string.h>
>> +#include <linux/sysfs.h>
>> #include <linux/types.h>
>> #include <linux/wmi.h>
>> @@ -34,9 +39,11 @@
>> #define MSI_WMI_PLATFORM_INTERFACE_VERSION 2
>> +/* Get_WMI() WMI method */
>> #define MSI_PLATFORM_WMI_MAJOR_OFFSET 1
>> #define MSI_PLATFORM_WMI_MINOR_OFFSET 2
>> +/* Get_EC() and Set_EC() WMI methods */
>> #define MSI_PLATFORM_EC_FLAGS_OFFSET 1
>> #define MSI_PLATFORM_EC_MINOR_MASK GENMASK(3, 0)
>> #define MSI_PLATFORM_EC_MAJOR_MASK GENMASK(5, 4)
>> @@ -44,6 +51,18 @@
>> #define MSI_PLATFORM_EC_IS_TIGERLAKE BIT(7)
>> #define MSI_PLATFORM_EC_VERSION_OFFSET 2
>> +/* Get_Fan() and Set_Fan() WMI methods */
>> +#define MSI_PLATFORM_FAN_SUBFEATURE_FAN_SPEED 0x0
>> +#define MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE 0x1
>> +#define MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE 0x2
>> +#define MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE 0x1
>> +#define MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE 0x2
>> +
>> +/* Get_AP() and Set_AP() WMI methods */
>> +#define MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE 0x1
>> +#define MSI_PLATFORM_AP_FAN_FLAGS_OFFSET 1
>> +#define MSI_PLATFORM_AP_ENABLE_FAN_TABLES BIT(7)
>> +
>> static bool force;
>> module_param_unsafe(force, bool, 0);
>> MODULE_PARM_DESC(force, "Force loading without checking for
>> supported WMI interface versions");
>> @@ -221,9 +240,201 @@ static int msi_wmi_platform_query(struct
>> msi_wmi_platform_data *data,
>> }
>> }
>> +static ssize_t msi_wmi_platform_fan_table_show(struct device *dev,
>> struct device_attribute *attr,
>> + char *buf)
>> +{
>> + struct sensor_device_attribute_2 *sattr =
>> to_sensor_dev_attr_2(attr);
>> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
>> + u8 buffer[32] = { sattr->nr };
>> + u8 fan_percent;
>> + int ret;
>> +
>> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buffer,
>> sizeof(buffer));
>> + if (ret < 0)
>> + return ret;
>> +
>> + fan_percent = buffer[sattr->index + 1];
>> + if (fan_percent > 100)
>> + return -EIO;
>> +
>> + return sysfs_emit(buf, "%d\n", fixp_linear_interpolate(0, 0,
>> 100, 255, fan_percent));
>> +}
>> +
>> +static ssize_t msi_wmi_platform_fan_table_store(struct device *dev,
>> struct device_attribute *attr,
>> + const char *buf, size_t count)
>> +{
>> + struct sensor_device_attribute_2 *sattr =
>> to_sensor_dev_attr_2(attr);
>> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
>> + u8 buffer[32] = { sattr->nr };
>> + long speed;
>> + int ret;
>> +
>> + ret = kstrtol(buf, 10, &speed);
>> + if (ret < 0)
>> + return ret;
>> +
>> + speed = clamp_val(speed, 0, 255);
>> +
>> + guard(mutex)(&data->wmi_lock);
>> +
>> + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_GET_FAN,
>> + buffer, sizeof(buffer));
>> + if (ret < 0)
>> + return ret;
>> +
>> + buffer[0] = sattr->nr;
>> + buffer[sattr->index + 1] = fixp_linear_interpolate(0, 0, 255,
>> 100, speed);
>> +
>> + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_FAN,
>> + buffer, sizeof(buffer));
>> + if (ret < 0)
>> + return ret;
>> +
>> + return count;
>> +}
>> +
>> +static ssize_t msi_wmi_platform_temp_table_show(struct device *dev,
>> struct device_attribute *attr,
>> + char *buf)
>> +{
>> + struct sensor_device_attribute_2 *sattr =
>> to_sensor_dev_attr_2(attr);
>> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
>> + u8 buffer[32] = { sattr->nr };
>> + u8 temp_c;
>> + int ret;
>> +
>> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_TEMPERATURE,
>> + buffer, sizeof(buffer));
>> + if (ret < 0)
>> + return ret;
>> +
>> + temp_c = buffer[sattr->index + 1];
>> +
>> + return sysfs_emit(buf, "%d\n", temp_c);
>> +}
>> +
>> +static ssize_t msi_wmi_platform_temp_table_store(struct device *dev,
>> struct device_attribute *attr,
>> + const char *buf, size_t count)
>> +{
>> + struct sensor_device_attribute_2 *sattr =
>> to_sensor_dev_attr_2(attr);
>> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
>> + u8 buffer[32] = { sattr->nr };
>> + long temp_c;
>> + int ret;
>> +
>> + ret = kstrtol(buf, 10, &temp_c);
>> + if (ret < 0)
>> + return ret;
>> +
>> + temp_c = clamp_val(temp_c, 0, 255);
>> +
>> + guard(mutex)(&data->wmi_lock);
>> +
>> + ret = msi_wmi_platform_query_unlocked(data,
>> MSI_PLATFORM_GET_TEMPERATURE,
>> + buffer, sizeof(buffer));
>> + if (ret < 0)
>> + return ret;
>> +
>> + buffer[0] = sattr->nr;
>> + buffer[sattr->index + 1] = temp_c;
>> +
>> + ret = msi_wmi_platform_query_unlocked(data,
>> MSI_PLATFORM_SET_TEMPERATURE,
>> + buffer, sizeof(buffer));
>> + if (ret < 0)
>> + return ret;
>> +
>> + return count;
>> +}
>> +
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_temp,
>> msi_wmi_platform_temp_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x0);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_temp,
>> msi_wmi_platform_temp_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x3);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_temp,
>> msi_wmi_platform_temp_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x4);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_temp,
>> msi_wmi_platform_temp_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x5);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_temp,
>> msi_wmi_platform_temp_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x6);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_temp,
>> msi_wmi_platform_temp_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x7);
I forgot to mention that the temp table is not documented inside the documentation.
>> +
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_pwm,
>> msi_wmi_platform_fan_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x1);
I just noticed that the documentation does not mention that the fan table starts at offset 0x1.
Please add this minor detail to the documentation.
Thanks,
Armin Wolf
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_pwm,
>> msi_wmi_platform_fan_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x2);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_pwm,
>> msi_wmi_platform_fan_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x3);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_pwm,
>> msi_wmi_platform_fan_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x4);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_pwm,
>> msi_wmi_platform_fan_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x5);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_pwm,
>> msi_wmi_platform_fan_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x6);
>> +
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_temp,
>> msi_wmi_platform_temp_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x0);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_temp,
>> msi_wmi_platform_temp_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x3);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_temp,
>> msi_wmi_platform_temp_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x4);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_temp,
>> msi_wmi_platform_temp_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x5);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_temp,
>> msi_wmi_platform_temp_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x6);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_temp,
>> msi_wmi_platform_temp_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x7);
>> +
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_pwm,
>> msi_wmi_platform_fan_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x1);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_pwm,
>> msi_wmi_platform_fan_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x2);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_pwm,
>> msi_wmi_platform_fan_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x3);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_pwm,
>> msi_wmi_platform_fan_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x4);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_pwm,
>> msi_wmi_platform_fan_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x5);
>> +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_pwm,
>> msi_wmi_platform_fan_table,
>> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x6);
>> +
>> +static struct attribute *msi_wmi_platform_hwmon_attrs[] = {
>> + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr,
>> +
>> + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr,
>> +
>> + &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm2_auto_point5_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm2_auto_point6_temp.dev_attr.attr,
>> +
>> + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm2_auto_point5_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm2_auto_point6_pwm.dev_attr.attr,
>> + NULL
>> +};
>> +ATTRIBUTE_GROUPS(msi_wmi_platform_hwmon);
>> +
>> static umode_t msi_wmi_platform_is_visible(const void *drvdata,
>> enum hwmon_sensor_types type,
>> u32 attr, int channel)
>> {
>> + if (type == hwmon_pwm && attr == hwmon_pwm_enable)
>> + return 0644;
>> +
>> return 0444;
>> }
>> @@ -233,24 +444,102 @@ static int msi_wmi_platform_read(struct
>> device *dev, enum hwmon_sensor_types typ
>> struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
>> u8 buffer[32] = { 0 };
>> u16 value;
>> + u8 flags;
>> int ret;
>> - ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buf,
>> sizeof(buf));
>> - if (ret < 0)
>> - return ret;
>> + switch (type) {
>> + case hwmon_fan:
>> + switch (attr) {
>> + case hwmon_fan_input:
>> + buffer[0] = MSI_PLATFORM_FAN_SUBFEATURE_FAN_SPEED;
>> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN,
>> buffer,
>> + sizeof(buffer));
>> + if (ret < 0)
>> + return ret;
>> +
>> + value = get_unaligned_be16(&buffer[channel * 2 + 1]);
>> + if (!value)
>> + *val = 0;
>> + else
>> + *val = 480000 / value;
>> +
>> + return 0;
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> + case hwmon_pwm:
>> + switch (attr) {
>> + case hwmon_pwm_enable:
>> + buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
>> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_AP,
>> buffer,
>> + sizeof(buffer));
>> + if (ret < 0)
>> + return ret;
>> +
>> + flags = buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET];
>> + if (flags & MSI_PLATFORM_AP_ENABLE_FAN_TABLES)
>> + *val = 1;
>> + else
>> + *val = 2;
>> +
>> + return 0;
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> +}
>> - value = get_unaligned_be16(&buffer[channel * 2 + 1]);
>> - if (!value)
>> - *val = 0;
>> - else
>> - *val = 480000 / value;
>> +static int msi_wmi_platform_write(struct device *dev, enum
>> hwmon_sensor_types type, u32 attr,
>> + int channel, long val)
>> +{
>> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
>> + u8 buffer[32] = { };
>> + int ret;
>> - return 0;
>> + switch (type) {
>> + case hwmon_pwm:
>> + switch (attr) {
>> + case hwmon_pwm_enable:
>> + guard(mutex)(&data->wmi_lock);
>> +
>> + buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
>> + ret = msi_wmi_platform_query_unlocked(
>> + data, MSI_PLATFORM_GET_AP, buffer,
>> + sizeof(buffer));
>> + if (ret < 0)
>> + return ret;
>> +
>> + buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
>> + switch (val) {
>> + case 1:
>> + buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET] |=
>> + MSI_PLATFORM_AP_ENABLE_FAN_TABLES;
>> + break;
>> + case 2:
>> + buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET] &=
>> + ~MSI_PLATFORM_AP_ENABLE_FAN_TABLES;
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> +
>> + return msi_wmi_platform_query_unlocked(
>> + data, MSI_PLATFORM_SET_AP, buffer,
>> + sizeof(buffer));
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> }
>> static const struct hwmon_ops msi_wmi_platform_ops = {
>> .is_visible = msi_wmi_platform_is_visible,
>> .read = msi_wmi_platform_read,
>> + .write = msi_wmi_platform_write,
>> };
>> static const struct hwmon_channel_info * const
>> msi_wmi_platform_info[] = {
>> @@ -260,6 +549,10 @@ static const struct hwmon_channel_info * const
>> msi_wmi_platform_info[] = {
>> HWMON_F_INPUT,
>> HWMON_F_INPUT
>> ),
>> + HWMON_CHANNEL_INFO(pwm,
>> + HWMON_PWM_ENABLE,
>> + HWMON_PWM_ENABLE
>> + ),
>> NULL
>> };
>> @@ -268,8 +561,8 @@ static const struct hwmon_chip_info
>> msi_wmi_platform_chip_info = {
>> .info = msi_wmi_platform_info,
>> };
>> -static ssize_t msi_wmi_platform_write(struct file *fp, const char
>> __user *input, size_t length,
>> - loff_t *offset)
>> +static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const
>> char __user *input,
>> + size_t length, loff_t *offset)
>> {
>> struct seq_file *seq = fp->private_data;
>> struct msi_wmi_platform_debugfs_data *data = seq->private;
>> @@ -303,7 +596,7 @@ static ssize_t msi_wmi_platform_write(struct file
>> *fp, const char __user *input,
>> return length;
>> }
>> -static int msi_wmi_platform_show(struct seq_file *seq, void *p)
>> +static int msi_wmi_platform_debugfs_show(struct seq_file *seq, void *p)
>> {
>> struct msi_wmi_platform_debugfs_data *data = seq->private;
>> int ret;
>> @@ -315,19 +608,19 @@ static int msi_wmi_platform_show(struct
>> seq_file *seq, void *p)
>> return ret;
>> }
>> -static int msi_wmi_platform_open(struct inode *inode, struct file
>> *fp)
>> +static int msi_wmi_platform_debugfs_open(struct inode *inode, struct
>> file *fp)
>> {
>> struct msi_wmi_platform_debugfs_data *data = inode->i_private;
>> /* The seq_file uses the last byte of the buffer for
>> detecting buffer overflows */
>> - return single_open_size(fp, msi_wmi_platform_show, data,
>> data->length + 1);
>> + return single_open_size(fp, msi_wmi_platform_debugfs_show, data,
>> data->length + 1);
>> }
>> static const struct file_operations msi_wmi_platform_debugfs_fops
>> = {
>> .owner = THIS_MODULE,
>> - .open = msi_wmi_platform_open,
>> + .open = msi_wmi_platform_debugfs_open,
>> .read = seq_read,
>> - .write = msi_wmi_platform_write,
>> + .write = msi_wmi_platform_debugfs_write,
>> .llseek = seq_lseek,
>> .release = single_release,
>> };
>> @@ -389,7 +682,8 @@ static int msi_wmi_platform_hwmon_init(struct
>> msi_wmi_platform_data *data)
>> struct device *hdev;
>> hdev = devm_hwmon_device_register_with_info(&data->wdev->dev,
>> "msi_wmi_platform", data,
>> - &msi_wmi_platform_chip_info, NULL);
>> + &msi_wmi_platform_chip_info,
>> + msi_wmi_platform_hwmon_groups);
>> return PTR_ERR_OR_ZERO(hdev);
>> }
>
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v1 05/10] platform/x86: msi-wmi-platform: Add platform profile through shift mode
2025-05-11 20:44 [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Antheas Kapenekakis
` (3 preceding siblings ...)
2025-05-11 20:44 ` [PATCH v1 04/10] platform/x86: msi-wmi-platform: Add support for fan control Antheas Kapenekakis
@ 2025-05-11 20:44 ` Antheas Kapenekakis
2025-05-11 23:33 ` Kurt Borja
2025-05-19 1:58 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 06/10] platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware attributes Antheas Kapenekakis
` (6 subsequent siblings)
11 siblings, 2 replies; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-11 20:44 UTC (permalink / raw)
To: platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, Kurt Borja, linux-doc, linux-kernel,
linux-hwmon, Antheas Kapenekakis
MSI's version of platform profile in Windows is called shift mode.
Introduce it here, and add a profile handler to it.
It has 5 modes: sport, comfort, green, eco, and user.
Confusingly, for the Claw, MSI only uses sport, green, and eco,
where they correspond to performance, balanced, and low-power.
Therefore, comfort is mapped to balanced-performance, and user to
custom.
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
drivers/platform/x86/Kconfig | 1 +
drivers/platform/x86/msi-wmi-platform.c | 117 ++++++++++++++++++++++++
2 files changed, 118 insertions(+)
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index bee98251b8f0b..57a48910c8fd4 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -746,6 +746,7 @@ config MSI_WMI_PLATFORM
tristate "MSI WMI Platform features"
depends on ACPI_WMI
depends on HWMON
+ select ACPI_PLATFORM_PROFILE
help
Say Y here if you want to have support for WMI-based platform features
like fan sensor access on MSI machines.
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
index 9ac3c6f1b3f1d..c0b577c95c079 100644
--- a/drivers/platform/x86/msi-wmi-platform.c
+++ b/drivers/platform/x86/msi-wmi-platform.c
@@ -17,6 +17,7 @@
#include <linux/dmi.h>
#include <linux/errno.h>
#include <linux/fixp-arith.h>
+#include <linux/platform_profile.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/kernel.h>
@@ -63,6 +64,16 @@
#define MSI_PLATFORM_AP_FAN_FLAGS_OFFSET 1
#define MSI_PLATFORM_AP_ENABLE_FAN_TABLES BIT(7)
+/* Get_Data() and Set_Data() Shift Mode Register */
+#define MSI_PLATFORM_SHIFT_ADDR 0xd2
+#define MSI_PLATFORM_SHIFT_DISABLE BIT(7)
+#define MSI_PLATFORM_SHIFT_ENABLE (BIT(7) | BIT(6))
+#define MSI_PLATFORM_SHIFT_SPORT (MSI_PLATFORM_SHIFT_ENABLE + 4)
+#define MSI_PLATFORM_SHIFT_COMFORT (MSI_PLATFORM_SHIFT_ENABLE + 0)
+#define MSI_PLATFORM_SHIFT_GREEN (MSI_PLATFORM_SHIFT_ENABLE + 1)
+#define MSI_PLATFORM_SHIFT_ECO (MSI_PLATFORM_SHIFT_ENABLE + 2)
+#define MSI_PLATFORM_SHIFT_USER (MSI_PLATFORM_SHIFT_ENABLE + 3)
+
static bool force;
module_param_unsafe(force, bool, 0);
MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
@@ -100,12 +111,14 @@ enum msi_wmi_platform_method {
};
struct msi_wmi_platform_quirk {
+ bool shift_mode; /* Shift mode is supported */
};
struct msi_wmi_platform_data {
struct wmi_device *wdev;
struct msi_wmi_platform_quirk *quirks;
struct mutex wmi_lock; /* Necessary when calling WMI methods */
+ struct device *ppdev;
};
struct msi_wmi_platform_debugfs_data {
@@ -150,8 +163,10 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
static struct msi_wmi_platform_quirk quirk_default = {};
static struct msi_wmi_platform_quirk quirk_gen1 = {
+ .shift_mode = true
};
static struct msi_wmi_platform_quirk quirk_gen2 = {
+ .shift_mode = true
};
static const struct dmi_system_id msi_quirks[] = {
@@ -561,6 +576,90 @@ static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
.info = msi_wmi_platform_info,
};
+static int msi_wmi_platform_profile_probe(void *drvdata, unsigned long *choices)
+{
+ set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
+ set_bit(PLATFORM_PROFILE_BALANCED, choices);
+ set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+ return 0;
+}
+
+static int msi_wmi_platform_profile_get(struct device *dev,
+ enum platform_profile_option *profile)
+{
+ struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ u8 buffer[32] = { };
+
+ buffer[0] = MSI_PLATFORM_SHIFT_ADDR;
+
+ ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ if (buffer[0] != 1)
+ return -EINVAL;
+
+ switch (buffer[1]) {
+ case MSI_PLATFORM_SHIFT_SPORT:
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ return 0;
+ case MSI_PLATFORM_SHIFT_COMFORT:
+ *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
+ return 0;
+ case MSI_PLATFORM_SHIFT_GREEN:
+ *profile = PLATFORM_PROFILE_BALANCED;
+ return 0;
+ case MSI_PLATFORM_SHIFT_ECO:
+ *profile = PLATFORM_PROFILE_LOW_POWER;
+ return 0;
+ case MSI_PLATFORM_SHIFT_USER:
+ *profile = PLATFORM_PROFILE_CUSTOM;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int msi_wmi_platform_profile_set(struct device *dev,
+ enum platform_profile_option profile)
+{
+ struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+ u8 buffer[32] = { };
+
+ buffer[0] = MSI_PLATFORM_SHIFT_ADDR;
+
+ switch (profile) {
+ case PLATFORM_PROFILE_PERFORMANCE:
+ buffer[1] = MSI_PLATFORM_SHIFT_SPORT;
+ break;
+ case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
+ buffer[1] = MSI_PLATFORM_SHIFT_COMFORT;
+ break;
+ case PLATFORM_PROFILE_BALANCED:
+ buffer[1] = MSI_PLATFORM_SHIFT_GREEN;
+ break;
+ case PLATFORM_PROFILE_LOW_POWER:
+ buffer[1] = MSI_PLATFORM_SHIFT_ECO;
+ break;
+ case PLATFORM_PROFILE_CUSTOM:
+ buffer[1] = MSI_PLATFORM_SHIFT_USER;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return msi_wmi_platform_query(data, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer));
+}
+
+static const struct platform_profile_ops msi_wmi_platform_profile_ops = {
+ .probe = msi_wmi_platform_profile_probe,
+ .profile_get = msi_wmi_platform_profile_get,
+ .profile_set = msi_wmi_platform_profile_set,
+};
+
static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
size_t length, loff_t *offset)
{
@@ -742,6 +841,22 @@ static int msi_wmi_platform_init(struct msi_wmi_platform_data *data)
return 0;
}
+static int msi_wmi_platform_profile_setup(struct msi_wmi_platform_data *data)
+{
+ int err;
+
+ if (!data->quirks->shift_mode)
+ return 0;
+
+ data->ppdev = devm_platform_profile_register(
+ &data->wdev->dev, "msi-wmi-platform", data,
+ &msi_wmi_platform_profile_ops);
+ if (err)
+ return err;
+
+ return PTR_ERR_OR_ZERO(data->ppdev);
+}
+
static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
{
struct msi_wmi_platform_data *data;
@@ -775,6 +890,8 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
msi_wmi_platform_debugfs_init(data);
+ msi_wmi_platform_profile_setup(data);
+
return msi_wmi_platform_hwmon_init(data);
}
--
2.49.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v1 05/10] platform/x86: msi-wmi-platform: Add platform profile through shift mode
2025-05-11 20:44 ` [PATCH v1 05/10] platform/x86: msi-wmi-platform: Add platform profile through shift mode Antheas Kapenekakis
@ 2025-05-11 23:33 ` Kurt Borja
2025-05-12 21:59 ` Antheas Kapenekakis
2025-05-19 1:58 ` Armin Wolf
1 sibling, 1 reply; 45+ messages in thread
From: Kurt Borja @ 2025-05-11 23:33 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, linux-doc, linux-kernel, linux-hwmon
[-- Attachment #1: Type: text/plain, Size: 7837 bytes --]
On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> MSI's version of platform profile in Windows is called shift mode.
> Introduce it here, and add a profile handler to it.
>
> It has 5 modes: sport, comfort, green, eco, and user.
> Confusingly, for the Claw, MSI only uses sport, green, and eco,
> where they correspond to performance, balanced, and low-power.
> Therefore, comfort is mapped to balanced-performance, and user to
> custom.
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/Kconfig | 1 +
> drivers/platform/x86/msi-wmi-platform.c | 117 ++++++++++++++++++++++++
> 2 files changed, 118 insertions(+)
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index bee98251b8f0b..57a48910c8fd4 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -746,6 +746,7 @@ config MSI_WMI_PLATFORM
> tristate "MSI WMI Platform features"
> depends on ACPI_WMI
> depends on HWMON
> + select ACPI_PLATFORM_PROFILE
> help
> Say Y here if you want to have support for WMI-based platform features
> like fan sensor access on MSI machines.
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index 9ac3c6f1b3f1d..c0b577c95c079 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -17,6 +17,7 @@
> #include <linux/dmi.h>
> #include <linux/errno.h>
> #include <linux/fixp-arith.h>
> +#include <linux/platform_profile.h>
> #include <linux/hwmon.h>
> #include <linux/hwmon-sysfs.h>
> #include <linux/kernel.h>
> @@ -63,6 +64,16 @@
> #define MSI_PLATFORM_AP_FAN_FLAGS_OFFSET 1
> #define MSI_PLATFORM_AP_ENABLE_FAN_TABLES BIT(7)
>
> +/* Get_Data() and Set_Data() Shift Mode Register */
Maybe you can write short documentation for these methods?
> +#define MSI_PLATFORM_SHIFT_ADDR 0xd2
> +#define MSI_PLATFORM_SHIFT_DISABLE BIT(7)
> +#define MSI_PLATFORM_SHIFT_ENABLE (BIT(7) | BIT(6))
> +#define MSI_PLATFORM_SHIFT_SPORT (MSI_PLATFORM_SHIFT_ENABLE + 4)
> +#define MSI_PLATFORM_SHIFT_COMFORT (MSI_PLATFORM_SHIFT_ENABLE + 0)
> +#define MSI_PLATFORM_SHIFT_GREEN (MSI_PLATFORM_SHIFT_ENABLE + 1)
> +#define MSI_PLATFORM_SHIFT_ECO (MSI_PLATFORM_SHIFT_ENABLE + 2)
> +#define MSI_PLATFORM_SHIFT_USER (MSI_PLATFORM_SHIFT_ENABLE + 3)
Instead of summing the profiles I suggest something like:
enum MSI_PLATFORM_PROFILES {
MSI_PROFILE_COMFORT,
MSI_PROFILE_GREEN,
MSI_PROFILE_ECO,
MSI_PROFILE_USER,
MSI_PROFILE_SPORT,
}
And you can prepare your commands like
command = MSI_PLATFORM_SHIT_ENABLE;
command |= FIELD_PREP(GENMASK(1,0), MSI_PROFILE_{profile});
I feel that it's cleaner this way. This is only a suggestion though.
> +
> static bool force;
> module_param_unsafe(force, bool, 0);
> MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
> @@ -100,12 +111,14 @@ enum msi_wmi_platform_method {
> };
>
> struct msi_wmi_platform_quirk {
> + bool shift_mode; /* Shift mode is supported */
> };
>
> struct msi_wmi_platform_data {
> struct wmi_device *wdev;
> struct msi_wmi_platform_quirk *quirks;
> struct mutex wmi_lock; /* Necessary when calling WMI methods */
> + struct device *ppdev;
> };
>
> struct msi_wmi_platform_debugfs_data {
> @@ -150,8 +163,10 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
>
> static struct msi_wmi_platform_quirk quirk_default = {};
> static struct msi_wmi_platform_quirk quirk_gen1 = {
> + .shift_mode = true
> };
> static struct msi_wmi_platform_quirk quirk_gen2 = {
> + .shift_mode = true
> };
>
> static const struct dmi_system_id msi_quirks[] = {
> @@ -561,6 +576,90 @@ static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
> .info = msi_wmi_platform_info,
> };
>
> +static int msi_wmi_platform_profile_probe(void *drvdata, unsigned long *choices)
> +{
> + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> + set_bit(PLATFORM_PROFILE_BALANCED, choices);
> + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
Please, use the non-atomic __set_bit(). `choices` is not shared between
threads.
> + return 0;
> +}
> +
> +static int msi_wmi_platform_profile_get(struct device *dev,
> + enum platform_profile_option *profile)
> +{
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + int ret;
> +
> + u8 buffer[32] = { };
Move this to the top.
> +
> + buffer[0] = MSI_PLATFORM_SHIFT_ADDR;
> +
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + if (buffer[0] != 1)
> + return -EINVAL;
> +
> + switch (buffer[1]) {
> + case MSI_PLATFORM_SHIFT_SPORT:
> + *profile = PLATFORM_PROFILE_PERFORMANCE;
> + return 0;
> + case MSI_PLATFORM_SHIFT_COMFORT:
> + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
Maybe comfort can be mapped to balanced and green to cool. What do you
think?
> + return 0;
> + case MSI_PLATFORM_SHIFT_GREEN:
> + *profile = PLATFORM_PROFILE_BALANCED;
> + return 0;
> + case MSI_PLATFORM_SHIFT_ECO:
> + *profile = PLATFORM_PROFILE_LOW_POWER;
> + return 0;
> + case MSI_PLATFORM_SHIFT_USER:
> + *profile = PLATFORM_PROFILE_CUSTOM;
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int msi_wmi_platform_profile_set(struct device *dev,
> + enum platform_profile_option profile)
> +{
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + u8 buffer[32] = { };
> +
> + buffer[0] = MSI_PLATFORM_SHIFT_ADDR;
> +
> + switch (profile) {
> + case PLATFORM_PROFILE_PERFORMANCE:
> + buffer[1] = MSI_PLATFORM_SHIFT_SPORT;
> + break;
> + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> + buffer[1] = MSI_PLATFORM_SHIFT_COMFORT;
> + break;
> + case PLATFORM_PROFILE_BALANCED:
> + buffer[1] = MSI_PLATFORM_SHIFT_GREEN;
> + break;
> + case PLATFORM_PROFILE_LOW_POWER:
> + buffer[1] = MSI_PLATFORM_SHIFT_ECO;
> + break;
> + case PLATFORM_PROFILE_CUSTOM:
> + buffer[1] = MSI_PLATFORM_SHIFT_USER;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return msi_wmi_platform_query(data, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer));
> +}
> +
> +static const struct platform_profile_ops msi_wmi_platform_profile_ops = {
> + .probe = msi_wmi_platform_profile_probe,
> + .profile_get = msi_wmi_platform_profile_get,
> + .profile_set = msi_wmi_platform_profile_set,
> +};
> +
> static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
> size_t length, loff_t *offset)
> {
> @@ -742,6 +841,22 @@ static int msi_wmi_platform_init(struct msi_wmi_platform_data *data)
> return 0;
> }
>
> +static int msi_wmi_platform_profile_setup(struct msi_wmi_platform_data *data)
> +{
> + int err;
> +
> + if (!data->quirks->shift_mode)
> + return 0;
> +
> + data->ppdev = devm_platform_profile_register(
> + &data->wdev->dev, "msi-wmi-platform", data,
> + &msi_wmi_platform_profile_ops);
Broken format.
> + if (err)
> + return err;
`err` is not initialized. Is it a leftover?
> +
> + return PTR_ERR_OR_ZERO(data->ppdev);
> +}
> +
> static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> {
> struct msi_wmi_platform_data *data;
> @@ -775,6 +890,8 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
>
> msi_wmi_platform_debugfs_init(data);
>
> + msi_wmi_platform_profile_setup(data);
Check return value.
--
~ Kurt
> +
> return msi_wmi_platform_hwmon_init(data);
> }
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 05/10] platform/x86: msi-wmi-platform: Add platform profile through shift mode
2025-05-11 23:33 ` Kurt Borja
@ 2025-05-12 21:59 ` Antheas Kapenekakis
2025-05-19 1:51 ` Armin Wolf
0 siblings, 1 reply; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-12 21:59 UTC (permalink / raw)
To: Kurt Borja
Cc: platform-driver-x86, Armin Wolf, Jonathan Corbet, Hans de Goede,
Ilpo Järvinen, Jean Delvare, Guenter Roeck, linux-doc,
linux-kernel, linux-hwmon
On Mon, 12 May 2025 at 01:34, Kurt Borja <kuurtb@gmail.com> wrote:
>
> On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> > MSI's version of platform profile in Windows is called shift mode.
> > Introduce it here, and add a profile handler to it.
> >
> > It has 5 modes: sport, comfort, green, eco, and user.
> > Confusingly, for the Claw, MSI only uses sport, green, and eco,
> > where they correspond to performance, balanced, and low-power.
> > Therefore, comfort is mapped to balanced-performance, and user to
> > custom.
> >
> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > ---
> > drivers/platform/x86/Kconfig | 1 +
> > drivers/platform/x86/msi-wmi-platform.c | 117 ++++++++++++++++++++++++
> > 2 files changed, 118 insertions(+)
> >
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index bee98251b8f0b..57a48910c8fd4 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -746,6 +746,7 @@ config MSI_WMI_PLATFORM
> > tristate "MSI WMI Platform features"
> > depends on ACPI_WMI
> > depends on HWMON
> > + select ACPI_PLATFORM_PROFILE
> > help
> > Say Y here if you want to have support for WMI-based platform features
> > like fan sensor access on MSI machines.
> > diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> > index 9ac3c6f1b3f1d..c0b577c95c079 100644
> > --- a/drivers/platform/x86/msi-wmi-platform.c
> > +++ b/drivers/platform/x86/msi-wmi-platform.c
> > @@ -17,6 +17,7 @@
> > #include <linux/dmi.h>
> > #include <linux/errno.h>
> > #include <linux/fixp-arith.h>
> > +#include <linux/platform_profile.h>
> > #include <linux/hwmon.h>
> > #include <linux/hwmon-sysfs.h>
> > #include <linux/kernel.h>
> > @@ -63,6 +64,16 @@
> > #define MSI_PLATFORM_AP_FAN_FLAGS_OFFSET 1
> > #define MSI_PLATFORM_AP_ENABLE_FAN_TABLES BIT(7)
> >
> > +/* Get_Data() and Set_Data() Shift Mode Register */
>
> Maybe you can write short documentation for these methods?
>
> > +#define MSI_PLATFORM_SHIFT_ADDR 0xd2
> > +#define MSI_PLATFORM_SHIFT_DISABLE BIT(7)
> > +#define MSI_PLATFORM_SHIFT_ENABLE (BIT(7) | BIT(6))
> > +#define MSI_PLATFORM_SHIFT_SPORT (MSI_PLATFORM_SHIFT_ENABLE + 4)
> > +#define MSI_PLATFORM_SHIFT_COMFORT (MSI_PLATFORM_SHIFT_ENABLE + 0)
> > +#define MSI_PLATFORM_SHIFT_GREEN (MSI_PLATFORM_SHIFT_ENABLE + 1)
> > +#define MSI_PLATFORM_SHIFT_ECO (MSI_PLATFORM_SHIFT_ENABLE + 2)
> > +#define MSI_PLATFORM_SHIFT_USER (MSI_PLATFORM_SHIFT_ENABLE + 3)
>
> Instead of summing the profiles I suggest something like:
>
> enum MSI_PLATFORM_PROFILES {
> MSI_PROFILE_COMFORT,
> MSI_PROFILE_GREEN,
> MSI_PROFILE_ECO,
> MSI_PROFILE_USER,
> MSI_PROFILE_SPORT,
> }
>
> And you can prepare your commands like
>
> command = MSI_PLATFORM_SHIT_ENABLE;
> command |= FIELD_PREP(GENMASK(1,0), MSI_PROFILE_{profile});
>
> I feel that it's cleaner this way. This is only a suggestion though.
>
> > +
> > static bool force;
> > module_param_unsafe(force, bool, 0);
> > MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
> > @@ -100,12 +111,14 @@ enum msi_wmi_platform_method {
> > };
> >
> > struct msi_wmi_platform_quirk {
> > + bool shift_mode; /* Shift mode is supported */
> > };
> >
> > struct msi_wmi_platform_data {
> > struct wmi_device *wdev;
> > struct msi_wmi_platform_quirk *quirks;
> > struct mutex wmi_lock; /* Necessary when calling WMI methods */
> > + struct device *ppdev;
> > };
> >
> > struct msi_wmi_platform_debugfs_data {
> > @@ -150,8 +163,10 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
> >
> > static struct msi_wmi_platform_quirk quirk_default = {};
> > static struct msi_wmi_platform_quirk quirk_gen1 = {
> > + .shift_mode = true
> > };
> > static struct msi_wmi_platform_quirk quirk_gen2 = {
> > + .shift_mode = true
> > };
> >
> > static const struct dmi_system_id msi_quirks[] = {
> > @@ -561,6 +576,90 @@ static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
> > .info = msi_wmi_platform_info,
> > };
> >
> > +static int msi_wmi_platform_profile_probe(void *drvdata, unsigned long *choices)
> > +{
> > + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> > + set_bit(PLATFORM_PROFILE_BALANCED, choices);
> > + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> > + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
>
> Please, use the non-atomic __set_bit(). `choices` is not shared between
> threads.
>
> > + return 0;
> > +}
> > +
> > +static int msi_wmi_platform_profile_get(struct device *dev,
> > + enum platform_profile_option *profile)
> > +{
> > + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> > + int ret;
> > +
> > + u8 buffer[32] = { };
>
> Move this to the top.
>
> > +
> > + buffer[0] = MSI_PLATFORM_SHIFT_ADDR;
> > +
> > + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer));
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (buffer[0] != 1)
> > + return -EINVAL;
> > +
> > + switch (buffer[1]) {
> > + case MSI_PLATFORM_SHIFT_SPORT:
> > + *profile = PLATFORM_PROFILE_PERFORMANCE;
> > + return 0;
> > + case MSI_PLATFORM_SHIFT_COMFORT:
> > + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
>
> Maybe comfort can be mapped to balanced and green to cool. What do you
> think?
I forgot to comment on this one. The Claw uses ECO, GREEN, and
PERFORMANCE as its primary modes with 8, 12, 30W respectively. Comfort
is not used specifically for it. So I chose to push Green upwards to
balanced.
@Armin might know more about this one. If it turns out using comfort
for balanced fits better for laptops we can do that instead.
Antheas
>
> > + return 0;
> > + case MSI_PLATFORM_SHIFT_GREEN:
> > + *profile = PLATFORM_PROFILE_BALANCED;
> > + return 0;
> > + case MSI_PLATFORM_SHIFT_ECO:
> > + *profile = PLATFORM_PROFILE_LOW_POWER;
> > + return 0;
> > + case MSI_PLATFORM_SHIFT_USER:
> > + *profile = PLATFORM_PROFILE_CUSTOM;
> > + return 0;
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int msi_wmi_platform_profile_set(struct device *dev,
> > + enum platform_profile_option profile)
> > +{
> > + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> > + u8 buffer[32] = { };
> > +
> > + buffer[0] = MSI_PLATFORM_SHIFT_ADDR;
> > +
> > + switch (profile) {
> > + case PLATFORM_PROFILE_PERFORMANCE:
> > + buffer[1] = MSI_PLATFORM_SHIFT_SPORT;
> > + break;
> > + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> > + buffer[1] = MSI_PLATFORM_SHIFT_COMFORT;
> > + break;
> > + case PLATFORM_PROFILE_BALANCED:
> > + buffer[1] = MSI_PLATFORM_SHIFT_GREEN;
> > + break;
> > + case PLATFORM_PROFILE_LOW_POWER:
> > + buffer[1] = MSI_PLATFORM_SHIFT_ECO;
> > + break;
> > + case PLATFORM_PROFILE_CUSTOM:
> > + buffer[1] = MSI_PLATFORM_SHIFT_USER;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + return msi_wmi_platform_query(data, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer));
> > +}
> > +
> > +static const struct platform_profile_ops msi_wmi_platform_profile_ops = {
> > + .probe = msi_wmi_platform_profile_probe,
> > + .profile_get = msi_wmi_platform_profile_get,
> > + .profile_set = msi_wmi_platform_profile_set,
> > +};
> > +
> > static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
> > size_t length, loff_t *offset)
> > {
> > @@ -742,6 +841,22 @@ static int msi_wmi_platform_init(struct msi_wmi_platform_data *data)
> > return 0;
> > }
> >
> > +static int msi_wmi_platform_profile_setup(struct msi_wmi_platform_data *data)
> > +{
> > + int err;
> > +
> > + if (!data->quirks->shift_mode)
> > + return 0;
> > +
> > + data->ppdev = devm_platform_profile_register(
> > + &data->wdev->dev, "msi-wmi-platform", data,
> > + &msi_wmi_platform_profile_ops);
>
> Broken format.
>
> > + if (err)
> > + return err;
>
> `err` is not initialized. Is it a leftover?
>
> > +
> > + return PTR_ERR_OR_ZERO(data->ppdev);
> > +}
> > +
> > static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> > {
> > struct msi_wmi_platform_data *data;
> > @@ -775,6 +890,8 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> >
> > msi_wmi_platform_debugfs_init(data);
> >
> > + msi_wmi_platform_profile_setup(data);
>
> Check return value.
>
> --
> ~ Kurt
>
> > +
> > return msi_wmi_platform_hwmon_init(data);
> > }
> >
>
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 05/10] platform/x86: msi-wmi-platform: Add platform profile through shift mode
2025-05-12 21:59 ` Antheas Kapenekakis
@ 2025-05-19 1:51 ` Armin Wolf
0 siblings, 0 replies; 45+ messages in thread
From: Armin Wolf @ 2025-05-19 1:51 UTC (permalink / raw)
To: Antheas Kapenekakis, Kurt Borja
Cc: platform-driver-x86, Jonathan Corbet, Hans de Goede,
Ilpo Järvinen, Jean Delvare, Guenter Roeck, linux-doc,
linux-kernel, linux-hwmon
Am 12.05.25 um 23:59 schrieb Antheas Kapenekakis:
> On Mon, 12 May 2025 at 01:34, Kurt Borja <kuurtb@gmail.com> wrote:
>> On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
>>> MSI's version of platform profile in Windows is called shift mode.
>>> Introduce it here, and add a profile handler to it.
>>>
>>> It has 5 modes: sport, comfort, green, eco, and user.
>>> Confusingly, for the Claw, MSI only uses sport, green, and eco,
>>> where they correspond to performance, balanced, and low-power.
>>> Therefore, comfort is mapped to balanced-performance, and user to
>>> custom.
>>>
>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>>> ---
>>> drivers/platform/x86/Kconfig | 1 +
>>> drivers/platform/x86/msi-wmi-platform.c | 117 ++++++++++++++++++++++++
>>> 2 files changed, 118 insertions(+)
>>>
>>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>>> index bee98251b8f0b..57a48910c8fd4 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -746,6 +746,7 @@ config MSI_WMI_PLATFORM
>>> tristate "MSI WMI Platform features"
>>> depends on ACPI_WMI
>>> depends on HWMON
>>> + select ACPI_PLATFORM_PROFILE
>>> help
>>> Say Y here if you want to have support for WMI-based platform features
>>> like fan sensor access on MSI machines.
>>> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
>>> index 9ac3c6f1b3f1d..c0b577c95c079 100644
>>> --- a/drivers/platform/x86/msi-wmi-platform.c
>>> +++ b/drivers/platform/x86/msi-wmi-platform.c
>>> @@ -17,6 +17,7 @@
>>> #include <linux/dmi.h>
>>> #include <linux/errno.h>
>>> #include <linux/fixp-arith.h>
>>> +#include <linux/platform_profile.h>
>>> #include <linux/hwmon.h>
>>> #include <linux/hwmon-sysfs.h>
>>> #include <linux/kernel.h>
>>> @@ -63,6 +64,16 @@
>>> #define MSI_PLATFORM_AP_FAN_FLAGS_OFFSET 1
>>> #define MSI_PLATFORM_AP_ENABLE_FAN_TABLES BIT(7)
>>>
>>> +/* Get_Data() and Set_Data() Shift Mode Register */
>> Maybe you can write short documentation for these methods?
>>
>>> +#define MSI_PLATFORM_SHIFT_ADDR 0xd2
>>> +#define MSI_PLATFORM_SHIFT_DISABLE BIT(7)
>>> +#define MSI_PLATFORM_SHIFT_ENABLE (BIT(7) | BIT(6))
>>> +#define MSI_PLATFORM_SHIFT_SPORT (MSI_PLATFORM_SHIFT_ENABLE + 4)
>>> +#define MSI_PLATFORM_SHIFT_COMFORT (MSI_PLATFORM_SHIFT_ENABLE + 0)
>>> +#define MSI_PLATFORM_SHIFT_GREEN (MSI_PLATFORM_SHIFT_ENABLE + 1)
>>> +#define MSI_PLATFORM_SHIFT_ECO (MSI_PLATFORM_SHIFT_ENABLE + 2)
>>> +#define MSI_PLATFORM_SHIFT_USER (MSI_PLATFORM_SHIFT_ENABLE + 3)
>> Instead of summing the profiles I suggest something like:
>>
>> enum MSI_PLATFORM_PROFILES {
>> MSI_PROFILE_COMFORT,
>> MSI_PROFILE_GREEN,
>> MSI_PROFILE_ECO,
>> MSI_PROFILE_USER,
>> MSI_PROFILE_SPORT,
>> }
>>
>> And you can prepare your commands like
>>
>> command = MSI_PLATFORM_SHIT_ENABLE;
>> command |= FIELD_PREP(GENMASK(1,0), MSI_PROFILE_{profile});
>>
>> I feel that it's cleaner this way. This is only a suggestion though.
>>
>>> +
>>> static bool force;
>>> module_param_unsafe(force, bool, 0);
>>> MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
>>> @@ -100,12 +111,14 @@ enum msi_wmi_platform_method {
>>> };
>>>
>>> struct msi_wmi_platform_quirk {
>>> + bool shift_mode; /* Shift mode is supported */
>>> };
>>>
>>> struct msi_wmi_platform_data {
>>> struct wmi_device *wdev;
>>> struct msi_wmi_platform_quirk *quirks;
>>> struct mutex wmi_lock; /* Necessary when calling WMI methods */
>>> + struct device *ppdev;
>>> };
>>>
>>> struct msi_wmi_platform_debugfs_data {
>>> @@ -150,8 +163,10 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
>>>
>>> static struct msi_wmi_platform_quirk quirk_default = {};
>>> static struct msi_wmi_platform_quirk quirk_gen1 = {
>>> + .shift_mode = true
>>> };
>>> static struct msi_wmi_platform_quirk quirk_gen2 = {
>>> + .shift_mode = true
>>> };
>>>
>>> static const struct dmi_system_id msi_quirks[] = {
>>> @@ -561,6 +576,90 @@ static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
>>> .info = msi_wmi_platform_info,
>>> };
>>>
>>> +static int msi_wmi_platform_profile_probe(void *drvdata, unsigned long *choices)
>>> +{
>>> + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
>>> + set_bit(PLATFORM_PROFILE_BALANCED, choices);
>>> + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
>>> + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
>> Please, use the non-atomic __set_bit(). `choices` is not shared between
>> threads.
>>
>>> + return 0;
>>> +}
>>> +
>>> +static int msi_wmi_platform_profile_get(struct device *dev,
>>> + enum platform_profile_option *profile)
>>> +{
>>> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
>>> + int ret;
>>> +
>>> + u8 buffer[32] = { };
>> Move this to the top.
>>
>>> +
>>> + buffer[0] = MSI_PLATFORM_SHIFT_ADDR;
>>> +
>>> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer));
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + if (buffer[0] != 1)
>>> + return -EINVAL;
>>> +
>>> + switch (buffer[1]) {
>>> + case MSI_PLATFORM_SHIFT_SPORT:
>>> + *profile = PLATFORM_PROFILE_PERFORMANCE;
>>> + return 0;
>>> + case MSI_PLATFORM_SHIFT_COMFORT:
>>> + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
>> Maybe comfort can be mapped to balanced and green to cool. What do you
>> think?
> I forgot to comment on this one. The Claw uses ECO, GREEN, and
> PERFORMANCE as its primary modes with 8, 12, 30W respectively. Comfort
> is not used specifically for it. So I chose to push Green upwards to
> balanced.
>
> @Armin might know more about this one. If it turns out using comfort
> for balanced fits better for laptops we can do that instead.
>
> Antheas
I prefer comfort mode to be mapped as "balanced".
Thanks,
Armin Wolf
>>> + return 0;
>>> + case MSI_PLATFORM_SHIFT_GREEN:
>>> + *profile = PLATFORM_PROFILE_BALANCED;
>>> + return 0;
>>> + case MSI_PLATFORM_SHIFT_ECO:
>>> + *profile = PLATFORM_PROFILE_LOW_POWER;
>>> + return 0;
>>> + case MSI_PLATFORM_SHIFT_USER:
>>> + *profile = PLATFORM_PROFILE_CUSTOM;
>>> + return 0;
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> +}
>>> +
>>> +static int msi_wmi_platform_profile_set(struct device *dev,
>>> + enum platform_profile_option profile)
>>> +{
>>> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
>>> + u8 buffer[32] = { };
>>> +
>>> + buffer[0] = MSI_PLATFORM_SHIFT_ADDR;
>>> +
>>> + switch (profile) {
>>> + case PLATFORM_PROFILE_PERFORMANCE:
>>> + buffer[1] = MSI_PLATFORM_SHIFT_SPORT;
>>> + break;
>>> + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
>>> + buffer[1] = MSI_PLATFORM_SHIFT_COMFORT;
>>> + break;
>>> + case PLATFORM_PROFILE_BALANCED:
>>> + buffer[1] = MSI_PLATFORM_SHIFT_GREEN;
>>> + break;
>>> + case PLATFORM_PROFILE_LOW_POWER:
>>> + buffer[1] = MSI_PLATFORM_SHIFT_ECO;
>>> + break;
>>> + case PLATFORM_PROFILE_CUSTOM:
>>> + buffer[1] = MSI_PLATFORM_SHIFT_USER;
>>> + break;
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> +
>>> + return msi_wmi_platform_query(data, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer));
>>> +}
>>> +
>>> +static const struct platform_profile_ops msi_wmi_platform_profile_ops = {
>>> + .probe = msi_wmi_platform_profile_probe,
>>> + .profile_get = msi_wmi_platform_profile_get,
>>> + .profile_set = msi_wmi_platform_profile_set,
>>> +};
>>> +
>>> static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
>>> size_t length, loff_t *offset)
>>> {
>>> @@ -742,6 +841,22 @@ static int msi_wmi_platform_init(struct msi_wmi_platform_data *data)
>>> return 0;
>>> }
>>>
>>> +static int msi_wmi_platform_profile_setup(struct msi_wmi_platform_data *data)
>>> +{
>>> + int err;
>>> +
>>> + if (!data->quirks->shift_mode)
>>> + return 0;
>>> +
>>> + data->ppdev = devm_platform_profile_register(
>>> + &data->wdev->dev, "msi-wmi-platform", data,
>>> + &msi_wmi_platform_profile_ops);
>> Broken format.
>>
>>> + if (err)
>>> + return err;
>> `err` is not initialized. Is it a leftover?
>>
>>> +
>>> + return PTR_ERR_OR_ZERO(data->ppdev);
>>> +}
>>> +
>>> static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
>>> {
>>> struct msi_wmi_platform_data *data;
>>> @@ -775,6 +890,8 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
>>>
>>> msi_wmi_platform_debugfs_init(data);
>>>
>>> + msi_wmi_platform_profile_setup(data);
>> Check return value.
>>
>> --
>> ~ Kurt
>>
>>> +
>>> return msi_wmi_platform_hwmon_init(data);
>>> }
>>>
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 05/10] platform/x86: msi-wmi-platform: Add platform profile through shift mode
2025-05-11 20:44 ` [PATCH v1 05/10] platform/x86: msi-wmi-platform: Add platform profile through shift mode Antheas Kapenekakis
2025-05-11 23:33 ` Kurt Borja
@ 2025-05-19 1:58 ` Armin Wolf
1 sibling, 0 replies; 45+ messages in thread
From: Armin Wolf @ 2025-05-19 1:58 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Jonathan Corbet, Hans de Goede, Ilpo Järvinen, Jean Delvare,
Guenter Roeck, Kurt Borja, linux-doc, linux-kernel, linux-hwmon
Am 11.05.25 um 22:44 schrieb Antheas Kapenekakis:
> MSI's version of platform profile in Windows is called shift mode.
> Introduce it here, and add a profile handler to it.
>
> It has 5 modes: sport, comfort, green, eco, and user.
> Confusingly, for the Claw, MSI only uses sport, green, and eco,
> where they correspond to performance, balanced, and low-power.
> Therefore, comfort is mapped to balanced-performance, and user to
> custom.
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/Kconfig | 1 +
> drivers/platform/x86/msi-wmi-platform.c | 117 ++++++++++++++++++++++++
> 2 files changed, 118 insertions(+)
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index bee98251b8f0b..57a48910c8fd4 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -746,6 +746,7 @@ config MSI_WMI_PLATFORM
> tristate "MSI WMI Platform features"
> depends on ACPI_WMI
> depends on HWMON
> + select ACPI_PLATFORM_PROFILE
> help
> Say Y here if you want to have support for WMI-based platform features
> like fan sensor access on MSI machines.
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index 9ac3c6f1b3f1d..c0b577c95c079 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -17,6 +17,7 @@
> #include <linux/dmi.h>
> #include <linux/errno.h>
> #include <linux/fixp-arith.h>
> +#include <linux/platform_profile.h>
> #include <linux/hwmon.h>
> #include <linux/hwmon-sysfs.h>
> #include <linux/kernel.h>
> @@ -63,6 +64,16 @@
> #define MSI_PLATFORM_AP_FAN_FLAGS_OFFSET 1
> #define MSI_PLATFORM_AP_ENABLE_FAN_TABLES BIT(7)
>
> +/* Get_Data() and Set_Data() Shift Mode Register */
> +#define MSI_PLATFORM_SHIFT_ADDR 0xd2
> +#define MSI_PLATFORM_SHIFT_DISABLE BIT(7)
> +#define MSI_PLATFORM_SHIFT_ENABLE (BIT(7) | BIT(6))
> +#define MSI_PLATFORM_SHIFT_SPORT (MSI_PLATFORM_SHIFT_ENABLE + 4)
> +#define MSI_PLATFORM_SHIFT_COMFORT (MSI_PLATFORM_SHIFT_ENABLE + 0)
> +#define MSI_PLATFORM_SHIFT_GREEN (MSI_PLATFORM_SHIFT_ENABLE + 1)
> +#define MSI_PLATFORM_SHIFT_ECO (MSI_PLATFORM_SHIFT_ENABLE + 2)
> +#define MSI_PLATFORM_SHIFT_USER (MSI_PLATFORM_SHIFT_ENABLE + 3)
> +
Please use a enum for the shift modes and built the final value later using FIELD_PREP().
Also please update the documentation to include the new WMI methods.
> static bool force;
> module_param_unsafe(force, bool, 0);
> MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
> @@ -100,12 +111,14 @@ enum msi_wmi_platform_method {
> };
>
> struct msi_wmi_platform_quirk {
> + bool shift_mode; /* Shift mode is supported */
> };
>
> struct msi_wmi_platform_data {
> struct wmi_device *wdev;
> struct msi_wmi_platform_quirk *quirks;
> struct mutex wmi_lock; /* Necessary when calling WMI methods */
> + struct device *ppdev;
> };
>
> struct msi_wmi_platform_debugfs_data {
> @@ -150,8 +163,10 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
>
> static struct msi_wmi_platform_quirk quirk_default = {};
> static struct msi_wmi_platform_quirk quirk_gen1 = {
> + .shift_mode = true
> };
> static struct msi_wmi_platform_quirk quirk_gen2 = {
> + .shift_mode = true
> };
>
> static const struct dmi_system_id msi_quirks[] = {
> @@ -561,6 +576,90 @@ static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
> .info = msi_wmi_platform_info,
> };
>
> +static int msi_wmi_platform_profile_probe(void *drvdata, unsigned long *choices)
> +{
> + set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> + set_bit(PLATFORM_PROFILE_BALANCED, choices);
> + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
> + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
> + return 0;
> +}
> +
> +static int msi_wmi_platform_profile_get(struct device *dev,
> + enum platform_profile_option *profile)
> +{
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + int ret;
> +
> + u8 buffer[32] = { };
> +
> + buffer[0] = MSI_PLATFORM_SHIFT_ADDR;
> +
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + if (buffer[0] != 1)
> + return -EINVAL;
Please omit this check, msi_wmi_platform_parse_buffer() already takes care of that.
Also could it be that the OEM application uses the Get_AP() WMI method for getting
this value? If yes when please follow this behavior to maximize compatibility.
> +
> + switch (buffer[1]) {
> + case MSI_PLATFORM_SHIFT_SPORT:
> + *profile = PLATFORM_PROFILE_PERFORMANCE;
> + return 0;
> + case MSI_PLATFORM_SHIFT_COMFORT:
> + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> + return 0;
> + case MSI_PLATFORM_SHIFT_GREEN:
> + *profile = PLATFORM_PROFILE_BALANCED;
> + return 0;
> + case MSI_PLATFORM_SHIFT_ECO:
> + *profile = PLATFORM_PROFILE_LOW_POWER;
> + return 0;
> + case MSI_PLATFORM_SHIFT_USER:
> + *profile = PLATFORM_PROFILE_CUSTOM;
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int msi_wmi_platform_profile_set(struct device *dev,
> + enum platform_profile_option profile)
> +{
> + struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
> + u8 buffer[32] = { };
> +
> + buffer[0] = MSI_PLATFORM_SHIFT_ADDR;
> +
> + switch (profile) {
> + case PLATFORM_PROFILE_PERFORMANCE:
> + buffer[1] = MSI_PLATFORM_SHIFT_SPORT;
> + break;
> + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
> + buffer[1] = MSI_PLATFORM_SHIFT_COMFORT;
> + break;
> + case PLATFORM_PROFILE_BALANCED:
> + buffer[1] = MSI_PLATFORM_SHIFT_GREEN;
> + break;
> + case PLATFORM_PROFILE_LOW_POWER:
> + buffer[1] = MSI_PLATFORM_SHIFT_ECO;
> + break;
> + case PLATFORM_PROFILE_CUSTOM:
> + buffer[1] = MSI_PLATFORM_SHIFT_USER;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return msi_wmi_platform_query(data, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer));
> +}
> +
> +static const struct platform_profile_ops msi_wmi_platform_profile_ops = {
> + .probe = msi_wmi_platform_profile_probe,
> + .profile_get = msi_wmi_platform_profile_get,
> + .profile_set = msi_wmi_platform_profile_set,
> +};
> +
> static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
> size_t length, loff_t *offset)
> {
> @@ -742,6 +841,22 @@ static int msi_wmi_platform_init(struct msi_wmi_platform_data *data)
> return 0;
> }
>
> +static int msi_wmi_platform_profile_setup(struct msi_wmi_platform_data *data)
Please rename this function to msi_wmi_platform_profile_init().
> +{
> + int err;
> +
> + if (!data->quirks->shift_mode)
> + return 0;
> +
> + data->ppdev = devm_platform_profile_register(
> + &data->wdev->dev, "msi-wmi-platform", data,
> + &msi_wmi_platform_profile_ops);
Please turn ppdev into a local variable. Also please remove the err variable.
Thanks,
Armin Wolf
> + if (err)
> + return err;
> +
> + return PTR_ERR_OR_ZERO(data->ppdev);
> +}
> +
> static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> {
> struct msi_wmi_platform_data *data;
> @@ -775,6 +890,8 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
>
> msi_wmi_platform_debugfs_init(data);
>
> + msi_wmi_platform_profile_setup(data);
> +
> return msi_wmi_platform_hwmon_init(data);
> }
>
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v1 06/10] platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware attributes
2025-05-11 20:44 [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Antheas Kapenekakis
` (4 preceding siblings ...)
2025-05-11 20:44 ` [PATCH v1 05/10] platform/x86: msi-wmi-platform: Add platform profile through shift mode Antheas Kapenekakis
@ 2025-05-11 20:44 ` Antheas Kapenekakis
2025-05-11 23:34 ` Kurt Borja
2025-05-19 2:08 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 07/10] platform/x86: msi-wmi-platform: Add charge_threshold support Antheas Kapenekakis
` (5 subsequent siblings)
11 siblings, 2 replies; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-11 20:44 UTC (permalink / raw)
To: platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, Kurt Borja, linux-doc, linux-kernel,
linux-hwmon, Antheas Kapenekakis
Adds PL1, and PL2 support through the firmware attributes interface.
The min and max values are quirked, and the attributes are only defined
if they are set to a non-zero value. These values are meant to be set
in conjunction with shift mode, where shift mode automatically sets
an upper bound on PL1/PL2 (e.g., low-power would be used with 8W).
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
drivers/platform/x86/Kconfig | 1 +
drivers/platform/x86/msi-wmi-platform.c | 361 +++++++++++++++++++++++-
2 files changed, 360 insertions(+), 2 deletions(-)
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 57a48910c8fd4..fd3da718731e7 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -747,6 +747,7 @@ config MSI_WMI_PLATFORM
depends on ACPI_WMI
depends on HWMON
select ACPI_PLATFORM_PROFILE
+ select FW_ATTR_CLASS
help
Say Y here if you want to have support for WMI-based platform features
like fan sensor access on MSI machines.
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
index c0b577c95c079..6498f4b44fe53 100644
--- a/drivers/platform/x86/msi-wmi-platform.c
+++ b/drivers/platform/x86/msi-wmi-platform.c
@@ -34,6 +34,8 @@
#include <linux/unaligned.h>
+#include "firmware_attributes_class.h"
+
#define DRIVER_NAME "msi-wmi-platform"
#define MSI_PLATFORM_GUID "ABBC0F6E-8EA1-11d1-00A0-C90629100000"
@@ -74,6 +76,10 @@
#define MSI_PLATFORM_SHIFT_ECO (MSI_PLATFORM_SHIFT_ENABLE + 2)
#define MSI_PLATFORM_SHIFT_USER (MSI_PLATFORM_SHIFT_ENABLE + 3)
+/* Get_Data() and Set_Data() Params */
+#define MSI_PLATFORM_PL1_ADDR 0x50
+#define MSI_PLATFORM_PL2_ADDR 0x51
+
static bool force;
module_param_unsafe(force, bool, 0);
MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
@@ -112,6 +118,9 @@ enum msi_wmi_platform_method {
struct msi_wmi_platform_quirk {
bool shift_mode; /* Shift mode is supported */
+ int pl_min; /* Minimum PLx value */
+ int pl1_max; /* Maximum PL1 value */
+ int pl2_max; /* Maximum PL2 value */
};
struct msi_wmi_platform_data {
@@ -119,6 +128,44 @@ struct msi_wmi_platform_data {
struct msi_wmi_platform_quirk *quirks;
struct mutex wmi_lock; /* Necessary when calling WMI methods */
struct device *ppdev;
+ struct device *fw_attrs_dev;
+ struct kset *fw_attrs_kset;
+};
+
+enum msi_fw_attr_id {
+ MSI_ATTR_PPT_PL1_SPL,
+ MSI_ATTR_PPT_PL2_SPPT,
+};
+
+static const char *const msi_fw_attr_name[] = {
+ [MSI_ATTR_PPT_PL1_SPL] = "ppt_pl1_spl",
+ [MSI_ATTR_PPT_PL2_SPPT] = "ppt_pl2_sppt",
+};
+
+static const char *const msi_fw_attr_desc[] = {
+ [MSI_ATTR_PPT_PL1_SPL] = "CPU Steady package limit (PL1/SPL)",
+ [MSI_ATTR_PPT_PL2_SPPT] = "CPU Boost slow package limit (PL2/SPPT)",
+};
+
+#define MSI_ATTR_LANGUAGE_CODE "en_US.UTF-8"
+
+struct msi_fw_attr {
+ struct msi_wmi_platform_data *data;
+ enum msi_fw_attr_id fw_attr_id;
+ struct attribute_group attr_group;
+ struct kobj_attribute display_name;
+ struct kobj_attribute current_value;
+ struct kobj_attribute min_value;
+ struct kobj_attribute max_value;
+
+ u32 min;
+ u32 max;
+
+ int (*get_value)(struct msi_wmi_platform_data *data,
+ struct msi_fw_attr *fw_attr, char *buf);
+ ssize_t (*set_value)(struct msi_wmi_platform_data *data,
+ struct msi_fw_attr *fw_attr, const char *buf,
+ size_t count);
};
struct msi_wmi_platform_debugfs_data {
@@ -163,10 +210,16 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
static struct msi_wmi_platform_quirk quirk_default = {};
static struct msi_wmi_platform_quirk quirk_gen1 = {
- .shift_mode = true
+ .shift_mode = true,
+ .pl_min = 8,
+ .pl1_max = 43,
+ .pl2_max = 45
};
static struct msi_wmi_platform_quirk quirk_gen2 = {
- .shift_mode = true
+ .shift_mode = true,
+ .pl_min = 8,
+ .pl1_max = 30,
+ .pl2_max = 37
};
static const struct dmi_system_id msi_quirks[] = {
@@ -660,6 +713,306 @@ static const struct platform_profile_ops msi_wmi_platform_profile_ops = {
.profile_set = msi_wmi_platform_profile_set,
};
+/* Firmware Attributes setup */
+static int data_get_addr(struct msi_wmi_platform_data *data,
+ const enum msi_fw_attr_id id)
+{
+ switch (id) {
+ case MSI_ATTR_PPT_PL1_SPL:
+ return MSI_PLATFORM_PL1_ADDR;
+ case MSI_ATTR_PPT_PL2_SPPT:
+ return MSI_PLATFORM_PL2_ADDR;
+ default:
+ pr_warn("Invalid attribute id %d\n", id);
+ return -EINVAL;
+ }
+}
+
+static ssize_t data_set_value(struct msi_wmi_platform_data *data,
+ struct msi_fw_attr *fw_attr, const char *buf,
+ size_t count)
+{
+ u8 buffer[32] = { 0 };
+ int ret, fwid;
+ u32 value;
+
+ fwid = data_get_addr(data, fw_attr->fw_attr_id);
+ if (fwid < 0)
+ return fwid;
+
+ ret = kstrtou32(buf, 10, &value);
+ if (ret)
+ return ret;
+
+ if (fw_attr->min >= 0 && value < fw_attr->min)
+ return -EINVAL;
+ if (fw_attr->max >= 0 && value > fw_attr->max)
+ return -EINVAL;
+
+ buffer[0] = fwid;
+ put_unaligned_le32(value, &buffer[1]);
+
+ ret = msi_wmi_platform_query(data, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer));
+ if (ret) {
+ pr_warn("Failed to set_data with id %d: %d\n",
+ fw_attr->fw_attr_id, ret);
+ return ret;
+ }
+
+ return count;
+}
+
+static int data_get_value(struct msi_wmi_platform_data *data,
+ struct msi_fw_attr *fw_attr, char *buf)
+{
+ u8 buffer[32] = { 0 };
+ u32 value;
+ int ret, addr;
+
+ addr = data_get_addr(data, fw_attr->fw_attr_id);
+ if (addr < 0)
+ return addr;
+
+ buffer[0] = addr;
+
+ ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer));
+ if (ret) {
+ pr_warn("Failed to show set_data for id %d: %d\n",
+ fw_attr->fw_attr_id, ret);
+ return ret;
+ }
+
+ value = get_unaligned_le32(&buffer[1]);
+
+ return sysfs_emit(buf, "%d\n", value);
+}
+
+static ssize_t display_name_language_code_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n", MSI_ATTR_LANGUAGE_CODE);
+}
+
+static struct kobj_attribute fw_attr_display_name_language_code =
+ __ATTR_RO(display_name_language_code);
+
+static ssize_t scalar_increment_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "1\n");
+}
+
+static struct kobj_attribute fw_attr_scalar_increment =
+ __ATTR_RO(scalar_increment);
+
+static ssize_t pending_reboot_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "0\n");
+}
+
+static struct kobj_attribute fw_attr_pending_reboot = __ATTR_RO(pending_reboot);
+
+static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ struct msi_fw_attr *fw_attr =
+ container_of(attr, struct msi_fw_attr, display_name);
+
+ return sysfs_emit(buf, "%s\n", msi_fw_attr_desc[fw_attr->fw_attr_id]);
+}
+
+static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ struct msi_fw_attr *fw_attr =
+ container_of(attr, struct msi_fw_attr, current_value);
+
+ return fw_attr->get_value(fw_attr->data, fw_attr, buf);
+}
+
+static ssize_t current_value_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct msi_fw_attr *fw_attr =
+ container_of(attr, struct msi_fw_attr, current_value);
+
+ return fw_attr->set_value(fw_attr->data, fw_attr, buf, count);
+}
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "integer\n");
+}
+
+static struct kobj_attribute fw_attr_type_int = {
+ .attr = { .name = "type", .mode = 0444 },
+ .show = type_show,
+};
+
+static ssize_t min_value_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ struct msi_fw_attr *fw_attr =
+ container_of(attr, struct msi_fw_attr, min_value);
+
+ return sysfs_emit(buf, "%d\n", fw_attr->min);
+}
+
+static ssize_t max_value_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ struct msi_fw_attr *fw_attr =
+ container_of(attr, struct msi_fw_attr, max_value);
+
+ return sysfs_emit(buf, "%d\n", fw_attr->max);
+}
+
+#define FW_ATTR_ENUM_MAX_ATTRS 7
+
+static int
+msi_fw_attr_init(struct msi_wmi_platform_data *data,
+ const enum msi_fw_attr_id fw_attr_id,
+ struct kobj_attribute *fw_attr_type, const s32 min,
+ const s32 max,
+ int (*get_value)(struct msi_wmi_platform_data *data,
+ struct msi_fw_attr *fw_attr, char *buf),
+ ssize_t (*set_value)(struct msi_wmi_platform_data *data,
+ struct msi_fw_attr *fw_attr,
+ const char *buf, size_t count))
+{
+ struct msi_fw_attr *fw_attr;
+ struct attribute **attrs;
+ int idx = 0;
+
+ fw_attr = devm_kzalloc(&data->wdev->dev, sizeof(*fw_attr), GFP_KERNEL);
+ if (!fw_attr)
+ return -ENOMEM;
+
+ attrs = devm_kcalloc(&data->wdev->dev, FW_ATTR_ENUM_MAX_ATTRS + 1,
+ sizeof(*attrs), GFP_KERNEL);
+ if (!attrs)
+ return -ENOMEM;
+
+ fw_attr->data = data;
+ fw_attr->fw_attr_id = fw_attr_id;
+ fw_attr->attr_group.name = msi_fw_attr_name[fw_attr_id];
+ fw_attr->attr_group.attrs = attrs;
+ fw_attr->get_value = get_value;
+ fw_attr->set_value = set_value;
+
+ attrs[idx++] = &fw_attr_type->attr;
+ if (fw_attr_type == &fw_attr_type_int)
+ attrs[idx++] = &fw_attr_scalar_increment.attr;
+ attrs[idx++] = &fw_attr_display_name_language_code.attr;
+
+ sysfs_attr_init(&fw_attr->display_name.attr);
+ fw_attr->display_name.attr.name = "display_name";
+ fw_attr->display_name.attr.mode = 0444;
+ fw_attr->display_name.show = display_name_show;
+ attrs[idx++] = &fw_attr->display_name.attr;
+
+ sysfs_attr_init(&fw_attr->current_value.attr);
+ fw_attr->current_value.attr.name = "current_value";
+ fw_attr->current_value.attr.mode = 0644;
+ fw_attr->current_value.show = current_value_show;
+ fw_attr->current_value.store = current_value_store;
+ attrs[idx++] = &fw_attr->current_value.attr;
+
+ if (min >= 0) {
+ fw_attr->min = min;
+ sysfs_attr_init(&fw_attr->min_value.attr);
+ fw_attr->min_value.attr.name = "min_value";
+ fw_attr->min_value.attr.mode = 0444;
+ fw_attr->min_value.show = min_value_show;
+ attrs[idx++] = &fw_attr->min_value.attr;
+ } else {
+ fw_attr->min = -1;
+ }
+
+ if (max >= 0) {
+ fw_attr->max = max;
+ sysfs_attr_init(&fw_attr->max_value.attr);
+ fw_attr->max_value.attr.name = "max_value";
+ fw_attr->max_value.attr.mode = 0444;
+ fw_attr->max_value.show = max_value_show;
+ attrs[idx++] = &fw_attr->max_value.attr;
+ } else {
+ fw_attr->max = -1;
+ }
+
+ attrs[idx] = NULL;
+ return sysfs_create_group(&data->fw_attrs_kset->kobj, &fw_attr->attr_group);
+}
+
+static void msi_kset_unregister(void *data)
+{
+ struct kset *kset = data;
+
+ sysfs_remove_file(&kset->kobj, &fw_attr_pending_reboot.attr);
+ kset_unregister(kset);
+}
+
+static void msi_fw_attrs_dev_unregister(void *data)
+{
+ struct device *fw_attrs_dev = data;
+
+ device_unregister(fw_attrs_dev);
+}
+
+static int msi_wmi_fw_attrs_init(struct msi_wmi_platform_data *data)
+{
+ int err;
+
+ data->fw_attrs_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),
+ NULL, "%s", DRIVER_NAME);
+ if (IS_ERR(data->fw_attrs_dev))
+ return PTR_ERR(data->fw_attrs_dev);
+
+ err = devm_add_action_or_reset(&data->wdev->dev,
+ msi_fw_attrs_dev_unregister,
+ data->fw_attrs_dev);
+ if (err)
+ return err;
+
+ data->fw_attrs_kset = kset_create_and_add("attributes", NULL,
+ &data->fw_attrs_dev->kobj);
+ if (!data->fw_attrs_kset)
+ return -ENOMEM;
+
+ err = sysfs_create_file(&data->fw_attrs_kset->kobj,
+ &fw_attr_pending_reboot.attr);
+ if (err) {
+ kset_unregister(data->fw_attrs_kset);
+ return err;
+ }
+
+ err = devm_add_action_or_reset(&data->wdev->dev, msi_kset_unregister,
+ data->fw_attrs_kset);
+ if (err)
+ return err;
+
+ if (data->quirks->pl1_max) {
+ err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL1_SPL,
+ &fw_attr_type_int, data->quirks->pl_min,
+ data->quirks->pl1_max, &data_get_value,
+ &data_set_value);
+ if (err)
+ return err;
+ }
+
+ if (data->quirks->pl2_max) {
+ err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL2_SPPT,
+ &fw_attr_type_int, data->quirks->pl_min,
+ data->quirks->pl2_max, &data_get_value,
+ &data_set_value);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
size_t length, loff_t *offset)
{
@@ -888,6 +1241,10 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
if (ret < 0)
return ret;
+ ret = msi_wmi_fw_attrs_init(data);
+ if (ret < 0)
+ return ret;
+
msi_wmi_platform_debugfs_init(data);
msi_wmi_platform_profile_setup(data);
--
2.49.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v1 06/10] platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware attributes
2025-05-11 20:44 ` [PATCH v1 06/10] platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware attributes Antheas Kapenekakis
@ 2025-05-11 23:34 ` Kurt Borja
2025-05-12 10:22 ` Antheas Kapenekakis
2025-05-19 2:08 ` Armin Wolf
1 sibling, 1 reply; 45+ messages in thread
From: Kurt Borja @ 2025-05-11 23:34 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, linux-doc, linux-kernel, linux-hwmon
[-- Attachment #1: Type: text/plain, Size: 14476 bytes --]
On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> Adds PL1, and PL2 support through the firmware attributes interface.
> The min and max values are quirked, and the attributes are only defined
> if they are set to a non-zero value. These values are meant to be set
> in conjunction with shift mode, where shift mode automatically sets
> an upper bound on PL1/PL2 (e.g., low-power would be used with 8W).
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/Kconfig | 1 +
> drivers/platform/x86/msi-wmi-platform.c | 361 +++++++++++++++++++++++-
> 2 files changed, 360 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 57a48910c8fd4..fd3da718731e7 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -747,6 +747,7 @@ config MSI_WMI_PLATFORM
> depends on ACPI_WMI
> depends on HWMON
> select ACPI_PLATFORM_PROFILE
> + select FW_ATTR_CLASS
> help
> Say Y here if you want to have support for WMI-based platform features
> like fan sensor access on MSI machines.
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index c0b577c95c079..6498f4b44fe53 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -34,6 +34,8 @@
>
> #include <linux/unaligned.h>
>
> +#include "firmware_attributes_class.h"
> +
> #define DRIVER_NAME "msi-wmi-platform"
>
> #define MSI_PLATFORM_GUID "ABBC0F6E-8EA1-11d1-00A0-C90629100000"
> @@ -74,6 +76,10 @@
> #define MSI_PLATFORM_SHIFT_ECO (MSI_PLATFORM_SHIFT_ENABLE + 2)
> #define MSI_PLATFORM_SHIFT_USER (MSI_PLATFORM_SHIFT_ENABLE + 3)
>
> +/* Get_Data() and Set_Data() Params */
> +#define MSI_PLATFORM_PL1_ADDR 0x50
> +#define MSI_PLATFORM_PL2_ADDR 0x51
> +
> static bool force;
> module_param_unsafe(force, bool, 0);
> MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
> @@ -112,6 +118,9 @@ enum msi_wmi_platform_method {
>
> struct msi_wmi_platform_quirk {
> bool shift_mode; /* Shift mode is supported */
> + int pl_min; /* Minimum PLx value */
> + int pl1_max; /* Maximum PL1 value */
> + int pl2_max; /* Maximum PL2 value */
> };
>
> struct msi_wmi_platform_data {
> @@ -119,6 +128,44 @@ struct msi_wmi_platform_data {
> struct msi_wmi_platform_quirk *quirks;
> struct mutex wmi_lock; /* Necessary when calling WMI methods */
> struct device *ppdev;
> + struct device *fw_attrs_dev;
> + struct kset *fw_attrs_kset;
> +};
> +
> +enum msi_fw_attr_id {
> + MSI_ATTR_PPT_PL1_SPL,
> + MSI_ATTR_PPT_PL2_SPPT,
> +};
> +
> +static const char *const msi_fw_attr_name[] = {
> + [MSI_ATTR_PPT_PL1_SPL] = "ppt_pl1_spl",
> + [MSI_ATTR_PPT_PL2_SPPT] = "ppt_pl2_sppt",
> +};
> +
> +static const char *const msi_fw_attr_desc[] = {
> + [MSI_ATTR_PPT_PL1_SPL] = "CPU Steady package limit (PL1/SPL)",
> + [MSI_ATTR_PPT_PL2_SPPT] = "CPU Boost slow package limit (PL2/SPPT)",
> +};
> +
> +#define MSI_ATTR_LANGUAGE_CODE "en_US.UTF-8"
> +
> +struct msi_fw_attr {
> + struct msi_wmi_platform_data *data;
> + enum msi_fw_attr_id fw_attr_id;
> + struct attribute_group attr_group;
> + struct kobj_attribute display_name;
> + struct kobj_attribute current_value;
> + struct kobj_attribute min_value;
> + struct kobj_attribute max_value;
> +
> + u32 min;
> + u32 max;
> +
> + int (*get_value)(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, char *buf);
> + ssize_t (*set_value)(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, const char *buf,
> + size_t count);
> };
>
> struct msi_wmi_platform_debugfs_data {
> @@ -163,10 +210,16 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
>
> static struct msi_wmi_platform_quirk quirk_default = {};
> static struct msi_wmi_platform_quirk quirk_gen1 = {
> - .shift_mode = true
> + .shift_mode = true,
> + .pl_min = 8,
> + .pl1_max = 43,
> + .pl2_max = 45
> };
> static struct msi_wmi_platform_quirk quirk_gen2 = {
> - .shift_mode = true
> + .shift_mode = true,
> + .pl_min = 8,
> + .pl1_max = 30,
> + .pl2_max = 37
> };
>
> static const struct dmi_system_id msi_quirks[] = {
> @@ -660,6 +713,306 @@ static const struct platform_profile_ops msi_wmi_platform_profile_ops = {
> .profile_set = msi_wmi_platform_profile_set,
> };
>
> +/* Firmware Attributes setup */
> +static int data_get_addr(struct msi_wmi_platform_data *data,
> + const enum msi_fw_attr_id id)
> +{
> + switch (id) {
> + case MSI_ATTR_PPT_PL1_SPL:
> + return MSI_PLATFORM_PL1_ADDR;
> + case MSI_ATTR_PPT_PL2_SPPT:
> + return MSI_PLATFORM_PL2_ADDR;
> + default:
> + pr_warn("Invalid attribute id %d\n", id);
> + return -EINVAL;
> + }
> +}
> +
> +static ssize_t data_set_value(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, const char *buf,
> + size_t count)
> +{
> + u8 buffer[32] = { 0 };
> + int ret, fwid;
> + u32 value;
> +
> + fwid = data_get_addr(data, fw_attr->fw_attr_id);
> + if (fwid < 0)
> + return fwid;
> +
> + ret = kstrtou32(buf, 10, &value);
> + if (ret)
> + return ret;
> +
> + if (fw_attr->min >= 0 && value < fw_attr->min)
> + return -EINVAL;
> + if (fw_attr->max >= 0 && value > fw_attr->max)
> + return -EINVAL;
Maybe clamp instead of failing?
> +
> + buffer[0] = fwid;
> + put_unaligned_le32(value, &buffer[1]);
> +
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer));
> + if (ret) {
> + pr_warn("Failed to set_data with id %d: %d\n",
> + fw_attr->fw_attr_id, ret);
> + return ret;
> + }
> +
> + return count;
> +}
> +
> +static int data_get_value(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, char *buf)
> +{
> + u8 buffer[32] = { 0 };
> + u32 value;
> + int ret, addr;
> +
> + addr = data_get_addr(data, fw_attr->fw_attr_id);
> + if (addr < 0)
> + return addr;
> +
> + buffer[0] = addr;
> +
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer));
> + if (ret) {
> + pr_warn("Failed to show set_data for id %d: %d\n",
> + fw_attr->fw_attr_id, ret);
> + return ret;
> + }
> +
> + value = get_unaligned_le32(&buffer[1]);
> +
> + return sysfs_emit(buf, "%d\n", value);
> +}
> +
> +static ssize_t display_name_language_code_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "%s\n", MSI_ATTR_LANGUAGE_CODE);
> +}
> +
> +static struct kobj_attribute fw_attr_display_name_language_code =
> + __ATTR_RO(display_name_language_code);
> +
> +static ssize_t scalar_increment_show(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "1\n");
> +}
> +
> +static struct kobj_attribute fw_attr_scalar_increment =
> + __ATTR_RO(scalar_increment);
> +
> +static ssize_t pending_reboot_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + return sysfs_emit(buf, "0\n");
> +}
> +
> +static struct kobj_attribute fw_attr_pending_reboot = __ATTR_RO(pending_reboot);
> +
> +static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, display_name);
> +
> + return sysfs_emit(buf, "%s\n", msi_fw_attr_desc[fw_attr->fw_attr_id]);
> +}
> +
> +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, current_value);
> +
> + return fw_attr->get_value(fw_attr->data, fw_attr, buf);
> +}
> +
> +static ssize_t current_value_store(struct kobject *kobj, struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, current_value);
> +
> + return fw_attr->set_value(fw_attr->data, fw_attr, buf, count);
> +}
> +
> +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "integer\n");
> +}
> +
> +static struct kobj_attribute fw_attr_type_int = {
> + .attr = { .name = "type", .mode = 0444 },
> + .show = type_show,
> +};
Use __ATTR_RO().
> +
> +static ssize_t min_value_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, min_value);
> +
> + return sysfs_emit(buf, "%d\n", fw_attr->min);
> +}
> +
> +static ssize_t max_value_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, max_value);
> +
> + return sysfs_emit(buf, "%d\n", fw_attr->max);
> +}
> +
> +#define FW_ATTR_ENUM_MAX_ATTRS 7
> +
> +static int
> +msi_fw_attr_init(struct msi_wmi_platform_data *data,
> + const enum msi_fw_attr_id fw_attr_id,
> + struct kobj_attribute *fw_attr_type, const s32 min,
> + const s32 max,
> + int (*get_value)(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, char *buf),
> + ssize_t (*set_value)(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr,
> + const char *buf, size_t count))
> +{
> + struct msi_fw_attr *fw_attr;
> + struct attribute **attrs;
> + int idx = 0;
> +
> + fw_attr = devm_kzalloc(&data->wdev->dev, sizeof(*fw_attr), GFP_KERNEL);
> + if (!fw_attr)
> + return -ENOMEM;
> +
> + attrs = devm_kcalloc(&data->wdev->dev, FW_ATTR_ENUM_MAX_ATTRS + 1,
> + sizeof(*attrs), GFP_KERNEL);
> + if (!attrs)
> + return -ENOMEM;
> +
> + fw_attr->data = data;
> + fw_attr->fw_attr_id = fw_attr_id;
> + fw_attr->attr_group.name = msi_fw_attr_name[fw_attr_id];
> + fw_attr->attr_group.attrs = attrs;
> + fw_attr->get_value = get_value;
> + fw_attr->set_value = set_value;
> +
> + attrs[idx++] = &fw_attr_type->attr;
> + if (fw_attr_type == &fw_attr_type_int)
> + attrs[idx++] = &fw_attr_scalar_increment.attr;
> + attrs[idx++] = &fw_attr_display_name_language_code.attr;
> +
> + sysfs_attr_init(&fw_attr->display_name.attr);
> + fw_attr->display_name.attr.name = "display_name";
> + fw_attr->display_name.attr.mode = 0444;
> + fw_attr->display_name.show = display_name_show;
> + attrs[idx++] = &fw_attr->display_name.attr;
> +
> + sysfs_attr_init(&fw_attr->current_value.attr);
> + fw_attr->current_value.attr.name = "current_value";
> + fw_attr->current_value.attr.mode = 0644;
> + fw_attr->current_value.show = current_value_show;
> + fw_attr->current_value.store = current_value_store;
> + attrs[idx++] = &fw_attr->current_value.attr;
> +
> + if (min >= 0) {
> + fw_attr->min = min;
> + sysfs_attr_init(&fw_attr->min_value.attr);
> + fw_attr->min_value.attr.name = "min_value";
> + fw_attr->min_value.attr.mode = 0444;
> + fw_attr->min_value.show = min_value_show;
> + attrs[idx++] = &fw_attr->min_value.attr;
> + } else {
> + fw_attr->min = -1;
> + }
> +
> + if (max >= 0) {
> + fw_attr->max = max;
> + sysfs_attr_init(&fw_attr->max_value.attr);
> + fw_attr->max_value.attr.name = "max_value";
> + fw_attr->max_value.attr.mode = 0444;
> + fw_attr->max_value.show = max_value_show;
> + attrs[idx++] = &fw_attr->max_value.attr;
> + } else {
> + fw_attr->max = -1;
> + }
> +
> + attrs[idx] = NULL;
kcalloc already sets this to 0.
> + return sysfs_create_group(&data->fw_attrs_kset->kobj, &fw_attr->attr_group);
This group is never removed.
I think it's not that big of a deal? But you should probably do it
anyway.
> +}
> +
> +static void msi_kset_unregister(void *data)
> +{
> + struct kset *kset = data;
> +
> + sysfs_remove_file(&kset->kobj, &fw_attr_pending_reboot.attr);
> + kset_unregister(kset);
> +}
> +
> +static void msi_fw_attrs_dev_unregister(void *data)
> +{
> + struct device *fw_attrs_dev = data;
> +
> + device_unregister(fw_attrs_dev);
> +}
> +
> +static int msi_wmi_fw_attrs_init(struct msi_wmi_platform_data *data)
> +{
> + int err;
> +
> + data->fw_attrs_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),
> + NULL, "%s", DRIVER_NAME);
Set the wmi device as the parent.
> + if (IS_ERR(data->fw_attrs_dev))
> + return PTR_ERR(data->fw_attrs_dev);
> +
> + err = devm_add_action_or_reset(&data->wdev->dev,
> + msi_fw_attrs_dev_unregister,
> + data->fw_attrs_dev);
> + if (err)
> + return err;
> +
> + data->fw_attrs_kset = kset_create_and_add("attributes", NULL,
> + &data->fw_attrs_dev->kobj);
> + if (!data->fw_attrs_kset)
> + return -ENOMEM;
> +
> + err = sysfs_create_file(&data->fw_attrs_kset->kobj,
> + &fw_attr_pending_reboot.attr);
If it's always 0 why include it? It's not mandatory anyway.
--
~ Kurt
> + if (err) {
> + kset_unregister(data->fw_attrs_kset);
> + return err;
> + }
> +
> + err = devm_add_action_or_reset(&data->wdev->dev, msi_kset_unregister,
> + data->fw_attrs_kset);
> + if (err)
> + return err;
> +
> + if (data->quirks->pl1_max) {
> + err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL1_SPL,
> + &fw_attr_type_int, data->quirks->pl_min,
> + data->quirks->pl1_max, &data_get_value,
> + &data_set_value);
> + if (err)
> + return err;
> + }
> +
> + if (data->quirks->pl2_max) {
> + err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL2_SPPT,
> + &fw_attr_type_int, data->quirks->pl_min,
> + data->quirks->pl2_max, &data_get_value,
> + &data_set_value);
> + if (err)
> + return err;
> + }
> +
> + return 0;
> +}
> +
> static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
> size_t length, loff_t *offset)
> {
> @@ -888,6 +1241,10 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> if (ret < 0)
> return ret;
>
> + ret = msi_wmi_fw_attrs_init(data);
> + if (ret < 0)
> + return ret;
> +
> msi_wmi_platform_debugfs_init(data);
>
> msi_wmi_platform_profile_setup(data);
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 06/10] platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware attributes
2025-05-11 23:34 ` Kurt Borja
@ 2025-05-12 10:22 ` Antheas Kapenekakis
0 siblings, 0 replies; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-12 10:22 UTC (permalink / raw)
To: Kurt Borja
Cc: platform-driver-x86, Armin Wolf, Jonathan Corbet, Hans de Goede,
Ilpo Järvinen, Jean Delvare, Guenter Roeck, linux-doc,
linux-kernel, linux-hwmon
On Mon, 12 May 2025 at 01:34, Kurt Borja <kuurtb@gmail.com> wrote:
>
> On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> > Adds PL1, and PL2 support through the firmware attributes interface.
> > The min and max values are quirked, and the attributes are only defined
> > if they are set to a non-zero value. These values are meant to be set
> > in conjunction with shift mode, where shift mode automatically sets
> > an upper bound on PL1/PL2 (e.g., low-power would be used with 8W).
> >
> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > ---
> > drivers/platform/x86/Kconfig | 1 +
> > drivers/platform/x86/msi-wmi-platform.c | 361 +++++++++++++++++++++++-
> > 2 files changed, 360 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> > index 57a48910c8fd4..fd3da718731e7 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -747,6 +747,7 @@ config MSI_WMI_PLATFORM
> > depends on ACPI_WMI
> > depends on HWMON
> > select ACPI_PLATFORM_PROFILE
> > + select FW_ATTR_CLASS
> > help
> > Say Y here if you want to have support for WMI-based platform features
> > like fan sensor access on MSI machines.
> > diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> > index c0b577c95c079..6498f4b44fe53 100644
> > --- a/drivers/platform/x86/msi-wmi-platform.c
> > +++ b/drivers/platform/x86/msi-wmi-platform.c
> > @@ -34,6 +34,8 @@
> >
> > #include <linux/unaligned.h>
> >
> > +#include "firmware_attributes_class.h"
> > +
> > #define DRIVER_NAME "msi-wmi-platform"
> >
> > #define MSI_PLATFORM_GUID "ABBC0F6E-8EA1-11d1-00A0-C90629100000"
> > @@ -74,6 +76,10 @@
> > #define MSI_PLATFORM_SHIFT_ECO (MSI_PLATFORM_SHIFT_ENABLE + 2)
> > #define MSI_PLATFORM_SHIFT_USER (MSI_PLATFORM_SHIFT_ENABLE + 3)
> >
> > +/* Get_Data() and Set_Data() Params */
> > +#define MSI_PLATFORM_PL1_ADDR 0x50
> > +#define MSI_PLATFORM_PL2_ADDR 0x51
> > +
> > static bool force;
> > module_param_unsafe(force, bool, 0);
> > MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
> > @@ -112,6 +118,9 @@ enum msi_wmi_platform_method {
> >
> > struct msi_wmi_platform_quirk {
> > bool shift_mode; /* Shift mode is supported */
> > + int pl_min; /* Minimum PLx value */
> > + int pl1_max; /* Maximum PL1 value */
> > + int pl2_max; /* Maximum PL2 value */
> > };
> >
> > struct msi_wmi_platform_data {
> > @@ -119,6 +128,44 @@ struct msi_wmi_platform_data {
> > struct msi_wmi_platform_quirk *quirks;
> > struct mutex wmi_lock; /* Necessary when calling WMI methods */
> > struct device *ppdev;
> > + struct device *fw_attrs_dev;
> > + struct kset *fw_attrs_kset;
> > +};
> > +
> > +enum msi_fw_attr_id {
> > + MSI_ATTR_PPT_PL1_SPL,
> > + MSI_ATTR_PPT_PL2_SPPT,
> > +};
> > +
> > +static const char *const msi_fw_attr_name[] = {
> > + [MSI_ATTR_PPT_PL1_SPL] = "ppt_pl1_spl",
> > + [MSI_ATTR_PPT_PL2_SPPT] = "ppt_pl2_sppt",
> > +};
> > +
> > +static const char *const msi_fw_attr_desc[] = {
> > + [MSI_ATTR_PPT_PL1_SPL] = "CPU Steady package limit (PL1/SPL)",
> > + [MSI_ATTR_PPT_PL2_SPPT] = "CPU Boost slow package limit (PL2/SPPT)",
> > +};
> > +
> > +#define MSI_ATTR_LANGUAGE_CODE "en_US.UTF-8"
> > +
> > +struct msi_fw_attr {
> > + struct msi_wmi_platform_data *data;
> > + enum msi_fw_attr_id fw_attr_id;
> > + struct attribute_group attr_group;
> > + struct kobj_attribute display_name;
> > + struct kobj_attribute current_value;
> > + struct kobj_attribute min_value;
> > + struct kobj_attribute max_value;
> > +
> > + u32 min;
> > + u32 max;
> > +
> > + int (*get_value)(struct msi_wmi_platform_data *data,
> > + struct msi_fw_attr *fw_attr, char *buf);
> > + ssize_t (*set_value)(struct msi_wmi_platform_data *data,
> > + struct msi_fw_attr *fw_attr, const char *buf,
> > + size_t count);
> > };
> >
> > struct msi_wmi_platform_debugfs_data {
> > @@ -163,10 +210,16 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
> >
> > static struct msi_wmi_platform_quirk quirk_default = {};
> > static struct msi_wmi_platform_quirk quirk_gen1 = {
> > - .shift_mode = true
> > + .shift_mode = true,
> > + .pl_min = 8,
> > + .pl1_max = 43,
> > + .pl2_max = 45
> > };
> > static struct msi_wmi_platform_quirk quirk_gen2 = {
> > - .shift_mode = true
> > + .shift_mode = true,
> > + .pl_min = 8,
> > + .pl1_max = 30,
> > + .pl2_max = 37
> > };
> >
> > static const struct dmi_system_id msi_quirks[] = {
> > @@ -660,6 +713,306 @@ static const struct platform_profile_ops msi_wmi_platform_profile_ops = {
> > .profile_set = msi_wmi_platform_profile_set,
> > };
> >
> > +/* Firmware Attributes setup */
> > +static int data_get_addr(struct msi_wmi_platform_data *data,
> > + const enum msi_fw_attr_id id)
> > +{
> > + switch (id) {
> > + case MSI_ATTR_PPT_PL1_SPL:
> > + return MSI_PLATFORM_PL1_ADDR;
> > + case MSI_ATTR_PPT_PL2_SPPT:
> > + return MSI_PLATFORM_PL2_ADDR;
> > + default:
> > + pr_warn("Invalid attribute id %d\n", id);
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static ssize_t data_set_value(struct msi_wmi_platform_data *data,
> > + struct msi_fw_attr *fw_attr, const char *buf,
> > + size_t count)
> > +{
> > + u8 buffer[32] = { 0 };
> > + int ret, fwid;
> > + u32 value;
> > +
> > + fwid = data_get_addr(data, fw_attr->fw_attr_id);
> > + if (fwid < 0)
> > + return fwid;
> > +
> > + ret = kstrtou32(buf, 10, &value);
> > + if (ret)
> > + return ret;
> > +
> > + if (fw_attr->min >= 0 && value < fw_attr->min)
> > + return -EINVAL;
> > + if (fw_attr->max >= 0 && value > fw_attr->max)
> > + return -EINVAL;
>
> Maybe clamp instead of failing?
>
> > +
> > + buffer[0] = fwid;
> > + put_unaligned_le32(value, &buffer[1]);
> > +
> > + ret = msi_wmi_platform_query(data, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer));
> > + if (ret) {
> > + pr_warn("Failed to set_data with id %d: %d\n",
> > + fw_attr->fw_attr_id, ret);
> > + return ret;
> > + }
> > +
> > + return count;
> > +}
> > +
> > +static int data_get_value(struct msi_wmi_platform_data *data,
> > + struct msi_fw_attr *fw_attr, char *buf)
> > +{
> > + u8 buffer[32] = { 0 };
> > + u32 value;
> > + int ret, addr;
> > +
> > + addr = data_get_addr(data, fw_attr->fw_attr_id);
> > + if (addr < 0)
> > + return addr;
> > +
> > + buffer[0] = addr;
> > +
> > + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer));
> > + if (ret) {
> > + pr_warn("Failed to show set_data for id %d: %d\n",
> > + fw_attr->fw_attr_id, ret);
> > + return ret;
> > + }
> > +
> > + value = get_unaligned_le32(&buffer[1]);
> > +
> > + return sysfs_emit(buf, "%d\n", value);
> > +}
> > +
> > +static ssize_t display_name_language_code_show(struct kobject *kobj, struct kobj_attribute *attr,
> > + char *buf)
> > +{
> > + return sysfs_emit(buf, "%s\n", MSI_ATTR_LANGUAGE_CODE);
> > +}
> > +
> > +static struct kobj_attribute fw_attr_display_name_language_code =
> > + __ATTR_RO(display_name_language_code);
> > +
> > +static ssize_t scalar_increment_show(struct kobject *kobj,
> > + struct kobj_attribute *attr,
> > + char *buf)
> > +{
> > + return sysfs_emit(buf, "1\n");
> > +}
> > +
> > +static struct kobj_attribute fw_attr_scalar_increment =
> > + __ATTR_RO(scalar_increment);
> > +
> > +static ssize_t pending_reboot_show(struct kobject *kobj,
> > + struct kobj_attribute *attr, char *buf)
> > +{
> > + return sysfs_emit(buf, "0\n");
> > +}
> > +
> > +static struct kobj_attribute fw_attr_pending_reboot = __ATTR_RO(pending_reboot);
> > +
> > +static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> > +{
> > + struct msi_fw_attr *fw_attr =
> > + container_of(attr, struct msi_fw_attr, display_name);
> > +
> > + return sysfs_emit(buf, "%s\n", msi_fw_attr_desc[fw_attr->fw_attr_id]);
> > +}
> > +
> > +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> > +{
> > + struct msi_fw_attr *fw_attr =
> > + container_of(attr, struct msi_fw_attr, current_value);
> > +
> > + return fw_attr->get_value(fw_attr->data, fw_attr, buf);
> > +}
> > +
> > +static ssize_t current_value_store(struct kobject *kobj, struct kobj_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + struct msi_fw_attr *fw_attr =
> > + container_of(attr, struct msi_fw_attr, current_value);
> > +
> > + return fw_attr->set_value(fw_attr->data, fw_attr, buf, count);
> > +}
> > +
> > +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
> > + char *buf)
> > +{
> > + return sysfs_emit(buf, "integer\n");
> > +}
> > +
> > +static struct kobj_attribute fw_attr_type_int = {
> > + .attr = { .name = "type", .mode = 0444 },
> > + .show = type_show,
> > +};
>
> Use __ATTR_RO().
>
> > +
> > +static ssize_t min_value_show(struct kobject *kobj, struct kobj_attribute *attr,
> > + char *buf)
> > +{
> > + struct msi_fw_attr *fw_attr =
> > + container_of(attr, struct msi_fw_attr, min_value);
> > +
> > + return sysfs_emit(buf, "%d\n", fw_attr->min);
> > +}
> > +
> > +static ssize_t max_value_show(struct kobject *kobj, struct kobj_attribute *attr,
> > + char *buf)
> > +{
> > + struct msi_fw_attr *fw_attr =
> > + container_of(attr, struct msi_fw_attr, max_value);
> > +
> > + return sysfs_emit(buf, "%d\n", fw_attr->max);
> > +}
> > +
> > +#define FW_ATTR_ENUM_MAX_ATTRS 7
> > +
> > +static int
> > +msi_fw_attr_init(struct msi_wmi_platform_data *data,
> > + const enum msi_fw_attr_id fw_attr_id,
> > + struct kobj_attribute *fw_attr_type, const s32 min,
> > + const s32 max,
> > + int (*get_value)(struct msi_wmi_platform_data *data,
> > + struct msi_fw_attr *fw_attr, char *buf),
> > + ssize_t (*set_value)(struct msi_wmi_platform_data *data,
> > + struct msi_fw_attr *fw_attr,
> > + const char *buf, size_t count))
> > +{
> > + struct msi_fw_attr *fw_attr;
> > + struct attribute **attrs;
> > + int idx = 0;
> > +
> > + fw_attr = devm_kzalloc(&data->wdev->dev, sizeof(*fw_attr), GFP_KERNEL);
> > + if (!fw_attr)
> > + return -ENOMEM;
> > +
> > + attrs = devm_kcalloc(&data->wdev->dev, FW_ATTR_ENUM_MAX_ATTRS + 1,
> > + sizeof(*attrs), GFP_KERNEL);
> > + if (!attrs)
> > + return -ENOMEM;
> > +
> > + fw_attr->data = data;
> > + fw_attr->fw_attr_id = fw_attr_id;
> > + fw_attr->attr_group.name = msi_fw_attr_name[fw_attr_id];
> > + fw_attr->attr_group.attrs = attrs;
> > + fw_attr->get_value = get_value;
> > + fw_attr->set_value = set_value;
> > +
> > + attrs[idx++] = &fw_attr_type->attr;
> > + if (fw_attr_type == &fw_attr_type_int)
> > + attrs[idx++] = &fw_attr_scalar_increment.attr;
> > + attrs[idx++] = &fw_attr_display_name_language_code.attr;
> > +
> > + sysfs_attr_init(&fw_attr->display_name.attr);
> > + fw_attr->display_name.attr.name = "display_name";
> > + fw_attr->display_name.attr.mode = 0444;
> > + fw_attr->display_name.show = display_name_show;
> > + attrs[idx++] = &fw_attr->display_name.attr;
> > +
> > + sysfs_attr_init(&fw_attr->current_value.attr);
> > + fw_attr->current_value.attr.name = "current_value";
> > + fw_attr->current_value.attr.mode = 0644;
> > + fw_attr->current_value.show = current_value_show;
> > + fw_attr->current_value.store = current_value_store;
> > + attrs[idx++] = &fw_attr->current_value.attr;
> > +
> > + if (min >= 0) {
> > + fw_attr->min = min;
> > + sysfs_attr_init(&fw_attr->min_value.attr);
> > + fw_attr->min_value.attr.name = "min_value";
> > + fw_attr->min_value.attr.mode = 0444;
> > + fw_attr->min_value.show = min_value_show;
> > + attrs[idx++] = &fw_attr->min_value.attr;
> > + } else {
> > + fw_attr->min = -1;
> > + }
> > +
> > + if (max >= 0) {
> > + fw_attr->max = max;
> > + sysfs_attr_init(&fw_attr->max_value.attr);
> > + fw_attr->max_value.attr.name = "max_value";
> > + fw_attr->max_value.attr.mode = 0444;
> > + fw_attr->max_value.show = max_value_show;
> > + attrs[idx++] = &fw_attr->max_value.attr;
> > + } else {
> > + fw_attr->max = -1;
> > + }
> > +
> > + attrs[idx] = NULL;
>
> kcalloc already sets this to 0.
>
> > + return sysfs_create_group(&data->fw_attrs_kset->kobj, &fw_attr->attr_group);
>
> This group is never removed.
>
> I think it's not that big of a deal? But you should probably do it
> anyway.
>
> > +}
> > +
> > +static void msi_kset_unregister(void *data)
> > +{
> > + struct kset *kset = data;
> > +
> > + sysfs_remove_file(&kset->kobj, &fw_attr_pending_reboot.attr);
> > + kset_unregister(kset);
> > +}
> > +
> > +static void msi_fw_attrs_dev_unregister(void *data)
> > +{
> > + struct device *fw_attrs_dev = data;
> > +
> > + device_unregister(fw_attrs_dev);
> > +}
> > +
> > +static int msi_wmi_fw_attrs_init(struct msi_wmi_platform_data *data)
> > +{
> > + int err;
> > +
> > + data->fw_attrs_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),
> > + NULL, "%s", DRIVER_NAME);
>
> Set the wmi device as the parent.
>
> > + if (IS_ERR(data->fw_attrs_dev))
> > + return PTR_ERR(data->fw_attrs_dev);
> > +
> > + err = devm_add_action_or_reset(&data->wdev->dev,
> > + msi_fw_attrs_dev_unregister,
> > + data->fw_attrs_dev);
> > + if (err)
> > + return err;
> > +
> > + data->fw_attrs_kset = kset_create_and_add("attributes", NULL,
> > + &data->fw_attrs_dev->kobj);
> > + if (!data->fw_attrs_kset)
> > + return -ENOMEM;
> > +
> > + err = sysfs_create_file(&data->fw_attrs_kset->kobj,
> > + &fw_attr_pending_reboot.attr);
>
> If it's always 0 why include it? It's not mandatory anyway.
fwupd complained. I am fine either way but without this there is no
userspace tooling to test the interface with. Ack for the rest.
Antheas
> --
> ~ Kurt
>
> > + if (err) {
> > + kset_unregister(data->fw_attrs_kset);
> > + return err;
> > + }
> > +
> > + err = devm_add_action_or_reset(&data->wdev->dev, msi_kset_unregister,
> > + data->fw_attrs_kset);
> > + if (err)
> > + return err;
> > +
> > + if (data->quirks->pl1_max) {
> > + err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL1_SPL,
> > + &fw_attr_type_int, data->quirks->pl_min,
> > + data->quirks->pl1_max, &data_get_value,
> > + &data_set_value);
> > + if (err)
> > + return err;
> > + }
> > +
> > + if (data->quirks->pl2_max) {
> > + err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL2_SPPT,
> > + &fw_attr_type_int, data->quirks->pl_min,
> > + data->quirks->pl2_max, &data_get_value,
> > + &data_set_value);
> > + if (err)
> > + return err;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
> > size_t length, loff_t *offset)
> > {
> > @@ -888,6 +1241,10 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> > if (ret < 0)
> > return ret;
> >
> > + ret = msi_wmi_fw_attrs_init(data);
> > + if (ret < 0)
> > + return ret;
> > +
> > msi_wmi_platform_debugfs_init(data);
> >
> > msi_wmi_platform_profile_setup(data);
>
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 06/10] platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware attributes
2025-05-11 20:44 ` [PATCH v1 06/10] platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware attributes Antheas Kapenekakis
2025-05-11 23:34 ` Kurt Borja
@ 2025-05-19 2:08 ` Armin Wolf
1 sibling, 0 replies; 45+ messages in thread
From: Armin Wolf @ 2025-05-19 2:08 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Jonathan Corbet, Hans de Goede, Ilpo Järvinen, Jean Delvare,
Guenter Roeck, Kurt Borja, linux-doc, linux-kernel, linux-hwmon
Am 11.05.25 um 22:44 schrieb Antheas Kapenekakis:
> Adds PL1, and PL2 support through the firmware attributes interface.
> The min and max values are quirked, and the attributes are only defined
> if they are set to a non-zero value. These values are meant to be set
> in conjunction with shift mode, where shift mode automatically sets
> an upper bound on PL1/PL2 (e.g., low-power would be used with 8W).
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/Kconfig | 1 +
> drivers/platform/x86/msi-wmi-platform.c | 361 +++++++++++++++++++++++-
> 2 files changed, 360 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 57a48910c8fd4..fd3da718731e7 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -747,6 +747,7 @@ config MSI_WMI_PLATFORM
> depends on ACPI_WMI
> depends on HWMON
> select ACPI_PLATFORM_PROFILE
> + select FW_ATTR_CLASS
> help
> Say Y here if you want to have support for WMI-based platform features
> like fan sensor access on MSI machines.
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index c0b577c95c079..6498f4b44fe53 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -34,6 +34,8 @@
>
> #include <linux/unaligned.h>
>
> +#include "firmware_attributes_class.h"
> +
> #define DRIVER_NAME "msi-wmi-platform"
>
> #define MSI_PLATFORM_GUID "ABBC0F6E-8EA1-11d1-00A0-C90629100000"
> @@ -74,6 +76,10 @@
> #define MSI_PLATFORM_SHIFT_ECO (MSI_PLATFORM_SHIFT_ENABLE + 2)
> #define MSI_PLATFORM_SHIFT_USER (MSI_PLATFORM_SHIFT_ENABLE + 3)
>
> +/* Get_Data() and Set_Data() Params */
> +#define MSI_PLATFORM_PL1_ADDR 0x50
> +#define MSI_PLATFORM_PL2_ADDR 0x51
> +
> static bool force;
> module_param_unsafe(force, bool, 0);
> MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
> @@ -112,6 +118,9 @@ enum msi_wmi_platform_method {
>
> struct msi_wmi_platform_quirk {
> bool shift_mode; /* Shift mode is supported */
> + int pl_min; /* Minimum PLx value */
> + int pl1_max; /* Maximum PL1 value */
> + int pl2_max; /* Maximum PL2 value */
> };
>
> struct msi_wmi_platform_data {
> @@ -119,6 +128,44 @@ struct msi_wmi_platform_data {
> struct msi_wmi_platform_quirk *quirks;
> struct mutex wmi_lock; /* Necessary when calling WMI methods */
> struct device *ppdev;
> + struct device *fw_attrs_dev;
> + struct kset *fw_attrs_kset;
> +};
> +
> +enum msi_fw_attr_id {
> + MSI_ATTR_PPT_PL1_SPL,
> + MSI_ATTR_PPT_PL2_SPPT,
> +};
> +
> +static const char *const msi_fw_attr_name[] = {
> + [MSI_ATTR_PPT_PL1_SPL] = "ppt_pl1_spl",
> + [MSI_ATTR_PPT_PL2_SPPT] = "ppt_pl2_sppt",
> +};
> +
> +static const char *const msi_fw_attr_desc[] = {
> + [MSI_ATTR_PPT_PL1_SPL] = "CPU Steady package limit (PL1/SPL)",
> + [MSI_ATTR_PPT_PL2_SPPT] = "CPU Boost slow package limit (PL2/SPPT)",
> +};
> +
> +#define MSI_ATTR_LANGUAGE_CODE "en_US.UTF-8"
> +
> +struct msi_fw_attr {
> + struct msi_wmi_platform_data *data;
> + enum msi_fw_attr_id fw_attr_id;
> + struct attribute_group attr_group;
> + struct kobj_attribute display_name;
> + struct kobj_attribute current_value;
> + struct kobj_attribute min_value;
> + struct kobj_attribute max_value;
> +
> + u32 min;
> + u32 max;
> +
> + int (*get_value)(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, char *buf);
> + ssize_t (*set_value)(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, const char *buf,
> + size_t count);
> };
>
> struct msi_wmi_platform_debugfs_data {
> @@ -163,10 +210,16 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
>
> static struct msi_wmi_platform_quirk quirk_default = {};
> static struct msi_wmi_platform_quirk quirk_gen1 = {
> - .shift_mode = true
> + .shift_mode = true,
> + .pl_min = 8,
> + .pl1_max = 43,
> + .pl2_max = 45
> };
> static struct msi_wmi_platform_quirk quirk_gen2 = {
> - .shift_mode = true
> + .shift_mode = true,
> + .pl_min = 8,
> + .pl1_max = 30,
> + .pl2_max = 37
> };
>
> static const struct dmi_system_id msi_quirks[] = {
> @@ -660,6 +713,306 @@ static const struct platform_profile_ops msi_wmi_platform_profile_ops = {
> .profile_set = msi_wmi_platform_profile_set,
> };
>
> +/* Firmware Attributes setup */
> +static int data_get_addr(struct msi_wmi_platform_data *data,
> + const enum msi_fw_attr_id id)
> +{
> + switch (id) {
> + case MSI_ATTR_PPT_PL1_SPL:
> + return MSI_PLATFORM_PL1_ADDR;
> + case MSI_ATTR_PPT_PL2_SPPT:
> + return MSI_PLATFORM_PL2_ADDR;
> + default:
> + pr_warn("Invalid attribute id %d\n", id);
> + return -EINVAL;
> + }
> +}
> +
> +static ssize_t data_set_value(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, const char *buf,
> + size_t count)
> +{
> + u8 buffer[32] = { 0 };
> + int ret, fwid;
> + u32 value;
> +
> + fwid = data_get_addr(data, fw_attr->fw_attr_id);
> + if (fwid < 0)
> + return fwid;
> +
> + ret = kstrtou32(buf, 10, &value);
> + if (ret)
> + return ret;
> +
> + if (fw_attr->min >= 0 && value < fw_attr->min)
> + return -EINVAL;
> + if (fw_attr->max >= 0 && value > fw_attr->max)
> + return -EINVAL;
Would it be possible to remove the check for min and max being 0 and instead only
register the associated attributes if this condition is met during probe?
> +
> + buffer[0] = fwid;
> + put_unaligned_le32(value, &buffer[1]);
> +
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer));
> + if (ret) {
> + pr_warn("Failed to set_data with id %d: %d\n",
> + fw_attr->fw_attr_id, ret);
Please remove this error message.
> + return ret;
> + }
> +
> + return count;
> +}
> +
> +static int data_get_value(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, char *buf)
> +{
> + u8 buffer[32] = { 0 };
> + u32 value;
> + int ret, addr;
> +
> + addr = data_get_addr(data, fw_attr->fw_attr_id);
> + if (addr < 0)
> + return addr;
> +
> + buffer[0] = addr;
> +
> + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer));
> + if (ret) {
> + pr_warn("Failed to show set_data for id %d: %d\n",
> + fw_attr->fw_attr_id, ret);
Same as above.
> + return ret;
> + }
> +
> + value = get_unaligned_le32(&buffer[1]);
> +
> + return sysfs_emit(buf, "%d\n", value);
> +}
> +
> +static ssize_t display_name_language_code_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "%s\n", MSI_ATTR_LANGUAGE_CODE);
> +}
> +
> +static struct kobj_attribute fw_attr_display_name_language_code =
> + __ATTR_RO(display_name_language_code);
> +
> +static ssize_t scalar_increment_show(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "1\n");
> +}
> +
> +static struct kobj_attribute fw_attr_scalar_increment =
> + __ATTR_RO(scalar_increment);
> +
> +static ssize_t pending_reboot_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + return sysfs_emit(buf, "0\n");
> +}
> +
> +static struct kobj_attribute fw_attr_pending_reboot = __ATTR_RO(pending_reboot);
> +
> +static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, display_name);
> +
> + return sysfs_emit(buf, "%s\n", msi_fw_attr_desc[fw_attr->fw_attr_id]);
> +}
> +
> +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, current_value);
> +
> + return fw_attr->get_value(fw_attr->data, fw_attr, buf);
> +}
> +
> +static ssize_t current_value_store(struct kobject *kobj, struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, current_value);
> +
> + return fw_attr->set_value(fw_attr->data, fw_attr, buf, count);
> +}
> +
> +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "integer\n");
> +}
> +
> +static struct kobj_attribute fw_attr_type_int = {
> + .attr = { .name = "type", .mode = 0444 },
> + .show = type_show,
> +};
> +
> +static ssize_t min_value_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, min_value);
> +
> + return sysfs_emit(buf, "%d\n", fw_attr->min);
> +}
> +
> +static ssize_t max_value_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + struct msi_fw_attr *fw_attr =
> + container_of(attr, struct msi_fw_attr, max_value);
> +
> + return sysfs_emit(buf, "%d\n", fw_attr->max);
> +}
> +
> +#define FW_ATTR_ENUM_MAX_ATTRS 7
> +
> +static int
> +msi_fw_attr_init(struct msi_wmi_platform_data *data,
> + const enum msi_fw_attr_id fw_attr_id,
> + struct kobj_attribute *fw_attr_type, const s32 min,
> + const s32 max,
> + int (*get_value)(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr, char *buf),
> + ssize_t (*set_value)(struct msi_wmi_platform_data *data,
> + struct msi_fw_attr *fw_attr,
> + const char *buf, size_t count))
> +{
> + struct msi_fw_attr *fw_attr;
> + struct attribute **attrs;
> + int idx = 0;
> +
> + fw_attr = devm_kzalloc(&data->wdev->dev, sizeof(*fw_attr), GFP_KERNEL);
> + if (!fw_attr)
> + return -ENOMEM;
> +
> + attrs = devm_kcalloc(&data->wdev->dev, FW_ATTR_ENUM_MAX_ATTRS + 1,
> + sizeof(*attrs), GFP_KERNEL);
> + if (!attrs)
> + return -ENOMEM;
> +
> + fw_attr->data = data;
> + fw_attr->fw_attr_id = fw_attr_id;
> + fw_attr->attr_group.name = msi_fw_attr_name[fw_attr_id];
> + fw_attr->attr_group.attrs = attrs;
> + fw_attr->get_value = get_value;
> + fw_attr->set_value = set_value;
> +
> + attrs[idx++] = &fw_attr_type->attr;
> + if (fw_attr_type == &fw_attr_type_int)
> + attrs[idx++] = &fw_attr_scalar_increment.attr;
> + attrs[idx++] = &fw_attr_display_name_language_code.attr;
> +
> + sysfs_attr_init(&fw_attr->display_name.attr);
> + fw_attr->display_name.attr.name = "display_name";
> + fw_attr->display_name.attr.mode = 0444;
> + fw_attr->display_name.show = display_name_show;
> + attrs[idx++] = &fw_attr->display_name.attr;
> +
> + sysfs_attr_init(&fw_attr->current_value.attr);
> + fw_attr->current_value.attr.name = "current_value";
> + fw_attr->current_value.attr.mode = 0644;
> + fw_attr->current_value.show = current_value_show;
> + fw_attr->current_value.store = current_value_store;
> + attrs[idx++] = &fw_attr->current_value.attr;
> +
> + if (min >= 0) {
> + fw_attr->min = min;
> + sysfs_attr_init(&fw_attr->min_value.attr);
> + fw_attr->min_value.attr.name = "min_value";
> + fw_attr->min_value.attr.mode = 0444;
> + fw_attr->min_value.show = min_value_show;
> + attrs[idx++] = &fw_attr->min_value.attr;
> + } else {
> + fw_attr->min = -1;
> + }
> +
> + if (max >= 0) {
> + fw_attr->max = max;
> + sysfs_attr_init(&fw_attr->max_value.attr);
> + fw_attr->max_value.attr.name = "max_value";
> + fw_attr->max_value.attr.mode = 0444;
> + fw_attr->max_value.show = max_value_show;
> + attrs[idx++] = &fw_attr->max_value.attr;
> + } else {
> + fw_attr->max = -1;
> + }
Would it be possible to require but min and max limits to be present when
registering the firmware attributes?
> +
> + attrs[idx] = NULL;
> + return sysfs_create_group(&data->fw_attrs_kset->kobj, &fw_attr->attr_group);
> +}
> +
> +static void msi_kset_unregister(void *data)
> +{
> + struct kset *kset = data;
> +
> + sysfs_remove_file(&kset->kobj, &fw_attr_pending_reboot.attr);
> + kset_unregister(kset);
> +}
> +
> +static void msi_fw_attrs_dev_unregister(void *data)
> +{
> + struct device *fw_attrs_dev = data;
> +
> + device_unregister(fw_attrs_dev);
> +}
> +
> +static int msi_wmi_fw_attrs_init(struct msi_wmi_platform_data *data)
> +{
> + int err;
> +
> + data->fw_attrs_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),
> + NULL, "%s", DRIVER_NAME);
Please make sure that the device name has an id in it to avoid naming collision should this driver
probe more than once.
Thanks,
Armin Wolf
> + if (IS_ERR(data->fw_attrs_dev))
> + return PTR_ERR(data->fw_attrs_dev);
> +
> + err = devm_add_action_or_reset(&data->wdev->dev,
> + msi_fw_attrs_dev_unregister,
> + data->fw_attrs_dev);
> + if (err)
> + return err;
> +
> + data->fw_attrs_kset = kset_create_and_add("attributes", NULL,
> + &data->fw_attrs_dev->kobj);
> + if (!data->fw_attrs_kset)
> + return -ENOMEM;
> +
> + err = sysfs_create_file(&data->fw_attrs_kset->kobj,
> + &fw_attr_pending_reboot.attr);
> + if (err) {
> + kset_unregister(data->fw_attrs_kset);
> + return err;
> + }
> +
> + err = devm_add_action_or_reset(&data->wdev->dev, msi_kset_unregister,
> + data->fw_attrs_kset);
> + if (err)
> + return err;
> +
> + if (data->quirks->pl1_max) {
> + err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL1_SPL,
> + &fw_attr_type_int, data->quirks->pl_min,
> + data->quirks->pl1_max, &data_get_value,
> + &data_set_value);
> + if (err)
> + return err;
> + }
> +
> + if (data->quirks->pl2_max) {
> + err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL2_SPPT,
> + &fw_attr_type_int, data->quirks->pl_min,
> + data->quirks->pl2_max, &data_get_value,
> + &data_set_value);
> + if (err)
> + return err;
> + }
> +
> + return 0;
> +}
> +
> static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
> size_t length, loff_t *offset)
> {
> @@ -888,6 +1241,10 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> if (ret < 0)
> return ret;
>
> + ret = msi_wmi_fw_attrs_init(data);
> + if (ret < 0)
> + return ret;
> +
> msi_wmi_platform_debugfs_init(data);
>
> msi_wmi_platform_profile_setup(data);
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v1 07/10] platform/x86: msi-wmi-platform: Add charge_threshold support
2025-05-11 20:44 [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Antheas Kapenekakis
` (5 preceding siblings ...)
2025-05-11 20:44 ` [PATCH v1 06/10] platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware attributes Antheas Kapenekakis
@ 2025-05-11 20:44 ` Antheas Kapenekakis
2025-05-11 23:34 ` Kurt Borja
2025-05-19 2:32 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 08/10] platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices Antheas Kapenekakis
` (4 subsequent siblings)
11 siblings, 2 replies; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-11 20:44 UTC (permalink / raw)
To: platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, Kurt Borja, linux-doc, linux-kernel,
linux-hwmon, Antheas Kapenekakis
The battery of MSI laptops supports charge threshold. Add support for it.
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
drivers/platform/x86/Kconfig | 1 +
drivers/platform/x86/msi-wmi-platform.c | 110 ++++++++++++++++++++++++
2 files changed, 111 insertions(+)
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index fd3da718731e7..51a34ab476ffc 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -744,6 +744,7 @@ config MSI_WMI
config MSI_WMI_PLATFORM
tristate "MSI WMI Platform features"
+ depends on ACPI_BATTERY
depends on ACPI_WMI
depends on HWMON
select ACPI_PLATFORM_PROFILE
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
index 6498f4b44fe53..46928fb4da8a6 100644
--- a/drivers/platform/x86/msi-wmi-platform.c
+++ b/drivers/platform/x86/msi-wmi-platform.c
@@ -31,6 +31,7 @@
#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/wmi.h>
+#include <acpi/battery.h>
#include <linux/unaligned.h>
@@ -79,6 +80,7 @@
/* Get_Data() and Set_Data() Params */
#define MSI_PLATFORM_PL1_ADDR 0x50
#define MSI_PLATFORM_PL2_ADDR 0x51
+#define MSI_PLATFORM_BAT_ADDR 0xd7
static bool force;
module_param_unsafe(force, bool, 0);
@@ -118,6 +120,7 @@ enum msi_wmi_platform_method {
struct msi_wmi_platform_quirk {
bool shift_mode; /* Shift mode is supported */
+ bool charge_threshold; /* Charge threshold is supported */
int pl_min; /* Minimum PLx value */
int pl1_max; /* Maximum PL1 value */
int pl2_max; /* Maximum PL2 value */
@@ -128,6 +131,7 @@ struct msi_wmi_platform_data {
struct msi_wmi_platform_quirk *quirks;
struct mutex wmi_lock; /* Necessary when calling WMI methods */
struct device *ppdev;
+ struct acpi_battery_hook battery_hook;
struct device *fw_attrs_dev;
struct kset *fw_attrs_kset;
};
@@ -211,12 +215,14 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
static struct msi_wmi_platform_quirk quirk_default = {};
static struct msi_wmi_platform_quirk quirk_gen1 = {
.shift_mode = true,
+ .charge_threshold = true,
.pl_min = 8,
.pl1_max = 43,
.pl2_max = 45
};
static struct msi_wmi_platform_quirk quirk_gen2 = {
.shift_mode = true,
+ .charge_threshold = true,
.pl_min = 8,
.pl1_max = 30,
.pl2_max = 37
@@ -1013,6 +1019,94 @@ static int msi_wmi_fw_attrs_init(struct msi_wmi_platform_data *data)
return 0;
}
+static int msi_platform_psy_ext_get_prop(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct msi_wmi_platform_data *msi = data;
+ u8 buffer[32] = { 0 };
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ buffer[0] = MSI_PLATFORM_BAT_ADDR;
+ ret = msi_wmi_platform_query(msi, MSI_PLATFORM_GET_DATA,
+ buffer, sizeof(buffer));
+ if (ret)
+ return ret;
+
+ val->intval = buffer[1] & ~BIT(7);
+ if (val->intval > 100)
+ return -EINVAL;
+
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int msi_platform_psy_ext_set_prop(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct msi_wmi_platform_data *msi = data;
+ u8 buffer[32] = { 0 };
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ if (val->intval > 100)
+ return -EINVAL;
+ buffer[0] = MSI_PLATFORM_BAT_ADDR;
+ buffer[1] = val->intval | BIT(7);
+ return msi_wmi_platform_query(msi, MSI_PLATFORM_SET_DATA,
+ buffer, sizeof(buffer));
+ default:
+ return -EINVAL;
+ }
+}
+
+static int
+msi_platform_psy_prop_is_writeable(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data, enum power_supply_property psp)
+{
+ return true;
+}
+
+static const enum power_supply_property oxp_psy_ext_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const struct power_supply_ext msi_platform_psy_ext = {
+ .name = "msi-platform-charge-control",
+ .properties = oxp_psy_ext_props,
+ .num_properties = ARRAY_SIZE(oxp_psy_ext_props),
+ .get_property = msi_platform_psy_ext_get_prop,
+ .set_property = msi_platform_psy_ext_set_prop,
+ .property_is_writeable = msi_platform_psy_prop_is_writeable,
+};
+
+static int msi_wmi_platform_battery_add(struct power_supply *battery,
+ struct acpi_battery_hook *hook)
+{
+ struct msi_wmi_platform_data *data =
+ container_of(hook, struct msi_wmi_platform_data, battery_hook);
+
+ return power_supply_register_extension(battery, &msi_platform_psy_ext,
+ &data->wdev->dev, data);
+}
+
+static int msi_wmi_platform_battery_remove(struct power_supply *battery,
+ struct acpi_battery_hook *hook)
+{
+ power_supply_unregister_extension(battery, &msi_platform_psy_ext);
+ return 0;
+}
+
static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
size_t length, loff_t *offset)
{
@@ -1245,6 +1339,13 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
if (ret < 0)
return ret;
+ if (data->quirks->charge_threshold) {
+ data->battery_hook.name = "MSI Battery";
+ data->battery_hook.add_battery = msi_wmi_platform_battery_add;
+ data->battery_hook.remove_battery = msi_wmi_platform_battery_remove;
+ battery_hook_register(&data->battery_hook);
+ }
+
msi_wmi_platform_debugfs_init(data);
msi_wmi_platform_profile_setup(data);
@@ -1252,6 +1353,14 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
return msi_wmi_platform_hwmon_init(data);
}
+static void msi_wmi_platform_remove(struct wmi_device *wdev)
+{
+ struct msi_wmi_platform_data *data = dev_get_drvdata(&wdev->dev);
+
+ if (data->quirks->charge_threshold)
+ battery_hook_unregister(&data->battery_hook);
+}
+
static const struct wmi_device_id msi_wmi_platform_id_table[] = {
{ MSI_PLATFORM_GUID, NULL },
{ }
@@ -1265,6 +1374,7 @@ static struct wmi_driver msi_wmi_platform_driver = {
},
.id_table = msi_wmi_platform_id_table,
.probe = msi_wmi_platform_probe,
+ .remove = msi_wmi_platform_remove,
.no_singleton = true,
};
module_wmi_driver(msi_wmi_platform_driver);
--
2.49.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v1 07/10] platform/x86: msi-wmi-platform: Add charge_threshold support
2025-05-11 20:44 ` [PATCH v1 07/10] platform/x86: msi-wmi-platform: Add charge_threshold support Antheas Kapenekakis
@ 2025-05-11 23:34 ` Kurt Borja
2025-05-19 2:32 ` Armin Wolf
1 sibling, 0 replies; 45+ messages in thread
From: Kurt Borja @ 2025-05-11 23:34 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, linux-doc, linux-kernel, linux-hwmon
[-- Attachment #1: Type: text/plain, Size: 7085 bytes --]
On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> The battery of MSI laptops supports charge threshold. Add support for it.
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/Kconfig | 1 +
> drivers/platform/x86/msi-wmi-platform.c | 110 ++++++++++++++++++++++++
> 2 files changed, 111 insertions(+)
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index fd3da718731e7..51a34ab476ffc 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -744,6 +744,7 @@ config MSI_WMI
>
> config MSI_WMI_PLATFORM
> tristate "MSI WMI Platform features"
> + depends on ACPI_BATTERY
> depends on ACPI_WMI
> depends on HWMON
> select ACPI_PLATFORM_PROFILE
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index 6498f4b44fe53..46928fb4da8a6 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -31,6 +31,7 @@
> #include <linux/sysfs.h>
> #include <linux/types.h>
> #include <linux/wmi.h>
> +#include <acpi/battery.h>
>
> #include <linux/unaligned.h>
>
> @@ -79,6 +80,7 @@
> /* Get_Data() and Set_Data() Params */
> #define MSI_PLATFORM_PL1_ADDR 0x50
> #define MSI_PLATFORM_PL2_ADDR 0x51
> +#define MSI_PLATFORM_BAT_ADDR 0xd7
>
> static bool force;
> module_param_unsafe(force, bool, 0);
> @@ -118,6 +120,7 @@ enum msi_wmi_platform_method {
>
> struct msi_wmi_platform_quirk {
> bool shift_mode; /* Shift mode is supported */
> + bool charge_threshold; /* Charge threshold is supported */
> int pl_min; /* Minimum PLx value */
> int pl1_max; /* Maximum PL1 value */
> int pl2_max; /* Maximum PL2 value */
> @@ -128,6 +131,7 @@ struct msi_wmi_platform_data {
> struct msi_wmi_platform_quirk *quirks;
> struct mutex wmi_lock; /* Necessary when calling WMI methods */
> struct device *ppdev;
> + struct acpi_battery_hook battery_hook;
> struct device *fw_attrs_dev;
> struct kset *fw_attrs_kset;
> };
> @@ -211,12 +215,14 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
> static struct msi_wmi_platform_quirk quirk_default = {};
> static struct msi_wmi_platform_quirk quirk_gen1 = {
> .shift_mode = true,
> + .charge_threshold = true,
> .pl_min = 8,
> .pl1_max = 43,
> .pl2_max = 45
> };
> static struct msi_wmi_platform_quirk quirk_gen2 = {
> .shift_mode = true,
> + .charge_threshold = true,
> .pl_min = 8,
> .pl1_max = 30,
> .pl2_max = 37
> @@ -1013,6 +1019,94 @@ static int msi_wmi_fw_attrs_init(struct msi_wmi_platform_data *data)
> return 0;
> }
>
> +static int msi_platform_psy_ext_get_prop(struct power_supply *psy,
> + const struct power_supply_ext *ext,
> + void *data,
> + enum power_supply_property psp,
> + union power_supply_propval *val)
> +{
> + struct msi_wmi_platform_data *msi = data;
> + u8 buffer[32] = { 0 };
> + int ret;
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
> + buffer[0] = MSI_PLATFORM_BAT_ADDR;
> + ret = msi_wmi_platform_query(msi, MSI_PLATFORM_GET_DATA,
> + buffer, sizeof(buffer));
> + if (ret)
> + return ret;
> +
> + val->intval = buffer[1] & ~BIT(7);
I think BIT(7) should be a named define.
> + if (val->intval > 100)
> + return -EINVAL;
> +
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int msi_platform_psy_ext_set_prop(struct power_supply *psy,
> + const struct power_supply_ext *ext,
> + void *data,
> + enum power_supply_property psp,
> + const union power_supply_propval *val)
> +{
> + struct msi_wmi_platform_data *msi = data;
> + u8 buffer[32] = { 0 };
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
> + if (val->intval > 100)
> + return -EINVAL;
> + buffer[0] = MSI_PLATFORM_BAT_ADDR;
> + buffer[1] = val->intval | BIT(7);
> + return msi_wmi_platform_query(msi, MSI_PLATFORM_SET_DATA,
> + buffer, sizeof(buffer));
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int
> +msi_platform_psy_prop_is_writeable(struct power_supply *psy,
> + const struct power_supply_ext *ext,
> + void *data, enum power_supply_property psp)
> +{
> + return true;
> +}
> +
> +static const enum power_supply_property oxp_psy_ext_props[] = {
Maybe s/oxp/msi/ ?
--
~ Kurt
> + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
> +};
> +
> +static const struct power_supply_ext msi_platform_psy_ext = {
> + .name = "msi-platform-charge-control",
> + .properties = oxp_psy_ext_props,
> + .num_properties = ARRAY_SIZE(oxp_psy_ext_props),
> + .get_property = msi_platform_psy_ext_get_prop,
> + .set_property = msi_platform_psy_ext_set_prop,
> + .property_is_writeable = msi_platform_psy_prop_is_writeable,
> +};
> +
> +static int msi_wmi_platform_battery_add(struct power_supply *battery,
> + struct acpi_battery_hook *hook)
> +{
> + struct msi_wmi_platform_data *data =
> + container_of(hook, struct msi_wmi_platform_data, battery_hook);
> +
> + return power_supply_register_extension(battery, &msi_platform_psy_ext,
> + &data->wdev->dev, data);
> +}
> +
> +static int msi_wmi_platform_battery_remove(struct power_supply *battery,
> + struct acpi_battery_hook *hook)
> +{
> + power_supply_unregister_extension(battery, &msi_platform_psy_ext);
> + return 0;
> +}
> +
> static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
> size_t length, loff_t *offset)
> {
> @@ -1245,6 +1339,13 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> if (ret < 0)
> return ret;
>
> + if (data->quirks->charge_threshold) {
> + data->battery_hook.name = "MSI Battery";
> + data->battery_hook.add_battery = msi_wmi_platform_battery_add;
> + data->battery_hook.remove_battery = msi_wmi_platform_battery_remove;
> + battery_hook_register(&data->battery_hook);
> + }
> +
> msi_wmi_platform_debugfs_init(data);
>
> msi_wmi_platform_profile_setup(data);
> @@ -1252,6 +1353,14 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> return msi_wmi_platform_hwmon_init(data);
> }
>
> +static void msi_wmi_platform_remove(struct wmi_device *wdev)
> +{
> + struct msi_wmi_platform_data *data = dev_get_drvdata(&wdev->dev);
> +
> + if (data->quirks->charge_threshold)
> + battery_hook_unregister(&data->battery_hook);
> +}
> +
> static const struct wmi_device_id msi_wmi_platform_id_table[] = {
> { MSI_PLATFORM_GUID, NULL },
> { }
> @@ -1265,6 +1374,7 @@ static struct wmi_driver msi_wmi_platform_driver = {
> },
> .id_table = msi_wmi_platform_id_table,
> .probe = msi_wmi_platform_probe,
> + .remove = msi_wmi_platform_remove,
> .no_singleton = true,
> };
> module_wmi_driver(msi_wmi_platform_driver);
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 07/10] platform/x86: msi-wmi-platform: Add charge_threshold support
2025-05-11 20:44 ` [PATCH v1 07/10] platform/x86: msi-wmi-platform: Add charge_threshold support Antheas Kapenekakis
2025-05-11 23:34 ` Kurt Borja
@ 2025-05-19 2:32 ` Armin Wolf
1 sibling, 0 replies; 45+ messages in thread
From: Armin Wolf @ 2025-05-19 2:32 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Jonathan Corbet, Hans de Goede, Ilpo Järvinen, Jean Delvare,
Guenter Roeck, Kurt Borja, linux-doc, linux-kernel, linux-hwmon
Am 11.05.25 um 22:44 schrieb Antheas Kapenekakis:
> The battery of MSI laptops supports charge threshold. Add support for it.
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/Kconfig | 1 +
> drivers/platform/x86/msi-wmi-platform.c | 110 ++++++++++++++++++++++++
> 2 files changed, 111 insertions(+)
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index fd3da718731e7..51a34ab476ffc 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -744,6 +744,7 @@ config MSI_WMI
>
> config MSI_WMI_PLATFORM
> tristate "MSI WMI Platform features"
> + depends on ACPI_BATTERY
> depends on ACPI_WMI
> depends on HWMON
> select ACPI_PLATFORM_PROFILE
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index 6498f4b44fe53..46928fb4da8a6 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -31,6 +31,7 @@
> #include <linux/sysfs.h>
> #include <linux/types.h>
> #include <linux/wmi.h>
> +#include <acpi/battery.h>
>
> #include <linux/unaligned.h>
>
> @@ -79,6 +80,7 @@
> /* Get_Data() and Set_Data() Params */
> #define MSI_PLATFORM_PL1_ADDR 0x50
> #define MSI_PLATFORM_PL2_ADDR 0x51
> +#define MSI_PLATFORM_BAT_ADDR 0xd7
>
> static bool force;
> module_param_unsafe(force, bool, 0);
> @@ -118,6 +120,7 @@ enum msi_wmi_platform_method {
>
> struct msi_wmi_platform_quirk {
> bool shift_mode; /* Shift mode is supported */
> + bool charge_threshold; /* Charge threshold is supported */
> int pl_min; /* Minimum PLx value */
> int pl1_max; /* Maximum PL1 value */
> int pl2_max; /* Maximum PL2 value */
> @@ -128,6 +131,7 @@ struct msi_wmi_platform_data {
> struct msi_wmi_platform_quirk *quirks;
> struct mutex wmi_lock; /* Necessary when calling WMI methods */
> struct device *ppdev;
> + struct acpi_battery_hook battery_hook;
> struct device *fw_attrs_dev;
> struct kset *fw_attrs_kset;
> };
> @@ -211,12 +215,14 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
> static struct msi_wmi_platform_quirk quirk_default = {};
> static struct msi_wmi_platform_quirk quirk_gen1 = {
> .shift_mode = true,
> + .charge_threshold = true,
> .pl_min = 8,
> .pl1_max = 43,
> .pl2_max = 45
> };
> static struct msi_wmi_platform_quirk quirk_gen2 = {
> .shift_mode = true,
> + .charge_threshold = true,
> .pl_min = 8,
> .pl1_max = 30,
> .pl2_max = 37
> @@ -1013,6 +1019,94 @@ static int msi_wmi_fw_attrs_init(struct msi_wmi_platform_data *data)
> return 0;
> }
>
> +static int msi_platform_psy_ext_get_prop(struct power_supply *psy,
> + const struct power_supply_ext *ext,
> + void *data,
> + enum power_supply_property psp,
> + union power_supply_propval *val)
> +{
> + struct msi_wmi_platform_data *msi = data;
> + u8 buffer[32] = { 0 };
> + int ret;
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
> + buffer[0] = MSI_PLATFORM_BAT_ADDR;
> + ret = msi_wmi_platform_query(msi, MSI_PLATFORM_GET_DATA,
> + buffer, sizeof(buffer));
> + if (ret)
> + return ret;
> +
> + val->intval = buffer[1] & ~BIT(7);
> + if (val->intval > 100)
> + return -EINVAL;
> +
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int msi_platform_psy_ext_set_prop(struct power_supply *psy,
> + const struct power_supply_ext *ext,
> + void *data,
> + enum power_supply_property psp,
> + const union power_supply_propval *val)
> +{
> + struct msi_wmi_platform_data *msi = data;
> + u8 buffer[32] = { 0 };
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
> + if (val->intval > 100)
> + return -EINVAL;
> + buffer[0] = MSI_PLATFORM_BAT_ADDR;
> + buffer[1] = val->intval | BIT(7);
> + return msi_wmi_platform_query(msi, MSI_PLATFORM_SET_DATA,
> + buffer, sizeof(buffer));
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int
> +msi_platform_psy_prop_is_writeable(struct power_supply *psy,
> + const struct power_supply_ext *ext,
> + void *data, enum power_supply_property psp)
> +{
> + return true;
> +}
> +
> +static const enum power_supply_property oxp_psy_ext_props[] = {
> + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
> +};
> +
> +static const struct power_supply_ext msi_platform_psy_ext = {
> + .name = "msi-platform-charge-control",
> + .properties = oxp_psy_ext_props,
> + .num_properties = ARRAY_SIZE(oxp_psy_ext_props),
> + .get_property = msi_platform_psy_ext_get_prop,
> + .set_property = msi_platform_psy_ext_set_prop,
> + .property_is_writeable = msi_platform_psy_prop_is_writeable,
> +};
> +
> +static int msi_wmi_platform_battery_add(struct power_supply *battery,
> + struct acpi_battery_hook *hook)
> +{
> + struct msi_wmi_platform_data *data =
> + container_of(hook, struct msi_wmi_platform_data, battery_hook);
> +
> + return power_supply_register_extension(battery, &msi_platform_psy_ext,
> + &data->wdev->dev, data);
> +}
> +
> +static int msi_wmi_platform_battery_remove(struct power_supply *battery,
> + struct acpi_battery_hook *hook)
> +{
> + power_supply_unregister_extension(battery, &msi_platform_psy_ext);
> + return 0;
> +}
> +
> static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
> size_t length, loff_t *offset)
> {
> @@ -1245,6 +1339,13 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> if (ret < 0)
> return ret;
>
> + if (data->quirks->charge_threshold) {
> + data->battery_hook.name = "MSI Battery";
> + data->battery_hook.add_battery = msi_wmi_platform_battery_add;
> + data->battery_hook.remove_battery = msi_wmi_platform_battery_remove;
> + battery_hook_register(&data->battery_hook);
Please use devm_battery_hook_reigister()
Thanks,
Armin Wolf
> + }
> +
> msi_wmi_platform_debugfs_init(data);
>
> msi_wmi_platform_profile_setup(data);
> @@ -1252,6 +1353,14 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> return msi_wmi_platform_hwmon_init(data);
> }
>
> +static void msi_wmi_platform_remove(struct wmi_device *wdev)
> +{
> + struct msi_wmi_platform_data *data = dev_get_drvdata(&wdev->dev);
> +
> + if (data->quirks->charge_threshold)
> + battery_hook_unregister(&data->battery_hook);
> +}
> +
> static const struct wmi_device_id msi_wmi_platform_id_table[] = {
> { MSI_PLATFORM_GUID, NULL },
> { }
> @@ -1265,6 +1374,7 @@ static struct wmi_driver msi_wmi_platform_driver = {
> },
> .id_table = msi_wmi_platform_id_table,
> .probe = msi_wmi_platform_probe,
> + .remove = msi_wmi_platform_remove,
> .no_singleton = true,
> };
> module_wmi_driver(msi_wmi_platform_driver);
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v1 08/10] platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices
2025-05-11 20:44 [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Antheas Kapenekakis
` (6 preceding siblings ...)
2025-05-11 20:44 ` [PATCH v1 07/10] platform/x86: msi-wmi-platform: Add charge_threshold support Antheas Kapenekakis
@ 2025-05-11 20:44 ` Antheas Kapenekakis
2025-05-11 23:35 ` Kurt Borja
2025-05-11 20:44 ` [PATCH v1 09/10] platform/x86: msi-wmi-platform: Update header text Antheas Kapenekakis
` (3 subsequent siblings)
11 siblings, 1 reply; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-11 20:44 UTC (permalink / raw)
To: platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, Kurt Borja, linux-doc, linux-kernel,
linux-hwmon, Antheas Kapenekakis
Currently, the platform driver always exposes 4 fans, since the
underlying WMI interface reads 4 values from the EC. However, most
devices only have two fans. Therefore, at least in the case of the
Claw series, quirk the driver to only show two hwmon fans.
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
drivers/platform/x86/msi-wmi-platform.c | 28 ++++++++++++++++++++++---
1 file changed, 25 insertions(+), 3 deletions(-)
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
index 46928fb4da8a6..eaf0eb25e349b 100644
--- a/drivers/platform/x86/msi-wmi-platform.c
+++ b/drivers/platform/x86/msi-wmi-platform.c
@@ -121,6 +121,7 @@ enum msi_wmi_platform_method {
struct msi_wmi_platform_quirk {
bool shift_mode; /* Shift mode is supported */
bool charge_threshold; /* Charge threshold is supported */
+ bool dual_fans; /* For devices with two hwmon fans */
int pl_min; /* Minimum PLx value */
int pl1_max; /* Maximum PL1 value */
int pl2_max; /* Maximum PL2 value */
@@ -216,6 +217,7 @@ static struct msi_wmi_platform_quirk quirk_default = {};
static struct msi_wmi_platform_quirk quirk_gen1 = {
.shift_mode = true,
.charge_threshold = true,
+ .dual_fans = true,
.pl_min = 8,
.pl1_max = 43,
.pl2_max = 45
@@ -223,6 +225,7 @@ static struct msi_wmi_platform_quirk quirk_gen1 = {
static struct msi_wmi_platform_quirk quirk_gen2 = {
.shift_mode = true,
.charge_threshold = true,
+ .dual_fans = true,
.pl_min = 8,
.pl1_max = 30,
.pl2_max = 37
@@ -635,6 +638,23 @@ static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
.info = msi_wmi_platform_info,
};
+static const struct hwmon_channel_info * const msi_wmi_platform_info_dual[] = {
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT,
+ HWMON_F_INPUT
+ ),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_ENABLE,
+ HWMON_PWM_ENABLE
+ ),
+ NULL
+};
+
+static const struct hwmon_chip_info msi_wmi_platform_chip_info_dual = {
+ .ops = &msi_wmi_platform_ops,
+ .info = msi_wmi_platform_info_dual,
+};
+
static int msi_wmi_platform_profile_probe(void *drvdata, unsigned long *choices)
{
set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
@@ -1227,9 +1247,11 @@ static int msi_wmi_platform_hwmon_init(struct msi_wmi_platform_data *data)
{
struct device *hdev;
- hdev = devm_hwmon_device_register_with_info(&data->wdev->dev, "msi_wmi_platform", data,
- &msi_wmi_platform_chip_info,
- msi_wmi_platform_hwmon_groups);
+ hdev = devm_hwmon_device_register_with_info(
+ &data->wdev->dev, "msi_wmi_platform", data,
+ data->quirks->dual_fans ? &msi_wmi_platform_chip_info_dual :
+ &msi_wmi_platform_chip_info,
+ msi_wmi_platform_hwmon_groups);
return PTR_ERR_OR_ZERO(hdev);
}
--
2.49.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v1 08/10] platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices
2025-05-11 20:44 ` [PATCH v1 08/10] platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices Antheas Kapenekakis
@ 2025-05-11 23:35 ` Kurt Borja
0 siblings, 0 replies; 45+ messages in thread
From: Kurt Borja @ 2025-05-11 23:35 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, linux-doc, linux-kernel, linux-hwmon
[-- Attachment #1: Type: text/plain, Size: 3221 bytes --]
On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> Currently, the platform driver always exposes 4 fans, since the
> underlying WMI interface reads 4 values from the EC. However, most
> devices only have two fans. Therefore, at least in the case of the
> Claw series, quirk the driver to only show two hwmon fans.
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/msi-wmi-platform.c | 28 ++++++++++++++++++++++---
> 1 file changed, 25 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index 46928fb4da8a6..eaf0eb25e349b 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -121,6 +121,7 @@ enum msi_wmi_platform_method {
> struct msi_wmi_platform_quirk {
> bool shift_mode; /* Shift mode is supported */
> bool charge_threshold; /* Charge threshold is supported */
> + bool dual_fans; /* For devices with two hwmon fans */
> int pl_min; /* Minimum PLx value */
> int pl1_max; /* Maximum PL1 value */
> int pl2_max; /* Maximum PL2 value */
> @@ -216,6 +217,7 @@ static struct msi_wmi_platform_quirk quirk_default = {};
> static struct msi_wmi_platform_quirk quirk_gen1 = {
> .shift_mode = true,
> .charge_threshold = true,
> + .dual_fans = true,
> .pl_min = 8,
> .pl1_max = 43,
> .pl2_max = 45
> @@ -223,6 +225,7 @@ static struct msi_wmi_platform_quirk quirk_gen1 = {
> static struct msi_wmi_platform_quirk quirk_gen2 = {
> .shift_mode = true,
> .charge_threshold = true,
> + .dual_fans = true,
> .pl_min = 8,
> .pl1_max = 30,
> .pl2_max = 37
> @@ -635,6 +638,23 @@ static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
> .info = msi_wmi_platform_info,
> };
>
> +static const struct hwmon_channel_info * const msi_wmi_platform_info_dual[] = {
> + HWMON_CHANNEL_INFO(fan,
> + HWMON_F_INPUT,
> + HWMON_F_INPUT
> + ),
> + HWMON_CHANNEL_INFO(pwm,
> + HWMON_PWM_ENABLE,
> + HWMON_PWM_ENABLE
> + ),
> + NULL
> +};
> +
> +static const struct hwmon_chip_info msi_wmi_platform_chip_info_dual = {
> + .ops = &msi_wmi_platform_ops,
> + .info = msi_wmi_platform_info_dual,
> +};
> +
> static int msi_wmi_platform_profile_probe(void *drvdata, unsigned long *choices)
> {
> set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
> @@ -1227,9 +1247,11 @@ static int msi_wmi_platform_hwmon_init(struct msi_wmi_platform_data *data)
> {
> struct device *hdev;
>
> - hdev = devm_hwmon_device_register_with_info(&data->wdev->dev, "msi_wmi_platform", data,
> - &msi_wmi_platform_chip_info,
> - msi_wmi_platform_hwmon_groups);
> + hdev = devm_hwmon_device_register_with_info(
> + &data->wdev->dev, "msi_wmi_platform", data,
> + data->quirks->dual_fans ? &msi_wmi_platform_chip_info_dual :
> + &msi_wmi_platform_chip_info,
This is the wrong approach.
Add the quirk and control visibility from msi_wmi_platform_is_visible(),
like I mentioned in the cover-letter.
--
~ Kurt
> + msi_wmi_platform_hwmon_groups);
>
> return PTR_ERR_OR_ZERO(hdev);
> }
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v1 09/10] platform/x86: msi-wmi-platform: Update header text
2025-05-11 20:44 [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Antheas Kapenekakis
` (7 preceding siblings ...)
2025-05-11 20:44 ` [PATCH v1 08/10] platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices Antheas Kapenekakis
@ 2025-05-11 20:44 ` Antheas Kapenekakis
2025-05-19 2:33 ` Armin Wolf
2025-05-11 20:44 ` [PATCH v1 10/10] platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and unload Antheas Kapenekakis
` (2 subsequent siblings)
11 siblings, 1 reply; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-11 20:44 UTC (permalink / raw)
To: platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, Kurt Borja, linux-doc, linux-kernel,
linux-hwmon, Antheas Kapenekakis
Update copyright information in the header and specify
that this driver also applies to handhelds.
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
drivers/platform/x86/msi-wmi-platform.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
index eaf0eb25e349b..7dafe17d4d6be 100644
--- a/drivers/platform/x86/msi-wmi-platform.c
+++ b/drivers/platform/x86/msi-wmi-platform.c
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
- * Linux driver for WMI platform features on MSI notebooks.
+ * Linux driver for WMI platform features on MSI notebooks and handhelds.
*
- * Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de>
+ * Copyright (C) 2024-2025 Armin Wolf <W_Armin@gmx.de>
+ * Copyright (C) 2025 Antheas Kapenekakis <lkml@antheas.dev>
*/
#define pr_format(fmt) KBUILD_MODNAME ": " fmt
--
2.49.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v1 09/10] platform/x86: msi-wmi-platform: Update header text
2025-05-11 20:44 ` [PATCH v1 09/10] platform/x86: msi-wmi-platform: Update header text Antheas Kapenekakis
@ 2025-05-19 2:33 ` Armin Wolf
0 siblings, 0 replies; 45+ messages in thread
From: Armin Wolf @ 2025-05-19 2:33 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Jonathan Corbet, Hans de Goede, Ilpo Järvinen, Jean Delvare,
Guenter Roeck, Kurt Borja, linux-doc, linux-kernel, linux-hwmon
Am 11.05.25 um 22:44 schrieb Antheas Kapenekakis:
> Update copyright information in the header and specify
> that this driver also applies to handhelds.
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/msi-wmi-platform.c | 5 +++--
> 1 file changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index eaf0eb25e349b..7dafe17d4d6be 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -1,8 +1,9 @@
> // SPDX-License-Identifier: GPL-2.0-or-later
> /*
> - * Linux driver for WMI platform features on MSI notebooks.
> + * Linux driver for WMI platform features on MSI notebooks and handhelds.
> *
> - * Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de>
> + * Copyright (C) 2024-2025 Armin Wolf <W_Armin@gmx.de>
> + * Copyright (C) 2025 Antheas Kapenekakis <lkml@antheas.dev>
> */
>
> #define pr_format(fmt) KBUILD_MODNAME ": " fmt
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v1 10/10] platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and unload
2025-05-11 20:44 [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Antheas Kapenekakis
` (8 preceding siblings ...)
2025-05-11 20:44 ` [PATCH v1 09/10] platform/x86: msi-wmi-platform: Update header text Antheas Kapenekakis
@ 2025-05-11 20:44 ` Antheas Kapenekakis
2025-05-12 19:16 ` Kurt Borja
2025-05-11 23:30 ` [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Kurt Borja
2025-05-19 2:37 ` Armin Wolf
11 siblings, 1 reply; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-11 20:44 UTC (permalink / raw)
To: platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, Kurt Borja, linux-doc, linux-kernel,
linux-hwmon, Antheas Kapenekakis
MSI software is a bit weird in that even when the manual fan curve is
disabled, the fan speed is still somewhat affected by the curve. So
we have to restore the fan curves on unload and PWM disable, as it
is done in Windows.
Suggested-by: Armin Wolf <W_Armin@gmx.de>
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
drivers/platform/x86/msi-wmi-platform.c | 123 +++++++++++++++++++++++-
1 file changed, 122 insertions(+), 1 deletion(-)
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
index 7dafe17d4d6be..a917db0300c06 100644
--- a/drivers/platform/x86/msi-wmi-platform.c
+++ b/drivers/platform/x86/msi-wmi-platform.c
@@ -123,16 +123,25 @@ struct msi_wmi_platform_quirk {
bool shift_mode; /* Shift mode is supported */
bool charge_threshold; /* Charge threshold is supported */
bool dual_fans; /* For devices with two hwmon fans */
+ bool restore_curves; /* Restore factory curves on unload */
int pl_min; /* Minimum PLx value */
int pl1_max; /* Maximum PL1 value */
int pl2_max; /* Maximum PL2 value */
};
+struct msi_wmi_platform_factory_curves {
+ u8 cpu_fan_table[32];
+ u8 gpu_fan_table[32];
+ u8 cpu_temp_table[32];
+ u8 gpu_temp_table[32];
+};
+
struct msi_wmi_platform_data {
struct wmi_device *wdev;
struct msi_wmi_platform_quirk *quirks;
struct mutex wmi_lock; /* Necessary when calling WMI methods */
struct device *ppdev;
+ struct msi_wmi_platform_factory_curves factory_curves;
struct acpi_battery_hook battery_hook;
struct device *fw_attrs_dev;
struct kset *fw_attrs_kset;
@@ -219,6 +228,7 @@ static struct msi_wmi_platform_quirk quirk_gen1 = {
.shift_mode = true,
.charge_threshold = true,
.dual_fans = true,
+ .restore_curves = true,
.pl_min = 8,
.pl1_max = 43,
.pl2_max = 45
@@ -227,6 +237,7 @@ static struct msi_wmi_platform_quirk quirk_gen2 = {
.shift_mode = true,
.charge_threshold = true,
.dual_fans = true,
+ .restore_curves = true,
.pl_min = 8,
.pl1_max = 30,
.pl2_max = 37
@@ -507,6 +518,94 @@ static struct attribute *msi_wmi_platform_hwmon_attrs[] = {
};
ATTRIBUTE_GROUPS(msi_wmi_platform_hwmon);
+static int msi_wmi_platform_curves_save(struct msi_wmi_platform_data *data)
+{
+ int ret;
+
+ data->factory_curves.cpu_fan_table[0] =
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE;
+ ret = msi_wmi_platform_query_unlocked(
+ data, MSI_PLATFORM_GET_FAN,
+ data->factory_curves.cpu_fan_table,
+ sizeof(data->factory_curves.cpu_fan_table));
+ if (ret < 0)
+ return ret;
+ data->factory_curves.cpu_fan_table[0] =
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE;
+
+ data->factory_curves.gpu_fan_table[0] =
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE;
+ ret = msi_wmi_platform_query_unlocked(
+ data, MSI_PLATFORM_GET_FAN,
+ data->factory_curves.gpu_fan_table,
+ sizeof(data->factory_curves.gpu_fan_table));
+ if (ret < 0)
+ return ret;
+ data->factory_curves.gpu_fan_table[0] =
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE;
+
+ data->factory_curves.cpu_temp_table[0] =
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE;
+ ret = msi_wmi_platform_query_unlocked(
+ data, MSI_PLATFORM_GET_TEMPERATURE,
+ data->factory_curves.cpu_temp_table,
+ sizeof(data->factory_curves.cpu_temp_table));
+ if (ret < 0)
+ return ret;
+ data->factory_curves.cpu_temp_table[0] =
+ MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE;
+
+ data->factory_curves.gpu_temp_table[0] =
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE;
+ ret = msi_wmi_platform_query_unlocked(
+ data, MSI_PLATFORM_GET_TEMPERATURE,
+ data->factory_curves.gpu_temp_table,
+ sizeof(data->factory_curves.gpu_temp_table));
+ if (ret < 0)
+ return ret;
+ data->factory_curves.gpu_temp_table[0] =
+ MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE;
+
+ return 0;
+}
+
+static int msi_wmi_platform_curves_load(struct msi_wmi_platform_data *data)
+{
+ u8 buffer[32] = { };
+ int ret;
+
+ memcpy(buffer, data->factory_curves.cpu_fan_table,
+ sizeof(data->factory_curves.cpu_fan_table));
+ ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_FAN,
+ buffer, sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ memcpy(buffer, data->factory_curves.gpu_fan_table,
+ sizeof(data->factory_curves.gpu_fan_table));
+ ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_FAN,
+ buffer, sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ memcpy(buffer, data->factory_curves.cpu_temp_table,
+ sizeof(data->factory_curves.cpu_temp_table));
+ ret = msi_wmi_platform_query_unlocked(
+ data, MSI_PLATFORM_SET_TEMPERATURE, buffer, sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ memcpy(buffer, data->factory_curves.gpu_temp_table,
+ sizeof(data->factory_curves.gpu_temp_table));
+ ret = msi_wmi_platform_query_unlocked(
+ data, MSI_PLATFORM_SET_TEMPERATURE, buffer, sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+
static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type,
u32 attr, int channel)
{
@@ -603,9 +702,19 @@ static int msi_wmi_platform_write(struct device *dev, enum hwmon_sensor_types ty
return -EINVAL;
}
- return msi_wmi_platform_query_unlocked(
+ ret = msi_wmi_platform_query_unlocked(
data, MSI_PLATFORM_SET_AP, buffer,
sizeof(buffer));
+ if (ret < 0)
+ return ret;
+
+ if (val == 2 && data->quirks->restore_curves) {
+ ret = msi_wmi_platform_curves_load(data);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
default:
return -EOPNOTSUPP;
}
@@ -1373,6 +1482,13 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
msi_wmi_platform_profile_setup(data);
+ if (data->quirks->restore_curves) {
+ guard(mutex)(&data->wmi_lock);
+ ret = msi_wmi_platform_curves_save(data);
+ if (ret < 0)
+ return ret;
+ }
+
return msi_wmi_platform_hwmon_init(data);
}
@@ -1382,6 +1498,11 @@ static void msi_wmi_platform_remove(struct wmi_device *wdev)
if (data->quirks->charge_threshold)
battery_hook_unregister(&data->battery_hook);
+
+ if (data->quirks->restore_curves) {
+ guard(mutex)(&data->wmi_lock);
+ msi_wmi_platform_curves_load(data);
+ }
}
static const struct wmi_device_id msi_wmi_platform_id_table[] = {
--
2.49.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v1 10/10] platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and unload
2025-05-11 20:44 ` [PATCH v1 10/10] platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and unload Antheas Kapenekakis
@ 2025-05-12 19:16 ` Kurt Borja
2025-05-12 20:50 ` Antheas Kapenekakis
0 siblings, 1 reply; 45+ messages in thread
From: Kurt Borja @ 2025-05-12 19:16 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, linux-doc, linux-kernel, linux-hwmon
[-- Attachment #1: Type: text/plain, Size: 7473 bytes --]
On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> MSI software is a bit weird in that even when the manual fan curve is
> disabled, the fan speed is still somewhat affected by the curve. So
> we have to restore the fan curves on unload and PWM disable, as it
> is done in Windows.
>
> Suggested-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/msi-wmi-platform.c | 123 +++++++++++++++++++++++-
> 1 file changed, 122 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> index 7dafe17d4d6be..a917db0300c06 100644
> --- a/drivers/platform/x86/msi-wmi-platform.c
> +++ b/drivers/platform/x86/msi-wmi-platform.c
> @@ -123,16 +123,25 @@ struct msi_wmi_platform_quirk {
> bool shift_mode; /* Shift mode is supported */
> bool charge_threshold; /* Charge threshold is supported */
> bool dual_fans; /* For devices with two hwmon fans */
> + bool restore_curves; /* Restore factory curves on unload */
> int pl_min; /* Minimum PLx value */
> int pl1_max; /* Maximum PL1 value */
> int pl2_max; /* Maximum PL2 value */
> };
>
> +struct msi_wmi_platform_factory_curves {
> + u8 cpu_fan_table[32];
> + u8 gpu_fan_table[32];
> + u8 cpu_temp_table[32];
> + u8 gpu_temp_table[32];
> +};
> +
> struct msi_wmi_platform_data {
> struct wmi_device *wdev;
> struct msi_wmi_platform_quirk *quirks;
> struct mutex wmi_lock; /* Necessary when calling WMI methods */
> struct device *ppdev;
> + struct msi_wmi_platform_factory_curves factory_curves;
> struct acpi_battery_hook battery_hook;
> struct device *fw_attrs_dev;
> struct kset *fw_attrs_kset;
> @@ -219,6 +228,7 @@ static struct msi_wmi_platform_quirk quirk_gen1 = {
> .shift_mode = true,
> .charge_threshold = true,
> .dual_fans = true,
> + .restore_curves = true,
> .pl_min = 8,
> .pl1_max = 43,
> .pl2_max = 45
> @@ -227,6 +237,7 @@ static struct msi_wmi_platform_quirk quirk_gen2 = {
> .shift_mode = true,
> .charge_threshold = true,
> .dual_fans = true,
> + .restore_curves = true,
> .pl_min = 8,
> .pl1_max = 30,
> .pl2_max = 37
> @@ -507,6 +518,94 @@ static struct attribute *msi_wmi_platform_hwmon_attrs[] = {
> };
> ATTRIBUTE_GROUPS(msi_wmi_platform_hwmon);
>
> +static int msi_wmi_platform_curves_save(struct msi_wmi_platform_data *data)
> +{
> + int ret;
> +
> + data->factory_curves.cpu_fan_table[0] =
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE;
> + ret = msi_wmi_platform_query_unlocked(
> + data, MSI_PLATFORM_GET_FAN,
> + data->factory_curves.cpu_fan_table,
> + sizeof(data->factory_curves.cpu_fan_table));
> + if (ret < 0)
> + return ret;
> + data->factory_curves.cpu_fan_table[0] =
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE;
Is it necessary to set the subfeature again here (and bellow)?
Also there is a lot of code repetition here, I would suggest a helper
function. It will be optimized/inlined by the compiler anyway.
> +
> + data->factory_curves.gpu_fan_table[0] =
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE;
> + ret = msi_wmi_platform_query_unlocked(
> + data, MSI_PLATFORM_GET_FAN,
> + data->factory_curves.gpu_fan_table,
> + sizeof(data->factory_curves.gpu_fan_table));
> + if (ret < 0)
> + return ret;
> + data->factory_curves.gpu_fan_table[0] =
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE;
> +
> + data->factory_curves.cpu_temp_table[0] =
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE;
> + ret = msi_wmi_platform_query_unlocked(
> + data, MSI_PLATFORM_GET_TEMPERATURE,
> + data->factory_curves.cpu_temp_table,
> + sizeof(data->factory_curves.cpu_temp_table));
> + if (ret < 0)
> + return ret;
> + data->factory_curves.cpu_temp_table[0] =
> + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE;
> +
> + data->factory_curves.gpu_temp_table[0] =
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE;
> + ret = msi_wmi_platform_query_unlocked(
> + data, MSI_PLATFORM_GET_TEMPERATURE,
> + data->factory_curves.gpu_temp_table,
> + sizeof(data->factory_curves.gpu_temp_table));
> + if (ret < 0)
> + return ret;
> + data->factory_curves.gpu_temp_table[0] =
> + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE;
> +
> + return 0;
> +}
> +
> +static int msi_wmi_platform_curves_load(struct msi_wmi_platform_data *data)
> +{
> + u8 buffer[32] = { };
> + int ret;
> +
> + memcpy(buffer, data->factory_curves.cpu_fan_table,
> + sizeof(data->factory_curves.cpu_fan_table));
> + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_FAN,
> + buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
A helper for this operation would be nice too.
> +
> + memcpy(buffer, data->factory_curves.gpu_fan_table,
> + sizeof(data->factory_curves.gpu_fan_table));
> + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_FAN,
> + buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + memcpy(buffer, data->factory_curves.cpu_temp_table,
> + sizeof(data->factory_curves.cpu_temp_table));
> + ret = msi_wmi_platform_query_unlocked(
> + data, MSI_PLATFORM_SET_TEMPERATURE, buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + memcpy(buffer, data->factory_curves.gpu_temp_table,
> + sizeof(data->factory_curves.gpu_temp_table));
> + ret = msi_wmi_platform_query_unlocked(
> + data, MSI_PLATFORM_SET_TEMPERATURE, buffer, sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +
> static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type,
> u32 attr, int channel)
> {
> @@ -603,9 +702,19 @@ static int msi_wmi_platform_write(struct device *dev, enum hwmon_sensor_types ty
> return -EINVAL;
> }
>
> - return msi_wmi_platform_query_unlocked(
> + ret = msi_wmi_platform_query_unlocked(
> data, MSI_PLATFORM_SET_AP, buffer,
> sizeof(buffer));
> + if (ret < 0)
> + return ret;
> +
> + if (val == 2 && data->quirks->restore_curves) {
> + ret = msi_wmi_platform_curves_load(data);
> + if (ret < 0)
> + return ret;
> + }
> +
> + return 0;
> default:
> return -EOPNOTSUPP;
> }
> @@ -1373,6 +1482,13 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
>
> msi_wmi_platform_profile_setup(data);
>
> + if (data->quirks->restore_curves) {
> + guard(mutex)(&data->wmi_lock);
We don't need locking here. data->factory_curves is not shared until you
register the hwmon device.
> + ret = msi_wmi_platform_curves_save(data);
> + if (ret < 0)
> + return ret;
> + }
> +
> return msi_wmi_platform_hwmon_init(data);
> }
>
> @@ -1382,6 +1498,11 @@ static void msi_wmi_platform_remove(struct wmi_device *wdev)
>
> if (data->quirks->charge_threshold)
> battery_hook_unregister(&data->battery_hook);
> +
> + if (data->quirks->restore_curves) {
> + guard(mutex)(&data->wmi_lock);
We can avoid locking here by adding a devm action that restores the
curves. devm resources are unloaded in LIFO order.
Please, also check my comments on [Patch 2]. I don't think that patch is
needed.
--
~ Kurt
> + msi_wmi_platform_curves_load(data);
> + }
> }
>
> static const struct wmi_device_id msi_wmi_platform_id_table[] = {
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 10/10] platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and unload
2025-05-12 19:16 ` Kurt Borja
@ 2025-05-12 20:50 ` Antheas Kapenekakis
0 siblings, 0 replies; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-12 20:50 UTC (permalink / raw)
To: Kurt Borja
Cc: platform-driver-x86, Armin Wolf, Jonathan Corbet, Hans de Goede,
Ilpo Järvinen, Jean Delvare, Guenter Roeck, linux-doc,
linux-kernel, linux-hwmon
On Mon, 12 May 2025 at 21:16, Kurt Borja <kuurtb@gmail.com> wrote:
>
> On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> > MSI software is a bit weird in that even when the manual fan curve is
> > disabled, the fan speed is still somewhat affected by the curve. So
> > we have to restore the fan curves on unload and PWM disable, as it
> > is done in Windows.
> >
> > Suggested-by: Armin Wolf <W_Armin@gmx.de>
> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > ---
> > drivers/platform/x86/msi-wmi-platform.c | 123 +++++++++++++++++++++++-
> > 1 file changed, 122 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
> > index 7dafe17d4d6be..a917db0300c06 100644
> > --- a/drivers/platform/x86/msi-wmi-platform.c
> > +++ b/drivers/platform/x86/msi-wmi-platform.c
> > @@ -123,16 +123,25 @@ struct msi_wmi_platform_quirk {
> > bool shift_mode; /* Shift mode is supported */
> > bool charge_threshold; /* Charge threshold is supported */
> > bool dual_fans; /* For devices with two hwmon fans */
> > + bool restore_curves; /* Restore factory curves on unload */
> > int pl_min; /* Minimum PLx value */
> > int pl1_max; /* Maximum PL1 value */
> > int pl2_max; /* Maximum PL2 value */
> > };
> >
> > +struct msi_wmi_platform_factory_curves {
> > + u8 cpu_fan_table[32];
> > + u8 gpu_fan_table[32];
> > + u8 cpu_temp_table[32];
> > + u8 gpu_temp_table[32];
> > +};
> > +
> > struct msi_wmi_platform_data {
> > struct wmi_device *wdev;
> > struct msi_wmi_platform_quirk *quirks;
> > struct mutex wmi_lock; /* Necessary when calling WMI methods */
> > struct device *ppdev;
> > + struct msi_wmi_platform_factory_curves factory_curves;
> > struct acpi_battery_hook battery_hook;
> > struct device *fw_attrs_dev;
> > struct kset *fw_attrs_kset;
> > @@ -219,6 +228,7 @@ static struct msi_wmi_platform_quirk quirk_gen1 = {
> > .shift_mode = true,
> > .charge_threshold = true,
> > .dual_fans = true,
> > + .restore_curves = true,
> > .pl_min = 8,
> > .pl1_max = 43,
> > .pl2_max = 45
> > @@ -227,6 +237,7 @@ static struct msi_wmi_platform_quirk quirk_gen2 = {
> > .shift_mode = true,
> > .charge_threshold = true,
> > .dual_fans = true,
> > + .restore_curves = true,
> > .pl_min = 8,
> > .pl1_max = 30,
> > .pl2_max = 37
> > @@ -507,6 +518,94 @@ static struct attribute *msi_wmi_platform_hwmon_attrs[] = {
> > };
> > ATTRIBUTE_GROUPS(msi_wmi_platform_hwmon);
> >
> > +static int msi_wmi_platform_curves_save(struct msi_wmi_platform_data *data)
> > +{
> > + int ret;
> > +
> > + data->factory_curves.cpu_fan_table[0] =
> > + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE;
> > + ret = msi_wmi_platform_query_unlocked(
> > + data, MSI_PLATFORM_GET_FAN,
> > + data->factory_curves.cpu_fan_table,
> > + sizeof(data->factory_curves.cpu_fan_table));
> > + if (ret < 0)
> > + return ret;
> > + data->factory_curves.cpu_fan_table[0] =
> > + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE;
>
> Is it necessary to set the subfeature again here (and bellow)?
The first time, it is used to prime the function so that we can get
the correct data.
However, on return, it gets replaced with a status byte (1). So we
need to set it again for it to index correctly. That needs to happen
either here, or when we send it, and I chose here.
The rest of the comments look good.
Antheas
> Also there is a lot of code repetition here, I would suggest a helper
> function. It will be optimized/inlined by the compiler anyway.
>
> > +
> > + data->factory_curves.gpu_fan_table[0] =
> > + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE;
> > + ret = msi_wmi_platform_query_unlocked(
> > + data, MSI_PLATFORM_GET_FAN,
> > + data->factory_curves.gpu_fan_table,
> > + sizeof(data->factory_curves.gpu_fan_table));
> > + if (ret < 0)
> > + return ret;
> > + data->factory_curves.gpu_fan_table[0] =
> > + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE;
> > +
> > + data->factory_curves.cpu_temp_table[0] =
> > + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE;
> > + ret = msi_wmi_platform_query_unlocked(
> > + data, MSI_PLATFORM_GET_TEMPERATURE,
> > + data->factory_curves.cpu_temp_table,
> > + sizeof(data->factory_curves.cpu_temp_table));
> > + if (ret < 0)
> > + return ret;
> > + data->factory_curves.cpu_temp_table[0] =
> > + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE;
> > +
> > + data->factory_curves.gpu_temp_table[0] =
> > + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE;
> > + ret = msi_wmi_platform_query_unlocked(
> > + data, MSI_PLATFORM_GET_TEMPERATURE,
> > + data->factory_curves.gpu_temp_table,
> > + sizeof(data->factory_curves.gpu_temp_table));
> > + if (ret < 0)
> > + return ret;
> > + data->factory_curves.gpu_temp_table[0] =
> > + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE;
> > +
> > + return 0;
> > +}
> > +
> > +static int msi_wmi_platform_curves_load(struct msi_wmi_platform_data *data)
> > +{
> > + u8 buffer[32] = { };
> > + int ret;
> > +
> > + memcpy(buffer, data->factory_curves.cpu_fan_table,
> > + sizeof(data->factory_curves.cpu_fan_table));
> > + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_FAN,
> > + buffer, sizeof(buffer));
> > + if (ret < 0)
> > + return ret;
>
> A helper for this operation would be nice too.
>
> > +
> > + memcpy(buffer, data->factory_curves.gpu_fan_table,
> > + sizeof(data->factory_curves.gpu_fan_table));
> > + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_FAN,
> > + buffer, sizeof(buffer));
> > + if (ret < 0)
> > + return ret;
> > +
> > + memcpy(buffer, data->factory_curves.cpu_temp_table,
> > + sizeof(data->factory_curves.cpu_temp_table));
> > + ret = msi_wmi_platform_query_unlocked(
> > + data, MSI_PLATFORM_SET_TEMPERATURE, buffer, sizeof(buffer));
> > + if (ret < 0)
> > + return ret;
> > +
> > + memcpy(buffer, data->factory_curves.gpu_temp_table,
> > + sizeof(data->factory_curves.gpu_temp_table));
> > + ret = msi_wmi_platform_query_unlocked(
> > + data, MSI_PLATFORM_SET_TEMPERATURE, buffer, sizeof(buffer));
> > + if (ret < 0)
> > + return ret;
> > +
> > + return 0;
> > +}
> > +
> > +
> > static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type,
> > u32 attr, int channel)
> > {
> > @@ -603,9 +702,19 @@ static int msi_wmi_platform_write(struct device *dev, enum hwmon_sensor_types ty
> > return -EINVAL;
> > }
> >
> > - return msi_wmi_platform_query_unlocked(
> > + ret = msi_wmi_platform_query_unlocked(
> > data, MSI_PLATFORM_SET_AP, buffer,
> > sizeof(buffer));
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (val == 2 && data->quirks->restore_curves) {
> > + ret = msi_wmi_platform_curves_load(data);
> > + if (ret < 0)
> > + return ret;
> > + }
> > +
> > + return 0;
> > default:
> > return -EOPNOTSUPP;
> > }
> > @@ -1373,6 +1482,13 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
> >
> > msi_wmi_platform_profile_setup(data);
> >
> > + if (data->quirks->restore_curves) {
> > + guard(mutex)(&data->wmi_lock);
>
> We don't need locking here. data->factory_curves is not shared until you
> register the hwmon device.
>
> > + ret = msi_wmi_platform_curves_save(data);
> > + if (ret < 0)
> > + return ret;
> > + }
> > +
> > return msi_wmi_platform_hwmon_init(data);
> > }
> >
> > @@ -1382,6 +1498,11 @@ static void msi_wmi_platform_remove(struct wmi_device *wdev)
> >
> > if (data->quirks->charge_threshold)
> > battery_hook_unregister(&data->battery_hook);
> > +
> > + if (data->quirks->restore_curves) {
> > + guard(mutex)(&data->wmi_lock);
>
> We can avoid locking here by adding a devm action that restores the
> curves. devm resources are unloaded in LIFO order.
>
> Please, also check my comments on [Patch 2]. I don't think that patch is
> needed.
>
> --
> ~ Kurt
>
> > + msi_wmi_platform_curves_load(data);
> > + }
> > }
> >
> > static const struct wmi_device_id msi_wmi_platform_id_table[] = {
>
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting
2025-05-11 20:44 [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Antheas Kapenekakis
` (9 preceding siblings ...)
2025-05-11 20:44 ` [PATCH v1 10/10] platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and unload Antheas Kapenekakis
@ 2025-05-11 23:30 ` Kurt Borja
2025-05-12 10:16 ` Antheas Kapenekakis
2025-05-19 2:37 ` Armin Wolf
11 siblings, 1 reply; 45+ messages in thread
From: Kurt Borja @ 2025-05-11 23:30 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Armin Wolf, Jonathan Corbet, Hans de Goede, Ilpo Järvinen,
Jean Delvare, Guenter Roeck, linux-doc, linux-kernel, linux-hwmon
[-- Attachment #1: Type: text/plain, Size: 2977 bytes --]
Hi Antheas,
On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> This draft patch series brings into parity the msi-wmi-platform driver with
> the MSI Center M Windows application for the MSI Claw (all models).
> Unfortunately, MSI Center M and this interface do not have a discovery API,
> necessitating the introduction of a quirk system.
>
> While this patch series is fully functional and tested, there are still
> some issues that need to be addressed:
> - Armin notes we need to disable fan curve support by default and quirk
> it as well, as it is not supported on all models. However, the way
> PWM enable ops work, this makes it a bit difficult, so I would like
> some suggestions on how to rework this.
If I understood the question correctly, then you should control the
visibility of all "curve" related attributes with the quirk.
The custom hwmon attribute_group has an is_visible callback, and so do
the hwmon_ops.
> - It turns out that to fully disable the fan curve, we have to restore
> the default fan values. This is also what is done on the OEM software.
> For this, the last patch in the series is used, which is a bit dirty.
I have a couple questions about this.
* What are the default fan curves? Can these be statically defined?
* Are user-defined fan curves persistent between reboots?
I have some doubts about the approach you took on the last patch, but I
want to understand how the platform works first.
>
> Sleep was tested with all values being preserved during S0iX (platform
> profile, fan curve, PL1/PL2), so we do not need suspend/resume hooks, at
> least for the Claw devices.
>
> For PL1/PL2, we use firmware-attributes. So for that I +cc Kurt since if
> his new high level interface is merged beforehand, we can use that instead.
Hopefully!
--
~ Kurt
>
> Antheas Kapenekakis (8):
> platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
> platform/x86: msi-wmi-platform: Add quirk system
> platform/x86: msi-wmi-platform: Add platform profile through shift
> mode
> platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware
> attributes
> platform/x86: msi-wmi-platform: Add charge_threshold support
> platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices
> platform/x86: msi-wmi-platform: Update header text
> platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and
> unload
>
> Armin Wolf (2):
> platform/x86: msi-wmi-platform: Use input buffer for returning result
> platform/x86: msi-wmi-platform: Add support for fan control
>
> .../wmi/devices/msi-wmi-platform.rst | 26 +
> drivers/platform/x86/Kconfig | 3 +
> drivers/platform/x86/msi-wmi-platform.c | 1181 ++++++++++++++++-
> 3 files changed, 1156 insertions(+), 54 deletions(-)
>
>
> base-commit: 62b1dcf2e7af3dc2879d1a39bf6823c99486a8c2
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting
2025-05-11 23:30 ` [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Kurt Borja
@ 2025-05-12 10:16 ` Antheas Kapenekakis
2025-05-12 19:05 ` Kurt Borja
0 siblings, 1 reply; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-12 10:16 UTC (permalink / raw)
To: Kurt Borja
Cc: platform-driver-x86, Armin Wolf, Jonathan Corbet, Hans de Goede,
Ilpo Järvinen, Jean Delvare, Guenter Roeck, linux-doc,
linux-kernel, linux-hwmon
On Mon, 12 May 2025 at 01:30, Kurt Borja <kuurtb@gmail.com> wrote:
>
> Hi Antheas,
>
> On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
> > This draft patch series brings into parity the msi-wmi-platform driver with
> > the MSI Center M Windows application for the MSI Claw (all models).
> > Unfortunately, MSI Center M and this interface do not have a discovery API,
> > necessitating the introduction of a quirk system.
> >
> > While this patch series is fully functional and tested, there are still
> > some issues that need to be addressed:
> > - Armin notes we need to disable fan curve support by default and quirk
> > it as well, as it is not supported on all models. However, the way
> > PWM enable ops work, this makes it a bit difficult, so I would like
> > some suggestions on how to rework this.
>
> If I understood the question correctly, then you should control the
> visibility of all "curve" related attributes with the quirk.
Yep, this is what I was wondering. I will investigate this. It would
be good to get some comments on the quirk naming as well.
> The custom hwmon attribute_group has an is_visible callback, and so do
> the hwmon_ops.
>
> > - It turns out that to fully disable the fan curve, we have to restore
> > the default fan values. This is also what is done on the OEM software.
> > For this, the last patch in the series is used, which is a bit dirty.
>
> I have a couple questions about this.
>
> * What are the default fan curves? Can these be statically defined?
> * Are user-defined fan curves persistent between reboots?
>
> I have some doubts about the approach you took on the last patch, but I
> want to understand how the platform works first.
So do I. Essentially here is how the Windows software works: when it
first opens, it saves the current curve in Windows registry. Then,
when the user sets a fan curve, it applies it in the same way we do
here and sets a bit in AP. When the custom curve is removed, it unsets
that bit and restores the original curve in WMI.
The logical reasoning would be that that bit controls the fan curve.
This is how it is named in the software. However, when setting that
bit on its own, it seems to only partially affect the fan curve. E.g.,
when the fan curve is 100% in all points, unsetting that bit makes it
go down to 50% when no load occurs. When using the default fan curve,
it goes to 0%. Therefore, it seems like that bit makes the fan curve
semi-autonomous?
The fan curve seems to be hardware specific and resets after reboots.
So a straightforward way to get it is to grab it on a fresh boot.
Antheas
> >
> > Sleep was tested with all values being preserved during S0iX (platform
> > profile, fan curve, PL1/PL2), so we do not need suspend/resume hooks, at
> > least for the Claw devices.
> >
> > For PL1/PL2, we use firmware-attributes. So for that I +cc Kurt since if
> > his new high level interface is merged beforehand, we can use that instead.
>
> Hopefully!
>
> --
> ~ Kurt
>
> >
> > Antheas Kapenekakis (8):
> > platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
> > platform/x86: msi-wmi-platform: Add quirk system
> > platform/x86: msi-wmi-platform: Add platform profile through shift
> > mode
> > platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware
> > attributes
> > platform/x86: msi-wmi-platform: Add charge_threshold support
> > platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices
> > platform/x86: msi-wmi-platform: Update header text
> > platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and
> > unload
> >
> > Armin Wolf (2):
> > platform/x86: msi-wmi-platform: Use input buffer for returning result
> > platform/x86: msi-wmi-platform: Add support for fan control
> >
> > .../wmi/devices/msi-wmi-platform.rst | 26 +
> > drivers/platform/x86/Kconfig | 3 +
> > drivers/platform/x86/msi-wmi-platform.c | 1181 ++++++++++++++++-
> > 3 files changed, 1156 insertions(+), 54 deletions(-)
> >
> >
> > base-commit: 62b1dcf2e7af3dc2879d1a39bf6823c99486a8c2
>
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting
2025-05-12 10:16 ` Antheas Kapenekakis
@ 2025-05-12 19:05 ` Kurt Borja
0 siblings, 0 replies; 45+ messages in thread
From: Kurt Borja @ 2025-05-12 19:05 UTC (permalink / raw)
To: Antheas Kapenekakis
Cc: platform-driver-x86, Armin Wolf, Jonathan Corbet, Hans de Goede,
Ilpo Järvinen, Jean Delvare, Guenter Roeck, linux-doc,
linux-kernel, linux-hwmon
[-- Attachment #1: Type: text/plain, Size: 5139 bytes --]
On Mon May 12, 2025 at 7:16 AM -03, Antheas Kapenekakis wrote:
> On Mon, 12 May 2025 at 01:30, Kurt Borja <kuurtb@gmail.com> wrote:
>>
>> Hi Antheas,
>>
>> On Sun May 11, 2025 at 5:44 PM -03, Antheas Kapenekakis wrote:
>> > This draft patch series brings into parity the msi-wmi-platform driver with
>> > the MSI Center M Windows application for the MSI Claw (all models).
>> > Unfortunately, MSI Center M and this interface do not have a discovery API,
>> > necessitating the introduction of a quirk system.
>> >
>> > While this patch series is fully functional and tested, there are still
>> > some issues that need to be addressed:
>> > - Armin notes we need to disable fan curve support by default and quirk
>> > it as well, as it is not supported on all models. However, the way
>> > PWM enable ops work, this makes it a bit difficult, so I would like
>> > some suggestions on how to rework this.
>>
>> If I understood the question correctly, then you should control the
>> visibility of all "curve" related attributes with the quirk.
>
> Yep, this is what I was wondering. I will investigate this. It would
> be good to get some comments on the quirk naming as well.
You can check [1] for an example of the hwmon visibility. It's a similar
problem because some models have 2 fans and others have 4. In the
alienware-wmi driver we also have custom hwmon attributes, see [2] for
how to handle visibility with those.
I would personally just name it fan_curve or has_fan_curve.
>
>> The custom hwmon attribute_group has an is_visible callback, and so do
>> the hwmon_ops.
>>
>> > - It turns out that to fully disable the fan curve, we have to restore
>> > the default fan values. This is also what is done on the OEM software.
>> > For this, the last patch in the series is used, which is a bit dirty.
>>
>> I have a couple questions about this.
>>
>> * What are the default fan curves? Can these be statically defined?
>> * Are user-defined fan curves persistent between reboots?
>>
>> I have some doubts about the approach you took on the last patch, but I
>> want to understand how the platform works first.
>
> So do I. Essentially here is how the Windows software works: when it
> first opens, it saves the current curve in Windows registry. Then,
> when the user sets a fan curve, it applies it in the same way we do
> here and sets a bit in AP. When the custom curve is removed, it unsets
> that bit and restores the original curve in WMI.
>
> The logical reasoning would be that that bit controls the fan curve.
> This is how it is named in the software. However, when setting that
> bit on its own, it seems to only partially affect the fan curve. E.g.,
> when the fan curve is 100% in all points, unsetting that bit makes it
> go down to 50% when no load occurs. When using the default fan curve,
> it goes to 0%. Therefore, it seems like that bit makes the fan curve
> semi-autonomous?
>
> The fan curve seems to be hardware specific and resets after reboots.
> So a straightforward way to get it is to grab it on a fresh boot.
Oh - this is interesting. Then I think it is the right approach. I'll
add a couple more comments.
>
> Antheas
>
>> >
>> > Sleep was tested with all values being preserved during S0iX (platform
>> > profile, fan curve, PL1/PL2), so we do not need suspend/resume hooks, at
>> > least for the Claw devices.
>> >
>> > For PL1/PL2, we use firmware-attributes. So for that I +cc Kurt since if
>> > his new high level interface is merged beforehand, we can use that instead.
>>
>> Hopefully!
>>
>> --
>> ~ Kurt
>>
>> >
>> > Antheas Kapenekakis (8):
>> > platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
>> > platform/x86: msi-wmi-platform: Add quirk system
>> > platform/x86: msi-wmi-platform: Add platform profile through shift
>> > mode
>> > platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware
>> > attributes
>> > platform/x86: msi-wmi-platform: Add charge_threshold support
>> > platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices
>> > platform/x86: msi-wmi-platform: Update header text
>> > platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and
>> > unload
>> >
>> > Armin Wolf (2):
>> > platform/x86: msi-wmi-platform: Use input buffer for returning result
>> > platform/x86: msi-wmi-platform: Add support for fan control
>> >
>> > .../wmi/devices/msi-wmi-platform.rst | 26 +
>> > drivers/platform/x86/Kconfig | 3 +
>> > drivers/platform/x86/msi-wmi-platform.c | 1181 ++++++++++++++++-
>> > 3 files changed, 1156 insertions(+), 54 deletions(-)
>> >
>> >
>> > base-commit: 62b1dcf2e7af3dc2879d1a39bf6823c99486a8c2
>>
[1] https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git/tree/drivers/platform/x86/dell/alienware-wmi-wmax.c?h=for-next#n742
[2] https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git/tree/drivers/platform/x86/dell/alienware-wmi-wmax.c?h=for-next#n942
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting
2025-05-11 20:44 [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Antheas Kapenekakis
` (10 preceding siblings ...)
2025-05-11 23:30 ` [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting Kurt Borja
@ 2025-05-19 2:37 ` Armin Wolf
2025-05-30 20:50 ` Antheas Kapenekakis
11 siblings, 1 reply; 45+ messages in thread
From: Armin Wolf @ 2025-05-19 2:37 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: Jonathan Corbet, Hans de Goede, Ilpo Järvinen, Jean Delvare,
Guenter Roeck, Kurt Borja, linux-doc, linux-kernel, linux-hwmon
Am 11.05.25 um 22:44 schrieb Antheas Kapenekakis:
> This draft patch series brings into parity the msi-wmi-platform driver with
> the MSI Center M Windows application for the MSI Claw (all models).
> Unfortunately, MSI Center M and this interface do not have a discovery API,
> necessitating the introduction of a quirk system.
>
> While this patch series is fully functional and tested, there are still
> some issues that need to be addressed:
> - Armin notes we need to disable fan curve support by default and quirk
> it as well, as it is not supported on all models. However, the way
> PWM enable ops work, this makes it a bit difficult, so I would like
> some suggestions on how to rework this.
> - It turns out that to fully disable the fan curve, we have to restore
> the default fan values. This is also what is done on the OEM software.
> For this, the last patch in the series is used, which is a bit dirty.
>
> Sleep was tested with all values being preserved during S0iX (platform
> profile, fan curve, PL1/PL2), so we do not need suspend/resume hooks, at
> least for the Claw devices.
>
> For PL1/PL2, we use firmware-attributes. So for that I +cc Kurt since if
> his new high level interface is merged beforehand, we can use that instead.
Overall the patch series looks promising, however the suspend/resume handling
and the quirk system still needs some work.
If you wish i can provide you with a patch for the EC-based quirk system. You
can then structure your exiting patches around that.
Thanks,
Armin Wolf
> Antheas Kapenekakis (8):
> platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
> platform/x86: msi-wmi-platform: Add quirk system
> platform/x86: msi-wmi-platform: Add platform profile through shift
> mode
> platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware
> attributes
> platform/x86: msi-wmi-platform: Add charge_threshold support
> platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices
> platform/x86: msi-wmi-platform: Update header text
> platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and
> unload
>
> Armin Wolf (2):
> platform/x86: msi-wmi-platform: Use input buffer for returning result
> platform/x86: msi-wmi-platform: Add support for fan control
>
> .../wmi/devices/msi-wmi-platform.rst | 26 +
> drivers/platform/x86/Kconfig | 3 +
> drivers/platform/x86/msi-wmi-platform.c | 1181 ++++++++++++++++-
> 3 files changed, 1156 insertions(+), 54 deletions(-)
>
>
> base-commit: 62b1dcf2e7af3dc2879d1a39bf6823c99486a8c2
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting
2025-05-19 2:37 ` Armin Wolf
@ 2025-05-30 20:50 ` Antheas Kapenekakis
2025-05-30 21:15 ` Armin Wolf
0 siblings, 1 reply; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-30 20:50 UTC (permalink / raw)
To: Armin Wolf
Cc: platform-driver-x86, Jonathan Corbet, Hans de Goede,
Ilpo Järvinen, Jean Delvare, Guenter Roeck, Kurt Borja,
linux-doc, linux-kernel, linux-hwmon
On Mon, 19 May 2025 at 04:38, Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 11.05.25 um 22:44 schrieb Antheas Kapenekakis:
>
> > This draft patch series brings into parity the msi-wmi-platform driver with
> > the MSI Center M Windows application for the MSI Claw (all models).
> > Unfortunately, MSI Center M and this interface do not have a discovery API,
> > necessitating the introduction of a quirk system.
> >
> > While this patch series is fully functional and tested, there are still
> > some issues that need to be addressed:
> > - Armin notes we need to disable fan curve support by default and quirk
> > it as well, as it is not supported on all models. However, the way
> > PWM enable ops work, this makes it a bit difficult, so I would like
> > some suggestions on how to rework this.
> > - It turns out that to fully disable the fan curve, we have to restore
> > the default fan values. This is also what is done on the OEM software.
> > For this, the last patch in the series is used, which is a bit dirty.
> >
> > Sleep was tested with all values being preserved during S0iX (platform
> > profile, fan curve, PL1/PL2), so we do not need suspend/resume hooks, at
> > least for the Claw devices.
> >
> > For PL1/PL2, we use firmware-attributes. So for that I +cc Kurt since if
> > his new high level interface is merged beforehand, we can use that instead.
>
> Overall the patch series looks promising, however the suspend/resume handling
> and the quirk system still needs some work.
>
> If you wish i can provide you with a patch for the EC-based quirk system. You
> can then structure your exiting patches around that.
Hi,
Sorry I have been busy with personal life. I will try to get back to
this in 1-2 weeks.
I have three minor concerns that mirror each other with using an EC based check.
1) First is that we use boardname on the userspace side to check for
the Claw. Therefore, using the EC ID kernel side introduces a failure
point I am not very fond of. 2) Second is that collecting the IDs from
users might prove more difficult 3) userspace software from MSI uses
boardname as well.
Could we use a hybrid approach perhaps? What do you think?
Antheas
> Thanks,
> Armin Wolf
>
> > Antheas Kapenekakis (8):
> > platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
> > platform/x86: msi-wmi-platform: Add quirk system
> > platform/x86: msi-wmi-platform: Add platform profile through shift
> > mode
> > platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware
> > attributes
> > platform/x86: msi-wmi-platform: Add charge_threshold support
> > platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices
> > platform/x86: msi-wmi-platform: Update header text
> > platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and
> > unload
> >
> > Armin Wolf (2):
> > platform/x86: msi-wmi-platform: Use input buffer for returning result
> > platform/x86: msi-wmi-platform: Add support for fan control
> >
> > .../wmi/devices/msi-wmi-platform.rst | 26 +
> > drivers/platform/x86/Kconfig | 3 +
> > drivers/platform/x86/msi-wmi-platform.c | 1181 ++++++++++++++++-
> > 3 files changed, 1156 insertions(+), 54 deletions(-)
> >
> >
> > base-commit: 62b1dcf2e7af3dc2879d1a39bf6823c99486a8c2
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting
2025-05-30 20:50 ` Antheas Kapenekakis
@ 2025-05-30 21:15 ` Armin Wolf
2025-05-30 21:28 ` Antheas Kapenekakis
0 siblings, 1 reply; 45+ messages in thread
From: Armin Wolf @ 2025-05-30 21:15 UTC (permalink / raw)
To: Antheas Kapenekakis
Cc: platform-driver-x86, Jonathan Corbet, Hans de Goede,
Ilpo Järvinen, Jean Delvare, Guenter Roeck, Kurt Borja,
linux-doc, linux-kernel, linux-hwmon
Am 30.05.25 um 22:50 schrieb Antheas Kapenekakis:
> On Mon, 19 May 2025 at 04:38, Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 11.05.25 um 22:44 schrieb Antheas Kapenekakis:
>>
>>> This draft patch series brings into parity the msi-wmi-platform driver with
>>> the MSI Center M Windows application for the MSI Claw (all models).
>>> Unfortunately, MSI Center M and this interface do not have a discovery API,
>>> necessitating the introduction of a quirk system.
>>>
>>> While this patch series is fully functional and tested, there are still
>>> some issues that need to be addressed:
>>> - Armin notes we need to disable fan curve support by default and quirk
>>> it as well, as it is not supported on all models. However, the way
>>> PWM enable ops work, this makes it a bit difficult, so I would like
>>> some suggestions on how to rework this.
>>> - It turns out that to fully disable the fan curve, we have to restore
>>> the default fan values. This is also what is done on the OEM software.
>>> For this, the last patch in the series is used, which is a bit dirty.
>>>
>>> Sleep was tested with all values being preserved during S0iX (platform
>>> profile, fan curve, PL1/PL2), so we do not need suspend/resume hooks, at
>>> least for the Claw devices.
>>>
>>> For PL1/PL2, we use firmware-attributes. So for that I +cc Kurt since if
>>> his new high level interface is merged beforehand, we can use that instead.
>> Overall the patch series looks promising, however the suspend/resume handling
>> and the quirk system still needs some work.
>>
>> If you wish i can provide you with a patch for the EC-based quirk system. You
>> can then structure your exiting patches around that.
> Hi,
> Sorry I have been busy with personal life. I will try to get back to
> this in 1-2 weeks.
>
> I have three minor concerns that mirror each other with using an EC based check.
>
> 1) First is that we use boardname on the userspace side to check for
> the Claw. Therefore, using the EC ID kernel side introduces a failure
> point I am not very fond of. 2) Second is that collecting the IDs from
> users might prove more difficult 3) userspace software from MSI uses
> boardname as well.
Actually the EC ID contains the board name (among other data). I envisioned that we
rely on the board name reported by the EC instead of the board name reported over SMBIOS.
This would allow us to better support model variations that share a common board name.
Maybe we can still expose some data (EC ID, debugfs interface) even if a given board is
not whitelisted. This way users can easily retrieve the EC ID with the board name even
on unknown boards.
Thanks,
Armin Wolf
> Could we use a hybrid approach perhaps? What do you think?
>
> Antheas
>
>> Thanks,
>> Armin Wolf
>>
>>> Antheas Kapenekakis (8):
>>> platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
>>> platform/x86: msi-wmi-platform: Add quirk system
>>> platform/x86: msi-wmi-platform: Add platform profile through shift
>>> mode
>>> platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware
>>> attributes
>>> platform/x86: msi-wmi-platform: Add charge_threshold support
>>> platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices
>>> platform/x86: msi-wmi-platform: Update header text
>>> platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and
>>> unload
>>>
>>> Armin Wolf (2):
>>> platform/x86: msi-wmi-platform: Use input buffer for returning result
>>> platform/x86: msi-wmi-platform: Add support for fan control
>>>
>>> .../wmi/devices/msi-wmi-platform.rst | 26 +
>>> drivers/platform/x86/Kconfig | 3 +
>>> drivers/platform/x86/msi-wmi-platform.c | 1181 ++++++++++++++++-
>>> 3 files changed, 1156 insertions(+), 54 deletions(-)
>>>
>>>
>>> base-commit: 62b1dcf2e7af3dc2879d1a39bf6823c99486a8c2
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting
2025-05-30 21:15 ` Armin Wolf
@ 2025-05-30 21:28 ` Antheas Kapenekakis
2025-05-30 22:00 ` Armin Wolf
0 siblings, 1 reply; 45+ messages in thread
From: Antheas Kapenekakis @ 2025-05-30 21:28 UTC (permalink / raw)
To: Armin Wolf
Cc: platform-driver-x86, Jonathan Corbet, Hans de Goede,
Ilpo Järvinen, Jean Delvare, Guenter Roeck, Kurt Borja,
linux-doc, linux-kernel, linux-hwmon
On Fri, 30 May 2025 at 23:16, Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 30.05.25 um 22:50 schrieb Antheas Kapenekakis:
>
> > On Mon, 19 May 2025 at 04:38, Armin Wolf <W_Armin@gmx.de> wrote:
> >> Am 11.05.25 um 22:44 schrieb Antheas Kapenekakis:
> >>
> >>> This draft patch series brings into parity the msi-wmi-platform driver with
> >>> the MSI Center M Windows application for the MSI Claw (all models).
> >>> Unfortunately, MSI Center M and this interface do not have a discovery API,
> >>> necessitating the introduction of a quirk system.
> >>>
> >>> While this patch series is fully functional and tested, there are still
> >>> some issues that need to be addressed:
> >>> - Armin notes we need to disable fan curve support by default and quirk
> >>> it as well, as it is not supported on all models. However, the way
> >>> PWM enable ops work, this makes it a bit difficult, so I would like
> >>> some suggestions on how to rework this.
> >>> - It turns out that to fully disable the fan curve, we have to restore
> >>> the default fan values. This is also what is done on the OEM software.
> >>> For this, the last patch in the series is used, which is a bit dirty.
> >>>
> >>> Sleep was tested with all values being preserved during S0iX (platform
> >>> profile, fan curve, PL1/PL2), so we do not need suspend/resume hooks, at
> >>> least for the Claw devices.
> >>>
> >>> For PL1/PL2, we use firmware-attributes. So for that I +cc Kurt since if
> >>> his new high level interface is merged beforehand, we can use that instead.
> >> Overall the patch series looks promising, however the suspend/resume handling
> >> and the quirk system still needs some work.
> >>
> >> If you wish i can provide you with a patch for the EC-based quirk system. You
> >> can then structure your exiting patches around that.
> > Hi,
> > Sorry I have been busy with personal life. I will try to get back to
> > this in 1-2 weeks.
> >
> > I have three minor concerns that mirror each other with using an EC based check.
> >
> > 1) First is that we use boardname on the userspace side to check for
> > the Claw. Therefore, using the EC ID kernel side introduces a failure
> > point I am not very fond of. 2) Second is that collecting the IDs from
> > users might prove more difficult 3) userspace software from MSI uses
> > boardname as well.
>
> Actually the EC ID contains the board name (among other data). I envisioned that we
> rely on the board name reported by the EC instead of the board name reported over SMBIOS.
> This would allow us to better support model variations that share a common board name.
>
> Maybe we can still expose some data (EC ID, debugfs interface) even if a given board is
> not whitelisted. This way users can easily retrieve the EC ID with the board name even
> on unknown boards.
Would a hybrid approach be an option perhaps?
In my mind, Id say an info message in dmesg if the board is not
supported should be enough. That's what MSI-EC does. Are there any
other platform drivers that bind to EC ID?
Antheas
> Thanks,
> Armin Wolf
>
> > Could we use a hybrid approach perhaps? What do you think?
> >
> > Antheas
> >
> >> Thanks,
> >> Armin Wolf
> >>
> >>> Antheas Kapenekakis (8):
> >>> platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
> >>> platform/x86: msi-wmi-platform: Add quirk system
> >>> platform/x86: msi-wmi-platform: Add platform profile through shift
> >>> mode
> >>> platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware
> >>> attributes
> >>> platform/x86: msi-wmi-platform: Add charge_threshold support
> >>> platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices
> >>> platform/x86: msi-wmi-platform: Update header text
> >>> platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and
> >>> unload
> >>>
> >>> Armin Wolf (2):
> >>> platform/x86: msi-wmi-platform: Use input buffer for returning result
> >>> platform/x86: msi-wmi-platform: Add support for fan control
> >>>
> >>> .../wmi/devices/msi-wmi-platform.rst | 26 +
> >>> drivers/platform/x86/Kconfig | 3 +
> >>> drivers/platform/x86/msi-wmi-platform.c | 1181 ++++++++++++++++-
> >>> 3 files changed, 1156 insertions(+), 54 deletions(-)
> >>>
> >>>
> >>> base-commit: 62b1dcf2e7af3dc2879d1a39bf6823c99486a8c2
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 00/10] platform/x86: msi-wmi-platform: Add fan curves/platform profile/tdp/battery limiting
2025-05-30 21:28 ` Antheas Kapenekakis
@ 2025-05-30 22:00 ` Armin Wolf
0 siblings, 0 replies; 45+ messages in thread
From: Armin Wolf @ 2025-05-30 22:00 UTC (permalink / raw)
To: Antheas Kapenekakis
Cc: platform-driver-x86, Jonathan Corbet, Hans de Goede,
Ilpo Järvinen, Jean Delvare, Guenter Roeck, Kurt Borja,
linux-doc, linux-kernel, linux-hwmon
Am 30.05.25 um 23:28 schrieb Antheas Kapenekakis:
> On Fri, 30 May 2025 at 23:16, Armin Wolf <W_Armin@gmx.de> wrote:
>> Am 30.05.25 um 22:50 schrieb Antheas Kapenekakis:
>>
>>> On Mon, 19 May 2025 at 04:38, Armin Wolf <W_Armin@gmx.de> wrote:
>>>> Am 11.05.25 um 22:44 schrieb Antheas Kapenekakis:
>>>>
>>>>> This draft patch series brings into parity the msi-wmi-platform driver with
>>>>> the MSI Center M Windows application for the MSI Claw (all models).
>>>>> Unfortunately, MSI Center M and this interface do not have a discovery API,
>>>>> necessitating the introduction of a quirk system.
>>>>>
>>>>> While this patch series is fully functional and tested, there are still
>>>>> some issues that need to be addressed:
>>>>> - Armin notes we need to disable fan curve support by default and quirk
>>>>> it as well, as it is not supported on all models. However, the way
>>>>> PWM enable ops work, this makes it a bit difficult, so I would like
>>>>> some suggestions on how to rework this.
>>>>> - It turns out that to fully disable the fan curve, we have to restore
>>>>> the default fan values. This is also what is done on the OEM software.
>>>>> For this, the last patch in the series is used, which is a bit dirty.
>>>>>
>>>>> Sleep was tested with all values being preserved during S0iX (platform
>>>>> profile, fan curve, PL1/PL2), so we do not need suspend/resume hooks, at
>>>>> least for the Claw devices.
>>>>>
>>>>> For PL1/PL2, we use firmware-attributes. So for that I +cc Kurt since if
>>>>> his new high level interface is merged beforehand, we can use that instead.
>>>> Overall the patch series looks promising, however the suspend/resume handling
>>>> and the quirk system still needs some work.
>>>>
>>>> If you wish i can provide you with a patch for the EC-based quirk system. You
>>>> can then structure your exiting patches around that.
>>> Hi,
>>> Sorry I have been busy with personal life. I will try to get back to
>>> this in 1-2 weeks.
>>>
>>> I have three minor concerns that mirror each other with using an EC based check.
>>>
>>> 1) First is that we use boardname on the userspace side to check for
>>> the Claw. Therefore, using the EC ID kernel side introduces a failure
>>> point I am not very fond of. 2) Second is that collecting the IDs from
>>> users might prove more difficult 3) userspace software from MSI uses
>>> boardname as well.
>> Actually the EC ID contains the board name (among other data). I envisioned that we
>> rely on the board name reported by the EC instead of the board name reported over SMBIOS.
>> This would allow us to better support model variations that share a common board name.
>>
>> Maybe we can still expose some data (EC ID, debugfs interface) even if a given board is
>> not whitelisted. This way users can easily retrieve the EC ID with the board name even
>> on unknown boards.
> Would a hybrid approach be an option perhaps?
>
> In my mind, Id say an info message in dmesg if the board is not
> supported should be enough. That's what MSI-EC does. Are there any
> other platform drivers that bind to EC ID?
>
> Antheas
How would such an hybrid approach work? AFAIK the board name inside the EC ID is the same as
the board name reported over DMI, so checking both seems unnecessary to me.
I know that msi-ec uses the full EC ID to match supported boards, but i think we only need the
four-character board name.
Thanks,
Armin Wolf
>> Thanks,
>> Armin Wolf
>>
>>> Could we use a hybrid approach perhaps? What do you think?
>>>
>>> Antheas
>>>
>>>> Thanks,
>>>> Armin Wolf
>>>>
>>>>> Antheas Kapenekakis (8):
>>>>> platform/x86: msi-wmi-platform: Add unlocked msi_wmi_platform_query
>>>>> platform/x86: msi-wmi-platform: Add quirk system
>>>>> platform/x86: msi-wmi-platform: Add platform profile through shift
>>>>> mode
>>>>> platform/x86: msi-wmi-platform: Add PL1/PL2 support via firmware
>>>>> attributes
>>>>> platform/x86: msi-wmi-platform: Add charge_threshold support
>>>>> platform/x86: msi-wmi-platform: Drop excess fans in dual fan devices
>>>>> platform/x86: msi-wmi-platform: Update header text
>>>>> platform/x86: msi-wmi-platform: Restore fan curves on PWM disable and
>>>>> unload
>>>>>
>>>>> Armin Wolf (2):
>>>>> platform/x86: msi-wmi-platform: Use input buffer for returning result
>>>>> platform/x86: msi-wmi-platform: Add support for fan control
>>>>>
>>>>> .../wmi/devices/msi-wmi-platform.rst | 26 +
>>>>> drivers/platform/x86/Kconfig | 3 +
>>>>> drivers/platform/x86/msi-wmi-platform.c | 1181 ++++++++++++++++-
>>>>> 3 files changed, 1156 insertions(+), 54 deletions(-)
>>>>>
>>>>>
>>>>> base-commit: 62b1dcf2e7af3dc2879d1a39bf6823c99486a8c2
^ permalink raw reply [flat|nested] 45+ messages in thread