]> Git Repo - linux.git/blob - drivers/cpufreq/loongson3_cpufreq.c
Merge tag 'alpha-fixes-v6.14-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git...
[linux.git] / drivers / cpufreq / loongson3_cpufreq.c
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 <[email protected]>
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 = raw_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                 ret = devm_mutex_init(&pdev->dev, &cpufreq_mutex[i]);
351                 if (ret)
352                         return ret;
353         }
354
355         ret = do_service_request(0, 0, CMD_GET_VERSION, 0, 0);
356         if (ret <= 0)
357                 return -EPERM;
358
359         ret = do_service_request(FEATURE_DVFS, 0, CMD_SET_FEATURE,
360                                  FEATURE_DVFS_ENABLE | FEATURE_DVFS_BOOST, 0);
361         if (ret < 0)
362                 return -EPERM;
363
364         loongson3_cpufreq_driver.driver_data = pdev;
365
366         ret = cpufreq_register_driver(&loongson3_cpufreq_driver);
367         if (ret)
368                 return ret;
369
370         pr_info("cpufreq: Loongson-3 CPU frequency driver.\n");
371
372         return 0;
373 }
374
375 static void loongson3_cpufreq_remove(struct platform_device *pdev)
376 {
377         cpufreq_unregister_driver(&loongson3_cpufreq_driver);
378 }
379
380 static struct platform_device_id cpufreq_id_table[] = {
381         { "loongson3_cpufreq", },
382         { /* sentinel */ }
383 };
384 MODULE_DEVICE_TABLE(platform, cpufreq_id_table);
385
386 static struct platform_driver loongson3_platform_driver = {
387         .driver = {
388                 .name = "loongson3_cpufreq",
389         },
390         .id_table = cpufreq_id_table,
391         .probe = loongson3_cpufreq_probe,
392         .remove = loongson3_cpufreq_remove,
393 };
394 module_platform_driver(loongson3_platform_driver);
395
396 MODULE_AUTHOR("Huacai Chen <[email protected]>");
397 MODULE_DESCRIPTION("CPUFreq driver for Loongson-3 processors");
398 MODULE_LICENSE("GPL");
This page took 0.053026 seconds and 4 git commands to generate.