Module Name:    src
Committed By:   jmcneill
Date:           Wed Jan 30 01:24:00 UTC 2019

Modified Files:
        src/sys/arch/arm/sunxi: files.sunxi sun50i_a64_ccu.c sunxi_ccu.h
            sunxi_ccu_fractional.c
        src/sys/dev/fdt: fdt_i2c.c fdt_phy.c fdt_port.c fdt_port.h fdtvar.h
            files.fdt
Added Files:
        src/sys/arch/arm/sunxi: sunxi_drm.c sunxi_drm.h sunxi_dwhdmi.c
            sunxi_fb.c sunxi_hdmiphy.c sunxi_hdmiphy.h sunxi_lcdc.c
            sunxi_mixer.c
        src/sys/dev/fdt: hdmi_connector.c

Log Message:
Add support for Allwinner A64's display pipeline.


To generate a diff of this commit:
cvs rdiff -u -r1.60 -r1.61 src/sys/arch/arm/sunxi/files.sunxi
cvs rdiff -u -r1.10 -r1.11 src/sys/arch/arm/sunxi/sun50i_a64_ccu.c
cvs rdiff -u -r1.20 -r1.21 src/sys/arch/arm/sunxi/sunxi_ccu.h
cvs rdiff -u -r1.3 -r1.4 src/sys/arch/arm/sunxi/sunxi_ccu_fractional.c
cvs rdiff -u -r0 -r1.1 src/sys/arch/arm/sunxi/sunxi_drm.c \
    src/sys/arch/arm/sunxi/sunxi_drm.h src/sys/arch/arm/sunxi/sunxi_dwhdmi.c \
    src/sys/arch/arm/sunxi/sunxi_fb.c src/sys/arch/arm/sunxi/sunxi_hdmiphy.c \
    src/sys/arch/arm/sunxi/sunxi_hdmiphy.h \
    src/sys/arch/arm/sunxi/sunxi_lcdc.c src/sys/arch/arm/sunxi/sunxi_mixer.c
cvs rdiff -u -r1.5 -r1.6 src/sys/dev/fdt/fdt_i2c.c
cvs rdiff -u -r1.2 -r1.3 src/sys/dev/fdt/fdt_phy.c
cvs rdiff -u -r1.1 -r1.2 src/sys/dev/fdt/fdt_port.c \
    src/sys/dev/fdt/fdt_port.h
cvs rdiff -u -r1.47 -r1.48 src/sys/dev/fdt/fdtvar.h
cvs rdiff -u -r1.41 -r1.42 src/sys/dev/fdt/files.fdt
cvs rdiff -u -r0 -r1.1 src/sys/dev/fdt/hdmi_connector.c

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.60 src/sys/arch/arm/sunxi/files.sunxi:1.61
--- src/sys/arch/arm/sunxi/files.sunxi:1.60	Tue Jan 22 20:17:36 2019
+++ src/sys/arch/arm/sunxi/files.sunxi	Wed Jan 30 01:24:00 2019
@@ -1,4 +1,4 @@
-#	$NetBSD: files.sunxi,v 1.60 2019/01/22 20:17:36 jmcneill Exp $
+#	$NetBSD: files.sunxi,v 1.61 2019/01/30 01:24:00 jmcneill Exp $
 #
 # Configuration info for Allwinner sunxi family SoCs
 #
@@ -307,6 +307,36 @@ device	sunxide2ccu: sunxi_ccu
 attach	sunxide2ccu at fdt with sunxi_de2ccu
 file	arch/arm/sunxi/sunxi_de2_ccu.c		sunxi_de2ccu
 
+# DE2 mixer
+device	sunximixer: drmkms
+attach	sunximixer at fdt with sunxi_mixer
+file	arch/arm/sunxi/sunxi_mixer.c		sunxi_mixer
+
+# DE2 timing controller
+device	sunxilcdc: drmkms
+attach	sunxilcdc at fdt with sunxi_lcdc
+file	arch/arm/sunxi/sunxi_lcdc.c		sunxi_lcdc
+
+# Display Pipeline
+define	sunxifbbus { }
+device	sunxidrm: drmkms, ddc_read_edid, sunxifbbus
+attach	sunxidrm at fdt with sunxi_drm
+file	arch/arm/sunxi/sunxi_drm.c		sunxi_drm
+
+# DRM framebuffer console
+device	sunxifb: sunxifbbus, drmfb, wsemuldisplaydev
+attach	sunxifb at sunxifbbus with sunxi_fb
+file	arch/arm/sunxi/sunxi_fb.c		sunxi_fb
+
+# Allwinner HDMI (Designware based)
+attach	dwhdmi at fdt with sunxi_dwhdmi
+file	arch/arm/sunxi/sunxi_dwhdmi.c		sunxi_dwhdmi
+
+# Allwinner HDMI TX PHY
+device	sunxihdmiphy: drmkms
+attach	sunxihdmiphy at fdt with sunxi_hdmiphy
+file	arch/arm/sunxi/sunxi_hdmiphy.c		sunxi_hdmiphy | sunxi_dwhdmi
+
 # SOC parameters
 defflag	opt_soc.h			SOC_SUNXI
 defflag	opt_soc.h			SOC_SUNXI_MC

