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