aw_ccu.c revision 302408
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/aw_ccu.c 301082 2016-05-31 21:58:09Z jmcneill $
27 */
28
29/*
30 * Allwinner oscillator clock
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/aw_ccu.c 301082 2016-05-31 21:58:09Z jmcneill $");
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/fdt/simplebus.h>
45#include <dev/fdt/fdt_common.h>
46
47#include <dev/ofw/ofw_bus.h>
48#include <dev/ofw/ofw_bus_subr.h>
49
50#include <dev/extres/clk/clk.h>
51
52#include "clkdev_if.h"
53
54#define	CCU_BASE	0x01c20000
55#define	CCU_SIZE	0x400
56
57#define	PRCM_BASE	0x01f01400
58#define	PRCM_SIZE	0x200
59
60#define	SYSCTRL_BASE	0x01c00000
61#define	SYSCTRL_SIZE	0x34
62
63struct aw_ccu_softc {
64	struct simplebus_softc	sc;
65	bus_space_tag_t		bst;
66	bus_space_handle_t	ccu_bsh;
67	bus_space_handle_t	prcm_bsh;
68	bus_space_handle_t	sysctrl_bsh;
69	struct mtx		mtx;
70	int			flags;
71};
72
73#define	CLOCK_CCU	(1 << 0)
74#define	CLOCK_PRCM	(1 << 1)
75#define	CLOCK_SYSCTRL	(1 << 2)
76
77static struct ofw_compat_data compat_data[] = {
78	{ "allwinner,sun4i-a10",	CLOCK_CCU },
79	{ "allwinner,sun7i-a20",	CLOCK_CCU },
80	{ "allwinner,sun6i-a31",	CLOCK_CCU },
81	{ "allwinner,sun6i-a31s",	CLOCK_CCU },
82	{ "allwinner,sun8i-a83t",	CLOCK_CCU|CLOCK_PRCM|CLOCK_SYSCTRL },
83	{ "allwinner,sun8i-h3",		CLOCK_CCU },
84	{ NULL, 0 }
85};
86
87static int
88aw_ccu_check_addr(struct aw_ccu_softc *sc, bus_addr_t addr,
89    bus_space_handle_t *pbsh, bus_size_t *poff)
90{
91	if (addr >= CCU_BASE && addr < (CCU_BASE + CCU_SIZE) &&
92	    (sc->flags & CLOCK_CCU) != 0) {
93		*poff = addr - CCU_BASE;
94		*pbsh = sc->ccu_bsh;
95		return (0);
96	}
97	if (addr >= PRCM_BASE && addr < (PRCM_BASE + PRCM_SIZE) &&
98	    (sc->flags & CLOCK_PRCM) != 0) {
99		*poff = addr - PRCM_BASE;
100		*pbsh = sc->prcm_bsh;
101		return (0);
102	}
103	if (addr >= SYSCTRL_BASE && addr < (SYSCTRL_BASE + SYSCTRL_SIZE) &&
104	    (sc->flags & CLOCK_SYSCTRL) != 0) {
105		*poff = addr - SYSCTRL_BASE;
106		*pbsh = sc->sysctrl_bsh;
107		return (0);
108	}
109	return (EINVAL);
110}
111
112static int
113aw_ccu_write_4(device_t dev, bus_addr_t addr, uint32_t val)
114{
115	struct aw_ccu_softc *sc;
116	bus_space_handle_t bsh;
117	bus_size_t reg;
118
119	sc = device_get_softc(dev);
120
121	if (aw_ccu_check_addr(sc, addr, &bsh, &reg) != 0)
122		return (EINVAL);
123
124	mtx_assert(&sc->mtx, MA_OWNED);
125	bus_space_write_4(sc->bst, bsh, reg, val);
126
127	return (0);
128}
129
130static int
131aw_ccu_read_4(device_t dev, bus_addr_t addr, uint32_t *val)
132{
133	struct aw_ccu_softc *sc;
134	bus_space_handle_t bsh;
135	bus_size_t reg;
136
137	sc = device_get_softc(dev);
138
139	if (aw_ccu_check_addr(sc, addr, &bsh, &reg) != 0)
140		return (EINVAL);
141
142	mtx_assert(&sc->mtx, MA_OWNED);
143	*val = bus_space_read_4(sc->bst, bsh, reg);
144
145	return (0);
146}
147
148static int
149aw_ccu_modify_4(device_t dev, bus_addr_t addr, uint32_t clr, uint32_t set)
150{
151	struct aw_ccu_softc *sc;
152	bus_space_handle_t bsh;
153	bus_size_t reg;
154	uint32_t val;
155
156	sc = device_get_softc(dev);
157
158	if (aw_ccu_check_addr(sc, addr, &bsh, &reg) != 0)
159		return (EINVAL);
160
161	mtx_assert(&sc->mtx, MA_OWNED);
162	val = bus_space_read_4(sc->bst, bsh, reg);
163	val &= ~clr;
164	val |= set;
165	bus_space_write_4(sc->bst, bsh, reg, val);
166
167	return (0);
168}
169
170static void
171aw_ccu_device_lock(device_t dev)
172{
173	struct aw_ccu_softc *sc;
174
175	sc = device_get_softc(dev);
176	mtx_lock(&sc->mtx);
177}
178
179static void
180aw_ccu_device_unlock(device_t dev)
181{
182	struct aw_ccu_softc *sc;
183
184	sc = device_get_softc(dev);
185	mtx_unlock(&sc->mtx);
186}
187
188static const struct ofw_compat_data *
189aw_ccu_search_compatible(void)
190{
191	const struct ofw_compat_data *compat;
192	phandle_t root;
193
194	root = OF_finddevice("/");
195	for (compat = compat_data; compat->ocd_str != NULL; compat++)
196		if (fdt_is_compatible(root, compat->ocd_str))
197			break;
198
199	return (compat);
200}
201
202static int
203aw_ccu_probe(device_t dev)
204{
205	const char *name;
206
207	name = ofw_bus_get_name(dev);
208
209	if (name == NULL || strcmp(name, "clocks") != 0)
210		return (ENXIO);
211
212	if (aw_ccu_search_compatible()->ocd_data == 0)
213		return (ENXIO);
214
215	device_set_desc(dev, "Allwinner Clock Control Unit");
216	return (BUS_PROBE_SPECIFIC);
217}
218
219static int
220aw_ccu_attach(device_t dev)
221{
222	struct aw_ccu_softc *sc;
223	phandle_t node, child;
224	device_t cdev;
225	int error;
226
227	sc = device_get_softc(dev);
228	node = ofw_bus_get_node(dev);
229
230	simplebus_init(dev, node);
231
232	sc->flags = aw_ccu_search_compatible()->ocd_data;
233
234	/*
235	 * Map registers. The DT doesn't have a "reg" property
236	 * for the /clocks node and child nodes have conflicting "reg"
237	 * properties.
238	 */
239	sc->bst = bus_get_bus_tag(dev);
240	if (sc->flags & CLOCK_CCU) {
241		error = bus_space_map(sc->bst, CCU_BASE, CCU_SIZE, 0,
242		    &sc->ccu_bsh);
243		if (error != 0) {
244			device_printf(dev, "couldn't map CCU: %d\n", error);
245			return (error);
246		}
247	}
248	if (sc->flags & CLOCK_PRCM) {
249		error = bus_space_map(sc->bst, PRCM_BASE, PRCM_SIZE, 0,
250		    &sc->prcm_bsh);
251		if (error != 0) {
252			device_printf(dev, "couldn't map PRCM: %d\n", error);
253			return (error);
254		}
255	}
256	if (sc->flags & CLOCK_SYSCTRL) {
257		error = bus_space_map(sc->bst, SYSCTRL_BASE, SYSCTRL_SIZE, 0,
258		    &sc->sysctrl_bsh);
259		if (error != 0) {
260			device_printf(dev, "couldn't map SYSCTRL: %d\n", error);
261			return (error);
262		}
263	}
264
265	mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF);
266
267	/* Attach child devices */
268	for (child = OF_child(node); child > 0; child = OF_peer(child)) {
269		cdev = simplebus_add_device(dev, child, 0, NULL, -1, NULL);
270		if (cdev != NULL)
271			device_probe_and_attach(cdev);
272	}
273
274	return (bus_generic_attach(dev));
275}
276
277static device_method_t aw_ccu_methods[] = {
278	/* Device interface */
279	DEVMETHOD(device_probe,		aw_ccu_probe),
280	DEVMETHOD(device_attach,	aw_ccu_attach),
281
282	/* clkdev interface */
283	DEVMETHOD(clkdev_write_4,	aw_ccu_write_4),
284	DEVMETHOD(clkdev_read_4,	aw_ccu_read_4),
285	DEVMETHOD(clkdev_modify_4,	aw_ccu_modify_4),
286	DEVMETHOD(clkdev_device_lock,	aw_ccu_device_lock),
287	DEVMETHOD(clkdev_device_unlock,	aw_ccu_device_unlock),
288
289	DEVMETHOD_END
290};
291
292DEFINE_CLASS_1(aw_ccu, aw_ccu_driver, aw_ccu_methods,
293    sizeof(struct aw_ccu_softc), simplebus_driver);
294
295static devclass_t aw_ccu_devclass;
296
297EARLY_DRIVER_MODULE(aw_ccu, simplebus, aw_ccu_driver, aw_ccu_devclass,
298    0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
299
300MODULE_VERSION(aw_ccu, 1);
301