aw_lcdclk.c revision 298634
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 298634 2016-04-26 12:36:12Z 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 298634 2016-04-26 12:36:12Z 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, src_sel;
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		src_sel = (val & CH1_SCLK1_SEL) >> CH1_SCLK1_SEL_SHIFT;
285		if (src_sel == CH1_SCLK1_SEL_SCLK2_DIV2)
286			*freq /= 2;
287	}
288
289	return (0);
290}
291
292static void
293calc_tcon_pll(uint64_t fin, uint64_t fout, uint32_t *pm, uint32_t *pn)
294{
295	int64_t diff, fcur, best;
296	int m, n;
297
298	best = fout;
299	for (m = TCON_PLL_M_MIN; m <= TCON_PLL_M_MAX; m++) {
300		for (n = TCON_PLL_N_MIN; n <= TCON_PLL_N_MAX; n++) {
301			fcur = (n * fin) / m;
302			diff = (int64_t)fout - fcur;
303			if (diff > 0 && diff < best) {
304				best = diff;
305				*pm = m;
306				*pn = n;
307			}
308		}
309	}
310}
311
312static int
313aw_lcdclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
314    int flags, int *stop)
315{
316	struct aw_lcdclk_softc *sc;
317	uint32_t val, m, m2, n, n2, src_sel;
318	uint64_t fsingle, fdouble;
319	int error;
320	bool dbl;
321
322	sc = clknode_get_softc(clk);
323
324	switch (sc->type) {
325	case AW_LCD_CH0:
326		*stop = 0;
327		break;
328	case AW_LCD_CH1:
329		if (sc->id != CLK_IDX_CH1_SCLK2)
330			return (ENXIO);
331
332		m = n = m2 = n2 = 0;
333		dbl = false;
334
335		/* Find the frequency closes to the target dot clock, using
336		 * both 1X and 2X PLL inputs as possible candidates.
337		 */
338		calc_tcon_pll(TCON_PLLREF, *fout, &m, &n);
339		calc_tcon_pll(TCON_PLLREF * 2, *fout, &m2, &n2);
340
341		fsingle = m ? (n * TCON_PLLREF) / m : 0;
342		fdouble = m2 ? (n2 * TCON_PLLREF * 2) / m2 : 0;
343
344		if (fdouble > fsingle) {
345			dbl = true;
346			m = m2;
347			n = n2;
348		}
349
350		src_sel = dbl ? CH0_CLK_SRC_SEL_PLL3_2X :
351		    CH0_CLK_SRC_SEL_PLL3_1X;
352
353		/* Switch parent clock if necessary */
354		if (src_sel != clknode_get_parent_idx(clk)) {
355			error = clknode_set_parent_by_idx(clk, src_sel);
356			if (error != 0)
357				return (error);
358		}
359
360		/* Set desired parent frequency */
361		fin = n * TCON_PLLREF;
362
363		error = clknode_set_freq(clknode_get_parent(clk), fin, 0, 0);
364		if (error != 0)
365			return (error);
366
367		error = clknode_enable(clknode_get_parent(clk));
368		if (error != 0)
369			return (error);
370
371		/* Fetch new input frequency */
372		error = clknode_get_freq(clknode_get_parent(clk), &fin);
373		if (error != 0)
374			return (error);
375
376		/* Set LCD divisor */
377		DEVICE_LOCK(sc);
378		LCDCLK_READ(sc, &val);
379		val &= ~CH1_CLK_DIV_RATIO_M;
380		val |= ((m - 1) << CH1_CLK_DIV_RATIO_M_SHIFT);
381		LCDCLK_WRITE(sc, val);
382		DEVICE_UNLOCK(sc);
383
384		*fout = fin / m;
385		*stop = 1;
386
387		break;
388	}
389
390	return (0);
391}
392
393static clknode_method_t aw_lcdclk_clknode_methods[] = {
394	/* Device interface */
395	CLKNODEMETHOD(clknode_init,		aw_lcdclk_init),
396	CLKNODEMETHOD(clknode_set_gate,		aw_lcdclk_set_gate),
397	CLKNODEMETHOD(clknode_set_mux,		aw_lcdclk_set_mux),
398	CLKNODEMETHOD(clknode_recalc_freq,	aw_lcdclk_recalc_freq),
399	CLKNODEMETHOD(clknode_set_freq,		aw_lcdclk_set_freq),
400	CLKNODEMETHOD_END
401};
402DEFINE_CLASS_1(aw_lcdclk_clknode, aw_lcdclk_clknode_class,
403    aw_lcdclk_clknode_methods, sizeof(struct aw_lcdclk_softc), clknode_class);
404
405static int
406aw_lcdclk_create(device_t dev, struct clkdom *clkdom,
407    const char **parent_names, int parent_cnt, const char *name, int index)
408{
409	struct aw_lcdclk_softc *sc, *clk_sc;
410	struct clknode_init_def def;
411	struct clknode *clk;
412	phandle_t node;
413
414	sc = device_get_softc(dev);
415	node = ofw_bus_get_node(dev);
416
417	memset(&def, 0, sizeof(def));
418	def.id = index;
419	def.name = name;
420	def.parent_names = parent_names;
421	def.parent_cnt = parent_cnt;
422
423	clk = clknode_create(clkdom, &aw_lcdclk_clknode_class, &def);
424	if (clk == NULL) {
425		device_printf(dev, "cannot create clknode\n");
426		return (ENXIO);
427	}
428
429	clk_sc = clknode_get_softc(clk);
430	clk_sc->type = sc->type;
431	clk_sc->reg = sc->reg;
432	clk_sc->clkdev = sc->clkdev;
433	clk_sc->id = index;
434
435	clknode_register(clkdom, clk);
436
437	return (0);
438}
439
440static int
441aw_lcdclk_probe(device_t dev)
442{
443	enum aw_lcdclk_type type;
444
445	if (!ofw_bus_status_okay(dev))
446		return (ENXIO);
447
448	type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
449	switch (type) {
450	case AW_LCD_CH0:
451		device_set_desc(dev, "Allwinner LCD CH0 Clock");
452		break;
453	case AW_LCD_CH1:
454		device_set_desc(dev, "Allwinner LCD CH1 Clock");
455		break;
456	default:
457		return (ENXIO);
458	}
459
460	return (BUS_PROBE_DEFAULT);
461}
462
463static int
464aw_lcdclk_attach(device_t dev)
465{
466	struct aw_lcdclk_softc *sc;
467	struct clkdom *clkdom;
468	clk_t clk_parent;
469	bus_size_t psize;
470	phandle_t node;
471	uint32_t *indices;
472	const char **parent_names;
473	const char **names;
474	int error, ncells, nout, i;
475
476	sc = device_get_softc(dev);
477	sc->clkdev = device_get_parent(dev);
478	sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
479
480	node = ofw_bus_get_node(dev);
481
482	if (ofw_reg_to_paddr(node, 0, &sc->reg, &psize, NULL) != 0) {
483		device_printf(dev, "cannot parse 'reg' property\n");
484		return (ENXIO);
485	}
486
487	error = ofw_bus_parse_xref_list_get_length(node, "clocks",
488	    "#clock-cells", &ncells);
489	if (error != 0) {
490		device_printf(dev, "cannot get clock count\n");
491		return (error);
492	}
493
494	parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK);
495	for (i = 0; i < ncells; i++) {
496		error = clk_get_by_ofw_index(dev, i, &clk_parent);
497		if (error != 0) {
498			device_printf(dev, "cannot get clock %d\n", i);
499			goto fail;
500		}
501		parent_names[i] = clk_get_name(clk_parent);
502		clk_release(clk_parent);
503	}
504
505	nout = clk_parse_ofw_out_names(dev, node, &names, &indices);
506	if (nout == 0) {
507		device_printf(dev, "no clock outputs found\n");
508		return (error);
509	}
510
511	clkdom = clkdom_create(dev);
512
513	for (i = 0; i < nout; i++) {
514		error = aw_lcdclk_create(dev, clkdom, parent_names, ncells,
515		    names[i], nout == 1 ? 1 : i);
516		if (error)
517			goto fail;
518	}
519
520	if (clkdom_finit(clkdom) != 0) {
521		device_printf(dev, "cannot finalize clkdom initialization\n");
522		error = ENXIO;
523		goto fail;
524	}
525
526	if (bootverbose)
527		clkdom_dump(clkdom);
528
529	if (sc->type == AW_LCD_CH0)
530		hwreset_register_ofw_provider(dev);
531
532	free(parent_names, M_OFWPROP);
533	return (0);
534
535fail:
536	free(parent_names, M_OFWPROP);
537	return (error);
538}
539
540static device_method_t aw_lcdclk_methods[] = {
541	/* Device interface */
542	DEVMETHOD(device_probe,		aw_lcdclk_probe),
543	DEVMETHOD(device_attach,	aw_lcdclk_attach),
544
545	/* Reset interface */
546	DEVMETHOD(hwreset_assert,	aw_lcdclk_hwreset_assert),
547	DEVMETHOD(hwreset_is_asserted,	aw_lcdclk_hwreset_is_asserted),
548
549	DEVMETHOD_END
550};
551
552static driver_t aw_lcdclk_driver = {
553	"aw_lcdclk",
554	aw_lcdclk_methods,
555	sizeof(struct aw_lcdclk_softc)
556};
557
558static devclass_t aw_lcdclk_devclass;
559
560EARLY_DRIVER_MODULE(aw_lcdclk, simplebus, aw_lcdclk_driver,
561    aw_lcdclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
562