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_modclk.c 309767 2016-12-09 20:52:48Z manu $
27 */
28
29/*
30 * Allwinner module clocks
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/clk/aw_modclk.c 309767 2016-12-09 20:52:48Z manu $");
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_mux.h>
49#include <dev/extres/clk/clk_gate.h>
50
51#include "clkdev_if.h"
52
53#define	SCLK_GATING		(1 << 31)
54#define	CLK_SRC_SEL		(0x3 << 24)
55#define	CLK_SRC_SEL_SHIFT	24
56#define	CLK_RATIO_N		(0x3 << 16)
57#define	CLK_RATIO_N_SHIFT	16
58#define	CLK_RATIO_N_MAX		0x3
59#define	CLK_RATIO_M		(0x1f << 0)
60#define	CLK_RATIO_M_SHIFT	0
61#define	CLK_RATIO_M_MAX		0x1f
62
63static struct ofw_compat_data compat_data[] = {
64	{ "allwinner,sun4i-a10-mod0-clk",	1 },
65	{ NULL, 0 }
66};
67
68struct aw_modclk_sc {
69	device_t	clkdev;
70	bus_addr_t	reg;
71	u_int		parent_cnt;
72};
73
74#define	MODCLK_READ(sc, val)	CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val))
75#define	MODCLK_WRITE(sc, val)	CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val))
76#define	DEVICE_LOCK(sc)		CLKDEV_DEVICE_LOCK((sc)->clkdev)
77#define	DEVICE_UNLOCK(sc)	CLKDEV_DEVICE_UNLOCK((sc)->clkdev)
78
79static int
80aw_modclk_init(struct clknode *clk, device_t dev)
81{
82	struct aw_modclk_sc *sc;
83	uint32_t val, index;
84
85	sc = clknode_get_softc(clk);
86
87	DEVICE_LOCK(sc);
88	MODCLK_READ(sc, &val);
89	DEVICE_UNLOCK(sc);
90
91	index = (val & CLK_SRC_SEL) >> CLK_SRC_SEL_SHIFT;
92
93	clknode_init_parent_idx(clk, index);
94	return (0);
95}
96
97static int
98aw_modclk_set_mux(struct clknode *clk, int index)
99{
100	struct aw_modclk_sc *sc;
101	uint32_t val;
102
103	sc = clknode_get_softc(clk);
104
105	if (index < 0 || index >= sc->parent_cnt)
106		return (ERANGE);
107
108	DEVICE_LOCK(sc);
109	MODCLK_READ(sc, &val);
110	val &= ~CLK_SRC_SEL;
111	val |= (index << CLK_SRC_SEL_SHIFT);
112	MODCLK_WRITE(sc, val);
113	DEVICE_UNLOCK(sc);
114
115	return (0);
116}
117
118static int
119aw_modclk_set_gate(struct clknode *clk, bool enable)
120{
121	struct aw_modclk_sc *sc;
122	uint32_t val;
123
124	sc = clknode_get_softc(clk);
125
126	DEVICE_LOCK(sc);
127	MODCLK_READ(sc, &val);
128	if (enable)
129		val |= SCLK_GATING;
130	else
131		val &= ~SCLK_GATING;
132	MODCLK_WRITE(sc, val);
133	DEVICE_UNLOCK(sc);
134
135	return (0);
136}
137
138static int
139aw_modclk_recalc_freq(struct clknode *clk, uint64_t *freq)
140{
141	struct aw_modclk_sc *sc;
142	uint32_t val, m, n;
143
144	sc = clknode_get_softc(clk);
145
146	DEVICE_LOCK(sc);
147	MODCLK_READ(sc, &val);
148	DEVICE_UNLOCK(sc);
149
150	n = 1 << ((val & CLK_RATIO_N) >> CLK_RATIO_N_SHIFT);
151	m = ((val & CLK_RATIO_M) >> CLK_RATIO_M_SHIFT) + 1;
152
153	*freq = *freq / n / m;
154
155	return (0);
156}
157
158static int
159aw_modclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
160    int flags, int *stop)
161{
162	struct aw_modclk_sc *sc;
163	uint32_t val, m, n, src, best_m, best_n, best_src;
164	uint64_t cur_freq;
165	int64_t best_diff, cur_diff;
166	int error;
167
168	sc = clknode_get_softc(clk);
169	best_n = best_m = 0;
170	best_diff = (int64_t)*fout;
171	best_src = 0;
172
173	for (src = 0; src < sc->parent_cnt; src++) {
174		error = clknode_set_parent_by_idx(clk, src);
175		if (error != 0)
176			continue;
177		error = clknode_get_freq(clknode_get_parent(clk), &fin);
178		if (error != 0)
179			continue;
180
181		for (n = 0; n <= CLK_RATIO_N_MAX; n++)
182			for (m = 0; m <= CLK_RATIO_M_MAX; m++) {
183				cur_freq = fin / (1 << n) / (m + 1);
184				cur_diff = (int64_t)*fout - cur_freq;
185				if (cur_diff >= 0 && cur_diff < best_diff) {
186					best_src = src;
187					best_diff = cur_diff;
188					best_m = m;
189					best_n = n;
190				}
191			}
192	}
193
194	if (best_diff == (int64_t)*fout)
195		return (ERANGE);
196
197	error = clknode_set_parent_by_idx(clk, best_src);
198	if (error != 0)
199		return (error);
200	error = clknode_get_freq(clknode_get_parent(clk), &fin);
201	if (error != 0)
202		return (error);
203
204	DEVICE_LOCK(sc);
205	MODCLK_READ(sc, &val);
206	val &= ~(CLK_RATIO_N | CLK_RATIO_M);
207	val |= (best_n << CLK_RATIO_N_SHIFT);
208	val |= (best_m << CLK_RATIO_M_SHIFT);
209	MODCLK_WRITE(sc, val);
210	DEVICE_UNLOCK(sc);
211
212	*fout = fin / (1 << best_n) / (best_m + 1);
213	*stop = 1;
214
215	return (0);
216}
217
218static clknode_method_t aw_modclk_clknode_methods[] = {
219	/* Device interface */
220	CLKNODEMETHOD(clknode_init,		aw_modclk_init),
221	CLKNODEMETHOD(clknode_set_gate,		aw_modclk_set_gate),
222	CLKNODEMETHOD(clknode_set_mux,		aw_modclk_set_mux),
223	CLKNODEMETHOD(clknode_recalc_freq,	aw_modclk_recalc_freq),
224	CLKNODEMETHOD(clknode_set_freq,		aw_modclk_set_freq),
225	CLKNODEMETHOD_END
226};
227DEFINE_CLASS_1(aw_modclk_clknode, aw_modclk_clknode_class,
228    aw_modclk_clknode_methods, sizeof(struct aw_modclk_sc), clknode_class);
229
230static int
231aw_modclk_probe(device_t dev)
232{
233	if (!ofw_bus_status_okay(dev))
234		return (ENXIO);
235
236	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
237		return (ENXIO);
238
239	device_set_desc(dev, "Allwinner Module Clock");
240	return (BUS_PROBE_DEFAULT);
241}
242
243static int
244aw_modclk_attach(device_t dev)
245{
246	struct clknode_init_def def;
247	struct aw_modclk_sc *sc;
248	struct clkdom *clkdom;
249	struct clknode *clk;
250	clk_t clk_parent;
251	bus_addr_t paddr;
252	bus_size_t psize;
253	phandle_t node;
254	int error, ncells, i;
255
256	node = ofw_bus_get_node(dev);
257
258	if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) {
259		device_printf(dev, "cannot parse 'reg' property\n");
260		return (ENXIO);
261	}
262
263	error = ofw_bus_parse_xref_list_get_length(node, "clocks",
264	    "#clock-cells", &ncells);
265	if (error != 0) {
266		device_printf(dev, "cannot get clock count\n");
267		return (error);
268	}
269
270	clkdom = clkdom_create(dev);
271
272	memset(&def, 0, sizeof(def));
273	error = clk_parse_ofw_clk_name(dev, node, &def.name);
274	if (error != 0) {
275		device_printf(dev, "cannot parse clock name\n");
276		error = ENXIO;
277		goto fail;
278	}
279	def.id = 1;
280	def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK);
281	for (i = 0; i < ncells; i++) {
282		error = clk_get_by_ofw_index(dev, 0, i, &clk_parent);
283		if (error != 0) {
284			device_printf(dev, "cannot get clock %d\n", i);
285			goto fail;
286		}
287		def.parent_names[i] = clk_get_name(clk_parent);
288		clk_release(clk_parent);
289	}
290	def.parent_cnt = ncells;
291
292	clk = clknode_create(clkdom, &aw_modclk_clknode_class, &def);
293	if (clk == NULL) {
294		device_printf(dev, "cannot create clknode\n");
295		error = ENXIO;
296		goto fail;
297	}
298
299	sc = clknode_get_softc(clk);
300	sc->reg = paddr;
301	sc->clkdev = device_get_parent(dev);
302	sc->parent_cnt = def.parent_cnt;
303
304	clknode_register(clkdom, clk);
305
306	if (clkdom_finit(clkdom) != 0) {
307		device_printf(dev, "cannot finalize clkdom initialization\n");
308		error = ENXIO;
309		goto fail;
310	}
311
312	if (bootverbose)
313		clkdom_dump(clkdom);
314
315	return (0);
316
317fail:
318	return (error);
319}
320
321static device_method_t aw_modclk_methods[] = {
322	/* Device interface */
323	DEVMETHOD(device_probe,		aw_modclk_probe),
324	DEVMETHOD(device_attach,	aw_modclk_attach),
325
326	DEVMETHOD_END
327};
328
329static driver_t aw_modclk_driver = {
330	"aw_modclk",
331	aw_modclk_methods,
332	0
333};
334
335static devclass_t aw_modclk_devclass;
336
337EARLY_DRIVER_MODULE(aw_modclk, simplebus, aw_modclk_driver,
338    aw_modclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
339