Module Name:    src
Committed By:   martin
Date:           Sat Nov 16 16:48:26 UTC 2019

Modified Files:
        src/sys/arch/arm/dts [netbsd-9]: rk3399-rockpro64.dts
        src/sys/arch/arm/rockchip [netbsd-9]: files.rockchip rk3399_cru.c
            rk_cru.h rk_cru_composite.c rk_i2c.c
        src/sys/arch/evbarm/conf [netbsd-9]: GENERIC64
        src/sys/conf [netbsd-9]: files
        src/sys/dev/fdt [netbsd-9]: ausoc.c fdt_clock.c fdtvar.h
        src/sys/dev/ic [netbsd-9]: dw_hdmi.c dw_hdmi.h
Added Files:
        src/sys/arch/arm/rockchip [netbsd-9]: rk_drm.c rk_drm.h rk_dwhdmi.c
            rk_fb.c rk_i2s.c rk_vop.c
        src/sys/dev/ic [netbsd-9]: dw_hdmi_phy.c

Log Message:
Pull up following revision(s) (requested by jmcneill in ticket #427):

        sys/dev/ic/dw_hdmi_phy.c: revision 1.2
        sys/dev/ic/dw_hdmi.c: revision 1.4
        sys/dev/fdt/ausoc.c: revision 1.5
        sys/dev/ic/dw_hdmi.h: revision 1.2
        sys/dev/ic/dw_hdmi.h: revision 1.3
        sys/dev/ic/dw_hdmi.h: revision 1.4
        sys/conf/files: revision 1.1242
        sys/dev/fdt/fdtvar.h: revision 1.57
        sys/arch/arm/rockchip/rk3399_cru.c: revision 1.11
        sys/arch/arm/rockchip/rk3399_cru.c: revision 1.12
        sys/arch/arm/rockchip/rk3399_cru.c: revision 1.13
        sys/arch/evbarm/conf/GENERIC64: revision 1.110
        sys/arch/arm/rockchip/rk_drm.c: revision 1.1
        sys/arch/arm/rockchip/rk_drm.c: revision 1.2
        sys/arch/evbarm/conf/GENERIC64: revision 1.112
        sys/arch/arm/rockchip/rk_dwhdmi.c: revision 1.1
        sys/dev/fdt/fdt_clock.c: revision 1.10
        sys/arch/evbarm/conf/GENERIC64: revision 1.113
        sys/arch/arm/rockchip/rk_dwhdmi.c: revision 1.2
        sys/arch/arm/rockchip/rk_drm.h: revision 1.1
        sys/arch/arm/rockchip/rk_dwhdmi.c: revision 1.3
        sys/arch/arm/rockchip/rk_fb.c: revision 1.1
        sys/arch/arm/dts/rk3399-rockpro64.dts: revision 1.9
        sys/arch/arm/rockchip/rk_vop.c: revision 1.1
        sys/arch/arm/rockchip/rk_vop.c: revision 1.2
        sys/arch/arm/rockchip/rk_i2c.c: revision 1.6
        sys/arch/arm/rockchip/rk_cru.h: revision 1.6
        sys/arch/arm/rockchip/rk_cru.h: revision 1.7
        sys/arch/arm/rockchip/rk_cru_composite.c: revision 1.4
        sys/arch/arm/rockchip/rk_cru_composite.c: revision 1.5
        sys/arch/arm/rockchip/files.rockchip: revision 1.21
        sys/arch/arm/rockchip/rk_i2s.c: revision 1.1
        sys/arch/arm/rockchip/files.rockchip: revision 1.22
        sys/dev/ic/dw_hdmi.c: revision 1.2
        sys/dev/ic/dw_hdmi_phy.c: revision 1.1
        sys/dev/ic/dw_hdmi.c: revision 1.3

Support reads of more than 32 bytes in a single xfer.

Add support for internal DesignWare HDMI PHYs

Add fdtbus_clock_enable and fdtbus_clock_enable_index shortcuts

Add HDMI and VOP clocks

WIP display driver for Rockchip RK3399

Add (commented out) Rockchip display support

Select the correct MPLL and PHY settings for the requested pixel clock
Force DCLK_VOP0/1 dividers to 1 and select closest match when setting PLL
rates.

Fix typo in phy config table

Fix a few swapped fields

Remove debug output

Enable Rockchip display support

Set sysclk rate at set_format time, so the link set_format callback can read 
the new sysclk

Add I2S audio input support.
Add software volume controls.
Add support for I2S clocks.
Add driver for Rockchip I2S/PCM controller.
Enable HDMI audio on ROCKPro64
Add rki2s
Add audio support


To generate a diff of this commit:
cvs rdiff -u -r1.7 -r1.7.2.1 src/sys/arch/arm/dts/rk3399-rockpro64.dts
cvs rdiff -u -r1.19 -r1.19.2.1 src/sys/arch/arm/rockchip/files.rockchip
cvs rdiff -u -r1.8 -r1.8.4.1 src/sys/arch/arm/rockchip/rk3399_cru.c
cvs rdiff -u -r1.4 -r1.4.4.1 src/sys/arch/arm/rockchip/rk_cru.h
cvs rdiff -u -r1.3 -r1.3.8.1 src/sys/arch/arm/rockchip/rk_cru_composite.c
cvs rdiff -u -r0 -r1.2.2.2 src/sys/arch/arm/rockchip/rk_drm.c \
    src/sys/arch/arm/rockchip/rk_vop.c
cvs rdiff -u -r0 -r1.1.2.2 src/sys/arch/arm/rockchip/rk_drm.h \
    src/sys/arch/arm/rockchip/rk_fb.c src/sys/arch/arm/rockchip/rk_i2s.c
cvs rdiff -u -r0 -r1.3.2.2 src/sys/arch/arm/rockchip/rk_dwhdmi.c
cvs rdiff -u -r1.4.6.1 -r1.4.6.2 src/sys/arch/arm/rockchip/rk_i2c.c
cvs rdiff -u -r1.103.2.2 -r1.103.2.3 src/sys/arch/evbarm/conf/GENERIC64
cvs rdiff -u -r1.1237.2.1 -r1.1237.2.2 src/sys/conf/files
cvs rdiff -u -r1.4 -r1.4.2.1 src/sys/dev/fdt/ausoc.c
cvs rdiff -u -r1.8 -r1.8.4.1 src/sys/dev/fdt/fdt_clock.c
cvs rdiff -u -r1.52.2.1 -r1.52.2.2 src/sys/dev/fdt/fdtvar.h
cvs rdiff -u -r1.1 -r1.1.6.1 src/sys/dev/ic/dw_hdmi.c \
    src/sys/dev/ic/dw_hdmi.h
cvs rdiff -u -r0 -r1.2.2.2 src/sys/dev/ic/dw_hdmi_phy.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/dts/rk3399-rockpro64.dts
diff -u src/sys/arch/arm/dts/rk3399-rockpro64.dts:1.7 src/sys/arch/arm/dts/rk3399-rockpro64.dts:1.7.2.1
--- src/sys/arch/arm/dts/rk3399-rockpro64.dts:1.7	Sun Jul 28 10:03:56 2019
+++ src/sys/arch/arm/dts/rk3399-rockpro64.dts	Sat Nov 16 16:48:26 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: rk3399-rockpro64.dts,v 1.7 2019/07/28 10:03:56 jmcneill Exp $ */
+/* $NetBSD: rk3399-rockpro64.dts,v 1.7.2.1 2019/11/16 16:48:26 martin Exp $ */
 
 /*-
  * Copyright (c) 2019 Jared McNeill <jmcne...@invisible.ca>
@@ -62,6 +62,10 @@
 	};
 };
 
+&hdmi_sound {
+	status = "okay";
+};
+
 &pwm1 {
 	status = "okay";
 };

Index: src/sys/arch/arm/rockchip/files.rockchip
diff -u src/sys/arch/arm/rockchip/files.rockchip:1.19 src/sys/arch/arm/rockchip/files.rockchip:1.19.2.1
--- src/sys/arch/arm/rockchip/files.rockchip:1.19	Wed May  1 10:41:33 2019
+++ src/sys/arch/arm/rockchip/files.rockchip	Sat Nov 16 16:48:25 2019
@@ -1,4 +1,4 @@
-#	$NetBSD: files.rockchip,v 1.19 2019/05/01 10:41:33 jmcneill Exp $
+#	$NetBSD: files.rockchip,v 1.19.2.1 2019/11/16 16:48:25 martin Exp $
 #
 # Configuration info for Rockchip family SoCs
 #
@@ -78,6 +78,31 @@ device	rkpwm: pwm
 attach	rkpwm at fdt with rk_pwm
 file	arch/arm/rockchip/rk_pwm.c		rk_pwm
 
+# DRM master
+define	rkfbbus { }
+device	rkdrm: drmkms, ddc_read_edid, rkfbbus
+attach	rkdrm at fdt with rk_drm
+file	arch/arm/rockchip/rk_drm.c		rk_drm
+
+# DRM framebuffer console
+device	rkfb: rkfbbus, drmfb, wsemuldisplaydev
+attach	rkfb at rkfbbus with rk_fb
+file	arch/arm/rockchip/rk_fb.c		rk_fb
+
+# Visual Output Processor
+device	rkvop: drmkms
+attach	rkvop at fdt with rk_vop
+file	arch/arm/rockchip/rk_vop.c		rk_vop
+
+# HDMI TX (Designware based)
+attach	dwhdmi at fdt with rk_dwhdmi
+file	arch/arm/rockchip/rk_dwhdmi.c		rk_dwhdmi
+
+# I2S/PCM controller
+device	rki2s	
+attach  rki2s at fdt with rk_i2s
+file    arch/arm/rockchip/rk_i2s.c		rk_i2s
+
 # SOC parameters
 defflag	opt_soc.h			SOC_ROCKCHIP
 defflag	opt_soc.h			SOC_RK3328: SOC_ROCKCHIP

Index: src/sys/arch/arm/rockchip/rk3399_cru.c
diff -u src/sys/arch/arm/rockchip/rk3399_cru.c:1.8 src/sys/arch/arm/rockchip/rk3399_cru.c:1.8.4.1
--- src/sys/arch/arm/rockchip/rk3399_cru.c:1.8	Sun Jun  9 16:14:53 2019
+++ src/sys/arch/arm/rockchip/rk3399_cru.c	Sat Nov 16 16:48:25 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: rk3399_cru.c,v 1.8 2019/06/09 16:14:53 jmcneill Exp $ */
+/* $NetBSD: rk3399_cru.c,v 1.8.4.1 2019/11/16 16:48:25 martin Exp $ */
 
 /*-
  * Copyright (c) 2018 Jared McNeill <jmcne...@invisible.ca>
@@ -28,7 +28,7 @@
 
 #include <sys/cdefs.h>
 
-__KERNEL_RCSID(1, "$NetBSD: rk3399_cru.c,v 1.8 2019/06/09 16:14:53 jmcneill Exp $");
+__KERNEL_RCSID(1, "$NetBSD: rk3399_cru.c,v 1.8.4.1 2019/11/16 16:48:25 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -271,20 +271,21 @@ rk3399_cru_pll_set_rate(struct rk_cru_so
 	struct rk_cru_pll *pll = &clk->u.pll;
 	const struct rk_cru_pll_rate *pll_rate = NULL;
 	uint32_t val;
-	int retry;
+	int retry, best_diff;
 
 	KASSERT(clk->type == RK_CRU_PLL);
 
 	if (pll->rates == NULL || rate == 0)
 		return EIO;
 
-	for (int i = 0; i < pll->nrates; i++)
-		if (pll->rates[i].rate == rate) {
+	best_diff = INT_MAX;
+	for (int i = 0; i < pll->nrates; i++) {
+		const int diff = (int)rate - (int)pll->rates[i].rate;
+		if (abs(diff) < best_diff) {
 			pll_rate = &pll->rates[i];
-			break;
+			best_diff = abs(diff);
 		}
-	if (pll_rate == NULL)
-		return EINVAL;
+	}
 
 	val = __SHIFTIN(PLL_WORK_MODE_SLOW, PLL_WORK_MODE) | (PLL_WORK_MODE << 16);
 	CRU_WRITE(sc, pll->con_base + PLL_CON3, val);
@@ -348,13 +349,23 @@ static const char * armclkb_parents[] = 
 static const char * mux_clk_tsadc_parents[] = { "xin24m", "xin32k" };
 static const char * mux_pll_src_cpll_gpll_parents[] = { "cpll", "gpll" };
 static const char * mux_pll_src_cpll_gpll_npll_parents[] = { "cpll", "gpll", "npll" };
+static const char * mux_pll_src_cpll_gpll_ppll_parents[] = { "cpll", "gpll", "npll" };
 static const char * mux_pll_src_cpll_gpll_upll_parents[] = { "cpll", "gpll", "upll" };
 static const char * mux_pll_src_cpll_gpll_npll_24m_parents[] = { "cpll", "gpll", "npll", "xin24m" };
 static const char * mux_pll_src_cpll_gpll_npll_ppll_upll_24m_parents[] = { "cpll", "gpll", "npll", "ppll", "upll", "xin24m" };
+static const char * mux_pll_src_vpll_cpll_gpll_parents[] = { "vpll", "cpll", "gpll" };
+static const char * mux_pll_src_vpll_cpll_gpll_npll_parents[] = { "vpll", "cpll", "gpll", "npll" };
 static const char * mux_aclk_perilp0_parents[] = { "cpll_aclk_perilp0_src", "gpll_aclk_perilp0_src" };
 static const char * mux_hclk_perilp1_parents[] = { "cpll_hclk_perilp1_src", "gpll_hclk_perilp1_src" };
 static const char * mux_aclk_perihp_parents[] = { "cpll_aclk_perihp_src", "gpll_aclk_perihp_src" };
 static const char * mux_aclk_cci_parents[] = { "cpll_aclk_cci_src", "gpll_aclk_cci_src", "npll_aclk_cci_src", "vpll_aclk_cci_src" };
+static const char * mux_dclk_vop0_parents[] = { "dclk_vop0_div", "dclk_vop0_frac" };
+static const char * mux_dclk_vop1_parents[] = { "dclk_vop1_div", "dclk_vop1_frac" };
+static const char * mux_i2s0_parents[] = { "clk_i2s0_div", "clk_i2s0_frac", "clkin_i2s", "xin12m" };
+static const char * mux_i2s1_parents[] = { "clk_i2s1_div", "clk_i2s1_frac", "clkin_i2s", "xin12m" };
+static const char * mux_i2s2_parents[] = { "clk_i2s2_div", "clk_i2s2_frac", "clkin_i2s", "xin12m" };
+static const char * mux_i2sch_parents[] = { "clk_i2s0", "clk_i2s1", "clk_i2s2" };
+static const char * mux_i2sout_parents[] = { "clk_i2sout_src", "xin12m" };
 static const char * mux_uart0_parents[] = { "clk_uart0_div", "clk_uart0_frac", "xin24m" };
 static const char * mux_uart1_parents[] = { "clk_uart1_div", "clk_uart1_frac", "xin24m" };
 static const char * mux_uart2_parents[] = { "clk_uart2_div", "clk_uart2_frac", "xin24m" };
@@ -403,7 +414,7 @@ static struct rk_cru_clk rk3399_cru_clks
 		   __BIT(31),		/* lock_mask */
 		   pll_rates),
 	RK3399_PLL(RK3399_PLL_VPLL, "vpll", pll_parents,
-		   PLL_CON(43),		/* con_base */
+		   PLL_CON(48),		/* con_base */
 		   PLL_CON(51),		/* mode_reg */
 		   __BIT(8),		/* mode_mask */
 		   __BIT(31),		/* lock_mask */
@@ -796,18 +807,196 @@ static struct rk_cru_clk rk3399_cru_clks
 		     __BIT(1),		/* gate_mask */
 		     RK_COMPOSITE_ROUND_DOWN),
 	RK_GATE(RK3399_PCLK_TSADC, "pclk_tsadc", "pclk_perilp1", CLKGATE_CON(22), 13),
+
+	/* VOP0 */
+	RK_COMPOSITE(RK3399_ACLK_VOP0_PRE, "aclk_vop0_pre", mux_pll_src_vpll_cpll_gpll_npll_parents,
+		     CLKSEL_CON(47),	/* muxdiv_reg */
+		     __BITS(7,6),	/* mux_mask */
+		     __BITS(4,0),	/* div_mask */
+		     CLKGATE_CON(10),	/* gate_reg */
+		     __BIT(8),		/* gate_mask */
+		     0),
+	RK_COMPOSITE_NOMUX(0, "hclk_vop0_pre", "aclk_vop0_pre",
+			   CLKSEL_CON(47),	/* div_reg */
+			   __BITS(12,8),	/* div_mask */
+			   CLKGATE_CON(10),	/* gate_reg */
+			   __BIT(9),		/* gate_mask */
+			   0),
+	RK_COMPOSITE(RK3399_DCLK_VOP0_DIV, "dclk_vop0_div", mux_pll_src_vpll_cpll_gpll_parents,
+		     CLKSEL_CON(49),	/* muxdiv_reg */
+		     __BITS(9,8),	/* mux_mask */
+		     __BITS(7,0),	/* div_mask */
+		     CLKGATE_CON(10),	/* gate_reg */
+		     __BIT(12),		/* gate_mask */
+		     RK_COMPOSITE_SET_RATE_PARENT),
+	RK_GATE(RK3399_ACLK_VOP0, "aclk_vop0", "aclk_vop0_pre", CLKGATE_CON(28), 3),
+	RK_GATE(RK3399_HCLK_VOP0, "hclk_vop0", "hclk_vop0_pre", CLKGATE_CON(28), 2),
+	RK_MUX(RK3399_DCLK_VOP0, "dclk_vop0", mux_dclk_vop0_parents, CLKSEL_CON(49), __BIT(11)),
+
+	/* VOP1 */
+	RK_COMPOSITE(RK3399_ACLK_VOP1_PRE, "aclk_vop1_pre", mux_pll_src_vpll_cpll_gpll_npll_parents,
+		     CLKSEL_CON(48),	/* muxdiv_reg */
+		     __BITS(7,6),	/* mux_mask */
+		     __BITS(4,0),	/* div_mask */
+		     CLKGATE_CON(10),	/* gate_reg */
+		     __BIT(10),		/* gate_mask */
+		     0),
+	RK_COMPOSITE_NOMUX(0, "hclk_vop1_pre", "aclk_vop1_pre",
+			   CLKSEL_CON(48),	/* div_reg */
+			   __BITS(12,8),	/* div_mask */
+			   CLKGATE_CON(10),	/* gate_reg */
+			   __BIT(11),		/* gate_mask */
+			   0),
+	RK_COMPOSITE(RK3399_DCLK_VOP1_DIV, "dclk_vop1_div", mux_pll_src_vpll_cpll_gpll_parents,
+		     CLKSEL_CON(50),	/* muxdiv_reg */
+		     __BITS(9,8),	/* mux_mask */
+		     __BITS(7,0),	/* div_mask */
+		     CLKGATE_CON(10),	/* gate_reg */
+		     __BIT(13),		/* gate_mask */
+		     RK_COMPOSITE_SET_RATE_PARENT),
+	RK_GATE(RK3399_ACLK_VOP1, "aclk_vop1", "aclk_vop1_pre", CLKGATE_CON(28), 7),
+	RK_GATE(RK3399_HCLK_VOP1, "hclk_vop1", "hclk_vop1_pre", CLKGATE_CON(28), 6),
+	RK_MUX(RK3399_DCLK_VOP1, "dclk_vop1", mux_dclk_vop1_parents, CLKSEL_CON(50), __BIT(11)),
+
+	/* VIO */
+	RK_COMPOSITE(RK3399_ACLK_VIO, "aclk_vio", mux_pll_src_cpll_gpll_ppll_parents,
+		     CLKSEL_CON(42),	/* muxdiv_reg */
+		     __BITS(7,6),	/* mux_mask */
+		     __BITS(4,0),	/* div_mask */
+		     CLKGATE_CON(11),	/* gate_reg */
+		     __BIT(0),		/* gate_mask */
+		     0),
+	RK_COMPOSITE_NOMUX(RK3399_PCLK_VIO, "pclk_vio", "aclk_vio",
+			   CLKSEL_CON(43),	/* div_reg */
+			   __BITS(4,0),		/* div_mask */
+			   CLKGATE_CON(11),	/* gate_reg */
+			   __BIT(1),		/* gate_mask */
+			   0),
+	RK_GATE(RK3399_PCLK_VIO_GRF, "pclk_vio_grf", "pclk_vio", CLKGATE_CON(29), 12),
+
+	/* HDMI */
+	RK_COMPOSITE(RK3399_ACLK_HDCP, "aclk_hdcp", mux_pll_src_cpll_gpll_ppll_parents,
+		     CLKSEL_CON(42),	/* muxdiv_reg */
+		     __BITS(15,14),	/* mux_mask */
+		     __BITS(12,8),	/* div_mask */
+		     CLKGATE_CON(11),	/* gate_reg */
+		     __BIT(12),		/* gate_mask */
+		     0),
+	RK_COMPOSITE_NOMUX(RK3399_PCLK_HDCP, "pclk_hdcp", "aclk_hdcp",
+			   CLKSEL_CON(43),	/* div_reg */
+			   __BITS(14,10),	/* div_mask */
+			   CLKGATE_CON(11),	/* gate_reg */
+			   __BIT(10),		/* gate_mask */
+			   0),
+	RK_COMPOSITE(RK3399_SCLK_HDMI_CEC, "clk_hdmi_cec", pll_parents,
+		     CLKSEL_CON(45),	/* muxdiv_reg */
+		     __BIT(15),		/* mux_mask */
+		     __BITS(9,0),	/* div_mask */
+		     CLKGATE_CON(11),	/* gate_reg */
+		     __BIT(7),		/* gate_mask */
+		     0),
+	RK_GATE(RK3399_PCLK_HDMI_CTRL, "pclk_hdmi_ctrl", "pclk_hdcp", CLKGATE_CON(29), 6),
+	RK_GATE(RK3399_SCLK_HDMI_SFR, "clk_hdmi_sfr", "xin24m", CLKGATE_CON(11), 6),
+
+	/* I2S2 */
+	RK_COMPOSITE(0, "clk_i2s0_div", mux_pll_src_cpll_gpll_parents,
+		     CLKSEL_CON(28),	/* muxdiv_reg */
+		     __BIT(7),		/* mux_mask */
+		     __BITS(6,0),	/* div_mask */
+		     CLKGATE_CON(8),	/* gate_reg */
+		     __BIT(3),		/* gate_mask */
+		     0),
+	RK_COMPOSITE(0, "clk_i2s1_div", mux_pll_src_cpll_gpll_parents,
+		     CLKSEL_CON(29),	/* muxdiv_reg */
+		     __BIT(7),		/* mux_mask */
+		     __BITS(6,0),	/* div_mask */
+		     CLKGATE_CON(8),	/* gate_reg */
+		     __BIT(6),		/* gate_mask */
+		     0),
+	RK_COMPOSITE(0, "clk_i2s2_div", mux_pll_src_cpll_gpll_parents,
+		     CLKSEL_CON(30),	/* muxdiv_reg */
+		     __BIT(7),		/* mux_mask */
+		     __BITS(6,0),	/* div_mask */
+		     CLKGATE_CON(8),	/* gate_reg */
+		     __BIT(9),		/* gate_mask */
+		     0),
+	RK_COMPOSITE_FRAC(0, "clk_i2s0_frac", "clk_i2s0_div",
+			  CLKSEL_CON(96),	/* frac_reg */
+			  0),
+	RK_COMPOSITE_FRAC(0, "clk_i2s1_frac", "clk_i2s1_div",
+			  CLKSEL_CON(97),	/* frac_reg */
+			  0),
+	RK_COMPOSITE_FRAC(0, "clk_i2s2_frac", "clk_i2s2_div",
+			  CLKSEL_CON(98),	/* frac_reg */
+			  0),
+	RK_MUX(0, "clk_i2s0_mux", mux_i2s0_parents, CLKSEL_CON(28), __BITS(9,8)),
+	RK_MUX(0, "clk_i2s1_mux", mux_i2s1_parents, CLKSEL_CON(29), __BITS(9,8)),
+	RK_MUX(0, "clk_i2s2_mux", mux_i2s2_parents, CLKSEL_CON(30), __BITS(9,8)),
+	RK_GATE(RK3399_SCLK_I2S0_8CH, "clk_i2s0", "clk_i2s0_mux", CLKGATE_CON(8), 5),
+	RK_GATE(RK3399_SCLK_I2S1_8CH, "clk_i2s1", "clk_i2s1_mux", CLKGATE_CON(8), 8),
+	RK_GATE(RK3399_SCLK_I2S2_8CH, "clk_i2s2", "clk_i2s2_mux", CLKGATE_CON(8), 11),
+	RK_GATE(RK3399_HCLK_I2S0_8CH, "hclk_i2s0", "hclk_perilp1", CLKGATE_CON(34), 0),
+	RK_GATE(RK3399_HCLK_I2S1_8CH, "hclk_i2s1", "hclk_perilp1", CLKGATE_CON(34), 1),
+	RK_GATE(RK3399_HCLK_I2S2_8CH, "hclk_i2s2", "hclk_perilp1", CLKGATE_CON(34), 2),
+	RK_MUX(0, "clk_i2sout_src", mux_i2sch_parents, CLKSEL_CON(31), __BITS(1,0)),
+	RK_COMPOSITE(RK3399_SCLK_I2S_8CH_OUT, "clk_i2sout", mux_i2sout_parents,
+		     CLKSEL_CON(31),	/* muxdiv_reg */
+		     __BIT(2),		/* mux_mask */
+		     0,			/* div_mask */
+		     CLKGATE_CON(8),	/* gate_reg */
+		     __BIT(12),		/* gate_mask */
+		     RK_COMPOSITE_SET_RATE_PARENT),
+};
+
+static const struct rk3399_init_param {
+	const char *clk;
+	const char *parent;
+} rk3399_init_params[] = {
+	{ .clk = "clk_i2s0_mux",	.parent = "clk_i2s0_frac" },
+	{ .clk = "clk_i2s1_mux",	.parent = "clk_i2s1_frac" },
+	{ .clk = "clk_i2s2_mux",	.parent = "clk_i2s2_frac" },
 };
 
 static void
 rk3399_cru_init(struct rk_cru_softc *sc)
 {
-	struct rk_cru_clk *clk;
+	struct rk_cru_clk *clk, *pclk;
+	uint32_t write_mask, write_val;
+	int error;
+	u_int n;
 
 	/*
 	 * Force an update of BPLL to bring it out of slow mode.
 	 */
 	clk = rk_cru_clock_find(sc, "armclkb");
 	clk_set_rate(&clk->base, clk_get_rate(&clk->base));
+
+	/*
+	 * Set DCLK_VOP0 and DCLK_VOP1 dividers to 1.
+	 */
+	write_mask = __BITS(7,0) << 16;
+	write_val = 0;
+	CRU_WRITE(sc, CLKSEL_CON(49), write_mask | write_val);
+	CRU_WRITE(sc, CLKSEL_CON(50), write_mask | write_val);
+
+	/*
+	 * Set defaults
+	 */
+	for (n = 0; n < __arraycount(rk3399_init_params); n++) {
+		const struct rk3399_init_param *param = &rk3399_init_params[n];
+		clk = rk_cru_clock_find(sc, param->clk);
+		KASSERTMSG(clk != NULL, "couldn't find clock %s", param->clk);
+		if (param->parent != NULL) {
+			pclk = rk_cru_clock_find(sc, param->parent);
+			KASSERTMSG(pclk != NULL, "couldn't find clock %s", param->parent);
+			error = clk_set_parent(&clk->base, &pclk->base);
+			if (error != 0) {
+				aprint_error_dev(sc->sc_dev, "couldn't set %s parent to %s: %d\n",
+				    param->clk, param->parent, error);
+				continue;
+			}
+		}
+	}
 }
 
 static int

Index: src/sys/arch/arm/rockchip/rk_cru.h
diff -u src/sys/arch/arm/rockchip/rk_cru.h:1.4 src/sys/arch/arm/rockchip/rk_cru.h:1.4.4.1
--- src/sys/arch/arm/rockchip/rk_cru.h:1.4	Sat Sep  1 19:35:53 2018
+++ src/sys/arch/arm/rockchip/rk_cru.h	Sat Nov 16 16:48:25 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: rk_cru.h,v 1.4 2018/09/01 19:35:53 jmcneill Exp $ */
+/* $NetBSD: rk_cru.h,v 1.4.4.1 2019/11/16 16:48:25 martin Exp $ */
 
 /*-
  * Copyright (c) 2018 Jared McNeill <jmcne...@invisible.ca>
@@ -200,10 +200,13 @@ struct rk_cru_composite {
 	uint32_t	div_mask;
 	bus_size_t	gate_reg;
 	uint32_t	gate_mask;
+	bus_size_t	frac_reg;
 	const char	**parents;
 	u_int		nparents;
 	u_int		flags;
 #define	RK_COMPOSITE_ROUND_DOWN		0x01
+#define	RK_COMPOSITE_SET_RATE_PARENT	0x02
+#define	RK_COMPOSITE_FRACDIV		0x04
 };
 
 int	rk_cru_composite_enable(struct rk_cru_softc *, struct rk_cru_clk *, int);
@@ -212,7 +215,7 @@ int	rk_cru_composite_set_rate(struct rk_
 const char *rk_cru_composite_get_parent(struct rk_cru_softc *, struct rk_cru_clk *);
 int	rk_cru_composite_set_parent(struct rk_cru_softc *, struct rk_cru_clk *, const char *);
 
-#define	RK_COMPOSITE(_id, _name, _parents, _muxdiv_reg, _mux_mask, _div_mask, _gate_reg, _gate_mask, _flags) \
+#define	_RK_COMPOSITE_INIT(_id, _name, _parents, _muxdiv_reg, _mux_mask, _div_mask, _gate_reg, _gate_mask, _frac_reg, _flags) \
 	{							\
 		.id = (_id),					\
 		.type = RK_CRU_COMPOSITE,			\
@@ -225,6 +228,7 @@ int	rk_cru_composite_set_parent(struct r
 		.u.composite.div_mask = (_div_mask),		\
 		.u.composite.gate_reg = (_gate_reg),		\
 		.u.composite.gate_mask = (_gate_mask),		\
+		.u.composite.frac_reg = (_frac_reg),		\
 		.u.composite.flags = (_flags),			\
 		.enable = rk_cru_composite_enable,		\
 		.get_rate = rk_cru_composite_get_rate,		\
@@ -233,14 +237,20 @@ int	rk_cru_composite_set_parent(struct r
 		.set_parent = rk_cru_composite_set_parent,	\
 	}
 
+#define	RK_COMPOSITE(_id, _name, _parents, _muxdiv_reg, _mux_mask, _div_mask, _gate_reg, _gate_mask, _flags) \
+	_RK_COMPOSITE_INIT(_id, _name, _parents, _muxdiv_reg, _mux_mask, _div_mask, _gate_reg, _gate_mask, 0, _flags)
+
 #define	RK_COMPOSITE_NOMUX(_id, _name, _parent, _div_reg, _div_mask, _gate_reg, _gate_mask, _flags) \
-	RK_COMPOSITE(_id, _name, (const char *[]){ _parent }, _div_reg, 0, _div_mask, _gate_reg, _gate_mask, _flags)
+	_RK_COMPOSITE_INIT(_id, _name, (const char *[]){ _parent }, _div_reg, 0, _div_mask, _gate_reg, _gate_mask, 0, _flags)
 
 #define	RK_COMPOSITE_NOGATE(_id, _name, _parents, _muxdiv_reg, _mux_mask, _div_mask, _flags) \
-	RK_COMPOSITE(_id, _name, _parents, _muxdiv_reg, _mux_mask, _div_mask, 0, 0, _flags)
+	_RK_COMPOSITE_INIT(_id, _name, _parents, _muxdiv_reg, _mux_mask, _div_mask, 0, 0, 0, _flags)
+
+#define	RK_COMPOSITE_FRAC(_id, _name, _parent, _frac_reg, _flags) \
+	_RK_COMPOSITE_INIT(_id, _name, (const char *[]){ _parent }, 0, 0, 0, 0, 0, _frac_reg, (_flags) | RK_COMPOSITE_FRACDIV)
 
 #define	RK_DIV(_id, _name, _parent, _div_reg, _div_mask, _flags) \
-	RK_COMPOSITE(_id, _name, (const char *[]){ _parent }, _div_reg, 0, _div_mask, 0, 0, _flags)
+	_RK_COMPOSITE_INIT(_id, _name, (const char *[]){ _parent }, _div_reg, 0, _div_mask, 0, 0, 0, _flags)
 
 /* Gate clocks */
 

Index: src/sys/arch/arm/rockchip/rk_cru_composite.c
diff -u src/sys/arch/arm/rockchip/rk_cru_composite.c:1.3 src/sys/arch/arm/rockchip/rk_cru_composite.c:1.3.8.1
--- src/sys/arch/arm/rockchip/rk_cru_composite.c:1.3	Tue Jun 19 01:24:17 2018
+++ src/sys/arch/arm/rockchip/rk_cru_composite.c	Sat Nov 16 16:48:25 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: rk_cru_composite.c,v 1.3 2018/06/19 01:24:17 jmcneill Exp $ */
+/* $NetBSD: rk_cru_composite.c,v 1.3.8.1 2019/11/16 16:48:25 martin Exp $ */
 
 /*-
  * Copyright (c) 2018 Jared McNeill <jmcne...@invisible.ca>
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: rk_cru_composite.c,v 1.3 2018/06/19 01:24:17 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: rk_cru_composite.c,v 1.3.8.1 2019/11/16 16:48:25 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -75,10 +75,55 @@ rk_cru_composite_get_rate(struct rk_cru_
 	if (prate == 0)
 		return 0;
 
-	const uint32_t val = CRU_READ(sc, composite->muxdiv_reg);
-	const u_int div = __SHIFTOUT(val, composite->div_mask) + 1;
+	if (composite->flags & RK_COMPOSITE_FRACDIV) {
+		const uint32_t val = CRU_READ(sc, composite->frac_reg);
+		const u_int num = (val >> 16) & 0xffff;
+		const u_int den = val & 0xffff;
 
-	return prate / div;
+		return (u_int)((uint64_t)prate * num / den);
+	} else {
+		const uint32_t val = CRU_READ(sc, composite->muxdiv_reg);
+		const u_int div = __SHIFTOUT(val, composite->div_mask) + 1;
+
+		return prate / div;
+	}
+}
+
+static u_int
+rk_cru_composite_get_frac_div(u_int n, u_int d)
+{
+	u_int tmp;
+
+	while (d > 0) {
+		tmp = d;
+		d = n % d;
+		n = tmp;
+	}
+
+	return n;
+}
+
+static int
+rk_cru_composite_set_rate_frac(struct rk_cru_softc *sc,
+    struct rk_cru_clk *clk, u_int rate)
+{
+	struct rk_cru_composite *composite = &clk->u.composite;
+	struct clk *clk_parent;
+
+	clk_parent = clk_get_parent(&clk->base);
+	if (clk_parent == NULL)
+		return ENXIO;
+
+	const u_int prate = clk_get_rate(clk_parent);
+	const u_int v = rk_cru_composite_get_frac_div(prate, rate);
+	const u_int num = (prate / v) & 0xffff;
+	const u_int den = (rate / v) & 0xffff;
+	if (prate / num * den != rate)
+		return EINVAL;
+
+	CRU_WRITE(sc, composite->frac_reg, (den << 16) | num);
+
+	return 0;
 }
 
 int
@@ -92,6 +137,17 @@ rk_cru_composite_set_rate(struct rk_cru_
 
 	KASSERT(clk->type == RK_CRU_COMPOSITE);
 
+	if (composite->flags & RK_COMPOSITE_SET_RATE_PARENT) {
+		clk_parent = clk_get_parent(&clk->base);
+		if (clk_parent == NULL)
+			return ENXIO;
+		return clk_set_rate(clk_parent, rate);
+	}
+
+	if (composite->flags & RK_COMPOSITE_FRACDIV) {
+		return rk_cru_composite_set_rate_frac(sc, clk, rate);
+	}
+
 	best_div = 0;
 	best_mux = 0;
 	best_diff = INT_MAX;

Index: src/sys/arch/arm/rockchip/rk_i2c.c
diff -u src/sys/arch/arm/rockchip/rk_i2c.c:1.4.6.1 src/sys/arch/arm/rockchip/rk_i2c.c:1.4.6.2
--- src/sys/arch/arm/rockchip/rk_i2c.c:1.4.6.1	Tue Sep 24 02:50:36 2019
+++ src/sys/arch/arm/rockchip/rk_i2c.c	Sat Nov 16 16:48:25 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: rk_i2c.c,v 1.4.6.1 2019/09/24 02:50:36 martin Exp $ */
+/* $NetBSD: rk_i2c.c,v 1.4.6.2 2019/11/16 16:48:25 martin Exp $ */
 
 /*-
  * Copyright (c) 2018 Jared McNeill <jmcne...@invisible.ca>
@@ -28,7 +28,7 @@
 
 #include <sys/cdefs.h>
 
-__KERNEL_RCSID(0, "$NetBSD: rk_i2c.c,v 1.4.6.1 2019/09/24 02:50:36 martin Exp $");
+__KERNEL_RCSID(0, "$NetBSD: rk_i2c.c,v 1.4.6.2 2019/11/16 16:48:25 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -284,7 +284,7 @@ rk_i2c_write(struct rk_i2c_softc *sc, i2
 static int
 rk_i2c_read(struct rk_i2c_softc *sc, i2c_addr_t addr,
     const uint8_t *cmd, size_t cmdlen, uint8_t *buf,
-    size_t buflen, int flags, bool send_start)
+    size_t buflen, int flags, bool send_start, bool last_ack)
 {
 	uint32_t rxdata[8];
 	uint32_t con, mrxaddr, mrxraddr;
@@ -296,24 +296,27 @@ rk_i2c_read(struct rk_i2c_softc *sc, i2c
 	if (cmdlen > 3)
 		return EINVAL;
 
-	mode = RKI2C_CON_I2C_MODE_RTX;
-	con = RKI2C_CON_I2C_EN | RKI2C_CON_ACK | __SHIFTIN(mode, RKI2C_CON_I2C_MODE);
+	mode = send_start ? RKI2C_CON_I2C_MODE_RTX : RKI2C_CON_I2C_MODE_RX;
+	con = RKI2C_CON_I2C_EN | __SHIFTIN(mode, RKI2C_CON_I2C_MODE);
 	WR4(sc, RKI2C_CON, con);
 
 	if (send_start && (error = rk_i2c_start(sc)) != 0)
 		return error;
 
-	mrxaddr = __SHIFTIN((addr << 1) | 1, RKI2C_MRXADDR_SADDR) |
-	    RKI2C_MRXADDR_ADDLVLD;
-	WR4(sc, RKI2C_MRXADDR, mrxaddr);
-	for (n = 0, mrxraddr = 0; n < cmdlen; n++) {
-		mrxraddr |= cmd[n] << (n * 8);
-		mrxraddr |= (RKI2C_MRXRADDR_ADDLVLD << n);
+	if (send_start) {
+		mrxaddr = __SHIFTIN((addr << 1) | 1, RKI2C_MRXADDR_SADDR) |
+		    RKI2C_MRXADDR_ADDLVLD;
+		WR4(sc, RKI2C_MRXADDR, mrxaddr);
+		for (n = 0, mrxraddr = 0; n < cmdlen; n++) {
+			mrxraddr |= cmd[n] << (n * 8);
+			mrxraddr |= (RKI2C_MRXRADDR_ADDLVLD << n);
+		}
+		WR4(sc, RKI2C_MRXRADDR, mrxraddr);
 	}
-	WR4(sc, RKI2C_MRXRADDR, mrxraddr);
 
-	/* Acknowledge last byte read */
-	con |= RKI2C_CON_ACK;
+	if (last_ack) {
+		con |= RKI2C_CON_ACK;
+	}
 	WR4(sc, RKI2C_CON, con);
 
 	/* Receive data. Slave address goes in the lower 8 bits of MRXADDR */
@@ -321,8 +324,14 @@ rk_i2c_read(struct rk_i2c_softc *sc, i2c
 	if ((error = rk_i2c_wait(sc, RKI2C_IPD_MBRFIPD)) != 0)
 		return error;
 
+#if 0
 	bus_space_read_region_4(sc->sc_bst, sc->sc_bsh, RKI2C_RXDATA(0),
 	    rxdata, howmany(buflen, 4));
+#else
+	for (n = 0; n < roundup(buflen, 4); n += 4)
+		rxdata[n/4] = RD4(sc, RKI2C_RXDATA(n/4));
+#endif
+
 	memcpy(buf, rxdata, buflen);
 
 	return 0;
@@ -339,7 +348,19 @@ rk_i2c_exec(void *priv, i2c_op_t op, i2c
 	KASSERT(mutex_owned(&sc->sc_lock));
 
 	if (I2C_OP_READ_P(op)) {
-		error = rk_i2c_read(sc, addr, cmdbuf, cmdlen, buf, buflen, flags, send_start);
+		uint8_t *databuf = buf;
+		while (buflen > 0) {
+			const size_t datalen = uimin(buflen, 32);
+			const bool last_ack = datalen == buflen;
+			error = rk_i2c_read(sc, addr, cmdbuf, cmdlen, databuf, datalen, flags, send_start, last_ack);
+			if (error != 0)
+				break;
+			databuf += datalen;
+			buflen -= datalen;
+			send_start = false;
+			cmdbuf = NULL;
+			cmdlen = 0;
+		}
 	} else {
 		error = rk_i2c_write(sc, addr, cmdbuf, cmdlen, buf, buflen, flags, send_start);
 	}

Index: src/sys/arch/evbarm/conf/GENERIC64
diff -u src/sys/arch/evbarm/conf/GENERIC64:1.103.2.2 src/sys/arch/evbarm/conf/GENERIC64:1.103.2.3
--- src/sys/arch/evbarm/conf/GENERIC64:1.103.2.2	Thu Oct  3 17:14:47 2019
+++ src/sys/arch/evbarm/conf/GENERIC64	Sat Nov 16 16:48:25 2019
@@ -1,5 +1,5 @@
 #
-#	$NetBSD: GENERIC64,v 1.103.2.2 2019/10/03 17:14:47 martin Exp $
+#	$NetBSD: GENERIC64,v 1.103.2.3 2019/11/16 16:48:25 martin Exp $
 #
 #	GENERIC ARM (aarch64) kernel
 #
@@ -340,7 +340,7 @@ options 	I2C_MAX_ADDR=0xfff
 bsciic*		at fdt?			# Broadcom BCM283x Serial Control
 dwiic*		at fdt?			# Designware I2C
 dwiic*		at acpi?
-rkiic*		at fdt?			# Rockchip I2C
+rkiic*		at fdt? pass 4		# Rockchip I2C
 sunxirsb*	at fdt?	pass 4		# Allwinner RSB
 sunxitwi*	at fdt?			# Allwinner TWI
 tegrai2c*	at fdt? pass 4		# NVIDIA Tegra I2C
@@ -412,6 +412,7 @@ options 	HDAUDIOVERBOSE
 options 	HDAUDIO_ENABLE_HDMI
 options 	HDAUDIO_ENABLE_DISPLAYPORT
 ausoc*		at fdt?			# Simple SoC audio card
+rki2s*		at fdt?			# Rockchip I2S/PCM
 sunxicodec*	at fdt?			# Allwinner audio codec
 sun8icodec*	at fdt?			# Allwinner audio codec (sun8i/sun50i)
 h3codec*	at fdt?			# Allwinner H3 audio codec (analog part)
@@ -429,6 +430,9 @@ hdmicec*	at hdmicecbus?
 anxedp*		at iic?			# Analogix eDP TX
 dispcon*	at fdt?			# Display connector devices
 dwhdmi* 	at fdt?			# Designware HDMI TX
+rkdrm*		at fdt? pass 5		# Rockchip DRM master
+rkfb*		at rkdrm?		# Rockchip DRM framebuffer
+rkvop*		at fdt?			# Rockchip Visual Output Processor
 sunxide2bus*	at fdt? pass 4		# Allwinner DE2 bus
 sunxidrm*	at fdt?	pass 5		# Allwinner Display Pipeline
 sunxifb*	at sunxidrm?		# Allwinner DRM framebuffer

Index: src/sys/conf/files
diff -u src/sys/conf/files:1.1237.2.1 src/sys/conf/files:1.1237.2.2
--- src/sys/conf/files:1.1237.2.1	Tue Sep  3 07:47:59 2019
+++ src/sys/conf/files	Sat Nov 16 16:48:25 2019
@@ -1,4 +1,4 @@
-#	$NetBSD: files,v 1.1237.2.1 2019/09/03 07:47:59 martin Exp $
+#	$NetBSD: files,v 1.1237.2.2 2019/11/16 16:48:25 martin Exp $
 #	@(#)files.newconf	7.5 (Berkeley) 5/10/93
 
 version 	20171118
@@ -1489,6 +1489,7 @@ attach	ipmi at ipmibus
 # Designware HDMI TX
 device	dwhdmi: edid, videomode, drmkms, drmkms_i2c
 file	dev/ic/dw_hdmi.c		dwhdmi
+file	dev/ic/dw_hdmi_phy.c		dwhdmi
 
 #
 # File systems

Index: src/sys/dev/fdt/ausoc.c
diff -u src/sys/dev/fdt/ausoc.c:1.4 src/sys/dev/fdt/ausoc.c:1.4.2.1
--- src/sys/dev/fdt/ausoc.c:1.4	Wed May  8 13:40:18 2019
+++ src/sys/dev/fdt/ausoc.c	Sat Nov 16 16:48:25 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: ausoc.c,v 1.4 2019/05/08 13:40:18 isaki Exp $ */
+/* $NetBSD: ausoc.c,v 1.4.2.1 2019/11/16 16:48:25 martin Exp $ */
 
 /*-
  * Copyright (c) 2018 Jared McNeill <jmcne...@invisible.ca>
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ausoc.c,v 1.4 2019/05/08 13:40:18 isaki Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ausoc.c,v 1.4.2.1 2019/11/16 16:48:25 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -121,8 +121,22 @@ ausoc_set_format(void *priv, int setmode
     audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
 {
 	struct ausoc_link * const link = priv;
+	const audio_params_t *params = (setmode & AUMODE_PLAY) != 0 ?
+	    play : rec;
 	int error;
 
+	if (link->link_mclk_fs) {
+		const u_int rate = params->sample_rate * link->link_mclk_fs;
+		error = audio_dai_set_sysclk(link->link_codec, rate,
+		    AUDIO_DAI_CLOCK_IN);
+		if (error)
+			return error;
+		error = audio_dai_set_sysclk(link->link_cpu, rate,
+		    AUDIO_DAI_CLOCK_OUT);
+		if (error)
+			return error;
+	}
+
 	error = audio_dai_mi_set_format(link->link_cpu, setmode,
 	    play, rec, pfil, rfil);
 	if (error)
@@ -246,20 +260,8 @@ ausoc_trigger_output(void *priv, void *s
     void (*intr)(void *), void *intrarg, const audio_params_t *params)
 {
 	struct ausoc_link * const link = priv;
-	u_int n, rate;
 	int error;
-
-	if (link->link_mclk_fs) {
-		rate = params->sample_rate * link->link_mclk_fs;
-		error = audio_dai_set_sysclk(link->link_codec, rate,
-		    AUDIO_DAI_CLOCK_IN);
-		if (error)
-			goto failed;
-		error = audio_dai_set_sysclk(link->link_cpu, rate,
-		    AUDIO_DAI_CLOCK_OUT);
-		if (error)
-			goto failed;
-	}
+	u_int n;
 
 	for (n = 0; n < link->link_naux; n++) {
 		error = audio_dai_trigger(link->link_aux[n], start, end,
@@ -285,20 +287,8 @@ ausoc_trigger_input(void *priv, void *st
     void (*intr)(void *), void *intrarg, const audio_params_t *params)
 {
 	struct ausoc_link * const link = priv;
-	u_int n, rate;
 	int error;
-
-	if (link->link_mclk_fs) {
-		rate = params->sample_rate * link->link_mclk_fs;
-		error = audio_dai_set_sysclk(link->link_codec, rate,
-		    AUDIO_DAI_CLOCK_IN);
-		if (error)
-			goto failed;
-		error = audio_dai_set_sysclk(link->link_cpu, rate,
-		    AUDIO_DAI_CLOCK_OUT);
-		if (error)
-			goto failed;
-	}
+	u_int n;
 
 	for (n = 0; n < link->link_naux; n++) {
 		error = audio_dai_trigger(link->link_aux[n], start, end,

Index: src/sys/dev/fdt/fdt_clock.c
diff -u src/sys/dev/fdt/fdt_clock.c:1.8 src/sys/dev/fdt/fdt_clock.c:1.8.4.1
--- src/sys/dev/fdt/fdt_clock.c:1.8	Wed Feb 27 16:56:00 2019
+++ src/sys/dev/fdt/fdt_clock.c	Sat Nov 16 16:48:25 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: fdt_clock.c,v 1.8 2019/02/27 16:56:00 jakllsch Exp $ */
+/* $NetBSD: fdt_clock.c,v 1.8.4.1 2019/11/16 16:48:25 martin Exp $ */
 
 /*-
  * Copyright (c) 2015 Jared D. McNeill <jmcne...@invisible.ca>
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: fdt_clock.c,v 1.8 2019/02/27 16:56:00 jakllsch Exp $");
+__KERNEL_RCSID(0, "$NetBSD: fdt_clock.c,v 1.8.4.1 2019/11/16 16:48:25 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -161,6 +161,30 @@ fdtbus_clock_get(int phandle, const char
 	return fdtbus_clock_get_prop(phandle, clkname, "clock-names");
 }
 
+int
+fdtbus_clock_enable(int phandle, const char *clkname, bool required)
+{
+	struct clk *clk;
+
+	clk = fdtbus_clock_get(phandle, clkname);
+	if (clk == NULL)
+		return required ? ENOENT : 0;
+
+	return clk_enable(clk);
+}
+
+int
+fdtbus_clock_enable_index(int phandle, u_int index, bool required)
+{
+	struct clk *clk;
+
+	clk = fdtbus_clock_get_index(phandle, index);
+	if (clk == NULL)
+		return required ? ENOENT : 0;
+
+	return clk_enable(clk);
+}
+
 /*
  * Search the DT for a clock by "clock-output-names" property.
  *

Index: src/sys/dev/fdt/fdtvar.h
diff -u src/sys/dev/fdt/fdtvar.h:1.52.2.1 src/sys/dev/fdt/fdtvar.h:1.52.2.2
--- src/sys/dev/fdt/fdtvar.h:1.52.2.1	Thu Oct  3 17:23:11 2019
+++ src/sys/dev/fdt/fdtvar.h	Sat Nov 16 16:48:25 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: fdtvar.h,v 1.52.2.1 2019/10/03 17:23:11 martin Exp $ */
+/* $NetBSD: fdtvar.h,v 1.52.2.2 2019/11/16 16:48:25 martin Exp $ */
 
 /*-
  * Copyright (c) 2015 Jared D. McNeill <jmcne...@invisible.ca>
@@ -345,6 +345,8 @@ struct clk *	fdtbus_clock_get(int, const
 struct clk *	fdtbus_clock_get_index(int, u_int);
 struct clk *	fdtbus_clock_byname(const char *);
 void		fdtbus_clock_assign(int);
+int		fdtbus_clock_enable(int, const char *, bool);
+int		fdtbus_clock_enable_index(int, u_int, bool);
 
 struct fdtbus_reset *fdtbus_reset_get(int, const char *);
 struct fdtbus_reset *fdtbus_reset_get_index(int, u_int);

Index: src/sys/dev/ic/dw_hdmi.c
diff -u src/sys/dev/ic/dw_hdmi.c:1.1 src/sys/dev/ic/dw_hdmi.c:1.1.6.1
--- src/sys/dev/ic/dw_hdmi.c:1.1	Wed Jan 30 01:19:49 2019
+++ src/sys/dev/ic/dw_hdmi.c	Sat Nov 16 16:48:25 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: dw_hdmi.c,v 1.1 2019/01/30 01:19:49 jmcneill Exp $ */
+/* $NetBSD: dw_hdmi.c,v 1.1.6.1 2019/11/16 16:48:25 martin Exp $ */
 
 /*-
  * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca>
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: dw_hdmi.c,v 1.1 2019/01/30 01:19:49 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: dw_hdmi.c,v 1.1.6.1 2019/11/16 16:48:25 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -45,11 +45,19 @@ __KERNEL_RCSID(0, "$NetBSD: dw_hdmi.c,v 
 #include <dev/videomode/videomode.h>
 #include <dev/videomode/edidvar.h>
 
+#include <dev/audio/audio_dai.h>
+
 #include <drm/drmP.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_edid.h>
 
+#define	HDMI_DESIGN_ID		0x0000
+#define	HDMI_REVISION_ID	0x0001
+#define	HDMI_CONFIG0_ID		0x0004
+#define	 HDMI_CONFIG0_ID_AUDI2S			__BIT(4)
+#define	HDMI_CONFIG2_ID		0x0006
+
 #define	HDMI_IH_I2CM_STAT0	0x0105
 #define	 HDMI_IH_I2CM_STAT0_DONE		__BIT(1)
 #define	 HDMI_IH_I2CM_STAT0_ERROR		__BIT(0)
@@ -127,6 +135,49 @@ __KERNEL_RCSID(0, "$NetBSD: dw_hdmi.c,v 
 #define	 HDMI_FC_CH1PREAM_DEFAULT		0x16
 #define	HDMI_FC_CH2PREAM	0x1016
 #define	 HDMI_FC_CH2PREAM_DEFAULT		0x21
+#define	HDMI_FC_AUDCONF0	0x1025
+#define	HDMI_FC_AUDCONF1	0x1026
+#define	HDMI_FC_AUDCONF2	0x1027
+#define	HDMI_FC_AUDCONF3	0x1028
+
+#define	HDMI_PHY_CONF0		0x3000
+#define	 HDMI_PHY_CONF0_PDZ			__BIT(7)
+#define	 HDMI_PHY_CONF0_ENTMDS			__BIT(6)
+#define	 HDMI_PHY_CONF0_SVSRET			__BIT(5)
+#define	 HDMI_PHY_CONF0_PDDQ			__BIT(4)
+#define	 HDMI_PHY_CONF0_TXPWRON			__BIT(3)
+#define	 HDMI_PHY_CONF0_ENHPDRXSENSE		__BIT(2)
+#define	 HDMI_PHY_CONF0_SELDATAENPOL		__BIT(1)
+#define	 HDMI_PHY_CONF0_SELDIPIF		__BIT(0)
+#define	HDMI_PHY_STAT0		0x3004
+#define	 HDMI_PHY_STAT0_RX_SENSE_3		__BIT(7)
+#define	 HDMI_PHY_STAT0_RX_SENSE_2		__BIT(6)
+#define	 HDMI_PHY_STAT0_RX_SENSE_1		__BIT(5)
+#define	 HDMI_PHY_STAT0_RX_SENSE_0		__BIT(4)
+#define	 HDMI_PHY_STAT0_HPD			__BIT(1)
+#define	 HDMI_PHY_STAT0_TX_PHY_LOCK		__BIT(0)
+
+#define	HDMI_AUD_CONF0		0x3100
+#define	 HDMI_AUD_CONF0_SW_AUDIO_FIFO_RST	__BIT(7)
+#define	 HDMI_AUD_CONF0_I2S_SELECT		__BIT(5)
+#define	 HDMI_AUD_CONF0_I2S_IN_EN		__BITS(3,0)
+#define	HDMI_AUD_CONF1		0x3101
+#define	 HDMI_AUD_CONF1_I2S_WIDTH		__BITS(4,0)
+#define	HDMI_AUD_INT		0x3102
+#define	HDMI_AUD_CONF2		0x3103
+#define	 HDMI_AUD_CONF2_INSERT_PCUV		__BIT(2)
+#define	 HDMI_AUD_CONF2_NLPCM			__BIT(1)
+#define	 HDMI_AUD_CONF2_HBR			__BIT(0)
+#define	HDMI_AUD_INT1		0x3104
+
+#define	HDMI_AUD_N1		0x3200
+#define	HDMI_AUD_N2		0x3201
+#define	HDMI_AUD_N3		0x3202
+#define	HDMI_AUD_CTS1		0x3203
+#define	HDMI_AUD_CTS2		0x3204
+#define	HDMI_AUD_CTS3		0x3205
+#define	HDMI_AUD_INPUTCLKFS	0x3206
+#define	 HDMI_AUD_INPUTCLKFS_IFSFACTOR		__BITS(2,0)
 
 #define	HDMI_MC_CLKDIS		0x4001
 #define	 HDMI_MC_CLKDIS_HDCPCLK_DISABLE		__BIT(6)
@@ -143,6 +194,8 @@ __KERNEL_RCSID(0, "$NetBSD: dw_hdmi.c,v 
 #define	 HDMI_MC_SWRSTZREQ_PIXELSWRST_REQ	__BIT(0)
 #define	HDMI_MC_FLOWCTRL	0x4004
 #define	HDMI_MC_PHYRSTZ		0x4005
+#define	 HDMI_MC_PHYRSTZ_ASSERT			__BIT(0)
+#define	 HDMI_MC_PHYRSTZ_DEASSERT		0
 #define	HDMI_MC_LOCKONCLOCK	0x4006
 #define	HDMI_MC_HEACPHY_RST	0x4007
 
@@ -187,6 +240,16 @@ __KERNEL_RCSID(0, "$NetBSD: dw_hdmi.c,v 
 #define	 HDMI_I2CM_SOFTRSTZ_I2C_SOFTRST		__BIT(0)
 #define	HDMI_I2CM_SEGPTR	0x7e0a
 
+enum dwhdmi_dai_mixer_ctrl {
+	DWHDMI_DAI_OUTPUT_CLASS,
+	DWHDMI_DAI_INPUT_CLASS,
+
+	DWHDMI_DAI_OUTPUT_MASTER_VOLUME,
+	DWHDMI_DAI_INPUT_DAC_VOLUME,
+
+	DWHDMI_DAI_MIXER_CTRL_LAST
+};
+
 static int
 dwhdmi_ddc_acquire_bus(void *priv, int flags)
 {
@@ -410,9 +473,8 @@ dwhdmi_fc_init(struct dwhdmi_softc *sc, 
 static void
 dwhdmi_mc_init(struct dwhdmi_softc *sc)
 {
-	struct dwhdmi_connector *dwhdmi_connector = &sc->sc_connector;
 	uint8_t val;
-	u_int n;
+	u_int n, iter;
 
 	/* Bypass colour space converter */
 	dwhdmi_write(sc, HDMI_MC_FLOWCTRL, 0);
@@ -422,16 +484,16 @@ dwhdmi_mc_init(struct dwhdmi_softc *sc)
 	      HDMI_MC_CLKDIS_CECCLK_DISABLE |
 	      HDMI_MC_CLKDIS_CSCCLK_DISABLE |
 	      HDMI_MC_CLKDIS_PREPCLK_DISABLE;
-	if (!dwhdmi_connector->monitor_audio)
-		val |= HDMI_MC_CLKDIS_AUDCLK_DISABLE;
 	dwhdmi_write(sc, HDMI_MC_CLKDIS, val);
 
 	/* Soft reset TMDS */
 	val = 0xff & ~HDMI_MC_SWRSTZREQ_TMDSSWRST_REQ;
 	dwhdmi_write(sc, HDMI_MC_SWRSTZREQ, val);
 
+	iter = sc->sc_version == 0x130a ? 4 : 1;
+
 	val = dwhdmi_read(sc, HDMI_FC_INVIDCONF);
-	for (n = 0; n < 4; n++)
+	for (n = 0; n < iter; n++)
 		dwhdmi_write(sc, HDMI_FC_INVIDCONF, val);
 }
 
@@ -442,6 +504,59 @@ dwhdmi_mc_disable(struct dwhdmi_softc *s
 	dwhdmi_write(sc, HDMI_MC_CLKDIS, 0xff);
 }
 
+static void
+dwhdmi_audio_init(struct dwhdmi_softc *sc)
+{
+	uint8_t val;
+	u_int n;
+
+	/* The following values are for 48 kHz */
+	switch (sc->sc_curmode.clock) {
+	case 25170:
+		n = 6864;
+		break;
+	case 74170:
+		n = 11648;
+		break;
+	case 148350:
+		n = 5824;
+		break;
+	default:
+		n = 6144;
+		break;
+	}
+
+	/* Use automatic CTS generation */
+	dwhdmi_write(sc, HDMI_AUD_CTS1, 0);
+	dwhdmi_write(sc, HDMI_AUD_CTS2, 0);
+	dwhdmi_write(sc, HDMI_AUD_CTS3, 0);
+
+	/* Set N factor for audio clock regeneration */
+	dwhdmi_write(sc, HDMI_AUD_N1, n & 0xff);
+	dwhdmi_write(sc, HDMI_AUD_N2, (n >> 8) & 0xff);
+	dwhdmi_write(sc, HDMI_AUD_N3, (n >> 16) & 0xff);
+
+	val = dwhdmi_read(sc, HDMI_AUD_CONF0);
+	val |= HDMI_AUD_CONF0_I2S_SELECT;		/* XXX i2s mode */
+	val &= ~HDMI_AUD_CONF0_I2S_IN_EN;
+	val |= __SHIFTIN(1, HDMI_AUD_CONF0_I2S_IN_EN);	/* XXX 2ch */
+	dwhdmi_write(sc, HDMI_AUD_CONF0, val);
+	
+	val = __SHIFTIN(16, HDMI_AUD_CONF1_I2S_WIDTH);
+	dwhdmi_write(sc, HDMI_AUD_CONF1, val);
+
+	dwhdmi_write(sc, HDMI_AUD_INPUTCLKFS, 4);	/* XXX 64 FS */
+
+	dwhdmi_write(sc, HDMI_FC_AUDCONF0, 1 << 4);	/* XXX 2ch */
+	dwhdmi_write(sc, HDMI_FC_AUDCONF1, 0);
+	dwhdmi_write(sc, HDMI_FC_AUDCONF2, 0);
+	dwhdmi_write(sc, HDMI_FC_AUDCONF3, 0);
+
+	val = dwhdmi_read(sc, HDMI_MC_CLKDIS);
+	val &= ~HDMI_MC_CLKDIS_PREPCLK_DISABLE;
+	dwhdmi_write(sc, HDMI_MC_CLKDIS, val);
+}
+
 static enum drm_connector_status
 dwhdmi_connector_detect(struct drm_connector *connector, bool force)
 {
@@ -479,7 +594,7 @@ dwhdmi_connector_get_modes(struct drm_co
 
 	memset(edid, 0, sizeof(edid));
 	for (block = 0; block < 4; block++) {
-		error = ddc_read_edid_block(&sc->sc_ic,
+		error = ddc_read_edid_block(sc->sc_ic,
 		    &edid[block * EDID_LENGTH], EDID_LENGTH, block);
 		if (error != 0)
 			break;
@@ -569,6 +684,9 @@ dwhdmi_bridge_enable(struct drm_bridge *
 
 	dwhdmi_tx_init(sc);
 	dwhdmi_mc_init(sc);
+
+	if (sc->sc_connector.monitor_audio)
+		dwhdmi_audio_init(sc);
 }
 
 static void
@@ -621,10 +739,136 @@ static const struct drm_bridge_funcs dwh
 	.mode_fixup = dwhdmi_bridge_mode_fixup,
 };
 
+static int
+dwhdmi_dai_set_format(audio_dai_tag_t dai, u_int format)
+{
+	return 0;
+}
+
+static int
+dwhdmi_dai_add_device(audio_dai_tag_t dai, audio_dai_tag_t aux)
+{
+	/* Not supported */
+	return 0;
+}
+
+static void
+dwhdmi_audio_swvol_codec(audio_filter_arg_t *arg)
+{
+	struct dwhdmi_softc * const sc = arg->context;
+	const aint_t *src;
+	aint_t *dst;
+	u_int sample_count;
+	u_int i;
+
+	src = arg->src;
+	dst = arg->dst;
+	sample_count = arg->count * arg->srcfmt->channels;
+	for (i = 0; i < sample_count; i++) {
+		aint2_t v = (aint2_t)(*src++);
+		v = v * sc->sc_swvol / 255;
+		*dst++ = (aint_t)v;
+	}
+}
+
+static int
+dwhdmi_audio_set_format(void *priv, int setmode,
+    const audio_params_t *play, const audio_params_t *rec,
+    audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
+{
+	struct dwhdmi_softc * const sc = priv;
+
+	pfil->codec = dwhdmi_audio_swvol_codec;
+	pfil->context = sc;
+
+	return 0;
+}
+
+static int
+dwhdmi_audio_set_port(void *priv, mixer_ctrl_t *mc)
+{
+	struct dwhdmi_softc * const sc = priv;
+
+	switch (mc->dev) {
+	case DWHDMI_DAI_OUTPUT_MASTER_VOLUME:
+	case DWHDMI_DAI_INPUT_DAC_VOLUME:
+		sc->sc_swvol = mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT];
+		return 0;
+	default:
+		return ENXIO;
+	}
+}
+
+static int
+dwhdmi_audio_get_port(void *priv, mixer_ctrl_t *mc)
+{
+	struct dwhdmi_softc * const sc = priv;
+
+	switch (mc->dev) {
+	case DWHDMI_DAI_OUTPUT_MASTER_VOLUME:
+	case DWHDMI_DAI_INPUT_DAC_VOLUME:
+		mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = sc->sc_swvol;
+		mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = sc->sc_swvol;
+		return 0;
+	default:
+		return ENXIO;
+	}
+}
+
+static int
+dwhdmi_audio_query_devinfo(void *priv, mixer_devinfo_t *di)
+{
+	switch (di->index) {
+	case DWHDMI_DAI_OUTPUT_CLASS:
+		di->mixer_class = di->index;
+		strcpy(di->label.name, AudioCoutputs);
+		di->type = AUDIO_MIXER_CLASS;
+		di->next = di->prev = AUDIO_MIXER_LAST;
+		return 0;
+
+	case DWHDMI_DAI_INPUT_CLASS:
+		di->mixer_class = di->index;
+		strcpy(di->label.name, AudioCinputs);
+		di->type = AUDIO_MIXER_CLASS;
+		di->next = di->prev = AUDIO_MIXER_LAST;
+		return 0;
+
+	case DWHDMI_DAI_OUTPUT_MASTER_VOLUME:
+		di->mixer_class = DWHDMI_DAI_OUTPUT_CLASS;
+		strcpy(di->label.name, AudioNmaster);
+		di->un.v.delta = 1;
+		di->un.v.num_channels = 2;
+		strcpy(di->un.v.units.name, AudioNvolume);
+		di->type = AUDIO_MIXER_VALUE;
+		di->next = di->prev = AUDIO_MIXER_LAST;
+		return 0;
+
+	case DWHDMI_DAI_INPUT_DAC_VOLUME:
+		di->mixer_class = DWHDMI_DAI_INPUT_CLASS;
+		strcpy(di->label.name, AudioNdac);
+		di->un.v.delta = 1;
+		di->un.v.num_channels = 2;
+		strcpy(di->un.v.units.name, AudioNvolume);
+		di->type = AUDIO_MIXER_VALUE;
+		di->next = di->prev = AUDIO_MIXER_LAST;
+		return 0;
+
+	default:
+		return ENXIO;
+	}
+}
+
+static const struct audio_hw_if dwhdmi_dai_hw_if = {
+	.set_format = dwhdmi_audio_set_format,
+	.set_port = dwhdmi_audio_set_port,
+	.get_port = dwhdmi_audio_get_port,
+	.query_devinfo = dwhdmi_audio_query_devinfo,
+};
+
 int
 dwhdmi_attach(struct dwhdmi_softc *sc)
 {
-	struct i2c_controller *ic = &sc->sc_ic;
+	uint8_t val;
 
 	if (sc->sc_reg_width != 1 && sc->sc_reg_width != 4) {
 		aprint_error_dev(sc->sc_dev, "unsupported register width %d\n", sc->sc_reg_width);
@@ -633,10 +877,48 @@ dwhdmi_attach(struct dwhdmi_softc *sc)
 
 	mutex_init(&sc->sc_ic_lock, MUTEX_DEFAULT, IPL_NONE);
 
-	ic->ic_cookie = sc;
-	ic->ic_acquire_bus = dwhdmi_ddc_acquire_bus;
-	ic->ic_release_bus = dwhdmi_ddc_release_bus;
-	ic->ic_exec = dwhdmi_ddc_exec;
+	sc->sc_version = dwhdmi_read(sc, HDMI_DESIGN_ID);
+	sc->sc_version <<= 8;
+	sc->sc_version |= dwhdmi_read(sc, HDMI_REVISION_ID);
+
+	sc->sc_phytype = dwhdmi_read(sc, HDMI_CONFIG2_ID);
+
+	aprint_normal_dev(sc->sc_dev, "version %x.%03x, phytype 0x%02x\n",
+	    sc->sc_version >> 12, sc->sc_version & 0xfff,
+	    sc->sc_phytype);
+
+	sc->sc_swvol = 255;
+
+	/*
+	 * If a DDC i2c bus tag is provided by the caller, use it. Otherwise,
+	 * use the I2C master built-in to DWC HDMI.
+	 */
+	if (sc->sc_ic == NULL) {
+		struct i2c_controller *ic = &sc->sc_ic_builtin;
+		ic->ic_cookie = sc;
+		ic->ic_acquire_bus = dwhdmi_ddc_acquire_bus;
+		ic->ic_release_bus = dwhdmi_ddc_release_bus;
+		ic->ic_exec = dwhdmi_ddc_exec;
+		sc->sc_ic = ic;
+	}
+
+	/*
+	 * Enable HPD on internal PHY
+	 */
+	if ((sc->sc_flags & DWHDMI_USE_INTERNAL_PHY) != 0) {
+		val = dwhdmi_read(sc, HDMI_PHY_CONF0);
+		val |= HDMI_PHY_CONF0_ENHPDRXSENSE;
+		dwhdmi_write(sc, HDMI_PHY_CONF0, val);
+	}
+
+	/*
+	 * Initialize audio DAI
+	 */
+	sc->sc_dai.dai_set_format = dwhdmi_dai_set_format;
+	sc->sc_dai.dai_add_device = dwhdmi_dai_add_device;
+	sc->sc_dai.dai_hw_if = &dwhdmi_dai_hw_if;
+	sc->sc_dai.dai_dev = sc->sc_dev;
+	sc->sc_dai.dai_priv = sc;
 
 	return 0;
 }
Index: src/sys/dev/ic/dw_hdmi.h
diff -u src/sys/dev/ic/dw_hdmi.h:1.1 src/sys/dev/ic/dw_hdmi.h:1.1.6.1
--- src/sys/dev/ic/dw_hdmi.h:1.1	Wed Jan 30 01:19:49 2019
+++ src/sys/dev/ic/dw_hdmi.h	Sat Nov 16 16:48:25 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: dw_hdmi.h,v 1.1 2019/01/30 01:19:49 jmcneill Exp $ */
+/* $NetBSD: dw_hdmi.h,v 1.1.6.1 2019/11/16 16:48:25 martin Exp $ */
 
 /*-
  * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca>
@@ -32,6 +32,8 @@
 #include <dev/i2c/i2cvar.h>
 #include <dev/i2c/ddcreg.h>
 
+#include <dev/audio/audio_dai.h>
+
 #include <drm/drmP.h>
 
 struct dwhdmi_softc;
@@ -44,20 +46,46 @@ struct dwhdmi_connector {
 	bool			monitor_audio;
 };
 
+struct dwhdmi_phy_config {
+	u_int			pixel_clock;
+	uint32_t		sym;
+	uint32_t		term;
+	uint32_t		vlev;
+};
+
+struct dwhdmi_mpll_config {
+	u_int			pixel_clock;
+	uint32_t		cpce;
+	uint32_t		gmp;
+	uint32_t		curr;
+};
+
 struct dwhdmi_softc {
 	device_t		sc_dev;
 	bus_space_tag_t		sc_bst;
 	bus_space_handle_t	sc_bsh;
 	u_int			sc_reg_width;
+	u_int			sc_flags;
+#define	DWHDMI_USE_INTERNAL_PHY	__BIT(0)
 
-	struct i2c_controller	sc_ic;
+	u_int			sc_phytype;
+	u_int			sc_version;
+
+	i2c_tag_t		sc_ic;
 	kmutex_t		sc_ic_lock;
+	struct i2c_controller	sc_ic_builtin;
+
+	struct audio_dai_device	sc_dai;
+	uint8_t			sc_swvol;
 
 	struct dwhdmi_connector	sc_connector;
 	struct drm_bridge	sc_bridge;
 
 	struct drm_display_mode	sc_curmode;
 
+	const struct dwhdmi_mpll_config *sc_mpll_config;
+	const struct dwhdmi_phy_config *sc_phy_config;
+
 	enum drm_connector_status (*sc_detect)(struct dwhdmi_softc *, bool);
 	void			(*sc_enable)(struct dwhdmi_softc *);
 	void			(*sc_disable)(struct dwhdmi_softc *);
@@ -74,4 +102,11 @@ int		dwhdmi_bind(struct dwhdmi_softc *, 
 uint8_t		dwhdmi_read(struct dwhdmi_softc *, bus_size_t);
 void		dwhdmi_write(struct dwhdmi_softc *, bus_size_t, uint8_t);
 
+enum drm_connector_status dwhdmi_phy_detect(struct dwhdmi_softc *, bool);
+void		dwhdmi_phy_enable(struct dwhdmi_softc *);
+void		dwhdmi_phy_disable(struct dwhdmi_softc *);
+void		dwhdmi_phy_mode_set(struct dwhdmi_softc *,
+				    struct drm_display_mode *,
+				    struct drm_display_mode *);
+
 #endif /* !_DEV_IC_DWHDMI_H */

Added files:

Index: src/sys/arch/arm/rockchip/rk_drm.c
diff -u /dev/null src/sys/arch/arm/rockchip/rk_drm.c:1.2.2.2
--- /dev/null	Sat Nov 16 16:48:26 2019
+++ src/sys/arch/arm/rockchip/rk_drm.c	Sat Nov 16 16:48:25 2019
@@ -0,0 +1,512 @@
+/* $NetBSD: rk_drm.c,v 1.2.2.2 2019/11/16 16:48:25 martin 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: rk_drm.c,v 1.2.2.2 2019/11/16 16:48:25 martin 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/rockchip/rk_drm.h>
+
+#define	RK_DRM_MAX_WIDTH	3840
+#define	RK_DRM_MAX_HEIGHT	2160
+
+static TAILQ_HEAD(, rk_drm_ports) rk_drm_ports =
+    TAILQ_HEAD_INITIALIZER(rk_drm_ports);
+
+static const char * const compatible[] = {
+	"rockchip,display-subsystem",
+	NULL
+};
+
+static const char * fb_compatible[] = {
+	"simple-framebuffer",
+	NULL
+};
+
+static int	rk_drm_match(device_t, cfdata_t, void *);
+static void	rk_drm_attach(device_t, device_t, void *);
+
+static void	rk_drm_init(device_t);
+static vmem_t	*rk_drm_alloc_cma_pool(struct drm_device *, size_t);
+
+static int	rk_drm_set_busid(struct drm_device *, struct drm_master *);
+
+static uint32_t	rk_drm_get_vblank_counter(struct drm_device *, unsigned int);
+static int	rk_drm_enable_vblank(struct drm_device *, unsigned int);
+static void	rk_drm_disable_vblank(struct drm_device *, unsigned int);
+
+static int	rk_drm_load(struct drm_device *, unsigned long);
+static int	rk_drm_unload(struct drm_device *);
+
+static struct drm_driver rk_drm_driver = {
+	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME,
+	.dev_priv_size = 0,
+	.load = rk_drm_load,
+	.unload = rk_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,
+
+	.get_vblank_counter = rk_drm_get_vblank_counter,
+	.enable_vblank = rk_drm_enable_vblank,
+	.disable_vblank = rk_drm_disable_vblank,
+
+	.name = DRIVER_NAME,
+	.desc = DRIVER_DESC,
+	.date = DRIVER_DATE,
+	.major = DRIVER_MAJOR,
+	.minor = DRIVER_MINOR,
+	.patchlevel = DRIVER_PATCHLEVEL,
+
+	.set_busid = rk_drm_set_busid,
+};
+
+CFATTACH_DECL_NEW(rk_drm, sizeof(struct rk_drm_softc),
+	rk_drm_match, rk_drm_attach, NULL, NULL);
+
+static int
+rk_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
+rk_drm_attach(device_t parent, device_t self, void *aux)
+{
+	struct rk_drm_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	struct drm_driver * const driver = &rk_drm_driver;
+	prop_dictionary_t dict = device_properties(self);
+	bool is_disabled;
+
+	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");
+
+	if (prop_dictionary_get_bool(dict, "disabled", &is_disabled) && is_disabled) {
+		aprint_normal(": (disabled)\n");
+		return;
+	}
+
+	aprint_normal("\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, rk_drm_init);
+}
+
+static void
+rk_drm_init(device_t dev)
+{
+	struct rk_drm_softc * const sc = device_private(dev);
+	struct drm_driver * const driver = &rk_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 vmem_t *
+rk_drm_alloc_cma_pool(struct drm_device *ddev, size_t cma_size)
+{
+	struct rk_drm_softc * const sc = rk_drm_private(ddev);
+	bus_dma_segment_t segs[1];
+	int nsegs;
+	int error;
+
+	error = bus_dmamem_alloc(sc->sc_dmat, cma_size, PAGE_SIZE, 0,
+	    segs, 1, &nsegs, BUS_DMA_NOWAIT);
+	if (error) {
+		aprint_error_dev(sc->sc_dev, "couldn't allocate CMA pool\n");
+		return NULL;
+	}
+
+	return vmem_create("rkdrm", segs[0].ds_addr, segs[0].ds_len,
+	    PAGE_SIZE, NULL, NULL, NULL, 0, VM_SLEEP, IPL_NONE);
+}
+
+static int
+rk_drm_set_busid(struct drm_device *ddev, struct drm_master *master)
+{
+	struct rk_drm_softc * const sc = rk_drm_private(ddev);
+	char id[32];
+
+	snprintf(id, sizeof(id), "platform:rk:%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
+rk_drm_fb_create_handle(struct drm_framebuffer *fb,
+    struct drm_file *file, unsigned int *handle)
+{
+	struct rk_drm_framebuffer *sfb = to_rk_drm_framebuffer(fb);
+
+	return drm_gem_handle_create(file, &sfb->obj->base, handle);
+}
+
+static void
+rk_drm_fb_destroy(struct drm_framebuffer *fb)
+{
+	struct rk_drm_framebuffer *sfb = to_rk_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 rk_drm_framebuffer_funcs = {
+	.create_handle = rk_drm_fb_create_handle,
+	.destroy = rk_drm_fb_destroy,
+};
+
+static struct drm_framebuffer *
+rk_drm_fb_create(struct drm_device *ddev, struct drm_file *file,
+    struct drm_mode_fb_cmd2 *cmd)
+{
+	struct rk_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.pitches[1] = cmd->pitches[1];
+	fb->base.pitches[2] = cmd->pitches[2];
+	fb->base.offsets[0] = cmd->offsets[0];
+	fb->base.offsets[1] = cmd->offsets[2];
+	fb->base.offsets[2] = cmd->offsets[1];
+	fb->base.width = cmd->width;
+	fb->base.height = cmd->height;
+	fb->base.pixel_format = cmd->pixel_format;
+	fb->base.bits_per_pixel = drm_format_plane_cpp(fb->base.pixel_format, 0) * 8;
+
+	switch (fb->base.pixel_format) {
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+		fb->base.depth = 32;
+		break;
+	default:
+		break;
+	}
+
+	error = drm_framebuffer_init(ddev, &fb->base, &rk_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 rk_drm_mode_config_funcs = {
+	.fb_create = rk_drm_fb_create,
+};
+
+static int
+rk_drm_fb_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes)
+{
+	struct rk_drm_softc * const sc = rk_drm_private(helper->dev);
+	struct drm_device *ddev = helper->dev;
+	struct rk_drm_framebuffer *sfb = to_rk_drm_framebuffer(helper->fb);
+	struct drm_framebuffer *fb = helper->fb;
+	struct rk_drmfb_attach_args sfa;
+	size_t cma_size;
+	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);
+
+	/* Reserve enough memory for the FB console plus a 4K plane, rounded to 1MB */
+	cma_size = size;
+	cma_size += (RK_DRM_MAX_WIDTH * RK_DRM_MAX_HEIGHT * 4);
+	cma_size = roundup(cma_size, 1024 * 1024);
+	sc->sc_ddev->cma_pool = rk_drm_alloc_cma_pool(sc->sc_ddev, cma_size);
+	if (sc->sc_ddev->cma_pool != NULL)
+		aprint_normal_dev(sc->sc_dev, "reserved %u MB DRAM for CMA\n",
+		    (u_int)(cma_size / (1024 * 1024)));
+
+	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, &rk_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, "rkfbbus", &sfa, NULL);
+	if (helper->fbdev == NULL) {
+		DRM_ERROR("unable to attach framebuffer\n");
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+static struct drm_fb_helper_funcs rk_drm_fb_helper_funcs = {
+	.fb_probe = rk_drm_fb_probe,
+};
+
+static int
+rk_drm_load(struct drm_device *ddev, unsigned long flags)
+{
+	struct rk_drm_softc * const sc = rk_drm_private(ddev);
+	struct rk_drm_ports *sport;
+	struct rk_drm_fbdev *fbdev;
+	struct fdt_endpoint *ep;
+	const u_int *data;
+	int datalen, error, num_crtc, ep_index;
+
+	drm_mode_config_init(ddev);
+	ddev->mode_config.min_width = 0;
+	ddev->mode_config.min_height = 0;
+	ddev->mode_config.max_width = RK_DRM_MAX_WIDTH;
+	ddev->mode_config.max_height = RK_DRM_MAX_HEIGHT;
+	ddev->mode_config.funcs = &rk_drm_mode_config_funcs;
+
+	num_crtc = 0;
+	data = fdtbus_get_prop(sc->sc_phandle, "ports", &datalen);
+	while (datalen >= 4) {
+		const int crtc_phandle = fdtbus_get_phandle_from_native(be32dec(data));
+
+		TAILQ_FOREACH(sport, &rk_drm_ports, entries)
+			if (sport->phandle == crtc_phandle && sport->ddev == NULL) {
+				sport->ddev = ddev;
+				for (ep_index = 0; (ep = fdt_endpoint_get_from_index(sport->port, 0, ep_index)) != NULL; ep_index++) {
+					error = fdt_endpoint_activate_direct(ep, true);
+					if (error != 0)
+						aprint_debug_dev(sc->sc_dev,
+						    "failed to activate endpoint %d: %d\n",
+						    ep_index, error);
+				}
+				num_crtc++;
+			}
+
+		datalen -= 4;
+		data++;
+	}
+
+	if (num_crtc == 0) {
+		aprint_error_dev(sc->sc_dev, "no display interface ports configured\n");
+		return ENXIO;
+	}
+
+	fbdev = kmem_zalloc(sizeof(*fbdev), KM_SLEEP);
+
+	drm_fb_helper_prepare(ddev, &fbdev->helper, &rk_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 rk_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);
+
+	/* XXX */
+	ddev->irq_enabled = true;
+	drm_vblank_init(ddev, num_crtc);
+
+	return 0;
+
+drmerr:
+	drm_mode_config_cleanup(ddev);
+	kmem_free(fbdev, sizeof(*fbdev));
+
+	return error;
+}
+
+static uint32_t
+rk_drm_get_vblank_counter(struct drm_device *ddev, unsigned int crtc)
+{
+	struct rk_drm_softc * const sc = rk_drm_private(ddev);
+
+	if (crtc >= __arraycount(sc->sc_vbl))
+		return 0;
+
+	if (sc->sc_vbl[crtc].get_vblank_counter == NULL)
+		return 0;
+
+	return sc->sc_vbl[crtc].get_vblank_counter(sc->sc_vbl[crtc].priv);
+}
+
+static int
+rk_drm_enable_vblank(struct drm_device *ddev, unsigned int crtc)
+{
+	struct rk_drm_softc * const sc = rk_drm_private(ddev);
+
+	if (crtc >= __arraycount(sc->sc_vbl))
+		return 0;
+
+	if (sc->sc_vbl[crtc].enable_vblank == NULL)
+		return 0;
+
+	sc->sc_vbl[crtc].enable_vblank(sc->sc_vbl[crtc].priv);
+
+	return 0;
+}
+
+static void
+rk_drm_disable_vblank(struct drm_device *ddev, unsigned int crtc)
+{
+	struct rk_drm_softc * const sc = rk_drm_private(ddev);
+
+	if (crtc >= __arraycount(sc->sc_vbl))
+		return;
+
+	if (sc->sc_vbl[crtc].disable_vblank == NULL)
+		return;
+
+	sc->sc_vbl[crtc].disable_vblank(sc->sc_vbl[crtc].priv);
+}
+
+static int
+rk_drm_unload(struct drm_device *ddev)
+{
+	drm_mode_config_cleanup(ddev);
+
+	return 0;
+}
+
+int
+rk_drm_register_port(int phandle, struct fdt_device_ports *port)
+{
+	struct rk_drm_ports *sport;
+
+	sport = kmem_zalloc(sizeof(*sport), KM_SLEEP);
+	sport->phandle = phandle;
+	sport->port = port;
+	sport->ddev = NULL;
+	TAILQ_INSERT_TAIL(&rk_drm_ports, sport, entries);
+
+	return 0;
+}
+
+struct drm_device *
+rk_drm_port_device(struct fdt_device_ports *port)
+{
+	struct rk_drm_ports *sport;
+
+	TAILQ_FOREACH(sport, &rk_drm_ports, entries)
+		if (sport->port == port)
+			return sport->ddev;
+
+	return NULL;
+}
Index: src/sys/arch/arm/rockchip/rk_vop.c
diff -u /dev/null src/sys/arch/arm/rockchip/rk_vop.c:1.2.2.2
--- /dev/null	Sat Nov 16 16:48:26 2019
+++ src/sys/arch/arm/rockchip/rk_vop.c	Sat Nov 16 16:48:25 2019
@@ -0,0 +1,659 @@
+/* $NetBSD: rk_vop.c,v 1.2.2.2 2019/11/16 16:48:25 martin 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: rk_vop.c,v 1.2.2.2 2019/11/16 16:48:25 martin 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 <sys/sysctl.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/rockchip/rk_drm.h>
+
+#define	VOP_REG_CFG_DONE		0x0000
+#define	 REG_LOAD_EN			__BIT(0)
+#define	VOP_SYS_CTRL			0x0008
+#define	 VOP_STANDBY_EN			__BIT(22)
+#define	 MIPI_OUT_EN			__BIT(15)
+#define	 EDP_OUT_EN			__BIT(14)
+#define	 HDMI_OUT_EN			__BIT(13)
+#define	 RGB_OUT_EN			__BIT(12)
+#define	VOP_DSP_CTRL0			0x0010
+#define	 DSP_OUT_MODE			__BITS(3,0)
+#define	  DSP_OUT_MODE_RGB888		0
+#define	  DSP_OUT_MODE_RGBaaa		15
+#define	VOP_DSP_CTRL1			0x0014
+#define	VOP_WIN0_CTRL			0x0030
+#define	 WIN0_LB_MODE			__BITS(7,5)
+#define	  WIN0_LB_MODE_RGB_3840X2	2
+#define	  WIN0_LB_MODE_RGB_2560X4	3
+#define	  WIN0_LB_MODE_RGB_1920X5	4
+#define	  WIN0_LB_MODE_RGB_1280X8	5
+#define	 WIN0_DATA_FMT			__BITS(3,1)
+#define	  WIN0_DATA_FMT_ARGB888		0
+#define	 WIN0_EN			__BIT(0)
+#define	VOP_WIN0_COLOR_KEY		0x0038
+#define	VOP_WIN0_VIR			0x003c
+#define	 WIN0_VIR_STRIDE		__BITS(13,0)
+#define	VOP_WIN0_YRGB_MST		0x0040
+#define	VOP_WIN0_ACT_INFO		0x0048
+#define	 WIN0_ACT_HEIGHT		__BITS(28,16)
+#define	 WIN0_ACT_WIDTH			__BITS(12,0)
+#define	VOP_WIN0_DSP_INFO		0x004c
+#define	 WIN0_DSP_HEIGHT		__BITS(27,16)
+#define	 WIN0_DSP_WIDTH			__BITS(11,0)
+#define	VOP_WIN0_DSP_ST			0x0050
+#define	 WIN0_DSP_YST			__BITS(28,16)
+#define	 WIN0_DSP_XST			__BITS(12,0)
+#define	VOP_POST_DSP_HACT_INFO		0x0170
+#define	 DSP_HACT_ST_POST		__BITS(28,16)
+#define	 DSP_HACT_END_POST		__BITS(12,0)
+#define	VOP_POST_DSP_VACT_INFO		0x0174
+#define	 DSP_VACT_ST_POST		__BITS(28,16)
+#define	 DSP_VACT_END_POST		__BITS(12,0)
+#define	VOP_DSP_HTOTAL_HS_END		0x0188
+#define	 DSP_HS_END			__BITS(28,16)
+#define	 DSP_HTOTAL			__BITS(12,0)
+#define	VOP_DSP_HACT_ST_END		0x018c
+#define	 DSP_HACT_ST			__BITS(28,16)
+#define	 DSP_HACT_END			__BITS(12,0)
+#define	VOP_DSP_VTOTAL_VS_END		0x0190
+#define	 DSP_VS_END			__BITS(28,16)
+#define	 DSP_VTOTAL			__BITS(12,0)
+#define	VOP_DSP_VACT_ST_END		0x0194
+#define	 DSP_VACT_ST			__BITS(28,16)
+#define	 DSP_VACT_END			__BITS(12,0)
+
+/*
+ * Polarity fields are in different locations depending on SoC and output type,
+ * but always in the same order.
+ */
+#define	DSP_DCLK_POL			__BIT(3)
+#define	DSP_DEN_POL			__BIT(2)
+#define	DSP_VSYNC_POL			__BIT(1)
+#define	DSP_HSYNC_POL			__BIT(0)
+
+enum vop_ep_type {
+	VOP_EP_MIPI,
+	VOP_EP_EDP,
+	VOP_EP_HDMI,
+	VOP_EP_MIPI1,
+	VOP_EP_DP,
+	VOP_NEP
+};
+
+struct rk_vop_softc;
+struct rk_vop_config;
+
+struct rk_vop_crtc {
+	struct drm_crtc		base;
+	struct rk_vop_softc	*sc;
+};
+
+struct rk_vop_encoder {
+	struct drm_encoder	base;
+	struct rk_vop_softc	*sc;
+	enum vop_ep_type	ep_type;
+};
+
+struct rk_vop_softc {
+	device_t		sc_dev;
+	bus_space_tag_t		sc_bst;
+	bus_space_handle_t	sc_bsh;
+	int			sc_phandle;
+
+	struct clk		*sc_dclk;
+
+	struct rk_vop_crtc	sc_crtc;
+	struct rk_vop_encoder	sc_encoder[VOP_NEP];
+
+	struct fdt_device_ports	sc_ports;
+
+	struct rk_vop_config	*sc_conf;
+};
+
+#define	to_rk_vop_crtc(x)	container_of(x, struct rk_vop_crtc, base)
+#define	to_rk_vop_encoder(x)	container_of(x, struct rk_vop_encoder, base)
+
+#define	RD4(sc, reg)				\
+	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
+#define	WR4(sc, reg, val)			\
+	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
+
+struct rk_vop_config {
+	const char		*descr;
+	u_int			out_mode;
+	void			(*init)(struct rk_vop_softc *);
+	void			(*set_polarity)(struct rk_vop_softc *,
+						enum vop_ep_type, uint32_t);
+};
+
+#define	RK3399_VOP_MIPI_POL	__BITS(31,28)
+#define	RK3399_VOP_EDP_POL	__BITS(27,24)
+#define	RK3399_VOP_HDMI_POL	__BITS(23,20)
+#define	RK3399_VOP_DP_POL	__BITS(19,16)
+
+#define	RK3399_VOP_SYS_CTRL_ENABLE	__BIT(11)
+
+static void
+rk3399_vop_set_polarity(struct rk_vop_softc *sc, enum vop_ep_type ep_type, uint32_t pol)
+{
+	uint32_t mask, val;
+
+	switch (ep_type) {
+	case VOP_EP_MIPI:
+	case VOP_EP_MIPI1:
+		mask = RK3399_VOP_MIPI_POL;
+		break;
+	case VOP_EP_EDP:
+		mask = RK3399_VOP_EDP_POL;
+		break;
+	case VOP_EP_HDMI:
+		mask = RK3399_VOP_HDMI_POL;
+		break;
+	case VOP_EP_DP:
+		mask = RK3399_VOP_DP_POL;
+		break;
+	default:
+		return;
+	}
+
+	val = RD4(sc, VOP_DSP_CTRL1);
+	val &= ~mask;
+	val |= __SHIFTIN(pol, mask);
+	WR4(sc, VOP_DSP_CTRL1, val);
+}
+
+static void
+rk3399_vop_init(struct rk_vop_softc *sc)
+{
+	uint32_t val;
+
+	val = RD4(sc, VOP_SYS_CTRL);
+	val |= RK3399_VOP_SYS_CTRL_ENABLE;
+	WR4(sc, VOP_SYS_CTRL, val);
+}
+
+static const struct rk_vop_config rk3399_vop_lit_config = {
+	.descr = "RK3399 VOPL",
+	.out_mode = DSP_OUT_MODE_RGB888,
+	.init = rk3399_vop_init,
+	.set_polarity = rk3399_vop_set_polarity,
+};
+
+static const struct rk_vop_config rk3399_vop_big_config = {
+	.descr = "RK3399 VOPB",
+	.out_mode = DSP_OUT_MODE_RGBaaa,
+	.init = rk3399_vop_init,
+	.set_polarity = rk3399_vop_set_polarity,
+};
+
+static const struct of_compat_data compat_data[] = {
+	{ "rockchip,rk3399-vop-big",		(uintptr_t)&rk3399_vop_big_config },
+	{ "rockchip,rk3399-vop-lit",		(uintptr_t)&rk3399_vop_lit_config },
+	{ NULL }
+};
+
+static int
+rk_vop_mode_do_set_base(struct drm_crtc *crtc, struct drm_framebuffer *fb,
+    int x, int y, int atomic)
+{
+	struct rk_vop_crtc *mixer_crtc = to_rk_vop_crtc(crtc);
+	struct rk_vop_softc * const sc = mixer_crtc->sc;
+	struct rk_drm_framebuffer *sfb = atomic?
+	    to_rk_drm_framebuffer(fb) :
+	    to_rk_drm_framebuffer(crtc->primary->fb);
+
+	uint64_t paddr = (uint64_t)sfb->obj->dmamap->dm_segs[0].ds_addr;
+
+	KASSERT((paddr & ~0xffffffff) == 0);
+
+	/* Framebuffer start address */
+	WR4(sc, VOP_WIN0_YRGB_MST, (uint32_t)paddr);
+
+	return 0;
+}
+
+static void
+rk_vop_destroy(struct drm_crtc *crtc)
+{
+	drm_crtc_cleanup(crtc);
+}
+
+static const struct drm_crtc_funcs rk_vop_crtc_funcs = {
+	.set_config = drm_crtc_helper_set_config,
+	.destroy = rk_vop_destroy,
+};
+
+static void
+rk_vop_dpms(struct drm_crtc *crtc, int mode)
+{
+}
+
+static bool
+rk_vop_mode_fixup(struct drm_crtc *crtc,
+    const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+static int
+rk_vop_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 rk_vop_crtc *mixer_crtc = to_rk_vop_crtc(crtc);
+	struct rk_vop_softc * const sc = mixer_crtc->sc;
+	uint32_t val;
+	u_int lb_mode;
+	int error;
+
+	const u_int hactive = adjusted_mode->hdisplay;
+	const u_int hsync_len = adjusted_mode->hsync_end - adjusted_mode->hsync_start;
+	const u_int hback_porch = adjusted_mode->htotal - adjusted_mode->hsync_end;
+
+	const u_int vactive = adjusted_mode->vdisplay;
+	const u_int vsync_len = adjusted_mode->vsync_end - adjusted_mode->vsync_start;
+	const u_int vback_porch = adjusted_mode->vtotal - adjusted_mode->vsync_end;
+
+	error = clk_set_rate(sc->sc_dclk, adjusted_mode->clock * 1000);
+	if (error != 0)
+		DRM_ERROR("couldn't set pixel clock: %d\n", error);
+
+	val = __SHIFTIN(hactive - 1, WIN0_ACT_WIDTH) |
+	      __SHIFTIN(vactive - 1, WIN0_ACT_HEIGHT);
+	WR4(sc, VOP_WIN0_ACT_INFO, val);
+
+	val = __SHIFTIN(hactive - 1, WIN0_DSP_WIDTH) |
+	      __SHIFTIN(vactive - 1, WIN0_DSP_HEIGHT);
+	WR4(sc, VOP_WIN0_DSP_INFO, val);
+
+	val = __SHIFTIN(hsync_len + hback_porch, WIN0_DSP_XST) |
+	      __SHIFTIN(vsync_len + vback_porch, WIN0_DSP_YST);
+	WR4(sc, VOP_WIN0_DSP_ST, val);
+
+	WR4(sc, VOP_WIN0_COLOR_KEY, 0);
+
+	val = __SHIFTIN(hactive, WIN0_VIR_STRIDE);
+	WR4(sc, VOP_WIN0_VIR, val);
+
+	if (adjusted_mode->hdisplay > 2560)
+		lb_mode = WIN0_LB_MODE_RGB_3840X2;
+	else if (adjusted_mode->hdisplay > 1920)
+		lb_mode = WIN0_LB_MODE_RGB_2560X4;
+	else if (adjusted_mode->hdisplay > 1280)
+		lb_mode = WIN0_LB_MODE_RGB_1920X5;
+	else
+		lb_mode = WIN0_LB_MODE_RGB_1280X8;
+
+	val = __SHIFTIN(lb_mode, WIN0_LB_MODE) |
+	      __SHIFTIN(WIN0_DATA_FMT_ARGB888, WIN0_DATA_FMT) |
+	      WIN0_EN;
+	WR4(sc, VOP_WIN0_CTRL, val);
+
+	rk_vop_mode_do_set_base(crtc, old_fb, x, y, 0);
+
+	return 0;
+}
+
+static int
+rk_vop_mode_set_base(struct drm_crtc *crtc, int x, int y,
+    struct drm_framebuffer *old_fb)
+{
+	struct rk_vop_crtc *mixer_crtc = to_rk_vop_crtc(crtc);
+	struct rk_vop_softc * const sc = mixer_crtc->sc;
+
+	rk_vop_mode_do_set_base(crtc, old_fb, x, y, 0);
+
+	/* Commit settings */
+	WR4(sc, VOP_REG_CFG_DONE, REG_LOAD_EN);
+
+	return 0;
+}
+
+static int
+rk_vop_mode_set_base_atomic(struct drm_crtc *crtc, struct drm_framebuffer *fb,
+    int x, int y, enum mode_set_atomic state)
+{
+	struct rk_vop_crtc *mixer_crtc = to_rk_vop_crtc(crtc);
+	struct rk_vop_softc * const sc = mixer_crtc->sc;
+
+	rk_vop_mode_do_set_base(crtc, fb, x, y, 1);
+
+	/* Commit settings */
+	WR4(sc, VOP_REG_CFG_DONE, REG_LOAD_EN);
+
+	return 0;
+}
+
+static void
+rk_vop_disable(struct drm_crtc *crtc)
+{
+}
+
+static void
+rk_vop_prepare(struct drm_crtc *crtc)
+{
+}
+
+static void
+rk_vop_commit(struct drm_crtc *crtc)
+{
+	struct rk_vop_crtc *mixer_crtc = to_rk_vop_crtc(crtc);
+	struct rk_vop_softc * const sc = mixer_crtc->sc;
+
+	/* Commit settings */
+	WR4(sc, VOP_REG_CFG_DONE, REG_LOAD_EN);
+}
+
+static const struct drm_crtc_helper_funcs rk_vop_crtc_helper_funcs = {
+	.dpms = rk_vop_dpms,
+	.mode_fixup = rk_vop_mode_fixup,
+	.mode_set = rk_vop_mode_set,
+	.mode_set_base = rk_vop_mode_set_base,
+	.mode_set_base_atomic = rk_vop_mode_set_base_atomic,
+	.disable = rk_vop_disable,
+	.prepare = rk_vop_prepare,
+	.commit = rk_vop_commit,
+};
+
+static void
+rk_vop_encoder_destroy(struct drm_encoder *encoder)
+{
+}
+
+static const struct drm_encoder_funcs rk_vop_encoder_funcs = {
+	.destroy = rk_vop_encoder_destroy,
+};
+
+static void
+rk_vop_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+}
+
+static bool
+rk_vop_encoder_mode_fixup(struct drm_encoder *encoder,
+    const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
+{
+        return true;
+}
+
+static void
+rk_vop_encoder_mode_set(struct drm_encoder *encoder,
+    struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
+{
+	struct rk_vop_encoder *rkencoder = to_rk_vop_encoder(encoder);
+	struct rk_vop_softc * const sc = rkencoder->sc;
+	uint32_t val;
+	u_int pol;
+
+	const u_int hactive = adjusted_mode->hdisplay;
+	const u_int hfront_porch = adjusted_mode->hsync_start - adjusted_mode->hdisplay;
+	const u_int hsync_len = adjusted_mode->hsync_end - adjusted_mode->hsync_start;
+	const u_int hback_porch = adjusted_mode->htotal - adjusted_mode->hsync_end;
+
+	const u_int vactive = adjusted_mode->vdisplay;
+	const u_int vfront_porch = adjusted_mode->vsync_start - adjusted_mode->vdisplay;
+	const u_int vsync_len = adjusted_mode->vsync_end - adjusted_mode->vsync_start;
+	const u_int vback_porch = adjusted_mode->vtotal - adjusted_mode->vsync_end;
+
+	pol = DSP_DCLK_POL;
+	if ((adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) != 0)
+		pol |= DSP_HSYNC_POL;
+	if ((adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) != 0)
+		pol |= DSP_VSYNC_POL;
+	sc->sc_conf->set_polarity(sc, rkencoder->ep_type, pol);
+
+	val = RD4(sc, VOP_SYS_CTRL);
+	val &= ~VOP_STANDBY_EN;
+	val &= ~(MIPI_OUT_EN|EDP_OUT_EN|HDMI_OUT_EN|RGB_OUT_EN);
+	switch (rkencoder->ep_type) {
+	case VOP_EP_MIPI:
+	case VOP_EP_MIPI1:
+		val |= MIPI_OUT_EN;
+		break;
+	case VOP_EP_EDP:
+	case VOP_EP_DP:
+		val |= EDP_OUT_EN;
+		break;
+	case VOP_EP_HDMI:
+		val |= HDMI_OUT_EN;
+		break;
+	default:
+		break;
+	}
+	WR4(sc, VOP_SYS_CTRL, val);
+
+	val = RD4(sc, VOP_DSP_CTRL0);
+	val &= ~DSP_OUT_MODE;
+	val |= __SHIFTIN(sc->sc_conf->out_mode, DSP_OUT_MODE);
+	WR4(sc, VOP_DSP_CTRL0, val);
+
+	val = __SHIFTIN(hsync_len + hback_porch, DSP_HACT_ST_POST) |
+	      __SHIFTIN(hsync_len + hback_porch + hactive, DSP_HACT_END_POST);
+	WR4(sc, VOP_POST_DSP_HACT_INFO, val);
+
+	val = __SHIFTIN(hsync_len + hback_porch, DSP_HACT_ST) |
+	      __SHIFTIN(hsync_len + hback_porch + hactive, DSP_HACT_END);
+	WR4(sc, VOP_DSP_HACT_ST_END, val);
+
+	val = __SHIFTIN(hsync_len, DSP_HTOTAL) |
+	      __SHIFTIN(hsync_len + hback_porch + hactive + hfront_porch, DSP_HS_END);
+	WR4(sc, VOP_DSP_HTOTAL_HS_END, val);
+
+	val = __SHIFTIN(vsync_len + vback_porch, DSP_VACT_ST_POST) |
+	      __SHIFTIN(vsync_len + vback_porch + vactive, DSP_VACT_END_POST);
+	WR4(sc, VOP_POST_DSP_VACT_INFO, val);
+
+	val = __SHIFTIN(vsync_len + vback_porch, DSP_VACT_ST) |
+	      __SHIFTIN(vsync_len + vback_porch + vactive, DSP_VACT_END);
+	WR4(sc, VOP_DSP_VACT_ST_END, val);
+
+	val = __SHIFTIN(vsync_len, DSP_VTOTAL) |
+	      __SHIFTIN(vsync_len + vback_porch + vactive + vfront_porch, DSP_VS_END);
+	WR4(sc, VOP_DSP_VTOTAL_VS_END, val);
+}
+
+static void
+rk_vop_encoder_prepare(struct drm_encoder *encoder)
+{
+}
+
+static void
+rk_vop_encoder_commit(struct drm_encoder *encoder)
+{
+	struct rk_vop_encoder *rkencoder = to_rk_vop_encoder(encoder);
+	struct rk_vop_softc * const sc = rkencoder->sc;
+
+	/* Commit settings */
+	WR4(sc, VOP_REG_CFG_DONE, REG_LOAD_EN);
+}
+
+static const struct drm_encoder_helper_funcs rk_vop_encoder_helper_funcs = {
+	.dpms = rk_vop_encoder_dpms,
+	.mode_fixup = rk_vop_encoder_mode_fixup,
+	.prepare = rk_vop_encoder_prepare,
+	.commit = rk_vop_encoder_commit,
+	.mode_set = rk_vop_encoder_mode_set,
+};
+
+static int
+rk_vop_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
+{
+	struct rk_vop_softc * const sc = device_private(dev);
+	struct drm_device *ddev;
+	u_int encoder_type;
+
+	if (!activate)
+		return EINVAL;
+
+	ddev = rk_drm_port_device(&sc->sc_ports);
+	if (ddev == NULL) {
+		DRM_ERROR("couldn't find DRM device\n");
+		return ENXIO;
+	}
+
+	if (sc->sc_crtc.sc == NULL) {
+		sc->sc_crtc.sc = sc;
+
+		drm_crtc_init(ddev, &sc->sc_crtc.base, &rk_vop_crtc_funcs);
+		drm_crtc_helper_add(&sc->sc_crtc.base, &rk_vop_crtc_helper_funcs);
+
+		aprint_debug_dev(dev, "using CRTC %d for %s\n",
+		    drm_crtc_index(&sc->sc_crtc.base), sc->sc_conf->descr);
+	}
+
+	const u_int ep_index = fdt_endpoint_index(ep);
+	if (ep_index >= VOP_NEP) {
+		DRM_ERROR("endpoint index %d out of range\n", ep_index);
+		return ENXIO;
+	}
+
+	switch (ep_index) {
+	case VOP_EP_MIPI:
+	case VOP_EP_MIPI1:
+		encoder_type = DRM_MODE_ENCODER_DSI;
+		break;
+	case VOP_EP_HDMI:
+	case VOP_EP_EDP:
+	case VOP_EP_DP:
+		encoder_type = DRM_MODE_ENCODER_TMDS;
+		break;
+	}
+
+	sc->sc_encoder[ep_index].sc = sc;
+	sc->sc_encoder[ep_index].ep_type = ep_index;
+	sc->sc_encoder[ep_index].base.possible_crtcs = 1 << drm_crtc_index(&sc->sc_crtc.base);
+	drm_encoder_init(ddev, &sc->sc_encoder[ep_index].base, &rk_vop_encoder_funcs,
+	    encoder_type);
+	drm_encoder_helper_add(&sc->sc_encoder[ep_index].base, &rk_vop_encoder_helper_funcs);
+
+	return fdt_endpoint_activate(ep, activate);
+}
+
+static void *
+rk_vop_ep_get_data(device_t dev, struct fdt_endpoint *ep)
+{
+	struct rk_vop_softc * const sc = device_private(dev);
+	const u_int ep_index = fdt_endpoint_index(ep);
+
+	if (ep_index >= VOP_NEP)
+		return NULL;
+
+	if (sc->sc_encoder[ep_index].sc == NULL)
+		return NULL;
+
+	return &sc->sc_encoder[ep_index].base;
+}
+
+static int
+rk_vop_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
+rk_vop_attach(device_t parent, device_t self, void *aux)
+{
+	struct rk_vop_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	const char * const reset_names[] = { "axi", "ahb", "dclk" };
+	const char * const clock_names[] = { "aclk_vop", "hclk_vop" };
+	struct fdtbus_reset *rst;
+	bus_addr_t addr;
+	bus_size_t size;
+	u_int n;
+
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+		return;
+	}
+
+	fdtbus_clock_assign(phandle);
+
+	for (n = 0; n < __arraycount(reset_names); n++) {
+		rst = fdtbus_reset_get(phandle, reset_names[n]);
+		if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
+			aprint_error(": couldn't de-assert reset %s\n", reset_names[n]);
+			return;
+		}
+	}
+	for (n = 0; n < __arraycount(clock_names); n++) {
+		if (fdtbus_clock_enable(phandle, clock_names[n], true) != 0) {
+			aprint_error(": couldn't enable clock %s\n", clock_names[n]);
+			return;
+		}
+	}
+	sc->sc_dclk = fdtbus_clock_get(phandle, "dclk_vop");
+	if (sc->sc_dclk == NULL || clk_enable(sc->sc_dclk) != 0) {
+		aprint_error(": couldn't enable clock %s\n", "dclk_vop");
+		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_conf = (void *)of_search_compatible(phandle, compat_data)->data;
+
+	aprint_naive("\n");
+	aprint_normal(": %s\n", sc->sc_conf->descr);
+
+	if (sc->sc_conf->init != NULL)
+		sc->sc_conf->init(sc);
+
+	sc->sc_ports.dp_ep_activate = rk_vop_ep_activate;
+	sc->sc_ports.dp_ep_get_data = rk_vop_ep_get_data;
+	fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_ENCODER);
+
+	const int port_phandle = of_find_firstchild_byname(phandle, "port");
+	if (port_phandle > 0)
+		rk_drm_register_port(port_phandle, &sc->sc_ports);
+}
+
+CFATTACH_DECL_NEW(rk_vop, sizeof(struct rk_vop_softc),
+	rk_vop_match, rk_vop_attach, NULL, NULL);

Index: src/sys/arch/arm/rockchip/rk_drm.h
diff -u /dev/null src/sys/arch/arm/rockchip/rk_drm.h:1.1.2.2
--- /dev/null	Sat Nov 16 16:48:26 2019
+++ src/sys/arch/arm/rockchip/rk_drm.h	Sat Nov 16 16:48:25 2019
@@ -0,0 +1,99 @@
+/* $NetBSD: rk_drm.h,v 1.1.2.2 2019/11/16 16:48:25 martin 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_RK_DRM_H
+#define _ARM_RK_DRM_H
+
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#define DRIVER_AUTHOR		"Jared McNeill"
+
+#define DRIVER_NAME		"rk"
+#define DRIVER_DESC		"Rockchip Display Subsystem"
+#define DRIVER_DATE		"20191109"
+
+#define DRIVER_MAJOR		1
+#define DRIVER_MINOR		0
+#define DRIVER_PATCHLEVEL	0
+
+struct rk_framebuffer;
+
+#define	RK_DRM_MAX_CRTC	2
+
+struct rk_drm_vblank {
+	void			*priv;
+	void			(*enable_vblank)(void *);
+	void			(*disable_vblank)(void *);
+	uint32_t		(*get_vblank_counter)(void *);
+};
+
+struct rk_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 rk_drm_vblank	sc_vbl[RK_DRM_MAX_CRTC];
+};
+
+struct rk_drm_framebuffer {
+	struct drm_framebuffer	base;
+	struct drm_gem_cma_object *obj;
+};
+
+struct rk_drm_ports {
+	int			phandle;
+	struct fdt_device_ports	*port;
+	struct drm_device	*ddev;
+	TAILQ_ENTRY(rk_drm_ports) entries;
+};
+
+struct rk_drm_fbdev {
+	struct drm_fb_helper	helper;
+};
+
+struct rk_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 rk_drm_private(ddev)		(ddev)->dev_private
+#define	to_rk_drm_framebuffer(x)	container_of(x, struct rk_drm_framebuffer, base)
+
+int	rk_drm_register_port(int, struct fdt_device_ports *);
+struct drm_device *rk_drm_port_device(struct fdt_device_ports *);
+
+#endif /* _ARM_RK_DRM_H */
Index: src/sys/arch/arm/rockchip/rk_fb.c
diff -u /dev/null src/sys/arch/arm/rockchip/rk_fb.c:1.1.2.2
--- /dev/null	Sat Nov 16 16:48:26 2019
+++ src/sys/arch/arm/rockchip/rk_fb.c	Sat Nov 16 16:48:25 2019
@@ -0,0 +1,160 @@
+/* $NetBSD: rk_fb.c,v 1.1.2.2 2019/11/16 16:48:25 martin 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: rk_fb.c,v 1.1.2.2 2019/11/16 16:48:25 martin 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/rockchip/rk_drm.h>
+
+static int	rk_fb_match(device_t, cfdata_t, void *);
+static void	rk_fb_attach(device_t, device_t, void *);
+
+static bool	rk_fb_shutdown(device_t, int);
+
+struct rk_fb_softc {
+	struct drmfb_softc	sc_drmfb;
+	device_t		sc_dev;
+	struct rk_drm_framebuffer *sc_fb;
+	struct rk_drmfb_attach_args sc_sfa;
+};
+
+static paddr_t	rk_fb_mmapfb(struct drmfb_softc *, off_t, int);
+static int	rk_fb_ioctl(struct drmfb_softc *, u_long, void *, int,
+			       lwp_t *);
+
+static const struct drmfb_params rkfb_drmfb_params = {
+	.dp_mmapfb = rk_fb_mmapfb,
+	.dp_ioctl = rk_fb_ioctl,
+	
+};
+
+CFATTACH_DECL_NEW(rk_fb, sizeof(struct rk_fb_softc),
+	rk_fb_match, rk_fb_attach, NULL, NULL);
+
+static int
+rk_fb_match(device_t parent, cfdata_t cf, void *aux)
+{
+	return 1;
+}
+
+static void
+rk_fb_attach(device_t parent, device_t self, void *aux)
+{
+	struct rk_fb_softc * const sc = device_private(self);
+	struct rk_drmfb_attach_args * const sfa = aux;
+	int error;
+
+	sc->sc_dev = self;
+	sc->sc_sfa = *sfa;
+	sc->sc_fb = to_rk_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 = &rkfb_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, rk_fb_shutdown);
+}
+
+static bool
+rk_fb_shutdown(device_t self, int flags)
+{
+	struct rk_fb_softc * const sc = device_private(self);
+
+	return drmfb_shutdown(&sc->sc_drmfb, flags);
+}
+
+static paddr_t
+rk_fb_mmapfb(struct drmfb_softc *sc, off_t off, int prot)
+{
+	struct rk_fb_softc * const tfb_sc = (struct rk_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
+rk_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/rockchip/rk_i2s.c
diff -u /dev/null src/sys/arch/arm/rockchip/rk_i2s.c:1.1.2.2
--- /dev/null	Sat Nov 16 16:48:26 2019
+++ src/sys/arch/arm/rockchip/rk_i2s.c	Sat Nov 16 16:48:25 2019
@@ -0,0 +1,638 @@
+/* $NetBSD: rk_i2s.c,v 1.1.2.2 2019/11/16 16:48:25 martin 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: rk_i2s.c,v 1.1.2.2 2019/11/16 16:48:25 martin Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/cpu.h>
+#include <sys/device.h>
+#include <sys/kmem.h>
+
+#include <sys/audioio.h>
+#include <dev/audio/audio_if.h>
+#include <dev/audio/linear.h>
+
+#include <dev/fdt/fdtvar.h>
+#include <dev/fdt/syscon.h>
+
+#define	RK_I2S_FIFO_DEPTH	32
+#define	RK_I2S_SAMPLE_RATE	48000
+
+#define	I2S_TXCR		0x00
+#define	 TXCR_RCNT			__BITS(22,17)
+#define	 TXCR_TCSR			__BITS(16,15)
+#define	 TXCR_HWT			__BIT(14)
+#define	 TXCR_SJM			__BIT(12)
+#define	 TXCR_FBM			__BIT(11)
+#define	 TXCR_IBM			__BITS(10,9)
+#define	 TXCR_PBM			__BITS(8,7)
+#define	 TXCR_TFS			__BIT(5)
+#define	 TXCR_VDW			__BITS(4,0)
+#define	I2S_RXCR		0x04
+#define	 RXCR_RCSR			__BITS(16,15)
+#define	 RXCR_HWT			__BIT(14)
+#define	 RXCR_SJM			__BIT(12)
+#define	 RXCR_FBM			__BIT(11)
+#define	 RXCR_IBM			__BITS(10,9)
+#define	 RXCR_PBM			__BITS(8,7)
+#define	 RXCR_TFS			__BIT(5)
+#define	 RXCR_VDW			__BITS(4,0)
+#define	I2S_CKR			0x08
+#define	 CKR_TRCM			__BITS(29,28)
+#define	 CKR_MSS			__BIT(27)
+#define	 CKR_CKP			__BIT(26)
+#define	 CKR_RLP			__BIT(25)
+#define	 CKR_TLP			__BIT(24)
+#define	 CKR_MDIV			__BITS(23,16)
+#define	 CKR_RSD			__BITS(15,8)
+#define	 CKR_TSD			__BITS(7,0)
+#define	I2S_TXFIFOLR		0x0c
+#define	 TXFIFOLR_TFL(n)		__BITS((n) * 6 + 5, (n) * 6)
+#define	I2S_DMACR		0x10
+#define	 DMACR_RDE			__BIT(24)
+#define	 DMACR_RDL			__BITS(20,16)
+#define	 DMACR_TDE			__BIT(8)
+#define	 DMACR_TDL			__BITS(4,0)
+#define	I2S_INTCR		0x14
+#define	 INTCR_RFT			__BITS(24,20)
+#define	 INTCR_RXOIC			__BIT(18)
+#define	 INTCR_RXOIE			__BIT(17)
+#define	 INTCR_RXFIE			__BIT(16)
+#define	 INTCR_TFT			__BITS(8,4)
+#define	 INTCR_TXUIC			__BIT(2)
+#define	 INTCR_TXUIE			__BIT(1)
+#define	 INTCR_TXEIE			__BIT(0)
+#define	I2S_INTSR		0x18
+#define	 INTSR_RXOI			__BIT(17)
+#define	 INTSR_RXFI			__BIT(16)
+#define	 INTSR_TXUI			__BIT(1)
+#define	 INTSR_TXEI			__BIT(0)
+#define	I2S_XFER		0x1c
+#define	 XFER_RXS			__BIT(1)
+#define	 XFER_TXS			__BIT(0)
+#define	I2S_CLR			0x20
+#define	 CLR_RXC			__BIT(1)
+#define	 CLR_TXC			__BIT(0)
+#define	I2S_TXDR		0x24
+#define	I2S_RXDR		0x28
+#define	I2S_RXFIFOLR		0x2c
+#define	 RXFIFOLR_RFL(n)		__BITS((n) * 6 + 5, (n) * 6)
+
+struct rk_i2s_config {
+	bus_size_t		oe_reg;
+	u_int			oe_mask;
+	u_int			oe_val;
+};
+
+static const struct rk_i2s_config rk3399_i2s_config = {
+	.oe_reg = 0x0e220,
+	.oe_mask = __BITS(13,11),
+	.oe_val = 0x7,
+};
+
+static const struct of_compat_data compat_data[] = {
+	{ "rockchip,rk3399-i2s",	(uintptr_t)&rk3399_i2s_config },
+	{ NULL }
+};
+
+struct rk_i2s_softc;
+
+struct rk_i2s_chan {
+	uint32_t		*ch_start;
+	uint32_t		*ch_end;
+	uint32_t		*ch_cur;
+
+	int			ch_blksize;
+	int			ch_resid;
+
+	void			(*ch_intr)(void *);
+	void			*ch_intrarg;
+};
+
+struct rk_i2s_softc {
+	device_t		sc_dev;
+	bus_space_tag_t		sc_bst;
+	bus_space_handle_t	sc_bsh;
+	int			sc_phandle;
+	struct clk		*sc_clk;
+	struct syscon		*sc_grf;
+	const struct rk_i2s_config *sc_conf;
+
+	kmutex_t		sc_lock;
+	kmutex_t		sc_intr_lock;
+
+	struct audio_format	sc_format;
+
+	struct rk_i2s_chan	sc_pchan;
+	struct rk_i2s_chan	sc_rchan;
+
+	u_int			sc_active;
+
+	struct audio_dai_device	sc_dai;
+};
+
+#define	RD4(sc, reg)			\
+	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
+#define	WR4(sc, reg, val)		\
+	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
+
+static int
+rk_i2s_query_format(void *priv, audio_format_query_t *afp)
+{
+	struct rk_i2s_softc * const sc = priv;
+
+	return audio_query_format(&sc->sc_format, 1, afp);
+}
+
+static int
+rk_i2s_set_format(void *priv, int setmode,
+    const audio_params_t *play, const audio_params_t *rec,
+    audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
+{
+	struct rk_i2s_softc * const sc = priv;
+	uint32_t ckr, txcr, rxcr;
+
+	ckr = RD4(sc, I2S_CKR);
+	if ((ckr & CKR_MSS) == 0) {
+		const u_int mclk_rate = clk_get_rate(sc->sc_clk);
+		device_printf(sc->sc_dev, "%s: sysclk rate %u Hz\n", __func__, mclk_rate);
+		const u_int bclk_rate = 2 * 32 * RK_I2S_SAMPLE_RATE;
+		const u_int bclk_div = mclk_rate / bclk_rate;
+		const u_int lrck_div = bclk_rate / RK_I2S_SAMPLE_RATE;
+
+		ckr &= ~CKR_MDIV;
+		ckr |= __SHIFTIN(bclk_div - 1, CKR_MDIV);
+		ckr &= ~CKR_TSD;
+		ckr |= __SHIFTIN(lrck_div - 1, CKR_TSD);
+		ckr &= ~CKR_RSD;
+		ckr |= __SHIFTIN(lrck_div - 1, CKR_RSD);
+	}
+
+	ckr &= ~CKR_TRCM;
+	ckr |= __SHIFTIN(0, CKR_TRCM);
+	WR4(sc, I2S_CKR, ckr);
+
+	if (play && (setmode & AUMODE_PLAY) != 0) {
+		if (play->channels & 1)
+			return EINVAL;
+		txcr = RD4(sc, I2S_TXCR);
+		txcr &= ~TXCR_VDW;
+		txcr |= __SHIFTIN(play->validbits - 1, TXCR_VDW);
+		txcr &= ~TXCR_TCSR;
+		txcr |= __SHIFTIN(play->channels / 2 - 1, TXCR_TCSR);
+		WR4(sc, I2S_TXCR, txcr);
+	}
+
+	if (rec && (setmode & AUMODE_RECORD) != 0) {
+		if (rec->channels & 1)
+			return EINVAL;
+		rxcr = RD4(sc, I2S_RXCR);
+		rxcr &= ~RXCR_VDW;
+		rxcr |= __SHIFTIN(rec->validbits - 1, RXCR_VDW);
+		rxcr &= ~RXCR_RCSR;
+		rxcr |= __SHIFTIN(rec->channels / 2 - 1, RXCR_RCSR);
+		WR4(sc, I2S_RXCR, rxcr);
+	}
+
+	return 0;
+}
+
+static int
+rk_i2s_get_props(void *priv)
+{
+
+	return AUDIO_PROP_PLAYBACK | AUDIO_PROP_CAPTURE |
+	    AUDIO_PROP_FULLDUPLEX;
+}
+
+static int
+rk_i2s_round_blocksize(void *priv, int bs, int mode,
+    const audio_params_t *params)
+{
+	bs &= ~3;
+	if (bs == 0)
+		bs = 4;
+	return bs;
+}
+
+static void *
+rk_i2s_allocm(void *priv, int dir, size_t size)
+{
+	return kmem_zalloc(size, KM_SLEEP);
+}
+
+static void
+rk_i2s_freem(void *priv, void *addr, size_t size)
+{
+	kmem_free(addr, size);
+}
+
+static int
+rk_i2s_trigger_output(void *priv, void *start, void *end, int blksize,
+    void (*intr)(void *), void *intrarg, const audio_params_t *params)
+{
+	struct rk_i2s_softc * const sc = priv;
+	struct rk_i2s_chan *ch = &sc->sc_pchan;
+	uint32_t val;
+
+	if (sc->sc_active == 0) {
+		val = RD4(sc, I2S_XFER);
+		val |= (XFER_TXS | XFER_RXS);
+		WR4(sc, I2S_XFER, val);
+	}
+
+	sc->sc_active |= XFER_TXS;
+
+	val = RD4(sc, I2S_INTCR);
+	val |= INTCR_TXEIE;
+	val &= ~INTCR_TFT;
+	val |= __SHIFTIN(RK_I2S_FIFO_DEPTH / 2, INTCR_TFT);
+	WR4(sc, I2S_INTCR, val);
+
+	ch->ch_intr = intr;
+	ch->ch_intrarg = intrarg;
+	ch->ch_start = ch->ch_cur = start;
+	ch->ch_end = end;
+	ch->ch_blksize = blksize;
+	ch->ch_resid = blksize;
+
+	return 0;
+}
+
+static int
+rk_i2s_trigger_input(void *priv, void *start, void *end, int blksize,
+    void (*intr)(void *), void *intrarg, const audio_params_t *params)
+{
+	return EIO;
+}
+
+static int
+rk_i2s_halt_output(void *priv)
+{
+	struct rk_i2s_softc * const sc = priv;
+	struct rk_i2s_chan *ch = &sc->sc_pchan;
+	uint32_t val;
+
+	sc->sc_active &= ~XFER_TXS;
+	if (sc->sc_active == 0) {
+		val = RD4(sc, I2S_XFER);
+		val &= ~(XFER_TXS|XFER_RXS);
+		WR4(sc, I2S_XFER, val);
+	}
+
+	val = RD4(sc, I2S_INTCR);
+	val &= ~INTCR_TXEIE;
+	WR4(sc, I2S_INTCR, val);
+
+	val = RD4(sc, I2S_CLR);
+	val |= CLR_TXC;
+	WR4(sc, I2S_CLR, val);
+
+	while ((RD4(sc, I2S_CLR) & CLR_TXC) != 0)
+		delay(1);
+
+	ch->ch_intr = NULL;
+	ch->ch_intrarg = NULL;
+
+	return 0;
+}
+
+static int
+rk_i2s_halt_input(void *priv)
+{
+	struct rk_i2s_softc * const sc = priv;
+	struct rk_i2s_chan *ch = &sc->sc_rchan;
+	uint32_t val;
+
+	sc->sc_active &= ~XFER_RXS;
+	if (sc->sc_active == 0) {
+		val = RD4(sc, I2S_XFER);
+		val &= ~(XFER_TXS|XFER_RXS);
+		WR4(sc, I2S_XFER, val);
+	}
+
+	val = RD4(sc, I2S_INTCR);
+	val &= ~INTCR_RXFIE;
+	WR4(sc, I2S_INTCR, val);
+
+	ch->ch_intr = NULL;
+	ch->ch_intrarg = NULL;
+
+	return 0;
+}
+
+static void
+rk_i2s_get_locks(void *priv, kmutex_t **intr, kmutex_t **thread)
+{
+	struct rk_i2s_softc * const sc = priv;
+
+	*intr = &sc->sc_intr_lock;
+	*thread = &sc->sc_lock;
+}
+
+static const struct audio_hw_if rk_i2s_hw_if = {
+	.query_format = rk_i2s_query_format,
+	.set_format = rk_i2s_set_format,
+	.get_props = rk_i2s_get_props,
+	.round_blocksize = rk_i2s_round_blocksize,
+	.allocm = rk_i2s_allocm,
+	.freem = rk_i2s_freem,
+	.trigger_output = rk_i2s_trigger_output,
+	.trigger_input = rk_i2s_trigger_input,
+	.halt_output = rk_i2s_halt_output,
+	.halt_input = rk_i2s_halt_input,
+	.get_locks = rk_i2s_get_locks,
+};
+
+static int
+rk_i2s_intr(void *priv)
+{
+	struct rk_i2s_softc * const sc = priv;
+	struct rk_i2s_chan * const pch = &sc->sc_pchan;
+#if notyet
+	struct rk_i2s_chan * const rch = &sc->sc_rchan;
+#endif
+	uint32_t sr, val;
+	int fifolr;
+
+	mutex_enter(&sc->sc_intr_lock);
+
+	sr = RD4(sc, I2S_INTSR);
+
+	if ((sr & INTSR_RXFI) != 0) {
+#if notyet
+		val = RD4(sc, I2S_RXFIFOLR);
+		fifolr = __SHIFTOUT(val, RXFIFOLR_RFL(0));
+		while (fifolr > 0) {
+			*rch->ch_data = RD4(sc, I2S_RXDR);
+			rch->ch_data++;
+			rch->ch_resid -= 4;
+			if (rch->ch_resid == 0)
+				rch->ch_intr(rch->ch_intrarg);
+			--fifolr;
+		}
+#endif
+	}
+
+	if ((sr & INTSR_TXEI) != 0) {
+		val = RD4(sc, I2S_TXFIFOLR);
+		fifolr = __SHIFTOUT(val, TXFIFOLR_TFL(0));
+		fifolr = uimin(fifolr, RK_I2S_FIFO_DEPTH);
+		while (fifolr < RK_I2S_FIFO_DEPTH - 1) {
+			WR4(sc, I2S_TXDR, *pch->ch_cur);
+			pch->ch_cur++;
+			if (pch->ch_cur == pch->ch_end)
+				pch->ch_cur = pch->ch_start;
+			pch->ch_resid -= 4;
+			if (pch->ch_resid == 0) {
+				pch->ch_intr(pch->ch_intrarg);
+				pch->ch_resid = pch->ch_blksize;
+			}
+			++fifolr;
+		}
+	}
+
+	mutex_exit(&sc->sc_intr_lock);
+
+	return 0;
+}
+
+static int
+rk_i2s_dai_set_sysclk(audio_dai_tag_t dai, u_int rate, int dir)
+{
+	struct rk_i2s_softc * const sc = audio_dai_private(dai);
+	int error;
+
+	device_printf(sc->sc_dev, "set sysclk %u Hz\n", rate);
+	error = clk_set_rate(sc->sc_clk, rate);
+	if (error != 0) {
+		device_printf(sc->sc_dev, "failed to set sysclk to %u Hz: %d\n",
+		    rate, error);
+		return error;
+	}
+
+	return 0;
+}
+
+static int
+rk_i2s_dai_set_format(audio_dai_tag_t dai, u_int format)
+{
+	struct rk_i2s_softc * const sc = audio_dai_private(dai);
+	uint32_t txcr, rxcr, ckr;
+
+	const u_int fmt = __SHIFTOUT(format, AUDIO_DAI_FORMAT_MASK);
+	const u_int pol = __SHIFTOUT(format, AUDIO_DAI_POLARITY_MASK);
+	const u_int clk = __SHIFTOUT(format, AUDIO_DAI_CLOCK_MASK);
+
+	txcr = RD4(sc, I2S_TXCR);
+	rxcr = RD4(sc, I2S_RXCR);
+	ckr = RD4(sc, I2S_CKR);
+
+	txcr &= ~(TXCR_IBM|TXCR_PBM|TXCR_TFS);
+	rxcr &= ~(RXCR_IBM|RXCR_PBM|RXCR_TFS);
+	switch (fmt) {
+	case AUDIO_DAI_FORMAT_I2S:
+		txcr |= __SHIFTIN(0, TXCR_IBM);
+		rxcr |= __SHIFTIN(0, RXCR_IBM);
+		break;
+	case AUDIO_DAI_FORMAT_LJ:
+		txcr |= __SHIFTIN(1, TXCR_IBM);
+		rxcr |= __SHIFTIN(1, RXCR_IBM);
+		break;
+	case AUDIO_DAI_FORMAT_RJ:
+		txcr |= __SHIFTIN(2, TXCR_IBM);
+		rxcr |= __SHIFTIN(2, RXCR_IBM);
+		break;
+	case AUDIO_DAI_FORMAT_DSPA:
+		txcr |= __SHIFTIN(0, TXCR_PBM);
+		txcr |= TXCR_TFS;
+		rxcr |= __SHIFTIN(0, RXCR_PBM);
+		txcr |= RXCR_TFS;
+		break;
+	case AUDIO_DAI_FORMAT_DSPB:
+		txcr |= __SHIFTIN(1, TXCR_PBM);
+		txcr |= TXCR_TFS;
+		rxcr |= __SHIFTIN(1, RXCR_PBM);
+		txcr |= RXCR_TFS;
+		break;
+	default:
+		return EINVAL;
+	}
+
+	WR4(sc, I2S_TXCR, txcr);
+	WR4(sc, I2S_RXCR, rxcr);
+
+	switch (pol) {
+	case AUDIO_DAI_POLARITY_IB_NF:
+		ckr |= CKR_CKP;
+		break;
+	case AUDIO_DAI_POLARITY_NB_NF:
+		ckr &= ~CKR_CKP;
+		break;
+	default:
+		return EINVAL;
+	}
+
+	switch (clk) {
+	case AUDIO_DAI_CLOCK_CBM_CFM:
+		ckr |= CKR_MSS;		/* sclk input */
+		break;
+	case AUDIO_DAI_CLOCK_CBS_CFS:
+		ckr &= ~CKR_MSS;	/* sclk output */
+		break;
+	default:
+		return EINVAL;
+	}
+
+	WR4(sc, I2S_CKR, ckr);
+
+	return 0;
+}
+
+static audio_dai_tag_t
+rk_i2s_dai_get_tag(device_t dev, const void *data, size_t len)
+{
+	struct rk_i2s_softc * const sc = device_private(dev);
+
+	if (len != 4)
+		return NULL;
+
+	return &sc->sc_dai;
+}
+
+static struct fdtbus_dai_controller_func rk_i2s_dai_funcs = {
+	.get_tag = rk_i2s_dai_get_tag
+};
+
+static int
+rk_i2s_clock_init(struct rk_i2s_softc *sc)
+{
+	const int phandle = sc->sc_phandle;
+	int error;
+
+	sc->sc_clk = fdtbus_clock_get(phandle, "i2s_clk");
+	if (sc->sc_clk == NULL) {
+		aprint_error(": couldn't find i2s_clk clock\n");
+		return ENXIO;
+	}
+	error = clk_enable(sc->sc_clk);
+	if (error != 0) {
+		aprint_error(": couldn't enable i2s_clk clock: %d\n", error);
+		return error;
+	}
+
+	/* Enable bus clock */
+	if (fdtbus_clock_enable(phandle, "i2s_hclk", true) != 0) {
+		aprint_error(": couldn't enable i2s_hclk clock: %d\n", error);
+		return error;
+	}
+
+	return 0;
+}
+
+static int
+rk_i2s_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
+rk_i2s_attach(device_t parent, device_t self, void *aux)
+{
+	struct rk_i2s_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	char intrstr[128];
+	bus_addr_t addr;
+	bus_size_t size;
+	uint32_t val;
+
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+		return;
+	}
+	if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
+		aprint_error(": couldn't decode interrupt\n");
+		return;
+	}
+
+	sc->sc_dev = self;
+	sc->sc_phandle = phandle;
+	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;
+	}
+	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
+	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SCHED);
+
+	sc->sc_conf = (void *)of_search_compatible(phandle, compat_data)->data;
+	sc->sc_grf = fdtbus_syscon_acquire(phandle, "rockchip,grf");
+	if (sc->sc_grf != NULL && sc->sc_conf->oe_mask != 0) {
+		syscon_lock(sc->sc_grf);
+		val = __SHIFTIN(sc->sc_conf->oe_val, sc->sc_conf->oe_mask);
+		val |= (sc->sc_conf->oe_mask << 16);
+		syscon_write_4(sc->sc_grf, sc->sc_conf->oe_reg, val);
+		syscon_unlock(sc->sc_grf);
+	}
+
+	if (rk_i2s_clock_init(sc) != 0)
+		return;
+
+	aprint_naive("\n");
+	aprint_normal(": I2S/PCM controller\n");
+
+	if (fdtbus_intr_establish(phandle, 0, IPL_AUDIO, FDT_INTR_MPSAFE, rk_i2s_intr, sc) == NULL) {
+		aprint_error_dev(self, "couldn't establish interrupt on %s\n", intrstr);
+		return;
+	}
+	aprint_normal_dev(self, "interrupting on %s\n", intrstr);
+
+	sc->sc_format.mode = AUMODE_PLAY|AUMODE_RECORD;
+	sc->sc_format.encoding = AUDIO_ENCODING_SLINEAR_LE;
+	sc->sc_format.validbits = 16;
+	sc->sc_format.precision = 16;
+	sc->sc_format.channels = 2;
+	sc->sc_format.channel_mask = AUFMT_STEREO;
+	sc->sc_format.frequency_type = 1;
+	sc->sc_format.frequency[0] = RK_I2S_SAMPLE_RATE;
+
+	sc->sc_dai.dai_set_sysclk = rk_i2s_dai_set_sysclk;
+	sc->sc_dai.dai_set_format = rk_i2s_dai_set_format;
+	sc->sc_dai.dai_hw_if = &rk_i2s_hw_if;
+	sc->sc_dai.dai_dev = self;
+	sc->sc_dai.dai_priv = sc;
+	fdtbus_register_dai_controller(self, phandle, &rk_i2s_dai_funcs);
+}
+
+CFATTACH_DECL_NEW(rk_i2s, sizeof(struct rk_i2s_softc),
+    rk_i2s_match, rk_i2s_attach, NULL, NULL);

