15a729246SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
28def3103SSudeep Holla /*
38def3103SSudeep Holla * System Control and Power Interface (SCPI) based CPUFreq Interface driver
48def3103SSudeep Holla *
58def3103SSudeep Holla * Copyright (C) 2015 ARM Ltd.
68def3103SSudeep Holla * Sudeep Holla <sudeep.holla@arm.com>
78def3103SSudeep Holla */
88def3103SSudeep Holla
98def3103SSudeep Holla #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
108def3103SSudeep Holla
11343a8d17SSudeep Holla #include <linux/clk.h>
12d9975b0bSSudeep Holla #include <linux/cpu.h>
138def3103SSudeep Holla #include <linux/cpufreq.h>
14343a8d17SSudeep Holla #include <linux/cpumask.h>
15343a8d17SSudeep Holla #include <linux/export.h>
168def3103SSudeep Holla #include <linux/module.h>
17a70eb93aSRob Herring #include <linux/platform_device.h>
188def3103SSudeep Holla #include <linux/pm_opp.h>
198def3103SSudeep Holla #include <linux/scpi_protocol.h>
20343a8d17SSudeep Holla #include <linux/slab.h>
218def3103SSudeep Holla #include <linux/types.h>
228def3103SSudeep Holla
23343a8d17SSudeep Holla struct scpi_data {
24343a8d17SSudeep Holla struct clk *clk;
25343a8d17SSudeep Holla struct device *cpu_dev;
26343a8d17SSudeep Holla };
278def3103SSudeep Holla
288def3103SSudeep Holla static struct scpi_ops *scpi_ops;
298def3103SSudeep Holla
scpi_cpufreq_get_rate(unsigned int cpu)30343a8d17SSudeep Holla static unsigned int scpi_cpufreq_get_rate(unsigned int cpu)
318def3103SSudeep Holla {
32343a8d17SSudeep Holla struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu);
33343a8d17SSudeep Holla struct scpi_data *priv = policy->driver_data;
34343a8d17SSudeep Holla unsigned long rate = clk_get_rate(priv->clk);
35343a8d17SSudeep Holla
36343a8d17SSudeep Holla return rate / 1000;
378def3103SSudeep Holla }
388def3103SSudeep Holla
39343a8d17SSudeep Holla static int
scpi_cpufreq_set_target(struct cpufreq_policy * policy,unsigned int index)40343a8d17SSudeep Holla scpi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index)
41343a8d17SSudeep Holla {
421a0419b0SIonela Voinescu u64 rate = policy->freq_table[index].frequency * 1000;
43343a8d17SSudeep Holla struct scpi_data *priv = policy->driver_data;
44343a8d17SSudeep Holla int ret;
45343a8d17SSudeep Holla
46343a8d17SSudeep Holla ret = clk_set_rate(priv->clk, rate);
47343a8d17SSudeep Holla
489326fdf3SDietmar Eggemann if (ret)
49343a8d17SSudeep Holla return ret;
509326fdf3SDietmar Eggemann
519326fdf3SDietmar Eggemann if (clk_get_rate(priv->clk) != rate)
529326fdf3SDietmar Eggemann return -EIO;
539326fdf3SDietmar Eggemann
549326fdf3SDietmar Eggemann return 0;
55343a8d17SSudeep Holla }
56343a8d17SSudeep Holla
57343a8d17SSudeep Holla static int
scpi_get_sharing_cpus(struct device * cpu_dev,struct cpumask * cpumask)58343a8d17SSudeep Holla scpi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
59343a8d17SSudeep Holla {
60343a8d17SSudeep Holla int cpu, domain, tdomain;
61343a8d17SSudeep Holla struct device *tcpu_dev;
62343a8d17SSudeep Holla
63343a8d17SSudeep Holla domain = scpi_ops->device_domain_id(cpu_dev);
64343a8d17SSudeep Holla if (domain < 0)
65343a8d17SSudeep Holla return domain;
66343a8d17SSudeep Holla
67343a8d17SSudeep Holla for_each_possible_cpu(cpu) {
68343a8d17SSudeep Holla if (cpu == cpu_dev->id)
69343a8d17SSudeep Holla continue;
70343a8d17SSudeep Holla
71343a8d17SSudeep Holla tcpu_dev = get_cpu_device(cpu);
72343a8d17SSudeep Holla if (!tcpu_dev)
73343a8d17SSudeep Holla continue;
74343a8d17SSudeep Holla
75343a8d17SSudeep Holla tdomain = scpi_ops->device_domain_id(tcpu_dev);
76343a8d17SSudeep Holla if (tdomain == domain)
77343a8d17SSudeep Holla cpumask_set_cpu(cpu, cpumask);
78343a8d17SSudeep Holla }
79343a8d17SSudeep Holla
80343a8d17SSudeep Holla return 0;
81343a8d17SSudeep Holla }
82343a8d17SSudeep Holla
scpi_cpufreq_init(struct cpufreq_policy * policy)83343a8d17SSudeep Holla static int scpi_cpufreq_init(struct cpufreq_policy *policy)
848def3103SSudeep Holla {
85c0f2e219SSudeep Holla int ret;
86343a8d17SSudeep Holla unsigned int latency;
87343a8d17SSudeep Holla struct device *cpu_dev;
88343a8d17SSudeep Holla struct scpi_data *priv;
89343a8d17SSudeep Holla struct cpufreq_frequency_table *freq_table;
90343a8d17SSudeep Holla
91343a8d17SSudeep Holla cpu_dev = get_cpu_device(policy->cpu);
92343a8d17SSudeep Holla if (!cpu_dev) {
93343a8d17SSudeep Holla pr_err("failed to get cpu%d device\n", policy->cpu);
94343a8d17SSudeep Holla return -ENODEV;
95343a8d17SSudeep Holla }
96d9975b0bSSudeep Holla
97c0f2e219SSudeep Holla ret = scpi_ops->add_opps_to_device(cpu_dev);
98d9975b0bSSudeep Holla if (ret) {
99c0f2e219SSudeep Holla dev_warn(cpu_dev, "failed to add opps to the device\n");
100d9975b0bSSudeep Holla return ret;
101d9975b0bSSudeep Holla }
1028def3103SSudeep Holla
103343a8d17SSudeep Holla ret = scpi_get_sharing_cpus(cpu_dev, policy->cpus);
104343a8d17SSudeep Holla if (ret) {
105343a8d17SSudeep Holla dev_warn(cpu_dev, "failed to get sharing cpumask\n");
106343a8d17SSudeep Holla return ret;
107343a8d17SSudeep Holla }
108343a8d17SSudeep Holla
109343a8d17SSudeep Holla ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus);
110343a8d17SSudeep Holla if (ret) {
111d9975b0bSSudeep Holla dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n",
112d9975b0bSSudeep Holla __func__, ret);
113d9975b0bSSudeep Holla return ret;
1148def3103SSudeep Holla }
1158def3103SSudeep Holla
116343a8d17SSudeep Holla ret = dev_pm_opp_get_opp_count(cpu_dev);
117343a8d17SSudeep Holla if (ret <= 0) {
118343a8d17SSudeep Holla dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n");
119343a8d17SSudeep Holla ret = -EPROBE_DEFER;
120343a8d17SSudeep Holla goto out_free_opp;
121343a8d17SSudeep Holla }
122343a8d17SSudeep Holla
123343a8d17SSudeep Holla priv = kzalloc(sizeof(*priv), GFP_KERNEL);
124343a8d17SSudeep Holla if (!priv) {
125343a8d17SSudeep Holla ret = -ENOMEM;
126343a8d17SSudeep Holla goto out_free_opp;
127343a8d17SSudeep Holla }
128343a8d17SSudeep Holla
129343a8d17SSudeep Holla ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
130343a8d17SSudeep Holla if (ret) {
131343a8d17SSudeep Holla dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
132343a8d17SSudeep Holla goto out_free_priv;
133343a8d17SSudeep Holla }
134343a8d17SSudeep Holla
135343a8d17SSudeep Holla priv->cpu_dev = cpu_dev;
136343a8d17SSudeep Holla priv->clk = clk_get(cpu_dev, NULL);
137343a8d17SSudeep Holla if (IS_ERR(priv->clk)) {
138343a8d17SSudeep Holla dev_err(cpu_dev, "%s: Failed to get clk for cpu: %d\n",
139343a8d17SSudeep Holla __func__, cpu_dev->id);
1400725390dSWei Yongjun ret = PTR_ERR(priv->clk);
141343a8d17SSudeep Holla goto out_free_cpufreq_table;
142343a8d17SSudeep Holla }
143343a8d17SSudeep Holla
144343a8d17SSudeep Holla policy->driver_data = priv;
145472ada60SViresh Kumar policy->freq_table = freq_table;
146343a8d17SSudeep Holla
147343a8d17SSudeep Holla /* scpi allows DVFS request for any domain from any CPU */
148343a8d17SSudeep Holla policy->dvfs_possible_from_any_cpu = true;
149343a8d17SSudeep Holla
150343a8d17SSudeep Holla latency = scpi_ops->get_transition_latency(cpu_dev);
151343a8d17SSudeep Holla if (!latency)
152343a8d17SSudeep Holla latency = CPUFREQ_ETERNAL;
153343a8d17SSudeep Holla
154343a8d17SSudeep Holla policy->cpuinfo.transition_latency = latency;
155343a8d17SSudeep Holla
156343a8d17SSudeep Holla policy->fast_switch_possible = false;
1576915d7adSQuentin Perret
158343a8d17SSudeep Holla return 0;
159343a8d17SSudeep Holla
160343a8d17SSudeep Holla out_free_cpufreq_table:
161343a8d17SSudeep Holla dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
162343a8d17SSudeep Holla out_free_priv:
163343a8d17SSudeep Holla kfree(priv);
164343a8d17SSudeep Holla out_free_opp:
1651690d8bbSViresh Kumar dev_pm_opp_remove_all_dynamic(cpu_dev);
166343a8d17SSudeep Holla
167343a8d17SSudeep Holla return ret;
168343a8d17SSudeep Holla }
169343a8d17SSudeep Holla
scpi_cpufreq_exit(struct cpufreq_policy * policy)170343a8d17SSudeep Holla static int scpi_cpufreq_exit(struct cpufreq_policy *policy)
171343a8d17SSudeep Holla {
172343a8d17SSudeep Holla struct scpi_data *priv = policy->driver_data;
173343a8d17SSudeep Holla
174343a8d17SSudeep Holla clk_put(priv->clk);
175343a8d17SSudeep Holla dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
1761690d8bbSViresh Kumar dev_pm_opp_remove_all_dynamic(priv->cpu_dev);
17731d4c528SVincent Stehlé kfree(priv);
178343a8d17SSudeep Holla
179343a8d17SSudeep Holla return 0;
180343a8d17SSudeep Holla }
181343a8d17SSudeep Holla
182343a8d17SSudeep Holla static struct cpufreq_driver scpi_cpufreq_driver = {
183343a8d17SSudeep Holla .name = "scpi-cpufreq",
1845ae4a4b4SViresh Kumar .flags = CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
185cb772b8cSAmit Kucheria CPUFREQ_NEED_INITIAL_FREQ_CHECK |
186cb772b8cSAmit Kucheria CPUFREQ_IS_COOLING_DEV,
187343a8d17SSudeep Holla .verify = cpufreq_generic_frequency_table_verify,
188343a8d17SSudeep Holla .attr = cpufreq_generic_attr,
189343a8d17SSudeep Holla .get = scpi_cpufreq_get_rate,
190343a8d17SSudeep Holla .init = scpi_cpufreq_init,
191343a8d17SSudeep Holla .exit = scpi_cpufreq_exit,
192343a8d17SSudeep Holla .target_index = scpi_cpufreq_set_target,
1934d584efaSViresh Kumar .register_em = cpufreq_register_em_with_opp,
1948def3103SSudeep Holla };
1958def3103SSudeep Holla
scpi_cpufreq_probe(struct platform_device * pdev)1968def3103SSudeep Holla static int scpi_cpufreq_probe(struct platform_device *pdev)
1978def3103SSudeep Holla {
198343a8d17SSudeep Holla int ret;
199343a8d17SSudeep Holla
2008def3103SSudeep Holla scpi_ops = get_scpi_ops();
2018def3103SSudeep Holla if (!scpi_ops)
2028def3103SSudeep Holla return -EIO;
2038def3103SSudeep Holla
204343a8d17SSudeep Holla ret = cpufreq_register_driver(&scpi_cpufreq_driver);
205343a8d17SSudeep Holla if (ret)
206343a8d17SSudeep Holla dev_err(&pdev->dev, "%s: registering cpufreq failed, err: %d\n",
207343a8d17SSudeep Holla __func__, ret);
208343a8d17SSudeep Holla return ret;
2098def3103SSudeep Holla }
2108def3103SSudeep Holla
scpi_cpufreq_remove(struct platform_device * pdev)211*d5aa35fcSYangtao Li static void scpi_cpufreq_remove(struct platform_device *pdev)
2128def3103SSudeep Holla {
213343a8d17SSudeep Holla cpufreq_unregister_driver(&scpi_cpufreq_driver);
2148def3103SSudeep Holla scpi_ops = NULL;
2158def3103SSudeep Holla }
2168def3103SSudeep Holla
2178def3103SSudeep Holla static struct platform_driver scpi_cpufreq_platdrv = {
2188def3103SSudeep Holla .driver = {
2198def3103SSudeep Holla .name = "scpi-cpufreq",
2208def3103SSudeep Holla },
2218def3103SSudeep Holla .probe = scpi_cpufreq_probe,
222*d5aa35fcSYangtao Li .remove_new = scpi_cpufreq_remove,
2238def3103SSudeep Holla };
2248def3103SSudeep Holla module_platform_driver(scpi_cpufreq_platdrv);
2258def3103SSudeep Holla
226c0382d04SPali Rohár MODULE_ALIAS("platform:scpi-cpufreq");
2278def3103SSudeep Holla MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
2288def3103SSudeep Holla MODULE_DESCRIPTION("ARM SCPI CPUFreq interface driver");
2298def3103SSudeep Holla MODULE_LICENSE("GPL v2");
230