sunxi_tcon.c revision 1.5 1 /* $NetBSD: sunxi_tcon.c,v 1.5 2018/04/06 08:23:40 bouyer Exp $ */
2
3 /*-
4 * Copyright (c) 2018 Manuel Bouyer <bouyer (at) antioche.eu.org>
5 * All rights reserved.
6 *
7 * Copyright (c) 2014 Jared D. McNeill <jmcneill (at) 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.5 2018/04/06 08:23:40 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 #if defined(DDB) || defined(SUNXI_TCON_DEBUG)
816 void
817 sunxi_tcon_dump_regs(int u)
818 {
819 static const struct {
820 const char *name;
821 uint16_t reg;
822 } regs[] = {
823 { "TCON0_BASIC0_REG", SUNXI_TCON0_BASIC0_REG },
824 { "TCON0_BASIC1_REG", SUNXI_TCON0_BASIC1_REG },
825 { "TCON0_BASIC2_REG", SUNXI_TCON0_BASIC2_REG },
826 { "TCON0_BASIC3_REG", SUNXI_TCON0_BASIC3_REG },
827 { "TCON0_CTL_REG", SUNXI_TCON0_CTL_REG },
828 { "TCON0_DCLK_REG", SUNXI_TCON0_DCLK_REG },
829 { "TCON0_IO_POL_REG", SUNXI_TCON0_IO_POL_REG },
830 { "TCON0_IO_TRI_REG", SUNXI_TCON0_IO_TRI_REG },
831 { "TCON0_LVDS_IF_REG", SUNXI_TCON0_LVDS_IF_REG },
832 { "TCON1_BASIC0_REG", SUNXI_TCON1_BASIC0_REG },
833 { "TCON1_BASIC1_REG", SUNXI_TCON1_BASIC1_REG },
834 { "TCON1_BASIC2_REG", SUNXI_TCON1_BASIC2_REG },
835 { "TCON1_BASIC3_REG", SUNXI_TCON1_BASIC3_REG },
836 { "TCON1_BASIC4_REG", SUNXI_TCON1_BASIC4_REG },
837 { "TCON1_BASIC5_REG", SUNXI_TCON1_BASIC5_REG },
838 { "TCON1_CTL_REG", SUNXI_TCON1_CTL_REG },
839 { "TCON1_IO_POL_REG", SUNXI_TCON1_IO_POL_REG },
840 { "TCON1_IO_TRI_REG", SUNXI_TCON1_IO_TRI_REG },
841 { "TCON_GCTL_REG", SUNXI_TCON_GCTL_REG },
842 { "TCON_GINT0_REG", SUNXI_TCON_GINT0_REG },
843 { "TCON_GINT1_REG", SUNXI_TCON_GINT1_REG },
844 { "TCON_MUX_CTL_REG", SUNXI_TCON_MUX_CTL_REG },
845 };
846 struct sunxi_tcon_softc *sc;
847 device_t dev;
848
849 dev = device_find_by_driver_unit("sunxitcon", u);
850 if (dev == NULL)
851 return;
852 sc = device_private(dev);
853
854 for (int i = 0; i < __arraycount(regs); i++) {
855 printf("%s: 0x%08x\n", regs[i].name,
856 TCON_READ(sc, regs[i].reg));
857 }
858 }
859 #endif
860