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: stable/11/sys/arm/allwinner/clk/aw_hdmiclk.c 308324 2016-11-05 04:17:32Z mmel $
27297627Sjmcneill */
28297627Sjmcneill
29297627Sjmcneill/*
30297627Sjmcneill * Allwinner HDMI clock
31297627Sjmcneill */
32297627Sjmcneill
33297627Sjmcneill#include <sys/cdefs.h>
34297627Sjmcneill__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/clk/aw_hdmiclk.c 308324 2016-11-05 04:17:32Z mmel $");
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_mux.h>
49297627Sjmcneill#include <dev/extres/clk/clk_gate.h>
50297627Sjmcneill
51297627Sjmcneill#include "clkdev_if.h"
52297627Sjmcneill
53297627Sjmcneill#define	SCLK_GATING		(1 << 31)
54297627Sjmcneill#define	CLK_SRC_SEL		(0x3 << 24)
55297627Sjmcneill#define	CLK_SRC_SEL_SHIFT	24
56297627Sjmcneill#define	CLK_SRC_SEL_MAX		0x3
57297627Sjmcneill#define	CLK_RATIO_N		(0x3 << 16)
58297627Sjmcneill#define	CLK_RATIO_N_SHIFT	16
59297627Sjmcneill#define	CLK_RATIO_N_MAX		0x3
60297627Sjmcneill#define	CLK_RATIO_M		(0x1f << 0)
61297627Sjmcneill#define	CLK_RATIO_M_SHIFT	0
62297627Sjmcneill#define	CLK_RATIO_M_MAX		0x1f
63297627Sjmcneill
64297627Sjmcneill#define	CLK_IDX_PLL3_1X		0
65297627Sjmcneill
66297627Sjmcneillstatic struct ofw_compat_data compat_data[] = {
67297627Sjmcneill	{ "allwinner,sun4i-a10-hdmi-clk",	1 },
68297627Sjmcneill	{ NULL, 0 }
69297627Sjmcneill};
70297627Sjmcneill
71297627Sjmcneillstruct aw_hdmiclk_sc {
72297627Sjmcneill	device_t	clkdev;
73297627Sjmcneill	bus_addr_t	reg;
74297627Sjmcneill};
75297627Sjmcneill
76297627Sjmcneill#define	HDMICLK_READ(sc, val)	CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val))
77297627Sjmcneill#define	HDMICLK_WRITE(sc, val)	CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val))
78297627Sjmcneill#define	DEVICE_LOCK(sc)		CLKDEV_DEVICE_LOCK((sc)->clkdev)
79297627Sjmcneill#define	DEVICE_UNLOCK(sc)	CLKDEV_DEVICE_UNLOCK((sc)->clkdev)
80297627Sjmcneill
81297627Sjmcneillstatic int
82297627Sjmcneillaw_hdmiclk_init(struct clknode *clk, device_t dev)
83297627Sjmcneill{
84297627Sjmcneill	struct aw_hdmiclk_sc *sc;
85297627Sjmcneill	uint32_t val, index;
86297627Sjmcneill
87297627Sjmcneill	sc = clknode_get_softc(clk);
88297627Sjmcneill
89297627Sjmcneill	/* Select PLL3(1X) clock source */
90297627Sjmcneill	index = CLK_IDX_PLL3_1X;
91297627Sjmcneill
92297627Sjmcneill	DEVICE_LOCK(sc);
93297627Sjmcneill	HDMICLK_READ(sc, &val);
94297627Sjmcneill	val &= ~CLK_SRC_SEL;
95297627Sjmcneill	val |= (index << CLK_SRC_SEL_SHIFT);
96297627Sjmcneill	HDMICLK_WRITE(sc, val);
97297627Sjmcneill	DEVICE_UNLOCK(sc);
98297627Sjmcneill
99297627Sjmcneill	clknode_init_parent_idx(clk, index);
100297627Sjmcneill	return (0);
101297627Sjmcneill}
102297627Sjmcneill
103297627Sjmcneillstatic int
104297627Sjmcneillaw_hdmiclk_set_mux(struct clknode *clk, int index)
105297627Sjmcneill{
106297627Sjmcneill	struct aw_hdmiclk_sc *sc;
107297627Sjmcneill	uint32_t val;
108297627Sjmcneill
109297627Sjmcneill	sc = clknode_get_softc(clk);
110297627Sjmcneill
111297627Sjmcneill	if (index < 0 || index > CLK_SRC_SEL_MAX)
112297627Sjmcneill		return (ERANGE);
113297627Sjmcneill
114297627Sjmcneill	DEVICE_LOCK(sc);
115297627Sjmcneill	HDMICLK_READ(sc, &val);
116297627Sjmcneill	val &= ~CLK_SRC_SEL;
117297627Sjmcneill	val |= (index << CLK_SRC_SEL_SHIFT);
118297627Sjmcneill	HDMICLK_WRITE(sc, val);
119297627Sjmcneill	DEVICE_UNLOCK(sc);
120297627Sjmcneill
121297627Sjmcneill	return (0);
122297627Sjmcneill}
123297627Sjmcneill
124297627Sjmcneillstatic int
125297627Sjmcneillaw_hdmiclk_set_gate(struct clknode *clk, bool enable)
126297627Sjmcneill{
127297627Sjmcneill	struct aw_hdmiclk_sc *sc;
128297627Sjmcneill	uint32_t val;
129297627Sjmcneill
130297627Sjmcneill	sc = clknode_get_softc(clk);
131297627Sjmcneill
132297627Sjmcneill	DEVICE_LOCK(sc);
133297627Sjmcneill	HDMICLK_READ(sc, &val);
134297627Sjmcneill	if (enable)
135297627Sjmcneill		val |= SCLK_GATING;
136297627Sjmcneill	else
137297627Sjmcneill		val &= ~SCLK_GATING;
138297627Sjmcneill	HDMICLK_WRITE(sc, val);
139297627Sjmcneill	DEVICE_UNLOCK(sc);
140297627Sjmcneill
141297627Sjmcneill	return (0);
142297627Sjmcneill}
143297627Sjmcneill
144297627Sjmcneillstatic int
145297627Sjmcneillaw_hdmiclk_recalc_freq(struct clknode *clk, uint64_t *freq)
146297627Sjmcneill{
147297627Sjmcneill	struct aw_hdmiclk_sc *sc;
148297627Sjmcneill	uint32_t val, m, n;
149297627Sjmcneill
150297627Sjmcneill	sc = clknode_get_softc(clk);
151297627Sjmcneill
152297627Sjmcneill	DEVICE_LOCK(sc);
153297627Sjmcneill	HDMICLK_READ(sc, &val);
154297627Sjmcneill	DEVICE_UNLOCK(sc);
155297627Sjmcneill
156297627Sjmcneill	n = 1 << ((val & CLK_RATIO_N) >> CLK_RATIO_N_SHIFT);
157297627Sjmcneill	m = ((val & CLK_RATIO_M) >> CLK_RATIO_M_SHIFT) + 1;
158297627Sjmcneill
159297627Sjmcneill	*freq = *freq / n / m;
160297627Sjmcneill
161297627Sjmcneill	return (0);
162297627Sjmcneill}
163297627Sjmcneill
164297627Sjmcneillstatic int
165297627Sjmcneillaw_hdmiclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
166297627Sjmcneill    int flags, int *stop)
167297627Sjmcneill{
168297627Sjmcneill	struct aw_hdmiclk_sc *sc;
169297627Sjmcneill	uint32_t val, m, n, best_m, best_n;
170297627Sjmcneill	uint64_t cur_freq;
171297627Sjmcneill	int64_t best_diff, cur_diff;
172297627Sjmcneill
173297627Sjmcneill	sc = clknode_get_softc(clk);
174297627Sjmcneill	best_n = best_m = 0;
175297627Sjmcneill	best_diff = (int64_t)*fout;
176297627Sjmcneill
177297627Sjmcneill	for (n = 0; n <= CLK_RATIO_N_MAX; n++)
178297627Sjmcneill		for (m = 0; m <= CLK_RATIO_M_MAX; m++) {
179297627Sjmcneill			cur_freq = fin / (1 << n) / (m + 1);
180297627Sjmcneill			cur_diff = (int64_t)*fout - cur_freq;
181297627Sjmcneill			if (cur_diff >= 0 && cur_diff < best_diff) {
182297627Sjmcneill				best_diff = cur_diff;
183297627Sjmcneill				best_m = m;
184297627Sjmcneill				best_n = n;
185297627Sjmcneill			}
186297627Sjmcneill		}
187297627Sjmcneill
188297627Sjmcneill	if (best_diff == (int64_t)*fout)
189297627Sjmcneill		return (ERANGE);
190297627Sjmcneill
191297627Sjmcneill	DEVICE_LOCK(sc);
192297627Sjmcneill	HDMICLK_READ(sc, &val);
193297627Sjmcneill	val &= ~(CLK_RATIO_N | CLK_RATIO_M);
194297627Sjmcneill	val |= (best_n << CLK_RATIO_N_SHIFT);
195297627Sjmcneill	val |= (best_m << CLK_RATIO_M_SHIFT);
196297627Sjmcneill	HDMICLK_WRITE(sc, val);
197297627Sjmcneill	DEVICE_UNLOCK(sc);
198297627Sjmcneill
199297627Sjmcneill	*fout = fin / (1 << best_n) / (best_m + 1);
200297627Sjmcneill	*stop = 1;
201297627Sjmcneill
202297627Sjmcneill	return (0);
203297627Sjmcneill}
204297627Sjmcneill
205297627Sjmcneillstatic clknode_method_t aw_hdmiclk_clknode_methods[] = {
206297627Sjmcneill	/* Device interface */
207297627Sjmcneill	CLKNODEMETHOD(clknode_init,		aw_hdmiclk_init),
208297627Sjmcneill	CLKNODEMETHOD(clknode_set_gate,		aw_hdmiclk_set_gate),
209297627Sjmcneill	CLKNODEMETHOD(clknode_set_mux,		aw_hdmiclk_set_mux),
210297627Sjmcneill	CLKNODEMETHOD(clknode_recalc_freq,	aw_hdmiclk_recalc_freq),
211297627Sjmcneill	CLKNODEMETHOD(clknode_set_freq,		aw_hdmiclk_set_freq),
212297627Sjmcneill	CLKNODEMETHOD_END
213297627Sjmcneill};
214297627SjmcneillDEFINE_CLASS_1(aw_hdmiclk_clknode, aw_hdmiclk_clknode_class,
215297627Sjmcneill    aw_hdmiclk_clknode_methods, sizeof(struct aw_hdmiclk_sc), clknode_class);
216297627Sjmcneill
217297627Sjmcneillstatic int
218297627Sjmcneillaw_hdmiclk_probe(device_t dev)
219297627Sjmcneill{
220297627Sjmcneill	if (!ofw_bus_status_okay(dev))
221297627Sjmcneill		return (ENXIO);
222297627Sjmcneill
223297627Sjmcneill	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
224297627Sjmcneill		return (ENXIO);
225297627Sjmcneill
226297627Sjmcneill	device_set_desc(dev, "Allwinner HDMI Clock");
227297627Sjmcneill	return (BUS_PROBE_DEFAULT);
228297627Sjmcneill}
229297627Sjmcneill
230297627Sjmcneillstatic int
231297627Sjmcneillaw_hdmiclk_attach(device_t dev)
232297627Sjmcneill{
233297627Sjmcneill	struct clknode_init_def def;
234297627Sjmcneill	struct aw_hdmiclk_sc *sc;
235297627Sjmcneill	struct clkdom *clkdom;
236297627Sjmcneill	struct clknode *clk;
237297627Sjmcneill	clk_t clk_parent;
238297627Sjmcneill	bus_addr_t paddr;
239297627Sjmcneill	bus_size_t psize;
240297627Sjmcneill	phandle_t node;
241297627Sjmcneill	int error;
242297627Sjmcneill
243297627Sjmcneill	node = ofw_bus_get_node(dev);
244297627Sjmcneill
245297627Sjmcneill	if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) {
246297627Sjmcneill		device_printf(dev, "cannot parse 'reg' property\n");
247297627Sjmcneill		return (ENXIO);
248297627Sjmcneill	}
249297627Sjmcneill
250297627Sjmcneill	clkdom = clkdom_create(dev);
251297627Sjmcneill
252308324Smmel	error = clk_get_by_ofw_index(dev, 0, 0, &clk_parent);
253297627Sjmcneill	if (error != 0) {
254297627Sjmcneill		device_printf(dev, "cannot parse clock parent\n");
255297627Sjmcneill		return (ENXIO);
256297627Sjmcneill	}
257297627Sjmcneill
258297627Sjmcneill	memset(&def, 0, sizeof(def));
259297627Sjmcneill	error = clk_parse_ofw_clk_name(dev, node, &def.name);
260297627Sjmcneill	if (error != 0) {
261297627Sjmcneill		device_printf(dev, "cannot parse clock name\n");
262297627Sjmcneill		error = ENXIO;
263297627Sjmcneill		goto fail;
264297627Sjmcneill	}
265297627Sjmcneill	def.id = 1;
266297627Sjmcneill	def.parent_names = malloc(sizeof(char *), M_OFWPROP, M_WAITOK);
267297627Sjmcneill	def.parent_names[0] = clk_get_name(clk_parent);
268297627Sjmcneill	def.parent_cnt = 1;
269297627Sjmcneill
270297627Sjmcneill	clk = clknode_create(clkdom, &aw_hdmiclk_clknode_class, &def);
271297627Sjmcneill	if (clk == NULL) {
272297627Sjmcneill		device_printf(dev, "cannot create clknode\n");
273297627Sjmcneill		error = ENXIO;
274297627Sjmcneill		goto fail;
275297627Sjmcneill	}
276297627Sjmcneill
277297627Sjmcneill	sc = clknode_get_softc(clk);
278297627Sjmcneill	sc->reg = paddr;
279297627Sjmcneill	sc->clkdev = device_get_parent(dev);
280297627Sjmcneill
281297627Sjmcneill	clknode_register(clkdom, clk);
282297627Sjmcneill
283297627Sjmcneill	if (clkdom_finit(clkdom) != 0) {
284297627Sjmcneill		device_printf(dev, "cannot finalize clkdom initialization\n");
285297627Sjmcneill		error = ENXIO;
286297627Sjmcneill		goto fail;
287297627Sjmcneill	}
288297627Sjmcneill
289297627Sjmcneill	if (bootverbose)
290297627Sjmcneill		clkdom_dump(clkdom);
291297627Sjmcneill
292297627Sjmcneill	return (0);
293297627Sjmcneill
294297627Sjmcneillfail:
295297627Sjmcneill	return (error);
296297627Sjmcneill}
297297627Sjmcneill
298297627Sjmcneillstatic device_method_t aw_hdmiclk_methods[] = {
299297627Sjmcneill	/* Device interface */
300297627Sjmcneill	DEVMETHOD(device_probe,		aw_hdmiclk_probe),
301297627Sjmcneill	DEVMETHOD(device_attach,	aw_hdmiclk_attach),
302297627Sjmcneill
303297627Sjmcneill	DEVMETHOD_END
304297627Sjmcneill};
305297627Sjmcneill
306297627Sjmcneillstatic driver_t aw_hdmiclk_driver = {
307297627Sjmcneill	"aw_hdmiclk",
308297627Sjmcneill	aw_hdmiclk_methods,
309297627Sjmcneill	0
310297627Sjmcneill};
311297627Sjmcneill
312297627Sjmcneillstatic devclass_t aw_hdmiclk_devclass;
313297627Sjmcneill
314297627SjmcneillEARLY_DRIVER_MODULE(aw_hdmiclk, simplebus, aw_hdmiclk_driver,
315297627Sjmcneill    aw_hdmiclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
316