Module Name: src Committed By: jmcneill Date: Wed Jan 30 01:24:00 UTC 2019
Modified Files: src/sys/arch/arm/sunxi: files.sunxi sun50i_a64_ccu.c sunxi_ccu.h sunxi_ccu_fractional.c src/sys/dev/fdt: fdt_i2c.c fdt_phy.c fdt_port.c fdt_port.h fdtvar.h files.fdt Added Files: src/sys/arch/arm/sunxi: sunxi_drm.c sunxi_drm.h sunxi_dwhdmi.c sunxi_fb.c sunxi_hdmiphy.c sunxi_hdmiphy.h sunxi_lcdc.c sunxi_mixer.c src/sys/dev/fdt: hdmi_connector.c Log Message: Add support for Allwinner A64's display pipeline. To generate a diff of this commit: cvs rdiff -u -r1.60 -r1.61 src/sys/arch/arm/sunxi/files.sunxi cvs rdiff -u -r1.10 -r1.11 src/sys/arch/arm/sunxi/sun50i_a64_ccu.c cvs rdiff -u -r1.20 -r1.21 src/sys/arch/arm/sunxi/sunxi_ccu.h cvs rdiff -u -r1.3 -r1.4 src/sys/arch/arm/sunxi/sunxi_ccu_fractional.c cvs rdiff -u -r0 -r1.1 src/sys/arch/arm/sunxi/sunxi_drm.c \ src/sys/arch/arm/sunxi/sunxi_drm.h src/sys/arch/arm/sunxi/sunxi_dwhdmi.c \ src/sys/arch/arm/sunxi/sunxi_fb.c src/sys/arch/arm/sunxi/sunxi_hdmiphy.c \ src/sys/arch/arm/sunxi/sunxi_hdmiphy.h \ src/sys/arch/arm/sunxi/sunxi_lcdc.c src/sys/arch/arm/sunxi/sunxi_mixer.c cvs rdiff -u -r1.5 -r1.6 src/sys/dev/fdt/fdt_i2c.c cvs rdiff -u -r1.2 -r1.3 src/sys/dev/fdt/fdt_phy.c cvs rdiff -u -r1.1 -r1.2 src/sys/dev/fdt/fdt_port.c \ src/sys/dev/fdt/fdt_port.h cvs rdiff -u -r1.47 -r1.48 src/sys/dev/fdt/fdtvar.h cvs rdiff -u -r1.41 -r1.42 src/sys/dev/fdt/files.fdt cvs rdiff -u -r0 -r1.1 src/sys/dev/fdt/hdmi_connector.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/arch/arm/sunxi/files.sunxi diff -u src/sys/arch/arm/sunxi/files.sunxi:1.60 src/sys/arch/arm/sunxi/files.sunxi:1.61 --- src/sys/arch/arm/sunxi/files.sunxi:1.60 Tue Jan 22 20:17:36 2019 +++ src/sys/arch/arm/sunxi/files.sunxi Wed Jan 30 01:24:00 2019 @@ -1,4 +1,4 @@ -# $NetBSD: files.sunxi,v 1.60 2019/01/22 20:17:36 jmcneill Exp $ +# $NetBSD: files.sunxi,v 1.61 2019/01/30 01:24:00 jmcneill Exp $ # # Configuration info for Allwinner sunxi family SoCs # @@ -307,6 +307,36 @@ device sunxide2ccu: sunxi_ccu attach sunxide2ccu at fdt with sunxi_de2ccu file arch/arm/sunxi/sunxi_de2_ccu.c sunxi_de2ccu +# DE2 mixer +device sunximixer: drmkms +attach sunximixer at fdt with sunxi_mixer +file arch/arm/sunxi/sunxi_mixer.c sunxi_mixer + +# DE2 timing controller +device sunxilcdc: drmkms +attach sunxilcdc at fdt with sunxi_lcdc +file arch/arm/sunxi/sunxi_lcdc.c sunxi_lcdc + +# Display Pipeline +define sunxifbbus { } +device sunxidrm: drmkms, ddc_read_edid, sunxifbbus +attach sunxidrm at fdt with sunxi_drm +file arch/arm/sunxi/sunxi_drm.c sunxi_drm + +# DRM framebuffer console +device sunxifb: sunxifbbus, drmfb, wsemuldisplaydev +attach sunxifb at sunxifbbus with sunxi_fb +file arch/arm/sunxi/sunxi_fb.c sunxi_fb + +# Allwinner HDMI (Designware based) +attach dwhdmi at fdt with sunxi_dwhdmi +file arch/arm/sunxi/sunxi_dwhdmi.c sunxi_dwhdmi + +# Allwinner HDMI TX PHY +device sunxihdmiphy: drmkms +attach sunxihdmiphy at fdt with sunxi_hdmiphy +file arch/arm/sunxi/sunxi_hdmiphy.c sunxi_hdmiphy | sunxi_dwhdmi + # SOC parameters defflag opt_soc.h SOC_SUNXI defflag opt_soc.h SOC_SUNXI_MC Index: src/sys/arch/arm/sunxi/sun50i_a64_ccu.c diff -u src/sys/arch/arm/sunxi/sun50i_a64_ccu.c:1.10 src/sys/arch/arm/sunxi/sun50i_a64_ccu.c:1.11 --- src/sys/arch/arm/sunxi/sun50i_a64_ccu.c:1.10 Tue Jan 22 23:06:49 2019 +++ src/sys/arch/arm/sunxi/sun50i_a64_ccu.c Wed Jan 30 01:24:00 2019 @@ -1,4 +1,4 @@ -/* $NetBSD: sun50i_a64_ccu.c,v 1.10 2019/01/22 23:06:49 jmcneill Exp $ */ +/* $NetBSD: sun50i_a64_ccu.c,v 1.11 2019/01/30 01:24:00 jmcneill Exp $ */ /*- * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca> @@ -28,7 +28,7 @@ #include <sys/cdefs.h> -__KERNEL_RCSID(1, "$NetBSD: sun50i_a64_ccu.c,v 1.10 2019/01/22 23:06:49 jmcneill Exp $"); +__KERNEL_RCSID(1, "$NetBSD: sun50i_a64_ccu.c,v 1.11 2019/01/30 01:24:00 jmcneill Exp $"); #include <sys/param.h> #include <sys/bus.h> @@ -42,8 +42,10 @@ __KERNEL_RCSID(1, "$NetBSD: sun50i_a64_c #define PLL_CPUX_CTRL_REG 0x000 #define PLL_AUDIO_CTRL_REG 0x008 +#define PLL_VIDEO0_CTRL_REG 0x010 #define PLL_PERIPH0_CTRL_REG 0x028 #define PLL_PERIPH1_CTRL_REG 0x02c +#define PLL_VIDEO1_CTRL_REG 0x030 #define PLL_DE_CTRL_REG 0x048 #define AHB1_APB1_CFG_REG 0x054 #define APB2_CFG_REG 0x058 @@ -61,7 +63,10 @@ __KERNEL_RCSID(1, "$NetBSD: sun50i_a64_c #define DRAM_CFG_REG 0x0f4 #define MBUS_RST_REG 0x0fc #define DE_CLK_REG 0x104 +#define TCON1_CLK_REG 0x11c #define AC_DIG_CLK_REG 0x140 +#define HDMI_CLK_REG 0x150 +#define HDMI_SLOW_CLK_REG 0x154 #define BUS_SOFT_RST_REG0 0x2c0 #define BUS_SOFT_RST_REG1 0x2c4 #define BUS_SOFT_RST_REG2 0x2c8 @@ -146,6 +151,8 @@ static const char *apb2_parents[] = { "l static const char *mmc_parents[] = { "hosc", "pll_periph0_2x", "pll_periph1_2x" }; static const char *ths_parents[] = { "hosc", NULL, NULL, NULL }; static const char *de_parents[] = { "pll_periph0_2x", "pll_de" }; +static const char *hdmi_parents[] = { "pll_video0", "pll_video1" }; +static const char *tcon1_parents[] = { "pll_video0", NULL, "pll_video1", NULL }; static const struct sunxi_ccu_nkmp_tbl sun50i_a64_cpux_table[] = { { 60000000, 9, 0, 0, 2 }, @@ -262,8 +269,36 @@ static struct sunxi_ccu_clk sun50i_a64_c SUNXI_CCU_FIXED_FACTOR(A64_CLK_PLL_AUDIO_4X, "pll_audio_4x", "pll_audio_base", 1, 4), SUNXI_CCU_FIXED_FACTOR(A64_CLK_PLL_AUDIO_8X, "pll_audio_8x", "pll_audio_base", 1, 8), + SUNXI_CCU_FRACTIONAL(A64_CLK_PLL_VIDEO0, "pll_video0", "hosc", + PLL_VIDEO0_CTRL_REG, /* reg */ + __BITS(14,8), /* m */ + 16, /* m_min */ + 50, /* m_max */ + __BIT(24), /* div_en */ + __BIT(25), /* frac_sel */ + 270000000, 297000000, /* frac values */ + __BITS(3,0), /* prediv */ + 4, /* prediv_val */ + __BIT(31), /* enable */ + SUNXI_CCU_FRACTIONAL_PLUSONE | SUNXI_CCU_FRACTIONAL_SET_ENABLE), + + SUNXI_CCU_FIXED_FACTOR(A64_CLK_PLL_VIDEO0_2X, "pll_video0_2x", "pll_video0", 1, 2), + + SUNXI_CCU_FRACTIONAL(A64_CLK_PLL_VIDEO1, "pll_video1", "hosc", + PLL_VIDEO1_CTRL_REG, /* reg */ + __BITS(14,8), /* m */ + 16, /* m_min */ + 50, /* m_max */ + __BIT(24), /* div_en */ + __BIT(25), /* frac_sel */ + 270000000, 297000000, /* frac values */ + __BITS(3,0), /* prediv */ + 4, /* prediv_val */ + __BIT(31), /* enable */ + SUNXI_CCU_FRACTIONAL_PLUSONE | SUNXI_CCU_FRACTIONAL_SET_ENABLE), + SUNXI_CCU_FRACTIONAL(A64_CLK_PLL_DE, "pll_de", "hosc", - DE_CLK_REG, /* reg */ + PLL_DE_CTRL_REG, /* reg */ __BITS(14,8), /* m */ 16, /* m_min */ 50, /* m_max */ @@ -273,7 +308,7 @@ static struct sunxi_ccu_clk sun50i_a64_c __BITS(3,0), /* prediv */ 2, /* prediv_val */ __BIT(31), /* enable */ - SUNXI_CCU_FRACTIONAL_PLUSONE), + SUNXI_CCU_FRACTIONAL_PLUSONE | SUNXI_CCU_FRACTIONAL_SET_ENABLE), SUNXI_CCU_PREDIV(A64_CLK_AHB1, "ahb1", ahb1_parents, AHB1_APB1_CFG_REG, /* reg */ @@ -346,6 +381,23 @@ static struct sunxi_ccu_clk sun50i_a64_c SUNXI_CCU_GATE(A64_CLK_AC_DIG_4X, "ac-dig-4x", "pll_audio_4x", AC_DIG_CLK_REG, 30), + SUNXI_CCU_DIV_GATE(A64_CLK_HDMI, "hdmi", hdmi_parents, + HDMI_CLK_REG, /* reg */ + __BITS(3,0), /* div */ + __BITS(25,24), /* sel */ + __BIT(31), /* enable */ + 0), + + SUNXI_CCU_GATE(A64_CLK_HDMI_DDC, "hdmi-ddc", "hosc", + HDMI_SLOW_CLK_REG, 31), + + SUNXI_CCU_DIV_GATE(A64_CLK_TCON1, "tcon1", tcon1_parents, + TCON1_CLK_REG, /* reg */ + __BITS(3,0), /* div */ + __BITS(25,24), /* sel */ + __BIT(31), /* enable */ + 0), + SUNXI_CCU_GATE(A64_CLK_BUS_MIPI_DSI, "bus-mipi-dsi", "ahb1", BUS_CLK_GATING_REG0, 1), SUNXI_CCU_GATE(A64_CLK_BUS_CE, "bus-ce", "ahb1", @@ -482,5 +534,9 @@ sun50i_a64_ccu_attach(device_t parent, d aprint_naive("\n"); aprint_normal(": A64 CCU\n"); + /* Set DE parent to PLL_DE */ + clk_set_parent(&sc->sc_clks[A64_CLK_DE].base, &sc->sc_clks[A64_CLK_PLL_DE].base); + clk_set_rate(&sc->sc_clks[A64_CLK_PLL_DE].base, 420000000); + sunxi_ccu_print(sc); } Index: src/sys/arch/arm/sunxi/sunxi_ccu.h diff -u src/sys/arch/arm/sunxi/sunxi_ccu.h:1.20 src/sys/arch/arm/sunxi/sunxi_ccu.h:1.21 --- src/sys/arch/arm/sunxi/sunxi_ccu.h:1.20 Tue Jan 22 23:06:49 2019 +++ src/sys/arch/arm/sunxi/sunxi_ccu.h Wed Jan 30 01:24:00 2019 @@ -1,4 +1,4 @@ -/* $NetBSD: sunxi_ccu.h,v 1.20 2019/01/22 23:06:49 jmcneill Exp $ */ +/* $NetBSD: sunxi_ccu.h,v 1.21 2019/01/30 01:24:00 jmcneill Exp $ */ /*- * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca> @@ -373,6 +373,7 @@ struct sunxi_ccu_fractional { uint32_t enable; uint32_t flags; #define SUNXI_CCU_FRACTIONAL_PLUSONE __BIT(0) +#define SUNXI_CCU_FRACTIONAL_SET_ENABLE __BIT(1) }; int sunxi_ccu_fractional_enable(struct sunxi_ccu_softc *, Index: src/sys/arch/arm/sunxi/sunxi_ccu_fractional.c diff -u src/sys/arch/arm/sunxi/sunxi_ccu_fractional.c:1.3 src/sys/arch/arm/sunxi/sunxi_ccu_fractional.c:1.4 --- src/sys/arch/arm/sunxi/sunxi_ccu_fractional.c:1.3 Tue Jan 22 23:06:49 2019 +++ src/sys/arch/arm/sunxi/sunxi_ccu_fractional.c Wed Jan 30 01:24:00 2019 @@ -1,4 +1,4 @@ -/* $NetBSD: sunxi_ccu_fractional.c,v 1.3 2019/01/22 23:06:49 jmcneill Exp $ */ +/* $NetBSD: sunxi_ccu_fractional.c,v 1.4 2019/01/30 01:24:00 jmcneill Exp $ */ /*- * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca> @@ -27,7 +27,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_fractional.c,v 1.3 2019/01/22 23:06:49 jmcneill Exp $"); +__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_fractional.c,v 1.4 2019/01/30 01:24:00 jmcneill Exp $"); #include <sys/param.h> #include <sys/bus.h> @@ -169,8 +169,11 @@ sunxi_ccu_fractional_set_rate(struct sun val &= ~fractional->m; val |= __SHIFTIN(best_m, fractional->m); + if (fractional->flags & SUNXI_CCU_FRACTIONAL_SET_ENABLE) + val |= fractional->enable; CCU_WRITE(sc, fractional->reg, val); + return 0; } Index: src/sys/dev/fdt/fdt_i2c.c diff -u src/sys/dev/fdt/fdt_i2c.c:1.5 src/sys/dev/fdt/fdt_i2c.c:1.6 --- src/sys/dev/fdt/fdt_i2c.c:1.5 Wed Sep 26 20:03:36 2018 +++ src/sys/dev/fdt/fdt_i2c.c Wed Jan 30 01:24:00 2019 @@ -1,4 +1,4 @@ -/* $NetBSD: fdt_i2c.c,v 1.5 2018/09/26 20:03:36 jakllsch Exp $ */ +/* $NetBSD: fdt_i2c.c,v 1.6 2019/01/30 01:24:00 jmcneill Exp $ */ /*- * Copyright (c) 2015 Jared D. McNeill <jmcne...@invisible.ca> @@ -27,7 +27,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: fdt_i2c.c,v 1.5 2018/09/26 20:03:36 jakllsch Exp $"); +__KERNEL_RCSID(0, "$NetBSD: fdt_i2c.c,v 1.6 2019/01/30 01:24:00 jmcneill Exp $"); #include <sys/param.h> #include <sys/bus.h> @@ -89,6 +89,18 @@ fdtbus_get_i2c_tag(int phandle) return i2c->i2c_funcs->get_tag(i2c->i2c_dev); } +i2c_tag_t +fdtbus_i2c_acquire(int phandle, const char *prop) +{ + int i2c_phandle; + + i2c_phandle = fdtbus_get_phandle(phandle, prop); + if (i2c_phandle == -1) + return NULL; + + return fdtbus_get_i2c_tag(i2c_phandle); +} + device_t fdtbus_attach_i2cbus(device_t dev, int phandle, i2c_tag_t tag, cfprint_t print) { Index: src/sys/dev/fdt/fdt_phy.c diff -u src/sys/dev/fdt/fdt_phy.c:1.2 src/sys/dev/fdt/fdt_phy.c:1.3 --- src/sys/dev/fdt/fdt_phy.c:1.2 Sat Jun 30 20:34:43 2018 +++ src/sys/dev/fdt/fdt_phy.c Wed Jan 30 01:24:00 2019 @@ -1,4 +1,4 @@ -/* $NetBSD: fdt_phy.c,v 1.2 2018/06/30 20:34:43 jmcneill Exp $ */ +/* $NetBSD: fdt_phy.c,v 1.3 2019/01/30 01:24:00 jmcneill Exp $ */ /*- * Copyright (c) 2015-2017 Jared McNeill <jmcne...@invisible.ca> @@ -27,7 +27,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: fdt_phy.c,v 1.2 2018/06/30 20:34:43 jmcneill Exp $"); +__KERNEL_RCSID(0, "$NetBSD: fdt_phy.c,v 1.3 2019/01/30 01:24:00 jmcneill Exp $"); #include <sys/param.h> #include <sys/bus.h> @@ -172,6 +172,12 @@ fdtbus_phy_put(struct fdtbus_phy *phy) kmem_free(phy, sizeof(*phy)); } +device_t +fdtbus_phy_device(struct fdtbus_phy *phy) +{ + return phy->phy_pc->pc_dev; +} + int fdtbus_phy_enable(struct fdtbus_phy *phy, bool enable) { Index: src/sys/dev/fdt/fdt_port.c diff -u src/sys/dev/fdt/fdt_port.c:1.1 src/sys/dev/fdt/fdt_port.c:1.2 --- src/sys/dev/fdt/fdt_port.c:1.1 Tue Apr 3 12:40:20 2018 +++ src/sys/dev/fdt/fdt_port.c Wed Jan 30 01:24:00 2019 @@ -1,4 +1,4 @@ -/* $NetBSD: fdt_port.c,v 1.1 2018/04/03 12:40:20 bouyer Exp $ */ +/* $NetBSD: fdt_port.c,v 1.2 2019/01/30 01:24:00 jmcneill Exp $ */ /*- * Copyright (c) 2018 The NetBSD Foundation, Inc. @@ -38,7 +38,7 @@ #include <sys/cdefs.h> -__KERNEL_RCSID(1, "$NetBSD: fdt_port.c,v 1.1 2018/04/03 12:40:20 bouyer Exp $"); +__KERNEL_RCSID(1, "$NetBSD: fdt_port.c,v 1.2 2019/01/30 01:24:00 jmcneill Exp $"); #include <sys/param.h> #include <sys/systm.h> @@ -119,6 +119,20 @@ fdt_endpoint_get_from_index(struct fdt_d } struct fdt_endpoint * +fdt_endpoint_remote_from_index(struct fdt_device_ports *device_ports, + int port_index, int ep_index) +{ + struct fdt_endpoint *ep; + + ep = fdt_endpoint_get_from_index(device_ports, port_index, + ep_index); + if (ep == NULL) + return NULL; + + return fdt_endpoint_remote(ep); +} + +struct fdt_endpoint * fdt_endpoint_remote(struct fdt_endpoint *ep) { return ep->ep_rep; @@ -154,6 +168,12 @@ fdt_endpoint_is_enabled(struct fdt_endpo return ep->ep_enabled; } +enum endpoint_type +fdt_endpoint_type(struct fdt_endpoint *ep) +{ + return ep->ep_type; +} + int fdt_endpoint_activate(struct fdt_endpoint *ep, bool activate) { @@ -170,6 +190,8 @@ fdt_endpoint_activate(struct fdt_endpoin return EBUSY; rdp = rep->ep_port->port_dp; +device_printf(rdp->dp_dev, "activating port %d endpoint %d\n", + fdt_endpoint_port_index(rep), fdt_endpoint_index(rep)); if (rdp->dp_ep_activate) error = rdp->dp_ep_activate(rdp->dp_dev, rep, activate); @@ -179,6 +201,21 @@ fdt_endpoint_activate(struct fdt_endpoin } int +fdt_endpoint_activate_direct(struct fdt_endpoint *ep, bool activate) +{ + struct fdt_device_ports *dp; + int error = 0; + + dp = ep->ep_port->port_dp; +device_printf(dp->dp_dev, "activating port %d endpoint %d (direct)\n", + fdt_endpoint_port_index(ep), fdt_endpoint_index(ep)); + if (dp->dp_ep_activate) + error = dp->dp_ep_activate(dp->dp_dev, ep, activate); + + return error; +} + +int fdt_endpoint_enable(struct fdt_endpoint *ep, bool enable) { struct fdt_endpoint *rep = fdt_endpoint_remote(ep); Index: src/sys/dev/fdt/fdt_port.h diff -u src/sys/dev/fdt/fdt_port.h:1.1 src/sys/dev/fdt/fdt_port.h:1.2 --- src/sys/dev/fdt/fdt_port.h:1.1 Tue Apr 3 12:40:20 2018 +++ src/sys/dev/fdt/fdt_port.h Wed Jan 30 01:24:00 2019 @@ -1,4 +1,4 @@ -/* $NetBSD: fdt_port.h,v 1.1 2018/04/03 12:40:20 bouyer Exp $ */ +/* $NetBSD: fdt_port.h,v 1.2 2019/01/30 01:24:00 jmcneill Exp $ */ /*- * Copyright (c) 2018 The NetBSD Foundation, Inc. @@ -44,6 +44,8 @@ #ifndef _DEV_FDT_FDT_PORT_H_ #define _DEV_FDT_FDT_PORT_H_ +struct drm_device; + struct fdt_port; struct fdt_endpoint; @@ -63,6 +65,11 @@ enum endpoint_type { EP_OTHER = 0, EP_CONNECTOR, EP_PANEL, + + EP_DRM_BRIDGE, /* struct drm_bridge */ + EP_DRM_CONNECTOR, /* struct drm_connector */ + EP_DRM_CRTC, /* struct drm_crtc */ + EP_DRM_ENCODER, /* struct drm_encoder */ }; @@ -79,15 +86,19 @@ struct fdt_endpoint *fdt_endpoint_get_fr struct fdt_endpoint *fdt_endpoint_get_from_index(struct fdt_device_ports *, int, int); struct fdt_endpoint *fdt_endpoint_remote(struct fdt_endpoint *); +struct fdt_endpoint *fdt_endpoint_remote_from_index(struct fdt_device_ports *, + int, int); /* * get informations/data for a given endpoint */ int fdt_endpoint_port_index(struct fdt_endpoint *); int fdt_endpoint_index(struct fdt_endpoint *); +int fdt_endpoint_phandle(struct fdt_endpoint *); device_t fdt_endpoint_device(struct fdt_endpoint *); bool fdt_endpoint_is_active(struct fdt_endpoint *); bool fdt_endpoint_is_enabled(struct fdt_endpoint *); +enum endpoint_type fdt_endpoint_type(struct fdt_endpoint *); /* * call dp_ep_get_data() for the endpoint. The returned pointer is * type of driver-specific. @@ -99,6 +110,12 @@ void * fdt_endpoint_get_data(struct fdt_ * called for the remote endpoint */ int fdt_endpoint_activate(struct fdt_endpoint *, bool); + +/* + * Activate/deactive an endpoint by direct reference. + */ +int fdt_endpoint_activate_direct(struct fdt_endpoint *, bool); + /* * Enable/disable an endpoint. This causes dp_ep_enable() to be called for * the remote endpoint Index: src/sys/dev/fdt/fdtvar.h diff -u src/sys/dev/fdt/fdtvar.h:1.47 src/sys/dev/fdt/fdtvar.h:1.48 --- src/sys/dev/fdt/fdtvar.h:1.47 Sat Jan 26 14:38:30 2019 +++ src/sys/dev/fdt/fdtvar.h Wed Jan 30 01:24:00 2019 @@ -1,4 +1,4 @@ -/* $NetBSD: fdtvar.h,v 1.47 2019/01/26 14:38:30 thorpej Exp $ */ +/* $NetBSD: fdtvar.h,v 1.48 2019/01/30 01:24:00 jmcneill Exp $ */ /*- * Copyright (c) 2015 Jared D. McNeill <jmcne...@invisible.ca> @@ -280,6 +280,7 @@ int fdtbus_get_reg64(int, u_int, uint64 int fdtbus_get_phandle(int, const char *); int fdtbus_get_phandle_from_native(int); i2c_tag_t fdtbus_get_i2c_tag(int); +i2c_tag_t fdtbus_i2c_acquire(int, const char *); void * fdtbus_intr_establish(int, u_int, int, int, int (*func)(void *), void *arg); void * fdtbus_intr_establish_raw(int, const u_int *, int, int, @@ -344,6 +345,7 @@ int fdtbus_reset_deassert(struct fdtbus struct fdtbus_phy *fdtbus_phy_get(int, const char *); struct fdtbus_phy *fdtbus_phy_get_index(int, u_int); void fdtbus_phy_put(struct fdtbus_phy *); +device_t fdtbus_phy_device(struct fdtbus_phy *); int fdtbus_phy_enable(struct fdtbus_phy *, bool); struct fdtbus_mmc_pwrseq *fdtbus_mmc_pwrseq_get(int); Index: src/sys/dev/fdt/files.fdt diff -u src/sys/dev/fdt/files.fdt:1.41 src/sys/dev/fdt/files.fdt:1.42 --- src/sys/dev/fdt/files.fdt:1.41 Fri Oct 19 21:09:10 2018 +++ src/sys/dev/fdt/files.fdt Wed Jan 30 01:24:00 2019 @@ -1,4 +1,4 @@ -# $NetBSD: files.fdt,v 1.41 2018/10/19 21:09:10 jakllsch Exp $ +# $NetBSD: files.fdt,v 1.42 2019/01/30 01:24:00 jmcneill Exp $ include "external/bsd/libfdt/conf/files.libfdt" @@ -45,6 +45,10 @@ device panel: fdt_port attach panel at fdt with fdt_panel file dev/fdt/panel_fdt.c fdt_panel +device dispcon: fdt_port, drmkms, ddc_read_edid +attach dispcon at fdt with dispcon_hdmi +file dev/fdt/hdmi_connector.c dispcon_hdmi + file dev/fdt/fdt_openfirm.c fdtbase file dev/fdt/fdt_subr.c fdtbase file dev/fdt/fdt_clock.c fdt Added files: Index: src/sys/arch/arm/sunxi/sunxi_drm.c diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_drm.c:1.1 --- /dev/null Wed Jan 30 01:24:00 2019 +++ src/sys/arch/arm/sunxi/sunxi_drm.c Wed Jan 30 01:24:00 2019 @@ -0,0 +1,407 @@ +/* $NetBSD: sunxi_drm.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */ + +/*- + * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: sunxi_drm.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/intr.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/conf.h> + +#include <uvm/uvm_extern.h> +#include <uvm/uvm_object.h> +#include <uvm/uvm_device.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> + +#include <dev/fdt/fdtvar.h> +#include <dev/fdt/fdt_port.h> + +#include <arm/sunxi/sunxi_drm.h> + +static TAILQ_HEAD(, sunxi_drm_endpoint) sunxi_drm_endpoints = + TAILQ_HEAD_INITIALIZER(sunxi_drm_endpoints); + +static const char * const compatible[] = { + "allwinner,sun50i-a64-display-engine", + NULL +}; + +static const char * fb_compatible[] = { + "allwinner,simple-framebuffer", + NULL +}; + +static int sunxi_drm_match(device_t, cfdata_t, void *); +static void sunxi_drm_attach(device_t, device_t, void *); + +static void sunxi_drm_init(device_t); + +static int sunxi_drm_set_busid(struct drm_device *, struct drm_master *); + +static int sunxi_drm_load(struct drm_device *, unsigned long); +static int sunxi_drm_unload(struct drm_device *); + +static struct drm_driver sunxi_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME, + .dev_priv_size = 0, + .load = sunxi_drm_load, + .unload = sunxi_drm_unload, + + .gem_free_object = drm_gem_cma_free_object, + .mmap_object = drm_gem_or_legacy_mmap_object, + .gem_uvm_ops = &drm_gem_cma_uvm_ops, + + .dumb_create = drm_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + +#if notyet + .get_vblank_counter = sunxi_drm_get_vblank_counter, + .enable_vblank = sunxi_drm_enable_vblank, + .disable_vblank = sunxi_drm_disable_vblank, +#endif + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, + + .set_busid = sunxi_drm_set_busid, +}; + +CFATTACH_DECL_NEW(sunxi_drm, sizeof(struct sunxi_drm_softc), + sunxi_drm_match, sunxi_drm_attach, NULL, NULL); + +static int +sunxi_drm_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compatible(faa->faa_phandle, compatible); +} + +static void +sunxi_drm_attach(device_t parent, device_t self, void *aux) +{ + struct sunxi_drm_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + struct drm_driver * const driver = &sunxi_drm_driver; + + sc->sc_dev = self; + sc->sc_dmat = faa->faa_dmat; + sc->sc_bst = faa->faa_bst; + sc->sc_phandle = faa->faa_phandle; + + aprint_naive("\n"); + aprint_normal(": Display Engine Pipeline\n"); + + sc->sc_ddev = drm_dev_alloc(driver, sc->sc_dev); + if (sc->sc_ddev == NULL) { + aprint_error_dev(self, "couldn't allocate DRM device\n"); + return; + } + sc->sc_ddev->dev_private = sc; + sc->sc_ddev->bst = sc->sc_bst; + sc->sc_ddev->bus_dmat = sc->sc_dmat; + sc->sc_ddev->dmat = sc->sc_ddev->bus_dmat; + sc->sc_ddev->dmat_subregion_p = false; + + fdt_remove_bycompat(fb_compatible); + + config_defer(self, sunxi_drm_init); +} + +static void +sunxi_drm_init(device_t dev) +{ + struct sunxi_drm_softc * const sc = device_private(dev); + struct drm_driver * const driver = &sunxi_drm_driver; + int error; + + error = -drm_dev_register(sc->sc_ddev, 0); + if (error) { + drm_dev_unref(sc->sc_ddev); + aprint_error_dev(dev, "couldn't register DRM device: %d\n", + error); + return; + } + + aprint_normal_dev(dev, "initialized %s %d.%d.%d %s on minor %d\n", + driver->name, driver->major, driver->minor, driver->patchlevel, + driver->date, sc->sc_ddev->primary->index); +} + +static int +sunxi_drm_set_busid(struct drm_device *ddev, struct drm_master *master) +{ + struct sunxi_drm_softc * const sc = sunxi_drm_private(ddev); + char id[32]; + + snprintf(id, sizeof(id), "platform:sunxi:%u", device_unit(sc->sc_dev)); + + master->unique = kzalloc(strlen(id) + 1, GFP_KERNEL); + if (master->unique == NULL) + return -ENOMEM; + strcpy(master->unique, id); + master->unique_len = strlen(master->unique); + + return 0; +} + +static int +sunxi_drm_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file, unsigned int *handle) +{ + struct sunxi_drm_framebuffer *sfb = to_sunxi_drm_framebuffer(fb); + + return drm_gem_handle_create(file, &sfb->obj->base, handle); +} + +static void +sunxi_drm_fb_destroy(struct drm_framebuffer *fb) +{ + struct sunxi_drm_framebuffer *sfb = to_sunxi_drm_framebuffer(fb); + + drm_framebuffer_cleanup(fb); + drm_gem_object_unreference_unlocked(&sfb->obj->base); + kmem_free(sfb, sizeof(*sfb)); +} + +static const struct drm_framebuffer_funcs sunxi_drm_framebuffer_funcs = { + .create_handle = sunxi_drm_fb_create_handle, + .destroy = sunxi_drm_fb_destroy, +}; + +static struct drm_framebuffer * +sunxi_drm_fb_create(struct drm_device *ddev, struct drm_file *file, + struct drm_mode_fb_cmd2 *cmd) +{ + struct sunxi_drm_framebuffer *fb; + struct drm_gem_object *gem_obj; + int error; + + if (cmd->flags) + return NULL; + + gem_obj = drm_gem_object_lookup(ddev, file, cmd->handles[0]); + if (gem_obj == NULL) + return NULL; + + fb = kmem_zalloc(sizeof(*fb), KM_SLEEP); + fb->obj = to_drm_gem_cma_obj(gem_obj); + fb->base.pitches[0] = cmd->pitches[0]; + fb->base.offsets[0] = cmd->offsets[0]; + fb->base.width = cmd->width; + fb->base.height = cmd->height; + fb->base.pixel_format = cmd->pixel_format; + drm_fb_get_bpp_depth(cmd->pixel_format, &fb->base.depth, + &fb->base.bits_per_pixel); + + error = drm_framebuffer_init(ddev, &fb->base, &sunxi_drm_framebuffer_funcs); + if (error != 0) + goto dealloc; + + return &fb->base; + +dealloc: + drm_framebuffer_cleanup(&fb->base); + kmem_free(fb, sizeof(*fb)); + drm_gem_object_unreference_unlocked(gem_obj); + + return NULL; +} + +static struct drm_mode_config_funcs sunxi_drm_mode_config_funcs = { + .fb_create = sunxi_drm_fb_create, +}; + +static int +sunxi_drm_fb_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) +{ + struct sunxi_drm_softc * const sc = sunxi_drm_private(helper->dev); + struct drm_device *ddev = helper->dev; + struct sunxi_drm_framebuffer *sfb = to_sunxi_drm_framebuffer(helper->fb); + struct drm_framebuffer *fb = helper->fb; + struct sunxi_drmfb_attach_args sfa; + int error; + + const u_int width = sizes->surface_width; + const u_int height = sizes->surface_height; + const u_int pitch = width * (32 / 8); + + const size_t size = roundup(height * pitch, PAGE_SIZE); + + sfb->obj = drm_gem_cma_create(ddev, size); + if (sfb->obj == NULL) { + DRM_ERROR("failed to allocate memory for framebuffer\n"); + return -ENOMEM; + } + + fb->pitches[0] = pitch; + fb->offsets[0] = 0; + fb->width = width; + fb->height = height; + fb->pixel_format = DRM_FORMAT_XRGB8888; + drm_fb_get_bpp_depth(fb->pixel_format, &fb->depth, &fb->bits_per_pixel); + + error = drm_framebuffer_init(ddev, fb, &sunxi_drm_framebuffer_funcs); + if (error != 0) { + DRM_ERROR("failed to initialize framebuffer\n"); + return error; + } + + memset(&sfa, 0, sizeof(sfa)); + sfa.sfa_drm_dev = ddev; + sfa.sfa_fb_helper = helper; + sfa.sfa_fb_sizes = *sizes; + sfa.sfa_fb_bst = sc->sc_bst; + sfa.sfa_fb_dmat = sc->sc_dmat; + sfa.sfa_fb_linebytes = helper->fb->pitches[0]; + + helper->fbdev = config_found_ia(ddev->dev, "sunxifbbus", &sfa, NULL); + if (helper->fbdev == NULL) { + DRM_ERROR("unable to attach framebuffer\n"); + return -ENXIO; + } + + return 0; +} + +static struct drm_fb_helper_funcs sunxi_drm_fb_helper_funcs = { + .fb_probe = sunxi_drm_fb_probe, +}; + +static int +sunxi_drm_load(struct drm_device *ddev, unsigned long flags) +{ + struct sunxi_drm_softc * const sc = sunxi_drm_private(ddev); + struct sunxi_drm_endpoint *sep; + struct sunxi_drm_fbdev *fbdev; + const u_int *data; + int datalen, error, num_crtc; + + drm_mode_config_init(ddev); + ddev->mode_config.min_width = 0; + ddev->mode_config.min_height = 0; + ddev->mode_config.max_width = 3840; + ddev->mode_config.max_height = 2160; + ddev->mode_config.funcs = &sunxi_drm_mode_config_funcs; + + num_crtc = 0; + data = fdtbus_get_prop(sc->sc_phandle, "allwinner,pipelines", &datalen); + while (datalen >= 4) { + const int crtc_phandle = fdtbus_get_phandle_from_native(be32dec(data)); + + TAILQ_FOREACH(sep, &sunxi_drm_endpoints, entries) + if (sep->phandle == crtc_phandle && sep->ddev == NULL) { + sep->ddev = ddev; + error = fdt_endpoint_activate_direct(sep->ep, true); + if (error != 0) { + aprint_error_dev(sc->sc_dev, "failed to activate endpoint: %d\n", + error); + } + if (fdt_endpoint_type(sep->ep) == EP_DRM_CRTC) + num_crtc++; + } + + datalen -= 4; + data++; + } + + if (num_crtc == 0) { + aprint_error_dev(sc->sc_dev, "no pipelines configured\n"); + return ENXIO; + } + + fbdev = kmem_zalloc(sizeof(*fbdev), KM_SLEEP); + + drm_fb_helper_prepare(ddev, &fbdev->helper, &sunxi_drm_fb_helper_funcs); + + error = drm_fb_helper_init(ddev, &fbdev->helper, num_crtc, num_crtc); + if (error) + goto drmerr; + + fbdev->helper.fb = kmem_zalloc(sizeof(struct sunxi_drm_framebuffer), KM_SLEEP); + + drm_fb_helper_single_add_all_connectors(&fbdev->helper); + + drm_helper_disable_unused_functions(ddev); + + drm_fb_helper_initial_config(&fbdev->helper, 32); + + return 0; + +drmerr: + drm_mode_config_cleanup(ddev); + kmem_free(fbdev, sizeof(*fbdev)); + + return error; +} + +static int +sunxi_drm_unload(struct drm_device *ddev) +{ + drm_mode_config_cleanup(ddev); + + return 0; +} + +int +sunxi_drm_register_endpoint(int phandle, struct fdt_endpoint *ep) +{ + struct sunxi_drm_endpoint *sep; + + sep = kmem_zalloc(sizeof(*sep), KM_SLEEP); + sep->phandle = phandle; + sep->ep = ep; + sep->ddev = NULL; + TAILQ_INSERT_TAIL(&sunxi_drm_endpoints, sep, entries); + + return 0; +} + +struct drm_device * +sunxi_drm_endpoint_device(struct fdt_endpoint *ep) +{ + struct sunxi_drm_endpoint *sep; + + TAILQ_FOREACH(sep, &sunxi_drm_endpoints, entries) + if (sep->ep == ep) + return sep->ddev; + + return NULL; +} Index: src/sys/arch/arm/sunxi/sunxi_drm.h diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_drm.h:1.1 --- /dev/null Wed Jan 30 01:24:00 2019 +++ src/sys/arch/arm/sunxi/sunxi_drm.h Wed Jan 30 01:24:00 2019 @@ -0,0 +1,88 @@ +/* $NetBSD: sunxi_drm.h,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */ + +/*- + * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _ARM_SUNXI_DRM_H +#define _ARM_SUNXI_DRM_H + +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_cma_helper.h> + +#define DRIVER_AUTHOR "Jared McNeill" + +#define DRIVER_NAME "sunxi" +#define DRIVER_DESC "Allwinner Display Engine" +#define DRIVER_DATE "20190123" + +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 0 + +struct sunxi_framebuffer; + +struct sunxi_drm_softc { + device_t sc_dev; + struct drm_device *sc_ddev; + + bus_space_tag_t sc_bst; + bus_dma_tag_t sc_dmat; + + int sc_phandle; +}; + +struct sunxi_drm_framebuffer { + struct drm_framebuffer base; + struct drm_gem_cma_object *obj; +}; + +struct sunxi_drm_endpoint { + int phandle; + struct fdt_endpoint *ep; + struct drm_device *ddev; + TAILQ_ENTRY(sunxi_drm_endpoint) entries; +}; + +struct sunxi_drm_fbdev { + struct drm_fb_helper helper; +}; + +struct sunxi_drmfb_attach_args { + struct drm_device *sfa_drm_dev; + struct drm_fb_helper *sfa_fb_helper; + struct drm_fb_helper_surface_size sfa_fb_sizes; + bus_space_tag_t sfa_fb_bst; + bus_dma_tag_t sfa_fb_dmat; + uint32_t sfa_fb_linebytes; +}; + +#define sunxi_drm_private(ddev) (ddev)->dev_private +#define to_sunxi_drm_framebuffer(x) container_of(x, struct sunxi_drm_framebuffer, base) + +int sunxi_drm_register_endpoint(int, struct fdt_endpoint *); +struct drm_device *sunxi_drm_endpoint_device(struct fdt_endpoint *); + +#endif /* _ARM_SUNXI_DRM_H */ Index: src/sys/arch/arm/sunxi/sunxi_dwhdmi.c diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_dwhdmi.c:1.1 --- /dev/null Wed Jan 30 01:24:00 2019 +++ src/sys/arch/arm/sunxi/sunxi_dwhdmi.c Wed Jan 30 01:24:00 2019 @@ -0,0 +1,258 @@ +/* $NetBSD: sunxi_dwhdmi.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */ + +/*- + * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: sunxi_dwhdmi.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/intr.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/conf.h> + +#include <drm/drmP.h> + +#include <dev/fdt/fdtvar.h> +#include <dev/fdt/fdt_port.h> + +#include <dev/ic/dw_hdmi.h> + +#include <arm/sunxi/sunxi_hdmiphy.h> + +enum { + DWHDMI_PORT_INPUT = 0, + DWHDMI_PORT_OUTPUT = 1, +}; + +static const char * const compatible[] = { + "allwinner,sun50i-a64-dw-hdmi", + NULL +}; + +struct sunxi_dwhdmi_softc { + struct dwhdmi_softc sc_base; + int sc_phandle; + struct fdtbus_phy *sc_phy; + + struct fdt_device_ports sc_ports; + struct drm_display_mode sc_curmode; +}; + +#define to_sunxi_dwhdmi_softc(x) container_of(x, struct sunxi_dwhdmi_softc, sc_base) + +static int +sunxi_dwhdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate) +{ + struct sunxi_dwhdmi_softc * const sc = device_private(dev); + struct fdt_endpoint *in_ep = fdt_endpoint_remote(ep); + struct fdt_endpoint *out_ep, *out_rep; + struct drm_encoder *encoder; + struct drm_bridge *bridge; + int error; + + if (!activate) + return EINVAL; + + if (fdt_endpoint_port_index(ep) != DWHDMI_PORT_INPUT) + return EINVAL; + + switch (fdt_endpoint_type(in_ep)) { + case EP_DRM_ENCODER: + encoder = fdt_endpoint_get_data(in_ep); + break; + case EP_DRM_BRIDGE: + bridge = fdt_endpoint_get_data(in_ep); + encoder = bridge->encoder; + break; + default: + encoder = NULL; + break; + } + + if (encoder == NULL) + return EINVAL; + + sc->sc_phy = fdtbus_phy_get(sc->sc_phandle, "hdmi-phy"); + if (sc->sc_phy == NULL) { + device_printf(dev, "couldn't find hdmi-phy\n"); + return ENXIO; + } + + error = dwhdmi_bind(&sc->sc_base, encoder); + if (error != 0) + return error; + + out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, DWHDMI_PORT_OUTPUT, 0); + if (out_ep != NULL) { + /* Ignore downstream connectors, we have our own. */ + out_rep = fdt_endpoint_remote(out_ep); + if (out_rep != NULL && fdt_endpoint_type(out_rep) == EP_DRM_CONNECTOR) + return 0; + + error = fdt_endpoint_activate(out_ep, activate); + if (error != 0) + return error; + } + + return 0; +} + +static void * +sunxi_dwhdmi_ep_get_data(device_t dev, struct fdt_endpoint *ep) +{ + struct sunxi_dwhdmi_softc * const sc = device_private(dev); + + return &sc->sc_base.sc_bridge; +} + +static enum drm_connector_status +sunxi_dwhdmi_detect(struct dwhdmi_softc *dsc, bool force) +{ + struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc); + + KASSERT(sc->sc_phy != NULL); + + if (sunxi_hdmiphy_detect(sc->sc_phy, force)) + return connector_status_connected; + else + return connector_status_disconnected; +} + +static void +sunxi_dwhdmi_enable(struct dwhdmi_softc *dsc) +{ + struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc); + int error; + + KASSERT(sc->sc_phy != NULL); + + error = fdtbus_phy_enable(sc->sc_phy, true); + if (error != 0) { + device_printf(dsc->sc_dev, "failed to enable phy: %d\n", error); + return; + } + + error = sunxi_hdmiphy_config(sc->sc_phy, &sc->sc_curmode); + if (error != 0) + device_printf(dsc->sc_dev, "failed to configure phy: %d\n", error); +} + +static void +sunxi_dwhdmi_disable(struct dwhdmi_softc *dsc) +{ + struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc); + int error; + + KASSERT(sc->sc_phy != NULL); + + error = fdtbus_phy_enable(sc->sc_phy, false); + if (error != 0) + device_printf(dsc->sc_dev, "failed to disable phy\n"); +} + +static void +sunxi_dwhdmi_mode_set(struct dwhdmi_softc *dsc, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc); + + sc->sc_curmode = *adjusted_mode; +} + +static int +sunxi_dwhdmi_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compatible(faa->faa_phandle, compatible); +} + +static void +sunxi_dwhdmi_attach(device_t parent, device_t self, void *aux) +{ + struct sunxi_dwhdmi_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + const int phandle = faa->faa_phandle; + struct clk *clk_iahb, *clk_isfr; + struct fdtbus_reset *rst; + bus_addr_t addr; + bus_size_t size; + + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { + aprint_error(": couldn't get registers\n"); + return; + } + + rst = fdtbus_reset_get(phandle, "ctrl"); + if (rst == NULL || fdtbus_reset_deassert(rst) != 0) { + aprint_error(": couldn't de-assert reset\n"); + return; + } + + clk_iahb = fdtbus_clock_get(phandle, "iahb"); + if (clk_iahb == NULL || clk_enable(clk_iahb) != 0) { + aprint_error(": couldn't enable iahb clock\n"); + return; + } + + clk_isfr = fdtbus_clock_get(phandle, "isfr"); + if (clk_isfr == NULL || clk_enable(clk_isfr) != 0) { + aprint_error(": couldn't enable isfr clock\n"); + return; + } + + sc->sc_base.sc_dev = self; + sc->sc_base.sc_reg_width = 1; + sc->sc_base.sc_bst = faa->faa_bst; + if (bus_space_map(sc->sc_base.sc_bst, addr, size, 0, &sc->sc_base.sc_bsh) != 0) { + aprint_error(": couldn't map registers\n"); + return; + } + sc->sc_base.sc_detect = sunxi_dwhdmi_detect; + sc->sc_base.sc_enable = sunxi_dwhdmi_enable; + sc->sc_base.sc_disable = sunxi_dwhdmi_disable; + sc->sc_base.sc_mode_set = sunxi_dwhdmi_mode_set; + sc->sc_phandle = faa->faa_phandle; + + aprint_naive("\n"); + aprint_normal(": HDMI TX\n"); + + if (dwhdmi_attach(&sc->sc_base) != 0) { + aprint_error_dev(self, "failed to attach driver\n"); + return; + } + + sc->sc_ports.dp_ep_activate = sunxi_dwhdmi_ep_activate; + sc->sc_ports.dp_ep_get_data = sunxi_dwhdmi_ep_get_data; + fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_BRIDGE); +} + +CFATTACH_DECL_NEW(sunxi_dwhdmi, sizeof(struct sunxi_dwhdmi_softc), + sunxi_dwhdmi_match, sunxi_dwhdmi_attach, NULL, NULL); Index: src/sys/arch/arm/sunxi/sunxi_fb.c diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_fb.c:1.1 --- /dev/null Wed Jan 30 01:24:00 2019 +++ src/sys/arch/arm/sunxi/sunxi_fb.c Wed Jan 30 01:24:00 2019 @@ -0,0 +1,163 @@ +/* $NetBSD: sunxi_fb.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */ + +/*- + * Copyright (c) 2015-2019 Jared McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "opt_wsdisplay_compat.h" + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: sunxi_fb.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> + +#include <dev/fdt/fdtvar.h> + +#include <drm/drmP.h> +#include <drm/drmfb.h> + +#include <arm/sunxi/sunxi_drm.h> + +static int sunxi_fb_match(device_t, cfdata_t, void *); +static void sunxi_fb_attach(device_t, device_t, void *); + +static bool sunxi_fb_shutdown(device_t, int); + +struct sunxi_fb_softc { + struct drmfb_softc sc_drmfb; + device_t sc_dev; + struct sunxi_drm_softc *sc_drm; + struct sunxi_drm_framebuffer *sc_fb; + struct sunxi_drmfb_attach_args sc_sfa; +}; + +static paddr_t sunxi_fb_mmapfb(struct drmfb_softc *, off_t, int); +static int sunxi_fb_ioctl(struct drmfb_softc *, u_long, void *, int, + lwp_t *); + +static const struct drmfb_params sunxifb_drmfb_params = { + .dp_mmapfb = sunxi_fb_mmapfb, + .dp_ioctl = sunxi_fb_ioctl, + +}; + +CFATTACH_DECL_NEW(sunxi_fb, sizeof(struct sunxi_fb_softc), + sunxi_fb_match, sunxi_fb_attach, NULL, NULL); + +static int +sunxi_fb_match(device_t parent, cfdata_t cf, void *aux) +{ + return 1; +} + +static void +sunxi_fb_attach(device_t parent, device_t self, void *aux) +{ + struct sunxi_fb_softc * const sc = device_private(self); + struct sunxi_drm_softc * const drmsc = device_private(parent); + struct sunxi_drmfb_attach_args * const sfa = aux; + int error; + + sc->sc_dev = self; + sc->sc_drm = drmsc; + sc->sc_sfa = *sfa; + sc->sc_fb = to_sunxi_drm_framebuffer(sfa->sfa_fb_helper->fb); + + aprint_naive("\n"); + aprint_normal("\n"); + +#ifdef WSDISPLAY_MULTICONS + prop_dictionary_t dict = device_properties(self); + const bool is_console = true; + prop_dictionary_set_bool(dict, "is_console", is_console); +#endif + + const struct drmfb_attach_args da = { + .da_dev = self, + .da_fb_helper = sfa->sfa_fb_helper, + .da_fb_sizes = &sfa->sfa_fb_sizes, + .da_fb_vaddr = sc->sc_fb->obj->vaddr, + .da_fb_linebytes = sfa->sfa_fb_linebytes, + .da_params = &sunxifb_drmfb_params, + }; + + error = drmfb_attach(&sc->sc_drmfb, &da); + if (error) { + aprint_error_dev(self, "failed to attach drmfb: %d\n", error); + return; + } + + pmf_device_register1(self, NULL, NULL, sunxi_fb_shutdown); +} + +static bool +sunxi_fb_shutdown(device_t self, int flags) +{ + struct sunxi_fb_softc * const sc = device_private(self); + + return drmfb_shutdown(&sc->sc_drmfb, flags); +} + +static paddr_t +sunxi_fb_mmapfb(struct drmfb_softc *sc, off_t off, int prot) +{ + struct sunxi_fb_softc * const tfb_sc = (struct sunxi_fb_softc *)sc; + struct drm_gem_cma_object *obj = tfb_sc->sc_fb->obj; + + KASSERT(off >= 0); + KASSERT(off < obj->dmasize); + + return bus_dmamem_mmap(obj->dmat, obj->dmasegs, 1, off, prot, + BUS_DMA_PREFETCHABLE); +} + +static int +sunxi_fb_ioctl(struct drmfb_softc *sc, u_long cmd, void *data, int flag, + lwp_t *l) +{ + struct wsdisplayio_bus_id *busid; + struct wsdisplayio_fbinfo *fbi; + struct rasops_info *ri = &sc->sc_genfb.vd.active->scr_ri; + int error; + + switch (cmd) { + case WSDISPLAYIO_GET_BUSID: + busid = data; + busid->bus_type = WSDISPLAYIO_BUS_SOC; + return 0; + case WSDISPLAYIO_GTYPE: + *(u_int *)data = WSDISPLAY_TYPE_GENFB; + return 0; + case WSDISPLAYIO_GET_FBINFO: + fbi = data; + error = wsdisplayio_get_fbinfo(ri, fbi); + fbi->fbi_flags |= WSFB_VRAM_IS_RAM; + return error; + default: + return EPASSTHROUGH; + } +} Index: src/sys/arch/arm/sunxi/sunxi_hdmiphy.c diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_hdmiphy.c:1.1 --- /dev/null Wed Jan 30 01:24:00 2019 +++ src/sys/arch/arm/sunxi/sunxi_hdmiphy.c Wed Jan 30 01:24:00 2019 @@ -0,0 +1,501 @@ +/* $NetBSD: sunxi_hdmiphy.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */ + +/*- + * Copyright (c) 2019 Jared McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> + +__KERNEL_RCSID(0, "$NetBSD: sunxi_hdmiphy.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/intr.h> +#include <sys/systm.h> + +#include <dev/fdt/fdtvar.h> + +#include <arm/sunxi/sunxi_hdmiphy.h> + +#define DBG_CTRL 0x000 +#define DBG_CTRL_POL __BITS(15,8) +#define DBG_CTRL_POL_NVSYNC 1 +#define DBG_CTRL_POL_NHSYNC 2 + +#define READ_EN 0x010 +#define READ_EN_MAGIC 0x54524545 /* "TREE" */ + +#define UNSCRAMBLE 0x014 +#define UNSCRAMBLE_MAGIC 0x42494E47 /* "BING" */ + +#define ANA_CFG1 0x020 +#define ANA_CFG1_ENRCAL __BIT(19) +#define ANA_CFG1_ENCALOG __BIT(18) +#define ANA_CFG1_TMDSCLK_EN __BIT(16) +#define ANA_CFG1_TXEN __BITS(15,12) +#define ANA_CFG1_BIASEN __BITS(11,8) +#define ANA_CFG1_ENP2S __BITS(7,4) +#define ANA_CFG1_CKEN __BIT(3) +#define ANA_CFG1_LDOEN __BIT(2) +#define ANA_CFG1_ENVBS __BIT(1) +#define ANA_CFG1_ENBI __BIT(0) + +#define ANA_CFG2 0x024 +#define ANA_CFG2_REG_RESDI __BITS(5,0) + +#define ANA_CFG3 0x028 +#define ANA_CFG3_REG_SDAEN __BIT(2) +#define ANA_CFG3_REG_SCLEN __BIT(0) + +#define PLL_CFG1 0x02c +#define PLL_CFG1_REG_OD1 __BIT(31) +#define PLL_CFG1_REG_OD0 __BIT(30) +#define PLL_CFG1_CKIN_SEL __BIT(26) +#define PLL_CFG1_PLLEN __BIT(25) +#define PLL_CFG1_B_IN __BITS(5,0) + +#define PLL_CFG2 0x030 +#define PLL_CFG2_PREDIV __BITS(3,0) + +#define PLL_CFG3 0x034 + +#define ANA_STS 0x038 +#define ANA_STS_HPDO __BIT(19) +#define ANA_STS_B_OUT __BITS(16,11) +#define ANA_STS_RCALEND2D __BIT(7) +#define ANA_STS_RESDO2D __BITS(5,0) + +#define CEC 0x03c +#define CEC_CONTROL_SEL __BIT(7) +#define CEC_INPUT_DATA __BIT(1) +#define CEC_OUTPUT_DATA __BIT(0) + +#define CONTROLLER_VER 0xff8 + +#define PHY_VER 0xffc + +struct sunxi_hdmiphy_softc; + +static int sunxi_hdmiphy_match(device_t, cfdata_t, void *); +static void sunxi_hdmiphy_attach(device_t, device_t, void *); + +static void sun50i_a64_hdmiphy_init(struct sunxi_hdmiphy_softc *); +static int sun50i_a64_hdmiphy_config(struct sunxi_hdmiphy_softc *, u_int); + +struct sunxi_hdmiphy_data { + void (*init)(struct sunxi_hdmiphy_softc *); + int (*config)(struct sunxi_hdmiphy_softc *, u_int); +}; + +static const struct sunxi_hdmiphy_data sun50i_a64_hdmiphy_data = { + .init = sun50i_a64_hdmiphy_init, + .config = sun50i_a64_hdmiphy_config, +}; + +static const struct of_compat_data compat_data[] = { + { "allwinner,sun50i-a64-hdmi-phy", (uintptr_t)&sun50i_a64_hdmiphy_data }, + { NULL } +}; + +struct sunxi_hdmiphy_softc { + device_t sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + + const struct sunxi_hdmiphy_data *sc_data; + + struct clk *sc_clk_pll0; + + u_int sc_rcalib; +}; + +#define PHY_READ(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) +#define PHY_WRITE(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) +#define PHY_SET_CLEAR(sc, reg, set, clr) \ + do { \ + uint32_t _tval = PHY_READ((sc), (reg)); \ + _tval &= ~(clr); \ + _tval |= (set); \ + PHY_WRITE((sc), (reg), _tval); \ + } while (0) +#define PHY_SET(sc, reg, set) \ + PHY_SET_CLEAR(sc, reg, set, 0) +#define PHY_CLEAR(sc, reg, clr) \ + PHY_SET_CLEAR(sc, reg, 0, clr) + +CFATTACH_DECL_NEW(sunxi_hdmiphy, sizeof(struct sunxi_hdmiphy_softc), + sunxi_hdmiphy_match, sunxi_hdmiphy_attach, NULL, NULL); + +static void * +sunxi_hdmiphy_acquire(device_t dev, const void *data, size_t len) +{ + struct sunxi_hdmiphy_softc * const sc = device_private(dev); + + if (len != 0) + return NULL; + + return sc; +} + +static void +sunxi_hdmiphy_release(device_t dev, void *priv) +{ +} + +static int +sunxi_hdmiphy_enable(device_t dev, void *priv, bool enable) +{ + struct sunxi_hdmiphy_softc * const sc = priv; + + if (enable) { + sc->sc_data->init(sc); + } else { + sc->sc_data->config(sc, 0); + } + + return 0; +} + +static const struct fdtbus_phy_controller_func sunxi_hdmiphy_funcs = { + .acquire = sunxi_hdmiphy_acquire, + .release = sunxi_hdmiphy_release, + .enable = sunxi_hdmiphy_enable, +}; + +#ifdef SUNXI_HDMIPHY_DEBUG +static void +sunxi_hdmiphy_dump(struct sunxi_hdmiphy_softc *sc) +{ + device_printf(sc->sc_dev, "ANA_CFG1: %#x\tANA_CFG2: %#x\tANA_CFG3: %#x\n", + PHY_READ(sc, ANA_CFG1), PHY_READ(sc, ANA_CFG2), PHY_READ(sc, ANA_CFG3)); + device_printf(sc->sc_dev, "PLL_CFG1: %#x\tPLL_CFG2: %#x\tPLL_CFG3: %#x\n", + PHY_READ(sc, PLL_CFG1), PHY_READ(sc, PLL_CFG2), PHY_READ(sc, PLL_CFG3)); + device_printf(sc->sc_dev, "DBG_CTRL: %#x\tANA_STS: %#x\n", + PHY_READ(sc, DBG_CTRL), PHY_READ(sc, ANA_STS)); +} +#endif + +static void +sun50i_a64_hdmiphy_init(struct sunxi_hdmiphy_softc *sc) +{ + uint32_t val; + int retry; + + PHY_WRITE(sc, ANA_CFG1, 0); + + PHY_SET(sc, ANA_CFG1, ANA_CFG1_ENBI); + delay(5); + + /* Enable TMDS clock */ + PHY_SET(sc, ANA_CFG1, ANA_CFG1_TMDSCLK_EN); + + /* Enable common voltage reference bias module */ + PHY_SET(sc, ANA_CFG1, ANA_CFG1_ENVBS); + delay(20); + + /* Enable internal LDO */ + PHY_SET(sc, ANA_CFG1, ANA_CFG1_LDOEN); + delay(5); + + /* Enable common clock module */ + PHY_SET(sc, ANA_CFG1, ANA_CFG1_CKEN); + delay(100); + + /* Enable resistance calibration analog and digital modules */ + PHY_SET(sc, ANA_CFG1, ANA_CFG1_ENRCAL); + delay(200); + PHY_SET(sc, ANA_CFG1, ANA_CFG1_ENCALOG); + + /* P2S module enable for TMDS data lane */ + PHY_SET_CLEAR(sc, ANA_CFG1, __SHIFTIN(0x7, ANA_CFG1_ENP2S), ANA_CFG1_ENP2S); + + /* Wait for resistance calibration to finish */ + for (retry = 2000; retry > 0; retry--) { + if ((PHY_READ(sc, ANA_STS) & ANA_STS_RCALEND2D) != 0) + break; + delay(1); + } + if (retry == 0) + aprint_error_dev(sc->sc_dev, "HDMI PHY resistance calibration timed out\n"); + + /* Enable current and voltage module */ + PHY_SET_CLEAR(sc, ANA_CFG1, __SHIFTIN(0xf, ANA_CFG1_BIASEN), ANA_CFG1_BIASEN); + + /* P2S module enable for TMDS clock lane */ + PHY_SET_CLEAR(sc, ANA_CFG1, __SHIFTIN(0xf, ANA_CFG1_ENP2S), ANA_CFG1_ENP2S); + + /* Enable DDC */ + PHY_SET(sc, ANA_CFG3, ANA_CFG3_REG_SDAEN | ANA_CFG3_REG_SCLEN); + + /* Set parent clock to videopll0 */ + PHY_CLEAR(sc, PLL_CFG1, PLL_CFG1_CKIN_SEL); + + /* Clear software control of CEC pins */ + PHY_CLEAR(sc, CEC, CEC_CONTROL_SEL); + + /* Read calibration value for source termination resistors */ + val = PHY_READ(sc, ANA_STS); + sc->sc_rcalib = __SHIFTOUT(val, ANA_STS_RESDO2D); +} + +/* + * The following table is based on data from the "HDMI TX PHY S40 Specification". + */ +static const struct sun50i_a64_hdmiphy_init { + /* PLL Recommended Configuration */ + uint32_t pll_cfg1; + uint32_t pll_cfg2; + uint32_t pll_cfg3; + /* TMDS Characteristics Recommended Configuration */ + uint32_t ana_cfg1; + uint32_t ana_cfg2; + uint32_t ana_cfg3; + bool ana_cfg2_rcal_200; + u_int b_offset; +} sun50i_a64_hdmiphy_inittab[] = { + /* 27 MHz */ + [0] = { + .pll_cfg1 = 0x3ddc5040, .pll_cfg2 = 0x8008430a, .pll_cfg3 = 0x1, + .ana_cfg1 = 0x11ffff7f, .ana_cfg2 = 0x80623000, .ana_cfg3 = 0x0f80c285, + .ana_cfg2_rcal_200 = true, + }, + /* 74.25 MHz */ + [1] = { + .pll_cfg1 = 0x3ddc5040, .pll_cfg2 = 0x80084343, .pll_cfg3 = 0x1, + .ana_cfg1 = 0x11ffff7f, .ana_cfg2 = 0x80623000, .ana_cfg3 = 0x0f814385, + .ana_cfg2_rcal_200 = true, + }, + /* 148.5 MHz */ + [2] = { + .pll_cfg1 = 0x3ddc5040, .pll_cfg2 = 0x80084381, .pll_cfg3 = 0x1, + .ana_cfg1 = 0x01ffff7f, .ana_cfg2 = 0x8063a800, .ana_cfg3 = 0x0f81c485, + }, + /* 297 MHz */ + [3] = { + .pll_cfg1 = 0x35dc5fc0, .pll_cfg2 = 0x800863c0, .pll_cfg3 = 0x1, + .ana_cfg1 = 0x01ffff7f, .ana_cfg2 = 0x8063b000, .ana_cfg3 = 0x0f8246b5, + .b_offset = 2, + }, +}; + +static int +sun50i_a64_hdmiphy_config(struct sunxi_hdmiphy_softc *sc, u_int rate) +{ + const struct sun50i_a64_hdmiphy_init *inittab; + u_int init_index, b_out, prediv; + uint32_t val, rcalib; + + if (rate == 0) { + /* Disable the PHY */ + PHY_WRITE(sc, ANA_CFG1, ANA_CFG1_LDOEN | ANA_CFG1_ENVBS | ANA_CFG1_ENBI); + PHY_WRITE(sc, PLL_CFG1, 0); + return 0; + } + + init_index = 0; + if (rate > 27000000) + init_index++; + if (rate > 74250000) + init_index++; + if (rate > 148500000) + init_index++; + inittab = &sun50i_a64_hdmiphy_inittab[init_index]; + + val = PHY_READ(sc, PLL_CFG2); + prediv = val & PLL_CFG2_PREDIV; + + /* Config PLL */ + PHY_WRITE(sc, PLL_CFG1, inittab->pll_cfg1 & ~PLL_CFG1_CKIN_SEL); + PHY_WRITE(sc, PLL_CFG2, (inittab->pll_cfg2 & ~PLL_CFG2_PREDIV) | prediv); + delay(15000); + PHY_WRITE(sc, PLL_CFG3, inittab->pll_cfg3); + + /* Enable PLL */ + PHY_SET(sc, PLL_CFG1, PLL_CFG1_PLLEN); + delay(100000); + + /* Config PLL */ + val = PHY_READ(sc, ANA_STS); + b_out = __SHIFTOUT(val, ANA_STS_B_OUT); + b_out = MIN(b_out + inittab->b_offset, __SHIFTOUT_MASK(ANA_STS_B_OUT)); + + PHY_SET(sc, PLL_CFG1, PLL_CFG1_REG_OD1 | PLL_CFG1_REG_OD0); + PHY_SET(sc, PLL_CFG1, __SHIFTIN(b_out, PLL_CFG1_B_IN)); + delay(100000); + + /* Config TMDS characteristics */ + if (inittab->ana_cfg2_rcal_200) + rcalib = sc->sc_rcalib >> 2; + else + rcalib = 0; + PHY_WRITE(sc, ANA_CFG1, inittab->ana_cfg1); + PHY_WRITE(sc, ANA_CFG2, inittab->ana_cfg2 | rcalib); + PHY_WRITE(sc, ANA_CFG3, inittab->ana_cfg3); + +#ifdef SUNXI_HDMIPHY_DEBUG + sunxi_hdmiphy_dump(sc); +#endif + + return 0; +} + +static int +sunxi_hdmiphy_set_rate(struct sunxi_hdmiphy_softc *sc, u_int new_rate) +{ + u_int prediv, best_prediv, best_rate; + + if (sc->sc_clk_pll0 == NULL) + return 0; + + const u_int parent_rate = clk_get_rate(sc->sc_clk_pll0); + + best_rate = 0; + + for (prediv = 0; prediv <= __SHIFTOUT_MASK(PLL_CFG2_PREDIV); prediv++) { + const u_int tmp_rate = parent_rate / (prediv + 1); + const int diff = new_rate - tmp_rate; + if (diff >= 0 && tmp_rate > best_rate) { + best_rate = tmp_rate; + best_prediv = prediv; + } + } + + if (best_rate == 0) + return ERANGE; + + PHY_SET_CLEAR(sc, PLL_CFG2, __SHIFTIN(best_prediv, PLL_CFG2_PREDIV), PLL_CFG2_PREDIV); + + return 0; +} + +static int +sunxi_hdmiphy_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compat_data(faa->faa_phandle, compat_data); +} + +static void +sunxi_hdmiphy_attach(device_t parent, device_t self, void *aux) +{ + struct sunxi_hdmiphy_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + const int phandle = faa->faa_phandle; + struct clk *clk_bus, *clk_mod, *clk_pll0; + struct fdtbus_reset *rst; + bus_addr_t addr; + bus_size_t size; + + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { + aprint_error(": couldn't get registers\n"); + return; + } + + rst = fdtbus_reset_get(phandle, "phy"); + if (rst == NULL || fdtbus_reset_deassert(rst) != 0) { + aprint_error(": couldn't de-assert reset\n"); + return; + } + + clk_bus = fdtbus_clock_get(phandle, "bus"); + if (clk_bus == NULL || clk_enable(clk_bus) != 0) { + aprint_error(": couldn't enable bus clock\n"); + return; + } + + clk_mod = fdtbus_clock_get(phandle, "mod"); + if (clk_mod == NULL || clk_enable(clk_mod) != 0) { + aprint_error(": couldn't enable mod clock\n"); + return; + } + + clk_pll0 = fdtbus_clock_get(phandle, "pll-0"); + if (clk_pll0 == NULL || clk_enable(clk_pll0) != 0) { + aprint_error(": couldn't enable pll-0 clock\n"); + return; + } + + sc->sc_dev = self; + sc->sc_bst = faa->faa_bst; + sc->sc_data = (void *)of_search_compatible(phandle, compat_data)->data; + if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { + aprint_error(": couldn't map registers\n"); + return; + } + sc->sc_clk_pll0 = clk_pll0; + + aprint_naive("\n"); + aprint_normal(": HDMI PHY\n"); + + fdtbus_register_phy_controller(self, phandle, &sunxi_hdmiphy_funcs); + + PHY_WRITE(sc, READ_EN, READ_EN_MAGIC); + PHY_WRITE(sc, UNSCRAMBLE, UNSCRAMBLE_MAGIC); + +#ifdef SUNXI_HDMIPHY_DEBUG + sunxi_hdmiphy_dump(sc); +#endif +} + +int +sunxi_hdmiphy_config(struct fdtbus_phy *phy, struct drm_display_mode *mode) +{ + device_t dev = fdtbus_phy_device(phy); + struct sunxi_hdmiphy_softc * const sc = device_private(dev); + u_int pol; + int error; + + pol = 0; + if ((mode->flags & DRM_MODE_FLAG_NHSYNC) != 0) + pol |= __SHIFTIN(DBG_CTRL_POL_NHSYNC, DBG_CTRL_POL); + if ((mode->flags & DRM_MODE_FLAG_NVSYNC) != 0) + pol |= __SHIFTIN(DBG_CTRL_POL_NVSYNC, DBG_CTRL_POL); + + PHY_SET_CLEAR(sc, DBG_CTRL, pol, DBG_CTRL_POL); + + error = sunxi_hdmiphy_set_rate(sc, mode->crtc_clock * 1000); + if (error != 0) { + aprint_error_dev(dev, "failed to set HDMI PHY clock: %d\n", error); + return error; + } + + return sc->sc_data->config(sc, mode->crtc_clock * 1000); +} + +bool +sunxi_hdmiphy_detect(struct fdtbus_phy *phy, bool force) +{ + device_t dev = fdtbus_phy_device(phy); + struct sunxi_hdmiphy_softc * const sc = device_private(dev); + uint32_t val; + + val = PHY_READ(sc, ANA_STS); + + return ISSET(val, ANA_STS_HPDO); +} Index: src/sys/arch/arm/sunxi/sunxi_hdmiphy.h diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_hdmiphy.h:1.1 --- /dev/null Wed Jan 30 01:24:00 2019 +++ src/sys/arch/arm/sunxi/sunxi_hdmiphy.h Wed Jan 30 01:24:00 2019 @@ -0,0 +1,38 @@ +/* $NetBSD: sunxi_hdmiphy.h,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */ + +/*- + * Copyright (c) 2019 Jared McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _ARM_SUNXI_HDMIPHY_H +#define _ARM_SUNXI_HDMIPHY_H + +#include <drm/drmP.h> +#include <drm/drm_modes.h> + +int sunxi_hdmiphy_config(struct fdtbus_phy *, struct drm_display_mode *); +bool sunxi_hdmiphy_detect(struct fdtbus_phy *, bool); + +#endif /* !_ARM_SUNXI_HDMIPHY_H */ Index: src/sys/arch/arm/sunxi/sunxi_lcdc.c diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_lcdc.c:1.1 --- /dev/null Wed Jan 30 01:24:00 2019 +++ src/sys/arch/arm/sunxi/sunxi_lcdc.c Wed Jan 30 01:24:00 2019 @@ -0,0 +1,317 @@ +/* $NetBSD: sunxi_lcdc.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */ + +/*- + * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: sunxi_lcdc.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/intr.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/conf.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include <dev/fdt/fdtvar.h> +#include <dev/fdt/fdt_port.h> + +#define TCON_GCTL_REG 0x000 +#define TCON_GCTL_TCON_EN __BIT(31) +#define TCON_GCTL_GAMMA_EN __BIT(30) +#define TCON_GCTL_IO_MAP_SEL __BIT(0) + +#define TCON_GINT0_REG 0x004 +#define TCON_GINT1_REG 0x008 +#define TCON_GINT1_TCON1_LINE_INT_NUM __BITS(11,0) + +#define TCON1_CTL_REG 0x090 +#define TCON1_CTL_TCON1_EN __BIT(31) +#define TCON1_CTL_START_DELAY __BITS(8,4) +#define TCON1_CTL_TCON1_SRC_SEL __BIT(1) +#define TCON1_BASIC0_REG 0x094 +#define TCON1_BASIC1_REG 0x098 +#define TCON1_BASIC2_REG 0x09c +#define TCON1_BASIC3_REG 0x0a0 +#define TCON1_BASIC4_REG 0x0a4 +#define TCON1_BASIC5_REG 0x0a8 + +#define TCON1_IO_POL_REG 0x0f0 +#define TCON1_IO_POL_IO3_INV __BIT(27) +#define TCON1_IO_POL_IO2_INV __BIT(26) +#define TCON1_IO_POL_IO1_INV __BIT(25) +#define TCON1_IO_POL_IO0_INV __BIT(24) +#define TCON1_IO_POL_DATA_INV __BITS(23,0) +#define TCON1_IO_TRI_REG 0x0f4 + +enum { + MIXER_PORT_INPUT = 0, + MIXER_PORT_OUTPUT = 1, +}; + +static const char * const compatible[] = { + "allwinner,sun50i-a64-tcon-lcd", + "allwinner,sun50i-a64-tcon-tv", + NULL +}; + +struct sunxi_lcdc_softc; + +struct sunxi_lcdc_encoder { + struct drm_encoder base; + struct sunxi_lcdc_softc *sc; + struct drm_display_mode curmode; +}; + +struct sunxi_lcdc_softc { + device_t sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + int sc_phandle; + + struct clk *sc_clk_ch[2]; + + struct sunxi_lcdc_encoder sc_encoder; + struct drm_connector sc_connector; + + struct fdt_device_ports sc_ports; +}; + +#define to_sunxi_lcdc_encoder(x) container_of(x, struct sunxi_lcdc_encoder, base) + +#define TCON_READ(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) +#define TCON_WRITE(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) + +static void +sunxi_lcdc_destroy(struct drm_encoder *encoder) +{ +} + +static const struct drm_encoder_funcs sunxi_lcdc_funcs = { + .destroy = sunxi_lcdc_destroy, +}; + +static void +sunxi_lcdc_tcon1_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool +sunxi_lcdc_tcon1_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void +sunxi_lcdc_tcon1_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder); + + lcdc_encoder->curmode = *adjusted_mode; +} + +static void +sunxi_lcdc_tcon1_prepare(struct drm_encoder *encoder) +{ + struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder); + struct sunxi_lcdc_softc * const sc = lcdc_encoder->sc; + uint32_t val; + + val = TCON_READ(sc, TCON_GCTL_REG); + val |= TCON_GCTL_TCON_EN; + TCON_WRITE(sc, TCON_GCTL_REG, val); + + TCON_WRITE(sc, TCON1_IO_POL_REG, 0); + TCON_WRITE(sc, TCON1_IO_TRI_REG, 0xffffffff); +} + +static void +sunxi_lcdc_tcon1_commit(struct drm_encoder *encoder) +{ + struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder); + struct sunxi_lcdc_softc * const sc = lcdc_encoder->sc; + struct drm_display_mode *mode = &lcdc_encoder->curmode; + uint32_t val; + int error; + + const u_int interlace_p = (mode->flags & DRM_MODE_FLAG_INTERLACE) != 0; + const u_int hspw = mode->hsync_end - mode->hsync_start; + const u_int hbp = mode->htotal - mode->hsync_start; + const u_int vspw = mode->vsync_end - mode->vsync_start; + const u_int vbp = mode->vtotal - mode->vsync_start; + const u_int vblank_len = + ((mode->vtotal << interlace_p) >> 1) - mode->vdisplay - 2; + const u_int start_delay = + vblank_len >= 32 ? 30 : vblank_len - 2; + + val = TCON1_CTL_TCON1_EN | + __SHIFTIN(start_delay, TCON1_CTL_START_DELAY); + TCON_WRITE(sc, TCON1_CTL_REG, val); + + TCON_WRITE(sc, TCON1_BASIC0_REG, ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1)); + TCON_WRITE(sc, TCON1_BASIC1_REG, ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1)); + TCON_WRITE(sc, TCON1_BASIC2_REG, ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1)); + TCON_WRITE(sc, TCON1_BASIC3_REG, ((mode->htotal - 1) << 16) | (hbp - 1)); + TCON_WRITE(sc, TCON1_BASIC4_REG, ((mode->vtotal * 2) << 16) | (vbp - 1)); + TCON_WRITE(sc, TCON1_BASIC5_REG, ((hspw - 1) << 16) | (vspw - 1)); + + TCON_WRITE(sc, TCON_GINT1_REG, + __SHIFTIN(start_delay + 2, TCON_GINT1_TCON1_LINE_INT_NUM)); + + if (sc->sc_clk_ch[1] != NULL) { + error = clk_set_rate(sc->sc_clk_ch[1], mode->crtc_clock * 1000); + if (error != 0) { + device_printf(sc->sc_dev, "failed to set CH1 PLL rate to %u Hz: %d\n", + mode->crtc_clock * 1000, error); + return; + } + error = clk_enable(sc->sc_clk_ch[1]); + if (error != 0) { + device_printf(sc->sc_dev, "failed to enable CH1 PLL: %d\n", error); + return; + } + } else { + device_printf(sc->sc_dev, "no CH1 PLL configured\n"); + } +} + +static const struct drm_encoder_helper_funcs sunxi_lcdc_tcon1_helper_funcs = { + .dpms = sunxi_lcdc_tcon1_dpms, + .mode_fixup = sunxi_lcdc_tcon1_mode_fixup, + .prepare = sunxi_lcdc_tcon1_prepare, + .commit = sunxi_lcdc_tcon1_commit, + .mode_set = sunxi_lcdc_tcon1_mode_set, +}; + +static int +sunxi_lcdc_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate) +{ + struct sunxi_lcdc_softc * const sc = device_private(dev); + struct fdt_endpoint *in_ep = fdt_endpoint_remote(ep); + struct fdt_endpoint *out_ep; + struct drm_crtc *crtc; + int error; + + if (!activate) + return EINVAL; + + if (fdt_endpoint_port_index(ep) != MIXER_PORT_INPUT) + return EINVAL; + + if (fdt_endpoint_type(in_ep) != EP_DRM_CRTC) + return EINVAL; + + crtc = fdt_endpoint_get_data(in_ep); + + sc->sc_encoder.sc = sc; + + out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, MIXER_PORT_OUTPUT, 1); + if (out_ep != NULL) { + drm_encoder_init(crtc->dev, &sc->sc_encoder.base, &sunxi_lcdc_funcs, + DRM_MODE_ENCODER_TMDS); + drm_encoder_helper_add(&sc->sc_encoder.base, &sunxi_lcdc_tcon1_helper_funcs); + + error = fdt_endpoint_activate(out_ep, activate); + if (error != 0) + return error; + sc->sc_encoder.base.possible_crtcs = 1 << drm_crtc_index(crtc); + } + + return 0; +} + +static void * +sunxi_lcdc_ep_get_data(device_t dev, struct fdt_endpoint *ep) +{ + struct sunxi_lcdc_softc * const sc = device_private(dev); + + return &sc->sc_encoder; +} + +static int +sunxi_lcdc_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compatible(faa->faa_phandle, compatible); +} + +static void +sunxi_lcdc_attach(device_t parent, device_t self, void *aux) +{ + struct sunxi_lcdc_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + const int phandle = faa->faa_phandle; + struct fdtbus_reset *rst; + struct clk *clk; + bus_addr_t addr; + bus_size_t size; + + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { + aprint_error(": couldn't get registers\n"); + return; + } + + rst = fdtbus_reset_get(phandle, "lcd"); + if (rst == NULL || fdtbus_reset_deassert(rst) != 0) { + aprint_error(": couldn't de-assert reset\n"); + return; + } + + clk = fdtbus_clock_get(phandle, "ahb"); + if (clk == NULL || clk_enable(clk) != 0) { + aprint_error(": couldn't enable bus clock\n"); + return; + } + + sc->sc_dev = self; + sc->sc_bst = faa->faa_bst; + if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { + aprint_error(": couldn't map registers\n"); + return; + } + sc->sc_phandle = faa->faa_phandle; + sc->sc_clk_ch[0] = fdtbus_clock_get(phandle, "tcon-ch0"); + sc->sc_clk_ch[1] = fdtbus_clock_get(phandle, "tcon-ch1"); + + aprint_naive("\n"); + aprint_normal(": Timing Controller\n"); + + sc->sc_ports.dp_ep_activate = sunxi_lcdc_ep_activate; + sc->sc_ports.dp_ep_get_data = sunxi_lcdc_ep_get_data; + fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_ENCODER); +} + +CFATTACH_DECL_NEW(sunxi_lcdc, sizeof(struct sunxi_lcdc_softc), + sunxi_lcdc_match, sunxi_lcdc_attach, NULL, NULL); Index: src/sys/arch/arm/sunxi/sunxi_mixer.c diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_mixer.c:1.1 --- /dev/null Wed Jan 30 01:24:00 2019 +++ src/sys/arch/arm/sunxi/sunxi_mixer.c Wed Jan 30 01:24:00 2019 @@ -0,0 +1,387 @@ +/* $NetBSD: sunxi_mixer.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */ + +/*- + * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: sunxi_mixer.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/intr.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/conf.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> + +#include <dev/fdt/fdtvar.h> +#include <dev/fdt/fdt_port.h> + +#include <arm/sunxi/sunxi_drm.h> + +#define SUNXI_MIXER_FREQ 432000000 + +#define GLB_BASE 0x00000 +#define BLD_BASE 0x01000 +#define OVL_BASE(n) (0x02000 + (n) * 0x1000) +#define OVL_UI_BASE OVL_BASE(1) + +/* GLB registers */ +#define GLB_CTL 0x000 +#define GLB_CTL_EN __BIT(0) +#define GLB_STS 0x004 +#define GLB_DBUFFER 0x008 +#define GLB_DBUFFER_DOUBLE_BUFFER_RDY __BIT(0) +#define GLB_SIZE 0x00c + +/* BLD registers */ +#define BLD_FILL_COLOR_CTL 0x000 +#define BLD_FILL_COLOR_CTL_P0_EN __BIT(8) +#define BLD_CH_ISIZE(n) (0x008 + (n) * 0x10) +#define BLD_CH_OFFSET(n) (0x00c + (n) * 0x10) +#define BLD_CH_RTCTL 0x080 +#define BLD_CH_RTCTL_P0 __BITS(3,0) +#define BLD_SIZE 0x08c +#define BLD_CTL(n) (0x090 + (n) * 0x04) + +/* OVL_UI registers */ +#define OVL_UI_ATTR_CTL(n) (0x000 + (n) * 0x20) +#define OVL_UI_ATTR_CTL_LAY_FBFMT __BITS(12,8) +#define OVL_UI_ATTR_CTL_LAY_FBFMT_XRGB_8888 0x04 +#define OVL_UI_ATTR_CTL_LAY_EN __BIT(0) +#define OVL_UI_MBSIZE(n) (0x004 + (n) * 0x20) +#define OVL_UI_COOR(n) (0x008 + (n) * 0x20) +#define OVL_UI_PITCH(n) (0x00c + (n) * 0x20) +#define OVL_UI_TOP_LADD(n) (0x010 + (n) * 0x20) +#define OVL_UI_TOP_HADD 0x080 +#define OVL_UI_TOP_HADD_LAYER0 __BITS(7,0) +#define OVL_UI_SIZE 0x088 + +enum { + MIXER_PORT_OUTPUT = 1, +}; + +static const char * const compatible[] = { + "allwinner,sun50i-a64-de2-mixer-0", + "allwinner,sun50i-a64-de2-mixer-1", + NULL +}; + +struct sunxi_mixer_softc; + +struct sunxi_mixer_crtc { + struct drm_crtc base; + struct sunxi_mixer_softc *sc; +}; + +struct sunxi_mixer_softc { + device_t sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + int sc_phandle; + + struct sunxi_mixer_crtc sc_crtc; + + struct fdt_device_ports sc_ports; +}; + +#define GLB_READ(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, GLB_BASE + (reg)) +#define GLB_WRITE(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, GLB_BASE + (reg), (val)) + +#define BLD_READ(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, BLD_BASE + (reg)) +#define BLD_WRITE(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, BLD_BASE + (reg), (val)) + +#define OVL_UI_READ(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, OVL_UI_BASE + (reg)) +#define OVL_UI_WRITE(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, OVL_UI_BASE + (reg), (val)) + +#define to_sunxi_mixer_crtc(x) container_of(x, struct sunxi_mixer_crtc, base) + +static void +sunxi_mixer_destroy(struct drm_crtc *crtc) +{ + drm_crtc_cleanup(crtc); +} + +static const struct drm_crtc_funcs sunxi_mixer_crtc_funcs = { + .set_config = drm_crtc_helper_set_config, + .destroy = sunxi_mixer_destroy, +}; + +static void +sunxi_mixer_dpms(struct drm_crtc *crtc, int mode) +{ +} + +static bool +sunxi_mixer_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int +sunxi_mixer_mode_do_set_base(struct drm_crtc *crtc, struct drm_framebuffer *fb, + int x, int y, int atomic) +{ + struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc); + struct sunxi_mixer_softc * const sc = mixer_crtc->sc; + struct sunxi_drm_framebuffer *sfb = atomic? + to_sunxi_drm_framebuffer(fb) : + to_sunxi_drm_framebuffer(crtc->primary->fb); + + uint64_t paddr = (uint64_t)sfb->obj->dmamap->dm_segs[0].ds_addr; + + uint32_t haddr = (paddr >> 32) & OVL_UI_TOP_HADD_LAYER0; + uint32_t laddr = paddr & 0xffffffff; + + /* Framebuffer start address */ + OVL_UI_WRITE(sc, OVL_UI_TOP_HADD, haddr); + OVL_UI_WRITE(sc, OVL_UI_TOP_LADD(0), laddr); + + return 0; +} + +static int +sunxi_mixer_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc); + struct sunxi_mixer_softc * const sc = mixer_crtc->sc; + uint32_t val; + + const uint32_t size = ((adjusted_mode->vdisplay - 1) << 16) | + (adjusted_mode->hdisplay - 1); + const uint32_t offset = (y << 16) | x; + + /* Set global size */ + GLB_WRITE(sc, GLB_SIZE, size); + + /* Enable pipe 0 */ + BLD_WRITE(sc, BLD_FILL_COLOR_CTL, BLD_FILL_COLOR_CTL_P0_EN); + + /* Set blender 0 input size */ + BLD_WRITE(sc, BLD_CH_ISIZE(0), size); + /* Set blender 0 offset */ + BLD_WRITE(sc, BLD_CH_OFFSET(0), offset); + /* Route channel 1 to pipe 0 */ + BLD_WRITE(sc, BLD_CH_RTCTL, __SHIFTIN(1, BLD_CH_RTCTL_P0)); + /* Set blender output size */ + BLD_WRITE(sc, BLD_SIZE, size); + + /* Enable UI overlay in XRGB8888 mode */ + val = OVL_UI_ATTR_CTL_LAY_EN | + __SHIFTIN(OVL_UI_ATTR_CTL_LAY_FBFMT_XRGB_8888, OVL_UI_ATTR_CTL_LAY_FBFMT); + OVL_UI_WRITE(sc, OVL_UI_ATTR_CTL(0), val); + /* Set UI overlay layer size */ + OVL_UI_WRITE(sc, OVL_UI_MBSIZE(0), size); + /* Set UI overlay offset */ + OVL_UI_WRITE(sc, OVL_UI_COOR(0), offset); + /* Set UI overlay line size */ + OVL_UI_WRITE(sc, OVL_UI_PITCH(0), adjusted_mode->hdisplay * 4); + /* Set UI overlay window size */ + OVL_UI_WRITE(sc, OVL_UI_SIZE, size); + + sunxi_mixer_mode_do_set_base(crtc, old_fb, x, y, 0); + + return 0; +} + +static int +sunxi_mixer_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc); + struct sunxi_mixer_softc * const sc = mixer_crtc->sc; + + sunxi_mixer_mode_do_set_base(crtc, old_fb, x, y, 0); + + /* Commit settings */ + GLB_WRITE(sc, GLB_DBUFFER, GLB_DBUFFER_DOUBLE_BUFFER_RDY); + + return 0; +} + +static int +sunxi_mixer_mode_set_base_atomic(struct drm_crtc *crtc, struct drm_framebuffer *fb, + int x, int y, enum mode_set_atomic state) +{ + struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc); + struct sunxi_mixer_softc * const sc = mixer_crtc->sc; + + sunxi_mixer_mode_do_set_base(crtc, fb, x, y, 1); + + /* Commit settings */ + GLB_WRITE(sc, GLB_DBUFFER, GLB_DBUFFER_DOUBLE_BUFFER_RDY); + + return 0; +} + +static void +sunxi_mixer_disable(struct drm_crtc *crtc) +{ +} + +static void +sunxi_mixer_prepare(struct drm_crtc *crtc) +{ + struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc); + struct sunxi_mixer_softc * const sc = mixer_crtc->sc; + + /* RT enable */ + GLB_WRITE(sc, GLB_CTL, GLB_CTL_EN); +} + +static void +sunxi_mixer_commit(struct drm_crtc *crtc) +{ + struct sunxi_mixer_crtc *mixer_crtc = to_sunxi_mixer_crtc(crtc); + struct sunxi_mixer_softc * const sc = mixer_crtc->sc; + + /* Commit settings */ + GLB_WRITE(sc, GLB_DBUFFER, GLB_DBUFFER_DOUBLE_BUFFER_RDY); +} + +static const struct drm_crtc_helper_funcs sunxi_mixer_crtc_helper_funcs = { + .dpms = sunxi_mixer_dpms, + .mode_fixup = sunxi_mixer_mode_fixup, + .mode_set = sunxi_mixer_mode_set, + .mode_set_base = sunxi_mixer_mode_set_base, + .mode_set_base_atomic = sunxi_mixer_mode_set_base_atomic, + .disable = sunxi_mixer_disable, + .prepare = sunxi_mixer_prepare, + .commit = sunxi_mixer_commit, +}; + +static int +sunxi_mixer_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate) +{ + struct sunxi_mixer_softc * const sc = device_private(dev); + struct drm_device *ddev; + + if (!activate) + return EINVAL; + + ddev = sunxi_drm_endpoint_device(ep); + if (ddev == NULL) { + DRM_ERROR("couldn't find DRM device\n"); + return ENXIO; + } + + sc->sc_crtc.sc = sc; + + drm_crtc_init(ddev, &sc->sc_crtc.base, &sunxi_mixer_crtc_funcs); + drm_crtc_helper_add(&sc->sc_crtc.base, &sunxi_mixer_crtc_helper_funcs); + + return fdt_endpoint_activate(ep, activate); +} + +static void * +sunxi_mixer_ep_get_data(device_t dev, struct fdt_endpoint *ep) +{ + struct sunxi_mixer_softc * const sc = device_private(dev); + + return &sc->sc_crtc; +} + +static int +sunxi_mixer_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compatible(faa->faa_phandle, compatible); +} + +static void +sunxi_mixer_attach(device_t parent, device_t self, void *aux) +{ + struct sunxi_mixer_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + struct fdt_endpoint *out_ep; + const int phandle = faa->faa_phandle; + struct clk *clk_bus, *clk_mod; + struct fdtbus_reset *rst; + bus_addr_t addr; + bus_size_t size; + + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { + aprint_error(": couldn't get registers\n"); + return; + } + + rst = fdtbus_reset_get_index(phandle, 0); + if (rst == NULL || fdtbus_reset_deassert(rst) != 0) { + aprint_error(": couldn't de-assert reset\n"); + return; + } + + clk_bus = fdtbus_clock_get(phandle, "bus"); + if (clk_bus == NULL || clk_enable(clk_bus) != 0) { + aprint_error(": couldn't enable bus clock\n"); + return; + } + + clk_mod = fdtbus_clock_get(phandle, "mod"); + if (clk_mod == NULL || + clk_set_rate(clk_mod, SUNXI_MIXER_FREQ) != 0 || + clk_enable(clk_mod) != 0) { + aprint_error(": couldn't enable mod clock\n"); + return; + } + + sc->sc_dev = self; + sc->sc_bst = faa->faa_bst; + if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { + aprint_error(": couldn't map registers\n"); + return; + } + sc->sc_phandle = faa->faa_phandle; + + aprint_naive("\n"); + aprint_normal(": Display Engine Mixer\n"); + + sc->sc_ports.dp_ep_activate = sunxi_mixer_ep_activate; + sc->sc_ports.dp_ep_get_data = sunxi_mixer_ep_get_data; + fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_CRTC); + + out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, MIXER_PORT_OUTPUT, 0); + if (out_ep != NULL) + sunxi_drm_register_endpoint(phandle, out_ep); +} + +CFATTACH_DECL_NEW(sunxi_mixer, sizeof(struct sunxi_mixer_softc), + sunxi_mixer_match, sunxi_mixer_attach, NULL, NULL); Index: src/sys/dev/fdt/hdmi_connector.c diff -u /dev/null src/sys/dev/fdt/hdmi_connector.c:1.1 --- /dev/null Wed Jan 30 01:24:00 2019 +++ src/sys/dev/fdt/hdmi_connector.c Wed Jan 30 01:24:00 2019 @@ -0,0 +1,237 @@ +/* $NetBSD: hdmi_connector.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */ + +/*- + * Copyright (c) 2019 Jared McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: hdmi_connector.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/systm.h> +#include <sys/gpio.h> + +#include <dev/fdt/fdtvar.h> +#include <dev/fdt/fdt_port.h> + +#include <dev/i2c/ddcvar.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> + +static const char * const compatible[] = { + "hdmi-connector", + NULL +}; + +struct dispcon_hdmi_connector { + struct drm_connector base; + struct fdtbus_gpio_pin *hpd; + i2c_tag_t ddc; + + int type; /* DRM_MODE_CONNECTOR_* */ +}; + +struct dispcon_hdmi_softc { + struct fdt_device_ports sc_ports; + struct dispcon_hdmi_connector sc_connector; +}; + +#define to_dispcon_hdmi_connector(x) container_of(x, struct dispcon_hdmi_connector, base) + +static enum drm_connector_status +dispcon_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct dispcon_hdmi_connector *hdmi_connector = to_dispcon_hdmi_connector(connector); + bool con; + + if (hdmi_connector->hpd == NULL) { + /* + * No hotplug detect pin available. Assume that we are connected. + */ + return connector_status_connected; + } + + /* + * Read connect status from hotplug detect pin. + */ + con = fdtbus_gpio_read(hdmi_connector->hpd); + if (con) { + return connector_status_connected; + } else { + return connector_status_disconnected; + } +} + +static void +dispcon_hdmi_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs dispcon_hdmi_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = dispcon_hdmi_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = dispcon_hdmi_connector_destroy, +}; + +static int +dispcon_hdmi_connector_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static int +dispcon_hdmi_connector_get_modes(struct drm_connector *connector) +{ + struct dispcon_hdmi_connector *hdmi_connector = to_dispcon_hdmi_connector(connector); + char edid[EDID_LENGTH * 4]; + struct edid *pedid = NULL; + int error, block; + + if (hdmi_connector->ddc != NULL) { + memset(edid, 0, sizeof(edid)); + for (block = 0; block < 4; block++) { + error = ddc_read_edid_block(hdmi_connector->ddc, + &edid[block * EDID_LENGTH], EDID_LENGTH, block); + if (error) + break; + if (block == 0) { + pedid = (struct edid *)edid; + if (edid[0x7e] == 0) + break; + } + } + } + + drm_mode_connector_update_edid_property(connector, pedid); + if (pedid == NULL) + return 0; + + error = drm_add_edid_modes(connector, pedid); + drm_edid_to_eld(connector, pedid); + + return error; +} + +static struct drm_encoder * +dispcon_hdmi_connector_best_encoder(struct drm_connector *connector) +{ + int enc_id = connector->encoder_ids[0]; + struct drm_mode_object *obj; + struct drm_encoder *encoder = NULL; + + if (enc_id) { + obj = drm_mode_object_find(connector->dev, enc_id, + DRM_MODE_OBJECT_ENCODER); + if (obj == NULL) + return NULL; + encoder = obj_to_encoder(obj); + } + + return encoder; +} + +static const struct drm_connector_helper_funcs dispcon_hdmi_connector_helper_funcs = { + .mode_valid = dispcon_hdmi_connector_mode_valid, + .get_modes = dispcon_hdmi_connector_get_modes, + .best_encoder = dispcon_hdmi_connector_best_encoder, +}; + +static int +dispcon_hdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate) +{ + struct drm_connector *connector = fdt_endpoint_get_data(ep); + struct dispcon_hdmi_connector *hdmi_connector = to_dispcon_hdmi_connector(connector); + struct fdt_endpoint *rep = fdt_endpoint_remote(ep); + struct drm_encoder *encoder; + + if (fdt_endpoint_port_index(ep) != 0) + return EINVAL; + + if (fdt_endpoint_type(rep) != EP_DRM_ENCODER) + return EINVAL; + + if (activate) { + encoder = fdt_endpoint_get_data(rep); + + connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + drm_connector_init(encoder->dev, connector, &dispcon_hdmi_connector_funcs, + hdmi_connector->type); + drm_connector_helper_add(connector, &dispcon_hdmi_connector_helper_funcs); + drm_connector_register(connector); + drm_mode_connector_attach_encoder(connector, encoder); + } + + return 0; +} + +static void * +dispcon_hdmi_ep_get_data(device_t dev, struct fdt_endpoint *ep) +{ + struct dispcon_hdmi_softc * const sc = device_private(dev); + + return &sc->sc_connector.base; +} + +static int +dispcon_hdmi_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compatible(faa->faa_phandle, compatible); +} + +static void +dispcon_hdmi_attach(device_t parent, device_t self, void *aux) +{ + struct dispcon_hdmi_softc * const sc = device_private(self); + struct dispcon_hdmi_connector * const hdmi_connector = &sc->sc_connector; + struct fdt_attach_args * const faa = aux; + const int phandle = faa->faa_phandle; + + aprint_naive("\n"); + aprint_normal(": HDMI connector\n"); + + hdmi_connector->type = DRM_MODE_CONNECTOR_HDMIA; + hdmi_connector->hpd = fdtbus_gpio_acquire(phandle, "hpd-gpios", GPIO_PIN_INPUT); + hdmi_connector->ddc = fdtbus_i2c_acquire(phandle, "ddc-i2c-bus"); + + sc->sc_ports.dp_ep_activate = dispcon_hdmi_ep_activate; + sc->sc_ports.dp_ep_get_data = dispcon_hdmi_ep_get_data; + fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_CONNECTOR); +} + +CFATTACH_DECL_NEW(dispcon_hdmi, sizeof(struct dispcon_hdmi_softc), + dispcon_hdmi_match, dispcon_hdmi_attach, NULL, NULL);