aw_cpusclk.c revision 308324
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: stable/11/sys/arm/allwinner/clk/aw_cpusclk.c 308324 2016-11-05 04:17:32Z mmel $
27 */
28
29/*
30 * Allwinner CPUS clock
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/clk/aw_cpusclk.c 308324 2016-11-05 04:17:32Z mmel $");
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
74enum aw_cpusclk_type {
75	AW_A80_CPUS = 1,
76	AW_A83T_CPUS,
77};
78
79static 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
85struct 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
96static int
97aw_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
126static int
127aw_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
170static int
171aw_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
201static 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};
208DEFINE_CLASS_1(aw_cpusclk_clknode, aw_cpusclk_clknode_class,
209    aw_cpusclk_clknode_methods, sizeof(struct aw_cpusclk_sc), clknode_class);
210
211static int
212aw_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
224static int
225aw_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
299fail:
300	return (error);
301}
302
303static 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
311static driver_t aw_cpusclk_driver = {
312	"aw_cpusclk",
313	aw_cpusclk_methods,
314	0
315};
316
317static devclass_t aw_cpusclk_devclass;
318
319EARLY_DRIVER_MODULE(aw_cpusclk, simplebus, aw_cpusclk_driver,
320    aw_cpusclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
321