aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaxime Ripard <maxime.ripard@free-electrons.com>2015-07-01 09:48:37 -0400
committerMaxime Ripard <maxime.ripard@free-electrons.com>2016-04-21 18:29:24 -0400
commitcc510c736b1b278a9925a4a051ecfa72ef8c21fc (patch)
tree06bce73a792b1e0d869d0d6b7d6ec1ae5953d6d6
parentfa4d0ca104bfdcda7b7e2bac855b358f302fd310 (diff)
clk: sunxi: Add TCON channel1 clock
The TCON is a controller generating the timings to output videos signals, acting like both a CRTC and an encoder. It has two channels depending on the output, each channel being driven by its own clock (and own clock controller). Add a driver for the channel 1 clock. Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Rob Herring <robh@kernel.org> Acked-by: Stephen Boyd <sboyd@codeaurora.org>
-rw-r--r--Documentation/devicetree/bindings/clock/sunxi.txt1
-rw-r--r--drivers/clk/sunxi/Makefile1
-rw-r--r--drivers/clk/sunxi/clk-sun4i-tcon-ch1.c300
3 files changed, 302 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt
index 005a24c6bd74..f70f500256bb 100644
--- a/Documentation/devicetree/bindings/clock/sunxi.txt
+++ b/Documentation/devicetree/bindings/clock/sunxi.txt
@@ -75,6 +75,7 @@ Required properties:
75 "allwinner,sun8i-a23-mbus-clk" - for the MBUS clock on A23 75 "allwinner,sun8i-a23-mbus-clk" - for the MBUS clock on A23
76 "allwinner,sun7i-a20-out-clk" - for the external output clocks 76 "allwinner,sun7i-a20-out-clk" - for the external output clocks
77 "allwinner,sun7i-a20-gmac-clk" - for the GMAC clock module on A20/A31 77 "allwinner,sun7i-a20-gmac-clk" - for the GMAC clock module on A20/A31
78 "allwinner,sun4i-a10-tcon-ch1-clk" - for the TCON channel 1 clock on the A10
78 "allwinner,sun4i-a10-usb-clk" - for usb gates + resets on A10 / A20 79 "allwinner,sun4i-a10-usb-clk" - for usb gates + resets on A10 / A20
79 "allwinner,sun5i-a13-usb-clk" - for usb gates + resets on A13 80 "allwinner,sun5i-a13-usb-clk" - for usb gates + resets on A13
80 "allwinner,sun6i-a31-usb-clk" - for usb gates + resets on A31 81 "allwinner,sun6i-a31-usb-clk" - for usb gates + resets on A31
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
index e2f72797bd9a..2d77407a0037 100644
--- a/drivers/clk/sunxi/Makefile
+++ b/drivers/clk/sunxi/Makefile
@@ -12,6 +12,7 @@ obj-y += clk-a20-gmac.o
12obj-y += clk-mod0.o 12obj-y += clk-mod0.o
13obj-y += clk-simple-gates.o 13obj-y += clk-simple-gates.o
14obj-y += clk-sun4i-pll3.o 14obj-y += clk-sun4i-pll3.o
15obj-y += clk-sun4i-tcon-ch1.o
15obj-y += clk-sun8i-bus-gates.o 16obj-y += clk-sun8i-bus-gates.o
16obj-y += clk-sun8i-mbus.o 17obj-y += clk-sun8i-mbus.o
17obj-y += clk-sun9i-core.o 18obj-y += clk-sun9i-core.o
diff --git a/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c b/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c
new file mode 100644
index 000000000000..98a4582de56a
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c
@@ -0,0 +1,300 @@
1/*
2 * Copyright 2015 Maxime Ripard
3 *
4 * Maxime Ripard <maxime.ripard@free-electrons.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 */
16
17#include <linux/clk-provider.h>
18#include <linux/of.h>
19#include <linux/of_address.h>
20#include <linux/slab.h>
21#include <linux/spinlock.h>
22
23#define TCON_CH1_SCLK2_PARENTS 4
24
25#define TCON_CH1_SCLK2_GATE_BIT BIT(31)
26#define TCON_CH1_SCLK2_MUX_MASK 3
27#define TCON_CH1_SCLK2_MUX_SHIFT 24
28#define TCON_CH1_SCLK2_DIV_MASK 0xf
29#define TCON_CH1_SCLK2_DIV_SHIFT 0
30
31#define TCON_CH1_SCLK1_GATE_BIT BIT(15)
32#define TCON_CH1_SCLK1_HALF_BIT BIT(11)
33
34struct tcon_ch1_clk {
35 struct clk_hw hw;
36 spinlock_t lock;
37 void __iomem *reg;
38};
39
40#define hw_to_tclk(hw) container_of(hw, struct tcon_ch1_clk, hw)
41
42static void tcon_ch1_disable(struct clk_hw *hw)
43{
44 struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
45 unsigned long flags;
46 u32 reg;
47
48 spin_lock_irqsave(&tclk->lock, flags);
49 reg = readl(tclk->reg);
50 reg &= ~(TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT);
51 writel(reg, tclk->reg);
52 spin_unlock_irqrestore(&tclk->lock, flags);
53}
54
55static int tcon_ch1_enable(struct clk_hw *hw)
56{
57 struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
58 unsigned long flags;
59 u32 reg;
60
61 spin_lock_irqsave(&tclk->lock, flags);
62 reg = readl(tclk->reg);
63 reg |= TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT;
64 writel(reg, tclk->reg);
65 spin_unlock_irqrestore(&tclk->lock, flags);
66
67 return 0;
68}
69
70static int tcon_ch1_is_enabled(struct clk_hw *hw)
71{
72 struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
73 u32 reg;
74
75 reg = readl(tclk->reg);
76 return reg & (TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT);
77}
78
79static u8 tcon_ch1_get_parent(struct clk_hw *hw)
80{
81 struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
82 int num_parents = clk_hw_get_num_parents(hw);
83 u32 reg;
84
85 reg = readl(tclk->reg) >> TCON_CH1_SCLK2_MUX_SHIFT;
86 reg &= reg >> TCON_CH1_SCLK2_MUX_MASK;
87
88 if (reg >= num_parents)
89 return -EINVAL;
90
91 return reg;
92}
93
94static int tcon_ch1_set_parent(struct clk_hw *hw, u8 index)
95{
96 struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
97 unsigned long flags;
98 u32 reg;
99
100 spin_lock_irqsave(&tclk->lock, flags);
101 reg = readl(tclk->reg);
102 reg &= ~(TCON_CH1_SCLK2_MUX_MASK << TCON_CH1_SCLK2_MUX_SHIFT);
103 reg |= index << TCON_CH1_SCLK2_MUX_SHIFT;
104 writel(reg, tclk->reg);
105 spin_unlock_irqrestore(&tclk->lock, flags);
106
107 return 0;
108};
109
110static unsigned long tcon_ch1_calc_divider(unsigned long rate,
111 unsigned long parent_rate,
112 u8 *div,
113 bool *half)
114{
115 unsigned long best_rate = 0;
116 u8 best_m = 0, m;
117 bool is_double;
118
119 for (m = 1; m < 16; m++) {
120 u8 d;
121
122 for (d = 1; d < 3; d++) {
123 unsigned long tmp_rate;
124
125 tmp_rate = parent_rate / m / d;
126
127 if (tmp_rate > rate)
128 continue;
129
130 if (!best_rate ||
131 (rate - tmp_rate) < (rate - best_rate)) {
132 best_rate = tmp_rate;
133 best_m = m;
134 is_double = d;
135 }
136 }
137 }
138
139 if (div && half) {
140 *div = best_m;
141 *half = is_double;
142 }
143
144 return best_rate;
145}
146
147static int tcon_ch1_determine_rate(struct clk_hw *hw,
148 struct clk_rate_request *req)
149{
150 long best_rate = -EINVAL;
151 int i;
152
153 for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
154 unsigned long parent_rate;
155 unsigned long tmp_rate;
156 struct clk_hw *parent;
157
158 parent = clk_hw_get_parent_by_index(hw, i);
159 if (!parent)
160 continue;
161
162 parent_rate = clk_hw_get_rate(parent);
163
164 tmp_rate = tcon_ch1_calc_divider(req->rate, parent_rate,
165 NULL, NULL);
166
167 if (best_rate < 0 ||
168 (req->rate - tmp_rate) < (req->rate - best_rate)) {
169 best_rate = tmp_rate;
170 req->best_parent_rate = parent_rate;
171 req->best_parent_hw = parent;
172 }
173 }
174
175 if (best_rate < 0)
176 return best_rate;
177
178 req->rate = best_rate;
179 return 0;
180}
181
182static unsigned long tcon_ch1_recalc_rate(struct clk_hw *hw,
183 unsigned long parent_rate)
184{
185 struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
186 u32 reg;
187
188 reg = readl(tclk->reg);
189
190 parent_rate /= (reg & TCON_CH1_SCLK2_DIV_MASK) + 1;
191
192 if (reg & TCON_CH1_SCLK1_HALF_BIT)
193 parent_rate /= 2;
194
195 return parent_rate;
196}
197
198static int tcon_ch1_set_rate(struct clk_hw *hw, unsigned long rate,
199 unsigned long parent_rate)
200{
201 struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
202 unsigned long flags;
203 bool half;
204 u8 div_m;
205 u32 reg;
206
207 tcon_ch1_calc_divider(rate, parent_rate, &div_m, &half);
208
209 spin_lock_irqsave(&tclk->lock, flags);
210 reg = readl(tclk->reg);
211 reg &= ~(TCON_CH1_SCLK2_DIV_MASK | TCON_CH1_SCLK1_HALF_BIT);
212 reg |= (div_m - 1) & TCON_CH1_SCLK2_DIV_MASK;
213
214 if (half)
215 reg |= TCON_CH1_SCLK1_HALF_BIT;
216
217 writel(reg, tclk->reg);
218 spin_unlock_irqrestore(&tclk->lock, flags);
219
220 return 0;
221}
222
223static const struct clk_ops tcon_ch1_ops = {
224 .disable = tcon_ch1_disable,
225 .enable = tcon_ch1_enable,
226 .is_enabled = tcon_ch1_is_enabled,
227
228 .get_parent = tcon_ch1_get_parent,
229 .set_parent = tcon_ch1_set_parent,
230
231 .determine_rate = tcon_ch1_determine_rate,
232 .recalc_rate = tcon_ch1_recalc_rate,
233 .set_rate = tcon_ch1_set_rate,
234};
235
236static void __init tcon_ch1_setup(struct device_node *node)
237{
238 const char *parents[TCON_CH1_SCLK2_PARENTS];
239 const char *clk_name = node->name;
240 struct clk_init_data init;
241 struct tcon_ch1_clk *tclk;
242 struct resource res;
243 struct clk *clk;
244 void __iomem *reg;
245 int ret;
246
247 of_property_read_string(node, "clock-output-names", &clk_name);
248
249 reg = of_io_request_and_map(node, 0, of_node_full_name(node));
250 if (IS_ERR(reg)) {
251 pr_err("%s: Could not map the clock registers\n", clk_name);
252 return;
253 }
254
255 ret = of_clk_parent_fill(node, parents, TCON_CH1_SCLK2_PARENTS);
256 if (ret != TCON_CH1_SCLK2_PARENTS) {
257 pr_err("%s Could not retrieve the parents\n", clk_name);
258 goto err_unmap;
259 }
260
261 tclk = kzalloc(sizeof(*tclk), GFP_KERNEL);
262 if (!tclk)
263 goto err_unmap;
264
265 init.name = clk_name;
266 init.ops = &tcon_ch1_ops;
267 init.parent_names = parents;
268 init.num_parents = TCON_CH1_SCLK2_PARENTS;
269 init.flags = CLK_SET_RATE_PARENT;
270
271 tclk->reg = reg;
272 tclk->hw.init = &init;
273 spin_lock_init(&tclk->lock);
274
275 clk = clk_register(NULL, &tclk->hw);
276 if (IS_ERR(clk)) {
277 pr_err("%s: Couldn't register the clock\n", clk_name);
278 goto err_free_data;
279 }
280
281 ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
282 if (ret) {
283 pr_err("%s: Couldn't register our clock provider\n", clk_name);
284 goto err_unregister_clk;
285 }
286
287 return;
288
289err_unregister_clk:
290 clk_unregister(clk);
291err_free_data:
292 kfree(tclk);
293err_unmap:
294 iounmap(reg);
295 of_address_to_resource(node, 0, &res);
296 release_mem_region(res.start, resource_size(&res));
297}
298
299CLK_OF_DECLARE(tcon_ch1, "allwinner,sun4i-a10-tcon-ch1-clk",
300 tcon_ch1_setup);