xref: /freebsd-11-stable/sys/arm/allwinner/clk/aw_cpusclk.c (revision 268971235b3b0a738602752716087d25fa1f3ef7)
1 /*-
2  * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  */
28 
29 /*
30  * Allwinner CPUS clock
31  */
32 
33 #include <sys/cdefs.h>
34 __FBSDID("$FreeBSD$");
35 
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 #include <sys/bus.h>
39 #include <sys/rman.h>
40 #include <sys/kernel.h>
41 #include <sys/module.h>
42 #include <machine/bus.h>
43 
44 #include <dev/ofw/ofw_bus.h>
45 #include <dev/ofw/ofw_bus_subr.h>
46 #include <dev/ofw/ofw_subr.h>
47 
48 #include <dev/extres/clk/clk.h>
49 
50 #include "clkdev_if.h"
51 
52 #define	A80_CPUS_CLK_SRC_SEL			(0x3 << 16)
53 #define	A80_CPUS_CLK_SRC_SEL_SHIFT		16
54 #define	A80_CPUS_CLK_SRC_SEL_X32KI		0
55 #define	A80_CPUS_CLK_SRC_SEL_OSC24M		1
56 #define	A80_CPUS_CLK_SRC_SEL_PLL_PERIPH		2
57 #define	A80_CPUS_CLK_SRC_SEL_PLL_AUDIO		3
58 #define	A80_CPUS_POST_DIV			(0x1f << 8)
59 #define	A80_CPUS_POST_DIV_SHIFT			8
60 #define	A80_CPUS_CLK_RATIO			(0x3 << 4)
61 #define	A80_CPUS_CLK_RATIO_SHIFT		4
62 
63 #define	A83T_CPUS_CLK_SRC_SEL			(0x3 << 16)
64 #define	A83T_CPUS_CLK_SRC_SEL_SHIFT		16
65 #define	A83T_CPUS_CLK_SRC_SEL_X32KI		0
66 #define	A83T_CPUS_CLK_SRC_SEL_OSC24M		1
67 #define	A83T_CPUS_CLK_SRC_SEL_PLL_PERIPH	2
68 #define	A83T_CPUS_CLK_SRC_SEL_INTERNAL_OSC	3
69 #define	A83T_CPUS_POST_DIV			(0x1f << 8)
70 #define	A83T_CPUS_POST_DIV_SHIFT		8
71 #define	A83T_CPUS_CLK_RATIO			(0x3 << 4)
72 #define	A83T_CPUS_CLK_RATIO_SHIFT		4
73 
74 enum aw_cpusclk_type {
75 	AW_A80_CPUS = 1,
76 	AW_A83T_CPUS,
77 };
78 
79 static struct ofw_compat_data compat_data[] = {
80 	{ "allwinner,sun9i-a80-cpus-clk",	AW_A80_CPUS },
81 	{ "allwinner,sun8i-a83t-cpus-clk",	AW_A83T_CPUS },
82 	{ NULL, 0 }
83 };
84 
85 struct aw_cpusclk_sc {
86 	device_t		clkdev;
87 	bus_addr_t		reg;
88 	enum aw_cpusclk_type	type;
89 };
90 
91 #define	CPUSCLK_READ(sc, val)	CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val))
92 #define	CPUSCLK_WRITE(sc, val)	CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val))
93 #define	DEVICE_LOCK(sc)		CLKDEV_DEVICE_LOCK((sc)->clkdev)
94 #define	DEVICE_UNLOCK(sc)	CLKDEV_DEVICE_UNLOCK((sc)->clkdev)
95 
96 static int
aw_cpusclk_init(struct clknode * clk,device_t dev)97 aw_cpusclk_init(struct clknode *clk, device_t dev)
98 {
99 	struct aw_cpusclk_sc *sc;
100 	uint32_t val, mask, shift, index;
101 
102 	sc = clknode_get_softc(clk);
103 
104 	switch (sc->type) {
105 	case AW_A80_CPUS:
106 		mask = A80_CPUS_CLK_SRC_SEL;
107 		shift = A80_CPUS_CLK_SRC_SEL_SHIFT;
108 		break;
109 	case AW_A83T_CPUS:
110 		mask = A83T_CPUS_CLK_SRC_SEL;
111 		shift = A83T_CPUS_CLK_SRC_SEL_SHIFT;
112 		break;
113 	default:
114 		return (ENXIO);
115 	}
116 
117 	DEVICE_LOCK(sc);
118 	CPUSCLK_READ(sc, &val);
119 	DEVICE_UNLOCK(sc);
120 	index = (val & mask) >> shift;
121 
122 	clknode_init_parent_idx(clk, index);
123 	return (0);
124 }
125 
126 static int
aw_cpusclk_recalc_freq(struct clknode * clk,uint64_t * freq)127 aw_cpusclk_recalc_freq(struct clknode *clk, uint64_t *freq)
128 {
129 	struct aw_cpusclk_sc *sc;
130 	uint32_t val, src_sel, post_div, clk_ratio;
131 
132 	sc = clknode_get_softc(clk);
133 
134 	DEVICE_LOCK(sc);
135 	CPUSCLK_READ(sc, &val);
136 	DEVICE_UNLOCK(sc);
137 
138 	switch (sc->type) {
139 	case AW_A80_CPUS:
140 		src_sel = (val & A80_CPUS_CLK_SRC_SEL) >>
141 		    A80_CPUS_CLK_SRC_SEL_SHIFT;
142 		post_div = ((val & A80_CPUS_POST_DIV) >>
143 		    A80_CPUS_POST_DIV_SHIFT) + 1;
144 		clk_ratio = ((val & A80_CPUS_CLK_RATIO) >>
145 		    A80_CPUS_CLK_RATIO_SHIFT) + 1;
146 		if (src_sel == A80_CPUS_CLK_SRC_SEL_PLL_PERIPH)
147 			*freq = *freq / post_div / clk_ratio;
148 		else
149 			*freq = *freq / clk_ratio;
150 		break;
151 	case AW_A83T_CPUS:
152 		src_sel = (val & A83T_CPUS_CLK_SRC_SEL) >>
153 		    A83T_CPUS_CLK_SRC_SEL_SHIFT;
154 		post_div = ((val & A83T_CPUS_POST_DIV) >>
155 		    A83T_CPUS_POST_DIV_SHIFT) + 1;
156 		clk_ratio = 1 << ((val & A83T_CPUS_CLK_RATIO) >>
157 		    A83T_CPUS_CLK_RATIO_SHIFT);
158 		if (src_sel == A83T_CPUS_CLK_SRC_SEL_PLL_PERIPH)
159 			*freq = *freq / post_div / clk_ratio;
160 		else
161 			*freq = *freq / clk_ratio;
162 		break;
163 	default:
164 		return (EINVAL);
165 	}
166 
167 	return (0);
168 }
169 
170 static int
aw_cpusclk_set_mux(struct clknode * clk,int index)171 aw_cpusclk_set_mux(struct clknode *clk, int index)
172 {
173 	struct aw_cpusclk_sc *sc;
174 	uint32_t mask, shift, val;
175 
176 	sc = clknode_get_softc(clk);
177 
178 	switch (sc->type) {
179 	case AW_A80_CPUS:
180 		mask = A80_CPUS_CLK_SRC_SEL;
181 		shift = A80_CPUS_CLK_SRC_SEL_SHIFT;
182 		break;
183 	case AW_A83T_CPUS:
184 		mask = A83T_CPUS_CLK_SRC_SEL;
185 		shift = A83T_CPUS_CLK_SRC_SEL_SHIFT;
186 		break;
187 	default:
188 		return (ENXIO);
189 	}
190 
191 	DEVICE_LOCK(sc);
192 	CPUSCLK_READ(sc, &val);
193 	val &= ~mask;
194 	val |= (index << shift);
195 	CPUSCLK_WRITE(sc, val);
196 	DEVICE_UNLOCK(sc);
197 
198 	return (0);
199 }
200 
201 static clknode_method_t aw_cpusclk_clknode_methods[] = {
202 	/* Device interface */
203 	CLKNODEMETHOD(clknode_init,		aw_cpusclk_init),
204 	CLKNODEMETHOD(clknode_recalc_freq,	aw_cpusclk_recalc_freq),
205 	CLKNODEMETHOD(clknode_set_mux,		aw_cpusclk_set_mux),
206 	CLKNODEMETHOD_END
207 };
208 DEFINE_CLASS_1(aw_cpusclk_clknode, aw_cpusclk_clknode_class,
209     aw_cpusclk_clknode_methods, sizeof(struct aw_cpusclk_sc), clknode_class);
210 
211 static int
aw_cpusclk_probe(device_t dev)212 aw_cpusclk_probe(device_t dev)
213 {
214 	if (!ofw_bus_status_okay(dev))
215 		return (ENXIO);
216 
217 	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
218 		return (ENXIO);
219 
220 	device_set_desc(dev, "Allwinner CPUS Clock");
221 	return (BUS_PROBE_DEFAULT);
222 }
223 
224 static int
aw_cpusclk_attach(device_t dev)225 aw_cpusclk_attach(device_t dev)
226 {
227 	struct clknode_init_def def;
228 	struct aw_cpusclk_sc *sc;
229 	struct clkdom *clkdom;
230 	struct clknode *clk;
231 	clk_t clk_parent;
232 	bus_addr_t paddr;
233 	bus_size_t psize;
234 	phandle_t node;
235 	int error, ncells, i;
236 
237 	node = ofw_bus_get_node(dev);
238 
239 	if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) {
240 		device_printf(dev, "cannot parse 'reg' property\n");
241 		return (ENXIO);
242 	}
243 
244 	error = ofw_bus_parse_xref_list_get_length(node, "clocks",
245 	    "#clock-cells", &ncells);
246 	if (error != 0) {
247 		device_printf(dev, "cannot get clock count\n");
248 		return (error);
249 	}
250 
251 	clkdom = clkdom_create(dev);
252 
253 	memset(&def, 0, sizeof(def));
254 	def.id = 1;
255 	def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP,
256 	    M_WAITOK);
257 	for (i = 0; i < ncells; i++) {
258 		error = clk_get_by_ofw_index(dev, 0, i, &clk_parent);
259 		if (error != 0) {
260 			device_printf(dev, "cannot get clock %d\n", i);
261 			goto fail;
262 		}
263 		def.parent_names[i] = clk_get_name(clk_parent);
264 		clk_release(clk_parent);
265 	}
266 	def.parent_cnt = ncells;
267 
268 	error = clk_parse_ofw_clk_name(dev, node, &def.name);
269 	if (error != 0) {
270 		device_printf(dev, "cannot parse clock name\n");
271 		error = ENXIO;
272 		goto fail;
273 	}
274 
275 	clk = clknode_create(clkdom, &aw_cpusclk_clknode_class, &def);
276 	if (clk == NULL) {
277 		device_printf(dev, "cannot create clknode\n");
278 		error = ENXIO;
279 		goto fail;
280 	}
281 	sc = clknode_get_softc(clk);
282 	sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
283 	sc->reg = paddr;
284 	sc->clkdev = device_get_parent(dev);
285 
286 	clknode_register(clkdom, clk);
287 
288 	if (clkdom_finit(clkdom) != 0) {
289 		device_printf(dev, "cannot finalize clkdom initialization\n");
290 		error = ENXIO;
291 		goto fail;
292 	}
293 
294 	if (bootverbose)
295 		clkdom_dump(clkdom);
296 
297 	return (0);
298 
299 fail:
300 	return (error);
301 }
302 
303 static device_method_t aw_cpusclk_methods[] = {
304 	/* Device interface */
305 	DEVMETHOD(device_probe,		aw_cpusclk_probe),
306 	DEVMETHOD(device_attach,	aw_cpusclk_attach),
307 
308 	DEVMETHOD_END
309 };
310 
311 static driver_t aw_cpusclk_driver = {
312 	"aw_cpusclk",
313 	aw_cpusclk_methods,
314 	0
315 };
316 
317 static devclass_t aw_cpusclk_devclass;
318 
319 EARLY_DRIVER_MODULE(aw_cpusclk, simplebus, aw_cpusclk_driver,
320     aw_cpusclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
321