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