aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChen-Yu Tsai <wens@csie.org>2015-11-28 22:03:08 -0500
committerMaxime Ripard <maxime.ripard@free-electrons.com>2015-12-01 08:06:47 -0500
commit77d16e2c66c86afc0130822b816ae26790a241fb (patch)
tree55b75f45992bb4ca21c94391c4b3d81e45dec493
parentbfcba2ed83f00e06f82fca3edcb5d723acce74c1 (diff)
clk: sunxi: Add sun9i A80 cpus (cpu special) clock support
The "cpus" clock is the clock for the embedded processor in the A80. It is also part of the PRCM clock tree. This clock includes a pre- divider on one of its inputs. For now we are using a custom clock driver for it. In the future we may want to develop a generalized driver for these types of clocks, which also includes the AHB clock driver on sun[5678]i. Signed-off-by: Chen-Yu Tsai <wens@csie.org> Acked-by: Rob Herring <robh@kernel.org> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
-rw-r--r--Documentation/devicetree/bindings/clock/sunxi.txt1
-rw-r--r--drivers/clk/sunxi/Makefile2
-rw-r--r--drivers/clk/sunxi/clk-sun9i-cpus.c240
3 files changed, 243 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt
index b6859ed6913f..153ac72869e8 100644
--- a/Documentation/devicetree/bindings/clock/sunxi.txt
+++ b/Documentation/devicetree/bindings/clock/sunxi.txt
@@ -27,6 +27,7 @@ Required properties:
27 "allwinner,sun5i-a10s-ahb-gates-clk" - for the AHB gates on A10s 27 "allwinner,sun5i-a10s-ahb-gates-clk" - for the AHB gates on A10s
28 "allwinner,sun7i-a20-ahb-gates-clk" - for the AHB gates on A20 28 "allwinner,sun7i-a20-ahb-gates-clk" - for the AHB gates on A20
29 "allwinner,sun6i-a31-ar100-clk" - for the AR100 on A31 29 "allwinner,sun6i-a31-ar100-clk" - for the AR100 on A31
30 "allwinner,sun9i-a80-cpus-clk" - for the CPUS on A80
30 "allwinner,sun6i-a31-ahb1-clk" - for the AHB1 clock on A31 31 "allwinner,sun6i-a31-ahb1-clk" - for the AHB1 clock on A31
31 "allwinner,sun6i-a31-ahb1-gates-clk" - for the AHB1 gates on A31 32 "allwinner,sun6i-a31-ahb1-gates-clk" - for the AHB1 gates on A31
32 "allwinner,sun8i-a23-ahb1-gates-clk" - for the AHB1 gates on A23 33 "allwinner,sun8i-a23-ahb1-gates-clk" - for the AHB1 gates on A23
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
index cb4c299214ce..103efab05ca8 100644
--- a/drivers/clk/sunxi/Makefile
+++ b/drivers/clk/sunxi/Makefile
@@ -15,6 +15,8 @@ obj-y += clk-sun9i-core.o
15obj-y += clk-sun9i-mmc.o 15obj-y += clk-sun9i-mmc.o
16obj-y += clk-usb.o 16obj-y += clk-usb.o
17 17
18obj-$(CONFIG_MACH_SUN9I) += clk-sun9i-cpus.o
19
18obj-$(CONFIG_MFD_SUN6I_PRCM) += \ 20obj-$(CONFIG_MFD_SUN6I_PRCM) += \
19 clk-sun6i-ar100.o clk-sun6i-apb0.o clk-sun6i-apb0-gates.o \ 21 clk-sun6i-ar100.o clk-sun6i-apb0.o clk-sun6i-apb0-gates.o \
20 clk-sun8i-apb0.o 22 clk-sun8i-apb0.o
diff --git a/drivers/clk/sunxi/clk-sun9i-cpus.c b/drivers/clk/sunxi/clk-sun9i-cpus.c
new file mode 100644
index 000000000000..7626d2194b96
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun9i-cpus.c
@@ -0,0 +1,240 @@
1/*
2 * Copyright (C) 2015 Chen-Yu Tsai
3 *
4 * Chen-Yu Tsai <wens@csie.org>
5 *
6 * Allwinner A80 CPUS clock driver
7 *
8 */
9
10#include <linux/clk.h>
11#include <linux/clk-provider.h>
12#include <linux/slab.h>
13#include <linux/spinlock.h>
14#include <linux/of.h>
15#include <linux/of_address.h>
16
17static DEFINE_SPINLOCK(sun9i_a80_cpus_lock);
18
19/**
20 * sun9i_a80_cpus_clk_setup() - Setup function for a80 cpus composite clk
21 */
22
23#define SUN9I_CPUS_MAX_PARENTS 4
24#define SUN9I_CPUS_MUX_PARENT_PLL4 3
25#define SUN9I_CPUS_MUX_SHIFT 16
26#define SUN9I_CPUS_MUX_MASK GENMASK(17, 16)
27#define SUN9I_CPUS_MUX_GET_PARENT(reg) ((reg & SUN9I_CPUS_MUX_MASK) >> \
28 SUN9I_CPUS_MUX_SHIFT)
29
30#define SUN9I_CPUS_DIV_SHIFT 4
31#define SUN9I_CPUS_DIV_MASK GENMASK(5, 4)
32#define SUN9I_CPUS_DIV_GET(reg) ((reg & SUN9I_CPUS_DIV_MASK) >> \
33 SUN9I_CPUS_DIV_SHIFT)
34#define SUN9I_CPUS_DIV_SET(reg, div) ((reg & ~SUN9I_CPUS_DIV_MASK) | \
35 (div << SUN9I_CPUS_DIV_SHIFT))
36#define SUN9I_CPUS_PLL4_DIV_SHIFT 8
37#define SUN9I_CPUS_PLL4_DIV_MASK GENMASK(12, 8)
38#define SUN9I_CPUS_PLL4_DIV_GET(reg) ((reg & SUN9I_CPUS_PLL4_DIV_MASK) >> \
39 SUN9I_CPUS_PLL4_DIV_SHIFT)
40#define SUN9I_CPUS_PLL4_DIV_SET(reg, div) ((reg & ~SUN9I_CPUS_PLL4_DIV_MASK) | \
41 (div << SUN9I_CPUS_PLL4_DIV_SHIFT))
42
43struct sun9i_a80_cpus_clk {
44 struct clk_hw hw;
45 void __iomem *reg;
46};
47
48#define to_sun9i_a80_cpus_clk(_hw) container_of(_hw, struct sun9i_a80_cpus_clk, hw)
49
50static unsigned long sun9i_a80_cpus_clk_recalc_rate(struct clk_hw *hw,
51 unsigned long parent_rate)
52{
53 struct sun9i_a80_cpus_clk *cpus = to_sun9i_a80_cpus_clk(hw);
54 unsigned long rate;
55 u32 reg;
56
57 /* Fetch the register value */
58 reg = readl(cpus->reg);
59
60 /* apply pre-divider first if parent is pll4 */
61 if (SUN9I_CPUS_MUX_GET_PARENT(reg) == SUN9I_CPUS_MUX_PARENT_PLL4)
62 parent_rate /= SUN9I_CPUS_PLL4_DIV_GET(reg) + 1;
63
64 /* clk divider */
65 rate = parent_rate / (SUN9I_CPUS_DIV_GET(reg) + 1);
66
67 return rate;
68}
69
70static long sun9i_a80_cpus_clk_round(unsigned long rate, u8 *divp, u8 *pre_divp,
71 u8 parent, unsigned long parent_rate)
72{
73 u8 div, pre_div = 1;
74
75 /*
76 * clock can only divide, so we will never be able to achieve
77 * frequencies higher than the parent frequency
78 */
79 if (parent_rate && rate > parent_rate)
80 rate = parent_rate;
81
82 div = DIV_ROUND_UP(parent_rate, rate);
83
84 /* calculate pre-divider if parent is pll4 */
85 if (parent == SUN9I_CPUS_MUX_PARENT_PLL4 && div > 4) {
86 /* pre-divider is 1 ~ 32 */
87 if (div < 32) {
88 pre_div = div;
89 div = 1;
90 } else if (div < 64) {
91 pre_div = DIV_ROUND_UP(div, 2);
92 div = 2;
93 } else if (div < 96) {
94 pre_div = DIV_ROUND_UP(div, 3);
95 div = 3;
96 } else {
97 pre_div = DIV_ROUND_UP(div, 4);
98 div = 4;
99 }
100 }
101
102 /* we were asked to pass back divider values */
103 if (divp) {
104 *divp = div - 1;
105 *pre_divp = pre_div - 1;
106 }
107
108 return parent_rate / pre_div / div;
109}
110
111static int sun9i_a80_cpus_clk_determine_rate(struct clk_hw *clk,
112 struct clk_rate_request *req)
113{
114 struct clk_hw *parent, *best_parent = NULL;
115 int i, num_parents;
116 unsigned long parent_rate, best = 0, child_rate, best_child_rate = 0;
117 unsigned long rate = req->rate;
118
119 /* find the parent that can help provide the fastest rate <= rate */
120 num_parents = clk_hw_get_num_parents(clk);
121 for (i = 0; i < num_parents; i++) {
122 parent = clk_hw_get_parent_by_index(clk, i);
123 if (!parent)
124 continue;
125 if (clk_hw_get_flags(clk) & CLK_SET_RATE_PARENT)
126 parent_rate = clk_hw_round_rate(parent, rate);
127 else
128 parent_rate = clk_hw_get_rate(parent);
129
130 child_rate = sun9i_a80_cpus_clk_round(rate, NULL, NULL, i,
131 parent_rate);
132
133 if (child_rate <= rate && child_rate > best_child_rate) {
134 best_parent = parent;
135 best = parent_rate;
136 best_child_rate = child_rate;
137 }
138 }
139
140 if (!best_parent)
141 return -EINVAL;
142
143 req->best_parent_hw = best_parent;
144 req->best_parent_rate = best;
145 req->rate = best_child_rate;
146
147 return 0;
148}
149
150static int sun9i_a80_cpus_clk_set_rate(struct clk_hw *hw, unsigned long rate,
151 unsigned long parent_rate)
152{
153 struct sun9i_a80_cpus_clk *cpus = to_sun9i_a80_cpus_clk(hw);
154 unsigned long flags;
155 u8 div, pre_div, parent;
156 u32 reg;
157
158 spin_lock_irqsave(&sun9i_a80_cpus_lock, flags);
159
160 reg = readl(cpus->reg);
161
162 /* need to know which parent is used to apply pre-divider */
163 parent = SUN9I_CPUS_MUX_GET_PARENT(reg);
164 sun9i_a80_cpus_clk_round(rate, &div, &pre_div, parent, parent_rate);
165
166 reg = SUN9I_CPUS_DIV_SET(reg, div);
167 reg = SUN9I_CPUS_PLL4_DIV_SET(reg, pre_div);
168 writel(reg, cpus->reg);
169
170 spin_unlock_irqrestore(&sun9i_a80_cpus_lock, flags);
171
172 return 0;
173}
174
175static const struct clk_ops sun9i_a80_cpus_clk_ops = {
176 .determine_rate = sun9i_a80_cpus_clk_determine_rate,
177 .recalc_rate = sun9i_a80_cpus_clk_recalc_rate,
178 .set_rate = sun9i_a80_cpus_clk_set_rate,
179};
180
181static void sun9i_a80_cpus_setup(struct device_node *node)
182{
183 const char *clk_name = node->name;
184 const char *parents[SUN9I_CPUS_MAX_PARENTS];
185 struct resource res;
186 struct sun9i_a80_cpus_clk *cpus;
187 struct clk_mux *mux;
188 struct clk *clk;
189 int ret;
190
191 cpus = kzalloc(sizeof(*cpus), GFP_KERNEL);
192 if (!cpus)
193 return;
194
195 cpus->reg = of_io_request_and_map(node, 0, of_node_full_name(node));
196 if (IS_ERR(cpus->reg))
197 goto err_free_cpus;
198
199 of_property_read_string(node, "clock-output-names", &clk_name);
200
201 /* we have a mux, we will have >1 parents */
202 ret = of_clk_parent_fill(node, parents, SUN9I_CPUS_MAX_PARENTS);
203
204 mux = kzalloc(sizeof(*mux), GFP_KERNEL);
205 if (!mux)
206 goto err_unmap;
207
208 /* set up clock properties */
209 mux->reg = cpus->reg;
210 mux->shift = SUN9I_CPUS_MUX_SHIFT;
211 /* un-shifted mask is what mux_clk expects */
212 mux->mask = SUN9I_CPUS_MUX_MASK >> SUN9I_CPUS_MUX_SHIFT;
213 mux->lock = &sun9i_a80_cpus_lock;
214
215 clk = clk_register_composite(NULL, clk_name, parents, ret,
216 &mux->hw, &clk_mux_ops,
217 &cpus->hw, &sun9i_a80_cpus_clk_ops,
218 NULL, NULL, 0);
219 if (IS_ERR(clk))
220 goto err_free_mux;
221
222 ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
223 if (ret)
224 goto err_unregister;
225
226 return;
227
228err_unregister:
229 clk_unregister(clk);
230err_free_mux:
231 kfree(mux);
232err_unmap:
233 iounmap(cpus->reg);
234 of_address_to_resource(node, 0, &res);
235 release_mem_region(res.start, resource_size(&res));
236err_free_cpus:
237 kfree(cpus);
238}
239CLK_OF_DECLARE(sun9i_a80_cpus, "allwinner,sun9i-a80-cpus-clk",
240 sun9i_a80_cpus_setup);