aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSudeep Holla <sudeep.holla@arm.com>2015-03-30 05:59:52 -0400
committerSudeep Holla <sudeep.holla@arm.com>2015-09-28 06:53:37 -0400
commitcd52c2a4b5c43631e429d06dce12e08b0cab477f (patch)
treea773fd60ff069b98fa28cff6f6ea4f3a31604568
parent8cb7cf56c9fe5412de238465b27ef35b4d2801aa (diff)
clk: add support for clocks provided by SCP(System Control Processor)
On some ARM based systems, a separate Cortex-M based System Control Processor(SCP) provides the overall power, clock, reset and system control. System Control and Power Interface(SCPI) Message Protocol is defined for the communication between the Application Cores(AP) and the SCP. This patch adds support for the clocks provided by SCP using SCPI protocol. Signed-off-by: Sudeep Holla <sudeep.holla@arm.com> Reviewed-by: Stephen Boyd <sboyd@codeaurora.org> Cc: Mike Turquette <mturquette@baylibre.com> Cc: Liviu Dudau <Liviu.Dudau@arm.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Jon Medhurst (Tixy) <tixy@linaro.org> Cc: linux-clk@vger.kernel.org
-rw-r--r--MAINTAINERS1
-rw-r--r--drivers/clk/Kconfig10
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/clk-scpi.c312
4 files changed, 324 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 9598821d6a37..613a116ab0f4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9154,6 +9154,7 @@ M: Sudeep Holla <sudeep.holla@arm.com>
9154L: linux-arm-kernel@lists.infradead.org 9154L: linux-arm-kernel@lists.infradead.org
9155S: Maintained 9155S: Maintained
9156F: Documentation/devicetree/bindings/arm/arm,scpi.txt 9156F: Documentation/devicetree/bindings/arm/arm,scpi.txt
9157F: drivers/clk/clk-scpi.c
9157F: drivers/firmware/arm_scpi.c 9158F: drivers/firmware/arm_scpi.c
9158F: include/linux/scpi_protocol.h 9159F: include/linux/scpi_protocol.h
9159 9160
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 42f7120ca9ce..4aec54b90331 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -59,6 +59,16 @@ config COMMON_CLK_RK808
59 clocked at 32KHz each. Clkout1 is always on, Clkout2 can off 59 clocked at 32KHz each. Clkout1 is always on, Clkout2 can off
60 by control register. 60 by control register.
61 61
62config COMMON_CLK_SCPI
63 tristate "Clock driver controlled via SCPI interface"
64 depends on ARM_SCPI_PROTOCOL || COMPILE_TEST
65 ---help---
66 This driver provides support for clocks that are controlled
67 by firmware that implements the SCPI interface.
68
69 This driver uses SCPI Message Protocol to interact with the
70 firmware providing all the clock controls.
71
62config COMMON_CLK_SI5351 72config COMMON_CLK_SI5351
63 tristate "Clock driver for SiLabs 5351A/B/C" 73 tristate "Clock driver for SiLabs 5351A/B/C"
64 depends on I2C 74 depends on I2C
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index d08b3e5985be..a381431bd950 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_COMMON_CLK_PALMAS) += clk-palmas.o
36obj-$(CONFIG_CLK_QORIQ) += clk-qoriq.o 36obj-$(CONFIG_CLK_QORIQ) += clk-qoriq.o
37obj-$(CONFIG_COMMON_CLK_RK808) += clk-rk808.o 37obj-$(CONFIG_COMMON_CLK_RK808) += clk-rk808.o
38obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o 38obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o
39obj-$(CONFIG_COMMON_CLK_SCPI) += clk-scpi.o
39obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o 40obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
40obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o 41obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o
41obj-$(CONFIG_COMMON_CLK_CDCE925) += clk-cdce925.o 42obj-$(CONFIG_COMMON_CLK_CDCE925) += clk-cdce925.o
diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c
new file mode 100644
index 000000000000..72f0486b5de6
--- /dev/null
+++ b/drivers/clk/clk-scpi.c
@@ -0,0 +1,312 @@
1/*
2 * System Control and Power Interface (SCPI) Protocol based clock driver
3 *
4 * Copyright (C) 2015 ARM Ltd.
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms and conditions of the GNU General Public License,
8 * version 2, as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include <linux/clk-provider.h>
20#include <linux/device.h>
21#include <linux/err.h>
22#include <linux/of.h>
23#include <linux/module.h>
24#include <linux/of_platform.h>
25#include <linux/platform_device.h>
26#include <linux/scpi_protocol.h>
27
28struct scpi_clk {
29 u32 id;
30 struct clk_hw hw;
31 struct scpi_dvfs_info *info;
32 struct scpi_ops *scpi_ops;
33};
34
35#define to_scpi_clk(clk) container_of(clk, struct scpi_clk, hw)
36
37static unsigned long scpi_clk_recalc_rate(struct clk_hw *hw,
38 unsigned long parent_rate)
39{
40 struct scpi_clk *clk = to_scpi_clk(hw);
41
42 return clk->scpi_ops->clk_get_val(clk->id);
43}
44
45static long scpi_clk_round_rate(struct clk_hw *hw, unsigned long rate,
46 unsigned long *parent_rate)
47{
48 /*
49 * We can't figure out what rate it will be, so just return the
50 * rate back to the caller. scpi_clk_recalc_rate() will be called
51 * after the rate is set and we'll know what rate the clock is
52 * running at then.
53 */
54 return rate;
55}
56
57static int scpi_clk_set_rate(struct clk_hw *hw, unsigned long rate,
58 unsigned long parent_rate)
59{
60 struct scpi_clk *clk = to_scpi_clk(hw);
61
62 return clk->scpi_ops->clk_set_val(clk->id, rate);
63}
64
65static const struct clk_ops scpi_clk_ops = {
66 .recalc_rate = scpi_clk_recalc_rate,
67 .round_rate = scpi_clk_round_rate,
68 .set_rate = scpi_clk_set_rate,
69};
70
71/* find closest match to given frequency in OPP table */
72static int __scpi_dvfs_round_rate(struct scpi_clk *clk, unsigned long rate)
73{
74 int idx;
75 u32 fmin = 0, fmax = ~0, ftmp;
76 const struct scpi_opp *opp = clk->info->opps;
77
78 for (idx = 0; idx < clk->info->count; idx++, opp++) {
79 ftmp = opp->freq;
80 if (ftmp >= (u32)rate) {
81 if (ftmp <= fmax)
82 fmax = ftmp;
83 break;
84 } else if (ftmp >= fmin) {
85 fmin = ftmp;
86 }
87 }
88 return fmax != ~0 ? fmax : fmin;
89}
90
91static unsigned long scpi_dvfs_recalc_rate(struct clk_hw *hw,
92 unsigned long parent_rate)
93{
94 struct scpi_clk *clk = to_scpi_clk(hw);
95 int idx = clk->scpi_ops->dvfs_get_idx(clk->id);
96 const struct scpi_opp *opp;
97
98 if (idx < 0)
99 return 0;
100
101 opp = clk->info->opps + idx;
102 return opp->freq;
103}
104
105static long scpi_dvfs_round_rate(struct clk_hw *hw, unsigned long rate,
106 unsigned long *parent_rate)
107{
108 struct scpi_clk *clk = to_scpi_clk(hw);
109
110 return __scpi_dvfs_round_rate(clk, rate);
111}
112
113static int __scpi_find_dvfs_index(struct scpi_clk *clk, unsigned long rate)
114{
115 int idx, max_opp = clk->info->count;
116 const struct scpi_opp *opp = clk->info->opps;
117
118 for (idx = 0; idx < max_opp; idx++, opp++)
119 if (opp->freq == rate)
120 return idx;
121 return -EINVAL;
122}
123
124static int scpi_dvfs_set_rate(struct clk_hw *hw, unsigned long rate,
125 unsigned long parent_rate)
126{
127 struct scpi_clk *clk = to_scpi_clk(hw);
128 int ret = __scpi_find_dvfs_index(clk, rate);
129
130 if (ret < 0)
131 return ret;
132 return clk->scpi_ops->dvfs_set_idx(clk->id, (u8)ret);
133}
134
135static const struct clk_ops scpi_dvfs_ops = {
136 .recalc_rate = scpi_dvfs_recalc_rate,
137 .round_rate = scpi_dvfs_round_rate,
138 .set_rate = scpi_dvfs_set_rate,
139};
140
141static const struct of_device_id scpi_clk_match[] = {
142 { .compatible = "arm,scpi-dvfs-clocks", .data = &scpi_dvfs_ops, },
143 { .compatible = "arm,scpi-variable-clocks", .data = &scpi_clk_ops, },
144 {}
145};
146
147static struct clk *
148scpi_clk_ops_init(struct device *dev, const struct of_device_id *match,
149 struct scpi_clk *sclk, const char *name)
150{
151 struct clk_init_data init;
152 struct clk *clk;
153 unsigned long min = 0, max = 0;
154
155 init.name = name;
156 init.flags = CLK_IS_ROOT;
157 init.num_parents = 0;
158 init.ops = match->data;
159 sclk->hw.init = &init;
160 sclk->scpi_ops = get_scpi_ops();
161
162 if (init.ops == &scpi_dvfs_ops) {
163 sclk->info = sclk->scpi_ops->dvfs_get_info(sclk->id);
164 if (IS_ERR(sclk->info))
165 return NULL;
166 } else if (init.ops == &scpi_clk_ops) {
167 if (sclk->scpi_ops->clk_get_range(sclk->id, &min, &max) || !max)
168 return NULL;
169 } else {
170 return NULL;
171 }
172
173 clk = devm_clk_register(dev, &sclk->hw);
174 if (!IS_ERR(clk) && max)
175 clk_hw_set_rate_range(&sclk->hw, min, max);
176 return clk;
177}
178
179struct scpi_clk_data {
180 struct scpi_clk **clk;
181 unsigned int clk_num;
182};
183
184static struct clk *
185scpi_of_clk_src_get(struct of_phandle_args *clkspec, void *data)
186{
187 struct scpi_clk *sclk;
188 struct scpi_clk_data *clk_data = data;
189 unsigned int idx = clkspec->args[0], count;
190
191 for (count = 0; count < clk_data->clk_num; count++) {
192 sclk = clk_data->clk[count];
193 if (idx == sclk->id)
194 return sclk->hw.clk;
195 }
196
197 return ERR_PTR(-EINVAL);
198}
199
200static int scpi_clk_add(struct device *dev, struct device_node *np,
201 const struct of_device_id *match)
202{
203 struct clk **clks;
204 int idx, count;
205 struct scpi_clk_data *clk_data;
206
207 count = of_property_count_strings(np, "clock-output-names");
208 if (count < 0) {
209 dev_err(dev, "%s: invalid clock output count\n", np->name);
210 return -EINVAL;
211 }
212
213 clk_data = devm_kmalloc(dev, sizeof(*clk_data), GFP_KERNEL);
214 if (!clk_data)
215 return -ENOMEM;
216
217 clk_data->clk_num = count;
218 clk_data->clk = devm_kcalloc(dev, count, sizeof(*clk_data->clk),
219 GFP_KERNEL);
220 if (!clk_data->clk)
221 return -ENOMEM;
222
223 clks = devm_kcalloc(dev, count, sizeof(*clks), GFP_KERNEL);
224 if (!clks)
225 return -ENOMEM;
226
227 for (idx = 0; idx < count; idx++) {
228 struct scpi_clk *sclk;
229 const char *name;
230 u32 val;
231
232 sclk = devm_kzalloc(dev, sizeof(*sclk), GFP_KERNEL);
233 if (!sclk)
234 return -ENOMEM;
235
236 if (of_property_read_string_index(np, "clock-output-names",
237 idx, &name)) {
238 dev_err(dev, "invalid clock name @ %s\n", np->name);
239 return -EINVAL;
240 }
241
242 if (of_property_read_u32_index(np, "clock-indices",
243 idx, &val)) {
244 dev_err(dev, "invalid clock index @ %s\n", np->name);
245 return -EINVAL;
246 }
247
248 sclk->id = val;
249
250 clks[idx] = scpi_clk_ops_init(dev, match, sclk, name);
251 if (IS_ERR_OR_NULL(clks[idx]))
252 dev_err(dev, "failed to register clock '%s'\n", name);
253 else
254 dev_dbg(dev, "Registered clock '%s'\n", name);
255 clk_data->clk[idx] = sclk;
256 }
257
258 return of_clk_add_provider(np, scpi_of_clk_src_get, clk_data);
259}
260
261static int scpi_clocks_remove(struct platform_device *pdev)
262{
263 struct device *dev = &pdev->dev;
264 struct device_node *child, *np = dev->of_node;
265
266 for_each_available_child_of_node(np, child)
267 of_clk_del_provider(np);
268 return 0;
269}
270
271static int scpi_clocks_probe(struct platform_device *pdev)
272{
273 int ret;
274 struct device *dev = &pdev->dev;
275 struct device_node *child, *np = dev->of_node;
276 const struct of_device_id *match;
277
278 if (!get_scpi_ops())
279 return -ENXIO;
280
281 for_each_available_child_of_node(np, child) {
282 match = of_match_node(scpi_clk_match, child);
283 if (!match)
284 continue;
285 ret = scpi_clk_add(dev, child, match);
286 if (ret) {
287 scpi_clocks_remove(pdev);
288 return ret;
289 }
290 }
291 return 0;
292}
293
294static const struct of_device_id scpi_clocks_ids[] = {
295 { .compatible = "arm,scpi-clocks", },
296 {}
297};
298MODULE_DEVICE_TABLE(of, scpi_clocks_ids);
299
300static struct platform_driver scpi_clocks_driver = {
301 .driver = {
302 .name = "scpi_clocks",
303 .of_match_table = scpi_clocks_ids,
304 },
305 .probe = scpi_clocks_probe,
306 .remove = scpi_clocks_remove,
307};
308module_platform_driver(scpi_clocks_driver);
309
310MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
311MODULE_DESCRIPTION("ARM SCPI clock driver");
312MODULE_LICENSE("GPL v2");