Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

thermal: intel: int340x: Add platform temperature control interface

Platform Temperature Control is a dynamic control loop implemented in
hardware to manage the skin or any board temperature of a device. The
reported skin or board temperature is controlled by comparing to a
configured target temperature and adjusting the SoC (System on Chip)
performance accordingly. The feature supports up to three platform
sensors.

OEMs (Original Equipment Manufacturers) can configure this feature
through the BIOS and provide temperature input directly to the hardware
via the Platform Environment Control Interface (PECI). As a result,
this feature can operate independently of any OS-level control.

The OS interface can be used to further fine-tune the default OEM
configuration. Here are some scenarios where the OS interface is
beneficial:

Verification of Firmware Control: Check if firmware-based control is
enabled. If it is, thermal controls from the OS/user space can be
backed out.

Adjusting Target Limits: While OEMs can set an aggressive target limit,
the OS can adjust this to a less aggressive limit based on operating
modes or conditions.

Given that this is platform temperature control, it is expected that a
single user-level manager owns and manages the controls. If multiple
user-level software applications attempt to write different targets, it
can lead to unexpected behavior. For instance, on a Linux desktop, the
Linux thermal daemon can manage these temperature controls, as it has
access to all other temperature control settings.

The hardware control interface is via MMIO offsets in the processor
thermal device MMIO space. There are three instances of MMIO registers.
Refer to the platform_temperature_control.c for MMIO details.

Expose "enable" and "temperature_target" via sysfs.

There are three instances of this controls. So up to three different
sensors can be controlled independently.

Sysfs interface:

tree /sys/bus/pci/devices/0000\:00\:04.0/ptc_?_control/
/sys/bus/pci/devices/0000:00:04.0/ptc_0_control/
├── enable
└── temperature_target
/sys/bus/pci/devices/0000:00:04.0/ptc_1_control/
├── enable
└── temperature_target
/sys/bus/pci/devices/0000:00:04.0/ptc_2_control/
├── enable
└── temperature_target

Description of attributes:

Enable: 1 for enable, 0 for disable. This attribute can be used to
read the current status. User space can write 0 or 1 to disable or
enable this feature respectively.

temperature_target: Target temperature limit to which hardware
will try to limit in milli degree C.

Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Link: https://patch.msgid.link/20250429000110.236243-2-srinivas.pandruvada@linux.intel.com
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

Srinivas Pandruvada and committed by
Rafael J. Wysocki
9befea30 92a09c47

