* [PATCH v3] platform/x86: hp-wmi: Add GPU MUX switch support
@ 2026-07-09 22:05 Kürşat Abaylı
2026-07-10 9:44 ` Ilpo Järvinen
0 siblings, 1 reply; 2+ messages in thread
From: Kürşat Abaylı @ 2026-07-09 22:05 UTC (permalink / raw)
To: ilpo.jarvinen, hansg
Cc: platform-driver-x86, linux-kernel, Kürşat Abaylı
Add support for querying and switching the graphics MUX mode on HP
systems via WMI. This introduces the 'gpu_mux_mode' sysfs attribute
under the hp-wmi platform device, allowing userspace tools to check
and safely switch between available graphics modes (e.g., UMA, Hybrid,
Discrete).
The hardware capabilities mask is primarily read using the modern
128-byte System Design Data query. However, to ensure backward
compatibility with older models, a fallback mechanism is implemented.
By mirroring the behavior of the Windows Omen Gaming Hub software, if
the modern query fails but the MUX WMI endpoint (0x52) responds
successfully to a read request, the driver defaults to a standard
Hybrid + Discrete support mask (0x06).
Signed-off-by: Kürşat Abaylı <hello@kursatabayli.dev>
---
Changes in v3:
- Rebased for the for-next branch (no functional changes).
Changes in v2:
- Replaced hardcoded bitmask with GENMASK(6, 0).
- Added specific defines for MUX modes instead of using raw BIT() macros.
- Initialized arrays with {} instead of { 0 }.
- Fixed reverse xmas-tree ordering for local variable declarations.
- Removed empty lines between function calls and their return value checks.
- Split ternary operators into standard if-statements for better readability.
- Added missing <linux/array_size.h> include.
- Removed unnecessary cast in hp_wmi_set_mux_mode.
---
drivers/platform/x86/hp/hp-wmi.c | 137 +++++++++++++++++++++++++++++++
1 file changed, 137 insertions(+)
diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c
index 34c9b941bdd8..3e50eaf6570c 100644
--- a/drivers/platform/x86/hp/hp-wmi.c
+++ b/drivers/platform/x86/hp/hp-wmi.c
@@ -14,6 +14,8 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/compiler_attributes.h>
#include <linux/dmi.h>
@@ -58,6 +60,12 @@ enum hp_ec_offsets {
#define HP_FAN_SPEED_AUTOMATIC 0x00
#define HP_POWER_LIMIT_DEFAULT 0x00
#define HP_POWER_LIMIT_NO_CHANGE 0xFF
+#define HPWMI_MUX_MODE_UMA BIT(0)
+#define HPWMI_MUX_MODE_HYBRID BIT(1)
+#define HPWMI_MUX_MODE_DISCRETE BIT(2)
+#define HPWMI_MUX_MODE_OPTIMUS BIT(3)
+#define HPWMI_MUX_MODE_MASK GENMASK(6, 0)
+#define HPWMI_MUX_LEGACY_MASK (HPWMI_MUX_MODE_HYBRID | HPWMI_MUX_MODE_DISCRETE)
#define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
@@ -381,6 +389,7 @@ enum hp_wmi_commandtype {
HPWMI_POSTCODEERROR_QUERY = 0x2a,
HPWMI_SYSTEM_DEVICE_MODE = 0x40,
HPWMI_THERMAL_PROFILE_QUERY = 0x4c,
+ HPWMI_GRAPHICS_MUX_QUERY = 0x52,
};
struct victus_power_limits {
@@ -1173,12 +1182,139 @@ static int camera_shutter_input_setup(void)
return err;
}
+static const u8 mux_bitmask_map[] = {
+ [0] = HPWMI_MUX_MODE_HYBRID,
+ [1] = HPWMI_MUX_MODE_DISCRETE,
+ [2] = HPWMI_MUX_MODE_OPTIMUS,
+ [3] = HPWMI_MUX_MODE_UMA,
+};
+
+static int hp_wmi_get_mux_supported_modes(u8 *supported)
+{
+ u8 legacy_buffer[4] = {};
+ u8 buffer[128] = {};
+ u32 req_packet = 0;
+ int ret;
+
+ if (!supported)
+ return -EINVAL;
+
+ /* Try modern BIOS design data query (128-byte buffer) */
+ ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM,
+ buffer, zero_if_sup(req_packet), sizeof(buffer));
+ if (ret == 0) {
+ *supported = buffer[7];
+ return 0;
+ }
+
+ /*
+ * (Fallback): Legacy BIOS behavior based on Omen Gaming Hub.
+ * If the modern query is not supported, check if the MUX query endpoint
+ * responds to a read request. If it succeeds, the hardware has MUX
+ * capability but lacks the mode map, defaulting to Hybrid + Discrete.
+ */
+ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ,
+ legacy_buffer, sizeof(legacy_buffer), 0);
+ if (ret == 0) {
+ *supported = HPWMI_MUX_LEGACY_MASK;
+ return 0;
+ }
+
+ if (ret < 0)
+ return ret;
+ return -EINVAL;
+}
+
+static int hp_wmi_get_mux_mode(u8 *mode)
+{
+ u8 buffer[4] = {};
+ int ret;
+
+ if (!mode)
+ return -EINVAL;
+
+ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ,
+ buffer, sizeof(buffer), sizeof(buffer));
+
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ return -EINVAL;
+
+ /* Mask the highest bit, which might be used as a BIOS status flag */
+ *mode = buffer[0] & HPWMI_MUX_MODE_MASK;
+
+ return 0;
+}
+
+static int hp_wmi_set_mux_mode(u8 mode)
+{
+ u8 buffer[4] = { mode, 0x00, 0x00, 0x00 };
+ int ret;
+
+ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_WRITE,
+ buffer, sizeof(buffer), sizeof(buffer));
+
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static ssize_t gpu_mux_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u8 mode;
+ int ret;
+
+ ret = hp_wmi_get_mux_mode(&mode);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", mode);
+}
+
+static ssize_t gpu_mux_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ u32 requested;
+ u8 supported;
+ int ret;
+
+ ret = kstrtou32(buf, 0, &requested);
+ if (ret)
+ return ret;
+
+ if (requested >= ARRAY_SIZE(mux_bitmask_map))
+ return -EINVAL;
+
+ ret = hp_wmi_get_mux_supported_modes(&supported);
+ if (ret)
+ return ret;
+
+ /* Verify if the requested mode is allowed by the hardware mask */
+ if (!(supported & mux_bitmask_map[requested]))
+ return -EOPNOTSUPP;
+
+ ret = hp_wmi_set_mux_mode(requested);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
static DEVICE_ATTR_RO(display);
static DEVICE_ATTR_RO(hddtemp);
static DEVICE_ATTR_RW(als);
static DEVICE_ATTR_RO(dock);
static DEVICE_ATTR_RO(tablet);
static DEVICE_ATTR_RW(postcode);
+static DEVICE_ATTR_RW(gpu_mux_mode);
static struct attribute *hp_wmi_attrs[] = {
&dev_attr_display.attr,
@@ -1187,6 +1323,7 @@ static struct attribute *hp_wmi_attrs[] = {
&dev_attr_dock.attr,
&dev_attr_tablet.attr,
&dev_attr_postcode.attr,
+ &dev_attr_gpu_mux_mode.attr,
NULL,
};
ATTRIBUTE_GROUPS(hp_wmi);
--
2.55.0
^ permalink raw reply related [flat|nested] 2+ messages in thread* Re: [PATCH v3] platform/x86: hp-wmi: Add GPU MUX switch support
2026-07-09 22:05 [PATCH v3] platform/x86: hp-wmi: Add GPU MUX switch support Kürşat Abaylı
@ 2026-07-10 9:44 ` Ilpo Järvinen
0 siblings, 0 replies; 2+ messages in thread
From: Ilpo Järvinen @ 2026-07-10 9:44 UTC (permalink / raw)
To: Kürşat Abaylı; +Cc: Hans de Goede, platform-driver-x86, LKML
[-- Attachment #1: Type: text/plain, Size: 6940 bytes --]
On Fri, 10 Jul 2026, Kürşat Abaylı wrote:
> Add support for querying and switching the graphics MUX mode on HP
> systems via WMI. This introduces the 'gpu_mux_mode' sysfs attribute
> under the hp-wmi platform device, allowing userspace tools to check
> and safely switch between available graphics modes (e.g., UMA, Hybrid,
> Discrete).
>
> The hardware capabilities mask is primarily read using the modern
> 128-byte System Design Data query. However, to ensure backward
> compatibility with older models, a fallback mechanism is implemented.
> By mirroring the behavior of the Windows Omen Gaming Hub software, if
> the modern query fails but the MUX WMI endpoint (0x52) responds
> successfully to a read request, the driver defaults to a standard
> Hybrid + Discrete support mask (0x06).
>
> Signed-off-by: Kürşat Abaylı <hello@kursatabayli.dev>
> ---
> Changes in v3:
> - Rebased for the for-next branch (no functional changes).
>
> Changes in v2:
> - Replaced hardcoded bitmask with GENMASK(6, 0).
> - Added specific defines for MUX modes instead of using raw BIT() macros.
> - Initialized arrays with {} instead of { 0 }.
> - Fixed reverse xmas-tree ordering for local variable declarations.
> - Removed empty lines between function calls and their return value checks.
> - Split ternary operators into standard if-statements for better readability.
> - Added missing <linux/array_size.h> include.
> - Removed unnecessary cast in hp_wmi_set_mux_mode.
> ---
> drivers/platform/x86/hp/hp-wmi.c | 137 +++++++++++++++++++++++++++++++
> 1 file changed, 137 insertions(+)
>
> diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c
> index 34c9b941bdd8..3e50eaf6570c 100644
> --- a/drivers/platform/x86/hp/hp-wmi.c
> +++ b/drivers/platform/x86/hp/hp-wmi.c
> @@ -14,6 +14,8 @@
> #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>
> #include <linux/acpi.h>
> +#include <linux/array_size.h>
> +#include <linux/bits.h>
> #include <linux/cleanup.h>
> #include <linux/compiler_attributes.h>
> #include <linux/dmi.h>
> @@ -58,6 +60,12 @@ enum hp_ec_offsets {
> #define HP_FAN_SPEED_AUTOMATIC 0x00
> #define HP_POWER_LIMIT_DEFAULT 0x00
> #define HP_POWER_LIMIT_NO_CHANGE 0xFF
> +#define HPWMI_MUX_MODE_UMA BIT(0)
> +#define HPWMI_MUX_MODE_HYBRID BIT(1)
> +#define HPWMI_MUX_MODE_DISCRETE BIT(2)
> +#define HPWMI_MUX_MODE_OPTIMUS BIT(3)
> +#define HPWMI_MUX_MODE_MASK GENMASK(6, 0)
> +#define HPWMI_MUX_LEGACY_MASK (HPWMI_MUX_MODE_HYBRID | HPWMI_MUX_MODE_DISCRETE)
>
> #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
>
> @@ -381,6 +389,7 @@ enum hp_wmi_commandtype {
> HPWMI_POSTCODEERROR_QUERY = 0x2a,
> HPWMI_SYSTEM_DEVICE_MODE = 0x40,
> HPWMI_THERMAL_PROFILE_QUERY = 0x4c,
> + HPWMI_GRAPHICS_MUX_QUERY = 0x52,
> };
>
> struct victus_power_limits {
> @@ -1173,12 +1182,139 @@ static int camera_shutter_input_setup(void)
> return err;
> }
>
> +static const u8 mux_bitmask_map[] = {
> + [0] = HPWMI_MUX_MODE_HYBRID,
> + [1] = HPWMI_MUX_MODE_DISCRETE,
> + [2] = HPWMI_MUX_MODE_OPTIMUS,
> + [3] = HPWMI_MUX_MODE_UMA,
> +};
> +
> +static int hp_wmi_get_mux_supported_modes(u8 *supported)
> +{
> + u8 legacy_buffer[4] = {};
> + u8 buffer[128] = {};
> + u32 req_packet = 0;
> + int ret;
> +
> + if (!supported)
> + return -EINVAL;
> +
> + /* Try modern BIOS design data query (128-byte buffer) */
> + ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM,
> + buffer, zero_if_sup(req_packet), sizeof(buffer));
> + if (ret == 0) {
> + *supported = buffer[7];
> + return 0;
> + }
> +
> + /*
> + * (Fallback): Legacy BIOS behavior based on Omen Gaming Hub.
> + * If the modern query is not supported, check if the MUX query endpoint
> + * responds to a read request. If it succeeds, the hardware has MUX
> + * capability but lacks the mode map, defaulting to Hybrid + Discrete.
> + */
> + ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ,
> + legacy_buffer, sizeof(legacy_buffer), 0);
> + if (ret == 0) {
> + *supported = HPWMI_MUX_LEGACY_MASK;
> + return 0;
> + }
> +
> + if (ret < 0)
> + return ret;
> + return -EINVAL;
> +}
> +
> +static int hp_wmi_get_mux_mode(u8 *mode)
> +{
> + u8 buffer[4] = {};
> + int ret;
> +
> + if (!mode)
> + return -EINVAL;
> +
> + ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ,
> + buffer, sizeof(buffer), sizeof(buffer));
> +
> + if (ret < 0)
Remove empty line in between call and its error handling.
> + return ret;
> + if (ret > 0)
> + return -EINVAL;
> +
> + /* Mask the highest bit, which might be used as a BIOS status flag */
> + *mode = buffer[0] & HPWMI_MUX_MODE_MASK;
> +
> + return 0;
> +}
> +
> +static int hp_wmi_set_mux_mode(u8 mode)
> +{
> + u8 buffer[4] = { mode, 0x00, 0x00, 0x00 };
> + int ret;
> +
> + ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_WRITE,
> + buffer, sizeof(buffer), sizeof(buffer));
> +
> + if (ret < 0)
Ditto.
> + return ret;
> + if (ret > 0)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static ssize_t gpu_mux_mode_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + u8 mode;
> + int ret;
> +
> + ret = hp_wmi_get_mux_mode(&mode);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%u\n", mode);
> +}
> +
> +static ssize_t gpu_mux_mode_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf,
> + size_t count)
> +{
> + u32 requested;
> + u8 supported;
> + int ret;
> +
> + ret = kstrtou32(buf, 0, &requested);
> + if (ret)
> + return ret;
> +
> + if (requested >= ARRAY_SIZE(mux_bitmask_map))
This is part of error handling for the kstrtou32() so please remove blank
line in between here as well.
> + return -EINVAL;
> +
> + ret = hp_wmi_get_mux_supported_modes(&supported);
> + if (ret)
> + return ret;
> +
> + /* Verify if the requested mode is allowed by the hardware mask */
> + if (!(supported & mux_bitmask_map[requested]))
> + return -EOPNOTSUPP;
> +
> + ret = hp_wmi_set_mux_mode(requested);
> + if (ret)
> + return ret;
> +
> + return count;
> +}
> +
> static DEVICE_ATTR_RO(display);
> static DEVICE_ATTR_RO(hddtemp);
> static DEVICE_ATTR_RW(als);
> static DEVICE_ATTR_RO(dock);
> static DEVICE_ATTR_RO(tablet);
> static DEVICE_ATTR_RW(postcode);
> +static DEVICE_ATTR_RW(gpu_mux_mode);
>
> static struct attribute *hp_wmi_attrs[] = {
> &dev_attr_display.attr,
> @@ -1187,6 +1323,7 @@ static struct attribute *hp_wmi_attrs[] = {
> &dev_attr_dock.attr,
> &dev_attr_tablet.attr,
> &dev_attr_postcode.attr,
> + &dev_attr_gpu_mux_mode.attr,
> NULL,
> };
> ATTRIBUTE_GROUPS(hp_wmi);
>
--
i.
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-10 9:44 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-09 22:05 [PATCH v3] platform/x86: hp-wmi: Add GPU MUX switch support Kürşat Abaylı
2026-07-10 9:44 ` Ilpo Järvinen
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox