summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sys/arch/arm/dts/rk3399-rockpro64.dts6
-rw-r--r--sys/arch/arm/rockchip/files.rockchip27
-rw-r--r--sys/arch/arm/rockchip/rk3399_cru.c209
-rw-r--r--sys/arch/arm/rockchip/rk_cru.h20
-rw-r--r--sys/arch/arm/rockchip/rk_cru_composite.c66
-rw-r--r--sys/arch/arm/rockchip/rk_drm.c512
-rw-r--r--sys/arch/arm/rockchip/rk_drm.h99
-rw-r--r--sys/arch/arm/rockchip/rk_dwhdmi.c310
-rw-r--r--sys/arch/arm/rockchip/rk_fb.c160
-rw-r--r--sys/arch/arm/rockchip/rk_i2c.c51
-rw-r--r--sys/arch/arm/rockchip/rk_i2s.c638
-rw-r--r--sys/arch/arm/rockchip/rk_vop.c659
-rw-r--r--sys/arch/evbarm/conf/GENERIC648
-rw-r--r--sys/conf/files3
-rw-r--r--sys/dev/fdt/ausoc.c46
-rw-r--r--sys/dev/fdt/fdt_clock.c28
-rw-r--r--sys/dev/fdt/fdtvar.h4
-rw-r--r--sys/dev/ic/dw_hdmi.c308
-rw-r--r--sys/dev/ic/dw_hdmi.h39
-rw-r--r--sys/dev/ic/dw_hdmi_phy.c401
20 files changed, 3508 insertions, 86 deletions
diff --git a/sys/arch/arm/dts/rk3399-rockpro64.dts b/sys/arch/arm/dts/rk3399-rockpro64.dts
index 5b656ace6d2..e3bbcc2a70e 100644
--- a/sys/arch/arm/dts/rk3399-rockpro64.dts
+++ b/sys/arch/arm/dts/rk3399-rockpro64.dts
@@ -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 <jmcneill@invisible.ca>
@@ -62,6 +62,10 @@
};
};
+&hdmi_sound {
+ status = "okay";
+};
+
&pwm1 {
status = "okay";
};
diff --git a/sys/arch/arm/rockchip/files.rockchip b/sys/arch/arm/rockchip/files.rockchip
index e5f38306e1c..fcd32b403a7 100644
--- a/sys/arch/arm/rockchip/files.rockchip
+++ b/sys/arch/arm/rockchip/files.rockchip
@@ -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
diff --git a/sys/arch/arm/rockchip/rk3399_cru.c b/sys/arch/arm/rockchip/rk3399_cru.c
index cb5186fec9d..1451743ed6e 100644
--- a/sys/arch/arm/rockchip/rk3399_cru.c
+++ b/sys/arch/arm/rockchip/rk3399_cru.c
@@ -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 <jmcneill@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_softc *sc,
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[] = { "clk_core_b_lpll_src", "clk_core_b_bpl
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
diff --git a/sys/arch/arm/rockchip/rk_cru.h b/sys/arch/arm/rockchip/rk_cru.h
index fae86c7a5be..cc12e44831e 100644
--- a/sys/arch/arm/rockchip/rk_cru.h
+++ b/sys/arch/arm/rockchip/rk_cru.h
@@ -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 <jmcneill@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_cru_softc *, struct rk_cru_clk *, u_int)
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 rk_cru_softc *, struct rk_cru_clk *, cons
.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 rk_cru_softc *, struct rk_cru_clk *, cons
.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 */
diff --git a/sys/arch/arm/rockchip/rk_cru_composite.c b/sys/arch/arm/rockchip/rk_cru_composite.c
index 770c00687ba..790dd0e7334 100644
--- a/sys/arch/arm/rockchip/rk_cru_composite.c
+++ b/sys/arch/arm/rockchip/rk_cru_composite.c
@@ -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 <jmcneill@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_softc *sc,
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_softc *sc,
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;
diff --git a/sys/arch/arm/rockchip/rk_drm.c b/sys/arch/arm/rockchip/rk_drm.c
new file mode 100644
index 00000000000..609f88471c1
--- /dev/null
+++ b/sys/arch/arm/rockchip/rk_drm.c
@@ -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 <jmcneill@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;
+}
diff --git a/sys/arch/arm/rockchip/rk_drm.h b/sys/arch/arm/rockchip/rk_drm.h
new file mode 100644
index 00000000000..7fab4f73d30
--- /dev/null
+++ b/sys/arch/arm/rockchip/rk_drm.h
@@ -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 <jmcneill@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 */
diff --git a/sys/arch/arm/rockchip/rk_dwhdmi.c b/sys/arch/arm/rockchip/rk_dwhdmi.c
new file mode 100644
index 00000000000..944ffe16c18
--- /dev/null
+++ b/sys/arch/arm/rockchip/rk_dwhdmi.c
@@ -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 <jmcneill@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);
diff --git a/sys/arch/arm/rockchip/rk_fb.c b/sys/arch/arm/rockchip/rk_fb.c
new file mode 100644
index 00000000000..393966f7ab5
--- /dev/null
+++ b/sys/arch/arm/rockchip/rk_fb.c
@@ -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 <jmcneill@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;
+ }
+}
diff --git a/sys/arch/arm/rockchip/rk_i2c.c b/sys/arch/arm/rockchip/rk_i2c.c
index 1feec2403ca..5232d233fc0 100644
--- a/sys/arch/arm/rockchip/rk_i2c.c
+++ b/sys/arch/arm/rockchip/rk_i2c.c
@@ -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 <jmcneill@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, i2c_addr_t addr, const uint8_t *cmd,
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_addr_t addr,
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_addr_t addr,
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_addr_t addr,
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);
}
diff --git a/sys/arch/arm/rockchip/rk_i2s.c b/sys/arch/arm/rockchip/rk_i2s.c
new file mode 100644
index 00000000000..1d26fbc6655
--- /dev/null
+++ b/sys/arch/arm/rockchip/rk_i2s.c
@@ -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 <jmcneill@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);
diff --git a/sys/arch/arm/rockchip/rk_vop.c b/sys/arch/arm/rockchip/rk_vop.c
new file mode 100644
index 00000000000..4c065a58aec
--- /dev/null
+++ b/sys/arch/arm/rockchip/rk_vop.c
@@ -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 <jmcneill@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);
diff --git a/sys/arch/evbarm/conf/GENERIC64 b/sys/arch/evbarm/conf/GENERIC64
index 11922b52fe1..017789ecc54 100644
--- a/sys/arch/evbarm/conf/GENERIC64
+++ b/sys/arch/evbarm/conf/GENERIC64
@@ -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
diff --git a/sys/conf/files b/sys/conf/files
index efab6b68897..744803d96ab 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -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
diff --git a/sys/dev/fdt/ausoc.c b/sys/dev/fdt/ausoc.c
index 3bb5245b2a2..88ea54afba1 100644
--- a/sys/dev/fdt/ausoc.c
+++ b/sys/dev/fdt/ausoc.c
@@ -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 <jmcneill@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 *start, void *end, int blksize,
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 *start, void *end, int blksize,
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,
diff --git a/sys/dev/fdt/fdt_clock.c b/sys/dev/fdt/fdt_clock.c
index 28db66c1522..0f2503b88e6 100644
--- a/sys/dev/fdt/fdt_clock.c
+++ b/sys/dev/fdt/fdt_clock.c
@@ -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 <jmcneill@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 *clkname)
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.
*
diff --git a/sys/dev/fdt/fdtvar.h b/sys/dev/fdt/fdtvar.h
index a0c4b7b374e..dc6d2665d7f 100644
--- a/sys/dev/fdt/fdtvar.h
+++ b/sys/dev/fdt/fdtvar.h
@@ -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 <jmcneill@invisible.ca>
@@ -345,6 +345,8 @@ struct clk * fdtbus_clock_get(int, const char *);
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);
diff --git a/sys/dev/ic/dw_hdmi.c b/sys/dev/ic/dw_hdmi.c
index ac374702636..41198497c38 100644
--- a/sys/dev/ic/dw_hdmi.c
+++ b/sys/dev/ic/dw_hdmi.c
@@ -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 <jmcneill@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 1.1 2019/01/30 01:19:49 jmcneill Exp $")
#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 1.1 2019/01/30 01:19:49 jmcneill Exp $")
#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 1.1 2019/01/30 01:19:49 jmcneill Exp $")
#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 1.1 2019/01/30 01:19:49 jmcneill Exp $")
#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, struct drm_display_mode *mode)
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 *sc)
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_connector *connector)
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 *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 dwhdmi_bridge_funcs = {
.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;
}
diff --git a/sys/dev/ic/dw_hdmi.h b/sys/dev/ic/dw_hdmi.h
index 52782b73b4e..9c6fb0580bd 100644
--- a/sys/dev/ic/dw_hdmi.h
+++ b/sys/dev/ic/dw_hdmi.h
@@ -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 <jmcneill@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 *, struct drm_encoder *);
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 */
diff --git a/sys/dev/ic/dw_hdmi_phy.c b/sys/dev/ic/dw_hdmi_phy.c
new file mode 100644
index 00000000000..bf29676fcf2
--- /dev/null
+++ b/sys/dev/ic/dw_hdmi_phy.c
@@ -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 <gonzo@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);
+}