Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

platform/x86: hp-wmi: add manual fan control for Victus S models

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>
Link: https://patch.msgid.link/20260113123738.222244-3-krishna.chomal108@gmail.com
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Krishna Chomal and committed by
Ilpo Järvinen
46be1453 60f2d5d0

+217 -46
+217 -46
drivers/platform/x86/hp/hp-wmi.c
··· 15 15 16 16 #include <linux/acpi.h> 17 17 #include <linux/cleanup.h> 18 + #include <linux/compiler_attributes.h> 18 19 #include <linux/dmi.h> 20 + #include <linux/fixp-arith.h> 19 21 #include <linux/hwmon.h> 20 22 #include <linux/init.h> 21 23 #include <linux/input.h> 22 24 #include <linux/input/sparse-keymap.h> 23 25 #include <linux/kernel.h> 26 + #include <linux/limits.h> 27 + #include <linux/minmax.h> 24 28 #include <linux/module.h> 25 29 #include <linux/mutex.h> 26 30 #include <linux/platform_device.h> ··· 194 190 HPWMI_SET_GPU_THERMAL_MODES_QUERY = 0x22, 195 191 HPWMI_SET_POWER_LIMITS_QUERY = 0x29, 196 192 HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY = 0x2D, 197 - HPWMI_FAN_SPEED_SET_QUERY = 0x2E, 193 + HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY = 0x2E, 194 + HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY = 0x2F, 198 195 }; 199 196 200 197 enum hp_wmi_command { ··· 352 347 }; 353 348 354 349 #define DEVICE_MODE_TABLET 0x06 350 + 351 + #define CPU_FAN 0 352 + #define GPU_FAN 1 353 + 354 + enum pwm_modes { 355 + PWM_MODE_MAX = 0, 356 + PWM_MODE_MANUAL = 1, 357 + PWM_MODE_AUTO = 2, 358 + }; 359 + 360 + struct hp_wmi_hwmon_priv { 361 + u8 min_rpm; 362 + u8 max_rpm; 363 + u8 gpu_delta; 364 + u8 mode; 365 + u8 pwm; 366 + }; 367 + 368 + struct victus_s_fan_table_header { 369 + u8 unknown; 370 + u8 num_entries; 371 + } __packed; 372 + 373 + struct victus_s_fan_table_entry { 374 + u8 cpu_rpm; 375 + u8 gpu_rpm; 376 + u8 unknown; 377 + } __packed; 378 + 379 + struct victus_s_fan_table { 380 + struct victus_s_fan_table_header header; 381 + struct victus_s_fan_table_entry entries[]; 382 + } __packed; 383 + 384 + static inline u8 rpm_to_pwm(u8 rpm, struct hp_wmi_hwmon_priv *priv) 385 + { 386 + return fixp_linear_interpolate(0, 0, priv->max_rpm, U8_MAX, 387 + clamp_val(rpm, 0, priv->max_rpm)); 388 + } 389 + 390 + static inline u8 pwm_to_rpm(u8 pwm, struct hp_wmi_hwmon_priv *priv) 391 + { 392 + return fixp_linear_interpolate(0, 0, U8_MAX, priv->max_rpm, 393 + clamp_val(pwm, 0, U8_MAX)); 394 + } 355 395 356 396 /* map output size to the corresponding WMI method id */ 357 397 static inline int encode_outsize_for_pvsz(int outsize) ··· 687 637 return enabled; 688 638 } 689 639 690 - static int hp_wmi_fan_speed_reset(void) 640 + static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) 691 641 { 692 - u8 fan_speed[2] = { HP_FAN_SPEED_AUTOMATIC, HP_FAN_SPEED_AUTOMATIC }; 693 - int ret; 642 + u8 fan_speed[2]; 643 + int gpu_speed, ret; 694 644 695 - ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM, 645 + fan_speed[CPU_FAN] = speed; 646 + fan_speed[GPU_FAN] = speed; 647 + 648 + /* 649 + * GPU fan speed is always a little higher than CPU fan speed, we fetch 650 + * this delta value from the fan table during hwmon init. 651 + * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to 652 + * automatic mode. 653 + */ 654 + if (speed != HP_FAN_SPEED_AUTOMATIC) { 655 + gpu_speed = speed + priv->gpu_delta; 656 + fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX); 657 + } 658 + 659 + ret = hp_wmi_get_fan_count_userdefine_trigger(); 660 + if (ret < 0) 661 + return ret; 662 + /* Max fans need to be explicitly disabled */ 663 + ret = hp_wmi_fan_speed_max_set(0); 664 + if (ret < 0) 665 + return ret; 666 + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY, HPWMI_GM, 696 667 &fan_speed, sizeof(fan_speed), 0); 697 668 698 669 return ret; 699 670 } 700 671 701 - static int hp_wmi_fan_speed_max_reset(void) 672 + static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv) 673 + { 674 + return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC); 675 + } 676 + 677 + static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv) 702 678 { 703 679 int ret; 704 680 ··· 733 657 return ret; 734 658 735 659 /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */ 736 - ret = hp_wmi_fan_speed_reset(); 737 - return ret; 738 - } 739 - 740 - static int hp_wmi_fan_speed_max_get(void) 741 - { 742 - int val = 0, ret; 743 - 744 - ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM, 745 - &val, zero_if_sup(val), sizeof(val)); 746 - 747 - if (ret) 748 - return ret < 0 ? ret : -EINVAL; 749 - 750 - return val; 660 + return hp_wmi_fan_speed_reset(priv); 751 661 } 752 662 753 663 static int __init hp_wmi_bios_2008_later(void) ··· 2170 2108 .remove = __exit_p(hp_wmi_bios_remove), 2171 2109 }; 2172 2110 2111 + static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) 2112 + { 2113 + int ret; 2114 + 2115 + switch (priv->mode) { 2116 + case PWM_MODE_MAX: 2117 + if (is_victus_s_thermal_profile()) 2118 + hp_wmi_get_fan_count_userdefine_trigger(); 2119 + ret = hp_wmi_fan_speed_max_set(1); 2120 + return ret; 2121 + case PWM_MODE_MANUAL: 2122 + if (!is_victus_s_thermal_profile()) 2123 + return -EOPNOTSUPP; 2124 + ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv)); 2125 + return ret; 2126 + case PWM_MODE_AUTO: 2127 + if (is_victus_s_thermal_profile()) { 2128 + hp_wmi_get_fan_count_userdefine_trigger(); 2129 + ret = hp_wmi_fan_speed_max_reset(priv); 2130 + } else { 2131 + ret = hp_wmi_fan_speed_max_set(0); 2132 + } 2133 + return ret; 2134 + default: 2135 + /* shouldn't happen */ 2136 + return -EINVAL; 2137 + } 2138 + 2139 + return 0; 2140 + } 2141 + 2173 2142 static umode_t hp_wmi_hwmon_is_visible(const void *data, 2174 2143 enum hwmon_sensor_types type, 2175 2144 u32 attr, int channel) 2176 2145 { 2177 2146 switch (type) { 2178 2147 case hwmon_pwm: 2148 + if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) 2149 + return 0; 2179 2150 return 0644; 2180 2151 case hwmon_fan: 2181 2152 if (is_victus_s_thermal_profile()) { ··· 2229 2134 static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 2230 2135 u32 attr, int channel, long *val) 2231 2136 { 2232 - int ret; 2137 + struct hp_wmi_hwmon_priv *priv; 2138 + int rpm, ret; 2233 2139 2140 + priv = dev_get_drvdata(dev); 2234 2141 switch (type) { 2235 2142 case hwmon_fan: 2236 2143 if (is_victus_s_thermal_profile()) ··· 2244 2147 *val = ret; 2245 2148 return 0; 2246 2149 case hwmon_pwm: 2247 - switch (hp_wmi_fan_speed_max_get()) { 2248 - case 0: 2249 - /* 0 is automatic fan, which is 2 for hwmon */ 2250 - *val = 2; 2150 + if (attr == hwmon_pwm_input) { 2151 + if (!is_victus_s_thermal_profile()) 2152 + return -EOPNOTSUPP; 2153 + 2154 + rpm = hp_wmi_get_fan_speed_victus_s(channel); 2155 + if (rpm < 0) 2156 + return rpm; 2157 + *val = rpm_to_pwm(rpm / 100, priv); 2251 2158 return 0; 2252 - case 1: 2253 - /* 1 is max fan, which is 0 2254 - * (no fan speed control) for hwmon 2255 - */ 2256 - *val = 0; 2159 + } 2160 + switch (priv->mode) { 2161 + case PWM_MODE_MAX: 2162 + case PWM_MODE_MANUAL: 2163 + case PWM_MODE_AUTO: 2164 + *val = priv->mode; 2257 2165 return 0; 2258 2166 default: 2259 2167 /* shouldn't happen */ ··· 2272 2170 static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, 2273 2171 u32 attr, int channel, long val) 2274 2172 { 2173 + struct hp_wmi_hwmon_priv *priv; 2174 + int rpm; 2175 + 2176 + priv = dev_get_drvdata(dev); 2275 2177 switch (type) { 2276 2178 case hwmon_pwm: 2179 + if (attr == hwmon_pwm_input) { 2180 + if (!is_victus_s_thermal_profile()) 2181 + return -EOPNOTSUPP; 2182 + /* PWM input is invalid when not in manual mode */ 2183 + if (priv->mode != PWM_MODE_MANUAL) 2184 + return -EINVAL; 2185 + 2186 + /* ensure PWM input is within valid fan speeds */ 2187 + rpm = pwm_to_rpm(val, priv); 2188 + rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm); 2189 + priv->pwm = rpm_to_pwm(rpm, priv); 2190 + return hp_wmi_apply_fan_settings(priv); 2191 + } 2277 2192 switch (val) { 2278 - case 0: 2279 - if (is_victus_s_thermal_profile()) 2280 - hp_wmi_get_fan_count_userdefine_trigger(); 2281 - /* 0 is no fan speed control (max), which is 1 for us */ 2282 - return hp_wmi_fan_speed_max_set(1); 2283 - case 2: 2284 - /* 2 is automatic speed control, which is 0 for us */ 2285 - if (is_victus_s_thermal_profile()) { 2286 - hp_wmi_get_fan_count_userdefine_trigger(); 2287 - return hp_wmi_fan_speed_max_reset(); 2288 - } else 2289 - return hp_wmi_fan_speed_max_set(0); 2193 + case PWM_MODE_MAX: 2194 + priv->mode = PWM_MODE_MAX; 2195 + return hp_wmi_apply_fan_settings(priv); 2196 + case PWM_MODE_MANUAL: 2197 + if (!is_victus_s_thermal_profile()) 2198 + return -EOPNOTSUPP; 2199 + /* 2200 + * When switching to manual mode, set fan speed to 2201 + * current RPM values to ensure a smooth transition. 2202 + */ 2203 + rpm = hp_wmi_get_fan_speed_victus_s(channel); 2204 + if (rpm < 0) 2205 + return rpm; 2206 + priv->pwm = rpm_to_pwm(rpm / 100, priv); 2207 + priv->mode = PWM_MODE_MANUAL; 2208 + return hp_wmi_apply_fan_settings(priv); 2209 + case PWM_MODE_AUTO: 2210 + priv->mode = PWM_MODE_AUTO; 2211 + return hp_wmi_apply_fan_settings(priv); 2290 2212 default: 2291 - /* we don't support manual fan speed control */ 2292 2213 return -EINVAL; 2293 2214 } 2294 2215 default: ··· 2321 2196 2322 2197 static const struct hwmon_channel_info * const info[] = { 2323 2198 HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), 2324 - HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE), 2199 + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT), 2325 2200 NULL 2326 2201 }; 2327 2202 ··· 2336 2211 .info = info, 2337 2212 }; 2338 2213 2214 + static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) 2215 + { 2216 + u8 fan_data[128] = { 0 }; 2217 + struct victus_s_fan_table *fan_table; 2218 + u8 min_rpm, max_rpm, gpu_delta; 2219 + int ret; 2220 + 2221 + /* Default behaviour on hwmon init is automatic mode */ 2222 + priv->mode = PWM_MODE_AUTO; 2223 + 2224 + /* Bypass all non-Victus S devices */ 2225 + if (!is_victus_s_thermal_profile()) 2226 + return 0; 2227 + 2228 + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, 2229 + HPWMI_GM, &fan_data, 4, sizeof(fan_data)); 2230 + if (ret) 2231 + return ret; 2232 + 2233 + fan_table = (struct victus_s_fan_table *)fan_data; 2234 + if (fan_table->header.num_entries == 0 || 2235 + sizeof(struct victus_s_fan_table_header) + 2236 + sizeof(struct victus_s_fan_table_entry) * fan_table->header.num_entries > sizeof(fan_data)) 2237 + return -EINVAL; 2238 + 2239 + min_rpm = fan_table->entries[0].cpu_rpm; 2240 + max_rpm = fan_table->entries[fan_table->header.num_entries - 1].cpu_rpm; 2241 + gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm; 2242 + priv->min_rpm = min_rpm; 2243 + priv->max_rpm = max_rpm; 2244 + priv->gpu_delta = gpu_delta; 2245 + 2246 + return 0; 2247 + } 2248 + 2339 2249 static int hp_wmi_hwmon_init(void) 2340 2250 { 2341 2251 struct device *dev = &hp_wmi_platform_dev->dev; 2252 + struct hp_wmi_hwmon_priv *priv; 2342 2253 struct device *hwmon; 2254 + int ret; 2343 2255 2344 - hwmon = devm_hwmon_device_register_with_info(dev, "hp", &hp_wmi_driver, 2256 + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 2257 + if (!priv) 2258 + return -ENOMEM; 2259 + 2260 + ret = hp_wmi_setup_fan_settings(priv); 2261 + if (ret) 2262 + return ret; 2263 + hwmon = devm_hwmon_device_register_with_info(dev, "hp", priv, 2345 2264 &chip_info, NULL); 2346 2265 2347 2266 if (IS_ERR(hwmon)) { 2348 2267 dev_err(dev, "Could not register hp hwmon device\n"); 2349 2268 return PTR_ERR(hwmon); 2350 2269 } 2270 + 2271 + hp_wmi_apply_fan_settings(priv); 2351 2272 2352 2273 return 0; 2353 2274 }