diff options
| author | Kürşat Abaylı <hello@kursatabayli.dev> | 2026-07-23 20:27:34 +0300 |
|---|---|---|
| committer | Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> | 2026-07-24 14:12:43 +0300 |
| commit | 59f586eb9394c7697cfcd2e1bb172c95e772559e (patch) | |
| tree | d2e2f3b03af10c00a44991eb26599a39fb3aec54 | |
| parent | caf8342512c3056005f475d350eeca089c3c6623 (diff) | |
| download | linux-next-59f586eb9394c7697cfcd2e1bb172c95e772559e.tar.gz linux-next-59f586eb9394c7697cfcd2e1bb172c95e772559e.zip | |
platform/x86: hp-wmi: Add GPU MUX switch support
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>
Link: https://patch.msgid.link/20260723172734.18361-1-hello@kursatabayli.dev
[ij: add kstrtox.h]
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
| -rw-r--r-- | drivers/platform/x86/hp/hp-wmi.c | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 5353d997d272..808b8f7d0adf 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> @@ -23,6 +25,7 @@ #include <linux/input.h> #include <linux/input/sparse-keymap.h> #include <linux/kernel.h> +#include <linux/kstrtox.h> #include <linux/limits.h> #include <linux/minmax.h> #include <linux/module.h> @@ -58,6 +61,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 +390,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 { @@ -1171,12 +1181,134 @@ 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) + return ret; + if (ret > 0) + return -EINVAL; + + *supported = HPWMI_MUX_LEGACY_MASK; + return 0; +} + +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, @@ -1185,6 +1317,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); |