Index: src/sys/arch/arm/sunxi/sun50i_a64_ccu.c
diff -u src/sys/arch/arm/sunxi/sun50i_a64_ccu.c:1.10 src/sys/arch/arm/sunxi/sun50i_a64_ccu.c:1.11
--- src/sys/arch/arm/sunxi/sun50i_a64_ccu.c:1.10	Tue Jan 22 23:06:49 2019
+++ src/sys/arch/arm/sunxi/sun50i_a64_ccu.c	Wed Jan 30 01:24:00 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: sun50i_a64_ccu.c,v 1.10 2019/01/22 23:06:49 jmcneill Exp $ */
+/* $NetBSD: sun50i_a64_ccu.c,v 1.11 2019/01/30 01:24:00 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca>
@@ -28,7 +28,7 @@
 
 #include <sys/cdefs.h>
 
-__KERNEL_RCSID(1, "$NetBSD: sun50i_a64_ccu.c,v 1.10 2019/01/22 23:06:49 jmcneill Exp $");
+__KERNEL_RCSID(1, "$NetBSD: sun50i_a64_ccu.c,v 1.11 2019/01/30 01:24:00 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -42,8 +42,10 @@ __KERNEL_RCSID(1, "$NetBSD: sun50i_a64_c
 
 #define	PLL_CPUX_CTRL_REG	0x000
 #define	PLL_AUDIO_CTRL_REG	0x008
+#define	PLL_VIDEO0_CTRL_REG	0x010
 #define	PLL_PERIPH0_CTRL_REG	0x028
 #define	PLL_PERIPH1_CTRL_REG	0x02c
+#define	PLL_VIDEO1_CTRL_REG	0x030
 #define	PLL_DE_CTRL_REG		0x048
 #define	AHB1_APB1_CFG_REG	0x054
 #define	APB2_CFG_REG		0x058
@@ -61,7 +63,10 @@ __KERNEL_RCSID(1, "$NetBSD: sun50i_a64_c
 #define	DRAM_CFG_REG		0x0f4
 #define	MBUS_RST_REG		0x0fc
 #define	DE_CLK_REG		0x104
+#define	TCON1_CLK_REG		0x11c
 #define	AC_DIG_CLK_REG		0x140
+#define	HDMI_CLK_REG		0x150
+#define	HDMI_SLOW_CLK_REG	0x154
 #define	BUS_SOFT_RST_REG0	0x2c0
 #define	BUS_SOFT_RST_REG1	0x2c4
 #define	BUS_SOFT_RST_REG2	0x2c8
@@ -146,6 +151,8 @@ static const char *apb2_parents[] = { "l
 static const char *mmc_parents[] = { "hosc", "pll_periph0_2x", "pll_periph1_2x" };
 static const char *ths_parents[] = { "hosc", NULL, NULL, NULL };
 static const char *de_parents[] = { "pll_periph0_2x", "pll_de" };
+static const char *hdmi_parents[] = { "pll_video0", "pll_video1" };
+static const char *tcon1_parents[] = { "pll_video0", NULL, "pll_video1", NULL };
 
 static const struct sunxi_ccu_nkmp_tbl sun50i_a64_cpux_table[] = {
 	{ 60000000, 9, 0, 0, 2 },
@@ -262,8 +269,36 @@ static struct sunxi_ccu_clk sun50i_a64_c
 	SUNXI_CCU_FIXED_FACTOR(A64_CLK_PLL_AUDIO_4X, "pll_audio_4x", "pll_audio_base", 1, 4),
 	SUNXI_CCU_FIXED_FACTOR(A64_CLK_PLL_AUDIO_8X, "pll_audio_8x", "pll_audio_base", 1, 8),
 
+	SUNXI_CCU_FRACTIONAL(A64_CLK_PLL_VIDEO0, "pll_video0", "hosc",
+	    PLL_VIDEO0_CTRL_REG,	/* reg */
+	    __BITS(14,8),		/* m */
+	    16,				/* m_min */
+	    50,				/* m_max */
+	    __BIT(24),			/* div_en */
+	    __BIT(25),			/* frac_sel */
+	    270000000, 297000000,	/* frac values */
+	    __BITS(3,0),		/* prediv */
+	    4,				/* prediv_val */
+	    __BIT(31),			/* enable */
+	    SUNXI_CCU_FRACTIONAL_PLUSONE | SUNXI_CCU_FRACTIONAL_SET_ENABLE),
+
+	SUNXI_CCU_FIXED_FACTOR(A64_CLK_PLL_VIDEO0_2X, "pll_video0_2x", "pll_video0", 1, 2),
+
+	SUNXI_CCU_FRACTIONAL(A64_CLK_PLL_VIDEO1, "pll_video1", "hosc",
+	    PLL_VIDEO1_CTRL_REG,	/* reg */
+	    __BITS(14,8),		/* m */
+	    16,				/* m_min */
+	    50,				/* m_max */
+	    __BIT(24),			/* div_en */
+	    __BIT(25),			/* frac_sel */
+	    270000000, 297000000,	/* frac values */
+	    __BITS(3,0),		/* prediv */
+	    4,				/* prediv_val */
+	    __BIT(31),			/* enable */
+	    SUNXI_CCU_FRACTIONAL_PLUSONE | SUNXI_CCU_FRACTIONAL_SET_ENABLE),
+
 	SUNXI_CCU_FRACTIONAL(A64_CLK_PLL_DE, "pll_de", "hosc",
-	    DE_CLK_REG,			/* reg */
+	    PLL_DE_CTRL_REG,		/* reg */
 	    __BITS(14,8),		/* m */
 	    16,				/* m_min */
 	    50,				/* m_max */
@@ -273,7 +308,7 @@ static struct sunxi_ccu_clk sun50i_a64_c
 	    __BITS(3,0),		/* prediv */
 	    2,				/* prediv_val */
 	    __BIT(31),			/* enable */
-	    SUNXI_CCU_FRACTIONAL_PLUSONE),
+	    SUNXI_CCU_FRACTIONAL_PLUSONE | SUNXI_CCU_FRACTIONAL_SET_ENABLE),
 
 	SUNXI_CCU_PREDIV(A64_CLK_AHB1, "ahb1", ahb1_parents,
 	    AHB1_APB1_CFG_REG,	/* reg */
@@ -346,6 +381,23 @@ static struct sunxi_ccu_clk sun50i_a64_c
 	SUNXI_CCU_GATE(A64_CLK_AC_DIG_4X, "ac-dig-4x", "pll_audio_4x",
 	    AC_DIG_CLK_REG, 30),
 
+	SUNXI_CCU_DIV_GATE(A64_CLK_HDMI, "hdmi", hdmi_parents,
+	    HDMI_CLK_REG,	/* reg */
+	    __BITS(3,0),	/* div */
+	    __BITS(25,24),	/* sel */
+	    __BIT(31),		/* enable */
+	   0),
+
+	SUNXI_CCU_GATE(A64_CLK_HDMI_DDC, "hdmi-ddc", "hosc",
+	    HDMI_SLOW_CLK_REG, 31),
+
+	SUNXI_CCU_DIV_GATE(A64_CLK_TCON1, "tcon1", tcon1_parents,
+	    TCON1_CLK_REG,	/* reg */
+	    __BITS(3,0),	/* div */
+	    __BITS(25,24),	/* sel */
+	    __BIT(31),		/* enable */
+	    0),
+
 	SUNXI_CCU_GATE(A64_CLK_BUS_MIPI_DSI, "bus-mipi-dsi", "ahb1",
 	    BUS_CLK_GATING_REG0, 1),
 	SUNXI_CCU_GATE(A64_CLK_BUS_CE, "bus-ce", "ahb1",
@@ -482,5 +534,9 @@ sun50i_a64_ccu_attach(device_t parent, d
 	aprint_naive("\n");
 	aprint_normal(": A64 CCU\n");
 
+	/* Set DE parent to PLL_DE */
+	clk_set_parent(&sc->sc_clks[A64_CLK_DE].base, &sc->sc_clks[A64_CLK_PLL_DE].base);
+	clk_set_rate(&sc->sc_clks[A64_CLK_PLL_DE].base, 420000000);
+
 	sunxi_ccu_print(sc);
 }

Index: src/sys/arch/arm/sunxi/sunxi_ccu.h
diff -u src/sys/arch/arm/sunxi/sunxi_ccu.h:1.20 src/sys/arch/arm/sunxi/sunxi_ccu.h:1.21
--- src/sys/arch/arm/sunxi/sunxi_ccu.h:1.20	Tue Jan 22 23:06:49 2019
+++ src/sys/arch/arm/sunxi/sunxi_ccu.h	Wed Jan 30 01:24:00 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: sunxi_ccu.h,v 1.20 2019/01/22 23:06:49 jmcneill Exp $ */
+/* $NetBSD: sunxi_ccu.h,v 1.21 2019/01/30 01:24:00 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca>
@@ -373,6 +373,7 @@ struct sunxi_ccu_fractional {
 	uint32_t	enable;
 	uint32_t	flags;
 #define	SUNXI_CCU_FRACTIONAL_PLUSONE	__BIT(0)
+#define	SUNXI_CCU_FRACTIONAL_SET_ENABLE	__BIT(1)
 };
 
 int	sunxi_ccu_fractional_enable(struct sunxi_ccu_softc *,

Index: src/sys/arch/arm/sunxi/sunxi_ccu_fractional.c
diff -u src/sys/arch/arm/sunxi/sunxi_ccu_fractional.c:1.3 src/sys/arch/arm/sunxi/sunxi_ccu_fractional.c:1.4
--- src/sys/arch/arm/sunxi/sunxi_ccu_fractional.c:1.3	Tue Jan 22 23:06:49 2019
+++ src/sys/arch/arm/sunxi/sunxi_ccu_fractional.c	Wed Jan 30 01:24:00 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: sunxi_ccu_fractional.c,v 1.3 2019/01/22 23:06:49 jmcneill Exp $ */
+/* $NetBSD: sunxi_ccu_fractional.c,v 1.4 2019/01/30 01:24:00 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca>
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_fractional.c,v 1.3 2019/01/22 23:06:49 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_fractional.c,v 1.4 2019/01/30 01:24:00 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -169,8 +169,11 @@ sunxi_ccu_fractional_set_rate(struct sun
 
 	val &= ~fractional->m;
 	val |= __SHIFTIN(best_m, fractional->m);
+	if (fractional->flags & SUNXI_CCU_FRACTIONAL_SET_ENABLE)
+		val |= fractional->enable;
 	CCU_WRITE(sc, fractional->reg, val);
 
+
 	return 0;
 }
 

Index: src/sys/dev/fdt/fdt_i2c.c
diff -u src/sys/dev/fdt/fdt_i2c.c:1.5 src/sys/dev/fdt/fdt_i2c.c:1.6
--- src/sys/dev/fdt/fdt_i2c.c:1.5	Wed Sep 26 20:03:36 2018
+++ src/sys/dev/fdt/fdt_i2c.c	Wed Jan 30 01:24:00 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: fdt_i2c.c,v 1.5 2018/09/26 20:03:36 jakllsch Exp $ */
+/* $NetBSD: fdt_i2c.c,v 1.6 2019/01/30 01:24:00 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2015 Jared D. McNeill <jmcne...@invisible.ca>
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: fdt_i2c.c,v 1.5 2018/09/26 20:03:36 jakllsch Exp $");
+__KERNEL_RCSID(0, "$NetBSD: fdt_i2c.c,v 1.6 2019/01/30 01:24:00 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -89,6 +89,18 @@ fdtbus_get_i2c_tag(int phandle)
 	return i2c->i2c_funcs->get_tag(i2c->i2c_dev);
 }
 
+i2c_tag_t
+fdtbus_i2c_acquire(int phandle, const char *prop)
+{
+	int i2c_phandle;
+
+	i2c_phandle = fdtbus_get_phandle(phandle, prop);
+	if (i2c_phandle == -1)
+		return NULL;
+
+	return fdtbus_get_i2c_tag(i2c_phandle);
+}
+
 device_t
 fdtbus_attach_i2cbus(device_t dev, int phandle, i2c_tag_t tag, cfprint_t print)
 {

Index: src/sys/dev/fdt/fdt_phy.c
diff -u src/sys/dev/fdt/fdt_phy.c:1.2 src/sys/dev/fdt/fdt_phy.c:1.3
--- src/sys/dev/fdt/fdt_phy.c:1.2	Sat Jun 30 20:34:43 2018
+++ src/sys/dev/fdt/fdt_phy.c	Wed Jan 30 01:24:00 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: fdt_phy.c,v 1.2 2018/06/30 20:34:43 jmcneill Exp $ */
+/* $NetBSD: fdt_phy.c,v 1.3 2019/01/30 01:24:00 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2015-2017 Jared McNeill <jmcne...@invisible.ca>
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: fdt_phy.c,v 1.2 2018/06/30 20:34:43 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: fdt_phy.c,v 1.3 2019/01/30 01:24:00 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -172,6 +172,12 @@ fdtbus_phy_put(struct fdtbus_phy *phy)
 	kmem_free(phy, sizeof(*phy));
 }
 
+device_t
+fdtbus_phy_device(struct fdtbus_phy *phy)
+{
+	return phy->phy_pc->pc_dev;
+}
+
 int
 fdtbus_phy_enable(struct fdtbus_phy *phy, bool enable)
 {

Index: src/sys/dev/fdt/fdt_port.c
diff -u src/sys/dev/fdt/fdt_port.c:1.1 src/sys/dev/fdt/fdt_port.c:1.2
--- src/sys/dev/fdt/fdt_port.c:1.1	Tue Apr  3 12:40:20 2018
+++ src/sys/dev/fdt/fdt_port.c	Wed Jan 30 01:24:00 2019
@@ -1,4 +1,4 @@
-/*	$NetBSD: fdt_port.c,v 1.1 2018/04/03 12:40:20 bouyer Exp $	*/
+/*	$NetBSD: fdt_port.c,v 1.2 2019/01/30 01:24:00 jmcneill Exp $	*/
 
 /*-
  * Copyright (c) 2018 The NetBSD Foundation, Inc.
@@ -38,7 +38,7 @@
 
 #include <sys/cdefs.h>
 
-__KERNEL_RCSID(1, "$NetBSD: fdt_port.c,v 1.1 2018/04/03 12:40:20 bouyer Exp $");
+__KERNEL_RCSID(1, "$NetBSD: fdt_port.c,v 1.2 2019/01/30 01:24:00 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -119,6 +119,20 @@ fdt_endpoint_get_from_index(struct fdt_d
 }
 
 struct fdt_endpoint *
+fdt_endpoint_remote_from_index(struct fdt_device_ports *device_ports,
+    int port_index, int ep_index)
+{
+	struct fdt_endpoint *ep;
+
+	ep = fdt_endpoint_get_from_index(device_ports, port_index,
+	    ep_index);
+	if (ep == NULL)
+		return NULL;
+
+	return fdt_endpoint_remote(ep);
+}
+
+struct fdt_endpoint *
 fdt_endpoint_remote(struct fdt_endpoint *ep)
 {
 	return ep->ep_rep;
@@ -154,6 +168,12 @@ fdt_endpoint_is_enabled(struct fdt_endpo
 	return ep->ep_enabled;
 }
 
+enum endpoint_type
+fdt_endpoint_type(struct fdt_endpoint *ep)
+{
+	return ep->ep_type;
+}
+
 int
 fdt_endpoint_activate(struct fdt_endpoint *ep, bool activate)
 {
@@ -170,6 +190,8 @@ fdt_endpoint_activate(struct fdt_endpoin
 		return EBUSY;
 
 	rdp = rep->ep_port->port_dp;
+device_printf(rdp->dp_dev, "activating port %d endpoint %d\n",
+    fdt_endpoint_port_index(rep), fdt_endpoint_index(rep));
 	if (rdp->dp_ep_activate)
 		error = rdp->dp_ep_activate(rdp->dp_dev, rep, activate);
 
@@ -179,6 +201,21 @@ fdt_endpoint_activate(struct fdt_endpoin
 }
 
 int
+fdt_endpoint_activate_direct(struct fdt_endpoint *ep, bool activate)
+{
+	struct fdt_device_ports *dp;
+	int error = 0;
+
+	dp = ep->ep_port->port_dp;
+device_printf(dp->dp_dev, "activating port %d endpoint %d (direct)\n",
+    fdt_endpoint_port_index(ep), fdt_endpoint_index(ep));
+	if (dp->dp_ep_activate)
+		error = dp->dp_ep_activate(dp->dp_dev, ep, activate);
+
+	return error;
+}
+
+int
 fdt_endpoint_enable(struct fdt_endpoint *ep, bool enable)
 {
 	struct fdt_endpoint *rep = fdt_endpoint_remote(ep);
Index: src/sys/dev/fdt/fdt_port.h
diff -u src/sys/dev/fdt/fdt_port.h:1.1 src/sys/dev/fdt/fdt_port.h:1.2
--- src/sys/dev/fdt/fdt_port.h:1.1	Tue Apr  3 12:40:20 2018
+++ src/sys/dev/fdt/fdt_port.h	Wed Jan 30 01:24:00 2019
@@ -1,4 +1,4 @@
-/*	$NetBSD: fdt_port.h,v 1.1 2018/04/03 12:40:20 bouyer Exp $	*/
+/*	$NetBSD: fdt_port.h,v 1.2 2019/01/30 01:24:00 jmcneill Exp $	*/
 
 /*-
  * Copyright (c) 2018 The NetBSD Foundation, Inc.
@@ -44,6 +44,8 @@
 #ifndef _DEV_FDT_FDT_PORT_H_
 #define _DEV_FDT_FDT_PORT_H_
 
+struct drm_device;
+
 struct fdt_port;
 struct fdt_endpoint;
 
@@ -63,6 +65,11 @@ enum endpoint_type {
 	EP_OTHER = 0,
 	EP_CONNECTOR,
 	EP_PANEL,
+
+	EP_DRM_BRIDGE,		/* struct drm_bridge */
+	EP_DRM_CONNECTOR,	/* struct drm_connector */
+	EP_DRM_CRTC,		/* struct drm_crtc */
+	EP_DRM_ENCODER,		/* struct drm_encoder */
 };
 
 
@@ -79,15 +86,19 @@ struct fdt_endpoint *fdt_endpoint_get_fr
 struct fdt_endpoint *fdt_endpoint_get_from_index(struct fdt_device_ports *,
 							int, int);
 struct fdt_endpoint *fdt_endpoint_remote(struct fdt_endpoint *);
+struct fdt_endpoint *fdt_endpoint_remote_from_index(struct fdt_device_ports *,
+							int, int);
 
 /*
  * get informations/data for a given endpoint
  */
 int fdt_endpoint_port_index(struct fdt_endpoint *);
 int fdt_endpoint_index(struct fdt_endpoint *);
+int fdt_endpoint_phandle(struct fdt_endpoint *);
 device_t fdt_endpoint_device(struct fdt_endpoint *);
 bool fdt_endpoint_is_active(struct fdt_endpoint *);
 bool fdt_endpoint_is_enabled(struct fdt_endpoint *);
+enum endpoint_type fdt_endpoint_type(struct fdt_endpoint *);
 /*
  * call dp_ep_get_data() for the endpoint. The returned pointer is
  * type of driver-specific.
@@ -99,6 +110,12 @@ void * fdt_endpoint_get_data(struct fdt_
  * called for the remote endpoint
  */
 int fdt_endpoint_activate(struct fdt_endpoint *, bool);
+
+/*
+ * Activate/deactive an endpoint by direct reference.
+ */
+int fdt_endpoint_activate_direct(struct fdt_endpoint *, bool);
+
 /*
  * Enable/disable an endpoint. This causes dp_ep_enable() to be called for
  * the remote endpoint

Index: src/sys/dev/fdt/fdtvar.h
diff -u src/sys/dev/fdt/fdtvar.h:1.47 src/sys/dev/fdt/fdtvar.h:1.48
--- src/sys/dev/fdt/fdtvar.h:1.47	Sat Jan 26 14:38:30 2019
+++ src/sys/dev/fdt/fdtvar.h	Wed Jan 30 01:24:00 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: fdtvar.h,v 1.47 2019/01/26 14:38:30 thorpej Exp $ */
+/* $NetBSD: fdtvar.h,v 1.48 2019/01/30 01:24:00 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2015 Jared D. McNeill <jmcne...@invisible.ca>
@@ -280,6 +280,7 @@ int		fdtbus_get_reg64(int, u_int, uint64
 int		fdtbus_get_phandle(int, const char *);
 int		fdtbus_get_phandle_from_native(int);
 i2c_tag_t	fdtbus_get_i2c_tag(int);
+i2c_tag_t	fdtbus_i2c_acquire(int, const char *);
 void *		fdtbus_intr_establish(int, u_int, int, int,
 		    int (*func)(void *), void *arg);
 void *		fdtbus_intr_establish_raw(int, const u_int *, int, int,
@@ -344,6 +345,7 @@ int		fdtbus_reset_deassert(struct fdtbus
 struct fdtbus_phy *fdtbus_phy_get(int, const char *);
 struct fdtbus_phy *fdtbus_phy_get_index(int, u_int);
 void		fdtbus_phy_put(struct fdtbus_phy *);
+device_t	fdtbus_phy_device(struct fdtbus_phy *);
 int		fdtbus_phy_enable(struct fdtbus_phy *, bool);
 
 struct fdtbus_mmc_pwrseq *fdtbus_mmc_pwrseq_get(int);

Index: src/sys/dev/fdt/files.fdt
diff -u src/sys/dev/fdt/files.fdt:1.41 src/sys/dev/fdt/files.fdt:1.42
--- src/sys/dev/fdt/files.fdt:1.41	Fri Oct 19 21:09:10 2018
+++ src/sys/dev/fdt/files.fdt	Wed Jan 30 01:24:00 2019
@@ -1,4 +1,4 @@
-# $NetBSD: files.fdt,v 1.41 2018/10/19 21:09:10 jakllsch Exp $
+# $NetBSD: files.fdt,v 1.42 2019/01/30 01:24:00 jmcneill Exp $
 
 include	"external/bsd/libfdt/conf/files.libfdt"
 
@@ -45,6 +45,10 @@ device	panel: fdt_port
 attach	panel at fdt with fdt_panel
 file	dev/fdt/panel_fdt.c			fdt_panel
 
+device	dispcon: fdt_port, drmkms, ddc_read_edid
+attach	dispcon at fdt with dispcon_hdmi
+file	dev/fdt/hdmi_connector.c		dispcon_hdmi
+
 file	dev/fdt/fdt_openfirm.c			fdtbase
 file	dev/fdt/fdt_subr.c			fdtbase
 file	dev/fdt/fdt_clock.c			fdt

Added files:

Index: src/sys/arch/arm/sunxi/sunxi_drm.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_drm.c:1.1
--- /dev/null	Wed Jan 30 01:24:00 2019
+++ src/sys/arch/arm/sunxi/sunxi_drm.c	Wed Jan 30 01:24:00 2019
@@ -0,0 +1,407 @@
+/* $NetBSD: sunxi_drm.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca>
+ * 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_drm.c,v 1.1 2019/01/30 01:24:00 jmcneill 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/conf.h>
+
+#include <uvm/uvm_extern.h>
+#include <uvm/uvm_object.h>
+#include <uvm/uvm_device.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+
+#include <dev/fdt/fdtvar.h>
+#include <dev/fdt/fdt_port.h>
+
+#include <arm/sunxi/sunxi_drm.h>
+
+static TAILQ_HEAD(, sunxi_drm_endpoint) sunxi_drm_endpoints =
+    TAILQ_HEAD_INITIALIZER(sunxi_drm_endpoints);
+
+static const char * const compatible[] = {
+	"allwinner,sun50i-a64-display-engine",
+	NULL
+};
+
+static const char * fb_compatible[] = {
+	"allwinner,simple-framebuffer",
+	NULL
+};
+
+static int	sunxi_drm_match(device_t, cfdata_t, void *);
+static void	sunxi_drm_attach(device_t, device_t, void *);
+
+static void	sunxi_drm_init(device_t);
+
+static int	sunxi_drm_set_busid(struct drm_device *, struct drm_master *);
+
+static int	sunxi_drm_load(struct drm_device *, unsigned long);
+static int	sunxi_drm_unload(struct drm_device *);
+
+static struct drm_driver sunxi_drm_driver = {
+	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME,
+	.dev_priv_size = 0,
+	.load = sunxi_drm_load,
+	.unload = sunxi_drm_unload,
+
+	.gem_free_object = drm_gem_cma_free_object,
+	.mmap_object = drm_gem_or_legacy_mmap_object,
+	.gem_uvm_ops = &drm_gem_cma_uvm_ops,
+
+	.dumb_create = drm_gem_cma_dumb_create,
+	.dumb_map_offset = drm_gem_cma_dumb_map_offset,
+	.dumb_destroy = drm_gem_dumb_destroy,
+
+#if notyet
+	.get_vblank_counter = sunxi_drm_get_vblank_counter,
+	.enable_vblank = sunxi_drm_enable_vblank,
+	.disable_vblank = sunxi_drm_disable_vblank,
+#endif
+
+	.name = DRIVER_NAME,
+	.desc = DRIVER_DESC,
+	.date = DRIVER_DATE,
+	.major = DRIVER_MAJOR,
+	.minor = DRIVER_MINOR,
+	.patchlevel = DRIVER_PATCHLEVEL,
+
+	.set_busid = sunxi_drm_set_busid,
+};
+
+CFATTACH_DECL_NEW(sunxi_drm, sizeof(struct sunxi_drm_softc),
+	sunxi_drm_match, sunxi_drm_attach, NULL, NULL);
+
+static int
+sunxi_drm_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct fdt_attach_args * const faa = aux;
+
+	return of_match_compatible(faa->faa_phandle, compatible);
+}
+
+static void
+sunxi_drm_attach(device_t parent, device_t self, void *aux)
+{
+	struct sunxi_drm_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	struct drm_driver * const driver = &sunxi_drm_driver;
+
+	sc->sc_dev = self;
+	sc->sc_dmat = faa->faa_dmat;
+	sc->sc_bst = faa->faa_bst;
+	sc->sc_phandle = faa->faa_phandle;
+
+	aprint_naive("\n");
+	aprint_normal(": Display Engine Pipeline\n");
+
+	sc->sc_ddev = drm_dev_alloc(driver, sc->sc_dev);
+	if (sc->sc_ddev == NULL) {
+		aprint_error_dev(self, "couldn't allocate DRM device\n");
+		return;
+	}
+	sc->sc_ddev->dev_private = sc;
+	sc->sc_ddev->bst = sc->sc_bst;
+	sc->sc_ddev->bus_dmat = sc->sc_dmat;
+	sc->sc_ddev->dmat = sc->sc_ddev->bus_dmat;
+	sc->sc_ddev->dmat_subregion_p = false;
+
+	fdt_remove_bycompat(fb_compatible);
+
+	config_defer(self, sunxi_drm_init);
+}
+
+static void
+sunxi_drm_init(device_t dev)
+{
+	struct sunxi_drm_softc * const sc = device_private(dev);
+	struct drm_driver * const driver = &sunxi_drm_driver;
+	int error;
+
+	error = -drm_dev_register(sc->sc_ddev, 0);
+	if (error) {
+		drm_dev_unref(sc->sc_ddev);
+		aprint_error_dev(dev, "couldn't register DRM device: %d\n",
+		    error);
+		return;
+	}
+
+	aprint_normal_dev(dev, "initialized %s %d.%d.%d %s on minor %d\n",
+	    driver->name, driver->major, driver->minor, driver->patchlevel,
+	    driver->date, sc->sc_ddev->primary->index);
+}
+
+static int
+sunxi_drm_set_busid(struct drm_device *ddev, struct drm_master *master)
+{
+	struct sunxi_drm_softc * const sc = sunxi_drm_private(ddev);
+	char id[32];
+
+	snprintf(id, sizeof(id), "platform:sunxi:%u", device_unit(sc->sc_dev));
+
+	master->unique = kzalloc(strlen(id) + 1, GFP_KERNEL);
+	if (master->unique == NULL)
+		return -ENOMEM;
+	strcpy(master->unique, id);
+	master->unique_len = strlen(master->unique);
+
+	return 0;
+}
+
+static int
+sunxi_drm_fb_create_handle(struct drm_framebuffer *fb,
+    struct drm_file *file, unsigned int *handle)
+{
+	struct sunxi_drm_framebuffer *sfb = to_sunxi_drm_framebuffer(fb);
+
+	return drm_gem_handle_create(file, &sfb->obj->base, handle);
+}
+
+static void
+sunxi_drm_fb_destroy(struct drm_framebuffer *fb)
+{
+	struct sunxi_drm_framebuffer *sfb = to_sunxi_drm_framebuffer(fb);
+
+	drm_framebuffer_cleanup(fb);
+	drm_gem_object_unreference_unlocked(&sfb->obj->base);
+	kmem_free(sfb, sizeof(*sfb));
+}
+
+static const struct drm_framebuffer_funcs sunxi_drm_framebuffer_funcs = {
+	.create_handle = sunxi_drm_fb_create_handle,
+	.destroy = sunxi_drm_fb_destroy,
+};
+
+static struct drm_framebuffer *
+sunxi_drm_fb_create(struct drm_device *ddev, struct drm_file *file,
+    struct drm_mode_fb_cmd2 *cmd)
+{
+	struct sunxi_drm_framebuffer *fb;
+	struct drm_gem_object *gem_obj;
+	int error;
+
+	if (cmd->flags)
+		return NULL;
+
+	gem_obj = drm_gem_object_lookup(ddev, file, cmd->handles[0]);
+	if (gem_obj == NULL)
+		return NULL;
+
+	fb = kmem_zalloc(sizeof(*fb), KM_SLEEP);
+	fb->obj = to_drm_gem_cma_obj(gem_obj);
+	fb->base.pitches[0] = cmd->pitches[0];
+	fb->base.offsets[0] = cmd->offsets[0];
+	fb->base.width = cmd->width;
+	fb->base.height = cmd->height;
+	fb->base.pixel_format = cmd->pixel_format;
+	drm_fb_get_bpp_depth(cmd->pixel_format, &fb->base.depth,
+	    &fb->base.bits_per_pixel);
+
+	error = drm_framebuffer_init(ddev, &fb->base, &sunxi_drm_framebuffer_funcs);
+	if (error != 0)
+		goto dealloc;
+
+	return &fb->base;
+
+dealloc:
+	drm_framebuffer_cleanup(&fb->base);
+	kmem_free(fb, sizeof(*fb));
+	drm_gem_object_unreference_unlocked(gem_obj);
+
+	return NULL;
+}
+
+static struct drm_mode_config_funcs sunxi_drm_mode_config_funcs = {
+	.fb_create = sunxi_drm_fb_create,
+};
+
+static int
+sunxi_drm_fb_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes)
+{
+	struct sunxi_drm_softc * const sc = sunxi_drm_private(helper->dev);
+	struct drm_device *ddev = helper->dev;
+	struct sunxi_drm_framebuffer *sfb = to_sunxi_drm_framebuffer(helper->fb);
+	struct drm_framebuffer *fb = helper->fb;
+	struct sunxi_drmfb_attach_args sfa;
+	int error;
+
+	const u_int width = sizes->surface_width;
+	const u_int height = sizes->surface_height;
+	const u_int pitch = width * (32 / 8);
+
+	const size_t size = roundup(height * pitch, PAGE_SIZE);
+
+	sfb->obj = drm_gem_cma_create(ddev, size);
+	if (sfb->obj == NULL) {
+		DRM_ERROR("failed to allocate memory for framebuffer\n");
+		return -ENOMEM;
+	}
+
+	fb->pitches[0] = pitch;
+	fb->offsets[0] = 0;
+	fb->width = width;
+	fb->height = height;
+	fb->pixel_format = DRM_FORMAT_XRGB8888;
+	drm_fb_get_bpp_depth(fb->pixel_format, &fb->depth, &fb->bits_per_pixel);
+
+	error = drm_framebuffer_init(ddev, fb, &sunxi_drm_framebuffer_funcs);
+	if (error != 0) {
+		DRM_ERROR("failed to initialize framebuffer\n");
+		return error;
+	}
+
+	memset(&sfa, 0, sizeof(sfa));
+	sfa.sfa_drm_dev = ddev;
+	sfa.sfa_fb_helper = helper;
+	sfa.sfa_fb_sizes = *sizes;
+	sfa.sfa_fb_bst = sc->sc_bst;
+	sfa.sfa_fb_dmat = sc->sc_dmat;
+	sfa.sfa_fb_linebytes = helper->fb->pitches[0];
+
+	helper->fbdev = config_found_ia(ddev->dev, "sunxifbbus", &sfa, NULL);
+	if (helper->fbdev == NULL) {
+		DRM_ERROR("unable to attach framebuffer\n");
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+static struct drm_fb_helper_funcs sunxi_drm_fb_helper_funcs = {
+	.fb_probe = sunxi_drm_fb_probe,
+};
+
+static int
+sunxi_drm_load(struct drm_device *ddev, unsigned long flags)
+{
+	struct sunxi_drm_softc * const sc = sunxi_drm_private(ddev);
+	struct sunxi_drm_endpoint *sep;
+	struct sunxi_drm_fbdev *fbdev;
+	const u_int *data;
+	int datalen, error, num_crtc;
+
+	drm_mode_config_init(ddev);
+	ddev->mode_config.min_width = 0;
+	ddev->mode_config.min_height = 0;
+	ddev->mode_config.max_width = 3840;
+	ddev->mode_config.max_height = 2160;
+	ddev->mode_config.funcs = &sunxi_drm_mode_config_funcs;
+
+	num_crtc = 0;
+	data = fdtbus_get_prop(sc->sc_phandle, "allwinner,pipelines", &datalen);
+	while (datalen >= 4) {
+		const int crtc_phandle = fdtbus_get_phandle_from_native(be32dec(data));
+
+		TAILQ_FOREACH(sep, &sunxi_drm_endpoints, entries)
+			if (sep->phandle == crtc_phandle && sep->ddev == NULL) {
+				sep->ddev = ddev;
+				error = fdt_endpoint_activate_direct(sep->ep, true);
+				if (error != 0) {
+					aprint_error_dev(sc->sc_dev, "failed to activate endpoint: %d\n",
+					    error);
+				}
+				if (fdt_endpoint_type(sep->ep) == EP_DRM_CRTC)
+					num_crtc++;
+			}
+
+		datalen -= 4;
+		data++;
+	}
+
+	if (num_crtc == 0) {
+		aprint_error_dev(sc->sc_dev, "no pipelines configured\n");
+		return ENXIO;
+	}
+
+	fbdev = kmem_zalloc(sizeof(*fbdev), KM_SLEEP);
+
+	drm_fb_helper_prepare(ddev, &fbdev->helper, &sunxi_drm_fb_helper_funcs);
+
+	error = drm_fb_helper_init(ddev, &fbdev->helper, num_crtc, num_crtc);
+	if (error)
+		goto drmerr;
+
+	fbdev->helper.fb = kmem_zalloc(sizeof(struct sunxi_drm_framebuffer), KM_SLEEP);
+
+	drm_fb_helper_single_add_all_connectors(&fbdev->helper);
+
+	drm_helper_disable_unused_functions(ddev);
+
+	drm_fb_helper_initial_config(&fbdev->helper, 32);
+
+	return 0;
+
+drmerr:
+	drm_mode_config_cleanup(ddev);
+	kmem_free(fbdev, sizeof(*fbdev));
+
+	return error;
+}
+
+static int
+sunxi_drm_unload(struct drm_device *ddev)
+{
+	drm_mode_config_cleanup(ddev);
+
+	return 0;
+}
+
+int
+sunxi_drm_register_endpoint(int phandle, struct fdt_endpoint *ep)
+{
+	struct sunxi_drm_endpoint *sep;
+
+	sep = kmem_zalloc(sizeof(*sep), KM_SLEEP);
+	sep->phandle = phandle;
+	sep->ep = ep;
+	sep->ddev = NULL;
+	TAILQ_INSERT_TAIL(&sunxi_drm_endpoints, sep, entries);
+
+	return 0;
+}
+
+struct drm_device *
+sunxi_drm_endpoint_device(struct fdt_endpoint *ep)
+{
+	struct sunxi_drm_endpoint *sep;
+
+	TAILQ_FOREACH(sep, &sunxi_drm_endpoints, entries)
+		if (sep->ep == ep)
+			return sep->ddev;
+
+	return NULL;
+}
Index: src/sys/arch/arm/sunxi/sunxi_drm.h
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_drm.h:1.1
--- /dev/null	Wed Jan 30 01:24:00 2019
+++ src/sys/arch/arm/sunxi/sunxi_drm.h	Wed Jan 30 01:24:00 2019
@@ -0,0 +1,88 @@
+/* $NetBSD: sunxi_drm.h,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca>
+ * 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.
+ */
+
+#ifndef _ARM_SUNXI_DRM_H
+#define _ARM_SUNXI_DRM_H
+
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#define DRIVER_AUTHOR		"Jared McNeill"
+
+#define DRIVER_NAME		"sunxi"
+#define DRIVER_DESC		"Allwinner Display Engine"
+#define DRIVER_DATE		"20190123"
+
+#define DRIVER_MAJOR		1
+#define DRIVER_MINOR		0
+#define DRIVER_PATCHLEVEL	0
+
+struct sunxi_framebuffer;
+
+struct sunxi_drm_softc {
+	device_t		sc_dev;
+	struct drm_device	*sc_ddev;
+
+	bus_space_tag_t		sc_bst;
+	bus_dma_tag_t		sc_dmat;
+
+	int			sc_phandle;
+};
+
+struct sunxi_drm_framebuffer {
+	struct drm_framebuffer	base;
+	struct drm_gem_cma_object *obj;
+};
+
+struct sunxi_drm_endpoint {
+	int			phandle;
+	struct fdt_endpoint	*ep;
+	struct drm_device	*ddev;
+	TAILQ_ENTRY(sunxi_drm_endpoint) entries;
+};
+
+struct sunxi_drm_fbdev {
+	struct drm_fb_helper	helper;
+};
+
+struct sunxi_drmfb_attach_args {
+	struct drm_device	*sfa_drm_dev;
+	struct drm_fb_helper	*sfa_fb_helper;
+	struct drm_fb_helper_surface_size sfa_fb_sizes;
+	bus_space_tag_t		sfa_fb_bst;
+	bus_dma_tag_t		sfa_fb_dmat;
+	uint32_t		sfa_fb_linebytes;
+};
+
+#define sunxi_drm_private(ddev)		(ddev)->dev_private
+#define	to_sunxi_drm_framebuffer(x)	container_of(x, struct sunxi_drm_framebuffer, base)
+
+int	sunxi_drm_register_endpoint(int, struct fdt_endpoint *);
+struct drm_device *sunxi_drm_endpoint_device(struct fdt_endpoint *);
+
+#endif /* _ARM_SUNXI_DRM_H */
Index: src/sys/arch/arm/sunxi/sunxi_dwhdmi.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_dwhdmi.c:1.1
--- /dev/null	Wed Jan 30 01:24:00 2019
+++ src/sys/arch/arm/sunxi/sunxi_dwhdmi.c	Wed Jan 30 01:24:00 2019
@@ -0,0 +1,258 @@
+/* $NetBSD: sunxi_dwhdmi.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca>
+ * 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_dwhdmi.c,v 1.1 2019/01/30 01:24:00 jmcneill 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/conf.h>
+
+#include <drm/drmP.h>
+
+#include <dev/fdt/fdtvar.h>
+#include <dev/fdt/fdt_port.h>
+
+#include <dev/ic/dw_hdmi.h>
+
+#include <arm/sunxi/sunxi_hdmiphy.h>
+
+enum {
+	DWHDMI_PORT_INPUT = 0,
+	DWHDMI_PORT_OUTPUT = 1,
+};
+
+static const char * const compatible[] = {
+	"allwinner,sun50i-a64-dw-hdmi",
+	NULL
+};
+
+struct sunxi_dwhdmi_softc {
+	struct dwhdmi_softc	sc_base;
+	int			sc_phandle;
+	struct fdtbus_phy	*sc_phy;
+
+	struct fdt_device_ports	sc_ports;
+	struct drm_display_mode	sc_curmode;
+};
+
+#define	to_sunxi_dwhdmi_softc(x)	container_of(x, struct sunxi_dwhdmi_softc, sc_base)
+
+static int
+sunxi_dwhdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
+{
+	struct sunxi_dwhdmi_softc * const sc = device_private(dev);
+	struct fdt_endpoint *in_ep = fdt_endpoint_remote(ep);
+	struct fdt_endpoint *out_ep, *out_rep;
+	struct drm_encoder *encoder;
+	struct drm_bridge *bridge;
+	int error;
+
+	if (!activate)
+		return EINVAL;
+
+	if (fdt_endpoint_port_index(ep) != DWHDMI_PORT_INPUT)
+		return EINVAL;
+
+	switch (fdt_endpoint_type(in_ep)) {
+	case EP_DRM_ENCODER:
+		encoder = fdt_endpoint_get_data(in_ep);
+		break;
+	case EP_DRM_BRIDGE:
+		bridge = fdt_endpoint_get_data(in_ep);
+		encoder = bridge->encoder;
+		break;
+	default:
+		encoder = NULL;
+		break;
+	}
+
+	if (encoder == NULL)
+		return EINVAL;
+
+	sc->sc_phy = fdtbus_phy_get(sc->sc_phandle, "hdmi-phy");
+	if (sc->sc_phy == NULL) {
+		device_printf(dev, "couldn't find hdmi-phy\n");
+		return ENXIO;
+	}
+
+	error = dwhdmi_bind(&sc->sc_base, encoder);
+	if (error != 0)
+		return error;
+
+	out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, DWHDMI_PORT_OUTPUT, 0);
+	if (out_ep != NULL) {
+		/* Ignore downstream connectors, we have our own. */
+		out_rep = fdt_endpoint_remote(out_ep);
+		if (out_rep != NULL && fdt_endpoint_type(out_rep) == EP_DRM_CONNECTOR)
+			return 0;
+
+		error = fdt_endpoint_activate(out_ep, activate);
+		if (error != 0)
+			return error;
+	}
+
+	return 0;
+}
+
+static void *
+sunxi_dwhdmi_ep_get_data(device_t dev, struct fdt_endpoint *ep)
+{
+	struct sunxi_dwhdmi_softc * const sc = device_private(dev);
+
+	return &sc->sc_base.sc_bridge;
+}
+
+static enum drm_connector_status
+sunxi_dwhdmi_detect(struct dwhdmi_softc *dsc, bool force)
+{
+	struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
+
+	KASSERT(sc->sc_phy != NULL);
+
+	if (sunxi_hdmiphy_detect(sc->sc_phy, force))
+		return connector_status_connected;
+	else
+		return connector_status_disconnected;
+}
+
+static void
+sunxi_dwhdmi_enable(struct dwhdmi_softc *dsc)
+{
+	struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
+	int error;
+
+	KASSERT(sc->sc_phy != NULL);
+
+	error = fdtbus_phy_enable(sc->sc_phy, true);
+	if (error != 0) {
+		device_printf(dsc->sc_dev, "failed to enable phy: %d\n", error);
+		return;
+	}
+
+	error = sunxi_hdmiphy_config(sc->sc_phy, &sc->sc_curmode);
+	if (error != 0)
+		device_printf(dsc->sc_dev, "failed to configure phy: %d\n", error);
+}
+
+static void
+sunxi_dwhdmi_disable(struct dwhdmi_softc *dsc)
+{
+	struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
+	int error;
+
+	KASSERT(sc->sc_phy != NULL);
+
+	error = fdtbus_phy_enable(sc->sc_phy, false);
+	if (error != 0)
+		device_printf(dsc->sc_dev, "failed to disable phy\n");
+}
+
+static void
+sunxi_dwhdmi_mode_set(struct dwhdmi_softc *dsc, struct drm_display_mode *mode,
+    struct drm_display_mode *adjusted_mode)
+{
+	struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
+
+	sc->sc_curmode = *adjusted_mode;
+}
+
+static int
+sunxi_dwhdmi_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct fdt_attach_args * const faa = aux;
+
+	return of_match_compatible(faa->faa_phandle, compatible);
+}
+
+static void
+sunxi_dwhdmi_attach(device_t parent, device_t self, void *aux)
+{
+	struct sunxi_dwhdmi_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	struct clk *clk_iahb, *clk_isfr;
+	struct fdtbus_reset *rst;
+	bus_addr_t addr;
+	bus_size_t size;
+
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+		return;
+	}
+
+	rst = fdtbus_reset_get(phandle, "ctrl");
+	if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
+		aprint_error(": couldn't de-assert reset\n");
+		return;
+	}
+
+	clk_iahb = fdtbus_clock_get(phandle, "iahb");
+	if (clk_iahb == NULL || clk_enable(clk_iahb) != 0) {
+		aprint_error(": couldn't enable iahb clock\n");
+		return;
+	}
+
+	clk_isfr = fdtbus_clock_get(phandle, "isfr");
+	if (clk_isfr == NULL || clk_enable(clk_isfr) != 0) {
+		aprint_error(": couldn't enable isfr clock\n");
+		return;
+	}
+
+	sc->sc_base.sc_dev = self;
+	sc->sc_base.sc_reg_width = 1;
+	sc->sc_base.sc_bst = faa->faa_bst;
+	if (bus_space_map(sc->sc_base.sc_bst, addr, size, 0, &sc->sc_base.sc_bsh) != 0) {
+		aprint_error(": couldn't map registers\n");
+		return;
+	}
+	sc->sc_base.sc_detect = sunxi_dwhdmi_detect;
+	sc->sc_base.sc_enable = sunxi_dwhdmi_enable;
+	sc->sc_base.sc_disable = sunxi_dwhdmi_disable;
+	sc->sc_base.sc_mode_set = sunxi_dwhdmi_mode_set;
+	sc->sc_phandle = faa->faa_phandle;
+
+	aprint_naive("\n");
+	aprint_normal(": HDMI TX\n");
+
+	if (dwhdmi_attach(&sc->sc_base) != 0) {
+		aprint_error_dev(self, "failed to attach driver\n");
+		return;
+	}
+
+	sc->sc_ports.dp_ep_activate = sunxi_dwhdmi_ep_activate;
+	sc->sc_ports.dp_ep_get_data = sunxi_dwhdmi_ep_get_data;
+	fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_BRIDGE);
+}
+
+CFATTACH_DECL_NEW(sunxi_dwhdmi, sizeof(struct sunxi_dwhdmi_softc),
+	sunxi_dwhdmi_match, sunxi_dwhdmi_attach, NULL, NULL);
Index: src/sys/arch/arm/sunxi/sunxi_fb.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_fb.c:1.1
--- /dev/null	Wed Jan 30 01:24:00 2019
+++ src/sys/arch/arm/sunxi/sunxi_fb.c	Wed Jan 30 01:24:00 2019
@@ -0,0 +1,163 @@
+/* $NetBSD: sunxi_fb.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2015-2019 Jared McNeill <jmcne...@invisible.ca>
+ * 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_wsdisplay_compat.h"
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: sunxi_fb.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+
+#include <dev/fdt/fdtvar.h>
+
+#include <drm/drmP.h>
+#include <drm/drmfb.h>
+
+#include <arm/sunxi/sunxi_drm.h>
+
+static int	sunxi_fb_match(device_t, cfdata_t, void *);
+static void	sunxi_fb_attach(device_t, device_t, void *);
+
+static bool	sunxi_fb_shutdown(device_t, int);
+
+struct sunxi_fb_softc {
+	struct drmfb_softc	sc_drmfb;
+	device_t		sc_dev;
+	struct sunxi_drm_softc	*sc_drm;
+	struct sunxi_drm_framebuffer *sc_fb;
+	struct sunxi_drmfb_attach_args sc_sfa;
+};
+
+static paddr_t	sunxi_fb_mmapfb(struct drmfb_softc *, off_t, int);
+static int	sunxi_fb_ioctl(struct drmfb_softc *, u_long, void *, int,
+			       lwp_t *);
+
+static const struct drmfb_params sunxifb_drmfb_params = {
+	.dp_mmapfb = sunxi_fb_mmapfb,
+	.dp_ioctl = sunxi_fb_ioctl,
+	
+};
+
+CFATTACH_DECL_NEW(sunxi_fb, sizeof(struct sunxi_fb_softc),
+	sunxi_fb_match, sunxi_fb_attach, NULL, NULL);
+
+static int
+sunxi_fb_match(device_t parent, cfdata_t cf, void *aux)
+{
+	return 1;
+}
+
+static void
+sunxi_fb_attach(device_t parent, device_t self, void *aux)
+{
+	struct sunxi_fb_softc * const sc = device_private(self);
+	struct sunxi_drm_softc * const drmsc = device_private(parent);
+	struct sunxi_drmfb_attach_args * const sfa = aux;
+	int error;
+
+	sc->sc_dev = self;
+	sc->sc_drm = drmsc;
+	sc->sc_sfa = *sfa;
+	sc->sc_fb = to_sunxi_drm_framebuffer(sfa->sfa_fb_helper->fb);
+
+	aprint_naive("\n");
+	aprint_normal("\n");
+
+#ifdef WSDISPLAY_MULTICONS
+	prop_dictionary_t dict = device_properties(self);
+	const bool is_console = true;
+	prop_dictionary_set_bool(dict, "is_console", is_console);
+#endif
+
+	const struct drmfb_attach_args da = {
+		.da_dev = self,
+		.da_fb_helper = sfa->sfa_fb_helper,
+		.da_fb_sizes = &sfa->sfa_fb_sizes,
+		.da_fb_vaddr = sc->sc_fb->obj->vaddr,
+		.da_fb_linebytes = sfa->sfa_fb_linebytes,
+		.da_params = &sunxifb_drmfb_params,
+	};
+
+	error = drmfb_attach(&sc->sc_drmfb, &da);
+	if (error) {
+		aprint_error_dev(self, "failed to attach drmfb: %d\n", error);
+		return;
+	}
+
+	pmf_device_register1(self, NULL, NULL, sunxi_fb_shutdown);
+}
+
+static bool
+sunxi_fb_shutdown(device_t self, int flags)
+{
+	struct sunxi_fb_softc * const sc = device_private(self);
+
+	return drmfb_shutdown(&sc->sc_drmfb, flags);
+}
+
+static paddr_t
+sunxi_fb_mmapfb(struct drmfb_softc *sc, off_t off, int prot)
+{
+	struct sunxi_fb_softc * const tfb_sc = (struct sunxi_fb_softc *)sc;
+	struct drm_gem_cma_object *obj = tfb_sc->sc_fb->obj;
+
+	KASSERT(off >= 0);
+	KASSERT(off < obj->dmasize);
+
+	return bus_dmamem_mmap(obj->dmat, obj->dmasegs, 1, off, prot,
+	    BUS_DMA_PREFETCHABLE);
+}
+
+static int
+sunxi_fb_ioctl(struct drmfb_softc *sc, u_long cmd, void *data, int flag,
+    lwp_t *l)
+{
+	struct wsdisplayio_bus_id *busid;
+	struct wsdisplayio_fbinfo *fbi;
+	struct rasops_info *ri = &sc->sc_genfb.vd.active->scr_ri;
+	int error;
+
+	switch (cmd) {
+	case WSDISPLAYIO_GET_BUSID:
+		busid = data;
+		busid->bus_type = WSDISPLAYIO_BUS_SOC;
+		return 0;
+	case WSDISPLAYIO_GTYPE:
+		*(u_int *)data = WSDISPLAY_TYPE_GENFB;
+		return 0;
+	case WSDISPLAYIO_GET_FBINFO:
+		fbi = data;
+		error = wsdisplayio_get_fbinfo(ri, fbi);
+		fbi->fbi_flags |= WSFB_VRAM_IS_RAM;
+		return error;
+	default:
+		return EPASSTHROUGH;
+	}
+}
Index: src/sys/arch/arm/sunxi/sunxi_hdmiphy.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_hdmiphy.c:1.1
--- /dev/null	Wed Jan 30 01:24:00 2019
+++ src/sys/arch/arm/sunxi/sunxi_hdmiphy.c	Wed Jan 30 01:24:00 2019
@@ -0,0 +1,501 @@
+/* $NetBSD: sunxi_hdmiphy.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2019 Jared McNeill <jmcne...@invisible.ca>
+ * 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 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(0, "$NetBSD: sunxi_hdmiphy.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/intr.h>
+#include <sys/systm.h>
+
+#include <dev/fdt/fdtvar.h>
+
+#include <arm/sunxi/sunxi_hdmiphy.h>
+
+#define	DBG_CTRL	0x000
+#define	 DBG_CTRL_POL			__BITS(15,8)
+#define	  DBG_CTRL_POL_NVSYNC		1
+#define	  DBG_CTRL_POL_NHSYNC		2
+
+#define	READ_EN		0x010
+#define	 READ_EN_MAGIC			0x54524545	/* "TREE" */
+
+#define	UNSCRAMBLE	0x014
+#define	 UNSCRAMBLE_MAGIC		0x42494E47	/* "BING" */
+
+#define	ANA_CFG1	0x020
+#define	 ANA_CFG1_ENRCAL		__BIT(19)
+#define	 ANA_CFG1_ENCALOG		__BIT(18)
+#define	 ANA_CFG1_TMDSCLK_EN		__BIT(16)
+#define	 ANA_CFG1_TXEN			__BITS(15,12)
+#define	 ANA_CFG1_BIASEN		__BITS(11,8)
+#define	 ANA_CFG1_ENP2S			__BITS(7,4)
+#define	 ANA_CFG1_CKEN			__BIT(3)
+#define	 ANA_CFG1_LDOEN			__BIT(2)
+#define	 ANA_CFG1_ENVBS			__BIT(1)
+#define	 ANA_CFG1_ENBI			__BIT(0)
+
+#define	ANA_CFG2	0x024
+#define	 ANA_CFG2_REG_RESDI		__BITS(5,0)
+
+#define	ANA_CFG3	0x028
+#define	 ANA_CFG3_REG_SDAEN		__BIT(2)
+#define	 ANA_CFG3_REG_SCLEN		__BIT(0)
+
+#define	PLL_CFG1	0x02c
+#define	 PLL_CFG1_REG_OD1		__BIT(31)
+#define	 PLL_CFG1_REG_OD0		__BIT(30)
+#define	 PLL_CFG1_CKIN_SEL		__BIT(26)
+#define	 PLL_CFG1_PLLEN			__BIT(25)
+#define	 PLL_CFG1_B_IN			__BITS(5,0)
+
+#define	PLL_CFG2	0x030
+#define	 PLL_CFG2_PREDIV		__BITS(3,0)
+
+#define	PLL_CFG3	0x034
+
+#define	ANA_STS		0x038
+#define	 ANA_STS_HPDO			__BIT(19)
+#define	 ANA_STS_B_OUT			__BITS(16,11)
+#define	 ANA_STS_RCALEND2D		__BIT(7)
+#define	 ANA_STS_RESDO2D		__BITS(5,0)
+
+#define	CEC		0x03c
+#define	 CEC_CONTROL_SEL		__BIT(7)
+#define	 CEC_INPUT_DATA			__BIT(1)
+#define	 CEC_OUTPUT_DATA		__BIT(0)
+
+#define	CONTROLLER_VER	0xff8
+
+#define	PHY_VER		0xffc
+
+struct sunxi_hdmiphy_softc;
+
+static int sunxi_hdmiphy_match(device_t, cfdata_t, void *);
+static void sunxi_hdmiphy_attach(device_t, device_t, void *);
+
+static void sun50i_a64_hdmiphy_init(struct sunxi_hdmiphy_softc *);
+static int sun50i_a64_hdmiphy_config(struct sunxi_hdmiphy_softc *, u_int);
+
+struct sunxi_hdmiphy_data {
+	void	(*init)(struct sunxi_hdmiphy_softc *);
+	int	(*config)(struct sunxi_hdmiphy_softc *, u_int);
+};
+
+static const struct sunxi_hdmiphy_data sun50i_a64_hdmiphy_data = {
+	.init = sun50i_a64_hdmiphy_init,
+	.config = sun50i_a64_hdmiphy_config,
+};
+
+static const struct of_compat_data compat_data[] = {
+	{ "allwinner,sun50i-a64-hdmi-phy",	(uintptr_t)&sun50i_a64_hdmiphy_data },
+	{ NULL }
+};
+
+struct sunxi_hdmiphy_softc {
+	device_t		sc_dev;
+	bus_space_tag_t		sc_bst;
+	bus_space_handle_t	sc_bsh;
+
+	const struct sunxi_hdmiphy_data *sc_data;
+
+	struct clk		*sc_clk_pll0;
+
+	u_int			sc_rcalib;
+};
+
+#define	PHY_READ(sc, reg)						\
+	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
+#define	PHY_WRITE(sc, reg, val)						\
+	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
+#define	PHY_SET_CLEAR(sc, reg, set, clr)				\
+	do {								\
+		uint32_t _tval = PHY_READ((sc), (reg));			\
+		_tval &= ~(clr);					\
+		_tval |= (set);						\
+		PHY_WRITE((sc), (reg), _tval);				\
+	} while (0)
+#define	PHY_SET(sc, reg, set)						\
+	PHY_SET_CLEAR(sc, reg, set, 0)
+#define	PHY_CLEAR(sc, reg, clr)						\
+	PHY_SET_CLEAR(sc, reg, 0, clr)
+
+CFATTACH_DECL_NEW(sunxi_hdmiphy, sizeof(struct sunxi_hdmiphy_softc),
+	sunxi_hdmiphy_match, sunxi_hdmiphy_attach, NULL, NULL);
+
+static void *
+sunxi_hdmiphy_acquire(device_t dev, const void *data, size_t len)
+{
+	struct sunxi_hdmiphy_softc * const sc = device_private(dev);
+
+	if (len != 0)
+		return NULL;
+
+	return sc;
+}
+
+static void
+sunxi_hdmiphy_release(device_t dev, void *priv)
+{
+}
+
+static int
+sunxi_hdmiphy_enable(device_t dev, void *priv, bool enable)
+{
+	struct sunxi_hdmiphy_softc * const sc = priv;
+
+	if (enable) {
+		sc->sc_data->init(sc);
+	} else {
+		sc->sc_data->config(sc, 0);
+	}
+
+	return 0;
+}
+
+static const struct fdtbus_phy_controller_func sunxi_hdmiphy_funcs = {
+	.acquire = sunxi_hdmiphy_acquire,
+	.release = sunxi_hdmiphy_release,
+	.enable = sunxi_hdmiphy_enable,
+};
+
+#ifdef SUNXI_HDMIPHY_DEBUG
+static void
+sunxi_hdmiphy_dump(struct sunxi_hdmiphy_softc *sc)
+{
+	device_printf(sc->sc_dev, "ANA_CFG1: %#x\tANA_CFG2: %#x\tANA_CFG3: %#x\n",
+	    PHY_READ(sc, ANA_CFG1), PHY_READ(sc, ANA_CFG2), PHY_READ(sc, ANA_CFG3));
+	device_printf(sc->sc_dev, "PLL_CFG1: %#x\tPLL_CFG2: %#x\tPLL_CFG3: %#x\n",
+	    PHY_READ(sc, PLL_CFG1), PHY_READ(sc, PLL_CFG2), PHY_READ(sc, PLL_CFG3));
+	device_printf(sc->sc_dev, "DBG_CTRL: %#x\tANA_STS: %#x\n",
+	    PHY_READ(sc, DBG_CTRL), PHY_READ(sc, ANA_STS));
+}
+#endif
+
+static void
+sun50i_a64_hdmiphy_init(struct sunxi_hdmiphy_softc *sc)
+{
+	uint32_t val;
+	int retry;
+
+	PHY_WRITE(sc, ANA_CFG1, 0);
+
+	PHY_SET(sc, ANA_CFG1, ANA_CFG1_ENBI);
+	delay(5);
+
+	/* Enable TMDS clock */
+	PHY_SET(sc, ANA_CFG1, ANA_CFG1_TMDSCLK_EN);
+
+	/* Enable common voltage reference bias module */
+	PHY_SET(sc, ANA_CFG1, ANA_CFG1_ENVBS);
+	delay(20);
+
+	/* Enable internal LDO */
+	PHY_SET(sc, ANA_CFG1, ANA_CFG1_LDOEN);
+	delay(5);
+
+	/* Enable common clock module */
+	PHY_SET(sc, ANA_CFG1, ANA_CFG1_CKEN);
+	delay(100);
+
+	/* Enable resistance calibration analog and digital modules */
+	PHY_SET(sc, ANA_CFG1, ANA_CFG1_ENRCAL);
+	delay(200);
+	PHY_SET(sc, ANA_CFG1, ANA_CFG1_ENCALOG);
+
+	/* P2S module enable for TMDS data lane */
+	PHY_SET_CLEAR(sc, ANA_CFG1, __SHIFTIN(0x7, ANA_CFG1_ENP2S), ANA_CFG1_ENP2S);
+
+	/* Wait for resistance calibration to finish */
+	for (retry = 2000; retry > 0; retry--) {
+		if ((PHY_READ(sc, ANA_STS) & ANA_STS_RCALEND2D) != 0)
+			break;
+		delay(1);
+	}
+	if (retry == 0)
+		aprint_error_dev(sc->sc_dev, "HDMI PHY resistance calibration timed out\n");
+
+	/* Enable current and voltage module */
+	PHY_SET_CLEAR(sc, ANA_CFG1, __SHIFTIN(0xf, ANA_CFG1_BIASEN), ANA_CFG1_BIASEN);
+
+	/* P2S module enable for TMDS clock lane */
+	PHY_SET_CLEAR(sc, ANA_CFG1, __SHIFTIN(0xf, ANA_CFG1_ENP2S), ANA_CFG1_ENP2S);
+
+	/* Enable DDC */
+	PHY_SET(sc, ANA_CFG3, ANA_CFG3_REG_SDAEN | ANA_CFG3_REG_SCLEN);
+
+	/* Set parent clock to videopll0 */
+	PHY_CLEAR(sc, PLL_CFG1, PLL_CFG1_CKIN_SEL);
+
+	/* Clear software control of CEC pins */
+	PHY_CLEAR(sc, CEC, CEC_CONTROL_SEL);
+
+	/* Read calibration value for source termination resistors */
+	val = PHY_READ(sc, ANA_STS);
+	sc->sc_rcalib = __SHIFTOUT(val, ANA_STS_RESDO2D);
+}
+
+/*
+ * The following table is based on data from the "HDMI TX PHY S40 Specification".
+ */
+static const struct sun50i_a64_hdmiphy_init {
+	/* PLL Recommended Configuration */
+	uint32_t pll_cfg1;
+	uint32_t pll_cfg2;
+	uint32_t pll_cfg3;
+	/* TMDS Characteristics Recommended Configuration */
+	uint32_t ana_cfg1;
+	uint32_t ana_cfg2;
+	uint32_t ana_cfg3;
+	bool ana_cfg2_rcal_200;
+	u_int b_offset;
+} sun50i_a64_hdmiphy_inittab[] = {
+	/* 27 MHz */
+	[0] = {
+		.pll_cfg1 = 0x3ddc5040,	.pll_cfg2 = 0x8008430a,	.pll_cfg3 = 0x1,
+		.ana_cfg1 = 0x11ffff7f,	.ana_cfg2 = 0x80623000,	.ana_cfg3 = 0x0f80c285,
+		.ana_cfg2_rcal_200 = true,
+	},
+	/* 74.25 MHz */
+	[1] = {
+		.pll_cfg1 = 0x3ddc5040,	.pll_cfg2 = 0x80084343,	.pll_cfg3 = 0x1,
+		.ana_cfg1 = 0x11ffff7f,	.ana_cfg2 = 0x80623000, .ana_cfg3 = 0x0f814385,
+		.ana_cfg2_rcal_200 = true,
+	},
+	/* 148.5 MHz */
+	[2] = {
+		.pll_cfg1 = 0x3ddc5040,	.pll_cfg2 = 0x80084381,	.pll_cfg3 = 0x1,
+		.ana_cfg1 = 0x01ffff7f,	.ana_cfg2 = 0x8063a800,	.ana_cfg3 = 0x0f81c485,
+	},
+	/* 297 MHz */
+	[3] = {
+		.pll_cfg1 = 0x35dc5fc0,	.pll_cfg2 = 0x800863c0,	.pll_cfg3 = 0x1,
+		.ana_cfg1 = 0x01ffff7f,	.ana_cfg2 = 0x8063b000,	.ana_cfg3 = 0x0f8246b5,
+		.b_offset = 2,
+	},
+};
+
+static int
+sun50i_a64_hdmiphy_config(struct sunxi_hdmiphy_softc *sc, u_int rate)
+{
+	const struct sun50i_a64_hdmiphy_init *inittab;
+	u_int init_index, b_out, prediv;
+	uint32_t val, rcalib;
+
+	if (rate == 0) {
+		/* Disable the PHY */
+		PHY_WRITE(sc, ANA_CFG1, ANA_CFG1_LDOEN | ANA_CFG1_ENVBS | ANA_CFG1_ENBI);
+		PHY_WRITE(sc, PLL_CFG1, 0);
+		return 0;
+	}
+
+	init_index = 0;
+	if (rate > 27000000)
+		init_index++;
+	if (rate > 74250000)
+		init_index++;
+	if (rate > 148500000)
+		init_index++;
+	inittab = &sun50i_a64_hdmiphy_inittab[init_index];
+
+	val = PHY_READ(sc, PLL_CFG2);
+	prediv = val & PLL_CFG2_PREDIV;
+
+	/* Config PLL */
+	PHY_WRITE(sc, PLL_CFG1, inittab->pll_cfg1 & ~PLL_CFG1_CKIN_SEL);
+	PHY_WRITE(sc, PLL_CFG2, (inittab->pll_cfg2 & ~PLL_CFG2_PREDIV) | prediv);
+	delay(15000);
+	PHY_WRITE(sc, PLL_CFG3, inittab->pll_cfg3);
+
+	/* Enable PLL */
+	PHY_SET(sc, PLL_CFG1, PLL_CFG1_PLLEN);
+	delay(100000);
+
+	/* Config PLL */
+	val = PHY_READ(sc, ANA_STS);
+	b_out = __SHIFTOUT(val, ANA_STS_B_OUT);
+	b_out = MIN(b_out + inittab->b_offset, __SHIFTOUT_MASK(ANA_STS_B_OUT));
+
+	PHY_SET(sc, PLL_CFG1, PLL_CFG1_REG_OD1 | PLL_CFG1_REG_OD0);
+	PHY_SET(sc, PLL_CFG1, __SHIFTIN(b_out, PLL_CFG1_B_IN));
+	delay(100000);
+
+	/* Config TMDS characteristics */
+	if (inittab->ana_cfg2_rcal_200)
+		rcalib = sc->sc_rcalib >> 2;
+	else
+		rcalib = 0;
+	PHY_WRITE(sc, ANA_CFG1, inittab->ana_cfg1);
+	PHY_WRITE(sc, ANA_CFG2, inittab->ana_cfg2 | rcalib);
+	PHY_WRITE(sc, ANA_CFG3, inittab->ana_cfg3);
+
+#ifdef SUNXI_HDMIPHY_DEBUG
+	sunxi_hdmiphy_dump(sc);
+#endif
+
+	return 0;
+}
+
+static int
+sunxi_hdmiphy_set_rate(struct sunxi_hdmiphy_softc *sc, u_int new_rate)
+{
+	u_int prediv, best_prediv, best_rate;
+
+	if (sc->sc_clk_pll0 == NULL)
+		return 0;
+
+	const u_int parent_rate = clk_get_rate(sc->sc_clk_pll0);
+
+	best_rate = 0;
+
+	for (prediv = 0; prediv <= __SHIFTOUT_MASK(PLL_CFG2_PREDIV); prediv++) {
+		const u_int tmp_rate = parent_rate / (prediv + 1);
+		const int diff = new_rate - tmp_rate;
+		if (diff >= 0 && tmp_rate > best_rate) {
+			best_rate = tmp_rate;
+			best_prediv = prediv;
+		}
+	}
+
+	if (best_rate == 0)
+		return ERANGE;
+
+	PHY_SET_CLEAR(sc, PLL_CFG2, __SHIFTIN(best_prediv, PLL_CFG2_PREDIV), PLL_CFG2_PREDIV);
+
+	return 0;
+}
+
+static int
+sunxi_hdmiphy_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_hdmiphy_attach(device_t parent, device_t self, void *aux)
+{
+	struct sunxi_hdmiphy_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	struct clk *clk_bus, *clk_mod, *clk_pll0;
+	struct fdtbus_reset *rst;
+	bus_addr_t addr;
+	bus_size_t size;
+
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+		return;
+	}
+
+	rst = fdtbus_reset_get(phandle, "phy");
+	if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
+		aprint_error(": couldn't de-assert reset\n");
+		return;
+	}
+
+	clk_bus = fdtbus_clock_get(phandle, "bus");
+	if (clk_bus == NULL || clk_enable(clk_bus) != 0) {
+		aprint_error(": couldn't enable bus clock\n");
+		return;
+	}
+
+	clk_mod = fdtbus_clock_get(phandle, "mod");
+	if (clk_mod == NULL || clk_enable(clk_mod) != 0) {
+		aprint_error(": couldn't enable mod clock\n");
+		return;
+	}
+
+	clk_pll0 = fdtbus_clock_get(phandle, "pll-0");
+	if (clk_pll0 == NULL || clk_enable(clk_pll0) != 0) {
+		aprint_error(": couldn't enable pll-0 clock\n");
+		return;
+	}
+
+	sc->sc_dev = self;
+	sc->sc_bst = faa->faa_bst;
+	sc->sc_data = (void *)of_search_compatible(phandle, compat_data)->data;
+	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_pll0 = clk_pll0;
+
+	aprint_naive("\n");
+	aprint_normal(": HDMI PHY\n");
+
+	fdtbus_register_phy_controller(self, phandle, &sunxi_hdmiphy_funcs);
+
+	PHY_WRITE(sc, READ_EN, READ_EN_MAGIC);
+	PHY_WRITE(sc, UNSCRAMBLE, UNSCRAMBLE_MAGIC);
+
+#ifdef SUNXI_HDMIPHY_DEBUG
+	sunxi_hdmiphy_dump(sc);
+#endif
+}
+
+int
+sunxi_hdmiphy_config(struct fdtbus_phy *phy, struct drm_display_mode *mode)
+{
+	device_t dev = fdtbus_phy_device(phy);
+	struct sunxi_hdmiphy_softc * const sc = device_private(dev);
+	u_int pol;
+	int error;
+
+	pol = 0;
+	if ((mode->flags & DRM_MODE_FLAG_NHSYNC) != 0)
+		pol |= __SHIFTIN(DBG_CTRL_POL_NHSYNC, DBG_CTRL_POL);
+	if ((mode->flags & DRM_MODE_FLAG_NVSYNC) != 0)
+		pol |= __SHIFTIN(DBG_CTRL_POL_NVSYNC, DBG_CTRL_POL);
+
+	PHY_SET_CLEAR(sc, DBG_CTRL, pol, DBG_CTRL_POL);
+
+	error = sunxi_hdmiphy_set_rate(sc, mode->crtc_clock * 1000);
+	if (error != 0) {
+		aprint_error_dev(dev, "failed to set HDMI PHY clock: %d\n", error);
+		return error;
+	}
+
+	return sc->sc_data->config(sc, mode->crtc_clock * 1000);
+}
+
+bool
+sunxi_hdmiphy_detect(struct fdtbus_phy *phy, bool force)
+{
+	device_t dev = fdtbus_phy_device(phy);
+	struct sunxi_hdmiphy_softc * const sc = device_private(dev);
+	uint32_t val;
+
+	val = PHY_READ(sc, ANA_STS);
+
+	return ISSET(val, ANA_STS_HPDO);
+}
Index: src/sys/arch/arm/sunxi/sunxi_hdmiphy.h
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_hdmiphy.h:1.1
--- /dev/null	Wed Jan 30 01:24:00 2019
+++ src/sys/arch/arm/sunxi/sunxi_hdmiphy.h	Wed Jan 30 01:24:00 2019
@@ -0,0 +1,38 @@
+/* $NetBSD: sunxi_hdmiphy.h,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2019 Jared McNeill <jmcne...@invisible.ca>
+ * 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.
+ */
+
+#ifndef _ARM_SUNXI_HDMIPHY_H
+#define _ARM_SUNXI_HDMIPHY_H
+
+#include <drm/drmP.h>
+#include <drm/drm_modes.h>
+
+int	sunxi_hdmiphy_config(struct fdtbus_phy *, struct drm_display_mode *);
+bool	sunxi_hdmiphy_detect(struct fdtbus_phy *, bool);
+
+#endif /* !_ARM_SUNXI_HDMIPHY_H */
Index: src/sys/arch/arm/sunxi/sunxi_lcdc.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_lcdc.c:1.1
--- /dev/null	Wed Jan 30 01:24:00 2019
+++ src/sys/arch/arm/sunxi/sunxi_lcdc.c	Wed Jan 30 01:24:00 2019
@@ -0,0 +1,317 @@
+/* $NetBSD: sunxi_lcdc.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca>
+ * 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_lcdc.c,v 1.1 2019/01/30 01:24:00 jmcneill 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/conf.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+
+#include <dev/fdt/fdtvar.h>
+#include <dev/fdt/fdt_port.h>
+
+#define	TCON_GCTL_REG		0x000
+#define	 TCON_GCTL_TCON_EN			__BIT(31)
+#define	 TCON_GCTL_GAMMA_EN			__BIT(30)
+#define	 TCON_GCTL_IO_MAP_SEL			__BIT(0)
+
+#define	TCON_GINT0_REG		0x004
+#define	TCON_GINT1_REG		0x008
+#define	 TCON_GINT1_TCON1_LINE_INT_NUM		__BITS(11,0)
+
+#define	TCON1_CTL_REG		0x090
+#define	 TCON1_CTL_TCON1_EN			__BIT(31)
+#define	 TCON1_CTL_START_DELAY			__BITS(8,4)
+#define	 TCON1_CTL_TCON1_SRC_SEL		__BIT(1)
+#define	TCON1_BASIC0_REG	0x094
+#define	TCON1_BASIC1_REG	0x098
+#define	TCON1_BASIC2_REG	0x09c
+#define	TCON1_BASIC3_REG	0x0a0
+#define	TCON1_BASIC4_REG	0x0a4
+#define	TCON1_BASIC5_REG	0x0a8
+
+#define	TCON1_IO_POL_REG	0x0f0
+#define	 TCON1_IO_POL_IO3_INV			__BIT(27)
+#define	 TCON1_IO_POL_IO2_INV			__BIT(26)
+#define	 TCON1_IO_POL_IO1_INV			__BIT(25)
+#define	 TCON1_IO_POL_IO0_INV			__BIT(24)
+#define	 TCON1_IO_POL_DATA_INV			__BITS(23,0)
+#define	TCON1_IO_TRI_REG	0x0f4
+
+enum {
+	MIXER_PORT_INPUT = 0,
+	MIXER_PORT_OUTPUT = 1,
+};
+
+static const char * const compatible[] = {
+	"allwinner,sun50i-a64-tcon-lcd",
+	"allwinner,sun50i-a64-tcon-tv",
+	NULL
+};
+
+struct sunxi_lcdc_softc;
+
+struct sunxi_lcdc_encoder {
+	struct drm_encoder	base;
+	struct sunxi_lcdc_softc *sc;
+	struct drm_display_mode	curmode;
+};
+
+struct sunxi_lcdc_softc {
+	device_t		sc_dev;
+	bus_space_tag_t		sc_bst;
+	bus_space_handle_t	sc_bsh;
+	int			sc_phandle;
+
+	struct clk		*sc_clk_ch[2];
+
+	struct sunxi_lcdc_encoder sc_encoder;
+	struct drm_connector	sc_connector;
+
+	struct fdt_device_ports	sc_ports;
+};
+
+#define	to_sunxi_lcdc_encoder(x)	container_of(x, struct sunxi_lcdc_encoder, base)
+
+#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 void
+sunxi_lcdc_destroy(struct drm_encoder *encoder)
+{
+}
+
+static const struct drm_encoder_funcs sunxi_lcdc_funcs = {
+	.destroy = sunxi_lcdc_destroy,
+};
+
+static void
+sunxi_lcdc_tcon1_dpms(struct drm_encoder *encoder, int mode)
+{
+}
+
+static bool
+sunxi_lcdc_tcon1_mode_fixup(struct drm_encoder *encoder,
+    const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+static void
+sunxi_lcdc_tcon1_mode_set(struct drm_encoder *encoder,
+    struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
+{
+	struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder);
+
+	lcdc_encoder->curmode = *adjusted_mode;
+}
+
+static void
+sunxi_lcdc_tcon1_prepare(struct drm_encoder *encoder)
+{
+	struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder);
+	struct sunxi_lcdc_softc * const sc = lcdc_encoder->sc;
+	uint32_t val;
+
+	val = TCON_READ(sc, TCON_GCTL_REG);
+	val |= TCON_GCTL_TCON_EN;
+	TCON_WRITE(sc, TCON_GCTL_REG, val);
+
+	TCON_WRITE(sc, TCON1_IO_POL_REG, 0);
+	TCON_WRITE(sc, TCON1_IO_TRI_REG, 0xffffffff);
+}
+
+static void
+sunxi_lcdc_tcon1_commit(struct drm_encoder *encoder)
+{
+	struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder);
+	struct sunxi_lcdc_softc * const sc = lcdc_encoder->sc;
+	struct drm_display_mode *mode = &lcdc_encoder->curmode;
+	uint32_t val;
+	int error;
+
+	const u_int interlace_p = (mode->flags & DRM_MODE_FLAG_INTERLACE) != 0;
+	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 = TCON1_CTL_TCON1_EN |
+	      __SHIFTIN(start_delay, TCON1_CTL_START_DELAY);
+	TCON_WRITE(sc, TCON1_CTL_REG, val);
+
+	TCON_WRITE(sc, TCON1_BASIC0_REG, ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1));
+	TCON_WRITE(sc, TCON1_BASIC1_REG, ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1));
+	TCON_WRITE(sc, TCON1_BASIC2_REG, ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1));
+	TCON_WRITE(sc, TCON1_BASIC3_REG, ((mode->htotal - 1) << 16) | (hbp - 1));
+	TCON_WRITE(sc, TCON1_BASIC4_REG, ((mode->vtotal * 2) << 16) | (vbp - 1));
+	TCON_WRITE(sc, TCON1_BASIC5_REG, ((hspw - 1) << 16) | (vspw - 1));
+
+	TCON_WRITE(sc, TCON_GINT1_REG,
+	    __SHIFTIN(start_delay + 2, TCON_GINT1_TCON1_LINE_INT_NUM));
+
+	if (sc->sc_clk_ch[1] != NULL) {
+		error = clk_set_rate(sc->sc_clk_ch[1], mode->crtc_clock * 1000);
+		if (error != 0) {
+			device_printf(sc->sc_dev, "failed to set CH1 PLL rate to %u Hz: %d\n",
+			    mode->crtc_clock * 1000, error);
+			return;
+		}
+		error = clk_enable(sc->sc_clk_ch[1]);
+		if (error != 0) {
+			device_printf(sc->sc_dev, "failed to enable CH1 PLL: %d\n", error);
+			return;
+		}
+	} else {
+		device_printf(sc->sc_dev, "no CH1 PLL configured\n");
+	}
+}
+
+static const struct drm_encoder_helper_funcs sunxi_lcdc_tcon1_helper_funcs = {
+	.dpms = sunxi_lcdc_tcon1_dpms,
+	.mode_fixup = sunxi_lcdc_tcon1_mode_fixup,
+	.prepare = sunxi_lcdc_tcon1_prepare,
+	.commit = sunxi_lcdc_tcon1_commit,
+	.mode_set = sunxi_lcdc_tcon1_mode_set,
+};
+
+static int
+sunxi_lcdc_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
+{
+	struct sunxi_lcdc_softc * const sc = device_private(dev);
+	struct fdt_endpoint *in_ep = fdt_endpoint_remote(ep);
+	struct fdt_endpoint *out_ep;
+	struct drm_crtc *crtc;
+	int error;
+
+	if (!activate)
+		return EINVAL;
+
+	if (fdt_endpoint_port_index(ep) != MIXER_PORT_INPUT)
+		return EINVAL;
+
+	if (fdt_endpoint_type(in_ep) != EP_DRM_CRTC)
+		return EINVAL;
+
+	crtc = fdt_endpoint_get_data(in_ep);
+
+	sc->sc_encoder.sc = sc;
+
+	out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, MIXER_PORT_OUTPUT, 1);
+	if (out_ep != NULL) {
+		drm_encoder_init(crtc->dev, &sc->sc_encoder.base, &sunxi_lcdc_funcs,
+		    DRM_MODE_ENCODER_TMDS);
+		drm_encoder_helper_add(&sc->sc_encoder.base, &sunxi_lcdc_tcon1_helper_funcs);
+
+		error = fdt_endpoint_activate(out_ep, activate);
+		if (error != 0)
+			return error;
+		sc->sc_encoder.base.possible_crtcs = 1 << drm_crtc_index(crtc);
+	}
+
+	return 0;
+}
+
+static void *
+sunxi_lcdc_ep_get_data(device_t dev, struct fdt_endpoint *ep)
+{
+	struct sunxi_lcdc_softc * const sc = device_private(dev);
+
+	return &sc->sc_encoder;
+}
+
+static int
+sunxi_lcdc_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct fdt_attach_args * const faa = aux;
+
+	return of_match_compatible(faa->faa_phandle, compatible);
+}
+
+static void
+sunxi_lcdc_attach(device_t parent, device_t self, void *aux)
+{
+	struct sunxi_lcdc_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	struct fdtbus_reset *rst;
+	struct clk *clk;
+	bus_addr_t addr;
+	bus_size_t size;
+
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+		return;
+	}
+
+	rst = fdtbus_reset_get(phandle, "lcd");
+	if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
+		aprint_error(": couldn't de-assert reset\n");
+		return;
+	}
+
+	clk = fdtbus_clock_get(phandle, "ahb");
+	if (clk == NULL || clk_enable(clk) != 0) {
+		aprint_error(": couldn't enable bus clock\n");
+		return;
+	}
+
+	sc->sc_dev = self;
+	sc->sc_bst = faa->faa_bst;
+	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
+		aprint_error(": couldn't map registers\n");
+		return;
+	}
+	sc->sc_phandle = faa->faa_phandle;
+	sc->sc_clk_ch[0] = fdtbus_clock_get(phandle, "tcon-ch0");
+	sc->sc_clk_ch[1] = fdtbus_clock_get(phandle, "tcon-ch1");
+
+	aprint_naive("\n");
+	aprint_normal(": Timing Controller\n");
+
+	sc->sc_ports.dp_ep_activate = sunxi_lcdc_ep_activate;
+	sc->sc_ports.dp_ep_get_data = sunxi_lcdc_ep_get_data;
+	fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_ENCODER);
+}
+
+CFATTACH_DECL_NEW(sunxi_lcdc, sizeof(struct sunxi_lcdc_softc),
+	sunxi_lcdc_match, sunxi_lcdc_attach, NULL, NULL);
Index: src/sys/arch/arm/sunxi/sunxi_mixer.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_mixer.c:1.1
--- /dev/null	Wed Jan 30 01:24:00 2019
+++ src/sys/arch/arm/sunxi/sunxi_mixer.c	Wed Jan 30 01:24:00 2019
@@ -0,0 +1,387 @@
+/* $NetBSD: sunxi_mixer.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca>
+ * 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_mixer.c,v 1.1 2019/01/30 01:24:00 jmcneill 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/conf.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include <dev/fdt/fdtvar.h>
+#include <dev/fdt/fdt_port.h>
+
+#include <arm/sunxi/sunxi_drm.h>
+
+#define	SUNXI_MIXER_FREQ	432000000
+
+#define	GLB_BASE		0x00000
+#define	BLD_BASE		0x01000
+#define	OVL_BASE(n)		(0x02000 + (n) * 0x1000)
+#define	OVL_UI_BASE		OVL_BASE(1)
+
+/* GLB registers */
+#define	GLB_CTL			0x000
+#define	 GLB_CTL_EN				__BIT(0)
+#define	GLB_STS			0x004	
+#define	GLB_DBUFFER		0x008
+#define	 GLB_DBUFFER_DOUBLE_BUFFER_RDY		__BIT(0)
+#define	GLB_SIZE		0x00c
+
+/* BLD registers */
+#define	BLD_FILL_COLOR_CTL	0x000
+#define	 BLD_FILL_COLOR_CTL_P0_EN		__BIT(8)
+#define	BLD_CH_ISIZE(n)		(0x008 + (n) * 0x10)
+#define	BLD_CH_OFFSET(n)	(0x00c + (n) * 0x10)
+#define	BLD_CH_RTCTL		0x080
+#define	 BLD_CH_RTCTL_P0			__BITS(3,0)
+#define	BLD_SIZE		0x08c
+#define	BLD_CTL(n)		(0x090 + (n) * 0x04)
+
+/* OVL_UI registers */
+#define	OVL_UI_ATTR_CTL(n)	(0x000 + (n) * 0x20)
+#define	 OVL_UI_ATTR_CTL_LAY_FBFMT		__BITS(12,8)
+#define	  OVL_UI_ATTR_CTL_LAY_FBFMT_XRGB_8888	0x04
+#define	 OVL_UI_ATTR_CTL_LAY_EN			__BIT(0)
+#define	OVL_UI_MBSIZE(n)	(0x004 + (n) * 0x20)
+#define	OVL_UI_COOR(n)		(0x008 + (n) * 0x20)
+#define	OVL_UI_PITCH(n)		(0x00c + (n) * 0x20)
+#define	OVL_UI_TOP_LADD(n)	(0x010 + (n) * 0x20)
+#define	OVL_UI_TOP_HADD		0x080
+#define	 OVL_UI_TOP_HADD_LAYER0	__BITS(7,0)
+#define	OVL_UI_SIZE		0x088
+
+enum {
+	MIXER_PORT_OUTPUT = 1,
+};
+
+static const char * const compatible[] = {
+	"allwinner,sun50i-a64-de2-mixer-0",
+	"allwinner,sun50i-a64-de2-mixer-1",
+	NULL
+};
+
+struct sunxi_mixer_softc;
+
+struct sunxi_mixer_crtc {
+	struct drm_crtc		base;
+	struct sunxi_mixer_softc *sc;
+};
+
+struct sunxi_mixer_softc {
+	device_t		sc_dev;
+	bus_space_tag_t		sc_bst;
+	bus_space_handle_t	sc_bsh;
+	int			sc_phandle;
+
+	struct sunxi_mixer_crtc	sc_crtc;
+
+	struct fdt_device_ports	sc_ports;
+};
+
+#define	GLB_READ(sc, reg)				\
+	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, GLB_BASE + (reg))
+#define	GLB_WRITE(sc, reg, val)				\
+	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, GLB_BASE + (reg), (val))
+
+#define	BLD_READ(sc, reg)				\
+	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, BLD_BASE + (reg))
+#define	BLD_WRITE(sc, reg, val)				\
+	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, BLD_BASE + (reg), (val))
+
+#define	OVL_UI_READ(sc, reg)				\
+	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, OVL_UI_BASE + (reg))
+#define	OVL_UI_WRITE(sc, reg, val)			\
+	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, OVL_UI_BASE + (reg), (val))
+
+#define	to_sunxi_mixer_crtc(x)	container_of(x, struct sunxi_mixer_crtc, base)
+
+static void
+sunxi_mixer_destroy(struct drm_crtc *crtc)
+{
+	drm_crtc_cleanup(crtc);
+}
+
+static const struct drm_crtc_funcs sunxi_mixer_crtc_funcs = {
+	.set_config = drm_crtc_helper_set_config,
+	.destroy = sunxi_mixer_destroy,
+};
+
+static void
+sunxi_mixer_dpms(struct drm_crtc *crtc, int mode)
+{
+}
+
+static bool
+sunxi_mixer_mode_fixup(struct drm_crtc *crtc,
+    const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+static int
+sunxi_mixer_mode_do_set_base(struct drm_crtc *crtc, struct drm_framebuffer *fb,
+    int x, int y, int atomic)
+{
+	struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc);
+	struct sunxi_mixer_softc * const sc = mixer_crtc->sc;
+	struct sunxi_drm_framebuffer *sfb = atomic?
+	    to_sunxi_drm_framebuffer(fb) :
+	    to_sunxi_drm_framebuffer(crtc->primary->fb);
+
+	uint64_t paddr = (uint64_t)sfb->obj->dmamap->dm_segs[0].ds_addr;
+
+	uint32_t haddr = (paddr >> 32) & OVL_UI_TOP_HADD_LAYER0;
+	uint32_t laddr = paddr & 0xffffffff;
+
+	/* Framebuffer start address */
+	OVL_UI_WRITE(sc, OVL_UI_TOP_HADD, haddr);
+	OVL_UI_WRITE(sc, OVL_UI_TOP_LADD(0), laddr);
+
+	return 0;
+}
+
+static int
+sunxi_mixer_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode,
+    struct drm_display_mode *adjusted_mode, int x, int y,
+    struct drm_framebuffer *old_fb)
+{
+	struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc);
+	struct sunxi_mixer_softc * const sc = mixer_crtc->sc;
+	uint32_t val;
+
+	const uint32_t size = ((adjusted_mode->vdisplay - 1) << 16) |
+			      (adjusted_mode->hdisplay - 1);
+	const uint32_t offset = (y << 16) | x;
+
+	/* Set global size */
+	GLB_WRITE(sc, GLB_SIZE, size);
+
+	/* Enable pipe 0 */
+	BLD_WRITE(sc, BLD_FILL_COLOR_CTL, BLD_FILL_COLOR_CTL_P0_EN);
+
+	/* Set blender 0 input size */
+	BLD_WRITE(sc, BLD_CH_ISIZE(0), size);
+	/* Set blender 0 offset */
+	BLD_WRITE(sc, BLD_CH_OFFSET(0), offset);
+	/* Route channel 1 to pipe 0 */
+	BLD_WRITE(sc, BLD_CH_RTCTL, __SHIFTIN(1, BLD_CH_RTCTL_P0));
+	/* Set blender output size */
+	BLD_WRITE(sc, BLD_SIZE, size);
+
+	/* Enable UI overlay in XRGB8888 mode */
+	val = OVL_UI_ATTR_CTL_LAY_EN |
+	      __SHIFTIN(OVL_UI_ATTR_CTL_LAY_FBFMT_XRGB_8888, OVL_UI_ATTR_CTL_LAY_FBFMT);
+	OVL_UI_WRITE(sc, OVL_UI_ATTR_CTL(0), val);
+	/* Set UI overlay layer size */
+	OVL_UI_WRITE(sc, OVL_UI_MBSIZE(0), size);
+	/* Set UI overlay offset */
+	OVL_UI_WRITE(sc, OVL_UI_COOR(0), offset);
+	/* Set UI overlay line size */
+	OVL_UI_WRITE(sc, OVL_UI_PITCH(0), adjusted_mode->hdisplay * 4);
+	/* Set UI overlay window size */
+	OVL_UI_WRITE(sc, OVL_UI_SIZE, size);
+
+	sunxi_mixer_mode_do_set_base(crtc, old_fb, x, y, 0);
+
+	return 0;
+}
+
+static int
+sunxi_mixer_mode_set_base(struct drm_crtc *crtc, int x, int y,
+    struct drm_framebuffer *old_fb)
+{
+	struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc);
+	struct sunxi_mixer_softc * const sc = mixer_crtc->sc;
+
+	sunxi_mixer_mode_do_set_base(crtc, old_fb, x, y, 0);
+
+	/* Commit settings */
+	GLB_WRITE(sc, GLB_DBUFFER, GLB_DBUFFER_DOUBLE_BUFFER_RDY);
+
+	return 0;
+}
+
+static int
+sunxi_mixer_mode_set_base_atomic(struct drm_crtc *crtc, struct drm_framebuffer *fb,
+    int x, int y, enum mode_set_atomic state)
+{
+	struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc);
+	struct sunxi_mixer_softc * const sc = mixer_crtc->sc;
+
+	sunxi_mixer_mode_do_set_base(crtc, fb, x, y, 1);
+
+	/* Commit settings */
+	GLB_WRITE(sc, GLB_DBUFFER, GLB_DBUFFER_DOUBLE_BUFFER_RDY);
+
+	return 0;
+}
+
+static void
+sunxi_mixer_disable(struct drm_crtc *crtc)
+{
+}
+
+static void
+sunxi_mixer_prepare(struct drm_crtc *crtc)
+{
+	struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc);
+	struct sunxi_mixer_softc * const sc = mixer_crtc->sc;
+
+	/* RT enable */
+	GLB_WRITE(sc, GLB_CTL, GLB_CTL_EN);
+}
+
+static void
+sunxi_mixer_commit(struct drm_crtc *crtc)
+{
+	struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc);
+	struct sunxi_mixer_softc * const sc = mixer_crtc->sc;
+
+	/* Commit settings */
+	GLB_WRITE(sc, GLB_DBUFFER, GLB_DBUFFER_DOUBLE_BUFFER_RDY);
+}
+
+static const struct drm_crtc_helper_funcs sunxi_mixer_crtc_helper_funcs = {
+	.dpms = sunxi_mixer_dpms,
+	.mode_fixup = sunxi_mixer_mode_fixup,
+	.mode_set = sunxi_mixer_mode_set,
+	.mode_set_base = sunxi_mixer_mode_set_base,
+	.mode_set_base_atomic = sunxi_mixer_mode_set_base_atomic,
+	.disable = sunxi_mixer_disable,
+	.prepare = sunxi_mixer_prepare,
+	.commit = sunxi_mixer_commit,
+};
+
+static int
+sunxi_mixer_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
+{
+	struct sunxi_mixer_softc * const sc = device_private(dev);
+	struct drm_device *ddev;
+
+	if (!activate)
+		return EINVAL;
+
+	ddev = sunxi_drm_endpoint_device(ep);
+	if (ddev == NULL) {
+		DRM_ERROR("couldn't find DRM device\n");
+		return ENXIO;
+	}
+
+	sc->sc_crtc.sc = sc;
+
+	drm_crtc_init(ddev, &sc->sc_crtc.base, &sunxi_mixer_crtc_funcs);
+	drm_crtc_helper_add(&sc->sc_crtc.base, &sunxi_mixer_crtc_helper_funcs);
+
+	return fdt_endpoint_activate(ep, activate);
+}
+
+static void *
+sunxi_mixer_ep_get_data(device_t dev, struct fdt_endpoint *ep)
+{
+	struct sunxi_mixer_softc * const sc = device_private(dev);
+
+	return &sc->sc_crtc;
+}
+
+static int
+sunxi_mixer_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct fdt_attach_args * const faa = aux;
+
+	return of_match_compatible(faa->faa_phandle, compatible);
+}
+
+static void
+sunxi_mixer_attach(device_t parent, device_t self, void *aux)
+{
+	struct sunxi_mixer_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	struct fdt_endpoint *out_ep;
+	const int phandle = faa->faa_phandle;
+	struct clk *clk_bus, *clk_mod;
+	struct fdtbus_reset *rst;
+	bus_addr_t addr;
+	bus_size_t size;
+
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+		return;
+	}
+
+	rst = fdtbus_reset_get_index(phandle, 0);
+	if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
+		aprint_error(": couldn't de-assert reset\n");
+		return;
+	}
+
+	clk_bus = fdtbus_clock_get(phandle, "bus");
+	if (clk_bus == NULL || clk_enable(clk_bus) != 0) {
+		aprint_error(": couldn't enable bus clock\n");
+		return;
+	}
+
+	clk_mod = fdtbus_clock_get(phandle, "mod");
+	if (clk_mod == NULL ||
+	    clk_set_rate(clk_mod, SUNXI_MIXER_FREQ) != 0 ||
+	    clk_enable(clk_mod) != 0) {
+		aprint_error(": couldn't enable mod clock\n");
+		return;
+	}
+
+	sc->sc_dev = self;
+	sc->sc_bst = faa->faa_bst;
+	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
+		aprint_error(": couldn't map registers\n");
+		return;
+	}
+	sc->sc_phandle = faa->faa_phandle;
+
+	aprint_naive("\n");
+	aprint_normal(": Display Engine Mixer\n");
+
+	sc->sc_ports.dp_ep_activate = sunxi_mixer_ep_activate;
+	sc->sc_ports.dp_ep_get_data = sunxi_mixer_ep_get_data;
+	fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_CRTC);
+
+	out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, MIXER_PORT_OUTPUT, 0);
+	if (out_ep != NULL)
+		sunxi_drm_register_endpoint(phandle, out_ep);
+}
+
+CFATTACH_DECL_NEW(sunxi_mixer, sizeof(struct sunxi_mixer_softc),
+	sunxi_mixer_match, sunxi_mixer_attach, NULL, NULL);

