sunxi_ccu.c revision 1.7.4.3 1 /* $NetBSD: sunxi_ccu.c,v 1.7.4.3 2018/05/02 07:20:04 pgoyette Exp $ */
2
3 /*-
4 * Copyright (c) 2017 Jared McNeill <jmcneill (at) invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include "opt_soc.h"
30 #include "opt_multiprocessor.h"
31 #include "opt_fdt_arm.h"
32
33 #include <sys/cdefs.h>
34 __KERNEL_RCSID(0, "$NetBSD: sunxi_ccu.c,v 1.7.4.3 2018/05/02 07:20:04 pgoyette Exp $");
35
36 #include <sys/param.h>
37 #include <sys/bus.h>
38 #include <sys/cpu.h>
39 #include <sys/device.h>
40
41 #include <dev/fdt/fdtvar.h>
42
43 #include <dev/clk/clk_backend.h>
44
45 #include <arm/sunxi/sunxi_ccu.h>
46
47 static void *
48 sunxi_ccu_reset_acquire(device_t dev, const void *data, size_t len)
49 {
50 struct sunxi_ccu_softc * const sc = device_private(dev);
51 struct sunxi_ccu_reset *reset;
52
53 if (len != 4)
54 return NULL;
55
56 const u_int reset_id = be32dec(data);
57
58 if (reset_id >= sc->sc_nresets)
59 return NULL;
60
61 reset = &sc->sc_resets[reset_id];
62 if (reset->mask == 0)
63 return NULL;
64
65 return reset;
66 }
67
68 static void
69 sunxi_ccu_reset_release(device_t dev, void *priv)
70 {
71 }
72
73 static int
74 sunxi_ccu_reset_assert(device_t dev, void *priv)
75 {
76 struct sunxi_ccu_softc * const sc = device_private(dev);
77 struct sunxi_ccu_reset * const reset = priv;
78
79 const uint32_t val = CCU_READ(sc, reset->reg);
80 CCU_WRITE(sc, reset->reg, val & ~reset->mask);
81
82 return 0;
83 }
84
85 static int
86 sunxi_ccu_reset_deassert(device_t dev, void *priv)
87 {
88 struct sunxi_ccu_softc * const sc = device_private(dev);
89 struct sunxi_ccu_reset * const reset = priv;
90
91 const uint32_t val = CCU_READ(sc, reset->reg);
92 CCU_WRITE(sc, reset->reg, val | reset->mask);
93
94 return 0;
95 }
96
97 static const struct fdtbus_reset_controller_func sunxi_ccu_fdtreset_funcs = {
98 .acquire = sunxi_ccu_reset_acquire,
99 .release = sunxi_ccu_reset_release,
100 .reset_assert = sunxi_ccu_reset_assert,
101 .reset_deassert = sunxi_ccu_reset_deassert,
102 };
103
104 static struct clk *
105 sunxi_ccu_clock_decode(device_t dev, const void *data, size_t len)
106 {
107 struct sunxi_ccu_softc * const sc = device_private(dev);
108 struct sunxi_ccu_clk *clk;
109
110 if (len != 4)
111 return NULL;
112
113 const u_int clock_id = be32dec(data);
114 if (clock_id >= sc->sc_nclks)
115 return NULL;
116
117 clk = &sc->sc_clks[clock_id];
118 if (clk->type == SUNXI_CCU_UNKNOWN)
119 return NULL;
120
121 return &clk->base;
122 }
123
124 static const struct fdtbus_clock_controller_func sunxi_ccu_fdtclock_funcs = {
125 .decode = sunxi_ccu_clock_decode,
126 };
127
128 static struct clk *
129 sunxi_ccu_clock_get(void *priv, const char *name)
130 {
131 struct sunxi_ccu_softc * const sc = priv;
132 struct sunxi_ccu_clk *clk;
133
134 clk = sunxi_ccu_clock_find(sc, name);
135 if (clk == NULL)
136 return NULL;
137
138 return &clk->base;
139 }
140
141 static void
142 sunxi_ccu_clock_put(void *priv, struct clk *clk)
143 {
144 }
145
146 static u_int
147 sunxi_ccu_clock_get_rate(void *priv, struct clk *clkp)
148 {
149 struct sunxi_ccu_softc * const sc = priv;
150 struct sunxi_ccu_clk *clk = (struct sunxi_ccu_clk *)clkp;
151 struct clk *clkp_parent;
152
153 if (clk->get_rate)
154 return clk->get_rate(sc, clk);
155
156 clkp_parent = clk_get_parent(clkp);
157 if (clkp_parent == NULL) {
158 aprint_error("%s: no parent for %s\n", __func__, clk->base.name);
159 return 0;
160 }
161
162 return clk_get_rate(clkp_parent);
163 }
164
165 static int
166 sunxi_ccu_clock_set_rate(void *priv, struct clk *clkp, u_int rate)
167 {
168 struct sunxi_ccu_softc * const sc = priv;
169 struct sunxi_ccu_clk *clk = (struct sunxi_ccu_clk *)clkp;
170 struct clk *clkp_parent;
171
172 if (clkp->flags & CLK_SET_RATE_PARENT) {
173 clkp_parent = clk_get_parent(clkp);
174 if (clkp_parent == NULL) {
175 aprint_error("%s: no parent for %s\n", __func__, clk->base.name);
176 return ENXIO;
177 }
178 return clk_set_rate(clkp_parent, rate);
179 }
180
181 if (clk->set_rate)
182 return clk->set_rate(sc, clk, rate);
183
184 return ENXIO;
185 }
186
187 static u_int
188 sunxi_ccu_clock_round_rate(void *priv, struct clk *clkp, u_int rate)
189 {
190 struct sunxi_ccu_softc * const sc = priv;
191 struct sunxi_ccu_clk *clk = (struct sunxi_ccu_clk *)clkp;
192 struct clk *clkp_parent;
193
194 if (clkp->flags & CLK_SET_RATE_PARENT) {
195 clkp_parent = clk_get_parent(clkp);
196 if (clkp_parent == NULL) {
197 aprint_error("%s: no parent for %s\n", __func__, clk->base.name);
198 return 0;
199 }
200 return clk_round_rate(clkp_parent, rate);
201 }
202
203 if (clk->round_rate)
204 return clk->round_rate(sc, clk, rate);
205
206 return 0;
207 }
208
209 static int
210 sunxi_ccu_clock_enable(void *priv, struct clk *clkp)
211 {
212 struct sunxi_ccu_softc * const sc = priv;
213 struct sunxi_ccu_clk *clk = (struct sunxi_ccu_clk *)clkp;
214 struct clk *clkp_parent;
215 int error = 0;
216
217 clkp_parent = clk_get_parent(clkp);
218 if (clkp_parent != NULL) {
219 error = clk_enable(clkp_parent);
220 if (error != 0)
221 return error;
222 }
223
224 if (clk->enable)
225 error = clk->enable(sc, clk, 1);
226
227 return error;
228 }
229
230 static int
231 sunxi_ccu_clock_disable(void *priv, struct clk *clkp)
232 {
233 struct sunxi_ccu_softc * const sc = priv;
234 struct sunxi_ccu_clk *clk = (struct sunxi_ccu_clk *)clkp;
235 int error = EINVAL;
236
237 if (clk->enable)
238 error = clk->enable(sc, clk, 0);
239
240 return error;
241 }
242
243 static int
244 sunxi_ccu_clock_set_parent(void *priv, struct clk *clkp,
245 struct clk *clkp_parent)
246 {
247 struct sunxi_ccu_softc * const sc = priv;
248 struct sunxi_ccu_clk *clk = (struct sunxi_ccu_clk *)clkp;
249
250 if (clk->set_parent == NULL)
251 return EINVAL;
252
253 return clk->set_parent(sc, clk, clkp_parent->name);
254 }
255
256 static struct clk *
257 sunxi_ccu_clock_get_parent(void *priv, struct clk *clkp)
258 {
259 struct sunxi_ccu_softc * const sc = priv;
260 struct sunxi_ccu_clk *clk = (struct sunxi_ccu_clk *)clkp;
261 struct sunxi_ccu_clk *clk_parent;
262 const char *parent;
263
264 if (clk->get_parent == NULL)
265 return NULL;
266
267 parent = clk->get_parent(sc, clk);
268 if (parent == NULL)
269 return NULL;
270
271 clk_parent = sunxi_ccu_clock_find(sc, parent);
272 if (clk_parent != NULL)
273 return &clk_parent->base;
274
275 /* No parent in this domain, try FDT */
276 return fdtbus_clock_get(sc->sc_phandle, parent);
277 }
278
279 static const struct clk_funcs sunxi_ccu_clock_funcs = {
280 .get = sunxi_ccu_clock_get,
281 .put = sunxi_ccu_clock_put,
282 .get_rate = sunxi_ccu_clock_get_rate,
283 .set_rate = sunxi_ccu_clock_set_rate,
284 .round_rate = sunxi_ccu_clock_round_rate,
285 .enable = sunxi_ccu_clock_enable,
286 .disable = sunxi_ccu_clock_disable,
287 .set_parent = sunxi_ccu_clock_set_parent,
288 .get_parent = sunxi_ccu_clock_get_parent,
289 };
290
291 struct sunxi_ccu_clk *
292 sunxi_ccu_clock_find(struct sunxi_ccu_softc *sc, const char *name)
293 {
294 for (int i = 0; i < sc->sc_nclks; i++) {
295 if (sc->sc_clks[i].base.name == NULL)
296 continue;
297 if (strcmp(sc->sc_clks[i].base.name, name) == 0)
298 return &sc->sc_clks[i];
299 }
300
301 return NULL;
302 }
303
304 int
305 sunxi_ccu_attach(struct sunxi_ccu_softc *sc)
306 {
307 bus_addr_t addr;
308 bus_size_t size;
309 int i;
310
311 if (fdtbus_get_reg(sc->sc_phandle, 0, &addr, &size) != 0) {
312 aprint_error(": couldn't get registers\n");
313 return ENXIO;
314 }
315 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
316 aprint_error(": couldn't map registers\n");
317 return ENXIO;
318 }
319
320 sc->sc_clkdom.name = device_xname(sc->sc_dev);
321 sc->sc_clkdom.funcs = &sunxi_ccu_clock_funcs;
322 sc->sc_clkdom.priv = sc;
323 for (i = 0; i < sc->sc_nclks; i++) {
324 sc->sc_clks[i].base.domain = &sc->sc_clkdom;
325 clk_attach(&sc->sc_clks[i].base);
326 }
327
328 fdtbus_register_clock_controller(sc->sc_dev, sc->sc_phandle,
329 &sunxi_ccu_fdtclock_funcs);
330
331 fdtbus_register_reset_controller(sc->sc_dev, sc->sc_phandle,
332 &sunxi_ccu_fdtreset_funcs);
333
334 return 0;
335 }
336
337 void
338 sunxi_ccu_print(struct sunxi_ccu_softc *sc)
339 {
340 struct sunxi_ccu_clk *clk;
341 struct clk *clkp_parent;
342 const char *type;
343 int i;
344
345 for (i = 0; i < sc->sc_nclks; i++) {
346 clk = &sc->sc_clks[i];
347 if (clk->type == SUNXI_CCU_UNKNOWN)
348 continue;
349
350 clkp_parent = clk_get_parent(&clk->base);
351
352 switch (clk->type) {
353 case SUNXI_CCU_GATE: type = "gate"; break;
354 case SUNXI_CCU_NM: type = "nm"; break;
355 case SUNXI_CCU_NKMP: type = "nkmp"; break;
356 case SUNXI_CCU_PREDIV: type = "prediv"; break;
357 case SUNXI_CCU_DIV: type = "div"; break;
358 case SUNXI_CCU_PHASE: type = "phase"; break;
359 case SUNXI_CCU_FIXED_FACTOR: type = "fixed-factor"; break;
360 case SUNXI_CCU_FRACTIONAL: type = "fractional"; break;
361 default: type = "???"; break;
362 }
363
364 aprint_debug_dev(sc->sc_dev,
365 "%3d %-12s %2s %-12s %-7s ",
366 i,
367 clk->base.name,
368 clkp_parent ? "<-" : "",
369 clkp_parent ? clkp_parent->name : "",
370 type);
371 aprint_debug("%10d Hz\n", clk_get_rate(&clk->base));
372 }
373 }
374