+261 -1
+1
drivers/thermal/intel/int340x_thermal/Makefile
··· 9 9 obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device_pci.o 10 10 obj-$(CONFIG_PROC_THERMAL_MMIO_RAPL) += processor_thermal_rapl.o 11 11 obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_rfim.o 12 + obj-$(CONFIG_INT340X_THERMAL) += platform_temperature_control.o 12 13 obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_mbox.o 13 14 obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_wt_req.o 14 15 obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_wt_hint.o
+243
drivers/thermal/intel/int340x_thermal/platform_temperature_control.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * processor thermal device platform temperature controls 4 + * Copyright (c) 2025, Intel Corporation. 5 + */ 6 + 7 + /* 8 + * Platform temperature controls hardware interface 9 + * 10 + * The hardware control interface is via MMIO offsets in the processor 11 + * thermal device MMIO space. There are three instances of MMIO registers. 12 + * All registers are 64 bit wide with RW access. 13 + * 14 + * Name: PLATFORM_TEMPERATURE_CONTROL 15 + * Offsets: 0x5B20, 0x5B28, 0x5B30 16 + * 17 + * Bits Description 18 + * 7:0 TARGET_TEMP : Target temperature limit to which the control 19 + * mechanism is regulating. Units: 0.5C. 20 + * 8:8 ENABLE: Read current enable status of the feature or enable 21 + * feature. 22 + * 11:9 GAIN: Sets the aggressiveness of control loop from 0 to 7 23 + * 7 graceful, favors performance at the expense of temperature 24 + * overshoots 25 + * 0 aggressive, favors tight regulation over performance 26 + * 12:12 TEMPERATURE_OVERRIDE_EN 27 + * When set, hardware will use TEMPERATURE_OVERRIDE values instead 28 + * of reading from corresponding sensor. 29 + * 15:13 RESERVED 30 + * 23:16 MIN_PERFORMANCE_LEVEL: Minimum Performance level below which the 31 + * there will be no throttling. 0 - all levels of throttling allowed 32 + * including survivability actions. 255 - no throttling allowed. 33 + * 31:24 TEMPERATURE_OVERRIDE: Allows SW to override the input temperature. 34 + * hardware will use this value instead of the sensor temperature. 35 + * Units: 0.5C. 36 + * 63:32 RESERVED 37 + */ 38 + 39 + #include <linux/kernel.h> 40 + #include <linux/module.h> 41 + #include <linux/pci.h> 42 + #include "processor_thermal_device.h" 43 + 44 + struct mmio_reg { 45 + int bits; 46 + u16 mask; 47 + u16 shift; 48 + u16 units; 49 + }; 50 + 51 + #define MAX_ATTR_GROUP_NAME_LEN 32 52 + #define PTC_MAX_ATTRS 3 53 + 54 + struct ptc_data { 55 + u32 offset; 56 + struct attribute_group ptc_attr_group; 57 + struct attribute *ptc_attrs[PTC_MAX_ATTRS]; 58 + struct device_attribute temperature_target_attr; 59 + struct device_attribute enable_attr; 60 + char group_name[MAX_ATTR_GROUP_NAME_LEN]; 61 + }; 62 + 63 + static const struct mmio_reg ptc_mmio_regs[] = { 64 + { 8, 0xFF, 0, 500}, /* temperature_target, units 0.5C*/ 65 + { 1, 0x01, 8, 0}, /* enable */ 66 + { 3, 0x7, 9, 0}, /* gain */ 67 + { 8, 0xFF, 16, 0}, /* min_performance_level */ 68 + { 1, 0x1, 12, 0}, /* temperature_override_enable */ 69 + { 8, 0xFF, 24, 500}, /* temperature_override, units 0.5C */ 70 + }; 71 + 72 + #define PTC_MAX_INSTANCES 3 73 + 74 + /* Unique offset for each PTC instance */ 75 + static u32 ptc_offsets[PTC_MAX_INSTANCES] = {0x5B20, 0x5B28, 0x5B30}; 76 + 77 + /* These will represent sysfs attribute names */ 78 + static const char * const ptc_strings[] = { 79 + "temperature_target", 80 + "enable", 81 + NULL 82 + }; 83 + 84 + /* Lock to protect concurrent read/write and read-modify-write */ 85 + static DEFINE_MUTEX(ptc_lock); 86 + 87 + static ssize_t ptc_mmio_show(struct ptc_data *data, struct device *dev, 88 + struct device_attribute *attr, char *buf) 89 + { 90 + struct pci_dev *pdev = to_pci_dev(dev); 91 + struct proc_thermal_device *proc_priv; 92 + const struct mmio_reg *mmio_regs; 93 + int ret, units; 94 + u64 reg_val; 95 + 96 + proc_priv = pci_get_drvdata(pdev); 97 + mmio_regs = ptc_mmio_regs; 98 + ret = match_string(ptc_strings, -1, attr->attr.name); 99 + if (ret < 0) 100 + return ret; 101 + 102 + units = mmio_regs[ret].units; 103 + 104 + guard(mutex)(&ptc_lock); 105 + 106 + reg_val = readq((void __iomem *) (proc_priv->mmio_base + data->offset)); 107 + ret = (reg_val >> mmio_regs[ret].shift) & mmio_regs[ret].mask; 108 + if (units) 109 + ret *= units; 110 + 111 + return sysfs_emit(buf, "%d\n", ret); 112 + } 113 + 114 + #define PTC_SHOW(suffix)\ 115 + static ssize_t suffix##_show(struct device *dev,\ 116 + struct device_attribute *attr,\ 117 + char *buf)\ 118 + {\ 119 + struct ptc_data *data = container_of(attr, struct ptc_data, suffix##_attr);\ 120 + return ptc_mmio_show(data, dev, attr, buf);\ 121 + } 122 + 123 + static void ptc_mmio_write(struct pci_dev *pdev, u32 offset, int index, u32 value) 124 + { 125 + struct proc_thermal_device *proc_priv; 126 + u64 mask, reg_val; 127 + 128 + proc_priv = pci_get_drvdata(pdev); 129 + 130 + mask = GENMASK_ULL(ptc_mmio_regs[index].shift + ptc_mmio_regs[index].bits - 1, 131 + ptc_mmio_regs[index].shift); 132 + 133 + guard(mutex)(&ptc_lock); 134 + 135 + reg_val = readq((void __iomem *) (proc_priv->mmio_base + offset)); 136 + reg_val &= ~mask; 137 + reg_val |= (value << ptc_mmio_regs[index].shift); 138 + writeq(reg_val, (void __iomem *) (proc_priv->mmio_base + offset)); 139 + } 140 + 141 + static int ptc_store(struct ptc_data *data, struct device *dev, struct device_attribute *attr, 142 + const char *buf, size_t count) 143 + { 144 + struct pci_dev *pdev = to_pci_dev(dev); 145 + unsigned int input; 146 + int ret; 147 + 148 + ret = kstrtouint(buf, 10, &input); 149 + if (ret) 150 + return ret; 151 + 152 + ret = match_string(ptc_strings, -1, attr->attr.name); 153 + if (ret < 0) 154 + return ret; 155 + 156 + if (ptc_mmio_regs[ret].units) 157 + input /= ptc_mmio_regs[ret].units; 158 + 159 + if (input > ptc_mmio_regs[ret].mask) 160 + return -EINVAL; 161 + 162 + ptc_mmio_write(pdev, data->offset, ret, input); 163 + 164 + return count; 165 + } 166 + 167 + #define PTC_STORE(suffix)\ 168 + static ssize_t suffix##_store(struct device *dev,\ 169 + struct device_attribute *attr,\ 170 + const char *buf, size_t count)\ 171 + {\ 172 + struct ptc_data *data = container_of(attr, struct ptc_data, suffix##_attr);\ 173 + return ptc_store(data, dev, attr, buf, count);\ 174 + } 175 + 176 + PTC_SHOW(temperature_target); 177 + PTC_STORE(temperature_target); 178 + PTC_SHOW(enable); 179 + PTC_STORE(enable); 180 + 181 + #define ptc_init_attribute(_name)\ 182 + do {\ 183 + sysfs_attr_init(&data->_name##_attr.attr);\ 184 + data->_name##_attr.show = _name##_show;\ 185 + data->_name##_attr.store = _name##_store;\ 186 + data->_name##_attr.attr.name = #_name;\ 187 + data->_name##_attr.attr.mode = 0644;\ 188 + } while (0) 189 + 190 + static int ptc_create_groups(struct pci_dev *pdev, int instance, struct ptc_data *data) 191 + { 192 + int ret, index = 0; 193 + 194 + ptc_init_attribute(temperature_target); 195 + ptc_init_attribute(enable); 196 + 197 + data->ptc_attrs[index++] = &data->temperature_target_attr.attr; 198 + data->ptc_attrs[index++] = &data->enable_attr.attr; 199 + data->ptc_attrs[index] = NULL; 200 + 201 + snprintf(data->group_name, MAX_ATTR_GROUP_NAME_LEN, 202 + "ptc_%d_control", instance); 203 + data->ptc_attr_group.name = data->group_name; 204 + data->ptc_attr_group.attrs = data->ptc_attrs; 205 + 206 + ret = sysfs_create_group(&pdev->dev.kobj, &data->ptc_attr_group); 207 + 208 + return ret; 209 + } 210 + 211 + static struct ptc_data ptc_instance[PTC_MAX_INSTANCES]; 212 + 213 + int proc_thermal_ptc_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv) 214 + { 215 + if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_PTC) { 216 + int i; 217 + 218 + for (i = 0; i < PTC_MAX_INSTANCES; i++) { 219 + ptc_instance[i].offset = ptc_offsets[i]; 220 + ptc_create_groups(pdev, i, &ptc_instance[i]); 221 + } 222 + } 223 + 224 + return 0; 225 + } 226 + EXPORT_SYMBOL_GPL(proc_thermal_ptc_add); 227 + 228 + void proc_thermal_ptc_remove(struct pci_dev *pdev) 229 + { 230 + struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev); 231 + 232 + if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_PTC) { 233 + int i; 234 + 235 + for (i = 0; i < PTC_MAX_INSTANCES; i++) 236 + sysfs_remove_group(&pdev->dev.kobj, &ptc_instance[i].ptc_attr_group); 237 + } 238 + } 239 + EXPORT_SYMBOL_GPL(proc_thermal_ptc_remove); 240 + 241 + MODULE_IMPORT_NS("INT340X_THERMAL"); 242 + MODULE_LICENSE("GPL"); 243 + MODULE_DESCRIPTION("Processor Thermal PTC Interface");
+14 -1
drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
··· 399 399 } 400 400 } 401 401 402 + if (feature_mask & PROC_THERMAL_FEATURE_PTC) { 403 + ret = proc_thermal_ptc_add(pdev, proc_priv); 404 + if (ret) { 405 + dev_err(&pdev->dev, "failed to add PTC MMIO interface\n"); 406 + goto err_rem_rapl; 407 + } 408 + } 409 + 402 410 if (feature_mask & PROC_THERMAL_FEATURE_FIVR || 403 411 feature_mask & PROC_THERMAL_FEATURE_DVFS || 404 412 feature_mask & PROC_THERMAL_FEATURE_DLVR) { 405 413 ret = proc_thermal_rfim_add(pdev, proc_priv); 406 414 if (ret) { 407 415 dev_err(&pdev->dev, "failed to add RFIM interface\n"); 408 - goto err_rem_rapl; 416 + goto err_rem_ptc; 409 417 } 410 418 } 411 419 ··· 435 427 436 428 err_rem_rfim: 437 429 proc_thermal_rfim_remove(pdev); 430 + err_rem_ptc: 431 + proc_thermal_ptc_remove(pdev); 438 432 err_rem_rapl: 439 433 proc_thermal_rapl_remove(); 440 434 ··· 448 438 { 449 439 if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_RAPL) 450 440 proc_thermal_rapl_remove(); 441 + 442 + if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_PTC) 443 + proc_thermal_ptc_remove(pdev); 451 444 452 445 if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR || 453 446 proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS ||
+3
drivers/thermal/intel/int340x_thermal/processor_thermal_device.h
··· 67 67 #define PROC_THERMAL_FEATURE_WT_HINT 0x20 68 68 #define PROC_THERMAL_FEATURE_POWER_FLOOR 0x40 69 69 #define PROC_THERMAL_FEATURE_MSI_SUPPORT 0x80 70 + #define PROC_THERMAL_FEATURE_PTC 0x100 70 71 71 72 #if IS_ENABLED(CONFIG_PROC_THERMAL_MMIO_RAPL) 72 73 int proc_thermal_rapl_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv); ··· 124 123 struct proc_thermal_device *proc_priv, 125 124 kernel_ulong_t feature_mask); 126 125 void proc_thermal_mmio_remove(struct pci_dev *pdev, struct proc_thermal_device *proc_priv); 126 + int proc_thermal_ptc_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv); 127 + void proc_thermal_ptc_remove(struct pci_dev *pdev); 127 128 #endif