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