All of lore.kernel.org
 help / color / mirror / Atom feed
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>,
	<ravi.kishore.koppuravuri@intel.com>
Subject: Re: [PATCH v3 4/9] drm/xe/hwmon: expose pwm[1-3]
Date: Thu, 29 Oct 2026 17:01:20 +0530	[thread overview]
Message-ID: <f0cb6786-c5af-41d7-ba7a-463eefeb42cc@intel.com> (raw)
In-Reply-To: <20261015190751.1268916-5-karthik.poosa@intel.com>

[-- Attachment #1: Type: text/plain, Size: 12159 bytes --]

Hi Karthik,

On 16-10-2026 00:37, 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 using hwmon->num_fans to get fan count in
>     xe_hwmon_pwm_is_visible().
>
> v3:
>   - Set PWM directly through the user table; avoid initializing it from the
>     stock table. (Soham)
>   - Move the unused xe_hwmon_activate_user_fan_table() to the subsequent
>     patch.
>   - Fail pwmN reads when no value has been written, rather than activating
>     the user table from the stock table.
>   - Define MIN_FAN_CONTROL_POINTS as 2.
>   - Use MIN_FAN_CONTROL_POINTS for PWM configuration. (Soham)
>   - Use batched writes for configuring fan control points when setting pwmN.
>   - Add and rephrase couple debug logs.
>   - Update the hwmon documentation.
>
> Signed-off-by: Karthik Poosa<karthik.poosa@intel.com>
> Assisted-by: LLM
> ---
>   .../ABI/testing/sysfs-driver-intel-xe-hwmon   |   9 +
>   drivers/gpu/drm/xe/xe_hwmon.c                 | 170 ++++++++++++++++++
>   drivers/gpu/drm/xe/xe_pcode_api.h             |   3 +
>   3 files changed, 182 insertions(+)
>
> diff --git a/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon b/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
> index 5ee68aefa485..ce17e9ca3ed8 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:           October 2026
> +KernelVersion:  7.3
> +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.
> +
> +                Only supported for particular Intel Xe graphics platforms.
Use tab indentation after the field labels.
> diff --git a/drivers/gpu/drm/xe/xe_hwmon.c b/drivers/gpu/drm/xe/xe_hwmon.c
> index 32016c5d4770..a86ed741feb3 100644
> --- a/drivers/gpu/drm/xe/xe_hwmon.c
> +++ b/drivers/gpu/drm/xe/xe_hwmon.c
> @@ -8,6 +8,7 @@
>   #include <linux/jiffies.h>
>   #include <linux/types.h>
>   #include <linux/units.h>
> +#include <linux/delay.h>
Is this header required ?
>   
>   #include <drm/drm_managed.h>
>   #include "regs/xe_gt_regs.h"
> @@ -153,6 +154,9 @@ enum fan_table_type {
>   /* Maximum number of fan control points supported by each fan. */
>   #define MAX_FAN_CONTROL_POINTS	(10)
>   
> +/* Minimum number of points required for a user fan table. */
> +#define MIN_FAN_CONTROL_POINTS  (2)
Use tab indentation.
> +
>   /* Fan control point index: bit31 selects fan table type; bits30:0 control point index. */
>   #define FCP_INDEX(FAN_TABLE_TYPE, POINT_NUM) (((FAN_TABLE_TYPE) << 31) | \
>   						((POINT_NUM) & REG_GENMASK(30, 0)))
> @@ -913,6 +917,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
>   };
>   
> @@ -1154,6 +1159,89 @@ static int xe_hwmon_get_fan_enable_mask(struct xe_hwmon *hwmon)
>   	return 0;
>   }
>   
> +static int xe_hwmon_get_fan_point_count(struct xe_hwmon *hwmon, u8 fan, u32 *point_count,
> +					enum fan_table_type 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;
> +	}
> +
> +	if (*point_count > MAX_FAN_CONTROL_POINTS) {
> +		xe_err(hwmon->xe, "fan %d %s point count %u exceeds max %u\n", fan,
> +		       (table_type == USER_FAN_TABLE) ? "user" : "stock", *point_count,
> +		       MAX_FAN_CONTROL_POINTS);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +/* Temperature to set when setting the fan PWM with pwmN. */
> +#define FIXED_PWM_TEMP	(1)
> +
> +static int xe_hwmon_set_user_fan_pwm(struct xe_hwmon *hwmon, u8 fan, u8 pwm)
> +{
> +	struct xe_hwmon_fan_info *fi = &hwmon->fi[fan];
> +	struct xe_pcode_write writes[MIN_FAN_CONTROL_POINTS + 1];
> +	u32 point_count = 0;
> +	u8 clamped_pwm;
> +	int ret, i;
> +
> +	pwm = DIV_ROUND_CLOSEST(pwm * 100, U8_MAX);
> +	clamped_pwm = max_t(u8, pwm, min_t(u32, fi->min_pwm, U8_MAX));
Is clamped_pwm and  fi->min_pwm in percentage ? If so use 100 *instead 
of* U8_MAX to be the max value.
> +
> +	/*
> +	 * Set the first fan control point in the user table to the clamped PWM value.
> +	 * Use only the first fan control point for setting the PWM value.
It says "only the first fan control point" but the loop end up writing 2 
points.
> +	 */
> +
> +	for (i = 0; i < MIN_FAN_CONTROL_POINTS; i++) {
> +		writes[i].mbox = PCODE_MBOX(FAN_SPEED_CONTROL, FSC_WRITE_FAN_TABLE, fan);
> +		writes[i].data = FIELD_PREP(FAN_CONTROL_POINT_TEMP_MASK, FIXED_PWM_TEMP + (i)) |
> +					    FIELD_PREP(FAN_CONTROL_POINT_SPEED_MASK, clamped_pwm);
> +		xe_dbg(hwmon->xe, "prepared fan control point %d with temp=%d, speed=%u\n", i,
> +		       FIXED_PWM_TEMP + (i), clamped_pwm);
> +	}
> +
> +	writes[i].mbox = PCODE_MBOX(FAN_SPEED_CONTROL, FSC_WRITE_NUM_FAN_CONTROL_POINTS, fan);
> +	writes[i].data = MIN_FAN_CONTROL_POINTS;
> +	ret = xe_pcode_write_batch(xe_device_get_root_tile(hwmon->xe), writes, ARRAY_SIZE(writes));
> +	if (ret) {
> +		xe_err(hwmon->xe, "failed to write pwm %d for fan %d, ret=%d\n", pwm, fan, ret);
"pwm" has been reassigned to a percentage by this point, so either print 
the original 0–255 value (keep it in a separate variable) or label this 
one as a percentage.
> +		return ret;
> +	}
> +	xe_dbg(hwmon->xe, "applied fixed pwm %u for fan %d\n", clamped_pwm, fan);
> +
> +	/* Verify if the fan points are set */
> +	ret = xe_hwmon_get_fan_point_count(hwmon, fan, &point_count, USER_FAN_TABLE);
> +	if (ret && point_count == 0) {
On failure point_count is already 0, so && point_count == 0 seems to be 
redundant.
Instead could this be more usable :
if (ret || point_count != MIN_FAN_CONTROL_POINTS),  if the valid write 
needed to be checked ?

> +		xe_err(hwmon->xe, "pwm set verify failed for fan %d, ret=%d, point count=%u\n", fan,
> +		       ret, point_count);
> +		return ret;
> +	}
> +
> +	/* Update the user fan table cache. */
> +	for (i = 0; i < MIN_FAN_CONTROL_POINTS; i++) {
> +		fi->fan_table[USER_FAN_TABLE].fcp[i].temp = FIXED_PWM_TEMP + (i);
Drop the parentheses around i: FIXED_PWM_TEMP + i.
> +		fi->fan_table[USER_FAN_TABLE].fcp[i].speed = clamped_pwm;
> +		xe_dbg(hwmon->xe, "fan %d user table cache point %d with temp=%d, speed=%u\n", fan,
> +		       i, fi->fan_table[USER_FAN_TABLE].fcp[i].temp,
> +			  fi->fan_table[USER_FAN_TABLE].fcp[i].speed);
The arguments don't align under the opening (.
> +	}
> +	fi->fan_table[USER_FAN_TABLE].point_count = MIN_FAN_CONTROL_POINTS;
> +
> +	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);
> @@ -1774,6 +1862,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 || !IS_FAN_ENABLED(channel))
> +		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)
>   {
> @@ -1828,6 +1930,67 @@ 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)
Add a short xe_dbg/xe_warn before these -EINVAL returns for clarity.
> +		return -EINVAL;
> +
> +	mutex_lock(&hwmon->hwmon_lock);
> +
> +	fi = &hwmon->fi[channel];
> +
> +	switch (attr) {
> +	case hwmon_pwm_input:
> +		/* Check if user fan table is set else activate it.*/
Where does it activate ? Use space before */.
> +		if (!fi->fan_table[USER_FAN_TABLE].point_count) {
> +			xe_warn(hwmon->xe, "pwm not set for fan %d\n", channel);
> +			ret = -ENXIO;
> +			goto unlock;
> +		}
> +		*val = DIV_ROUND_CLOSEST(fi->fan_table[USER_FAN_TABLE].fcp[0].speed * U8_MAX, 100);
The 0–255 <-> percentage round-trip is lossy for small values; Better to 
add a one-line note since the ABI says a full 0–255 range.
> +		xe_dbg(hwmon->xe, "pwm read for fan %d: %ld\n", channel, *val);
> +		break;
> +	default:
> +		ret = -EOPNOTSUPP;
> +		break;
> +	}
> +
> +unlock:
> +	mutex_unlock(&hwmon->hwmon_lock);
> +	return ret;
> +}
> +
> +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)

