* [PATCH 0/2] platform/x86: hp-wmi: Add manual fan support for Victus S laptops
@ 2025-12-25 14:23 Krishna Chomal
2025-12-25 14:23 ` [PATCH 1/2] platform/x86: hp-wmi: add manual fan control for Victus S models Krishna Chomal
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Krishna Chomal @ 2025-12-25 14:23 UTC (permalink / raw)
To: ilpo.jarvinen, hansg, linux
Cc: platform-driver-x86, linux-hwmon, linux-kernel, Krishna Chomal
This series adds support for manual fan speed control and PWM reporting
for HP Victus S-style laptops.
The first patch refactors the hwmon implementation to use a per-device
private context for state tracking. It implements RPM-to-PWM conversion
using linear interpolation based on the laptop's internal fan tables
retrieved via WMI. It also introduces PWM_MODE_MANUAL, allowing users
to set specific fan speeds.
The second patch addresses a firmware-specific behavior where the
system reverts to "Auto" fan mode after a 120-second timeout if no WMI
fan commands are received. A delayed workqueue is implemented to act
as a keep-alive heartbeat, refreshing the fan state every 90 seconds
to ensure user-selected profiles remain active indefinitely.
Tested on: HP Omen 16-wf1xxx (board ID 8C78)
Krishna Chomal (2):
platform/x86: hp-wmi: add manual fan control for Victus S models
platform/x86: hp-wmi: implement fan keep-alive
drivers/platform/x86/hp/hp-wmi.c | 287 ++++++++++++++++++++++++++-----
1 file changed, 243 insertions(+), 44 deletions(-)
--
2.52.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 1/2] platform/x86: hp-wmi: add manual fan control for Victus S models 2025-12-25 14:23 [PATCH 0/2] platform/x86: hp-wmi: Add manual fan support for Victus S laptops Krishna Chomal @ 2025-12-25 14:23 ` Krishna Chomal 2025-12-29 13:08 ` Ilpo Järvinen 2025-12-25 14:23 ` [PATCH 2/2] platform/x86: hp-wmi: implement fan keep-alive Krishna Chomal 2025-12-30 14:50 ` [PATCH v2 0/2] platform/x86: hp-wmi: Add manual fan support for Victus S laptops Krishna Chomal 2 siblings, 1 reply; 10+ messages in thread From: Krishna Chomal @ 2025-12-25 14:23 UTC (permalink / raw) To: ilpo.jarvinen, hansg, linux Cc: platform-driver-x86, linux-hwmon, linux-kernel, Krishna Chomal Add manual fan speed control and PWM reporting for HP Victus S-series laptops. While HPWMI_FAN_SPEED_SET_QUERY was previously added to reset max fan mode, it is actually capable of individual fan control. This patch implements hp_wmi_fan_speed_set() to allow manual control and hides PWM inputs for non-Victus devices as the query is Victus specific. The existing hp_wmi_fan_speed_max_get() query is unreliable on Victus S firmware, often incorrectly reporting "Auto" mode even when "Max" is active. To resolve this synchronization issue, move state tracking to a per-device private context and enforce "Auto" mode during driver initialization to ensure a consistent starting point. Tested on: HP Omen 16-wf1xxx (board ID 8C78) Signed-off-by: Krishna Chomal <krishna.chomal108@gmail.com> --- drivers/platform/x86/hp/hp-wmi.c | 243 +++++++++++++++++++++++++------ 1 file changed, 199 insertions(+), 44 deletions(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index f4ea1ea05997..9fb956ce809a 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -30,6 +30,7 @@ #include <linux/rfkill.h> #include <linux/string.h> #include <linux/dmi.h> +#include <linux/fixp-arith.h> MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>"); MODULE_DESCRIPTION("HP laptop WMI driver"); @@ -190,7 +191,8 @@ enum hp_wmi_gm_commandtype { HPWMI_SET_GPU_THERMAL_MODES_QUERY = 0x22, HPWMI_SET_POWER_LIMITS_QUERY = 0x29, HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY = 0x2D, - HPWMI_FAN_SPEED_SET_QUERY = 0x2E, + HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY = 0x2E, + HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY = 0x2F, }; enum hp_wmi_command { @@ -348,6 +350,32 @@ static const char * const tablet_chassis_types[] = { #define DEVICE_MODE_TABLET 0x06 +#define CPU_FAN 0 +#define GPU_FAN 1 + +enum pwm_modes { + PWM_MODE_MAX = 0, + PWM_MODE_MANUAL = 1, + PWM_MODE_AUTO = 2, +}; + +struct hp_wmi_hwmon_priv { + u8 min_rpm; + u8 max_rpm; + u8 gpu_delta; + u8 mode; + u8 pwm; +}; + +#define RPM_TO_PWM(rpm, ctx) fixp_linear_interpolate(0, 0, \ + (ctx)->max_rpm, U8_MAX, \ + clamp_val((rpm), \ + 0, (ctx)->max_rpm)) +#define PWM_TO_RPM(pwm, ctx) fixp_linear_interpolate(0, 0, \ + U8_MAX, (ctx)->max_rpm, \ + clamp_val((pwm), \ + 0, U8_MAX)) + /* map output size to the corresponding WMI method id */ static inline int encode_outsize_for_pvsz(int outsize) { @@ -637,41 +665,55 @@ static int hp_wmi_fan_speed_max_set(int enabled) return enabled; } -static int hp_wmi_fan_speed_reset(void) +static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *ctx, u8 speed) { - u8 fan_speed[2] = { HP_FAN_SPEED_AUTOMATIC, HP_FAN_SPEED_AUTOMATIC }; + u8 fan_speed[2]; int ret; - ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM, - &fan_speed, sizeof(fan_speed), 0); + if (!ctx) + return -ENODEV; - return ret; -} + fan_speed[CPU_FAN] = speed; + fan_speed[GPU_FAN] = speed; -static int hp_wmi_fan_speed_max_reset(void) -{ - int ret; + /* + * GPU fan speed is always a little higher than CPU fan speed, we fetch + * this delta value from the fan table during hwmon init. + * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to + * automatic mode. + */ + if (speed != HP_FAN_SPEED_AUTOMATIC) + fan_speed[GPU_FAN] = clamp_val(speed + ctx->gpu_delta, 0, U8_MAX); + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) + return ret; + /* Max fans need to be explicitly disabled */ ret = hp_wmi_fan_speed_max_set(0); - if (ret) + if (ret < 0) return ret; + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY, HPWMI_GM, + &fan_speed, sizeof(fan_speed), 0); - /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */ - ret = hp_wmi_fan_speed_reset(); return ret; } -static int hp_wmi_fan_speed_max_get(void) +static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *ctx) { - int val = 0, ret; + return hp_wmi_fan_speed_set(ctx, HP_FAN_SPEED_AUTOMATIC); +} - ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM, - &val, zero_if_sup(val), sizeof(val)); +static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *ctx) +{ + int ret; + ret = hp_wmi_fan_speed_max_set(0); if (ret) - return ret < 0 ? ret : -EINVAL; + return ret; - return val; + /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */ + ret = hp_wmi_fan_speed_reset(ctx); + return ret; } static int __init hp_wmi_bios_2008_later(void) @@ -2108,12 +2150,43 @@ static struct platform_driver hp_wmi_driver __refdata = { .remove = __exit_p(hp_wmi_bios_remove), }; +static int hp_wmi_hwmon_enforce_ctx(struct hp_wmi_hwmon_priv *ctx) +{ + if (!ctx) + return -ENODEV; + + switch (ctx->mode) { + case PWM_MODE_MAX: + if (is_victus_s_thermal_profile()) + hp_wmi_get_fan_count_userdefine_trigger(); + return hp_wmi_fan_speed_max_set(1); + case PWM_MODE_MANUAL: + if (!is_victus_s_thermal_profile()) + return -EOPNOTSUPP; + return hp_wmi_fan_speed_set(ctx, PWM_TO_RPM(ctx->pwm, ctx)); + case PWM_MODE_AUTO: + if (is_victus_s_thermal_profile()) { + hp_wmi_get_fan_count_userdefine_trigger(); + return hp_wmi_fan_speed_max_reset(ctx); + } else { + return hp_wmi_fan_speed_max_set(0); + } + default: + /* shouldn't happen */ + return -EINVAL; + } + + return 0; +} + static umode_t hp_wmi_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { switch (type) { case hwmon_pwm: + if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) + return 0; /* Hide PWM input if not Victus S */ return 0644; case hwmon_fan: if (is_victus_s_thermal_profile()) { @@ -2134,7 +2207,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { - int ret; + struct hp_wmi_hwmon_priv *ctx; + int current_rpm, ret; + + ctx = dev_get_drvdata(dev); + if (!ctx) + return -ENODEV; switch (type) { case hwmon_fan: @@ -2147,16 +2225,21 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, *val = ret; return 0; case hwmon_pwm: - switch (hp_wmi_fan_speed_max_get()) { - case 0: - /* 0 is automatic fan, which is 2 for hwmon */ - *val = 2; + if (attr == hwmon_pwm_input) { + if (!is_victus_s_thermal_profile()) + return -EOPNOTSUPP; + ret = hp_wmi_get_fan_speed_victus_s(channel); + if (ret < 0) + return ret; + current_rpm = ret; + *val = RPM_TO_PWM(current_rpm / 100, ctx); return 0; - case 1: - /* 1 is max fan, which is 0 - * (no fan speed control) for hwmon - */ - *val = 0; + } + switch (ctx->mode) { + case PWM_MODE_MAX: + case PWM_MODE_MANUAL: + case PWM_MODE_AUTO: + *val = ctx->mode; return 0; default: /* shouldn't happen */ @@ -2170,23 +2253,50 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { + struct hp_wmi_hwmon_priv *ctx; + int current_rpm, ret; + + ctx = dev_get_drvdata(dev); + if (!ctx) + return -ENODEV; + switch (type) { case hwmon_pwm: + if (attr == hwmon_pwm_input) { + if (!is_victus_s_thermal_profile()) + return -EOPNOTSUPP; + /* pwm input is invalid when not in manual mode */ + if (ctx->mode != PWM_MODE_MANUAL) + return -EINVAL; + /* ensure pwm input is within valid fan speeds */ + ctx->pwm = RPM_TO_PWM(clamp_val(PWM_TO_RPM(val, ctx), + ctx->min_rpm, + ctx->max_rpm), + ctx); + return hp_wmi_hwmon_enforce_ctx(ctx); + } switch (val) { - case 0: - if (is_victus_s_thermal_profile()) - hp_wmi_get_fan_count_userdefine_trigger(); - /* 0 is no fan speed control (max), which is 1 for us */ - return hp_wmi_fan_speed_max_set(1); - case 2: - /* 2 is automatic speed control, which is 0 for us */ - if (is_victus_s_thermal_profile()) { - hp_wmi_get_fan_count_userdefine_trigger(); - return hp_wmi_fan_speed_max_reset(); - } else - return hp_wmi_fan_speed_max_set(0); + case PWM_MODE_MAX: + ctx->mode = PWM_MODE_MAX; + return hp_wmi_hwmon_enforce_ctx(ctx); + case PWM_MODE_MANUAL: + if (!is_victus_s_thermal_profile()) + return -EOPNOTSUPP; + /* + * When switching to manual mode, set fan speed to + * current RPM values to ensure a smooth transition. + */ + ret = hp_wmi_get_fan_speed_victus_s(channel); + if (ret < 0) + return ret; + current_rpm = ret; + ctx->pwm = RPM_TO_PWM(current_rpm / 100, ctx); + ctx->mode = PWM_MODE_MANUAL; + return hp_wmi_hwmon_enforce_ctx(ctx); + case PWM_MODE_AUTO: + ctx->mode = PWM_MODE_AUTO; + return hp_wmi_hwmon_enforce_ctx(ctx); default: - /* we don't support manual fan speed control */ return -EINVAL; } default: @@ -2196,7 +2306,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, static const struct hwmon_channel_info * const info[] = { HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), - HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT), NULL }; @@ -2211,12 +2321,55 @@ static const struct hwmon_chip_info chip_info = { .info = info, }; +static int hp_wmi_hwmon_setup_ctx(struct hp_wmi_hwmon_priv *ctx) +{ + u8 fan_data[128]; + u8 (*fan_table)[3]; + u8 rows, min_rpm, max_rpm, gpu_delta; + int ret; + + /* Default behaviour on hwmon init is automatic mode */ + ctx->mode = PWM_MODE_AUTO; + + /* Bypass all non-Victus S devices */ + if (!is_victus_s_thermal_profile()) + return 0; + + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, + HPWMI_GM, &fan_data, 4, sizeof(fan_data)); + if (ret != 0) + return ret; + + rows = fan_data[1]; + if (2 + rows * 3 >= sizeof(fan_data)) + return -EINVAL; + + fan_table = (u8 (*)[3]) & fan_data[2]; + min_rpm = fan_table[0][CPU_FAN]; + max_rpm = fan_table[rows - 1][CPU_FAN]; + gpu_delta = fan_table[0][GPU_FAN] - fan_table[0][CPU_FAN]; + ctx->min_rpm = min_rpm; + ctx->max_rpm = max_rpm; + ctx->gpu_delta = gpu_delta; + + return 0; +} + static int hp_wmi_hwmon_init(void) { struct device *dev = &hp_wmi_platform_dev->dev; + struct hp_wmi_hwmon_priv *ctx; struct device *hwmon; + int ret; - hwmon = devm_hwmon_device_register_with_info(dev, "hp", &hp_wmi_driver, + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ret = hp_wmi_hwmon_setup_ctx(ctx); + if (ret != 0) + return ret; + hwmon = devm_hwmon_device_register_with_info(dev, "hp", ctx, &chip_info, NULL); if (IS_ERR(hwmon)) { @@ -2224,6 +2377,8 @@ static int hp_wmi_hwmon_init(void) return PTR_ERR(hwmon); } + hp_wmi_hwmon_enforce_ctx(ctx); + return 0; } -- 2.52.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] platform/x86: hp-wmi: add manual fan control for Victus S models 2025-12-25 14:23 ` [PATCH 1/2] platform/x86: hp-wmi: add manual fan control for Victus S models Krishna Chomal @ 2025-12-29 13:08 ` Ilpo Järvinen 2025-12-30 14:22 ` Krishna Chomal 0 siblings, 1 reply; 10+ messages in thread From: Ilpo Järvinen @ 2025-12-29 13:08 UTC (permalink / raw) To: Krishna Chomal Cc: Hans de Goede, linux, platform-driver-x86, linux-hwmon, LKML On Thu, 25 Dec 2025, Krishna Chomal wrote: > Add manual fan speed control and PWM reporting for HP Victus S-series > laptops. > > While HPWMI_FAN_SPEED_SET_QUERY was previously added to reset max fan > mode, it is actually capable of individual fan control. This patch > implements hp_wmi_fan_speed_set() to allow manual control and hides > PWM inputs for non-Victus devices as the query is Victus specific. > > The existing hp_wmi_fan_speed_max_get() query is unreliable on Victus S > firmware, often incorrectly reporting "Auto" mode even when "Max" is > active. To resolve this synchronization issue, move state tracking to > a per-device private context and enforce "Auto" mode during driver > initialization to ensure a consistent starting point. > > Tested on: HP Omen 16-wf1xxx (board ID 8C78) > > Signed-off-by: Krishna Chomal <krishna.chomal108@gmail.com> > --- > drivers/platform/x86/hp/hp-wmi.c | 243 +++++++++++++++++++++++++------ > 1 file changed, 199 insertions(+), 44 deletions(-) > > diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c > index f4ea1ea05997..9fb956ce809a 100644 > --- a/drivers/platform/x86/hp/hp-wmi.c > +++ b/drivers/platform/x86/hp/hp-wmi.c > @@ -30,6 +30,7 @@ > #include <linux/rfkill.h> > #include <linux/string.h> > #include <linux/dmi.h> > +#include <linux/fixp-arith.h> > > MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>"); > MODULE_DESCRIPTION("HP laptop WMI driver"); > @@ -190,7 +191,8 @@ enum hp_wmi_gm_commandtype { > HPWMI_SET_GPU_THERMAL_MODES_QUERY = 0x22, > HPWMI_SET_POWER_LIMITS_QUERY = 0x29, > HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY = 0x2D, > - HPWMI_FAN_SPEED_SET_QUERY = 0x2E, > + HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY = 0x2E, > + HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY = 0x2F, > }; > > enum hp_wmi_command { > @@ -348,6 +350,32 @@ static const char * const tablet_chassis_types[] = { > > #define DEVICE_MODE_TABLET 0x06 > > +#define CPU_FAN 0 > +#define GPU_FAN 1 > + > +enum pwm_modes { > + PWM_MODE_MAX = 0, > + PWM_MODE_MANUAL = 1, > + PWM_MODE_AUTO = 2, > +}; > + > +struct hp_wmi_hwmon_priv { > + u8 min_rpm; > + u8 max_rpm; > + u8 gpu_delta; > + u8 mode; > + u8 pwm; > +}; > + > +#define RPM_TO_PWM(rpm, ctx) fixp_linear_interpolate(0, 0, \ > + (ctx)->max_rpm, U8_MAX, \ + limits.h > + clamp_val((rpm), \ + minmax.h > + 0, (ctx)->max_rpm)) > +#define PWM_TO_RPM(pwm, ctx) fixp_linear_interpolate(0, 0, \ > + U8_MAX, (ctx)->max_rpm, \ > + clamp_val((pwm), \ > + 0, U8_MAX)) These look not needing to be macros at all but could be written as static functions which makes typing explicit. > + > /* map output size to the corresponding WMI method id */ > static inline int encode_outsize_for_pvsz(int outsize) > { > @@ -637,41 +665,55 @@ static int hp_wmi_fan_speed_max_set(int enabled) > return enabled; > } > > -static int hp_wmi_fan_speed_reset(void) > +static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *ctx, u8 speed) > { > - u8 fan_speed[2] = { HP_FAN_SPEED_AUTOMATIC, HP_FAN_SPEED_AUTOMATIC }; > + u8 fan_speed[2]; > int ret; > > - ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM, > - &fan_speed, sizeof(fan_speed), 0); > + if (!ctx) > + return -ENODEV; Can this be NULL? Is it a bug in the driver/kernel if it is? > - return ret; > -} > + fan_speed[CPU_FAN] = speed; > + fan_speed[GPU_FAN] = speed; > > -static int hp_wmi_fan_speed_max_reset(void) > -{ > - int ret; > + /* > + * GPU fan speed is always a little higher than CPU fan speed, we fetch > + * this delta value from the fan table during hwmon init. > + * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to > + * automatic mode. > + */ > + if (speed != HP_FAN_SPEED_AUTOMATIC) > + fan_speed[GPU_FAN] = clamp_val(speed + ctx->gpu_delta, 0, U8_MAX); So this only works correctly due to C's integer promotion when doing arithmetic on them? > > + ret = hp_wmi_get_fan_count_userdefine_trigger(); > + if (ret < 0) > + return ret; > + /* Max fans need to be explicitly disabled */ > ret = hp_wmi_fan_speed_max_set(0); > - if (ret) > + if (ret < 0) > return ret; > + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY, HPWMI_GM, > + &fan_speed, sizeof(fan_speed), 0); > > - /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */ > - ret = hp_wmi_fan_speed_reset(); > return ret; > } > > -static int hp_wmi_fan_speed_max_get(void) > +static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *ctx) > { > - int val = 0, ret; > + return hp_wmi_fan_speed_set(ctx, HP_FAN_SPEED_AUTOMATIC); > +} > > - ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM, > - &val, zero_if_sup(val), sizeof(val)); > +static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *ctx) > +{ > + int ret; > > + ret = hp_wmi_fan_speed_max_set(0); > if (ret) > - return ret < 0 ? ret : -EINVAL; > + return ret; > > - return val; > + /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */ > + ret = hp_wmi_fan_speed_reset(ctx); > + return ret; Return can be done directly on the line with the call. > } > > static int __init hp_wmi_bios_2008_later(void) > @@ -2108,12 +2150,43 @@ static struct platform_driver hp_wmi_driver __refdata = { > .remove = __exit_p(hp_wmi_bios_remove), > }; > > +static int hp_wmi_hwmon_enforce_ctx(struct hp_wmi_hwmon_priv *ctx) I don't understand why this function is named as "enforce context". Perhaps change function's name to something that better describes what it does. > +{ > + if (!ctx) > + return -ENODEV; > + > + switch (ctx->mode) { > + case PWM_MODE_MAX: > + if (is_victus_s_thermal_profile()) > + hp_wmi_get_fan_count_userdefine_trigger(); > + return hp_wmi_fan_speed_max_set(1); > + case PWM_MODE_MANUAL: > + if (!is_victus_s_thermal_profile()) > + return -EOPNOTSUPP; > + return hp_wmi_fan_speed_set(ctx, PWM_TO_RPM(ctx->pwm, ctx)); > + case PWM_MODE_AUTO: > + if (is_victus_s_thermal_profile()) { > + hp_wmi_get_fan_count_userdefine_trigger(); > + return hp_wmi_fan_speed_max_reset(ctx); > + } else { Unnecessary else. > + return hp_wmi_fan_speed_max_set(0); > + } > + default: > + /* shouldn't happen */ > + return -EINVAL; > + } > + > + return 0; > +} > + > static umode_t hp_wmi_hwmon_is_visible(const void *data, > enum hwmon_sensor_types type, > u32 attr, int channel) > { > switch (type) { > case hwmon_pwm: > + if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) > + return 0; /* Hide PWM input if not Victus S */ The comment add no information beyond what the code already tells us. > return 0644; > case hwmon_fan: > if (is_victus_s_thermal_profile()) { > @@ -2134,7 +2207,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, > static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, > u32 attr, int channel, long *val) > { > - int ret; > + struct hp_wmi_hwmon_priv *ctx; > + int current_rpm, ret; > + > + ctx = dev_get_drvdata(dev); > + if (!ctx) > + return -ENODEV; > > switch (type) { > case hwmon_fan: > @@ -2147,16 +2225,21 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, > *val = ret; > return 0; > case hwmon_pwm: > - switch (hp_wmi_fan_speed_max_get()) { > - case 0: > - /* 0 is automatic fan, which is 2 for hwmon */ > - *val = 2; > + if (attr == hwmon_pwm_input) { > + if (!is_victus_s_thermal_profile()) > + return -EOPNOTSUPP; > + ret = hp_wmi_get_fan_speed_victus_s(channel); > + if (ret < 0) > + return ret; > + current_rpm = ret; > + *val = RPM_TO_PWM(current_rpm / 100, ctx); > return 0; > - case 1: > - /* 1 is max fan, which is 0 > - * (no fan speed control) for hwmon > - */ > - *val = 0; > + } > + switch (ctx->mode) { > + case PWM_MODE_MAX: > + case PWM_MODE_MANUAL: > + case PWM_MODE_AUTO: > + *val = ctx->mode; > return 0; > default: > /* shouldn't happen */ > @@ -2170,23 +2253,50 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, > static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, > u32 attr, int channel, long val) > { > + struct hp_wmi_hwmon_priv *ctx; > + int current_rpm, ret; > + > + ctx = dev_get_drvdata(dev); > + if (!ctx) Don't you register it with a non-NULL drvdata always? > + return -ENODEV; > + > switch (type) { > case hwmon_pwm: > + if (attr == hwmon_pwm_input) { > + if (!is_victus_s_thermal_profile()) > + return -EOPNOTSUPP; > + /* pwm input is invalid when not in manual mode */ > + if (ctx->mode != PWM_MODE_MANUAL) > + return -EINVAL; > + /* ensure pwm input is within valid fan speeds */ > + ctx->pwm = RPM_TO_PWM(clamp_val(PWM_TO_RPM(val, ctx), > + ctx->min_rpm, > + ctx->max_rpm), > + ctx); > + return hp_wmi_hwmon_enforce_ctx(ctx); > + } > switch (val) { > - case 0: > - if (is_victus_s_thermal_profile()) > - hp_wmi_get_fan_count_userdefine_trigger(); > - /* 0 is no fan speed control (max), which is 1 for us */ > - return hp_wmi_fan_speed_max_set(1); > - case 2: > - /* 2 is automatic speed control, which is 0 for us */ > - if (is_victus_s_thermal_profile()) { > - hp_wmi_get_fan_count_userdefine_trigger(); > - return hp_wmi_fan_speed_max_reset(); > - } else > - return hp_wmi_fan_speed_max_set(0); > + case PWM_MODE_MAX: > + ctx->mode = PWM_MODE_MAX; > + return hp_wmi_hwmon_enforce_ctx(ctx); > + case PWM_MODE_MANUAL: > + if (!is_victus_s_thermal_profile()) > + return -EOPNOTSUPP; > + /* > + * When switching to manual mode, set fan speed to > + * current RPM values to ensure a smooth transition. > + */ > + ret = hp_wmi_get_fan_speed_victus_s(channel); > + if (ret < 0) > + return ret; > + current_rpm = ret; > + ctx->pwm = RPM_TO_PWM(current_rpm / 100, ctx); > + ctx->mode = PWM_MODE_MANUAL; > + return hp_wmi_hwmon_enforce_ctx(ctx); > + case PWM_MODE_AUTO: > + ctx->mode = PWM_MODE_AUTO; > + return hp_wmi_hwmon_enforce_ctx(ctx); > default: > - /* we don't support manual fan speed control */ > return -EINVAL; > } > default: > @@ -2196,7 +2306,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, > > static const struct hwmon_channel_info * const info[] = { > HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), > - HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE), > + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT), > NULL > }; > > @@ -2211,12 +2321,55 @@ static const struct hwmon_chip_info chip_info = { > .info = info, > }; > > +static int hp_wmi_hwmon_setup_ctx(struct hp_wmi_hwmon_priv *ctx) I suggest you change "ctx" in the name to something more meaningful. > +{ > + u8 fan_data[128]; > + u8 (*fan_table)[3]; > + u8 rows, min_rpm, max_rpm, gpu_delta; > + int ret; > + > + /* Default behaviour on hwmon init is automatic mode */ > + ctx->mode = PWM_MODE_AUTO; > + > + /* Bypass all non-Victus S devices */ > + if (!is_victus_s_thermal_profile()) > + return 0; > + > + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, > + HPWMI_GM, &fan_data, 4, sizeof(fan_data)); Does this end up coping from uninitialized fan_data (insize = 4)? > + if (ret != 0) if (ret) > + return ret; > + > + rows = fan_data[1]; > + if (2 + rows * 3 >= sizeof(fan_data)) > + return -EINVAL; > + > + fan_table = (u8 (*)[3]) & fan_data[2]; Heh, a cast disguished as a bitwise and (due to misleading spacing). Can you make a real struct out of this so you could access the members properly without these literal offsets and casting madness? You might need to use DECLARE_FLEX_ARRAY(). > + min_rpm = fan_table[0][CPU_FAN]; > + max_rpm = fan_table[rows - 1][CPU_FAN]; > + gpu_delta = fan_table[0][GPU_FAN] - fan_table[0][CPU_FAN]; > + ctx->min_rpm = min_rpm; > + ctx->max_rpm = max_rpm; > + ctx->gpu_delta = gpu_delta; > + > + return 0; > +} > + > static int hp_wmi_hwmon_init(void) > { > struct device *dev = &hp_wmi_platform_dev->dev; > + struct hp_wmi_hwmon_priv *ctx; > struct device *hwmon; > + int ret; > > - hwmon = devm_hwmon_device_register_with_info(dev, "hp", &hp_wmi_driver, > + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); > + if (!ctx) > + return -ENOMEM; > + > + ret = hp_wmi_hwmon_setup_ctx(ctx); > + if (ret != 0) > + return ret; > + hwmon = devm_hwmon_device_register_with_info(dev, "hp", ctx, > &chip_info, NULL); > > if (IS_ERR(hwmon)) { > @@ -2224,6 +2377,8 @@ static int hp_wmi_hwmon_init(void) > return PTR_ERR(hwmon); > } > > + hp_wmi_hwmon_enforce_ctx(ctx); > + > return 0; > } > > -- i. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] platform/x86: hp-wmi: add manual fan control for Victus S models 2025-12-29 13:08 ` Ilpo Järvinen @ 2025-12-30 14:22 ` Krishna Chomal 0 siblings, 0 replies; 10+ messages in thread From: Krishna Chomal @ 2025-12-30 14:22 UTC (permalink / raw) To: Ilpo Järvinen Cc: hansg, linux, platform-driver-x86, linux-hwmon, linux-kernel On Mon, Dec 29, 2025 at 03:08:16PM +0200, Ilpo Järvinen wrote: [snip] >> + >> +#define RPM_TO_PWM(rpm, ctx) fixp_linear_interpolate(0, 0, \ >> + (ctx)->max_rpm, U8_MAX, \ > >+ limits.h > >> + clamp_val((rpm), \ > >+ minmax.h > Added in v2. >> + 0, (ctx)->max_rpm)) >> +#define PWM_TO_RPM(pwm, ctx) fixp_linear_interpolate(0, 0, \ >> + U8_MAX, (ctx)->max_rpm, \ >> + clamp_val((pwm), \ >> + 0, U8_MAX)) > >These look not needing to be macros at all but could be written as static >functions which makes typing explicit. Fixed. I have converted both of them into static inlines. >> + >> /* map output size to the corresponding WMI method id */ >> static inline int encode_outsize_for_pvsz(int outsize) >> { >> @@ -637,41 +665,55 @@ static int hp_wmi_fan_speed_max_set(int enabled) >> return enabled; >> } >> >> -static int hp_wmi_fan_speed_reset(void) >> +static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *ctx, u8 speed) >> { >> - u8 fan_speed[2] = { HP_FAN_SPEED_AUTOMATIC, HP_FAN_SPEED_AUTOMATIC }; >> + u8 fan_speed[2]; >> int ret; >> >> - ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM, >> - &fan_speed, sizeof(fan_speed), 0); >> + if (!ctx) >> + return -ENODEV; > >Can this be NULL? Is it a bug in the driver/kernel if it is? > No, even I think this is redundant check. Removed in v2. >> - return ret; >> -} >> + fan_speed[CPU_FAN] = speed; >> + fan_speed[GPU_FAN] = speed; >> >> -static int hp_wmi_fan_speed_max_reset(void) >> -{ >> - int ret; >> + /* >> + * GPU fan speed is always a little higher than CPU fan speed, we fetch >> + * this delta value from the fan table during hwmon init. >> + * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to >> + * automatic mode. >> + */ >> + if (speed != HP_FAN_SPEED_AUTOMATIC) >> + fan_speed[GPU_FAN] = clamp_val(speed + ctx->gpu_delta, 0, U8_MAX); > >So this only works correctly due to C's integer promotion when doing >arithmetic on them? > Yes, it relies on promotion, but for clarity I have added explicit cast to unsigned int. [snip] >> +static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *ctx) >> +{ >> + int ret; >> >> + ret = hp_wmi_fan_speed_max_set(0); >> if (ret) >> - return ret < 0 ? ret : -EINVAL; >> + return ret; >> >> - return val; >> + /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */ >> + ret = hp_wmi_fan_speed_reset(ctx); >> + return ret; > >Return can be done directly on the line with the call. > Fixed. >> } >> >> static int __init hp_wmi_bios_2008_later(void) >> @@ -2108,12 +2150,43 @@ static struct platform_driver hp_wmi_driver __refdata = { >> .remove = __exit_p(hp_wmi_bios_remove), >> }; >> >> +static int hp_wmi_hwmon_enforce_ctx(struct hp_wmi_hwmon_priv *ctx) > >I don't understand why this function is named as "enforce context". >Perhaps change function's name to something that better describes what it >does. > I have renamed "enforce_ctx" to "apply_fan_settings" in v2. That seems like a more descriptive and self-explanatory name. >> +{ >> + if (!ctx) >> + return -ENODEV; >> + >> + switch (ctx->mode) { >> + case PWM_MODE_MAX: >> + if (is_victus_s_thermal_profile()) >> + hp_wmi_get_fan_count_userdefine_trigger(); >> + return hp_wmi_fan_speed_max_set(1); >> + case PWM_MODE_MANUAL: >> + if (!is_victus_s_thermal_profile()) >> + return -EOPNOTSUPP; >> + return hp_wmi_fan_speed_set(ctx, PWM_TO_RPM(ctx->pwm, ctx)); >> + case PWM_MODE_AUTO: >> + if (is_victus_s_thermal_profile()) { >> + hp_wmi_get_fan_count_userdefine_trigger(); >> + return hp_wmi_fan_speed_max_reset(ctx); >> + } else { > >Unnecessary else. > Actually, this is needed to store the intermediate ret variable, to prepare for the keep-alive rescheduling in patch 2/2. >> + return hp_wmi_fan_speed_max_set(0); >> + } >> + default: >> + /* shouldn't happen */ >> + return -EINVAL; >> + } >> + >> + return 0; >> +} >> + >> static umode_t hp_wmi_hwmon_is_visible(const void *data, >> enum hwmon_sensor_types type, >> u32 attr, int channel) >> { >> switch (type) { >> case hwmon_pwm: >> + if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) >> + return 0; /* Hide PWM input if not Victus S */ > >The comment add no information beyond what the code already tells us. > Agreed, this is also removed in v2. [snip] >> static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, >> u32 attr, int channel, long val) >> { >> + struct hp_wmi_hwmon_priv *ctx; >> + int current_rpm, ret; >> + >> + ctx = dev_get_drvdata(dev); >> + if (!ctx) > >Don't you register it with a non-NULL drvdata always? > Yes this is also redundant. Removed. [snip] >> >> +static int hp_wmi_hwmon_setup_ctx(struct hp_wmi_hwmon_priv *ctx) > >I suggest you change "ctx" in the name to something more meaningful. > Renamed "ctx" to "priv" as it is more consistent with drvdata. >> +{ >> + u8 fan_data[128]; >> + u8 (*fan_table)[3]; >> + u8 rows, min_rpm, max_rpm, gpu_delta; >> + int ret; >> + >> + /* Default behaviour on hwmon init is automatic mode */ >> + ctx->mode = PWM_MODE_AUTO; >> + >> + /* Bypass all non-Victus S devices */ >> + if (!is_victus_s_thermal_profile()) >> + return 0; >> + >> + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, >> + HPWMI_GM, &fan_data, 4, sizeof(fan_data)); > >Does this end up coping from uninitialized fan_data (insize = 4)? > Yes, that was a mistake. Fixed in v2 by explicitly initialising fan_data[] to zeros. >> + if (ret != 0) > >if (ret) > Fixed. >> + return ret; >> + >> + rows = fan_data[1]; >> + if (2 + rows * 3 >= sizeof(fan_data)) >> + return -EINVAL; >> + >> + fan_table = (u8 (*)[3]) & fan_data[2]; > >Heh, a cast disguished as a bitwise and (due to misleading spacing). > >Can you make a real struct out of this so you could access the members >properly without these literal offsets and casting madness? You might need >to use DECLARE_FLEX_ARRAY(). > Yes that cast does look like a bitwise AND. I was merely trying to satisfy checkpatch --strict, but it seems like it is best to ignore the warning in this case. Anyway, I agree that such casting creates unnecessary complexity. I have added struct victus_s_fan_table to handle this more gracefully in v2. ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 2/2] platform/x86: hp-wmi: implement fan keep-alive 2025-12-25 14:23 [PATCH 0/2] platform/x86: hp-wmi: Add manual fan support for Victus S laptops Krishna Chomal 2025-12-25 14:23 ` [PATCH 1/2] platform/x86: hp-wmi: add manual fan control for Victus S models Krishna Chomal @ 2025-12-25 14:23 ` Krishna Chomal 2025-12-29 13:21 ` Ilpo Järvinen 2025-12-30 14:50 ` [PATCH v2 0/2] platform/x86: hp-wmi: Add manual fan support for Victus S laptops Krishna Chomal 2 siblings, 1 reply; 10+ messages in thread From: Krishna Chomal @ 2025-12-25 14:23 UTC (permalink / raw) To: ilpo.jarvinen, hansg, linux Cc: platform-driver-x86, linux-hwmon, linux-kernel, Krishna Chomal The firmware on some HP laptops automatically reverts the fan speed control to "Auto" mode after a 120 second timeout window. To ensure that the user-selected fan profile (Max/Manual) persists, implement a keep-alive mechanism that periodically refreshes the fan mode trigger before the timeout occurs. - Introduce a delayed workqueue to trigger the fan mode refresh every 90 seconds, ensuring the system maintains the correct fan mode setting. - Integrate the refresh mechanism into hp_wmi_hwmon_enforce_ctx() to start, update or cancel the keep-alive process based on the current fan mode. This ensures that the driver stays in sync with the hardware. Tested on: HP Omen 16-wf1xxx (board ID 8C78) Signed-off-by: Krishna Chomal <krishna.chomal108@gmail.com> --- drivers/platform/x86/hp/hp-wmi.c | 52 +++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 9fb956ce809a..fbe012e7a342 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -31,6 +31,7 @@ #include <linux/string.h> #include <linux/dmi.h> #include <linux/fixp-arith.h> +#include <linux/workqueue.h> MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>"); MODULE_DESCRIPTION("HP laptop WMI driver"); @@ -365,8 +366,15 @@ struct hp_wmi_hwmon_priv { u8 gpu_delta; u8 mode; u8 pwm; + struct delayed_work keep_alive_dwork; }; +/* + * 90s delay to prevent the firmware from resetting fan mode after fixed + * 120s timeout + */ +#define KEEP_ALIVE_DELAY 90 + #define RPM_TO_PWM(rpm, ctx) fixp_linear_interpolate(0, 0, \ (ctx)->max_rpm, U8_MAX, \ clamp_val((rpm), \ @@ -2073,6 +2081,7 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) static void __exit hp_wmi_bios_remove(struct platform_device *device) { int i; + struct hp_wmi_hwmon_priv *ctx; for (i = 0; i < rfkill2_count; i++) { rfkill_unregister(rfkill2[i].rfkill); @@ -2091,6 +2100,10 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device) rfkill_unregister(wwan_rfkill); rfkill_destroy(wwan_rfkill); } + + ctx = platform_get_drvdata(device); + if (ctx) + cancel_delayed_work_sync(&ctx->keep_alive_dwork); } static int hp_wmi_resume_handler(struct device *device) @@ -2152,6 +2165,8 @@ static struct platform_driver hp_wmi_driver __refdata = { static int hp_wmi_hwmon_enforce_ctx(struct hp_wmi_hwmon_priv *ctx) { + int ret; + if (!ctx) return -ENODEV; @@ -2159,23 +2174,36 @@ static int hp_wmi_hwmon_enforce_ctx(struct hp_wmi_hwmon_priv *ctx) case PWM_MODE_MAX: if (is_victus_s_thermal_profile()) hp_wmi_get_fan_count_userdefine_trigger(); - return hp_wmi_fan_speed_max_set(1); + ret = hp_wmi_fan_speed_max_set(1); + break; case PWM_MODE_MANUAL: if (!is_victus_s_thermal_profile()) return -EOPNOTSUPP; - return hp_wmi_fan_speed_set(ctx, PWM_TO_RPM(ctx->pwm, ctx)); + ret = hp_wmi_fan_speed_set(ctx, PWM_TO_RPM(ctx->pwm, ctx)); + break; case PWM_MODE_AUTO: if (is_victus_s_thermal_profile()) { hp_wmi_get_fan_count_userdefine_trigger(); - return hp_wmi_fan_speed_max_reset(ctx); + ret = hp_wmi_fan_speed_max_reset(ctx); } else { - return hp_wmi_fan_speed_max_set(0); + ret = hp_wmi_fan_speed_max_set(0); } + break; default: /* shouldn't happen */ return -EINVAL; } + if (ret < 0) + return ret; + + /* Reschedule keep-alive work based on the new state */ + if (ctx->mode == PWM_MODE_MAX || ctx->mode == PWM_MODE_MANUAL) + schedule_delayed_work(&ctx->keep_alive_dwork, + secs_to_jiffies(KEEP_ALIVE_DELAY)); + else + cancel_delayed_work_sync(&ctx->keep_alive_dwork); + return 0; } @@ -2321,6 +2349,20 @@ static const struct hwmon_chip_info chip_info = { .info = info, }; +static void hp_wmi_hwmon_keep_alive_handler(struct work_struct *work) +{ + struct delayed_work *dwork; + struct hp_wmi_hwmon_priv *ctx; + + dwork = to_delayed_work(work); + ctx = container_of(dwork, struct hp_wmi_hwmon_priv, keep_alive_dwork); + /* + * Re-apply the current hwmon context settings. + * NOTE: hp_wmi_hwmon_enforce_ctx will handle the re-scheduling. + */ + hp_wmi_hwmon_enforce_ctx(ctx); +} + static int hp_wmi_hwmon_setup_ctx(struct hp_wmi_hwmon_priv *ctx) { u8 fan_data[128]; @@ -2377,6 +2419,8 @@ static int hp_wmi_hwmon_init(void) return PTR_ERR(hwmon); } + INIT_DELAYED_WORK(&ctx->keep_alive_dwork, hp_wmi_hwmon_keep_alive_handler); + platform_set_drvdata(hp_wmi_platform_dev, ctx); hp_wmi_hwmon_enforce_ctx(ctx); return 0; -- 2.52.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 2/2] platform/x86: hp-wmi: implement fan keep-alive 2025-12-25 14:23 ` [PATCH 2/2] platform/x86: hp-wmi: implement fan keep-alive Krishna Chomal @ 2025-12-29 13:21 ` Ilpo Järvinen 2025-12-30 14:33 ` Krishna Chomal 0 siblings, 1 reply; 10+ messages in thread From: Ilpo Järvinen @ 2025-12-29 13:21 UTC (permalink / raw) To: Krishna Chomal Cc: Hans de Goede, linux, platform-driver-x86, linux-hwmon, LKML On Thu, 25 Dec 2025, Krishna Chomal wrote: > The firmware on some HP laptops automatically reverts the fan speed > control to "Auto" mode after a 120 second timeout window. > > To ensure that the user-selected fan profile (Max/Manual) persists, > implement a keep-alive mechanism that periodically refreshes the fan > mode trigger before the timeout occurs. > > - Introduce a delayed workqueue to trigger the fan mode refresh every 90 > seconds, ensuring the system maintains the correct fan mode setting. > - Integrate the refresh mechanism into hp_wmi_hwmon_enforce_ctx() to > start, update or cancel the keep-alive process based on the current > fan mode. > > This ensures that the driver stays in sync with the hardware. > > Tested on: HP Omen 16-wf1xxx (board ID 8C78) > > Signed-off-by: Krishna Chomal <krishna.chomal108@gmail.com> > --- > drivers/platform/x86/hp/hp-wmi.c | 52 +++++++++++++++++++++++++++++--- > 1 file changed, 48 insertions(+), 4 deletions(-) > > diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c > index 9fb956ce809a..fbe012e7a342 100644 > --- a/drivers/platform/x86/hp/hp-wmi.c > +++ b/drivers/platform/x86/hp/hp-wmi.c > @@ -31,6 +31,7 @@ > #include <linux/string.h> > #include <linux/dmi.h> > #include <linux/fixp-arith.h> > +#include <linux/workqueue.h> > > MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>"); > MODULE_DESCRIPTION("HP laptop WMI driver"); > @@ -365,8 +366,15 @@ struct hp_wmi_hwmon_priv { > u8 gpu_delta; > u8 mode; > u8 pwm; > + struct delayed_work keep_alive_dwork; > }; > > +/* > + * 90s delay to prevent the firmware from resetting fan mode after fixed > + * 120s timeout > + */ > +#define KEEP_ALIVE_DELAY 90 For any time related define, please include its unit in the name so a person reading code can immediately know it. > + > #define RPM_TO_PWM(rpm, ctx) fixp_linear_interpolate(0, 0, \ > (ctx)->max_rpm, U8_MAX, \ > clamp_val((rpm), \ > @@ -2073,6 +2081,7 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) > static void __exit hp_wmi_bios_remove(struct platform_device *device) > { > int i; > + struct hp_wmi_hwmon_priv *ctx; > > for (i = 0; i < rfkill2_count; i++) { > rfkill_unregister(rfkill2[i].rfkill); > @@ -2091,6 +2100,10 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device) > rfkill_unregister(wwan_rfkill); > rfkill_destroy(wwan_rfkill); > } > + > + ctx = platform_get_drvdata(device); > + if (ctx) > + cancel_delayed_work_sync(&ctx->keep_alive_dwork); > } > > static int hp_wmi_resume_handler(struct device *device) > @@ -2152,6 +2165,8 @@ static struct platform_driver hp_wmi_driver __refdata = { > > static int hp_wmi_hwmon_enforce_ctx(struct hp_wmi_hwmon_priv *ctx) > { > + int ret; > + > if (!ctx) > return -ENODEV; > > @@ -2159,23 +2174,36 @@ static int hp_wmi_hwmon_enforce_ctx(struct hp_wmi_hwmon_priv *ctx) > case PWM_MODE_MAX: > if (is_victus_s_thermal_profile()) > hp_wmi_get_fan_count_userdefine_trigger(); > - return hp_wmi_fan_speed_max_set(1); > + ret = hp_wmi_fan_speed_max_set(1); > + break; > case PWM_MODE_MANUAL: > if (!is_victus_s_thermal_profile()) > return -EOPNOTSUPP; > - return hp_wmi_fan_speed_set(ctx, PWM_TO_RPM(ctx->pwm, ctx)); > + ret = hp_wmi_fan_speed_set(ctx, PWM_TO_RPM(ctx->pwm, ctx)); > + break; > case PWM_MODE_AUTO: > if (is_victus_s_thermal_profile()) { > hp_wmi_get_fan_count_userdefine_trigger(); > - return hp_wmi_fan_speed_max_reset(ctx); > + ret = hp_wmi_fan_speed_max_reset(ctx); > } else { > - return hp_wmi_fan_speed_max_set(0); > + ret = hp_wmi_fan_speed_max_set(0); > } > + break; > default: > /* shouldn't happen */ > return -EINVAL; > } > > + if (ret < 0) > + return ret; > + > + /* Reschedule keep-alive work based on the new state */ > + if (ctx->mode == PWM_MODE_MAX || ctx->mode == PWM_MODE_MANUAL) > + schedule_delayed_work(&ctx->keep_alive_dwork, > + secs_to_jiffies(KEEP_ALIVE_DELAY)); > + else > + cancel_delayed_work_sync(&ctx->keep_alive_dwork); This is now duplicating the switch/case, just add these to the existing cases. You may want to introduce ret variable already in PATCH 1/2 and add a note there that an upcoming change is going to add keep-alive so the return value has to be stored temporarily. > + > return 0; > } > > @@ -2321,6 +2349,20 @@ static const struct hwmon_chip_info chip_info = { > .info = info, > }; > > +static void hp_wmi_hwmon_keep_alive_handler(struct work_struct *work) > +{ > + struct delayed_work *dwork; > + struct hp_wmi_hwmon_priv *ctx; > + > + dwork = to_delayed_work(work); > + ctx = container_of(dwork, struct hp_wmi_hwmon_priv, keep_alive_dwork); > + /* > + * Re-apply the current hwmon context settings. > + * NOTE: hp_wmi_hwmon_enforce_ctx will handle the re-scheduling. > + */ > + hp_wmi_hwmon_enforce_ctx(ctx); (I only now understand why you named this function like this, and I still don't think it's a good name for it as you've other callers besides this one real "enforcing" case.) > +} > + > static int hp_wmi_hwmon_setup_ctx(struct hp_wmi_hwmon_priv *ctx) > { > u8 fan_data[128]; > @@ -2377,6 +2419,8 @@ static int hp_wmi_hwmon_init(void) > return PTR_ERR(hwmon); > } > > + INIT_DELAYED_WORK(&ctx->keep_alive_dwork, hp_wmi_hwmon_keep_alive_handler); > + platform_set_drvdata(hp_wmi_platform_dev, ctx); > hp_wmi_hwmon_enforce_ctx(ctx); > > return 0; > -- i. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 2/2] platform/x86: hp-wmi: implement fan keep-alive 2025-12-29 13:21 ` Ilpo Järvinen @ 2025-12-30 14:33 ` Krishna Chomal 0 siblings, 0 replies; 10+ messages in thread From: Krishna Chomal @ 2025-12-30 14:33 UTC (permalink / raw) To: Ilpo Järvinen Cc: Hans de Goede, linux, platform-driver-x86, linux-hwmon, LKML On Mon, Dec 29, 2025 at 03:21:42PM +0200, Ilpo Järvinen wrote: [snip] >> +/* >> + * 90s delay to prevent the firmware from resetting fan mode after fixed >> + * 120s timeout >> + */ >> +#define KEEP_ALIVE_DELAY 90 > >For any time related define, please include its unit in the name so a >person reading code can immediately know it. > Renamed to KEEP_ALIVE_DELAY_SECS v2. [snip] >> @@ -2159,23 +2174,36 @@ static int hp_wmi_hwmon_enforce_ctx(struct hp_wmi_hwmon_priv *ctx) >> case PWM_MODE_MAX: >> if (is_victus_s_thermal_profile()) >> hp_wmi_get_fan_count_userdefine_trigger(); >> - return hp_wmi_fan_speed_max_set(1); >> + ret = hp_wmi_fan_speed_max_set(1); >> + break; >> case PWM_MODE_MANUAL: >> if (!is_victus_s_thermal_profile()) >> return -EOPNOTSUPP; >> - return hp_wmi_fan_speed_set(ctx, PWM_TO_RPM(ctx->pwm, ctx)); >> + ret = hp_wmi_fan_speed_set(ctx, PWM_TO_RPM(ctx->pwm, ctx)); >> + break; >> case PWM_MODE_AUTO: >> if (is_victus_s_thermal_profile()) { >> hp_wmi_get_fan_count_userdefine_trigger(); >> - return hp_wmi_fan_speed_max_reset(ctx); >> + ret = hp_wmi_fan_speed_max_reset(ctx); >> } else { >> - return hp_wmi_fan_speed_max_set(0); >> + ret = hp_wmi_fan_speed_max_set(0); >> } >> + break; >> default: >> /* shouldn't happen */ >> return -EINVAL; >> } >> >> + if (ret < 0) >> + return ret; >> + >> + /* Reschedule keep-alive work based on the new state */ >> + if (ctx->mode == PWM_MODE_MAX || ctx->mode == PWM_MODE_MANUAL) >> + schedule_delayed_work(&ctx->keep_alive_dwork, >> + secs_to_jiffies(KEEP_ALIVE_DELAY)); >> + else >> + cancel_delayed_work_sync(&ctx->keep_alive_dwork); > >This is now duplicating the switch/case, just add these to the existing >cases. > >You may want to introduce ret variable already in PATCH 1/2 and add a note >there that an upcoming change is going to add keep-alive so the return >value has to be stored temporarily. > Yes, handled the re-scheduling in switch/case itself in v2 and introduced the temporary ret variable in patch 1/2. >> + >> return 0; >> } >> >> @@ -2321,6 +2349,20 @@ static const struct hwmon_chip_info chip_info = { >> .info = info, >> }; >> >> +static void hp_wmi_hwmon_keep_alive_handler(struct work_struct *work) >> +{ >> + struct delayed_work *dwork; >> + struct hp_wmi_hwmon_priv *ctx; >> + >> + dwork = to_delayed_work(work); >> + ctx = container_of(dwork, struct hp_wmi_hwmon_priv, keep_alive_dwork); >> + /* >> + * Re-apply the current hwmon context settings. >> + * NOTE: hp_wmi_hwmon_enforce_ctx will handle the re-scheduling. >> + */ >> + hp_wmi_hwmon_enforce_ctx(ctx); > >(I only now understand why you named this function like this, and I still >don't think it's a good name for it as you've other callers besides this >one real "enforcing" case.) > Yes named to "apply_fan_settings" in patch 1/2 of this series. ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 0/2] platform/x86: hp-wmi: Add manual fan support for Victus S laptops 2025-12-25 14:23 [PATCH 0/2] platform/x86: hp-wmi: Add manual fan support for Victus S laptops Krishna Chomal 2025-12-25 14:23 ` [PATCH 1/2] platform/x86: hp-wmi: add manual fan control for Victus S models Krishna Chomal 2025-12-25 14:23 ` [PATCH 2/2] platform/x86: hp-wmi: implement fan keep-alive Krishna Chomal @ 2025-12-30 14:50 ` Krishna Chomal 2025-12-30 14:50 ` [PATCH v2 1/2] platform/x86: hp-wmi: add manual fan control for Victus S models Krishna Chomal 2025-12-30 14:50 ` [PATCH v2 2/2] platform/x86: hp-wmi: implement fan keep-alive Krishna Chomal 2 siblings, 2 replies; 10+ messages in thread From: Krishna Chomal @ 2025-12-30 14:50 UTC (permalink / raw) To: ilpo.jarvinen, hansg, linux Cc: platform-driver-x86, linux-hwmon, linux-kernel, Krishna Chomal This series adds support for manual fan speed control and PWM reporting for HP Victus S-style laptops. The first patch refactors the hwmon implementation to use a per-device private context for state tracking. It implements RPM-to-PWM conversion using linear interpolation based on the laptop's internal fan tables retrieved via WMI. It also introduces PWM_MODE_MANUAL, allowing users to set specific fan speeds. The second patch addresses a firmware-specific behavior where the system reverts to "Auto" fan mode after a 120-second timeout if no WMI fan commands are received. A delayed workqueue is implemented to act as a keep-alive heartbeat, refreshing the fan state every 90 seconds to ensure user-selected profiles remain active indefinitely. Changes in v2: - Refactored hp_wmi_apply_fan_settings() to use a 'ret' variable and use a common path to set fan settings and prepare for keep-alive logic. - Replaced raw buffer casting with proper fan table structs. - Converted RPM/PWM macros to static inline functions. - Renamed internal context variable from 'ctx' to 'priv' for consistency. - Renamed delay macro to KEEP_ALIVE_DELAY_SECS. - Added missing headers and removed redundant NULL checks. Tested on: HP Omen 16-wf1xxx (board ID 8C78) Krishna Chomal (2): platform/x86: hp-wmi: add manual fan control for Victus S models platform/x86: hp-wmi: implement fan keep-alive drivers/platform/x86/hp/hp-wmi.c | 303 ++++++++++++++++++++++++++----- 1 file changed, 258 insertions(+), 45 deletions(-) -- 2.52.0 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 1/2] platform/x86: hp-wmi: add manual fan control for Victus S models 2025-12-30 14:50 ` [PATCH v2 0/2] platform/x86: hp-wmi: Add manual fan support for Victus S laptops Krishna Chomal @ 2025-12-30 14:50 ` Krishna Chomal 2025-12-30 14:50 ` [PATCH v2 2/2] platform/x86: hp-wmi: implement fan keep-alive Krishna Chomal 1 sibling, 0 replies; 10+ messages in thread From: Krishna Chomal @ 2025-12-30 14:50 UTC (permalink / raw) To: ilpo.jarvinen, hansg, linux Cc: platform-driver-x86, linux-hwmon, linux-kernel, Krishna Chomal Add manual fan speed control and PWM reporting for HP Victus S-series laptops. While HPWMI_FAN_SPEED_SET_QUERY was previously added to reset max fan mode, it is actually capable of individual fan control. This patch implements hp_wmi_fan_speed_set() to allow manual control and hides PWM inputs for non-Victus devices as the query is Victus specific. The existing hp_wmi_fan_speed_max_get() query is unreliable on Victus S firmware, often incorrectly reporting "Auto" mode even when "Max" is active. To resolve this synchronization issue, move state tracking to a per-device private context and apply "Auto" mode during driver initialization to ensure a consistent starting point. Refactor hp_wmi_apply_fan_settings() to use an intermediate ret variable. This prepares the switch block for keep-alive logic being added in a later patch, avoiding the need for duplicated mode check. Tested on: HP Omen 16-wf1xxx (board ID 8C78) Signed-off-by: Krishna Chomal <krishna.chomal108@gmail.com> --- Changes in v2: - Added limits.h and minmax.h headers - Re-written pwm-to-rpm (and vice-versa) conversion from macros to static inline functions - Removed redundant NULL checking of drvdata - Made integer promotion explicit by casting intermediate result to unsigned int when calculating the GPU fan speed - Renamed "enforce_ctx" to "apply_fan_settings" for clarity - Removed unnecessary comments - Renamed "ctx" to "priv" as it is more consistent for drvdata - Added new struct victus_s_fan_table to avoid complex type casting --- drivers/platform/x86/hp/hp-wmi.c | 263 +++++++++++++++++++++++++------ 1 file changed, 218 insertions(+), 45 deletions(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index f4ea1ea05997..ef419575174c 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -30,6 +30,10 @@ #include <linux/rfkill.h> #include <linux/string.h> #include <linux/dmi.h> +#include <linux/fixp-arith.h> +#include <linux/limits.h> +#include <linux/minmax.h> +#include <linux/compiler_attributes.h> MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>"); MODULE_DESCRIPTION("HP laptop WMI driver"); @@ -190,7 +194,8 @@ enum hp_wmi_gm_commandtype { HPWMI_SET_GPU_THERMAL_MODES_QUERY = 0x22, HPWMI_SET_POWER_LIMITS_QUERY = 0x29, HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY = 0x2D, - HPWMI_FAN_SPEED_SET_QUERY = 0x2E, + HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY = 0x2E, + HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY = 0x2F, }; enum hp_wmi_command { @@ -348,6 +353,51 @@ static const char * const tablet_chassis_types[] = { #define DEVICE_MODE_TABLET 0x06 +#define CPU_FAN 0 +#define GPU_FAN 1 + +enum pwm_modes { + PWM_MODE_MAX = 0, + PWM_MODE_MANUAL = 1, + PWM_MODE_AUTO = 2, +}; + +struct hp_wmi_hwmon_priv { + u8 min_rpm; + u8 max_rpm; + u8 gpu_delta; + u8 mode; + u8 pwm; +}; + +struct victus_s_fan_table_header { + u8 unknown; + u8 num_entries; +} __packed; + +struct victus_s_fan_table_entry { + u8 cpu_rpm; + u8 gpu_rpm; + u8 unknown; +} __packed; + +struct victus_s_fan_table { + struct victus_s_fan_table_header header; + struct victus_s_fan_table_entry entries[]; +} __packed; + +static inline u8 rpm_to_pwm(u8 rpm, struct hp_wmi_hwmon_priv *priv) +{ + return fixp_linear_interpolate(0, 0, priv->max_rpm, U8_MAX, + clamp_val(rpm, 0, priv->max_rpm)); +} + +static inline u8 pwm_to_rpm(u8 pwm, struct hp_wmi_hwmon_priv *priv) +{ + return fixp_linear_interpolate(0, 0, U8_MAX, priv->max_rpm, + clamp_val(pwm, 0, U8_MAX)); +} + /* map output size to the corresponding WMI method id */ static inline int encode_outsize_for_pvsz(int outsize) { @@ -637,41 +687,53 @@ static int hp_wmi_fan_speed_max_set(int enabled) return enabled; } -static int hp_wmi_fan_speed_reset(void) +static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) { - u8 fan_speed[2] = { HP_FAN_SPEED_AUTOMATIC, HP_FAN_SPEED_AUTOMATIC }; + u8 fan_speed[2]; int ret; - ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM, - &fan_speed, sizeof(fan_speed), 0); - - return ret; -} + fan_speed[CPU_FAN] = speed; + fan_speed[GPU_FAN] = speed; -static int hp_wmi_fan_speed_max_reset(void) -{ - int ret; + /* + * GPU fan speed is always a little higher than CPU fan speed, we fetch + * this delta value from the fan table during hwmon init. + * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to + * automatic mode. + */ + if (speed != HP_FAN_SPEED_AUTOMATIC) + fan_speed[GPU_FAN] = clamp_val((unsigned int)speed + + (unsigned int)priv->gpu_delta, + 0, U8_MAX); + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) + return ret; + /* Max fans need to be explicitly disabled */ ret = hp_wmi_fan_speed_max_set(0); - if (ret) + if (ret < 0) return ret; + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY, HPWMI_GM, + &fan_speed, sizeof(fan_speed), 0); - /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */ - ret = hp_wmi_fan_speed_reset(); return ret; } -static int hp_wmi_fan_speed_max_get(void) +static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv) { - int val = 0, ret; + return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC); +} - ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM, - &val, zero_if_sup(val), sizeof(val)); +static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv) +{ + int ret; + ret = hp_wmi_fan_speed_max_set(0); if (ret) - return ret < 0 ? ret : -EINVAL; + return ret; - return val; + /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */ + return hp_wmi_fan_speed_reset(priv); } static int __init hp_wmi_bios_2008_later(void) @@ -2108,12 +2170,45 @@ static struct platform_driver hp_wmi_driver __refdata = { .remove = __exit_p(hp_wmi_bios_remove), }; +static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) +{ + int ret; + + switch (priv->mode) { + case PWM_MODE_MAX: + if (is_victus_s_thermal_profile()) + hp_wmi_get_fan_count_userdefine_trigger(); + ret = hp_wmi_fan_speed_max_set(1); + return ret; + case PWM_MODE_MANUAL: + if (!is_victus_s_thermal_profile()) + return -EOPNOTSUPP; + ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv)); + return ret; + case PWM_MODE_AUTO: + if (is_victus_s_thermal_profile()) { + hp_wmi_get_fan_count_userdefine_trigger(); + ret = hp_wmi_fan_speed_max_reset(priv); + } else { + ret = hp_wmi_fan_speed_max_set(0); + } + return ret; + default: + /* shouldn't happen */ + return -EINVAL; + } + + return 0; +} + static umode_t hp_wmi_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { switch (type) { case hwmon_pwm: + if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) + return 0; return 0644; case hwmon_fan: if (is_victus_s_thermal_profile()) { @@ -2134,8 +2229,10 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { - int ret; + struct hp_wmi_hwmon_priv *priv; + int current_rpm, ret; + priv = dev_get_drvdata(dev); switch (type) { case hwmon_fan: if (is_victus_s_thermal_profile()) @@ -2147,16 +2244,21 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, *val = ret; return 0; case hwmon_pwm: - switch (hp_wmi_fan_speed_max_get()) { - case 0: - /* 0 is automatic fan, which is 2 for hwmon */ - *val = 2; + if (attr == hwmon_pwm_input) { + if (!is_victus_s_thermal_profile()) + return -EOPNOTSUPP; + ret = hp_wmi_get_fan_speed_victus_s(channel); + if (ret < 0) + return ret; + current_rpm = ret; + *val = rpm_to_pwm(current_rpm / 100, priv); return 0; - case 1: - /* 1 is max fan, which is 0 - * (no fan speed control) for hwmon - */ - *val = 0; + } + switch (priv->mode) { + case PWM_MODE_MAX: + case PWM_MODE_MANUAL: + case PWM_MODE_AUTO: + *val = priv->mode; return 0; default: /* shouldn't happen */ @@ -2170,23 +2272,47 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { + struct hp_wmi_hwmon_priv *priv; + int current_rpm, ret; + + priv = dev_get_drvdata(dev); switch (type) { case hwmon_pwm: + if (attr == hwmon_pwm_input) { + if (!is_victus_s_thermal_profile()) + return -EOPNOTSUPP; + /* pwm input is invalid when not in manual mode */ + if (priv->mode != PWM_MODE_MANUAL) + return -EINVAL; + /* ensure pwm input is within valid fan speeds */ + priv->pwm = rpm_to_pwm(clamp_val(pwm_to_rpm(val, priv), + priv->min_rpm, + priv->max_rpm), + priv); + return hp_wmi_apply_fan_settings(priv); + } switch (val) { - case 0: - if (is_victus_s_thermal_profile()) - hp_wmi_get_fan_count_userdefine_trigger(); - /* 0 is no fan speed control (max), which is 1 for us */ - return hp_wmi_fan_speed_max_set(1); - case 2: - /* 2 is automatic speed control, which is 0 for us */ - if (is_victus_s_thermal_profile()) { - hp_wmi_get_fan_count_userdefine_trigger(); - return hp_wmi_fan_speed_max_reset(); - } else - return hp_wmi_fan_speed_max_set(0); + case PWM_MODE_MAX: + priv->mode = PWM_MODE_MAX; + return hp_wmi_apply_fan_settings(priv); + case PWM_MODE_MANUAL: + if (!is_victus_s_thermal_profile()) + return -EOPNOTSUPP; + /* + * When switching to manual mode, set fan speed to + * current RPM values to ensure a smooth transition. + */ + ret = hp_wmi_get_fan_speed_victus_s(channel); + if (ret < 0) + return ret; + current_rpm = ret; + priv->pwm = rpm_to_pwm(current_rpm / 100, priv); + priv->mode = PWM_MODE_MANUAL; + return hp_wmi_apply_fan_settings(priv); + case PWM_MODE_AUTO: + priv->mode = PWM_MODE_AUTO; + return hp_wmi_apply_fan_settings(priv); default: - /* we don't support manual fan speed control */ return -EINVAL; } default: @@ -2196,7 +2322,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, static const struct hwmon_channel_info * const info[] = { HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), - HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT), NULL }; @@ -2211,12 +2337,57 @@ static const struct hwmon_chip_info chip_info = { .info = info, }; +static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) +{ + u8 fan_data[128] = { 0 }; + struct victus_s_fan_table *fan_table; + u8 min_rpm, max_rpm, gpu_delta; + int ret; + + /* Default behaviour on hwmon init is automatic mode */ + priv->mode = PWM_MODE_AUTO; + + /* Bypass all non-Victus S devices */ + if (!is_victus_s_thermal_profile()) + return 0; + + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, + HPWMI_GM, &fan_data, 4, sizeof(fan_data)); + if (ret) + return ret; + + fan_table = (struct victus_s_fan_table *)fan_data; + if (fan_table->header.num_entries == 0 || + sizeof(struct victus_s_fan_table_header) + + sizeof(struct victus_s_fan_table_entry) * fan_table->header.num_entries > + sizeof(fan_data)) + return -EINVAL; + + min_rpm = fan_table->entries[0].cpu_rpm; + max_rpm = fan_table->entries[fan_table->header.num_entries - 1].cpu_rpm; + gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm; + priv->min_rpm = min_rpm; + priv->max_rpm = max_rpm; + priv->gpu_delta = gpu_delta; + + return 0; +} + static int hp_wmi_hwmon_init(void) { struct device *dev = &hp_wmi_platform_dev->dev; + struct hp_wmi_hwmon_priv *priv; struct device *hwmon; + int ret; - hwmon = devm_hwmon_device_register_with_info(dev, "hp", &hp_wmi_driver, + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ret = hp_wmi_setup_fan_settings(priv); + if (ret) + return ret; + hwmon = devm_hwmon_device_register_with_info(dev, "hp", priv, &chip_info, NULL); if (IS_ERR(hwmon)) { @@ -2224,6 +2395,8 @@ static int hp_wmi_hwmon_init(void) return PTR_ERR(hwmon); } + hp_wmi_apply_fan_settings(priv); + return 0; } -- 2.52.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 2/2] platform/x86: hp-wmi: implement fan keep-alive 2025-12-30 14:50 ` [PATCH v2 0/2] platform/x86: hp-wmi: Add manual fan support for Victus S laptops Krishna Chomal 2025-12-30 14:50 ` [PATCH v2 1/2] platform/x86: hp-wmi: add manual fan control for Victus S models Krishna Chomal @ 2025-12-30 14:50 ` Krishna Chomal 1 sibling, 0 replies; 10+ messages in thread From: Krishna Chomal @ 2025-12-30 14:50 UTC (permalink / raw) To: ilpo.jarvinen, hansg, linux Cc: platform-driver-x86, linux-hwmon, linux-kernel, Krishna Chomal The firmware on some HP laptops automatically reverts the fan speed control to "Auto" mode after a 120 second timeout window. To ensure that the user-selected fan profile (Max/Manual) persists, implement a keep-alive mechanism that periodically refreshes the fan mode trigger before the timeout occurs. - Introduce a delayed workqueue to trigger the fan mode refresh every 90 seconds, ensuring the system maintains the correct fan mode setting. - Integrate the refresh mechanism into hp_wmi_hwmon_enforce_ctx() to start, update or cancel the keep-alive process based on the current fan mode. This ensures that the driver stays in sync with the hardware. Tested on: HP Omen 16-wf1xxx (board ID 8C78) Signed-off-by: Krishna Chomal <krishna.chomal108@gmail.com> --- Changes in v2: - Explicitly specify time unit in KEEP_ALIVE_DELAY macro - Handle work rescheduling directly in switch/case --- drivers/platform/x86/hp/hp-wmi.c | 46 +++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index ef419575174c..cf9327e1f40e 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -34,6 +34,7 @@ #include <linux/limits.h> #include <linux/minmax.h> #include <linux/compiler_attributes.h> +#include <linux/workqueue.h> MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>"); MODULE_DESCRIPTION("HP laptop WMI driver"); @@ -368,6 +369,7 @@ struct hp_wmi_hwmon_priv { u8 gpu_delta; u8 mode; u8 pwm; + struct delayed_work keep_alive_dwork; }; struct victus_s_fan_table_header { @@ -386,6 +388,12 @@ struct victus_s_fan_table { struct victus_s_fan_table_entry entries[]; } __packed; +/* + * 90s delay to prevent the firmware from resetting fan mode after fixed + * 120s timeout + */ +#define KEEP_ALIVE_DELAY_SECS 90 + static inline u8 rpm_to_pwm(u8 rpm, struct hp_wmi_hwmon_priv *priv) { return fixp_linear_interpolate(0, 0, priv->max_rpm, U8_MAX, @@ -2093,6 +2101,7 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) static void __exit hp_wmi_bios_remove(struct platform_device *device) { int i; + struct hp_wmi_hwmon_priv *ctx; for (i = 0; i < rfkill2_count; i++) { rfkill_unregister(rfkill2[i].rfkill); @@ -2111,6 +2120,10 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device) rfkill_unregister(wwan_rfkill); rfkill_destroy(wwan_rfkill); } + + ctx = platform_get_drvdata(device); + if (ctx) + cancel_delayed_work_sync(&ctx->keep_alive_dwork); } static int hp_wmi_resume_handler(struct device *device) @@ -2179,12 +2192,20 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) if (is_victus_s_thermal_profile()) hp_wmi_get_fan_count_userdefine_trigger(); ret = hp_wmi_fan_speed_max_set(1); - return ret; + if (ret < 0) + return ret; + schedule_delayed_work(&priv->keep_alive_dwork, + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); + return 0; case PWM_MODE_MANUAL: if (!is_victus_s_thermal_profile()) return -EOPNOTSUPP; ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv)); - return ret; + if (ret < 0) + return ret; + schedule_delayed_work(&priv->keep_alive_dwork, + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); + return 0; case PWM_MODE_AUTO: if (is_victus_s_thermal_profile()) { hp_wmi_get_fan_count_userdefine_trigger(); @@ -2192,7 +2213,10 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) } else { ret = hp_wmi_fan_speed_max_set(0); } - return ret; + if (ret < 0) + return ret; + cancel_delayed_work_sync(&priv->keep_alive_dwork); + return 0; default: /* shouldn't happen */ return -EINVAL; @@ -2337,6 +2361,20 @@ static const struct hwmon_chip_info chip_info = { .info = info, }; +static void hp_wmi_hwmon_keep_alive_handler(struct work_struct *work) +{ + struct delayed_work *dwork; + struct hp_wmi_hwmon_priv *ctx; + + dwork = to_delayed_work(work); + ctx = container_of(dwork, struct hp_wmi_hwmon_priv, keep_alive_dwork); + /* + * Re-apply the current hwmon context settings. + * NOTE: hp_wmi_apply_fan_settings will handle the re-scheduling. + */ + hp_wmi_apply_fan_settings(ctx); +} + static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) { u8 fan_data[128] = { 0 }; @@ -2395,6 +2433,8 @@ static int hp_wmi_hwmon_init(void) return PTR_ERR(hwmon); } + INIT_DELAYED_WORK(&priv->keep_alive_dwork, hp_wmi_hwmon_keep_alive_handler); + platform_set_drvdata(hp_wmi_platform_dev, priv); hp_wmi_apply_fan_settings(priv); return 0; -- 2.52.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
end of thread, other threads:[~2025-12-30 14:51 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-12-25 14:23 [PATCH 0/2] platform/x86: hp-wmi: Add manual fan support for Victus S laptops Krishna Chomal 2025-12-25 14:23 ` [PATCH 1/2] platform/x86: hp-wmi: add manual fan control for Victus S models Krishna Chomal 2025-12-29 13:08 ` Ilpo Järvinen 2025-12-30 14:22 ` Krishna Chomal 2025-12-25 14:23 ` [PATCH 2/2] platform/x86: hp-wmi: implement fan keep-alive Krishna Chomal 2025-12-29 13:21 ` Ilpo Järvinen 2025-12-30 14:33 ` Krishna Chomal 2025-12-30 14:50 ` [PATCH v2 0/2] platform/x86: hp-wmi: Add manual fan support for Victus S laptops Krishna Chomal 2025-12-30 14:50 ` [PATCH v2 1/2] platform/x86: hp-wmi: add manual fan control for Victus S models Krishna Chomal 2025-12-30 14:50 ` [PATCH v2 2/2] platform/x86: hp-wmi: implement fan keep-alive Krishna Chomal
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).