xref: /netbsd-src/sys/arch/arm/sunxi/sunxi_tcon.c (revision c38e7cc395b1472a774ff828e46123de44c628e9)
1 /* $NetBSD: sunxi_tcon.c,v 1.6 2018/04/07 18:09:33 bouyer Exp $ */
2 
3 /*-
4  * Copyright (c) 2018 Manuel Bouyer <bouyer@antioche.eu.org>
5  * All rights reserved.
6  *
7  * Copyright (c) 2014 Jared D. McNeill <jmcneill@invisible.ca>
8  * All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 __KERNEL_RCSID(0, "$NetBSD: sunxi_tcon.c,v 1.6 2018/04/07 18:09:33 bouyer Exp $");
34 
35 #include <sys/param.h>
36 #include <sys/bus.h>
37 #include <sys/device.h>
38 #include <sys/intr.h>
39 #include <sys/systm.h>
40 #include <sys/kernel.h>
41 #include <sys/mutex.h>
42 #include <sys/condvar.h>
43 
44 #include <dev/fdt/fdtvar.h>
45 #include <dev/fdt/fdt_port.h>
46 #include <dev/fdt/panel_fdt.h>
47 
48 #include <dev/videomode/videomode.h>
49 
50 #include <arm/sunxi/sunxi_tconreg.h>
51 #include <arm/sunxi/sunxi_display.h>
52 
53 #define DIVIDE(x,y)     (((x) + ((y) / 2)) / (y))
54 
55 enum sunxi_tcon_type {
56 	TCON_A10 = 1,
57 };
58 
59 struct sunxi_tcon_softc {
60 	device_t sc_dev;
61 	enum sunxi_tcon_type sc_type;
62 	int sc_phandle;
63 	bus_space_tag_t sc_bst;
64 	bus_space_handle_t sc_bsh;
65 	struct clk *sc_clk_ahb;
66 	struct clk *sc_clk_ch0;
67 	struct clk *sc_clk_ch1;
68 	unsigned int sc_output_type;
69 #define OUTPUT_HDMI 0
70 #define OUTPUT_LVDS 1
71 #define OUTPUT_VGA 2
72 	struct fdt_device_ports sc_ports;
73 	int sc_unit; /* tcon0 or tcon1 */
74 	struct fdt_endpoint *sc_in_ep;
75 	struct fdt_endpoint *sc_in_rep;
76 	struct fdt_endpoint *sc_out_ep;
77 };
78 
79 static bus_space_handle_t tcon_mux_bsh;
80 static bool tcon_mux_inited = false;
81 
82 static void sunxi_tcon_ep_connect(device_t, struct fdt_endpoint *, bool);
83 static int  sunxi_tcon_ep_activate(device_t, struct fdt_endpoint *, bool);
84 static int  sunxi_tcon_ep_enable(device_t, struct fdt_endpoint *, bool);
85 static int  sunxi_tcon0_set_video(struct sunxi_tcon_softc *);
86 static int  sunxi_tcon0_enable(struct sunxi_tcon_softc *, bool);
87 static int  sunxi_tcon1_enable(struct sunxi_tcon_softc *, bool);
88 void sunxi_tcon_dump_regs(int);
89 
90 #define TCON_READ(sc, reg) \
91     bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
92 #define TCON_WRITE(sc, reg, val) \
93     bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
94 
95 static const struct of_compat_data compat_data[] = {
96 	{"allwinner,sun4i-a10-tcon", TCON_A10},
97 	{"allwinner,sun7i-a20-tcon", TCON_A10},
98 	{NULL}
99 };
100 
101 static int	sunxi_tcon_match(device_t, cfdata_t, void *);
102 static void	sunxi_tcon_attach(device_t, device_t, void *);
103 
104 CFATTACH_DECL_NEW(sunxi_tcon, sizeof(struct sunxi_tcon_softc),
105 	sunxi_tcon_match, sunxi_tcon_attach, NULL, NULL);
106 
107 static int
108 sunxi_tcon_match(device_t parent, cfdata_t cf, void *aux)
109 {
110 	struct fdt_attach_args * const faa = aux;
111 
112 	return of_match_compat_data(faa->faa_phandle, compat_data);
113 }
114 
115 static void
116 sunxi_tcon_attach(device_t parent, device_t self, void *aux)
117 {
118 	struct sunxi_tcon_softc *sc = device_private(self);
119 	struct fdt_attach_args * const faa = aux;
120 	const int phandle = faa->faa_phandle;
121 	bus_addr_t addr;
122 	bus_size_t size;
123 	struct fdtbus_reset *rst, *lvds_rst;
124 
125 
126 	sc->sc_dev = self;
127 	sc->sc_phandle = phandle;
128 	sc->sc_bst = faa->faa_bst;
129 
130 	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
131 		aprint_error(": couldn't get registers\n");
132 	}
133 	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
134 		aprint_error(": couldn't map registers\n");
135 		return;
136 	}
137 
138 	sc->sc_clk_ahb = fdtbus_clock_get(phandle, "ahb");
139 	sc->sc_clk_ch0 = fdtbus_clock_get(phandle, "tcon-ch0");
140 	sc->sc_clk_ch1 = fdtbus_clock_get(phandle, "tcon-ch1");
141 
142 	if (sc->sc_clk_ahb == NULL || sc->sc_clk_ch0 == NULL
143 	    || sc->sc_clk_ch1 == NULL) {
144 		aprint_error(": couldn't get clocks\n");
145 		aprint_debug_dev(self, "clk ahb %s tcon-ch0 %s tcon-ch1 %s\n",
146 		    sc->sc_clk_ahb == NULL ? "missing" : "present",
147 		    sc->sc_clk_ch0 == NULL ? "missing" : "present",
148 		    sc->sc_clk_ch1 == NULL ? "missing" : "present");
149 		return;
150 	}
151 
152 	rst = fdtbus_reset_get(phandle, "lcd");
153 	if (rst == NULL) {
154 		aprint_error(": couldn't get lcd reset\n");
155 		return;
156 	}
157 
158 	lvds_rst = fdtbus_reset_get(phandle, "lvds");
159 
160 	if (clk_disable(sc->sc_clk_ahb) != 0) {
161 		aprint_error(": couldn't disable ahb clock\n");
162 		return;
163 	}
164 	if (clk_disable(sc->sc_clk_ch0) != 0) {
165 		aprint_error(": couldn't disable ch0 clock\n");
166 		return;
167 	}
168 
169 	if (clk_disable(sc->sc_clk_ch1) != 0) {
170 		aprint_error(": couldn't disable ch1 clock\n");
171 		return;
172 	}
173 
174 	if (fdtbus_reset_assert(rst) != 0) {
175 		aprint_error(": couldn't assert lcd reset\n");
176 		return;
177 	}
178 	if (lvds_rst != NULL) {
179 		if (fdtbus_reset_assert(lvds_rst) != 0) {
180 			aprint_error(": couldn't assert lvds reset\n");
181 			return;
182 		}
183 	}
184 	delay(1);
185 	if (fdtbus_reset_deassert(rst) != 0) {
186 		aprint_error(": couldn't de-assert lcd reset\n");
187 		return;
188 	}
189 	if (lvds_rst != NULL) {
190 		if (fdtbus_reset_deassert(lvds_rst) != 0) {
191 			aprint_error(": couldn't de-assert lvds reset\n");
192 			return;
193 		}
194 	}
195 
196 	if (clk_enable(sc->sc_clk_ahb) != 0) {
197 		aprint_error(": couldn't enable ahb clock\n");
198 		return;
199 	}
200 
201 	sc->sc_type = of_search_compatible(faa->faa_phandle, compat_data)->data;
202 
203 	aprint_naive("\n");
204 	aprint_normal(": LCD/TV timing controller (%s)\n",
205 	    fdtbus_get_string(phandle, "name"));
206 
207 	sc->sc_unit = -1;
208 	sc->sc_ports.dp_ep_connect = sunxi_tcon_ep_connect;
209 	sc->sc_ports.dp_ep_activate = sunxi_tcon_ep_activate;
210 	sc->sc_ports.dp_ep_enable = sunxi_tcon_ep_enable;
211 	fdt_ports_register(&sc->sc_ports, self, phandle, EP_OTHER);
212 
213 	TCON_WRITE(sc, SUNXI_TCON_GINT0_REG, 0);
214 	TCON_WRITE(sc, SUNXI_TCON_GINT1_REG,
215 	    __SHIFTIN(0x20, SUNXI_TCON_GINT1_TCON0_LINENO));
216 	TCON_WRITE(sc, SUNXI_TCON0_DCLK_REG, 0xf0000000);
217 	TCON_WRITE(sc, SUNXI_TCON0_LVDS_IF_REG, 0x0);
218 	TCON_WRITE(sc, SUNXI_TCON0_CTL_REG, 0);
219 	TCON_WRITE(sc, SUNXI_TCON0_IO_TRI_REG, 0xffffffff);
220 	TCON_WRITE(sc, SUNXI_TCON1_CTL_REG, 0);
221 	TCON_WRITE(sc, SUNXI_TCON1_IO_TRI_REG, 0xffffffff);
222 	TCON_WRITE(sc, SUNXI_TCON_GCTL_REG, 0);
223 
224 	/* clock needed for the mux in unit 0 */
225 	if (sc->sc_unit != 0) {
226 		if (clk_disable(sc->sc_clk_ahb) != 0) {
227 			aprint_error(": couldn't disable ahb clock\n");
228 			return;
229 		}
230 	}
231 }
232 
233 static void
234 sunxi_tcon_ep_connect(device_t self, struct fdt_endpoint *ep, bool connect)
235 {
236 	struct sunxi_tcon_softc *sc = device_private(self);
237 	struct fdt_endpoint *rep = fdt_endpoint_remote(ep);
238 	int rep_idx = fdt_endpoint_index(rep);
239 
240 	KASSERT(device_is_a(self, "sunxitcon"));
241 	if (!connect) {
242 		aprint_error_dev(self, "endpoint disconnect not supported\n");
243 		return;
244 	}
245 
246 	if (fdt_endpoint_port_index(ep) == 0) {
247 		bool do_print = (sc->sc_unit == -1);
248 		/*
249 		 * one of our input endpoints has been connected.
250 		 * the remote id is our unit number
251 		 */
252 		if (sc->sc_unit != -1 && rep_idx != -1 &&
253 		    sc->sc_unit != rep_idx) {
254 			aprint_error_dev(self, ": remote id %d doens't match"
255 			    " discovered unit number %d\n",
256 			    rep_idx, sc->sc_unit);
257 			return;
258 		}
259 		if (!device_is_a(fdt_endpoint_device(rep), "sunxidebe")) {
260 			aprint_error_dev(self,
261 			    ": input %d connected to unknown device\n",
262 			    fdt_endpoint_index(ep));
263 			return;
264 		}
265 
266 		if (rep_idx != -1)
267 			sc->sc_unit = rep_idx;
268 		else {
269 			/* assume only one tcon */
270 			sc->sc_unit = 0;
271 		}
272 		if (do_print)
273 			aprint_verbose_dev(self, "tcon unit %d\n", sc->sc_unit);
274 		if (!tcon_mux_inited && sc->sc_unit == 0) {
275 			/* the mux register is only in LCD0 */
276 			if (clk_enable(sc->sc_clk_ahb) != 0) {
277 				aprint_error_dev(self,
278 				    "couldn't enable ahb clock\n");
279 				return;
280 			}
281 			bus_space_subregion(sc->sc_bst, sc->sc_bsh,
282 			    SUNXI_TCON_MUX_CTL_REG, 4, &tcon_mux_bsh);
283 			tcon_mux_inited = true;
284 			bus_space_write_4(sc->sc_bst, tcon_mux_bsh, 0,
285 			    __SHIFTIN(SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC_CLOSE,
286 			    SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC));
287 		}
288 	} else if (fdt_endpoint_port_index(ep) == 1) {
289 		device_t rep_dev = fdt_endpoint_device(rep);
290 		switch(fdt_endpoint_index(ep)) {
291 		case 0:
292 			break;
293 		case 1:
294 			if (!device_is_a(rep_dev, "sunxihdmi")) {
295 				aprint_error_dev(self,
296 				    ": output 1 connected to unknown device\n");
297 				return;
298 			}
299 			break;
300 		default:
301 			break;
302 		}
303 	}
304 }
305 
306 static int
307 sunxi_tcon_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
308 {
309 	struct sunxi_tcon_softc *sc = device_private(dev);
310 	struct fdt_endpoint *in_ep, *out_ep;
311 	int outi;
312 	int error = ENODEV;
313 
314 	KASSERT(device_is_a(dev, "sunxitcon"));
315 	/* our input is activated by debe, we activate our output */
316 	if (fdt_endpoint_port_index(ep) != SUNXI_PORT_INPUT) {
317 		panic("sunxi_tcon_ep_activate: port %d",
318 		    fdt_endpoint_port_index(ep));
319 	}
320 
321 	if (!activate)
322 		return EOPNOTSUPP;
323 
324 	if (clk_enable(sc->sc_clk_ahb) != 0) {
325 		aprint_error_dev(dev, "couldn't enable ahb clock\n");
326 		return EIO;
327 	}
328 	sc->sc_in_ep = ep;
329 	sc->sc_in_rep = fdt_endpoint_remote(ep);
330 	/* check that our other input is not active */
331 	switch (fdt_endpoint_index(ep)) {
332 	case 0:
333 		in_ep = fdt_endpoint_get_from_index(&sc->sc_ports,
334 		    SUNXI_PORT_INPUT, 1);
335 		break;
336 	case 1:
337 		in_ep = fdt_endpoint_get_from_index(&sc->sc_ports,
338 		    SUNXI_PORT_INPUT, 0);
339 		break;
340 	default:
341 		in_ep = NULL;
342 		panic("sunxi_tcon_ep_activate: input index %d",
343 		    fdt_endpoint_index(ep));
344 	}
345 	if (in_ep != NULL) {
346 		if (fdt_endpoint_is_active(in_ep))
347 			return EBUSY;
348 	}
349 	/* try output 0 (RGB/LVDS) first, then ouput 1 (HDMI) if it fails */
350 	for (outi = 0; outi < 2; outi++) {
351 		out_ep = fdt_endpoint_get_from_index(&sc->sc_ports,
352 		    SUNXI_PORT_OUTPUT, outi);
353 		if (out_ep == NULL)
354 			continue;
355 		error = fdt_endpoint_activate(out_ep, activate);
356 		if (error == 0) {
357 			struct fdt_endpoint *rep = fdt_endpoint_remote(out_ep);
358 			aprint_verbose_dev(dev, "output to %s\n",
359 			    device_xname(fdt_endpoint_device(rep)));
360 			sc->sc_out_ep = out_ep;
361 			if (outi == 0)
362 				return sunxi_tcon0_set_video(sc);
363 			/* XXX should check VGA here */
364 			sc->sc_output_type = OUTPUT_HDMI;
365 			return 0;
366 		}
367 	}
368 	if (out_ep == NULL) {
369 		aprint_error_dev(dev, "no output endpoint\n");
370 		return ENODEV;
371 	}
372 	return error;
373 }
374 
375 static int
376 sunxi_tcon_ep_enable(device_t dev, struct fdt_endpoint *ep, bool enable)
377 {
378 	struct sunxi_tcon_softc *sc = device_private(dev);
379 	int error;
380 	KASSERT(device_is_a(dev, "sunxitcon"));
381 	switch (fdt_endpoint_port_index(ep)) {
382 	case SUNXI_PORT_INPUT:
383 		KASSERT(ep == sc->sc_in_ep);
384 		if (fdt_endpoint_index(sc->sc_out_ep) == 0) {
385 			/* tcon0 active */
386 			return sunxi_tcon0_enable(sc, enable);
387 		}
388 		/* propagate to our output, it will get back to us */
389 		return fdt_endpoint_enable(sc->sc_out_ep, enable);
390 	case SUNXI_PORT_OUTPUT:
391 		KASSERT(ep == sc->sc_out_ep);
392 		switch (fdt_endpoint_index(ep)) {
393 		case 0:
394 			panic("sunxi_tcon0_ep_enable");
395 		case 1:
396 			error = sunxi_tcon1_enable(sc, enable);
397 			break;
398 		default:
399 			panic("sunxi_tcon_ep_enable ep %d",
400 			    fdt_endpoint_index(ep));
401 
402 		}
403 		break;
404 	default:
405 		panic("sunxi_tcon_ep_enable port %d", fdt_endpoint_port_index(ep));
406 	}
407 #if defined(SUNXI_TCON_DEBUG)
408 	sunxi_tcon_dump_regs(device_unit(dev));
409 #endif
410 	return error;
411 }
412 
413 static int
414 sunxi_tcon0_set_video(struct sunxi_tcon_softc *sc)
415 {
416 	const struct fdt_panel * panel;
417 	int32_t lcd_x, lcd_y;
418 	int32_t lcd_hbp, lcd_ht, lcd_vbp, lcd_vt;
419 	int32_t lcd_hspw, lcd_vspw, lcd_io_cfg0;
420 	uint32_t vblk, start_delay;
421 	uint32_t val;
422 	uint32_t best_div;
423 	int best_diff, best_clk_freq, clk_freq, lcd_dclk_freq;
424 	bool dualchan = false;
425 	static struct videomode mode;
426 	int error;
427 
428 	panel = fdt_endpoint_get_data(fdt_endpoint_remote(sc->sc_out_ep));
429 	KASSERT(panel != NULL);
430 	KASSERT(panel->panel_type == PANEL_DUAL_LVDS ||
431 	    panel->panel_type == PANEL_LVDS);
432 	sc->sc_output_type = OUTPUT_LVDS;
433 
434 	lcd_x = panel->panel_timing.hactive;
435 	lcd_y = panel->panel_timing.vactive;
436 
437 	lcd_dclk_freq = panel->panel_timing.clock_freq;
438 
439 	lcd_hbp = panel->panel_timing.hback_porch;
440 	lcd_hspw = panel->panel_timing.hsync_len;
441 	lcd_ht = panel->panel_timing.hfront_porch + lcd_hspw + lcd_x + lcd_hbp;
442 
443 	lcd_vbp = panel->panel_timing.vback_porch;
444 	lcd_vspw = panel->panel_timing.vsync_len;
445 	lcd_vt = panel->panel_timing.vfront_porch + lcd_vspw + lcd_y + lcd_vbp;
446 
447 	lcd_io_cfg0 = 0x10000000; /* XXX */
448 
449 	if (panel->panel_type == PANEL_DUAL_LVDS)
450 		dualchan = true;
451 
452 	vblk = lcd_vt - lcd_y;
453 	start_delay = (vblk >= 32) ? 30 : (vblk - 2);
454 
455 	if (lcd_dclk_freq > 150000000) /* hardware limit ? */
456 		lcd_dclk_freq = 150000000;
457 
458 	best_diff = INT_MAX;
459 	best_div = 0;
460 	best_clk_freq = 0;
461 	for (u_int div = 7; div <= 15; div++) {
462 		int dot_freq, diff;
463 		clk_freq = clk_round_rate(sc->sc_clk_ch0, lcd_dclk_freq * div);
464 		if (clk_freq == 0)
465 			continue;
466 		dot_freq = clk_freq / div;
467 		diff = abs(lcd_dclk_freq - dot_freq);
468 		if (best_diff > diff) {
469 			best_diff = diff;
470 			best_div = div;
471 			best_clk_freq = clk_freq;
472 			if (diff == 0)
473 				break;
474 		}
475 	}
476 	if (best_clk_freq == 0) {
477 		device_printf(sc->sc_dev,
478 		    ": failed to find params for dot clock %d\n",
479 		    lcd_dclk_freq);
480 		return EINVAL;
481 	}
482 
483 	error = clk_set_rate(sc->sc_clk_ch0, best_clk_freq);
484 	if (error) {
485 		device_printf(sc->sc_dev,
486 		    ": failed to set ch0 clock to %d for %d: %d\n",
487 		    best_clk_freq, lcd_dclk_freq, error);
488 		panic("tcon0 set clk");
489 	}
490 	error = clk_enable(sc->sc_clk_ch0);
491 	if (error) {
492 		device_printf(sc->sc_dev,
493 		    ": failed to enable ch0 clock: %d\n", error);
494 		return EIO;
495 	}
496 
497 	val = __SHIFTIN(start_delay, SUNXI_TCONx_CTL_START_DELAY);
498 	/*
499 	 * the DE selector selects the primary DEBE for this tcon:
500 	 * 0 selects debe0 for tcon0 and debe1 for tcon1
501 	 */
502 	val |= __SHIFTIN(SUNXI_TCONx_CTL_SRC_SEL_DE0,
503 			 SUNXI_TCONx_CTL_SRC_SEL);
504 	TCON_WRITE(sc, SUNXI_TCON0_CTL_REG, val);
505 
506 	val =  (lcd_x - 1) << 16 |  (lcd_y - 1);
507 	TCON_WRITE(sc, SUNXI_TCON0_BASIC0_REG, val);
508 	val = (lcd_ht - 1) << 16 | (lcd_hbp - 1);
509 	TCON_WRITE(sc, SUNXI_TCON0_BASIC1_REG, val);
510 	val = (lcd_vt * 2) << 16 | (lcd_vbp - 1);
511 	TCON_WRITE(sc, SUNXI_TCON0_BASIC2_REG, val);
512 	val =  ((lcd_hspw > 0) ? (lcd_hspw - 1) : 0) << 16;
513 	val |= ((lcd_vspw > 0) ? (lcd_vspw - 1) : 0);
514 	TCON_WRITE(sc, SUNXI_TCON0_BASIC3_REG, val);
515 
516 	val = 0;
517 	if (dualchan)
518 		val |= SUNXI_TCON0_LVDS_IF_DUALCHAN;
519 	if (panel->panel_lvds_format == LVDS_JEIDA_24)
520 		val |= SUNXI_TCON0_LVDS_IF_MODE_JEIDA;
521 	if (panel->panel_lvds_format == LVDS_JEIDA_18)
522 		val |= SUNXI_TCON0_LVDS_IF_18BITS;
523 	TCON_WRITE(sc, SUNXI_TCON0_LVDS_IF_REG, val);
524 
525 	TCON_WRITE(sc, SUNXI_TCON0_IO_POL_REG, lcd_io_cfg0);
526 	TCON_WRITE(sc, SUNXI_TCON0_IO_TRI_REG, 0);
527 	TCON_WRITE(sc, SUNXI_TCON_GINT1_REG,
528 	    __SHIFTIN(start_delay + 2, SUNXI_TCON_GINT1_TCON0_LINENO));
529 
530 	val = 0xf0000000;
531 	val &= ~SUNXI_TCON0_DCLK_DIV;
532 	val |= __SHIFTIN(best_div, SUNXI_TCON0_DCLK_DIV);
533 	TCON_WRITE(sc, SUNXI_TCON0_DCLK_REG, val);
534 
535 	mode.dot_clock = lcd_dclk_freq;
536 	mode.hdisplay = lcd_x;
537 	mode.hsync_start = lcd_ht - lcd_hbp;
538 	mode.hsync_end = lcd_hspw + mode.hsync_start;
539 	mode.htotal = lcd_ht;
540 	mode.vdisplay = lcd_y;
541 	mode.vsync_start = lcd_vt - lcd_vbp;
542 	mode.vsync_end = lcd_vspw + mode.vsync_start;
543 	mode.vtotal = lcd_vt;
544 	mode.flags = 0;
545 	mode.name = NULL;
546 
547 	sunxi_debe_set_videomode(fdt_endpoint_device(sc->sc_in_rep), &mode);
548 
549 	/* XXX
550 	 * magic values here from linux. these are not documented
551 	 * in the A20 user manual, and other Allwiner LVDS-capable SoC
552 	 * documentation don't make sense with these values
553 	 */
554 	val = TCON_READ(sc, SUNXI_TCON_LVDS_ANA0);
555 	val |= 0x3F310000;
556 	TCON_WRITE(sc, SUNXI_TCON_LVDS_ANA0, val);
557 	val = TCON_READ(sc, SUNXI_TCON_LVDS_ANA0);
558 	val |= 1 << 22;
559 	TCON_WRITE(sc, SUNXI_TCON_LVDS_ANA0, val);
560 	delay(2);
561 	val = TCON_READ(sc, SUNXI_TCON_LVDS_ANA1);
562 	val |= (0x1f << 26 | 0x1f << 10);
563 	TCON_WRITE(sc, SUNXI_TCON_LVDS_ANA1, val);
564 	delay(2);
565 	val = TCON_READ(sc, SUNXI_TCON_LVDS_ANA1);
566 	val |= (0x1f << 16 | 0x1f << 0);
567 	TCON_WRITE(sc, SUNXI_TCON_LVDS_ANA1, val);
568 	val = TCON_READ(sc, SUNXI_TCON_LVDS_ANA0);
569 	val |= 1 << 22;
570 	TCON_WRITE(sc, SUNXI_TCON_LVDS_ANA0, val);
571 	return 0;
572 }
573 
574 static int
575 sunxi_tcon0_enable(struct sunxi_tcon_softc *sc, bool enable)
576 {
577 	uint32_t val;
578 	int error;
579 
580 	/* turn on/off backlight and lcd  */
581 	error = fdt_endpoint_enable(sc->sc_out_ep, enable);
582 	if (error)
583 		return error;
584 
585 	/* and finally disable or enable the tcon */
586 	error = fdt_endpoint_enable(sc->sc_in_ep, enable);
587 	if (error)
588 		return error;
589 	delay(20000);
590 	if (enable) {
591 		if ((error = clk_enable(sc->sc_clk_ch0)) != 0) {
592 			device_printf(sc->sc_dev,
593 			    ": couldn't enable ch0 clock\n");
594 			return error;
595 		}
596 		val = TCON_READ(sc, SUNXI_TCON_GCTL_REG);
597 		val |= SUNXI_TCON_GCTL_EN;
598 		TCON_WRITE(sc, SUNXI_TCON_GCTL_REG, val);
599 		val = TCON_READ(sc, SUNXI_TCON0_CTL_REG);
600 		val |= SUNXI_TCONx_CTL_EN;
601 		TCON_WRITE(sc, SUNXI_TCON0_CTL_REG, val);
602 		val = TCON_READ(sc, SUNXI_TCON0_LVDS_IF_REG);
603 		val |= SUNXI_TCON0_LVDS_IF_EN;
604 		TCON_WRITE(sc, SUNXI_TCON0_LVDS_IF_REG, val);
605 		TCON_WRITE(sc, SUNXI_TCON0_IO_TRI_REG, 0);
606 	} else {
607 		TCON_WRITE(sc, SUNXI_TCON0_IO_TRI_REG, 0xffffffff);
608 		val = TCON_READ(sc, SUNXI_TCON0_LVDS_IF_REG);
609 		val &= ~SUNXI_TCON0_LVDS_IF_EN;
610 		TCON_WRITE(sc, SUNXI_TCON0_LVDS_IF_REG, val);
611 		val = TCON_READ(sc, SUNXI_TCON0_CTL_REG);
612 		val &= ~SUNXI_TCONx_CTL_EN;
613 		TCON_WRITE(sc, SUNXI_TCON0_CTL_REG, val);
614 		val = TCON_READ(sc, SUNXI_TCON_GCTL_REG);
615 		val &= ~SUNXI_TCON_GCTL_EN;
616 		TCON_WRITE(sc, SUNXI_TCON_GCTL_REG, val);
617 		if ((error = clk_disable(sc->sc_clk_ch0)) != 0) {
618 			device_printf(sc->sc_dev,
619 			    ": couldn't disable ch0 clock\n");
620 			return error;
621 		}
622 	}
623 #ifdef SUNXI_TCON_DEBUG
624 	sunxi_tcon_dump_regs(device_unit(sc->sc_dev));
625 #endif
626 	return 0;
627 }
628 
629 static int
630 sunxi_tcon1_enable(struct sunxi_tcon_softc *sc, bool enable)
631 {
632 	uint32_t val;
633 	int error;
634 
635 	KASSERT((sc->sc_output_type == OUTPUT_HDMI) ||
636 		    (sc->sc_output_type == OUTPUT_VGA));
637 
638 	fdt_endpoint_enable(sc->sc_in_ep, enable);
639 	delay(20000);
640 	if (enable) {
641 		if ((error = clk_enable(sc->sc_clk_ch1)) != 0) {
642 			device_printf(sc->sc_dev,
643 			    ": couldn't enable ch1 clock\n");
644 			return error;
645 		}
646 		val = TCON_READ(sc, SUNXI_TCON_GCTL_REG);
647 		val |= SUNXI_TCON_GCTL_EN;
648 		TCON_WRITE(sc, SUNXI_TCON_GCTL_REG, val);
649 		val = TCON_READ(sc, SUNXI_TCON1_CTL_REG);
650 		val |= SUNXI_TCONx_CTL_EN;
651 		TCON_WRITE(sc, SUNXI_TCON1_CTL_REG, val);
652 		if (sc->sc_output_type == OUTPUT_VGA) {
653 			TCON_WRITE(sc, SUNXI_TCON1_IO_TRI_REG, 0x0cffffff);
654 		} else
655 			TCON_WRITE(sc, SUNXI_TCON1_IO_TRI_REG, 0);
656 	} else {
657 		TCON_WRITE(sc, SUNXI_TCON1_IO_TRI_REG, 0xffffffff);
658 		val = TCON_READ(sc, SUNXI_TCON1_CTL_REG);
659 		val &= ~SUNXI_TCONx_CTL_EN;
660 		TCON_WRITE(sc, SUNXI_TCON1_CTL_REG, val);
661 		val = TCON_READ(sc, SUNXI_TCON_GCTL_REG);
662 		val &= ~SUNXI_TCON_GCTL_EN;
663 		TCON_WRITE(sc, SUNXI_TCON_GCTL_REG, val);
664 		if ((error = clk_disable(sc->sc_clk_ch1)) != 0) {
665 			device_printf(sc->sc_dev,
666 			    ": couldn't disable ch1 clock\n");
667 			return error;
668 		}
669 	}
670 
671 	KASSERT(tcon_mux_inited);
672 	val = bus_space_read_4(sc->sc_bst, tcon_mux_bsh, 0);
673 #ifdef SUNXI_TCON_DEBUG
674 	printf("sunxi_tcon1_enable(%d) %d val 0x%x", sc->sc_unit, enable, val);
675 #endif
676 	val &= ~ SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC;
677 	switch(sc->sc_unit) {
678 	case 0:
679 		val |= __SHIFTIN(SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC_LCDC0_TCON1,
680 		    SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC);
681 		break;
682 	case 1:
683 		val |= __SHIFTIN(SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC_LCDC1_TCON1,
684 		    SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC);
685 		break;
686 	default:
687 		panic("tcon: invalid unid %d\n", sc->sc_unit);
688 	}
689 #ifdef SUNXI_TCON_DEBUG
690 	printf(" -> 0x%x", val);
691 #endif
692 	bus_space_write_4(sc->sc_bst, tcon_mux_bsh, 0, val);
693 #ifdef SUNXI_TCON_DEBUG
694 	printf(": 0x%" PRIxBSH " 0x%" PRIxBSH " 0x%x 0x%x\n", sc->sc_bsh,
695 	    tcon_mux_bsh, bus_space_read_4(sc->sc_bst, tcon_mux_bsh, 0),
696 	    TCON_READ(sc, SUNXI_TCON_MUX_CTL_REG));
697 #endif
698 	return 0;
699 }
700 
701 void
702 sunxi_tcon1_set_videomode(device_t dev, const struct videomode *mode)
703 {
704 	struct sunxi_tcon_softc *sc = device_private(dev);
705 	uint32_t val;
706 	int error;
707 
708 	KASSERT(device_is_a(dev, "sunxitcon"));
709 	KASSERT((sc->sc_output_type == OUTPUT_HDMI) ||
710 		    (sc->sc_output_type == OUTPUT_VGA));
711 
712 	sunxi_debe_set_videomode(fdt_endpoint_device(sc->sc_in_rep), mode);
713 	if (mode) {
714 		const u_int interlace_p = !!(mode->flags & VID_INTERLACE);
715 		const u_int phsync_p = !!(mode->flags & VID_PHSYNC);
716 		const u_int pvsync_p = !!(mode->flags & VID_PVSYNC);
717 		const u_int hspw = mode->hsync_end - mode->hsync_start;
718 		const u_int hbp = mode->htotal - mode->hsync_start;
719 		const u_int vspw = mode->vsync_end - mode->vsync_start;
720 		const u_int vbp = mode->vtotal - mode->vsync_start;
721 		const u_int vblank_len =
722 		    ((mode->vtotal << interlace_p) >> 1) - mode->vdisplay - 2;
723 		const u_int start_delay =
724 		    vblank_len >= 32 ? 30 : vblank_len - 2;
725 
726 		val = TCON_READ(sc, SUNXI_TCON_GCTL_REG);
727 		val |= SUNXI_TCON_GCTL_IO_MAP_SEL;
728 		TCON_WRITE(sc, SUNXI_TCON_GCTL_REG, val);
729 
730 		/* enable */
731 		val = SUNXI_TCONx_CTL_EN;
732 		if (interlace_p)
733 			val |= SUNXI_TCONx_CTL_INTERLACE_EN;
734 		val |= __SHIFTIN(start_delay, SUNXI_TCONx_CTL_START_DELAY);
735 #ifdef SUNXI_TCON1_BLUEDATA
736 		val |= __SHIFTIN(SUNXI_TCONx_CTL_SRC_SEL_BLUEDATA,
737 				 SUNXI_TCONx_CTL_SRC_SEL);
738 #else
739 		/*
740 		 * the DE selector selects the primary DEBE for this tcon:
741 		 * 0 selects debe0 for tcon0 and debe1 for tcon1
742 		 */
743 		val |= __SHIFTIN(SUNXI_TCONx_CTL_SRC_SEL_DE0,
744 				 SUNXI_TCONx_CTL_SRC_SEL);
745 #endif
746 		TCON_WRITE(sc, SUNXI_TCON1_CTL_REG, val);
747 
748 		/* Source width/height */
749 		TCON_WRITE(sc, SUNXI_TCON1_BASIC0_REG,
750 		    ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1));
751 		/* Scaler width/height */
752 		TCON_WRITE(sc, SUNXI_TCON1_BASIC1_REG,
753 		    ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1));
754 		/* Output width/height */
755 		TCON_WRITE(sc, SUNXI_TCON1_BASIC2_REG,
756 		    ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1));
757 		/* Horizontal total + back porch */
758 		TCON_WRITE(sc, SUNXI_TCON1_BASIC3_REG,
759 		    ((mode->htotal - 1) << 16) | (hbp - 1));
760 		/* Vertical total + back porch */
761 		u_int vtotal = mode->vtotal * 2;
762 		if (interlace_p) {
763 			u_int framerate =
764 			    DIVIDE(DIVIDE(mode->dot_clock * 1000, mode->htotal),
765 			    mode->vtotal);
766 			u_int clk = mode->htotal * (mode->vtotal * 2 + 1) *
767 			    framerate;
768 			if ((clk / 2) == mode->dot_clock * 1000)
769 				vtotal += 1;
770 		}
771 		TCON_WRITE(sc, SUNXI_TCON1_BASIC4_REG,
772 		    (vtotal << 16) | (vbp - 1));
773 
774 		/* Sync */
775 		TCON_WRITE(sc, SUNXI_TCON1_BASIC5_REG,
776 		    ((hspw - 1) << 16) | (vspw - 1));
777 		/* Polarity */
778 		val = SUNXI_TCON_IO_POL_IO2_INV;
779 		if (phsync_p)
780 			val |= SUNXI_TCON_IO_POL_PHSYNC;
781 		if (pvsync_p)
782 			val |= SUNXI_TCON_IO_POL_PVSYNC;
783 		TCON_WRITE(sc, SUNXI_TCON1_IO_POL_REG, val);
784 
785 		TCON_WRITE(sc, SUNXI_TCON_GINT1_REG,
786 		    __SHIFTIN(start_delay + 2, SUNXI_TCON_GINT1_TCON1_LINENO));
787 
788 		/* Setup LCDx CH1 PLL */
789 		error = clk_set_rate(sc->sc_clk_ch1, mode->dot_clock * 1000);
790 		if (error) {
791 			device_printf(sc->sc_dev,
792 			    ": failed to set ch1 clock to %d: %d\n",
793 			    mode->dot_clock, error);
794 		}
795 		error = clk_enable(sc->sc_clk_ch1);
796 		if (error) {
797 			device_printf(sc->sc_dev,
798 			    ": failed to enable ch1 clock: %d\n",
799 			    error);
800 		}
801 	} else {
802 		/* disable */
803 		val = TCON_READ(sc, SUNXI_TCON1_CTL_REG);
804 		val &= ~SUNXI_TCONx_CTL_EN;
805 		TCON_WRITE(sc, SUNXI_TCON1_CTL_REG, val);
806 		error = clk_disable(sc->sc_clk_ch1);
807 		if (error) {
808 			device_printf(sc->sc_dev,
809 			    ": failed to disable ch1 clock: %d\n",
810 			    error);
811 		}
812 	}
813 }
814 
815 /* check if this tcon is the console chosen by firmare */
816 bool
817 sunxi_tcon_is_console(device_t dev, const char *pipeline)
818 {
819 	struct sunxi_tcon_softc *sc = device_private(dev);
820 	char p[64];
821 	char *e, *n = p;
822 	bool is_console = false;
823 
824 	KASSERT(device_is_a(dev, "sunxitcon"));
825 	strncpy(p, pipeline, sizeof(p) - 1);
826 	p[sizeof(p) - 1] = '\0';
827 
828 	/*
829 	 * pipeline is like "de_be0-lcd0-hdmi"
830 	 * of "de_be0-lcd1".
831 	 * In the first case check output type
832 	 * In the second check tcon unit number
833 	 */
834 	 n = p;
835 	 e = strsep(&n, "-");
836 	 if (e == NULL || memcmp(e, "de_be", 5) != 0)
837 		goto bad;
838 	 e = strsep(&n, "-");
839 	 if (e == NULL)
840 		goto bad;
841 	 if (n == NULL) {
842 		/* second case */
843 		if (strcmp(e, "lcd0") == 0) {
844 			if (sc->sc_unit == 0)
845 				is_console = true;
846 		 } else if (strcmp(e, "lcd1") == 0) {
847 			if (sc->sc_unit == 1)
848 				is_console = true;
849 		} else
850 			goto bad;
851 		return is_console;
852 	}
853 	/* first case */
854 	if (strcmp(n, "hdmi") == 0) {
855 		if (sc->sc_output_type == OUTPUT_HDMI)
856 			is_console = true;
857 		return is_console;
858 	}
859 bad:
860 	aprint_error("warning: can't parse pipeline %s\n", pipeline);
861 	return is_console;
862 }
863 
864 #if defined(DDB) || defined(SUNXI_TCON_DEBUG)
865 void
866 sunxi_tcon_dump_regs(int u)
867 {
868 	static const struct {
869 		const char *name;
870 		uint16_t reg;
871 	} regs[] = {
872 		{ "TCON0_BASIC0_REG", SUNXI_TCON0_BASIC0_REG },
873 		{ "TCON0_BASIC1_REG", SUNXI_TCON0_BASIC1_REG },
874 		{ "TCON0_BASIC2_REG", SUNXI_TCON0_BASIC2_REG },
875 		{ "TCON0_BASIC3_REG", SUNXI_TCON0_BASIC3_REG },
876 		{ "TCON0_CTL_REG", SUNXI_TCON0_CTL_REG },
877 		{ "TCON0_DCLK_REG", SUNXI_TCON0_DCLK_REG },
878 		{ "TCON0_IO_POL_REG", SUNXI_TCON0_IO_POL_REG },
879 		{ "TCON0_IO_TRI_REG", SUNXI_TCON0_IO_TRI_REG },
880 		{ "TCON0_LVDS_IF_REG", SUNXI_TCON0_LVDS_IF_REG },
881 		{ "TCON1_BASIC0_REG", SUNXI_TCON1_BASIC0_REG },
882 		{ "TCON1_BASIC1_REG", SUNXI_TCON1_BASIC1_REG },
883 		{ "TCON1_BASIC2_REG", SUNXI_TCON1_BASIC2_REG },
884 		{ "TCON1_BASIC3_REG", SUNXI_TCON1_BASIC3_REG },
885 		{ "TCON1_BASIC4_REG", SUNXI_TCON1_BASIC4_REG },
886 		{ "TCON1_BASIC5_REG", SUNXI_TCON1_BASIC5_REG },
887 		{ "TCON1_CTL_REG", SUNXI_TCON1_CTL_REG },
888 		{ "TCON1_IO_POL_REG", SUNXI_TCON1_IO_POL_REG },
889 		{ "TCON1_IO_TRI_REG", SUNXI_TCON1_IO_TRI_REG },
890 		{ "TCON_GCTL_REG", SUNXI_TCON_GCTL_REG },
891 		{ "TCON_GINT0_REG", SUNXI_TCON_GINT0_REG },
892 		{ "TCON_GINT1_REG", SUNXI_TCON_GINT1_REG },
893 		{ "TCON_MUX_CTL_REG", SUNXI_TCON_MUX_CTL_REG },
894 	};
895 	struct sunxi_tcon_softc *sc;
896 	device_t dev;
897 
898 	dev = device_find_by_driver_unit("sunxitcon", u);
899 	if (dev == NULL)
900 		return;
901 	sc = device_private(dev);
902 
903 	for (int i = 0; i < __arraycount(regs); i++) {
904 		printf("%s: 0x%08x\n", regs[i].name,
905 		    TCON_READ(sc, regs[i].reg));
906 	}
907 }
908 #endif
909