xref: /openbmc/linux/drivers/cpufreq/scpi-cpufreq.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
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