diff options
author | 2020-03-02 10:37:22 +0000 | |
---|---|---|
committer | 2020-03-02 10:37:22 +0000 | |
commit | 6a2cdf39d707ccbfc372d87afc6aa32df35340b4 (patch) | |
tree | 760e19e183e68ab4d596e090d81f80f3e50abea4 | |
parent | Use current session for cwd of new sessions, not the new session which (diff) | |
download | wireguard-openbsd-6a2cdf39d707ccbfc372d87afc6aa32df35340b4.tar.xz wireguard-openbsd-6a2cdf39d707ccbfc372d87afc6aa32df35340b4.zip |
Add rkdwhdmi(4), a driver for the HDMI transmitter found on the Rockchip
RK3399 SoC.
ok patrick@
-rw-r--r-- | sys/conf/files | 7 | ||||
-rw-r--r-- | sys/dev/fdt/files.fdt | 6 | ||||
-rw-r--r-- | sys/dev/fdt/rkdwhdmi.c | 385 | ||||
-rw-r--r-- | sys/dev/ic/dwhdmi.c | 919 | ||||
-rw-r--r-- | sys/dev/ic/dwhdmi.h | 117 | ||||
-rw-r--r-- | sys/dev/ic/dwhdmiphy.c | 400 |
6 files changed, 1832 insertions, 2 deletions
diff --git a/sys/conf/files b/sys/conf/files index 1c623921b54..c039d07f5d0 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1,4 +1,4 @@ -# $OpenBSD: files,v 1.684 2020/01/24 05:14:51 jsg Exp $ +# $OpenBSD: files,v 1.685 2020/03/02 10:37:22 kettenis Exp $ # $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $ # @(#)files.newconf 7.5 (Berkeley) 5/10/93 @@ -518,6 +518,11 @@ file dev/spdmem.c spdmem device oaic: scsi file dev/ic/aic6250.c oaic +# Synopsys DesignWare HDMI transmitter +define dwhdmi +file dev/ic/dwhdmi.c dwhdmi +file dev/ic/dwhdmiphy.c dwhdmi + # Synopsys DesignWare I2C controller device dwiic: i2cbus file dev/ic/dwiic.c dwiic diff --git a/sys/dev/fdt/files.fdt b/sys/dev/fdt/files.fdt index ed9fcb37f70..a2df1d5d3a5 100644 --- a/sys/dev/fdt/files.fdt +++ b/sys/dev/fdt/files.fdt @@ -1,4 +1,4 @@ -# $OpenBSD: files.fdt,v 1.112 2020/02/21 15:51:45 patrick Exp $ +# $OpenBSD: files.fdt,v 1.113 2020/03/02 10:37:22 kettenis Exp $ # # Config file and device description for machine-independent FDT code. # Included by ports that need it. @@ -238,6 +238,10 @@ attach rkdrm at fdt file dev/fdt/rkdrm.c rkdrm file dev/pci/drm/drm_gem_cma_helper.c rkdrm +device rkdwhdmi: dwhdmi, i2cbus +attach rkdwhdmi at fdt +file dev/fdt/rkdwhdmi.c rkdwhdmi + device rkdwusb: fdt attach rkdwusb at fdt file dev/fdt/rkdwusb.c rkdwusb diff --git a/sys/dev/fdt/rkdwhdmi.c b/sys/dev/fdt/rkdwhdmi.c new file mode 100644 index 00000000000..3ce2b14b53e --- /dev/null +++ b/sys/dev/fdt/rkdwhdmi.c @@ -0,0 +1,385 @@ +/* $OpenBSD: rkdwhdmi.c,v 1.1 2020/03/02 10:37:22 kettenis Exp $ */ +/* $NetBSD: rk_dwhdmi.c,v 1.4 2019/12/17 18:26:36 jakllsch Exp $ */ + +/*- + * Copyright (c) 2019 Jared D. McNeill <jmcneill@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/systm.h> +#include <sys/kernel.h> + +#include <machine/bus.h> +#include <machine/fdt.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_clock.h> +#include <dev/ofw/ofw_misc.h> +#include <dev/ofw/ofw_pinctrl.h> +#include <dev/ofw/fdt.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include <dev/ic/dwhdmi.h> + +#define RK3399_GRF_SOC_CON20 0x6250 +#define HDMI_LCDC_SEL (1 << 6) + +const struct dwhdmi_mpll_config rkdwhdmi_mpll_config[] = { + { 40000, 0x00b3, 0x0000, 0x0018 }, + { 65000, 0x0072, 0x0001, 0x0028 }, + { 66000, 0x013e, 0x0003, 0x0038 }, + { 83500, 0x0072, 0x0001, 0x0028 }, + { 146250, 0x0051, 0x0002, 0x0038 }, + { 148500, 0x0051, 0x0003, 0x0000 }, + { 272000, 0x0040, 0x0003, 0x0000 }, + { 340000, 0x0040, 0x0003, 0x0000 }, + { 0, 0x0051, 0x0003, 0x0000 }, +}; + +const struct dwhdmi_phy_config rkdwhdmi_phy_config[] = { + { 74250, 0x8009, 0x0004, 0x0272 }, + { 148500, 0x802b, 0x0004, 0x028d }, + { 297000, 0x8039, 0x0005, 0x028d }, + { 594000, 0x8039, 0x0000, 0x019d }, + { 0, 0x0000, 0x0000, 0x0000 } +}; + +enum { + DWHDMI_PORT_INPUT = 0, + DWHDMI_PORT_OUTPUT = 1, +}; + +struct rkdwhdmi_port { + struct rkdwhdmi_softc *sc; + struct rkdwhdmi_ep *ep; + int nep; +}; + +struct rkdwhdmi_ep { + struct rkdwhdmi_port *port; + struct video_device vd; +}; + +struct rkdwhdmi_softc { + struct dwhdmi_softc sc_base; + int sc_node; + int sc_clk_vpll; + + struct drm_display_mode sc_curmode; + struct drm_encoder sc_encoder; + struct regmap *sc_grf; + + int sc_activated; + + struct rkdwhdmi_port *sc_port; + int sc_nport; +}; + +#define to_rkdwhdmi_softc(x) container_of(x, struct rkdwhdmi_softc, sc_base) +#define to_rkdwhdmi_encoder(x) container_of(x, struct rkdwhdmi_softc, sc_encoder) + +int rkdwhdmi_match(struct device *, void *, void *); +void rkdwhdmi_attach(struct device *, struct device *, void *); + +void rkdwhdmi_select_input(struct rkdwhdmi_softc *, u_int); +bool rkdwhdmi_encoder_mode_fixup(struct drm_encoder *, + const struct drm_display_mode *, struct drm_display_mode *); +void rkdwhdmi_encoder_mode_set(struct drm_encoder *, + struct drm_display_mode *, struct drm_display_mode *); +void rkdwhdmi_encoder_enable(struct drm_encoder *); +void rkdwhdmi_encoder_disable(struct drm_encoder *); +void rkdwhdmi_encoder_prepare(struct drm_encoder *); +void rkdwhdmi_encoder_commit(struct drm_encoder *); +void rkdwhdmi_encoder_dpms(struct drm_encoder *, int); + +int rkdwhdmi_ep_activate(void *, struct drm_device *); +void *rkdwhdmi_ep_get_data(void *); + +void rkdwhdmi_enable(struct dwhdmi_softc *); +void rkdwhdmi_mode_set(struct dwhdmi_softc *, struct drm_display_mode *, + struct drm_display_mode *); + +struct cfattach rkdwhdmi_ca = { + sizeof (struct rkdwhdmi_softc), rkdwhdmi_match, rkdwhdmi_attach +}; + +struct cfdriver rkdwhdmi_cd = { + NULL, "rkdwhdmi", DV_DULL +}; + +int +rkdwhdmi_match(struct device *parent, void *match, void *aux) +{ + struct fdt_attach_args *faa = aux; + + return OF_is_compatible(faa->fa_node, "rockchip,rk3399-dw-hdmi"); +} + +void +rkdwhdmi_attach(struct device *parent, struct device *self, void *aux) +{ + struct rkdwhdmi_softc *sc = (struct rkdwhdmi_softc *)self; + struct fdt_attach_args *faa = aux; + int i, j, ep, port, ports, grf; + bus_addr_t addr; + bus_size_t size; + uint32_t phandle; + + if (faa->fa_nreg < 1) { + printf(": no registers\n"); + return; + } + + pinctrl_byname(sc->sc_node, "default"); + + clock_enable(faa->fa_node, "iahb"); + clock_enable(faa->fa_node, "isfr"); + clock_enable(faa->fa_node, "vpll"); + clock_enable(faa->fa_node, "grf"); + clock_enable(faa->fa_node, "cec"); + + sc->sc_base.sc_reg_width = + OF_getpropint(faa->fa_node, "reg-io-width", 4); + + sc->sc_base.sc_bst = faa->fa_iot; + if (bus_space_map(sc->sc_base.sc_bst, faa->fa_reg[0].addr, + faa->fa_reg[0].size, 0, &sc->sc_base.sc_bsh)) { + printf(": can't map registers\n"); + return; + } + + sc->sc_node = faa->fa_node; + sc->sc_clk_vpll = OF_getindex(faa->fa_node, "vpll", "clock-names"); + + grf = OF_getpropint(faa->fa_node, "rockchip,grf", 0); + sc->sc_grf = regmap_byphandle(grf); + if (sc->sc_grf == NULL) { + printf(": can't get grf\n"); + return; + } + + printf(": HDMI TX\n"); + + phandle = OF_getpropint(faa->fa_node, "ddc-i2c-bus", 0); + sc->sc_base.sc_ic = i2c_byphandle(phandle); + if (phandle && sc->sc_base.sc_ic == NULL) { + printf("%s: couldn't find external I2C master\n", + self->dv_xname); + return; + } + + sc->sc_base.sc_flags |= DWHDMI_USE_INTERNAL_PHY; + sc->sc_base.sc_detect = dwhdmi_phy_detect; + sc->sc_base.sc_enable = rkdwhdmi_enable; + sc->sc_base.sc_disable = dwhdmi_phy_disable; + sc->sc_base.sc_mode_set = rkdwhdmi_mode_set; + sc->sc_base.sc_mpll_config = rkdwhdmi_mpll_config; + sc->sc_base.sc_phy_config = rkdwhdmi_phy_config; + + if (dwhdmi_attach(&sc->sc_base) != 0) { + printf("%s: failed to attach driver\n", self->dv_xname); + return; + } + + ports = OF_getnodebyname(faa->fa_node, "ports"); + if (!ports) + return; + + for (port = OF_child(ports); port; port = OF_peer(port)) + sc->sc_nport++; + if (!sc->sc_nport) + return; + + sc->sc_port = mallocarray(sc->sc_nport, sizeof(*sc->sc_port), M_DEVBUF, + M_WAITOK | M_ZERO); + for (i = 0, port = OF_child(ports); port; port = OF_peer(port), i++) { + for (ep = OF_child(port); ep; ep = OF_peer(ep)) + sc->sc_port[i].nep++; + if (!sc->sc_port[i].nep) + continue; + sc->sc_port[i].sc = sc; + sc->sc_port[i].ep = mallocarray(sc->sc_port[i].nep, + sizeof(*sc->sc_port[i].ep), M_DEVBUF, M_WAITOK | M_ZERO); + for (j = 0, ep = OF_child(port); ep; ep = OF_peer(ep), j++) { + sc->sc_port[i].ep[j].port = &sc->sc_port[i]; + sc->sc_port[i].ep[j].vd.vd_node = ep; + sc->sc_port[i].ep[j].vd.vd_cookie = + &sc->sc_port[i].ep[j]; + sc->sc_port[i].ep[j].vd.vd_ep_activate = + rkdwhdmi_ep_activate; + sc->sc_port[i].ep[j].vd.vd_ep_get_data = + rkdwhdmi_ep_get_data; + video_register(&sc->sc_port[i].ep[j].vd); + } + } + +#ifdef notyet + fdtbus_register_dai_controller(self, phandle, &rkdwhdmi_dai_funcs); +#endif +} + +void +rkdwhdmi_select_input(struct rkdwhdmi_softc *sc, u_int crtc_index) +{ + const uint32_t write_mask = HDMI_LCDC_SEL << 16; + const uint32_t write_val = crtc_index == 0 ? HDMI_LCDC_SEL : 0; + + regmap_write_4(sc->sc_grf, RK3399_GRF_SOC_CON20, write_mask | write_val); +} + +bool +rkdwhdmi_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + return true; +} + +void +rkdwhdmi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, struct drm_display_mode *adjusted) +{ +} + +void +rkdwhdmi_encoder_enable(struct drm_encoder *encoder) +{ +} + +void +rkdwhdmi_encoder_disable(struct drm_encoder *encoder) +{ +} + +void +rkdwhdmi_encoder_prepare(struct drm_encoder *encoder) +{ + struct rkdwhdmi_softc * const sc = to_rkdwhdmi_encoder(encoder); + const u_int crtc_index = drm_crtc_index(encoder->crtc); + + rkdwhdmi_select_input(sc, crtc_index); +} + +void +rkdwhdmi_encoder_commit(struct drm_encoder *encoder) +{ +} + +struct drm_encoder_funcs rkdwhdmi_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +struct drm_encoder_helper_funcs rkdwhdmi_encoder_helper_funcs = { + .prepare = rkdwhdmi_encoder_prepare, + .mode_fixup = rkdwhdmi_encoder_mode_fixup, + .mode_set = rkdwhdmi_encoder_mode_set, + .enable = rkdwhdmi_encoder_enable, + .disable = rkdwhdmi_encoder_disable, + .commit = rkdwhdmi_encoder_commit, +}; + +int +rkdwhdmi_ep_activate(void *cookie, struct drm_device *ddev) +{ + struct rkdwhdmi_ep *ep = cookie; + struct rkdwhdmi_port *port = ep->port; + struct rkdwhdmi_softc *sc = port->sc; + int error; + + if (sc->sc_activated) + return 0; + + if (OF_getpropint(OF_parent(ep->vd.vd_node), "reg", 0) != DWHDMI_PORT_INPUT) + return EINVAL; + + sc->sc_encoder.possible_crtcs = 0x3; /* XXX */ + drm_encoder_init(ddev, &sc->sc_encoder, &rkdwhdmi_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + drm_encoder_helper_add(&sc->sc_encoder, &rkdwhdmi_encoder_helper_funcs); + + sc->sc_base.sc_connector.base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + error = dwhdmi_bind(&sc->sc_base, &sc->sc_encoder); + if (error != 0) + return error; + + sc->sc_activated = 1; + return 0; +} + +void * +rkdwhdmi_ep_get_data(void *cookie) +{ + struct rkdwhdmi_ep *ep = cookie; + struct rkdwhdmi_port *port = ep->port; + struct rkdwhdmi_softc *sc = port->sc; + + return &sc->sc_encoder; +} + +void +rkdwhdmi_enable(struct dwhdmi_softc *dsc) +{ + dwhdmi_phy_enable(dsc); +} + +void +rkdwhdmi_mode_set(struct dwhdmi_softc *dsc, + struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + struct rkdwhdmi_softc *sc = to_rkdwhdmi_softc(dsc); + int error; + + if (sc->sc_clk_vpll != -1) { + error = clock_set_frequency(sc->sc_node, "vpll", + adjusted_mode->clock * 1000); + if (error != 0) + printf("%s: couldn't set pixel clock to %u Hz: %d\n", + dsc->sc_dev.dv_xname, adjusted_mode->clock * 1000, + error); + } + + dwhdmi_phy_mode_set(dsc, mode, adjusted_mode); +} + +#ifdef notyet + +static audio_dai_tag_t +rkdwhdmi_dai_get_tag(device_t dev, const void *data, size_t len) +{ + struct rkdwhdmi_softc * const sc = device_private(dev); + + if (len != 4) + return NULL; + + return &sc->sc_base.sc_dai; +} + +static struct fdtbus_dai_controller_func rkdwhdmi_dai_funcs = { + .get_tag = rkdwhdmi_dai_get_tag +}; + +#endif diff --git a/sys/dev/ic/dwhdmi.c b/sys/dev/ic/dwhdmi.c new file mode 100644 index 00000000000..ac147d184ad --- /dev/null +++ b/sys/dev/ic/dwhdmi.c @@ -0,0 +1,919 @@ +/* $OpenBSD: dwhdmi.c,v 1.1 2020/03/02 10:37:23 kettenis Exp $ */ +/* $NetBSD: dw_hdmi.c,v 1.7 2019/12/22 23:23:32 thorpej Exp $ */ + +/*- + * Copyright (c) 2019 Jared D. McNeill <jmcneill@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/systm.h> +#include <sys/kernel.h> + +#include <dev/ic/dwhdmi.h> + +#include <dev/i2c/i2cvar.h> + +#ifdef notyet +#include <dev/audio/audio_dai.h> +#endif + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> + +#define DDC_SEGMENT_ADDR 0x30 + +#define HDMI_DESIGN_ID 0x0000 +#define HDMI_REVISION_ID 0x0001 +#define HDMI_CONFIG0_ID 0x0004 +#define HDMI_CONFIG0_ID_AUDI2S (1 << 4) +#define HDMI_CONFIG2_ID 0x0006 + +#define HDMI_IH_I2CM_STAT0 0x0105 +#define HDMI_IH_I2CM_STAT0_DONE (1 << 1) +#define HDMI_IH_I2CM_STAT0_ERROR (1 << 0) +#define HDMI_IH_MUTE 0x01ff +#define HDMI_IH_MUTE_WAKEUP_INTERRUPT (1 << 1) +#define HDMI_IH_MUTE_ALL_INTERRUPT (1 << 0) + +#define HDMI_TX_INVID0 0x0200 +#define HDMI_TX_INVID0_VIDEO_MAPPING (0x1f << 0) +#define HDMI_TX_INVID0_VIDEO_MAPPING_DEFAULT (1 << 0) +#define HDMI_TX_INSTUFFING 0x0201 +#define HDMI_TX_INSTUFFING_BCBDATA_STUFFING (1 << 2) +#define HDMI_TX_INSTUFFING_RCRDATA_STUFFING (1 << 1) +#define HDMI_TX_INSTUFFING_GYDATA_STUFFING (1 << 0) +#define HDMI_TX_GYDATA0 0x0202 +#define HDMI_TX_GYDATA1 0x0203 +#define HDMI_TX_RCRDATA0 0x0204 +#define HDMI_TX_RCRDATA1 0x0205 +#define HDMI_TX_BCBDATA0 0x0206 +#define HDMI_TX_BCBDATA1 0x0207 + +#define HDMI_VP_STATUS 0x0800 +#define HDMI_VP_PR_CD 0x0801 +#define HDMI_VP_PR_CD_COLOR_DEPTH (0xf << 4) +#define HDMI_VP_PR_CD_COLOR_DEPTH_24 0 +#define HDMI_VP_PR_CD_DESIRED_PR_FACTOR (0xf << 0) +#define HDMI_VP_PR_CD_DESIRED_PR_FACTOR_NONE 0 +#define HDMI_VP_STUFF 0x0802 +#define HDMI_VP_STUFF_IDEFAULT_PHASE (1 << 5) +#define HDMI_VP_STUFF_YCC422_STUFFING (1 << 2) +#define HDMI_VP_STUFF_PP_STUFFING (1 << 1) +#define HDMI_VP_STUFF_PR_STUFFING (1 << 0) +#define HDMI_VP_REMAP 0x0803 +#define HDMI_VP_REMAP_YCC422_SIZE (0x3 << 0) +#define HDMI_VP_REMAP_YCC422_SIZE_16 0 +#define HDMI_VP_CONF 0x0804 +#define HDMI_VP_CONF_BYPASS_EN (1 << 6) +#define HDMI_VP_CONF_BYPASS_SELECT (1 << 2) +#define HDMI_VP_CONF_OUTPUT_SELECT (0x3 << 0) +#define HDMI_VP_CONF_OUTPUT_SELECT_BYPASS (2 << 0) +#define HDMI_VP_STAT 0x0805 +#define HDMI_VP_INT 0x0806 +#define HDMI_VP_MASK 0x0807 +#define HDMI_VP_POL 0x0808 + +#define HDMI_FC_INVIDCONF 0x1000 +#define HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY (1 << 6) +#define HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY (1 << 5) +#define HDMI_FC_INVIDCONF_DE_IN_POLARITY (1 << 4) +#define HDMI_FC_INVIDCONF_DVI_MODE (1 << 3) +#define HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC (1 << 1) +#define HDMI_FC_INVIDCONF_IN_I_P (1 << 0) +#define HDMI_FC_INHACTIV0 0x1001 +#define HDMI_FC_INHACTIV1 0x1002 +#define HDMI_FC_INHBLANK0 0x1003 +#define HDMI_FC_INHBLANK1 0x1004 +#define HDMI_FC_INVACTIV0 0x1005 +#define HDMI_FC_INVACTIV1 0x1006 +#define HDMI_FC_INVBLANK 0x1007 +#define HDMI_FC_HSYNCINDELAY0 0x1008 +#define HDMI_FC_HSYNCINDELAY1 0x1009 +#define HDMI_FC_HSYNCINWIDTH0 0x100a +#define HDMI_FC_HSYNCINWIDTH1 0x100b +#define HDMI_FC_VSYNCINDELAY 0x100c +#define HDMI_FC_VSYNCINWIDTH 0x100d +#define HDMI_FC_CTRLDUR 0x1011 +#define HDMI_FC_CTRLDUR_DEFAULT 12 +#define HDMI_FC_EXCTRLDUR 0x1012 +#define HDMI_FC_EXCTRLDUR_DEFAULT 32 +#define HDMI_FC_EXCTRLSPAC 0x1013 +#define HDMI_FC_EXCTRLSPAC_DEFAULT 1 +#define HDMI_FC_CH0PREAM 0x1014 +#define HDMI_FC_CH0PREAM_DEFAULT 0x0b +#define HDMI_FC_CH1PREAM 0x1015 +#define HDMI_FC_CH1PREAM_DEFAULT 0x16 +#define HDMI_FC_CH2PREAM 0x1016 +#define HDMI_FC_CH2PREAM_DEFAULT 0x21 +#define HDMI_FC_AUDCONF0 0x1025 +#define HDMI_FC_AUDCONF1 0x1026 +#define HDMI_FC_AUDCONF2 0x1027 +#define HDMI_FC_AUDCONF3 0x1028 + +#define HDMI_PHY_CONF0 0x3000 +#define HDMI_PHY_CONF0_PDZ (1 << 7) +#define HDMI_PHY_CONF0_ENTMDS (1 << 6) +#define HDMI_PHY_CONF0_SVSRET (1 << 5) +#define HDMI_PHY_CONF0_PDDQ (1 << 4) +#define HDMI_PHY_CONF0_TXPWRON (1 << 3) +#define HDMI_PHY_CONF0_ENHPDRXSENSE (1 << 2) +#define HDMI_PHY_CONF0_SELDATAENPOL (1 << 1) +#define HDMI_PHY_CONF0_SELDIPIF (1 << 0) +#define HDMI_PHY_STAT0 0x3004 +#define HDMI_PHY_STAT0_RX_SENSE_3 (1 << 7) +#define HDMI_PHY_STAT0_RX_SENSE_2 (1 << 6) +#define HDMI_PHY_STAT0_RX_SENSE_1 (1 << 5) +#define HDMI_PHY_STAT0_RX_SENSE_0 (1 << 4) +#define HDMI_PHY_STAT0_HPD (1 << 1) +#define HDMI_PHY_STAT0_TX_PHY_LOCK (1 << 0) + +#define HDMI_AUD_CONF0 0x3100 +#define HDMI_AUD_CONF0_SW_AUDIO_FIFO_RST (1 << 7) +#define HDMI_AUD_CONF0_I2S_SELECT (1 << 5) +#define HDMI_AUD_CONF0_I2S_IN_EN (0xf << 0) +#define HDMI_AUD_CONF1 0x3101 +#define HDMI_AUD_CONF1_I2S_WIDTH (0x1f << 0) +#define HDMI_AUD_INT 0x3102 +#define HDMI_AUD_CONF2 0x3103 +#define HDMI_AUD_CONF2_INSERT_PCUV (1 << 2) +#define HDMI_AUD_CONF2_NLPCM (1 << 1) +#define HDMI_AUD_CONF2_HBR (1 << 0) +#define HDMI_AUD_INT1 0x3104 + +#define HDMI_AUD_N1 0x3200 +#define HDMI_AUD_N2 0x3201 +#define HDMI_AUD_N3 0x3202 +#define HDMI_AUD_CTS1 0x3203 +#define HDMI_AUD_CTS2 0x3204 +#define HDMI_AUD_CTS3 0x3205 +#define HDMI_AUD_INPUTCLKFS 0x3206 +#define HDMI_AUD_INPUTCLKFS_IFSFACTOR (0x7 << 0) + +#define HDMI_MC_CLKDIS 0x4001 +#define HDMI_MC_CLKDIS_HDCPCLK_DISABLE (1 << 6) +#define HDMI_MC_CLKDIS_CECCLK_DISABLE (1 << 5) +#define HDMI_MC_CLKDIS_CSCCLK_DISABLE (1 << 4) +#define HDMI_MC_CLKDIS_AUDCLK_DISABLE (1 << 3) +#define HDMI_MC_CLKDIS_PREPCLK_DISABLE (1 << 2) +#define HDMI_MC_CLKDIS_TMDSCLK_DISABLE (1 << 1) +#define HDMI_MC_CLKDIS_PIXELCLK_DISABLE (1 << 0) +#define HDMI_MC_SWRSTZREQ 0x4002 +#define HDMI_MC_SWRSTZREQ_CECSWRST_REQ __BIT(6) +#define HDMI_MC_SWRSTZREQ_PREPSWRST_REQ (1 << 2) +#define HDMI_MC_SWRSTZREQ_TMDSSWRST_REQ (1 << 1) +#define HDMI_MC_SWRSTZREQ_PIXELSWRST_REQ (1 << 0) +#define HDMI_MC_FLOWCTRL 0x4004 +#define HDMI_MC_PHYRSTZ 0x4005 +#define HDMI_MC_PHYRSTZ_ASSERT (1 << 0) +#define HDMI_MC_PHYRSTZ_DEASSERT 0 +#define HDMI_MC_LOCKONCLOCK 0x4006 +#define HDMI_MC_HEACPHY_RST 0x4007 + +#define HDMI_I2CM_SLAVE 0x7e00 +#define HDMI_I2CM_ADDRESS 0x7e01 +#define HDMI_I2CM_DATAO 0x7e02 +#define HDMI_I2CM_DATAI 0x7e03 +#define HDMI_I2CM_OPERATION 0x7e04 +#define HDMI_I2CM_OPERATION_WR (1 << 4) +#define HDMI_I2CM_OPERATION_RD_EXT (1 << 1) +#define HDMI_I2CM_OPERATION_RD (1 << 0) +#define HDMI_I2CM_INT 0x7e05 +#define HDMI_I2CM_INT_DONE_POL (1 << 3) +#define HDMI_I2CM_INT_DONE_MASK (1 << 2) +#define HDMI_I2CM_INT_DONE_INTERRUPT (1 << 1) +#define HDMI_I2CM_INT_DONE_STATUS (1 << 0) +#define HDMI_I2CM_INT_DEFAULT \ + (HDMI_I2CM_INT_DONE_POL| \ + HDMI_I2CM_INT_DONE_INTERRUPT| \ + HDMI_I2CM_INT_DONE_STATUS) +#define HDMI_I2CM_CTLINT 0x7e06 +#define HDMI_I2CM_CTLINT_NACK_POL (1 << 7) +#define HDMI_I2CM_CTLINT_NACK_MASK (1 << 6) +#define HDMI_I2CM_CTLINT_NACK_INTERRUPT (1 << 5) +#define HDMI_I2CM_CTLINT_NACK_STATUS (1 << 4) +#define HDMI_I2CM_CTLINT_ARB_POL (1 << 3) +#define HDMI_I2CM_CTLINT_ARB_MASK (1 << 2) +#define HDMI_I2CM_CTLINT_ARB_INTERRUPT (1 << 1) +#define HDMI_I2CM_CTLINT_ARB_STATUS (1 << 0) +#define HDMI_I2CM_CTLINT_DEFAULT \ + (HDMI_I2CM_CTLINT_NACK_POL| \ + HDMI_I2CM_CTLINT_NACK_INTERRUPT| \ + HDMI_I2CM_CTLINT_NACK_STATUS| \ + HDMI_I2CM_CTLINT_ARB_POL| \ + HDMI_I2CM_CTLINT_ARB_INTERRUPT| \ + HDMI_I2CM_CTLINT_ARB_STATUS) +#define HDMI_I2CM_DIV 0x7e07 +#define HDMI_I2CM_DIV_FAST_STD_MODE (1 << 3) +#define HDMI_I2CM_SEGADDR 0x7e08 +#define HDMI_I2CM_SEGADDR_SEGADDR (0x7f << 0) +#define HDMI_I2CM_SOFTRSTZ 0x7e09 +#define HDMI_I2CM_SOFTRSTZ_I2C_SOFTRST (1 << 0) +#define HDMI_I2CM_SEGPTR 0x7e0a +#define HDMI_I2CM_SS_SCL_HCNT_0_ADDR 0x730c +#define HDMI_I2CM_SS_SCL_LCNT_0_ADDR 0x730e + +enum dwhdmi_dai_mixer_ctrl { + DWHDMI_DAI_OUTPUT_CLASS, + DWHDMI_DAI_INPUT_CLASS, + + DWHDMI_DAI_OUTPUT_MASTER_VOLUME, + DWHDMI_DAI_INPUT_DAC_VOLUME, + + DWHDMI_DAI_MIXER_CTRL_LAST +}; + +int +dwhdmi_ddc_exec(void *priv, i2c_op_t op, i2c_addr_t addr, + const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags) +{ + struct dwhdmi_softc * const sc = priv; + uint8_t block, operation, val; + uint8_t *pbuf = buf; + int off, n, retry; + + if (addr != DDC_ADDR || op != I2C_OP_READ_WITH_STOP || cmdlen == 0 || buf == NULL) { + printf("%s: bad args addr=%#x op=%#x cmdlen=%d buf=%p\n", + __func__, addr, op, (int)cmdlen, buf); + return ENXIO; + } + if (len > 256) { + printf("dwhdmi_ddc_exec: bad len %d\n", (int)len); + return ERANGE; + } + + dwhdmi_write(sc, HDMI_I2CM_SOFTRSTZ, 0); + dwhdmi_write(sc, HDMI_IH_I2CM_STAT0, dwhdmi_read(sc, HDMI_IH_I2CM_STAT0)); + if (sc->sc_scl_hcnt) + dwhdmi_write(sc, HDMI_I2CM_SS_SCL_HCNT_0_ADDR, sc->sc_scl_hcnt); + if (sc->sc_scl_lcnt) + dwhdmi_write(sc, HDMI_I2CM_SS_SCL_LCNT_0_ADDR, sc->sc_scl_lcnt); + dwhdmi_write(sc, HDMI_I2CM_DIV, 0); + dwhdmi_write(sc, HDMI_I2CM_SLAVE, DDC_ADDR); + dwhdmi_write(sc, HDMI_I2CM_SEGADDR, DDC_SEGMENT_ADDR); + + block = *(const uint8_t *)cmdbuf; + operation = block ? HDMI_I2CM_OPERATION_RD_EXT : HDMI_I2CM_OPERATION_RD; + off = (block & 1) ? 128 : 0; + + dwhdmi_write(sc, HDMI_I2CM_SEGPTR, block >> 1); + + for (n = 0; n < len; n++) { + dwhdmi_write(sc, HDMI_I2CM_ADDRESS, n + off); + dwhdmi_write(sc, HDMI_I2CM_OPERATION, operation); + for (retry = 10000; retry > 0; retry--) { + val = dwhdmi_read(sc, HDMI_IH_I2CM_STAT0); + if (val & HDMI_IH_I2CM_STAT0_ERROR) { + return EIO; + } + if (val & HDMI_IH_I2CM_STAT0_DONE) { + dwhdmi_write(sc, HDMI_IH_I2CM_STAT0, val); + break; + } + delay(1); + } + if (retry == 0) { + printf("dwhdmi_ddc_exec: timeout waiting for xfer, stat0=%#x\n", dwhdmi_read(sc, HDMI_IH_I2CM_STAT0)); + return ETIMEDOUT; + } + + pbuf[n] = dwhdmi_read(sc, HDMI_I2CM_DATAI); + } + + return 0; +} + +uint8_t +dwhdmi_read(struct dwhdmi_softc *sc, bus_size_t reg) +{ + uint8_t val; + + switch (sc->sc_reg_width) { + case 1: + val = bus_space_read_1(sc->sc_bst, sc->sc_bsh, reg); + break; + case 4: + val = bus_space_read_4(sc->sc_bst, sc->sc_bsh, reg * 4) & 0xff; + break; + default: + val = 0; + break; + } + + return val; +} + +void +dwhdmi_write(struct dwhdmi_softc *sc, bus_size_t reg, uint8_t val) +{ + switch (sc->sc_reg_width) { + case 1: + bus_space_write_1(sc->sc_bst, sc->sc_bsh, reg, val); + break; + case 4: + bus_space_write_4(sc->sc_bst, sc->sc_bsh, reg * 4, val); + break; + } +} + +void +dwhdmi_vp_init(struct dwhdmi_softc *sc) +{ + uint8_t val; + + /* Select 24-bits per pixel video, 8-bit packing mode and disable pixel repetition */ + val = HDMI_VP_PR_CD_COLOR_DEPTH_24 << 4 | + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_NONE << 0; + dwhdmi_write(sc, HDMI_VP_PR_CD, val); + + /* Configure stuffing */ + val = HDMI_VP_STUFF_IDEFAULT_PHASE | + HDMI_VP_STUFF_YCC422_STUFFING | + HDMI_VP_STUFF_PP_STUFFING | + HDMI_VP_STUFF_PR_STUFFING; + dwhdmi_write(sc, HDMI_VP_STUFF, val); + + /* Set YCC422 remap to 16-bit input video */ + val = HDMI_VP_REMAP_YCC422_SIZE_16 << 0; + dwhdmi_write(sc, HDMI_VP_REMAP, val); + + /* Configure video packetizer */ + val = HDMI_VP_CONF_BYPASS_EN | + HDMI_VP_CONF_BYPASS_SELECT | + HDMI_VP_CONF_OUTPUT_SELECT_BYPASS; + dwhdmi_write(sc, HDMI_VP_CONF, val); +} + +void +dwhdmi_tx_init(struct dwhdmi_softc *sc) +{ + uint8_t val; + + /* Disable internal data enable generator and set default video mapping */ + val = HDMI_TX_INVID0_VIDEO_MAPPING_DEFAULT; + dwhdmi_write(sc, HDMI_TX_INVID0, val); + + /* Enable video sampler stuffing */ + val = HDMI_TX_INSTUFFING_BCBDATA_STUFFING | + HDMI_TX_INSTUFFING_RCRDATA_STUFFING | + HDMI_TX_INSTUFFING_GYDATA_STUFFING; + dwhdmi_write(sc, HDMI_TX_INSTUFFING, val); +} + +int +dwhdmi_cea_mode_uses_fractional_vblank(uint8_t vic) +{ + const uint8_t match[] = { 5, 6, 7, 10, 11, 20, 21, 22 }; + u_int n; + + for (n = 0; n < nitems(match); n++) + if (match[n] == vic) + return true; + + return false; +} + +void +dwhdmi_fc_init(struct dwhdmi_softc *sc, struct drm_display_mode *mode) +{ + struct dwhdmi_connector *dwhdmi_connector = &sc->sc_connector; + uint8_t val; + + const uint8_t vic = drm_match_cea_mode(mode); + const uint16_t inhactiv = mode->crtc_hdisplay; + const uint16_t inhblank = mode->crtc_htotal - mode->crtc_hdisplay; + const uint16_t invactiv = mode->crtc_vdisplay; + const uint8_t invblank = mode->crtc_vtotal - mode->crtc_vdisplay; + const uint16_t hsyncindelay = mode->crtc_hsync_start - mode->crtc_hdisplay; + const uint16_t hsyncinwidth = mode->crtc_hsync_end - mode->crtc_hsync_start; + const uint8_t vsyncindelay = mode->crtc_vsync_start - mode->crtc_vdisplay; + const uint8_t vsyncinwidth = mode->crtc_vsync_end - mode->crtc_vsync_start; + + /* Input video configuration for frame composer */ + val = HDMI_FC_INVIDCONF_DE_IN_POLARITY; + if ((mode->flags & DRM_MODE_FLAG_PVSYNC) != 0) + val |= HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY; + if ((mode->flags & DRM_MODE_FLAG_PHSYNC) != 0) + val |= HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY; + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) != 0) + val |= HDMI_FC_INVIDCONF_IN_I_P; + if (dwhdmi_connector->hdmi_monitor) + val |= HDMI_FC_INVIDCONF_DVI_MODE; + if (dwhdmi_cea_mode_uses_fractional_vblank(vic)) + val |= HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC; + dwhdmi_write(sc, HDMI_FC_INVIDCONF, val); + + /* Input video mode timings */ + dwhdmi_write(sc, HDMI_FC_INHACTIV0, inhactiv & 0xff); + dwhdmi_write(sc, HDMI_FC_INHACTIV1, inhactiv >> 8); + dwhdmi_write(sc, HDMI_FC_INHBLANK0, inhblank & 0xff); + dwhdmi_write(sc, HDMI_FC_INHBLANK1, inhblank >> 8); + dwhdmi_write(sc, HDMI_FC_INVACTIV0, invactiv & 0xff); + dwhdmi_write(sc, HDMI_FC_INVACTIV1, invactiv >> 8); + dwhdmi_write(sc, HDMI_FC_INVBLANK, invblank); + dwhdmi_write(sc, HDMI_FC_HSYNCINDELAY0, hsyncindelay & 0xff); + dwhdmi_write(sc, HDMI_FC_HSYNCINDELAY1, hsyncindelay >> 8); + dwhdmi_write(sc, HDMI_FC_HSYNCINWIDTH0, hsyncinwidth & 0xff); + dwhdmi_write(sc, HDMI_FC_HSYNCINWIDTH1, hsyncinwidth >> 8); + dwhdmi_write(sc, HDMI_FC_VSYNCINDELAY, vsyncindelay); + dwhdmi_write(sc, HDMI_FC_VSYNCINWIDTH, vsyncinwidth); + + /* Setup control period minimum durations */ + dwhdmi_write(sc, HDMI_FC_CTRLDUR, HDMI_FC_CTRLDUR_DEFAULT); + dwhdmi_write(sc, HDMI_FC_EXCTRLDUR, HDMI_FC_EXCTRLDUR_DEFAULT); + dwhdmi_write(sc, HDMI_FC_EXCTRLSPAC, HDMI_FC_EXCTRLSPAC_DEFAULT); + + /* Setup channel preamble filters */ + dwhdmi_write(sc, HDMI_FC_CH0PREAM, HDMI_FC_CH0PREAM_DEFAULT); + dwhdmi_write(sc, HDMI_FC_CH1PREAM, HDMI_FC_CH1PREAM_DEFAULT); + dwhdmi_write(sc, HDMI_FC_CH2PREAM, HDMI_FC_CH2PREAM_DEFAULT); +} + +void +dwhdmi_mc_init(struct dwhdmi_softc *sc) +{ + uint8_t val; + u_int n, iter; + + /* Bypass colour space converter */ + dwhdmi_write(sc, HDMI_MC_FLOWCTRL, 0); + + /* Enable TMDS, pixel, and (if required) audio sampler clocks */ + val = HDMI_MC_CLKDIS_HDCPCLK_DISABLE | + HDMI_MC_CLKDIS_CECCLK_DISABLE | + HDMI_MC_CLKDIS_CSCCLK_DISABLE | + HDMI_MC_CLKDIS_PREPCLK_DISABLE; + dwhdmi_write(sc, HDMI_MC_CLKDIS, val); + + /* Soft reset TMDS */ + val = 0xff & ~HDMI_MC_SWRSTZREQ_TMDSSWRST_REQ; + dwhdmi_write(sc, HDMI_MC_SWRSTZREQ, val); + + iter = sc->sc_version == 0x130a ? 4 : 1; + + val = dwhdmi_read(sc, HDMI_FC_INVIDCONF); + for (n = 0; n < iter; n++) + dwhdmi_write(sc, HDMI_FC_INVIDCONF, val); +} + +void +dwhdmi_mc_disable(struct dwhdmi_softc *sc) +{ + /* Disable clocks */ + dwhdmi_write(sc, HDMI_MC_CLKDIS, 0xff); +} + +void +dwhdmi_audio_init(struct dwhdmi_softc *sc) +{ + uint8_t val; + u_int n; + + /* The following values are for 48 kHz */ + switch (sc->sc_curmode.clock) { + case 25170: + n = 6864; + break; + case 74170: + n = 11648; + break; + case 148350: + n = 5824; + break; + default: + n = 6144; + break; + } + + /* Use automatic CTS generation */ + dwhdmi_write(sc, HDMI_AUD_CTS1, 0); + dwhdmi_write(sc, HDMI_AUD_CTS2, 0); + dwhdmi_write(sc, HDMI_AUD_CTS3, 0); + + /* Set N factor for audio clock regeneration */ + dwhdmi_write(sc, HDMI_AUD_N1, n & 0xff); + dwhdmi_write(sc, HDMI_AUD_N2, (n >> 8) & 0xff); + dwhdmi_write(sc, HDMI_AUD_N3, (n >> 16) & 0xff); + + val = dwhdmi_read(sc, HDMI_AUD_CONF0); + val |= HDMI_AUD_CONF0_I2S_SELECT; /* XXX i2s mode */ + val &= ~HDMI_AUD_CONF0_I2S_IN_EN; + val |= (1 << 0); /* XXX 2ch */ + dwhdmi_write(sc, HDMI_AUD_CONF0, val); + + val = (16 << 0); + dwhdmi_write(sc, HDMI_AUD_CONF1, val); + + dwhdmi_write(sc, HDMI_AUD_INPUTCLKFS, 4); /* XXX 64 FS */ + + dwhdmi_write(sc, HDMI_FC_AUDCONF0, 1 << 4); /* XXX 2ch */ + dwhdmi_write(sc, HDMI_FC_AUDCONF1, 0); + dwhdmi_write(sc, HDMI_FC_AUDCONF2, 0); + dwhdmi_write(sc, HDMI_FC_AUDCONF3, 0); + + val = dwhdmi_read(sc, HDMI_MC_CLKDIS); + val &= ~HDMI_MC_CLKDIS_PREPCLK_DISABLE; + dwhdmi_write(sc, HDMI_MC_CLKDIS, val); +} + +enum drm_connector_status +dwhdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct dwhdmi_connector *dwhdmi_connector = to_dwhdmi_connector(connector); + struct dwhdmi_softc * const sc = dwhdmi_connector->sc; + + if (sc->sc_detect != NULL) + return sc->sc_detect(sc, force); + + return connector_status_connected; +} + +void +dwhdmi_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +const struct drm_connector_funcs dwhdmi_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = dwhdmi_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = dwhdmi_connector_destroy, +}; + +int +dwhdmi_connector_get_modes(struct drm_connector *connector) +{ + struct dwhdmi_connector *dwhdmi_connector = to_dwhdmi_connector(connector); + struct dwhdmi_softc * const sc = dwhdmi_connector->sc; + struct i2c_adapter ddc; + struct edid *edid; + int error = 0; + + memset(&ddc, 0, sizeof(ddc)); + ddc.ic = *sc->sc_ic; + + edid = drm_get_edid(connector, &ddc); + if (edid) { + dwhdmi_connector->hdmi_monitor = drm_detect_hdmi_monitor(edid); + dwhdmi_connector->monitor_audio = drm_detect_monitor_audio(edid); + drm_connector_update_edid_property(connector, edid); + error = drm_add_edid_modes(connector, edid); + kfree(edid); + } else { + dwhdmi_connector->hdmi_monitor = false; + dwhdmi_connector->monitor_audio = false; + } + + return error; +} + +struct drm_encoder * +dwhdmi_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, NULL, enc_id, + DRM_MODE_OBJECT_ENCODER); + if (obj == NULL) + return NULL; + encoder = obj_to_encoder(obj); + } + + return encoder; +} + +const struct drm_connector_helper_funcs dwhdmi_connector_helper_funcs = { + .get_modes = dwhdmi_connector_get_modes, + .best_encoder = dwhdmi_connector_best_encoder, +}; + +int +dwhdmi_bridge_attach(struct drm_bridge *bridge) +{ + struct dwhdmi_softc * const sc = bridge->driver_private; + struct dwhdmi_connector *dwhdmi_connector = &sc->sc_connector; + struct drm_connector *connector = &dwhdmi_connector->base; + int error; + + dwhdmi_connector->sc = sc; + + connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + drm_connector_init(bridge->dev, connector, &dwhdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + drm_connector_helper_add(connector, &dwhdmi_connector_helper_funcs); + + error = drm_connector_attach_encoder(connector, bridge->encoder); + if (error != 0) + return error; + + return drm_connector_register(connector); +} + +void +dwhdmi_bridge_enable(struct drm_bridge *bridge) +{ + struct dwhdmi_softc * const sc = bridge->driver_private; + + dwhdmi_vp_init(sc); + dwhdmi_fc_init(sc, &sc->sc_curmode); + + if (sc->sc_enable) + sc->sc_enable(sc); + + dwhdmi_tx_init(sc); + dwhdmi_mc_init(sc); + + if (sc->sc_connector.monitor_audio) + dwhdmi_audio_init(sc); +} + +void +dwhdmi_bridge_pre_enable(struct drm_bridge *bridge) +{ +} + +void +dwhdmi_bridge_disable(struct drm_bridge *bridge) +{ + struct dwhdmi_softc * const sc = bridge->driver_private; + + if (sc->sc_disable) + sc->sc_disable(sc); + + dwhdmi_mc_disable(sc); +} + +void +dwhdmi_bridge_post_disable(struct drm_bridge *bridge) +{ +} + +void +dwhdmi_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + struct dwhdmi_softc * const sc = bridge->driver_private; + + if (sc->sc_mode_set) + sc->sc_mode_set(sc, mode, adjusted_mode); + + sc->sc_curmode = *adjusted_mode; +} + +bool +dwhdmi_bridge_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + return true; +} + +const struct drm_bridge_funcs dwhdmi_bridge_funcs = { + .attach = dwhdmi_bridge_attach, + .enable = dwhdmi_bridge_enable, + .pre_enable = dwhdmi_bridge_pre_enable, + .disable = dwhdmi_bridge_disable, + .post_disable = dwhdmi_bridge_post_disable, + .mode_set = dwhdmi_bridge_mode_set, + .mode_fixup = dwhdmi_bridge_mode_fixup, +}; + +#ifdef notyet + +static int +dwhdmi_dai_set_format(audio_dai_tag_t dai, u_int format) +{ + return 0; +} + +static int +dwhdmi_dai_add_device(audio_dai_tag_t dai, audio_dai_tag_t aux) +{ + /* Not supported */ + return 0; +} + +static void +dwhdmi_audio_swvol_codec(audio_filter_arg_t *arg) +{ + struct dwhdmi_softc * const sc = arg->context; + const aint_t *src; + aint_t *dst; + u_int sample_count; + u_int i; + + src = arg->src; + dst = arg->dst; + sample_count = arg->count * arg->srcfmt->channels; + for (i = 0; i < sample_count; i++) { + aint2_t v = (aint2_t)(*src++); + v = v * sc->sc_swvol / 255; + *dst++ = (aint_t)v; + } +} + +static int +dwhdmi_audio_set_format(void *priv, int setmode, + const audio_params_t *play, const audio_params_t *rec, + audio_filter_reg_t *pfil, audio_filter_reg_t *rfil) +{ + struct dwhdmi_softc * const sc = priv; + + pfil->codec = dwhdmi_audio_swvol_codec; + pfil->context = sc; + + return 0; +} + +static int +dwhdmi_audio_set_port(void *priv, mixer_ctrl_t *mc) +{ + struct dwhdmi_softc * const sc = priv; + + switch (mc->dev) { + case DWHDMI_DAI_OUTPUT_MASTER_VOLUME: + case DWHDMI_DAI_INPUT_DAC_VOLUME: + sc->sc_swvol = mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT]; + return 0; + default: + return ENXIO; + } +} + +static int +dwhdmi_audio_get_port(void *priv, mixer_ctrl_t *mc) +{ + struct dwhdmi_softc * const sc = priv; + + switch (mc->dev) { + case DWHDMI_DAI_OUTPUT_MASTER_VOLUME: + case DWHDMI_DAI_INPUT_DAC_VOLUME: + mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = sc->sc_swvol; + mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = sc->sc_swvol; + return 0; + default: + return ENXIO; + } +} + +static int +dwhdmi_audio_query_devinfo(void *priv, mixer_devinfo_t *di) +{ + switch (di->index) { + case DWHDMI_DAI_OUTPUT_CLASS: + di->mixer_class = di->index; + strcpy(di->label.name, AudioCoutputs); + di->type = AUDIO_MIXER_CLASS; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + case DWHDMI_DAI_INPUT_CLASS: + di->mixer_class = di->index; + strcpy(di->label.name, AudioCinputs); + di->type = AUDIO_MIXER_CLASS; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + case DWHDMI_DAI_OUTPUT_MASTER_VOLUME: + di->mixer_class = DWHDMI_DAI_OUTPUT_CLASS; + strcpy(di->label.name, AudioNmaster); + di->un.v.delta = 1; + di->un.v.num_channels = 2; + strcpy(di->un.v.units.name, AudioNvolume); + di->type = AUDIO_MIXER_VALUE; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + case DWHDMI_DAI_INPUT_DAC_VOLUME: + di->mixer_class = DWHDMI_DAI_INPUT_CLASS; + strcpy(di->label.name, AudioNdac); + di->un.v.delta = 1; + di->un.v.num_channels = 2; + strcpy(di->un.v.units.name, AudioNvolume); + di->type = AUDIO_MIXER_VALUE; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + default: + return ENXIO; + } +} + +static const struct audio_hw_if dwhdmi_dai_hw_if = { + .set_format = dwhdmi_audio_set_format, + .set_port = dwhdmi_audio_set_port, + .get_port = dwhdmi_audio_get_port, + .query_devinfo = dwhdmi_audio_query_devinfo, +}; + +#endif + +int +dwhdmi_attach(struct dwhdmi_softc *sc) +{ + uint8_t val; + + if (sc->sc_reg_width != 1 && sc->sc_reg_width != 4) { + printf("%s: unsupported register width %d\n", + sc->sc_dev.dv_xname, sc->sc_reg_width); + return EINVAL; + } + + sc->sc_version = dwhdmi_read(sc, HDMI_DESIGN_ID); + sc->sc_version <<= 8; + sc->sc_version |= dwhdmi_read(sc, HDMI_REVISION_ID); + + sc->sc_phytype = dwhdmi_read(sc, HDMI_CONFIG2_ID); + + printf("%s: version %x.%03x, phytype 0x%02x\n", sc->sc_dev.dv_xname, + sc->sc_version >> 12, sc->sc_version & 0xfff, + sc->sc_phytype); + +#ifdef notyet + sc->sc_swvol = 255; +#endif + + /* + * If a DDC i2c bus tag is provided by the caller, use it. Otherwise, + * use the I2C master built-in to DWC HDMI. + */ + if (sc->sc_ic == NULL) { + struct i2c_controller *ic = &sc->sc_ic_builtin; + + memset(ic, 0, sizeof(*ic)); + ic->ic_cookie = sc; + ic->ic_exec = dwhdmi_ddc_exec; + sc->sc_ic = ic; + } + + /* + * Enable HPD on internal PHY + */ + if ((sc->sc_flags & DWHDMI_USE_INTERNAL_PHY) != 0) { + val = dwhdmi_read(sc, HDMI_PHY_CONF0); + val |= HDMI_PHY_CONF0_ENHPDRXSENSE; + dwhdmi_write(sc, HDMI_PHY_CONF0, val); + } + +#ifdef notyet + /* + * Initialize audio DAI + */ + sc->sc_dai.dai_set_format = dwhdmi_dai_set_format; + sc->sc_dai.dai_add_device = dwhdmi_dai_add_device; + sc->sc_dai.dai_hw_if = &dwhdmi_dai_hw_if; + sc->sc_dai.dai_dev = sc->sc_dev; + sc->sc_dai.dai_priv = sc; +#endif + + return 0; +} + +int +dwhdmi_bind(struct dwhdmi_softc *sc, struct drm_encoder *encoder) +{ + int error; + + sc->sc_bridge.driver_private = sc; + sc->sc_bridge.funcs = &dwhdmi_bridge_funcs; + sc->sc_bridge.encoder = encoder; + + error = drm_bridge_attach(encoder, &sc->sc_bridge, NULL); + if (error != 0) + return EIO; + + encoder->bridge = &sc->sc_bridge; + + return 0; +} diff --git a/sys/dev/ic/dwhdmi.h b/sys/dev/ic/dwhdmi.h new file mode 100644 index 00000000000..4913d7c0da0 --- /dev/null +++ b/sys/dev/ic/dwhdmi.h @@ -0,0 +1,117 @@ +/* $OpenBSD: dwhdmi.h,v 1.1 2020/03/02 10:37:23 kettenis Exp $ */ +/* $NetBSD: dw_hdmi.h,v 1.6 2019/12/22 23:23:32 thorpej Exp $ */ + +/*- + * Copyright (c) 2019 Jared D. McNeill <jmcneill@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _DEV_IC_DWHDMI_H +#define _DEV_IC_DWHDMI_H + +#include <dev/i2c/i2cvar.h> + +#ifdef notyet +#include <dev/audio/audio_dai.h> +#endif + +#include <drm/drmP.h> + +struct dwhdmi_softc; + +struct dwhdmi_connector { + struct drm_connector base; + struct dwhdmi_softc *sc; + + int hdmi_monitor; + int monitor_audio; +}; + +struct dwhdmi_phy_config { + u_int pixel_clock; + uint32_t sym; + uint32_t term; + uint32_t vlev; +}; + +struct dwhdmi_mpll_config { + u_int pixel_clock; + uint32_t cpce; + uint32_t gmp; + uint32_t curr; +}; + +struct dwhdmi_softc { + struct device sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + u_int sc_reg_width; + u_int sc_flags; +#define DWHDMI_USE_INTERNAL_PHY (1 << 0) + u_int sc_scl_hcnt; + u_int sc_scl_lcnt; + + u_int sc_phytype; + u_int sc_version; + + i2c_tag_t sc_ic; + struct i2c_controller sc_ic_builtin; + +#ifdef notyet + struct audio_dai_device sc_dai; + uint8_t sc_swvol; +#endif + + struct dwhdmi_connector sc_connector; + struct drm_bridge sc_bridge; + + struct drm_display_mode sc_curmode; + + const struct dwhdmi_mpll_config *sc_mpll_config; + const struct dwhdmi_phy_config *sc_phy_config; + + enum drm_connector_status (*sc_detect)(struct dwhdmi_softc *, int); + void (*sc_enable)(struct dwhdmi_softc *); + void (*sc_disable)(struct dwhdmi_softc *); + void (*sc_mode_set)(struct dwhdmi_softc *, + struct drm_display_mode *, + struct drm_display_mode *); +}; + +#define to_dwhdmi_connector(x) container_of(x, struct dwhdmi_connector, base) + +int dwhdmi_attach(struct dwhdmi_softc *); +int dwhdmi_bind(struct dwhdmi_softc *, struct drm_encoder *); + +uint8_t dwhdmi_read(struct dwhdmi_softc *, bus_size_t); +void dwhdmi_write(struct dwhdmi_softc *, bus_size_t, uint8_t); + +enum drm_connector_status dwhdmi_phy_detect(struct dwhdmi_softc *, int); +void dwhdmi_phy_enable(struct dwhdmi_softc *); +void dwhdmi_phy_disable(struct dwhdmi_softc *); +void dwhdmi_phy_mode_set(struct dwhdmi_softc *, + struct drm_display_mode *, + struct drm_display_mode *); + +#endif /* !_DEV_IC_DWHDMI_H */ diff --git a/sys/dev/ic/dwhdmiphy.c b/sys/dev/ic/dwhdmiphy.c new file mode 100644 index 00000000000..1dd2fdbf036 --- /dev/null +++ b/sys/dev/ic/dwhdmiphy.c @@ -0,0 +1,400 @@ +/* $OpenBSD: dwhdmiphy.c,v 1.1 2020/03/02 10:37:23 kettenis Exp $ */ +/* $NetBSD: dw_hdmi_phy.c,v 1.2 2019/11/10 10:36:01 jmcneill Exp $ */ + +/*- + * Copyright (c) 2015 Oleksandr Tymoshenko <gonzo@freebsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> + +#include <drm/drmP.h> + +#include <dev/ic/dwhdmi.h> + +#define HDMI_IH_PHY_STAT0 0x0104 +#define HDMI_IH_PHY_STAT0_HPD (1 << 0) +#define HDMI_IH_I2CMPHY_STAT0 0x0108 +#define HDMI_IH_I2CMPHY_STAT0_DONE (1 << 1) +#define HDMI_IH_I2CMPHY_STAT0_ERROR (1 << 0) + +#define HDMI_PHY_CONF0 0x3000 +#define HDMI_PHY_CONF0_PDZ_MASK 0x80 +#define HDMI_PHY_CONF0_PDZ_OFFSET 7 +#define HDMI_PHY_CONF0_ENTMDS_MASK 0x40 +#define HDMI_PHY_CONF0_ENTMDS_OFFSET 6 +#define HDMI_PHY_CONF0_SVSRET_MASK 0x20 +#define HDMI_PHY_CONF0_SVSRET_OFFSET 5 +#define HDMI_PHY_CONF0_GEN2_PDDQ_MASK 0x10 +#define HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET 4 +#define HDMI_PHY_CONF0_GEN2_TXPWRON_MASK 0x8 +#define HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET 3 +#define HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_MASK 0x4 +#define HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_OFFSET 2 +#define HDMI_PHY_CONF0_SELDATAENPOL_MASK 0x2 +#define HDMI_PHY_CONF0_SELDATAENPOL_OFFSET 1 +#define HDMI_PHY_CONF0_SELDIPIF_MASK 0x1 +#define HDMI_PHY_CONF0_SELDIPIF_OFFSET 0 +#define HDMI_PHY_TST0 0x3001 +#define HDMI_PHY_TST0_TSTCLR_MASK 0x20 +#define HDMI_PHY_TST0_TSTCLR_OFFSET 5 +#define HDMI_PHY_TST0_TSTEN_MASK 0x10 +#define HDMI_PHY_TST0_TSTEN_OFFSET 4 +#define HDMI_PHY_TST0_TSTCLK_MASK 0x1 +#define HDMI_PHY_TST0_TSTCLK_OFFSET 0 +#define HDMI_PHY_TST1 0x3002 +#define HDMI_PHY_TST2 0x3003 +#define HDMI_PHY_STAT0 0x3004 +#define HDMI_PHY_STAT0_RX_SENSE3 0x80 +#define HDMI_PHY_STAT0_RX_SENSE2 0x40 +#define HDMI_PHY_STAT0_RX_SENSE1 0x20 +#define HDMI_PHY_STAT0_RX_SENSE0 0x10 +#define HDMI_PHY_STAT0_RX_SENSE 0xf0 +#define HDMI_PHY_STAT0_HPD 0x02 +#define HDMI_PHY_TX_PHY_LOCK 0x01 +#define HDMI_PHY_INT0 0x3005 +#define HDMI_PHY_MASK0 0x3006 +#define HDMI_PHY_POL0 0x3007 +#define HDMI_PHY_POL0_HPD 0x02 + +/* HDMI Master PHY Registers */ +#define HDMI_PHY_I2CM_SLAVE_ADDR 0x3020 +#define HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2 0x69 +#define HDMI_PHY_I2CM_SLAVE_ADDR_HEAC_PHY 0x49 +#define HDMI_PHY_I2CM_ADDRESS_ADDR 0x3021 +#define HDMI_PHY_I2CM_DATAO_1_ADDR 0x3022 +#define HDMI_PHY_I2CM_DATAO_0_ADDR 0x3023 +#define HDMI_PHY_I2CM_DATAI_1_ADDR 0x3024 +#define HDMI_PHY_I2CM_DATAI_0_ADDR 0x3025 +#define HDMI_PHY_I2CM_OPERATION_ADDR 0x3026 +#define HDMI_PHY_I2CM_OPERATION_ADDR_WRITE 0x10 +#define HDMI_PHY_I2CM_OPERATION_ADDR_READ 0x1 +#define HDMI_PHY_I2CM_INT_ADDR 0x3027 +#define HDMI_PHY_I2CM_CTLINT_ADDR 0x3028 +#define HDMI_PHY_I2CM_DIV_ADDR 0x3029 +#define HDMI_PHY_I2CM_SOFTRSTZ_ADDR 0x302a +#define HDMI_PHY_I2CM_SS_SCL_HCNT_1_ADDR 0x302b +#define HDMI_PHY_I2CM_SS_SCL_HCNT_0_ADDR 0x302c +#define HDMI_PHY_I2CM_SS_SCL_LCNT_1_ADDR 0x302d +#define HDMI_PHY_I2CM_SS_SCL_LCNT_0_ADDR 0x302e +#define HDMI_PHY_I2CM_FS_SCL_HCNT_1_ADDR 0x302f +#define HDMI_PHY_I2CM_FS_SCL_HCNT_0_ADDR 0x3030 +#define HDMI_PHY_I2CM_FS_SCL_LCNT_1_ADDR 0x3031 +#define HDMI_PHY_I2CM_FS_SCL_LCNT_0_ADDR 0x3032 + +#define HDMI_MC_FLOWCTRL 0x4004 +#define HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_MASK 0x1 +#define HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH 0x1 +#define HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS 0x0 +#define HDMI_MC_PHYRSTZ 0x4005 +#define HDMI_MC_PHYRSTZ_ASSERT 0x0 +#define HDMI_MC_PHYRSTZ_DEASSERT 0x1 +#define HDMI_MC_HEACPHY_RST 0x4007 +#define HDMI_MC_HEACPHY_RST_ASSERT 0x1 +#define HDMI_MC_HEACPHY_RST_DEASSERT 0x0 + +/* HDMI PHY register with access through I2C */ +#define HDMI_PHY_I2C_CKCALCTRL 0x5 +#define CKCALCTRL_OVERRIDE (1 << 15) +#define HDMI_PHY_I2C_CPCE_CTRL 0x6 +#define CPCE_CTRL_45_25 ((3 << 7) | (3 << 5)) +#define CPCE_CTRL_92_50 ((2 << 7) | (2 << 5)) +#define CPCE_CTRL_185 ((1 << 7) | (1 << 5)) +#define CPCE_CTRL_370 ((0 << 7) | (0 << 5)) +#define HDMI_PHY_I2C_CKSYMTXCTRL 0x9 +#define CKSYMTXCTRL_OVERRIDE (1 << 15) +#define CKSYMTXCTRL_TX_SYMON (1 << 3) +#define CKSYMTXCTRL_TX_TRAON (1 << 2) +#define CKSYMTXCTRL_TX_TRBON (1 << 1) +#define CKSYMTXCTRL_TX_CK_SYMON (1 << 0) +#define HDMI_PHY_I2C_VLEVCTRL 0x0E +#define HDMI_PHY_I2C_CURRCTRL 0x10 +#define HDMI_PHY_I2C_PLLPHBYCTRL 0x13 +#define VLEVCTRL_TX_LVL(x) ((x) << 5) +#define VLEVCTRL_CK_LVL(x) (x) +#define HDMI_PHY_I2C_GMPCTRL 0x15 +#define GMPCTRL_45_25 0x00 +#define GMPCTRL_92_50 0x05 +#define GMPCTRL_185 0x0a +#define GMPCTRL_370 0x0f +#define HDMI_PHY_I2C_MSM_CTRL 0x17 +#define MSM_CTRL_FB_CLK (0x3 << 1) +#define HDMI_PHY_I2C_TXTERM 0x19 +#define TXTERM_133 0x5 + +void +dwhdmi_phy_wait_i2c_done(struct dwhdmi_softc *sc, int msec) +{ + uint8_t val; + + val = dwhdmi_read(sc, HDMI_IH_I2CMPHY_STAT0) & + (HDMI_IH_I2CMPHY_STAT0_DONE | HDMI_IH_I2CMPHY_STAT0_ERROR); + while (val == 0) { + delay(1000); + msec -= 10; + if (msec <= 0) + return; + val = dwhdmi_read(sc, HDMI_IH_I2CMPHY_STAT0) & + (HDMI_IH_I2CMPHY_STAT0_DONE | HDMI_IH_I2CMPHY_STAT0_ERROR); + } +} + +void +dwhdmi_phy_i2c_write(struct dwhdmi_softc *sc, unsigned short data, + unsigned char addr) +{ + + /* clear DONE and ERROR flags */ + dwhdmi_write(sc, HDMI_IH_I2CMPHY_STAT0, + HDMI_IH_I2CMPHY_STAT0_DONE | HDMI_IH_I2CMPHY_STAT0_ERROR); + dwhdmi_write(sc, HDMI_PHY_I2CM_ADDRESS_ADDR, addr); + dwhdmi_write(sc, HDMI_PHY_I2CM_DATAO_1_ADDR, ((data >> 8) & 0xff)); + dwhdmi_write(sc, HDMI_PHY_I2CM_DATAO_0_ADDR, ((data >> 0) & 0xff)); + dwhdmi_write(sc, HDMI_PHY_I2CM_OPERATION_ADDR, HDMI_PHY_I2CM_OPERATION_ADDR_WRITE); + dwhdmi_phy_wait_i2c_done(sc, 1000); +} + +void +dwhdmi_phy_enable_power(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_PDZ_MASK; + reg |= (enable << HDMI_PHY_CONF0_PDZ_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +void +dwhdmi_phy_enable_tmds(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_ENTMDS_MASK; + reg |= (enable << HDMI_PHY_CONF0_ENTMDS_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +void +dwhdmi_phy_gen2_pddq(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_GEN2_PDDQ_MASK; + reg |= (enable << HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +void +dwhdmi_phy_gen2_txpwron(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_GEN2_TXPWRON_MASK; + reg |= (enable << HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +void +dwhdmi_phy_sel_data_en_pol(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_SELDATAENPOL_MASK; + reg |= (enable << HDMI_PHY_CONF0_SELDATAENPOL_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +void +dwhdmi_phy_sel_interface_control(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_SELDIPIF_MASK; + reg |= (enable << HDMI_PHY_CONF0_SELDIPIF_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +void +dwhdmi_phy_enable_svsret(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_SVSRET_MASK; + reg |= (enable << HDMI_PHY_CONF0_SVSRET_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +void +dwhdmi_phy_test_clear(struct dwhdmi_softc *sc, unsigned char bit) +{ + uint8_t val; + + val = dwhdmi_read(sc, HDMI_PHY_TST0); + val &= ~HDMI_PHY_TST0_TSTCLR_MASK; + val |= (bit << HDMI_PHY_TST0_TSTCLR_OFFSET) & + HDMI_PHY_TST0_TSTCLR_MASK; + dwhdmi_write(sc, HDMI_PHY_TST0, val); +} + +int +dwhdmi_phy_configure(struct dwhdmi_softc *sc, struct drm_display_mode *mode) +{ + const struct dwhdmi_mpll_config *mpll_conf; + const struct dwhdmi_phy_config *phy_conf; + uint8_t val; + uint8_t msec; + + dwhdmi_write(sc, HDMI_MC_FLOWCTRL, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS); + + /* gen2 tx power off */ + dwhdmi_phy_gen2_txpwron(sc, 0); + + /* gen2 pddq */ + dwhdmi_phy_gen2_pddq(sc, 1); + + /* PHY reset */ + dwhdmi_write(sc, HDMI_MC_PHYRSTZ, HDMI_MC_PHYRSTZ_DEASSERT); + dwhdmi_write(sc, HDMI_MC_PHYRSTZ, HDMI_MC_PHYRSTZ_ASSERT); + + dwhdmi_write(sc, HDMI_MC_HEACPHY_RST, HDMI_MC_HEACPHY_RST_ASSERT); + + dwhdmi_phy_test_clear(sc, 1); + dwhdmi_write(sc, HDMI_PHY_I2CM_SLAVE_ADDR, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2); + dwhdmi_phy_test_clear(sc, 0); + + /* + * Following initialization are for 8bit per color case + */ + + /* + * PLL/MPLL config + */ + for (mpll_conf = &sc->sc_mpll_config[0]; mpll_conf->pixel_clock != 0; mpll_conf++) + if (mode->clock <= mpll_conf->pixel_clock) + break; + + dwhdmi_phy_i2c_write(sc, mpll_conf->cpce, HDMI_PHY_I2C_CPCE_CTRL); + dwhdmi_phy_i2c_write(sc, mpll_conf->gmp, HDMI_PHY_I2C_GMPCTRL); + dwhdmi_phy_i2c_write(sc, mpll_conf->curr, HDMI_PHY_I2C_CURRCTRL); + + for (phy_conf = &sc->sc_phy_config[0]; phy_conf->pixel_clock != 0; phy_conf++) + if (mode->clock <= phy_conf->pixel_clock) + break; + + dwhdmi_phy_i2c_write(sc, 0x0000, HDMI_PHY_I2C_PLLPHBYCTRL); + dwhdmi_phy_i2c_write(sc, MSM_CTRL_FB_CLK, HDMI_PHY_I2C_MSM_CTRL); + + dwhdmi_phy_i2c_write(sc, phy_conf->term, HDMI_PHY_I2C_TXTERM); + dwhdmi_phy_i2c_write(sc, phy_conf->sym, HDMI_PHY_I2C_CKSYMTXCTRL); + dwhdmi_phy_i2c_write(sc, phy_conf->vlev, HDMI_PHY_I2C_VLEVCTRL); + + /* REMOVE CLK TERM */ + dwhdmi_phy_i2c_write(sc, CKCALCTRL_OVERRIDE, HDMI_PHY_I2C_CKCALCTRL); + + dwhdmi_phy_enable_power(sc, 1); + + /* toggle TMDS enable */ + dwhdmi_phy_enable_tmds(sc, 0); + dwhdmi_phy_enable_tmds(sc, 1); + + /* gen2 tx power on */ + dwhdmi_phy_gen2_txpwron(sc, 1); + dwhdmi_phy_gen2_pddq(sc, 0); + + switch (sc->sc_phytype) { + case 0xb2: /* MHL PHY HEAC */ + case 0xc2: /* MHL PHY */ + case 0xf3: /* HDMI 2.0 TX PHY */ + dwhdmi_phy_enable_svsret(sc, 1); + break; + } + + /*Wait for PHY PLL lock */ + msec = 4; + val = dwhdmi_read(sc, HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK; + while (val == 0) { + delay(1000); + if (msec-- == 0) { + printf("%s: PHY PLL not locked\n", + sc->sc_dev.dv_xname); + return (-1); + } + val = dwhdmi_read(sc, HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK; + } + + return (0); +} + +void +dwhdmi_phy_init(struct dwhdmi_softc *sc, struct drm_display_mode *mode) +{ + int i; + + /* HDMI Phy spec says to do the phy initialization sequence twice */ + for (i = 0 ; i < 2 ; i++) { + dwhdmi_phy_sel_data_en_pol(sc, 1); + dwhdmi_phy_sel_interface_control(sc, 0); + dwhdmi_phy_enable_tmds(sc, 0); + dwhdmi_phy_enable_power(sc, 0); + + /* Enable CSC */ + dwhdmi_phy_configure(sc, mode); + } +} + +enum drm_connector_status +dwhdmi_phy_detect(struct dwhdmi_softc *sc, int force) +{ + uint8_t val; + + val = dwhdmi_read(sc, HDMI_PHY_STAT0); + + return ((val & HDMI_PHY_STAT0_HPD) != 0) ? + connector_status_connected : + connector_status_disconnected; +} + +void +dwhdmi_phy_enable(struct dwhdmi_softc *sc) +{ +} + +void +dwhdmi_phy_disable(struct dwhdmi_softc *sc) +{ +} + +void +dwhdmi_phy_mode_set(struct dwhdmi_softc *sc, + struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + dwhdmi_phy_init(sc, adjusted_mode); +} |