sunxi_ccu.c revision 1.7 1 /* $NetBSD: sunxi_ccu.c,v 1.7 2017/09/30 12:48:58 jmcneill 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 2017/09/30 12:48:58 jmcneill 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 int
188 sunxi_ccu_clock_enable(void *priv, struct clk *clkp)
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 int error = 0;
194
195 clkp_parent = clk_get_parent(clkp);
196 if (clkp_parent != NULL) {
197 error = clk_enable(clkp_parent);
198 if (error != 0)
199 return error;
200 }
201
202 if (clk->enable)
203 error = clk->enable(sc, clk, 1);
204
205 return error;
206 }
207
208 static int
209 sunxi_ccu_clock_disable(void *priv, struct clk *clkp)
210 {
211 struct sunxi_ccu_softc * const sc = priv;
212 struct sunxi_ccu_clk *clk = (struct sunxi_ccu_clk *)clkp;
213 int error = EINVAL;
214
215 if (clk->enable)
216 error = clk->enable(sc, clk, 0);
217
218 return error;
219 }
220
221 static int
222 sunxi_ccu_clock_set_parent(void *priv, struct clk *clkp,
223 struct clk *clkp_parent)
224 {
225 struct sunxi_ccu_softc * const sc = priv;
226 struct sunxi_ccu_clk *clk = (struct sunxi_ccu_clk *)clkp;
227
228 if (clk->set_parent == NULL)
229 return EINVAL;
230
231 return clk->set_parent(sc, clk, clkp_parent->name);
232 }
233
234 static struct clk *
235 sunxi_ccu_clock_get_parent(void *priv, struct clk *clkp)
236 {
237 struct sunxi_ccu_softc * const sc = priv;
238 struct sunxi_ccu_clk *clk = (struct sunxi_ccu_clk *)clkp;
239 struct sunxi_ccu_clk *clk_parent;
240 const char *parent;
241
242 if (clk->get_parent == NULL)
243 return NULL;
244
245 parent = clk->get_parent(sc, clk);
246 if (parent == NULL)
247 return NULL;
248
249 clk_parent = sunxi_ccu_clock_find(sc, parent);
250 if (clk_parent != NULL)
251 return &clk_parent->base;
252
253 /* No parent in this domain, try FDT */
254 return fdtbus_clock_get(sc->sc_phandle, parent);
255 }
256
257 static const struct clk_funcs sunxi_ccu_clock_funcs = {
258 .get = sunxi_ccu_clock_get,
259 .put = sunxi_ccu_clock_put,
260 .get_rate = sunxi_ccu_clock_get_rate,
261 .set_rate = sunxi_ccu_clock_set_rate,
262 .enable = sunxi_ccu_clock_enable,
263 .disable = sunxi_ccu_clock_disable,
264 .set_parent = sunxi_ccu_clock_set_parent,
265 .get_parent = sunxi_ccu_clock_get_parent,
266 };
267
268 struct sunxi_ccu_clk *
269 sunxi_ccu_clock_find(struct sunxi_ccu_softc *sc, const char *name)
270 {
271 for (int i = 0; i < sc->sc_nclks; i++) {
272 if (sc->sc_clks[i].base.name == NULL)
273 continue;
274 if (strcmp(sc->sc_clks[i].base.name, name) == 0)
275 return &sc->sc_clks[i];
276 }
277
278 return NULL;
279 }
280
281 int
282 sunxi_ccu_attach(struct sunxi_ccu_softc *sc)
283 {
284 bus_addr_t addr;
285 bus_size_t size;
286 int i;
287
288 if (fdtbus_get_reg(sc->sc_phandle, 0, &addr, &size) != 0) {
289 aprint_error(": couldn't get registers\n");
290 return ENXIO;
291 }
292 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
293 aprint_error(": couldn't map registers\n");
294 return ENXIO;
295 }
296
297 sc->sc_clkdom.funcs = &sunxi_ccu_clock_funcs;
298 sc->sc_clkdom.priv = sc;
299 for (i = 0; i < sc->sc_nclks; i++)
300 sc->sc_clks[i].base.domain = &sc->sc_clkdom;
301
302 fdtbus_register_clock_controller(sc->sc_dev, sc->sc_phandle,
303 &sunxi_ccu_fdtclock_funcs);
304
305 fdtbus_register_reset_controller(sc->sc_dev, sc->sc_phandle,
306 &sunxi_ccu_fdtreset_funcs);
307
308 return 0;
309 }
310
311 void
312 sunxi_ccu_print(struct sunxi_ccu_softc *sc)
313 {
314 struct sunxi_ccu_clk *clk;
315 struct clk *clkp_parent;
316 const char *type;
317 int i;
318
319 for (i = 0; i < sc->sc_nclks; i++) {
320 clk = &sc->sc_clks[i];
321 if (clk->type == SUNXI_CCU_UNKNOWN)
322 continue;
323
324 clkp_parent = clk_get_parent(&clk->base);
325
326 switch (clk->type) {
327 case SUNXI_CCU_GATE: type = "gate"; break;
328 case SUNXI_CCU_NM: type = "nm"; break;
329 case SUNXI_CCU_NKMP: type = "nkmp"; break;
330 case SUNXI_CCU_PREDIV: type = "prediv"; break;
331 case SUNXI_CCU_DIV: type = "div"; break;
332 case SUNXI_CCU_PHASE: type = "phase"; break;
333 case SUNXI_CCU_FIXED_FACTOR: type = "fixed-factor"; break;
334 default: type = "???"; break;
335 }
336
337 aprint_debug_dev(sc->sc_dev,
338 "%3d %-12s %2s %-12s %-7s ",
339 i,
340 clk->base.name,
341 clkp_parent ? "<-" : "",
342 clkp_parent ? clkp_parent->name : "",
343 type);
344 aprint_debug("%10d Hz\n", clk_get_rate(&clk->base));
345 }
346 }
347