Message ID | 20241007093324.49631-3-kuurtb@gmail.com (mailing list archive) |
---|---|
State | Changes Requested, archived |
Headers | show |
Series | Dell AWCC platform_profile support | expand |
Am 07.10.24 um 11:33 schrieb Kurt Borja: > This patch adds platform_profile support for Dell devices which implement > User Selectable Thermal Tables (USTT) that are meant to be controlled by > Alienware Command Center (AWCC). These devices may include newer Alienware > M-Series, Alienware X-Series and Dell's G-Series. This patch, was tested > by me on an Alienware x15 R1. > > It is suspected that Alienware Command Center manages thermal profiles > through the WMI interface, specifically through a device with identifier > \_SB_.AMW1.WMAX. This device was reverse engineered and the relevant > functionality is documented here [1]. This driver interacts with this > WMI device and thus is able to mimic AWCC's thermal profiles functionality > through the platform_profile API. In consequence the user would be able > to set and retrieve thermal profiles, which are just fan speed profiles. > > This driver was heavily inspired on inspur_platform_profile, special > thanks. > > Notes: > - Performance (FullSpeed) profile is a special profile which has it's own > entry in the Firmware Settings of the Alienware x15 R1. It also changes > the color of the F1 key. I suspect this behavior would be replicated in > other X-Series or M-Series laptops. > - G-Mode is a profile documented on [1] which mimics the behavior of > FullSpeed mode but it does not have an entry on the Firmware Settings of > the Alienware x15 R1, this may correspond to the G-Mode functionality on > G-Series laptops (activated by a special button) but I cannot test it. I > did not include this code in the driver as G-Mode causes unexpected > behavior on X-Series laptops. > > Thanks for your time and patiente in advance. > > Regards, > > Kurt > > [1] https://gist.github.com/kuu-rt/b22328ff2b454be505387e2a38c61ee4 Hi, this WMI device is already handled by the alienware-wmi driver. Could you please integrate this functionality into this driver instead of creating a new one? Thanks, Armin Wolf > Signed-off-by: Kurt Borja <kuurtb@gmail.com> > --- > drivers/platform/x86/dell/Kconfig | 9 + > drivers/platform/x86/dell/Makefile | 1 + > drivers/platform/x86/dell/dell-wmi-awcc.c | 204 ++++++++++++++++++++++ > 3 files changed, 214 insertions(+) > create mode 100644 drivers/platform/x86/dell/dell-wmi-awcc.c > > diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig > index 68a49788a..20300ff98 100644 > --- a/drivers/platform/x86/dell/Kconfig > +++ b/drivers/platform/x86/dell/Kconfig > @@ -27,6 +27,15 @@ config ALIENWARE_WMI > zones on Alienware machines that don't contain a dedicated AlienFX > USB MCU such as the X51 and X51-R2. > > +config AWCC_PLATFORM_PROFILE > + tristate "AWCC Platform Profile support" > + depends on ACPI_WMI > + select ACPI_PLATFORM_PROFILE > + help > + This driver provides platform_profile support for selecting thermal > + profiles on Dell devices with User Selectable Thermal Tables, > + controlled by AWCC's WMI interface. > + > config DCDBAS > tristate "Dell Systems Management Base Driver" > default m > diff --git a/drivers/platform/x86/dell/Makefile b/drivers/platform/x86/dell/Makefile > index 79d60f1bf..bfef99580 100644 > --- a/drivers/platform/x86/dell/Makefile > +++ b/drivers/platform/x86/dell/Makefile > @@ -23,4 +23,5 @@ obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o > obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o > obj-$(CONFIG_DELL_WMI_DDV) += dell-wmi-ddv.o > obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o > +obj-$(CONFIG_AWCC_PLATFORM_PROFILE) += dell-wmi-awcc.o > obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/ > diff --git a/drivers/platform/x86/dell/dell-wmi-awcc.c b/drivers/platform/x86/dell/dell-wmi-awcc.c > new file mode 100644 > index 000000000..0837d1bc6 > --- /dev/null > +++ b/drivers/platform/x86/dell/dell-wmi-awcc.c > @@ -0,0 +1,204 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * WMI driver for Dell's AWCC platform_profile > + * > + * Copyright (c) Kurt Borja <kuurtb@gmail.com> > + * > + */ > + > +#include <linux/acpi.h> > +#include <linux/device.h> > +#include <linux/module.h> > +#include <linux/platform_profile.h> > +#include <linux/wmi.h> > + > +#define PROF_TO_ARG(mode) ((mode << 8) | 1) > + > +#define DELL_AWCC_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" > + > +enum awcc_wmi_method { > + AWCC_WMI_THERMAL_INFORMATION = 0x14, > + AWCC_WMI_THERMAL_CONTROL = 0x15, > +}; > + > +enum awcc_tmp_profile { > + AWCC_TMP_PROFILE_BALANCED = 0xA0, > + AWCC_TMP_PROFILE_BALANCED_PERFORMANCE = 0xA1, > + AWCC_TMP_PROFILE_COOL = 0xA2, > + AWCC_TMP_PROFILE_QUIET = 0xA3, > + AWCC_TMP_PROFILE_PERFORMANCE = 0xA4, > + AWCC_TMP_PROFILE_LOW_POWER = 0xA5, > +}; > + > +struct awcc_wmi_priv { > + struct wmi_device *wdev; > + struct platform_profile_handler handler; > +}; > + > +static int awcc_wmi_query(struct wmi_device *wdev, enum awcc_wmi_method method, > + u32 arg, u32 *res) > +{ > + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; > + const struct acpi_buffer in = { sizeof(arg), &arg }; > + union acpi_object *obj; > + acpi_status status; > + int ret = 0; > + > + status = wmidev_evaluate_method(wdev, 0x0, method, &in, &out); > + > + if (ACPI_FAILURE(status)) > + return -EIO; > + > + obj = out.pointer; > + if (!obj) > + return -ENODATA; > + > + if (obj->type != ACPI_TYPE_INTEGER) { > + ret = -EINVAL; > + goto out_free; > + } > + > + if (obj->integer.value <= U32_MAX) > + *res = (u32)obj->integer.value; > + else > + ret = -ERANGE; > + > +out_free: > + kfree(obj); > + > + return ret; > +} > + > +static int awcc_platform_profile_get(struct platform_profile_handler *pprof, > + enum platform_profile_option *profile) > +{ > + struct awcc_wmi_priv *priv = > + container_of(pprof, struct awcc_wmi_priv, handler); > + > + u32 res; > + int ret; > + > + ret = awcc_wmi_query(priv->wdev, AWCC_WMI_THERMAL_INFORMATION, 0x0B, > + &res); > + > + if (ret < 0) > + return ret; > + > + if (res < 0) > + return -EBADRQC; > + > + switch (res) { > + case AWCC_TMP_PROFILE_LOW_POWER: > + *profile = PLATFORM_PROFILE_LOW_POWER; > + break; > + case AWCC_TMP_PROFILE_QUIET: > + *profile = PLATFORM_PROFILE_QUIET; > + break; > + case AWCC_TMP_PROFILE_BALANCED: > + *profile = PLATFORM_PROFILE_BALANCED; > + break; > + case AWCC_TMP_PROFILE_BALANCED_PERFORMANCE: > + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE; > + break; > + case AWCC_TMP_PROFILE_PERFORMANCE: > + *profile = PLATFORM_PROFILE_PERFORMANCE; > + break; > + default: > + return -ENODATA; > + } > + > + return 0; > +} > + > +static int awcc_platform_profile_set(struct platform_profile_handler *pprof, > + enum platform_profile_option profile) > +{ > + struct awcc_wmi_priv *priv = > + container_of(pprof, struct awcc_wmi_priv, handler); > + > + u32 arg; > + u32 res; > + int ret; > + > + switch (profile) { > + case PLATFORM_PROFILE_LOW_POWER: > + arg = PROF_TO_ARG(AWCC_TMP_PROFILE_LOW_POWER); > + break; > + case PLATFORM_PROFILE_QUIET: > + arg = PROF_TO_ARG(AWCC_TMP_PROFILE_QUIET); > + break; > + case PLATFORM_PROFILE_BALANCED: > + arg = PROF_TO_ARG(AWCC_TMP_PROFILE_BALANCED); > + break; > + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: > + arg = PROF_TO_ARG(AWCC_TMP_PROFILE_BALANCED_PERFORMANCE); > + break; > + case PLATFORM_PROFILE_PERFORMANCE: > + arg = PROF_TO_ARG(AWCC_TMP_PROFILE_PERFORMANCE); > + break; > + default: > + return -EOPNOTSUPP; > + } > + > + ret = awcc_wmi_query(priv->wdev, AWCC_WMI_THERMAL_CONTROL, arg, &res); > + > + if (ret < 0) > + return ret; > + > + if (res < 0) > + return -EBADRQC; > + > + return 0; > +} > + > +static int awcc_wmi_probe(struct wmi_device *wdev, const void *context) > +{ > + struct awcc_wmi_priv *priv; > + > + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + priv->wdev = wdev; > + dev_set_drvdata(&wdev->dev, priv); > + > + priv->handler.profile_set = awcc_platform_profile_set; > + priv->handler.profile_get = awcc_platform_profile_get; > + > + set_bit(PLATFORM_PROFILE_LOW_POWER, priv->handler.choices); > + set_bit(PLATFORM_PROFILE_QUIET, priv->handler.choices); > + set_bit(PLATFORM_PROFILE_BALANCED, priv->handler.choices); > + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, priv->handler.choices); > + set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->handler.choices); > + > + return platform_profile_register(&priv->handler); > +} > + > +static void awcc_wmi_remove(struct wmi_device *wdev) > +{ > + platform_profile_remove(); > +} > + > +static const struct wmi_device_id awcc_wmi_id_table[] = { > + { .guid_string = DELL_AWCC_GUID }, > + {}, > +}; > + > +MODULE_DEVICE_TABLE(wmi, awcc_wmi_id_table); > + > +static struct wmi_driver awcc_wmi_driver = { > + .driver = { > + .name = "dell-wmi-awcc-platform-profile", > + .probe_type = PROBE_PREFER_ASYNCHRONOUS, > + }, > + .id_table = awcc_wmi_id_table, > + .probe = awcc_wmi_probe, > + .remove = awcc_wmi_remove, > + .no_singleton = true, > +}; > + > +module_wmi_driver(awcc_wmi_driver); > + > +MODULE_AUTHOR("Kurt Borja"); > +MODULE_DESCRIPTION("Dell AWCC WMI driver"); > +MODULE_LICENSE("GPL");
On Mon, Oct 07, 2024 at 02:24:52PM +0200, Armin Wolf wrote: > Hi, > > this WMI device is already handled by the alienware-wmi driver. Could you please integrate > this functionality into this driver instead of creating a new one? > > Thanks, > Armin Wolf Hi, Thank you for your feedback. Although they the same name and same GUID, both interfaces are very different. Alienware x15's WMAX method doesn't support any of the methods listed on alienware-wmi driver and the de-compiled MOF file on [1] which is an open source alternative to AWCC, makes me think this might be the case for various other newer models (G, M, X Series). Still I could implement it as a quirk of newer models. Would this be ok? My only worry was that it could make alienware-wmi's logic overly complex and cumbersome, as it would support two very different interfaces with the same GUID. Kurt [1] https://github.com/AlexIII/tcc-g15/blob/master/WMI-AWCC-doc.md
diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig index 68a49788a..20300ff98 100644 --- a/drivers/platform/x86/dell/Kconfig +++ b/drivers/platform/x86/dell/Kconfig @@ -27,6 +27,15 @@ config ALIENWARE_WMI zones on Alienware machines that don't contain a dedicated AlienFX USB MCU such as the X51 and X51-R2. +config AWCC_PLATFORM_PROFILE + tristate "AWCC Platform Profile support" + depends on ACPI_WMI + select ACPI_PLATFORM_PROFILE + help + This driver provides platform_profile support for selecting thermal + profiles on Dell devices with User Selectable Thermal Tables, + controlled by AWCC's WMI interface. + config DCDBAS tristate "Dell Systems Management Base Driver" default m diff --git a/drivers/platform/x86/dell/Makefile b/drivers/platform/x86/dell/Makefile index 79d60f1bf..bfef99580 100644 --- a/drivers/platform/x86/dell/Makefile +++ b/drivers/platform/x86/dell/Makefile @@ -23,4 +23,5 @@ obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o obj-$(CONFIG_DELL_WMI_DDV) += dell-wmi-ddv.o obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o +obj-$(CONFIG_AWCC_PLATFORM_PROFILE) += dell-wmi-awcc.o obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/ diff --git a/drivers/platform/x86/dell/dell-wmi-awcc.c b/drivers/platform/x86/dell/dell-wmi-awcc.c new file mode 100644 index 000000000..0837d1bc6 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-awcc.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * WMI driver for Dell's AWCC platform_profile + * + * Copyright (c) Kurt Borja <kuurtb@gmail.com> + * + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/platform_profile.h> +#include <linux/wmi.h> + +#define PROF_TO_ARG(mode) ((mode << 8) | 1) + +#define DELL_AWCC_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" + +enum awcc_wmi_method { + AWCC_WMI_THERMAL_INFORMATION = 0x14, + AWCC_WMI_THERMAL_CONTROL = 0x15, +}; + +enum awcc_tmp_profile { + AWCC_TMP_PROFILE_BALANCED = 0xA0, + AWCC_TMP_PROFILE_BALANCED_PERFORMANCE = 0xA1, + AWCC_TMP_PROFILE_COOL = 0xA2, + AWCC_TMP_PROFILE_QUIET = 0xA3, + AWCC_TMP_PROFILE_PERFORMANCE = 0xA4, + AWCC_TMP_PROFILE_LOW_POWER = 0xA5, +}; + +struct awcc_wmi_priv { + struct wmi_device *wdev; + struct platform_profile_handler handler; +}; + +static int awcc_wmi_query(struct wmi_device *wdev, enum awcc_wmi_method method, + u32 arg, u32 *res) +{ + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + const struct acpi_buffer in = { sizeof(arg), &arg }; + union acpi_object *obj; + acpi_status status; + int ret = 0; + + status = wmidev_evaluate_method(wdev, 0x0, method, &in, &out); + + if (ACPI_FAILURE(status)) + return -EIO; + + obj = out.pointer; + if (!obj) + return -ENODATA; + + if (obj->type != ACPI_TYPE_INTEGER) { + ret = -EINVAL; + goto out_free; + } + + if (obj->integer.value <= U32_MAX) + *res = (u32)obj->integer.value; + else + ret = -ERANGE; + +out_free: + kfree(obj); + + return ret; +} + +static int awcc_platform_profile_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) +{ + struct awcc_wmi_priv *priv = + container_of(pprof, struct awcc_wmi_priv, handler); + + u32 res; + int ret; + + ret = awcc_wmi_query(priv->wdev, AWCC_WMI_THERMAL_INFORMATION, 0x0B, + &res); + + if (ret < 0) + return ret; + + if (res < 0) + return -EBADRQC; + + switch (res) { + case AWCC_TMP_PROFILE_LOW_POWER: + *profile = PLATFORM_PROFILE_LOW_POWER; + break; + case AWCC_TMP_PROFILE_QUIET: + *profile = PLATFORM_PROFILE_QUIET; + break; + case AWCC_TMP_PROFILE_BALANCED: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case AWCC_TMP_PROFILE_BALANCED_PERFORMANCE: + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE; + break; + case AWCC_TMP_PROFILE_PERFORMANCE: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + default: + return -ENODATA; + } + + return 0; +} + +static int awcc_platform_profile_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) +{ + struct awcc_wmi_priv *priv = + container_of(pprof, struct awcc_wmi_priv, handler); + + u32 arg; + u32 res; + int ret; + + switch (profile) { + case PLATFORM_PROFILE_LOW_POWER: + arg = PROF_TO_ARG(AWCC_TMP_PROFILE_LOW_POWER); + break; + case PLATFORM_PROFILE_QUIET: + arg = PROF_TO_ARG(AWCC_TMP_PROFILE_QUIET); + break; + case PLATFORM_PROFILE_BALANCED: + arg = PROF_TO_ARG(AWCC_TMP_PROFILE_BALANCED); + break; + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: + arg = PROF_TO_ARG(AWCC_TMP_PROFILE_BALANCED_PERFORMANCE); + break; + case PLATFORM_PROFILE_PERFORMANCE: + arg = PROF_TO_ARG(AWCC_TMP_PROFILE_PERFORMANCE); + break; + default: + return -EOPNOTSUPP; + } + + ret = awcc_wmi_query(priv->wdev, AWCC_WMI_THERMAL_CONTROL, arg, &res); + + if (ret < 0) + return ret; + + if (res < 0) + return -EBADRQC; + + return 0; +} + +static int awcc_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct awcc_wmi_priv *priv; + + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->wdev = wdev; + dev_set_drvdata(&wdev->dev, priv); + + priv->handler.profile_set = awcc_platform_profile_set; + priv->handler.profile_get = awcc_platform_profile_get; + + set_bit(PLATFORM_PROFILE_LOW_POWER, priv->handler.choices); + set_bit(PLATFORM_PROFILE_QUIET, priv->handler.choices); + set_bit(PLATFORM_PROFILE_BALANCED, priv->handler.choices); + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, priv->handler.choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->handler.choices); + + return platform_profile_register(&priv->handler); +} + +static void awcc_wmi_remove(struct wmi_device *wdev) +{ + platform_profile_remove(); +} + +static const struct wmi_device_id awcc_wmi_id_table[] = { + { .guid_string = DELL_AWCC_GUID }, + {}, +}; + +MODULE_DEVICE_TABLE(wmi, awcc_wmi_id_table); + +static struct wmi_driver awcc_wmi_driver = { + .driver = { + .name = "dell-wmi-awcc-platform-profile", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = awcc_wmi_id_table, + .probe = awcc_wmi_probe, + .remove = awcc_wmi_remove, + .no_singleton = true, +}; + +module_wmi_driver(awcc_wmi_driver); + +MODULE_AUTHOR("Kurt Borja"); +MODULE_DESCRIPTION("Dell AWCC WMI driver"); +MODULE_LICENSE("GPL");
This patch adds platform_profile support for Dell devices which implement User Selectable Thermal Tables (USTT) that are meant to be controlled by Alienware Command Center (AWCC). These devices may include newer Alienware M-Series, Alienware X-Series and Dell's G-Series. This patch, was tested by me on an Alienware x15 R1. It is suspected that Alienware Command Center manages thermal profiles through the WMI interface, specifically through a device with identifier \_SB_.AMW1.WMAX. This device was reverse engineered and the relevant functionality is documented here [1]. This driver interacts with this WMI device and thus is able to mimic AWCC's thermal profiles functionality through the platform_profile API. In consequence the user would be able to set and retrieve thermal profiles, which are just fan speed profiles. This driver was heavily inspired on inspur_platform_profile, special thanks. Notes: - Performance (FullSpeed) profile is a special profile which has it's own entry in the Firmware Settings of the Alienware x15 R1. It also changes the color of the F1 key. I suspect this behavior would be replicated in other X-Series or M-Series laptops. - G-Mode is a profile documented on [1] which mimics the behavior of FullSpeed mode but it does not have an entry on the Firmware Settings of the Alienware x15 R1, this may correspond to the G-Mode functionality on G-Series laptops (activated by a special button) but I cannot test it. I did not include this code in the driver as G-Mode causes unexpected behavior on X-Series laptops. Thanks for your time and patiente in advance. Regards, Kurt [1] https://gist.github.com/kuu-rt/b22328ff2b454be505387e2a38c61ee4 Signed-off-by: Kurt Borja <kuurtb@gmail.com> --- drivers/platform/x86/dell/Kconfig | 9 + drivers/platform/x86/dell/Makefile | 1 + drivers/platform/x86/dell/dell-wmi-awcc.c | 204 ++++++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 drivers/platform/x86/dell/dell-wmi-awcc.c