Index: src/sys/arch/arm/rockchip/rk_dwhdmi.c
diff -u /dev/null src/sys/arch/arm/rockchip/rk_dwhdmi.c:1.3.2.2
--- /dev/null	Sat Nov 16 16:48:26 2019
+++ src/sys/arch/arm/rockchip/rk_dwhdmi.c	Sat Nov 16 16:48:25 2019
@@ -0,0 +1,310 @@
+/* $NetBSD: rk_dwhdmi.c,v 1.3.2.2 2019/11/16 16:48:25 martin 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: rk_dwhdmi.c,v 1.3.2.2 2019/11/16 16:48:25 martin 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/fdt/syscon.h>
+
+#include <dev/ic/dw_hdmi.h>
+
+#define	RK3399_GRF_SOC_CON20		0x6250
+#define	 HDMI_LCDC_SEL			__BIT(6)
+
+static const struct dwhdmi_mpll_config rk_dwhdmi_mpll_config[] = {
+	{ 40000,	0x00b3, 0x0000, 0x0018 },
+	{ 65000,	0x0072, 0x0001, 0x0028 },
+	{ 66000,	0x013e, 0x0003, 0x0038 },
+	{ 83500,	0x0072, 0x0001, 0x0028 },
+	{ 146250,	0x0051, 0x0002, 0x0038 },
+	{ 148500,	0x0051, 0x0003, 0x0000 },
+	{ 272000,	0x0040, 0x0003, 0x0000 },
+	{ 340000,	0x0040, 0x0003, 0x0000 },
+	{ 0,		0x0051, 0x0003, 0x0000 },
+};
+
+static const struct dwhdmi_phy_config rk_dwhdmi_phy_config[] = {
+	{ 74250,	0x8009, 0x0004, 0x0272 },
+	{ 148500,	0x802b, 0x0004, 0x028d },
+	{ 297000,	0x8039, 0x0005, 0x028d },
+	{ 594000,	0x8039, 0x0000, 0x019d },
+	{ 0,		0x0000, 0x0000, 0x0000 }
+};
+
+enum {
+	DWHDMI_PORT_INPUT = 0,
+	DWHDMI_PORT_OUTPUT = 1,
+};
+
+static const char * const compatible[] = {
+	"rockchip,rk3399-dw-hdmi",
+	NULL
+};
+
+struct rk_dwhdmi_softc {
+	struct dwhdmi_softc	sc_base;
+	int			sc_phandle;
+	struct clk		*sc_clk_vpll;
+
+	struct fdt_device_ports	sc_ports;
+	struct drm_display_mode	sc_curmode;
+	struct syscon		*sc_grf;
+
+	bool			sc_activated;
+};
+
+#define	to_rk_dwhdmi_softc(x)	container_of(x, struct rk_dwhdmi_softc, sc_base)
+
+static void
+rk_dwhdmi_select_input(struct rk_dwhdmi_softc *sc, u_int crtc_index)
+{
+	const uint32_t write_mask = HDMI_LCDC_SEL << 16;
+	const uint32_t write_val = crtc_index == 0 ? HDMI_LCDC_SEL : 0;
+
+	syscon_lock(sc->sc_grf);
+	syscon_write_4(sc->sc_grf, RK3399_GRF_SOC_CON20, write_mask | write_val);
+	syscon_unlock(sc->sc_grf);
+}
+
+static int
+rk_dwhdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
+{
+	struct rk_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;
+
+	if (sc->sc_activated == false) {
+		error = dwhdmi_bind(&sc->sc_base, encoder);
+		if (error != 0)
+			return error;
+		sc->sc_activated = true;
+	}
+
+	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 *
+rk_dwhdmi_ep_get_data(device_t dev, struct fdt_endpoint *ep)
+{
+	struct rk_dwhdmi_softc * const sc = device_private(dev);
+
+	return &sc->sc_base.sc_bridge;
+}
+
+static void
+rk_dwhdmi_enable(struct dwhdmi_softc *dsc)
+{
+	struct rk_dwhdmi_softc * const sc = to_rk_dwhdmi_softc(dsc);
+
+	const u_int crtc_index = drm_crtc_index(dsc->sc_bridge.encoder->crtc);
+
+	rk_dwhdmi_select_input(sc, crtc_index);
+
+	dwhdmi_phy_enable(dsc);
+}
+
+static void
+rk_dwhdmi_mode_set(struct dwhdmi_softc *dsc,
+    struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
+{
+	struct rk_dwhdmi_softc * const sc = to_rk_dwhdmi_softc(dsc);
+	int error;
+
+	if (sc->sc_clk_vpll != NULL) {
+		error = clk_set_rate(sc->sc_clk_vpll, adjusted_mode->clock * 1000);
+		if (error != 0)
+			device_printf(dsc->sc_dev, "couldn't set pixel clock to %u Hz: %d\n",
+			    adjusted_mode->clock * 1000, error);
+	}
+
+	dwhdmi_phy_mode_set(dsc, mode, adjusted_mode);
+}
+
+static audio_dai_tag_t
+rk_dwhdmi_dai_get_tag(device_t dev, const void *data, size_t len)
+{
+	struct rk_dwhdmi_softc * const sc = device_private(dev);
+
+	if (len != 4)
+		return NULL;
+
+	return &sc->sc_base.sc_dai;
+}
+
+static struct fdtbus_dai_controller_func rk_dwhdmi_dai_funcs = {
+	.get_tag = rk_dwhdmi_dai_get_tag
+};
+
+static int
+rk_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
+rk_dwhdmi_attach(device_t parent, device_t self, void *aux)
+{
+	struct rk_dwhdmi_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	bus_addr_t addr;
+	bus_size_t size;
+
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+		return;
+	}
+
+	/* Required */
+	if (fdtbus_clock_enable(phandle, "iahb", true) != 0) {
+		aprint_error(": couldn't enable iahb clock\n");
+		return;
+	}
+
+	/* Required */
+	if (fdtbus_clock_enable(phandle, "isfr", true) != 0) {
+		aprint_error(": couldn't enable isfr clock\n");
+		return;
+	}
+
+	/* Optional */
+	sc->sc_clk_vpll = fdtbus_clock_get(phandle, "vpll");
+	if (sc->sc_clk_vpll != NULL && clk_enable(sc->sc_clk_vpll) != 0) {
+		aprint_error(": couldn't enable vpll clock\n");
+		return;
+	}
+
+	/* Optional */
+	if (fdtbus_clock_enable(phandle, "grf", false) != 0) {
+		aprint_error(": couldn't enable grf clock\n");
+		return;
+	}
+
+	/* Optional */
+	if (fdtbus_clock_enable(phandle, "cec", false) != 0) {
+		aprint_error(": couldn't enable cec clock\n");
+		return;
+	}
+
+	sc->sc_base.sc_dev = self;
+	if (of_getprop_uint32(phandle, "reg-io-width", &sc->sc_base.sc_reg_width) != 0)
+		sc->sc_base.sc_reg_width = 4;
+	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_phandle = faa->faa_phandle;
+	sc->sc_grf = fdtbus_syscon_acquire(phandle, "rockchip,grf");
+	if (sc->sc_grf == NULL) {
+		aprint_error(": couldn't get grf syscon\n");
+		return;
+	}
+
+	aprint_naive("\n");
+	aprint_normal(": HDMI TX\n");
+
+	sc->sc_base.sc_ic = fdtbus_i2c_acquire(phandle, "ddc-i2c-bus");
+	if (of_hasprop(phandle, "ddc-i2c-bus") && sc->sc_base.sc_ic == NULL) {
+		aprint_error_dev(self, "couldn't find external I2C master\n");
+		return;
+	}
+
+	sc->sc_base.sc_flags |= DWHDMI_USE_INTERNAL_PHY;
+	sc->sc_base.sc_detect = dwhdmi_phy_detect;
+	sc->sc_base.sc_enable = rk_dwhdmi_enable;
+	sc->sc_base.sc_disable = dwhdmi_phy_disable;
+	sc->sc_base.sc_mode_set = rk_dwhdmi_mode_set;
+	sc->sc_base.sc_mpll_config = rk_dwhdmi_mpll_config;
+	sc->sc_base.sc_phy_config = rk_dwhdmi_phy_config;
+
+	if (dwhdmi_attach(&sc->sc_base) != 0) {
+		aprint_error_dev(self, "failed to attach driver\n");
+		return;
+	}
+
+	sc->sc_ports.dp_ep_activate = rk_dwhdmi_ep_activate;
+	sc->sc_ports.dp_ep_get_data = rk_dwhdmi_ep_get_data;
+	fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_BRIDGE);
+
+	fdtbus_register_dai_controller(self, phandle, &rk_dwhdmi_dai_funcs);
+}
+
+CFATTACH_DECL_NEW(rk_dwhdmi, sizeof(struct rk_dwhdmi_softc),
+	rk_dwhdmi_match, rk_dwhdmi_attach, NULL, NULL);

