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