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
slider_def_balance_set(const char * arg,const struct kernel_param * kp)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
slider_def_balance_get(char * buf,const struct kernel_param * kp)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
slider_def_offset_set(const char * arg,const struct kernel_param * kp)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
slider_def_offset_get(char * buf,const struct kernel_param * kp)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 */
convert_profile_to_power_slider(enum platform_profile_option profile)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 */
convert_power_slider_to_profile(u8 slider)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
read_soc_slider(struct proc_thermal_device * proc_priv)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
write_soc_slider(struct proc_thermal_device * proc_priv,u64 val)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
set_soc_power_profile(struct proc_thermal_device * proc_priv,int slider)177 static void set_soc_power_profile(struct proc_thermal_device *proc_priv, int slider)
178 {
179 u8 offset;
180 u64 val;
181
182 val = read_soc_slider(proc_priv);
183 val &= ~SLIDER_MASK;
184 val |= FIELD_PREP(SLIDER_MASK, slider) | BIT(SLIDER_ENABLE_BIT);
185
186 if (slider == SOC_SLIDER_VALUE_MINIMUM || slider == SOC_SLIDER_VALUE_MAXIMUM)
187 offset = 0;
188 else
189 offset = slider_offset;
190
191 /* Set the slider offset from module params */
192 val &= ~SLIDER_OFFSET_MASK;
193 val |= FIELD_PREP(SLIDER_OFFSET_MASK, offset);
194
195 write_soc_slider(proc_priv, val);
196 }
197
198 /* profile get/set callbacks are called with a profile lock, so no need for local locks */
199
power_slider_platform_profile_set(struct device * dev,enum platform_profile_option profile)200 static int power_slider_platform_profile_set(struct device *dev,
201 enum platform_profile_option profile)
202 {
203 struct proc_thermal_device *proc_priv;
204 int slider;
205
206 proc_priv = dev_get_drvdata(dev);
207 if (!proc_priv)
208 return -EOPNOTSUPP;
209
210 guard(mutex)(&slider_param_lock);
211
212 slider_values[SOC_POWER_SLIDER_BALANCE] = slider_balanced_param;
213
214 slider = convert_profile_to_power_slider(profile);
215 if (slider < 0)
216 return slider;
217
218 set_soc_power_profile(proc_priv, slider);
219
220 return 0;
221 }
222
power_slider_platform_profile_get(struct device * dev,enum platform_profile_option * profile)223 static int power_slider_platform_profile_get(struct device *dev,
224 enum platform_profile_option *profile)
225 {
226 struct proc_thermal_device *proc_priv;
227 int slider, ret;
228 u64 val;
229
230 proc_priv = dev_get_drvdata(dev);
231 if (!proc_priv)
232 return -EOPNOTSUPP;
233
234 val = read_soc_slider(proc_priv);
235 slider = FIELD_GET(SLIDER_MASK, val);
236
237 ret = convert_power_slider_to_profile(slider);
238 if (ret < 0)
239 return ret;
240
241 *profile = ret;
242
243 return 0;
244 }
245
power_slider_platform_profile_probe(void * drvdata,unsigned long * choices)246 static int power_slider_platform_profile_probe(void *drvdata, unsigned long *choices)
247 {
248 set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
249 set_bit(PLATFORM_PROFILE_BALANCED, choices);
250 set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
251
252 return 0;
253 }
254
255 static const struct platform_profile_ops power_slider_platform_profile_ops = {
256 .probe = power_slider_platform_profile_probe,
257 .profile_get = power_slider_platform_profile_get,
258 .profile_set = power_slider_platform_profile_set,
259 };
260
proc_thermal_soc_power_slider_add(struct pci_dev * pdev,struct proc_thermal_device * proc_priv)261 int proc_thermal_soc_power_slider_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
262 {
263 struct device *ppdev;
264
265 set_soc_power_profile(proc_priv, slider_values[SOC_POWER_SLIDER_BALANCE]);
266
267 ppdev = devm_platform_profile_register(&pdev->dev, "SoC Power Slider", proc_priv,
268 &power_slider_platform_profile_ops);
269
270 return PTR_ERR_OR_ZERO(ppdev);
271 }
272 EXPORT_SYMBOL_NS_GPL(proc_thermal_soc_power_slider_add, "INT340X_THERMAL");
273
274 static u64 soc_slider_save;
275
proc_thermal_soc_power_slider_suspend(struct proc_thermal_device * proc_priv)276 void proc_thermal_soc_power_slider_suspend(struct proc_thermal_device *proc_priv)
277 {
278 soc_slider_save = read_soc_slider(proc_priv);
279 }
280 EXPORT_SYMBOL_NS_GPL(proc_thermal_soc_power_slider_suspend, "INT340X_THERMAL");
281
proc_thermal_soc_power_slider_resume(struct proc_thermal_device * proc_priv)282 void proc_thermal_soc_power_slider_resume(struct proc_thermal_device *proc_priv)
283 {
284 write_soc_slider(proc_priv, soc_slider_save);
285 }
286 EXPORT_SYMBOL_NS_GPL(proc_thermal_soc_power_slider_resume, "INT340X_THERMAL");
287
288 MODULE_IMPORT_NS("INT340X_THERMAL");
289 MODULE_LICENSE("GPL");
290 MODULE_DESCRIPTION("Processor Thermal Power Slider Interface");
291