diff options
Diffstat (limited to 'sys/dev/ic')
| -rw-r--r-- | sys/dev/ic/dw_hdmi.c | 308 | ||||
| -rw-r--r-- | sys/dev/ic/dw_hdmi.h | 39 | ||||
| -rw-r--r-- | sys/dev/ic/dw_hdmi_phy.c | 401 |
3 files changed, 733 insertions, 15 deletions
diff --git a/sys/dev/ic/dw_hdmi.c b/sys/dev/ic/dw_hdmi.c index ac374702636..41198497c38 100644 --- a/sys/dev/ic/dw_hdmi.c +++ b/sys/dev/ic/dw_hdmi.c @@ -1,4 +1,4 @@ -/* $NetBSD: dw_hdmi.c,v 1.1 2019/01/30 01:19:49 jmcneill Exp $ */ +/* $NetBSD: dw_hdmi.c,v 1.1.6.1 2019/11/16 16:48:25 martin Exp $ */ /*- * Copyright (c) 2019 Jared D. McNeill <jmcneill@invisible.ca> @@ -27,7 +27,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: dw_hdmi.c,v 1.1 2019/01/30 01:19:49 jmcneill Exp $"); +__KERNEL_RCSID(0, "$NetBSD: dw_hdmi.c,v 1.1.6.1 2019/11/16 16:48:25 martin Exp $"); #include <sys/param.h> #include <sys/bus.h> @@ -45,11 +45,19 @@ __KERNEL_RCSID(0, "$NetBSD: dw_hdmi.c,v 1.1 2019/01/30 01:19:49 jmcneill Exp $") #include <dev/videomode/videomode.h> #include <dev/videomode/edidvar.h> +#include <dev/audio/audio_dai.h> + #include <drm/drmP.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> +#define HDMI_DESIGN_ID 0x0000 +#define HDMI_REVISION_ID 0x0001 +#define HDMI_CONFIG0_ID 0x0004 +#define HDMI_CONFIG0_ID_AUDI2S __BIT(4) +#define HDMI_CONFIG2_ID 0x0006 + #define HDMI_IH_I2CM_STAT0 0x0105 #define HDMI_IH_I2CM_STAT0_DONE __BIT(1) #define HDMI_IH_I2CM_STAT0_ERROR __BIT(0) @@ -127,6 +135,49 @@ __KERNEL_RCSID(0, "$NetBSD: dw_hdmi.c,v 1.1 2019/01/30 01:19:49 jmcneill Exp $") #define HDMI_FC_CH1PREAM_DEFAULT 0x16 #define HDMI_FC_CH2PREAM 0x1016 #define HDMI_FC_CH2PREAM_DEFAULT 0x21 +#define HDMI_FC_AUDCONF0 0x1025 +#define HDMI_FC_AUDCONF1 0x1026 +#define HDMI_FC_AUDCONF2 0x1027 +#define HDMI_FC_AUDCONF3 0x1028 + +#define HDMI_PHY_CONF0 0x3000 +#define HDMI_PHY_CONF0_PDZ __BIT(7) +#define HDMI_PHY_CONF0_ENTMDS __BIT(6) +#define HDMI_PHY_CONF0_SVSRET __BIT(5) +#define HDMI_PHY_CONF0_PDDQ __BIT(4) +#define HDMI_PHY_CONF0_TXPWRON __BIT(3) +#define HDMI_PHY_CONF0_ENHPDRXSENSE __BIT(2) +#define HDMI_PHY_CONF0_SELDATAENPOL __BIT(1) +#define HDMI_PHY_CONF0_SELDIPIF __BIT(0) +#define HDMI_PHY_STAT0 0x3004 +#define HDMI_PHY_STAT0_RX_SENSE_3 __BIT(7) +#define HDMI_PHY_STAT0_RX_SENSE_2 __BIT(6) +#define HDMI_PHY_STAT0_RX_SENSE_1 __BIT(5) +#define HDMI_PHY_STAT0_RX_SENSE_0 __BIT(4) +#define HDMI_PHY_STAT0_HPD __BIT(1) +#define HDMI_PHY_STAT0_TX_PHY_LOCK __BIT(0) + +#define HDMI_AUD_CONF0 0x3100 +#define HDMI_AUD_CONF0_SW_AUDIO_FIFO_RST __BIT(7) +#define HDMI_AUD_CONF0_I2S_SELECT __BIT(5) +#define HDMI_AUD_CONF0_I2S_IN_EN __BITS(3,0) +#define HDMI_AUD_CONF1 0x3101 +#define HDMI_AUD_CONF1_I2S_WIDTH __BITS(4,0) +#define HDMI_AUD_INT 0x3102 +#define HDMI_AUD_CONF2 0x3103 +#define HDMI_AUD_CONF2_INSERT_PCUV __BIT(2) +#define HDMI_AUD_CONF2_NLPCM __BIT(1) +#define HDMI_AUD_CONF2_HBR __BIT(0) +#define HDMI_AUD_INT1 0x3104 + +#define HDMI_AUD_N1 0x3200 +#define HDMI_AUD_N2 0x3201 +#define HDMI_AUD_N3 0x3202 +#define HDMI_AUD_CTS1 0x3203 +#define HDMI_AUD_CTS2 0x3204 +#define HDMI_AUD_CTS3 0x3205 +#define HDMI_AUD_INPUTCLKFS 0x3206 +#define HDMI_AUD_INPUTCLKFS_IFSFACTOR __BITS(2,0) #define HDMI_MC_CLKDIS 0x4001 #define HDMI_MC_CLKDIS_HDCPCLK_DISABLE __BIT(6) @@ -143,6 +194,8 @@ __KERNEL_RCSID(0, "$NetBSD: dw_hdmi.c,v 1.1 2019/01/30 01:19:49 jmcneill Exp $") #define HDMI_MC_SWRSTZREQ_PIXELSWRST_REQ __BIT(0) #define HDMI_MC_FLOWCTRL 0x4004 #define HDMI_MC_PHYRSTZ 0x4005 +#define HDMI_MC_PHYRSTZ_ASSERT __BIT(0) +#define HDMI_MC_PHYRSTZ_DEASSERT 0 #define HDMI_MC_LOCKONCLOCK 0x4006 #define HDMI_MC_HEACPHY_RST 0x4007 @@ -187,6 +240,16 @@ __KERNEL_RCSID(0, "$NetBSD: dw_hdmi.c,v 1.1 2019/01/30 01:19:49 jmcneill Exp $") #define HDMI_I2CM_SOFTRSTZ_I2C_SOFTRST __BIT(0) #define HDMI_I2CM_SEGPTR 0x7e0a +enum dwhdmi_dai_mixer_ctrl { + DWHDMI_DAI_OUTPUT_CLASS, + DWHDMI_DAI_INPUT_CLASS, + + DWHDMI_DAI_OUTPUT_MASTER_VOLUME, + DWHDMI_DAI_INPUT_DAC_VOLUME, + + DWHDMI_DAI_MIXER_CTRL_LAST +}; + static int dwhdmi_ddc_acquire_bus(void *priv, int flags) { @@ -410,9 +473,8 @@ dwhdmi_fc_init(struct dwhdmi_softc *sc, struct drm_display_mode *mode) static void dwhdmi_mc_init(struct dwhdmi_softc *sc) { - struct dwhdmi_connector *dwhdmi_connector = &sc->sc_connector; uint8_t val; - u_int n; + u_int n, iter; /* Bypass colour space converter */ dwhdmi_write(sc, HDMI_MC_FLOWCTRL, 0); @@ -422,16 +484,16 @@ dwhdmi_mc_init(struct dwhdmi_softc *sc) HDMI_MC_CLKDIS_CECCLK_DISABLE | HDMI_MC_CLKDIS_CSCCLK_DISABLE | HDMI_MC_CLKDIS_PREPCLK_DISABLE; - if (!dwhdmi_connector->monitor_audio) - val |= HDMI_MC_CLKDIS_AUDCLK_DISABLE; dwhdmi_write(sc, HDMI_MC_CLKDIS, val); /* Soft reset TMDS */ val = 0xff & ~HDMI_MC_SWRSTZREQ_TMDSSWRST_REQ; dwhdmi_write(sc, HDMI_MC_SWRSTZREQ, val); + iter = sc->sc_version == 0x130a ? 4 : 1; + val = dwhdmi_read(sc, HDMI_FC_INVIDCONF); - for (n = 0; n < 4; n++) + for (n = 0; n < iter; n++) dwhdmi_write(sc, HDMI_FC_INVIDCONF, val); } @@ -442,6 +504,59 @@ dwhdmi_mc_disable(struct dwhdmi_softc *sc) dwhdmi_write(sc, HDMI_MC_CLKDIS, 0xff); } +static void +dwhdmi_audio_init(struct dwhdmi_softc *sc) +{ + uint8_t val; + u_int n; + + /* The following values are for 48 kHz */ + switch (sc->sc_curmode.clock) { + case 25170: + n = 6864; + break; + case 74170: + n = 11648; + break; + case 148350: + n = 5824; + break; + default: + n = 6144; + break; + } + + /* Use automatic CTS generation */ + dwhdmi_write(sc, HDMI_AUD_CTS1, 0); + dwhdmi_write(sc, HDMI_AUD_CTS2, 0); + dwhdmi_write(sc, HDMI_AUD_CTS3, 0); + + /* Set N factor for audio clock regeneration */ + dwhdmi_write(sc, HDMI_AUD_N1, n & 0xff); + dwhdmi_write(sc, HDMI_AUD_N2, (n >> 8) & 0xff); + dwhdmi_write(sc, HDMI_AUD_N3, (n >> 16) & 0xff); + + val = dwhdmi_read(sc, HDMI_AUD_CONF0); + val |= HDMI_AUD_CONF0_I2S_SELECT; /* XXX i2s mode */ + val &= ~HDMI_AUD_CONF0_I2S_IN_EN; + val |= __SHIFTIN(1, HDMI_AUD_CONF0_I2S_IN_EN); /* XXX 2ch */ + dwhdmi_write(sc, HDMI_AUD_CONF0, val); + + val = __SHIFTIN(16, HDMI_AUD_CONF1_I2S_WIDTH); + dwhdmi_write(sc, HDMI_AUD_CONF1, val); + + dwhdmi_write(sc, HDMI_AUD_INPUTCLKFS, 4); /* XXX 64 FS */ + + dwhdmi_write(sc, HDMI_FC_AUDCONF0, 1 << 4); /* XXX 2ch */ + dwhdmi_write(sc, HDMI_FC_AUDCONF1, 0); + dwhdmi_write(sc, HDMI_FC_AUDCONF2, 0); + dwhdmi_write(sc, HDMI_FC_AUDCONF3, 0); + + val = dwhdmi_read(sc, HDMI_MC_CLKDIS); + val &= ~HDMI_MC_CLKDIS_PREPCLK_DISABLE; + dwhdmi_write(sc, HDMI_MC_CLKDIS, val); +} + static enum drm_connector_status dwhdmi_connector_detect(struct drm_connector *connector, bool force) { @@ -479,7 +594,7 @@ dwhdmi_connector_get_modes(struct drm_connector *connector) memset(edid, 0, sizeof(edid)); for (block = 0; block < 4; block++) { - error = ddc_read_edid_block(&sc->sc_ic, + error = ddc_read_edid_block(sc->sc_ic, &edid[block * EDID_LENGTH], EDID_LENGTH, block); if (error != 0) break; @@ -569,6 +684,9 @@ dwhdmi_bridge_enable(struct drm_bridge *bridge) dwhdmi_tx_init(sc); dwhdmi_mc_init(sc); + + if (sc->sc_connector.monitor_audio) + dwhdmi_audio_init(sc); } static void @@ -621,10 +739,136 @@ static const struct drm_bridge_funcs dwhdmi_bridge_funcs = { .mode_fixup = dwhdmi_bridge_mode_fixup, }; +static int +dwhdmi_dai_set_format(audio_dai_tag_t dai, u_int format) +{ + return 0; +} + +static int +dwhdmi_dai_add_device(audio_dai_tag_t dai, audio_dai_tag_t aux) +{ + /* Not supported */ + return 0; +} + +static void +dwhdmi_audio_swvol_codec(audio_filter_arg_t *arg) +{ + struct dwhdmi_softc * const sc = arg->context; + const aint_t *src; + aint_t *dst; + u_int sample_count; + u_int i; + + src = arg->src; + dst = arg->dst; + sample_count = arg->count * arg->srcfmt->channels; + for (i = 0; i < sample_count; i++) { + aint2_t v = (aint2_t)(*src++); + v = v * sc->sc_swvol / 255; + *dst++ = (aint_t)v; + } +} + +static int +dwhdmi_audio_set_format(void *priv, int setmode, + const audio_params_t *play, const audio_params_t *rec, + audio_filter_reg_t *pfil, audio_filter_reg_t *rfil) +{ + struct dwhdmi_softc * const sc = priv; + + pfil->codec = dwhdmi_audio_swvol_codec; + pfil->context = sc; + + return 0; +} + +static int +dwhdmi_audio_set_port(void *priv, mixer_ctrl_t *mc) +{ + struct dwhdmi_softc * const sc = priv; + + switch (mc->dev) { + case DWHDMI_DAI_OUTPUT_MASTER_VOLUME: + case DWHDMI_DAI_INPUT_DAC_VOLUME: + sc->sc_swvol = mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT]; + return 0; + default: + return ENXIO; + } +} + +static int +dwhdmi_audio_get_port(void *priv, mixer_ctrl_t *mc) +{ + struct dwhdmi_softc * const sc = priv; + + switch (mc->dev) { + case DWHDMI_DAI_OUTPUT_MASTER_VOLUME: + case DWHDMI_DAI_INPUT_DAC_VOLUME: + mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = sc->sc_swvol; + mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = sc->sc_swvol; + return 0; + default: + return ENXIO; + } +} + +static int +dwhdmi_audio_query_devinfo(void *priv, mixer_devinfo_t *di) +{ + switch (di->index) { + case DWHDMI_DAI_OUTPUT_CLASS: + di->mixer_class = di->index; + strcpy(di->label.name, AudioCoutputs); + di->type = AUDIO_MIXER_CLASS; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + case DWHDMI_DAI_INPUT_CLASS: + di->mixer_class = di->index; + strcpy(di->label.name, AudioCinputs); + di->type = AUDIO_MIXER_CLASS; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + case DWHDMI_DAI_OUTPUT_MASTER_VOLUME: + di->mixer_class = DWHDMI_DAI_OUTPUT_CLASS; + strcpy(di->label.name, AudioNmaster); + di->un.v.delta = 1; + di->un.v.num_channels = 2; + strcpy(di->un.v.units.name, AudioNvolume); + di->type = AUDIO_MIXER_VALUE; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + case DWHDMI_DAI_INPUT_DAC_VOLUME: + di->mixer_class = DWHDMI_DAI_INPUT_CLASS; + strcpy(di->label.name, AudioNdac); + di->un.v.delta = 1; + di->un.v.num_channels = 2; + strcpy(di->un.v.units.name, AudioNvolume); + di->type = AUDIO_MIXER_VALUE; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + default: + return ENXIO; + } +} + +static const struct audio_hw_if dwhdmi_dai_hw_if = { + .set_format = dwhdmi_audio_set_format, + .set_port = dwhdmi_audio_set_port, + .get_port = dwhdmi_audio_get_port, + .query_devinfo = dwhdmi_audio_query_devinfo, +}; + int dwhdmi_attach(struct dwhdmi_softc *sc) { - struct i2c_controller *ic = &sc->sc_ic; + uint8_t val; if (sc->sc_reg_width != 1 && sc->sc_reg_width != 4) { aprint_error_dev(sc->sc_dev, "unsupported register width %d\n", sc->sc_reg_width); @@ -633,10 +877,48 @@ dwhdmi_attach(struct dwhdmi_softc *sc) mutex_init(&sc->sc_ic_lock, MUTEX_DEFAULT, IPL_NONE); - ic->ic_cookie = sc; - ic->ic_acquire_bus = dwhdmi_ddc_acquire_bus; - ic->ic_release_bus = dwhdmi_ddc_release_bus; - ic->ic_exec = dwhdmi_ddc_exec; + sc->sc_version = dwhdmi_read(sc, HDMI_DESIGN_ID); + sc->sc_version <<= 8; + sc->sc_version |= dwhdmi_read(sc, HDMI_REVISION_ID); + + sc->sc_phytype = dwhdmi_read(sc, HDMI_CONFIG2_ID); + + aprint_normal_dev(sc->sc_dev, "version %x.%03x, phytype 0x%02x\n", + sc->sc_version >> 12, sc->sc_version & 0xfff, + sc->sc_phytype); + + sc->sc_swvol = 255; + + /* + * If a DDC i2c bus tag is provided by the caller, use it. Otherwise, + * use the I2C master built-in to DWC HDMI. + */ + if (sc->sc_ic == NULL) { + struct i2c_controller *ic = &sc->sc_ic_builtin; + ic->ic_cookie = sc; + ic->ic_acquire_bus = dwhdmi_ddc_acquire_bus; + ic->ic_release_bus = dwhdmi_ddc_release_bus; + ic->ic_exec = dwhdmi_ddc_exec; + sc->sc_ic = ic; + } + + /* + * Enable HPD on internal PHY + */ + if ((sc->sc_flags & DWHDMI_USE_INTERNAL_PHY) != 0) { + val = dwhdmi_read(sc, HDMI_PHY_CONF0); + val |= HDMI_PHY_CONF0_ENHPDRXSENSE; + dwhdmi_write(sc, HDMI_PHY_CONF0, val); + } + + /* + * Initialize audio DAI + */ + sc->sc_dai.dai_set_format = dwhdmi_dai_set_format; + sc->sc_dai.dai_add_device = dwhdmi_dai_add_device; + sc->sc_dai.dai_hw_if = &dwhdmi_dai_hw_if; + sc->sc_dai.dai_dev = sc->sc_dev; + sc->sc_dai.dai_priv = sc; return 0; } diff --git a/sys/dev/ic/dw_hdmi.h b/sys/dev/ic/dw_hdmi.h index 52782b73b4e..9c6fb0580bd 100644 --- a/sys/dev/ic/dw_hdmi.h +++ b/sys/dev/ic/dw_hdmi.h @@ -1,4 +1,4 @@ -/* $NetBSD: dw_hdmi.h,v 1.1 2019/01/30 01:19:49 jmcneill Exp $ */ +/* $NetBSD: dw_hdmi.h,v 1.1.6.1 2019/11/16 16:48:25 martin Exp $ */ /*- * Copyright (c) 2019 Jared D. McNeill <jmcneill@invisible.ca> @@ -32,6 +32,8 @@ #include <dev/i2c/i2cvar.h> #include <dev/i2c/ddcreg.h> +#include <dev/audio/audio_dai.h> + #include <drm/drmP.h> struct dwhdmi_softc; @@ -44,20 +46,46 @@ struct dwhdmi_connector { bool monitor_audio; }; +struct dwhdmi_phy_config { + u_int pixel_clock; + uint32_t sym; + uint32_t term; + uint32_t vlev; +}; + +struct dwhdmi_mpll_config { + u_int pixel_clock; + uint32_t cpce; + uint32_t gmp; + uint32_t curr; +}; + struct dwhdmi_softc { device_t sc_dev; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; u_int sc_reg_width; + u_int sc_flags; +#define DWHDMI_USE_INTERNAL_PHY __BIT(0) - struct i2c_controller sc_ic; + u_int sc_phytype; + u_int sc_version; + + i2c_tag_t sc_ic; kmutex_t sc_ic_lock; + struct i2c_controller sc_ic_builtin; + + struct audio_dai_device sc_dai; + uint8_t sc_swvol; struct dwhdmi_connector sc_connector; struct drm_bridge sc_bridge; struct drm_display_mode sc_curmode; + const struct dwhdmi_mpll_config *sc_mpll_config; + const struct dwhdmi_phy_config *sc_phy_config; + enum drm_connector_status (*sc_detect)(struct dwhdmi_softc *, bool); void (*sc_enable)(struct dwhdmi_softc *); void (*sc_disable)(struct dwhdmi_softc *); @@ -74,4 +102,11 @@ int dwhdmi_bind(struct dwhdmi_softc *, struct drm_encoder *); uint8_t dwhdmi_read(struct dwhdmi_softc *, bus_size_t); void dwhdmi_write(struct dwhdmi_softc *, bus_size_t, uint8_t); +enum drm_connector_status dwhdmi_phy_detect(struct dwhdmi_softc *, bool); +void dwhdmi_phy_enable(struct dwhdmi_softc *); +void dwhdmi_phy_disable(struct dwhdmi_softc *); +void dwhdmi_phy_mode_set(struct dwhdmi_softc *, + struct drm_display_mode *, + struct drm_display_mode *); + #endif /* !_DEV_IC_DWHDMI_H */ diff --git a/sys/dev/ic/dw_hdmi_phy.c b/sys/dev/ic/dw_hdmi_phy.c new file mode 100644 index 00000000000..bf29676fcf2 --- /dev/null +++ b/sys/dev/ic/dw_hdmi_phy.c @@ -0,0 +1,401 @@ +/* $NetBSD: dw_hdmi_phy.c,v 1.2.2.2 2019/11/16 16:48:25 martin Exp $ */ + +/*- + * Copyright (c) 2015 Oleksandr Tymoshenko <gonzo@freebsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: dw_hdmi_phy.c,v 1.2.2.2 2019/11/16 16:48:25 martin Exp $"); + +#include <sys/param.h> + +#include <drm/drmP.h> + +#include <dev/ic/dw_hdmi.h> + +#define HDMI_IH_PHY_STAT0 0x0104 +#define HDMI_IH_PHY_STAT0_HPD (1 << 0) +#define HDMI_IH_I2CMPHY_STAT0 0x0108 +#define HDMI_IH_I2CMPHY_STAT0_DONE (1 << 1) +#define HDMI_IH_I2CMPHY_STAT0_ERROR (1 << 0) + +#define HDMI_PHY_CONF0 0x3000 +#define HDMI_PHY_CONF0_PDZ_MASK 0x80 +#define HDMI_PHY_CONF0_PDZ_OFFSET 7 +#define HDMI_PHY_CONF0_ENTMDS_MASK 0x40 +#define HDMI_PHY_CONF0_ENTMDS_OFFSET 6 +#define HDMI_PHY_CONF0_SVSRET_MASK 0x20 +#define HDMI_PHY_CONF0_SVSRET_OFFSET 5 +#define HDMI_PHY_CONF0_GEN2_PDDQ_MASK 0x10 +#define HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET 4 +#define HDMI_PHY_CONF0_GEN2_TXPWRON_MASK 0x8 +#define HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET 3 +#define HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_MASK 0x4 +#define HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_OFFSET 2 +#define HDMI_PHY_CONF0_SELDATAENPOL_MASK 0x2 +#define HDMI_PHY_CONF0_SELDATAENPOL_OFFSET 1 +#define HDMI_PHY_CONF0_SELDIPIF_MASK 0x1 +#define HDMI_PHY_CONF0_SELDIPIF_OFFSET 0 +#define HDMI_PHY_TST0 0x3001 +#define HDMI_PHY_TST0_TSTCLR_MASK 0x20 +#define HDMI_PHY_TST0_TSTCLR_OFFSET 5 +#define HDMI_PHY_TST0_TSTEN_MASK 0x10 +#define HDMI_PHY_TST0_TSTEN_OFFSET 4 +#define HDMI_PHY_TST0_TSTCLK_MASK 0x1 +#define HDMI_PHY_TST0_TSTCLK_OFFSET 0 +#define HDMI_PHY_TST1 0x3002 +#define HDMI_PHY_TST2 0x3003 +#define HDMI_PHY_STAT0 0x3004 +#define HDMI_PHY_STAT0_RX_SENSE3 0x80 +#define HDMI_PHY_STAT0_RX_SENSE2 0x40 +#define HDMI_PHY_STAT0_RX_SENSE1 0x20 +#define HDMI_PHY_STAT0_RX_SENSE0 0x10 +#define HDMI_PHY_STAT0_RX_SENSE 0xf0 +#define HDMI_PHY_STAT0_HPD 0x02 +#define HDMI_PHY_TX_PHY_LOCK 0x01 +#define HDMI_PHY_INT0 0x3005 +#define HDMI_PHY_MASK0 0x3006 +#define HDMI_PHY_POL0 0x3007 +#define HDMI_PHY_POL0_HPD 0x02 + +/* HDMI Master PHY Registers */ +#define HDMI_PHY_I2CM_SLAVE_ADDR 0x3020 +#define HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2 0x69 +#define HDMI_PHY_I2CM_SLAVE_ADDR_HEAC_PHY 0x49 +#define HDMI_PHY_I2CM_ADDRESS_ADDR 0x3021 +#define HDMI_PHY_I2CM_DATAO_1_ADDR 0x3022 +#define HDMI_PHY_I2CM_DATAO_0_ADDR 0x3023 +#define HDMI_PHY_I2CM_DATAI_1_ADDR 0x3024 +#define HDMI_PHY_I2CM_DATAI_0_ADDR 0x3025 +#define HDMI_PHY_I2CM_OPERATION_ADDR 0x3026 +#define HDMI_PHY_I2CM_OPERATION_ADDR_WRITE 0x10 +#define HDMI_PHY_I2CM_OPERATION_ADDR_READ 0x1 +#define HDMI_PHY_I2CM_INT_ADDR 0x3027 +#define HDMI_PHY_I2CM_CTLINT_ADDR 0x3028 +#define HDMI_PHY_I2CM_DIV_ADDR 0x3029 +#define HDMI_PHY_I2CM_SOFTRSTZ_ADDR 0x302a +#define HDMI_PHY_I2CM_SS_SCL_HCNT_1_ADDR 0x302b +#define HDMI_PHY_I2CM_SS_SCL_HCNT_0_ADDR 0x302c +#define HDMI_PHY_I2CM_SS_SCL_LCNT_1_ADDR 0x302d +#define HDMI_PHY_I2CM_SS_SCL_LCNT_0_ADDR 0x302e +#define HDMI_PHY_I2CM_FS_SCL_HCNT_1_ADDR 0x302f +#define HDMI_PHY_I2CM_FS_SCL_HCNT_0_ADDR 0x3030 +#define HDMI_PHY_I2CM_FS_SCL_LCNT_1_ADDR 0x3031 +#define HDMI_PHY_I2CM_FS_SCL_LCNT_0_ADDR 0x3032 + +#define HDMI_MC_FLOWCTRL 0x4004 +#define HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_MASK 0x1 +#define HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH 0x1 +#define HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS 0x0 +#define HDMI_MC_PHYRSTZ 0x4005 +#define HDMI_MC_PHYRSTZ_ASSERT 0x0 +#define HDMI_MC_PHYRSTZ_DEASSERT 0x1 +#define HDMI_MC_HEACPHY_RST 0x4007 +#define HDMI_MC_HEACPHY_RST_ASSERT 0x1 +#define HDMI_MC_HEACPHY_RST_DEASSERT 0x0 + +/* HDMI PHY register with access through I2C */ +#define HDMI_PHY_I2C_CKCALCTRL 0x5 +#define CKCALCTRL_OVERRIDE (1 << 15) +#define HDMI_PHY_I2C_CPCE_CTRL 0x6 +#define CPCE_CTRL_45_25 ((3 << 7) | (3 << 5)) +#define CPCE_CTRL_92_50 ((2 << 7) | (2 << 5)) +#define CPCE_CTRL_185 ((1 << 7) | (1 << 5)) +#define CPCE_CTRL_370 ((0 << 7) | (0 << 5)) +#define HDMI_PHY_I2C_CKSYMTXCTRL 0x9 +#define CKSYMTXCTRL_OVERRIDE (1 << 15) +#define CKSYMTXCTRL_TX_SYMON (1 << 3) +#define CKSYMTXCTRL_TX_TRAON (1 << 2) +#define CKSYMTXCTRL_TX_TRBON (1 << 1) +#define CKSYMTXCTRL_TX_CK_SYMON (1 << 0) +#define HDMI_PHY_I2C_VLEVCTRL 0x0E +#define HDMI_PHY_I2C_CURRCTRL 0x10 +#define HDMI_PHY_I2C_PLLPHBYCTRL 0x13 +#define VLEVCTRL_TX_LVL(x) ((x) << 5) +#define VLEVCTRL_CK_LVL(x) (x) +#define HDMI_PHY_I2C_GMPCTRL 0x15 +#define GMPCTRL_45_25 0x00 +#define GMPCTRL_92_50 0x05 +#define GMPCTRL_185 0x0a +#define GMPCTRL_370 0x0f +#define HDMI_PHY_I2C_MSM_CTRL 0x17 +#define MSM_CTRL_FB_CLK (0x3 << 1) +#define HDMI_PHY_I2C_TXTERM 0x19 +#define TXTERM_133 0x5 + +static void +dwhdmi_phy_wait_i2c_done(struct dwhdmi_softc *sc, int msec) +{ + uint8_t val; + + val = dwhdmi_read(sc, HDMI_IH_I2CMPHY_STAT0) & + (HDMI_IH_I2CMPHY_STAT0_DONE | HDMI_IH_I2CMPHY_STAT0_ERROR); + while (val == 0) { + delay(1000); + msec -= 10; + if (msec <= 0) + return; + val = dwhdmi_read(sc, HDMI_IH_I2CMPHY_STAT0) & + (HDMI_IH_I2CMPHY_STAT0_DONE | HDMI_IH_I2CMPHY_STAT0_ERROR); + } +} + +static void +dwhdmi_phy_i2c_write(struct dwhdmi_softc *sc, unsigned short data, + unsigned char addr) +{ + + /* clear DONE and ERROR flags */ + dwhdmi_write(sc, HDMI_IH_I2CMPHY_STAT0, + HDMI_IH_I2CMPHY_STAT0_DONE | HDMI_IH_I2CMPHY_STAT0_ERROR); + dwhdmi_write(sc, HDMI_PHY_I2CM_ADDRESS_ADDR, addr); + dwhdmi_write(sc, HDMI_PHY_I2CM_DATAO_1_ADDR, ((data >> 8) & 0xff)); + dwhdmi_write(sc, HDMI_PHY_I2CM_DATAO_0_ADDR, ((data >> 0) & 0xff)); + dwhdmi_write(sc, HDMI_PHY_I2CM_OPERATION_ADDR, HDMI_PHY_I2CM_OPERATION_ADDR_WRITE); + dwhdmi_phy_wait_i2c_done(sc, 1000); +} + +static void +dwhdmi_phy_enable_power(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_PDZ_MASK; + reg |= (enable << HDMI_PHY_CONF0_PDZ_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +static void +dwhdmi_phy_enable_tmds(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_ENTMDS_MASK; + reg |= (enable << HDMI_PHY_CONF0_ENTMDS_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +static void +dwhdmi_phy_gen2_pddq(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_GEN2_PDDQ_MASK; + reg |= (enable << HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +static void +dwhdmi_phy_gen2_txpwron(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_GEN2_TXPWRON_MASK; + reg |= (enable << HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +static void +dwhdmi_phy_sel_data_en_pol(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_SELDATAENPOL_MASK; + reg |= (enable << HDMI_PHY_CONF0_SELDATAENPOL_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +static void +dwhdmi_phy_sel_interface_control(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_SELDIPIF_MASK; + reg |= (enable << HDMI_PHY_CONF0_SELDIPIF_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +static void +dwhdmi_phy_enable_svsret(struct dwhdmi_softc *sc, uint8_t enable) +{ + uint8_t reg; + + reg = dwhdmi_read(sc, HDMI_PHY_CONF0); + reg &= ~HDMI_PHY_CONF0_SVSRET_MASK; + reg |= (enable << HDMI_PHY_CONF0_SVSRET_OFFSET); + dwhdmi_write(sc, HDMI_PHY_CONF0, reg); +} + +static inline void +dwhdmi_phy_test_clear(struct dwhdmi_softc *sc, unsigned char bit) +{ + uint8_t val; + + val = dwhdmi_read(sc, HDMI_PHY_TST0); + val &= ~HDMI_PHY_TST0_TSTCLR_MASK; + val |= (bit << HDMI_PHY_TST0_TSTCLR_OFFSET) & + HDMI_PHY_TST0_TSTCLR_MASK; + dwhdmi_write(sc, HDMI_PHY_TST0, val); +} + +static int +dwhdmi_phy_configure(struct dwhdmi_softc *sc, struct drm_display_mode *mode) +{ + const struct dwhdmi_mpll_config *mpll_conf; + const struct dwhdmi_phy_config *phy_conf; + uint8_t val; + uint8_t msec; + + dwhdmi_write(sc, HDMI_MC_FLOWCTRL, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS); + + /* gen2 tx power off */ + dwhdmi_phy_gen2_txpwron(sc, 0); + + /* gen2 pddq */ + dwhdmi_phy_gen2_pddq(sc, 1); + + /* PHY reset */ + dwhdmi_write(sc, HDMI_MC_PHYRSTZ, HDMI_MC_PHYRSTZ_DEASSERT); + dwhdmi_write(sc, HDMI_MC_PHYRSTZ, HDMI_MC_PHYRSTZ_ASSERT); + + dwhdmi_write(sc, HDMI_MC_HEACPHY_RST, HDMI_MC_HEACPHY_RST_ASSERT); + + dwhdmi_phy_test_clear(sc, 1); + dwhdmi_write(sc, HDMI_PHY_I2CM_SLAVE_ADDR, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2); + dwhdmi_phy_test_clear(sc, 0); + + /* + * Following initialization are for 8bit per color case + */ + + /* + * PLL/MPLL config + */ + for (mpll_conf = &sc->sc_mpll_config[0]; mpll_conf->pixel_clock != 0; mpll_conf++) + if (mode->clock <= mpll_conf->pixel_clock) + break; + + dwhdmi_phy_i2c_write(sc, mpll_conf->cpce, HDMI_PHY_I2C_CPCE_CTRL); + dwhdmi_phy_i2c_write(sc, mpll_conf->gmp, HDMI_PHY_I2C_GMPCTRL); + dwhdmi_phy_i2c_write(sc, mpll_conf->curr, HDMI_PHY_I2C_CURRCTRL); + + for (phy_conf = &sc->sc_phy_config[0]; phy_conf->pixel_clock != 0; phy_conf++) + if (mode->clock <= phy_conf->pixel_clock) + break; + + dwhdmi_phy_i2c_write(sc, 0x0000, HDMI_PHY_I2C_PLLPHBYCTRL); + dwhdmi_phy_i2c_write(sc, MSM_CTRL_FB_CLK, HDMI_PHY_I2C_MSM_CTRL); + + dwhdmi_phy_i2c_write(sc, phy_conf->term, HDMI_PHY_I2C_TXTERM); + dwhdmi_phy_i2c_write(sc, phy_conf->sym, HDMI_PHY_I2C_CKSYMTXCTRL); + dwhdmi_phy_i2c_write(sc, phy_conf->vlev, HDMI_PHY_I2C_VLEVCTRL); + + /* REMOVE CLK TERM */ + dwhdmi_phy_i2c_write(sc, CKCALCTRL_OVERRIDE, HDMI_PHY_I2C_CKCALCTRL); + + dwhdmi_phy_enable_power(sc, 1); + + /* toggle TMDS enable */ + dwhdmi_phy_enable_tmds(sc, 0); + dwhdmi_phy_enable_tmds(sc, 1); + + /* gen2 tx power on */ + dwhdmi_phy_gen2_txpwron(sc, 1); + dwhdmi_phy_gen2_pddq(sc, 0); + + switch (sc->sc_phytype) { + case 0xb2: /* MHL PHY HEAC */ + case 0xc2: /* MHL PHY */ + case 0xf3: /* HDMI 2.0 TX PHY */ + dwhdmi_phy_enable_svsret(sc, 1); + break; + } + + /*Wait for PHY PLL lock */ + msec = 4; + val = dwhdmi_read(sc, HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK; + while (val == 0) { + delay(1000); + if (msec-- == 0) { + device_printf(sc->sc_dev, "PHY PLL not locked\n"); + return (-1); + } + val = dwhdmi_read(sc, HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK; + } + + return (0); +} + +static void +dwhdmi_phy_init(struct dwhdmi_softc *sc, struct drm_display_mode *mode) +{ + int i; + + /* HDMI Phy spec says to do the phy initialization sequence twice */ + for (i = 0 ; i < 2 ; i++) { + dwhdmi_phy_sel_data_en_pol(sc, 1); + dwhdmi_phy_sel_interface_control(sc, 0); + dwhdmi_phy_enable_tmds(sc, 0); + dwhdmi_phy_enable_power(sc, 0); + + /* Enable CSC */ + dwhdmi_phy_configure(sc, mode); + } +} + +enum drm_connector_status +dwhdmi_phy_detect(struct dwhdmi_softc *sc, bool force) +{ + uint8_t val; + + val = dwhdmi_read(sc, HDMI_PHY_STAT0); + + return ((val & HDMI_PHY_STAT0_HPD) != 0) ? + connector_status_connected : + connector_status_disconnected; +} + +void +dwhdmi_phy_enable(struct dwhdmi_softc *sc) +{ +} + +void +dwhdmi_phy_disable(struct dwhdmi_softc *sc) +{ +} + +void +dwhdmi_phy_mode_set(struct dwhdmi_softc *sc, + struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + dwhdmi_phy_init(sc, adjusted_mode); +} |
