Hi Karthik, On 17-07-2026 09:47, Karthik Poosa wrote: > Expose pwm[1-3]_enable for fan control mode selection. > Value of 0 runs fan at full speed. > A value of 1 keeps user fan-table control active, while value 2 > switches back to firmware control. > > Update Xe hwmon ABI documentation for pwm[1-3]_enable. > > v2: > - Use xe helpers for dmesg logs. > - Update kernel version in Xe hwmon documentation. > - Correct level of some dmesg logs. > > Signed-off-by: Karthik Poosa > Assisted-by: Codex:gpt-5-4 > --- > .../ABI/testing/sysfs-driver-intel-xe-hwmon | 11 +++ > drivers/gpu/drm/xe/xe_hwmon.c | 91 ++++++++++++++++++- > 2 files changed, 100 insertions(+), 2 deletions(-) > > diff --git a/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon b/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon > index 7383898890aa..effc7d38ce1f 100644 > --- a/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon > +++ b/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon > @@ -330,3 +330,14 @@ Description: RW. Target fan PWM duty value in the range 0..255. 0 is zero > fan speed and 255 is 100% fan speed. > > Only supported for particular Intel Xe graphics platforms. > + > +What: /sys/bus/pci/drivers/xe/.../hwmon/hwmon/pwm[1-3]_enable > +Date: July 2026 > +KernelVersion: 7.2 > +Contact:intel-xe@lists.freedesktop.org > +Description: RW. Fan control mode selector. > + > + Use value 0 to run fans at full speed. Use value 1 to enable > + user table control and value 2 to use firmware default behavior. > + > + Only supported for particular Intel Xe graphics platforms. > diff --git a/drivers/gpu/drm/xe/xe_hwmon.c b/drivers/gpu/drm/xe/xe_hwmon.c > index d3379727b1a9..fa9dbadd5fd6 100644 > --- a/drivers/gpu/drm/xe/xe_hwmon.c > +++ b/drivers/gpu/drm/xe/xe_hwmon.c > @@ -123,6 +123,15 @@ struct xe_hwmon_energy_info { > long accum_energy; > }; > > +enum xe_fan_pwm_enable_mode { > + /** @XE_FAN_PWM_FULL_SPEED: force all user table points to full speed */ > + XE_FAN_PWM_FULL_SPEED = 0, > + /** @XE_FAN_PWM_MANUAL_USER_TABLE: use the driver-managed user table */ > + XE_FAN_PWM_MANUAL_USER_TABLE = 1, > + /** @XE_FAN_PWM_AUTO_STOCK_TABLE: use the firmware stock table */ > + XE_FAN_PWM_AUTO_STOCK_TABLE = 2, > +}; > + > enum fan_table_type { > /** @STOCK_FAN_TABLE: firmware-provided fan table */ > STOCK_FAN_TABLE, > @@ -169,6 +178,10 @@ struct xe_hwmon_fan_info { > u32 max_rps; > /** @min_pwm: minimum fan PWM */ > u32 min_pwm; > + /** @pwm_enable_mode: current xe_fan_pwm_enable_mode */ > + u8 pwm_enable_mode; > + /** @is_full_speed: flag indicating if fan is in full speed */ > + bool is_full_speed; > }; > > /** > @@ -803,7 +816,9 @@ static const struct hwmon_channel_info * const hwmon_info[] = { > HWMON_CHANNEL_INFO(energy, HWMON_E_INPUT | HWMON_E_LABEL, HWMON_E_INPUT | HWMON_E_LABEL), > HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_MAX, HWMON_F_INPUT | HWMON_F_MAX, > HWMON_F_INPUT | HWMON_F_MAX), > - HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT, HWMON_PWM_INPUT, HWMON_PWM_INPUT), > + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE, > + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, > + HWMON_PWM_INPUT | HWMON_PWM_ENABLE), > NULL > }; > > @@ -1033,7 +1048,8 @@ static int xe_hwmon_activate_user_fan_table(struct xe_hwmon *hwmon, u8 fan, > } > > fi->fan_table[USER_FAN_TABLE].fan_control_point_count = point_count; > - > + fi->pwm_enable_mode = XE_FAN_PWM_MANUAL_USER_TABLE; > + fi->is_full_speed = is_full_speed; > return 0; > } > > @@ -1046,6 +1062,11 @@ static int xe_hwmon_set_user_fan_pwm(struct xe_hwmon *hwmon, u8 fan, u8 pwm) > int ret; > int point; > > + if (fi->pwm_enable_mode == XE_FAN_PWM_AUTO_STOCK_TABLE) { > + xe_dbg(hwmon->xe, "fan %d manual table is not active, cannot set pwm\n", fan); > + return -EINVAL; > + } > + > /* Read user table fan point count, if it is not set, activate the user table */ > ret = xe_hwmon_get_fan_point_count(hwmon, fan, &point_count, USER_FAN_TABLE); > if (ret) > @@ -1078,6 +1099,9 @@ static int xe_hwmon_set_user_fan_pwm(struct xe_hwmon *hwmon, u8 fan, u8 pwm) > xe_dbg(hwmon->xe, "failed to update fan %d user table count, ret=%d\n", fan, ret); > return ret; > } > + > + fi->pwm_enable_mode = XE_FAN_PWM_MANUAL_USER_TABLE; > + fi->is_full_speed = false; > return 0; > } > > @@ -1134,6 +1158,9 @@ static int xe_hwmon_read_fan_control_info(struct xe_hwmon *hwmon) > fi->fan_table[STOCK_FAN_TABLE].fcp[point].speed); > } > > + /* Set PWM enable mode to automatic i.e stock table in use. */ > + fi->pwm_enable_mode = XE_FAN_PWM_AUTO_STOCK_TABLE; > + > /* Read minimum fan PWM */ > ret = xe_hwmon_pcode_read_fan_control(hwmon, FSC_READ_FAN_MIN_PWM, fan, > &fi->min_pwm); > @@ -1585,6 +1612,7 @@ xe_hwmon_pwm_is_visible(struct xe_hwmon *hwmon, u32 attr, int channel) > > switch (attr) { > case hwmon_pwm_input: > + case hwmon_pwm_enable: > return 0644; > default: > return 0; > @@ -1669,6 +1697,9 @@ xe_hwmon_pwm_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *val) > *val = DIV_ROUND_CLOSEST(fi->fan_table[USER_FAN_TABLE].fcp[0].speed * > U8_MAX, 100); > return 0; > + case hwmon_pwm_enable: > + *val = fi->is_full_speed ? XE_FAN_PWM_FULL_SPEED : fi->pwm_enable_mode; > + return 0; > default: > return -EOPNOTSUPP; > } > @@ -1677,11 +1708,15 @@ xe_hwmon_pwm_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *val) > static int > xe_hwmon_pwm_write(struct xe_hwmon *hwmon, u32 attr, int channel, long val) > { > + struct xe_tile *root_tile = xe_device_get_root_tile(hwmon->xe); > + struct xe_hwmon_fan_info *fi; > int ret = 0; > > if (channel < 0 || channel >= FAN_MAX) > return -EINVAL; > > + fi = &hwmon->fi[channel]; > + > mutex_lock(&hwmon->hwmon_lock); > > switch (attr) { > @@ -1692,10 +1727,62 @@ xe_hwmon_pwm_write(struct xe_hwmon *hwmon, u32 attr, int channel, long val) > } > ret = xe_hwmon_set_user_fan_pwm(hwmon, channel, (u8)val); > break; > + case hwmon_pwm_enable: > + if (val == XE_FAN_PWM_FULL_SPEED) { > + /* Set fans at full speed. */ > + ret = xe_hwmon_activate_user_fan_table(hwmon, channel, true, > + STOCK_FAN_TABLE); > + if (ret) { > + xe_warn(hwmon->xe, > + "full speed enable for fan %d failed, ret=%d\n", > + channel, ret); > + } > + } else if (val == XE_FAN_PWM_MANUAL_USER_TABLE) { > + /* > + * Manual fan speed control using user fan table. > + * If user fan table is not programmed, it would be initialized with nit: s/programmed/populated/ > + * clamped stock points which allows manual control with some > + * default points. > + */ > + ret = xe_hwmon_activate_user_fan_table(hwmon, channel, false, > + USER_FAN_TABLE); How can USER_FAN_TABLE be the source of itself ? Should it be STOCK_FAN_TABLE ? Thanks, Soham > + if (ret) { > + xe_warn(hwmon->xe, > + "failed to activate user fan table for fan %d, ret=%d\n", > + channel, ret); > + } > + } else if (val == XE_FAN_PWM_AUTO_STOCK_TABLE) { > + /* Switch to stock table specifying the user points to 0. */ > + ret = xe_pcode_write_timeout(root_tile, > + PCODE_MBOX(FAN_SPEED_CONTROL, > + FSC_WRITE_NUM_FAN_CONTROL_POINTS, > + channel), > + 0, XE_PCODE_FAN_CONTROL_TIMEOUT_MS); > + if (ret) { > + xe_warn(hwmon->xe, > + "failed to disable user fan table for fan %d, ret=%d\n", > + channel, ret); > + break; > + } > + xe_dbg(hwmon->xe, > + "automatic control enabled for fan %d using stock table\n", > + channel); > + ret = 0; > + } else { > + ret = -EINVAL; > + } > + break; > default: > ret = -EOPNOTSUPP; > break; > } > + > + /* Update PWM enable mode and full speed status if no error occurred */ > + if (!ret && attr == hwmon_pwm_enable) { > + fi->pwm_enable_mode = val; > + fi->is_full_speed = (val == XE_FAN_PWM_FULL_SPEED); > + } > + > mutex_unlock(&hwmon->hwmon_lock); > > return ret;