mirror of
https://github.com/torvalds/linux.git
synced 2026-06-01 19:13:47 +02:00
drm/xe/hwmon: Add support to manage PL2 though mailbox
Add support to manage power limit PL2 (burst limit) through pcode mailbox commands. v2: - Update power1_cap definition in hwmon documentation. (Badal) - Clamp PL2 power limit to GPU firmware default value. v3: - Activate the power label when either the PL1 or PL2 power limit is enabled. v4: - Update description of pl2_on_boot variable to fix kernel-doc error. v5: - Remove unnecessary drm_warn. - Rectify powerX_label permission to read-only on platforms without mailbox power limits support. - Expose powerX_cap entries only on platforms with mailbox support. v6: - Improve commit message, refer to BIOS as GPU firmware. - Refer to card firmware as GPU firmware in code. - Remove unnecessary drm_dbg. - Print supported and unsupported power limits. (Rodrigo) - Enable powerN_cap/max_xxx entries only when power limits supported in GPU firmware. Signed-off-by: Karthik Poosa <karthik.poosa@intel.com> Reviewed-by: Badal Nilawar <badal.nilawar@intel.com> Link: https://lore.kernel.org/r/20250529163458.2354509-4-karthik.poosa@intel.com Signed-off-by: Rodrigo Vivi <rodrigo.vivi@intel.com>
This commit is contained in:
parent
25e963a09e
commit
c713b9a23c
|
|
@ -148,3 +148,33 @@ Contact: intel-xe@lists.freedesktop.org
|
|||
Description: RO. Fan 3 speed in RPM.
|
||||
|
||||
Only supported for particular Intel Xe graphics platforms.
|
||||
|
||||
What: /sys/bus/pci/drivers/xe/.../hwmon/hwmon<i>/power1_cap
|
||||
Date: May 2025
|
||||
KernelVersion: 6.15
|
||||
Contact: intel-xe@lists.freedesktop.org
|
||||
Description: RW. Card burst (PL2) power limit in microwatts.
|
||||
|
||||
The power controller will throttle the operating frequency
|
||||
if the power averaged over a window (typically milli seconds)
|
||||
exceeds this limit. A read value of 0 means that the PL2
|
||||
power limit is disabled, writing 0 disables the limit.
|
||||
PL2 is greater than PL1 and its time window is lesser
|
||||
compared to PL1.
|
||||
|
||||
Only supported for particular Intel Xe graphics platforms.
|
||||
|
||||
What: /sys/bus/pci/drivers/xe/.../hwmon/hwmon<i>/power2_cap
|
||||
Date: May 2025
|
||||
KernelVersion: 6.15
|
||||
Contact: intel-xe@lists.freedesktop.org
|
||||
Description: RW. Package burst (PL2) power limit in microwatts.
|
||||
|
||||
The power controller will throttle the operating frequency
|
||||
if the power averaged over a window (typically milli seconds)
|
||||
exceeds this limit. A read value of 0 means that the PL2
|
||||
power limit is disabled, writing 0 disables the limit.
|
||||
PL2 is greater than PL1 and its time window is lesser
|
||||
compared to PL1.
|
||||
|
||||
Only supported for particular Intel Xe graphics platforms.
|
||||
|
|
|
|||
|
|
@ -51,6 +51,14 @@ enum xe_fan_channel {
|
|||
FAN_MAX,
|
||||
};
|
||||
|
||||
/* Attribute index for powerX_xxx_interval sysfs entries */
|
||||
enum sensor_attr_power {
|
||||
SENSOR_INDEX_PSYS_PL1,
|
||||
SENSOR_INDEX_PKG_PL1,
|
||||
SENSOR_INDEX_PSYS_PL2,
|
||||
SENSOR_INDEX_PKG_PL2,
|
||||
};
|
||||
|
||||
/*
|
||||
* For platforms that support mailbox commands for power limits, REG_PKG_POWER_SKU_UNIT is
|
||||
* not supported and below are SKU units to be used.
|
||||
|
|
@ -72,8 +80,9 @@ enum xe_fan_channel {
|
|||
* PL*_HWMON_ATTR - mapping of hardware power limits to corresponding hwmon power attribute.
|
||||
*/
|
||||
#define PL1_HWMON_ATTR hwmon_power_max
|
||||
#define PL2_HWMON_ATTR hwmon_power_cap
|
||||
|
||||
#define PWR_ATTR_TO_STR(attr) (((attr) == hwmon_power_max) ? "PL1" : "Invalid")
|
||||
#define PWR_ATTR_TO_STR(attr) (((attr) == hwmon_power_max) ? "PL1" : "PL2")
|
||||
|
||||
/*
|
||||
* Timeout for power limit write mailbox command.
|
||||
|
|
@ -124,6 +133,9 @@ struct xe_hwmon {
|
|||
bool boot_power_limit_read;
|
||||
/** @pl1_on_boot: power limit PL1 on boot */
|
||||
u32 pl1_on_boot[CHANNEL_MAX];
|
||||
/** @pl2_on_boot: power limit PL2 on boot */
|
||||
u32 pl2_on_boot[CHANNEL_MAX];
|
||||
|
||||
};
|
||||
|
||||
static int xe_hwmon_pcode_read_power_limit(const struct xe_hwmon *hwmon, u32 attr, int channel,
|
||||
|
|
@ -151,8 +163,10 @@ static int xe_hwmon_pcode_read_power_limit(const struct xe_hwmon *hwmon, u32 att
|
|||
/* return the value only if limit is enabled */
|
||||
if (attr == PL1_HWMON_ATTR)
|
||||
*uval = (val0 & PWR_LIM_EN) ? val0 : 0;
|
||||
else if (attr == PL2_HWMON_ATTR)
|
||||
*uval = (val1 & PWR_LIM_EN) ? val1 : 0;
|
||||
else if (attr == hwmon_power_label)
|
||||
*uval = (val0 & PWR_LIM_EN) ? 1 : 0;
|
||||
*uval = (val0 & PWR_LIM_EN) ? 1 : (val1 & PWR_LIM_EN) ? 1 : 0;
|
||||
else
|
||||
*uval = 0;
|
||||
|
||||
|
|
@ -180,6 +194,8 @@ static int xe_hwmon_pcode_write_power_limit(const struct xe_hwmon *hwmon, u32 at
|
|||
|
||||
if (attr == PL1_HWMON_ATTR)
|
||||
val0 = uval;
|
||||
else if (attr == PL2_HWMON_ATTR)
|
||||
val1 = uval;
|
||||
else
|
||||
return -EIO;
|
||||
|
||||
|
|
@ -273,7 +289,7 @@ static struct xe_reg xe_hwmon_get_reg(struct xe_hwmon *hwmon, enum xe_hwmon_reg
|
|||
*/
|
||||
static void xe_hwmon_power_max_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *value)
|
||||
{
|
||||
u64 reg_val, min, max;
|
||||
u64 reg_val = 0, min, max;
|
||||
struct xe_device *xe = hwmon->xe;
|
||||
struct xe_reg rapl_limit, pkg_power_sku;
|
||||
struct xe_mmio *mmio = xe_root_tile_mmio(xe);
|
||||
|
|
@ -327,7 +343,7 @@ static int xe_hwmon_power_max_write(struct xe_hwmon *hwmon, u32 attr, int channe
|
|||
{
|
||||
struct xe_mmio *mmio = xe_root_tile_mmio(hwmon->xe);
|
||||
int ret = 0;
|
||||
u32 reg_val;
|
||||
u32 reg_val, max;
|
||||
struct xe_reg rapl_limit;
|
||||
|
||||
mutex_lock(&hwmon->hwmon_lock);
|
||||
|
|
@ -355,20 +371,25 @@ static int xe_hwmon_power_max_write(struct xe_hwmon *hwmon, u32 attr, int channe
|
|||
|
||||
/* Computation in 64-bits to avoid overflow. Round to nearest. */
|
||||
reg_val = DIV_ROUND_CLOSEST_ULL((u64)value << hwmon->scl_shift_power, SF_POWER);
|
||||
reg_val = PWR_LIM_EN | REG_FIELD_PREP(PWR_LIM_VAL, reg_val);
|
||||
|
||||
/*
|
||||
* Clamp power limit to card-firmware default as maximum, as an additional protection to
|
||||
* Clamp power limit to GPU firmware default as maximum, as an additional protection to
|
||||
* pcode clamp.
|
||||
*/
|
||||
if (hwmon->xe->info.has_mbx_power_limits) {
|
||||
if (reg_val > REG_FIELD_GET(PWR_LIM_VAL, hwmon->pl1_on_boot[channel])) {
|
||||
reg_val = REG_FIELD_GET(PWR_LIM_VAL, hwmon->pl1_on_boot[channel]);
|
||||
drm_dbg(&hwmon->xe->drm, "Clamping power limit to firmware default 0x%x\n",
|
||||
max = (attr == PL1_HWMON_ATTR) ?
|
||||
hwmon->pl1_on_boot[channel] : hwmon->pl2_on_boot[channel];
|
||||
max = REG_FIELD_PREP(PWR_LIM_VAL, max);
|
||||
if (reg_val > max) {
|
||||
reg_val = max;
|
||||
drm_dbg(&hwmon->xe->drm,
|
||||
"Clamping power limit to GPU firmware default 0x%x\n",
|
||||
reg_val);
|
||||
}
|
||||
}
|
||||
|
||||
reg_val = PWR_LIM_EN | REG_FIELD_PREP(PWR_LIM_VAL, reg_val);
|
||||
|
||||
if (hwmon->xe->info.has_mbx_power_limits)
|
||||
ret = xe_hwmon_pcode_write_power_limit(hwmon, attr, channel, reg_val);
|
||||
else
|
||||
|
|
@ -452,8 +473,9 @@ xe_hwmon_power_max_interval_show(struct device *dev, struct device_attribute *at
|
|||
struct xe_mmio *mmio = xe_root_tile_mmio(hwmon->xe);
|
||||
u32 x, y, x_w = 2; /* 2 bits */
|
||||
u64 r, tau4, out;
|
||||
int channel = to_sensor_dev_attr(attr)->index;
|
||||
int channel = (to_sensor_dev_attr(attr)->index % 2) ? CHANNEL_PKG : CHANNEL_CARD;
|
||||
u32 power_attr = PL1_HWMON_ATTR;
|
||||
|
||||
int ret = 0;
|
||||
|
||||
xe_pm_runtime_get(hwmon->xe);
|
||||
|
|
@ -506,9 +528,9 @@ xe_hwmon_power_max_interval_store(struct device *dev, struct device_attribute *a
|
|||
u32 x, y, rxy, x_w = 2; /* 2 bits */
|
||||
u64 tau4, r, max_win;
|
||||
unsigned long val;
|
||||
int ret;
|
||||
int channel = to_sensor_dev_attr(attr)->index;
|
||||
int channel = (to_sensor_dev_attr(attr)->index % 2) ? CHANNEL_PKG : CHANNEL_CARD;
|
||||
u32 power_attr = PL1_HWMON_ATTR;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoul(buf, 0, &val);
|
||||
if (ret)
|
||||
|
|
@ -535,10 +557,8 @@ xe_hwmon_power_max_interval_store(struct device *dev, struct device_attribute *a
|
|||
tau4 = (u64)((1 << x_w) | x) << y;
|
||||
max_win = mul_u64_u32_shr(tau4, SF_TIME, hwmon->scl_shift_time + x_w);
|
||||
|
||||
if (val > max_win) {
|
||||
drm_warn(&hwmon->xe->drm, "power_interval invalid val 0x%lx\n", val);
|
||||
if (val > max_win)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* val in hw units */
|
||||
val = DIV_ROUND_CLOSEST_ULL((u64)val << hwmon->scl_shift_time, SF_TIME) + 1;
|
||||
|
|
@ -582,11 +602,11 @@ xe_hwmon_power_max_interval_store(struct device *dev, struct device_attribute *a
|
|||
/* PSYS PL1 */
|
||||
static SENSOR_DEVICE_ATTR(power1_max_interval, 0664,
|
||||
xe_hwmon_power_max_interval_show,
|
||||
xe_hwmon_power_max_interval_store, CHANNEL_CARD);
|
||||
|
||||
xe_hwmon_power_max_interval_store, SENSOR_INDEX_PSYS_PL1);
|
||||
/* PKG PL1 */
|
||||
static SENSOR_DEVICE_ATTR(power2_max_interval, 0664,
|
||||
xe_hwmon_power_max_interval_show,
|
||||
xe_hwmon_power_max_interval_store, CHANNEL_PKG);
|
||||
xe_hwmon_power_max_interval_store, SENSOR_INDEX_PKG_PL1);
|
||||
|
||||
static struct attribute *hwmon_attributes[] = {
|
||||
&sensor_dev_attr_power1_max_interval.dev_attr.attr,
|
||||
|
|
@ -600,7 +620,7 @@ static umode_t xe_hwmon_attributes_visible(struct kobject *kobj,
|
|||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct xe_hwmon *hwmon = dev_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
int channel = index ? CHANNEL_PKG : CHANNEL_CARD;
|
||||
int channel = (index % 2) ? CHANNEL_PKG : CHANNEL_CARD;
|
||||
u32 power_attr = PL1_HWMON_ATTR;
|
||||
u32 uval;
|
||||
|
||||
|
|
@ -609,7 +629,7 @@ static umode_t xe_hwmon_attributes_visible(struct kobject *kobj,
|
|||
if (hwmon->xe->info.has_mbx_power_limits) {
|
||||
xe_hwmon_pcode_read_power_limit(hwmon, power_attr, channel, &uval);
|
||||
ret = (uval & PWR_LIM_EN) ? attr->mode : 0;
|
||||
} else {
|
||||
} else if (power_attr != PL2_HWMON_ATTR) {
|
||||
ret = xe_reg_is_valid(xe_hwmon_get_reg(hwmon, REG_PKG_RAPL_LIMIT,
|
||||
channel)) ? attr->mode : 0;
|
||||
}
|
||||
|
|
@ -632,8 +652,9 @@ static const struct attribute_group *hwmon_groups[] = {
|
|||
static const struct hwmon_channel_info * const hwmon_info[] = {
|
||||
HWMON_CHANNEL_INFO(temp, HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL),
|
||||
HWMON_CHANNEL_INFO(power, HWMON_P_MAX | HWMON_P_RATED_MAX | HWMON_P_LABEL | HWMON_P_CRIT,
|
||||
HWMON_P_MAX | HWMON_P_RATED_MAX | HWMON_P_LABEL),
|
||||
HWMON_CHANNEL_INFO(power, HWMON_P_MAX | HWMON_P_RATED_MAX | HWMON_P_LABEL | HWMON_P_CRIT |
|
||||
HWMON_P_CAP,
|
||||
HWMON_P_MAX | HWMON_P_RATED_MAX | HWMON_P_LABEL | HWMON_P_CAP),
|
||||
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),
|
||||
|
|
@ -754,17 +775,32 @@ xe_hwmon_temp_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *val)
|
|||
static umode_t
|
||||
xe_hwmon_power_is_visible(struct xe_hwmon *hwmon, u32 attr, int channel)
|
||||
{
|
||||
u32 uval;
|
||||
u32 uval = 0;
|
||||
struct xe_reg rapl_limit;
|
||||
struct xe_mmio *mmio = xe_root_tile_mmio(hwmon->xe);
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_power_max:
|
||||
case hwmon_power_cap:
|
||||
case hwmon_power_label:
|
||||
if (hwmon->xe->info.has_mbx_power_limits) {
|
||||
xe_hwmon_pcode_read_power_limit(hwmon, attr, channel, &uval);
|
||||
return (uval) ? 0664 : 0;
|
||||
} else {
|
||||
return xe_reg_is_valid(xe_hwmon_get_reg(hwmon, REG_PKG_RAPL_LIMIT,
|
||||
channel)) ? 0664 : 0;
|
||||
} else if (attr != PL2_HWMON_ATTR) {
|
||||
rapl_limit = xe_hwmon_get_reg(hwmon, REG_PKG_RAPL_LIMIT, channel);
|
||||
if (xe_reg_is_valid(rapl_limit))
|
||||
uval = xe_mmio_read32(mmio, rapl_limit);
|
||||
}
|
||||
if (uval & PWR_LIM_EN) {
|
||||
if (attr == hwmon_power_label)
|
||||
return 0444;
|
||||
|
||||
drm_info(&hwmon->xe->drm, "%s is supported on channel %d\n",
|
||||
PWR_ATTR_TO_STR(attr), channel);
|
||||
return 0664;
|
||||
}
|
||||
drm_dbg(&hwmon->xe->drm, "%s is unsupported on channel %d\n",
|
||||
PWR_ATTR_TO_STR(attr), channel);
|
||||
return 0;
|
||||
case hwmon_power_rated_max:
|
||||
if (hwmon->xe->info.has_mbx_power_limits)
|
||||
return 0;
|
||||
|
|
@ -772,11 +808,9 @@ xe_hwmon_power_is_visible(struct xe_hwmon *hwmon, u32 attr, int channel)
|
|||
return xe_reg_is_valid(xe_hwmon_get_reg(hwmon, REG_PKG_POWER_SKU,
|
||||
channel)) ? 0444 : 0;
|
||||
case hwmon_power_crit:
|
||||
case hwmon_power_label:
|
||||
if (channel == CHANNEL_CARD) {
|
||||
xe_hwmon_pcode_read_i1(hwmon, &uval);
|
||||
return (uval & POWER_SETUP_I1_WATTS) ? (attr == hwmon_power_label) ?
|
||||
0444 : 0644 : 0;
|
||||
return (uval & POWER_SETUP_I1_WATTS) ? 0644 : 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
@ -790,6 +824,7 @@ xe_hwmon_power_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *val)
|
|||
{
|
||||
switch (attr) {
|
||||
case hwmon_power_max:
|
||||
case hwmon_power_cap:
|
||||
xe_hwmon_power_max_read(hwmon, attr, channel, val);
|
||||
return 0;
|
||||
case hwmon_power_rated_max:
|
||||
|
|
@ -806,6 +841,7 @@ static int
|
|||
xe_hwmon_power_write(struct xe_hwmon *hwmon, u32 attr, int channel, long val)
|
||||
{
|
||||
switch (attr) {
|
||||
case hwmon_power_cap:
|
||||
case hwmon_power_max:
|
||||
return xe_hwmon_power_max_write(hwmon, attr, channel, val);
|
||||
case hwmon_power_crit:
|
||||
|
|
@ -1128,13 +1164,17 @@ xe_hwmon_get_preregistration_info(struct xe_hwmon *hwmon)
|
|||
struct xe_reg pkg_power_sku_unit;
|
||||
|
||||
if (hwmon->xe->info.has_mbx_power_limits) {
|
||||
/* Check if card firmware support mailbox power limits commands. */
|
||||
/* Check if GPU firmware support mailbox power limits commands. */
|
||||
if (xe_hwmon_pcode_read_power_limit(hwmon, PL1_HWMON_ATTR, CHANNEL_CARD,
|
||||
&hwmon->pl1_on_boot[CHANNEL_CARD]) |
|
||||
xe_hwmon_pcode_read_power_limit(hwmon, PL1_HWMON_ATTR, CHANNEL_PKG,
|
||||
&hwmon->pl1_on_boot[CHANNEL_PKG])) {
|
||||
&hwmon->pl1_on_boot[CHANNEL_PKG]) |
|
||||
xe_hwmon_pcode_read_power_limit(hwmon, PL2_HWMON_ATTR, CHANNEL_CARD,
|
||||
&hwmon->pl2_on_boot[CHANNEL_CARD]) |
|
||||
xe_hwmon_pcode_read_power_limit(hwmon, PL1_HWMON_ATTR, CHANNEL_PKG,
|
||||
&hwmon->pl2_on_boot[CHANNEL_PKG])) {
|
||||
drm_warn(&hwmon->xe->drm,
|
||||
"Failed to read power limits, check card firmware !\n");
|
||||
"Failed to read power limits, check GPU firmware !\n");
|
||||
} else {
|
||||
drm_info(&hwmon->xe->drm, "Using mailbox commands for power limits\n");
|
||||
/* Write default limits to read from pcode from now on. */
|
||||
|
|
@ -1144,6 +1184,12 @@ xe_hwmon_get_preregistration_info(struct xe_hwmon *hwmon)
|
|||
xe_hwmon_pcode_write_power_limit(hwmon, PL1_HWMON_ATTR,
|
||||
CHANNEL_PKG,
|
||||
hwmon->pl1_on_boot[CHANNEL_PKG]);
|
||||
xe_hwmon_pcode_write_power_limit(hwmon, PL2_HWMON_ATTR,
|
||||
CHANNEL_CARD,
|
||||
hwmon->pl2_on_boot[CHANNEL_CARD]);
|
||||
xe_hwmon_pcode_write_power_limit(hwmon, PL2_HWMON_ATTR,
|
||||
CHANNEL_PKG,
|
||||
hwmon->pl2_on_boot[CHANNEL_PKG]);
|
||||
hwmon->scl_shift_power = PWR_UNIT;
|
||||
hwmon->scl_shift_energy = ENERGY_UNIT;
|
||||
hwmon->scl_shift_time = TIME_UNIT;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user