Module Name:    src
Committed By:   bouyer
Date:           Tue Apr  3 12:52:16 UTC 2018

Modified Files:
        src/sys/arch/arm/sunxi: files.sunxi
Added Files:
        src/sys/arch/arm/sunxi: sunxi_debe.c sunxi_debereg.h sunxi_dep.c
            sunxi_display.h sunxi_hdmi.c sunxi_hdmireg.h sunxi_tcon.c
            sunxi_tconreg.h

Log Message:
work in progress on porting graphics drivers from arm/allwinner to fdt.
sunxidebe: display backend drivers
sunxitcon: lcd controller driver
sunxihdmi: hdmi controller driver
sunxidep: display engine pipeline driver. Its role is only to parse the
   fdt display-engine node, and activate de backend drivers based on the
   content of allwinner,pipelines

So far HDMI and lvds output works, in dual-framebuffer mode.
It has only been tested on A20. It should be OK on the A10 too, but
will likely need more work for other SoCs.
Console is not handled yet, and it conflicts with the simplefb driver if
it has been activated by u-boot.


To generate a diff of this commit:
cvs rdiff -u -r1.47 -r1.48 src/sys/arch/arm/sunxi/files.sunxi
cvs rdiff -u -r0 -r1.3 src/sys/arch/arm/sunxi/sunxi_debe.c
cvs rdiff -u -r0 -r1.1 src/sys/arch/arm/sunxi/sunxi_debereg.h \
    src/sys/arch/arm/sunxi/sunxi_dep.c src/sys/arch/arm/sunxi/sunxi_display.h \
    src/sys/arch/arm/sunxi/sunxi_hdmi.c \
    src/sys/arch/arm/sunxi/sunxi_hdmireg.h \
    src/sys/arch/arm/sunxi/sunxi_tcon.c \
    src/sys/arch/arm/sunxi/sunxi_tconreg.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/arch/arm/sunxi/files.sunxi
diff -u src/sys/arch/arm/sunxi/files.sunxi:1.47 src/sys/arch/arm/sunxi/files.sunxi:1.48
--- src/sys/arch/arm/sunxi/files.sunxi:1.47	Sun Apr  1 21:19:17 2018
+++ src/sys/arch/arm/sunxi/files.sunxi	Tue Apr  3 12:52:16 2018
@@ -1,4 +1,4 @@
-#	$NetBSD: files.sunxi,v 1.47 2018/04/01 21:19:17 bouyer Exp $
+#	$NetBSD: files.sunxi,v 1.48 2018/04/03 12:52:16 bouyer Exp $
 #
 # Configuration info for Allwinner sunxi family SoCs
 #
@@ -189,6 +189,28 @@ file	arch/arm/sunxi/sun8i_h3_codec.c		h3
 attach	genfb at fdt with simplefb
 file	dev/fdt/simplefb.c			simplefb
 
+# A10/A20 LCD/TV timing controller (TCON)
+device	sunxitcon
+attach	sunxitcon at fdt with sunxi_tcon
+file	arch/arm/sunxi/sunxi_tcon.c		sunxi_tcon needs-flag
+
+# A10/A20 Display engine backend (DE-BE)
+device	sunxidebe { }
+attach	sunxidebe at fdt with sunxi_debe
+file	arch/arm/sunxi/sunxi_debe.c		sunxi_debe needs-flag
+
+attach	genfb at sunxidebe with sunxi_befb
+
+# A10/A20 HDMI
+device	sunxihdmi: edid, videomode
+attach	sunxihdmi at fdt with sunxi_hdmi
+file	arch/arm/sunxi/sunxi_hdmi.c		sunxi_hdmi needs-flag
+
+# A10/A20 display engine pipeline
+device sunxidep
+attach	sunxidep at fdt with sunxi_dep
+file	arch/arm/sunxi/sunxi_dep.c		sunxi_dep
+
 # Touch Screen controller
 device	sunxits: wsmousedev, tpcalib, sysmon_envsys
 attach	sunxits at fdt with sunxi_ts

Added files:

