mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] platform/x86: hp-wmi: Add GPU MUX switch support
@ 2026-06-28  0:21 Kürşat Abaylı
  2026-06-29 11:21 ` Ilpo Järvinen
  0 siblings, 1 reply; 2+ messages in thread
From: Kürşat Abaylı @ 2026-06-28  0:21 UTC (permalink / raw)
  To: hansg, ilpo.jarvinen
  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).

The patch relies on the zero_if_sup() macro to handle input size
requirements safely across different BIOS implementations and uses
standard error handling paths.

Signed-off-by: Kürşat Abaylı <hello@kursatabayli.dev>
---
 drivers/platform/x86/hp/hp-wmi.c | 128 +++++++++++++++++++++++++++++++
 1 file changed, 128 insertions(+)

diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c
index 8ba286ed8721..256e37d91767 100644
--- a/drivers/platform/x86/hp/hp-wmi.c
+++ b/drivers/platform/x86/hp/hp-wmi.c
@@ -14,6 +14,7 @@
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/acpi.h>
+#include <linux/bits.h>
 #include <linux/cleanup.h>
 #include <linux/compiler_attributes.h>
 #include <linux/dmi.h>
@@ -58,6 +59,8 @@ 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_MASK 0x7F
+#define HPWMI_MUX_LEGACY_MASK (BIT(1) | BIT(2))
 
 #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
 
@@ -332,6 +335,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 {
@@ -1124,12 +1128,135 @@ static int camera_shutter_input_setup(void)
 	return err;
 }
 
+static const u8 mux_bitmask_map[] = {
+	[0] = BIT(1), /* Hybrid */
+	[1] = BIT(2), /* Discrete */
+	[2] = BIT(3), /* Optimus */
+	[3] = BIT(0), /* UMA */
+};
+
+static int hp_wmi_get_mux_supported_modes(u8 *supported)
+{
+	u8 buffer[128] = { 0 };
+	u8 legacy_buffer[4] = { 0 };
+	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;
+	}
+
+	return ret < 0 ? ret : -EINVAL;
+}
+
+static int hp_wmi_get_mux_mode(u8 *mode)
+{
+	u8 buffer[4] = { 0 };
+	int ret;
+
+	if (!mode)
+		return -EINVAL;
+
+	ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ,
+				   buffer, sizeof(buffer), sizeof(buffer));
+
+	if (ret)
+		return ret < 0 ? ret : -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)
+		return ret < 0 ? ret : -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((u8)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,
@@ -1138,6 +1265,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.54.0


^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [PATCH] platform/x86: hp-wmi: Add GPU MUX switch support
  2026-06-28  0:21 [PATCH] platform/x86: hp-wmi: Add GPU MUX switch support Kürşat Abaylı
@ 2026-06-29 11:21 ` Ilpo Järvinen
  0 siblings, 0 replies; 2+ messages in thread
From: Ilpo Järvinen @ 2026-06-29 11:21 UTC (permalink / raw)
  To: Kürşat Abaylı; +Cc: Hans de Goede, platform-driver-x86, LKML

[-- Attachment #1: Type: text/plain, Size: 6721 bytes --]

On Sun, 28 Jun 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).
> 
> The patch relies on the zero_if_sup() macro to handle input size
> requirements safely across different BIOS implementations and uses
> standard error handling paths.
> 
> Signed-off-by: Kürşat Abaylı <hello@kursatabayli.dev>
> ---
>  drivers/platform/x86/hp/hp-wmi.c | 128 +++++++++++++++++++++++++++++++
>  1 file changed, 128 insertions(+)
> 
> diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c
> index 8ba286ed8721..256e37d91767 100644
> --- a/drivers/platform/x86/hp/hp-wmi.c
> +++ b/drivers/platform/x86/hp/hp-wmi.c
> @@ -14,6 +14,7 @@
>  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>  
>  #include <linux/acpi.h>
> +#include <linux/bits.h>
>  #include <linux/cleanup.h>
>  #include <linux/compiler_attributes.h>
>  #include <linux/dmi.h>
> @@ -58,6 +59,8 @@ 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_MASK 0x7F

GENMASK()

> +#define HPWMI_MUX_LEGACY_MASK (BIT(1) | BIT(2))

Do we know what these bits are so they could be named with defines and 
then combined with NAMED1 | NAMED2?
 
>  #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
>  
> @@ -332,6 +335,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 {
> @@ -1124,12 +1128,135 @@ static int camera_shutter_input_setup(void)
>  	return err;
>  }
>  
> +static const u8 mux_bitmask_map[] = {
> +	[0] = BIT(1), /* Hybrid */
> +	[1] = BIT(2), /* Discrete */
> +	[2] = BIT(3), /* Optimus */
> +	[3] = BIT(0), /* UMA */

Please use named defines instead of BIT(x) so you won't need comments 
anymore.

Are the above BIT(1) | BIT(2) actually same as hybrid & discrete here? In 
that case the naming them with defines makes the connection between them 
and these array entries much clearer.

> +};
> +
> +static int hp_wmi_get_mux_supported_modes(u8 *supported)
> +{
> +	u8 buffer[128] = { 0 };
> +	u8 legacy_buffer[4] = { 0 };

= {}; is enough to initialize to default values (x2).

Please use reverse xmas-tree order.

> +	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) {

Please don't leave empty line between call and it's return value check.

> +		*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;
> +	}
> +
> +	return ret < 0 ? ret : -EINVAL;

This should probably be split for clarity.

> +}
> +
> +static int hp_wmi_get_mux_mode(u8 *mode)
> +{
> +	u8 buffer[4] = { 0 };
> +	int ret;
> +
> +	if (!mode)
> +		return -EINVAL;
> +
> +	ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ,
> +				   buffer, sizeof(buffer), sizeof(buffer));
> +
> +	if (ret)
> +		return ret < 0 ? ret : -EINVAL;

Please split to two ifs.

> +
> +	/* 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)
> +		return ret < 0 ? ret : -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))

Please add include for ARRAY_SIZE().

> +		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((u8)requested);

Unnecessary cast.

> +	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,
> @@ -1138,6 +1265,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-06-29 11:27 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-28  0:21 [PATCH] platform/x86: hp-wmi: Add GPU MUX switch support Kürşat Abaylı
2026-06-29 11:21 ` 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

Powered by JetHome