Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: "Poosa, Karthik" <karthik.poosa@intel.com>
To: "Purkait, Soham" <soham.purkait@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: Wed, 2 Sep 2026 12:44:59 +0530	[thread overview]
Message-ID: <d71f2cd8-e1bf-42ea-bd20-654b0224e613@intel.com> (raw)
In-Reply-To: <b106d161-a6b9-4742-a447-be786619252c@intel.com>


On 01-09-2026 11:36, Purkait, Soham wrote:
> 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 ?

If the platform supports min pwm of 0, driver will set pwm to 0 in the 
hardware,

else it will be clamped to min_pwm.

>
> And does the write only succeeds when pwm_enable == 1 ? if so, kindly 
> also mention the same in the doc.
Yes we can write to pwmX only in manual mode i.e pwm_enable=1
I shall update this 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)

  reply	other threads:[~2026-09-02  7:15 UTC|newest]

Thread overview: 36+ 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
2026-09-02  7:14     ` Poosa, Karthik [this message]
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-03 17:13       ` Purkait, Soham
2026-09-02  6:35   ` Purkait, Soham
2026-09-02  7:36     ` Poosa, Karthik
2026-09-03 17:36   ` Purkait, Soham
2026-07-17  4:17 ` [PATCH v2 7/9] drm/xe/hwmon: Add kernel-doc for fan control Karthik Poosa
2026-09-04  4:02   ` Purkait, Soham
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=d71f2cd8-e1bf-42ea-bd20-654b0224e613@intel.com \
    --to=karthik.poosa@intel.com \
    --cc=anshuman.gupta@intel.com \
    --cc=badal.nilawar@intel.com \
    --cc=intel-xe@lists.freedesktop.org \
    --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 \
    --cc=soham.purkait@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