xref: /linux/drivers/cpufreq/loongson3_cpufreq.c (revision 46e6acfe3501fa938af9c5bd730f0020235b08a2)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * CPUFreq driver for the Loongson-3 processors.
4  *
5  * All revisions of Loongson-3 processor support cpu_has_scalefreq feature.
6  *
7  * Author: Huacai Chen <chenhuacai@loongson.cn>
8  * Copyright (C) 2024 Loongson Technology Corporation Limited
9  */
10 #include <linux/cpufreq.h>
11 #include <linux/delay.h>
12 #include <linux/module.h>
13 #include <linux/platform_device.h>
14 #include <linux/units.h>
15 
16 #include <asm/idle.h>
17 #include <asm/loongarch.h>
18 #include <asm/loongson.h>
19 
20 /* Message */
21 union smc_message {
22 	u32 value;
23 	struct {
24 		u32 id		: 4;
25 		u32 info	: 4;
26 		u32 val		: 16;
27 		u32 cmd		: 6;
28 		u32 extra	: 1;
29 		u32 complete	: 1;
30 	};
31 };
32 
33 /* Command return values */
34 #define CMD_OK				0 /* No error */
35 #define CMD_ERROR			1 /* Regular error */
36 #define CMD_NOCMD			2 /* Command does not support */
37 #define CMD_INVAL			3 /* Invalid Parameter */
38 
39 /* Version commands */
40 /*
41  * CMD_GET_VERSION - Get interface version
42  * Input: none
43  * Output: version
44  */
45 #define CMD_GET_VERSION			0x1
46 
47 /* Feature commands */
48 /*
49  * CMD_GET_FEATURE - Get feature state
50  * Input: feature ID
51  * Output: feature flag
52  */
53 #define CMD_GET_FEATURE			0x2
54 
55 /*
56  * CMD_SET_FEATURE - Set feature state
57  * Input: feature ID, feature flag
58  * output: none
59  */
60 #define CMD_SET_FEATURE			0x3
61 
62 /* Feature IDs */
63 #define FEATURE_SENSOR			0
64 #define FEATURE_FAN			1
65 #define FEATURE_DVFS			2
66 
67 /* Sensor feature flags */
68 #define FEATURE_SENSOR_ENABLE		BIT(0)
69 #define FEATURE_SENSOR_SAMPLE		BIT(1)
70 
71 /* Fan feature flags */
72 #define FEATURE_FAN_ENABLE		BIT(0)
73 #define FEATURE_FAN_AUTO		BIT(1)
74 
75 /* DVFS feature flags */
76 #define FEATURE_DVFS_ENABLE		BIT(0)
77 #define FEATURE_DVFS_BOOST		BIT(1)
78 #define FEATURE_DVFS_AUTO		BIT(2)
79 #define FEATURE_DVFS_SINGLE_BOOST	BIT(3)
80 
81 /* Sensor commands */
82 /*
83  * CMD_GET_SENSOR_NUM - Get number of sensors
84  * Input: none
85  * Output: number
86  */
87 #define CMD_GET_SENSOR_NUM		0x4
88 
89 /*
90  * CMD_GET_SENSOR_STATUS - Get sensor status
91  * Input: sensor ID, type
92  * Output: sensor status
93  */
94 #define CMD_GET_SENSOR_STATUS		0x5
95 
96 /* Sensor types */
97 #define SENSOR_INFO_TYPE		0
98 #define SENSOR_INFO_TYPE_TEMP		1
99 
100 /* Fan commands */
101 /*
102  * CMD_GET_FAN_NUM - Get number of fans
103  * Input: none
104  * Output: number
105  */
106 #define CMD_GET_FAN_NUM			0x6
107 
108 /*
109  * CMD_GET_FAN_INFO - Get fan status
110  * Input: fan ID, type
111  * Output: fan info
112  */
113 #define CMD_GET_FAN_INFO		0x7
114 
115 /*
116  * CMD_SET_FAN_INFO - Set fan status
117  * Input: fan ID, type, value
118  * Output: none
119  */
120 #define CMD_SET_FAN_INFO		0x8
121 
122 /* Fan types */
123 #define FAN_INFO_TYPE_LEVEL		0
124 
125 /* DVFS commands */
126 /*
127  * CMD_GET_FREQ_LEVEL_NUM - Get number of freq levels
128  * Input: CPU ID
129  * Output: number
130  */
131 #define CMD_GET_FREQ_LEVEL_NUM		0x9
132 
133 /*
134  * CMD_GET_FREQ_BOOST_LEVEL - Get the first boost level
135  * Input: CPU ID
136  * Output: number
137  */
138 #define CMD_GET_FREQ_BOOST_LEVEL	0x10
139 
140 /*
141  * CMD_GET_FREQ_LEVEL_INFO - Get freq level info
142  * Input: CPU ID, level ID
143  * Output: level info
144  */
145 #define CMD_GET_FREQ_LEVEL_INFO		0x11
146 
147 /*
148  * CMD_GET_FREQ_INFO - Get freq info
149  * Input: CPU ID, type
150  * Output: freq info
151  */
152 #define CMD_GET_FREQ_INFO		0x12
153 
154 /*
155  * CMD_SET_FREQ_INFO - Set freq info
156  * Input: CPU ID, type, value
157  * Output: none
158  */
159 #define CMD_SET_FREQ_INFO		0x13
160 
161 /* Freq types */
162 #define FREQ_INFO_TYPE_FREQ		0
163 #define FREQ_INFO_TYPE_LEVEL		1
164 
165 #define FREQ_MAX_LEVEL			16
166 
167 struct loongson3_freq_data {
168 	unsigned int def_freq_level;
169 	struct cpufreq_frequency_table table[];
170 };
171 
172 static struct mutex cpufreq_mutex[MAX_PACKAGES];
173 static struct cpufreq_driver loongson3_cpufreq_driver;
174 static DEFINE_PER_CPU(struct loongson3_freq_data *, freq_data);
175 
176 static inline int do_service_request(u32 id, u32 info, u32 cmd, u32 val, u32 extra)
177 {
178 	int retries;
179 	unsigned int cpu = smp_processor_id();
180 	unsigned int package = cpu_data[cpu].package;
181 	union smc_message msg, last;
182 
183 	mutex_lock(&cpufreq_mutex[package]);
184 
185 	last.value = iocsr_read32(LOONGARCH_IOCSR_SMCMBX);
186 	if (!last.complete) {
187 		mutex_unlock(&cpufreq_mutex[package]);
188 		return -EPERM;
189 	}
190 
191 	msg.id		= id;
192 	msg.info	= info;
193 	msg.cmd		= cmd;
194 	msg.val		= val;
195 	msg.extra	= extra;
196 	msg.complete	= 0;
197 
198 	iocsr_write32(msg.value, LOONGARCH_IOCSR_SMCMBX);
199 	iocsr_write32(iocsr_read32(LOONGARCH_IOCSR_MISC_FUNC) | IOCSR_MISC_FUNC_SOFT_INT,
200 		      LOONGARCH_IOCSR_MISC_FUNC);
201 
202 	for (retries = 0; retries < 10000; retries++) {
203 		msg.value = iocsr_read32(LOONGARCH_IOCSR_SMCMBX);
204 		if (msg.complete)
205 			break;
206 
207 		usleep_range(8, 12);
208 	}
209 
210 	if (!msg.complete || msg.cmd != CMD_OK) {
211 		mutex_unlock(&cpufreq_mutex[package]);
212 		return -EPERM;
213 	}
214 
215 	mutex_unlock(&cpufreq_mutex[package]);
216 
217 	return msg.val;
218 }
219 
220 static unsigned int loongson3_cpufreq_get(unsigned int cpu)
221 {
222 	int ret;
223 
224 	ret = do_service_request(cpu, FREQ_INFO_TYPE_FREQ, CMD_GET_FREQ_INFO, 0, 0);
225 
226 	return ret * KILO;
227 }
228 
229 static int loongson3_cpufreq_target(struct cpufreq_policy *policy, unsigned int index)
230 {
231 	int ret;
232 
233 	ret = do_service_request(cpu_data[policy->cpu].core,
234 				 FREQ_INFO_TYPE_LEVEL, CMD_SET_FREQ_INFO, index, 0);
235 
236 	return (ret >= 0) ? 0 : ret;
237 }
238 
239 static int configure_freq_table(int cpu)
240 {
241 	int i, ret, boost_level, max_level, freq_level;
242 	struct platform_device *pdev = cpufreq_get_driver_data();
243 	struct loongson3_freq_data *data;
244 
245 	if (per_cpu(freq_data, cpu))
246 		return 0;
247 
248 	ret = do_service_request(cpu, 0, CMD_GET_FREQ_LEVEL_NUM, 0, 0);
249 	if (ret < 0)
250 		return ret;
251 	max_level = ret;
252 
253 	ret = do_service_request(cpu, 0, CMD_GET_FREQ_BOOST_LEVEL, 0, 0);
254 	if (ret < 0)
255 		return ret;
256 	boost_level = ret;
257 
258 	freq_level = min(max_level, FREQ_MAX_LEVEL);
259 	data = devm_kzalloc(&pdev->dev, struct_size(data, table, freq_level + 1), GFP_KERNEL);
260 	if (!data)
261 		return -ENOMEM;
262 
263 	data->def_freq_level = boost_level - 1;
264 
265 	for (i = 0; i < freq_level; i++) {
266 		ret = do_service_request(cpu, FREQ_INFO_TYPE_FREQ, CMD_GET_FREQ_LEVEL_INFO, i, 0);
267 		if (ret < 0) {
268 			devm_kfree(&pdev->dev, data);
269 			return ret;
270 		}
271 
272 		data->table[i].frequency = ret * KILO;
273 		data->table[i].flags = (i >= boost_level) ? CPUFREQ_BOOST_FREQ : 0;
274 	}
275 
276 	data->table[freq_level].flags = 0;
277 	data->table[freq_level].frequency = CPUFREQ_TABLE_END;
278 
279 	per_cpu(freq_data, cpu) = data;
280 
281 	return 0;
282 }
283 
284 static int loongson3_cpufreq_cpu_init(struct cpufreq_policy *policy)
285 {
286 	int i, ret, cpu = policy->cpu;
287 
288 	ret = configure_freq_table(cpu);
289 	if (ret < 0)
290 		return ret;
291 
292 	policy->cpuinfo.transition_latency = 10000;
293 	policy->freq_table = per_cpu(freq_data, cpu)->table;
294 	policy->suspend_freq = policy->freq_table[per_cpu(freq_data, cpu)->def_freq_level].frequency;
295 	cpumask_copy(policy->cpus, topology_sibling_cpumask(cpu));
296 
297 	for_each_cpu(i, policy->cpus) {
298 		if (i != cpu)
299 			per_cpu(freq_data, i) = per_cpu(freq_data, cpu);
300 	}
301 
302 	if (policy_has_boost_freq(policy)) {
303 		ret = cpufreq_enable_boost_support();
304 		if (ret < 0) {
305 			pr_warn("cpufreq: Failed to enable boost: %d\n", ret);
306 			return ret;
307 		}
308 		loongson3_cpufreq_driver.boost_enabled = true;
309 	}
310 
311 	return 0;
312 }
313 
314 static void loongson3_cpufreq_cpu_exit(struct cpufreq_policy *policy)
315 {
316 	int cpu = policy->cpu;
317 
318 	loongson3_cpufreq_target(policy, per_cpu(freq_data, cpu)->def_freq_level);
319 }
320 
321 static int loongson3_cpufreq_cpu_online(struct cpufreq_policy *policy)
322 {
323 	return 0;
324 }
325 
326 static int loongson3_cpufreq_cpu_offline(struct cpufreq_policy *policy)
327 {
328 	return 0;
329 }
330 
331 static struct cpufreq_driver loongson3_cpufreq_driver = {
332 	.name = "loongson3",
333 	.flags = CPUFREQ_CONST_LOOPS,
334 	.init = loongson3_cpufreq_cpu_init,
335 	.exit = loongson3_cpufreq_cpu_exit,
336 	.online = loongson3_cpufreq_cpu_online,
337 	.offline = loongson3_cpufreq_cpu_offline,
338 	.get = loongson3_cpufreq_get,
339 	.target_index = loongson3_cpufreq_target,
340 	.attr = cpufreq_generic_attr,
341 	.verify = cpufreq_generic_frequency_table_verify,
342 	.suspend = cpufreq_generic_suspend,
343 };
344 
345 static int loongson3_cpufreq_probe(struct platform_device *pdev)
346 {
347 	int i, ret;
348 
349 	for (i = 0; i < MAX_PACKAGES; i++)
350 		devm_mutex_init(&pdev->dev, &cpufreq_mutex[i]);
351 
352 	ret = do_service_request(0, 0, CMD_GET_VERSION, 0, 0);
353 	if (ret <= 0)
354 		return -EPERM;
355 
356 	ret = do_service_request(FEATURE_DVFS, 0, CMD_SET_FEATURE,
357 				 FEATURE_DVFS_ENABLE | FEATURE_DVFS_BOOST, 0);
358 	if (ret < 0)
359 		return -EPERM;
360 
361 	loongson3_cpufreq_driver.driver_data = pdev;
362 
363 	ret = cpufreq_register_driver(&loongson3_cpufreq_driver);
364 	if (ret)
365 		return ret;
366 
367 	pr_info("cpufreq: Loongson-3 CPU frequency driver.\n");
368 
369 	return 0;
370 }
371 
372 static void loongson3_cpufreq_remove(struct platform_device *pdev)
373 {
374 	cpufreq_unregister_driver(&loongson3_cpufreq_driver);
375 }
376 
377 static struct platform_device_id cpufreq_id_table[] = {
378 	{ "loongson3_cpufreq", },
379 	{ /* sentinel */ }
380 };
381 MODULE_DEVICE_TABLE(platform, cpufreq_id_table);
382 
383 static struct platform_driver loongson3_platform_driver = {
384 	.driver = {
385 		.name = "loongson3_cpufreq",
386 	},
387 	.id_table = cpufreq_id_table,
388 	.probe = loongson3_cpufreq_probe,
389 	.remove_new = loongson3_cpufreq_remove,
390 };
391 module_platform_driver(loongson3_platform_driver);
392 
393 MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>");
394 MODULE_DESCRIPTION("CPUFreq driver for Loongson-3 processors");
395 MODULE_LICENSE("GPL");
396