Index: src/sys/dev/ic/dw_hdmi_phy.c
diff -u /dev/null src/sys/dev/ic/dw_hdmi_phy.c:1.2.2.2
--- /dev/null	Sat Nov 16 16:48:26 2019
+++ src/sys/dev/ic/dw_hdmi_phy.c	Sat Nov 16 16:48:25 2019
@@ -0,0 +1,401 @@
+/* $NetBSD: dw_hdmi_phy.c,v 1.2.2.2 2019/11/16 16:48:25 martin Exp $ */
+
+/*-
+ * Copyright (c) 2015 Oleksandr Tymoshenko <go...@freebsd.org>
+ * 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 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 AUTHOR 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: dw_hdmi_phy.c,v 1.2.2.2 2019/11/16 16:48:25 martin Exp $");
+
+#include <sys/param.h>
+
+#include <drm/drmP.h>
+
+#include <dev/ic/dw_hdmi.h>
+
+#define	HDMI_IH_PHY_STAT0                       0x0104
+#define	  HDMI_IH_PHY_STAT0_HPD (1 << 0)
+#define	HDMI_IH_I2CMPHY_STAT0                   0x0108
+#define	  HDMI_IH_I2CMPHY_STAT0_DONE            (1 << 1)
+#define	  HDMI_IH_I2CMPHY_STAT0_ERROR           (1 << 0)
+
+#define	HDMI_PHY_CONF0				0x3000
+#define	  HDMI_PHY_CONF0_PDZ_MASK			0x80
+#define	  HDMI_PHY_CONF0_PDZ_OFFSET		7
+#define	  HDMI_PHY_CONF0_ENTMDS_MASK		0x40
+#define	  HDMI_PHY_CONF0_ENTMDS_OFFSET		6
+#define	  HDMI_PHY_CONF0_SVSRET_MASK		0x20
+#define	  HDMI_PHY_CONF0_SVSRET_OFFSET			5
+#define	  HDMI_PHY_CONF0_GEN2_PDDQ_MASK		0x10
+#define	  HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET		4
+#define	  HDMI_PHY_CONF0_GEN2_TXPWRON_MASK	0x8
+#define	  HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET	3
+#define	  HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_MASK	0x4
+#define	  HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_OFFSET	2
+#define	  HDMI_PHY_CONF0_SELDATAENPOL_MASK	0x2
+#define	  HDMI_PHY_CONF0_SELDATAENPOL_OFFSET	1
+#define	  HDMI_PHY_CONF0_SELDIPIF_MASK		0x1
+#define	  HDMI_PHY_CONF0_SELDIPIF_OFFSET		0
+#define	HDMI_PHY_TST0				0x3001
+#define	  HDMI_PHY_TST0_TSTCLR_MASK		0x20
+#define	  HDMI_PHY_TST0_TSTCLR_OFFSET		5
+#define	  HDMI_PHY_TST0_TSTEN_MASK		0x10
+#define	  HDMI_PHY_TST0_TSTEN_OFFSET		4
+#define	  HDMI_PHY_TST0_TSTCLK_MASK		0x1
+#define	  HDMI_PHY_TST0_TSTCLK_OFFSET		0
+#define	HDMI_PHY_TST1				0x3002
+#define	HDMI_PHY_TST2				0x3003
+#define	HDMI_PHY_STAT0				0x3004
+#define	  HDMI_PHY_STAT0_RX_SENSE3		0x80
+#define	  HDMI_PHY_STAT0_RX_SENSE2		0x40
+#define	  HDMI_PHY_STAT0_RX_SENSE1		0x20
+#define	  HDMI_PHY_STAT0_RX_SENSE0		0x10
+#define	  HDMI_PHY_STAT0_RX_SENSE		0xf0
+#define	  HDMI_PHY_STAT0_HPD			0x02
+#define	  HDMI_PHY_TX_PHY_LOCK			0x01
+#define	HDMI_PHY_INT0				0x3005
+#define	HDMI_PHY_MASK0				0x3006
+#define	HDMI_PHY_POL0				0x3007
+#define	  HDMI_PHY_POL0_HPD			0x02
+
+/* HDMI Master PHY Registers */
+#define	HDMI_PHY_I2CM_SLAVE_ADDR		0x3020
+#define	  HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2	0x69
+#define	  HDMI_PHY_I2CM_SLAVE_ADDR_HEAC_PHY	0x49
+#define	HDMI_PHY_I2CM_ADDRESS_ADDR		0x3021
+#define	HDMI_PHY_I2CM_DATAO_1_ADDR		0x3022
+#define	HDMI_PHY_I2CM_DATAO_0_ADDR		0x3023
+#define	HDMI_PHY_I2CM_DATAI_1_ADDR		0x3024
+#define	HDMI_PHY_I2CM_DATAI_0_ADDR		0x3025
+#define	HDMI_PHY_I2CM_OPERATION_ADDR		0x3026
+#define	  HDMI_PHY_I2CM_OPERATION_ADDR_WRITE    0x10
+#define	  HDMI_PHY_I2CM_OPERATION_ADDR_READ     0x1
+#define	HDMI_PHY_I2CM_INT_ADDR			0x3027
+#define	HDMI_PHY_I2CM_CTLINT_ADDR		0x3028
+#define	HDMI_PHY_I2CM_DIV_ADDR			0x3029
+#define	HDMI_PHY_I2CM_SOFTRSTZ_ADDR		0x302a
+#define	HDMI_PHY_I2CM_SS_SCL_HCNT_1_ADDR	0x302b
+#define	HDMI_PHY_I2CM_SS_SCL_HCNT_0_ADDR	0x302c
+#define	HDMI_PHY_I2CM_SS_SCL_LCNT_1_ADDR	0x302d
+#define	HDMI_PHY_I2CM_SS_SCL_LCNT_0_ADDR	0x302e
+#define	HDMI_PHY_I2CM_FS_SCL_HCNT_1_ADDR	0x302f
+#define	HDMI_PHY_I2CM_FS_SCL_HCNT_0_ADDR	0x3030
+#define	HDMI_PHY_I2CM_FS_SCL_LCNT_1_ADDR	0x3031
+#define	HDMI_PHY_I2CM_FS_SCL_LCNT_0_ADDR	0x3032
+
+#define	HDMI_MC_FLOWCTRL                        0x4004
+#define	  HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_MASK                0x1
+#define	  HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH 0x1
+#define	  HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS  0x0
+#define	HDMI_MC_PHYRSTZ                         0x4005
+#define	  HDMI_MC_PHYRSTZ_ASSERT                        0x0
+#define	  HDMI_MC_PHYRSTZ_DEASSERT              0x1
+#define	HDMI_MC_HEACPHY_RST                     0x4007
+#define	  HDMI_MC_HEACPHY_RST_ASSERT            0x1
+#define	  HDMI_MC_HEACPHY_RST_DEASSERT          0x0
+
+/* HDMI PHY register with access through I2C */
+#define	HDMI_PHY_I2C_CKCALCTRL	0x5
+#define	  CKCALCTRL_OVERRIDE	(1 << 15)
+#define	HDMI_PHY_I2C_CPCE_CTRL	0x6
+#define	  CPCE_CTRL_45_25		((3 << 7) | (3 << 5))
+#define	  CPCE_CTRL_92_50		((2 << 7) | (2 << 5))
+#define	  CPCE_CTRL_185		((1 << 7) | (1 << 5))
+#define	  CPCE_CTRL_370		((0 << 7) | (0 << 5))
+#define	HDMI_PHY_I2C_CKSYMTXCTRL	0x9
+#define	  CKSYMTXCTRL_OVERRIDE	(1 << 15)
+#define	  CKSYMTXCTRL_TX_SYMON	(1 << 3)
+#define	  CKSYMTXCTRL_TX_TRAON	(1 << 2)
+#define	  CKSYMTXCTRL_TX_TRBON	(1 << 1)
+#define	  CKSYMTXCTRL_TX_CK_SYMON	(1 << 0)
+#define	HDMI_PHY_I2C_VLEVCTRL		0x0E
+#define	HDMI_PHY_I2C_CURRCTRL		0x10
+#define	HDMI_PHY_I2C_PLLPHBYCTRL	0x13
+#define	  VLEVCTRL_TX_LVL(x)	((x) << 5)
+#define	  VLEVCTRL_CK_LVL(x)	(x)
+#define	HDMI_PHY_I2C_GMPCTRL	0x15
+#define	  GMPCTRL_45_25		0x00
+#define	  GMPCTRL_92_50		0x05
+#define	  GMPCTRL_185		0x0a
+#define	  GMPCTRL_370		0x0f
+#define	HDMI_PHY_I2C_MSM_CTRL	0x17
+#define	  MSM_CTRL_FB_CLK		(0x3 << 1)
+#define	HDMI_PHY_I2C_TXTERM	0x19
+#define	  TXTERM_133		0x5
+
+static void
+dwhdmi_phy_wait_i2c_done(struct dwhdmi_softc *sc, int msec)
+{
+	uint8_t val;
+
+	val = dwhdmi_read(sc, HDMI_IH_I2CMPHY_STAT0) &
+	    (HDMI_IH_I2CMPHY_STAT0_DONE | HDMI_IH_I2CMPHY_STAT0_ERROR);
+	while (val == 0) {
+		delay(1000);
+		msec -= 10;
+		if (msec <= 0)
+			return;
+		val = dwhdmi_read(sc, HDMI_IH_I2CMPHY_STAT0) &
+		    (HDMI_IH_I2CMPHY_STAT0_DONE | HDMI_IH_I2CMPHY_STAT0_ERROR);
+	}
+}
+
+static void
+dwhdmi_phy_i2c_write(struct dwhdmi_softc *sc, unsigned short data,
+    unsigned char addr)
+{
+
+	/* clear DONE and ERROR flags */
+	dwhdmi_write(sc, HDMI_IH_I2CMPHY_STAT0,
+	    HDMI_IH_I2CMPHY_STAT0_DONE | HDMI_IH_I2CMPHY_STAT0_ERROR);
+	dwhdmi_write(sc, HDMI_PHY_I2CM_ADDRESS_ADDR, addr);
+	dwhdmi_write(sc, HDMI_PHY_I2CM_DATAO_1_ADDR, ((data >> 8) & 0xff));
+	dwhdmi_write(sc, HDMI_PHY_I2CM_DATAO_0_ADDR, ((data >> 0) & 0xff));
+	dwhdmi_write(sc, HDMI_PHY_I2CM_OPERATION_ADDR, HDMI_PHY_I2CM_OPERATION_ADDR_WRITE);
+	dwhdmi_phy_wait_i2c_done(sc, 1000);
+}
+
+static void
+dwhdmi_phy_enable_power(struct dwhdmi_softc *sc, uint8_t enable)
+{
+	uint8_t reg;
+
+	reg = dwhdmi_read(sc, HDMI_PHY_CONF0);
+	reg &= ~HDMI_PHY_CONF0_PDZ_MASK;
+	reg |= (enable << HDMI_PHY_CONF0_PDZ_OFFSET);
+	dwhdmi_write(sc, HDMI_PHY_CONF0, reg);
+}
+
+static void
+dwhdmi_phy_enable_tmds(struct dwhdmi_softc *sc, uint8_t enable)
+{
+	uint8_t reg;
+
+	reg = dwhdmi_read(sc, HDMI_PHY_CONF0);
+	reg &= ~HDMI_PHY_CONF0_ENTMDS_MASK;
+	reg |= (enable << HDMI_PHY_CONF0_ENTMDS_OFFSET);
+	dwhdmi_write(sc, HDMI_PHY_CONF0, reg);
+}
+
+static void
+dwhdmi_phy_gen2_pddq(struct dwhdmi_softc *sc, uint8_t enable)
+{
+	uint8_t reg;
+
+	reg = dwhdmi_read(sc, HDMI_PHY_CONF0);
+	reg &= ~HDMI_PHY_CONF0_GEN2_PDDQ_MASK;
+	reg |= (enable << HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET);
+	dwhdmi_write(sc, HDMI_PHY_CONF0, reg);
+}
+
+static void
+dwhdmi_phy_gen2_txpwron(struct dwhdmi_softc *sc, uint8_t enable)
+{
+	uint8_t reg;
+
+	reg = dwhdmi_read(sc, HDMI_PHY_CONF0);
+	reg &= ~HDMI_PHY_CONF0_GEN2_TXPWRON_MASK;
+	reg |= (enable << HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET);
+	dwhdmi_write(sc, HDMI_PHY_CONF0, reg);
+}
+
+static void
+dwhdmi_phy_sel_data_en_pol(struct dwhdmi_softc *sc, uint8_t enable)
+{
+	uint8_t reg;
+
+	reg = dwhdmi_read(sc, HDMI_PHY_CONF0);
+	reg &= ~HDMI_PHY_CONF0_SELDATAENPOL_MASK;
+	reg |= (enable << HDMI_PHY_CONF0_SELDATAENPOL_OFFSET);
+	dwhdmi_write(sc, HDMI_PHY_CONF0, reg);
+}
+
+static void
+dwhdmi_phy_sel_interface_control(struct dwhdmi_softc *sc, uint8_t enable)
+{
+	uint8_t reg;
+
+	reg = dwhdmi_read(sc, HDMI_PHY_CONF0);
+	reg &= ~HDMI_PHY_CONF0_SELDIPIF_MASK;
+	reg |= (enable << HDMI_PHY_CONF0_SELDIPIF_OFFSET);
+	dwhdmi_write(sc, HDMI_PHY_CONF0, reg);
+}
+
+static void
+dwhdmi_phy_enable_svsret(struct dwhdmi_softc *sc, uint8_t enable)
+{
+	uint8_t reg;
+
+	reg = dwhdmi_read(sc, HDMI_PHY_CONF0);
+	reg &= ~HDMI_PHY_CONF0_SVSRET_MASK;
+	reg |= (enable << HDMI_PHY_CONF0_SVSRET_OFFSET);
+	dwhdmi_write(sc, HDMI_PHY_CONF0, reg);
+}
+
+static inline void
+dwhdmi_phy_test_clear(struct dwhdmi_softc *sc, unsigned char bit)
+{
+	uint8_t val;
+
+	val = dwhdmi_read(sc, HDMI_PHY_TST0);
+	val &= ~HDMI_PHY_TST0_TSTCLR_MASK;
+	val |= (bit << HDMI_PHY_TST0_TSTCLR_OFFSET) &
+		HDMI_PHY_TST0_TSTCLR_MASK;
+	dwhdmi_write(sc, HDMI_PHY_TST0, val);
+}
+
+static int
+dwhdmi_phy_configure(struct dwhdmi_softc *sc, struct drm_display_mode *mode)
+{
+	const struct dwhdmi_mpll_config *mpll_conf;
+	const struct dwhdmi_phy_config *phy_conf;
+	uint8_t val;
+	uint8_t msec;
+
+	dwhdmi_write(sc, HDMI_MC_FLOWCTRL, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS);
+
+	/* gen2 tx power off */
+	dwhdmi_phy_gen2_txpwron(sc, 0);
+
+	/* gen2 pddq */
+	dwhdmi_phy_gen2_pddq(sc, 1);
+
+	/* PHY reset */
+	dwhdmi_write(sc, HDMI_MC_PHYRSTZ, HDMI_MC_PHYRSTZ_DEASSERT);
+	dwhdmi_write(sc, HDMI_MC_PHYRSTZ, HDMI_MC_PHYRSTZ_ASSERT);
+
+	dwhdmi_write(sc, HDMI_MC_HEACPHY_RST, HDMI_MC_HEACPHY_RST_ASSERT);
+
+	dwhdmi_phy_test_clear(sc, 1);
+	dwhdmi_write(sc, HDMI_PHY_I2CM_SLAVE_ADDR, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2);
+	dwhdmi_phy_test_clear(sc, 0);
+
+	/*
+	 * Following initialization are for 8bit per color case
+	 */
+
+	/*
+	 * PLL/MPLL config
+	 */
+	for (mpll_conf = &sc->sc_mpll_config[0]; mpll_conf->pixel_clock != 0; mpll_conf++)
+		if (mode->clock <= mpll_conf->pixel_clock)
+			break;
+
+	dwhdmi_phy_i2c_write(sc, mpll_conf->cpce, HDMI_PHY_I2C_CPCE_CTRL);
+	dwhdmi_phy_i2c_write(sc, mpll_conf->gmp, HDMI_PHY_I2C_GMPCTRL);
+	dwhdmi_phy_i2c_write(sc, mpll_conf->curr, HDMI_PHY_I2C_CURRCTRL);
+
+	for (phy_conf = &sc->sc_phy_config[0]; phy_conf->pixel_clock != 0; phy_conf++)
+		if (mode->clock <= phy_conf->pixel_clock)
+			break;
+
+	dwhdmi_phy_i2c_write(sc, 0x0000, HDMI_PHY_I2C_PLLPHBYCTRL);
+	dwhdmi_phy_i2c_write(sc, MSM_CTRL_FB_CLK, HDMI_PHY_I2C_MSM_CTRL);
+
+	dwhdmi_phy_i2c_write(sc, phy_conf->term, HDMI_PHY_I2C_TXTERM);
+	dwhdmi_phy_i2c_write(sc, phy_conf->sym, HDMI_PHY_I2C_CKSYMTXCTRL);
+	dwhdmi_phy_i2c_write(sc, phy_conf->vlev, HDMI_PHY_I2C_VLEVCTRL);
+
+	/* REMOVE CLK TERM */
+	dwhdmi_phy_i2c_write(sc, CKCALCTRL_OVERRIDE, HDMI_PHY_I2C_CKCALCTRL);
+
+	dwhdmi_phy_enable_power(sc, 1);
+
+	/* toggle TMDS enable */
+	dwhdmi_phy_enable_tmds(sc, 0);
+	dwhdmi_phy_enable_tmds(sc, 1);
+
+	/* gen2 tx power on */
+	dwhdmi_phy_gen2_txpwron(sc, 1);
+	dwhdmi_phy_gen2_pddq(sc, 0);
+
+	switch (sc->sc_phytype) {
+	case 0xb2:	/* MHL PHY HEAC */
+	case 0xc2:	/* MHL PHY */
+	case 0xf3:	/* HDMI 2.0 TX PHY */
+		dwhdmi_phy_enable_svsret(sc, 1);
+		break;
+	}
+
+	/*Wait for PHY PLL lock */
+	msec = 4;
+	val = dwhdmi_read(sc, HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK;
+	while (val == 0) {
+		delay(1000);
+		if (msec-- == 0) {
+			device_printf(sc->sc_dev, "PHY PLL not locked\n");
+			return (-1);
+		}
+		val = dwhdmi_read(sc, HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK;
+	}
+
+	return (0);
+}
+
+static void
+dwhdmi_phy_init(struct dwhdmi_softc *sc, struct drm_display_mode *mode)
+{
+	int i;
+
+	/* HDMI Phy spec says to do the phy initialization sequence twice */
+	for (i = 0 ; i < 2 ; i++) {
+		dwhdmi_phy_sel_data_en_pol(sc, 1);
+		dwhdmi_phy_sel_interface_control(sc, 0);
+		dwhdmi_phy_enable_tmds(sc, 0);
+		dwhdmi_phy_enable_power(sc, 0);
+
+		/* Enable CSC */
+		dwhdmi_phy_configure(sc, mode);
+	}
+}
+
+enum drm_connector_status
+dwhdmi_phy_detect(struct dwhdmi_softc *sc, bool force)
+{
+	uint8_t val;
+
+	val = dwhdmi_read(sc, HDMI_PHY_STAT0);
+
+	return ((val & HDMI_PHY_STAT0_HPD) != 0) ?
+	    connector_status_connected :
+	    connector_status_disconnected;
+}
+
+void
+dwhdmi_phy_enable(struct dwhdmi_softc *sc)
+{
+}
+
+void
+dwhdmi_phy_disable(struct dwhdmi_softc *sc)
+{
+}
+
+void
+dwhdmi_phy_mode_set(struct dwhdmi_softc *sc,
+    struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
+{
+	dwhdmi_phy_init(sc, adjusted_mode);
+}

Reply via email to