Index: src/sys/arch/arm/sunxi/sunxi_debe.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_debe.c:1.3
--- /dev/null	Tue Apr  3 12:52:16 2018
+++ src/sys/arch/arm/sunxi/sunxi_debe.c	Tue Apr  3 12:52:16 2018
@@ -0,0 +1,937 @@
+/* $NetBSD: sunxi_debe.c,v 1.3 2018/04/03 12:52:16 bouyer Exp $ */
+
+/*-
+ * Copyright (c) 2018 Manuel Bouyer <[email protected]>
+ * All rights reserved.
+ *
+ * Copyright (c) 2014 Jared D. McNeill <[email protected]>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "genfb.h"
+
+#ifndef SUNXI_DEBE_VIDEOMEM
+#define SUNXI_DEBE_VIDEOMEM	(16 * 1024 * 1024)
+#endif
+
+#define SUNXI_DEBE_CURMAX	64
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: sunxi_debe.c,v 1.3 2018/04/03 12:52:16 bouyer Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/intr.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+
+#include <dev/fdt/fdtvar.h>
+#include <dev/fdt/fdt_port.h>
+
+#include <dev/videomode/videomode.h>
+#include <dev/wscons/wsconsio.h>
+#include <dev/wsfb/genfbvar.h>
+
+#include <arm/sunxi/sunxi_debereg.h>
+#include <arm/sunxi/sunxi_display.h>
+
+enum sunxi_debe_type {
+	DEBE_A10 = 1,
+	DEBE_A20,
+};
+
+struct sunxi_debe_softc {
+	device_t sc_dev;
+	device_t sc_fbdev;
+	enum sunxi_debe_type sc_type;
+	bus_space_tag_t sc_bst;
+	bus_space_handle_t sc_bsh;
+	bus_dma_tag_t sc_dmat;
+
+	struct clk *sc_clk_ahb;
+	struct clk *sc_clk_mod;
+	struct clk *sc_clk_ram;
+
+	bus_dma_segment_t sc_dmasegs[1];
+	bus_size_t sc_dmasize;
+	bus_dmamap_t sc_dmamap;
+	void *sc_dmap;
+
+	bool sc_cursor_enable;
+	int sc_cursor_x, sc_cursor_y;
+	int sc_hot_x, sc_hot_y;
+	uint8_t sc_cursor_bitmap[8 * SUNXI_DEBE_CURMAX];
+	uint8_t sc_cursor_mask[8 * SUNXI_DEBE_CURMAX];
+
+	int	sc_phandle;
+	struct fdt_device_ports sc_ports;
+	struct fdt_endpoint *sc_out_ep;
+	int sc_unit; /* debe0 or debe1 */
+};
+
+#define DEBE_READ(sc, reg) \
+    bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
+#define DEBE_WRITE(sc, reg, val) \
+    bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
+
+static const struct of_compat_data compat_data[] = {
+	{"allwinner,sun4i-a10-display-backend", DEBE_A10},
+	{"allwinner,sun7i-a20-display-backend", DEBE_A20},
+	{NULL}
+};
+
+struct sunxifb_attach_args {
+	void *afb_fb;
+	uint32_t afb_width;
+	uint32_t afb_height;
+	bus_dma_tag_t afb_dmat;
+	bus_dma_segment_t *afb_dmasegs;
+	int afb_ndmasegs;
+};
+
+static void	sunxi_debe_ep_connect(device_t, struct fdt_endpoint *, bool);
+static int	sunxi_debe_ep_enable(device_t, struct fdt_endpoint *, bool);
+static int	sunxi_debe_match(device_t, cfdata_t, void *);
+static void	sunxi_debe_attach(device_t, device_t, void *);
+
+static int	sunxi_debe_alloc_videomem(struct sunxi_debe_softc *);
+static void	sunxi_debe_setup_fbdev(struct sunxi_debe_softc *,
+				      const struct videomode *);
+
+static int	sunxi_debe_set_curpos(struct sunxi_debe_softc *, int, int);
+static int	sunxi_debe_set_cursor(struct sunxi_debe_softc *,
+				     struct wsdisplay_cursor *);
+static int	sunxi_debe_ioctl(device_t, u_long, void *);
+static void	sunxi_befb_set_videomode(device_t, u_int, u_int);
+void sunxi_debe_dump_regs(int);
+
+CFATTACH_DECL_NEW(sunxi_debe, sizeof(struct sunxi_debe_softc),
+	sunxi_debe_match, sunxi_debe_attach, NULL, NULL);
+
+static int
+sunxi_debe_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct fdt_attach_args * const faa = aux;
+
+	return of_match_compat_data(faa->faa_phandle, compat_data);
+}
+
+static void
+sunxi_debe_attach(device_t parent, device_t self, void *aux)
+{
+	struct sunxi_debe_softc *sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	bus_addr_t addr;
+	bus_size_t size;
+	struct fdtbus_reset *rst;
+#if NAWIN_MP > 0
+	device_t mpdev;
+#endif
+#ifdef AWIN_DEBE_FWINIT
+	struct videomode mode;
+#endif
+	int error;
+
+	sc->sc_dev = self;
+	sc->sc_phandle = phandle;
+	sc->sc_bst = faa->faa_bst;
+	sc->sc_dmat = faa->faa_dmat;
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+	}
+	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
+		aprint_error(": couldn't map registers\n");
+		return;
+	}
+
+	sc->sc_clk_ahb = fdtbus_clock_get(phandle, "ahb");
+	sc->sc_clk_mod = fdtbus_clock_get(phandle, "mod");
+	sc->sc_clk_ram = fdtbus_clock_get(phandle, "ram");
+
+	rst = fdtbus_reset_get_index(phandle, 0);
+	if (rst == NULL) {
+		aprint_error(": couldn't get reset\n");
+		return;
+	}
+	if (fdtbus_reset_assert(rst) != 0) {
+		aprint_error(": couldn't assert reset\n");
+		return;
+	}
+	delay(1);
+	if (fdtbus_reset_deassert(rst) != 0) {
+		aprint_error(": couldn't de-assert reset\n");
+		return;
+	}
+
+	if (sc->sc_clk_ahb == NULL || sc->sc_clk_mod == NULL
+	    || sc->sc_clk_ram == NULL) {
+		aprint_error(": couldn't get clocks\n");
+		aprint_debug_dev(self, "clk ahb %s mod %s ram %s\n",
+		    sc->sc_clk_ahb == NULL ? "missing" : "present",
+		    sc->sc_clk_mod == NULL ? "missing" : "present",
+		    sc->sc_clk_ram == NULL ? "missing" : "present");
+		return;
+	}
+
+	error = clk_set_rate(sc->sc_clk_mod, 300000000);
+	if (error) {
+		aprint_error("couln't set mod clock rate (%d)\n", error);
+		return;
+	}
+
+	if (clk_enable(sc->sc_clk_ahb) != 0 ||
+	    clk_enable(sc->sc_clk_mod) != 0 ||
+	    clk_enable(sc->sc_clk_ram) != 0) {
+		aprint_error(": couldn't enable clocks\n");
+		return;
+	}
+
+	sc->sc_type = of_search_compatible(faa->faa_phandle, compat_data)->data;
+
+	aprint_naive("\n");
+	aprint_normal(": Display Engine Backend (%s)\n",
+	    fdtbus_get_string(phandle, "name"));
+
+
+#ifdef AWIN_DEBE_FWINIT
+	const uint32_t modctl = DEBE_READ(sc, SUNXI_DEBE_MODCTL_REG);
+	const uint32_t dissize = DEBE_READ(sc, SUNXI_DEBE_DISSIZE_REG);
+	if ((modctl & SUNXI_DEBE_MODCTL_EN) == 0) {
+		aprint_error_dev(sc->sc_dev, "disabled\n");
+		return;
+	}
+	if ((modctl & SUNXI_DEBE_MODCTL_START_CTL) == 0) {
+		aprint_error_dev(sc->sc_dev, "stopped\n");
+		return;
+	}
+	memset(&mode, 0, sizeof(mode));
+	mode.hdisplay = (dissize & 0xffff) + 1;
+	mode.vdisplay = ((dissize >> 16) & 0xffff) + 1;
+
+	if (mode.hdisplay == 1 || mode.vdisplay == 1) {
+		aprint_error_dev(sc->sc_dev,
+		    "couldn't determine video mode\n");
+		return;
+	}
+
+	aprint_verbose_dev(sc->sc_dev, "using %dx%d mode from firmware\n",
+	    mode.hdisplay, mode.vdisplay);
+
+	sc->sc_dmasize = mode.hdisplay * mode.vdisplay * 4;
+#else
+	for (unsigned int reg = 0x800; reg < 0x1000; reg += 4) {
+		DEBE_WRITE(sc, reg, 0);
+	}
+
+	DEBE_WRITE(sc, SUNXI_DEBE_MODCTL_REG, SUNXI_DEBE_MODCTL_EN);
+
+	sc->sc_dmasize = SUNXI_DEBE_VIDEOMEM;
+#endif
+
+	DEBE_WRITE(sc, SUNXI_DEBE_HWC_PALETTE_TABLE, 0);
+
+	error = sunxi_debe_alloc_videomem(sc);
+	if (error) {
+		aprint_error_dev(sc->sc_dev,
+		    "couldn't allocate video memory, error = %d\n", error);
+		return;
+	}
+
+#if NAWIN_MP > 0
+	mpdev = device_find_by_driver_unit("sunximp", 0);
+	if (mpdev) {
+		paddr_t pa = sc->sc_dmamap->dm_segs[0].ds_addr;
+		if (pa >= SUNXI_SDRAM_PBASE)
+			pa -= SUNXI_SDRAM_PBASE;
+		sunxi_mp_setbase(mpdev, pa, sc->sc_dmasize);
+	}
+#endif
+	sc->sc_unit = -1;
+	sc->sc_ports.dp_ep_connect = sunxi_debe_ep_connect;
+	sc->sc_ports.dp_ep_enable = sunxi_debe_ep_enable;
+	fdt_ports_register(&sc->sc_ports, self, phandle, EP_OTHER);
+
+#ifdef AWIN_DEBE_FWINIT
+	sunxi_debe_set_videomode(device_unit(self), &mode);
+	sunxi_debe_enable(device_unit(self), true);
+#endif
+}
+
+
+
+static void
+sunxi_debe_ep_connect(device_t self, struct fdt_endpoint *ep, bool connect)
+{
+	struct sunxi_debe_softc *sc = device_private(self);
+	struct fdt_endpoint *rep = fdt_endpoint_remote(ep);
+	int rep_idx = fdt_endpoint_index(rep);
+
+	KASSERT(device_is_a(self, "sunxidebe"));
+	if (!connect) {
+		aprint_error_dev(self, "endpoint disconnect not supported\n");
+		return;
+	}
+
+	if (fdt_endpoint_port_index(ep) == 1) {
+		bool do_print = (sc->sc_unit == -1);
+		/*
+		 * one of our output endpoints has been connected.
+		 * the remote id is our unit number
+		 */
+		if (sc->sc_unit != -1 && rep_idx != -1 &&
+		    sc->sc_unit != rep_idx) {
+			aprint_error_dev(self, ": remote id %d doens't match"
+			    " discovered unit number %d\n",
+			    rep_idx, sc->sc_unit);
+			return;
+		}
+		if (!device_is_a(fdt_endpoint_device(rep), "sunxitcon")) {
+			aprint_error_dev(self,
+			    ": output %d connected to unknown device\n",
+			    fdt_endpoint_index(ep));
+			return;
+		}
+		if (rep_idx != -1)
+			sc->sc_unit = rep_idx;
+		else {
+			/* assume only one debe */
+			sc->sc_unit = 0;
+		}
+		if (do_print)
+			aprint_verbose_dev(self, "debe unit %d\n", sc->sc_unit);
+	}
+}
+
+static int
+sunxi_debe_alloc_videomem(struct sunxi_debe_softc *sc)
+{
+	int error, nsegs;
+
+	error = bus_dmamem_alloc(sc->sc_dmat, sc->sc_dmasize, 0x1000, 0,
+	    sc->sc_dmasegs, 1, &nsegs, BUS_DMA_WAITOK);
+	if (error)
+		return error;
+	error = bus_dmamem_map(sc->sc_dmat, sc->sc_dmasegs, nsegs,
+	    sc->sc_dmasize, &sc->sc_dmap, BUS_DMA_WAITOK | BUS_DMA_COHERENT);
+	if (error)
+		goto free;
+	error = bus_dmamap_create(sc->sc_dmat, sc->sc_dmasize, 1,
+	    sc->sc_dmasize, 0, BUS_DMA_WAITOK, &sc->sc_dmamap);
+	if (error)
+		goto unmap;
+	error = bus_dmamap_load(sc->sc_dmat, sc->sc_dmamap, sc->sc_dmap,
+	    sc->sc_dmasize, NULL, BUS_DMA_WAITOK);
+	if (error)
+		goto destroy;
+
+	memset(sc->sc_dmap, 0, sc->sc_dmasize);
+
+	return 0;
+
+destroy:
+	bus_dmamap_destroy(sc->sc_dmat, sc->sc_dmamap);
+unmap:
+	bus_dmamem_unmap(sc->sc_dmat, sc->sc_dmap, sc->sc_dmasize);
+free:
+	bus_dmamem_free(sc->sc_dmat, sc->sc_dmasegs, nsegs);
+
+	sc->sc_dmasize = 0;
+	sc->sc_dmap = NULL;
+
+	return error;
+}
+
+static void
+sunxi_debe_setup_fbdev(struct sunxi_debe_softc *sc, const struct videomode *mode)
+{
+	if (mode == NULL)
+		return;
+
+	const u_int interlace_p = !!(mode->flags & VID_INTERLACE);
+	const u_int fb_width = mode->hdisplay;
+	const u_int fb_height = (mode->vdisplay << interlace_p);
+
+	if (mode && sc->sc_fbdev == NULL) {
+		struct sunxifb_attach_args afb = {
+			.afb_fb = sc->sc_dmap,
+			.afb_width = fb_width,
+			.afb_height = fb_height,
+			.afb_dmat = sc->sc_dmat,
+			.afb_dmasegs = sc->sc_dmasegs,
+			.afb_ndmasegs = 1
+		};
+		sc->sc_fbdev = config_found_ia(sc->sc_dev, "sunxidebe",
+		    &afb, NULL);
+	} else if (sc->sc_fbdev != NULL) {
+		sunxi_befb_set_videomode(sc->sc_fbdev, fb_width, fb_height);
+	}
+}
+
+static int
+sunxi_debe_set_curpos(struct sunxi_debe_softc *sc, int x, int y)
+{
+	int xx, yy;
+	u_int yoff, xoff;
+
+	xoff = yoff = 0;
+	xx = x - sc->sc_hot_x;
+	yy = y - sc->sc_hot_y;
+	if (xx < 0) {
+		xoff -= xx;
+		xx = 0;
+	}
+	if (yy < 0) {
+		yoff -= yy;
+		yy = 0;
+	}
+
+	DEBE_WRITE(sc, SUNXI_DEBE_HWCCTL_REG,
+	    __SHIFTIN(yy, SUNXI_DEBE_HWCCTL_YCOOR) |
+	    __SHIFTIN(xx, SUNXI_DEBE_HWCCTL_XCOOR));
+	DEBE_WRITE(sc, SUNXI_DEBE_HWCFBCTL_REG,
+#if SUNXI_DEBE_CURMAX == 32
+	    __SHIFTIN(SUNXI_DEBE_HWCFBCTL_YSIZE_32, SUNXI_DEBE_HWCFBCTL_YSIZE) |
+	    __SHIFTIN(SUNXI_DEBE_HWCFBCTL_XSIZE_32, SUNXI_DEBE_HWCFBCTL_XSIZE) |
+#else
+	    __SHIFTIN(SUNXI_DEBE_HWCFBCTL_YSIZE_64, SUNXI_DEBE_HWCFBCTL_YSIZE) |
+	    __SHIFTIN(SUNXI_DEBE_HWCFBCTL_XSIZE_64, SUNXI_DEBE_HWCFBCTL_XSIZE) |
+#endif
+	    __SHIFTIN(SUNXI_DEBE_HWCFBCTL_FBFMT_2BPP, SUNXI_DEBE_HWCFBCTL_FBFMT) |
+	    __SHIFTIN(yoff, SUNXI_DEBE_HWCFBCTL_YCOOROFF) |
+	    __SHIFTIN(xoff, SUNXI_DEBE_HWCFBCTL_XCOOROFF));
+
+	return 0;
+}
+
+static int
+sunxi_debe_set_cursor(struct sunxi_debe_softc *sc, struct wsdisplay_cursor *cur)
+{
+	uint32_t val;
+	uint8_t r[4], g[4], b[4];
+	u_int index, count, shift, off, pcnt;
+	int i, j, idx, error;
+	uint8_t mask;
+
+	if (cur->which & WSDISPLAY_CURSOR_DOCUR) {
+		val = DEBE_READ(sc, SUNXI_DEBE_MODCTL_REG);
+		if (cur->enable)
+			val |= SUNXI_DEBE_MODCTL_HWC_EN;
+		else
+			val &= ~SUNXI_DEBE_MODCTL_HWC_EN;
+		DEBE_WRITE(sc, SUNXI_DEBE_MODCTL_REG, val);
+
+		sc->sc_cursor_enable = cur->enable;
+	}
+
+	if (cur->which & WSDISPLAY_CURSOR_DOHOT) {
+		sc->sc_hot_x = cur->hot.x;
+		sc->sc_hot_y = cur->hot.y;
+		cur->which |= WSDISPLAY_CURSOR_DOPOS;
+	}
+
+	if (cur->which & WSDISPLAY_CURSOR_DOPOS) {
+		sunxi_debe_set_curpos(sc, cur->pos.x, cur->pos.y);
+	}
+
+	if (cur->which & WSDISPLAY_CURSOR_DOCMAP) {
+		index = cur->cmap.index;
+		count = cur->cmap.count;
+		if (index >= 2 || count > 2 - index)
+			return EINVAL;
+		error = copyin(cur->cmap.red, &r[index], count);
+		if (error)
+			return error;
+		error = copyin(cur->cmap.green, &g[index], count);
+		if (error)
+			return error;
+		error = copyin(cur->cmap.blue, &b[index], count);
+		if (error)
+			return error;
+
+		for (i = index; i < (index + count); i++) {
+			DEBE_WRITE(sc,
+			    SUNXI_DEBE_HWC_PALETTE_TABLE + (4 * (i + 2)),
+			    (r[i] << 16) | (g[i] << 8) | b[i] | 0xff000000);
+		}
+	}
+
+	if (cur->which & WSDISPLAY_CURSOR_DOSHAPE) {
+		error = copyin(cur->mask, sc->sc_cursor_mask,
+		    SUNXI_DEBE_CURMAX * 8);
+		if (error)
+			return error;
+		error = copyin(cur->image, sc->sc_cursor_bitmap,
+		    SUNXI_DEBE_CURMAX * 8);
+		if (error)
+			return error;
+	}
+
+	if (cur->which & (WSDISPLAY_CURSOR_DOCMAP|WSDISPLAY_CURSOR_DOSHAPE)) {
+		for (i = 0, pcnt = 0; i < SUNXI_DEBE_CURMAX * 8; i++) {
+			for (j = 0, mask = 1; j < 8; j++, mask <<= 1, pcnt++) {
+				idx = ((sc->sc_cursor_mask[i] & mask) ? 2 : 0) |
+				    ((sc->sc_cursor_bitmap[i] & mask) ? 1 : 0);
+				off = (pcnt >> 4) * 4;
+				shift = (pcnt & 0xf) * 2;
+				val = DEBE_READ(sc,
+				    SUNXI_DEBE_HWC_PATTERN_BLOCK + off);
+				val &= ~(3 << shift);
+				val |= (idx << shift);
+				DEBE_WRITE(sc,
+				    SUNXI_DEBE_HWC_PATTERN_BLOCK + off, val);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int
+sunxi_debe_ep_enable(device_t dev, struct fdt_endpoint *ep, bool enable)
+{
+	struct sunxi_debe_softc *sc;
+	uint32_t val;
+
+	KASSERT(device_is_a(dev, "sunxidebe"));
+	sc = device_private(dev);
+
+	if (enable) {
+		val = DEBE_READ(sc, SUNXI_DEBE_REGBUFFCTL_REG);
+		val |= SUNXI_DEBE_REGBUFFCTL_REGLOADCTL;
+		DEBE_WRITE(sc, SUNXI_DEBE_REGBUFFCTL_REG, val);
+
+		val = DEBE_READ(sc, SUNXI_DEBE_MODCTL_REG);
+		val |= SUNXI_DEBE_MODCTL_START_CTL;
+		DEBE_WRITE(sc, SUNXI_DEBE_MODCTL_REG, val);
+#ifdef SUNXI_DEBE_DEBUG
+		sunxi_debe_dump_regs(sc->sc_unit);
+#endif
+	} else {
+		val = DEBE_READ(sc, SUNXI_DEBE_MODCTL_REG);
+		val &= ~SUNXI_DEBE_MODCTL_START_CTL;
+		DEBE_WRITE(sc, SUNXI_DEBE_MODCTL_REG, val);
+	}
+#if 0
+	for (int i = 0; i < 0x1000; i += 4) {
+		printf("DEBE 0x%04x: 0x%08x\n", i, DEBE_READ(sc, i));
+	}
+#endif
+	return 0;
+}
+
+void
+sunxi_debe_set_videomode(device_t dev, const struct videomode *mode)
+{
+	struct sunxi_debe_softc *sc;
+	uint32_t val;
+
+	KASSERT(device_is_a(dev, "sunxidebe"));
+	sc = device_private(dev);
+
+	if (mode) {
+		const u_int interlace_p = !!(mode->flags & VID_INTERLACE);
+		const u_int width = mode->hdisplay;
+		const u_int height = (mode->vdisplay << interlace_p);
+		const u_int fb_width = width;
+		const u_int fb_height = height;
+		uint32_t vmem = width * height * 4;
+
+		if (vmem > sc->sc_dmasize) {
+			device_printf(sc->sc_dev,
+			    "not enough memory for %ux%u fb (req %u have %u)\n",
+			    width, height, vmem, (unsigned int)sc->sc_dmasize);
+			return;
+		}
+
+		paddr_t pa = sc->sc_dmamap->dm_segs[0].ds_addr;
+#if !defined(ALLWINNER_A80) && 0
+#define SUNXI_SDRAM_PBASE-0 0x40000000
+		/*
+		 * On 2GB systems, we need to subtract AWIN_SDRAM_PBASE from
+		 * the phys addr.
+		 */
+		if (pa >= SUNXI_SDRAM_PBASE)
+			pa -= SUNXI_SDRAM_PBASE;
+#endif
+
+		/* notify fb */
+		sunxi_debe_setup_fbdev(sc, mode);
+
+		DEBE_WRITE(sc, SUNXI_DEBE_DISSIZE_REG,
+		    ((height - 1) << 16) | (width - 1));
+		DEBE_WRITE(sc, SUNXI_DEBE_LAYSIZE_REG,
+		    ((fb_height - 1) << 16) | (fb_width - 1));
+		DEBE_WRITE(sc, SUNXI_DEBE_LAYCOOR_REG, 0);
+		DEBE_WRITE(sc, SUNXI_DEBE_LAYLINEWIDTH_REG, (fb_width << 5));
+		DEBE_WRITE(sc, SUNXI_DEBE_LAYFB_L32ADD_REG, pa << 3);
+		DEBE_WRITE(sc, SUNXI_DEBE_LAYFB_H4ADD_REG, pa >> 29);
+
+		val = DEBE_READ(sc, SUNXI_DEBE_ATTCTL1_REG);
+		val &= ~SUNXI_DEBE_ATTCTL1_LAY_FBFMT;
+		val |= __SHIFTIN(SUNXI_DEBE_ATTCTL1_LAY_FBFMT_XRGB8888,
+				 SUNXI_DEBE_ATTCTL1_LAY_FBFMT);
+		val &= ~SUNXI_DEBE_ATTCTL1_LAY_BRSWAPEN;
+		val &= ~SUNXI_DEBE_ATTCTL1_LAY_FBPS;
+#if __ARMEB__
+		val |= __SHIFTIN(SUNXI_DEBE_ATTCTL1_LAY_FBPS_32BPP_BGRA,
+				 SUNXI_DEBE_ATTCTL1_LAY_FBPS);
+#else
+		val |= __SHIFTIN(SUNXI_DEBE_ATTCTL1_LAY_FBPS_32BPP_ARGB,
+				 SUNXI_DEBE_ATTCTL1_LAY_FBPS);
+#endif
+		DEBE_WRITE(sc, SUNXI_DEBE_ATTCTL1_REG, val);
+
+		val = DEBE_READ(sc, SUNXI_DEBE_MODCTL_REG);
+		val |= SUNXI_DEBE_MODCTL_LAY0_EN;
+		if (interlace_p) {
+			val |= SUNXI_DEBE_MODCTL_ITLMOD_EN;
+		} else {
+			val &= ~SUNXI_DEBE_MODCTL_ITLMOD_EN;
+		}
+		val &= ~SUNXI_DEBE_MODCTL_OUT_SEL;
+		if (sc->sc_unit == 1) {
+			val |= __SHIFTIN(SUNXI_DEBE_MODCTL_OUT_SEL_LCD1,
+			    SUNXI_DEBE_MODCTL_OUT_SEL);
+		}
+		DEBE_WRITE(sc, SUNXI_DEBE_MODCTL_REG, val);
+	} else {
+		/* disable */
+		val = DEBE_READ(sc, SUNXI_DEBE_MODCTL_REG);
+		val &= ~SUNXI_DEBE_MODCTL_LAY0_EN;
+		val &= ~SUNXI_DEBE_MODCTL_START_CTL;
+		DEBE_WRITE(sc, SUNXI_DEBE_MODCTL_REG, val);
+
+		/* notify fb */
+		sunxi_debe_setup_fbdev(sc, mode);
+	}
+}
+
+static int
+sunxi_debe_ioctl(device_t self, u_long cmd, void *data)
+{
+	struct sunxi_debe_softc *sc = device_private(self);
+	struct wsdisplay_curpos *cp;
+	uint32_t val;
+	int enable;
+
+	switch (cmd) {
+	case WSDISPLAYIO_SVIDEO:
+		enable = *(int *)data;
+		val = DEBE_READ(sc, SUNXI_DEBE_MODCTL_REG);
+		if (enable) {
+			if (val & SUNXI_DEBE_MODCTL_START_CTL) {
+				/* already enabled */
+				return 0;
+			}
+		} else {
+			if ((val & SUNXI_DEBE_MODCTL_START_CTL) == 0) {
+				/* already disabled */
+				return 0;
+			}
+		}
+		return fdt_endpoint_enable(sc->sc_out_ep, enable);
+	case WSDISPLAYIO_GVIDEO:
+		val = DEBE_READ(sc, SUNXI_DEBE_MODCTL_REG);
+		*(int *)data = !!(val & SUNXI_DEBE_MODCTL_LAY0_EN);
+		return 0;
+	case WSDISPLAYIO_GCURPOS:
+		cp = data;
+		cp->x = sc->sc_cursor_x;
+		cp->y = sc->sc_cursor_y;
+		return 0;
+	case WSDISPLAYIO_SCURPOS:
+		cp = data;
+		return sunxi_debe_set_curpos(sc, cp->x, cp->y);
+	case WSDISPLAYIO_GCURMAX:
+		cp = data;
+		cp->x = SUNXI_DEBE_CURMAX;
+		cp->y = SUNXI_DEBE_CURMAX;
+		return 0;
+	case WSDISPLAYIO_SCURSOR:
+		return sunxi_debe_set_cursor(sc, data);
+	}
+
+	return EPASSTHROUGH;
+}
+
+/* genfb attachement */
+
+struct sunxi_befb_softc {
+	struct genfb_softc sc_gen;
+	device_t sc_debedev;
+	device_t sc_mpdev;
+
+	bus_dma_tag_t sc_dmat;
+	bus_dma_segment_t *sc_dmasegs;
+	int sc_ndmasegs;
+};
+
+static device_t	sunxi_befb_consoledev = NULL;
+
+static int	sunxi_befb_match(device_t, cfdata_t, void *);
+static void	sunxi_befb_attach(device_t, device_t, void *);
+
+static int	sunxi_befb_ioctl(void *, void *, u_long, void *, int, lwp_t *);
+static paddr_t	sunxi_befb_mmap(void *, void *, off_t, int);
+static bool	sunxi_befb_shutdown(device_t, int);
+
+CFATTACH_DECL_NEW(sunxi_befb, sizeof(struct sunxi_befb_softc),
+	sunxi_befb_match, sunxi_befb_attach, NULL, NULL);
+
+static int
+sunxi_befb_match(device_t parent, cfdata_t cf, void *aux)
+{
+	return 1;
+}
+
+static void
+sunxi_befb_attach(device_t parent, device_t self, void *aux)
+{
+	struct sunxi_befb_softc *sc = device_private(self);
+	struct sunxifb_attach_args * const afb = aux;
+	prop_dictionary_t cfg = device_properties(self);
+	struct genfb_ops ops;
+
+	if (sunxi_befb_consoledev == NULL)
+		sunxi_befb_consoledev = self;
+
+	sc->sc_gen.sc_dev = self;
+	sc->sc_debedev = parent;
+	sc->sc_dmat = afb->afb_dmat;
+	sc->sc_dmasegs = afb->afb_dmasegs;
+	sc->sc_ndmasegs = afb->afb_ndmasegs;
+	sc->sc_mpdev = device_find_by_driver_unit("sunximp", 0);
+
+	prop_dictionary_set_uint32(cfg, "width", afb->afb_width);
+	prop_dictionary_set_uint32(cfg, "height", afb->afb_height);
+	prop_dictionary_set_uint8(cfg, "depth", 32);
+	prop_dictionary_set_uint16(cfg, "linebytes", afb->afb_width * 4);
+	prop_dictionary_set_uint32(cfg, "address", 0);
+	prop_dictionary_set_uint32(cfg, "virtual_address",
+	    (uintptr_t)afb->afb_fb);
+
+	genfb_init(&sc->sc_gen);
+
+	if (sc->sc_gen.sc_width == 0 || sc->sc_gen.sc_fbsize == 0) {
+		aprint_normal(": disabled\n");
+		return;
+	}
+
+	pmf_device_register1(self, NULL, NULL, sunxi_befb_shutdown);
+
+	memset(&ops, 0, sizeof(ops));
+	ops.genfb_ioctl = sunxi_befb_ioctl;
+	ops.genfb_mmap = sunxi_befb_mmap;
+
+	aprint_naive("\n");
+
+	bool is_console = false;
+	prop_dictionary_set_bool(cfg, "is_console", is_console);
+
+	if (is_console)
+		aprint_normal(": switching to framebuffer console\n");
+	else
+		aprint_normal("\n");
+
+	genfb_attach(&sc->sc_gen, &ops);
+}
+
+static int
+sunxi_befb_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, lwp_t *l)
+{
+	struct sunxi_befb_softc *sc = v;
+	struct wsdisplayio_bus_id *busid;
+	struct wsdisplayio_fbinfo *fbi;
+	struct rasops_info *ri;
+	int error;
+
+	switch (cmd) {
+	case WSDISPLAYIO_GTYPE:
+		*(u_int *)data = WSDISPLAY_TYPE_ALLWINNER;
+		return 0;
+	case WSDISPLAYIO_GET_BUSID:
+		busid = data;
+		busid->bus_type = WSDISPLAYIO_BUS_SOC;
+		return 0;
+	case WSDISPLAYIO_GET_FBINFO:
+		fbi = data;
+		ri = &sc->sc_gen.vd.active->scr_ri;
+		error = wsdisplayio_get_fbinfo(ri, fbi);
+		if (error == 0) {
+			fbi->fbi_flags |= WSFB_VRAM_IS_RAM;
+			fbi->fbi_fbsize = sc->sc_dmasegs[0].ds_len;
+#if NAWIN_MP > 0
+			if (sc->sc_mpdev)
+				fbi->fbi_flags |= WSFB_ACCEL;
+#endif
+		}
+		return error;
+	case WSDISPLAYIO_SVIDEO:
+	case WSDISPLAYIO_GVIDEO:
+	case WSDISPLAYIO_GCURPOS:
+	case WSDISPLAYIO_SCURPOS:
+	case WSDISPLAYIO_GCURMAX:
+	case WSDISPLAYIO_SCURSOR:
+		return sunxi_debe_ioctl(sc->sc_debedev, cmd, data);
+#if NAWIN_MP > 0
+	case WSDISPLAYIO_FILL:
+	case WSDISPLAYIO_COPY:
+	case WSDISPLAYIO_SYNC:
+		if (sc->sc_mpdev == NULL)
+			return EPASSTHROUGH;
+		return sunxi_mp_ioctl(sc->sc_mpdev, cmd, data);
+#endif
+	default:
+		return EPASSTHROUGH;
+	}
+}
+
+static paddr_t
+sunxi_befb_mmap(void *v, void *vs, off_t off, int prot)
+{
+	struct sunxi_befb_softc *sc = v;
+
+	if (off < 0 || off >= sc->sc_dmasegs[0].ds_len)
+		return -1;
+
+	return bus_dmamem_mmap(sc->sc_dmat, sc->sc_dmasegs, sc->sc_ndmasegs,
+	    off, prot, BUS_DMA_PREFETCHABLE);
+}
+
+static bool
+sunxi_befb_shutdown(device_t self, int flags)
+{
+	genfb_enable_polling(self);
+	return true;
+}
+
+static void
+sunxi_befb_set_videomode(device_t dev, u_int width, u_int height)
+{
+	struct sunxi_befb_softc *sc = device_private(dev);
+
+	if (sc->sc_gen.sc_width != width || sc->sc_gen.sc_height != height) {
+		device_printf(sc->sc_gen.sc_dev,
+		    "mode switching not yet supported\n");
+	}
+}
+
+int
+sunxi_debe_pipeline(int phandle, bool active)
+{
+	device_t dev;
+	struct sunxi_debe_softc *sc;
+	struct fdt_endpoint *ep;
+	int i, error;
+
+	if (!active)
+		return EOPNOTSUPP;
+
+	for (i = 0;;i++) {
+		dev = device_find_by_driver_unit("sunxidebe", i);
+		if (dev == NULL)
+			return ENODEV;
+		sc = device_private(dev);
+		if (sc->sc_phandle == phandle)
+			break;
+	}
+	aprint_normal("activate %s\n", device_xname(dev));
+	/* connect debd0 to tcon0, debe1 to tcon1 */
+	ep = fdt_endpoint_get_from_index(&sc->sc_ports, SUNXI_PORT_OUTPUT,
+	    sc->sc_unit);
+	if (ep == NULL) {
+		aprint_error_dev(dev, "no output endpoint for %d\n",
+		    sc->sc_unit);
+		return ENODEV;
+	}
+	error = fdt_endpoint_activate(ep, true);
+	if (error == 0) {
+		sc->sc_out_ep = ep;
+		fdt_endpoint_enable(ep, true);
+	}
+	return error;
+}
+
+#if defined(SUNXI_DEBE_DEBUG)
+void
+sunxi_debe_dump_regs(int u)
+{
+	static const struct {
+		const char *name;
+		uint16_t reg;
+	} regs[] = {
+		{ "SUNXI_DEBE_MODCTL_REG", SUNXI_DEBE_MODCTL_REG},
+		{ "SUNXI_DEBE_BACKCOLOR_REG", SUNXI_DEBE_BACKCOLOR_REG},
+		{ "SUNXI_DEBE_DISSIZE_REG", SUNXI_DEBE_DISSIZE_REG},
+		{ "SUNXI_DEBE_LAYSIZE_REG", SUNXI_DEBE_LAYSIZE_REG},
+		{ "SUNXI_DEBE_LAYCOOR_REG", SUNXI_DEBE_LAYCOOR_REG},
+		{ "SUNXI_DEBE_LAYLINEWIDTH_REG", SUNXI_DEBE_LAYLINEWIDTH_REG},
+		{ "SUNXI_DEBE_LAYFB_L32ADD_REG", SUNXI_DEBE_LAYFB_L32ADD_REG},
+		{ "SUNXI_DEBE_LAYFB_H4ADD_REG", SUNXI_DEBE_LAYFB_H4ADD_REG},
+		{ "SUNXI_DEBE_REGBUFFCTL_REG", SUNXI_DEBE_REGBUFFCTL_REG},
+		{ "SUNXI_DEBE_CKMAX_REG", SUNXI_DEBE_CKMAX_REG},
+		{ "SUNXI_DEBE_CKMIN_REG", SUNXI_DEBE_CKMIN_REG},
+		{ "SUNXI_DEBE_CKCFG_REG", SUNXI_DEBE_CKCFG_REG},
+		{ "SUNXI_DEBE_ATTCTL0_REG", SUNXI_DEBE_ATTCTL0_REG},
+		{ "SUNXI_DEBE_ATTCTL1_REG", SUNXI_DEBE_ATTCTL1_REG},
+		{ "SUNXI_DEBE_HWCCTL_REG", SUNXI_DEBE_HWCCTL_REG},
+		{ "SUNXI_DEBE_HWCFBCTL_REG", SUNXI_DEBE_HWCFBCTL_REG},
+		{ "SUNXI_DEBE_WBCTL_REG", SUNXI_DEBE_WBCTL_REG},
+		{ "SUNXI_DEBE_WBADD_REG", SUNXI_DEBE_WBADD_REG},
+		{ "SUNXI_DEBE_WBLINEWIDTH_REG", SUNXI_DEBE_WBLINEWIDTH_REG},
+		{ "SUNXI_DEBE_IYUVCTL_REG", SUNXI_DEBE_IYUVCTL_REG},
+		{ "SUNXI_DEBE_IYUVADD_REG", SUNXI_DEBE_IYUVADD_REG},
+		{ "SUNXI_DEBE_IYUVLINEWIDTH_REG", SUNXI_DEBE_IYUVLINEWIDTH_REG},
+		{ "SUNXI_DEBE_YGCOEF_REG", SUNXI_DEBE_YGCOEF_REG},
+		{ "SUNXI_DEBE_YGCONS_REG", SUNXI_DEBE_YGCONS_REG},
+		{ "SUNXI_DEBE_URCOEF_REG", SUNXI_DEBE_URCOEF_REG},
+		{ "SUNXI_DEBE_URCONS_REG", SUNXI_DEBE_URCONS_REG},
+		{ "SUNXI_DEBE_VBCOEF_REG", SUNXI_DEBE_VBCOEF_REG},
+		{ "SUNXI_DEBE_VBCONS_REG", SUNXI_DEBE_VBCONS_REG},
+		{ "SUNXI_DEBE_OCCTL_REG", SUNXI_DEBE_OCCTL_REG},
+		{ "SUNXI_DEBE_OCRCOEF_REG", SUNXI_DEBE_OCRCOEF_REG},
+		{ "SUNXI_DEBE_OCRCONS_REG", SUNXI_DEBE_OCRCONS_REG},
+		{ "SUNXI_DEBE_OCGCOEF_REG", SUNXI_DEBE_OCGCOEF_REG},
+		{ "SUNXI_DEBE_OCGCONS_REG", SUNXI_DEBE_OCGCONS_REG},
+		{ "SUNXI_DEBE_OCBCOEF_REG", SUNXI_DEBE_OCBCOEF_REG},
+		{ "SUNXI_DEBE_OCBCONS_REG", SUNXI_DEBE_OCBCONS_REG},
+	};
+	struct sunxi_debe_softc *sc;
+	device_t dev;
+
+	dev = device_find_by_driver_unit("sunxidebe", u);
+	if (dev == NULL)
+		return;
+	sc = device_private(dev);
+
+	for (int i = 0; i < __arraycount(regs); i++) {
+		printf("%s: 0x%08x\n", regs[i].name,
+		    DEBE_READ(sc, regs[i].reg));
+	}
+}
+#endif

Index: src/sys/arch/arm/sunxi/sunxi_debereg.h
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_debereg.h:1.1
--- /dev/null	Tue Apr  3 12:52:16 2018
+++ src/sys/arch/arm/sunxi/sunxi_debereg.h	Tue Apr  3 12:52:16 2018
@@ -0,0 +1,138 @@
+/* $NetBSD: sunxi_debereg.h,v 1.1 2018/04/03 12:52:16 bouyer Exp $ */
+
+/*-
+ * Copyright (c) 2013 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Matt Thomas of 3am Software Foundry.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define SUNXI_DEBE_MODCTL_REG		0x0800
+#define SUNXI_DEBE_BACKCOLOR_REG	0x0804
+#define SUNXI_DEBE_DISSIZE_REG		0x0808
+#define SUNXI_DEBE_LAYSIZE_REG		0x0810
+#define SUNXI_DEBE_LAYCOOR_REG		0x0820
+#define SUNXI_DEBE_LAYLINEWIDTH_REG	0x0840
+#define SUNXI_DEBE_LAYFB_L32ADD_REG	0x0850
+#define SUNXI_DEBE_LAYFB_H4ADD_REG	0x0860
+#define SUNXI_DEBE_REGBUFFCTL_REG	0x0870
+#define SUNXI_DEBE_CKMAX_REG		0x0880
+#define SUNXI_DEBE_CKMIN_REG		0x0884
+#define SUNXI_DEBE_CKCFG_REG		0x0888
+#define SUNXI_DEBE_ATTCTL0_REG		0x0890
+#define SUNXI_DEBE_ATTCTL1_REG		0x08A0
+#define SUNXI_DEBE_HWCCTL_REG		0x08D8
+#define SUNXI_DEBE_HWCFBCTL_REG		0x08E0
+#define SUNXI_DEBE_WBCTL_REG		0x08F0
+#define SUNXI_DEBE_WBADD_REG		0x08F4
+#define SUNXI_DEBE_WBLINEWIDTH_REG	0x08F8
+#define SUNXI_DEBE_IYUVCTL_REG		0x0920
+#define SUNXI_DEBE_IYUVADD_REG		0x0930
+#define SUNXI_DEBE_IYUVLINEWIDTH_REG	0x0940
+#define SUNXI_DEBE_YGCOEF_REG		0x0950
+#define SUNXI_DEBE_YGCONS_REG		0x095C
+#define SUNXI_DEBE_URCOEF_REG		0x0960
+#define SUNXI_DEBE_URCONS_REG		0x096C
+#define SUNXI_DEBE_VBCOEF_REG		0x0970
+#define SUNXI_DEBE_VBCONS_REG		0x097C
+#define SUNXI_DEBE_OCCTL_REG		0x09C0
+#define SUNXI_DEBE_OCRCOEF_REG		0x09D0
+#define SUNXI_DEBE_OCRCONS_REG		0x09DC
+#define SUNXI_DEBE_OCGCOEF_REG		0x09E0
+#define SUNXI_DEBE_OCGCONS_REG		0x09EC
+#define SUNXI_DEBE_OCBCOEF_REG		0x09F0
+#define SUNXI_DEBE_OCBCONS_REG		0x09FC
+#define SUNXI_DEBE_HWC_PATTERN_BLOCK	0x4800
+#define SUNXI_DEBE_HWC_PALETTE_TABLE	0x4C00
+
+#define SUNXI_DEBE_MODCTL_LINE_SEL	__BIT(29)
+#define SUNXI_DEBE_MODCTL_ITLMOD_EN	__BIT(28)
+#define SUNXI_DEBE_MODCTL_OUT_SEL	__BITS(22,20)
+#define SUNXI_DEBE_MODCTL_OUT_SEL_LCD0	0
+#define SUNXI_DEBE_MODCTL_OUT_SEL_LCD1	1
+#define SUNXI_DEBE_MODCTL_OUT_SEL_FE0	6
+#define SUNXI_DEBE_MODCTL_OUT_SEL_FE1	7
+#define SUNXI_DEBE_MODCTL_OSCA_EN	__BIT(17)
+#define SUNXI_DEBE_MODCTL_HWC_EN	__BIT(16)
+#define SUNXI_DEBE_MODCTL_LAY3_EN	__BIT(11)
+#define SUNXI_DEBE_MODCTL_LAY2_EN	__BIT(10)
+#define SUNXI_DEBE_MODCTL_LAY1_EN	__BIT(9)
+#define SUNXI_DEBE_MODCTL_LAY0_EN	__BIT(8)
+#define SUNXI_DEBE_MODCTL_START_CTL	__BIT(1)
+#define SUNXI_DEBE_MODCTL_EN		__BIT(0)
+
+#define SUNXI_DEBE_ATTCTL0_LAY_GLBALPHA	__BITS(31,24)
+#define SUNXI_DEBE_ATTCTL0_LAY_WORKMOD	__BITS(23,22)
+#define SUNXI_DEBE_ATTCTL0_PREMUL	__BIT(21,20)
+#define SUNXI_DEBE_ATTCTL0_CKEN		__BITS(19,18)
+#define SUNXI_DEBE_ATTCTL0_LAY_PIPESEL	__BIT(15)
+#define SUNXI_DEBE_ATTCTL0_LAY_PRISEL	__BITS(11,10)
+#define SUNXI_DEBE_ATTCTL0_LAY_VDOSEL	__BIT(4)
+#define SUNXI_DEBE_ATTCTL0_LAY_YUVEN	__BIT(2)
+#define SUNXI_DEBE_ATTCTL0_LAY_VDOEN	__BIT(1)
+#define SUNXI_DEBE_ATTCTL0_LAY_GLBALPHAEN __BIT(0)
+
+#define SUNXI_DEBE_ATTCTL1_LAY_HSCAFCT	__BITS(15,14)
+#define SUNXI_DEBE_ATTCTL1_LAY_WSCAFCT	__BITS(13,12)
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT	__BITS(11,8)
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_MONO_1BPP	0
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_MONO_2BPP	1
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_MONO_4BPP	2
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_MONO_8BPP	3
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_RGB655	4
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_RGB565	5
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_RGB556	6
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_ARGB1555	7
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_RGBA5551	8
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_XRGB8888	9
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_ARGB8888	10
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_RGB888	11
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_ARGB4444	12
+#define SUNXI_DEBE_ATTCTL1_LAY_FBFMT_RGBA4444	13
+#define SUNXI_DEBE_ATTCTL1_LAY_BRSWAPEN	__BIT(2)
+#define SUNXI_DEBE_ATTCTL1_LAY_FBPS	__BITS(1,0)
+#define SUNXI_DEBE_ATTCTL1_LAY_FBPS_32BPP_ARGB	0
+#define SUNXI_DEBE_ATTCTL1_LAY_FBPS_32BPP_BGRA	2
+
+#define SUNXI_DEBE_REGBUFFCTL_REGAUTOLOAD_DIS __BIT(1)
+#define SUNXI_DEBE_REGBUFFCTL_REGLOADCTL	__BIT(0)
+
+#define SUNXI_DEBE_HWCCTL_YCOOR		__BITS(31,16)
+#define SUNXI_DEBE_HWCCTL_XCOOR		__BITS(15,0)
+
+#define SUNXI_DEBE_HWCFBCTL_YCOOROFF	__BITS(31,24)
+#define SUNXI_DEBE_HWCFBCTL_XCOOROFF	__BITS(23,16)
+#define SUNXI_DEBE_HWCFBCTL_YSIZE	__BITS(5,4)
+#define SUNXI_DEBE_HWCFBCTL_YSIZE_32	0
+#define SUNXI_DEBE_HWCFBCTL_YSIZE_64	1
+#define SUNXI_DEBE_HWCFBCTL_XSIZE	__BITS(3,2)
+#define SUNXI_DEBE_HWCFBCTL_XSIZE_32	0
+#define SUNXI_DEBE_HWCFBCTL_XSIZE_64	1
+#define SUNXI_DEBE_HWCFBCTL_FBFMT	__BITS(1,0)
+#define SUNXI_DEBE_HWCFBCTL_FBFMT_1BPP	0
+#define SUNXI_DEBE_HWCFBCTL_FBFMT_2BPP	1
+#define SUNXI_DEBE_HWCFBCTL_FBFMT_4BPP	2
+#define SUNXI_DEBE_HWCFBCTL_FBFMT_8BPP	3
+
Index: src/sys/arch/arm/sunxi/sunxi_dep.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_dep.c:1.1
--- /dev/null	Tue Apr  3 12:52:16 2018
+++ src/sys/arch/arm/sunxi/sunxi_dep.c	Tue Apr  3 12:52:16 2018
@@ -0,0 +1,113 @@
+/*	$NetBSD: sunxi_dep.c,v 1.1 2018/04/03 12:52:16 bouyer Exp $	*/
+
+/*-
+ * Copyright (c) 2018 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Manuel Bouyer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include <sys/cdefs.h>
+
+__KERNEL_RCSID(1, "$NetBSD: sunxi_dep.c,v 1.1 2018/04/03 12:52:16 bouyer Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/systm.h>
+
+#include <libfdt.h>
+
+#include <dev/fdt/fdtvar.h>
+#include <arm/sunxi/sunxi_display.h>
+
+#include "sunxi_debe.h"
+
+struct sunxi_dep_softc {
+	device_t sc_dev;
+	int	sc_phandle;
+};
+
+static const struct of_compat_data compat_data[] = {
+	{"allwinner,sun7i-a20-display-engine", 0},
+	{NULL}
+};
+
+static int sunxi_dep_match(device_t, cfdata_t, void *);
+static void sunxi_dep_attach(device_t, device_t, void *);
+
+CFATTACH_DECL_NEW(sunxi_dep, sizeof(struct sunxi_dep_softc),
+	sunxi_dep_match, sunxi_dep_attach, NULL, NULL);
+
+static int
+sunxi_dep_match(device_t parent, cfdata_t cf, void *aux)
+{
+#if NSUNXI_DEBE > 0
+	struct fdt_attach_args * const faa = aux;
+	if (!of_match_compat_data(faa->faa_phandle, compat_data))
+		return 0;
+	return 1;
+#else
+	return 0;
+#endif
+}
+
+static void
+sunxi_dep_attach(device_t parent, device_t self, void *aux)
+{
+	struct sunxi_dep_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	int len;
+	const u_int *buf;
+	u_int ref;
+	int error;
+
+	sc->sc_dev = self;
+
+	buf = fdt_getprop(fdtbus_get_data(),
+	    fdtbus_phandle2offset(phandle), "allwinner,pipelines", &len);
+	if (buf == NULL || len < sizeof(ref) || (len % sizeof(ref)) != 0) {
+		aprint_error("bad/missing allwinner,pipelines property\n");
+		return;
+	}
+	aprint_naive("\n");
+	aprint_normal(": ");
+#if NSUNXI_DEBE > 0
+	for (int i = 0; i < (len / sizeof(ref)); i++) {
+		if (i > 0)
+			aprint_normal_dev(self, "");
+		ref = be32dec(&buf[i]);
+		error = sunxi_debe_pipeline(
+		    fdtbus_get_phandle_from_native(ref), true);
+		if (error)
+			aprint_error("can't activate pipeline %d\n", i);
+	}
+#else
+	aprint_error("debe not configured\n");
+	return;
+#endif
+}
Index: src/sys/arch/arm/sunxi/sunxi_display.h
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_display.h:1.1
--- /dev/null	Tue Apr  3 12:52:16 2018
+++ src/sys/arch/arm/sunxi/sunxi_display.h	Tue Apr  3 12:52:16 2018
@@ -0,0 +1,39 @@
+/*	$NetBSD: sunxi_display.h,v 1.1 2018/04/03 12:52:16 bouyer Exp $	*/
+
+/*-
+ * Copyright (c) 2018 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Manuel Bouyer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#define SUNXI_PORT_INPUT	0
+#define SUNXI_PORT_OUTPUT	1
+
+struct videomode;
+int sunxi_debe_pipeline(int, bool);
+void sunxi_tcon1_set_videomode(device_t, const struct videomode *);
+void sunxi_debe_set_videomode(device_t, const struct videomode *);
Index: src/sys/arch/arm/sunxi/sunxi_hdmi.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_hdmi.c:1.1
--- /dev/null	Tue Apr  3 12:52:16 2018
+++ src/sys/arch/arm/sunxi/sunxi_hdmi.c	Tue Apr  3 12:52:16 2018
@@ -0,0 +1,1221 @@
+/* $NetBSD: sunxi_hdmi.c,v 1.1 2018/04/03 12:52:16 bouyer Exp $ */
+
+/*-
+ * Copyright (c) 2014 Jared D. McNeill <[email protected]>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_ddb.h"
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: sunxi_hdmi.c,v 1.1 2018/04/03 12:52:16 bouyer Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/intr.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/proc.h>
+#include <sys/mutex.h>
+#include <sys/kthread.h>
+
+#include <dev/fdt/fdtvar.h>
+#include <dev/fdt/fdt_port.h>
+
+#include <dev/i2c/i2cvar.h>
+#include <dev/i2c/ddcvar.h>
+#include <dev/i2c/ddcreg.h>
+#include <dev/videomode/videomode.h>
+#include <dev/videomode/edidvar.h>
+
+#include <arm/sunxi/sunxi_hdmireg.h>
+#include <arm/sunxi/sunxi_display.h>
+
+enum sunxi_hdmi_type {
+	HDMI_A10 = 1,
+	HDMI_A20,
+	HDMI_A31,
+};
+
+struct sunxi_hdmi_softc {
+	device_t sc_dev;
+	int sc_phandle;
+	enum sunxi_hdmi_type sc_type;
+	bus_space_tag_t sc_bst;
+	bus_space_handle_t sc_bsh;
+	struct clk *sc_clk_ahb;
+	struct clk *sc_clk_mod;
+	struct clk *sc_clk_pll0;
+	struct clk *sc_clk_pll1;
+	void *sc_ih;
+	lwp_t *sc_thread;
+
+	struct i2c_controller sc_ic;
+	kmutex_t sc_ic_lock;
+
+	bool sc_display_connected;
+	char sc_display_vendor[16];
+	char sc_display_product[16];
+
+	u_int sc_display_mode;
+	u_int sc_current_display_mode;
+#define DISPLAY_MODE_AUTO	0
+#define DISPLAY_MODE_HDMI	1
+#define DISPLAY_MODE_DVI	2
+	
+	kmutex_t sc_pwr_lock;
+	int	sc_pwr_refcount; /* reference who needs HDMI */
+
+	uint32_t sc_ver;
+	unsigned int sc_i2c_blklen;
+
+	struct fdt_device_ports sc_ports;
+	struct fdt_endpoint *sc_in_ep;
+	struct fdt_endpoint *sc_in_rep;
+	struct fdt_endpoint *sc_out_ep;
+};
+
+#define HDMI_READ(sc, reg)			\
+    bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
+#define HDMI_WRITE(sc, reg, val)		\
+    bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val));
+
+#define HDMI_1_3_P(sc)	((sc)->sc_ver == 0x00010003)
+#define HDMI_1_4_P(sc)	((sc)->sc_ver == 0x00010004)
+
+static const struct of_compat_data compat_data[] = {
+	{"allwinner,sun4i-a10-hdmi", HDMI_A10},
+	{"allwinner,sun7i-a20-hdmi", HDMI_A20},
+	{NULL}
+};
+
+static int	sunxi_hdmi_match(device_t, cfdata_t, void *);
+static void	sunxi_hdmi_attach(device_t, device_t, void *);
+static void	sunxi_hdmi_i2c_init(struct sunxi_hdmi_softc *);
+static int	sunxi_hdmi_i2c_acquire_bus(void *, int);
+static void	sunxi_hdmi_i2c_release_bus(void *, int);
+static int	sunxi_hdmi_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *,
+				   size_t, void *, size_t, int);
+static int	sunxi_hdmi_i2c_xfer(void *, i2c_addr_t, uint8_t, uint8_t,
+				   size_t, int, int);
+static int	sunxi_hdmi_i2c_reset(struct sunxi_hdmi_softc *, int);
+
+static int	sunxi_hdmi_ep_activate(device_t, struct fdt_endpoint *, bool);
+static int	sunxi_hdmi_ep_enable(device_t, struct fdt_endpoint *, bool);
+static void	sunxi_hdmi_do_enable(struct sunxi_hdmi_softc *);
+static void	sunxi_hdmi_read_edid(struct sunxi_hdmi_softc *);
+static int	sunxi_hdmi_read_edid_block(struct sunxi_hdmi_softc *, uint8_t *,
+					  uint8_t);
+static u_int	sunxi_hdmi_get_display_mode(struct sunxi_hdmi_softc *,
+					   const struct edid_info *);
+static void	sunxi_hdmi_video_enable(struct sunxi_hdmi_softc *, bool);
+static void	sunxi_hdmi_set_videomode(struct sunxi_hdmi_softc *,
+					const struct videomode *, u_int);
+static void	sunxi_hdmi_set_audiomode(struct sunxi_hdmi_softc *,
+					const struct videomode *, u_int);
+static void	sunxi_hdmi_hpd(struct sunxi_hdmi_softc *);
+static void	sunxi_hdmi_thread(void *);
+static int	sunxi_hdmi_poweron(struct sunxi_hdmi_softc *, bool);
+#if 0
+static int	sunxi_hdmi_intr(void *);
+#endif
+
+#if defined(DDB)
+void		sunxi_hdmi_dump_regs(void);
+#endif
+
+CFATTACH_DECL_NEW(sunxi_hdmi, sizeof(struct sunxi_hdmi_softc),
+	sunxi_hdmi_match, sunxi_hdmi_attach, NULL, NULL);
+
+static int
+sunxi_hdmi_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct fdt_attach_args * const faa = aux;
+
+	return of_match_compat_data(faa->faa_phandle, compat_data);
+}
+
+static void
+sunxi_hdmi_attach(device_t parent, device_t self, void *aux)
+{
+	struct sunxi_hdmi_softc *sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	bus_addr_t addr;
+	bus_size_t size;
+	uint32_t ver;
+	int error;
+
+	sc->sc_dev = self;
+	sc->sc_phandle = phandle;
+	sc->sc_bst = faa->faa_bst;
+
+	sc->sc_type = of_search_compatible(faa->faa_phandle, compat_data)->data;
+
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+	}
+	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
+		aprint_error(": couldn't map registers\n");
+		return;
+	}
+
+	sc->sc_clk_ahb = fdtbus_clock_get(phandle, "ahb");
+	sc->sc_clk_mod = fdtbus_clock_get(phandle, "mod");
+	sc->sc_clk_pll0 = fdtbus_clock_get(phandle, "pll-0");
+	sc->sc_clk_pll1 = fdtbus_clock_get(phandle, "pll-1");
+
+	if (sc->sc_clk_ahb == NULL || sc->sc_clk_mod == NULL
+	    || sc->sc_clk_pll0 == NULL || sc->sc_clk_pll1 == NULL) {
+		aprint_error(": couldn't get clocks\n");
+		aprint_debug_dev(self, "clk ahb %s mod %s pll-0 %s pll-1 %s\n",
+		    sc->sc_clk_ahb == NULL ? "missing" : "present",
+		    sc->sc_clk_mod == NULL ? "missing" : "present",
+		    sc->sc_clk_pll0 == NULL ? "missing" : "present",
+		    sc->sc_clk_pll1 == NULL ? "missing" : "present");
+		return;
+	}
+
+	error = clk_disable(sc->sc_clk_mod);
+	if (error) {
+		aprint_error(": couldn't disable mod clock\n");
+		return;
+	}
+
+	if (clk_enable(sc->sc_clk_ahb) != 0) {
+		aprint_error(": couldn't enable ahb clock\n");
+		return;
+	}
+#if defined(SUNXI_HDMI_DEBUG)
+	sunxi_hdmi_dump_regs();
+#endif
+
+	/*
+	 * reset device, in case it has been setup by firmware in an
+	 * incompatible way
+	 */
+	for (int i = 0; i <= 0x500; i += 4) {
+		HDMI_WRITE(sc, i, 0);
+	}
+
+	ver = HDMI_READ(sc, SUNXI_HDMI_VERSION_ID_REG);
+
+	const int vmaj = __SHIFTOUT(ver, SUNXI_HDMI_VERSION_ID_H);
+	const int vmin = __SHIFTOUT(ver, SUNXI_HDMI_VERSION_ID_L);
+
+	aprint_naive("\n");
+	aprint_normal(": HDMI %d.%d\n", vmaj, vmin);
+
+	sc->sc_ver = ver;
+	sc->sc_i2c_blklen = 16;
+
+	sc->sc_ports.dp_ep_activate = sunxi_hdmi_ep_activate;
+	sc->sc_ports.dp_ep_enable = sunxi_hdmi_ep_enable;
+	fdt_ports_register(&sc->sc_ports, self, phandle, EP_OTHER);
+
+	mutex_init(&sc->sc_pwr_lock, MUTEX_DEFAULT, IPL_NONE);
+	sunxi_hdmi_i2c_init(sc);
+
+}
+
+static void
+sunxi_hdmi_i2c_init(struct sunxi_hdmi_softc *sc)
+{
+	struct i2c_controller *ic = &sc->sc_ic;
+
+	mutex_init(&sc->sc_ic_lock, MUTEX_DEFAULT, IPL_NONE);
+
+	ic->ic_cookie = sc;
+	ic->ic_acquire_bus = sunxi_hdmi_i2c_acquire_bus;
+	ic->ic_release_bus = sunxi_hdmi_i2c_release_bus;
+	ic->ic_exec = sunxi_hdmi_i2c_exec;
+}
+
+static int
+sunxi_hdmi_i2c_acquire_bus(void *priv, int flags)
+{
+	struct sunxi_hdmi_softc *sc = priv;
+
+	if (flags & I2C_F_POLL) {
+		if (!mutex_tryenter(&sc->sc_ic_lock))
+			return EBUSY;
+	} else {
+		mutex_enter(&sc->sc_ic_lock);
+	}
+
+	return 0;
+}
+
+static void
+sunxi_hdmi_i2c_release_bus(void *priv, int flags)
+{
+	struct sunxi_hdmi_softc *sc = priv;
+
+	mutex_exit(&sc->sc_ic_lock);
+}
+
+static int
+sunxi_hdmi_i2c_exec(void *priv, i2c_op_t op, i2c_addr_t addr,
+    const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
+{
+	struct sunxi_hdmi_softc *sc = priv;
+	uint8_t *pbuf;
+	uint8_t block;
+	int resid;
+	off_t off;
+	int err;
+
+	KASSERT(mutex_owned(&sc->sc_ic_lock));
+	KASSERT(op == I2C_OP_READ_WITH_STOP);
+	KASSERT(addr == DDC_ADDR);
+	KASSERT(cmdlen > 0);
+	KASSERT(buf != NULL);
+
+	err = sunxi_hdmi_i2c_reset(sc, flags);
+	if (err)
+		goto done;
+
+	block = *(const uint8_t *)cmdbuf;
+	off = (block & 1) ? 128 : 0;
+
+	pbuf = buf;
+	resid = len;
+	while (resid > 0) {
+		size_t blklen = min(resid, sc->sc_i2c_blklen);
+
+		err = sunxi_hdmi_i2c_xfer(sc, addr, block >> 1, off, blklen,
+		      SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_EOREAD, flags);
+		if (err)
+			goto done;
+
+		if (HDMI_1_3_P(sc)) {
+			bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh,
+			    SUNXI_HDMI_DDC_FIFO_ACCESS_REG, pbuf, blklen);
+		} else {
+			bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh,
+			    SUNXI_A31_HDMI_DDC_FIFO_ACCESS_REG, pbuf, blklen);
+		}
+
+#ifdef SUNXI_HDMI_DEBUG
+		printf("off=%d:", (int)off);
+		for (int i = 0; i < blklen; i++)
+			printf(" %02x", pbuf[i]);
+		printf("\n");
+#endif
+
+		pbuf += blklen;
+		off += blklen;
+		resid -= blklen;
+	}
+
+done:
+	return err;
+}
+
+static int
+sunxi_hdmi_i2c_xfer_1_3(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg,
+    size_t len, int type, int flags)
+{
+	struct sunxi_hdmi_softc *sc = priv;
+	uint32_t val;
+	int retry;
+
+	val = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG);
+	val &= ~SUNXI_HDMI_DDC_CTRL_FIFO_DIR;
+	HDMI_WRITE(sc, SUNXI_HDMI_DDC_CTRL_REG, val);
+
+	val |= __SHIFTIN(block, SUNXI_HDMI_DDC_SLAVE_ADDR_0);
+	val |= __SHIFTIN(0x60, SUNXI_HDMI_DDC_SLAVE_ADDR_1);
+	val |= __SHIFTIN(reg, SUNXI_HDMI_DDC_SLAVE_ADDR_2);
+	val |= __SHIFTIN(addr, SUNXI_HDMI_DDC_SLAVE_ADDR_3);
+	HDMI_WRITE(sc, SUNXI_HDMI_DDC_SLAVE_ADDR_REG, val);
+
+	val = HDMI_READ(sc, SUNXI_HDMI_DDC_FIFO_CTRL_REG);
+	val |= SUNXI_HDMI_DDC_FIFO_CTRL_ADDR_CLEAR;
+	HDMI_WRITE(sc, SUNXI_HDMI_DDC_FIFO_CTRL_REG, val);
+
+	HDMI_WRITE(sc, SUNXI_HDMI_DDC_BYTE_COUNTER_REG, len);
+
+	HDMI_WRITE(sc, SUNXI_HDMI_DDC_COMMAND_REG, type);
+
+	val = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG);
+	val |= SUNXI_HDMI_DDC_CTRL_ACCESS_CMD_START;
+	HDMI_WRITE(sc, SUNXI_HDMI_DDC_CTRL_REG, val);
+
+	retry = 1000;
+	while (--retry > 0) {
+		val = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG);
+		if ((val & SUNXI_HDMI_DDC_CTRL_ACCESS_CMD_START) == 0)
+			break;
+		delay(1000);
+	}
+	if (retry == 0)
+		return ETIMEDOUT;
+
+	val = HDMI_READ(sc, SUNXI_HDMI_DDC_INT_STATUS_REG);
+	if ((val & SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE) == 0) {
+		device_printf(sc->sc_dev, "xfer failed, status=%08x\n", val);
+		return EIO;
+	}
+
+	return 0;
+}
+
+static int
+sunxi_hdmi_i2c_xfer_1_4(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg,
+    size_t len, int type, int flags)
+{
+	struct sunxi_hdmi_softc *sc = priv;
+	uint32_t val;
+	int retry;
+
+	val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_FIFO_CTRL_REG);
+	val |= SUNXI_A31_HDMI_DDC_FIFO_CTRL_RST;
+	HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_FIFO_CTRL_REG, val);
+
+	val = __SHIFTIN(block, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_SEG_PTR);
+	val |= __SHIFTIN(0x60, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_DDC_CMD);
+	val |= __SHIFTIN(reg, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_OFF_ADR);
+	val |= __SHIFTIN(addr, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_DEV_ADR);
+	HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_REG, val);
+
+	HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_COMMAND_REG,
+	    __SHIFTIN(len, SUNXI_A31_HDMI_DDC_COMMAND_DTC) |
+	    __SHIFTIN(type, SUNXI_A31_HDMI_DDC_COMMAND_CMD));
+
+	val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_CTRL_REG);
+	val |= SUNXI_A31_HDMI_DDC_CTRL_ACCESS_CMD_START;
+	HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_CTRL_REG, val);
+
+	retry = 1000;
+	while (--retry > 0) {
+		val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_CTRL_REG);
+		if ((val & SUNXI_A31_HDMI_DDC_CTRL_ACCESS_CMD_START) == 0)
+			break;
+		if (cold)
+			delay(1000);
+		else
+			kpause("hdmiddc", false, mstohz(10), &sc->sc_ic_lock);
+	}
+	if (retry == 0)
+		return ETIMEDOUT;
+
+	return 0;
+}
+
+static int
+sunxi_hdmi_i2c_xfer(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg,
+    size_t len, int type, int flags)
+{
+	struct sunxi_hdmi_softc *sc = priv;
+	int rv;
+
+	if (HDMI_1_3_P(sc)) {
+		rv = sunxi_hdmi_i2c_xfer_1_3(priv, addr, block, reg, len,
+		    type, flags);
+	} else {
+		rv = sunxi_hdmi_i2c_xfer_1_4(priv, addr, block, reg, len,
+		    type, flags);
+	}
+
+	return rv;
+}
+
+static int
+sunxi_hdmi_i2c_reset(struct sunxi_hdmi_softc *sc, int flags)
+{
+	uint32_t hpd, ctrl;
+
+	hpd = HDMI_READ(sc, SUNXI_HDMI_HPD_REG);
+	if ((hpd & SUNXI_HDMI_HPD_HOTPLUG_DET) == 0) {
+		device_printf(sc->sc_dev, "no device detected\n");
+		return ENODEV;	/* no device plugged in */
+	}
+
+	if (HDMI_1_3_P(sc)) {
+		HDMI_WRITE(sc, SUNXI_HDMI_DDC_FIFO_CTRL_REG, 0);
+		HDMI_WRITE(sc, SUNXI_HDMI_DDC_CTRL_REG,
+		    SUNXI_HDMI_DDC_CTRL_EN | SUNXI_HDMI_DDC_CTRL_SW_RST); 
+
+		delay(1000);
+
+		ctrl = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG);
+		if (ctrl & SUNXI_HDMI_DDC_CTRL_SW_RST) {
+			device_printf(sc->sc_dev, "reset failed (1.3)\n");
+			return EBUSY;
+		}
+
+		/* N=5,M=1 */
+		HDMI_WRITE(sc, SUNXI_HDMI_DDC_CLOCK_REG,
+		    __SHIFTIN(5, SUNXI_HDMI_DDC_CLOCK_N) |
+		    __SHIFTIN(1, SUNXI_HDMI_DDC_CLOCK_M));
+
+		HDMI_WRITE(sc, SUNXI_HDMI_DDC_DBG_REG, 0x300);
+	} else {
+		HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_CTRL_REG,
+		    SUNXI_A31_HDMI_DDC_CTRL_SW_RST);
+
+		/* N=1,M=12 */
+		HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_CLOCK_REG,
+		    __SHIFTIN(1, SUNXI_HDMI_DDC_CLOCK_N) |
+		    __SHIFTIN(12, SUNXI_HDMI_DDC_CLOCK_M));
+
+		HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_CTRL_REG,
+		    SUNXI_A31_HDMI_DDC_CTRL_SDA_PAD_EN |
+		    SUNXI_A31_HDMI_DDC_CTRL_SCL_PAD_EN |
+		    SUNXI_A31_HDMI_DDC_CTRL_EN);
+	}
+
+	return 0;
+}
+
+static int
+sunxi_hdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
+{
+	struct sunxi_hdmi_softc *sc = device_private(dev);
+	struct fdt_endpoint *in_ep, *out_ep;
+	int error;
+
+	/* our input is activated by tcon, we activate our output */
+	if (fdt_endpoint_port_index(ep) != SUNXI_PORT_INPUT) {
+		panic("sunxi_hdmi_ep_activate: port %d",
+		    fdt_endpoint_port_index(ep));
+	}
+
+	if (!activate)
+		return EOPNOTSUPP;
+
+	/* check that out other input is not active */
+	switch (fdt_endpoint_index(ep)) {
+	case 0:
+		in_ep = fdt_endpoint_get_from_index(&sc->sc_ports,
+		    SUNXI_PORT_INPUT, 1);
+		break;
+	case 1:
+		in_ep = fdt_endpoint_get_from_index(&sc->sc_ports,
+		    SUNXI_PORT_INPUT, 0);
+		break;
+	default:
+		in_ep = NULL;
+		panic("sunxi_hdmi_ep_activate: input index %d",
+		    fdt_endpoint_index(ep));
+	}
+	if (in_ep != NULL) {
+		if (fdt_endpoint_is_active(in_ep))
+			return EBUSY;
+	}
+	/* only one output */
+	out_ep = fdt_endpoint_get_from_index(&sc->sc_ports,
+		   SUNXI_PORT_OUTPUT, 0);
+	if (out_ep == NULL) {
+		aprint_error_dev(dev, "no output endpoint\n");
+		return ENODEV;
+	}
+	error = fdt_endpoint_activate(out_ep, activate);
+	if (error == 0) {
+		sc->sc_in_ep = ep;
+		sc->sc_in_rep = fdt_endpoint_remote(ep);
+		sc->sc_out_ep = out_ep;
+		sunxi_hdmi_do_enable(sc);
+		return 0;
+	}
+	return error;
+}
+
+static int
+sunxi_hdmi_ep_enable(device_t dev, struct fdt_endpoint *ep, bool enable)
+{
+	struct sunxi_hdmi_softc *sc = device_private(dev);
+	int error;
+
+	if (fdt_endpoint_port_index(ep) == SUNXI_PORT_INPUT) {
+		KASSERT(ep == sc->sc_in_ep);
+		if (sc->sc_thread == NULL) {
+			if (enable) {
+				delay(50000);
+				mutex_enter(&sc->sc_pwr_lock);
+				sunxi_hdmi_hpd(sc);
+				mutex_exit(&sc->sc_pwr_lock);
+				kthread_create(PRI_NONE, KTHREAD_MPSAFE, NULL,
+				    sunxi_hdmi_thread, sc, &sc->sc_thread, "%s",
+				    device_xname(dev));
+			}
+			return 0;
+		} else {
+			mutex_enter(&sc->sc_pwr_lock);
+			error = sunxi_hdmi_poweron(sc, enable);
+			mutex_exit(&sc->sc_pwr_lock);
+			return error;
+		}
+	}
+	panic("sunxi_hdmi_ep_enable");
+}
+
+static void
+sunxi_hdmi_do_enable(struct sunxi_hdmi_softc *sc)
+{
+	/* complete attach */
+	struct clk *clk;
+	int error;
+	uint32_t dbg0_reg;
+
+	/* assume tcon0 uses pll3, tcon1 uses pll7 */
+	switch(fdt_endpoint_index(sc->sc_in_ep)) {
+	case 0:
+		clk = sc->sc_clk_pll0;
+		dbg0_reg = (0<<21);
+		break;
+	case 1:
+		clk = sc->sc_clk_pll1;
+		dbg0_reg = (1<<21);
+		break;
+	default:
+		panic("sunxi_hdmi pll");
+	}
+	error = clk_set_rate(clk, 270000000);
+	if (error) {
+		clk = clk_get_parent(clk);
+		/* probably because this is pllx2 */
+		error = clk_set_rate(clk, 270000000);
+	}
+	if (error) {
+		aprint_error_dev(sc->sc_dev, ": couldn't init pll clock\n");
+		return;
+	}
+	error = clk_set_parent(sc->sc_clk_mod, clk);
+	if (error) {
+		aprint_error_dev(sc->sc_dev, ": couldn't set mod clock parent\n");
+		return;
+	}
+	error = clk_enable(sc->sc_clk_mod);
+	if (error) {
+		aprint_error_dev(sc->sc_dev, ": couldn't enable mod clock\n");
+		return;
+	}
+	delay(1000);
+
+	HDMI_WRITE(sc, SUNXI_HDMI_CTRL_REG, SUNXI_HDMI_CTRL_MODULE_EN);
+	delay(1000);
+	if (sc->sc_type == HDMI_A20) {
+		HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL0_REG, 0xfe800000);
+		HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL1_REG, 0x00d8c830);
+	} else if (sc->sc_type == HDMI_A31) {
+		HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL0_REG, 0x7e80000f);
+		HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL1_REG, 0x01ded030);
+	}
+	HDMI_WRITE(sc, SUNXI_HDMI_PLL_DBG0_REG, dbg0_reg);
+	delay(1000);
+}
+
+static int
+sunxi_hdmi_read_edid_block(struct sunxi_hdmi_softc *sc, uint8_t *data,
+    uint8_t block)
+{
+	i2c_tag_t tag = &sc->sc_ic;
+	uint8_t wbuf[2];
+	int error;
+
+	if ((error = iic_acquire_bus(tag, I2C_F_POLL)) != 0)
+		return error;
+
+	wbuf[0] = block;	/* start address */
+
+	if ((error = iic_exec(tag, I2C_OP_READ_WITH_STOP, DDC_ADDR, wbuf, 1,
+	    data, 128, I2C_F_POLL)) != 0) {
+		iic_release_bus(tag, I2C_F_POLL);
+		return error;
+	}
+	iic_release_bus(tag, I2C_F_POLL);
+
+	return 0;
+}
+
+static void
+sunxi_hdmi_read_edid(struct sunxi_hdmi_softc *sc)
+{
+	const struct videomode *mode;
+	char edid[128];
+	struct edid_info ei;
+	int retry = 4;
+	u_int display_mode;
+
+	memset(edid, 0, sizeof(edid));
+	memset(&ei, 0, sizeof(ei));
+
+	while (--retry > 0) {
+		if (!sunxi_hdmi_read_edid_block(sc, edid, 0))
+			break;
+	}
+	if (retry == 0) {
+		device_printf(sc->sc_dev, "failed to read EDID\n");
+	} else {
+		if (edid_parse(edid, &ei) != 0) {
+			device_printf(sc->sc_dev, "failed to parse EDID\n");
+		}
+#ifdef SUNXI_HDMI_DEBUG
+		else {
+			edid_print(&ei);
+		}
+#endif
+	}
+
+	if (sc->sc_display_mode == DISPLAY_MODE_AUTO)
+		display_mode = sunxi_hdmi_get_display_mode(sc, &ei);
+	else
+		display_mode = sc->sc_display_mode;
+
+	const char *forced = sc->sc_display_mode == DISPLAY_MODE_AUTO ?
+	    "auto-detected" : "forced";
+	device_printf(sc->sc_dev, "%s mode (%s)\n",
+	    display_mode == DISPLAY_MODE_HDMI ? "HDMI" : "DVI", forced);
+
+	strlcpy(sc->sc_display_vendor, ei.edid_vendorname,
+	    sizeof(sc->sc_display_vendor));
+	strlcpy(sc->sc_display_product, ei.edid_productname,
+	    sizeof(sc->sc_display_product));
+	sc->sc_current_display_mode = display_mode;
+
+	mode = ei.edid_preferred_mode;
+	if (mode == NULL)
+		mode = pick_mode_by_ref(640, 480, 60);
+
+	if (mode != NULL) {
+		sunxi_hdmi_video_enable(sc, false);
+		fdt_endpoint_enable(sc->sc_in_ep, false);
+		delay(20000);
+
+		sunxi_tcon1_set_videomode(
+		    fdt_endpoint_device(sc->sc_in_rep), mode);
+		sunxi_hdmi_set_videomode(sc, mode, display_mode);
+		sunxi_hdmi_set_audiomode(sc, mode, display_mode);
+		fdt_endpoint_enable(sc->sc_in_ep, true);
+		delay(20000);
+		sunxi_hdmi_video_enable(sc, true);
+	}
+}
+
+static u_int
+sunxi_hdmi_get_display_mode(struct sunxi_hdmi_softc *sc,
+    const struct edid_info *ei)
+{
+	char edid[128];
+	bool found_hdmi = false;
+	unsigned int n, p;
+
+	/*
+	 * Scan through extension blocks, looking for a CEA-861-D v3
+	 * block. If an HDMI Vendor-Specific Data Block (HDMI VSDB) is
+	 * found in that, assume HDMI mode.
+	 */
+	for (n = 1; n <= MIN(ei->edid_ext_block_count, 4); n++) {
+		if (sunxi_hdmi_read_edid_block(sc, edid, n)) {
+#ifdef SUNXI_HDMI_DEBUG
+			device_printf(sc->sc_dev,
+			    "Failed to read EDID block %d\n", n);
+#endif
+			break;
+		}
+
+#ifdef SUNXI_HDMI_DEBUG
+		device_printf(sc->sc_dev, "EDID block #%d:\n", n);
+#endif
+
+		const uint8_t tag = edid[0];
+		const uint8_t rev = edid[1];
+		const uint8_t off = edid[2];
+
+#ifdef SUNXI_HDMI_DEBUG
+		device_printf(sc->sc_dev, "  Tag %d, Revision %d, Offset %d\n",
+		    tag, rev, off);
+		device_printf(sc->sc_dev, "  Flags: 0x%02x\n", edid[3]);
+#endif
+
+		/* We are looking for a CEA-861-D tag (02h) with revision 3 */
+		if (tag != 0x02 || rev != 3)
+			continue;
+		/*
+		 * CEA data block collection starts at byte 4, so the
+		 * DTD blocks must start after it.
+		 */
+		if (off <= 4)
+			continue;
+
+		/* Parse the CEA data blocks */
+		for (p = 4; p < off;) {
+			const uint8_t btag = (edid[p] >> 5) & 0x7;
+			const uint8_t blen = edid[p] & 0x1f;
+
+#ifdef SUNXI_HDMI_DEBUG
+			device_printf(sc->sc_dev, "  CEA data block @ %d\n", p);
+			device_printf(sc->sc_dev, "    Tag %d, Length %d\n",
+			    btag, blen);
+#endif
+
+			/* Make sure the length is sane */
+			if (p + blen + 1 > off)
+				break;
+			/* Looking for a VSDB tag */
+			if (btag != 3)
+				goto next_block;
+			/* HDMI VSDB is at least 5 bytes long */
+			if (blen < 5)
+				goto next_block;
+
+#ifdef SUNXI_HDMI_DEBUG
+			device_printf(sc->sc_dev, "    ID: %02x%02x%02x\n",
+			    edid[p + 1], edid[p + 2], edid[p + 3]);
+#endif
+
+			/* HDMI 24-bit IEEE registration ID is 0x000C03 */
+			if (memcmp(&edid[p + 1], "\x03\x0c\x00", 3) == 0)
+				found_hdmi = true;
+
+next_block:
+			p += (1 + blen);
+		}
+	}
+
+	return found_hdmi ? DISPLAY_MODE_HDMI : DISPLAY_MODE_DVI;
+}
+
+static void
+sunxi_hdmi_video_enable(struct sunxi_hdmi_softc *sc, bool enable)
+{
+	uint32_t val;
+
+	fdt_endpoint_enable(sc->sc_out_ep, enable);
+	val = HDMI_READ(sc, SUNXI_HDMI_VID_CTRL_REG);
+	val &= ~SUNXI_HDMI_VID_CTRL_SRC_SEL;
+#ifdef SUNXI_HDMI_CBGEN
+	val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_SRC_SEL_CBGEN,
+			 SUNXI_HDMI_VID_CTRL_SRC_SEL);
+#else
+	val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_SRC_SEL_RGB,
+			 SUNXI_HDMI_VID_CTRL_SRC_SEL);
+#endif
+	if (enable) {
+		val |= SUNXI_HDMI_VID_CTRL_VIDEO_EN;
+	} else {
+		val &= ~SUNXI_HDMI_VID_CTRL_VIDEO_EN;
+	}
+	HDMI_WRITE(sc, SUNXI_HDMI_VID_CTRL_REG, val);
+
+#if defined(SUNXI_HDMI_DEBUG)
+	sunxi_hdmi_dump_regs();
+#endif
+}
+
+static void
+sunxi_hdmi_set_videomode(struct sunxi_hdmi_softc *sc,
+    const struct videomode *mode, u_int display_mode)
+{
+	uint32_t val;
+	const u_int dblscan_p = !!(mode->flags & VID_DBLSCAN);
+	const u_int interlace_p = !!(mode->flags & VID_INTERLACE);
+	const u_int phsync_p = !!(mode->flags & VID_PHSYNC);
+	const u_int pvsync_p = !!(mode->flags & VID_PVSYNC);
+	const u_int hfp = mode->hsync_start - mode->hdisplay;
+	const u_int hspw = mode->hsync_end - mode->hsync_start;
+	const u_int hbp = mode->htotal - mode->hsync_start;
+	const u_int vfp = mode->vsync_start - mode->vdisplay;
+	const u_int vspw = mode->vsync_end - mode->vsync_start;
+	const u_int vbp = mode->vtotal - mode->vsync_start;
+	struct clk *clk_pll;
+	int parent_rate;
+	int best_div, best_dbl, best_diff;
+	int target_rate = mode->dot_clock * 1000;
+
+#ifdef SUNXI_HDMI_DEBUG
+	device_printf(sc->sc_dev,
+	    "dblscan %d, interlace %d, phsync %d, pvsync %d\n",
+	    dblscan_p, interlace_p, phsync_p, pvsync_p);
+	device_printf(sc->sc_dev, "h: %u %u %u %u\n",
+	    mode->hdisplay, hbp, hfp, hspw);
+	device_printf(sc->sc_dev, "v: %u %u %u %u\n",
+	    mode->vdisplay, vbp, vfp, vspw);
+#endif
+
+	HDMI_WRITE(sc, SUNXI_HDMI_INT_STATUS_REG, 0xffffffff);
+
+	/* assume tcon0 uses pll3, tcon1 uses pll7 */
+	switch(fdt_endpoint_index(sc->sc_in_ep)) {
+	case 0:
+		clk_pll = sc->sc_clk_pll0;
+		break;
+	case 1:
+		clk_pll = sc->sc_clk_pll1;
+		break;
+	default:
+		panic("sunxi_hdmi pll");
+	}
+	parent_rate = clk_get_rate(clk_pll);
+	KASSERT(parent_rate > 0);
+	best_div = best_dbl = 0;
+	best_diff = INT_MAX;
+	for (int d = 2; d > 0 && best_diff != 0; d--) {
+		for (int m = 1; m <= 16 && best_diff != 0; m++) {
+			int cur_rate = parent_rate / m / d;
+			int diff = abs(target_rate - cur_rate);
+			if (diff >= 0 && diff < best_diff) {
+				best_diff = diff;
+				best_div = m;
+				best_dbl = d;
+			}
+		}
+	}
+
+#ifdef SUNXI_HDMI_DEBUG
+	device_printf(sc->sc_dev, "parent rate: %d\n", parent_rate);
+	device_printf(sc->sc_dev, "dot_clock: %d\n", mode->dot_clock);
+	device_printf(sc->sc_dev, "clkdiv: %d\n", best_div);
+	device_printf(sc->sc_dev, "clkdbl: %c\n", (best_dbl == 1) ? 'Y' : 'N');
+#endif
+
+	if (best_div == 0) {
+		device_printf(sc->sc_dev, "ERROR: TCON clk not configured\n");
+		return;
+	}
+
+	uint32_t pll_ctrl, pad_ctrl0, pad_ctrl1;
+	if (HDMI_1_4_P(sc)) {
+		pad_ctrl0 = 0x7e8000ff;
+		pad_ctrl1 = 0x01ded030;
+		pll_ctrl = 0xba48a308;
+		pll_ctrl |= __SHIFTIN(best_div - 1, SUNXI_HDMI_PLL_CTRL_PREDIV);
+	} else {
+		pad_ctrl0 = 0xfe800000;
+		pad_ctrl1 = 0x00d8c830;
+		pll_ctrl = 0xfa4ef708;
+		pll_ctrl |= __SHIFTIN(best_div, SUNXI_HDMI_PLL_CTRL_PREDIV);
+	}
+	if (best_dbl == 2)
+		pad_ctrl1 |= 0x40;
+
+	HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL0_REG, pad_ctrl0);
+	HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL1_REG, pad_ctrl1);
+	HDMI_WRITE(sc, SUNXI_HDMI_PLL_CTRL_REG, pll_ctrl);
+	/* assume tcon0 uses pll3, tcon1 uses pll7 */
+	switch(fdt_endpoint_index(sc->sc_in_ep)) {
+	case 0:
+		HDMI_WRITE(sc, SUNXI_HDMI_PLL_DBG0_REG, (0<<21));
+		break;
+	case 1:
+		HDMI_WRITE(sc, SUNXI_HDMI_PLL_DBG0_REG, (1<<21));
+		break;
+	default:
+		panic("sunxi_hdmi pll");
+	}
+
+	val = HDMI_READ(sc, SUNXI_HDMI_VID_CTRL_REG);
+	val &= ~SUNXI_HDMI_VID_CTRL_HDMI_MODE;
+	if (display_mode == DISPLAY_MODE_DVI) {
+		val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_HDMI_MODE_DVI,
+				 SUNXI_HDMI_VID_CTRL_HDMI_MODE);
+	} else {
+		val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_HDMI_MODE_HDMI,
+				 SUNXI_HDMI_VID_CTRL_HDMI_MODE);
+	}
+	val &= ~SUNXI_HDMI_VID_CTRL_REPEATER_SEL;
+	if (dblscan_p) {
+		val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_REPEATER_SEL_2X,
+				 SUNXI_HDMI_VID_CTRL_REPEATER_SEL);
+	}
+	val &= ~SUNXI_HDMI_VID_CTRL_OUTPUT_FMT;
+	if (interlace_p) {
+		val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_OUTPUT_FMT_INTERLACE,
+				 SUNXI_HDMI_VID_CTRL_OUTPUT_FMT);
+	}
+	HDMI_WRITE(sc, SUNXI_HDMI_VID_CTRL_REG, val);
+
+	val = __SHIFTIN((mode->hdisplay << dblscan_p) - 1,
+			SUNXI_HDMI_VID_TIMING_0_ACT_H);
+	val |= __SHIFTIN(mode->vdisplay - 1,
+			 SUNXI_HDMI_VID_TIMING_0_ACT_V);
+	HDMI_WRITE(sc, SUNXI_HDMI_VID_TIMING_0_REG, val);
+
+	val = __SHIFTIN((hbp << dblscan_p) - 1,
+			SUNXI_HDMI_VID_TIMING_1_HBP);
+	val |= __SHIFTIN(vbp - 1,
+			 SUNXI_HDMI_VID_TIMING_1_VBP);
+	HDMI_WRITE(sc, SUNXI_HDMI_VID_TIMING_1_REG, val);
+
+	val = __SHIFTIN((hfp << dblscan_p) - 1,
+			SUNXI_HDMI_VID_TIMING_2_HFP);
+	val |= __SHIFTIN(vfp - 1,
+			 SUNXI_HDMI_VID_TIMING_2_VFP);
+	HDMI_WRITE(sc, SUNXI_HDMI_VID_TIMING_2_REG, val);
+
+	val = __SHIFTIN((hspw << dblscan_p) - 1,
+			SUNXI_HDMI_VID_TIMING_3_HSPW);
+	val |= __SHIFTIN(vspw - 1,
+			 SUNXI_HDMI_VID_TIMING_3_VSPW);
+	HDMI_WRITE(sc, SUNXI_HDMI_VID_TIMING_3_REG, val);
+
+	val = 0;
+	if (phsync_p) {
+		val |= SUNXI_HDMI_VID_TIMING_4_HSYNC_ACTIVE_SEL;
+	}
+	if (pvsync_p) {
+		val |= SUNXI_HDMI_VID_TIMING_4_VSYNC_ACTIVE_SEL;
+	}
+	val |= __SHIFTIN(SUNXI_HDMI_VID_TIMING_4_TX_CLOCK_NORMAL,
+			 SUNXI_HDMI_VID_TIMING_4_TX_CLOCK);
+	HDMI_WRITE(sc, SUNXI_HDMI_VID_TIMING_4_REG, val);
+
+	/* Packet control */
+	HDMI_WRITE(sc, SUNXI_HDMI_GP_PKT0_REG, 0);
+	HDMI_WRITE(sc, SUNXI_HDMI_GP_PKT1_REG, 0);
+	HDMI_WRITE(sc, SUNXI_HDMI_PKT_CTRL0_REG, 0x00005321);
+	HDMI_WRITE(sc, SUNXI_HDMI_PKT_CTRL1_REG, 0x0000000f);
+}
+
+static void
+sunxi_hdmi_set_audiomode(struct sunxi_hdmi_softc *sc,
+    const struct videomode *mode, u_int display_mode)
+{
+	uint32_t cts, n, val;
+
+	/*
+	 * Before changing audio parameters, disable and reset the
+	 * audio module. Wait for the soft reset bit to clear before
+	 * configuring the audio parameters.
+	 */
+	val = HDMI_READ(sc, SUNXI_HDMI_AUD_CTRL_REG);
+	val &= ~SUNXI_HDMI_AUD_CTRL_EN;
+	val |= SUNXI_HDMI_AUD_CTRL_RST;
+	HDMI_WRITE(sc, SUNXI_HDMI_AUD_CTRL_REG, val);
+	do {
+		val = HDMI_READ(sc, SUNXI_HDMI_AUD_CTRL_REG);
+	} while (val & SUNXI_HDMI_AUD_CTRL_RST);
+
+	/* No audio support in DVI mode */
+	if (display_mode != DISPLAY_MODE_HDMI) {
+		return;
+	}
+
+	/* DMA & FIFO control */
+	val = HDMI_READ(sc, SUNXI_HDMI_ADMA_CTRL_REG);
+	if (sc->sc_type == HDMI_A31) {
+		val |= SUNXI_HDMI_ADMA_CTRL_SRC_DMA_MODE;	/* NDMA */
+	} else {
+		val &= ~SUNXI_HDMI_ADMA_CTRL_SRC_DMA_MODE;	/* DDMA */
+	}
+	val &= ~SUNXI_HDMI_ADMA_CTRL_SRC_DMA_SAMPLE_RATE;
+	val &= ~SUNXI_HDMI_ADMA_CTRL_SRC_SAMPLE_LAYOUT;
+	val &= ~SUNXI_HDMI_ADMA_CTRL_SRC_WORD_LEN;
+	val &= ~SUNXI_HDMI_ADMA_CTRL_DATA_SEL;
+	HDMI_WRITE(sc, SUNXI_HDMI_ADMA_CTRL_REG, val);
+
+	/* Audio format control */
+	val = HDMI_READ(sc, SUNXI_HDMI_AUD_FMT_REG);
+	val &= ~SUNXI_HDMI_AUD_FMT_SRC_SEL;
+	val &= ~SUNXI_HDMI_AUD_FMT_SEL;
+	val &= ~SUNXI_HDMI_AUD_FMT_DSD_FMT;
+	val &= ~SUNXI_HDMI_AUD_FMT_LAYOUT;
+	val &= ~SUNXI_HDMI_AUD_FMT_SRC_CH_CFG;
+	val |= __SHIFTIN(1, SUNXI_HDMI_AUD_FMT_SRC_CH_CFG);
+	HDMI_WRITE(sc, SUNXI_HDMI_AUD_FMT_REG, val);
+
+	/* PCM control (channel map) */
+	HDMI_WRITE(sc, SUNXI_HDMI_AUD_PCM_CTRL_REG, 0x76543210);
+
+	/* Clock setup */
+	n = 6144;	/* 48 kHz */
+	cts = ((mode->dot_clock * 10) * (n / 128)) / 480;
+	HDMI_WRITE(sc, SUNXI_HDMI_AUD_CTS_REG, cts);
+	HDMI_WRITE(sc, SUNXI_HDMI_AUD_N_REG, n);
+
+	/* Audio PCM channel status 0 */
+	val = __SHIFTIN(SUNXI_HDMI_AUD_CH_STATUS0_FS_FREQ_48,
+			SUNXI_HDMI_AUD_CH_STATUS0_FS_FREQ);
+	HDMI_WRITE(sc, SUNXI_HDMI_AUD_CH_STATUS0_REG, val);
+
+	/* Audio PCM channel status 1 */
+	val = HDMI_READ(sc, SUNXI_HDMI_AUD_CH_STATUS1_REG);
+	val &= ~SUNXI_HDMI_AUD_CH_STATUS1_CGMS_A;
+	val &= ~SUNXI_HDMI_AUD_CH_STATUS1_ORIGINAL_FS;
+	val &= ~SUNXI_HDMI_AUD_CH_STATUS1_WORD_LEN;
+	val |= __SHIFTIN(5, SUNXI_HDMI_AUD_CH_STATUS1_WORD_LEN);
+	val |= SUNXI_HDMI_AUD_CH_STATUS1_WORD_LEN_MAX;
+	HDMI_WRITE(sc, SUNXI_HDMI_AUD_CH_STATUS1_REG, val);
+
+	/* Re-enable */
+	val = HDMI_READ(sc, SUNXI_HDMI_AUD_CTRL_REG);
+	val |= SUNXI_HDMI_AUD_CTRL_EN;
+	HDMI_WRITE(sc, SUNXI_HDMI_AUD_CTRL_REG, val);
+
+#if defined(SUNXI_HDMI_DEBUG)
+	sunxi_hdmi_dump_regs();
+#endif
+}
+
+static void
+sunxi_hdmi_hpd(struct sunxi_hdmi_softc *sc)
+{
+	uint32_t hpd = HDMI_READ(sc, SUNXI_HDMI_HPD_REG);
+	bool con = !!(hpd & SUNXI_HDMI_HPD_HOTPLUG_DET);
+
+	KASSERT(mutex_owned(&sc->sc_pwr_lock));
+	if (sc->sc_display_connected == con)
+		return;
+
+	if (con) {
+		device_printf(sc->sc_dev, "display connected\n");
+		sc->sc_pwr_refcount  = 1;
+		sunxi_hdmi_read_edid(sc);
+	} else {
+		device_printf(sc->sc_dev, "display disconnected\n");
+		sc->sc_pwr_refcount = 0;
+		sunxi_hdmi_video_enable(sc, false);
+		fdt_endpoint_enable(sc->sc_in_ep, false);
+		sunxi_tcon1_set_videomode(
+		    fdt_endpoint_device(sc->sc_in_rep), NULL);
+	}
+
+	sc->sc_display_connected = con;
+}
+
+static void
+sunxi_hdmi_thread(void *priv)
+{
+	struct sunxi_hdmi_softc *sc = priv;
+
+	for (;;) {
+		mutex_enter(&sc->sc_pwr_lock);
+		sunxi_hdmi_hpd(sc);
+		mutex_exit(&sc->sc_pwr_lock);
+		kpause("hdmihotplug", false, mstohz(1000), NULL);
+	}
+}
+
+static int
+sunxi_hdmi_poweron(struct sunxi_hdmi_softc *sc, bool enable)
+{
+	int error = 0;
+	KASSERT(mutex_owned(&sc->sc_pwr_lock));
+	if (!sc->sc_display_connected)
+		return EOPNOTSUPP;
+	if (enable) {
+		KASSERT(sc->sc_pwr_refcount >= 0);
+		if (sc->sc_pwr_refcount == 0) {
+			error = fdt_endpoint_enable(sc->sc_in_ep, true);
+			if (error)
+				return error;
+			sunxi_hdmi_video_enable(sc, true);
+		}
+		sc->sc_pwr_refcount++;
+	} else {
+		sc->sc_pwr_refcount--;
+		KASSERT(sc->sc_pwr_refcount >= 0);
+		if (sc->sc_pwr_refcount == 0) {
+			sunxi_hdmi_video_enable(sc, false);
+			error = fdt_endpoint_enable(sc->sc_in_ep, false);
+		}
+	}
+	return error;
+}
+#if 0
+static int
+sunxi_hdmi_intr(void *priv)
+{
+	struct sunxi_hdmi_softc *sc = priv;
+	uint32_t intsts;
+
+	intsts = HDMI_READ(sc, SUNXI_HDMI_INT_STATUS_REG);
+	if (!(intsts & 0x73))
+		return 0;
+
+	HDMI_WRITE(sc, SUNXI_HDMI_INT_STATUS_REG, intsts);
+
+	device_printf(sc->sc_dev, "INT_STATUS %08X\n", intsts);
+
+	return 1;
+}
+#endif
+
+#if 0 /* XXX audio */
+void
+sunxi_hdmi_get_info(struct sunxi_hdmi_info *info)
+{
+	struct sunxi_hdmi_softc *sc;
+	device_t dev;
+
+	memset(info, 0, sizeof(*info));
+
+	dev = device_find_by_driver_unit("sunxihdmi", 0);
+	if (dev == NULL) {
+		info->display_connected = false;
+		return;
+	}
+	sc = device_private(dev);
+
+	info->display_connected = sc->sc_display_connected;
+	if (info->display_connected) {
+		strlcpy(info->display_vendor, sc->sc_display_vendor,
+		    sizeof(info->display_vendor));
+		strlcpy(info->display_product, sc->sc_display_product,
+		    sizeof(info->display_product));
+		info->display_hdmimode =
+		    sc->sc_current_display_mode == DISPLAY_MODE_HDMI;
+	}
+}
+#endif
+
+#if defined(SUNXI_HDMI_DEBUG)
+void
+sunxi_hdmi_dump_regs(void)
+{
+	static const struct {
+		const char *name;
+		uint16_t reg;
+	} regs[] = {
+		{ "CTRL", SUNXI_HDMI_CTRL_REG },
+		{ "INT_STATUS", SUNXI_HDMI_INT_STATUS_REG },
+		{ "VID_CTRL", SUNXI_HDMI_VID_CTRL_REG },
+		{ "VID_TIMING_0", SUNXI_HDMI_VID_TIMING_0_REG },
+		{ "VID_TIMING_1", SUNXI_HDMI_VID_TIMING_1_REG },
+		{ "VID_TIMING_2", SUNXI_HDMI_VID_TIMING_2_REG },
+		{ "VID_TIMING_3", SUNXI_HDMI_VID_TIMING_3_REG },
+		{ "VID_TIMING_4", SUNXI_HDMI_VID_TIMING_4_REG },
+		{ "PAD_CTRL0", SUNXI_HDMI_PAD_CTRL0_REG },
+		{ "PAD_CTRL1", SUNXI_HDMI_PAD_CTRL1_REG },
+		{ "PLL_CTRL", SUNXI_HDMI_PLL_CTRL_REG },
+		{ "PLL_DBG0", SUNXI_HDMI_PLL_DBG0_REG },
+		{ "PLL_DBG1", SUNXI_HDMI_PLL_DBG1_REG },
+	};
+	struct sunxi_hdmi_softc *sc;
+	device_t dev;
+
+	dev = device_find_by_driver_unit("sunxihdmi", 0);
+	if (dev == NULL)
+		return;
+	sc = device_private(dev);
+
+	for (int i = 0; i < __arraycount(regs); i++) {
+		printf("%s: 0x%08x\n", regs[i].name,
+		    HDMI_READ(sc, regs[i].reg));
+	}
+}
+#endif
Index: src/sys/arch/arm/sunxi/sunxi_hdmireg.h
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_hdmireg.h:1.1
--- /dev/null	Tue Apr  3 12:52:16 2018
+++ src/sys/arch/arm/sunxi/sunxi_hdmireg.h	Tue Apr  3 12:52:16 2018
@@ -0,0 +1,299 @@
+/* $NetBSD: sunxi_hdmireg.h,v 1.1 2018/04/03 12:52:16 bouyer Exp $ */
+
+/*-
+ * Copyright (c) 2013 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Matt Thomas of 3am Software Foundry.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#define SUNXI_HDMI_VERSION_ID_REG	0x0000
+#define SUNXI_HDMI_CTRL_REG		0x0004
+#define SUNXI_HDMI_INT_STATUS_REG	0x0008
+#define SUNXI_HDMI_HPD_REG		0x000c
+#define SUNXI_HDMI_VID_CTRL_REG		0x0010
+#define SUNXI_HDMI_VID_TIMING_0_REG	0x0014
+#define SUNXI_HDMI_VID_TIMING_1_REG	0x0018
+#define SUNXI_HDMI_VID_TIMING_2_REG	0x001c
+#define SUNXI_HDMI_VID_TIMING_3_REG	0x0020
+#define SUNXI_HDMI_VID_TIMING_4_REG	0x0024
+#define SUNXI_HDMI_AUD_CTRL_REG		0x0040
+#define SUNXI_HDMI_ADMA_CTRL_REG	0x0044
+#define SUNXI_HDMI_AUD_FMT_REG		0x0048
+#define SUNXI_HDMI_AUD_PCM_CTRL_REG	0x004c
+#define SUNXI_HDMI_AUD_CTS_REG		0x0050
+#define SUNXI_HDMI_AUD_N_REG		0x0054
+#define SUNXI_HDMI_AUD_CH_STATUS0_REG	0x0058
+#define SUNXI_HDMI_AUD_CH_STATUS1_REG	0x005c
+#define SUNXI_HDMI_AVI_INFO_PKT_REG	0x0080
+#define SUNXI_HDMI_AUD_INFO_PKT_REG	0x00a0
+#define SUNXI_HDMI_ACP_PKT_REG		0x00c0
+#define SUNXI_HDMI_GP_PKT0_REG		0x00e0
+#define SUNXI_HDMI_GP_PKT1_REG		0x00e0
+#define SUNXI_HDMI_PAD_CTRL0_REG	0x0200
+#define SUNXI_HDMI_PAD_CTRL1_REG	0x0204
+#define SUNXI_HDMI_PLL_CTRL_REG		0x0208
+#define SUNXI_HDMI_PLL_DBG0_REG		0x020c
+#define SUNXI_HDMI_PLL_DBG1_REG		0x0210
+#define SUNXI_HDMI_HPD_CEC_REG		0x0214
+#define SUNXI_HDMI_SPD_PKT_REG		0x0240
+#define SUNXI_HDMI_PKT_CTRL0_REG	0x02f0
+#define SUNXI_HDMI_PKT_CTRL1_REG	0x02f4
+#define SUNXI_HDMI_DBG4_REG		0x0310
+#define SUNXI_HDMI_AUX_TX_FIFO_REG	0x0400
+#define SUNXI_HDMI_DDC_CTRL_REG		0x0500
+#define SUNXI_HDMI_DDC_SLAVE_ADDR_REG	0x0504
+#define SUNXI_HDMI_DDC_INT_MASK_REG	0x0508
+#define SUNXI_HDMI_DDC_INT_STATUS_REG	0x050c
+#define SUNXI_HDMI_DDC_FIFO_CTRL_REG	0x0510
+#define SUNXI_HDMI_DDC_FIFO_STATUS_REG	0x0514
+#define SUNXI_HDMI_DDC_FIFO_ACCESS_REG	0x0518
+#define SUNXI_HDMI_DDC_BYTE_COUNTER_REG	0x051c
+#define SUNXI_HDMI_DDC_COMMAND_REG	0x0520
+#define SUNXI_HDMI_DDC_EX_REG		0x0524
+#define SUNXI_HDMI_DDC_CLOCK_REG	0x0528
+#define SUNXI_HDMI_DDC_DBG_REG		0x0540
+
+#define SUNXI_HDMI_VERSION_ID_H		__BITS(31,16)
+#define SUNXI_HDMI_VERSION_ID_L		__BITS(15,0)
+
+#define SUNXI_HDMI_CTRL_MODULE_EN	__BIT(31)
+#define SUNXI_HDMI_CTRL_HDCP_EN		__BIT(30)
+#define SUNXI_HDMI_CTRL_CLR_AVMUTE	__BIT(1)
+#define SUNXI_HDMI_CTRL_SET_AVMUTE	__BIT(0)
+
+#define SUNXI_HDMI_HPD_HOTPLUG_DET	__BIT(0)
+
+#define SUNXI_HDMI_VID_CTRL_VIDEO_EN	__BIT(31)
+#define SUNXI_HDMI_VID_CTRL_HDMI_MODE	__BIT(30)
+#define SUNXI_HDMI_VID_CTRL_HDMI_MODE_DVI	0
+#define SUNXI_HDMI_VID_CTRL_HDMI_MODE_HDMI	1
+#define SUNXI_HDMI_VID_CTRL_SRC_SEL	__BIT(5)
+#define SUNXI_HDMI_VID_CTRL_SRC_SEL_RGB		0
+#define SUNXI_HDMI_VID_CTRL_SRC_SEL_CBGEN	1
+#define SUNXI_HDMI_VID_CTRL_OUTPUT_FMT	__BIT(4)
+#define SUNXI_HDMI_VID_CTRL_OUTPUT_FMT_PROGRESS	0
+#define SUNXI_HDMI_VID_CTRL_OUTPUT_FMT_INTERLACE	1
+#define SUNXI_HDMI_VID_CTRL_COLOR_MODE	__BITS(3,2)
+#define SUNXI_HDMI_VID_CTRL_COLOR_MODE_24	0
+#define SUNXI_HDMI_VID_CTRL_COLOR_MODE_30	1
+#define SUNXI_HDMI_VID_CTRL_COLOR_MODE_36	2
+#define SUNXI_HDMI_VID_CTRL_COLOR_MODE_48	3
+#define SUNXI_HDMI_VID_CTRL_REPEATER_SEL	__BITS(1,0)
+#define SUNXI_HDMI_VID_CTRL_REPEATER_SEL_NORMAL	0
+#define SUNXI_HDMI_VID_CTRL_REPEATER_SEL_2X	1
+#define SUNXI_HDMI_VID_CTRL_REPEATER_SEL_4X	2
+
+#define SUNXI_HDMI_VID_TIMING_0_ACT_V	__BITS(27,16)
+#define SUNXI_HDMI_VID_TIMING_0_ACT_H	__BITS(11,0)
+
+#define SUNXI_HDMI_VID_TIMING_1_VBP	__BITS(27,16)
+#define SUNXI_HDMI_VID_TIMING_1_HBP	__BITS(11,0)
+
+#define SUNXI_HDMI_VID_TIMING_2_VFP	__BITS(27,16)
+#define SUNXI_HDMI_VID_TIMING_2_HFP	__BITS(11,0)
+
+#define SUNXI_HDMI_VID_TIMING_3_VSPW	__BITS(27,16)
+#define SUNXI_HDMI_VID_TIMING_3_HSPW	__BITS(11,0)
+
+#define SUNXI_HDMI_VID_TIMING_4_TX_CLOCK	__BITS(25,16)
+#define SUNXI_HDMI_VID_TIMING_4_TX_CLOCK_NORMAL	0x3e0
+#define SUNXI_HDMI_VID_TIMING_4_VSYNC_ACTIVE_SEL __BIT(1)
+#define SUNXI_HDMI_VID_TIMING_4_HSYNC_ACTIVE_SEL __BIT(0)
+
+#define SUNXI_HDMI_PAD_CTRL0_BIAS	__BIT(31)
+#define SUNXI_HDMI_PAD_CTRL0_LDOCEN	__BIT(30)
+#define SUNXI_HDMI_PAD_CTRL0_LD0DEN	__BIT(29)
+#define SUNXI_HDMI_PAD_CTRL0_PWENC	__BIT(28)
+#define SUNXI_HDMI_PAD_CTRL0_PWEND	__BIT(27)
+#define SUNXI_HDMI_PAD_CTRL0_PWENG	__BIT(26)
+#define SUNXI_HDMI_PAD_CTRL0_CKEN	__BIT(25)
+#define SUNXI_HDMI_PAD_CTRL0_SEN	__BIT(24)
+#define SUNXI_HDMI_PAD_CTRL0_TXEN	__BIT(23)
+#define SUNXI_HDMI_PAD_CTRL0_AUTOSYNC_DIS __BIT(22)
+#define SUNXI_HDMI_PAD_CTRL0_LSB_MSB	__BIT(21)
+
+#define SUNXI_HDMI_PAD_CTRL1_AMP_OPT	__BIT(23)
+#define SUNXI_HDMI_PAD_CTRL1_AMPCK_OPT	__BIT(22)
+#define SUNXI_HDMI_PAD_CTRL1_DMP_OPT	__BIT(21)
+#define SUNXI_HDMI_PAD_CTRL1_EMP_OPT	__BIT(20)
+#define SUNXI_HDMI_PAD_CTRL1_EMPCK_OPT	__BIT(19)
+#define SUNXI_HDMI_PAD_CTRL1_PWSCK	__BIT(18)
+#define SUNXI_HDMI_PAD_CTRL1_PWSDT	__BIT(17)
+#define SUNXI_HDMI_PAD_CTRL1_REG_CSMPS	__BIT(16)
+#define SUNXI_HDMI_PAD_CTRL1_REG_DEN	__BIT(15)
+#define SUNXI_HDMI_PAD_CTRL1_REG_DENCK	__BIT(14)
+#define SUNXI_HDMI_PAD_CTRL1_REG_PLRCK	__BIT(13)
+#define SUNXI_HDMI_PAD_CTRL1_REG_EMP	__BITS(12,10)
+#define SUNXI_HDMI_PAD_CTRL1_REG_CD	__BITS(9,8)
+#define SUNXI_HDMI_PAD_CTRL1_REG_CKSS	__BITS(7,6)
+#define SUNXI_HDMI_PAD_CTRL1_REG_AMP	__BITS(5,3)
+#define SUNXI_HDMI_PAD_CTRL1_REG_PLR	__BITS(2,0)
+
+#define SUNXI_HDMI_PLL_CTRL_PLL_EN	__BIT(31)
+#define SUNXI_HDMI_PLL_CTRL_BWS		__BIT(30)
+#define SUNXI_HDMI_PLL_CTRL_HV_IS_33	__BIT(29)
+#define SUNXI_HDMI_PLL_CTRL_LDO1_EN	__BIT(28)
+#define SUNXI_HDMI_PLL_CTRL_LDO2_EN	__BIT(27)
+#define SUNXI_HDMI_PLL_CTRL_S6P25_7P5	__BIT(26)
+#define SUNXI_HDMI_PLL_CTRL_SDIV2	__BIT(25)
+#define SUNXI_HDMI_PLL_CTRL_SINT_FRAC	__BIT(24)
+#define SUNXI_HDMI_PLL_CTRL_VCO_GAIN_EN	__BIT(23)
+#define SUNXI_HDMI_PLL_CTRL_VCO_GAIN	__BITS(22,20)
+#define SUNXI_HDMI_PLL_CTRL_S		__BITS(19,17)
+#define SUNXI_HDMI_PLL_CTRL_CP_S	__BITS(16,12)
+#define SUNXI_HDMI_PLL_CTRL_CS		__BITS(11,8)
+#define SUNXI_HDMI_PLL_CTRL_PREDIV	__BITS(7,4)
+#define SUNXI_HDMI_PLL_CTRL_VCO_S	__BITS(3,0)
+
+#define SUNXI_HDMI_AUD_CTRL_EN		__BIT(31)
+#define SUNXI_HDMI_AUD_CTRL_RST		__BIT(30)
+
+#define SUNXI_HDMI_ADMA_CTRL_SRC_DMA_MODE __BIT(31)
+#define SUNXI_HDMI_ADMA_CTRL_DMA_REQ_CTRL __BITS(25,24)
+#define SUNXI_HDMI_ADMA_CTRL_SRC_DMA_SAMPLE_RATE __BIT(19)
+#define SUNXI_HDMI_ADMA_CTRL_SRC_SAMPLE_LAYOUT __BIT(18)
+#define SUNXI_HDMI_ADMA_CTRL_SRC_WORD_LEN __BITS(17,16)
+#define SUNXI_HDMI_ADMA_CTRL_FIFO_CLEAR	__BIT(15)
+#define SUNXI_HDMI_ADMA_CTRL_DATA_SEL	__BIT(0)
+
+#define SUNXI_HDMI_AUD_FMT_SRC_SEL	__BIT(31)
+#define SUNXI_HDMI_AUD_FMT_SEL		__BITS(26,24)
+#define SUNXI_HDMI_AUD_FMT_DSD_FMT	__BIT(4)
+#define SUNXI_HDMI_AUD_FMT_LAYOUT	__BIT(3)
+#define SUNXI_HDMI_AUD_FMT_SRC_CH_CFG	__BITS(2,0)
+
+#define SUNXI_HDMI_AUD_CH_STATUS0_CHNL_BIT1	__BITS(31,30)
+#define SUNXI_HDMI_AUD_CH_STATUS0_CLK_ACCUR	__BITS(29,28)
+#define SUNXI_HDMI_AUD_CH_STATUS0_FS_FREQ	__BITS(27,24)
+#define SUNXI_HDMI_AUD_CH_STATUS0_FS_FREQ_44_1	0
+#define SUNXI_HDMI_AUD_CH_STATUS0_FS_FREQ_48	2
+#define SUNXI_HDMI_AUD_CH_STATUS0_FS_FREQ_32	3
+#define SUNXI_HDMI_AUD_CH_STATUS0_FS_FREQ_88_2	8
+#define SUNXI_HDMI_AUD_CH_STATUS0_FS_FREQ_96	10
+#define SUNXI_HDMI_AUD_CH_STATUS0_FS_FREQ_176_4	12
+#define SUNXI_HDMI_AUD_CH_STATUS0_FS_FREQ_192	14
+#define SUNXI_HDMI_AUD_CH_STATUS0_CH_NUM	__BITS(23,20)
+#define SUNXI_HDMI_AUD_CH_STATUS0_SOURCE_NUM	__BITS(19,16)
+#define SUNXI_HDMI_AUD_CH_STATUS0_CATEGORY_CODE	__BITS(15,8)
+#define SUNXI_HDMI_AUD_CH_STATUS0_MODE		__BITS(7,6)
+#define SUNXI_HDMI_AUD_CH_STATUS0_EMPHASIS	__BITS(5,3)
+#define SUNXI_HDMI_AUD_CH_STATUS0_CP		__BIT(2)
+#define SUNXI_HDMI_AUD_CH_STATUS0_DATA_TYPE	__BIT(1)
+#define SUNXI_HDMI_AUD_CH_STATUS0_APP_TYPE	__BIT(0)
+
+#define SUNXI_HDMI_AUD_CH_STATUS1_CGMS_A	__BITS(9,8)
+#define SUNXI_HDMI_AUD_CH_STATUS1_ORIGINAL_FS	__BITS(7,4)
+#define SUNXI_HDMI_AUD_CH_STATUS1_WORD_LEN	__BITS(3,1)
+#define SUNXI_HDMI_AUD_CH_STATUS1_WORD_LEN_MAX	__BIT(0)
+
+#define SUNXI_HDMI_DDC_CTRL_EN		__BIT(31)
+#define SUNXI_HDMI_DDC_CTRL_ACCESS_CMD_START __BIT(30)
+#define SUNXI_HDMI_DDC_CTRL_FIFO_DIR	__BIT(8)
+#define SUNXI_HDMI_DDC_CTRL_FIFO_DIR_READ	0
+#define SUNXI_HDMI_DDC_CTRL_FIFO_DIR_WRITE	1
+#define SUNXI_HDMI_DDC_CTRL_SW_RST	__BIT(0)
+
+#define SUNXI_HDMI_DDC_SLAVE_ADDR_0	__BITS(31,24)
+#define SUNXI_HDMI_DDC_SLAVE_ADDR_1	__BITS(23,16)
+#define SUNXI_HDMI_DDC_SLAVE_ADDR_2	__BITS(15,8)
+#define SUNXI_HDMI_DDC_SLAVE_ADDR_3	__BITS(6,0)
+
+#define SUNXI_HDMI_DDC_INT_STATUS_CLEAR	__BIT(8)
+#define SUNXI_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OP __BIT(7)
+#define SUNXI_HDMI_DDC_INT_STATUS_RX_UNDERFLOW __BIT(6)
+#define SUNXI_HDMI_DDC_INT_STATUS_TX_OVERFLOW __BIT(5)
+#define SUNXI_HDMI_DDC_INT_STATUS_FIFO_REQ __BIT(4)
+#define SUNXI_HDMI_DDC_INT_STATUS_ARB_ERR __BIT(3)
+#define SUNXI_HDMI_DDC_INT_STATUS_ACK_ERR __BIT(2)
+#define SUNXI_HDMI_DDC_INT_STATUS_BUS_ERR __BIT(1)
+#define SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE __BIT(0)
+
+#define SUNXI_HDMI_DDC_FIFO_CTRL_ADDR_CLEAR __BIT(31)
+#define SUNXI_HDMI_DDC_FIFO_CTRL_REQUEST_EN __BIT(8)
+#define SUNXI_HDMI_DDC_FIFO_CTRL_RX_TRIGGER_THRESH __BITS(7,4)
+#define SUNXI_HDMI_DDC_FIFO_CTRL_TX_TRIGGER_THRESH __BITS(3,0)
+
+#define SUNXI_HDMI_DDC_FIFO_STATUS_REQ_READY	__BIT(7)
+#define SUNXI_HDMI_DDC_FIFO_STATUS_FULL		__BIT(6)
+#define SUNXI_HDMI_DDC_FIFO_STATUS_EMPTY	__BIT(5)
+#define SUNXI_HDMI_DDC_FIFO_STATUS_LEVEL	__BITS(4,0)
+
+#define SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD	__BITS(2,0)
+#define SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_ABORT	0
+#define SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_SOREAD	1
+#define SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_EOWRITE 2
+#define SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_IOWRITE 3
+#define SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_EOREAD	4
+#define SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_IOREAD	5
+#define SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_EOEDDCREAD 6
+#define SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_IOEDDCREAD 7
+
+#define SUNXI_HDMI_DDC_CLOCK_M		__BITS(6,3)
+#define SUNXI_HDMI_DDC_CLOCK_N		__BITS(2,0)
+
+#define SUNXI_A31_HDMI_DDC_CTRL_REG		0x0500
+#define SUNXI_A31_HDMI_DDC_EXCTRL_REG		0x0504
+#define SUNXI_A31_HDMI_DDC_COMMAND_REG		0x0508
+#define SUNXI_A31_HDMI_DDC_SLAVE_ADDR_REG	0x050c
+#define SUNXI_A31_HDMI_DDC_INT_MASK_REG		0x0510
+#define SUNXI_A31_HDMI_DDC_INT_STATUS_REG	0x0514
+#define SUNXI_A31_HDMI_DDC_FIFO_CTRL_REG	0x0518
+#define SUNXI_A31_HDMI_DDC_FIFO_STATUS_REG	0x051c
+#define SUNXI_A31_HDMI_DDC_CLOCK_REG		0x0520
+#define SUNXI_A31_HDMI_DDC_TIMEOUT_REG		0x0524
+#define SUNXI_A31_HDMI_DDC_FIFO_ACCESS_REG	0x0580
+
+#define SUNXI_A31_HDMI_DDC_CTRL_SW_RST		__BIT(31)
+#define SUNXI_A31_HDMI_DDC_CTRL_ACCESS_CMD_START __BIT(27)
+#define SUNXI_A31_HDMI_DDC_CTRL_SDA_PAD_PULLDOWN __BIT(7)
+#define SUNXI_A31_HDMI_DDC_CTRL_SDA_PAD_EN	__BIT(6)
+#define SUNXI_A31_HDMI_DDC_CTRL_SCL_PAD_PULLDOWN __BIT(5)
+#define SUNXI_A31_HDMI_DDC_CTRL_SCL_PAD_EN	__BIT(4)
+#define SUNXI_A31_HDMI_DDC_CTRL_EN		__BIT(0)
+
+#define SUNXI_A31_HDMI_DDC_EXCTRL_BUS_BUSY	__BIT(10)
+#define SUNXI_A31_HDMI_DDC_EXCTRL_SCL_STATUS	__BIT(9)
+#define SUNXI_A31_HDMI_DDC_EXCTRL_SDA_STATUS	__BIT(8)
+#define SUNXI_A31_HDMI_DDC_EXCTRL_SEGMENT_SEL	__BIT(7)
+#define SUNXI_A31_HDMI_DDC_EXCTRL_SEGMENT0_DET	__BIT(6)
+#define SUNXI_A31_HDMI_DDC_EXCTRL_INIT_SEQ_MODE	__BIT(5)
+#define SUNXI_A31_HDMI_DDC_EXCTRL_INIT_SEQ_EN	__BIT(4)
+#define SUNXI_A31_HDMI_DDC_EXCTRL_SW_SCL	__BIT(3)
+#define SUNXI_A31_HDMI_DDC_EXCTRL_SW_SCL_EN	__BIT(2)
+#define SUNXI_A31_HDMI_DDC_EXCTRL_SW_SDA	__BIT(1)
+#define SUNXI_A31_HDMI_DDC_EXCTRL_SW_SDA_EN	__BIT(0)
+
+#define SUNXI_A31_HDMI_DDC_COMMAND_DTC		__BITS(25,16)
+#define SUNXI_A31_HDMI_DDC_COMMAND_CMD		__BITS(2,0)
+
+#define SUNXI_A31_HDMI_DDC_FIFO_CTRL_RST	__BIT(15)
+
+#define SUNXI_A31_HDMI_DDC_SLAVE_ADDR_SEG_PTR	__BITS(31,24)
+#define SUNXI_A31_HDMI_DDC_SLAVE_ADDR_DDC_CMD	__BITS(23,16)
+#define SUNXI_A31_HDMI_DDC_SLAVE_ADDR_OFF_ADR	__BITS(15,8)
+#define SUNXI_A31_HDMI_DDC_SLAVE_ADDR_DEV_ADR	__BITS(7,1)
+
Index: src/sys/arch/arm/sunxi/sunxi_tcon.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_tcon.c:1.1
--- /dev/null	Tue Apr  3 12:52:16 2018
+++ src/sys/arch/arm/sunxi/sunxi_tcon.c	Tue Apr  3 12:52:16 2018
@@ -0,0 +1,817 @@
+/* $NetBSD: sunxi_tcon.c,v 1.1 2018/04/03 12:52:16 bouyer Exp $ */
+
+/*-
+ * Copyright (c) 2018 Manuel Bouyer <[email protected]>
+ * All rights reserved.
+ *
+ * Copyright (c) 2014 Jared D. McNeill <[email protected]>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: sunxi_tcon.c,v 1.1 2018/04/03 12:52:16 bouyer Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/intr.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+
+#include <dev/fdt/fdtvar.h>
+#include <dev/fdt/fdt_port.h>
+#include <dev/fdt/panel_fdt.h>
+
+#include <dev/videomode/videomode.h>
+
+#include <arm/sunxi/sunxi_tconreg.h>
+#include <arm/sunxi/sunxi_display.h>
+
+#define DIVIDE(x,y)     (((x) + ((y) / 2)) / (y))
+
+enum sunxi_tcon_type {
+	TCON_A10 = 1,
+	TCON_A20,
+};
+
+struct sunxi_tcon_softc {
+	device_t sc_dev;
+	enum sunxi_tcon_type sc_type;
+	int sc_phandle;
+	bus_space_tag_t sc_bst;
+	bus_space_handle_t sc_bsh;
+	struct clk *sc_clk_ahb;
+	struct clk *sc_clk_ch0;
+	struct clk *sc_clk_ch1;
+	unsigned int sc_output_type;
+#define OUTPUT_HDMI 0
+#define OUTPUT_LVDS 1
+#define OUTPUT_VGA 2
+	struct fdt_device_ports sc_ports;
+	int sc_unit; /* tcon0 or tcon1 */
+	struct fdt_endpoint *sc_in_ep;
+	struct fdt_endpoint *sc_in_rep;
+	struct fdt_endpoint *sc_out_ep;
+};
+
+static bus_space_handle_t tcon_mux_bsh;
+static bool tcon_mux_inited = false;
+
+static void sunxi_tcon_ep_connect(device_t, struct fdt_endpoint *, bool);
+static int  sunxi_tcon_ep_activate(device_t, struct fdt_endpoint *, bool);
+static int  sunxi_tcon_ep_enable(device_t, struct fdt_endpoint *, bool);
+static int  sunxi_tcon0_set_video(struct sunxi_tcon_softc *);
+static int  sunxi_tcon0_enable(struct sunxi_tcon_softc *, bool);
+static int  sunxi_tcon1_enable(struct sunxi_tcon_softc *, bool);
+void sunxi_tcon_dump_regs(int);
+
+#define TCON_READ(sc, reg) \
+    bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
+#define TCON_WRITE(sc, reg, val) \
+    bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
+
+static const struct of_compat_data compat_data[] = {
+	{"allwinner,sun4i-a10-tcon", TCON_A10},
+	{"allwinner,sun7i-a20-tcon", TCON_A20},
+	{NULL}
+};
+
+static int	sunxi_tcon_match(device_t, cfdata_t, void *);
+static void	sunxi_tcon_attach(device_t, device_t, void *);
+
+CFATTACH_DECL_NEW(sunxi_tcon, sizeof(struct sunxi_tcon_softc),
+	sunxi_tcon_match, sunxi_tcon_attach, NULL, NULL);
+
+static int
+sunxi_tcon_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct fdt_attach_args * const faa = aux;
+
+	return of_match_compat_data(faa->faa_phandle, compat_data);
+}
+
+static void
+sunxi_tcon_attach(device_t parent, device_t self, void *aux)
+{
+	struct sunxi_tcon_softc *sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	bus_addr_t addr;
+	bus_size_t size;
+	struct fdtbus_reset *rst, *lvds_rst;
+
+
+	sc->sc_dev = self;
+	sc->sc_phandle = phandle;
+	sc->sc_bst = faa->faa_bst;
+
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+	}
+	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
+		aprint_error(": couldn't map registers\n");
+		return;
+	}
+
+	sc->sc_clk_ahb = fdtbus_clock_get(phandle, "ahb");
+	sc->sc_clk_ch0 = fdtbus_clock_get(phandle, "tcon-ch0");
+	sc->sc_clk_ch1 = fdtbus_clock_get(phandle, "tcon-ch1");
+
+	if (sc->sc_clk_ahb == NULL || sc->sc_clk_ch0 == NULL
+	    || sc->sc_clk_ch0 == NULL) {
+		aprint_error(": couldn't get clocks\n");
+		aprint_debug_dev(self, "clk ahb %s tcon-ch0 %s tcon-ch1 %s\n",
+		    sc->sc_clk_ahb == NULL ? "missing" : "present",
+		    sc->sc_clk_ch0 == NULL ? "missing" : "present",
+		    sc->sc_clk_ch1 == NULL ? "missing" : "present");
+		return;
+	}
+
+	rst = fdtbus_reset_get(phandle, "lcd");
+	if (rst == NULL) {
+		aprint_error(": couldn't get lcd reset\n");
+		return;
+	}
+
+	lvds_rst = fdtbus_reset_get(phandle, "lvds");
+
+	if (clk_disable(sc->sc_clk_ahb) != 0) {
+		aprint_error(": couldn't disable ahb clock\n");
+		return;
+	}
+	if (clk_disable(sc->sc_clk_ch0) != 0) {
+		aprint_error(": couldn't disable ch0 clock\n");
+		return;
+	}
+
+	if (clk_disable(sc->sc_clk_ch1) != 0) {
+		aprint_error(": couldn't disable ch1 clock\n");
+		return;
+	}
+
+	if (fdtbus_reset_assert(rst) != 0) {
+		aprint_error(": couldn't assert lcd reset\n");
+		return;
+	}
+	if (lvds_rst != NULL) {
+		if (fdtbus_reset_assert(lvds_rst) != 0) {
+			aprint_error(": couldn't assert lvds reset\n");
+			return;
+		}
+	}
+	delay(1);
+	if (fdtbus_reset_deassert(rst) != 0) {
+		aprint_error(": couldn't de-assert lcd reset\n");
+		return;
+	}
+	if (lvds_rst != NULL) {
+		if (fdtbus_reset_deassert(lvds_rst) != 0) {
+			aprint_error(": couldn't de-assert lvds reset\n");
+			return;
+		}
+	}
+
+	if (clk_enable(sc->sc_clk_ahb) != 0) {
+		aprint_error(": couldn't enable ahb clock\n");
+		return;
+	}
+
+	sc->sc_type = of_search_compatible(faa->faa_phandle, compat_data)->data;
+
+	aprint_naive("\n");
+	aprint_normal(": LCD/TV timing controller (%s)\n", 
+	    fdtbus_get_string(phandle, "name"));
+
+	sc->sc_unit = -1;
+	sc->sc_ports.dp_ep_connect = sunxi_tcon_ep_connect;
+	sc->sc_ports.dp_ep_activate = sunxi_tcon_ep_activate;
+	sc->sc_ports.dp_ep_enable = sunxi_tcon_ep_enable;
+	fdt_ports_register(&sc->sc_ports, self, phandle, EP_OTHER);
+
+	TCON_WRITE(sc, SUNXI_TCON_GCTL_REG, 0);
+	TCON_WRITE(sc, SUNXI_TCON_GINT0_REG, 0);
+	TCON_WRITE(sc, SUNXI_TCON_GINT1_REG,
+	    __SHIFTIN(0x20, SUNXI_TCON_GINT1_TCON0_LINENO));
+	TCON_WRITE(sc, SUNXI_TCON0_DCLK_REG, 0xf0000000);
+	TCON_WRITE(sc, SUNXI_TCON0_CTL_REG, 0);
+	TCON_WRITE(sc, SUNXI_TCON0_IO_TRI_REG, 0xffffffff);
+	TCON_WRITE(sc, SUNXI_TCON1_CTL_REG, 0);
+	TCON_WRITE(sc, SUNXI_TCON1_IO_TRI_REG, 0xffffffff);
+}
+
+static void
+sunxi_tcon_ep_connect(device_t self, struct fdt_endpoint *ep, bool connect)
+{
+	struct sunxi_tcon_softc *sc = device_private(self);
+	struct fdt_endpoint *rep = fdt_endpoint_remote(ep);
+	int rep_idx = fdt_endpoint_index(rep);
+
+	KASSERT(device_is_a(self, "sunxitcon"));
+	if (!connect) {
+		aprint_error_dev(self, "endpoint disconnect not supported\n");
+		return;
+	}
+
+	if (fdt_endpoint_port_index(ep) == 0) {
+		bool do_print = (sc->sc_unit == -1);
+		/*
+		 * one of our input endpoints has been connected.
+		 * the remote id is our unit number
+		 */
+		if (sc->sc_unit != -1 && rep_idx != -1 &&
+		    sc->sc_unit != rep_idx) {
+			aprint_error_dev(self, ": remote id %d doens't match"
+			    " discovered unit number %d\n",
+			    rep_idx, sc->sc_unit);
+			return;
+		}
+		if (!device_is_a(fdt_endpoint_device(rep), "sunxidebe")) {
+			aprint_error_dev(self,
+			    ": input %d connected to unknown device\n",
+			    fdt_endpoint_index(ep));
+			return;
+		}
+
+		if (rep_idx != -1)
+			sc->sc_unit = rep_idx;
+		else {
+			/* assume only one tcon */
+			sc->sc_unit = 0;
+		}
+		if (do_print)
+			aprint_verbose_dev(self, "tcon unit %d\n", sc->sc_unit);
+		if (!tcon_mux_inited && sc->sc_unit == 0) {
+			/* the mux register is only in LCD0 */
+			bus_space_subregion(sc->sc_bst, sc->sc_bsh,
+			    SUNXI_TCON_MUX_CTL_REG, 4, &tcon_mux_bsh);
+			tcon_mux_inited = true;
+		}
+	} else if (fdt_endpoint_port_index(ep) == 1) {
+		device_t rep_dev = fdt_endpoint_device(rep);
+		switch(fdt_endpoint_index(ep)) {
+		case 0:
+			break;
+		case 1:
+			if (!device_is_a(rep_dev, "sunxihdmi")) {
+				aprint_error_dev(self,
+				    ": output 1 connected to unknown device\n");
+				return;
+			}
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+static int
+sunxi_tcon_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
+{
+	struct sunxi_tcon_softc *sc = device_private(dev);
+	struct fdt_endpoint *in_ep, *out_ep;
+	int outi;
+	int error = ENODEV;
+
+	KASSERT(device_is_a(dev, "sunxitcon"));
+	/* our input is activated by debe, we activate our output */
+	if (fdt_endpoint_port_index(ep) != SUNXI_PORT_INPUT) {
+		panic("sunxi_tcon_ep_activate: port %d",
+		    fdt_endpoint_port_index(ep));
+	}
+
+	if (!activate)
+		return EOPNOTSUPP;
+
+	sc->sc_in_ep = ep;
+	sc->sc_in_rep = fdt_endpoint_remote(ep);
+	/* check that our other input is not active */
+	switch (fdt_endpoint_index(ep)) {
+	case 0:
+		in_ep = fdt_endpoint_get_from_index(&sc->sc_ports,
+		    SUNXI_PORT_INPUT, 1);
+		break;
+	case 1:
+		in_ep = fdt_endpoint_get_from_index(&sc->sc_ports,
+		    SUNXI_PORT_INPUT, 0);
+		break;
+	default:
+		in_ep = NULL;
+		panic("sunxi_tcon_ep_activate: input index %d",
+		    fdt_endpoint_index(ep));
+	}
+	if (in_ep != NULL) {
+		if (fdt_endpoint_is_active(in_ep))
+			return EBUSY;
+	}
+	/* try output 0 (RGB/LVDS) first, then ouput 1 (HDMI) if it fails */
+	for (outi = 0; outi < 2; outi++) {
+		out_ep = fdt_endpoint_get_from_index(&sc->sc_ports,
+		    SUNXI_PORT_OUTPUT, outi);
+		if (out_ep == NULL)
+			continue;
+		error = fdt_endpoint_activate(out_ep, activate);
+		if (error == 0) {
+			struct fdt_endpoint *rep = fdt_endpoint_remote(out_ep);
+			aprint_verbose_dev(dev, "output to %s\n",
+			    device_xname(fdt_endpoint_device(rep)));
+			sc->sc_out_ep = out_ep;
+			if (outi == 0)
+				return sunxi_tcon0_set_video(sc);
+			return 0;
+		}
+	}
+	if (out_ep == NULL) {
+		aprint_error_dev(dev, "no output endpoint\n");
+		return ENODEV;
+	}
+	return error;
+}
+
+static int
+sunxi_tcon_ep_enable(device_t dev, struct fdt_endpoint *ep, bool enable)
+{
+	struct sunxi_tcon_softc *sc = device_private(dev);
+	int error;
+	KASSERT(device_is_a(dev, "sunxitcon"));
+	switch (fdt_endpoint_port_index(ep)) {
+	case SUNXI_PORT_INPUT:
+		KASSERT(ep == sc->sc_in_ep);
+		if (fdt_endpoint_index(sc->sc_out_ep) == 0) {
+			/* tcon0 active */
+			return sunxi_tcon0_enable(sc, enable);
+		}
+		/* propagate to our output, it will get back to us */
+		return fdt_endpoint_enable(sc->sc_out_ep, enable);
+	case SUNXI_PORT_OUTPUT:
+		KASSERT(ep == sc->sc_out_ep);
+		switch (fdt_endpoint_index(ep)) {
+		case 0:
+			panic("sunxi_tcon0_ep_enable");
+		case 1:
+			error = sunxi_tcon1_enable(sc, enable);
+			break;
+		default:
+			panic("sunxi_tcon_ep_enable ep %d",
+			    fdt_endpoint_index(ep));
+			
+		}
+		break;
+	default:
+		panic("sunxi_tcon_ep_enable port %d", fdt_endpoint_port_index(ep));
+	}
+#if defined(SUNXI_TCON_DEBUG)
+	sunxi_tcon_dump_regs(device_unit(dev));
+#endif
+	return error;
+}
+
+static int
+sunxi_tcon0_set_video(struct sunxi_tcon_softc *sc)
+{
+	const struct fdt_panel * panel;
+	int32_t lcd_x, lcd_y;
+	int32_t lcd_hbp, lcd_ht, lcd_vbp, lcd_vt;
+	int32_t lcd_hspw, lcd_vspw, lcd_io_cfg0;
+	uint32_t vblk, start_delay;
+	uint32_t val;
+	uint32_t best_div;
+	int best_diff, best_clk_freq, clk_freq, lcd_dclk_freq;
+	bool dualchan = false;
+	static struct videomode mode;
+	int error;
+
+	panel = fdt_endpoint_get_data(fdt_endpoint_remote(sc->sc_out_ep));
+	KASSERT(panel != NULL);
+	KASSERT(panel->panel_type == PANEL_DUAL_LVDS ||
+	    panel->panel_type == PANEL_LVDS);
+
+	lcd_x = panel->panel_timing.hactive;
+	lcd_y = panel->panel_timing.vactive;
+
+	lcd_dclk_freq = panel->panel_timing.clock_freq;
+
+	lcd_hbp = panel->panel_timing.hback_porch;
+	lcd_hspw = panel->panel_timing.hsync_len;
+	lcd_ht = panel->panel_timing.hfront_porch + lcd_hspw + lcd_x + lcd_hbp;
+
+	lcd_vbp = panel->panel_timing.vback_porch;
+	lcd_vspw = panel->panel_timing.vsync_len;
+	lcd_vt = panel->panel_timing.vfront_porch + lcd_vspw + lcd_y + lcd_vbp;
+	
+	lcd_io_cfg0 = 0x10000000; /* XXX */
+
+	if (panel->panel_type == PANEL_DUAL_LVDS)
+		dualchan = true;
+
+	vblk = lcd_vt - lcd_y;
+	start_delay = (vblk >= 32) ? 30 : (vblk - 2);
+
+	if (lcd_dclk_freq > 150000000) /* hardware limit ? */
+		lcd_dclk_freq = 150000000;
+
+	best_diff = INT_MAX;
+	best_div = 0;
+	best_clk_freq = 0;
+	for (u_int div = 7; div <= 15; div++) {
+		int dot_freq, diff;
+		clk_freq = clk_round_rate(sc->sc_clk_ch0, lcd_dclk_freq * div);
+		if (clk_freq == 0)
+			continue;
+		dot_freq = clk_freq / div;
+		diff = abs(lcd_dclk_freq - dot_freq);
+		if (best_diff > diff) {
+			best_diff = diff;
+			best_div = div;
+			best_clk_freq = clk_freq;
+			if (diff == 0)
+				break;
+		}
+	}
+	if (best_clk_freq == 0) {
+		device_printf(sc->sc_dev,
+		    ": failed to find params for dot clock %d\n",
+		    lcd_dclk_freq);
+		return EINVAL;
+	}
+
+	error = clk_set_rate(sc->sc_clk_ch0, best_clk_freq);
+	if (error) {
+		device_printf(sc->sc_dev,
+		    ": failed to set ch0 clock to %d for %d: %d\n",
+		    best_clk_freq, lcd_dclk_freq, error);
+		panic("tcon0 set clk");
+	}
+	error = clk_enable(sc->sc_clk_ch0);
+	if (error) {
+		device_printf(sc->sc_dev,
+		    ": failed to enable ch0 clock: %d\n", error);
+		return EIO;
+	}
+
+	val = __SHIFTIN(start_delay, SUNXI_TCONx_CTL_START_DELAY);
+	/*
+	 * the DE selector selects the primary DEBE for this tcon:
+	 * 0 selects debe0 for tcon0 and debe1 for tcon1
+	 */
+	val |= __SHIFTIN(SUNXI_TCONx_CTL_SRC_SEL_DE0,
+			 SUNXI_TCONx_CTL_SRC_SEL);
+	TCON_WRITE(sc, SUNXI_TCON0_CTL_REG, val);
+
+	val =  (lcd_x - 1) << 16 |  (lcd_y - 1);
+	TCON_WRITE(sc, SUNXI_TCON0_BASIC0_REG, val);
+	val = (lcd_ht - 1) << 16 | (lcd_hbp - 1);
+	TCON_WRITE(sc, SUNXI_TCON0_BASIC1_REG, val);
+	val = (lcd_vt * 2) << 16 | (lcd_vbp - 1);
+	TCON_WRITE(sc, SUNXI_TCON0_BASIC2_REG, val);
+	val =  ((lcd_hspw > 0) ? (lcd_hspw - 1) : 0) << 16;
+	val |= ((lcd_vspw > 0) ? (lcd_vspw - 1) : 0);
+	TCON_WRITE(sc, SUNXI_TCON0_BASIC3_REG, val);
+
+	val = 0;
+	if (dualchan)
+		val |= SUNXI_TCON0_LVDS_IF_DUALCHAN;
+	if (panel->panel_lvds_format == LVDS_JEIDA_24)
+		val |= SUNXI_TCON0_LVDS_IF_MODE_JEIDA;
+	if (panel->panel_lvds_format == LVDS_JEIDA_18)
+		val |= SUNXI_TCON0_LVDS_IF_18BITS;
+	TCON_WRITE(sc, SUNXI_TCON0_LVDS_IF_REG, val);
+
+
+	TCON_WRITE(sc, SUNXI_TCON0_IO_POL_REG, lcd_io_cfg0);
+	TCON_WRITE(sc, SUNXI_TCON0_IO_TRI_REG, 0);
+	TCON_WRITE(sc, SUNXI_TCON_GINT1_REG,
+	    __SHIFTIN(start_delay + 2, SUNXI_TCON_GINT1_TCON0_LINENO));
+
+	val = 0xf0000000;
+	val &= ~SUNXI_TCON0_DCLK_DIV;
+	val |= __SHIFTIN(best_div, SUNXI_TCON0_DCLK_DIV);
+	TCON_WRITE(sc, SUNXI_TCON0_DCLK_REG, val);
+
+	mode.dot_clock = lcd_dclk_freq;
+	mode.hdisplay = lcd_x;
+	mode.hsync_start = lcd_ht - lcd_hbp;
+	mode.hsync_end = lcd_hspw + mode.hsync_start;
+	mode.htotal = lcd_ht;
+	mode.vdisplay = lcd_y;
+	mode.vsync_start = lcd_vt - lcd_vbp;
+	mode.vsync_end = lcd_vspw + mode.vsync_start;
+	mode.vtotal = lcd_vt;
+	mode.flags = 0;
+	mode.name = NULL;
+
+	sunxi_debe_set_videomode(fdt_endpoint_device(sc->sc_in_rep), &mode);
+
+	/* XXX
+	 * magic values here from linux. these are not documented
+	 * in the A20 user manual, and other Allwiner LVDS-capable SoC
+	 * documentation don't make sense with these values
+	 */
+	val = TCON_READ(sc, SUNXI_TCON_LVDS_ANA0);
+	val |= 0x3F310000;
+	TCON_WRITE(sc, SUNXI_TCON_LVDS_ANA0, val);
+	val = TCON_READ(sc, SUNXI_TCON_LVDS_ANA0);
+	val |= 1 << 22;
+	TCON_WRITE(sc, SUNXI_TCON_LVDS_ANA0, val);
+	delay(2);
+	val = TCON_READ(sc, SUNXI_TCON_LVDS_ANA1);
+	val |= (0x1f << 26 | 0x1f << 10);
+	TCON_WRITE(sc, SUNXI_TCON_LVDS_ANA1, val);
+	delay(2);
+	val = TCON_READ(sc, SUNXI_TCON_LVDS_ANA1);
+	val |= (0x1f << 16 | 0x1f << 0);
+	TCON_WRITE(sc, SUNXI_TCON_LVDS_ANA1, val);
+	val = TCON_READ(sc, SUNXI_TCON_LVDS_ANA0);
+	val |= 1 << 22;
+	TCON_WRITE(sc, SUNXI_TCON_LVDS_ANA0, val);
+	return 0;
+}
+
+static int
+sunxi_tcon0_enable(struct sunxi_tcon_softc *sc, bool enable)
+{
+	uint32_t val;
+	int error;
+
+	/* turn on/off backlight and lcd  */
+	error = fdt_endpoint_enable(sc->sc_out_ep, enable);
+	if (error)
+		return error;
+
+	/* and finally disable or enable the tcon */
+	error = fdt_endpoint_enable(sc->sc_in_ep, enable);
+	if (error)
+		return error;
+	delay(20000);
+	if (enable) {
+		val = TCON_READ(sc, SUNXI_TCON_GCTL_REG);
+		val |= SUNXI_TCON_GCTL_EN;
+		TCON_WRITE(sc, SUNXI_TCON_GCTL_REG, val);
+		val = TCON_READ(sc, SUNXI_TCON0_CTL_REG);
+		val |= SUNXI_TCONx_CTL_EN;
+		TCON_WRITE(sc, SUNXI_TCON0_CTL_REG, val);
+		val = TCON_READ(sc, SUNXI_TCON0_LVDS_IF_REG);
+		val |= SUNXI_TCON0_LVDS_IF_EN;
+		TCON_WRITE(sc, SUNXI_TCON0_LVDS_IF_REG, val);
+		TCON_WRITE(sc, SUNXI_TCON0_IO_TRI_REG, 0);
+	} else {
+		TCON_WRITE(sc, SUNXI_TCON0_IO_TRI_REG, 0xffffffff);
+		val = TCON_READ(sc, SUNXI_TCON0_LVDS_IF_REG);
+		val &= ~SUNXI_TCON0_LVDS_IF_EN;
+		TCON_WRITE(sc, SUNXI_TCON0_LVDS_IF_REG, val);
+		val = TCON_READ(sc, SUNXI_TCON0_CTL_REG);
+		val &= ~SUNXI_TCONx_CTL_EN;
+		TCON_WRITE(sc, SUNXI_TCON0_CTL_REG, val);
+		val = TCON_READ(sc, SUNXI_TCON_GCTL_REG);
+		val &= ~SUNXI_TCON_GCTL_EN;
+		TCON_WRITE(sc, SUNXI_TCON_GCTL_REG, val);
+	}
+#ifdef SUNXI_TCON_DEBUG
+	sunxi_tcon_dump_regs(device_unit(sc->sc_dev));
+#endif
+	return 0;
+}
+
+static int
+sunxi_tcon1_enable(struct sunxi_tcon_softc *sc, bool enable)
+{
+	uint32_t val;
+
+	KASSERT((sc->sc_output_type == OUTPUT_HDMI) || 
+		    (sc->sc_output_type == OUTPUT_VGA));
+
+	fdt_endpoint_enable(sc->sc_in_ep, enable);
+	delay(20000);
+	if (enable) {
+		val = TCON_READ(sc, SUNXI_TCON_GCTL_REG);
+		val |= SUNXI_TCON_GCTL_EN;
+		TCON_WRITE(sc, SUNXI_TCON_GCTL_REG, val);
+		val = TCON_READ(sc, SUNXI_TCON1_CTL_REG);
+		val |= SUNXI_TCONx_CTL_EN;
+		TCON_WRITE(sc, SUNXI_TCON1_CTL_REG, val);
+		if (sc->sc_output_type == OUTPUT_VGA) {
+			TCON_WRITE(sc, SUNXI_TCON1_IO_TRI_REG, 0x0cffffff);
+		} else
+			TCON_WRITE(sc, SUNXI_TCON1_IO_TRI_REG, 0);
+	} else {
+		TCON_WRITE(sc, SUNXI_TCON1_IO_TRI_REG, 0xffffffff);
+		val = TCON_READ(sc, SUNXI_TCON1_CTL_REG);
+		val &= ~SUNXI_TCONx_CTL_EN;
+		TCON_WRITE(sc, SUNXI_TCON1_CTL_REG, val);
+		val = TCON_READ(sc, SUNXI_TCON_GCTL_REG);
+		val &= ~SUNXI_TCON_GCTL_EN;
+		TCON_WRITE(sc, SUNXI_TCON_GCTL_REG, val);
+	}
+
+	KASSERT(tcon_mux_inited);
+	val = bus_space_read_4(sc->sc_bst, tcon_mux_bsh, 0);
+#ifdef SUNXI_TCON_DEBUG
+	printf("sunxi_tcon1_enable(%d) %d val 0x%x", sc->sc_unit, enable, val);
+#endif
+	val &= ~ SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC;
+	switch(sc->sc_unit) {
+	case 0:
+		val |= __SHIFTIN(SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC_LCDC0_TCON1,
+		    SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC);
+		break;
+	case 1:
+		val |= __SHIFTIN(SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC_LCDC1_TCON1,
+		    SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC);
+		break;
+	default:
+		panic("tcon: invalid unid %d\n", sc->sc_unit);
+	}
+#ifdef SUNXI_TCON_DEBUG
+	printf(" -> 0x%x", val);
+#endif
+	bus_space_write_4(sc->sc_bst, tcon_mux_bsh, 0, val);
+#ifdef SUNXI_TCON_DEBUG
+	printf(": 0x%" PRIxBSH " 0x%" PRIxBSH " 0x%x 0x%x\n", sc->sc_bsh,
+	    tcon_mux_bsh, bus_space_read_4(sc->sc_bst, tcon_mux_bsh, 0),
+	    TCON_READ(sc, SUNXI_TCON_MUX_CTL_REG));
+#endif
+	return 0;
+}
+
+void
+sunxi_tcon1_set_videomode(device_t dev, const struct videomode *mode)
+{
+	struct sunxi_tcon_softc *sc = device_private(dev);
+	uint32_t val;
+	int error;
+
+	KASSERT(device_is_a(dev, "sunxitcon"));
+	sc = device_private(dev);
+	KASSERT((sc->sc_output_type == OUTPUT_HDMI) || 
+		    (sc->sc_output_type == OUTPUT_VGA));
+
+	sunxi_debe_set_videomode(fdt_endpoint_device(sc->sc_in_rep), mode);
+	if (mode) {
+		const u_int interlace_p = !!(mode->flags & VID_INTERLACE);
+		const u_int phsync_p = !!(mode->flags & VID_PHSYNC);
+		const u_int pvsync_p = !!(mode->flags & VID_PVSYNC);
+		const u_int hspw = mode->hsync_end - mode->hsync_start;
+		const u_int hbp = mode->htotal - mode->hsync_start;
+		const u_int vspw = mode->vsync_end - mode->vsync_start;
+		const u_int vbp = mode->vtotal - mode->vsync_start;
+		const u_int vblank_len =
+		    ((mode->vtotal << interlace_p) >> 1) - mode->vdisplay - 2;
+		const u_int start_delay =
+		    vblank_len >= 32 ? 30 : vblank_len - 2;
+
+		val = TCON_READ(sc, SUNXI_TCON_GCTL_REG);
+		val |= SUNXI_TCON_GCTL_IO_MAP_SEL;
+		TCON_WRITE(sc, SUNXI_TCON_GCTL_REG, val);
+
+		/* enable */
+		val = SUNXI_TCONx_CTL_EN;
+		if (interlace_p)
+			val |= SUNXI_TCONx_CTL_INTERLACE_EN;
+		val |= __SHIFTIN(start_delay, SUNXI_TCONx_CTL_START_DELAY);
+#ifdef SUNXI_TCON1_BLUEDATA
+		val |= __SHIFTIN(SUNXI_TCONx_CTL_SRC_SEL_BLUEDATA,
+				 SUNXI_TCONx_CTL_SRC_SEL);
+#else
+		/*
+		 * the DE selector selects the primary DEBE for this tcon:
+		 * 0 selects debe0 for tcon0 and debe1 for tcon1
+		 */
+		val |= __SHIFTIN(SUNXI_TCONx_CTL_SRC_SEL_DE0,
+				 SUNXI_TCONx_CTL_SRC_SEL);
+#endif
+		TCON_WRITE(sc, SUNXI_TCON1_CTL_REG, val);
+
+		/* Source width/height */
+		TCON_WRITE(sc, SUNXI_TCON1_BASIC0_REG,
+		    ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1));
+		/* Scaler width/height */
+		TCON_WRITE(sc, SUNXI_TCON1_BASIC1_REG,
+		    ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1));
+		/* Output width/height */
+		TCON_WRITE(sc, SUNXI_TCON1_BASIC2_REG,
+		    ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1));
+		/* Horizontal total + back porch */
+		TCON_WRITE(sc, SUNXI_TCON1_BASIC3_REG,
+		    ((mode->htotal - 1) << 16) | (hbp - 1));
+		/* Vertical total + back porch */
+		u_int vtotal = mode->vtotal * 2;
+		if (interlace_p) {
+			u_int framerate =
+			    DIVIDE(DIVIDE(mode->dot_clock * 1000, mode->htotal),
+			    mode->vtotal);
+			u_int clk = mode->htotal * (mode->vtotal * 2 + 1) *
+			    framerate;
+			if ((clk / 2) == mode->dot_clock * 1000)
+				vtotal += 1;
+		}
+		TCON_WRITE(sc, SUNXI_TCON1_BASIC4_REG,
+		    (vtotal << 16) | (vbp - 1));
+
+		/* Sync */
+		TCON_WRITE(sc, SUNXI_TCON1_BASIC5_REG,
+		    ((hspw - 1) << 16) | (vspw - 1));
+		/* Polarity */
+		val = SUNXI_TCON_IO_POL_IO2_INV;
+		if (phsync_p)
+			val |= SUNXI_TCON_IO_POL_PHSYNC;
+		if (pvsync_p)
+			val |= SUNXI_TCON_IO_POL_PVSYNC;
+		TCON_WRITE(sc, SUNXI_TCON1_IO_POL_REG, val);
+
+		TCON_WRITE(sc, SUNXI_TCON_GINT1_REG,
+		    __SHIFTIN(start_delay + 2, SUNXI_TCON_GINT1_TCON1_LINENO));
+
+		/* Setup LCDx CH1 PLL */
+		error = clk_set_rate(sc->sc_clk_ch1, mode->dot_clock * 1000);
+		if (error) {
+			device_printf(sc->sc_dev,
+			    ": failed to set ch1 clock to %d: %d\n",
+			    mode->dot_clock, error);
+		}
+		error = clk_enable(sc->sc_clk_ch1);
+		if (error) {
+			device_printf(sc->sc_dev,
+			    ": failed to enable ch1 clock: %d\n",
+			    error);
+		}
+	} else {
+		/* disable */
+		val = TCON_READ(sc, SUNXI_TCON1_CTL_REG);
+		val &= ~SUNXI_TCONx_CTL_EN;
+		TCON_WRITE(sc, SUNXI_TCON1_CTL_REG, val);
+		error = clk_disable(sc->sc_clk_ch1);
+		if (error) {
+			device_printf(sc->sc_dev,
+			    ": failed to disable ch1 clock: %d\n",
+			    error);
+		}
+	}
+}
+
+#if defined(DDB) || defined(SUNXI_TCON_DEBUG)
+void
+sunxi_tcon_dump_regs(int u)
+{
+	static const struct {
+		const char *name;
+		uint16_t reg;
+	} regs[] = {
+		{ "TCON0_BASIC0_REG", SUNXI_TCON0_BASIC0_REG },
+		{ "TCON0_BASIC1_REG", SUNXI_TCON0_BASIC1_REG },
+		{ "TCON0_BASIC2_REG", SUNXI_TCON0_BASIC2_REG },
+		{ "TCON0_BASIC3_REG", SUNXI_TCON0_BASIC3_REG },
+		{ "TCON0_CTL_REG", SUNXI_TCON0_CTL_REG },
+		{ "TCON0_DCLK_REG", SUNXI_TCON0_DCLK_REG },
+		{ "TCON0_IO_POL_REG", SUNXI_TCON0_IO_POL_REG },
+		{ "TCON0_IO_TRI_REG", SUNXI_TCON0_IO_TRI_REG },
+		{ "TCON0_LVDS_IF_REG", SUNXI_TCON0_LVDS_IF_REG },
+		{ "TCON1_BASIC0_REG", SUNXI_TCON1_BASIC0_REG },
+		{ "TCON1_BASIC1_REG", SUNXI_TCON1_BASIC1_REG },
+		{ "TCON1_BASIC2_REG", SUNXI_TCON1_BASIC2_REG },
+		{ "TCON1_BASIC3_REG", SUNXI_TCON1_BASIC3_REG },
+		{ "TCON1_BASIC4_REG", SUNXI_TCON1_BASIC4_REG },
+		{ "TCON1_BASIC5_REG", SUNXI_TCON1_BASIC5_REG },
+		{ "TCON1_CTL_REG", SUNXI_TCON1_CTL_REG },
+		{ "TCON1_IO_POL_REG", SUNXI_TCON1_IO_POL_REG },
+		{ "TCON1_IO_TRI_REG", SUNXI_TCON1_IO_TRI_REG },
+		{ "TCON_GCTL_REG", SUNXI_TCON_GCTL_REG },
+		{ "TCON_GINT0_REG", SUNXI_TCON_GINT0_REG },
+		{ "TCON_GINT1_REG", SUNXI_TCON_GINT1_REG },
+		{ "TCON_MUX_CTL_REG", SUNXI_TCON_MUX_CTL_REG },
+	};
+	struct sunxi_tcon_softc *sc;
+	device_t dev;
+
+	dev = device_find_by_driver_unit("sunxitcon", u);
+	if (dev == NULL)
+		return;
+	sc = device_private(dev);
+
+	for (int i = 0; i < __arraycount(regs); i++) {
+		printf("%s: 0x%08x\n", regs[i].name,
+		    TCON_READ(sc, regs[i].reg));
+	}
+}
+#endif
Index: src/sys/arch/arm/sunxi/sunxi_tconreg.h
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_tconreg.h:1.1
--- /dev/null	Tue Apr  3 12:52:16 2018
+++ src/sys/arch/arm/sunxi/sunxi_tconreg.h	Tue Apr  3 12:52:16 2018
@@ -0,0 +1,125 @@
+/* $NetBSD: sunxi_tconreg.h,v 1.1 2018/04/03 12:52:16 bouyer Exp $ */
+
+/*-
+ * Copyright (c) 2013 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Matt Thomas of 3am Software Foundry.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define SUNXI_TCON_GCTL_REG		0x0000
+#define SUNXI_TCON_GINT0_REG		0x0004
+#define SUNXI_TCON_GINT1_REG		0x0008
+#define SUNXI_TCON0_FRM_CTL_REG		0x0010
+#define SUNXI_TCON0_FRM1_CTL_REG	0x0014
+#define SUNXI_TCON0_FRM2_CTL_REG	0x002c
+#define SUNXI_TCON0_CTL_REG		0x0040
+#define SUNXI_TCON0_DCLK_REG		0x0044
+#define SUNXI_TCON0_BASIC0_REG		0x0048
+#define SUNXI_TCON0_BASIC1_REG		0x004C
+#define SUNXI_TCON0_BASIC2_REG		0x0050
+#define SUNXI_TCON0_BASIC3_REG		0x0054
+#define SUNXI_TCON0_HV_IF_REG		0x0058
+#define SUNXI_TCON0_CPU_IF_REG		0x0060
+#define SUNXI_TCON0_CPU_WR_REG		0x0064
+#define SUNXI_TCON0_CPU_RD0_REG		0x0068
+#define SUNXI_TCON0_CPU_RD1_REG		0x006C
+#define SUNXI_TCON0_LVDS_IF_REG		0x0084
+#define SUNXI_TCON0_IO_POL_REG		0x0088
+#define SUNXI_TCON0_IO_TRI_REG		0x008C
+#define SUNXI_TCON1_CTL_REG		0x0090
+#define SUNXI_TCON1_BASIC0_REG		0x0094
+#define SUNXI_TCON1_BASIC1_REG		0x0098
+#define SUNXI_TCON1_BASIC2_REG		0x009C
+#define SUNXI_TCON1_BASIC3_REG		0x00A0
+#define SUNXI_TCON1_BASIC4_REG		0x00A4
+#define SUNXI_TCON1_BASIC5_REG		0x00A8
+#define SUNXI_TCON1_IO_POL_REG		0x00F0
+#define SUNXI_TCON1_IO_TRI_REG		0x00F4
+#define SUNXI_TCON_CEU_CTL_REG		0x0100
+#define SUNXI_TCON0_CPU_TRI0_REG	0x0160
+#define SUNXI_TCON0_CPU_TRI1_REG	0x0164
+#define SUNXI_TCON0_CPU_TRI2_REG	0x0168
+#define SUNXI_TCON0_CPU_TRI3_REG	0x016C
+#define SUNXI_TCON_CMAP_CTL_REG		0x0180
+#define SUNXI_TCON_CMAP_ODD0_REG	0x0190
+#define SUNXI_TCON_CMAP_ODD1_REG	0x0194
+#define SUNXI_TCON_CMAP_EVEN0_REG	0x0198
+#define SUNXI_TCON_CMAP_EVEN1_REG	0x019C
+#define SUNXI_TCON_MUX_CTL_REG		0x0200 /* only in TCON0 */
+#define SUNXI_TCON_LVDS_ANA0		0x220
+#define SUNXI_TCON_LVDS_ANA1		0x224
+
+#define SUNXI_TCON_GCTL_EN		__BIT(31)
+#define SUNXI_TCON_GCTL_GAMMA_EN	__BIT(30)
+#define SUNXI_TCON_GCTL_IO_MAP_SEL	__BIT(0)
+#define SUNXI_TCON_GCTL_IO_MAP_SEL_TCON0	0
+#define SUNXI_TCON_GCTL_IO_MAP_SEL_TCON1	1
+
+#define SUNXI_TCON_GINT1_TCON0_LINENO	__BITS(27,16)
+#define SUNXI_TCON_GINT1_TCON1_LINENO	__BITS(11,0)
+
+#define SUNXI_TCON0_FRM_ENABLE		__BIT(31)
+#define SUNXI_TCON0_FRM_R5BITS		__BIT(6)
+#define SUNXI_TCON0_FRM_G5BITS		__BIT(5)
+#define SUNXI_TCON0_FRM_B5BITS		__BIT(4)
+
+#define SUNXI_TCONx_CTL_EN		__BIT(31)
+#define SUNXI_TCON0_CTL0_IF		__BITS(25,24)
+#define SUNXI_TCON0_CTL0_IF_HV		0
+#define SUNXI_TCON0_CTL0_IF_8080	1
+#define SUNXI_TCON0_CTL0_IF_TTL		2
+#define SUNXI_TCON0_CTL_RG_SWAP		__BIT(23)
+#define SUNXI_TCON0_CTL_TSTV		__BIT(22)
+#define SUNXI_TCONx_CTL_INTERLACE_EN	__BIT(20)
+#define SUNXI_TCONx_CTL_START_DELAY	__BITS(8,4)
+#define SUNXI_TCONx_CTL_SRC_SEL		__BITS(1,0)
+#define SUNXI_TCONx_CTL_SRC_SEL_DE0	0
+#define SUNXI_TCONx_CTL_SRC_SEL_DE1	1
+#define SUNXI_TCONx_CTL_SRC_SEL_BLUEDATA 2
+
+#define SUNXI_TCON0_DCLK_DIV		__BITS(6,0)
+
+#define SUNXI_TCON0_LVDS_IF_EN		__BIT(31)
+#define SUNXI_TCON0_LVDS_IF_DUALCHAN	__BIT(30)
+#define SUNXI_TCON0_LVDS_IF_DIR_REV	__BIT(28)
+#define SUNXI_TCON0_LVDS_IF_MODE_JEIDA	__BIT(27)
+#define SUNXI_TCON0_LVDS_IF_18BITS	__BIT(26)
+#define SUNXI_TCON0_LVDS_IF_CORR_MODE1	__BIT(23)
+
+#define SUNXI_TCON_IO_POL_IO2_INV	__BIT(26)
+#define SUNXI_TCON_IO_POL_PVSYNC	__BIT(25)
+#define SUNXI_TCON_IO_POL_PHSYNC	__BIT(24)
+
+#define SUNXI_TCON_IO_TRI_IO3		__BIT(27)
+#define SUNXI_TCON_IO_TRI_IO2		__BIT(26)
+#define SUNXI_TCON_IO_TRI_IO1		__BIT(25)
+#define SUNXI_TCON_IO_TRI_IO0		__BIT(24)
+#define SUNXI_TCON_IO_TRI_DATA		__BITS(23,0)
+
+#define SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC __BITS(9,8)
+#define SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC_LCDC0_TCON1	0
+#define SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC_LCDC1_TCON1	1
+#define SUNXI_TCON_MUX_CTL_HDMI_OUTPUT_SRC_CLOSE	2

Reply via email to