[PATCH v3] drm/xe/hwmon: expose fan speed

Rodrigo Vivi rodrigo.vivi at intel.com
Fri Mar 14 18:09:19 UTC 2025


On Wed, Mar 12, 2025 at 02:29:09PM +0530, Raag Jadav wrote:
> Add hwmon support for fan1_input, fan2_input and fan3_input attributes,
> which will expose fan speed of respective channels in RPM when supported
> by hardware. With this in place we can monitor fan speed using lm-sensors
> tool.
> 
> v2: Rely on platform checks instead of mailbox error (Aravind, Rodrigo)
> v3: Introduce has_fan_control flag (Rodrigo)
> 
> Signed-off-by: Raag Jadav <raag.jadav at intel.com>
> Reviewed-by: Andi Shyti <andi.shyti at linux.intel.com>

pushed to drm-xe-next, thanks for the patch and reviews

> ---
>  .../ABI/testing/sysfs-driver-intel-xe-hwmon   |  24 ++++
>  drivers/gpu/drm/xe/regs/xe_pcode_regs.h       |   3 +
>  drivers/gpu/drm/xe/xe_device_types.h          |   2 +
>  drivers/gpu/drm/xe/xe_hwmon.c                 | 125 +++++++++++++++++-
>  drivers/gpu/drm/xe/xe_pci.c                   |   4 +
>  drivers/gpu/drm/xe/xe_pcode_api.h             |   3 +
>  6 files changed, 160 insertions(+), 1 deletion(-)
> 
> diff --git a/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon b/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
> index 9bce281314df..adbb9bce15a5 100644
> --- a/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
> +++ b/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
> @@ -124,3 +124,27 @@ Contact:	intel-xe at lists.freedesktop.org
>  Description:	RO. VRAM temperature in millidegree Celsius.
>  
>  		Only supported for particular Intel Xe graphics platforms.
> +
> +What:		/sys/bus/pci/drivers/xe/.../hwmon/hwmon<i>/fan1_input
> +Date:		March 2025
> +KernelVersion:	6.14
> +Contact:	intel-xe at lists.freedesktop.org
> +Description:	RO. Fan 1 speed in RPM.
> +
> +		Only supported for particular Intel Xe graphics platforms.
> +
> +What:		/sys/bus/pci/drivers/xe/.../hwmon/hwmon<i>/fan2_input
> +Date:		March 2025
> +KernelVersion:	6.14
> +Contact:	intel-xe at lists.freedesktop.org
> +Description:	RO. Fan 2 speed in RPM.
> +
> +		Only supported for particular Intel Xe graphics platforms.
> +
> +What:		/sys/bus/pci/drivers/xe/.../hwmon/hwmon<i>/fan3_input
> +Date:		March 2025
> +KernelVersion:	6.14
> +Contact:	intel-xe at lists.freedesktop.org
> +Description:	RO. Fan 3 speed in RPM.
> +
> +		Only supported for particular Intel Xe graphics platforms.
> diff --git a/drivers/gpu/drm/xe/regs/xe_pcode_regs.h b/drivers/gpu/drm/xe/regs/xe_pcode_regs.h
> index 8846eb9ce2a4..c7d5d782e3f9 100644
> --- a/drivers/gpu/drm/xe/regs/xe_pcode_regs.h
> +++ b/drivers/gpu/drm/xe/regs/xe_pcode_regs.h
> @@ -21,6 +21,9 @@
>  #define BMG_PACKAGE_POWER_SKU			XE_REG(0x138098)
>  #define BMG_PACKAGE_POWER_SKU_UNIT		XE_REG(0x1380dc)
>  #define BMG_PACKAGE_ENERGY_STATUS		XE_REG(0x138120)
> +#define BMG_FAN_1_SPEED				XE_REG(0x138140)
> +#define BMG_FAN_2_SPEED				XE_REG(0x138170)
> +#define BMG_FAN_3_SPEED				XE_REG(0x1381a0)
>  #define BMG_VRAM_TEMPERATURE			XE_REG(0x1382c0)
>  #define BMG_PACKAGE_TEMPERATURE			XE_REG(0x138434)
>  #define BMG_PACKAGE_RAPL_LIMIT			XE_REG(0x138440)
> diff --git a/drivers/gpu/drm/xe/xe_device_types.h b/drivers/gpu/drm/xe/xe_device_types.h
> index 833c29fed3a3..6bde5b5e45f9 100644
> --- a/drivers/gpu/drm/xe/xe_device_types.h
> +++ b/drivers/gpu/drm/xe/xe_device_types.h
> @@ -300,6 +300,8 @@ struct xe_device {
>  		u8 has_atomic_enable_pte_bit:1;
>  		/** @info.has_device_atomics_on_smem: Supports device atomics on SMEM */
>  		u8 has_device_atomics_on_smem:1;
> +		/** @info.has_fan_control: Device supports fan control */
> +		u8 has_fan_control:1;
>  		/** @info.has_flat_ccs: Whether flat CCS metadata is used */
>  		u8 has_flat_ccs:1;
>  		/** @info.has_heci_cscfi: device has heci cscfi */
> diff --git a/drivers/gpu/drm/xe/xe_hwmon.c b/drivers/gpu/drm/xe/xe_hwmon.c
> index 48d80ffdf7bb..eb293aec36a0 100644
> --- a/drivers/gpu/drm/xe/xe_hwmon.c
> +++ b/drivers/gpu/drm/xe/xe_hwmon.c
> @@ -5,6 +5,7 @@
>  
>  #include <linux/hwmon-sysfs.h>
>  #include <linux/hwmon.h>
> +#include <linux/jiffies.h>
>  #include <linux/types.h>
>  #include <linux/units.h>
>  
> @@ -27,6 +28,7 @@ enum xe_hwmon_reg {
>  	REG_PKG_POWER_SKU_UNIT,
>  	REG_GT_PERF_STATUS,
>  	REG_PKG_ENERGY_STATUS,
> +	REG_FAN_SPEED,
>  };
>  
>  enum xe_hwmon_reg_operation {
> @@ -42,6 +44,13 @@ enum xe_hwmon_channel {
>  	CHANNEL_MAX,
>  };
>  
> +enum xe_fan_channel {
> +	FAN_1,
> +	FAN_2,
> +	FAN_3,
> +	FAN_MAX,
> +};
> +
>  /*
>   * SF_* - scale factors for particular quantities according to hwmon spec.
>   */
> @@ -61,6 +70,16 @@ struct xe_hwmon_energy_info {
>  	long accum_energy;
>  };
>  
> +/**
> + * struct xe_hwmon_fan_info - to cache previous fan reading
> + */
> +struct xe_hwmon_fan_info {
> +	/** @reg_val_prev: previous fan reg val */
> +	u32 reg_val_prev;
> +	/** @time_prev: previous timestamp */
> +	u64 time_prev;
> +};
> +
>  /**
>   * struct xe_hwmon - xe hwmon data structure
>   */
> @@ -79,6 +98,8 @@ struct xe_hwmon {
>  	int scl_shift_time;
>  	/** @ei: Energy info for energyN_input */
>  	struct xe_hwmon_energy_info ei[CHANNEL_MAX];
> +	/** @fi: Fan info for fanN_input */
> +	struct xe_hwmon_fan_info fi[FAN_MAX];
>  };
>  
>  static struct xe_reg xe_hwmon_get_reg(struct xe_hwmon *hwmon, enum xe_hwmon_reg hwmon_reg,
> @@ -144,6 +165,14 @@ static struct xe_reg xe_hwmon_get_reg(struct xe_hwmon *hwmon, enum xe_hwmon_reg
>  			return PCU_CR_PACKAGE_ENERGY_STATUS;
>  		}
>  		break;
> +	case REG_FAN_SPEED:
> +		if (channel == FAN_1)
> +			return BMG_FAN_1_SPEED;
> +		else if (channel == FAN_2)
> +			return BMG_FAN_2_SPEED;
> +		else if (channel == FAN_3)
> +			return BMG_FAN_3_SPEED;
> +		break;
>  	default:
>  		drm_warn(&xe->drm, "Unknown xe hwmon reg id: %d\n", hwmon_reg);
>  		break;
> @@ -454,6 +483,7 @@ static const struct hwmon_channel_info * const hwmon_info[] = {
>  	HWMON_CHANNEL_INFO(curr, HWMON_C_LABEL, HWMON_C_CRIT | HWMON_C_LABEL),
>  	HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL),
>  	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_INPUT, HWMON_F_INPUT),
>  	NULL
>  };
>  
> @@ -480,6 +510,19 @@ static int xe_hwmon_pcode_write_i1(const struct xe_hwmon *hwmon, u32 uval)
>  			      (uval & POWER_SETUP_I1_DATA_MASK));
>  }
>  
> +static int xe_hwmon_pcode_read_fan_control(const struct xe_hwmon *hwmon, u32 subcmd, u32 *uval)
> +{
> +	struct xe_tile *root_tile = xe_device_get_root_tile(hwmon->xe);
> +
> +	/* Platforms that don't return correct value */
> +	if (hwmon->xe->info.platform == XE_DG2 && subcmd == FSC_READ_NUM_FANS) {
> +		*uval = 2;
> +		return 0;
> +	}
> +
> +	return xe_pcode_read(root_tile, PCODE_MBOX(FAN_SPEED_CONTROL, subcmd, 0), uval, NULL);
> +}
> +
>  static int xe_hwmon_power_curr_crit_read(struct xe_hwmon *hwmon, int channel,
>  					 long *value, u32 scale_factor)
>  {
> @@ -705,6 +748,75 @@ xe_hwmon_energy_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *val)
>  	}
>  }
>  
> +static umode_t
> +xe_hwmon_fan_is_visible(struct xe_hwmon *hwmon, u32 attr, int channel)
> +{
> +	u32 uval;
> +
> +	if (!hwmon->xe->info.has_fan_control)
> +		return 0;
> +
> +	switch (attr) {
> +	case hwmon_fan_input:
> +		if (xe_hwmon_pcode_read_fan_control(hwmon, FSC_READ_NUM_FANS, &uval))
> +			return 0;
> +
> +		return channel < uval ? 0444 : 0;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static int
> +xe_hwmon_fan_input_read(struct xe_hwmon *hwmon, int channel, long *val)
> +{
> +	struct xe_mmio *mmio = xe_root_tile_mmio(hwmon->xe);
> +	struct xe_hwmon_fan_info *fi = &hwmon->fi[channel];
> +	u64 rotations, time_now, time;
> +	u32 reg_val;
> +	int ret = 0;
> +
> +	mutex_lock(&hwmon->hwmon_lock);
> +
> +	reg_val = xe_mmio_read32(mmio, xe_hwmon_get_reg(hwmon, REG_FAN_SPEED, channel));
> +	time_now = get_jiffies_64();
> +
> +	/*
> +	 * HW register value is accumulated count of pulses from PWM fan with the scale
> +	 * of 2 pulses per rotation.
> +	 */
> +	rotations = (reg_val - fi->reg_val_prev) / 2;
> +
> +	time = jiffies_delta_to_msecs(time_now - fi->time_prev);
> +	if (unlikely(!time)) {
> +		ret = -EAGAIN;
> +		goto unlock;
> +	}
> +
> +	/*
> +	 * Calculate fan speed in RPM by time averaging two subsequent readings in minutes.
> +	 * RPM = number of rotations * msecs per minute / time in msecs
> +	 */
> +	*val = DIV_ROUND_UP_ULL(rotations * (MSEC_PER_SEC * 60), time);
> +
> +	fi->reg_val_prev = reg_val;
> +	fi->time_prev = time_now;
> +unlock:
> +	mutex_unlock(&hwmon->hwmon_lock);
> +	return ret;
> +}
> +
> +static int
> +xe_hwmon_fan_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *val)
> +{
> +	switch (attr) {
> +	case hwmon_fan_input:
> +		return xe_hwmon_fan_input_read(hwmon, channel, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
>  static umode_t
>  xe_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
>  		    u32 attr, int channel)
> @@ -730,6 +842,9 @@ xe_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
>  	case hwmon_energy:
>  		ret = xe_hwmon_energy_is_visible(hwmon, attr, channel);
>  		break;
> +	case hwmon_fan:
> +		ret = xe_hwmon_fan_is_visible(hwmon, attr, channel);
> +		break;
>  	default:
>  		ret = 0;
>  		break;
> @@ -765,6 +880,9 @@ xe_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
>  	case hwmon_energy:
>  		ret = xe_hwmon_energy_read(hwmon, attr, channel, val);
>  		break;
> +	case hwmon_fan:
> +		ret = xe_hwmon_fan_read(hwmon, attr, channel, val);
> +		break;
>  	default:
>  		ret = -EOPNOTSUPP;
>  		break;
> @@ -842,7 +960,7 @@ static void
>  xe_hwmon_get_preregistration_info(struct xe_hwmon *hwmon)
>  {
>  	struct xe_mmio *mmio = xe_root_tile_mmio(hwmon->xe);
> -	long energy;
> +	long energy, fan_speed;
>  	u64 val_sku_unit = 0;
>  	int channel;
>  	struct xe_reg pkg_power_sku_unit;
> @@ -866,6 +984,11 @@ xe_hwmon_get_preregistration_info(struct xe_hwmon *hwmon)
>  	for (channel = 0; channel < CHANNEL_MAX; channel++)
>  		if (xe_hwmon_is_visible(hwmon, hwmon_energy, hwmon_energy_input, channel))
>  			xe_hwmon_energy_get(hwmon, channel, &energy);
> +
> +	/* Initialize 'struct xe_hwmon_fan_info' with initial fan register reading. */
> +	for (channel = 0; channel < FAN_MAX; channel++)
> +		if (xe_hwmon_is_visible(hwmon, hwmon_fan, hwmon_fan_input, channel))
> +			xe_hwmon_fan_input_read(hwmon, channel, &fan_speed);
>  }
>  
>  static void xe_hwmon_mutex_destroy(void *arg)
> diff --git a/drivers/gpu/drm/xe/xe_pci.c b/drivers/gpu/drm/xe/xe_pci.c
> index 8b6658b214be..2eaee2476c65 100644
> --- a/drivers/gpu/drm/xe/xe_pci.c
> +++ b/drivers/gpu/drm/xe/xe_pci.c
> @@ -62,6 +62,7 @@ struct xe_device_desc {
>  	u8 is_dgfx:1;
>  
>  	u8 has_display:1;
> +	u8 has_fan_control:1;
>  	u8 has_heci_gscfi:1;
>  	u8 has_heci_cscfi:1;
>  	u8 has_llc:1;
> @@ -322,6 +323,7 @@ static const struct xe_device_desc dg2_desc = {
>  
>  	DG2_FEATURES,
>  	.has_display = true,
> +	.has_fan_control = true,
>  };
>  
>  static const __maybe_unused struct xe_device_desc pvc_desc = {
> @@ -356,6 +358,7 @@ static const struct xe_device_desc bmg_desc = {
>  	PLATFORM(BATTLEMAGE),
>  	.dma_mask_size = 46,
>  	.has_display = true,
> +	.has_fan_control = true,
>  	.has_heci_cscfi = 1,
>  };
>  
> @@ -631,6 +634,7 @@ static int xe_info_init_early(struct xe_device *xe,
>  
>  	xe->info.dma_mask_size = desc->dma_mask_size;
>  	xe->info.is_dgfx = desc->is_dgfx;
> +	xe->info.has_fan_control = desc->has_fan_control;
>  	xe->info.has_heci_gscfi = desc->has_heci_gscfi;
>  	xe->info.has_heci_cscfi = desc->has_heci_cscfi;
>  	xe->info.has_llc = desc->has_llc;
> diff --git a/drivers/gpu/drm/xe/xe_pcode_api.h b/drivers/gpu/drm/xe/xe_pcode_api.h
> index 2bae9afdbd35..e622ae17f08d 100644
> --- a/drivers/gpu/drm/xe/xe_pcode_api.h
> +++ b/drivers/gpu/drm/xe/xe_pcode_api.h
> @@ -49,6 +49,9 @@
>  /* Domain IDs (param2) */
>  #define     PCODE_MBOX_DOMAIN_HBM		0x2
>  
> +#define   FAN_SPEED_CONTROL			0x7D
> +#define     FSC_READ_NUM_FANS			0x4
> +
>  #define PCODE_SCRATCH(x)		XE_REG(0x138320 + ((x) * 4))
>  /* PCODE_SCRATCH0 */
>  #define   AUXINFO_REG_OFFSET		REG_GENMASK(17, 15)
> -- 
> 2.34.1
> 


More information about the Intel-xe mailing list