Add a short xe_dbg/xe_warn before these -EINVAL returns for clarity.


Regards,
Soham
> +		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)
> @@ -1854,6 +2017,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;
> @@ -1883,6 +2049,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;
>   	}
> @@ -1901,6 +2069,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 32bac74c4281..80642bee0ac8 100644
> --- a/drivers/gpu/drm/xe/xe_pcode_api.h
> +++ b/drivers/gpu/drm/xe/xe_pcode_api.h
> @@ -82,9 +82,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)

[-- Attachment #2: Type: text/html, Size: 15122 bytes --]

  reply	other threads:[~2026-10-29 11:32 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-15 19:07 [PATCH v3 0/9] Add fan control support Karthik Poosa
2026-10-15 19:07 ` [PATCH v3 1/9] drm/xe/hwmon: initialize fan-control backend and table cache Karthik Poosa
2026-10-28 18:46   ` Purkait, Soham
2026-10-29 11:34     ` Poosa, Karthik
2026-10-29 12:02       ` Purkait, Soham
2026-10-29 12:04       ` Purkait, Soham
2026-10-15 19:07 ` [PATCH v3 2/9] drm/xe/hwmon: expose fanN_max Karthik Poosa
2026-10-29  4:35   ` Purkait, Soham
2026-10-29 11:39     ` Poosa, Karthik
2026-10-15 19:07 ` [PATCH v3 3/9] drm/xe/pcode: add ordered write batch helper Karthik Poosa
2026-10-29  5:45   ` Purkait, Soham
2026-10-29 11:40     ` Poosa, Karthik
2026-10-15 19:07 ` [PATCH v3 4/9] drm/xe/hwmon: expose pwm[1-3] Karthik Poosa
2026-10-29 11:31   ` Purkait, Soham [this message]
2026-10-15 19:07 ` [PATCH v3 5/9] drm/xe/hwmon: expose pwm[1-3]_enable Karthik Poosa
2026-10-29 18:33   ` Purkait, Soham
2026-10-15 19:07 ` [PATCH v3 6/9] drm/xe/hwmon: Enable fan curve control support Karthik Poosa
2026-10-15 19:07 ` [PATCH v3 7/9] drm/xe/hwmon: Add kernel-doc for fan control Karthik Poosa
2026-10-15 19:07 ` [PATCH v3 8/9] drm/xe/hwmon: preserve fan user table across suspend resume Karthik Poosa
2026-10-15 19:07 ` [PATCH v3 9/9] drm/xe/hwmon: Update fan info after late binding Karthik Poosa
2026-10-15 19:46 ` ✗ CI.checkpatch: warning for Add fan control support Patchwork
2026-10-15 19:48 ` ✓ CI.KUnit: success " Patchwork
2026-10-15 21:01 ` ✗ Xe.CI.BAT: failure " Patchwork
2026-10-16  7:56 ` ✗ Xe.CI.FULL: " Patchwork
  -- strict thread matches above, loose matches on Subject: below --
2026-10-15 19:01 [PATCH v3 0/9] " Karthik Poosa
2026-10-15 19:01 ` [PATCH v3 4/9] drm/xe/hwmon: expose pwm[1-3] Karthik Poosa

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=f0cb6786-c5af-41d7-ba7a-463eefeb42cc@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=ravi.kishore.koppuravuri@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.