Commit | Line | Data |
---|---|---|
99d6bdf3 SH |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * System Control and Power Interface (SCMI) based CPUFreq Interface driver | |
4 | * | |
eb1d35c6 | 5 | * Copyright (C) 2018-2021 ARM Ltd. |
99d6bdf3 SH |
6 | * Sudeep Holla <sudeep.holla@arm.com> |
7 | */ | |
8 | ||
9 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
10 | ||
8410e7f3 | 11 | #include <linux/clk-provider.h> |
99d6bdf3 SH |
12 | #include <linux/cpu.h> |
13 | #include <linux/cpufreq.h> | |
14 | #include <linux/cpumask.h> | |
3c429851 | 15 | #include <linux/energy_model.h> |
99d6bdf3 SH |
16 | #include <linux/export.h> |
17 | #include <linux/module.h> | |
18 | #include <linux/pm_opp.h> | |
19 | #include <linux/slab.h> | |
20 | #include <linux/scmi_protocol.h> | |
21 | #include <linux/types.h> | |
ae6ccaa6 | 22 | #include <linux/units.h> |
99d6bdf3 SH |
23 | |
24 | struct scmi_data { | |
25 | int domain_id; | |
37f18831 | 26 | int nr_opp; |
99d6bdf3 | 27 | struct device *cpu_dev; |
37f18831 | 28 | cpumask_var_t opp_shared_cpus; |
99d6bdf3 SH |
29 | }; |
30 | ||
eb1d35c6 CM |
31 | static struct scmi_protocol_handle *ph; |
32 | static const struct scmi_perf_proto_ops *perf_ops; | |
99d6bdf3 SH |
33 | |
34 | static unsigned int scmi_cpufreq_get_rate(unsigned int cpu) | |
35 | { | |
36 | struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu); | |
99d6bdf3 SH |
37 | struct scmi_data *priv = policy->driver_data; |
38 | unsigned long rate; | |
39 | int ret; | |
40 | ||
eb1d35c6 | 41 | ret = perf_ops->freq_get(ph, priv->domain_id, &rate, false); |
99d6bdf3 SH |
42 | if (ret) |
43 | return 0; | |
44 | return rate / 1000; | |
45 | } | |
46 | ||
47 | /* | |
48 | * perf_ops->freq_set is not a synchronous, the actual OPP change will | |
49 | * happen asynchronously and can get notified if the events are | |
50 | * subscribed for by the SCMI firmware | |
51 | */ | |
52 | static int | |
53 | scmi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index) | |
54 | { | |
99d6bdf3 | 55 | struct scmi_data *priv = policy->driver_data; |
0e141d1c | 56 | u64 freq = policy->freq_table[index].frequency; |
99d6bdf3 | 57 | |
eb1d35c6 | 58 | return perf_ops->freq_set(ph, priv->domain_id, freq * 1000, false); |
99d6bdf3 SH |
59 | } |
60 | ||
02f208c5 SH |
61 | static unsigned int scmi_cpufreq_fast_switch(struct cpufreq_policy *policy, |
62 | unsigned int target_freq) | |
63 | { | |
64 | struct scmi_data *priv = policy->driver_data; | |
02f208c5 | 65 | |
eb1d35c6 | 66 | if (!perf_ops->freq_set(ph, priv->domain_id, |
1a0419b0 | 67 | target_freq * 1000, true)) |
02f208c5 | 68 | return target_freq; |
02f208c5 SH |
69 | |
70 | return 0; | |
71 | } | |
72 | ||
99d6bdf3 SH |
73 | static int |
74 | scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask) | |
75 | { | |
76 | int cpu, domain, tdomain; | |
77 | struct device *tcpu_dev; | |
78 | ||
eb1d35c6 | 79 | domain = perf_ops->device_domain_id(cpu_dev); |
99d6bdf3 SH |
80 | if (domain < 0) |
81 | return domain; | |
82 | ||
83 | for_each_possible_cpu(cpu) { | |
84 | if (cpu == cpu_dev->id) | |
85 | continue; | |
86 | ||
87 | tcpu_dev = get_cpu_device(cpu); | |
88 | if (!tcpu_dev) | |
89 | continue; | |
90 | ||
eb1d35c6 | 91 | tdomain = perf_ops->device_domain_id(tcpu_dev); |
99d6bdf3 SH |
92 | if (tdomain == domain) |
93 | cpumask_set_cpu(cpu, cpumask); | |
94 | } | |
95 | ||
96 | return 0; | |
97 | } | |
98 | ||
3c429851 | 99 | static int __maybe_unused |
75a3a99a LL |
100 | scmi_get_cpu_power(struct device *cpu_dev, unsigned long *power, |
101 | unsigned long *KHz) | |
3c429851 | 102 | { |
f3ac888f | 103 | enum scmi_power_scale power_scale = perf_ops->power_scale_get(ph); |
3c429851 QP |
104 | unsigned long Hz; |
105 | int ret, domain; | |
106 | ||
eb1d35c6 | 107 | domain = perf_ops->device_domain_id(cpu_dev); |
3c429851 QP |
108 | if (domain < 0) |
109 | return domain; | |
110 | ||
111 | /* Get the power cost of the performance domain. */ | |
112 | Hz = *KHz * 1000; | |
eb1d35c6 | 113 | ret = perf_ops->est_power_get(ph, domain, &Hz, power); |
3c429851 QP |
114 | if (ret) |
115 | return ret; | |
116 | ||
f3ac888f LL |
117 | /* Convert the power to uW if it is mW (ignore bogoW) */ |
118 | if (power_scale == SCMI_POWER_MILLIWATTS) | |
ae6ccaa6 LL |
119 | *power *= MICROWATT_PER_MILLIWATT; |
120 | ||
3c429851 QP |
121 | /* The EM framework specifies the frequency in KHz. */ |
122 | *KHz = Hz / 1000; | |
123 | ||
124 | return 0; | |
125 | } | |
126 | ||
99d6bdf3 SH |
127 | static int scmi_cpufreq_init(struct cpufreq_policy *policy) |
128 | { | |
3c429851 | 129 | int ret, nr_opp; |
99d6bdf3 SH |
130 | unsigned int latency; |
131 | struct device *cpu_dev; | |
132 | struct scmi_data *priv; | |
133 | struct cpufreq_frequency_table *freq_table; | |
134 | ||
135 | cpu_dev = get_cpu_device(policy->cpu); | |
136 | if (!cpu_dev) { | |
137 | pr_err("failed to get cpu%d device\n", policy->cpu); | |
138 | return -ENODEV; | |
139 | } | |
140 | ||
37f18831 VK |
141 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); |
142 | if (!priv) | |
f7d63588 | 143 | return -ENOMEM; |
99d6bdf3 | 144 | |
37f18831 VK |
145 | if (!zalloc_cpumask_var(&priv->opp_shared_cpus, GFP_KERNEL)) { |
146 | ret = -ENOMEM; | |
147 | goto out_free_priv; | |
148 | } | |
149 | ||
80a064db | 150 | /* Obtain CPUs that share SCMI performance controls */ |
99d6bdf3 SH |
151 | ret = scmi_get_sharing_cpus(cpu_dev, policy->cpus); |
152 | if (ret) { | |
153 | dev_warn(cpu_dev, "failed to get sharing cpumask\n"); | |
80a064db | 154 | goto out_free_cpumask; |
99d6bdf3 SH |
155 | } |
156 | ||
80a064db NM |
157 | /* |
158 | * Obtain CPUs that share performance levels. | |
159 | * The OPP 'sharing cpus' info may come from DT through an empty opp | |
160 | * table and opp-shared. | |
161 | */ | |
37f18831 | 162 | ret = dev_pm_opp_of_get_sharing_cpus(cpu_dev, priv->opp_shared_cpus); |
b48cd0d1 | 163 | if (ret || cpumask_empty(priv->opp_shared_cpus)) { |
80a064db NM |
164 | /* |
165 | * Either opp-table is not set or no opp-shared was found. | |
166 | * Use the CPU mask from SCMI to designate CPUs sharing an OPP | |
167 | * table. | |
168 | */ | |
37f18831 | 169 | cpumask_copy(priv->opp_shared_cpus, policy->cpus); |
99d6bdf3 SH |
170 | } |
171 | ||
80a064db NM |
172 | /* |
173 | * A previous CPU may have marked OPPs as shared for a few CPUs, based on | |
174 | * what OPP core provided. If the current CPU is part of those few, then | |
175 | * there is no need to add OPPs again. | |
176 | */ | |
3c429851 QP |
177 | nr_opp = dev_pm_opp_get_opp_count(cpu_dev); |
178 | if (nr_opp <= 0) { | |
eb1d35c6 | 179 | ret = perf_ops->device_opps_add(ph, cpu_dev); |
80a064db NM |
180 | if (ret) { |
181 | dev_warn(cpu_dev, "failed to add opps to the device\n"); | |
182 | goto out_free_cpumask; | |
183 | } | |
184 | ||
185 | nr_opp = dev_pm_opp_get_opp_count(cpu_dev); | |
186 | if (nr_opp <= 0) { | |
187 | dev_err(cpu_dev, "%s: No OPPs for this device: %d\n", | |
b791c7f9 | 188 | __func__, nr_opp); |
80a064db NM |
189 | |
190 | ret = -ENODEV; | |
191 | goto out_free_opp; | |
192 | } | |
193 | ||
37f18831 | 194 | ret = dev_pm_opp_set_sharing_cpus(cpu_dev, priv->opp_shared_cpus); |
80a064db NM |
195 | if (ret) { |
196 | dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n", | |
197 | __func__, ret); | |
198 | ||
199 | goto out_free_opp; | |
200 | } | |
201 | ||
37f18831 | 202 | priv->nr_opp = nr_opp; |
99d6bdf3 SH |
203 | } |
204 | ||
205 | ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table); | |
206 | if (ret) { | |
207 | dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret); | |
37f18831 | 208 | goto out_free_opp; |
99d6bdf3 SH |
209 | } |
210 | ||
211 | priv->cpu_dev = cpu_dev; | |
eb1d35c6 | 212 | priv->domain_id = perf_ops->device_domain_id(cpu_dev); |
99d6bdf3 SH |
213 | |
214 | policy->driver_data = priv; | |
d983af98 | 215 | policy->freq_table = freq_table; |
99d6bdf3 SH |
216 | |
217 | /* SCMI allows DVFS request for any domain from any CPU */ | |
218 | policy->dvfs_possible_from_any_cpu = true; | |
219 | ||
eb1d35c6 | 220 | latency = perf_ops->transition_latency_get(ph, cpu_dev); |
99d6bdf3 SH |
221 | if (!latency) |
222 | latency = CPUFREQ_ETERNAL; | |
223 | ||
224 | policy->cpuinfo.transition_latency = latency; | |
225 | ||
fb357127 | 226 | policy->fast_switch_possible = |
eb1d35c6 | 227 | perf_ops->fast_switch_possible(ph, cpu_dev); |
3c429851 | 228 | |
99d6bdf3 SH |
229 | return 0; |
230 | ||
99d6bdf3 | 231 | out_free_opp: |
1690d8bb | 232 | dev_pm_opp_remove_all_dynamic(cpu_dev); |
99d6bdf3 | 233 | |
80a064db | 234 | out_free_cpumask: |
37f18831 VK |
235 | free_cpumask_var(priv->opp_shared_cpus); |
236 | ||
237 | out_free_priv: | |
238 | kfree(priv); | |
80a064db | 239 | |
99d6bdf3 SH |
240 | return ret; |
241 | } | |
242 | ||
243 | static int scmi_cpufreq_exit(struct cpufreq_policy *policy) | |
244 | { | |
245 | struct scmi_data *priv = policy->driver_data; | |
246 | ||
99d6bdf3 | 247 | dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); |
1690d8bb | 248 | dev_pm_opp_remove_all_dynamic(priv->cpu_dev); |
37f18831 | 249 | free_cpumask_var(priv->opp_shared_cpus); |
8cbd468b | 250 | kfree(priv); |
99d6bdf3 SH |
251 | |
252 | return 0; | |
253 | } | |
254 | ||
37f18831 VK |
255 | static void scmi_cpufreq_register_em(struct cpufreq_policy *policy) |
256 | { | |
257 | struct em_data_callback em_cb = EM_DATA_CB(scmi_get_cpu_power); | |
f3ac888f | 258 | enum scmi_power_scale power_scale = perf_ops->power_scale_get(ph); |
37f18831 | 259 | struct scmi_data *priv = policy->driver_data; |
f3ac888f | 260 | bool em_power_scale = false; |
37f18831 VK |
261 | |
262 | /* | |
263 | * This callback will be called for each policy, but we don't need to | |
264 | * register with EM every time. Despite not being part of the same | |
265 | * policy, some CPUs may still share their perf-domains, and a CPU from | |
266 | * another policy may already have registered with EM on behalf of CPUs | |
267 | * of this policy. | |
268 | */ | |
269 | if (!priv->nr_opp) | |
270 | return; | |
271 | ||
f3ac888f LL |
272 | if (power_scale == SCMI_POWER_MILLIWATTS |
273 | || power_scale == SCMI_POWER_MICROWATTS) | |
274 | em_power_scale = true; | |
275 | ||
37f18831 VK |
276 | em_dev_register_perf_domain(get_cpu_device(policy->cpu), priv->nr_opp, |
277 | &em_cb, priv->opp_shared_cpus, | |
f3ac888f | 278 | em_power_scale); |
37f18831 VK |
279 | } |
280 | ||
99d6bdf3 SH |
281 | static struct cpufreq_driver scmi_cpufreq_driver = { |
282 | .name = "scmi", | |
5ae4a4b4 | 283 | .flags = CPUFREQ_HAVE_GOVERNOR_PER_POLICY | |
5da7af9a AK |
284 | CPUFREQ_NEED_INITIAL_FREQ_CHECK | |
285 | CPUFREQ_IS_COOLING_DEV, | |
99d6bdf3 SH |
286 | .verify = cpufreq_generic_frequency_table_verify, |
287 | .attr = cpufreq_generic_attr, | |
288 | .target_index = scmi_cpufreq_set_target, | |
02f208c5 | 289 | .fast_switch = scmi_cpufreq_fast_switch, |
99d6bdf3 SH |
290 | .get = scmi_cpufreq_get_rate, |
291 | .init = scmi_cpufreq_init, | |
292 | .exit = scmi_cpufreq_exit, | |
37f18831 | 293 | .register_em = scmi_cpufreq_register_em, |
99d6bdf3 SH |
294 | }; |
295 | ||
296 | static int scmi_cpufreq_probe(struct scmi_device *sdev) | |
297 | { | |
298 | int ret; | |
8410e7f3 | 299 | struct device *dev = &sdev->dev; |
eb1d35c6 | 300 | const struct scmi_handle *handle; |
99d6bdf3 SH |
301 | |
302 | handle = sdev->handle; | |
303 | ||
eb1d35c6 | 304 | if (!handle) |
99d6bdf3 SH |
305 | return -ENODEV; |
306 | ||
eb1d35c6 CM |
307 | perf_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_PERF, &ph); |
308 | if (IS_ERR(perf_ops)) | |
309 | return PTR_ERR(perf_ops); | |
310 | ||
f943849f | 311 | #ifdef CONFIG_COMMON_CLK |
8410e7f3 | 312 | /* dummy clock provider as needed by OPP if clocks property is used */ |
b8f3a396 | 313 | if (of_property_present(dev->of_node, "#clock-cells")) |
8410e7f3 | 314 | devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, NULL); |
f943849f | 315 | #endif |
8410e7f3 | 316 | |
99d6bdf3 SH |
317 | ret = cpufreq_register_driver(&scmi_cpufreq_driver); |
318 | if (ret) { | |
f943849f | 319 | dev_err(dev, "%s: registering cpufreq failed, err: %d\n", |
99d6bdf3 SH |
320 | __func__, ret); |
321 | } | |
322 | ||
323 | return ret; | |
324 | } | |
325 | ||
326 | static void scmi_cpufreq_remove(struct scmi_device *sdev) | |
327 | { | |
328 | cpufreq_unregister_driver(&scmi_cpufreq_driver); | |
329 | } | |
330 | ||
331 | static const struct scmi_device_id scmi_id_table[] = { | |
12b76626 | 332 | { SCMI_PROTOCOL_PERF, "cpufreq" }, |
99d6bdf3 SH |
333 | { }, |
334 | }; | |
335 | MODULE_DEVICE_TABLE(scmi, scmi_id_table); | |
336 | ||
337 | static struct scmi_driver scmi_cpufreq_drv = { | |
338 | .name = "scmi-cpufreq", | |
339 | .probe = scmi_cpufreq_probe, | |
340 | .remove = scmi_cpufreq_remove, | |
341 | .id_table = scmi_id_table, | |
342 | }; | |
343 | module_scmi_driver(scmi_cpufreq_drv); | |
344 | ||
345 | MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); | |
346 | MODULE_DESCRIPTION("ARM SCMI CPUFreq interface driver"); | |
347 | MODULE_LICENSE("GPL v2"); |