Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

/* $NetBSD: dw_hdmi_phy.c,v 1.3 2021/12/19 11:00:47 riastradh 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.3 2021/12/19 11:00:47 riastradh Exp $");

#include <sys/param.h>

#include <drm/drm_drv.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, const 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, const 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,
    const struct drm_display_mode *mode,
    const struct drm_display_mode *adjusted_mode)
{
	dwhdmi_phy_init(sc, adjusted_mode);
}