1 /*
2 * CPUFreq driver for the loongson-3 processors
3 *
4 * All revisions of Loongson-3 processor support this feature.
5 *
6 * Author: Huacai Chen <chenhuacai@loongson.cn>
7 * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
8 */
9 #include <linux/delay.h>
10 #include <linux/module.h>
11 #include <linux/cpufreq.h>
12 #include <linux/platform_device.h>
13
14 #include <asm/idle.h>
15 #include <asm/loongarchregs.h>
16 #include <loongson.h>
17
18 /* Message */
19 union smc_message
20 {
21 u32 value;
22 struct
23 {
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 (1 << 0)
69 #define FEATURE_SENSOR_SAMPLE (1 << 1)
70
71 /* Fan feature flags */
72 #define FEATURE_FAN_ENABLE (1 << 0)
73 #define FEATURE_FAN_AUTO (1 << 1)
74
75 /* DVFS feature flags */
76 #define FEATURE_DVFS_ENABLE (1 << 0)
77 #define FEATURE_DVFS_BOOST (1 << 1)
78
79 /* Sensor commands */
80 /*
81 * CMD_GET_SENSOR_NUM - Get number of sensors
82 * Input: none
83 * Output: number
84 */
85 #define CMD_GET_SENSOR_NUM 0x4
86
87 /*
88 * CMD_GET_SENSOR_STATUS - Get sensor status
89 * Input: sensor ID, type
90 * Output: sensor status
91 */
92 #define CMD_GET_SENSOR_STATUS 0x5
93
94 /* Sensor types */
95 #define SENSOR_INFO_TYPE 0
96 #define SENSOR_INFO_TYPE_TEMP 1
97
98 /* Fan commands */
99 /*
100 * CMD_GET_FAN_NUM - Get number of fans
101 * Input: none
102 * Output: number
103 */
104 #define CMD_GET_FAN_NUM 0x6
105
106 /*
107 * CMD_GET_FAN_INFO - Get fan status
108 * Input: fan ID, type
109 * Output: fan info
110 */
111 #define CMD_GET_FAN_INFO 0x7
112
113 /*
114 * CMD_SET_FAN_INFO - Set fan status
115 * Input: fan ID, type, value
116 * Output: none
117 */
118 #define CMD_SET_FAN_INFO 0x8
119
120 /* Fan types */
121 #define FAN_INFO_TYPE_LEVEL 0
122
123 /* DVFS commands */
124 /*
125 * CMD_GET_FREQ_LEVEL_NUM - Get number of freq levels
126 * Input: none
127 * Output: number
128 */
129 #define CMD_GET_FREQ_LEVEL_NUM 0x9
130
131 /*
132 * CMD_GET_FREQ_BOOST_LEVEL - Get number of boost levels
133 * Input: none
134 * Output: number
135 */
136 #define CMD_GET_FREQ_BOOST_LEVEL 0x10
137
138 /*
139 * CMD_GET_FREQ_LEVEL_INFO - Get freq level info
140 * Input: level ID
141 * Output: level info
142 */
143 #define CMD_GET_FREQ_LEVEL_INFO 0x11
144
145 /*
146 * CMD_GET_FREQ_INFO - Get freq info
147 * Input: CPU ID, type
148 * Output: freq info
149 */
150 #define CMD_GET_FREQ_INFO 0x12
151
152 /*
153 * CMD_SET_FREQ_INFO - Set freq info
154 * Input: CPU ID, type, value
155 * Output: none
156 */
157 #define CMD_SET_FREQ_INFO 0x13
158
159 /* Freq types */
160 #define FREQ_INFO_TYPE_FREQ 0
161 #define FREQ_INFO_TYPE_LEVEL 1
162
do_service_request(union smc_message *msg)163 static inline int do_service_request(union smc_message *msg)
164 {
165 int retries;
166 union smc_message last;
167
168 last.value = iocsr_read32(LOONGARCH_IOCSR_SMCMBX);
169 if (!last.complete)
170 return -1;
171
172 iocsr_write32(msg->value, LOONGARCH_IOCSR_SMCMBX);
173 iocsr_write32(iocsr_read32(LOONGARCH_IOCSR_MISC_FUNC) | IOCSR_MISC_FUNC_SOFT_INT,
174 LOONGARCH_IOCSR_MISC_FUNC);
175
176 for (retries = 0; retries < 10000; retries++) {
177 msg->value = iocsr_read32(LOONGARCH_IOCSR_SMCMBX);
178 if (msg->complete)
179 break;
180
181 usleep_range(4, 8);
182 }
183
184 if (!msg->complete || msg->cmd != CMD_OK)
185 return -1;
186
187 return 0;
188 }
189
190 static int boost_supported = 0;
191 static struct mutex cpufreq_mutex[MAX_PACKAGES];
192
193 enum freq {
194 FREQ_LEV0, /* Reserved */
195 FREQ_LEV1, FREQ_LEV2, FREQ_LEV3, FREQ_LEV4,
196 FREQ_LEV5, FREQ_LEV6, FREQ_LEV7, FREQ_LEV8,
197 FREQ_LEV9, FREQ_LEV10, FREQ_LEV11, FREQ_LEV12,
198 FREQ_LEV13, FREQ_LEV14, FREQ_LEV15, FREQ_LEV16,
199 FREQ_RESV
200 };
201
202 /* For Loongson-3A5000, support boost */
203 static struct cpufreq_frequency_table loongson3_cpufreq_table[] = {
204 {0, FREQ_LEV0, CPUFREQ_ENTRY_INVALID},
205 {0, FREQ_LEV1, CPUFREQ_ENTRY_INVALID},
206 {0, FREQ_LEV2, CPUFREQ_ENTRY_INVALID},
207 {0, FREQ_LEV3, CPUFREQ_ENTRY_INVALID},
208 {0, FREQ_LEV4, CPUFREQ_ENTRY_INVALID},
209 {0, FREQ_LEV5, CPUFREQ_ENTRY_INVALID},
210 {0, FREQ_LEV6, CPUFREQ_ENTRY_INVALID},
211 {0, FREQ_LEV7, CPUFREQ_ENTRY_INVALID},
212 {0, FREQ_LEV8, CPUFREQ_ENTRY_INVALID},
213 {0, FREQ_LEV9, CPUFREQ_ENTRY_INVALID},
214 {0, FREQ_LEV10, CPUFREQ_ENTRY_INVALID},
215 {0, FREQ_LEV11, CPUFREQ_ENTRY_INVALID},
216 {0, FREQ_LEV12, CPUFREQ_ENTRY_INVALID},
217 {0, FREQ_LEV13, CPUFREQ_ENTRY_INVALID},
218 {0, FREQ_LEV14, CPUFREQ_ENTRY_INVALID},
219 {0, FREQ_LEV15, CPUFREQ_ENTRY_INVALID},
220 {0, FREQ_LEV16, CPUFREQ_ENTRY_INVALID},
221 {0, FREQ_RESV, CPUFREQ_TABLE_END},
222 };
223
loongson3_cpufreq_get(unsigned int cpu)224 static unsigned int loongson3_cpufreq_get(unsigned int cpu)
225 {
226 union smc_message msg;
227
228 msg.id = cpu;
229 msg.info = FREQ_INFO_TYPE_FREQ;
230 msg.cmd = CMD_GET_FREQ_INFO;
231 msg.extra = 0;
232 msg.complete = 0;
233 do_service_request(&msg);
234
235 return msg.val * 1000;
236 }
237
loongson3_cpufreq_set(struct cpufreq_policy *policy, int freq_level)238 static int loongson3_cpufreq_set(struct cpufreq_policy *policy, int freq_level)
239 {
240 uint32_t core_id = cpu_data[policy->cpu].core;
241 union smc_message msg;
242
243 msg.id = core_id;
244 msg.info = FREQ_INFO_TYPE_LEVEL;
245 msg.val = freq_level;
246 msg.cmd = CMD_SET_FREQ_INFO;
247 msg.extra = 0;
248 msg.complete = 0;
249 do_service_request(&msg);
250
251 return 0;
252 }
253
254 /*
255 * Here we notify other drivers of the proposed change and the final change.
256 */
loongson3_cpufreq_target(struct cpufreq_policy *policy, unsigned int index)257 static int loongson3_cpufreq_target(struct cpufreq_policy *policy,
258 unsigned int index)
259 {
260 unsigned int cpu = policy->cpu;
261 unsigned int package = cpu_data[cpu].package;
262
263 if (!cpu_online(cpu))
264 return -ENODEV;
265
266 /* setting the cpu frequency */
267 mutex_lock(&cpufreq_mutex[package]);
268 loongson3_cpufreq_set(policy, index);
269 mutex_unlock(&cpufreq_mutex[package]);
270
271 return 0;
272 }
273
loongson3_cpufreq_cpu_init(struct cpufreq_policy *policy)274 static int loongson3_cpufreq_cpu_init(struct cpufreq_policy *policy)
275 {
276 if (!cpu_online(policy->cpu))
277 return -ENODEV;
278
279 policy->cur = loongson3_cpufreq_get(policy->cpu);
280
281 policy->cpuinfo.transition_latency = 5000;
282 policy->freq_table = loongson3_cpufreq_table;
283 cpumask_copy(policy->cpus, topology_sibling_cpumask(policy->cpu));
284
285 return 0;
286 }
287
loongson3_cpufreq_exit(struct cpufreq_policy *policy)288 static int loongson3_cpufreq_exit(struct cpufreq_policy *policy)
289 {
290 return 0;
291 }
292
293 static struct cpufreq_driver loongson3_cpufreq_driver = {
294 .name = "loongson3",
295 .flags = CPUFREQ_CONST_LOOPS,
296 .init = loongson3_cpufreq_cpu_init,
297 .verify = cpufreq_generic_frequency_table_verify,
298 .target_index = loongson3_cpufreq_target,
299 .get = loongson3_cpufreq_get,
300 .exit = loongson3_cpufreq_exit,
301 .attr = cpufreq_generic_attr,
302 };
303
304 static struct platform_device_id cpufreq_id_table[] = {
305 { "loongson3_cpufreq", },
306 { /* sentinel */ }
307 };
308
309 MODULE_DEVICE_TABLE(platform, cpufreq_id_table);
310
311 static struct platform_driver cpufreq_driver = {
312 .driver = {
313 .name = "loongson3_cpufreq",
314 .owner = THIS_MODULE,
315 },
316 .id_table = cpufreq_id_table,
317 };
318
configure_cpufreq_info(void)319 static int configure_cpufreq_info(void)
320 {
321 int i, r, boost_level, max_level;
322 union smc_message msg;
323
324 if (!cpu_has_csr)
325 return -EPERM;
326
327 msg.cmd = CMD_GET_VERSION;
328 msg.extra = 0;
329 msg.complete = 0;
330 r = do_service_request(&msg);
331 if (r < 0 || msg.val < 0x1)
332 return -EPERM;
333
334 msg.id = FEATURE_DVFS;
335 msg.val = FEATURE_DVFS_ENABLE | FEATURE_DVFS_BOOST;
336 msg.cmd = CMD_SET_FEATURE;
337 msg.extra = 0;
338 msg.complete = 0;
339 r = do_service_request(&msg);
340 if (r < 0)
341 return -EPERM;
342
343 msg.cmd = CMD_GET_FREQ_LEVEL_NUM;
344 msg.extra = 0;
345 msg.complete = 0;
346 r = do_service_request(&msg);
347 if (r < 0)
348 return -EPERM;
349 max_level = msg.val;
350
351 msg.cmd = CMD_GET_FREQ_BOOST_LEVEL;
352 msg.extra = 0;
353 msg.complete = 0;
354 r = do_service_request(&msg);
355 if (r < 0)
356 return -EPERM;
357 boost_level = msg.val;
358 if (boost_level < max_level)
359 boost_supported = 1;
360
361 for (i = 0; i < max_level; i++) {
362 msg.cmd = CMD_GET_FREQ_LEVEL_INFO;
363 msg.id = i;
364 msg.info = FREQ_INFO_TYPE_FREQ;
365 msg.extra = 0;
366 msg.complete = 0;
367 r = do_service_request(&msg);
368 if (r < 0)
369 return -EPERM;
370
371 loongson3_cpufreq_table[i].frequency = msg.val * 1000;
372 if (i >= boost_level)
373 loongson3_cpufreq_table[i].flags = CPUFREQ_BOOST_FREQ;
374 }
375
376 return 0;
377 }
378
cpufreq_init(void)379 static int __init cpufreq_init(void)
380 {
381 int i, ret;
382
383 ret = platform_driver_register(&cpufreq_driver);
384 if (ret)
385 goto err;
386
387 ret = configure_cpufreq_info();
388 if (ret)
389 goto err;
390
391 for (i = 0; i < MAX_PACKAGES; i++)
392 mutex_init(&cpufreq_mutex[i]);
393
394 ret = cpufreq_register_driver(&loongson3_cpufreq_driver);
395
396 if (boost_supported)
397 cpufreq_enable_boost_support();
398
399 pr_info("cpufreq: Loongson-3 CPU frequency driver.\n");
400
401 return ret;
402
403 err:
404 platform_driver_unregister(&cpufreq_driver);
405 return ret;
406 }
407
cpufreq_exit(void)408 static void __exit cpufreq_exit(void)
409 {
410 cpufreq_unregister_driver(&loongson3_cpufreq_driver);
411 platform_driver_unregister(&cpufreq_driver);
412 }
413
414 module_init(cpufreq_init);
415 module_exit(cpufreq_exit);
416
417 MODULE_AUTHOR("Huacai Chen <chenhuacaic@loongson.cn>");
418 MODULE_DESCRIPTION("CPUFreq driver for Loongson-3 processors");
419 MODULE_LICENSE("GPL");
420