1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Processor Thermal Device Interface for Reading and Writing 4 * SoC Power Slider Values from User Space. 5 * 6 * Operation: 7 * The SOC_EFFICIENCY_SLIDER_0_0_0_MCHBAR register is accessed 8 * using the MMIO (Memory-Mapped I/O) interface with an MMIO offset of 0x5B38. 9 * Although this register is 64 bits wide, only bits 7:0 are used, 10 * and the other bits remain unchanged. 11 * 12 * Bit definitions 13 * 14 * Bits 2:0 (Slider value): 15 * The SoC optimizer slider value indicates the system wide energy performance 16 * hint. The slider has no specific units and ranges from 0 (highest 17 * performance) to 6 (highest energy efficiency). Value of 7 is reserved. 18 * Bits 3 : Reserved 19 * Bits 6:4 (Offset) 20 * Offset allows the SoC to automatically switch slider position in range 21 * [slider value (bits 2:0) + offset] to improve power efficiency based on 22 * internal SoC algorithms. 23 * Bit 7 (Enable): 24 * If this bit is set, the SoC Optimization sliders will be processed by the 25 * SoC firmware. 26 * 27 * Copyright (c) 2025, Intel Corporation. 28 */ 29 30 #include <linux/bitfield.h> 31 #include <linux/pci.h> 32 #include <linux/platform_profile.h> 33 #include "processor_thermal_device.h" 34 35 #define SOC_POWER_SLIDER_OFFSET 0x5B38 36 37 enum power_slider_preference { 38 SOC_POWER_SLIDER_PERFORMANCE, 39 SOC_POWER_SLIDER_BALANCE, 40 SOC_POWER_SLIDER_POWERSAVE, 41 }; 42 43 #define SOC_SLIDER_VALUE_MINIMUM 0x00 44 #define SOC_SLIDER_VALUE_BALANCE 0x03 45 #define SOC_SLIDER_VALUE_MAXIMUM 0x06 46 47 #define SLIDER_MASK GENMASK_ULL(2, 0) 48 #define SLIDER_ENABLE_BIT 7 49 50 static u8 slider_values[] = { 51 [SOC_POWER_SLIDER_PERFORMANCE] = SOC_SLIDER_VALUE_MINIMUM, 52 [SOC_POWER_SLIDER_BALANCE] = SOC_SLIDER_VALUE_BALANCE, 53 [SOC_POWER_SLIDER_POWERSAVE] = SOC_SLIDER_VALUE_MAXIMUM, 54 }; 55 56 /* Lock to protect module param updates */ 57 static DEFINE_MUTEX(slider_param_lock); 58 59 static int slider_balanced_param = SOC_SLIDER_VALUE_BALANCE; 60 61 static int slider_def_balance_set(const char *arg, const struct kernel_param *kp) 62 { 63 u8 slider_val; 64 int ret; 65 66 guard(mutex)(&slider_param_lock); 67 68 ret = kstrtou8(arg, 16, &slider_val); 69 if (!ret) { 70 if (slider_val <= slider_values[SOC_POWER_SLIDER_PERFORMANCE] || 71 slider_val >= slider_values[SOC_POWER_SLIDER_POWERSAVE]) 72 return -EINVAL; 73 74 slider_balanced_param = slider_val; 75 } 76 77 return ret; 78 } 79 80 static int slider_def_balance_get(char *buf, const struct kernel_param *kp) 81 { 82 guard(mutex)(&slider_param_lock); 83 return sysfs_emit(buf, "%02x\n", slider_values[SOC_POWER_SLIDER_BALANCE]); 84 } 85 86 static const struct kernel_param_ops slider_def_balance_ops = { 87 .set = slider_def_balance_set, 88 .get = slider_def_balance_get, 89 }; 90 91 module_param_cb(slider_balance, &slider_def_balance_ops, NULL, 0644); 92 MODULE_PARM_DESC(slider_balance, "Set slider default value for balance"); 93 94 static u8 slider_offset; 95 96 static int slider_def_offset_set(const char *arg, const struct kernel_param *kp) 97 { 98 u8 offset; 99 int ret; 100 101 guard(mutex)(&slider_param_lock); 102 103 ret = kstrtou8(arg, 16, &offset); 104 if (!ret) { 105 if (offset > SOC_SLIDER_VALUE_MAXIMUM) 106 return -EINVAL; 107 108 slider_offset = offset; 109 } 110 111 return ret; 112 } 113 114 static int slider_def_offset_get(char *buf, const struct kernel_param *kp) 115 { 116 guard(mutex)(&slider_param_lock); 117 return sysfs_emit(buf, "%02x\n", slider_offset); 118 } 119 120 static const struct kernel_param_ops slider_offset_ops = { 121 .set = slider_def_offset_set, 122 .get = slider_def_offset_get, 123 }; 124 125 /* 126 * To enhance power efficiency dynamically, the firmware can optionally 127 * auto-adjust the slider value based on the current workload. This 128 * adjustment is controlled by the "slider_offset" module parameter. 129 * This offset permits the firmware to increase the slider value 130 * up to and including "SoC slider + slider offset,". 131 */ 132 module_param_cb(slider_offset, &slider_offset_ops, NULL, 0644); 133 MODULE_PARM_DESC(slider_offset, "Set slider offset"); 134 135 /* Convert from platform power profile option to SoC slider value */ 136 static int convert_profile_to_power_slider(enum platform_profile_option profile) 137 { 138 switch (profile) { 139 case PLATFORM_PROFILE_LOW_POWER: 140 return slider_values[SOC_POWER_SLIDER_POWERSAVE]; 141 case PLATFORM_PROFILE_BALANCED: 142 return slider_values[SOC_POWER_SLIDER_BALANCE]; 143 case PLATFORM_PROFILE_PERFORMANCE: 144 return slider_values[SOC_POWER_SLIDER_PERFORMANCE]; 145 default: 146 break; 147 } 148 149 return -EOPNOTSUPP; 150 } 151 152 /* Convert to platform power profile option from SoC slider values */ 153 static int convert_power_slider_to_profile(u8 slider) 154 { 155 if (slider == slider_values[SOC_POWER_SLIDER_PERFORMANCE]) 156 return PLATFORM_PROFILE_PERFORMANCE; 157 if (slider == slider_values[SOC_POWER_SLIDER_BALANCE]) 158 return PLATFORM_PROFILE_BALANCED; 159 if (slider == slider_values[SOC_POWER_SLIDER_POWERSAVE]) 160 return PLATFORM_PROFILE_LOW_POWER; 161 162 return -EOPNOTSUPP; 163 } 164 165 static inline u64 read_soc_slider(struct proc_thermal_device *proc_priv) 166 { 167 return readq(proc_priv->mmio_base + SOC_POWER_SLIDER_OFFSET); 168 } 169 170 static inline void write_soc_slider(struct proc_thermal_device *proc_priv, u64 val) 171 { 172 writeq(val, proc_priv->mmio_base + SOC_POWER_SLIDER_OFFSET); 173 } 174 175 #define SLIDER_OFFSET_MASK GENMASK_ULL(6, 4) 176 177 static void set_soc_power_profile(struct proc_thermal_device *proc_priv, int slider) 178 { 179 u64 val; 180 181 val = read_soc_slider(proc_priv); 182 val &= ~SLIDER_MASK; 183 val |= FIELD_PREP(SLIDER_MASK, slider) | BIT(SLIDER_ENABLE_BIT); 184 185 /* Set the slider offset from module params */ 186 val &= ~SLIDER_OFFSET_MASK; 187 val |= FIELD_PREP(SLIDER_OFFSET_MASK, slider_offset); 188 189 write_soc_slider(proc_priv, val); 190 } 191 192 /* profile get/set callbacks are called with a profile lock, so no need for local locks */ 193 194 static int power_slider_platform_profile_set(struct device *dev, 195 enum platform_profile_option profile) 196 { 197 struct proc_thermal_device *proc_priv; 198 int slider; 199 200 proc_priv = dev_get_drvdata(dev); 201 if (!proc_priv) 202 return -EOPNOTSUPP; 203 204 guard(mutex)(&slider_param_lock); 205 206 slider_values[SOC_POWER_SLIDER_BALANCE] = slider_balanced_param; 207 208 slider = convert_profile_to_power_slider(profile); 209 if (slider < 0) 210 return slider; 211 212 set_soc_power_profile(proc_priv, slider); 213 214 return 0; 215 } 216 217 static int power_slider_platform_profile_get(struct device *dev, 218 enum platform_profile_option *profile) 219 { 220 struct proc_thermal_device *proc_priv; 221 int slider, ret; 222 u64 val; 223 224 proc_priv = dev_get_drvdata(dev); 225 if (!proc_priv) 226 return -EOPNOTSUPP; 227 228 val = read_soc_slider(proc_priv); 229 slider = FIELD_GET(SLIDER_MASK, val); 230 231 ret = convert_power_slider_to_profile(slider); 232 if (ret < 0) 233 return ret; 234 235 *profile = ret; 236 237 return 0; 238 } 239 240 static int power_slider_platform_profile_probe(void *drvdata, unsigned long *choices) 241 { 242 set_bit(PLATFORM_PROFILE_LOW_POWER, choices); 243 set_bit(PLATFORM_PROFILE_BALANCED, choices); 244 set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); 245 246 return 0; 247 } 248 249 static const struct platform_profile_ops power_slider_platform_profile_ops = { 250 .probe = power_slider_platform_profile_probe, 251 .profile_get = power_slider_platform_profile_get, 252 .profile_set = power_slider_platform_profile_set, 253 }; 254 255 int proc_thermal_soc_power_slider_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv) 256 { 257 struct device *ppdev; 258 259 set_soc_power_profile(proc_priv, slider_values[SOC_POWER_SLIDER_BALANCE]); 260 261 ppdev = devm_platform_profile_register(&pdev->dev, "SoC Power Slider", proc_priv, 262 &power_slider_platform_profile_ops); 263 264 return PTR_ERR_OR_ZERO(ppdev); 265 } 266 EXPORT_SYMBOL_NS_GPL(proc_thermal_soc_power_slider_add, "INT340X_THERMAL"); 267 268 static u64 soc_slider_save; 269 270 void proc_thermal_soc_power_slider_suspend(struct proc_thermal_device *proc_priv) 271 { 272 soc_slider_save = read_soc_slider(proc_priv); 273 } 274 EXPORT_SYMBOL_NS_GPL(proc_thermal_soc_power_slider_suspend, "INT340X_THERMAL"); 275 276 void proc_thermal_soc_power_slider_resume(struct proc_thermal_device *proc_priv) 277 { 278 write_soc_slider(proc_priv, soc_slider_save); 279 } 280 EXPORT_SYMBOL_NS_GPL(proc_thermal_soc_power_slider_resume, "INT340X_THERMAL"); 281 282 MODULE_IMPORT_NS("INT340X_THERMAL"); 283 MODULE_LICENSE("GPL"); 284 MODULE_DESCRIPTION("Processor Thermal Power Slider Interface"); 285