From: "Purkait, Soham" <soham.purkait@intel.com>
To: Karthik Poosa <karthik.poosa@intel.com>,
<intel-xe@lists.freedesktop.org>
Cc: <rodrigo.vivi@intel.com>, <anshuman.gupta@intel.com>,
<badal.nilawar@intel.com>, <raag.jadav@intel.com>,
<riana.tauro@intel.com>, <sk.anirban@intel.com>,
<mallesh.koujalagi@intel.com>
Subject: Re: [PATCH v2 4/9] drm/xe/hwmon: expose pwm[1-3]
Date: Tue, 1 Sep 2026 11:36:11 +0530 [thread overview]
Message-ID: <b106d161-a6b9-4742-a447-be786619252c@intel.com> (raw)
In-Reply-To: <20260717041757.2759084-5-karthik.poosa@intel.com>
Hi Karthik,
On 17-07-2026 09:47, Karthik Poosa wrote:
> Expose pwm[1-3] fan duty attributes through hwmon.
>
> This enables reading and writing user PWM values (0..255) for each
> available fan channel.
>
> Update Xe hwmon ABI documentation for pwm[1-3].
>
> This sysfs node can be used for manual control of fan speed,
> irrespective of fan curve.
>
> v2:
> - Use xe helpers for dmesg logs.
> - Avoid use hwmon->num_fans to get fan count in xe_hwmon_pwm_is_visible().
>
> Signed-off-by: Karthik Poosa <karthik.poosa@intel.com>
> Assisted-by: Codex:gpt-5-4
> ---
> .../ABI/testing/sysfs-driver-intel-xe-hwmon | 9 +
> drivers/gpu/drm/xe/xe_hwmon.c | 230 ++++++++++++++++++
> drivers/gpu/drm/xe/xe_pcode_api.h | 3 +
> 3 files changed, 242 insertions(+)
>
> diff --git a/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon b/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
> index ec0b94d76e22..7383898890aa 100644
> --- a/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
> +++ b/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
> @@ -321,3 +321,12 @@ Description: RW. Package burst power limit interval (Tau in PL2/Tau) in
> milliseconds over which sustained power is averaged.
>
> Only supported for particular Intel Xe graphics platforms.
> +
> +What: /sys/bus/pci/drivers/xe/.../hwmon/hwmon<i>/pwm[1-3]
> +Date: July 2026
> +KernelVersion: 7.2
> +Contact: intel-xe@lists.freedesktop.org
> +Description: RW. Target fan PWM duty value in the range 0..255. 0 is zero
> + fan speed and 255 is 100% fan speed.
Does zero fan speed means complete stop ? Or will it clamped up to the
firmware-reported min_pwm ?
And does the write only succeeds when pwm_enable == 1 ? if so, kindly
also mention the same in the doc.
Thanks,
Soham
> +
> + 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 769f4d1da83e..d3379727b1a9 100644
> --- a/drivers/gpu/drm/xe/xe_hwmon.c
> +++ b/drivers/gpu/drm/xe/xe_hwmon.c
> @@ -803,6 +803,7 @@ 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),
> NULL
> };
>
> @@ -928,6 +929,158 @@ static int xe_hwmon_get_num_fans(const struct xe_hwmon *hwmon, u32 *num_fans)
> return 0;
> }
>
> +static int xe_hwmon_get_fan_point_count(struct xe_hwmon *hwmon, u8 fan, u32 *point_count,
> + int table_type)
> +{
> + int ret;
> +
> + ret = xe_hwmon_pcode_read_fan_control(hwmon,
> + (table_type == USER_FAN_TABLE) ?
> + FSC_READ_USER_FAN_CONTROL_POINTS :
> + FSC_READ_STOCK_FAN_CONTROL_POINTS,
> + fan, point_count);
> + if (ret) {
> + xe_err(hwmon->xe, "failed to read fan %d %s point count, ret=%d\n", fan,
> + (table_type == USER_FAN_TABLE) ? "user" : "stock", ret);
> + return ret;
> + }
> +
> + xe_dbg(hwmon->xe, "fan %d %s point count read as %u\n", fan,
> + (table_type == USER_FAN_TABLE) ? "user" : "stock", *point_count);
> +
> + return 0;
> +}
> +
> +static int xe_hwmon_write_user_fan_point(struct xe_hwmon *hwmon, u8 fan, u8 point, u8 temp,
> + u8 speed)
> +{
> + struct xe_tile *root_tile = xe_device_get_root_tile(hwmon->xe);
> + u16 user_fcp_raw = FIELD_PREP(FAN_CONTROL_POINT_TEMP_MASK, temp) |
> + FIELD_PREP(FAN_CONTROL_POINT_SPEED_MASK, speed);
> + int ret;
> +
> + ret = xe_pcode_write_timeout(root_tile,
> + PCODE_MBOX(FAN_SPEED_CONTROL, FSC_WRITE_FAN_TABLE, fan),
> + (u32)user_fcp_raw, XE_PCODE_FAN_CONTROL_TIMEOUT_MS);
> + if (ret)
> + xe_dbg(hwmon->xe,
> + "failed to write fan %d user point %d temp %u, speed %u %%, ret=%d\n",
> + fan, point, temp, speed, ret);
> +
> + return ret;
> +}
> +
> +static int xe_hwmon_activate_user_fan_table(struct xe_hwmon *hwmon, u8 fan,
> + bool is_full_speed, enum fan_table_type table_source)
> +{
> + struct xe_tile *root_tile = xe_device_get_root_tile(hwmon->xe);
> + struct xe_hwmon_fan_info *fi = &hwmon->fi[fan];
> + u32 point_count;
> + int ret;
> + int point;
> + struct fan_table *source_table = &fi->fan_table[table_source];
> +
> + xe_dbg(hwmon->xe, "activating fan %d user table from %s table\n", fan,
> + (table_source == USER_FAN_TABLE) ? "user" : "stock");
> +
> + point_count = source_table->fan_control_point_count;
> +
> + if (!point_count) {
> + xe_err(hwmon->xe,
> + "fan %d %s table point count is 0, cannot activate user table\n", fan,
> + (table_source == USER_FAN_TABLE) ? "user" : "stock");
> + return -ENODATA;
> + }
> +
> + for (point = 0; point < point_count; point++) {
> + u8 temp = source_table->fcp[point].temp;
> + u8 speed = is_full_speed ? 100 : max_t(u8, source_table->fcp[point].speed,
> + min_t(u32, fi->min_pwm, U8_MAX));
> +
> + ret = xe_hwmon_write_user_fan_point(hwmon, fan, point, temp, speed);
> + if (ret)
> + return ret;
> +
> + if (table_source == STOCK_FAN_TABLE) {
> + /* Update user table cache with stock table values */
> + fi->fan_table[USER_FAN_TABLE].fcp[point].temp = temp;
> + fi->fan_table[USER_FAN_TABLE].fcp[point].speed = speed;
> + }
> + if (is_full_speed)
> + fi->fan_table[USER_FAN_TABLE].fcp[point].speed = speed;
> + }
> +
> + ret = xe_pcode_write_timeout(root_tile,
> + PCODE_MBOX(FAN_SPEED_CONTROL,
> + FSC_WRITE_NUM_FAN_CONTROL_POINTS, fan),
> + point_count, XE_PCODE_FAN_CONTROL_TIMEOUT_MS);
> + if (ret) {
> + xe_dbg(hwmon->xe, "failed to write fan %d user table, ret=%d\n", fan, ret);
> + return ret;
> + }
> +
> + /* Verify if all the user points are set */
> + ret = xe_hwmon_get_fan_point_count(hwmon, fan, &point_count, USER_FAN_TABLE);
> + if (ret) {
> + xe_err(hwmon->xe,
> + "failed to read fan %d user table point count, ret=%d\n", fan, ret);
> + return ret;
> + }
> + if (point_count != source_table->fan_control_point_count) {
> + xe_err(hwmon->xe, "fan %d user table point count mismatch, expected %u, got %u\n",
> + fan, source_table->fan_control_point_count, point_count);
> + return -EIO;
> + }
> +
> + fi->fan_table[USER_FAN_TABLE].fan_control_point_count = point_count;
> +
> + return 0;
> +}
> +
> +static int xe_hwmon_set_user_fan_pwm(struct xe_hwmon *hwmon, u8 fan, u8 pwm)
> +{
> + struct xe_tile *root_tile = xe_device_get_root_tile(hwmon->xe);
> + struct xe_hwmon_fan_info *fi = &hwmon->fi[fan];
> + u32 point_count = fi->fan_table[USER_FAN_TABLE].fan_control_point_count;
> + u8 clamped_pwm;
> + int ret;
> + int point;
> +
> + /* 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)
> + return ret;
> +
> + if (!point_count) {
> + xe_dbg(hwmon->xe, "fan %d user table not set, activating it\n", fan);
> + ret = xe_hwmon_activate_user_fan_table(hwmon, fan, false, STOCK_FAN_TABLE);
> + if (ret)
> + return ret;
> + }
> +
> + pwm = DIV_ROUND_CLOSEST(pwm * 100, U8_MAX);
> + clamped_pwm = max_t(u8, pwm, min_t(u32, fi->min_pwm, U8_MAX));
> +
> + for (point = 0; point < point_count; point++) {
> + u8 temp = fi->fan_table[USER_FAN_TABLE].fcp[point].temp;
> +
> + ret = xe_hwmon_write_user_fan_point(hwmon, fan, point, temp, clamped_pwm);
> + if (ret)
> + return ret;
> +
> + fi->fan_table[USER_FAN_TABLE].fcp[point].speed = clamped_pwm;
> + }
> +
> + ret = xe_pcode_write(root_tile,
> + PCODE_MBOX(FAN_SPEED_CONTROL, FSC_WRITE_NUM_FAN_CONTROL_POINTS, fan),
> + point_count);
> + if (ret) {
> + xe_dbg(hwmon->xe, "failed to update fan %d user table count, ret=%d\n", fan, ret);
> + return ret;
> + }
> + return 0;
> +}
> +
> static int xe_hwmon_read_fan_control_info(struct xe_hwmon *hwmon)
> {
> struct xe_tile *root_tile = xe_device_get_root_tile(hwmon->xe);
> @@ -1424,6 +1577,20 @@ xe_hwmon_fan_is_visible(struct xe_hwmon *hwmon, u32 attr, int channel)
> }
> }
>
> +static umode_t
> +xe_hwmon_pwm_is_visible(struct xe_hwmon *hwmon, u32 attr, int channel)
> +{
> + if (!hwmon->xe->info.has_fan_control || channel >= hwmon->num_fans)
> + return 0;
> +
> + switch (attr) {
> + case hwmon_pwm_input:
> + return 0644;
> + default:
> + return 0;
> + }
> +}
> +
> static int
> xe_hwmon_fan_input_read(struct xe_hwmon *hwmon, int channel, long *val)
> {
> @@ -1478,6 +1645,62 @@ xe_hwmon_fan_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *val)
> }
> }
>
> +static int
> +xe_hwmon_pwm_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *val)
> +{
> + struct xe_hwmon_fan_info *fi;
> + int ret = 0;
> +
> + if (channel < 0 || channel >= FAN_MAX)
> + return -EINVAL;
> +
> + fi = &hwmon->fi[channel];
> +
> + switch (attr) {
> + case hwmon_pwm_input:
> + /* Check if user fan table is set else activate it.*/
> + if (!fi->fan_table[USER_FAN_TABLE].fan_control_point_count) {
> + xe_dbg(hwmon->xe, "fan %d user table not set, activating it\n", channel);
> + ret = xe_hwmon_activate_user_fan_table(hwmon, channel, false,
> + STOCK_FAN_TABLE);
> + if (ret)
> + return ret;
> + }
> + *val = DIV_ROUND_CLOSEST(fi->fan_table[USER_FAN_TABLE].fcp[0].speed *
> + U8_MAX, 100);
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int
> +xe_hwmon_pwm_write(struct xe_hwmon *hwmon, u32 attr, int channel, long val)
> +{
> + int ret = 0;
> +
> + if (channel < 0 || channel >= FAN_MAX)
> + return -EINVAL;
> +
> + mutex_lock(&hwmon->hwmon_lock);
> +
> + switch (attr) {
> + case hwmon_pwm_input:
> + if (val < 0 || val > U8_MAX) {
> + ret = -EINVAL;
> + break;
> + }
> + ret = xe_hwmon_set_user_fan_pwm(hwmon, channel, (u8)val);
> + break;
> + default:
> + ret = -EOPNOTSUPP;
> + break;
> + }
> + mutex_unlock(&hwmon->hwmon_lock);
> +
> + return ret;
> +}
> +
> static umode_t
> xe_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
> u32 attr, int channel)
> @@ -1504,6 +1727,9 @@ xe_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
> case hwmon_fan:
> ret = xe_hwmon_fan_is_visible(hwmon, attr, channel);
> break;
> + case hwmon_pwm:
> + ret = xe_hwmon_pwm_is_visible(hwmon, attr, channel);
> + break;
> default:
> ret = 0;
> break;
> @@ -1533,6 +1759,8 @@ xe_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> return xe_hwmon_energy_read(hwmon, attr, channel, val);
> case hwmon_fan:
> return xe_hwmon_fan_read(hwmon, attr, channel, val);
> + case hwmon_pwm:
> + return xe_hwmon_pwm_read(hwmon, attr, channel, val);
> default:
> return -EOPNOTSUPP;
> }
> @@ -1551,6 +1779,8 @@ xe_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> return xe_hwmon_power_write(hwmon, attr, channel, val);
> case hwmon_curr:
> return xe_hwmon_curr_write(hwmon, attr, channel, val);
> + case hwmon_pwm:
> + return xe_hwmon_pwm_write(hwmon, attr, channel, val);
> default:
> return -EOPNOTSUPP;
> }
> diff --git a/drivers/gpu/drm/xe/xe_pcode_api.h b/drivers/gpu/drm/xe/xe_pcode_api.h
> index 419ab4f416fa..5c5fdc650a97 100644
> --- a/drivers/gpu/drm/xe/xe_pcode_api.h
> +++ b/drivers/gpu/drm/xe/xe_pcode_api.h
> @@ -84,9 +84,12 @@
> #define PCODE_MBOX_DOMAIN_HBM 0x2
>
> #define FAN_SPEED_CONTROL 0x7D
> +#define FSC_WRITE_NUM_FAN_CONTROL_POINTS 0x0
> +#define FSC_WRITE_FAN_TABLE 0x1
> #define FSC_READ_MAX_FAN_RPS 0x3
> #define FSC_READ_NUM_FANS 0x4
> #define FSC_READ_STOCK_FAN_CONTROL_POINTS 0x5
> +#define FSC_READ_USER_FAN_CONTROL_POINTS 0x6
> #define FSC_READ_FAN_TABLE 0x7
> #define FAN_CONTROL_POINT_TEMP_MASK REG_GENMASK(7, 0)
> #define FAN_CONTROL_POINT_SPEED_MASK REG_GENMASK(15, 8)
next prev parent reply other threads:[~2026-09-01 6:06 UTC|newest]
Thread overview: 33+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-17 4:17 [PATCH v2 0/9] Add fan control support Karthik Poosa
2026-07-17 4:16 ` ✓ CI.KUnit: success for Add fan control support (rev2) Patchwork
2026-07-17 4:17 ` [PATCH v2 1/9] drm/xe/pcode: Introduce xe_pcode_read_timeout() API Karthik Poosa
2026-07-17 4:17 ` [PATCH v2 2/9] drm/xe/hwmon: initialize fan-control backend and table cache Karthik Poosa
2026-08-10 6:03 ` Nilawar, Badal
2026-08-25 16:10 ` Poosa, Karthik
2026-08-11 13:11 ` Nilawar, Badal
2026-08-17 17:01 ` Poosa, Karthik
2026-08-25 16:14 ` Poosa, Karthik
2026-07-17 4:17 ` [PATCH v2 3/9] drm/xe/hwmon: expose fanN_max Karthik Poosa
2026-07-23 5:34 ` Purkait, Soham
2026-07-17 4:17 ` [PATCH v2 4/9] drm/xe/hwmon: expose pwm[1-3] Karthik Poosa
2026-07-24 7:02 ` Purkait, Soham
2026-08-11 13:58 ` Poosa, Karthik
2026-09-01 6:06 ` Purkait, Soham [this message]
2026-09-02 7:14 ` Poosa, Karthik
2026-09-03 6:22 ` Purkait, Soham
2026-07-17 4:17 ` [PATCH v2 5/9] drm/xe/hwmon: expose pwm[1-3]_enable Karthik Poosa
2026-09-02 5:57 ` Purkait, Soham
2026-07-17 4:17 ` [PATCH v2 6/9] drm/xe/hwmon: Enable fan curve control support Karthik Poosa
2026-07-22 18:16 ` Purkait, Soham
2026-08-27 12:07 ` Poosa, Karthik
2026-09-02 6:35 ` Purkait, Soham
2026-09-02 7:36 ` Poosa, Karthik
2026-07-17 4:17 ` [PATCH v2 7/9] drm/xe/hwmon: Add kernel-doc for fan control Karthik Poosa
2026-07-17 4:17 ` [PATCH v2 8/9] drm/xe/hwmon: preserve fan user table across suspend resume Karthik Poosa
2026-08-10 5:53 ` Nilawar, Badal
2026-08-27 10:27 ` Poosa, Karthik
2026-07-17 4:17 ` [PATCH v2 9/9] drm/xe/hwmon: Update fan info after late binding Karthik Poosa
2026-07-20 6:20 ` Purkait, Soham
2026-08-27 10:36 ` Poosa, Karthik
2026-07-17 5:00 ` ✓ Xe.CI.BAT: success for Add fan control support (rev2) Patchwork
2026-07-17 8:18 ` ✗ Xe.CI.FULL: failure " Patchwork
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=b106d161-a6b9-4742-a447-be786619252c@intel.com \
--to=soham.purkait@intel.com \
--cc=anshuman.gupta@intel.com \
--cc=badal.nilawar@intel.com \
--cc=intel-xe@lists.freedesktop.org \
--cc=karthik.poosa@intel.com \
--cc=mallesh.koujalagi@intel.com \
--cc=raag.jadav@intel.com \
--cc=riana.tauro@intel.com \
--cc=rodrigo.vivi@intel.com \
--cc=sk.anirban@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox