Home | History | Annotate | Line # | Download | only in sunxi
sunxi_dwhdmi.c revision 1.12
      1  1.12   thorpej /* $NetBSD: sunxi_dwhdmi.c,v 1.12 2025/10/13 14:12:13 thorpej Exp $ */
      2   1.1  jmcneill 
      3   1.1  jmcneill /*-
      4   1.1  jmcneill  * Copyright (c) 2019 Jared D. McNeill <jmcneill (at) invisible.ca>
      5   1.1  jmcneill  * All rights reserved.
      6   1.1  jmcneill  *
      7   1.1  jmcneill  * Redistribution and use in source and binary forms, with or without
      8   1.1  jmcneill  * modification, are permitted provided that the following conditions
      9   1.1  jmcneill  * are met:
     10   1.1  jmcneill  * 1. Redistributions of source code must retain the above copyright
     11   1.1  jmcneill  *    notice, this list of conditions and the following disclaimer.
     12   1.1  jmcneill  * 2. Redistributions in binary form must reproduce the above copyright
     13   1.1  jmcneill  *    notice, this list of conditions and the following disclaimer in the
     14   1.1  jmcneill  *    documentation and/or other materials provided with the distribution.
     15   1.1  jmcneill  *
     16   1.1  jmcneill  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     17   1.1  jmcneill  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     18   1.1  jmcneill  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     19   1.1  jmcneill  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     20   1.1  jmcneill  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
     21   1.1  jmcneill  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     22   1.1  jmcneill  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
     23   1.1  jmcneill  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     24   1.1  jmcneill  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25   1.1  jmcneill  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26   1.1  jmcneill  * SUCH DAMAGE.
     27   1.1  jmcneill  */
     28   1.1  jmcneill 
     29   1.1  jmcneill #include <sys/cdefs.h>
     30  1.12   thorpej __KERNEL_RCSID(0, "$NetBSD: sunxi_dwhdmi.c,v 1.12 2025/10/13 14:12:13 thorpej Exp $");
     31   1.1  jmcneill 
     32   1.1  jmcneill #include <sys/param.h>
     33   1.1  jmcneill #include <sys/bus.h>
     34  1.11  riastrad #include <sys/conf.h>
     35   1.1  jmcneill #include <sys/device.h>
     36   1.1  jmcneill #include <sys/intr.h>
     37  1.11  riastrad #include <sys/kernel.h>
     38   1.1  jmcneill #include <sys/systm.h>
     39   1.1  jmcneill 
     40  1.11  riastrad #include <dev/fdt/fdt_port.h>
     41   1.1  jmcneill #include <dev/fdt/fdtvar.h>
     42   1.1  jmcneill 
     43   1.1  jmcneill #include <dev/ic/dw_hdmi.h>
     44   1.1  jmcneill 
     45   1.1  jmcneill #include <arm/sunxi/sunxi_hdmiphy.h>
     46   1.1  jmcneill 
     47  1.11  riastrad #include <drm/drm_drv.h>
     48  1.11  riastrad 
     49   1.1  jmcneill enum {
     50   1.1  jmcneill 	DWHDMI_PORT_INPUT = 0,
     51   1.1  jmcneill 	DWHDMI_PORT_OUTPUT = 1,
     52   1.1  jmcneill };
     53   1.1  jmcneill 
     54   1.9   thorpej static const struct device_compatible_entry compat_data[] = {
     55   1.9   thorpej 	{ .compat = "allwinner,sun8i-h3-dw-hdmi" },
     56   1.9   thorpej 	{ .compat = "allwinner,sun50i-a64-dw-hdmi" },
     57   1.9   thorpej 	DEVICE_COMPAT_EOL
     58   1.1  jmcneill };
     59   1.1  jmcneill 
     60   1.1  jmcneill struct sunxi_dwhdmi_softc {
     61   1.1  jmcneill 	struct dwhdmi_softc	sc_base;
     62   1.1  jmcneill 	int			sc_phandle;
     63   1.1  jmcneill 	struct fdtbus_phy	*sc_phy;
     64   1.3  jmcneill 	struct fdtbus_regulator	*sc_regulator;
     65   1.7  jmcneill 	struct clk		*sc_clk;
     66   1.1  jmcneill 
     67   1.1  jmcneill 	struct fdt_device_ports	sc_ports;
     68   1.1  jmcneill 	struct drm_display_mode	sc_curmode;
     69   1.1  jmcneill };
     70   1.1  jmcneill 
     71   1.1  jmcneill #define	to_sunxi_dwhdmi_softc(x)	container_of(x, struct sunxi_dwhdmi_softc, sc_base)
     72   1.1  jmcneill 
     73   1.1  jmcneill static int
     74   1.1  jmcneill sunxi_dwhdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
     75   1.1  jmcneill {
     76   1.1  jmcneill 	struct sunxi_dwhdmi_softc * const sc = device_private(dev);
     77   1.1  jmcneill 	struct fdt_endpoint *in_ep = fdt_endpoint_remote(ep);
     78   1.1  jmcneill 	struct fdt_endpoint *out_ep, *out_rep;
     79   1.1  jmcneill 	struct drm_encoder *encoder;
     80   1.1  jmcneill 	struct drm_bridge *bridge;
     81   1.1  jmcneill 	int error;
     82   1.1  jmcneill 
     83   1.1  jmcneill 	if (!activate)
     84   1.1  jmcneill 		return EINVAL;
     85   1.1  jmcneill 
     86   1.1  jmcneill 	if (fdt_endpoint_port_index(ep) != DWHDMI_PORT_INPUT)
     87   1.1  jmcneill 		return EINVAL;
     88   1.1  jmcneill 
     89   1.1  jmcneill 	switch (fdt_endpoint_type(in_ep)) {
     90   1.1  jmcneill 	case EP_DRM_ENCODER:
     91   1.1  jmcneill 		encoder = fdt_endpoint_get_data(in_ep);
     92   1.1  jmcneill 		break;
     93   1.1  jmcneill 	case EP_DRM_BRIDGE:
     94   1.1  jmcneill 		bridge = fdt_endpoint_get_data(in_ep);
     95   1.1  jmcneill 		encoder = bridge->encoder;
     96   1.1  jmcneill 		break;
     97   1.1  jmcneill 	default:
     98   1.1  jmcneill 		encoder = NULL;
     99   1.1  jmcneill 		break;
    100   1.1  jmcneill 	}
    101   1.1  jmcneill 
    102   1.1  jmcneill 	if (encoder == NULL)
    103   1.1  jmcneill 		return EINVAL;
    104   1.1  jmcneill 
    105   1.3  jmcneill 	if (sc->sc_regulator != NULL) {
    106   1.3  jmcneill 		error = fdtbus_regulator_enable(sc->sc_regulator);
    107   1.3  jmcneill 		if (error != 0) {
    108   1.3  jmcneill 			device_printf(dev, "couldn't enable supply\n");
    109   1.3  jmcneill 			return error;
    110   1.3  jmcneill 		}
    111   1.3  jmcneill 	}
    112   1.3  jmcneill 
    113   1.1  jmcneill 	error = dwhdmi_bind(&sc->sc_base, encoder);
    114   1.1  jmcneill 	if (error != 0)
    115   1.1  jmcneill 		return error;
    116   1.1  jmcneill 
    117   1.1  jmcneill 	out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, DWHDMI_PORT_OUTPUT, 0);
    118   1.1  jmcneill 	if (out_ep != NULL) {
    119   1.1  jmcneill 		/* Ignore downstream connectors, we have our own. */
    120   1.1  jmcneill 		out_rep = fdt_endpoint_remote(out_ep);
    121   1.1  jmcneill 		if (out_rep != NULL && fdt_endpoint_type(out_rep) == EP_DRM_CONNECTOR)
    122   1.1  jmcneill 			return 0;
    123   1.1  jmcneill 
    124   1.1  jmcneill 		error = fdt_endpoint_activate(out_ep, activate);
    125   1.1  jmcneill 		if (error != 0)
    126   1.1  jmcneill 			return error;
    127   1.1  jmcneill 	}
    128   1.1  jmcneill 
    129   1.1  jmcneill 	return 0;
    130   1.1  jmcneill }
    131   1.1  jmcneill 
    132   1.1  jmcneill static void *
    133   1.1  jmcneill sunxi_dwhdmi_ep_get_data(device_t dev, struct fdt_endpoint *ep)
    134   1.1  jmcneill {
    135   1.1  jmcneill 	struct sunxi_dwhdmi_softc * const sc = device_private(dev);
    136   1.1  jmcneill 
    137   1.1  jmcneill 	return &sc->sc_base.sc_bridge;
    138   1.1  jmcneill }
    139   1.1  jmcneill 
    140   1.1  jmcneill static enum drm_connector_status
    141   1.1  jmcneill sunxi_dwhdmi_detect(struct dwhdmi_softc *dsc, bool force)
    142   1.1  jmcneill {
    143   1.1  jmcneill 	struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
    144   1.1  jmcneill 
    145   1.1  jmcneill 	KASSERT(sc->sc_phy != NULL);
    146   1.1  jmcneill 
    147   1.1  jmcneill 	if (sunxi_hdmiphy_detect(sc->sc_phy, force))
    148   1.1  jmcneill 		return connector_status_connected;
    149   1.1  jmcneill 	else
    150   1.1  jmcneill 		return connector_status_disconnected;
    151   1.1  jmcneill }
    152   1.1  jmcneill 
    153   1.1  jmcneill static void
    154   1.1  jmcneill sunxi_dwhdmi_enable(struct dwhdmi_softc *dsc)
    155   1.1  jmcneill {
    156   1.1  jmcneill 	struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
    157   1.1  jmcneill 	int error;
    158   1.1  jmcneill 
    159   1.1  jmcneill 	KASSERT(sc->sc_phy != NULL);
    160   1.1  jmcneill 
    161   1.1  jmcneill 	error = fdtbus_phy_enable(sc->sc_phy, true);
    162   1.1  jmcneill 	if (error != 0) {
    163   1.1  jmcneill 		device_printf(dsc->sc_dev, "failed to enable phy: %d\n", error);
    164   1.1  jmcneill 		return;
    165   1.1  jmcneill 	}
    166   1.1  jmcneill 
    167   1.1  jmcneill 	error = sunxi_hdmiphy_config(sc->sc_phy, &sc->sc_curmode);
    168   1.1  jmcneill 	if (error != 0)
    169   1.1  jmcneill 		device_printf(dsc->sc_dev, "failed to configure phy: %d\n", error);
    170   1.1  jmcneill }
    171   1.1  jmcneill 
    172   1.1  jmcneill static void
    173   1.1  jmcneill sunxi_dwhdmi_disable(struct dwhdmi_softc *dsc)
    174   1.1  jmcneill {
    175   1.1  jmcneill 	struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
    176   1.1  jmcneill 	int error;
    177   1.1  jmcneill 
    178   1.1  jmcneill 	KASSERT(sc->sc_phy != NULL);
    179   1.1  jmcneill 
    180   1.1  jmcneill 	error = fdtbus_phy_enable(sc->sc_phy, false);
    181   1.1  jmcneill 	if (error != 0)
    182   1.1  jmcneill 		device_printf(dsc->sc_dev, "failed to disable phy\n");
    183   1.1  jmcneill }
    184   1.1  jmcneill 
    185   1.1  jmcneill static void
    186  1.10  riastrad sunxi_dwhdmi_mode_set(struct dwhdmi_softc *dsc,
    187  1.10  riastrad     const struct drm_display_mode *mode,
    188  1.10  riastrad     const struct drm_display_mode *adjusted_mode)
    189   1.1  jmcneill {
    190   1.1  jmcneill 	struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
    191   1.7  jmcneill 	int error;
    192   1.7  jmcneill 
    193   1.7  jmcneill 	if (sc->sc_clk != NULL) {
    194   1.7  jmcneill 		error = clk_set_rate(sc->sc_clk, adjusted_mode->clock * 1000);
    195   1.7  jmcneill 		if (error != 0)
    196   1.7  jmcneill 			device_printf(sc->sc_base.sc_dev,
    197   1.7  jmcneill 			    "couldn't set pixel clock to %u Hz: %d\n",
    198   1.7  jmcneill 			    adjusted_mode->clock * 1000, error);
    199   1.7  jmcneill 	}
    200   1.1  jmcneill 
    201   1.1  jmcneill 	sc->sc_curmode = *adjusted_mode;
    202   1.1  jmcneill }
    203   1.1  jmcneill 
    204   1.4  jmcneill static audio_dai_tag_t
    205   1.4  jmcneill sunxi_dwhdmi_dai_get_tag(device_t dev, const void *data, size_t len)
    206   1.4  jmcneill {
    207   1.4  jmcneill 	struct sunxi_dwhdmi_softc * const sc = device_private(dev);
    208   1.4  jmcneill 
    209   1.4  jmcneill 	if (len != 4)
    210   1.4  jmcneill 		return NULL;
    211   1.4  jmcneill 
    212   1.4  jmcneill 	return &sc->sc_base.sc_dai;
    213   1.4  jmcneill }
    214   1.4  jmcneill 
    215   1.4  jmcneill static struct fdtbus_dai_controller_func sunxi_dwhdmi_dai_funcs = {
    216   1.4  jmcneill 	.get_tag = sunxi_dwhdmi_dai_get_tag
    217   1.4  jmcneill };
    218   1.4  jmcneill 
    219   1.1  jmcneill static int
    220   1.1  jmcneill sunxi_dwhdmi_match(device_t parent, cfdata_t cf, void *aux)
    221   1.1  jmcneill {
    222   1.1  jmcneill 	struct fdt_attach_args * const faa = aux;
    223   1.1  jmcneill 
    224   1.9   thorpej 	return of_compatible_match(faa->faa_phandle, compat_data);
    225   1.1  jmcneill }
    226   1.1  jmcneill 
    227   1.1  jmcneill static void
    228   1.1  jmcneill sunxi_dwhdmi_attach(device_t parent, device_t self, void *aux)
    229   1.1  jmcneill {
    230   1.1  jmcneill 	struct sunxi_dwhdmi_softc * const sc = device_private(self);
    231   1.1  jmcneill 	struct fdt_attach_args * const faa = aux;
    232   1.1  jmcneill 	const int phandle = faa->faa_phandle;
    233   1.5  jmcneill 	struct clk *clk_iahb, *clk_isfr, *clk_tmds;
    234   1.1  jmcneill 	struct fdtbus_reset *rst;
    235   1.1  jmcneill 	bus_addr_t addr;
    236   1.1  jmcneill 	bus_size_t size;
    237   1.1  jmcneill 
    238  1.12   thorpej 	if (device_getprop_bool(self, "disabled")) {
    239   1.8  jmcneill 		aprint_naive("\n");
    240   1.8  jmcneill 		aprint_normal(": HDMI TX (disabled)\n");
    241   1.8  jmcneill 		return;
    242   1.8  jmcneill 	}
    243   1.8  jmcneill 
    244   1.1  jmcneill 	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
    245   1.1  jmcneill 		aprint_error(": couldn't get registers\n");
    246   1.1  jmcneill 		return;
    247   1.1  jmcneill 	}
    248   1.1  jmcneill 
    249   1.1  jmcneill 	clk_iahb = fdtbus_clock_get(phandle, "iahb");
    250   1.1  jmcneill 	if (clk_iahb == NULL || clk_enable(clk_iahb) != 0) {
    251   1.1  jmcneill 		aprint_error(": couldn't enable iahb clock\n");
    252   1.1  jmcneill 		return;
    253   1.1  jmcneill 	}
    254   1.1  jmcneill 
    255   1.1  jmcneill 	clk_isfr = fdtbus_clock_get(phandle, "isfr");
    256   1.1  jmcneill 	if (clk_isfr == NULL || clk_enable(clk_isfr) != 0) {
    257   1.1  jmcneill 		aprint_error(": couldn't enable isfr clock\n");
    258   1.1  jmcneill 		return;
    259   1.1  jmcneill 	}
    260   1.1  jmcneill 
    261   1.5  jmcneill 	clk_tmds = fdtbus_clock_get(phandle, "tmds");
    262   1.5  jmcneill 	if (clk_tmds == NULL || clk_enable(clk_tmds) != 0) {
    263   1.5  jmcneill 		aprint_error(": couldn't enable tmds clock\n");
    264   1.5  jmcneill 		return;
    265   1.5  jmcneill 	}
    266   1.5  jmcneill 
    267   1.1  jmcneill 	sc->sc_base.sc_dev = self;
    268   1.1  jmcneill 	sc->sc_base.sc_reg_width = 1;
    269   1.1  jmcneill 	sc->sc_base.sc_bst = faa->faa_bst;
    270   1.1  jmcneill 	if (bus_space_map(sc->sc_base.sc_bst, addr, size, 0, &sc->sc_base.sc_bsh) != 0) {
    271   1.1  jmcneill 		aprint_error(": couldn't map registers\n");
    272   1.1  jmcneill 		return;
    273   1.1  jmcneill 	}
    274   1.1  jmcneill 	sc->sc_base.sc_detect = sunxi_dwhdmi_detect;
    275   1.1  jmcneill 	sc->sc_base.sc_enable = sunxi_dwhdmi_enable;
    276   1.1  jmcneill 	sc->sc_base.sc_disable = sunxi_dwhdmi_disable;
    277   1.1  jmcneill 	sc->sc_base.sc_mode_set = sunxi_dwhdmi_mode_set;
    278   1.6  jmcneill 	sc->sc_base.sc_scl_hcnt = 0xd8;
    279   1.6  jmcneill 	sc->sc_base.sc_scl_lcnt = 0xfe;
    280   1.1  jmcneill 	sc->sc_phandle = faa->faa_phandle;
    281   1.7  jmcneill 	sc->sc_clk = clk_tmds;
    282   1.1  jmcneill 
    283   1.1  jmcneill 	aprint_naive("\n");
    284   1.1  jmcneill 	aprint_normal(": HDMI TX\n");
    285   1.1  jmcneill 
    286   1.6  jmcneill 	sc->sc_regulator = fdtbus_regulator_acquire(sc->sc_phandle, "hvcc-supply");
    287   1.6  jmcneill 
    288   1.6  jmcneill 	sc->sc_phy = fdtbus_phy_get(sc->sc_phandle, "hdmi-phy");
    289   1.6  jmcneill 	if (sc->sc_phy == NULL)
    290   1.6  jmcneill 		sc->sc_phy = fdtbus_phy_get(sc->sc_phandle, "phy");
    291   1.6  jmcneill 	if (sc->sc_phy == NULL) {
    292   1.6  jmcneill 		device_printf(self, "couldn't find PHY\n");
    293   1.6  jmcneill 		return;
    294   1.6  jmcneill 	}
    295   1.6  jmcneill 
    296   1.7  jmcneill 	rst = fdtbus_reset_get(phandle, "ctrl");
    297   1.7  jmcneill 	if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
    298   1.7  jmcneill 		aprint_error_dev(self, "couldn't de-assert reset\n");
    299   1.7  jmcneill 		return;
    300   1.7  jmcneill 	}
    301   1.7  jmcneill 
    302   1.6  jmcneill 	sunxi_hdmiphy_init(sc->sc_phy);
    303   1.6  jmcneill 
    304   1.1  jmcneill 	if (dwhdmi_attach(&sc->sc_base) != 0) {
    305   1.1  jmcneill 		aprint_error_dev(self, "failed to attach driver\n");
    306   1.1  jmcneill 		return;
    307   1.1  jmcneill 	}
    308   1.1  jmcneill 
    309   1.1  jmcneill 	sc->sc_ports.dp_ep_activate = sunxi_dwhdmi_ep_activate;
    310   1.1  jmcneill 	sc->sc_ports.dp_ep_get_data = sunxi_dwhdmi_ep_get_data;
    311   1.1  jmcneill 	fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_BRIDGE);
    312   1.4  jmcneill 
    313   1.4  jmcneill 	fdtbus_register_dai_controller(self, phandle, &sunxi_dwhdmi_dai_funcs);
    314   1.1  jmcneill }
    315   1.1  jmcneill 
    316   1.1  jmcneill CFATTACH_DECL_NEW(sunxi_dwhdmi, sizeof(struct sunxi_dwhdmi_softc),
    317   1.1  jmcneill 	sunxi_dwhdmi_match, sunxi_dwhdmi_attach, NULL, NULL);
    318