Index: src/sys/dev/fdt/hdmi_connector.c
diff -u /dev/null src/sys/dev/fdt/hdmi_connector.c:1.1
--- /dev/null	Wed Jan 30 01:24:00 2019
+++ src/sys/dev/fdt/hdmi_connector.c	Wed Jan 30 01:24:00 2019
@@ -0,0 +1,237 @@
+/* $NetBSD: hdmi_connector.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2019 Jared McNeill <jmcne...@invisible.ca>
+ * 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: hdmi_connector.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/systm.h>
+#include <sys/gpio.h>
+
+#include <dev/fdt/fdtvar.h>
+#include <dev/fdt/fdt_port.h>
+
+#include <dev/i2c/ddcvar.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+
+static const char * const compatible[] = {
+	"hdmi-connector",
+	NULL
+};
+
+struct dispcon_hdmi_connector {
+	struct drm_connector		base;
+	struct fdtbus_gpio_pin		*hpd;
+	i2c_tag_t			ddc;
+
+	int				type;	/* DRM_MODE_CONNECTOR_* */
+};
+
+struct dispcon_hdmi_softc {
+	struct fdt_device_ports		sc_ports;
+	struct dispcon_hdmi_connector	sc_connector;
+};
+
+#define	to_dispcon_hdmi_connector(x)	container_of(x, struct dispcon_hdmi_connector, base)
+
+static enum drm_connector_status
+dispcon_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct dispcon_hdmi_connector *hdmi_connector = to_dispcon_hdmi_connector(connector);
+	bool con;
+
+	if (hdmi_connector->hpd == NULL) {
+		/*
+		 * No hotplug detect pin available. Assume that we are connected.
+		 */
+		return connector_status_connected;
+	}
+
+	/*
+	 * Read connect status from hotplug detect pin.
+	 */
+	con = fdtbus_gpio_read(hdmi_connector->hpd);
+	if (con) {
+		return connector_status_connected;
+	} else {
+		return connector_status_disconnected;
+	}
+}
+
+static void
+dispcon_hdmi_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs dispcon_hdmi_connector_funcs = {
+	.dpms = drm_helper_connector_dpms,
+	.detect = dispcon_hdmi_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = dispcon_hdmi_connector_destroy,
+};
+
+static int
+dispcon_hdmi_connector_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode)
+{
+	return MODE_OK;
+}
+
+static int
+dispcon_hdmi_connector_get_modes(struct drm_connector *connector)
+{
+	struct dispcon_hdmi_connector *hdmi_connector = to_dispcon_hdmi_connector(connector);
+	char edid[EDID_LENGTH * 4];
+	struct edid *pedid = NULL;
+	int error, block;
+
+	if (hdmi_connector->ddc != NULL) {
+		memset(edid, 0, sizeof(edid));
+		for (block = 0; block < 4; block++) {
+			error = ddc_read_edid_block(hdmi_connector->ddc,
+			    &edid[block * EDID_LENGTH], EDID_LENGTH, block);
+			if (error)
+				break;
+			if (block == 0) {
+				pedid = (struct edid *)edid;
+				if (edid[0x7e] == 0)
+					break;
+			}
+		}
+	}
+
+	drm_mode_connector_update_edid_property(connector, pedid);
+	if (pedid == NULL)
+		return 0;
+
+	error = drm_add_edid_modes(connector, pedid);
+	drm_edid_to_eld(connector, pedid);
+
+	return error;
+}
+
+static struct drm_encoder *
+dispcon_hdmi_connector_best_encoder(struct drm_connector *connector)
+{
+	int enc_id = connector->encoder_ids[0];
+	struct drm_mode_object *obj;
+	struct drm_encoder *encoder = NULL;
+
+	if (enc_id) {
+		obj = drm_mode_object_find(connector->dev, enc_id,
+		    DRM_MODE_OBJECT_ENCODER);
+		if (obj == NULL)
+			return NULL;
+		encoder = obj_to_encoder(obj);
+	}
+
+	return encoder;
+}
+
+static const struct drm_connector_helper_funcs dispcon_hdmi_connector_helper_funcs = {
+	.mode_valid = dispcon_hdmi_connector_mode_valid,
+	.get_modes = dispcon_hdmi_connector_get_modes,
+	.best_encoder = dispcon_hdmi_connector_best_encoder,
+};
+
+static int
+dispcon_hdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
+{
+	struct drm_connector *connector = fdt_endpoint_get_data(ep);
+	struct dispcon_hdmi_connector *hdmi_connector = to_dispcon_hdmi_connector(connector);
+	struct fdt_endpoint *rep = fdt_endpoint_remote(ep);
+	struct drm_encoder *encoder;
+
+	if (fdt_endpoint_port_index(ep) != 0)
+		return EINVAL;
+
+	if (fdt_endpoint_type(rep) != EP_DRM_ENCODER)
+		return EINVAL;
+
+	if (activate) {
+		encoder = fdt_endpoint_get_data(rep);
+
+		connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
+		connector->interlace_allowed = 0;
+		connector->doublescan_allowed = 0;
+
+		drm_connector_init(encoder->dev, connector, &dispcon_hdmi_connector_funcs,
+		    hdmi_connector->type);
+		drm_connector_helper_add(connector, &dispcon_hdmi_connector_helper_funcs);
+		drm_connector_register(connector);
+		drm_mode_connector_attach_encoder(connector, encoder);
+	}
+
+	return 0;
+}
+
+static void *
+dispcon_hdmi_ep_get_data(device_t dev, struct fdt_endpoint *ep)
+{
+	struct dispcon_hdmi_softc * const sc = device_private(dev);
+
+	return &sc->sc_connector.base;
+}
+
+static int
+dispcon_hdmi_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct fdt_attach_args * const faa = aux;
+
+	return of_match_compatible(faa->faa_phandle, compatible);
+}
+
+static void
+dispcon_hdmi_attach(device_t parent, device_t self, void *aux)
+{
+	struct dispcon_hdmi_softc * const sc = device_private(self);
+	struct dispcon_hdmi_connector * const hdmi_connector = &sc->sc_connector;
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+
+	aprint_naive("\n");
+	aprint_normal(": HDMI connector\n");
+
+	hdmi_connector->type = DRM_MODE_CONNECTOR_HDMIA;
+	hdmi_connector->hpd = fdtbus_gpio_acquire(phandle, "hpd-gpios", GPIO_PIN_INPUT);
+	hdmi_connector->ddc = fdtbus_i2c_acquire(phandle, "ddc-i2c-bus");
+
+	sc->sc_ports.dp_ep_activate = dispcon_hdmi_ep_activate;
+	sc->sc_ports.dp_ep_get_data = dispcon_hdmi_ep_get_data;
+	fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_CONNECTOR);
+}
+
+CFATTACH_DECL_NEW(dispcon_hdmi, sizeof(struct dispcon_hdmi_softc),
+	dispcon_hdmi_match, dispcon_hdmi_attach, NULL, NULL);

Reply via email to