1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * System Control and Power Interface (SCPI) based CPUFreq Interface driver |
4 | * |
5 | * Copyright (C) 2015 ARM Ltd. |
6 | * Sudeep Holla <sudeep.holla@arm.com> |
7 | */ |
8 | |
9 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
10 | |
11 | #include <linux/clk.h> |
12 | #include <linux/cpu.h> |
13 | #include <linux/cpufreq.h> |
14 | #include <linux/cpumask.h> |
15 | #include <linux/export.h> |
16 | #include <linux/module.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/pm_opp.h> |
19 | #include <linux/scpi_protocol.h> |
20 | #include <linux/slab.h> |
21 | #include <linux/types.h> |
22 | |
23 | struct scpi_data { |
24 | struct clk *clk; |
25 | struct device *cpu_dev; |
26 | }; |
27 | |
28 | static struct scpi_ops *scpi_ops; |
29 | |
30 | static unsigned int scpi_cpufreq_get_rate(unsigned int cpu) |
31 | { |
32 | struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu); |
33 | struct scpi_data *priv = policy->driver_data; |
34 | unsigned long rate = clk_get_rate(clk: priv->clk); |
35 | |
36 | return rate / 1000; |
37 | } |
38 | |
39 | static int |
40 | scpi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index) |
41 | { |
42 | u64 rate = policy->freq_table[index].frequency * 1000; |
43 | struct scpi_data *priv = policy->driver_data; |
44 | int ret; |
45 | |
46 | ret = clk_set_rate(clk: priv->clk, rate); |
47 | |
48 | if (ret) |
49 | return ret; |
50 | |
51 | if (clk_get_rate(clk: priv->clk) != rate) |
52 | return -EIO; |
53 | |
54 | return 0; |
55 | } |
56 | |
57 | static int |
58 | scpi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask) |
59 | { |
60 | int cpu, domain, tdomain; |
61 | struct device *tcpu_dev; |
62 | |
63 | domain = scpi_ops->device_domain_id(cpu_dev); |
64 | if (domain < 0) |
65 | return domain; |
66 | |
67 | for_each_possible_cpu(cpu) { |
68 | if (cpu == cpu_dev->id) |
69 | continue; |
70 | |
71 | tcpu_dev = get_cpu_device(cpu); |
72 | if (!tcpu_dev) |
73 | continue; |
74 | |
75 | tdomain = scpi_ops->device_domain_id(tcpu_dev); |
76 | if (tdomain == domain) |
77 | cpumask_set_cpu(cpu, dstp: cpumask); |
78 | } |
79 | |
80 | return 0; |
81 | } |
82 | |
83 | static int scpi_cpufreq_init(struct cpufreq_policy *policy) |
84 | { |
85 | int ret; |
86 | unsigned int latency; |
87 | struct device *cpu_dev; |
88 | struct scpi_data *priv; |
89 | struct cpufreq_frequency_table *freq_table; |
90 | |
91 | cpu_dev = get_cpu_device(cpu: policy->cpu); |
92 | if (!cpu_dev) { |
93 | pr_err("failed to get cpu%d device\n" , policy->cpu); |
94 | return -ENODEV; |
95 | } |
96 | |
97 | ret = scpi_ops->add_opps_to_device(cpu_dev); |
98 | if (ret) { |
99 | dev_warn(cpu_dev, "failed to add opps to the device\n" ); |
100 | return ret; |
101 | } |
102 | |
103 | ret = scpi_get_sharing_cpus(cpu_dev, cpumask: policy->cpus); |
104 | if (ret) { |
105 | dev_warn(cpu_dev, "failed to get sharing cpumask\n" ); |
106 | return ret; |
107 | } |
108 | |
109 | ret = dev_pm_opp_set_sharing_cpus(cpu_dev, cpumask: policy->cpus); |
110 | if (ret) { |
111 | dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n" , |
112 | __func__, ret); |
113 | return ret; |
114 | } |
115 | |
116 | ret = dev_pm_opp_get_opp_count(dev: cpu_dev); |
117 | if (ret <= 0) { |
118 | dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n" ); |
119 | ret = -EPROBE_DEFER; |
120 | goto out_free_opp; |
121 | } |
122 | |
123 | priv = kzalloc(size: sizeof(*priv), GFP_KERNEL); |
124 | if (!priv) { |
125 | ret = -ENOMEM; |
126 | goto out_free_opp; |
127 | } |
128 | |
129 | ret = dev_pm_opp_init_cpufreq_table(dev: cpu_dev, table: &freq_table); |
130 | if (ret) { |
131 | dev_err(cpu_dev, "failed to init cpufreq table: %d\n" , ret); |
132 | goto out_free_priv; |
133 | } |
134 | |
135 | priv->cpu_dev = cpu_dev; |
136 | priv->clk = clk_get(dev: cpu_dev, NULL); |
137 | if (IS_ERR(ptr: priv->clk)) { |
138 | dev_err(cpu_dev, "%s: Failed to get clk for cpu: %d\n" , |
139 | __func__, cpu_dev->id); |
140 | ret = PTR_ERR(ptr: priv->clk); |
141 | goto out_free_cpufreq_table; |
142 | } |
143 | |
144 | policy->driver_data = priv; |
145 | policy->freq_table = freq_table; |
146 | |
147 | /* scpi allows DVFS request for any domain from any CPU */ |
148 | policy->dvfs_possible_from_any_cpu = true; |
149 | |
150 | latency = scpi_ops->get_transition_latency(cpu_dev); |
151 | if (!latency) |
152 | latency = CPUFREQ_ETERNAL; |
153 | |
154 | policy->cpuinfo.transition_latency = latency; |
155 | |
156 | policy->fast_switch_possible = false; |
157 | |
158 | return 0; |
159 | |
160 | out_free_cpufreq_table: |
161 | dev_pm_opp_free_cpufreq_table(dev: cpu_dev, table: &freq_table); |
162 | out_free_priv: |
163 | kfree(objp: priv); |
164 | out_free_opp: |
165 | dev_pm_opp_remove_all_dynamic(dev: cpu_dev); |
166 | |
167 | return ret; |
168 | } |
169 | |
170 | static int scpi_cpufreq_exit(struct cpufreq_policy *policy) |
171 | { |
172 | struct scpi_data *priv = policy->driver_data; |
173 | |
174 | clk_put(clk: priv->clk); |
175 | dev_pm_opp_free_cpufreq_table(dev: priv->cpu_dev, table: &policy->freq_table); |
176 | dev_pm_opp_remove_all_dynamic(dev: priv->cpu_dev); |
177 | kfree(objp: priv); |
178 | |
179 | return 0; |
180 | } |
181 | |
182 | static struct cpufreq_driver scpi_cpufreq_driver = { |
183 | .name = "scpi-cpufreq" , |
184 | .flags = CPUFREQ_HAVE_GOVERNOR_PER_POLICY | |
185 | CPUFREQ_NEED_INITIAL_FREQ_CHECK | |
186 | CPUFREQ_IS_COOLING_DEV, |
187 | .verify = cpufreq_generic_frequency_table_verify, |
188 | .attr = cpufreq_generic_attr, |
189 | .get = scpi_cpufreq_get_rate, |
190 | .init = scpi_cpufreq_init, |
191 | .exit = scpi_cpufreq_exit, |
192 | .target_index = scpi_cpufreq_set_target, |
193 | .register_em = cpufreq_register_em_with_opp, |
194 | }; |
195 | |
196 | static int scpi_cpufreq_probe(struct platform_device *pdev) |
197 | { |
198 | int ret; |
199 | |
200 | scpi_ops = get_scpi_ops(); |
201 | if (!scpi_ops) |
202 | return -EIO; |
203 | |
204 | ret = cpufreq_register_driver(driver_data: &scpi_cpufreq_driver); |
205 | if (ret) |
206 | dev_err(&pdev->dev, "%s: registering cpufreq failed, err: %d\n" , |
207 | __func__, ret); |
208 | return ret; |
209 | } |
210 | |
211 | static void scpi_cpufreq_remove(struct platform_device *pdev) |
212 | { |
213 | cpufreq_unregister_driver(driver_data: &scpi_cpufreq_driver); |
214 | scpi_ops = NULL; |
215 | } |
216 | |
217 | static struct platform_driver scpi_cpufreq_platdrv = { |
218 | .driver = { |
219 | .name = "scpi-cpufreq" , |
220 | }, |
221 | .probe = scpi_cpufreq_probe, |
222 | .remove_new = scpi_cpufreq_remove, |
223 | }; |
224 | module_platform_driver(scpi_cpufreq_platdrv); |
225 | |
226 | MODULE_ALIAS("platform:scpi-cpufreq" ); |
227 | MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>" ); |
228 | MODULE_DESCRIPTION("ARM SCPI CPUFreq interface driver" ); |
229 | MODULE_LICENSE("GPL v2" ); |
230 | |