diff options
| -rw-r--r-- | sys/arch/arm/dts/rk3399-rockpro64.dts | 6 | ||||
| -rw-r--r-- | sys/arch/arm/rockchip/files.rockchip | 27 | ||||
| -rw-r--r-- | sys/arch/arm/rockchip/rk3399_cru.c | 209 | ||||
| -rw-r--r-- | sys/arch/arm/rockchip/rk_cru.h | 20 | ||||
| -rw-r--r-- | sys/arch/arm/rockchip/rk_cru_composite.c | 66 | ||||
| -rw-r--r-- | sys/arch/arm/rockchip/rk_drm.c | 512 | ||||
| -rw-r--r-- | sys/arch/arm/rockchip/rk_drm.h | 99 | ||||
| -rw-r--r-- | sys/arch/arm/rockchip/rk_dwhdmi.c | 310 | ||||
| -rw-r--r-- | sys/arch/arm/rockchip/rk_fb.c | 160 | ||||
| -rw-r--r-- | sys/arch/arm/rockchip/rk_i2c.c | 51 | ||||
| -rw-r--r-- | sys/arch/arm/rockchip/rk_i2s.c | 638 | ||||
| -rw-r--r-- | sys/arch/arm/rockchip/rk_vop.c | 659 | ||||
| -rw-r--r-- | sys/arch/evbarm/conf/GENERIC64 | 8 | ||||
| -rw-r--r-- | sys/conf/files | 3 | ||||
| -rw-r--r-- | sys/dev/fdt/ausoc.c | 46 | ||||
| -rw-r--r-- | sys/dev/fdt/fdt_clock.c | 28 | ||||
| -rw-r--r-- | sys/dev/fdt/fdtvar.h | 4 | ||||
| -rw-r--r-- | sys/dev/ic/dw_hdmi.c | 308 | ||||
| -rw-r--r-- | sys/dev/ic/dw_hdmi.h | 39 | ||||
| -rw-r--r-- | sys/dev/ic/dw_hdmi_phy.c | 401 |
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); +} |
