aw_apbclk.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_apbclk.c 308324 2016-11-05 04:17:32Z mmel $
27 */
28
29/*
30 * Allwinner APB clock
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/clk/aw_apbclk.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	A10_APB0_CLK_RATIO		(0x3 << 8)
53#define	A10_APB0_CLK_RATIO_SHIFT	8
54#define	A10_APB1_CLK_SRC_SEL		(0x3 << 24)
55#define	A10_APB1_CLK_SRC_SEL_SHIFT	24
56#define	A10_APB1_CLK_SRC_SEL_MAX	0x3
57#define	A10_APB1_CLK_RAT_N		(0x3 << 16)
58#define	A10_APB1_CLK_RAT_N_SHIFT	16
59#define	A10_APB1_CLK_RAT_M		(0x1f << 0)
60#define	A10_APB1_CLK_RAT_M_SHIFT	0
61#define	A23_APB0_CLK_RATIO		(0x3 << 0)
62#define	A23_APB0_CLK_RATIO_SHIFT	0
63#define	A83T_APB1_CLK_RATIO		(0x3 << 8)
64#define	A83T_APB1_CLK_RATIO_SHIFT	8
65
66enum aw_apbclk_type {
67	AW_A10_APB0 = 1,
68	AW_A10_APB1,
69	AW_A23_APB0,
70	AW_A83T_APB1,
71};
72
73static struct ofw_compat_data compat_data[] = {
74	{ "allwinner,sun4i-a10-apb0-clk",	AW_A10_APB0 },
75	{ "allwinner,sun4i-a10-apb1-clk",	AW_A10_APB1 },
76	{ "allwinner,sun8i-a23-apb0-clk",	AW_A23_APB0 },
77	{ "allwinner,sun8i-a83t-apb1-clk",	AW_A83T_APB1 },
78	{ NULL, 0 }
79};
80
81struct aw_apbclk_sc {
82	device_t		clkdev;
83	bus_addr_t		reg;
84	enum aw_apbclk_type	type;
85};
86
87#define	APBCLK_READ(sc, val)	CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val))
88#define	APBCLK_WRITE(sc, val)	CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val))
89#define	DEVICE_LOCK(sc)		CLKDEV_DEVICE_LOCK((sc)->clkdev)
90#define	DEVICE_UNLOCK(sc)	CLKDEV_DEVICE_UNLOCK((sc)->clkdev)
91
92static int
93aw_apbclk_init(struct clknode *clk, device_t dev)
94{
95	struct aw_apbclk_sc *sc;
96	uint32_t val, index;
97
98	sc = clknode_get_softc(clk);
99
100	switch (sc->type) {
101	case AW_A10_APB0:
102	case AW_A23_APB0:
103	case AW_A83T_APB1:
104		index = 0;
105		break;
106	case AW_A10_APB1:
107		DEVICE_LOCK(sc);
108		APBCLK_READ(sc, &val);
109		DEVICE_UNLOCK(sc);
110		index = (val & A10_APB1_CLK_SRC_SEL) >>
111		    A10_APB1_CLK_SRC_SEL_SHIFT;
112		break;
113	default:
114		return (ENXIO);
115	}
116
117	clknode_init_parent_idx(clk, index);
118	return (0);
119}
120
121static int
122aw_apbclk_recalc_freq(struct clknode *clk, uint64_t *freq)
123{
124	struct aw_apbclk_sc *sc;
125	uint32_t val, div, m, n;
126
127	sc = clknode_get_softc(clk);
128
129	DEVICE_LOCK(sc);
130	APBCLK_READ(sc, &val);
131	DEVICE_UNLOCK(sc);
132
133	switch (sc->type) {
134	case AW_A10_APB0:
135		div = 1 << ((val & A10_APB0_CLK_RATIO) >>
136		    A10_APB0_CLK_RATIO_SHIFT);
137		if (div == 1)
138			div = 2;
139		*freq = *freq / div;
140		break;
141	case AW_A10_APB1:
142		n = 1 << ((val & A10_APB1_CLK_RAT_N) >>
143		    A10_APB1_CLK_RAT_N_SHIFT);
144		m = ((val & A10_APB1_CLK_RAT_N) >>
145		    A10_APB1_CLK_RAT_M_SHIFT) + 1;
146		*freq = *freq / n / m;
147		break;
148	case AW_A23_APB0:
149		div = 1 << ((val & A23_APB0_CLK_RATIO) >>
150		    A23_APB0_CLK_RATIO_SHIFT);
151		*freq = *freq / div;
152		break;
153	case AW_A83T_APB1:
154		div = ((val & A83T_APB1_CLK_RATIO) >>
155		    A83T_APB1_CLK_RATIO_SHIFT) + 1;
156		*freq = *freq / div;
157		break;
158	default:
159		return (ENXIO);
160	}
161
162	return (0);
163}
164
165static int
166aw_apbclk_set_mux(struct clknode *clk, int index)
167{
168	struct aw_apbclk_sc *sc;
169	uint32_t val;
170
171	sc = clknode_get_softc(clk);
172
173	if (sc->type != AW_A10_APB1)
174		return (ENXIO);
175
176	if (index < 0 || index > A10_APB1_CLK_SRC_SEL_MAX)
177		return (ERANGE);
178
179	DEVICE_LOCK(sc);
180	APBCLK_READ(sc, &val);
181	val &= ~A10_APB1_CLK_SRC_SEL;
182	val |= (index << A10_APB1_CLK_SRC_SEL_SHIFT);
183	APBCLK_WRITE(sc, val);
184	DEVICE_UNLOCK(sc);
185
186	return (0);
187}
188
189static clknode_method_t aw_apbclk_clknode_methods[] = {
190	/* Device interface */
191	CLKNODEMETHOD(clknode_init,		aw_apbclk_init),
192	CLKNODEMETHOD(clknode_recalc_freq,	aw_apbclk_recalc_freq),
193	CLKNODEMETHOD(clknode_set_mux,		aw_apbclk_set_mux),
194	CLKNODEMETHOD_END
195};
196DEFINE_CLASS_1(aw_apbclk_clknode, aw_apbclk_clknode_class,
197    aw_apbclk_clknode_methods, sizeof(struct aw_apbclk_sc), clknode_class);
198
199static int
200aw_apbclk_probe(device_t dev)
201{
202	if (!ofw_bus_status_okay(dev))
203		return (ENXIO);
204
205	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
206		return (ENXIO);
207
208	device_set_desc(dev, "Allwinner APB Clock");
209	return (BUS_PROBE_DEFAULT);
210}
211
212static int
213aw_apbclk_attach(device_t dev)
214{
215	struct clknode_init_def def;
216	struct aw_apbclk_sc *sc;
217	struct clkdom *clkdom;
218	struct clknode *clk;
219	clk_t clk_parent;
220	bus_addr_t paddr;
221	bus_size_t psize;
222	phandle_t node;
223	int error, ncells, i;
224
225	node = ofw_bus_get_node(dev);
226
227	if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) {
228		device_printf(dev, "cannot parse 'reg' property\n");
229		return (ENXIO);
230	}
231
232	error = ofw_bus_parse_xref_list_get_length(node, "clocks",
233	    "#clock-cells", &ncells);
234	if (error != 0) {
235		device_printf(dev, "cannot get clock count\n");
236		return (error);
237	}
238
239	clkdom = clkdom_create(dev);
240
241	memset(&def, 0, sizeof(def));
242	error = clk_parse_ofw_clk_name(dev, node, &def.name);
243	if (error != 0) {
244		device_printf(dev, "cannot parse clock name\n");
245		error = ENXIO;
246		goto fail;
247	}
248	def.id = 1;
249	def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK);
250	for (i = 0; i < ncells; i++) {
251		error = clk_get_by_ofw_index(dev, 0, i, &clk_parent);
252		if (error != 0) {
253			device_printf(dev, "cannot get clock %d\n", i);
254			goto fail;
255		}
256		def.parent_names[i] = clk_get_name(clk_parent);
257		clk_release(clk_parent);
258	}
259	def.parent_cnt = ncells;
260
261	clk = clknode_create(clkdom, &aw_apbclk_clknode_class, &def);
262	if (clk == NULL) {
263		device_printf(dev, "cannot create clknode\n");
264		error = ENXIO;
265		goto fail;
266	}
267
268	sc = clknode_get_softc(clk);
269	sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
270	sc->reg = paddr;
271	sc->clkdev = device_get_parent(dev);
272
273	clknode_register(clkdom, clk);
274
275	if (clkdom_finit(clkdom) != 0) {
276		device_printf(dev, "cannot finalize clkdom initialization\n");
277		error = ENXIO;
278		goto fail;
279	}
280
281	if (bootverbose)
282		clkdom_dump(clkdom);
283
284	return (0);
285
286fail:
287	return (error);
288}
289
290static device_method_t aw_apbclk_methods[] = {
291	/* Device interface */
292	DEVMETHOD(device_probe,		aw_apbclk_probe),
293	DEVMETHOD(device_attach,	aw_apbclk_attach),
294
295	DEVMETHOD_END
296};
297
298static driver_t aw_apbclk_driver = {
299	"aw_apbclk",
300	aw_apbclk_methods,
301	0
302};
303
304static devclass_t aw_apbclk_devclass;
305
306EARLY_DRIVER_MODULE(aw_apbclk, simplebus, aw_apbclk_driver,
307    aw_apbclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
308