xref: /linux/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c (revision ec2e0fb07d789976c601bec19ecced7a501c3705)
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