aw_lcdclk.c revision 297627
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: head/sys/arm/allwinner/clk/aw_lcdclk.c 297627 2016-04-06 23:11:03Z jmcneill $
27 */
28
29/*
30 * Allwinner LCD clocks
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: head/sys/arm/allwinner/clk/aw_lcdclk.c 297627 2016-04-06 23:11:03Z 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/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.h>
49#include <dev/extres/hwreset/hwreset.h>
50
51#include "clkdev_if.h"
52#include "hwreset_if.h"
53
54/* CH0 */
55#define	CH0_SCLK_GATING			(1 << 31)
56#define	CH0_LCD_RST			(1 << 30)
57#define	CH0_CLK_SRC_SEL			(0x3 << 24)
58#define	CH0_CLK_SRC_SEL_SHIFT		24
59#define	CH0_CLK_SRC_SEL_PLL3_1X		0
60#define	CH0_CLK_SRC_SEL_PLL7_1X		1
61#define	CH0_CLK_SRC_SEL_PLL3_2X		2
62#define	CH0_CLK_SRC_SEL_PLL6		3
63
64/* CH1 */
65#define	CH1_SCLK2_GATING		(1 << 31)
66#define	CH1_SCLK2_SEL			(0x3 << 24)
67#define	CH1_SCLK2_SEL_SHIFT		24
68#define	CH1_SCLK2_SEL_PLL3_1X		0
69#define	CH1_SCLK2_SEL_PLL7_1X		1
70#define	CH1_SCLK2_SEL_PLL3_2X		2
71#define	CH1_SCLK2_SEL_PLL7_2X		3
72#define	CH1_SCLK1_GATING		(1 << 15)
73#define	CH1_SCLK1_SEL			(0x1 << 11)
74#define	CH1_SCLK1_SEL_SHIFT		11
75#define	CH1_SCLK1_SEL_SCLK2		0
76#define	CH1_SCLK1_SEL_SCLK2_DIV2	1
77#define	CH1_CLK_DIV_RATIO_M		(0x1f << 0)
78#define	CH1_CLK_DIV_RATIO_M_SHIFT	0
79
80#define	TCON_PLLREF			3000000ULL
81#define	TCON_PLL_M_MIN			1
82#define	TCON_PLL_M_MAX			15
83#define	TCON_PLL_N_MIN			9
84#define	TCON_PLL_N_MAX			127
85
86#define	CLK_IDX_CH1_SCLK1		0
87#define	CLK_IDX_CH1_SCLK2		1
88
89#define	CLK_IDX_
90
91enum aw_lcdclk_type {
92	AW_LCD_CH0 = 1,
93	AW_LCD_CH1,
94};
95
96static struct ofw_compat_data compat_data[] = {
97	{ "allwinner,sun4i-a10-lcd-ch0-clk",	AW_LCD_CH0 },
98	{ "allwinner,sun4i-a10-lcd-ch1-clk",	AW_LCD_CH1 },
99	{ NULL, 0 }
100};
101
102struct aw_lcdclk_softc {
103	enum aw_lcdclk_type	type;
104	device_t		clkdev;
105	bus_addr_t		reg;
106	int			id;
107};
108
109#define	LCDCLK_READ(sc, val)	CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val))
110#define	LCDCLK_WRITE(sc, val)	CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val))
111#define	LCDCLK_MODIFY(sc, clr, set)	\
112	CLKDEV_MODIFY_4((sc)->clkdev, (sc)->reg, (clr), (set))
113#define	DEVICE_LOCK(sc)		CLKDEV_DEVICE_LOCK((sc)->clkdev)
114#define	DEVICE_UNLOCK(sc)	CLKDEV_DEVICE_UNLOCK((sc)->clkdev)
115
116static int
117aw_lcdclk_hwreset_assert(device_t dev, intptr_t id, bool value)
118{
119	struct aw_lcdclk_softc *sc;
120	int error;
121
122	sc = device_get_softc(dev);
123
124	if (sc->type != AW_LCD_CH0)
125		return (ENXIO);
126
127	DEVICE_LOCK(sc);
128	error = LCDCLK_MODIFY(sc, CH0_LCD_RST, value ? 0 : CH0_LCD_RST);
129	DEVICE_UNLOCK(sc);
130
131	return (error);
132}
133
134static int
135aw_lcdclk_hwreset_is_asserted(device_t dev, intptr_t id, bool *value)
136{
137	struct aw_lcdclk_softc *sc;
138	uint32_t val;
139	int error;
140
141	sc = device_get_softc(dev);
142
143	if (sc->type != AW_LCD_CH0)
144		return (ENXIO);
145
146	DEVICE_LOCK(sc);
147	error = LCDCLK_READ(sc, &val);
148	DEVICE_UNLOCK(sc);
149
150	if (error)
151		return (error);
152
153	*value = (val & CH0_LCD_RST) != 0 ? false : true;
154
155	return (0);
156}
157
158static int
159aw_lcdclk_init(struct clknode *clk, device_t dev)
160{
161	struct aw_lcdclk_softc *sc;
162	uint32_t val, index;
163
164	sc = clknode_get_softc(clk);
165
166	DEVICE_LOCK(sc);
167	LCDCLK_READ(sc, &val);
168	DEVICE_UNLOCK(sc);
169
170	switch (sc->type) {
171	case AW_LCD_CH0:
172		index = (val & CH0_CLK_SRC_SEL) >> CH0_CLK_SRC_SEL_SHIFT;
173		break;
174	case AW_LCD_CH1:
175		switch (sc->id) {
176		case CLK_IDX_CH1_SCLK1:
177			index = 0;
178			break;
179		case CLK_IDX_CH1_SCLK2:
180			index = (val & CH1_SCLK2_SEL_SHIFT) >>
181			    CH1_SCLK2_SEL_SHIFT;
182			break;
183		default:
184			return (ENXIO);
185		}
186		break;
187	default:
188		return (ENXIO);
189	}
190
191	clknode_init_parent_idx(clk, index);
192	return (0);
193}
194
195static int
196aw_lcdclk_set_mux(struct clknode *clk, int index)
197{
198	struct aw_lcdclk_softc *sc;
199	uint32_t val;
200
201	sc = clknode_get_softc(clk);
202
203	switch (sc->type) {
204	case AW_LCD_CH0:
205		DEVICE_LOCK(sc);
206		LCDCLK_READ(sc, &val);
207		val &= ~CH0_CLK_SRC_SEL;
208		val |= (index << CH0_CLK_SRC_SEL_SHIFT);
209		LCDCLK_WRITE(sc, val);
210		DEVICE_UNLOCK(sc);
211		break;
212	case AW_LCD_CH1:
213		switch (sc->id) {
214		case CLK_IDX_CH1_SCLK2:
215			DEVICE_LOCK(sc);
216			LCDCLK_READ(sc, &val);
217			val &= ~CH1_SCLK2_SEL;
218			val |= (index << CH1_SCLK2_SEL_SHIFT);
219			LCDCLK_WRITE(sc, val);
220			DEVICE_UNLOCK(sc);
221			break;
222		default:
223			return (ENXIO);
224		}
225		break;
226	default:
227		return (ENXIO);
228	}
229
230	return (0);
231}
232
233static int
234aw_lcdclk_set_gate(struct clknode *clk, bool enable)
235{
236	struct aw_lcdclk_softc *sc;
237	uint32_t val, mask;
238
239	sc = clknode_get_softc(clk);
240
241	switch (sc->type) {
242	case AW_LCD_CH0:
243		mask = CH0_SCLK_GATING;
244		break;
245	case AW_LCD_CH1:
246		mask = (sc->id == CLK_IDX_CH1_SCLK1) ? CH1_SCLK1_GATING :
247		    CH1_SCLK2_GATING;
248		break;
249	default:
250		return (ENXIO);
251	}
252
253	DEVICE_LOCK(sc);
254	LCDCLK_READ(sc, &val);
255	if (enable)
256		val |= mask;
257	else
258		val &= ~mask;
259	LCDCLK_WRITE(sc, val);
260	DEVICE_UNLOCK(sc);
261
262	return (0);
263}
264
265static int
266aw_lcdclk_recalc_freq(struct clknode *clk, uint64_t *freq)
267{
268	struct aw_lcdclk_softc *sc;
269	uint32_t val, m;
270
271	sc = clknode_get_softc(clk);
272
273	if (sc->type != AW_LCD_CH1)
274		return (0);
275
276	DEVICE_LOCK(sc);
277	LCDCLK_READ(sc, &val);
278	DEVICE_UNLOCK(sc);
279
280	m = ((val & CH1_CLK_DIV_RATIO_M) >> CH1_CLK_DIV_RATIO_M_SHIFT) + 1;
281	*freq = *freq / m;
282
283	if (sc->id == CLK_IDX_CH1_SCLK1) {
284		if ((val & CH1_SCLK1_SEL) == CH1_SCLK1_SEL_SCLK2_DIV2)
285			*freq /= 2;
286	}
287
288	return (0);
289}
290
291static void
292calc_tcon_pll(uint64_t fin, uint64_t fout, uint32_t *pm, uint32_t *pn)
293{
294	int64_t diff, fcur, best;
295	int m, n;
296
297	best = fout;
298	for (m = TCON_PLL_M_MIN; m <= TCON_PLL_M_MAX; m++) {
299		for (n = TCON_PLL_N_MIN; n <= TCON_PLL_N_MAX; n++) {
300			fcur = (n * fin) / m;
301			diff = (int64_t)fout - fcur;
302			if (diff > 0 && diff < best) {
303				best = diff;
304				*pm = m;
305				*pn = n;
306			}
307		}
308	}
309}
310
311static int
312aw_lcdclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
313    int flags, int *stop)
314{
315	struct aw_lcdclk_softc *sc;
316	uint32_t val, m, m2, n, n2, src_sel;
317	uint64_t fsingle, fdouble;
318	int error;
319	bool dbl;
320
321	sc = clknode_get_softc(clk);
322
323	switch (sc->type) {
324	case AW_LCD_CH0:
325		*stop = 0;
326		break;
327	case AW_LCD_CH1:
328		if (sc->id != CLK_IDX_CH1_SCLK2)
329			return (ENXIO);
330
331		m = n = m2 = n2 = 0;
332		dbl = false;
333
334		/* Find the frequency closes to the target dot clock, using
335		 * both 1X and 2X PLL inputs as possible candidates.
336		 */
337		calc_tcon_pll(TCON_PLLREF, *fout, &m, &n);
338		calc_tcon_pll(TCON_PLLREF * 2, *fout, &m2, &n2);
339
340		fsingle = m ? (n * TCON_PLLREF) / m : 0;
341		fdouble = m2 ? (n2 * TCON_PLLREF * 2) / m2 : 0;
342
343		if (fdouble > fsingle) {
344			dbl = true;
345			m = m2;
346			n = n2;
347		}
348
349		src_sel = dbl ? CH0_CLK_SRC_SEL_PLL3_2X :
350		    CH0_CLK_SRC_SEL_PLL3_1X;
351
352		/* Switch parent clock if necessary */
353		if (src_sel != clknode_get_parent_idx(clk)) {
354			error = clknode_set_parent_by_idx(clk, src_sel);
355			if (error != 0)
356				return (error);
357		}
358
359		/* Set desired parent frequency */
360		fin = n * TCON_PLLREF;
361
362		error = clknode_set_freq(clknode_get_parent(clk), fin, 0, 0);
363		if (error != 0)
364			return (error);
365
366		error = clknode_enable(clknode_get_parent(clk));
367		if (error != 0)
368			return (error);
369
370		/* Fetch new input frequency */
371		error = clknode_get_freq(clknode_get_parent(clk), &fin);
372		if (error != 0)
373			return (error);
374
375		/* Set LCD divisor */
376		DEVICE_LOCK(sc);
377		LCDCLK_READ(sc, &val);
378		val &= ~CH1_CLK_DIV_RATIO_M;
379		val |= ((m - 1) << CH1_CLK_DIV_RATIO_M_SHIFT);
380		LCDCLK_WRITE(sc, val);
381		DEVICE_UNLOCK(sc);
382
383		*fout = fin / m;
384		*stop = 1;
385
386		break;
387	}
388
389	return (0);
390}
391
392static clknode_method_t aw_lcdclk_clknode_methods[] = {
393	/* Device interface */
394	CLKNODEMETHOD(clknode_init,		aw_lcdclk_init),
395	CLKNODEMETHOD(clknode_set_gate,		aw_lcdclk_set_gate),
396	CLKNODEMETHOD(clknode_set_mux,		aw_lcdclk_set_mux),
397	CLKNODEMETHOD(clknode_recalc_freq,	aw_lcdclk_recalc_freq),
398	CLKNODEMETHOD(clknode_set_freq,		aw_lcdclk_set_freq),
399	CLKNODEMETHOD_END
400};
401DEFINE_CLASS_1(aw_lcdclk_clknode, aw_lcdclk_clknode_class,
402    aw_lcdclk_clknode_methods, sizeof(struct aw_lcdclk_softc), clknode_class);
403
404static int
405aw_lcdclk_create(device_t dev, struct clkdom *clkdom,
406    const char **parent_names, int parent_cnt, const char *name, int index)
407{
408	struct aw_lcdclk_softc *sc, *clk_sc;
409	struct clknode_init_def def;
410	struct clknode *clk;
411	phandle_t node;
412
413	sc = device_get_softc(dev);
414	node = ofw_bus_get_node(dev);
415
416	memset(&def, 0, sizeof(def));
417	def.id = index;
418	def.name = name;
419	def.parent_names = parent_names;
420	def.parent_cnt = parent_cnt;
421
422	clk = clknode_create(clkdom, &aw_lcdclk_clknode_class, &def);
423	if (clk == NULL) {
424		device_printf(dev, "cannot create clknode\n");
425		return (ENXIO);
426	}
427
428	clk_sc = clknode_get_softc(clk);
429	clk_sc->type = sc->type;
430	clk_sc->reg = sc->reg;
431	clk_sc->clkdev = sc->clkdev;
432	clk_sc->id = index;
433
434	clknode_register(clkdom, clk);
435
436	return (0);
437}
438
439static int
440aw_lcdclk_probe(device_t dev)
441{
442	enum aw_lcdclk_type type;
443
444	if (!ofw_bus_status_okay(dev))
445		return (ENXIO);
446
447	type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
448	switch (type) {
449	case AW_LCD_CH0:
450		device_set_desc(dev, "Allwinner LCD CH0 Clock");
451		break;
452	case AW_LCD_CH1:
453		device_set_desc(dev, "Allwinner LCD CH1 Clock");
454		break;
455	default:
456		return (ENXIO);
457	}
458
459	return (BUS_PROBE_DEFAULT);
460}
461
462static int
463aw_lcdclk_attach(device_t dev)
464{
465	struct aw_lcdclk_softc *sc;
466	struct clkdom *clkdom;
467	clk_t clk_parent;
468	bus_size_t psize;
469	phandle_t node;
470	uint32_t *indices;
471	const char **parent_names;
472	const char **names;
473	int error, ncells, nout, i;
474
475	sc = device_get_softc(dev);
476	sc->clkdev = device_get_parent(dev);
477	sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
478
479	node = ofw_bus_get_node(dev);
480
481	if (ofw_reg_to_paddr(node, 0, &sc->reg, &psize, NULL) != 0) {
482		device_printf(dev, "cannot parse 'reg' property\n");
483		return (ENXIO);
484	}
485
486	error = ofw_bus_parse_xref_list_get_length(node, "clocks",
487	    "#clock-cells", &ncells);
488	if (error != 0) {
489		device_printf(dev, "cannot get clock count\n");
490		return (error);
491	}
492
493	parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK);
494	for (i = 0; i < ncells; i++) {
495		error = clk_get_by_ofw_index(dev, i, &clk_parent);
496		if (error != 0) {
497			device_printf(dev, "cannot get clock %d\n", i);
498			goto fail;
499		}
500		parent_names[i] = clk_get_name(clk_parent);
501		clk_release(clk_parent);
502	}
503
504	nout = clk_parse_ofw_out_names(dev, node, &names, &indices);
505	if (nout == 0) {
506		device_printf(dev, "no clock outputs found\n");
507		return (error);
508	}
509
510	clkdom = clkdom_create(dev);
511
512	for (i = 0; i < nout; i++) {
513		error = aw_lcdclk_create(dev, clkdom, parent_names, ncells,
514		    names[i], nout == 1 ? 1 : i);
515		if (error)
516			goto fail;
517	}
518
519	if (clkdom_finit(clkdom) != 0) {
520		device_printf(dev, "cannot finalize clkdom initialization\n");
521		error = ENXIO;
522		goto fail;
523	}
524
525	if (bootverbose)
526		clkdom_dump(clkdom);
527
528	if (sc->type == AW_LCD_CH0)
529		hwreset_register_ofw_provider(dev);
530
531	free(parent_names, M_OFWPROP);
532	return (0);
533
534fail:
535	free(parent_names, M_OFWPROP);
536	return (error);
537}
538
539static device_method_t aw_lcdclk_methods[] = {
540	/* Device interface */
541	DEVMETHOD(device_probe,		aw_lcdclk_probe),
542	DEVMETHOD(device_attach,	aw_lcdclk_attach),
543
544	/* Reset interface */
545	DEVMETHOD(hwreset_assert,	aw_lcdclk_hwreset_assert),
546	DEVMETHOD(hwreset_is_asserted,	aw_lcdclk_hwreset_is_asserted),
547
548	DEVMETHOD_END
549};
550
551static driver_t aw_lcdclk_driver = {
552	"aw_lcdclk",
553	aw_lcdclk_methods,
554	sizeof(struct aw_lcdclk_softc)
555};
556
557static devclass_t aw_lcdclk_devclass;
558
559EARLY_DRIVER_MODULE(aw_lcdclk, simplebus, aw_lcdclk_driver,
560    aw_lcdclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
561