1297627Sjmcneill/*-
2297627Sjmcneill * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
3297627Sjmcneill * All rights reserved.
4297627Sjmcneill *
5297627Sjmcneill * Redistribution and use in source and binary forms, with or without
6297627Sjmcneill * modification, are permitted provided that the following conditions
7297627Sjmcneill * are met:
8297627Sjmcneill * 1. Redistributions of source code must retain the above copyright
9297627Sjmcneill *    notice, this list of conditions and the following disclaimer.
10297627Sjmcneill * 2. Redistributions in binary form must reproduce the above copyright
11297627Sjmcneill *    notice, this list of conditions and the following disclaimer in the
12297627Sjmcneill *    documentation and/or other materials provided with the distribution.
13297627Sjmcneill *
14297627Sjmcneill * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15297627Sjmcneill * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16297627Sjmcneill * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17297627Sjmcneill * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18297627Sjmcneill * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19297627Sjmcneill * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20297627Sjmcneill * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21297627Sjmcneill * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22297627Sjmcneill * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23297627Sjmcneill * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24297627Sjmcneill * SUCH DAMAGE.
25297627Sjmcneill *
26297627Sjmcneill * $FreeBSD: releng/11.0/sys/arm/allwinner/clk/aw_apbclk.c 299113 2016-05-05 09:41:57Z jmcneill $
27297627Sjmcneill */
28297627Sjmcneill
29297627Sjmcneill/*
30297627Sjmcneill * Allwinner APB clock
31297627Sjmcneill */
32297627Sjmcneill
33297627Sjmcneill#include <sys/cdefs.h>
34297627Sjmcneill__FBSDID("$FreeBSD: releng/11.0/sys/arm/allwinner/clk/aw_apbclk.c 299113 2016-05-05 09:41:57Z jmcneill $");
35297627Sjmcneill
36297627Sjmcneill#include <sys/param.h>
37297627Sjmcneill#include <sys/systm.h>
38297627Sjmcneill#include <sys/bus.h>
39297627Sjmcneill#include <sys/rman.h>
40297627Sjmcneill#include <sys/kernel.h>
41297627Sjmcneill#include <sys/module.h>
42297627Sjmcneill#include <machine/bus.h>
43297627Sjmcneill
44297627Sjmcneill#include <dev/ofw/ofw_bus.h>
45297627Sjmcneill#include <dev/ofw/ofw_bus_subr.h>
46297627Sjmcneill#include <dev/ofw/ofw_subr.h>
47297627Sjmcneill
48297627Sjmcneill#include <dev/extres/clk/clk.h>
49297627Sjmcneill
50297627Sjmcneill#include "clkdev_if.h"
51297627Sjmcneill
52299113Sjmcneill#define	A10_APB0_CLK_RATIO		(0x3 << 8)
53299113Sjmcneill#define	A10_APB0_CLK_RATIO_SHIFT	8
54299113Sjmcneill#define	A10_APB1_CLK_SRC_SEL		(0x3 << 24)
55299113Sjmcneill#define	A10_APB1_CLK_SRC_SEL_SHIFT	24
56299113Sjmcneill#define	A10_APB1_CLK_SRC_SEL_MAX	0x3
57299113Sjmcneill#define	A10_APB1_CLK_RAT_N		(0x3 << 16)
58299113Sjmcneill#define	A10_APB1_CLK_RAT_N_SHIFT	16
59299113Sjmcneill#define	A10_APB1_CLK_RAT_M		(0x1f << 0)
60299113Sjmcneill#define	A10_APB1_CLK_RAT_M_SHIFT	0
61299113Sjmcneill#define	A23_APB0_CLK_RATIO		(0x3 << 0)
62299113Sjmcneill#define	A23_APB0_CLK_RATIO_SHIFT	0
63299113Sjmcneill#define	A83T_APB1_CLK_RATIO		(0x3 << 8)
64299113Sjmcneill#define	A83T_APB1_CLK_RATIO_SHIFT	8
65297627Sjmcneill
66297627Sjmcneillenum aw_apbclk_type {
67297627Sjmcneill	AW_A10_APB0 = 1,
68297627Sjmcneill	AW_A10_APB1,
69299113Sjmcneill	AW_A23_APB0,
70299113Sjmcneill	AW_A83T_APB1,
71297627Sjmcneill};
72297627Sjmcneill
73297627Sjmcneillstatic struct ofw_compat_data compat_data[] = {
74297627Sjmcneill	{ "allwinner,sun4i-a10-apb0-clk",	AW_A10_APB0 },
75297627Sjmcneill	{ "allwinner,sun4i-a10-apb1-clk",	AW_A10_APB1 },
76299113Sjmcneill	{ "allwinner,sun8i-a23-apb0-clk",	AW_A23_APB0 },
77299113Sjmcneill	{ "allwinner,sun8i-a83t-apb1-clk",	AW_A83T_APB1 },
78297627Sjmcneill	{ NULL, 0 }
79297627Sjmcneill};
80297627Sjmcneill
81297627Sjmcneillstruct aw_apbclk_sc {
82297627Sjmcneill	device_t		clkdev;
83297627Sjmcneill	bus_addr_t		reg;
84297627Sjmcneill	enum aw_apbclk_type	type;
85297627Sjmcneill};
86297627Sjmcneill
87297627Sjmcneill#define	APBCLK_READ(sc, val)	CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val))
88297627Sjmcneill#define	APBCLK_WRITE(sc, val)	CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val))
89297627Sjmcneill#define	DEVICE_LOCK(sc)		CLKDEV_DEVICE_LOCK((sc)->clkdev)
90297627Sjmcneill#define	DEVICE_UNLOCK(sc)	CLKDEV_DEVICE_UNLOCK((sc)->clkdev)
91297627Sjmcneill
92297627Sjmcneillstatic int
93297627Sjmcneillaw_apbclk_init(struct clknode *clk, device_t dev)
94297627Sjmcneill{
95297627Sjmcneill	struct aw_apbclk_sc *sc;
96297627Sjmcneill	uint32_t val, index;
97297627Sjmcneill
98297627Sjmcneill	sc = clknode_get_softc(clk);
99297627Sjmcneill
100297627Sjmcneill	switch (sc->type) {
101297627Sjmcneill	case AW_A10_APB0:
102299113Sjmcneill	case AW_A23_APB0:
103299113Sjmcneill	case AW_A83T_APB1:
104297627Sjmcneill		index = 0;
105297627Sjmcneill		break;
106297627Sjmcneill	case AW_A10_APB1:
107297627Sjmcneill		DEVICE_LOCK(sc);
108297627Sjmcneill		APBCLK_READ(sc, &val);
109297627Sjmcneill		DEVICE_UNLOCK(sc);
110299113Sjmcneill		index = (val & A10_APB1_CLK_SRC_SEL) >>
111299113Sjmcneill		    A10_APB1_CLK_SRC_SEL_SHIFT;
112297627Sjmcneill		break;
113297627Sjmcneill	default:
114297627Sjmcneill		return (ENXIO);
115297627Sjmcneill	}
116297627Sjmcneill
117297627Sjmcneill	clknode_init_parent_idx(clk, index);
118297627Sjmcneill	return (0);
119297627Sjmcneill}
120297627Sjmcneill
121297627Sjmcneillstatic int
122297627Sjmcneillaw_apbclk_recalc_freq(struct clknode *clk, uint64_t *freq)
123297627Sjmcneill{
124297627Sjmcneill	struct aw_apbclk_sc *sc;
125297627Sjmcneill	uint32_t val, div, m, n;
126297627Sjmcneill
127297627Sjmcneill	sc = clknode_get_softc(clk);
128297627Sjmcneill
129297627Sjmcneill	DEVICE_LOCK(sc);
130297627Sjmcneill	APBCLK_READ(sc, &val);
131297627Sjmcneill	DEVICE_UNLOCK(sc);
132297627Sjmcneill
133297627Sjmcneill	switch (sc->type) {
134297627Sjmcneill	case AW_A10_APB0:
135299113Sjmcneill		div = 1 << ((val & A10_APB0_CLK_RATIO) >>
136299113Sjmcneill		    A10_APB0_CLK_RATIO_SHIFT);
137297627Sjmcneill		if (div == 1)
138297627Sjmcneill			div = 2;
139297627Sjmcneill		*freq = *freq / div;
140297627Sjmcneill		break;
141297627Sjmcneill	case AW_A10_APB1:
142299113Sjmcneill		n = 1 << ((val & A10_APB1_CLK_RAT_N) >>
143299113Sjmcneill		    A10_APB1_CLK_RAT_N_SHIFT);
144299113Sjmcneill		m = ((val & A10_APB1_CLK_RAT_N) >>
145299113Sjmcneill		    A10_APB1_CLK_RAT_M_SHIFT) + 1;
146297627Sjmcneill		*freq = *freq / n / m;
147297627Sjmcneill		break;
148299113Sjmcneill	case AW_A23_APB0:
149299113Sjmcneill		div = 1 << ((val & A23_APB0_CLK_RATIO) >>
150299113Sjmcneill		    A23_APB0_CLK_RATIO_SHIFT);
151299113Sjmcneill		*freq = *freq / div;
152299113Sjmcneill		break;
153299113Sjmcneill	case AW_A83T_APB1:
154299113Sjmcneill		div = ((val & A83T_APB1_CLK_RATIO) >>
155299113Sjmcneill		    A83T_APB1_CLK_RATIO_SHIFT) + 1;
156299113Sjmcneill		*freq = *freq / div;
157299113Sjmcneill		break;
158297627Sjmcneill	default:
159297627Sjmcneill		return (ENXIO);
160297627Sjmcneill	}
161297627Sjmcneill
162297627Sjmcneill	return (0);
163297627Sjmcneill}
164297627Sjmcneill
165297627Sjmcneillstatic int
166297627Sjmcneillaw_apbclk_set_mux(struct clknode *clk, int index)
167297627Sjmcneill{
168297627Sjmcneill	struct aw_apbclk_sc *sc;
169297627Sjmcneill	uint32_t val;
170297627Sjmcneill
171297627Sjmcneill	sc = clknode_get_softc(clk);
172297627Sjmcneill
173297627Sjmcneill	if (sc->type != AW_A10_APB1)
174297627Sjmcneill		return (ENXIO);
175297627Sjmcneill
176299113Sjmcneill	if (index < 0 || index > A10_APB1_CLK_SRC_SEL_MAX)
177297627Sjmcneill		return (ERANGE);
178297627Sjmcneill
179297627Sjmcneill	DEVICE_LOCK(sc);
180297627Sjmcneill	APBCLK_READ(sc, &val);
181299113Sjmcneill	val &= ~A10_APB1_CLK_SRC_SEL;
182299113Sjmcneill	val |= (index << A10_APB1_CLK_SRC_SEL_SHIFT);
183297627Sjmcneill	APBCLK_WRITE(sc, val);
184297627Sjmcneill	DEVICE_UNLOCK(sc);
185297627Sjmcneill
186297627Sjmcneill	return (0);
187297627Sjmcneill}
188297627Sjmcneill
189297627Sjmcneillstatic clknode_method_t aw_apbclk_clknode_methods[] = {
190297627Sjmcneill	/* Device interface */
191297627Sjmcneill	CLKNODEMETHOD(clknode_init,		aw_apbclk_init),
192297627Sjmcneill	CLKNODEMETHOD(clknode_recalc_freq,	aw_apbclk_recalc_freq),
193297627Sjmcneill	CLKNODEMETHOD(clknode_set_mux,		aw_apbclk_set_mux),
194297627Sjmcneill	CLKNODEMETHOD_END
195297627Sjmcneill};
196297627SjmcneillDEFINE_CLASS_1(aw_apbclk_clknode, aw_apbclk_clknode_class,
197297627Sjmcneill    aw_apbclk_clknode_methods, sizeof(struct aw_apbclk_sc), clknode_class);
198297627Sjmcneill
199297627Sjmcneillstatic int
200297627Sjmcneillaw_apbclk_probe(device_t dev)
201297627Sjmcneill{
202297627Sjmcneill	if (!ofw_bus_status_okay(dev))
203297627Sjmcneill		return (ENXIO);
204297627Sjmcneill
205297627Sjmcneill	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
206297627Sjmcneill		return (ENXIO);
207297627Sjmcneill
208297627Sjmcneill	device_set_desc(dev, "Allwinner APB Clock");
209297627Sjmcneill	return (BUS_PROBE_DEFAULT);
210297627Sjmcneill}
211297627Sjmcneill
212297627Sjmcneillstatic int
213297627Sjmcneillaw_apbclk_attach(device_t dev)
214297627Sjmcneill{
215297627Sjmcneill	struct clknode_init_def def;
216297627Sjmcneill	struct aw_apbclk_sc *sc;
217297627Sjmcneill	struct clkdom *clkdom;
218297627Sjmcneill	struct clknode *clk;
219297627Sjmcneill	clk_t clk_parent;
220297627Sjmcneill	bus_addr_t paddr;
221297627Sjmcneill	bus_size_t psize;
222297627Sjmcneill	phandle_t node;
223297627Sjmcneill	int error, ncells, i;
224297627Sjmcneill
225297627Sjmcneill	node = ofw_bus_get_node(dev);
226297627Sjmcneill
227297627Sjmcneill	if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) {
228297627Sjmcneill		device_printf(dev, "cannot parse 'reg' property\n");
229297627Sjmcneill		return (ENXIO);
230297627Sjmcneill	}
231297627Sjmcneill
232297627Sjmcneill	error = ofw_bus_parse_xref_list_get_length(node, "clocks",
233297627Sjmcneill	    "#clock-cells", &ncells);
234297627Sjmcneill	if (error != 0) {
235297627Sjmcneill		device_printf(dev, "cannot get clock count\n");
236297627Sjmcneill		return (error);
237297627Sjmcneill	}
238297627Sjmcneill
239297627Sjmcneill	clkdom = clkdom_create(dev);
240297627Sjmcneill
241297627Sjmcneill	memset(&def, 0, sizeof(def));
242297627Sjmcneill	error = clk_parse_ofw_clk_name(dev, node, &def.name);
243297627Sjmcneill	if (error != 0) {
244297627Sjmcneill		device_printf(dev, "cannot parse clock name\n");
245297627Sjmcneill		error = ENXIO;
246297627Sjmcneill		goto fail;
247297627Sjmcneill	}
248297627Sjmcneill	def.id = 1;
249297627Sjmcneill	def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK);
250297627Sjmcneill	for (i = 0; i < ncells; i++) {
251297627Sjmcneill		error = clk_get_by_ofw_index(dev, i, &clk_parent);
252297627Sjmcneill		if (error != 0) {
253297627Sjmcneill			device_printf(dev, "cannot get clock %d\n", i);
254297627Sjmcneill			goto fail;
255297627Sjmcneill		}
256297627Sjmcneill		def.parent_names[i] = clk_get_name(clk_parent);
257297627Sjmcneill		clk_release(clk_parent);
258297627Sjmcneill	}
259297627Sjmcneill	def.parent_cnt = ncells;
260297627Sjmcneill
261297627Sjmcneill	clk = clknode_create(clkdom, &aw_apbclk_clknode_class, &def);
262297627Sjmcneill	if (clk == NULL) {
263297627Sjmcneill		device_printf(dev, "cannot create clknode\n");
264297627Sjmcneill		error = ENXIO;
265297627Sjmcneill		goto fail;
266297627Sjmcneill	}
267297627Sjmcneill
268297627Sjmcneill	sc = clknode_get_softc(clk);
269297627Sjmcneill	sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
270297627Sjmcneill	sc->reg = paddr;
271297627Sjmcneill	sc->clkdev = device_get_parent(dev);
272297627Sjmcneill
273297627Sjmcneill	clknode_register(clkdom, clk);
274297627Sjmcneill
275297627Sjmcneill	if (clkdom_finit(clkdom) != 0) {
276297627Sjmcneill		device_printf(dev, "cannot finalize clkdom initialization\n");
277297627Sjmcneill		error = ENXIO;
278297627Sjmcneill		goto fail;
279297627Sjmcneill	}
280297627Sjmcneill
281297627Sjmcneill	if (bootverbose)
282297627Sjmcneill		clkdom_dump(clkdom);
283297627Sjmcneill
284297627Sjmcneill	return (0);
285297627Sjmcneill
286297627Sjmcneillfail:
287297627Sjmcneill	return (error);
288297627Sjmcneill}
289297627Sjmcneill
290297627Sjmcneillstatic device_method_t aw_apbclk_methods[] = {
291297627Sjmcneill	/* Device interface */
292297627Sjmcneill	DEVMETHOD(device_probe,		aw_apbclk_probe),
293297627Sjmcneill	DEVMETHOD(device_attach,	aw_apbclk_attach),
294297627Sjmcneill
295297627Sjmcneill	DEVMETHOD_END
296297627Sjmcneill};
297297627Sjmcneill
298297627Sjmcneillstatic driver_t aw_apbclk_driver = {
299297627Sjmcneill	"aw_apbclk",
300297627Sjmcneill	aw_apbclk_methods,
301297627Sjmcneill	0
302297627Sjmcneill};
303297627Sjmcneill
304297627Sjmcneillstatic devclass_t aw_apbclk_devclass;
305297627Sjmcneill
306297627SjmcneillEARLY_DRIVER_MODULE(aw_apbclk, simplebus, aw_apbclk_driver,
307297627Sjmcneill    aw_apbclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
308