diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /drivers/gpu/drm/bridge | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/bridge')
63 files changed, 40269 insertions, 0 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig new file mode 100644 index 000000000..4e82647a6 --- /dev/null +++ b/drivers/gpu/drm/bridge/Kconfig @@ -0,0 +1,251 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_BRIDGE + def_bool y + depends on DRM + help + Bridge registration and lookup framework. + +config DRM_PANEL_BRIDGE + def_bool y + depends on DRM_BRIDGE + depends on DRM_KMS_HELPER + select DRM_PANEL + help + DRM bridge wrapper of DRM panels + +menu "Display Interface Bridges" + depends on DRM && DRM_BRIDGE + +config DRM_CDNS_DSI + tristate "Cadence DPI/DSI bridge" + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL_BRIDGE + select GENERIC_PHY_MIPI_DPHY + depends on OF + help + Support Cadence DPI to DSI bridge. This is an internal + bridge and is meant to be directly embedded in a SoC. + +config DRM_CHRONTEL_CH7033 + tristate "Chrontel CH7033 Video Encoder" + depends on OF + select DRM_KMS_HELPER + help + Enable support for the Chrontel CH7033 VGA/DVI/HDMI Encoder, as + found in the Dell Wyse 3020 thin client. + + If in doubt, say "N". + +config DRM_DISPLAY_CONNECTOR + tristate "Display connector support" + depends on OF + help + Driver for display connectors with support for DDC and hot-plug + detection. Most display controller handle display connectors + internally and don't need this driver, but the DRM subsystem is + moving towards separating connector handling from display controllers + on ARM-based platforms. Saying Y here when this driver is not needed + will not cause any issue. + +config DRM_LONTIUM_LT9611 + tristate "Lontium LT9611 DSI/HDMI bridge" + select SND_SOC_HDMI_CODEC if SND_SOC + depends on OF + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select REGMAP_I2C + help + Driver for Lontium LT9611 DSI to HDMI bridge + chip driver that converts dual DSI and I2S to + HDMI signals + Please say Y if you have such hardware. + +config DRM_LVDS_CODEC + tristate "Transparent LVDS encoders and decoders support" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL_BRIDGE + help + Support for transparent LVDS encoders and decoders that don't + require any configuration. + +config DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW + tristate "MegaChips stdp4028-ge-b850v3-fw and stdp2690-ge-b850v3-fw" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + help + This is a driver for the display bridges of + GE B850v3 that convert dual channel LVDS + to DP++. This is used with the i.MX6 imx-ldb + driver. You are likely to say N here. + +config DRM_NWL_MIPI_DSI + tristate "Northwest Logic MIPI DSI Host controller" + depends on DRM + depends on COMMON_CLK + depends on OF && HAS_IOMEM + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL_BRIDGE + select GENERIC_PHY_MIPI_DPHY + select MFD_SYSCON + select MULTIPLEXER + select REGMAP_MMIO + help + This enables the Northwest Logic MIPI DSI Host controller as + for example found on NXP's i.MX8 Processors. + +config DRM_NXP_PTN3460 + tristate "NXP PTN3460 DP/LVDS bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + help + NXP PTN3460 eDP-LVDS bridge chip driver. + +config DRM_PARADE_PS8622 + tristate "Parade eDP/LVDS bridge" + depends on OF + select DRM_PANEL + select DRM_KMS_HELPER + select BACKLIGHT_CLASS_DEVICE + help + Parade eDP-LVDS bridge chip driver. + +config DRM_PARADE_PS8640 + tristate "Parade PS8640 MIPI DSI to eDP Converter" + depends on OF + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL + help + Choose this option if you have PS8640 for display + The PS8640 is a high-performance and low-power + MIPI DSI to eDP converter + +config DRM_SIL_SII8620 + tristate "Silicon Image SII8620 HDMI/MHL bridge" + depends on OF + select DRM_KMS_HELPER + select EXTCON + depends on RC_CORE || !RC_CORE + help + Silicon Image SII8620 HDMI/MHL bridge chip driver. + +config DRM_SII902X + tristate "Silicon Image sii902x RGB/HDMI bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select REGMAP_I2C + select I2C_MUX + select SND_SOC_HDMI_CODEC if SND_SOC + help + Silicon Image sii902x bridge chip driver. + +config DRM_SII9234 + tristate "Silicon Image SII9234 HDMI/MHL bridge" + depends on OF + help + Say Y here if you want support for the MHL interface. + It is an I2C driver, that detects connection of MHL bridge + and starts encapsulation of HDMI signal. + +config DRM_SIMPLE_BRIDGE + tristate "Simple DRM bridge support" + depends on OF + select DRM_KMS_HELPER + help + Support for non-programmable DRM bridges, such as ADI ADV7123, TI + THS8134 and THS8135 or passive resistor ladder DACs. + +config DRM_THINE_THC63LVD1024 + tristate "Thine THC63LVD1024 LVDS decoder bridge" + depends on OF + help + Thine THC63LVD1024 LVDS/parallel converter driver. + +config DRM_TOSHIBA_TC358762 + tristate "TC358762 DSI/DPI bridge" + depends on OF + select DRM_MIPI_DSI + select DRM_PANEL_BRIDGE + help + Toshiba TC358762 DSI/DPI bridge driver. + +config DRM_TOSHIBA_TC358764 + tristate "TC358764 DSI/LVDS bridge" + depends on OF + select DRM_MIPI_DSI + select DRM_KMS_HELPER + select DRM_PANEL + help + Toshiba TC358764 DSI/LVDS bridge driver. + +config DRM_TOSHIBA_TC358767 + tristate "Toshiba TC358767 eDP bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select REGMAP_I2C + select DRM_PANEL + help + Toshiba TC358767 eDP bridge chip driver. + +config DRM_TOSHIBA_TC358768 + tristate "Toshiba TC358768 MIPI DSI bridge" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + select DRM_PANEL + select DRM_MIPI_DSI + help + Toshiba TC358768AXBG/TC358778XBG DSI bridge chip driver. + +config DRM_TOSHIBA_TC358775 + tristate "Toshiba TC358775 DSI/LVDS bridge" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + select DRM_PANEL + select DRM_MIPI_DSI + help + Toshiba TC358775 DSI/LVDS bridge chip driver. + +config DRM_TI_TFP410 + tristate "TI TFP410 DVI/HDMI bridge" + depends on OF + select DRM_KMS_HELPER + help + Texas Instruments TFP410 DVI/HDMI Transmitter driver + +config DRM_TI_SN65DSI86 + tristate "TI SN65DSI86 DSI to eDP bridge" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + select DRM_PANEL + select DRM_MIPI_DSI + help + Texas Instruments SN65DSI86 DSI to eDP Bridge driver + +config DRM_TI_TPD12S015 + tristate "TI TPD12S015 HDMI level shifter and ESD protection" + depends on OF + select DRM_KMS_HELPER + help + Texas Instruments TPD12S015 HDMI level shifter and ESD protection + driver. + +source "drivers/gpu/drm/bridge/analogix/Kconfig" + +source "drivers/gpu/drm/bridge/adv7511/Kconfig" + +source "drivers/gpu/drm/bridge/cadence/Kconfig" + +source "drivers/gpu/drm/bridge/synopsys/Kconfig" + +endmenu diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile new file mode 100644 index 000000000..2b3aff104 --- /dev/null +++ b/drivers/gpu/drm/bridge/Makefile @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o +obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o +obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o +obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o +obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o +obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o +obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o +obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o +obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o +obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o +obj-$(CONFIG_DRM_SII902X) += sii902x.o +obj-$(CONFIG_DRM_SII9234) += sii9234.o +obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.o +obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o +obj-$(CONFIG_DRM_TOSHIBA_TC358762) += tc358762.o +obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o +obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o +obj-$(CONFIG_DRM_TOSHIBA_TC358768) += tc358768.o +obj-$(CONFIG_DRM_TOSHIBA_TC358775) += tc358775.o +obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/ +obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o +obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o +obj-$(CONFIG_DRM_TI_TPD12S015) += ti-tpd12s015.o +obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-dsi.o + +obj-y += analogix/ +obj-y += cadence/ +obj-y += synopsys/ diff --git a/drivers/gpu/drm/bridge/adv7511/Kconfig b/drivers/gpu/drm/bridge/adv7511/Kconfig new file mode 100644 index 000000000..f46a5e26b --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/Kconfig @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_I2C_ADV7511 + tristate "ADV7511 encoder" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + select DRM_MIPI_DSI + help + Support for the Analog Devices ADV7511(W)/13/33/35 HDMI encoders. + +config DRM_I2C_ADV7511_AUDIO + bool "ADV7511 HDMI Audio driver" + depends on DRM_I2C_ADV7511 && SND_SOC + select SND_SOC_HDMI_CODEC + help + Support the ADV7511 HDMI Audio interface. This is used in + conjunction with the AV7511 HDMI driver. + +config DRM_I2C_ADV7511_CEC + bool "ADV7511/33/35 HDMI CEC driver" + depends on DRM_I2C_ADV7511 + select CEC_CORE + default y + help + When selected the HDMI transmitter will support the CEC feature. diff --git a/drivers/gpu/drm/bridge/adv7511/Makefile b/drivers/gpu/drm/bridge/adv7511/Makefile new file mode 100644 index 000000000..d8ceb534b --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +adv7511-y := adv7511_drv.o adv7533.o +adv7511-$(CONFIG_DRM_I2C_ADV7511_AUDIO) += adv7511_audio.o +adv7511-$(CONFIG_DRM_I2C_ADV7511_CEC) += adv7511_cec.o +obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h new file mode 100644 index 000000000..e95abeb64 --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -0,0 +1,418 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Analog Devices ADV7511 HDMI transmitter driver + * + * Copyright 2012 Analog Devices Inc. + */ + +#ifndef __DRM_I2C_ADV7511_H__ +#define __DRM_I2C_ADV7511_H__ + +#include <linux/hdmi.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> + +#define ADV7511_REG_CHIP_REVISION 0x00 +#define ADV7511_REG_N0 0x01 +#define ADV7511_REG_N1 0x02 +#define ADV7511_REG_N2 0x03 +#define ADV7511_REG_SPDIF_FREQ 0x04 +#define ADV7511_REG_CTS_AUTOMATIC1 0x05 +#define ADV7511_REG_CTS_AUTOMATIC2 0x06 +#define ADV7511_REG_CTS_MANUAL0 0x07 +#define ADV7511_REG_CTS_MANUAL1 0x08 +#define ADV7511_REG_CTS_MANUAL2 0x09 +#define ADV7511_REG_AUDIO_SOURCE 0x0a +#define ADV7511_REG_AUDIO_CONFIG 0x0b +#define ADV7511_REG_I2S_CONFIG 0x0c +#define ADV7511_REG_I2S_WIDTH 0x0d +#define ADV7511_REG_AUDIO_SUB_SRC0 0x0e +#define ADV7511_REG_AUDIO_SUB_SRC1 0x0f +#define ADV7511_REG_AUDIO_SUB_SRC2 0x10 +#define ADV7511_REG_AUDIO_SUB_SRC3 0x11 +#define ADV7511_REG_AUDIO_CFG1 0x12 +#define ADV7511_REG_AUDIO_CFG2 0x13 +#define ADV7511_REG_AUDIO_CFG3 0x14 +#define ADV7511_REG_I2C_FREQ_ID_CFG 0x15 +#define ADV7511_REG_VIDEO_INPUT_CFG1 0x16 +#define ADV7511_REG_CSC_UPPER(x) (0x18 + (x) * 2) +#define ADV7511_REG_CSC_LOWER(x) (0x19 + (x) * 2) +#define ADV7511_REG_SYNC_DECODER(x) (0x30 + (x)) +#define ADV7511_REG_DE_GENERATOR (0x35 + (x)) +#define ADV7511_REG_PIXEL_REPETITION 0x3b +#define ADV7511_REG_VIC_MANUAL 0x3c +#define ADV7511_REG_VIC_SEND 0x3d +#define ADV7511_REG_VIC_DETECTED 0x3e +#define ADV7511_REG_AUX_VIC_DETECTED 0x3f +#define ADV7511_REG_PACKET_ENABLE0 0x40 +#define ADV7511_REG_POWER 0x41 +#define ADV7511_REG_STATUS 0x42 +#define ADV7511_REG_EDID_I2C_ADDR 0x43 +#define ADV7511_REG_PACKET_ENABLE1 0x44 +#define ADV7511_REG_PACKET_I2C_ADDR 0x45 +#define ADV7511_REG_DSD_ENABLE 0x46 +#define ADV7511_REG_VIDEO_INPUT_CFG2 0x48 +#define ADV7511_REG_INFOFRAME_UPDATE 0x4a +#define ADV7511_REG_GC(x) (0x4b + (x)) /* 0x4b - 0x51 */ +#define ADV7511_REG_AVI_INFOFRAME_VERSION 0x52 +#define ADV7511_REG_AVI_INFOFRAME_LENGTH 0x53 +#define ADV7511_REG_AVI_INFOFRAME_CHECKSUM 0x54 +#define ADV7511_REG_AVI_INFOFRAME(x) (0x55 + (x)) /* 0x55 - 0x6f */ +#define ADV7511_REG_AUDIO_INFOFRAME_VERSION 0x70 +#define ADV7511_REG_AUDIO_INFOFRAME_LENGTH 0x71 +#define ADV7511_REG_AUDIO_INFOFRAME_CHECKSUM 0x72 +#define ADV7511_REG_AUDIO_INFOFRAME(x) (0x73 + (x)) /* 0x73 - 0x7c */ +#define ADV7511_REG_INT_ENABLE(x) (0x94 + (x)) +#define ADV7511_REG_INT(x) (0x96 + (x)) +#define ADV7511_REG_INPUT_CLK_DIV 0x9d +#define ADV7511_REG_PLL_STATUS 0x9e +#define ADV7511_REG_HDMI_POWER 0xa1 +#define ADV7511_REG_HDCP_HDMI_CFG 0xaf +#define ADV7511_REG_AN(x) (0xb0 + (x)) /* 0xb0 - 0xb7 */ +#define ADV7511_REG_HDCP_STATUS 0xb8 +#define ADV7511_REG_BCAPS 0xbe +#define ADV7511_REG_BKSV(x) (0xc0 + (x)) /* 0xc0 - 0xc3 */ +#define ADV7511_REG_EDID_SEGMENT 0xc4 +#define ADV7511_REG_DDC_STATUS 0xc8 +#define ADV7511_REG_EDID_READ_CTRL 0xc9 +#define ADV7511_REG_BSTATUS(x) (0xca + (x)) /* 0xca - 0xcb */ +#define ADV7511_REG_TIMING_GEN_SEQ 0xd0 +#define ADV7511_REG_POWER2 0xd6 +#define ADV7511_REG_HSYNC_PLACEMENT_MSB 0xfa + +#define ADV7511_REG_SYNC_ADJUSTMENT(x) (0xd7 + (x)) /* 0xd7 - 0xdc */ +#define ADV7511_REG_TMDS_CLOCK_INV 0xde +#define ADV7511_REG_ARC_CTRL 0xdf +#define ADV7511_REG_CEC_I2C_ADDR 0xe1 +#define ADV7511_REG_CEC_CTRL 0xe2 +#define ADV7511_REG_CHIP_ID_HIGH 0xf5 +#define ADV7511_REG_CHIP_ID_LOW 0xf6 + +/* Hardware defined default addresses for I2C register maps */ +#define ADV7511_CEC_I2C_ADDR_DEFAULT 0x3c +#define ADV7511_EDID_I2C_ADDR_DEFAULT 0x3f +#define ADV7511_PACKET_I2C_ADDR_DEFAULT 0x38 + +#define ADV7511_CSC_ENABLE BIT(7) +#define ADV7511_CSC_UPDATE_MODE BIT(5) + +#define ADV7511_INT0_HPD BIT(7) +#define ADV7511_INT0_VSYNC BIT(5) +#define ADV7511_INT0_AUDIO_FIFO_FULL BIT(4) +#define ADV7511_INT0_EDID_READY BIT(2) +#define ADV7511_INT0_HDCP_AUTHENTICATED BIT(1) + +#define ADV7511_INT1_DDC_ERROR BIT(7) +#define ADV7511_INT1_BKSV BIT(6) +#define ADV7511_INT1_CEC_TX_READY BIT(5) +#define ADV7511_INT1_CEC_TX_ARBIT_LOST BIT(4) +#define ADV7511_INT1_CEC_TX_RETRY_TIMEOUT BIT(3) +#define ADV7511_INT1_CEC_RX_READY3 BIT(2) +#define ADV7511_INT1_CEC_RX_READY2 BIT(1) +#define ADV7511_INT1_CEC_RX_READY1 BIT(0) + +#define ADV7511_ARC_CTRL_POWER_DOWN BIT(0) + +#define ADV7511_CEC_CTRL_POWER_DOWN BIT(0) + +#define ADV7511_POWER_POWER_DOWN BIT(6) + +#define ADV7511_HDMI_CFG_MODE_MASK 0x2 +#define ADV7511_HDMI_CFG_MODE_DVI 0x0 +#define ADV7511_HDMI_CFG_MODE_HDMI 0x2 + +#define ADV7511_AUDIO_SELECT_I2C 0x0 +#define ADV7511_AUDIO_SELECT_SPDIF 0x1 +#define ADV7511_AUDIO_SELECT_DSD 0x2 +#define ADV7511_AUDIO_SELECT_HBR 0x3 +#define ADV7511_AUDIO_SELECT_DST 0x4 + +#define ADV7511_I2S_SAMPLE_LEN_16 0x2 +#define ADV7511_I2S_SAMPLE_LEN_20 0x3 +#define ADV7511_I2S_SAMPLE_LEN_18 0x4 +#define ADV7511_I2S_SAMPLE_LEN_22 0x5 +#define ADV7511_I2S_SAMPLE_LEN_19 0x8 +#define ADV7511_I2S_SAMPLE_LEN_23 0x9 +#define ADV7511_I2S_SAMPLE_LEN_24 0xb +#define ADV7511_I2S_SAMPLE_LEN_17 0xc +#define ADV7511_I2S_SAMPLE_LEN_21 0xd + +#define ADV7511_SAMPLE_FREQ_44100 0x0 +#define ADV7511_SAMPLE_FREQ_48000 0x2 +#define ADV7511_SAMPLE_FREQ_32000 0x3 +#define ADV7511_SAMPLE_FREQ_88200 0x8 +#define ADV7511_SAMPLE_FREQ_96000 0xa +#define ADV7511_SAMPLE_FREQ_176400 0xc +#define ADV7511_SAMPLE_FREQ_192000 0xe + +#define ADV7511_STATUS_POWER_DOWN_POLARITY BIT(7) +#define ADV7511_STATUS_HPD BIT(6) +#define ADV7511_STATUS_MONITOR_SENSE BIT(5) +#define ADV7511_STATUS_I2S_32BIT_MODE BIT(3) + +#define ADV7511_PACKET_ENABLE_N_CTS BIT(8+6) +#define ADV7511_PACKET_ENABLE_AUDIO_SAMPLE BIT(8+5) +#define ADV7511_PACKET_ENABLE_AVI_INFOFRAME BIT(8+4) +#define ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME BIT(8+3) +#define ADV7511_PACKET_ENABLE_GC BIT(7) +#define ADV7511_PACKET_ENABLE_SPD BIT(6) +#define ADV7511_PACKET_ENABLE_MPEG BIT(5) +#define ADV7511_PACKET_ENABLE_ACP BIT(4) +#define ADV7511_PACKET_ENABLE_ISRC BIT(3) +#define ADV7511_PACKET_ENABLE_GM BIT(2) +#define ADV7511_PACKET_ENABLE_SPARE2 BIT(1) +#define ADV7511_PACKET_ENABLE_SPARE1 BIT(0) + +#define ADV7535_REG_POWER2_HPD_OVERRIDE BIT(6) +#define ADV7511_REG_POWER2_HPD_SRC_MASK 0xc0 +#define ADV7511_REG_POWER2_HPD_SRC_BOTH 0x00 +#define ADV7511_REG_POWER2_HPD_SRC_HPD 0x40 +#define ADV7511_REG_POWER2_HPD_SRC_CEC 0x80 +#define ADV7511_REG_POWER2_HPD_SRC_NONE 0xc0 +#define ADV7511_REG_POWER2_TDMS_ENABLE BIT(4) +#define ADV7511_REG_POWER2_GATE_INPUT_CLK BIT(0) + +#define ADV7511_LOW_REFRESH_RATE_NONE 0x0 +#define ADV7511_LOW_REFRESH_RATE_24HZ 0x1 +#define ADV7511_LOW_REFRESH_RATE_25HZ 0x2 +#define ADV7511_LOW_REFRESH_RATE_30HZ 0x3 + +#define ADV7511_AUDIO_CFG3_LEN_MASK 0x0f +#define ADV7511_I2C_FREQ_ID_CFG_RATE_MASK 0xf0 + +#define ADV7511_AUDIO_SOURCE_I2S 0 +#define ADV7511_AUDIO_SOURCE_SPDIF 1 + +#define ADV7511_I2S_FORMAT_I2S 0 +#define ADV7511_I2S_FORMAT_RIGHT_J 1 +#define ADV7511_I2S_FORMAT_LEFT_J 2 + +#define ADV7511_PACKET(p, x) ((p) * 0x20 + (x)) +#define ADV7511_PACKET_SDP(x) ADV7511_PACKET(0, x) +#define ADV7511_PACKET_MPEG(x) ADV7511_PACKET(1, x) +#define ADV7511_PACKET_ACP(x) ADV7511_PACKET(2, x) +#define ADV7511_PACKET_ISRC1(x) ADV7511_PACKET(3, x) +#define ADV7511_PACKET_ISRC2(x) ADV7511_PACKET(4, x) +#define ADV7511_PACKET_GM(x) ADV7511_PACKET(5, x) +#define ADV7511_PACKET_SPARE(x) ADV7511_PACKET(6, x) + +#define ADV7511_REG_CEC_TX_FRAME_HDR 0x00 +#define ADV7511_REG_CEC_TX_FRAME_DATA0 0x01 +#define ADV7511_REG_CEC_TX_FRAME_LEN 0x10 +#define ADV7511_REG_CEC_TX_ENABLE 0x11 +#define ADV7511_REG_CEC_TX_RETRY 0x12 +#define ADV7511_REG_CEC_TX_LOW_DRV_CNT 0x14 +#define ADV7511_REG_CEC_RX_FRAME_HDR 0x15 +#define ADV7511_REG_CEC_RX_FRAME_DATA0 0x16 +#define ADV7511_REG_CEC_RX_FRAME_LEN 0x25 +#define ADV7511_REG_CEC_RX_ENABLE 0x26 +#define ADV7511_REG_CEC_RX_BUFFERS 0x4a +#define ADV7511_REG_CEC_LOG_ADDR_MASK 0x4b +#define ADV7511_REG_CEC_LOG_ADDR_0_1 0x4c +#define ADV7511_REG_CEC_LOG_ADDR_2 0x4d +#define ADV7511_REG_CEC_CLK_DIV 0x4e +#define ADV7511_REG_CEC_SOFT_RESET 0x50 + +#define ADV7533_REG_CEC_OFFSET 0x70 + +enum adv7511_input_clock { + ADV7511_INPUT_CLOCK_1X, + ADV7511_INPUT_CLOCK_2X, + ADV7511_INPUT_CLOCK_DDR, +}; + +enum adv7511_input_justification { + ADV7511_INPUT_JUSTIFICATION_EVENLY = 0, + ADV7511_INPUT_JUSTIFICATION_RIGHT = 1, + ADV7511_INPUT_JUSTIFICATION_LEFT = 2, +}; + +enum adv7511_input_sync_pulse { + ADV7511_INPUT_SYNC_PULSE_DE = 0, + ADV7511_INPUT_SYNC_PULSE_HSYNC = 1, + ADV7511_INPUT_SYNC_PULSE_VSYNC = 2, + ADV7511_INPUT_SYNC_PULSE_NONE = 3, +}; + +/** + * enum adv7511_sync_polarity - Polarity for the input sync signals + * @ADV7511_SYNC_POLARITY_PASSTHROUGH: Sync polarity matches that of + * the currently configured mode. + * @ADV7511_SYNC_POLARITY_LOW: Sync polarity is low + * @ADV7511_SYNC_POLARITY_HIGH: Sync polarity is high + * + * If the polarity is set to either LOW or HIGH the driver will configure the + * ADV7511 to internally invert the sync signal if required to match the sync + * polarity setting for the currently selected output mode. + * + * If the polarity is set to PASSTHROUGH, the ADV7511 will route the signal + * unchanged. This is used when the upstream graphics core already generates + * the sync signals with the correct polarity. + */ +enum adv7511_sync_polarity { + ADV7511_SYNC_POLARITY_PASSTHROUGH, + ADV7511_SYNC_POLARITY_LOW, + ADV7511_SYNC_POLARITY_HIGH, +}; + +/** + * struct adv7511_link_config - Describes adv7511 hardware configuration + * @input_color_depth: Number of bits per color component (8, 10 or 12) + * @input_colorspace: The input colorspace (RGB, YUV444, YUV422) + * @input_clock: The input video clock style (1x, 2x, DDR) + * @input_style: The input component arrangement variant + * @input_justification: Video input format bit justification + * @clock_delay: Clock delay for the input clock (in ps) + * @embedded_sync: Video input uses BT.656-style embedded sync + * @sync_pulse: Select the sync pulse + * @vsync_polarity: vsync input signal configuration + * @hsync_polarity: hsync input signal configuration + */ +struct adv7511_link_config { + unsigned int input_color_depth; + enum hdmi_colorspace input_colorspace; + enum adv7511_input_clock input_clock; + unsigned int input_style; + enum adv7511_input_justification input_justification; + + int clock_delay; + + bool embedded_sync; + enum adv7511_input_sync_pulse sync_pulse; + enum adv7511_sync_polarity vsync_polarity; + enum adv7511_sync_polarity hsync_polarity; +}; + +/** + * enum adv7511_csc_scaling - Scaling factor for the ADV7511 CSC + * @ADV7511_CSC_SCALING_1: CSC results are not scaled + * @ADV7511_CSC_SCALING_2: CSC results are scaled by a factor of two + * @ADV7511_CSC_SCALING_4: CSC results are scalled by a factor of four + */ +enum adv7511_csc_scaling { + ADV7511_CSC_SCALING_1 = 0, + ADV7511_CSC_SCALING_2 = 1, + ADV7511_CSC_SCALING_4 = 2, +}; + +/** + * struct adv7511_video_config - Describes adv7511 hardware configuration + * @csc_enable: Whether to enable color space conversion + * @csc_scaling_factor: Color space conversion scaling factor + * @csc_coefficents: Color space conversion coefficents + * @hdmi_mode: Whether to use HDMI or DVI output mode + * @avi_infoframe: HDMI infoframe + */ +struct adv7511_video_config { + bool csc_enable; + enum adv7511_csc_scaling csc_scaling_factor; + const uint16_t *csc_coefficents; + + bool hdmi_mode; + struct hdmi_avi_infoframe avi_infoframe; +}; + +enum adv7511_type { + ADV7511, + ADV7533, + ADV7535, +}; + +#define ADV7511_MAX_ADDRS 3 + +struct adv7511 { + struct i2c_client *i2c_main; + struct i2c_client *i2c_edid; + struct i2c_client *i2c_packet; + struct i2c_client *i2c_cec; + + struct regmap *regmap; + struct regmap *regmap_cec; + enum drm_connector_status status; + bool powered; + + struct drm_display_mode curr_mode; + + unsigned int f_tmds; + unsigned int f_audio; + unsigned int audio_source; + + unsigned int current_edid_segment; + uint8_t edid_buf[256]; + bool edid_read; + + wait_queue_head_t wq; + struct work_struct hpd_work; + + struct drm_bridge bridge; + struct drm_connector connector; + + bool embedded_sync; + enum adv7511_sync_polarity vsync_polarity; + enum adv7511_sync_polarity hsync_polarity; + bool rgb; + + struct gpio_desc *gpio_pd; + + struct regulator_bulk_data *supplies; + unsigned int num_supplies; + + /* ADV7533 DSI RX related params */ + struct device_node *host_node; + struct mipi_dsi_device *dsi; + u8 num_dsi_lanes; + bool use_timing_gen; + + enum adv7511_type type; + struct platform_device *audio_pdev; + + struct cec_adapter *cec_adap; + u8 cec_addr[ADV7511_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; + struct clk *cec_clk; + u32 cec_clk_freq; +}; + +#ifdef CONFIG_DRM_I2C_ADV7511_CEC +int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511); +void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1); +#else +static inline int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511) +{ + regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL, + ADV7511_CEC_CTRL_POWER_DOWN); + return 0; +} +#endif + +void adv7533_dsi_power_on(struct adv7511 *adv); +void adv7533_dsi_power_off(struct adv7511 *adv); +enum drm_mode_status adv7533_mode_valid(struct adv7511 *adv, + const struct drm_display_mode *mode); +int adv7533_patch_registers(struct adv7511 *adv); +int adv7533_patch_cec_registers(struct adv7511 *adv); +int adv7533_attach_dsi(struct adv7511 *adv); +void adv7533_detach_dsi(struct adv7511 *adv); +int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv); + +#ifdef CONFIG_DRM_I2C_ADV7511_AUDIO +int adv7511_audio_init(struct device *dev, struct adv7511 *adv7511); +void adv7511_audio_exit(struct adv7511 *adv7511); +#else /*CONFIG_DRM_I2C_ADV7511_AUDIO */ +static inline int adv7511_audio_init(struct device *dev, struct adv7511 *adv7511) +{ + return 0; +} +static inline void adv7511_audio_exit(struct adv7511 *adv7511) +{ +} +#endif /* CONFIG_DRM_I2C_ADV7511_AUDIO */ + +#endif /* __DRM_I2C_ADV7511_H__ */ diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_audio.c b/drivers/gpu/drm/bridge/adv7511/adv7511_audio.c new file mode 100644 index 000000000..f101dd281 --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_audio.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices ADV7511 HDMI transmitter driver + * + * Copyright 2012 Analog Devices Inc. + * Copyright (c) 2016, Linaro Limited + */ + +#include <sound/core.h> +#include <sound/hdmi-codec.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <linux/of_graph.h> + +#include "adv7511.h" + +static void adv7511_calc_cts_n(unsigned int f_tmds, unsigned int fs, + unsigned int *cts, unsigned int *n) +{ + switch (fs) { + case 32000: + case 48000: + case 96000: + case 192000: + *n = fs * 128 / 1000; + break; + case 44100: + case 88200: + case 176400: + *n = fs * 128 / 900; + break; + } + + *cts = ((f_tmds * *n) / (128 * fs)) * 1000; +} + +static int adv7511_update_cts_n(struct adv7511 *adv7511) +{ + unsigned int cts = 0; + unsigned int n = 0; + + adv7511_calc_cts_n(adv7511->f_tmds, adv7511->f_audio, &cts, &n); + + regmap_write(adv7511->regmap, ADV7511_REG_N0, (n >> 16) & 0xf); + regmap_write(adv7511->regmap, ADV7511_REG_N1, (n >> 8) & 0xff); + regmap_write(adv7511->regmap, ADV7511_REG_N2, n & 0xff); + + regmap_write(adv7511->regmap, ADV7511_REG_CTS_MANUAL0, + (cts >> 16) & 0xf); + regmap_write(adv7511->regmap, ADV7511_REG_CTS_MANUAL1, + (cts >> 8) & 0xff); + regmap_write(adv7511->regmap, ADV7511_REG_CTS_MANUAL2, + cts & 0xff); + + return 0; +} + +int adv7511_hdmi_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct adv7511 *adv7511 = dev_get_drvdata(dev); + unsigned int audio_source, i2s_format = 0; + unsigned int invert_clock; + unsigned int rate; + unsigned int len; + + switch (hparms->sample_rate) { + case 32000: + rate = ADV7511_SAMPLE_FREQ_32000; + break; + case 44100: + rate = ADV7511_SAMPLE_FREQ_44100; + break; + case 48000: + rate = ADV7511_SAMPLE_FREQ_48000; + break; + case 88200: + rate = ADV7511_SAMPLE_FREQ_88200; + break; + case 96000: + rate = ADV7511_SAMPLE_FREQ_96000; + break; + case 176400: + rate = ADV7511_SAMPLE_FREQ_176400; + break; + case 192000: + rate = ADV7511_SAMPLE_FREQ_192000; + break; + default: + return -EINVAL; + } + + switch (hparms->sample_width) { + case 16: + len = ADV7511_I2S_SAMPLE_LEN_16; + break; + case 18: + len = ADV7511_I2S_SAMPLE_LEN_18; + break; + case 20: + len = ADV7511_I2S_SAMPLE_LEN_20; + break; + case 24: + len = ADV7511_I2S_SAMPLE_LEN_24; + break; + default: + return -EINVAL; + } + + switch (fmt->fmt) { + case HDMI_I2S: + audio_source = ADV7511_AUDIO_SOURCE_I2S; + i2s_format = ADV7511_I2S_FORMAT_I2S; + break; + case HDMI_RIGHT_J: + audio_source = ADV7511_AUDIO_SOURCE_I2S; + i2s_format = ADV7511_I2S_FORMAT_RIGHT_J; + break; + case HDMI_LEFT_J: + audio_source = ADV7511_AUDIO_SOURCE_I2S; + i2s_format = ADV7511_I2S_FORMAT_LEFT_J; + break; + case HDMI_SPDIF: + audio_source = ADV7511_AUDIO_SOURCE_SPDIF; + break; + default: + return -EINVAL; + } + + invert_clock = fmt->bit_clk_inv; + + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_SOURCE, 0x70, + audio_source << 4); + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG, BIT(6), + invert_clock << 6); + regmap_update_bits(adv7511->regmap, ADV7511_REG_I2S_CONFIG, 0x03, + i2s_format); + + adv7511->audio_source = audio_source; + + adv7511->f_audio = hparms->sample_rate; + + adv7511_update_cts_n(adv7511); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CFG3, + ADV7511_AUDIO_CFG3_LEN_MASK, len); + regmap_update_bits(adv7511->regmap, ADV7511_REG_I2C_FREQ_ID_CFG, + ADV7511_I2C_FREQ_ID_CFG_RATE_MASK, rate << 4); + regmap_write(adv7511->regmap, 0x73, 0x1); + + return 0; +} + +static int audio_startup(struct device *dev, void *data) +{ + struct adv7511 *adv7511 = dev_get_drvdata(dev); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG, + BIT(7), 0); + + /* hide Audio infoframe updates */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, + BIT(5), BIT(5)); + /* enable N/CTS, enable Audio sample packets */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1, + BIT(5), BIT(5)); + /* enable N/CTS */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1, + BIT(6), BIT(6)); + /* not copyrighted */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CFG1, + BIT(5), BIT(5)); + /* enable audio infoframes */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1, + BIT(3), BIT(3)); + /* AV mute disable */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_GC(0), + BIT(7) | BIT(6), BIT(7)); + /* use Audio infoframe updated info */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_GC(1), + BIT(5), 0); + /* enable SPDIF receiver */ + if (adv7511->audio_source == ADV7511_AUDIO_SOURCE_SPDIF) + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG, + BIT(7), BIT(7)); + + return 0; +} + +static void audio_shutdown(struct device *dev, void *data) +{ + struct adv7511 *adv7511 = dev_get_drvdata(dev); + + if (adv7511->audio_source == ADV7511_AUDIO_SOURCE_SPDIF) + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG, + BIT(7), 0); +} + +static int adv7511_hdmi_i2s_get_dai_id(struct snd_soc_component *component, + struct device_node *endpoint) +{ + struct of_endpoint of_ep; + int ret; + + ret = of_graph_parse_endpoint(endpoint, &of_ep); + if (ret < 0) + return ret; + + /* + * HDMI sound should be located as reg = <2> + * Then, it is sound port 0 + */ + if (of_ep.port == 2) + return 0; + + return -EINVAL; +} + +static const struct hdmi_codec_ops adv7511_codec_ops = { + .hw_params = adv7511_hdmi_hw_params, + .audio_shutdown = audio_shutdown, + .audio_startup = audio_startup, + .get_dai_id = adv7511_hdmi_i2s_get_dai_id, +}; + +static const struct hdmi_codec_pdata codec_data = { + .ops = &adv7511_codec_ops, + .max_i2s_channels = 2, + .i2s = 1, + .spdif = 1, +}; + +int adv7511_audio_init(struct device *dev, struct adv7511 *adv7511) +{ + adv7511->audio_pdev = platform_device_register_data(dev, + HDMI_CODEC_DRV_NAME, + PLATFORM_DEVID_AUTO, + &codec_data, + sizeof(codec_data)); + return PTR_ERR_OR_ZERO(adv7511->audio_pdev); +} + +void adv7511_audio_exit(struct adv7511 *adv7511) +{ + if (adv7511->audio_pdev) { + platform_device_unregister(adv7511->audio_pdev); + adv7511->audio_pdev = NULL; + } +} diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c new file mode 100644 index 000000000..ddd1305b8 --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c @@ -0,0 +1,349 @@ +/* + * adv7511_cec.c - Analog Devices ADV7511/33 cec driver + * + * Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/slab.h> +#include <linux/clk.h> + +#include <media/cec.h> + +#include "adv7511.h" + +#define ADV7511_INT1_CEC_MASK \ + (ADV7511_INT1_CEC_TX_READY | ADV7511_INT1_CEC_TX_ARBIT_LOST | \ + ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1) + +static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status) +{ + unsigned int offset = adv7511->type == ADV7533 ? + ADV7533_REG_CEC_OFFSET : 0; + unsigned int val; + + if (regmap_read(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_ENABLE + offset, &val)) + return; + + if ((val & 0x01) == 0) + return; + + if (tx_raw_status & ADV7511_INT1_CEC_TX_ARBIT_LOST) { + cec_transmit_attempt_done(adv7511->cec_adap, + CEC_TX_STATUS_ARB_LOST); + return; + } + if (tx_raw_status & ADV7511_INT1_CEC_TX_RETRY_TIMEOUT) { + u8 status; + u8 err_cnt = 0; + u8 nack_cnt = 0; + u8 low_drive_cnt = 0; + unsigned int cnt; + + /* + * We set this status bit since this hardware performs + * retransmissions. + */ + status = CEC_TX_STATUS_MAX_RETRIES; + if (regmap_read(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_LOW_DRV_CNT + offset, &cnt)) { + err_cnt = 1; + status |= CEC_TX_STATUS_ERROR; + } else { + nack_cnt = cnt & 0xf; + if (nack_cnt) + status |= CEC_TX_STATUS_NACK; + low_drive_cnt = cnt >> 4; + if (low_drive_cnt) + status |= CEC_TX_STATUS_LOW_DRIVE; + } + cec_transmit_done(adv7511->cec_adap, status, + 0, nack_cnt, low_drive_cnt, err_cnt); + return; + } + if (tx_raw_status & ADV7511_INT1_CEC_TX_READY) { + cec_transmit_attempt_done(adv7511->cec_adap, CEC_TX_STATUS_OK); + return; + } +} + +void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1) +{ + unsigned int offset = adv7511->type == ADV7533 ? + ADV7533_REG_CEC_OFFSET : 0; + const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY | + ADV7511_INT1_CEC_TX_ARBIT_LOST | + ADV7511_INT1_CEC_TX_RETRY_TIMEOUT; + struct cec_msg msg = {}; + unsigned int len; + unsigned int val; + u8 i; + + if (irq1 & irq_tx_mask) + adv_cec_tx_raw_status(adv7511, irq1); + + if (!(irq1 & ADV7511_INT1_CEC_RX_READY1)) + return; + + if (regmap_read(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_FRAME_LEN + offset, &len)) + return; + + msg.len = len & 0x1f; + + if (msg.len > 16) + msg.len = 16; + + if (!msg.len) + return; + + for (i = 0; i < msg.len; i++) { + regmap_read(adv7511->regmap_cec, + i + ADV7511_REG_CEC_RX_FRAME_HDR + offset, &val); + msg.msg[i] = val; + } + + /* toggle to re-enable rx 1 */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, 1); + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, 0); + cec_received_msg(adv7511->cec_adap, &msg); +} + +static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct adv7511 *adv7511 = cec_get_drvdata(adap); + unsigned int offset = adv7511->type == ADV7533 ? + ADV7533_REG_CEC_OFFSET : 0; + + if (adv7511->i2c_cec == NULL) + return -EIO; + + if (!adv7511->cec_enabled_adap && enable) { + /* power up cec section */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_CLK_DIV + offset, + 0x03, 0x01); + /* legacy mode and clear all rx buffers */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, 0x07); + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, 0); + /* initially disable tx */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_ENABLE + offset, 1, 0); + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready 1 */ + regmap_update_bits(adv7511->regmap, + ADV7511_REG_INT_ENABLE(1), 0x3f, + ADV7511_INT1_CEC_MASK); + } else if (adv7511->cec_enabled_adap && !enable) { + regmap_update_bits(adv7511->regmap, + ADV7511_REG_INT_ENABLE(1), 0x3f, 0); + /* disable address mask 1-3 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x70, 0x00); + /* power down cec section */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_CLK_DIV + offset, + 0x03, 0x00); + adv7511->cec_valid_addrs = 0; + } + adv7511->cec_enabled_adap = enable; + return 0; +} + +static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct adv7511 *adv7511 = cec_get_drvdata(adap); + unsigned int offset = adv7511->type == ADV7533 ? + ADV7533_REG_CEC_OFFSET : 0; + unsigned int i, free_idx = ADV7511_MAX_ADDRS; + + if (!adv7511->cec_enabled_adap) + return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x70, 0); + adv7511->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV7511_MAX_ADDRS; i++) { + bool is_valid = adv7511->cec_valid_addrs & (1 << i); + + if (free_idx == ADV7511_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && adv7511->cec_addr[i] == addr) + return 0; + } + if (i == ADV7511_MAX_ADDRS) { + i = free_idx; + if (i == ADV7511_MAX_ADDRS) + return -ENXIO; + } + adv7511->cec_addr[i] = addr; + adv7511->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x10, 0x10); + /* set address for mask 0 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_0_1 + offset, + 0x0f, addr); + break; + case 1: + /* enable address mask 1 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x20, 0x20); + /* set address for mask 1 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_0_1 + offset, + 0xf0, addr << 4); + break; + case 2: + /* enable address mask 2 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x40, 0x40); + /* set address for mask 1 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_2 + offset, + 0x0f, addr); + break; + } + return 0; +} + +static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct adv7511 *adv7511 = cec_get_drvdata(adap); + unsigned int offset = adv7511->type == ADV7533 ? + ADV7533_REG_CEC_OFFSET : 0; + u8 len = msg->len; + unsigned int i; + + /* + * The number of retries is the number of attempts - 1, but retry + * at least once. It's not clear if a value of 0 is allowed, so + * let's do at least one retry. + */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_RETRY + offset, + 0x70, max(1, attempts - 1) << 4); + + /* blocking, clear cec tx irq status */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_INT(1), 0x38, 0x38); + + /* write data */ + for (i = 0; i < len; i++) + regmap_write(adv7511->regmap_cec, + i + ADV7511_REG_CEC_TX_FRAME_HDR + offset, + msg->msg[i]); + + /* set length (data + header) */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_FRAME_LEN + offset, len); + /* start transmit, enable tx */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_ENABLE + offset, 0x01); + return 0; +} + +static const struct cec_adap_ops adv7511_cec_adap_ops = { + .adap_enable = adv7511_cec_adap_enable, + .adap_log_addr = adv7511_cec_adap_log_addr, + .adap_transmit = adv7511_cec_adap_transmit, +}; + +static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511) +{ + adv7511->cec_clk = devm_clk_get(dev, "cec"); + if (IS_ERR(adv7511->cec_clk)) { + int ret = PTR_ERR(adv7511->cec_clk); + + adv7511->cec_clk = NULL; + return ret; + } + clk_prepare_enable(adv7511->cec_clk); + adv7511->cec_clk_freq = clk_get_rate(adv7511->cec_clk); + return 0; +} + +int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511) +{ + unsigned int offset = adv7511->type == ADV7533 ? + ADV7533_REG_CEC_OFFSET : 0; + int ret = adv7511_cec_parse_dt(dev, adv7511); + + if (ret) + goto err_cec_parse_dt; + + adv7511->cec_adap = cec_allocate_adapter(&adv7511_cec_adap_ops, + adv7511, dev_name(dev), CEC_CAP_DEFAULTS, ADV7511_MAX_ADDRS); + if (IS_ERR(adv7511->cec_adap)) { + ret = PTR_ERR(adv7511->cec_adap); + goto err_cec_alloc; + } + + regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL, 0); + /* cec soft reset */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_SOFT_RESET + offset, 0x01); + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_SOFT_RESET + offset, 0x00); + + /* legacy mode */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, 0x00); + + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_CLK_DIV + offset, + ((adv7511->cec_clk_freq / 750000) - 1) << 2); + + ret = cec_register_adapter(adv7511->cec_adap, dev); + if (ret) + goto err_cec_register; + return 0; + +err_cec_register: + cec_delete_adapter(adv7511->cec_adap); + adv7511->cec_adap = NULL; +err_cec_alloc: + dev_info(dev, "Initializing CEC failed with error %d, disabling CEC\n", + ret); +err_cec_parse_dt: + regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL, + ADV7511_CEC_CTRL_POWER_DOWN); + return ret == -EPROBE_DEFER ? ret : 0; +} diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c new file mode 100644 index 000000000..e50c741cb --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -0,0 +1,1428 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices ADV7511 HDMI transmitter driver + * + * Copyright 2012 Analog Devices Inc. + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/slab.h> + +#include <media/cec.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "adv7511.h" + +/* ADI recommended values for proper operation. */ +static const struct reg_sequence adv7511_fixed_registers[] = { + { 0x98, 0x03 }, + { 0x9a, 0xe0 }, + { 0x9c, 0x30 }, + { 0x9d, 0x61 }, + { 0xa2, 0xa4 }, + { 0xa3, 0xa4 }, + { 0xe0, 0xd0 }, + { 0xf9, 0x00 }, + { 0x55, 0x02 }, +}; + +/* ----------------------------------------------------------------------------- + * Register access + */ + +static const uint8_t adv7511_register_defaults[] = { + 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00 */ + 0x00, 0x00, 0x01, 0x0e, 0xbc, 0x18, 0x01, 0x13, + 0x25, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10 */ + 0x46, 0x62, 0x04, 0xa8, 0x00, 0x00, 0x1c, 0x84, + 0x1c, 0xbf, 0x04, 0xa8, 0x1e, 0x70, 0x02, 0x1e, /* 20 */ + 0x00, 0x00, 0x04, 0xa8, 0x08, 0x12, 0x1b, 0xac, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 30 */ + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xb0, + 0x00, 0x50, 0x90, 0x7e, 0x79, 0x70, 0x00, 0x00, /* 40 */ + 0x00, 0xa8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x0d, 0x00, 0x00, 0x00, 0x00, /* 50 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 60 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 70 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 80 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, /* 90 */ + 0x0b, 0x02, 0x00, 0x18, 0x5a, 0x60, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x80, 0x08, 0x04, 0x00, 0x00, /* a0 */ + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* b0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* c0 */ + 0x00, 0x03, 0x00, 0x00, 0x02, 0x00, 0x01, 0x04, + 0x30, 0xff, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, /* d0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, + 0x80, 0x75, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, /* e0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x11, 0x00, /* f0 */ + 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static bool adv7511_register_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADV7511_REG_CHIP_REVISION: + case ADV7511_REG_SPDIF_FREQ: + case ADV7511_REG_CTS_AUTOMATIC1: + case ADV7511_REG_CTS_AUTOMATIC2: + case ADV7511_REG_VIC_DETECTED: + case ADV7511_REG_VIC_SEND: + case ADV7511_REG_AUX_VIC_DETECTED: + case ADV7511_REG_STATUS: + case ADV7511_REG_GC(1): + case ADV7511_REG_INT(0): + case ADV7511_REG_INT(1): + case ADV7511_REG_PLL_STATUS: + case ADV7511_REG_AN(0): + case ADV7511_REG_AN(1): + case ADV7511_REG_AN(2): + case ADV7511_REG_AN(3): + case ADV7511_REG_AN(4): + case ADV7511_REG_AN(5): + case ADV7511_REG_AN(6): + case ADV7511_REG_AN(7): + case ADV7511_REG_HDCP_STATUS: + case ADV7511_REG_BCAPS: + case ADV7511_REG_BKSV(0): + case ADV7511_REG_BKSV(1): + case ADV7511_REG_BKSV(2): + case ADV7511_REG_BKSV(3): + case ADV7511_REG_BKSV(4): + case ADV7511_REG_DDC_STATUS: + case ADV7511_REG_EDID_READ_CTRL: + case ADV7511_REG_BSTATUS(0): + case ADV7511_REG_BSTATUS(1): + case ADV7511_REG_CHIP_ID_HIGH: + case ADV7511_REG_CHIP_ID_LOW: + return true; + } + + return false; +} + +static const struct regmap_config adv7511_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_RBTREE, + .reg_defaults_raw = adv7511_register_defaults, + .num_reg_defaults_raw = ARRAY_SIZE(adv7511_register_defaults), + + .volatile_reg = adv7511_register_volatile, +}; + +/* ----------------------------------------------------------------------------- + * Hardware configuration + */ + +static void adv7511_set_colormap(struct adv7511 *adv7511, bool enable, + const uint16_t *coeff, + unsigned int scaling_factor) +{ + unsigned int i; + + regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(1), + ADV7511_CSC_UPDATE_MODE, ADV7511_CSC_UPDATE_MODE); + + if (enable) { + for (i = 0; i < 12; ++i) { + regmap_update_bits(adv7511->regmap, + ADV7511_REG_CSC_UPPER(i), + 0x1f, coeff[i] >> 8); + regmap_write(adv7511->regmap, + ADV7511_REG_CSC_LOWER(i), + coeff[i] & 0xff); + } + } + + if (enable) + regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(0), + 0xe0, 0x80 | (scaling_factor << 5)); + else + regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(0), + 0x80, 0x00); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(1), + ADV7511_CSC_UPDATE_MODE, 0); +} + +static int adv7511_packet_enable(struct adv7511 *adv7511, unsigned int packet) +{ + if (packet & 0xff) + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0, + packet, 0xff); + + if (packet & 0xff00) { + packet >>= 8; + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1, + packet, 0xff); + } + + return 0; +} + +static int adv7511_packet_disable(struct adv7511 *adv7511, unsigned int packet) +{ + if (packet & 0xff) + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0, + packet, 0x00); + + if (packet & 0xff00) { + packet >>= 8; + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1, + packet, 0x00); + } + + return 0; +} + +/* Coefficients for adv7511 color space conversion */ +static const uint16_t adv7511_csc_ycbcr_to_rgb[] = { + 0x0734, 0x04ad, 0x0000, 0x1c1b, + 0x1ddc, 0x04ad, 0x1f24, 0x0135, + 0x0000, 0x04ad, 0x087c, 0x1b77, +}; + +static void adv7511_set_config_csc(struct adv7511 *adv7511, + struct drm_connector *connector, + bool rgb, bool hdmi_mode) +{ + struct adv7511_video_config config; + bool output_format_422, output_format_ycbcr; + unsigned int mode; + uint8_t infoframe[17]; + + config.hdmi_mode = hdmi_mode; + + hdmi_avi_infoframe_init(&config.avi_infoframe); + + config.avi_infoframe.scan_mode = HDMI_SCAN_MODE_UNDERSCAN; + + if (rgb) { + config.csc_enable = false; + config.avi_infoframe.colorspace = HDMI_COLORSPACE_RGB; + } else { + config.csc_scaling_factor = ADV7511_CSC_SCALING_4; + config.csc_coefficents = adv7511_csc_ycbcr_to_rgb; + + if ((connector->display_info.color_formats & + DRM_COLOR_FORMAT_YCRCB422) && + config.hdmi_mode) { + config.csc_enable = false; + config.avi_infoframe.colorspace = + HDMI_COLORSPACE_YUV422; + } else { + config.csc_enable = true; + config.avi_infoframe.colorspace = HDMI_COLORSPACE_RGB; + } + } + + if (config.hdmi_mode) { + mode = ADV7511_HDMI_CFG_MODE_HDMI; + + switch (config.avi_infoframe.colorspace) { + case HDMI_COLORSPACE_YUV444: + output_format_422 = false; + output_format_ycbcr = true; + break; + case HDMI_COLORSPACE_YUV422: + output_format_422 = true; + output_format_ycbcr = true; + break; + default: + output_format_422 = false; + output_format_ycbcr = false; + break; + } + } else { + mode = ADV7511_HDMI_CFG_MODE_DVI; + output_format_422 = false; + output_format_ycbcr = false; + } + + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME); + + adv7511_set_colormap(adv7511, config.csc_enable, + config.csc_coefficents, + config.csc_scaling_factor); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG1, 0x81, + (output_format_422 << 7) | output_format_ycbcr); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_HDCP_HDMI_CFG, + ADV7511_HDMI_CFG_MODE_MASK, mode); + + hdmi_avi_infoframe_pack(&config.avi_infoframe, infoframe, + sizeof(infoframe)); + + /* The AVI infoframe id is not configurable */ + regmap_bulk_write(adv7511->regmap, ADV7511_REG_AVI_INFOFRAME_VERSION, + infoframe + 1, sizeof(infoframe) - 1); + + adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME); +} + +static void adv7511_set_link_config(struct adv7511 *adv7511, + const struct adv7511_link_config *config) +{ + /* + * The input style values documented in the datasheet don't match the + * hardware register field values :-( + */ + static const unsigned int input_styles[4] = { 0, 2, 1, 3 }; + + unsigned int clock_delay; + unsigned int color_depth; + unsigned int input_id; + + clock_delay = (config->clock_delay + 1200) / 400; + color_depth = config->input_color_depth == 8 ? 3 + : (config->input_color_depth == 10 ? 1 : 2); + + /* TODO Support input ID 6 */ + if (config->input_colorspace != HDMI_COLORSPACE_YUV422) + input_id = config->input_clock == ADV7511_INPUT_CLOCK_DDR + ? 5 : 0; + else if (config->input_clock == ADV7511_INPUT_CLOCK_DDR) + input_id = config->embedded_sync ? 8 : 7; + else if (config->input_clock == ADV7511_INPUT_CLOCK_2X) + input_id = config->embedded_sync ? 4 : 3; + else + input_id = config->embedded_sync ? 2 : 1; + + regmap_update_bits(adv7511->regmap, ADV7511_REG_I2C_FREQ_ID_CFG, 0xf, + input_id); + regmap_update_bits(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG1, 0x7e, + (color_depth << 4) | + (input_styles[config->input_style] << 2)); + regmap_write(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG2, + config->input_justification << 3); + regmap_write(adv7511->regmap, ADV7511_REG_TIMING_GEN_SEQ, + config->sync_pulse << 2); + + regmap_write(adv7511->regmap, 0xba, clock_delay << 5); + + adv7511->embedded_sync = config->embedded_sync; + adv7511->hsync_polarity = config->hsync_polarity; + adv7511->vsync_polarity = config->vsync_polarity; + adv7511->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB; +} + +static void __adv7511_power_on(struct adv7511 *adv7511) +{ + adv7511->current_edid_segment = -1; + + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, + ADV7511_POWER_POWER_DOWN, 0); + if (adv7511->i2c_main->irq) { + /* + * Documentation says the INT_ENABLE registers are reset in + * POWER_DOWN mode. My 7511w preserved the bits, however. + * Still, let's be safe and stick to the documentation. + */ + regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0), + ADV7511_INT0_EDID_READY | ADV7511_INT0_HPD); + regmap_update_bits(adv7511->regmap, + ADV7511_REG_INT_ENABLE(1), + ADV7511_INT1_DDC_ERROR, + ADV7511_INT1_DDC_ERROR); + } + + /* + * Per spec it is allowed to pulse the HPD signal to indicate that the + * EDID information has changed. Some monitors do this when they wakeup + * from standby or are enabled. When the HPD goes low the adv7511 is + * reset and the outputs are disabled which might cause the monitor to + * go to standby again. To avoid this we ignore the HPD pin for the + * first few seconds after enabling the output. On the other hand + * adv7535 require to enable HPD Override bit for proper HPD. + */ + if (adv7511->type == ADV7535) + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7535_REG_POWER2_HPD_OVERRIDE, + ADV7535_REG_POWER2_HPD_OVERRIDE); + else + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7511_REG_POWER2_HPD_SRC_MASK, + ADV7511_REG_POWER2_HPD_SRC_NONE); +} + +static void adv7511_power_on(struct adv7511 *adv7511) +{ + __adv7511_power_on(adv7511); + + /* + * Most of the registers are reset during power down or when HPD is low. + */ + regcache_sync(adv7511->regmap); + + if (adv7511->type == ADV7533 || adv7511->type == ADV7535) + adv7533_dsi_power_on(adv7511); + adv7511->powered = true; +} + +static void __adv7511_power_off(struct adv7511 *adv7511) +{ + /* TODO: setup additional power down modes */ + if (adv7511->type == ADV7535) + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7535_REG_POWER2_HPD_OVERRIDE, 0); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, + ADV7511_POWER_POWER_DOWN, + ADV7511_POWER_POWER_DOWN); + regmap_update_bits(adv7511->regmap, + ADV7511_REG_INT_ENABLE(1), + ADV7511_INT1_DDC_ERROR, 0); + regcache_mark_dirty(adv7511->regmap); +} + +static void adv7511_power_off(struct adv7511 *adv7511) +{ + __adv7511_power_off(adv7511); + if (adv7511->type == ADV7533 || adv7511->type == ADV7535) + adv7533_dsi_power_off(adv7511); + adv7511->powered = false; +} + +/* ----------------------------------------------------------------------------- + * Interrupt and hotplug detection + */ + +static bool adv7511_hpd(struct adv7511 *adv7511) +{ + unsigned int irq0; + int ret; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0); + if (ret < 0) + return false; + + if (irq0 & ADV7511_INT0_HPD) { + regmap_write(adv7511->regmap, ADV7511_REG_INT(0), + ADV7511_INT0_HPD); + return true; + } + + return false; +} + +static void adv7511_hpd_work(struct work_struct *work) +{ + struct adv7511 *adv7511 = container_of(work, struct adv7511, hpd_work); + enum drm_connector_status status; + unsigned int val; + int ret; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_STATUS, &val); + if (ret < 0) + status = connector_status_disconnected; + else if (val & ADV7511_STATUS_HPD) + status = connector_status_connected; + else + status = connector_status_disconnected; + + /* + * The bridge resets its registers on unplug. So when we get a plug + * event and we're already supposed to be powered, cycle the bridge to + * restore its state. + */ + if (status == connector_status_connected && + adv7511->connector.status == connector_status_disconnected && + adv7511->powered) { + regcache_mark_dirty(adv7511->regmap); + adv7511_power_on(adv7511); + } + + if (adv7511->connector.status != status) { + adv7511->connector.status = status; + + if (adv7511->connector.dev) { + if (status == connector_status_disconnected) + cec_phys_addr_invalidate(adv7511->cec_adap); + drm_kms_helper_hotplug_event(adv7511->connector.dev); + } else { + drm_bridge_hpd_notify(&adv7511->bridge, status); + } + } +} + +static int adv7511_irq_process(struct adv7511 *adv7511, bool process_hpd) +{ + unsigned int irq0, irq1; + int ret; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0); + if (ret < 0) + return ret; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(1), &irq1); + if (ret < 0) + return ret; + + regmap_write(adv7511->regmap, ADV7511_REG_INT(0), irq0); + regmap_write(adv7511->regmap, ADV7511_REG_INT(1), irq1); + + if (process_hpd && irq0 & ADV7511_INT0_HPD && adv7511->bridge.encoder) + schedule_work(&adv7511->hpd_work); + + if (irq0 & ADV7511_INT0_EDID_READY || irq1 & ADV7511_INT1_DDC_ERROR) { + adv7511->edid_read = true; + + if (adv7511->i2c_main->irq) + wake_up_all(&adv7511->wq); + } + +#ifdef CONFIG_DRM_I2C_ADV7511_CEC + adv7511_cec_irq_process(adv7511, irq1); +#endif + + return 0; +} + +static irqreturn_t adv7511_irq_handler(int irq, void *devid) +{ + struct adv7511 *adv7511 = devid; + int ret; + + ret = adv7511_irq_process(adv7511, true); + return ret < 0 ? IRQ_NONE : IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * EDID retrieval + */ + +static int adv7511_wait_for_edid(struct adv7511 *adv7511, int timeout) +{ + int ret; + + if (adv7511->i2c_main->irq) { + ret = wait_event_interruptible_timeout(adv7511->wq, + adv7511->edid_read, msecs_to_jiffies(timeout)); + } else { + for (; timeout > 0; timeout -= 25) { + ret = adv7511_irq_process(adv7511, false); + if (ret < 0) + break; + + if (adv7511->edid_read) + break; + + msleep(25); + } + } + + return adv7511->edid_read ? 0 : -EIO; +} + +static int adv7511_get_edid_block(void *data, u8 *buf, unsigned int block, + size_t len) +{ + struct adv7511 *adv7511 = data; + struct i2c_msg xfer[2]; + uint8_t offset; + unsigned int i; + int ret; + + if (len > 128) + return -EINVAL; + + if (adv7511->current_edid_segment != block / 2) { + unsigned int status; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_DDC_STATUS, + &status); + if (ret < 0) + return ret; + + if (status != 2) { + adv7511->edid_read = false; + regmap_write(adv7511->regmap, ADV7511_REG_EDID_SEGMENT, + block); + ret = adv7511_wait_for_edid(adv7511, 200); + if (ret < 0) + return ret; + } + + /* Break this apart, hopefully more I2C controllers will + * support 64 byte transfers than 256 byte transfers + */ + + xfer[0].addr = adv7511->i2c_edid->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = &offset; + xfer[1].addr = adv7511->i2c_edid->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 64; + xfer[1].buf = adv7511->edid_buf; + + offset = 0; + + for (i = 0; i < 4; ++i) { + ret = i2c_transfer(adv7511->i2c_edid->adapter, xfer, + ARRAY_SIZE(xfer)); + if (ret < 0) + return ret; + else if (ret != 2) + return -EIO; + + xfer[1].buf += 64; + offset += 64; + } + + adv7511->current_edid_segment = block / 2; + } + + if (block % 2 == 0) + memcpy(buf, adv7511->edid_buf, len); + else + memcpy(buf, adv7511->edid_buf + 128, len); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * ADV75xx helpers + */ + +static struct edid *adv7511_get_edid(struct adv7511 *adv7511, + struct drm_connector *connector) +{ + struct edid *edid; + + /* Reading the EDID only works if the device is powered */ + if (!adv7511->powered) { + unsigned int edid_i2c_addr = + (adv7511->i2c_edid->addr << 1); + + __adv7511_power_on(adv7511); + + /* Reset the EDID_I2C_ADDR register as it might be cleared */ + regmap_write(adv7511->regmap, ADV7511_REG_EDID_I2C_ADDR, + edid_i2c_addr); + } + + edid = drm_do_get_edid(connector, adv7511_get_edid_block, adv7511); + + if (!adv7511->powered) + __adv7511_power_off(adv7511); + + adv7511_set_config_csc(adv7511, connector, adv7511->rgb, + drm_detect_hdmi_monitor(edid)); + + cec_s_phys_addr_from_edid(adv7511->cec_adap, edid); + + return edid; +} + +static int adv7511_get_modes(struct adv7511 *adv7511, + struct drm_connector *connector) +{ + struct edid *edid; + unsigned int count; + + edid = adv7511_get_edid(adv7511, connector); + + drm_connector_update_edid_property(connector, edid); + count = drm_add_edid_modes(connector, edid); + + kfree(edid); + + return count; +} + +static enum drm_connector_status +adv7511_detect(struct adv7511 *adv7511, struct drm_connector *connector) +{ + enum drm_connector_status status; + unsigned int val; + bool hpd; + int ret; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_STATUS, &val); + if (ret < 0) + return connector_status_disconnected; + + if (val & ADV7511_STATUS_HPD) + status = connector_status_connected; + else + status = connector_status_disconnected; + + hpd = adv7511_hpd(adv7511); + + /* The chip resets itself when the cable is disconnected, so in case + * there is a pending HPD interrupt and the cable is connected there was + * at least one transition from disconnected to connected and the chip + * has to be reinitialized. */ + if (status == connector_status_connected && hpd && adv7511->powered) { + regcache_mark_dirty(adv7511->regmap); + adv7511_power_on(adv7511); + if (connector) + adv7511_get_modes(adv7511, connector); + if (adv7511->status == connector_status_connected) + status = connector_status_disconnected; + } else { + /* Renable HPD sensing */ + if (adv7511->type == ADV7535) + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7535_REG_POWER2_HPD_OVERRIDE, + ADV7535_REG_POWER2_HPD_OVERRIDE); + else + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7511_REG_POWER2_HPD_SRC_MASK, + ADV7511_REG_POWER2_HPD_SRC_BOTH); + } + + adv7511->status = status; + return status; +} + +static enum drm_mode_status adv7511_mode_valid(struct adv7511 *adv7511, + const struct drm_display_mode *mode) +{ + if (mode->clock > 165000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static void adv7511_mode_set(struct adv7511 *adv7511, + const struct drm_display_mode *mode, + const struct drm_display_mode *adj_mode) +{ + unsigned int low_refresh_rate; + unsigned int hsync_polarity = 0; + unsigned int vsync_polarity = 0; + + if (adv7511->embedded_sync) { + unsigned int hsync_offset, hsync_len; + unsigned int vsync_offset, vsync_len; + + hsync_offset = adj_mode->crtc_hsync_start - + adj_mode->crtc_hdisplay; + vsync_offset = adj_mode->crtc_vsync_start - + adj_mode->crtc_vdisplay; + hsync_len = adj_mode->crtc_hsync_end - + adj_mode->crtc_hsync_start; + vsync_len = adj_mode->crtc_vsync_end - + adj_mode->crtc_vsync_start; + + /* The hardware vsync generator has a off-by-one bug */ + vsync_offset += 1; + + regmap_write(adv7511->regmap, ADV7511_REG_HSYNC_PLACEMENT_MSB, + ((hsync_offset >> 10) & 0x7) << 5); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(0), + (hsync_offset >> 2) & 0xff); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(1), + ((hsync_offset & 0x3) << 6) | + ((hsync_len >> 4) & 0x3f)); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(2), + ((hsync_len & 0xf) << 4) | + ((vsync_offset >> 6) & 0xf)); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(3), + ((vsync_offset & 0x3f) << 2) | + ((vsync_len >> 8) & 0x3)); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(4), + vsync_len & 0xff); + + hsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PHSYNC); + vsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PVSYNC); + } else { + enum adv7511_sync_polarity mode_hsync_polarity; + enum adv7511_sync_polarity mode_vsync_polarity; + + /** + * If the input signal is always low or always high we want to + * invert or let it passthrough depending on the polarity of the + * current mode. + **/ + if (adj_mode->flags & DRM_MODE_FLAG_NHSYNC) + mode_hsync_polarity = ADV7511_SYNC_POLARITY_LOW; + else + mode_hsync_polarity = ADV7511_SYNC_POLARITY_HIGH; + + if (adj_mode->flags & DRM_MODE_FLAG_NVSYNC) + mode_vsync_polarity = ADV7511_SYNC_POLARITY_LOW; + else + mode_vsync_polarity = ADV7511_SYNC_POLARITY_HIGH; + + if (adv7511->hsync_polarity != mode_hsync_polarity && + adv7511->hsync_polarity != + ADV7511_SYNC_POLARITY_PASSTHROUGH) + hsync_polarity = 1; + + if (adv7511->vsync_polarity != mode_vsync_polarity && + adv7511->vsync_polarity != + ADV7511_SYNC_POLARITY_PASSTHROUGH) + vsync_polarity = 1; + } + + if (drm_mode_vrefresh(mode) <= 24) + low_refresh_rate = ADV7511_LOW_REFRESH_RATE_24HZ; + else if (drm_mode_vrefresh(mode) <= 25) + low_refresh_rate = ADV7511_LOW_REFRESH_RATE_25HZ; + else if (drm_mode_vrefresh(mode) <= 30) + low_refresh_rate = ADV7511_LOW_REFRESH_RATE_30HZ; + else + low_refresh_rate = ADV7511_LOW_REFRESH_RATE_NONE; + + if (adv7511->type == ADV7511) + regmap_update_bits(adv7511->regmap, 0xfb, + 0x6, low_refresh_rate << 1); + else + regmap_update_bits(adv7511->regmap, 0x4a, + 0xc, low_refresh_rate << 2); + + regmap_update_bits(adv7511->regmap, 0x17, + 0x60, (vsync_polarity << 6) | (hsync_polarity << 5)); + + drm_mode_copy(&adv7511->curr_mode, adj_mode); + + /* + * TODO Test first order 4:2:2 to 4:4:4 up conversion method, which is + * supposed to give better results. + */ + + adv7511->f_tmds = mode->clock; +} + +/* ----------------------------------------------------------------------------- + * DRM Connector Operations + */ + +static struct adv7511 *connector_to_adv7511(struct drm_connector *connector) +{ + return container_of(connector, struct adv7511, connector); +} + +static int adv7511_connector_get_modes(struct drm_connector *connector) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv7511_get_modes(adv, connector); +} + +static enum drm_mode_status +adv7511_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv7511_mode_valid(adv, mode); +} + +static struct drm_connector_helper_funcs adv7511_connector_helper_funcs = { + .get_modes = adv7511_connector_get_modes, + .mode_valid = adv7511_connector_mode_valid, +}; + +static enum drm_connector_status +adv7511_connector_detect(struct drm_connector *connector, bool force) +{ + struct adv7511 *adv = connector_to_adv7511(connector); + + return adv7511_detect(adv, connector); +} + +static const struct drm_connector_funcs adv7511_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = adv7511_connector_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int adv7511_connector_init(struct adv7511 *adv) +{ + struct drm_bridge *bridge = &adv->bridge; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + if (adv->i2c_main->irq) + adv->connector.polled = DRM_CONNECTOR_POLL_HPD; + else + adv->connector.polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + ret = drm_connector_init(bridge->dev, &adv->connector, + &adv7511_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret < 0) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + drm_connector_helper_add(&adv->connector, + &adv7511_connector_helper_funcs); + drm_connector_attach_encoder(&adv->connector, bridge->encoder); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * DRM Bridge Operations + */ + +static struct adv7511 *bridge_to_adv7511(struct drm_bridge *bridge) +{ + return container_of(bridge, struct adv7511, bridge); +} + +static void adv7511_bridge_enable(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_power_on(adv); +} + +static void adv7511_bridge_disable(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_power_off(adv); +} + +static void adv7511_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adj_mode) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_mode_set(adv, mode, adj_mode); +} + +static enum drm_mode_status adv7511_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + if (adv->type == ADV7533 || adv->type == ADV7535) + return adv7533_mode_valid(adv, mode); + else + return adv7511_mode_valid(adv, mode); +} + +static int adv7511_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + int ret = 0; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + ret = adv7511_connector_init(adv); + if (ret < 0) + return ret; + } + + if (adv->type == ADV7533 || adv->type == ADV7535) + ret = adv7533_attach_dsi(adv); + + if (adv->i2c_main->irq) + regmap_write(adv->regmap, ADV7511_REG_INT_ENABLE(0), + ADV7511_INT0_HPD); + + return ret; +} + +static enum drm_connector_status adv7511_bridge_detect(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + return adv7511_detect(adv, NULL); +} + +static struct edid *adv7511_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + return adv7511_get_edid(adv, connector); +} + +static void adv7511_bridge_hpd_notify(struct drm_bridge *bridge, + enum drm_connector_status status) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + if (status == connector_status_disconnected) + cec_phys_addr_invalidate(adv->cec_adap); +} + +static const struct drm_bridge_funcs adv7511_bridge_funcs = { + .enable = adv7511_bridge_enable, + .disable = adv7511_bridge_disable, + .mode_set = adv7511_bridge_mode_set, + .mode_valid = adv7511_bridge_mode_valid, + .attach = adv7511_bridge_attach, + .detect = adv7511_bridge_detect, + .get_edid = adv7511_bridge_get_edid, + .hpd_notify = adv7511_bridge_hpd_notify, +}; + +/* ----------------------------------------------------------------------------- + * Probe & remove + */ + +static const char * const adv7511_supply_names[] = { + "avdd", + "dvdd", + "pvdd", + "bgvdd", + "dvdd-3v", +}; + +static const char * const adv7533_supply_names[] = { + "avdd", + "dvdd", + "pvdd", + "a2vdd", + "v3p3", + "v1p2", +}; + +static int adv7511_init_regulators(struct adv7511 *adv) +{ + struct device *dev = &adv->i2c_main->dev; + const char * const *supply_names; + unsigned int i; + int ret; + + if (adv->type == ADV7511) { + supply_names = adv7511_supply_names; + adv->num_supplies = ARRAY_SIZE(adv7511_supply_names); + } else { + supply_names = adv7533_supply_names; + adv->num_supplies = ARRAY_SIZE(adv7533_supply_names); + } + + adv->supplies = devm_kcalloc(dev, adv->num_supplies, + sizeof(*adv->supplies), GFP_KERNEL); + if (!adv->supplies) + return -ENOMEM; + + for (i = 0; i < adv->num_supplies; i++) + adv->supplies[i].supply = supply_names[i]; + + ret = devm_regulator_bulk_get(dev, adv->num_supplies, adv->supplies); + if (ret) + return ret; + + return regulator_bulk_enable(adv->num_supplies, adv->supplies); +} + +static void adv7511_uninit_regulators(struct adv7511 *adv) +{ + regulator_bulk_disable(adv->num_supplies, adv->supplies); +} + +static bool adv7511_cec_register_volatile(struct device *dev, unsigned int reg) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct adv7511 *adv7511 = i2c_get_clientdata(i2c); + + if (adv7511->type == ADV7533 || adv7511->type == ADV7535) + reg -= ADV7533_REG_CEC_OFFSET; + + switch (reg) { + case ADV7511_REG_CEC_RX_FRAME_HDR: + case ADV7511_REG_CEC_RX_FRAME_DATA0... + ADV7511_REG_CEC_RX_FRAME_DATA0 + 14: + case ADV7511_REG_CEC_RX_FRAME_LEN: + case ADV7511_REG_CEC_RX_BUFFERS: + case ADV7511_REG_CEC_TX_LOW_DRV_CNT: + return true; + } + + return false; +} + +static const struct regmap_config adv7511_cec_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = adv7511_cec_register_volatile, +}; + +static int adv7511_init_cec_regmap(struct adv7511 *adv) +{ + int ret; + + adv->i2c_cec = i2c_new_ancillary_device(adv->i2c_main, "cec", + ADV7511_CEC_I2C_ADDR_DEFAULT); + if (IS_ERR(adv->i2c_cec)) + return PTR_ERR(adv->i2c_cec); + + regmap_write(adv->regmap, ADV7511_REG_CEC_I2C_ADDR, + adv->i2c_cec->addr << 1); + + i2c_set_clientdata(adv->i2c_cec, adv); + + adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec, + &adv7511_cec_regmap_config); + if (IS_ERR(adv->regmap_cec)) { + ret = PTR_ERR(adv->regmap_cec); + goto err; + } + + if (adv->type == ADV7533 || adv->type == ADV7535) { + ret = adv7533_patch_cec_registers(adv); + if (ret) + goto err; + } + + return 0; +err: + i2c_unregister_device(adv->i2c_cec); + return ret; +} + +static int adv7511_parse_dt(struct device_node *np, + struct adv7511_link_config *config) +{ + const char *str; + int ret; + + of_property_read_u32(np, "adi,input-depth", &config->input_color_depth); + if (config->input_color_depth != 8 && config->input_color_depth != 10 && + config->input_color_depth != 12) + return -EINVAL; + + ret = of_property_read_string(np, "adi,input-colorspace", &str); + if (ret < 0) + return ret; + + if (!strcmp(str, "rgb")) + config->input_colorspace = HDMI_COLORSPACE_RGB; + else if (!strcmp(str, "yuv422")) + config->input_colorspace = HDMI_COLORSPACE_YUV422; + else if (!strcmp(str, "yuv444")) + config->input_colorspace = HDMI_COLORSPACE_YUV444; + else + return -EINVAL; + + ret = of_property_read_string(np, "adi,input-clock", &str); + if (ret < 0) + return ret; + + if (!strcmp(str, "1x")) + config->input_clock = ADV7511_INPUT_CLOCK_1X; + else if (!strcmp(str, "2x")) + config->input_clock = ADV7511_INPUT_CLOCK_2X; + else if (!strcmp(str, "ddr")) + config->input_clock = ADV7511_INPUT_CLOCK_DDR; + else + return -EINVAL; + + if (config->input_colorspace == HDMI_COLORSPACE_YUV422 || + config->input_clock != ADV7511_INPUT_CLOCK_1X) { + ret = of_property_read_u32(np, "adi,input-style", + &config->input_style); + if (ret) + return ret; + + if (config->input_style < 1 || config->input_style > 3) + return -EINVAL; + + ret = of_property_read_string(np, "adi,input-justification", + &str); + if (ret < 0) + return ret; + + if (!strcmp(str, "left")) + config->input_justification = + ADV7511_INPUT_JUSTIFICATION_LEFT; + else if (!strcmp(str, "evenly")) + config->input_justification = + ADV7511_INPUT_JUSTIFICATION_EVENLY; + else if (!strcmp(str, "right")) + config->input_justification = + ADV7511_INPUT_JUSTIFICATION_RIGHT; + else + return -EINVAL; + + } else { + config->input_style = 1; + config->input_justification = ADV7511_INPUT_JUSTIFICATION_LEFT; + } + + of_property_read_u32(np, "adi,clock-delay", &config->clock_delay); + if (config->clock_delay < -1200 || config->clock_delay > 1600) + return -EINVAL; + + config->embedded_sync = of_property_read_bool(np, "adi,embedded-sync"); + + /* Hardcode the sync pulse configurations for now. */ + config->sync_pulse = ADV7511_INPUT_SYNC_PULSE_NONE; + config->vsync_polarity = ADV7511_SYNC_POLARITY_PASSTHROUGH; + config->hsync_polarity = ADV7511_SYNC_POLARITY_PASSTHROUGH; + + return 0; +} + +static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + struct adv7511_link_config link_config; + struct adv7511 *adv7511; + struct device *dev = &i2c->dev; + unsigned int val; + int ret; + + if (!dev->of_node) + return -EINVAL; + + adv7511 = devm_kzalloc(dev, sizeof(*adv7511), GFP_KERNEL); + if (!adv7511) + return -ENOMEM; + + adv7511->i2c_main = i2c; + adv7511->powered = false; + adv7511->status = connector_status_disconnected; + + if (dev->of_node) + adv7511->type = (enum adv7511_type)of_device_get_match_data(dev); + else + adv7511->type = id->driver_data; + + memset(&link_config, 0, sizeof(link_config)); + + if (adv7511->type == ADV7511) + ret = adv7511_parse_dt(dev->of_node, &link_config); + else + ret = adv7533_parse_dt(dev->of_node, adv7511); + if (ret) + return ret; + + ret = adv7511_init_regulators(adv7511); + if (ret) { + dev_err(dev, "failed to init regulators\n"); + return ret; + } + + /* + * The power down GPIO is optional. If present, toggle it from active to + * inactive to wake up the encoder. + */ + adv7511->gpio_pd = devm_gpiod_get_optional(dev, "pd", GPIOD_OUT_HIGH); + if (IS_ERR(adv7511->gpio_pd)) { + ret = PTR_ERR(adv7511->gpio_pd); + goto uninit_regulators; + } + + if (adv7511->gpio_pd) { + usleep_range(5000, 6000); + gpiod_set_value_cansleep(adv7511->gpio_pd, 0); + } + + adv7511->regmap = devm_regmap_init_i2c(i2c, &adv7511_regmap_config); + if (IS_ERR(adv7511->regmap)) { + ret = PTR_ERR(adv7511->regmap); + goto uninit_regulators; + } + + ret = regmap_read(adv7511->regmap, ADV7511_REG_CHIP_REVISION, &val); + if (ret) + goto uninit_regulators; + dev_dbg(dev, "Rev. %d\n", val); + + if (adv7511->type == ADV7511) + ret = regmap_register_patch(adv7511->regmap, + adv7511_fixed_registers, + ARRAY_SIZE(adv7511_fixed_registers)); + else + ret = adv7533_patch_registers(adv7511); + if (ret) + goto uninit_regulators; + + adv7511_packet_disable(adv7511, 0xffff); + + adv7511->i2c_edid = i2c_new_ancillary_device(i2c, "edid", + ADV7511_EDID_I2C_ADDR_DEFAULT); + if (IS_ERR(adv7511->i2c_edid)) { + ret = PTR_ERR(adv7511->i2c_edid); + goto uninit_regulators; + } + + regmap_write(adv7511->regmap, ADV7511_REG_EDID_I2C_ADDR, + adv7511->i2c_edid->addr << 1); + + adv7511->i2c_packet = i2c_new_ancillary_device(i2c, "packet", + ADV7511_PACKET_I2C_ADDR_DEFAULT); + if (IS_ERR(adv7511->i2c_packet)) { + ret = PTR_ERR(adv7511->i2c_packet); + goto err_i2c_unregister_edid; + } + + regmap_write(adv7511->regmap, ADV7511_REG_PACKET_I2C_ADDR, + adv7511->i2c_packet->addr << 1); + + ret = adv7511_init_cec_regmap(adv7511); + if (ret) + goto err_i2c_unregister_packet; + + INIT_WORK(&adv7511->hpd_work, adv7511_hpd_work); + + if (i2c->irq) { + init_waitqueue_head(&adv7511->wq); + + ret = devm_request_threaded_irq(dev, i2c->irq, NULL, + adv7511_irq_handler, + IRQF_ONESHOT, dev_name(dev), + adv7511); + if (ret) + goto err_unregister_cec; + } + + adv7511_power_off(adv7511); + + i2c_set_clientdata(i2c, adv7511); + + if (adv7511->type == ADV7511) + adv7511_set_link_config(adv7511, &link_config); + + ret = adv7511_cec_init(dev, adv7511); + if (ret) + goto err_unregister_cec; + + adv7511->bridge.funcs = &adv7511_bridge_funcs; + adv7511->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID + | DRM_BRIDGE_OP_HPD; + adv7511->bridge.of_node = dev->of_node; + adv7511->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + drm_bridge_add(&adv7511->bridge); + + adv7511_audio_init(dev, adv7511); + return 0; + +err_unregister_cec: + cec_unregister_adapter(adv7511->cec_adap); + i2c_unregister_device(adv7511->i2c_cec); + if (adv7511->cec_clk) + clk_disable_unprepare(adv7511->cec_clk); +err_i2c_unregister_packet: + i2c_unregister_device(adv7511->i2c_packet); +err_i2c_unregister_edid: + i2c_unregister_device(adv7511->i2c_edid); +uninit_regulators: + adv7511_uninit_regulators(adv7511); + + return ret; +} + +static int adv7511_remove(struct i2c_client *i2c) +{ + struct adv7511 *adv7511 = i2c_get_clientdata(i2c); + + if (adv7511->type == ADV7533 || adv7511->type == ADV7535) + adv7533_detach_dsi(adv7511); + i2c_unregister_device(adv7511->i2c_cec); + if (adv7511->cec_clk) + clk_disable_unprepare(adv7511->cec_clk); + + adv7511_uninit_regulators(adv7511); + + drm_bridge_remove(&adv7511->bridge); + + adv7511_audio_exit(adv7511); + + cec_unregister_adapter(adv7511->cec_adap); + + i2c_unregister_device(adv7511->i2c_packet); + i2c_unregister_device(adv7511->i2c_edid); + + return 0; +} + +static const struct i2c_device_id adv7511_i2c_ids[] = { + { "adv7511", ADV7511 }, + { "adv7511w", ADV7511 }, + { "adv7513", ADV7511 }, + { "adv7533", ADV7533 }, + { "adv7535", ADV7535 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7511_i2c_ids); + +static const struct of_device_id adv7511_of_ids[] = { + { .compatible = "adi,adv7511", .data = (void *)ADV7511 }, + { .compatible = "adi,adv7511w", .data = (void *)ADV7511 }, + { .compatible = "adi,adv7513", .data = (void *)ADV7511 }, + { .compatible = "adi,adv7533", .data = (void *)ADV7533 }, + { .compatible = "adi,adv7535", .data = (void *)ADV7535 }, + { } +}; +MODULE_DEVICE_TABLE(of, adv7511_of_ids); + +static struct mipi_dsi_driver adv7533_dsi_driver = { + .driver.name = "adv7533", +}; + +static struct i2c_driver adv7511_driver = { + .driver = { + .name = "adv7511", + .of_match_table = adv7511_of_ids, + }, + .id_table = adv7511_i2c_ids, + .probe = adv7511_probe, + .remove = adv7511_remove, +}; + +static int __init adv7511_init(void) +{ + int ret; + + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) { + ret = mipi_dsi_driver_register(&adv7533_dsi_driver); + if (ret) + return ret; + } + + ret = i2c_add_driver(&adv7511_driver); + if (ret) { + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) + mipi_dsi_driver_unregister(&adv7533_dsi_driver); + } + + return ret; +} +module_init(adv7511_init); + +static void __exit adv7511_exit(void) +{ + i2c_del_driver(&adv7511_driver); + + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) + mipi_dsi_driver_unregister(&adv7533_dsi_driver); +} +module_exit(adv7511_exit); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/adv7511/adv7533.c b/drivers/gpu/drm/bridge/adv7511/adv7533.c new file mode 100644 index 000000000..e0bdedf22 --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7533.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + */ + +#include <linux/of_graph.h> + +#include "adv7511.h" + +static const struct reg_sequence adv7533_fixed_registers[] = { + { 0x16, 0x20 }, + { 0x9a, 0xe0 }, + { 0xba, 0x70 }, + { 0xde, 0x82 }, + { 0xe4, 0x40 }, + { 0xe5, 0x80 }, +}; + +static const struct reg_sequence adv7533_cec_fixed_registers[] = { + { 0x15, 0xd0 }, + { 0x17, 0xd0 }, + { 0x24, 0x20 }, + { 0x57, 0x11 }, + { 0x05, 0xc8 }, +}; + +static void adv7511_dsi_config_timing_gen(struct adv7511 *adv) +{ + struct mipi_dsi_device *dsi = adv->dsi; + struct drm_display_mode *mode = &adv->curr_mode; + unsigned int hsw, hfp, hbp, vsw, vfp, vbp; + u8 clock_div_by_lanes[] = { 6, 4, 3 }; /* 2, 3, 4 lanes */ + + hsw = mode->hsync_end - mode->hsync_start; + hfp = mode->hsync_start - mode->hdisplay; + hbp = mode->htotal - mode->hsync_end; + vsw = mode->vsync_end - mode->vsync_start; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + + /* set pixel clock divider mode */ + regmap_write(adv->regmap_cec, 0x16, + clock_div_by_lanes[dsi->lanes - 2] << 3); + + /* horizontal porch params */ + regmap_write(adv->regmap_cec, 0x28, mode->htotal >> 4); + regmap_write(adv->regmap_cec, 0x29, (mode->htotal << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x2a, hsw >> 4); + regmap_write(adv->regmap_cec, 0x2b, (hsw << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x2c, hfp >> 4); + regmap_write(adv->regmap_cec, 0x2d, (hfp << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x2e, hbp >> 4); + regmap_write(adv->regmap_cec, 0x2f, (hbp << 4) & 0xff); + + /* vertical porch params */ + regmap_write(adv->regmap_cec, 0x30, mode->vtotal >> 4); + regmap_write(adv->regmap_cec, 0x31, (mode->vtotal << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x32, vsw >> 4); + regmap_write(adv->regmap_cec, 0x33, (vsw << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x34, vfp >> 4); + regmap_write(adv->regmap_cec, 0x35, (vfp << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x36, vbp >> 4); + regmap_write(adv->regmap_cec, 0x37, (vbp << 4) & 0xff); +} + +void adv7533_dsi_power_on(struct adv7511 *adv) +{ + struct mipi_dsi_device *dsi = adv->dsi; + + if (adv->use_timing_gen) + adv7511_dsi_config_timing_gen(adv); + + /* set number of dsi lanes */ + regmap_write(adv->regmap_cec, 0x1c, dsi->lanes << 4); + + if (adv->use_timing_gen) { + /* reset internal timing generator */ + regmap_write(adv->regmap_cec, 0x27, 0xcb); + regmap_write(adv->regmap_cec, 0x27, 0x8b); + regmap_write(adv->regmap_cec, 0x27, 0xcb); + } else { + /* disable internal timing generator */ + regmap_write(adv->regmap_cec, 0x27, 0x0b); + } + + /* enable hdmi */ + regmap_write(adv->regmap_cec, 0x03, 0x89); + /* disable test mode */ + regmap_write(adv->regmap_cec, 0x55, 0x00); + + regmap_register_patch(adv->regmap_cec, adv7533_cec_fixed_registers, + ARRAY_SIZE(adv7533_cec_fixed_registers)); +} + +void adv7533_dsi_power_off(struct adv7511 *adv) +{ + /* disable hdmi */ + regmap_write(adv->regmap_cec, 0x03, 0x0b); + /* disable internal timing generator */ + regmap_write(adv->regmap_cec, 0x27, 0x0b); +} + +enum drm_mode_status adv7533_mode_valid(struct adv7511 *adv, + const struct drm_display_mode *mode) +{ + unsigned long max_lane_freq; + struct mipi_dsi_device *dsi = adv->dsi; + u8 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + + /* Check max clock for either 7533 or 7535 */ + if (mode->clock > (adv->type == ADV7533 ? 80000 : 148500)) + return MODE_CLOCK_HIGH; + + /* Check max clock for each lane */ + max_lane_freq = (adv->type == ADV7533 ? 800000 : 891000); + + if (mode->clock * bpp > max_lane_freq * adv->num_dsi_lanes) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +int adv7533_patch_registers(struct adv7511 *adv) +{ + return regmap_register_patch(adv->regmap, + adv7533_fixed_registers, + ARRAY_SIZE(adv7533_fixed_registers)); +} + +int adv7533_patch_cec_registers(struct adv7511 *adv) +{ + return regmap_register_patch(adv->regmap_cec, + adv7533_cec_fixed_registers, + ARRAY_SIZE(adv7533_cec_fixed_registers)); +} + +int adv7533_attach_dsi(struct adv7511 *adv) +{ + struct device *dev = &adv->i2c_main->dev; + struct mipi_dsi_host *host; + struct mipi_dsi_device *dsi; + int ret = 0; + const struct mipi_dsi_device_info info = { .type = "adv7533", + .channel = 0, + .node = NULL, + }; + + host = of_find_mipi_dsi_host_by_node(adv->host_node); + if (!host) { + dev_err(dev, "failed to find dsi host\n"); + return -EPROBE_DEFER; + } + + dsi = mipi_dsi_device_register_full(host, &info); + if (IS_ERR(dsi)) { + dev_err(dev, "failed to create dsi device\n"); + ret = PTR_ERR(dsi); + goto err_dsi_device; + } + + adv->dsi = dsi; + + dsi->lanes = adv->num_dsi_lanes; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "failed to attach dsi to host\n"); + goto err_dsi_attach; + } + + return 0; + +err_dsi_attach: + mipi_dsi_device_unregister(dsi); +err_dsi_device: + return ret; +} + +void adv7533_detach_dsi(struct adv7511 *adv) +{ + mipi_dsi_detach(adv->dsi); + mipi_dsi_device_unregister(adv->dsi); +} + +int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv) +{ + u32 num_lanes; + + of_property_read_u32(np, "adi,dsi-lanes", &num_lanes); + + if (num_lanes < 1 || num_lanes > 4) + return -EINVAL; + + adv->num_dsi_lanes = num_lanes; + + adv->host_node = of_graph_get_remote_node(np, 0, 0); + if (!adv->host_node) + return -ENODEV; + + of_node_put(adv->host_node); + + adv->use_timing_gen = !of_property_read_bool(np, + "adi,disable-timing-generator"); + + /* TODO: Check if these need to be parsed by DT or not */ + adv->rgb = true; + adv->embedded_sync = false; + + return 0; +} diff --git a/drivers/gpu/drm/bridge/analogix/Kconfig b/drivers/gpu/drm/bridge/analogix/Kconfig new file mode 100644 index 000000000..e1fa7d820 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/Kconfig @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_ANALOGIX_ANX6345 + tristate "Analogix ANX6345 bridge" + depends on OF + select DRM_ANALOGIX_DP + select DRM_KMS_HELPER + select REGMAP_I2C + help + ANX6345 is an ultra-low Full-HD DisplayPort/eDP + transmitter designed for portable devices. The + ANX6345 transforms the LVTTL RGB output of an + application processor to eDP or DisplayPort. + +config DRM_ANALOGIX_ANX78XX + tristate "Analogix ANX78XX bridge" + select DRM_ANALOGIX_DP + select DRM_KMS_HELPER + select REGMAP_I2C + help + ANX78XX is an ultra-low power Full-HD SlimPort transmitter + designed for portable devices. The ANX78XX transforms + the HDMI output of an application processor to MyDP + or DisplayPort. + +config DRM_ANALOGIX_DP + tristate + depends on DRM diff --git a/drivers/gpu/drm/bridge/analogix/Makefile b/drivers/gpu/drm/bridge/analogix/Makefile new file mode 100644 index 000000000..97669b374 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +analogix_dp-objs := analogix_dp_core.o analogix_dp_reg.o analogix-i2c-dptx.o +obj-$(CONFIG_DRM_ANALOGIX_ANX6345) += analogix-anx6345.o +obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o +obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix_dp.o diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c new file mode 100644 index 000000000..d9164fab0 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c @@ -0,0 +1,818 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2016, Analogix Semiconductor. + * Copyright(c) 2017, Icenowy Zheng <icenowy@aosc.io> + * + * Based on anx7808 driver obtained from chromeos with copyright: + * Copyright(c) 2013, Google Inc. + */ +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/types.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_dp_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "analogix-i2c-dptx.h" +#include "analogix-i2c-txcommon.h" + +#define POLL_DELAY 50000 /* us */ +#define POLL_TIMEOUT 5000000 /* us */ + +#define I2C_IDX_DPTX 0 +#define I2C_IDX_TXCOM 1 + +static const u8 anx6345_i2c_addresses[] = { + [I2C_IDX_DPTX] = 0x70, + [I2C_IDX_TXCOM] = 0x72, +}; +#define I2C_NUM_ADDRESSES ARRAY_SIZE(anx6345_i2c_addresses) + +struct anx6345 { + struct drm_dp_aux aux; + struct drm_bridge bridge; + struct i2c_client *client; + struct edid *edid; + struct drm_connector connector; + struct drm_panel *panel; + struct regulator *dvdd12; + struct regulator *dvdd25; + struct gpio_desc *gpiod_reset; + struct mutex lock; /* protect EDID access */ + + /* I2C Slave addresses of ANX6345 are mapped as DPTX and SYS */ + struct i2c_client *i2c_clients[I2C_NUM_ADDRESSES]; + struct regmap *map[I2C_NUM_ADDRESSES]; + + u16 chipid; + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + + bool powered; +}; + +static inline struct anx6345 *connector_to_anx6345(struct drm_connector *c) +{ + return container_of(c, struct anx6345, connector); +} + +static inline struct anx6345 *bridge_to_anx6345(struct drm_bridge *bridge) +{ + return container_of(bridge, struct anx6345, bridge); +} + +static int anx6345_set_bits(struct regmap *map, u8 reg, u8 mask) +{ + return regmap_update_bits(map, reg, mask, mask); +} + +static int anx6345_clear_bits(struct regmap *map, u8 reg, u8 mask) +{ + return regmap_update_bits(map, reg, mask, 0); +} + +static ssize_t anx6345_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct anx6345 *anx6345 = container_of(aux, struct anx6345, aux); + + return anx_dp_aux_transfer(anx6345->map[I2C_IDX_DPTX], msg); +} + +static int anx6345_dp_link_training(struct anx6345 *anx6345) +{ + unsigned int value; + u8 dp_bw, dpcd[2]; + int err; + + err = anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], + SP_POWERDOWN_CTRL_REG, + SP_TOTAL_PD); + if (err) + return err; + + err = drm_dp_dpcd_readb(&anx6345->aux, DP_MAX_LINK_RATE, &dp_bw); + if (err < 0) + return err; + + switch (dp_bw) { + case DP_LINK_BW_1_62: + case DP_LINK_BW_2_7: + break; + + default: + DRM_DEBUG_KMS("DP bandwidth (%#02x) not supported\n", dp_bw); + return -EINVAL; + } + + err = anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], SP_VID_CTRL1_REG, + SP_VIDEO_MUTE); + if (err) + return err; + + err = anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], + SP_VID_CTRL1_REG, SP_VIDEO_EN); + if (err) + return err; + + /* Get DPCD info */ + err = drm_dp_dpcd_read(&anx6345->aux, DP_DPCD_REV, + &anx6345->dpcd, DP_RECEIVER_CAP_SIZE); + if (err < 0) { + DRM_ERROR("Failed to read DPCD: %d\n", err); + return err; + } + + /* Clear channel x SERDES power down */ + err = anx6345_clear_bits(anx6345->map[I2C_IDX_DPTX], + SP_DP_ANALOG_POWER_DOWN_REG, SP_CH0_PD); + if (err) + return err; + + /* + * Power up the sink (DP_SET_POWER register is only available on DPCD + * v1.1 and later). + */ + if (anx6345->dpcd[DP_DPCD_REV] >= 0x11) { + err = drm_dp_dpcd_readb(&anx6345->aux, DP_SET_POWER, &dpcd[0]); + if (err < 0) { + DRM_ERROR("Failed to read DP_SET_POWER register: %d\n", + err); + return err; + } + + dpcd[0] &= ~DP_SET_POWER_MASK; + dpcd[0] |= DP_SET_POWER_D0; + + err = drm_dp_dpcd_writeb(&anx6345->aux, DP_SET_POWER, dpcd[0]); + if (err < 0) { + DRM_ERROR("Failed to power up DisplayPort link: %d\n", + err); + return err; + } + + /* + * According to the DP 1.1 specification, a "Sink Device must + * exit the power saving state within 1 ms" (Section 2.5.3.1, + * Table 5-52, "Sink Control Field" (register 0x600). + */ + usleep_range(1000, 2000); + } + + /* Possibly enable downspread on the sink */ + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_DOWNSPREAD_CTRL1_REG, 0); + if (err) + return err; + + if (anx6345->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5) { + DRM_DEBUG("Enable downspread on the sink\n"); + /* 4000PPM */ + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_DOWNSPREAD_CTRL1_REG, 8); + if (err) + return err; + + err = drm_dp_dpcd_writeb(&anx6345->aux, DP_DOWNSPREAD_CTRL, + DP_SPREAD_AMP_0_5); + if (err < 0) + return err; + } else { + err = drm_dp_dpcd_writeb(&anx6345->aux, DP_DOWNSPREAD_CTRL, 0); + if (err < 0) + return err; + } + + /* Set the lane count and the link rate on the sink */ + if (drm_dp_enhanced_frame_cap(anx6345->dpcd)) + err = anx6345_set_bits(anx6345->map[I2C_IDX_DPTX], + SP_DP_SYSTEM_CTRL_BASE + 4, + SP_ENHANCED_MODE); + else + err = anx6345_clear_bits(anx6345->map[I2C_IDX_DPTX], + SP_DP_SYSTEM_CTRL_BASE + 4, + SP_ENHANCED_MODE); + if (err) + return err; + + dpcd[0] = dp_bw; + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_MAIN_LINK_BW_SET_REG, dpcd[0]); + if (err) + return err; + + dpcd[1] = drm_dp_max_lane_count(anx6345->dpcd); + + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_LANE_COUNT_SET_REG, dpcd[1]); + if (err) + return err; + + if (drm_dp_enhanced_frame_cap(anx6345->dpcd)) + dpcd[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + + err = drm_dp_dpcd_write(&anx6345->aux, DP_LINK_BW_SET, dpcd, + sizeof(dpcd)); + + if (err < 0) { + DRM_ERROR("Failed to configure link: %d\n", err); + return err; + } + + /* Start training on the source */ + err = regmap_write(anx6345->map[I2C_IDX_DPTX], SP_DP_LT_CTRL_REG, + SP_LT_EN); + if (err) + return err; + + return regmap_read_poll_timeout(anx6345->map[I2C_IDX_DPTX], + SP_DP_LT_CTRL_REG, + value, !(value & SP_DP_LT_INPROGRESS), + POLL_DELAY, POLL_TIMEOUT); +} + +static int anx6345_tx_initialization(struct anx6345 *anx6345) +{ + int err, i; + + /* FIXME: colordepth is hardcoded for now */ + err = regmap_write(anx6345->map[I2C_IDX_TXCOM], SP_VID_CTRL2_REG, + SP_IN_BPC_6BIT << SP_IN_BPC_SHIFT); + if (err) + return err; + + err = regmap_write(anx6345->map[I2C_IDX_DPTX], SP_DP_PLL_CTRL_REG, 0); + if (err) + return err; + + err = regmap_write(anx6345->map[I2C_IDX_TXCOM], + SP_ANALOG_DEBUG1_REG, 0); + if (err) + return err; + + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_LINK_DEBUG_CTRL_REG, + SP_NEW_PRBS7 | SP_M_VID_DEBUG); + if (err) + return err; + + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_ANALOG_POWER_DOWN_REG, 0); + if (err) + return err; + + /* Force HPD */ + err = anx6345_set_bits(anx6345->map[I2C_IDX_DPTX], + SP_DP_SYSTEM_CTRL_BASE + 3, + SP_HPD_FORCE | SP_HPD_CTRL); + if (err) + return err; + + for (i = 0; i < 4; i++) { + /* 4 lanes */ + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_LANE0_LT_CTRL_REG + i, 0); + if (err) + return err; + } + + /* Reset AUX */ + err = anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], + SP_RESET_CTRL2_REG, SP_AUX_RST); + if (err) + return err; + + return anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], + SP_RESET_CTRL2_REG, SP_AUX_RST); +} + +static void anx6345_poweron(struct anx6345 *anx6345) +{ + int err; + + /* Ensure reset is asserted before starting power on sequence */ + gpiod_set_value_cansleep(anx6345->gpiod_reset, 1); + usleep_range(1000, 2000); + + err = regulator_enable(anx6345->dvdd12); + if (err) { + DRM_ERROR("Failed to enable dvdd12 regulator: %d\n", + err); + return; + } + + /* T1 - delay between VDD12 and VDD25 should be 0-2ms */ + usleep_range(1000, 2000); + + err = regulator_enable(anx6345->dvdd25); + if (err) { + DRM_ERROR("Failed to enable dvdd25 regulator: %d\n", + err); + return; + } + + /* T2 - delay between RESETN and all power rail stable, + * should be 2-5ms + */ + usleep_range(2000, 5000); + + gpiod_set_value_cansleep(anx6345->gpiod_reset, 0); + + /* Power on registers module */ + anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], SP_POWERDOWN_CTRL_REG, + SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | SP_LINK_PD); + anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], SP_POWERDOWN_CTRL_REG, + SP_REGISTER_PD | SP_TOTAL_PD); + + if (anx6345->panel) + drm_panel_prepare(anx6345->panel); + + anx6345->powered = true; +} + +static void anx6345_poweroff(struct anx6345 *anx6345) +{ + int err; + + gpiod_set_value_cansleep(anx6345->gpiod_reset, 1); + usleep_range(1000, 2000); + + if (anx6345->panel) + drm_panel_unprepare(anx6345->panel); + + err = regulator_disable(anx6345->dvdd25); + if (err) { + DRM_ERROR("Failed to disable dvdd25 regulator: %d\n", + err); + return; + } + + usleep_range(5000, 10000); + + err = regulator_disable(anx6345->dvdd12); + if (err) { + DRM_ERROR("Failed to disable dvdd12 regulator: %d\n", + err); + return; + } + + usleep_range(1000, 2000); + + anx6345->powered = false; +} + +static int anx6345_start(struct anx6345 *anx6345) +{ + int err; + + if (!anx6345->powered) + anx6345_poweron(anx6345); + + /* Power on needed modules */ + err = anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], + SP_POWERDOWN_CTRL_REG, + SP_VIDEO_PD | SP_LINK_PD); + + err = anx6345_tx_initialization(anx6345); + if (err) { + DRM_ERROR("Failed eDP transmitter initialization: %d\n", err); + anx6345_poweroff(anx6345); + return err; + } + + err = anx6345_dp_link_training(anx6345); + if (err) { + DRM_ERROR("Failed link training: %d\n", err); + anx6345_poweroff(anx6345); + return err; + } + + /* + * This delay seems to help keep the hardware in a good state. Without + * it, there are times where it fails silently. + */ + usleep_range(10000, 15000); + + return 0; +} + +static int anx6345_config_dp_output(struct anx6345 *anx6345) +{ + int err; + + err = anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], SP_VID_CTRL1_REG, + SP_VIDEO_MUTE); + if (err) + return err; + + /* Enable DP output */ + err = anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], SP_VID_CTRL1_REG, + SP_VIDEO_EN); + if (err) + return err; + + /* Force stream valid */ + return anx6345_set_bits(anx6345->map[I2C_IDX_DPTX], + SP_DP_SYSTEM_CTRL_BASE + 3, + SP_STRM_FORCE | SP_STRM_CTRL); +} + +static int anx6345_get_downstream_info(struct anx6345 *anx6345) +{ + u8 value; + int err; + + err = drm_dp_dpcd_readb(&anx6345->aux, DP_SINK_COUNT, &value); + if (err < 0) { + DRM_ERROR("Get sink count failed %d\n", err); + return err; + } + + if (!DP_GET_SINK_COUNT(value)) { + DRM_ERROR("Downstream disconnected\n"); + return -EIO; + } + + return 0; +} + +static int anx6345_get_modes(struct drm_connector *connector) +{ + struct anx6345 *anx6345 = connector_to_anx6345(connector); + int err, num_modes = 0; + bool power_off = false; + + mutex_lock(&anx6345->lock); + + if (!anx6345->edid) { + if (!anx6345->powered) { + anx6345_poweron(anx6345); + power_off = true; + } + + err = anx6345_get_downstream_info(anx6345); + if (err) { + DRM_ERROR("Failed to get downstream info: %d\n", err); + goto unlock; + } + + anx6345->edid = drm_get_edid(connector, &anx6345->aux.ddc); + if (!anx6345->edid) + DRM_ERROR("Failed to read EDID from panel\n"); + + err = drm_connector_update_edid_property(connector, + anx6345->edid); + if (err) { + DRM_ERROR("Failed to update EDID property: %d\n", err); + goto unlock; + } + } + + num_modes += drm_add_edid_modes(connector, anx6345->edid); + + /* Driver currently supports only 6bpc */ + connector->display_info.bpc = 6; + +unlock: + if (power_off) + anx6345_poweroff(anx6345); + + mutex_unlock(&anx6345->lock); + + if (!num_modes && anx6345->panel) + num_modes += drm_panel_get_modes(anx6345->panel, connector); + + return num_modes; +} + +static const struct drm_connector_helper_funcs anx6345_connector_helper_funcs = { + .get_modes = anx6345_get_modes, +}; + +static void +anx6345_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs anx6345_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = anx6345_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int anx6345_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct anx6345 *anx6345 = bridge_to_anx6345(bridge); + int err; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + DRM_ERROR("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + /* Register aux channel */ + anx6345->aux.name = "DP-AUX"; + anx6345->aux.dev = &anx6345->client->dev; + anx6345->aux.transfer = anx6345_aux_transfer; + + err = drm_dp_aux_register(&anx6345->aux); + if (err < 0) { + DRM_ERROR("Failed to register aux channel: %d\n", err); + return err; + } + + err = drm_connector_init(bridge->dev, &anx6345->connector, + &anx6345_connector_funcs, + DRM_MODE_CONNECTOR_eDP); + if (err) { + DRM_ERROR("Failed to initialize connector: %d\n", err); + return err; + } + + drm_connector_helper_add(&anx6345->connector, + &anx6345_connector_helper_funcs); + + err = drm_connector_register(&anx6345->connector); + if (err) { + DRM_ERROR("Failed to register connector: %d\n", err); + return err; + } + + anx6345->connector.polled = DRM_CONNECTOR_POLL_HPD; + + err = drm_connector_attach_encoder(&anx6345->connector, + bridge->encoder); + if (err) { + DRM_ERROR("Failed to link up connector to encoder: %d\n", err); + return err; + } + + return 0; +} + +static enum drm_mode_status +anx6345_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_NO_INTERLACE; + + /* Max 1200p at 5.4 Ghz, one lane */ + if (mode->clock > 154000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static void anx6345_bridge_disable(struct drm_bridge *bridge) +{ + struct anx6345 *anx6345 = bridge_to_anx6345(bridge); + + /* Power off all modules except configuration registers access */ + anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], SP_POWERDOWN_CTRL_REG, + SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | SP_LINK_PD); + if (anx6345->panel) + drm_panel_disable(anx6345->panel); + + if (anx6345->powered) + anx6345_poweroff(anx6345); +} + +static void anx6345_bridge_enable(struct drm_bridge *bridge) +{ + struct anx6345 *anx6345 = bridge_to_anx6345(bridge); + int err; + + if (anx6345->panel) + drm_panel_enable(anx6345->panel); + + err = anx6345_start(anx6345); + if (err) { + DRM_ERROR("Failed to initialize: %d\n", err); + return; + } + + err = anx6345_config_dp_output(anx6345); + if (err) + DRM_ERROR("Failed to enable DP output: %d\n", err); +} + +static const struct drm_bridge_funcs anx6345_bridge_funcs = { + .attach = anx6345_bridge_attach, + .mode_valid = anx6345_bridge_mode_valid, + .disable = anx6345_bridge_disable, + .enable = anx6345_bridge_enable, +}; + +static void unregister_i2c_dummy_clients(struct anx6345 *anx6345) +{ + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(anx6345->i2c_clients); i++) + if (anx6345->i2c_clients[i] && + anx6345->i2c_clients[i]->addr != anx6345->client->addr) + i2c_unregister_device(anx6345->i2c_clients[i]); +} + +static const struct regmap_config anx6345_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_NONE, +}; + +static const u16 anx6345_chipid_list[] = { + 0x6345, +}; + +static bool anx6345_get_chip_id(struct anx6345 *anx6345) +{ + unsigned int i, idl, idh, version; + + if (regmap_read(anx6345->map[I2C_IDX_TXCOM], SP_DEVICE_IDL_REG, &idl)) + return false; + + if (regmap_read(anx6345->map[I2C_IDX_TXCOM], SP_DEVICE_IDH_REG, &idh)) + return false; + + anx6345->chipid = (u8)idl | ((u8)idh << 8); + + if (regmap_read(anx6345->map[I2C_IDX_TXCOM], SP_DEVICE_VERSION_REG, + &version)) + return false; + + for (i = 0; i < ARRAY_SIZE(anx6345_chipid_list); i++) { + if (anx6345->chipid == anx6345_chipid_list[i]) { + DRM_INFO("Found ANX%x (ver. %d) eDP Transmitter\n", + anx6345->chipid, version); + return true; + } + } + + DRM_ERROR("ANX%x (ver. %d) not supported by this driver\n", + anx6345->chipid, version); + + return false; +} + +static int anx6345_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct anx6345 *anx6345; + struct device *dev; + int i, err; + + anx6345 = devm_kzalloc(&client->dev, sizeof(*anx6345), GFP_KERNEL); + if (!anx6345) + return -ENOMEM; + + mutex_init(&anx6345->lock); + + anx6345->bridge.of_node = client->dev.of_node; + + anx6345->client = client; + i2c_set_clientdata(client, anx6345); + + dev = &anx6345->client->dev; + + err = drm_of_find_panel_or_bridge(client->dev.of_node, 1, 0, + &anx6345->panel, NULL); + if (err == -EPROBE_DEFER) + return err; + + if (err) + DRM_DEBUG("No panel found\n"); + + /* 1.2V digital core power regulator */ + anx6345->dvdd12 = devm_regulator_get(dev, "dvdd12"); + if (IS_ERR(anx6345->dvdd12)) { + if (PTR_ERR(anx6345->dvdd12) != -EPROBE_DEFER) + DRM_ERROR("Failed to get dvdd12 supply (%ld)\n", + PTR_ERR(anx6345->dvdd12)); + return PTR_ERR(anx6345->dvdd12); + } + + /* 2.5V digital core power regulator */ + anx6345->dvdd25 = devm_regulator_get(dev, "dvdd25"); + if (IS_ERR(anx6345->dvdd25)) { + if (PTR_ERR(anx6345->dvdd25) != -EPROBE_DEFER) + DRM_ERROR("Failed to get dvdd25 supply (%ld)\n", + PTR_ERR(anx6345->dvdd25)); + return PTR_ERR(anx6345->dvdd25); + } + + /* GPIO for chip reset */ + anx6345->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(anx6345->gpiod_reset)) { + DRM_ERROR("Reset gpio not found\n"); + return PTR_ERR(anx6345->gpiod_reset); + } + + /* Map slave addresses of ANX6345 */ + for (i = 0; i < I2C_NUM_ADDRESSES; i++) { + if (anx6345_i2c_addresses[i] >> 1 != client->addr) + anx6345->i2c_clients[i] = i2c_new_dummy_device(client->adapter, + anx6345_i2c_addresses[i] >> 1); + else + anx6345->i2c_clients[i] = client; + + if (IS_ERR(anx6345->i2c_clients[i])) { + err = PTR_ERR(anx6345->i2c_clients[i]); + DRM_ERROR("Failed to reserve I2C bus %02x\n", + anx6345_i2c_addresses[i]); + goto err_unregister_i2c; + } + + anx6345->map[i] = devm_regmap_init_i2c(anx6345->i2c_clients[i], + &anx6345_regmap_config); + if (IS_ERR(anx6345->map[i])) { + err = PTR_ERR(anx6345->map[i]); + DRM_ERROR("Failed regmap initialization %02x\n", + anx6345_i2c_addresses[i]); + goto err_unregister_i2c; + } + } + + /* Look for supported chip ID */ + anx6345_poweron(anx6345); + if (anx6345_get_chip_id(anx6345)) { + anx6345->bridge.funcs = &anx6345_bridge_funcs; + drm_bridge_add(&anx6345->bridge); + + return 0; + } else { + anx6345_poweroff(anx6345); + err = -ENODEV; + } + +err_unregister_i2c: + unregister_i2c_dummy_clients(anx6345); + return err; +} + +static int anx6345_i2c_remove(struct i2c_client *client) +{ + struct anx6345 *anx6345 = i2c_get_clientdata(client); + + drm_bridge_remove(&anx6345->bridge); + + unregister_i2c_dummy_clients(anx6345); + + kfree(anx6345->edid); + + mutex_destroy(&anx6345->lock); + + return 0; +} + +static const struct i2c_device_id anx6345_id[] = { + { "anx6345", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, anx6345_id); + +static const struct of_device_id anx6345_match_table[] = { + { .compatible = "analogix,anx6345", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, anx6345_match_table); + +static struct i2c_driver anx6345_driver = { + .driver = { + .name = "anx6345", + .of_match_table = of_match_ptr(anx6345_match_table), + }, + .probe = anx6345_i2c_probe, + .remove = anx6345_i2c_remove, + .id_table = anx6345_id, +}; +module_i2c_driver(anx6345_driver); + +MODULE_DESCRIPTION("ANX6345 eDP Transmitter driver"); +MODULE_AUTHOR("Icenowy Zheng <icenowy@aosc.io>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c new file mode 100644 index 000000000..81debd02c --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c @@ -0,0 +1,1391 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(c) 2016, Analogix Semiconductor. + * + * Based on anx7808 driver obtained from chromeos with copyright: + * Copyright(c) 2013, Google Inc. + */ +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/types.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_dp_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "analogix-anx78xx.h" + +#define I2C_NUM_ADDRESSES 5 +#define I2C_IDX_TX_P0 0 +#define I2C_IDX_TX_P1 1 +#define I2C_IDX_TX_P2 2 +#define I2C_IDX_RX_P0 3 +#define I2C_IDX_RX_P1 4 + +#define XTAL_CLK 270 /* 27M */ + +static const u8 anx7808_i2c_addresses[] = { + [I2C_IDX_TX_P0] = 0x78, + [I2C_IDX_TX_P1] = 0x7a, + [I2C_IDX_TX_P2] = 0x72, + [I2C_IDX_RX_P0] = 0x7e, + [I2C_IDX_RX_P1] = 0x80, +}; + +static const u8 anx781x_i2c_addresses[] = { + [I2C_IDX_TX_P0] = 0x70, + [I2C_IDX_TX_P1] = 0x7a, + [I2C_IDX_TX_P2] = 0x72, + [I2C_IDX_RX_P0] = 0x7e, + [I2C_IDX_RX_P1] = 0x80, +}; + +struct anx78xx_platform_data { + struct regulator *dvdd10; + struct gpio_desc *gpiod_hpd; + struct gpio_desc *gpiod_pd; + struct gpio_desc *gpiod_reset; + + int hpd_irq; + int intp_irq; +}; + +struct anx78xx { + struct drm_dp_aux aux; + struct drm_bridge bridge; + struct i2c_client *client; + struct edid *edid; + struct drm_connector connector; + struct anx78xx_platform_data pdata; + struct mutex lock; + + /* + * I2C Slave addresses of ANX7814 are mapped as TX_P0, TX_P1, TX_P2, + * RX_P0 and RX_P1. + */ + struct i2c_client *i2c_dummy[I2C_NUM_ADDRESSES]; + struct regmap *map[I2C_NUM_ADDRESSES]; + + u16 chipid; + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + + bool powered; +}; + +static inline struct anx78xx *connector_to_anx78xx(struct drm_connector *c) +{ + return container_of(c, struct anx78xx, connector); +} + +static inline struct anx78xx *bridge_to_anx78xx(struct drm_bridge *bridge) +{ + return container_of(bridge, struct anx78xx, bridge); +} + +static int anx78xx_set_bits(struct regmap *map, u8 reg, u8 mask) +{ + return regmap_update_bits(map, reg, mask, mask); +} + +static int anx78xx_clear_bits(struct regmap *map, u8 reg, u8 mask) +{ + return regmap_update_bits(map, reg, mask, 0); +} + +static ssize_t anx78xx_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct anx78xx *anx78xx = container_of(aux, struct anx78xx, aux); + return anx_dp_aux_transfer(anx78xx->map[I2C_IDX_TX_P0], msg); +} + +static int anx78xx_set_hpd(struct anx78xx *anx78xx) +{ + int err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_TMDS_CTRL_BASE + 7, SP_PD_RT); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL3_REG, + SP_HPD_OUT); + if (err) + return err; + + return 0; +} + +static int anx78xx_clear_hpd(struct anx78xx *anx78xx) +{ + int err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL3_REG, + SP_HPD_OUT); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_TMDS_CTRL_BASE + 7, SP_PD_RT); + if (err) + return err; + + return 0; +} + +static const struct reg_sequence tmds_phy_initialization[] = { + { SP_TMDS_CTRL_BASE + 1, 0x90 }, + { SP_TMDS_CTRL_BASE + 2, 0xa9 }, + { SP_TMDS_CTRL_BASE + 6, 0x92 }, + { SP_TMDS_CTRL_BASE + 7, 0x80 }, + { SP_TMDS_CTRL_BASE + 20, 0xf2 }, + { SP_TMDS_CTRL_BASE + 22, 0xc4 }, + { SP_TMDS_CTRL_BASE + 23, 0x18 }, +}; + +static int anx78xx_rx_initialization(struct anx78xx *anx78xx) +{ + int err; + + err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], SP_HDMI_MUTE_CTRL_REG, + SP_AUD_MUTE | SP_VID_MUTE); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], SP_CHIP_CTRL_REG, + SP_MAN_HDMI5V_DET | SP_PLLLOCK_CKDT_EN | + SP_DIGITAL_CKDT_EN); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_SOFTWARE_RESET1_REG, SP_HDCP_MAN_RST | + SP_SW_MAN_RST | SP_TMDS_RST | SP_VIDEO_RST); + if (err) + return err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_SOFTWARE_RESET1_REG, SP_HDCP_MAN_RST | + SP_SW_MAN_RST | SP_TMDS_RST | SP_VIDEO_RST); + if (err) + return err; + + /* Sync detect change, GP set mute */ + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_AUD_EXCEPTION_ENABLE_BASE + 1, BIT(5) | + BIT(6)); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_AUD_EXCEPTION_ENABLE_BASE + 3, + SP_AEC_EN21); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], SP_AUDVID_CTRL_REG, + SP_AVC_EN | SP_AAC_OE | SP_AAC_EN); + if (err) + return err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_SYSTEM_POWER_DOWN1_REG, SP_PWDN_CTRL); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_VID_DATA_RANGE_CTRL_REG, SP_R2Y_INPUT_LIMIT); + if (err) + return err; + + /* Enable DDC stretch */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_EXTRA_I2C_DEV_ADDR_REG, SP_I2C_EXTRA_ADDR); + if (err) + return err; + + /* TMDS phy initialization */ + err = regmap_multi_reg_write(anx78xx->map[I2C_IDX_RX_P0], + tmds_phy_initialization, + ARRAY_SIZE(tmds_phy_initialization)); + if (err) + return err; + + err = anx78xx_clear_hpd(anx78xx); + if (err) + return err; + + return 0; +} + +static const u8 dp_tx_output_precise_tune_bits[20] = { + 0x01, 0x03, 0x07, 0x7f, 0x71, 0x6b, 0x7f, + 0x73, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x42, 0x1e, 0x3e, 0x72, 0x7e, +}; + +static int anx78xx_link_phy_initialization(struct anx78xx *anx78xx) +{ + int err; + + /* + * REVISIT : It is writing to a RESERVED bits in Analog Control 0 + * register. + */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_ANALOG_CTRL0_REG, + 0x02); + if (err) + return err; + + /* + * Write DP TX output emphasis precise tune bits. + */ + err = regmap_bulk_write(anx78xx->map[I2C_IDX_TX_P1], + SP_DP_TX_LT_CTRL0_REG, + dp_tx_output_precise_tune_bits, + ARRAY_SIZE(dp_tx_output_precise_tune_bits)); + + if (err) + return err; + + return 0; +} + +static int anx78xx_xtal_clk_sel(struct anx78xx *anx78xx) +{ + unsigned int value; + int err; + + err = regmap_update_bits(anx78xx->map[I2C_IDX_TX_P2], + SP_ANALOG_DEBUG2_REG, + SP_XTAL_FRQ | SP_FORCE_SW_OFF_BYPASS, + SP_XTAL_FRQ_27M); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_DP_AUX_CH_CTRL3_REG, + XTAL_CLK & SP_WAIT_COUNTER_7_0_MASK); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_DP_AUX_CH_CTRL4_REG, + ((XTAL_CLK & 0xff00) >> 2) | (XTAL_CLK / 10)); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_I2C_GEN_10US_TIMER0_REG, XTAL_CLK & 0xff); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_I2C_GEN_10US_TIMER1_REG, + (XTAL_CLK & 0xff00) >> 8); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_AUX_MISC_CTRL_REG, + XTAL_CLK / 10 - 1); + if (err) + return err; + + err = regmap_read(anx78xx->map[I2C_IDX_RX_P0], + SP_HDMI_US_TIMER_CTRL_REG, + &value); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], + SP_HDMI_US_TIMER_CTRL_REG, + (value & SP_MS_TIMER_MARGIN_10_8_MASK) | + ((((XTAL_CLK / 10) >> 1) - 2) << 3)); + if (err) + return err; + + return 0; +} + +static const struct reg_sequence otp_key_protect[] = { + { SP_OTP_KEY_PROTECT1_REG, SP_OTP_PSW1 }, + { SP_OTP_KEY_PROTECT2_REG, SP_OTP_PSW2 }, + { SP_OTP_KEY_PROTECT3_REG, SP_OTP_PSW3 }, +}; + +static int anx78xx_tx_initialization(struct anx78xx *anx78xx) +{ + int err; + + /* Set terminal resistor to 50 ohm */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_DP_AUX_CH_CTRL2_REG, + 0x30); + if (err) + return err; + + /* Enable aux double diff output */ + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_AUX_CH_CTRL2_REG, 0x08); + if (err) + return err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_HDCP_CTRL_REG, SP_AUTO_EN | + SP_AUTO_START); + if (err) + return err; + + err = regmap_multi_reg_write(anx78xx->map[I2C_IDX_TX_P0], + otp_key_protect, + ARRAY_SIZE(otp_key_protect)); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_HDCP_KEY_COMMAND_REG, SP_DISABLE_SYNC_HDCP); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL8_REG, + SP_VID_VRES_TH); + if (err) + return err; + + /* + * DP HDCP auto authentication wait timer (when downstream starts to + * auth, DP side will wait for this period then do auth automatically) + */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_HDCP_AUTO_TIMER_REG, + 0x00); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_HDCP_CTRL_REG, SP_LINK_POLLING); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_LINK_DEBUG_CTRL_REG, SP_M_VID_DEBUG); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], + SP_ANALOG_DEBUG2_REG, SP_POWERON_TIME_1P5MS); + if (err) + return err; + + err = anx78xx_xtal_clk_sel(anx78xx); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_AUX_DEFER_CTRL_REG, + SP_DEFER_CTRL_EN | 0x0c); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_POLLING_CTRL_REG, + SP_AUTO_POLLING_DISABLE); + if (err) + return err; + + /* + * Short the link integrity check timer to speed up bstatus + * polling for HDCP CTS item 1A-07 + */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_HDCP_LINK_CHECK_TIMER_REG, 0x1d); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_MISC_CTRL_REG, SP_EQ_TRAINING_LOOP); + if (err) + return err; + + /* Power down the main link by default */ + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_ANALOG_POWER_DOWN_REG, SP_CH0_PD); + if (err) + return err; + + err = anx78xx_link_phy_initialization(anx78xx); + if (err) + return err; + + /* Gen m_clk with downspreading */ + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_M_CALCULATION_CTRL_REG, SP_M_GEN_CLK_SEL); + if (err) + return err; + + return 0; +} + +static int anx78xx_enable_interrupts(struct anx78xx *anx78xx) +{ + int err; + + /* + * BIT0: INT pin assertion polarity: 1 = assert high + * BIT1: INT pin output type: 0 = push/pull + */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_INT_CTRL_REG, 0x01); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], + SP_COMMON_INT_MASK4_REG, SP_HPD_LOST | SP_HPD_PLUG); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_DP_INT_MASK1_REG, + SP_TRAINING_FINISH); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], SP_INT_MASK1_REG, + SP_CKDT_CHG | SP_SCDT_CHG); + if (err) + return err; + + return 0; +} + +static void anx78xx_poweron(struct anx78xx *anx78xx) +{ + struct anx78xx_platform_data *pdata = &anx78xx->pdata; + int err; + + if (WARN_ON(anx78xx->powered)) + return; + + if (pdata->dvdd10) { + err = regulator_enable(pdata->dvdd10); + if (err) { + DRM_ERROR("Failed to enable DVDD10 regulator: %d\n", + err); + return; + } + + usleep_range(1000, 2000); + } + + gpiod_set_value_cansleep(pdata->gpiod_reset, 1); + usleep_range(1000, 2000); + + gpiod_set_value_cansleep(pdata->gpiod_pd, 0); + usleep_range(1000, 2000); + + gpiod_set_value_cansleep(pdata->gpiod_reset, 0); + + /* Power on registers module */ + anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_POWERDOWN_CTRL_REG, + SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | SP_LINK_PD); + anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], SP_POWERDOWN_CTRL_REG, + SP_REGISTER_PD | SP_TOTAL_PD); + + anx78xx->powered = true; +} + +static void anx78xx_poweroff(struct anx78xx *anx78xx) +{ + struct anx78xx_platform_data *pdata = &anx78xx->pdata; + int err; + + if (WARN_ON(!anx78xx->powered)) + return; + + gpiod_set_value_cansleep(pdata->gpiod_reset, 1); + usleep_range(1000, 2000); + + gpiod_set_value_cansleep(pdata->gpiod_pd, 1); + usleep_range(1000, 2000); + + if (pdata->dvdd10) { + err = regulator_disable(pdata->dvdd10); + if (err) { + DRM_ERROR("Failed to disable DVDD10 regulator: %d\n", + err); + return; + } + + usleep_range(1000, 2000); + } + + anx78xx->powered = false; +} + +static int anx78xx_start(struct anx78xx *anx78xx) +{ + int err; + + /* Power on all modules */ + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], + SP_POWERDOWN_CTRL_REG, + SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | + SP_LINK_PD); + + err = anx78xx_enable_interrupts(anx78xx); + if (err) { + DRM_ERROR("Failed to enable interrupts: %d\n", err); + goto err_poweroff; + } + + err = anx78xx_rx_initialization(anx78xx); + if (err) { + DRM_ERROR("Failed receiver initialization: %d\n", err); + goto err_poweroff; + } + + err = anx78xx_tx_initialization(anx78xx); + if (err) { + DRM_ERROR("Failed transmitter initialization: %d\n", err); + goto err_poweroff; + } + + /* + * This delay seems to help keep the hardware in a good state. Without + * it, there are times where it fails silently. + */ + usleep_range(10000, 15000); + + return 0; + +err_poweroff: + DRM_ERROR("Failed SlimPort transmitter initialization: %d\n", err); + anx78xx_poweroff(anx78xx); + + return err; +} + +static int anx78xx_init_pdata(struct anx78xx *anx78xx) +{ + struct anx78xx_platform_data *pdata = &anx78xx->pdata; + struct device *dev = &anx78xx->client->dev; + + /* 1.0V digital core power regulator */ + pdata->dvdd10 = devm_regulator_get(dev, "dvdd10"); + if (IS_ERR(pdata->dvdd10)) { + if (PTR_ERR(pdata->dvdd10) != -EPROBE_DEFER) + DRM_ERROR("DVDD10 regulator not found\n"); + + return PTR_ERR(pdata->dvdd10); + } + + /* GPIO for HPD */ + pdata->gpiod_hpd = devm_gpiod_get(dev, "hpd", GPIOD_IN); + if (IS_ERR(pdata->gpiod_hpd)) + return PTR_ERR(pdata->gpiod_hpd); + + /* GPIO for chip power down */ + pdata->gpiod_pd = devm_gpiod_get(dev, "pd", GPIOD_OUT_HIGH); + if (IS_ERR(pdata->gpiod_pd)) + return PTR_ERR(pdata->gpiod_pd); + + /* GPIO for chip reset */ + pdata->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + + return PTR_ERR_OR_ZERO(pdata->gpiod_reset); +} + +static int anx78xx_dp_link_training(struct anx78xx *anx78xx) +{ + u8 dp_bw, dpcd[2]; + int err; + + err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], SP_HDMI_MUTE_CTRL_REG, + 0x0); + if (err) + return err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], + SP_POWERDOWN_CTRL_REG, + SP_TOTAL_PD); + if (err) + return err; + + err = drm_dp_dpcd_readb(&anx78xx->aux, DP_MAX_LINK_RATE, &dp_bw); + if (err < 0) + return err; + + switch (dp_bw) { + case DP_LINK_BW_1_62: + case DP_LINK_BW_2_7: + case DP_LINK_BW_5_4: + break; + + default: + DRM_DEBUG_KMS("DP bandwidth (%#02x) not supported\n", dp_bw); + return -EINVAL; + } + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL1_REG, + SP_VIDEO_MUTE); + if (err) + return err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], + SP_VID_CTRL1_REG, SP_VIDEO_EN); + if (err) + return err; + + /* Get DPCD info */ + err = drm_dp_dpcd_read(&anx78xx->aux, DP_DPCD_REV, + &anx78xx->dpcd, DP_RECEIVER_CAP_SIZE); + if (err < 0) { + DRM_ERROR("Failed to read DPCD: %d\n", err); + return err; + } + + /* Clear channel x SERDES power down */ + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_ANALOG_POWER_DOWN_REG, SP_CH0_PD); + if (err) + return err; + + /* + * Power up the sink (DP_SET_POWER register is only available on DPCD + * v1.1 and later). + */ + if (anx78xx->dpcd[DP_DPCD_REV] >= 0x11) { + err = drm_dp_dpcd_readb(&anx78xx->aux, DP_SET_POWER, &dpcd[0]); + if (err < 0) { + DRM_ERROR("Failed to read DP_SET_POWER register: %d\n", + err); + return err; + } + + dpcd[0] &= ~DP_SET_POWER_MASK; + dpcd[0] |= DP_SET_POWER_D0; + + err = drm_dp_dpcd_writeb(&anx78xx->aux, DP_SET_POWER, dpcd[0]); + if (err < 0) { + DRM_ERROR("Failed to power up DisplayPort link: %d\n", + err); + return err; + } + + /* + * According to the DP 1.1 specification, a "Sink Device must + * exit the power saving state within 1 ms" (Section 2.5.3.1, + * Table 5-52, "Sink Control Field" (register 0x600). + */ + usleep_range(1000, 2000); + } + + /* Possibly enable downspread on the sink */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_DOWNSPREAD_CTRL1_REG, 0); + if (err) + return err; + + if (anx78xx->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5) { + DRM_DEBUG("Enable downspread on the sink\n"); + /* 4000PPM */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_DOWNSPREAD_CTRL1_REG, 8); + if (err) + return err; + + err = drm_dp_dpcd_writeb(&anx78xx->aux, DP_DOWNSPREAD_CTRL, + DP_SPREAD_AMP_0_5); + if (err < 0) + return err; + } else { + err = drm_dp_dpcd_writeb(&anx78xx->aux, DP_DOWNSPREAD_CTRL, 0); + if (err < 0) + return err; + } + + /* Set the lane count and the link rate on the sink */ + if (drm_dp_enhanced_frame_cap(anx78xx->dpcd)) + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_SYSTEM_CTRL_BASE + 4, + SP_ENHANCED_MODE); + else + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_SYSTEM_CTRL_BASE + 4, + SP_ENHANCED_MODE); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_MAIN_LINK_BW_SET_REG, + anx78xx->dpcd[DP_MAX_LINK_RATE]); + if (err) + return err; + + dpcd[1] = drm_dp_max_lane_count(anx78xx->dpcd); + + if (drm_dp_enhanced_frame_cap(anx78xx->dpcd)) + dpcd[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + + err = drm_dp_dpcd_write(&anx78xx->aux, DP_LINK_BW_SET, dpcd, + sizeof(dpcd)); + if (err < 0) { + DRM_ERROR("Failed to configure link: %d\n", err); + return err; + } + + /* Start training on the source */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_DP_LT_CTRL_REG, + SP_LT_EN); + if (err) + return err; + + return 0; +} + +static int anx78xx_config_dp_output(struct anx78xx *anx78xx) +{ + int err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL1_REG, + SP_VIDEO_MUTE); + if (err) + return err; + + /* Enable DP output */ + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL1_REG, + SP_VIDEO_EN); + if (err) + return err; + + return 0; +} + +static int anx78xx_send_video_infoframe(struct anx78xx *anx78xx, + struct hdmi_avi_infoframe *frame) +{ + u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE]; + int err; + + err = hdmi_avi_infoframe_pack(frame, buffer, sizeof(buffer)); + if (err < 0) { + DRM_ERROR("Failed to pack AVI infoframe: %d\n", err); + return err; + } + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_PACKET_SEND_CTRL_REG, SP_AVI_IF_EN); + if (err) + return err; + + err = regmap_bulk_write(anx78xx->map[I2C_IDX_TX_P2], + SP_INFOFRAME_AVI_DB1_REG, buffer, + frame->length); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_PACKET_SEND_CTRL_REG, SP_AVI_IF_UD); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_PACKET_SEND_CTRL_REG, SP_AVI_IF_EN); + if (err) + return err; + + return 0; +} + +static int anx78xx_get_downstream_info(struct anx78xx *anx78xx) +{ + u8 value; + int err; + + err = drm_dp_dpcd_readb(&anx78xx->aux, DP_SINK_COUNT, &value); + if (err < 0) { + DRM_ERROR("Get sink count failed %d\n", err); + return err; + } + + if (!DP_GET_SINK_COUNT(value)) { + DRM_ERROR("Downstream disconnected\n"); + return -EIO; + } + + return 0; +} + +static int anx78xx_get_modes(struct drm_connector *connector) +{ + struct anx78xx *anx78xx = connector_to_anx78xx(connector); + int err, num_modes = 0; + + if (WARN_ON(!anx78xx->powered)) + return 0; + + if (anx78xx->edid) + return drm_add_edid_modes(connector, anx78xx->edid); + + mutex_lock(&anx78xx->lock); + + err = anx78xx_get_downstream_info(anx78xx); + if (err) { + DRM_ERROR("Failed to get downstream info: %d\n", err); + goto unlock; + } + + anx78xx->edid = drm_get_edid(connector, &anx78xx->aux.ddc); + if (!anx78xx->edid) { + DRM_ERROR("Failed to read EDID\n"); + goto unlock; + } + + err = drm_connector_update_edid_property(connector, + anx78xx->edid); + if (err) { + DRM_ERROR("Failed to update EDID property: %d\n", err); + goto unlock; + } + + num_modes = drm_add_edid_modes(connector, anx78xx->edid); + +unlock: + mutex_unlock(&anx78xx->lock); + + return num_modes; +} + +static const struct drm_connector_helper_funcs anx78xx_connector_helper_funcs = { + .get_modes = anx78xx_get_modes, +}; + +static enum drm_connector_status anx78xx_detect(struct drm_connector *connector, + bool force) +{ + struct anx78xx *anx78xx = connector_to_anx78xx(connector); + + if (!gpiod_get_value(anx78xx->pdata.gpiod_hpd)) + return connector_status_disconnected; + + return connector_status_connected; +} + +static const struct drm_connector_funcs anx78xx_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = anx78xx_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int anx78xx_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); + int err; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + DRM_ERROR("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + /* Register aux channel */ + anx78xx->aux.name = "DP-AUX"; + anx78xx->aux.dev = &anx78xx->client->dev; + anx78xx->aux.transfer = anx78xx_aux_transfer; + + err = drm_dp_aux_register(&anx78xx->aux); + if (err < 0) { + DRM_ERROR("Failed to register aux channel: %d\n", err); + return err; + } + + err = drm_connector_init(bridge->dev, &anx78xx->connector, + &anx78xx_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (err) { + DRM_ERROR("Failed to initialize connector: %d\n", err); + return err; + } + + drm_connector_helper_add(&anx78xx->connector, + &anx78xx_connector_helper_funcs); + + err = drm_connector_register(&anx78xx->connector); + if (err) { + DRM_ERROR("Failed to register connector: %d\n", err); + return err; + } + + anx78xx->connector.polled = DRM_CONNECTOR_POLL_HPD; + + err = drm_connector_attach_encoder(&anx78xx->connector, + bridge->encoder); + if (err) { + DRM_ERROR("Failed to link up connector to encoder: %d\n", err); + return err; + } + + return 0; +} + +static enum drm_mode_status +anx78xx_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_NO_INTERLACE; + + /* Max 1200p at 5.4 Ghz, one lane */ + if (mode->clock > 154000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static void anx78xx_bridge_disable(struct drm_bridge *bridge) +{ + struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); + + /* Power off all modules except configuration registers access */ + anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_POWERDOWN_CTRL_REG, + SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | SP_LINK_PD); +} + +static void anx78xx_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); + struct hdmi_avi_infoframe frame; + int err; + + if (WARN_ON(!anx78xx->powered)) + return; + + mutex_lock(&anx78xx->lock); + + err = drm_hdmi_avi_infoframe_from_display_mode(&frame, + &anx78xx->connector, + adjusted_mode); + if (err) { + DRM_ERROR("Failed to setup AVI infoframe: %d\n", err); + goto unlock; + } + + err = anx78xx_send_video_infoframe(anx78xx, &frame); + if (err) + DRM_ERROR("Failed to send AVI infoframe: %d\n", err); + +unlock: + mutex_unlock(&anx78xx->lock); +} + +static void anx78xx_bridge_enable(struct drm_bridge *bridge) +{ + struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); + int err; + + err = anx78xx_start(anx78xx); + if (err) { + DRM_ERROR("Failed to initialize: %d\n", err); + return; + } + + err = anx78xx_set_hpd(anx78xx); + if (err) + DRM_ERROR("Failed to set HPD: %d\n", err); +} + +static const struct drm_bridge_funcs anx78xx_bridge_funcs = { + .attach = anx78xx_bridge_attach, + .mode_valid = anx78xx_bridge_mode_valid, + .disable = anx78xx_bridge_disable, + .mode_set = anx78xx_bridge_mode_set, + .enable = anx78xx_bridge_enable, +}; + +static irqreturn_t anx78xx_hpd_threaded_handler(int irq, void *data) +{ + struct anx78xx *anx78xx = data; + int err; + + if (anx78xx->powered) + return IRQ_HANDLED; + + mutex_lock(&anx78xx->lock); + + /* Cable is pulled, power on the chip */ + anx78xx_poweron(anx78xx); + + err = anx78xx_enable_interrupts(anx78xx); + if (err) + DRM_ERROR("Failed to enable interrupts: %d\n", err); + + mutex_unlock(&anx78xx->lock); + + return IRQ_HANDLED; +} + +static int anx78xx_handle_dp_int_1(struct anx78xx *anx78xx, u8 irq) +{ + int err; + + DRM_DEBUG_KMS("Handle DP interrupt 1: %02x\n", irq); + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_DP_INT_STATUS1_REG, + irq); + if (err) + return err; + + if (irq & SP_TRAINING_FINISH) { + DRM_DEBUG_KMS("IRQ: hardware link training finished\n"); + err = anx78xx_config_dp_output(anx78xx); + } + + return err; +} + +static bool anx78xx_handle_common_int_4(struct anx78xx *anx78xx, u8 irq) +{ + bool event = false; + int err; + + DRM_DEBUG_KMS("Handle common interrupt 4: %02x\n", irq); + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], + SP_COMMON_INT_STATUS4_REG, irq); + if (err) { + DRM_ERROR("Failed to write SP_COMMON_INT_STATUS4 %d\n", err); + return event; + } + + if (irq & SP_HPD_LOST) { + DRM_DEBUG_KMS("IRQ: Hot plug detect - cable is pulled out\n"); + event = true; + anx78xx_poweroff(anx78xx); + /* Free cached EDID */ + kfree(anx78xx->edid); + anx78xx->edid = NULL; + } else if (irq & SP_HPD_PLUG) { + DRM_DEBUG_KMS("IRQ: Hot plug detect - cable plug\n"); + event = true; + } + + return event; +} + +static void anx78xx_handle_hdmi_int_1(struct anx78xx *anx78xx, u8 irq) +{ + unsigned int value; + int err; + + DRM_DEBUG_KMS("Handle HDMI interrupt 1: %02x\n", irq); + + err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], SP_INT_STATUS1_REG, + irq); + if (err) { + DRM_ERROR("Write HDMI int 1 failed: %d\n", err); + return; + } + + if ((irq & SP_CKDT_CHG) || (irq & SP_SCDT_CHG)) { + DRM_DEBUG_KMS("IRQ: HDMI input detected\n"); + + err = regmap_read(anx78xx->map[I2C_IDX_RX_P0], + SP_SYSTEM_STATUS_REG, &value); + if (err) { + DRM_ERROR("Read system status reg failed: %d\n", err); + return; + } + + if (!(value & SP_TMDS_CLOCK_DET)) { + DRM_DEBUG_KMS("IRQ: *** Waiting for HDMI clock ***\n"); + return; + } + + if (!(value & SP_TMDS_DE_DET)) { + DRM_DEBUG_KMS("IRQ: *** Waiting for HDMI signal ***\n"); + return; + } + + err = anx78xx_dp_link_training(anx78xx); + if (err) + DRM_ERROR("Failed to start link training: %d\n", err); + } +} + +static irqreturn_t anx78xx_intp_threaded_handler(int unused, void *data) +{ + struct anx78xx *anx78xx = data; + bool event = false; + unsigned int irq; + int err; + + mutex_lock(&anx78xx->lock); + + err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], SP_DP_INT_STATUS1_REG, + &irq); + if (err) { + DRM_ERROR("Failed to read DP interrupt 1 status: %d\n", err); + goto unlock; + } + + if (irq) + anx78xx_handle_dp_int_1(anx78xx, irq); + + err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], + SP_COMMON_INT_STATUS4_REG, &irq); + if (err) { + DRM_ERROR("Failed to read common interrupt 4 status: %d\n", + err); + goto unlock; + } + + if (irq) + event = anx78xx_handle_common_int_4(anx78xx, irq); + + /* Make sure we are still powered after handle HPD events */ + if (!anx78xx->powered) + goto unlock; + + err = regmap_read(anx78xx->map[I2C_IDX_RX_P0], SP_INT_STATUS1_REG, + &irq); + if (err) { + DRM_ERROR("Failed to read HDMI int 1 status: %d\n", err); + goto unlock; + } + + if (irq) + anx78xx_handle_hdmi_int_1(anx78xx, irq); + +unlock: + mutex_unlock(&anx78xx->lock); + + if (event) + drm_helper_hpd_irq_event(anx78xx->connector.dev); + + return IRQ_HANDLED; +} + +static void unregister_i2c_dummy_clients(struct anx78xx *anx78xx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(anx78xx->i2c_dummy); i++) + i2c_unregister_device(anx78xx->i2c_dummy[i]); +} + +static const struct regmap_config anx78xx_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const u16 anx78xx_chipid_list[] = { + 0x7808, + 0x7812, + 0x7814, + 0x7818, +}; + +static int anx78xx_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct anx78xx *anx78xx; + struct anx78xx_platform_data *pdata; + unsigned int i, idl, idh, version; + const u8 *i2c_addresses; + bool found = false; + int err; + + anx78xx = devm_kzalloc(&client->dev, sizeof(*anx78xx), GFP_KERNEL); + if (!anx78xx) + return -ENOMEM; + + pdata = &anx78xx->pdata; + + mutex_init(&anx78xx->lock); + +#if IS_ENABLED(CONFIG_OF) + anx78xx->bridge.of_node = client->dev.of_node; +#endif + + anx78xx->client = client; + i2c_set_clientdata(client, anx78xx); + + err = anx78xx_init_pdata(anx78xx); + if (err) { + if (err != -EPROBE_DEFER) + DRM_ERROR("Failed to initialize pdata: %d\n", err); + + return err; + } + + pdata->hpd_irq = gpiod_to_irq(pdata->gpiod_hpd); + if (pdata->hpd_irq < 0) { + DRM_ERROR("Failed to get HPD IRQ: %d\n", pdata->hpd_irq); + return -ENODEV; + } + + pdata->intp_irq = client->irq; + if (!pdata->intp_irq) { + DRM_ERROR("Failed to get CABLE_DET and INTP IRQ\n"); + return -ENODEV; + } + + /* Map slave addresses of ANX7814 */ + i2c_addresses = device_get_match_data(&client->dev); + for (i = 0; i < I2C_NUM_ADDRESSES; i++) { + struct i2c_client *i2c_dummy; + + i2c_dummy = i2c_new_dummy_device(client->adapter, + i2c_addresses[i] >> 1); + if (IS_ERR(i2c_dummy)) { + err = PTR_ERR(i2c_dummy); + DRM_ERROR("Failed to reserve I2C bus %02x: %d\n", + i2c_addresses[i], err); + goto err_unregister_i2c; + } + + anx78xx->i2c_dummy[i] = i2c_dummy; + anx78xx->map[i] = devm_regmap_init_i2c(anx78xx->i2c_dummy[i], + &anx78xx_regmap_config); + if (IS_ERR(anx78xx->map[i])) { + err = PTR_ERR(anx78xx->map[i]); + DRM_ERROR("Failed regmap initialization %02x\n", + i2c_addresses[i]); + goto err_unregister_i2c; + } + } + + /* Look for supported chip ID */ + anx78xx_poweron(anx78xx); + + err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], SP_DEVICE_IDL_REG, + &idl); + if (err) + goto err_poweroff; + + err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], SP_DEVICE_IDH_REG, + &idh); + if (err) + goto err_poweroff; + + anx78xx->chipid = (u8)idl | ((u8)idh << 8); + + err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], SP_DEVICE_VERSION_REG, + &version); + if (err) + goto err_poweroff; + + for (i = 0; i < ARRAY_SIZE(anx78xx_chipid_list); i++) { + if (anx78xx->chipid == anx78xx_chipid_list[i]) { + DRM_INFO("Found ANX%x (ver. %d) SlimPort Transmitter\n", + anx78xx->chipid, version); + found = true; + break; + } + } + + if (!found) { + DRM_ERROR("ANX%x (ver. %d) not supported by this driver\n", + anx78xx->chipid, version); + err = -ENODEV; + goto err_poweroff; + } + + err = devm_request_threaded_irq(&client->dev, pdata->hpd_irq, NULL, + anx78xx_hpd_threaded_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "anx78xx-hpd", anx78xx); + if (err) { + DRM_ERROR("Failed to request CABLE_DET threaded IRQ: %d\n", + err); + goto err_poweroff; + } + + err = devm_request_threaded_irq(&client->dev, pdata->intp_irq, NULL, + anx78xx_intp_threaded_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "anx78xx-intp", anx78xx); + if (err) { + DRM_ERROR("Failed to request INTP threaded IRQ: %d\n", err); + goto err_poweroff; + } + + anx78xx->bridge.funcs = &anx78xx_bridge_funcs; + + drm_bridge_add(&anx78xx->bridge); + + /* If cable is pulled out, just poweroff and wait for HPD event */ + if (!gpiod_get_value(anx78xx->pdata.gpiod_hpd)) + anx78xx_poweroff(anx78xx); + + return 0; + +err_poweroff: + anx78xx_poweroff(anx78xx); + +err_unregister_i2c: + unregister_i2c_dummy_clients(anx78xx); + return err; +} + +static int anx78xx_i2c_remove(struct i2c_client *client) +{ + struct anx78xx *anx78xx = i2c_get_clientdata(client); + + drm_bridge_remove(&anx78xx->bridge); + + unregister_i2c_dummy_clients(anx78xx); + + kfree(anx78xx->edid); + + return 0; +} + +static const struct i2c_device_id anx78xx_id[] = { + { "anx7814", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, anx78xx_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id anx78xx_match_table[] = { + { .compatible = "analogix,anx7808", .data = anx7808_i2c_addresses }, + { .compatible = "analogix,anx7812", .data = anx781x_i2c_addresses }, + { .compatible = "analogix,anx7814", .data = anx781x_i2c_addresses }, + { .compatible = "analogix,anx7818", .data = anx781x_i2c_addresses }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, anx78xx_match_table); +#endif + +static struct i2c_driver anx78xx_driver = { + .driver = { + .name = "anx7814", + .of_match_table = of_match_ptr(anx78xx_match_table), + }, + .probe = anx78xx_i2c_probe, + .remove = anx78xx_i2c_remove, + .id_table = anx78xx_id, +}; +module_i2c_driver(anx78xx_driver); + +MODULE_DESCRIPTION("ANX78xx SlimPort Transmitter driver"); +MODULE_AUTHOR("Enric Balletbo i Serra <enric.balletbo@collabora.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.h b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.h new file mode 100644 index 000000000..db2a2725a --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.h @@ -0,0 +1,249 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2016, Analogix Semiconductor. All rights reserved. + */ + +#ifndef __ANX78xx_H +#define __ANX78xx_H + +#include "analogix-i2c-dptx.h" +#include "analogix-i2c-txcommon.h" + +/***************************************************************/ +/* Register definitions for RX_PO */ +/***************************************************************/ + +/* + * System Control and Status + */ + +/* Software Reset Register 1 */ +#define SP_SOFTWARE_RESET1_REG 0x11 +#define SP_VIDEO_RST BIT(4) +#define SP_HDCP_MAN_RST BIT(2) +#define SP_TMDS_RST BIT(1) +#define SP_SW_MAN_RST BIT(0) + +/* System Status Register */ +#define SP_SYSTEM_STATUS_REG 0x14 +#define SP_TMDS_CLOCK_DET BIT(1) +#define SP_TMDS_DE_DET BIT(0) + +/* HDMI Status Register */ +#define SP_HDMI_STATUS_REG 0x15 +#define SP_HDMI_AUD_LAYOUT BIT(3) +#define SP_HDMI_DET BIT(0) +# define SP_DVI_MODE 0 +# define SP_HDMI_MODE 1 + +/* HDMI Mute Control Register */ +#define SP_HDMI_MUTE_CTRL_REG 0x16 +#define SP_AUD_MUTE BIT(1) +#define SP_VID_MUTE BIT(0) + +/* System Power Down Register 1 */ +#define SP_SYSTEM_POWER_DOWN1_REG 0x18 +#define SP_PWDN_CTRL BIT(0) + +/* + * Audio and Video Auto Control + */ + +/* Auto Audio and Video Control register */ +#define SP_AUDVID_CTRL_REG 0x20 +#define SP_AVC_OE BIT(7) +#define SP_AAC_OE BIT(6) +#define SP_AVC_EN BIT(1) +#define SP_AAC_EN BIT(0) + +/* Audio Exception Enable Registers */ +#define SP_AUD_EXCEPTION_ENABLE_BASE (0x24 - 1) +/* Bits for Audio Exception Enable Register 3 */ +#define SP_AEC_EN21 BIT(5) + +/* + * Interrupt + */ + +/* Interrupt Status Register 1 */ +#define SP_INT_STATUS1_REG 0x31 +/* Bits for Interrupt Status Register 1 */ +#define SP_HDMI_DVI BIT(7) +#define SP_CKDT_CHG BIT(6) +#define SP_SCDT_CHG BIT(5) +#define SP_PCLK_CHG BIT(4) +#define SP_PLL_UNLOCK BIT(3) +#define SP_CABLE_PLUG_CHG BIT(2) +#define SP_SET_MUTE BIT(1) +#define SP_SW_INTR BIT(0) +/* Bits for Interrupt Status Register 2 */ +#define SP_HDCP_ERR BIT(5) +#define SP_AUDIO_SAMPLE_CHG BIT(0) /* undocumented */ +/* Bits for Interrupt Status Register 3 */ +#define SP_AUD_MODE_CHG BIT(0) +/* Bits for Interrupt Status Register 5 */ +#define SP_AUDIO_RCV BIT(0) +/* Bits for Interrupt Status Register 6 */ +#define SP_INT_STATUS6_REG 0x36 +#define SP_CTS_RCV BIT(7) +#define SP_NEW_AUD_PKT BIT(4) +#define SP_NEW_AVI_PKT BIT(1) +#define SP_NEW_CP_PKT BIT(0) +/* Bits for Interrupt Status Register 7 */ +#define SP_NO_VSI BIT(7) +#define SP_NEW_VS BIT(4) + +/* Interrupt Mask 1 Status Registers */ +#define SP_INT_MASK1_REG 0x41 + +/* HDMI US TIMER Control Register */ +#define SP_HDMI_US_TIMER_CTRL_REG 0x49 +#define SP_MS_TIMER_MARGIN_10_8_MASK 0x07 + +/* + * TMDS Control + */ + +/* TMDS Control Registers */ +#define SP_TMDS_CTRL_BASE (0x50 - 1) +/* Bits for TMDS Control Register 7 */ +#define SP_PD_RT BIT(0) + +/* + * Video Control + */ + +/* Video Status Register */ +#define SP_VIDEO_STATUS_REG 0x70 +#define SP_COLOR_DEPTH_MASK 0xf0 +#define SP_COLOR_DEPTH_SHIFT 4 +# define SP_COLOR_DEPTH_MODE_LEGACY 0x00 +# define SP_COLOR_DEPTH_MODE_24BIT 0x04 +# define SP_COLOR_DEPTH_MODE_30BIT 0x05 +# define SP_COLOR_DEPTH_MODE_36BIT 0x06 +# define SP_COLOR_DEPTH_MODE_48BIT 0x07 + +/* Video Data Range Control Register */ +#define SP_VID_DATA_RANGE_CTRL_REG 0x83 +#define SP_R2Y_INPUT_LIMIT BIT(1) + +/* Pixel Clock High Resolution Counter Registers */ +#define SP_PCLK_HIGHRES_CNT_BASE (0x8c - 1) + +/* + * Audio Control + */ + +/* Number of Audio Channels Status Registers */ +#define SP_AUD_CH_STATUS_REG_NUM 6 + +/* Audio IN S/PDIF Channel Status Registers */ +#define SP_AUD_SPDIF_CH_STATUS_BASE 0xc7 + +/* Audio IN S/PDIF Channel Status Register 4 */ +#define SP_FS_FREQ_MASK 0x0f +# define SP_FS_FREQ_44100HZ 0x00 +# define SP_FS_FREQ_48000HZ 0x02 +# define SP_FS_FREQ_32000HZ 0x03 +# define SP_FS_FREQ_88200HZ 0x08 +# define SP_FS_FREQ_96000HZ 0x0a +# define SP_FS_FREQ_176400HZ 0x0c +# define SP_FS_FREQ_192000HZ 0x0e + +/* + * Micellaneous Control Block + */ + +/* CHIP Control Register */ +#define SP_CHIP_CTRL_REG 0xe3 +#define SP_MAN_HDMI5V_DET BIT(3) +#define SP_PLLLOCK_CKDT_EN BIT(2) +#define SP_ANALOG_CKDT_EN BIT(1) +#define SP_DIGITAL_CKDT_EN BIT(0) + +/* Packet Receiving Status Register */ +#define SP_PACKET_RECEIVING_STATUS_REG 0xf3 +#define SP_AVI_RCVD BIT(5) +#define SP_VSI_RCVD BIT(1) + +/***************************************************************/ +/* Register definitions for RX_P1 */ +/***************************************************************/ + +/* HDCP BCAPS Shadow Register */ +#define SP_HDCP_BCAPS_SHADOW_REG 0x2a +#define SP_BCAPS_REPEATER BIT(5) + +/* HDCP Status Register */ +#define SP_RX_HDCP_STATUS_REG 0x3f +#define SP_AUTH_EN BIT(4) + +/* + * InfoFrame and Control Packet Registers + */ + +/* AVI InfoFrame packet checksum */ +#define SP_AVI_INFOFRAME_CHECKSUM 0xa3 + +/* AVI InfoFrame Registers */ +#define SP_AVI_INFOFRAME_DATA_BASE 0xa4 + +#define SP_AVI_COLOR_F_MASK 0x60 +#define SP_AVI_COLOR_F_SHIFT 5 + +/* Audio InfoFrame Registers */ +#define SP_AUD_INFOFRAME_DATA_BASE 0xc4 +#define SP_AUD_INFOFRAME_LAYOUT_MASK 0x0f + +/* MPEG/HDMI Vendor Specific InfoFrame Packet type code */ +#define SP_MPEG_VS_INFOFRAME_TYPE_REG 0xe0 + +/* MPEG/HDMI Vendor Specific InfoFrame Packet length */ +#define SP_MPEG_VS_INFOFRAME_LEN_REG 0xe2 + +/* MPEG/HDMI Vendor Specific InfoFrame Packet version number */ +#define SP_MPEG_VS_INFOFRAME_VER_REG 0xe1 + +/* MPEG/HDMI Vendor Specific InfoFrame Packet content */ +#define SP_MPEG_VS_INFOFRAME_DATA_BASE 0xe4 + +/* General Control Packet Register */ +#define SP_GENERAL_CTRL_PACKET_REG 0x9f +#define SP_CLEAR_AVMUTE BIT(4) +#define SP_SET_AVMUTE BIT(0) + +/***************************************************************/ +/* Register definitions for TX_P1 */ +/***************************************************************/ + +/* DP TX Link Training Control Register */ +#define SP_DP_TX_LT_CTRL0_REG 0x30 + +/* PD 1.2 Lint Training 80bit Pattern Register */ +#define SP_DP_LT_80BIT_PATTERN0_REG 0x80 +#define SP_DP_LT_80BIT_PATTERN_REG_NUM 10 + +/* Audio Interface Control Register 0 */ +#define SP_AUD_INTERFACE_CTRL0_REG 0x5f +#define SP_AUD_INTERFACE_DISABLE 0x80 + +/* Audio Interface Control Register 2 */ +#define SP_AUD_INTERFACE_CTRL2_REG 0x60 +#define SP_M_AUD_ADJUST_ST 0x04 + +/* Audio Interface Control Register 3 */ +#define SP_AUD_INTERFACE_CTRL3_REG 0x62 + +/* Audio Interface Control Register 4 */ +#define SP_AUD_INTERFACE_CTRL4_REG 0x67 + +/* Audio Interface Control Register 5 */ +#define SP_AUD_INTERFACE_CTRL5_REG 0x68 + +/* Audio Interface Control Register 6 */ +#define SP_AUD_INTERFACE_CTRL6_REG 0x69 + +/* Firmware Version Register */ +#define SP_FW_VER_REG 0xb7 + +#endif diff --git a/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c new file mode 100644 index 000000000..fe40bab21 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2016, Analogix Semiconductor. + * + * Based on anx7808 driver obtained from chromeos with copyright: + * Copyright(c) 2013, Google Inc. + */ +#include <linux/regmap.h> + +#include <drm/drm.h> +#include <drm/drm_dp_helper.h> +#include <drm/drm_print.h> + +#include "analogix-i2c-dptx.h" + +#define AUX_WAIT_TIMEOUT_MS 15 +#define AUX_CH_BUFFER_SIZE 16 + +static int anx_i2c_dp_clear_bits(struct regmap *map, u8 reg, u8 mask) +{ + return regmap_update_bits(map, reg, mask, 0); +} + +static bool anx_dp_aux_op_finished(struct regmap *map_dptx) +{ + unsigned int value; + int err; + + err = regmap_read(map_dptx, SP_DP_AUX_CH_CTRL2_REG, &value); + if (err < 0) + return false; + + return (value & SP_AUX_EN) == 0; +} + +static int anx_dp_aux_wait(struct regmap *map_dptx) +{ + unsigned long timeout; + unsigned int status; + int err; + + timeout = jiffies + msecs_to_jiffies(AUX_WAIT_TIMEOUT_MS) + 1; + + while (!anx_dp_aux_op_finished(map_dptx)) { + if (time_after(jiffies, timeout)) { + if (!anx_dp_aux_op_finished(map_dptx)) { + DRM_ERROR("Timed out waiting AUX to finish\n"); + return -ETIMEDOUT; + } + + break; + } + + usleep_range(1000, 2000); + } + + /* Read the AUX channel access status */ + err = regmap_read(map_dptx, SP_AUX_CH_STATUS_REG, &status); + if (err < 0) { + DRM_ERROR("Failed to read from AUX channel: %d\n", err); + return err; + } + + if (status & SP_AUX_STATUS) { + DRM_ERROR("Failed to wait for AUX channel (status: %02x)\n", + status); + return -ETIMEDOUT; + } + + return 0; +} + +static int anx_dp_aux_address(struct regmap *map_dptx, unsigned int addr) +{ + int err; + + err = regmap_write(map_dptx, SP_AUX_ADDR_7_0_REG, addr & 0xff); + if (err) + return err; + + err = regmap_write(map_dptx, SP_AUX_ADDR_15_8_REG, + (addr & 0xff00) >> 8); + if (err) + return err; + + /* + * DP AUX CH Address Register #2, only update bits[3:0] + * [7:4] RESERVED + * [3:0] AUX_ADDR[19:16], Register control AUX CH address. + */ + err = regmap_update_bits(map_dptx, SP_AUX_ADDR_19_16_REG, + SP_AUX_ADDR_19_16_MASK, + (addr & 0xf0000) >> 16); + + if (err) + return err; + + return 0; +} + +ssize_t anx_dp_aux_transfer(struct regmap *map_dptx, + struct drm_dp_aux_msg *msg) +{ + u8 ctrl1 = msg->request; + u8 ctrl2 = SP_AUX_EN; + u8 *buffer = msg->buffer; + int err; + + /* The DP AUX transmit and receive buffer has 16 bytes. */ + if (WARN_ON(msg->size > AUX_CH_BUFFER_SIZE)) + return -E2BIG; + + /* Zero-sized messages specify address-only transactions. */ + if (msg->size < 1) + ctrl2 |= SP_ADDR_ONLY; + else /* For non-zero-sized set the length field. */ + ctrl1 |= (msg->size - 1) << SP_AUX_LENGTH_SHIFT; + + if ((msg->size > 0) && ((msg->request & DP_AUX_I2C_READ) == 0)) { + /* When WRITE | MOT write values to data buffer */ + err = regmap_bulk_write(map_dptx, + SP_DP_BUF_DATA0_REG, buffer, + msg->size); + if (err) + return err; + } + + /* Write address and request */ + err = anx_dp_aux_address(map_dptx, msg->address); + if (err) + return err; + + err = regmap_write(map_dptx, SP_DP_AUX_CH_CTRL1_REG, ctrl1); + if (err) + return err; + + /* Start transaction */ + err = regmap_update_bits(map_dptx, SP_DP_AUX_CH_CTRL2_REG, + SP_ADDR_ONLY | SP_AUX_EN, ctrl2); + if (err) + return err; + + err = anx_dp_aux_wait(map_dptx); + if (err) + return err; + + msg->reply = DP_AUX_I2C_REPLY_ACK; + + if ((msg->size > 0) && (msg->request & DP_AUX_I2C_READ)) { + /* Read values from data buffer */ + err = regmap_bulk_read(map_dptx, + SP_DP_BUF_DATA0_REG, buffer, + msg->size); + if (err) + return err; + } + + err = anx_i2c_dp_clear_bits(map_dptx, SP_DP_AUX_CH_CTRL2_REG, + SP_ADDR_ONLY); + if (err) + return err; + + return msg->size; +} +EXPORT_SYMBOL_GPL(anx_dp_aux_transfer); diff --git a/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.h b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.h new file mode 100644 index 000000000..663c4bea6 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.h @@ -0,0 +1,256 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2016, Analogix Semiconductor. + * + * Based on anx7808 driver obtained from chromeos with copyright: + * Copyright(c) 2013, Google Inc. + */ +#ifndef _ANALOGIX_I2C_DPTX_H_ +#define _ANALOGIX_I2C_DPTX_H_ + +/***************************************************************/ +/* Register definitions for TX_P0 */ +/***************************************************************/ + +/* HDCP Status Register */ +#define SP_TX_HDCP_STATUS_REG 0x00 +#define SP_AUTH_FAIL BIT(5) +#define SP_AUTHEN_PASS BIT(1) + +/* HDCP Control Register 0 */ +#define SP_HDCP_CTRL0_REG 0x01 +#define SP_RX_REPEATER BIT(6) +#define SP_RE_AUTH BIT(5) +#define SP_SW_AUTH_OK BIT(4) +#define SP_HARD_AUTH_EN BIT(3) +#define SP_HDCP_ENC_EN BIT(2) +#define SP_BKSV_SRM_PASS BIT(1) +#define SP_KSVLIST_VLD BIT(0) +/* HDCP Function Enabled */ +#define SP_HDCP_FUNCTION_ENABLED (BIT(0) | BIT(1) | BIT(2) | BIT(3)) + +/* HDCP Receiver BSTATUS Register 0 */ +#define SP_HDCP_RX_BSTATUS0_REG 0x1b +/* HDCP Receiver BSTATUS Register 1 */ +#define SP_HDCP_RX_BSTATUS1_REG 0x1c + +/* HDCP Embedded "Blue Screen" Content Registers */ +#define SP_HDCP_VID0_BLUE_SCREEN_REG 0x2c +#define SP_HDCP_VID1_BLUE_SCREEN_REG 0x2d +#define SP_HDCP_VID2_BLUE_SCREEN_REG 0x2e + +/* HDCP Wait R0 Timing Register */ +#define SP_HDCP_WAIT_R0_TIME_REG 0x40 + +/* HDCP Link Integrity Check Timer Register */ +#define SP_HDCP_LINK_CHECK_TIMER_REG 0x41 + +/* HDCP Repeater Ready Wait Timer Register */ +#define SP_HDCP_RPTR_RDY_WAIT_TIME_REG 0x42 + +/* HDCP Auto Timer Register */ +#define SP_HDCP_AUTO_TIMER_REG 0x51 + +/* HDCP Key Status Register */ +#define SP_HDCP_KEY_STATUS_REG 0x5e + +/* HDCP Key Command Register */ +#define SP_HDCP_KEY_COMMAND_REG 0x5f +#define SP_DISABLE_SYNC_HDCP BIT(2) + +/* OTP Memory Key Protection Registers */ +#define SP_OTP_KEY_PROTECT1_REG 0x60 +#define SP_OTP_KEY_PROTECT2_REG 0x61 +#define SP_OTP_KEY_PROTECT3_REG 0x62 +#define SP_OTP_PSW1 0xa2 +#define SP_OTP_PSW2 0x7e +#define SP_OTP_PSW3 0xc6 + +/* DP System Control Registers */ +#define SP_DP_SYSTEM_CTRL_BASE (0x80 - 1) +/* Bits for DP System Control Register 2 */ +#define SP_CHA_STA BIT(2) +/* Bits for DP System Control Register 3 */ +#define SP_HPD_STATUS BIT(6) +#define SP_HPD_FORCE BIT(5) +#define SP_HPD_CTRL BIT(4) +#define SP_STRM_VALID BIT(2) +#define SP_STRM_FORCE BIT(1) +#define SP_STRM_CTRL BIT(0) +/* Bits for DP System Control Register 4 */ +#define SP_ENHANCED_MODE BIT(3) + +/* DP Video Control Register */ +#define SP_DP_VIDEO_CTRL_REG 0x84 +#define SP_COLOR_F_MASK 0x06 +#define SP_COLOR_F_SHIFT 1 +#define SP_BPC_MASK 0xe0 +#define SP_BPC_SHIFT 5 +# define SP_BPC_6BITS 0x00 +# define SP_BPC_8BITS 0x01 +# define SP_BPC_10BITS 0x02 +# define SP_BPC_12BITS 0x03 + +/* DP Audio Control Register */ +#define SP_DP_AUDIO_CTRL_REG 0x87 +#define SP_AUD_EN BIT(0) + +/* 10us Pulse Generate Timer Registers */ +#define SP_I2C_GEN_10US_TIMER0_REG 0x88 +#define SP_I2C_GEN_10US_TIMER1_REG 0x89 + +/* Packet Send Control Register */ +#define SP_PACKET_SEND_CTRL_REG 0x90 +#define SP_AUD_IF_UP BIT(7) +#define SP_AVI_IF_UD BIT(6) +#define SP_MPEG_IF_UD BIT(5) +#define SP_SPD_IF_UD BIT(4) +#define SP_AUD_IF_EN BIT(3) +#define SP_AVI_IF_EN BIT(2) +#define SP_MPEG_IF_EN BIT(1) +#define SP_SPD_IF_EN BIT(0) + +/* DP HDCP Control Register */ +#define SP_DP_HDCP_CTRL_REG 0x92 +#define SP_AUTO_EN BIT(7) +#define SP_AUTO_START BIT(5) +#define SP_LINK_POLLING BIT(1) + +/* DP Main Link Bandwidth Setting Register */ +#define SP_DP_MAIN_LINK_BW_SET_REG 0xa0 +#define SP_LINK_BW_SET_MASK 0x1f +#define SP_INITIAL_SLIM_M_AUD_SEL BIT(5) + +/* DP Lane Count Setting Register */ +#define SP_DP_LANE_COUNT_SET_REG 0xa1 + +/* DP Training Pattern Set Register */ +#define SP_DP_TRAINING_PATTERN_SET_REG 0xa2 + +/* DP Lane 0 Link Training Control Register */ +#define SP_DP_LANE0_LT_CTRL_REG 0xa3 +#define SP_TX_SW_SET_MASK 0x1b +#define SP_MAX_PRE_REACH BIT(5) +#define SP_MAX_DRIVE_REACH BIT(4) +#define SP_PRE_EMP_LEVEL1 BIT(3) +#define SP_DRVIE_CURRENT_LEVEL1 BIT(0) + +/* DP Link Training Control Register */ +#define SP_DP_LT_CTRL_REG 0xa8 +#define SP_DP_LT_INPROGRESS 0x80 +#define SP_LT_ERROR_TYPE_MASK 0x70 +# define SP_LT_NO_ERROR 0x00 +# define SP_LT_AUX_WRITE_ERROR 0x01 +# define SP_LT_MAX_DRIVE_REACHED 0x02 +# define SP_LT_WRONG_LANE_COUNT_SET 0x03 +# define SP_LT_LOOP_SAME_5_TIME 0x04 +# define SP_LT_CR_FAIL_IN_EQ 0x05 +# define SP_LT_EQ_LOOP_5_TIME 0x06 +#define SP_LT_EN BIT(0) + +/* DP CEP Training Control Registers */ +#define SP_DP_CEP_TRAINING_CTRL0_REG 0xa9 +#define SP_DP_CEP_TRAINING_CTRL1_REG 0xaa + +/* DP Debug Register 1 */ +#define SP_DP_DEBUG1_REG 0xb0 +#define SP_DEBUG_PLL_LOCK BIT(4) +#define SP_POLLING_EN BIT(1) + +/* DP Polling Control Register */ +#define SP_DP_POLLING_CTRL_REG 0xb4 +#define SP_AUTO_POLLING_DISABLE BIT(0) + +/* DP Link Debug Control Register */ +#define SP_DP_LINK_DEBUG_CTRL_REG 0xb8 +#define SP_M_VID_DEBUG BIT(5) +#define SP_NEW_PRBS7 BIT(4) +#define SP_INSERT_ER BIT(1) +#define SP_PRBS31_EN BIT(0) + +/* AUX Misc control Register */ +#define SP_AUX_MISC_CTRL_REG 0xbf + +/* DP PLL control Register */ +#define SP_DP_PLL_CTRL_REG 0xc7 +#define SP_PLL_RST BIT(6) + +/* DP Analog Power Down Register */ +#define SP_DP_ANALOG_POWER_DOWN_REG 0xc8 +#define SP_CH0_PD BIT(0) + +/* DP Misc Control Register */ +#define SP_DP_MISC_CTRL_REG 0xcd +#define SP_EQ_TRAINING_LOOP BIT(6) + +/* DP Extra I2C Device Address Register */ +#define SP_DP_EXTRA_I2C_DEV_ADDR_REG 0xce +#define SP_I2C_STRETCH_DISABLE BIT(7) + +#define SP_I2C_EXTRA_ADDR 0x50 + +/* DP Downspread Control Register 1 */ +#define SP_DP_DOWNSPREAD_CTRL1_REG 0xd0 + +/* DP M Value Calculation Control Register */ +#define SP_DP_M_CALCULATION_CTRL_REG 0xd9 +#define SP_M_GEN_CLK_SEL BIT(0) + +/* AUX Channel Access Status Register */ +#define SP_AUX_CH_STATUS_REG 0xe0 +#define SP_AUX_STATUS 0x0f + +/* AUX Channel DEFER Control Register */ +#define SP_AUX_DEFER_CTRL_REG 0xe2 +#define SP_DEFER_CTRL_EN BIT(7) + +/* DP Buffer Data Count Register */ +#define SP_BUF_DATA_COUNT_REG 0xe4 +#define SP_BUF_DATA_COUNT_MASK 0x1f +#define SP_BUF_CLR BIT(7) + +/* DP AUX Channel Control Register 1 */ +#define SP_DP_AUX_CH_CTRL1_REG 0xe5 +#define SP_AUX_TX_COMM_MASK 0x0f +#define SP_AUX_LENGTH_MASK 0xf0 +#define SP_AUX_LENGTH_SHIFT 4 + +/* DP AUX CH Address Register 0 */ +#define SP_AUX_ADDR_7_0_REG 0xe6 + +/* DP AUX CH Address Register 1 */ +#define SP_AUX_ADDR_15_8_REG 0xe7 + +/* DP AUX CH Address Register 2 */ +#define SP_AUX_ADDR_19_16_REG 0xe8 +#define SP_AUX_ADDR_19_16_MASK 0x0f + +/* DP AUX Channel Control Register 2 */ +#define SP_DP_AUX_CH_CTRL2_REG 0xe9 +#define SP_AUX_SEL_RXCM BIT(6) +#define SP_AUX_CHSEL BIT(3) +#define SP_AUX_PN_INV BIT(2) +#define SP_ADDR_ONLY BIT(1) +#define SP_AUX_EN BIT(0) + +/* DP Video Stream Control InfoFrame Register */ +#define SP_DP_3D_VSC_CTRL_REG 0xea +#define SP_INFO_FRAME_VSC_EN BIT(0) + +/* DP Video Stream Data Byte 1 Register */ +#define SP_DP_VSC_DB1_REG 0xeb + +/* DP AUX Channel Control Register 3 */ +#define SP_DP_AUX_CH_CTRL3_REG 0xec +#define SP_WAIT_COUNTER_7_0_MASK 0xff + +/* DP AUX Channel Control Register 4 */ +#define SP_DP_AUX_CH_CTRL4_REG 0xed + +/* DP AUX Buffer Data Registers */ +#define SP_DP_BUF_DATA0_REG 0xf0 + +ssize_t anx_dp_aux_transfer(struct regmap *map_dptx, + struct drm_dp_aux_msg *msg); + +#endif diff --git a/drivers/gpu/drm/bridge/analogix/analogix-i2c-txcommon.h b/drivers/gpu/drm/bridge/analogix/analogix-i2c-txcommon.h new file mode 100644 index 000000000..3c843497d --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix-i2c-txcommon.h @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2016, Analogix Semiconductor. All rights reserved. + */ +#ifndef _ANALOGIX_I2C_TXCOMMON_H_ +#define _ANALOGIX_I2C_TXCOMMON_H_ + +/***************************************************************/ +/* Register definitions for TX_P2 */ +/***************************************************************/ + +/* + * Core Register Definitions + */ + +/* Device ID Low Byte Register */ +#define SP_DEVICE_IDL_REG 0x02 + +/* Device ID High Byte Register */ +#define SP_DEVICE_IDH_REG 0x03 + +/* Device version register */ +#define SP_DEVICE_VERSION_REG 0x04 + +/* Power Down Control Register */ +#define SP_POWERDOWN_CTRL_REG 0x05 +#define SP_REGISTER_PD BIT(7) +#define SP_HDCP_PD BIT(5) +#define SP_AUDIO_PD BIT(4) +#define SP_VIDEO_PD BIT(3) +#define SP_LINK_PD BIT(2) +#define SP_TOTAL_PD BIT(1) + +/* Reset Control Register 1 */ +#define SP_RESET_CTRL1_REG 0x06 +#define SP_MISC_RST BIT(7) +#define SP_VIDCAP_RST BIT(6) +#define SP_VIDFIF_RST BIT(5) +#define SP_AUDFIF_RST BIT(4) +#define SP_AUDCAP_RST BIT(3) +#define SP_HDCP_RST BIT(2) +#define SP_SW_RST BIT(1) +#define SP_HW_RST BIT(0) + +/* Reset Control Register 2 */ +#define SP_RESET_CTRL2_REG 0x07 +#define SP_AUX_RST BIT(2) +#define SP_SERDES_FIFO_RST BIT(1) +#define SP_I2C_REG_RST BIT(0) + +/* Video Control Register 1 */ +#define SP_VID_CTRL1_REG 0x08 +#define SP_VIDEO_EN BIT(7) +#define SP_VIDEO_MUTE BIT(2) +#define SP_DE_GEN BIT(1) +#define SP_DEMUX BIT(0) + +/* Video Control Register 2 */ +#define SP_VID_CTRL2_REG 0x09 +#define SP_IN_COLOR_F_MASK 0x03 +#define SP_IN_YC_BIT_SEL BIT(2) +#define SP_IN_BPC_MASK 0x70 +#define SP_IN_BPC_SHIFT 4 +# define SP_IN_BPC_12BIT 0x03 +# define SP_IN_BPC_10BIT 0x02 +# define SP_IN_BPC_8BIT 0x01 +# define SP_IN_BPC_6BIT 0x00 +#define SP_IN_D_RANGE BIT(7) + +/* Video Control Register 3 */ +#define SP_VID_CTRL3_REG 0x0a +#define SP_HPD_OUT BIT(6) + +/* Video Control Register 5 */ +#define SP_VID_CTRL5_REG 0x0c +#define SP_CSC_STD_SEL BIT(7) +#define SP_XVYCC_RNG_LMT BIT(6) +#define SP_RANGE_Y2R BIT(5) +#define SP_CSPACE_Y2R BIT(4) +#define SP_RGB_RNG_LMT BIT(3) +#define SP_Y_RNG_LMT BIT(2) +#define SP_RANGE_R2Y BIT(1) +#define SP_CSPACE_R2Y BIT(0) + +/* Video Control Register 6 */ +#define SP_VID_CTRL6_REG 0x0d +#define SP_TEST_PATTERN_EN BIT(7) +#define SP_VIDEO_PROCESS_EN BIT(6) +#define SP_VID_US_MODE BIT(3) +#define SP_VID_DS_MODE BIT(2) +#define SP_UP_SAMPLE BIT(1) +#define SP_DOWN_SAMPLE BIT(0) + +/* Video Control Register 8 */ +#define SP_VID_CTRL8_REG 0x0f +#define SP_VID_VRES_TH BIT(0) + +/* Total Line Status Low Byte Register */ +#define SP_TOTAL_LINE_STAL_REG 0x24 + +/* Total Line Status High Byte Register */ +#define SP_TOTAL_LINE_STAH_REG 0x25 + +/* Active Line Status Low Byte Register */ +#define SP_ACT_LINE_STAL_REG 0x26 + +/* Active Line Status High Byte Register */ +#define SP_ACT_LINE_STAH_REG 0x27 + +/* Vertical Front Porch Status Register */ +#define SP_V_F_PORCH_STA_REG 0x28 + +/* Vertical SYNC Width Status Register */ +#define SP_V_SYNC_STA_REG 0x29 + +/* Vertical Back Porch Status Register */ +#define SP_V_B_PORCH_STA_REG 0x2a + +/* Total Pixel Status Low Byte Register */ +#define SP_TOTAL_PIXEL_STAL_REG 0x2b + +/* Total Pixel Status High Byte Register */ +#define SP_TOTAL_PIXEL_STAH_REG 0x2c + +/* Active Pixel Status Low Byte Register */ +#define SP_ACT_PIXEL_STAL_REG 0x2d + +/* Active Pixel Status High Byte Register */ +#define SP_ACT_PIXEL_STAH_REG 0x2e + +/* Horizontal Front Porch Status Low Byte Register */ +#define SP_H_F_PORCH_STAL_REG 0x2f + +/* Horizontal Front Porch Statys High Byte Register */ +#define SP_H_F_PORCH_STAH_REG 0x30 + +/* Horizontal SYNC Width Status Low Byte Register */ +#define SP_H_SYNC_STAL_REG 0x31 + +/* Horizontal SYNC Width Status High Byte Register */ +#define SP_H_SYNC_STAH_REG 0x32 + +/* Horizontal Back Porch Status Low Byte Register */ +#define SP_H_B_PORCH_STAL_REG 0x33 + +/* Horizontal Back Porch Status High Byte Register */ +#define SP_H_B_PORCH_STAH_REG 0x34 + +/* InfoFrame AVI Packet DB1 Register */ +#define SP_INFOFRAME_AVI_DB1_REG 0x70 + +/* Bit Control Specific Register */ +#define SP_BIT_CTRL_SPECIFIC_REG 0x80 +#define SP_BIT_CTRL_SELECT_SHIFT 1 +#define SP_ENABLE_BIT_CTRL BIT(0) + +/* InfoFrame Audio Packet DB1 Register */ +#define SP_INFOFRAME_AUD_DB1_REG 0x83 + +/* InfoFrame MPEG Packet DB1 Register */ +#define SP_INFOFRAME_MPEG_DB1_REG 0xb0 + +/* Audio Channel Status Registers */ +#define SP_AUD_CH_STATUS_BASE 0xd0 + +/* Audio Channel Num Register 5 */ +#define SP_I2S_CHANNEL_NUM_MASK 0xe0 +# define SP_I2S_CH_NUM_1 (0x00 << 5) +# define SP_I2S_CH_NUM_2 (0x01 << 5) +# define SP_I2S_CH_NUM_3 (0x02 << 5) +# define SP_I2S_CH_NUM_4 (0x03 << 5) +# define SP_I2S_CH_NUM_5 (0x04 << 5) +# define SP_I2S_CH_NUM_6 (0x05 << 5) +# define SP_I2S_CH_NUM_7 (0x06 << 5) +# define SP_I2S_CH_NUM_8 (0x07 << 5) +#define SP_EXT_VUCP BIT(2) +#define SP_VBIT BIT(1) +#define SP_AUDIO_LAYOUT BIT(0) + +/* Analog Debug Register 1 */ +#define SP_ANALOG_DEBUG1_REG 0xdc + +/* Analog Debug Register 2 */ +#define SP_ANALOG_DEBUG2_REG 0xdd +#define SP_FORCE_SW_OFF_BYPASS 0x20 +#define SP_XTAL_FRQ 0x1c +# define SP_XTAL_FRQ_19M2 (0x00 << 2) +# define SP_XTAL_FRQ_24M (0x01 << 2) +# define SP_XTAL_FRQ_25M (0x02 << 2) +# define SP_XTAL_FRQ_26M (0x03 << 2) +# define SP_XTAL_FRQ_27M (0x04 << 2) +# define SP_XTAL_FRQ_38M4 (0x05 << 2) +# define SP_XTAL_FRQ_52M (0x06 << 2) +#define SP_POWERON_TIME_1P5MS 0x03 + +/* Analog Control 0 Register */ +#define SP_ANALOG_CTRL0_REG 0xe1 + +/* Common Interrupt Status Register 1 */ +#define SP_COMMON_INT_STATUS_BASE (0xf1 - 1) +#define SP_PLL_LOCK_CHG 0x40 + +/* Common Interrupt Status Register 2 */ +#define SP_COMMON_INT_STATUS2 0xf2 +#define SP_HDCP_AUTH_CHG BIT(1) +#define SP_HDCP_AUTH_DONE BIT(0) + +#define SP_HDCP_LINK_CHECK_FAIL BIT(0) + +/* Common Interrupt Status Register 4 */ +#define SP_COMMON_INT_STATUS4_REG 0xf4 +#define SP_HPD_IRQ BIT(6) +#define SP_HPD_ESYNC_ERR BIT(4) +#define SP_HPD_CHG BIT(2) +#define SP_HPD_LOST BIT(1) +#define SP_HPD_PLUG BIT(0) + +/* DP Interrupt Status Register */ +#define SP_DP_INT_STATUS1_REG 0xf7 +#define SP_TRAINING_FINISH BIT(5) +#define SP_POLLING_ERR BIT(4) + +/* Common Interrupt Mask Register */ +#define SP_COMMON_INT_MASK_BASE (0xf8 - 1) + +#define SP_COMMON_INT_MASK4_REG 0xfb + +/* DP Interrupts Mask Register */ +#define SP_DP_INT_MASK1_REG 0xfe + +/* Interrupt Control Register */ +#define SP_INT_CTRL_REG 0xff + +#endif /* _ANALOGIX_I2C_TXCOMMON_H_ */ diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c new file mode 100644 index 000000000..e8baa0745 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -0,0 +1,1911 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* +* Analogix DP (Display Port) core interface driver. +* +* Copyright (C) 2012 Samsung Electronics Co., Ltd. +* Author: Jingoo Han <jg1.han@samsung.com> +*/ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +#include <drm/bridge/analogix_dp.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "analogix_dp_core.h" +#include "analogix_dp_reg.h" + +#define to_dp(nm) container_of(nm, struct analogix_dp_device, nm) + +static const bool verify_fast_training; + +struct bridge_init { + struct i2c_client *client; + struct device_node *node; +}; + +static int analogix_dp_init_dp(struct analogix_dp_device *dp) +{ + int ret; + + analogix_dp_reset(dp); + + analogix_dp_swreset(dp); + + analogix_dp_init_analog_param(dp); + analogix_dp_init_interrupt(dp); + + /* SW defined function Normal operation */ + analogix_dp_enable_sw_function(dp); + + analogix_dp_config_interrupt(dp); + ret = analogix_dp_init_analog_func(dp); + if (ret) + return ret; + + analogix_dp_init_hpd(dp); + analogix_dp_init_aux(dp); + return 0; +} + +static int analogix_dp_detect_hpd(struct analogix_dp_device *dp) +{ + int timeout_loop = 0; + + while (timeout_loop < DP_TIMEOUT_LOOP_COUNT) { + if (analogix_dp_get_plug_in_status(dp) == 0) + return 0; + + timeout_loop++; + usleep_range(1000, 1100); + } + + /* + * Some edp screen do not have hpd signal, so we can't just + * return failed when hpd plug in detect failed, DT property + * "force-hpd" would indicate whether driver need this. + */ + if (!dp->force_hpd) + return -ETIMEDOUT; + + /* + * The eDP TRM indicate that if HPD_STATUS(RO) is 0, AUX CH + * will not work, so we need to give a force hpd action to + * set HPD_STATUS manually. + */ + dev_dbg(dp->dev, "failed to get hpd plug status, try to force hpd\n"); + + analogix_dp_force_hpd(dp); + + if (analogix_dp_get_plug_in_status(dp) != 0) { + dev_err(dp->dev, "failed to get hpd plug in status\n"); + return -EINVAL; + } + + dev_dbg(dp->dev, "success to get plug in status after force hpd\n"); + + return 0; +} + +static bool analogix_dp_detect_sink_psr(struct analogix_dp_device *dp) +{ + unsigned char psr_version; + int ret; + + ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_SUPPORT, &psr_version); + if (ret != 1) { + dev_err(dp->dev, "failed to get PSR version, disable it\n"); + return false; + } + + dev_dbg(dp->dev, "Panel PSR version : %x\n", psr_version); + return psr_version & DP_PSR_IS_SUPPORTED; +} + +static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp) +{ + unsigned char psr_en; + int ret; + + /* Disable psr function */ + ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_EN_CFG, &psr_en); + if (ret != 1) { + dev_err(dp->dev, "failed to get psr config\n"); + goto end; + } + + psr_en &= ~DP_PSR_ENABLE; + ret = drm_dp_dpcd_writeb(&dp->aux, DP_PSR_EN_CFG, psr_en); + if (ret != 1) { + dev_err(dp->dev, "failed to disable panel psr\n"); + goto end; + } + + /* Main-Link transmitter remains active during PSR active states */ + psr_en = DP_PSR_CRC_VERIFICATION; + ret = drm_dp_dpcd_writeb(&dp->aux, DP_PSR_EN_CFG, psr_en); + if (ret != 1) { + dev_err(dp->dev, "failed to set panel psr\n"); + goto end; + } + + /* Enable psr function */ + psr_en = DP_PSR_ENABLE | DP_PSR_CRC_VERIFICATION; + ret = drm_dp_dpcd_writeb(&dp->aux, DP_PSR_EN_CFG, psr_en); + if (ret != 1) { + dev_err(dp->dev, "failed to set panel psr\n"); + goto end; + } + + analogix_dp_enable_psr_crc(dp); + + dp->psr_supported = true; + + return 0; +end: + dev_err(dp->dev, "enable psr fail, force to disable psr\n"); + + return ret; +} + +static int +analogix_dp_enable_rx_to_enhanced_mode(struct analogix_dp_device *dp, + bool enable) +{ + u8 data; + int ret; + + ret = drm_dp_dpcd_readb(&dp->aux, DP_LANE_COUNT_SET, &data); + if (ret != 1) + return ret; + + if (enable) + ret = drm_dp_dpcd_writeb(&dp->aux, DP_LANE_COUNT_SET, + DP_LANE_COUNT_ENHANCED_FRAME_EN | + DPCD_LANE_COUNT_SET(data)); + else + ret = drm_dp_dpcd_writeb(&dp->aux, DP_LANE_COUNT_SET, + DPCD_LANE_COUNT_SET(data)); + + return ret < 0 ? ret : 0; +} + +static int analogix_dp_is_enhanced_mode_available(struct analogix_dp_device *dp, + u8 *enhanced_mode_support) +{ + u8 data; + int ret; + + ret = drm_dp_dpcd_readb(&dp->aux, DP_MAX_LANE_COUNT, &data); + if (ret != 1) { + *enhanced_mode_support = 0; + return ret; + } + + *enhanced_mode_support = DPCD_ENHANCED_FRAME_CAP(data); + + return 0; +} + +static int analogix_dp_set_enhanced_mode(struct analogix_dp_device *dp) +{ + u8 data; + int ret; + + ret = analogix_dp_is_enhanced_mode_available(dp, &data); + if (ret < 0) + return ret; + + ret = analogix_dp_enable_rx_to_enhanced_mode(dp, data); + if (ret < 0) + return ret; + + analogix_dp_enable_enhanced_mode(dp, data); + + return 0; +} + +static int analogix_dp_training_pattern_dis(struct analogix_dp_device *dp) +{ + int ret; + + analogix_dp_set_training_pattern(dp, DP_NONE); + + ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_DISABLE); + + return ret < 0 ? ret : 0; +} + +static void +analogix_dp_set_lane_lane_pre_emphasis(struct analogix_dp_device *dp, + int pre_emphasis, int lane) +{ + switch (lane) { + case 0: + analogix_dp_set_lane0_pre_emphasis(dp, pre_emphasis); + break; + case 1: + analogix_dp_set_lane1_pre_emphasis(dp, pre_emphasis); + break; + + case 2: + analogix_dp_set_lane2_pre_emphasis(dp, pre_emphasis); + break; + + case 3: + analogix_dp_set_lane3_pre_emphasis(dp, pre_emphasis); + break; + } +} + +static int analogix_dp_link_start(struct analogix_dp_device *dp) +{ + u8 buf[4]; + int lane, lane_count, pll_tries, retval; + + lane_count = dp->link_train.lane_count; + + dp->link_train.lt_state = CLOCK_RECOVERY; + dp->link_train.eq_loop = 0; + + for (lane = 0; lane < lane_count; lane++) + dp->link_train.cr_loop[lane] = 0; + + /* Set link rate and count as you want to establish*/ + analogix_dp_set_link_bandwidth(dp, dp->link_train.link_rate); + analogix_dp_set_lane_count(dp, dp->link_train.lane_count); + + /* Setup RX configuration */ + buf[0] = dp->link_train.link_rate; + buf[1] = dp->link_train.lane_count; + retval = drm_dp_dpcd_write(&dp->aux, DP_LINK_BW_SET, buf, 2); + if (retval < 0) + return retval; + /* set enhanced mode if available */ + retval = analogix_dp_set_enhanced_mode(dp); + if (retval < 0) { + dev_err(dp->dev, "failed to set enhance mode\n"); + return retval; + } + + /* Set TX pre-emphasis to minimum */ + for (lane = 0; lane < lane_count; lane++) + analogix_dp_set_lane_lane_pre_emphasis(dp, + PRE_EMPHASIS_LEVEL_0, lane); + + /* Wait for PLL lock */ + pll_tries = 0; + while (analogix_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { + if (pll_tries == DP_TIMEOUT_LOOP_COUNT) { + dev_err(dp->dev, "Wait for PLL lock timed out\n"); + return -ETIMEDOUT; + } + + pll_tries++; + usleep_range(90, 120); + } + + /* Set training pattern 1 */ + analogix_dp_set_training_pattern(dp, TRAINING_PTN1); + + /* Set RX training pattern */ + retval = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, + DP_LINK_SCRAMBLING_DISABLE | + DP_TRAINING_PATTERN_1); + if (retval < 0) + return retval; + + for (lane = 0; lane < lane_count; lane++) + buf[lane] = DP_TRAIN_PRE_EMPH_LEVEL_0 | + DP_TRAIN_VOLTAGE_SWING_LEVEL_0; + + retval = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, buf, + lane_count); + if (retval < 0) + return retval; + + return 0; +} + +static unsigned char analogix_dp_get_lane_status(u8 link_status[2], int lane) +{ + int shift = (lane & 1) * 4; + u8 link_value = link_status[lane >> 1]; + + return (link_value >> shift) & 0xf; +} + +static int analogix_dp_clock_recovery_ok(u8 link_status[2], int lane_count) +{ + int lane; + u8 lane_status; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = analogix_dp_get_lane_status(link_status, lane); + if ((lane_status & DP_LANE_CR_DONE) == 0) + return -EINVAL; + } + return 0; +} + +static int analogix_dp_channel_eq_ok(u8 link_status[2], u8 link_align, + int lane_count) +{ + int lane; + u8 lane_status; + + if ((link_align & DP_INTERLANE_ALIGN_DONE) == 0) + return -EINVAL; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = analogix_dp_get_lane_status(link_status, lane); + lane_status &= DP_CHANNEL_EQ_BITS; + if (lane_status != DP_CHANNEL_EQ_BITS) + return -EINVAL; + } + + return 0; +} + +static unsigned char +analogix_dp_get_adjust_request_voltage(u8 adjust_request[2], int lane) +{ + int shift = (lane & 1) * 4; + u8 link_value = adjust_request[lane >> 1]; + + return (link_value >> shift) & 0x3; +} + +static unsigned char analogix_dp_get_adjust_request_pre_emphasis( + u8 adjust_request[2], + int lane) +{ + int shift = (lane & 1) * 4; + u8 link_value = adjust_request[lane >> 1]; + + return ((link_value >> shift) & 0xc) >> 2; +} + +static void analogix_dp_set_lane_link_training(struct analogix_dp_device *dp, + u8 training_lane_set, int lane) +{ + switch (lane) { + case 0: + analogix_dp_set_lane0_link_training(dp, training_lane_set); + break; + case 1: + analogix_dp_set_lane1_link_training(dp, training_lane_set); + break; + + case 2: + analogix_dp_set_lane2_link_training(dp, training_lane_set); + break; + + case 3: + analogix_dp_set_lane3_link_training(dp, training_lane_set); + break; + } +} + +static unsigned int +analogix_dp_get_lane_link_training(struct analogix_dp_device *dp, + int lane) +{ + u32 reg; + + switch (lane) { + case 0: + reg = analogix_dp_get_lane0_link_training(dp); + break; + case 1: + reg = analogix_dp_get_lane1_link_training(dp); + break; + case 2: + reg = analogix_dp_get_lane2_link_training(dp); + break; + case 3: + reg = analogix_dp_get_lane3_link_training(dp); + break; + default: + WARN_ON(1); + return 0; + } + + return reg; +} + +static void analogix_dp_reduce_link_rate(struct analogix_dp_device *dp) +{ + analogix_dp_training_pattern_dis(dp); + analogix_dp_set_enhanced_mode(dp); + + dp->link_train.lt_state = FAILED; +} + +static void analogix_dp_get_adjust_training_lane(struct analogix_dp_device *dp, + u8 adjust_request[2]) +{ + int lane, lane_count; + u8 voltage_swing, pre_emphasis, training_lane; + + lane_count = dp->link_train.lane_count; + for (lane = 0; lane < lane_count; lane++) { + voltage_swing = analogix_dp_get_adjust_request_voltage( + adjust_request, lane); + pre_emphasis = analogix_dp_get_adjust_request_pre_emphasis( + adjust_request, lane); + training_lane = DPCD_VOLTAGE_SWING_SET(voltage_swing) | + DPCD_PRE_EMPHASIS_SET(pre_emphasis); + + if (voltage_swing == VOLTAGE_LEVEL_3) + training_lane |= DP_TRAIN_MAX_SWING_REACHED; + if (pre_emphasis == PRE_EMPHASIS_LEVEL_3) + training_lane |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + + dp->link_train.training_lane[lane] = training_lane; + } +} + +static int analogix_dp_process_clock_recovery(struct analogix_dp_device *dp) +{ + int lane, lane_count, retval; + u8 voltage_swing, pre_emphasis, training_lane; + u8 link_status[2], adjust_request[2]; + + usleep_range(100, 101); + + lane_count = dp->link_train.lane_count; + + retval = drm_dp_dpcd_read(&dp->aux, DP_LANE0_1_STATUS, link_status, 2); + if (retval < 0) + return retval; + + retval = drm_dp_dpcd_read(&dp->aux, DP_ADJUST_REQUEST_LANE0_1, + adjust_request, 2); + if (retval < 0) + return retval; + + if (analogix_dp_clock_recovery_ok(link_status, lane_count) == 0) { + /* set training pattern 2 for EQ */ + analogix_dp_set_training_pattern(dp, TRAINING_PTN2); + + retval = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, + DP_LINK_SCRAMBLING_DISABLE | + DP_TRAINING_PATTERN_2); + if (retval < 0) + return retval; + + dev_dbg(dp->dev, "Link Training Clock Recovery success\n"); + dp->link_train.lt_state = EQUALIZER_TRAINING; + } else { + for (lane = 0; lane < lane_count; lane++) { + training_lane = analogix_dp_get_lane_link_training( + dp, lane); + voltage_swing = analogix_dp_get_adjust_request_voltage( + adjust_request, lane); + pre_emphasis = analogix_dp_get_adjust_request_pre_emphasis( + adjust_request, lane); + + if (DPCD_VOLTAGE_SWING_GET(training_lane) == + voltage_swing && + DPCD_PRE_EMPHASIS_GET(training_lane) == + pre_emphasis) + dp->link_train.cr_loop[lane]++; + + if (dp->link_train.cr_loop[lane] == MAX_CR_LOOP || + voltage_swing == VOLTAGE_LEVEL_3 || + pre_emphasis == PRE_EMPHASIS_LEVEL_3) { + dev_err(dp->dev, "CR Max reached (%d,%d,%d)\n", + dp->link_train.cr_loop[lane], + voltage_swing, pre_emphasis); + analogix_dp_reduce_link_rate(dp); + return -EIO; + } + } + } + + analogix_dp_get_adjust_training_lane(dp, adjust_request); + + for (lane = 0; lane < lane_count; lane++) + analogix_dp_set_lane_link_training(dp, + dp->link_train.training_lane[lane], lane); + + retval = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, + dp->link_train.training_lane, lane_count); + if (retval < 0) + return retval; + + return 0; +} + +static int analogix_dp_process_equalizer_training(struct analogix_dp_device *dp) +{ + int lane, lane_count, retval; + u32 reg; + u8 link_align, link_status[2], adjust_request[2]; + + usleep_range(400, 401); + + lane_count = dp->link_train.lane_count; + + retval = drm_dp_dpcd_read(&dp->aux, DP_LANE0_1_STATUS, link_status, 2); + if (retval < 0) + return retval; + + if (analogix_dp_clock_recovery_ok(link_status, lane_count)) { + analogix_dp_reduce_link_rate(dp); + return -EIO; + } + + retval = drm_dp_dpcd_read(&dp->aux, DP_ADJUST_REQUEST_LANE0_1, + adjust_request, 2); + if (retval < 0) + return retval; + + retval = drm_dp_dpcd_readb(&dp->aux, DP_LANE_ALIGN_STATUS_UPDATED, + &link_align); + if (retval < 0) + return retval; + + analogix_dp_get_adjust_training_lane(dp, adjust_request); + + if (!analogix_dp_channel_eq_ok(link_status, link_align, lane_count)) { + /* traing pattern Set to Normal */ + retval = analogix_dp_training_pattern_dis(dp); + if (retval < 0) + return retval; + + dev_dbg(dp->dev, "Link Training success!\n"); + analogix_dp_get_link_bandwidth(dp, ®); + dp->link_train.link_rate = reg; + dev_dbg(dp->dev, "final bandwidth = %.2x\n", + dp->link_train.link_rate); + + analogix_dp_get_lane_count(dp, ®); + dp->link_train.lane_count = reg; + dev_dbg(dp->dev, "final lane count = %.2x\n", + dp->link_train.lane_count); + + dp->link_train.lt_state = FINISHED; + + return 0; + } + + /* not all locked */ + dp->link_train.eq_loop++; + + if (dp->link_train.eq_loop > MAX_EQ_LOOP) { + dev_err(dp->dev, "EQ Max loop\n"); + analogix_dp_reduce_link_rate(dp); + return -EIO; + } + + for (lane = 0; lane < lane_count; lane++) + analogix_dp_set_lane_link_training(dp, + dp->link_train.training_lane[lane], lane); + + retval = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, + dp->link_train.training_lane, lane_count); + if (retval < 0) + return retval; + + return 0; +} + +static void analogix_dp_get_max_rx_bandwidth(struct analogix_dp_device *dp, + u8 *bandwidth) +{ + u8 data; + + /* + * For DP rev.1.1, Maximum link rate of Main Link lanes + * 0x06 = 1.62 Gbps, 0x0a = 2.7 Gbps + * For DP rev.1.2, Maximum link rate of Main Link lanes + * 0x06 = 1.62 Gbps, 0x0a = 2.7 Gbps, 0x14 = 5.4Gbps + */ + drm_dp_dpcd_readb(&dp->aux, DP_MAX_LINK_RATE, &data); + *bandwidth = data; +} + +static void analogix_dp_get_max_rx_lane_count(struct analogix_dp_device *dp, + u8 *lane_count) +{ + u8 data; + + /* + * For DP rev.1.1, Maximum number of Main Link lanes + * 0x01 = 1 lane, 0x02 = 2 lanes, 0x04 = 4 lanes + */ + drm_dp_dpcd_readb(&dp->aux, DP_MAX_LANE_COUNT, &data); + *lane_count = DPCD_MAX_LANE_COUNT(data); +} + +static int analogix_dp_full_link_train(struct analogix_dp_device *dp, + u32 max_lanes, u32 max_rate) +{ + int retval = 0; + bool training_finished = false; + + /* + * MACRO_RST must be applied after the PLL_LOCK to avoid + * the DP inter pair skew issue for at least 10 us + */ + analogix_dp_reset_macro(dp); + + /* Initialize by reading RX's DPCD */ + analogix_dp_get_max_rx_bandwidth(dp, &dp->link_train.link_rate); + analogix_dp_get_max_rx_lane_count(dp, &dp->link_train.lane_count); + + if ((dp->link_train.link_rate != DP_LINK_BW_1_62) && + (dp->link_train.link_rate != DP_LINK_BW_2_7) && + (dp->link_train.link_rate != DP_LINK_BW_5_4)) { + dev_err(dp->dev, "Rx Max Link Rate is abnormal :%x !\n", + dp->link_train.link_rate); + dp->link_train.link_rate = DP_LINK_BW_1_62; + } + + if (dp->link_train.lane_count == 0) { + dev_err(dp->dev, "Rx Max Lane count is abnormal :%x !\n", + dp->link_train.lane_count); + dp->link_train.lane_count = (u8)LANE_COUNT1; + } + + /* Setup TX lane count & rate */ + if (dp->link_train.lane_count > max_lanes) + dp->link_train.lane_count = max_lanes; + if (dp->link_train.link_rate > max_rate) + dp->link_train.link_rate = max_rate; + + /* All DP analog module power up */ + analogix_dp_set_analog_power_down(dp, POWER_ALL, 0); + + dp->link_train.lt_state = START; + + /* Process here */ + while (!retval && !training_finished) { + switch (dp->link_train.lt_state) { + case START: + retval = analogix_dp_link_start(dp); + if (retval) + dev_err(dp->dev, "LT link start failed!\n"); + break; + case CLOCK_RECOVERY: + retval = analogix_dp_process_clock_recovery(dp); + if (retval) + dev_err(dp->dev, "LT CR failed!\n"); + break; + case EQUALIZER_TRAINING: + retval = analogix_dp_process_equalizer_training(dp); + if (retval) + dev_err(dp->dev, "LT EQ failed!\n"); + break; + case FINISHED: + training_finished = 1; + break; + case FAILED: + return -EREMOTEIO; + } + } + if (retval) + dev_err(dp->dev, "eDP link training failed (%d)\n", retval); + + return retval; +} + +static int analogix_dp_fast_link_train(struct analogix_dp_device *dp) +{ + int i, ret; + u8 link_align, link_status[2]; + enum pll_status status; + + analogix_dp_reset_macro(dp); + + analogix_dp_set_link_bandwidth(dp, dp->link_train.link_rate); + analogix_dp_set_lane_count(dp, dp->link_train.lane_count); + + for (i = 0; i < dp->link_train.lane_count; i++) { + analogix_dp_set_lane_link_training(dp, + dp->link_train.training_lane[i], i); + } + + ret = readx_poll_timeout(analogix_dp_get_pll_lock_status, dp, status, + status != PLL_UNLOCKED, 120, + 120 * DP_TIMEOUT_LOOP_COUNT); + if (ret) { + DRM_DEV_ERROR(dp->dev, "Wait for pll lock failed %d\n", ret); + return ret; + } + + /* source Set training pattern 1 */ + analogix_dp_set_training_pattern(dp, TRAINING_PTN1); + /* From DP spec, pattern must be on-screen for a minimum 500us */ + usleep_range(500, 600); + + analogix_dp_set_training_pattern(dp, TRAINING_PTN2); + /* From DP spec, pattern must be on-screen for a minimum 500us */ + usleep_range(500, 600); + + /* TODO: enhanced_mode?*/ + analogix_dp_set_training_pattern(dp, DP_NONE); + + /* + * Useful for debugging issues with fast link training, disable for more + * speed + */ + if (verify_fast_training) { + ret = drm_dp_dpcd_readb(&dp->aux, DP_LANE_ALIGN_STATUS_UPDATED, + &link_align); + if (ret < 0) { + DRM_DEV_ERROR(dp->dev, "Read align status failed %d\n", + ret); + return ret; + } + + ret = drm_dp_dpcd_read(&dp->aux, DP_LANE0_1_STATUS, link_status, + 2); + if (ret < 0) { + DRM_DEV_ERROR(dp->dev, "Read link status failed %d\n", + ret); + return ret; + } + + if (analogix_dp_clock_recovery_ok(link_status, + dp->link_train.lane_count)) { + DRM_DEV_ERROR(dp->dev, "Clock recovery failed\n"); + analogix_dp_reduce_link_rate(dp); + return -EIO; + } + + if (analogix_dp_channel_eq_ok(link_status, link_align, + dp->link_train.lane_count)) { + DRM_DEV_ERROR(dp->dev, "Channel EQ failed\n"); + analogix_dp_reduce_link_rate(dp); + return -EIO; + } + } + + return 0; +} + +static int analogix_dp_train_link(struct analogix_dp_device *dp) +{ + if (dp->fast_train_enable) + return analogix_dp_fast_link_train(dp); + + return analogix_dp_full_link_train(dp, dp->video_info.max_lane_count, + dp->video_info.max_link_rate); +} + +static int analogix_dp_config_video(struct analogix_dp_device *dp) +{ + int timeout_loop = 0; + int done_count = 0; + + analogix_dp_config_video_slave_mode(dp); + + analogix_dp_set_video_color_format(dp); + + if (analogix_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { + dev_err(dp->dev, "PLL is not locked yet.\n"); + return -EINVAL; + } + + for (;;) { + timeout_loop++; + if (analogix_dp_is_slave_video_stream_clock_on(dp) == 0) + break; + if (timeout_loop > DP_TIMEOUT_LOOP_COUNT) { + dev_err(dp->dev, "Timeout of slave video streamclk ok\n"); + return -ETIMEDOUT; + } + usleep_range(1000, 1001); + } + + /* Set to use the register calculated M/N video */ + analogix_dp_set_video_cr_mn(dp, CALCULATED_M, 0, 0); + + /* For video bist, Video timing must be generated by register */ + analogix_dp_set_video_timing_mode(dp, VIDEO_TIMING_FROM_CAPTURE); + + /* Disable video mute */ + analogix_dp_enable_video_mute(dp, 0); + + /* Configure video slave mode */ + analogix_dp_enable_video_master(dp, 0); + + /* Enable video */ + analogix_dp_start_video(dp); + + timeout_loop = 0; + + for (;;) { + timeout_loop++; + if (analogix_dp_is_video_stream_on(dp) == 0) { + done_count++; + if (done_count > 10) + break; + } else if (done_count) { + done_count = 0; + } + if (timeout_loop > DP_TIMEOUT_LOOP_COUNT) { + dev_warn(dp->dev, + "Ignoring timeout of video streamclk ok\n"); + break; + } + + usleep_range(1000, 1001); + } + + return 0; +} + +static int analogix_dp_enable_scramble(struct analogix_dp_device *dp, + bool enable) +{ + u8 data; + int ret; + + if (enable) { + analogix_dp_enable_scrambling(dp); + + ret = drm_dp_dpcd_readb(&dp->aux, DP_TRAINING_PATTERN_SET, + &data); + if (ret != 1) + return ret; + ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, + (u8)(data & ~DP_LINK_SCRAMBLING_DISABLE)); + } else { + analogix_dp_disable_scrambling(dp); + + ret = drm_dp_dpcd_readb(&dp->aux, DP_TRAINING_PATTERN_SET, + &data); + if (ret != 1) + return ret; + ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, + (u8)(data | DP_LINK_SCRAMBLING_DISABLE)); + } + return ret < 0 ? ret : 0; +} + +static irqreturn_t analogix_dp_hardirq(int irq, void *arg) +{ + struct analogix_dp_device *dp = arg; + irqreturn_t ret = IRQ_NONE; + enum dp_irq_type irq_type; + + irq_type = analogix_dp_get_irq_type(dp); + if (irq_type != DP_IRQ_TYPE_UNKNOWN) { + analogix_dp_mute_hpd_interrupt(dp); + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +static irqreturn_t analogix_dp_irq_thread(int irq, void *arg) +{ + struct analogix_dp_device *dp = arg; + enum dp_irq_type irq_type; + + irq_type = analogix_dp_get_irq_type(dp); + if (irq_type & DP_IRQ_TYPE_HP_CABLE_IN || + irq_type & DP_IRQ_TYPE_HP_CABLE_OUT) { + dev_dbg(dp->dev, "Detected cable status changed!\n"); + if (dp->drm_dev) + drm_helper_hpd_irq_event(dp->drm_dev); + } + + if (irq_type != DP_IRQ_TYPE_UNKNOWN) { + analogix_dp_clear_hotplug_interrupts(dp); + analogix_dp_unmute_hpd_interrupt(dp); + } + + return IRQ_HANDLED; +} + +static int analogix_dp_fast_link_train_detection(struct analogix_dp_device *dp) +{ + int ret; + u8 spread; + + ret = drm_dp_dpcd_readb(&dp->aux, DP_MAX_DOWNSPREAD, &spread); + if (ret != 1) { + dev_err(dp->dev, "failed to read downspread %d\n", ret); + return ret; + } + dp->fast_train_enable = !!(spread & DP_NO_AUX_HANDSHAKE_LINK_TRAINING); + dev_dbg(dp->dev, "fast link training %s\n", + dp->fast_train_enable ? "supported" : "unsupported"); + return 0; +} + +static int analogix_dp_commit(struct analogix_dp_device *dp) +{ + int ret; + + /* Keep the panel disabled while we configure video */ + if (dp->plat_data->panel) { + if (drm_panel_disable(dp->plat_data->panel)) + DRM_ERROR("failed to disable the panel\n"); + } + + ret = analogix_dp_train_link(dp); + if (ret) { + dev_err(dp->dev, "unable to do link train, ret=%d\n", ret); + return ret; + } + + ret = analogix_dp_enable_scramble(dp, 1); + if (ret < 0) { + dev_err(dp->dev, "can not enable scramble\n"); + return ret; + } + + analogix_dp_init_video(dp); + ret = analogix_dp_config_video(dp); + if (ret) { + dev_err(dp->dev, "unable to config video\n"); + return ret; + } + + /* Safe to enable the panel now */ + if (dp->plat_data->panel) { + ret = drm_panel_enable(dp->plat_data->panel); + if (ret) { + DRM_ERROR("failed to enable the panel\n"); + return ret; + } + } + + /* Check whether panel supports fast training */ + ret = analogix_dp_fast_link_train_detection(dp); + if (ret) + return ret; + + if (analogix_dp_detect_sink_psr(dp)) { + ret = analogix_dp_enable_sink_psr(dp); + if (ret) + return ret; + } + + return ret; +} + +static int analogix_dp_enable_psr(struct analogix_dp_device *dp) +{ + struct dp_sdp psr_vsc; + int ret; + u8 sink; + + ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_STATUS, &sink); + if (ret != 1) + DRM_DEV_ERROR(dp->dev, "Failed to read psr status %d\n", ret); + else if (sink == DP_PSR_SINK_ACTIVE_RFB) + return 0; + + /* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */ + memset(&psr_vsc, 0, sizeof(psr_vsc)); + psr_vsc.sdp_header.HB0 = 0; + psr_vsc.sdp_header.HB1 = 0x7; + psr_vsc.sdp_header.HB2 = 0x2; + psr_vsc.sdp_header.HB3 = 0x8; + psr_vsc.db[0] = 0; + psr_vsc.db[1] = EDP_VSC_PSR_STATE_ACTIVE | EDP_VSC_PSR_CRC_VALUES_VALID; + + ret = analogix_dp_send_psr_spd(dp, &psr_vsc, true); + if (!ret) + analogix_dp_set_analog_power_down(dp, POWER_ALL, true); + + return ret; +} + +static int analogix_dp_disable_psr(struct analogix_dp_device *dp) +{ + struct dp_sdp psr_vsc; + int ret; + u8 sink; + + analogix_dp_set_analog_power_down(dp, POWER_ALL, false); + + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D0); + if (ret != 1) { + DRM_DEV_ERROR(dp->dev, "Failed to set DP Power0 %d\n", ret); + return ret; + } + + ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_STATUS, &sink); + if (ret != 1) { + DRM_DEV_ERROR(dp->dev, "Failed to read psr status %d\n", ret); + return ret; + } else if (sink == DP_PSR_SINK_INACTIVE) { + DRM_DEV_ERROR(dp->dev, "sink inactive, skip disable psr"); + return 0; + } + + ret = analogix_dp_train_link(dp); + if (ret) { + DRM_DEV_ERROR(dp->dev, "Failed to train the link %d\n", ret); + return ret; + } + + /* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */ + memset(&psr_vsc, 0, sizeof(psr_vsc)); + psr_vsc.sdp_header.HB0 = 0; + psr_vsc.sdp_header.HB1 = 0x7; + psr_vsc.sdp_header.HB2 = 0x2; + psr_vsc.sdp_header.HB3 = 0x8; + + psr_vsc.db[0] = 0; + psr_vsc.db[1] = 0; + + return analogix_dp_send_psr_spd(dp, &psr_vsc, true); +} + +/* + * This function is a bit of a catch-all for panel preparation, hopefully + * simplifying the logic of functions that need to prepare/unprepare the panel + * below. + * + * If @prepare is true, this function will prepare the panel. Conversely, if it + * is false, the panel will be unprepared. + * + * If @is_modeset_prepare is true, the function will disregard the current state + * of the panel and either prepare/unprepare the panel based on @prepare. Once + * it finishes, it will update dp->panel_is_modeset to reflect the current state + * of the panel. + */ +static int analogix_dp_prepare_panel(struct analogix_dp_device *dp, + bool prepare, bool is_modeset_prepare) +{ + int ret = 0; + + if (!dp->plat_data->panel) + return 0; + + mutex_lock(&dp->panel_lock); + + /* + * Exit early if this is a temporary prepare/unprepare and we're already + * modeset (since we neither want to prepare twice or unprepare early). + */ + if (dp->panel_is_modeset && !is_modeset_prepare) + goto out; + + if (prepare) + ret = drm_panel_prepare(dp->plat_data->panel); + else + ret = drm_panel_unprepare(dp->plat_data->panel); + + if (ret) + goto out; + + if (is_modeset_prepare) + dp->panel_is_modeset = prepare; + +out: + mutex_unlock(&dp->panel_lock); + return ret; +} + +static int analogix_dp_get_modes(struct drm_connector *connector) +{ + struct analogix_dp_device *dp = to_dp(connector); + struct edid *edid; + int ret, num_modes = 0; + + if (dp->plat_data->panel) { + num_modes += drm_panel_get_modes(dp->plat_data->panel, connector); + } else { + ret = analogix_dp_prepare_panel(dp, true, false); + if (ret) { + DRM_ERROR("Failed to prepare panel (%d)\n", ret); + return 0; + } + + pm_runtime_get_sync(dp->dev); + edid = drm_get_edid(connector, &dp->aux.ddc); + pm_runtime_put(dp->dev); + if (edid) { + drm_connector_update_edid_property(&dp->connector, + edid); + num_modes += drm_add_edid_modes(&dp->connector, edid); + kfree(edid); + } + + ret = analogix_dp_prepare_panel(dp, false, false); + if (ret) + DRM_ERROR("Failed to unprepare panel (%d)\n", ret); + } + + if (dp->plat_data->get_modes) + num_modes += dp->plat_data->get_modes(dp->plat_data, connector); + + return num_modes; +} + +static struct drm_encoder * +analogix_dp_best_encoder(struct drm_connector *connector) +{ + struct analogix_dp_device *dp = to_dp(connector); + + return dp->encoder; +} + + +static int analogix_dp_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct analogix_dp_device *dp = to_dp(connector); + struct drm_connector_state *conn_state; + struct drm_crtc_state *crtc_state; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (WARN_ON(!conn_state)) + return -ENODEV; + + conn_state->self_refresh_aware = true; + + if (!conn_state->crtc) + return 0; + + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + if (!crtc_state) + return 0; + + if (crtc_state->self_refresh_active && !dp->psr_supported) + return -EINVAL; + + return 0; +} + +static const struct drm_connector_helper_funcs analogix_dp_connector_helper_funcs = { + .get_modes = analogix_dp_get_modes, + .best_encoder = analogix_dp_best_encoder, + .atomic_check = analogix_dp_atomic_check, +}; + +static enum drm_connector_status +analogix_dp_detect(struct drm_connector *connector, bool force) +{ + struct analogix_dp_device *dp = to_dp(connector); + enum drm_connector_status status = connector_status_disconnected; + int ret; + + if (dp->plat_data->panel) + return connector_status_connected; + + ret = analogix_dp_prepare_panel(dp, true, false); + if (ret) { + DRM_ERROR("Failed to prepare panel (%d)\n", ret); + return connector_status_disconnected; + } + + if (!analogix_dp_detect_hpd(dp)) + status = connector_status_connected; + + ret = analogix_dp_prepare_panel(dp, false, false); + if (ret) + DRM_ERROR("Failed to unprepare panel (%d)\n", ret); + + return status; +} + +static const struct drm_connector_funcs analogix_dp_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = analogix_dp_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int analogix_dp_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct analogix_dp_device *dp = bridge->driver_private; + struct drm_encoder *encoder = dp->encoder; + struct drm_connector *connector = NULL; + int ret = 0; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + DRM_ERROR("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + if (!dp->plat_data->skip_connector) { + connector = &dp->connector; + connector->polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(dp->drm_dev, connector, + &analogix_dp_connector_funcs, + DRM_MODE_CONNECTOR_eDP); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_helper_add(connector, + &analogix_dp_connector_helper_funcs); + drm_connector_attach_encoder(connector, encoder); + } + + /* + * NOTE: the connector registration is implemented in analogix + * platform driver, that to say connector would be exist after + * plat_data->attch return, that's why we record the connector + * point after plat attached. + */ + if (dp->plat_data->attach) { + ret = dp->plat_data->attach(dp->plat_data, bridge, connector); + if (ret) { + DRM_ERROR("Failed at platform attach func\n"); + return ret; + } + } + + return 0; +} + +static +struct drm_crtc *analogix_dp_get_old_crtc(struct analogix_dp_device *dp, + struct drm_atomic_state *state) +{ + struct drm_encoder *encoder = dp->encoder; + struct drm_connector *connector; + struct drm_connector_state *conn_state; + + connector = drm_atomic_get_old_connector_for_encoder(state, encoder); + if (!connector) + return NULL; + + conn_state = drm_atomic_get_old_connector_state(state, connector); + if (!conn_state) + return NULL; + + return conn_state->crtc; +} + +static +struct drm_crtc *analogix_dp_get_new_crtc(struct analogix_dp_device *dp, + struct drm_atomic_state *state) +{ + struct drm_encoder *encoder = dp->encoder; + struct drm_connector *connector; + struct drm_connector_state *conn_state; + + connector = drm_atomic_get_new_connector_for_encoder(state, encoder); + if (!connector) + return NULL; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (!conn_state) + return NULL; + + return conn_state->crtc; +} + +static void +analogix_dp_bridge_atomic_pre_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct drm_atomic_state *old_state = old_bridge_state->base.state; + struct analogix_dp_device *dp = bridge->driver_private; + struct drm_crtc *crtc; + struct drm_crtc_state *old_crtc_state; + int ret; + + crtc = analogix_dp_get_new_crtc(dp, old_state); + if (!crtc) + return; + + old_crtc_state = drm_atomic_get_old_crtc_state(old_state, crtc); + /* Don't touch the panel if we're coming back from PSR */ + if (old_crtc_state && old_crtc_state->self_refresh_active) + return; + + ret = analogix_dp_prepare_panel(dp, true, true); + if (ret) + DRM_ERROR("failed to setup the panel ret = %d\n", ret); +} + +static int analogix_dp_set_bridge(struct analogix_dp_device *dp) +{ + int ret; + + pm_runtime_get_sync(dp->dev); + + ret = clk_prepare_enable(dp->clock); + if (ret < 0) { + DRM_ERROR("Failed to prepare_enable the clock clk [%d]\n", ret); + goto out_dp_clk_pre; + } + + if (dp->plat_data->power_on_start) + dp->plat_data->power_on_start(dp->plat_data); + + phy_power_on(dp->phy); + + ret = analogix_dp_init_dp(dp); + if (ret) + goto out_dp_init; + + /* + * According to DP spec v1.3 chap 3.5.1.2 Link Training, + * We should first make sure the HPD signal is asserted high by device + * when we want to establish a link with it. + */ + ret = analogix_dp_detect_hpd(dp); + if (ret) { + DRM_ERROR("failed to get hpd single ret = %d\n", ret); + goto out_dp_init; + } + + ret = analogix_dp_commit(dp); + if (ret) { + DRM_ERROR("dp commit error, ret = %d\n", ret); + goto out_dp_init; + } + + if (dp->plat_data->power_on_end) + dp->plat_data->power_on_end(dp->plat_data); + + enable_irq(dp->irq); + return 0; + +out_dp_init: + phy_power_off(dp->phy); + if (dp->plat_data->power_off) + dp->plat_data->power_off(dp->plat_data); + clk_disable_unprepare(dp->clock); +out_dp_clk_pre: + pm_runtime_put_sync(dp->dev); + + return ret; +} + +static void +analogix_dp_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct drm_atomic_state *old_state = old_bridge_state->base.state; + struct analogix_dp_device *dp = bridge->driver_private; + struct drm_crtc *crtc; + struct drm_crtc_state *old_crtc_state; + int timeout_loop = 0; + int ret; + + crtc = analogix_dp_get_new_crtc(dp, old_state); + if (!crtc) + return; + + old_crtc_state = drm_atomic_get_old_crtc_state(old_state, crtc); + /* Not a full enable, just disable PSR and continue */ + if (old_crtc_state && old_crtc_state->self_refresh_active) { + ret = analogix_dp_disable_psr(dp); + if (ret) + DRM_ERROR("Failed to disable psr %d\n", ret); + return; + } + + if (dp->dpms_mode == DRM_MODE_DPMS_ON) + return; + + while (timeout_loop < MAX_PLL_LOCK_LOOP) { + if (analogix_dp_set_bridge(dp) == 0) { + dp->dpms_mode = DRM_MODE_DPMS_ON; + return; + } + dev_err(dp->dev, "failed to set bridge, retry: %d\n", + timeout_loop); + timeout_loop++; + usleep_range(10, 11); + } + dev_err(dp->dev, "too many times retry set bridge, give it up\n"); +} + +static void analogix_dp_bridge_disable(struct drm_bridge *bridge) +{ + struct analogix_dp_device *dp = bridge->driver_private; + int ret; + + if (dp->dpms_mode != DRM_MODE_DPMS_ON) + return; + + if (dp->plat_data->panel) { + if (drm_panel_disable(dp->plat_data->panel)) { + DRM_ERROR("failed to disable the panel\n"); + return; + } + } + + disable_irq(dp->irq); + + if (dp->plat_data->power_off) + dp->plat_data->power_off(dp->plat_data); + + analogix_dp_set_analog_power_down(dp, POWER_ALL, 1); + phy_power_off(dp->phy); + + clk_disable_unprepare(dp->clock); + + pm_runtime_put_sync(dp->dev); + + ret = analogix_dp_prepare_panel(dp, false, true); + if (ret) + DRM_ERROR("failed to setup the panel ret = %d\n", ret); + + dp->fast_train_enable = false; + dp->psr_supported = false; + dp->dpms_mode = DRM_MODE_DPMS_OFF; +} + +static void +analogix_dp_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct drm_atomic_state *old_state = old_bridge_state->base.state; + struct analogix_dp_device *dp = bridge->driver_private; + struct drm_crtc *old_crtc, *new_crtc; + struct drm_crtc_state *old_crtc_state = NULL; + struct drm_crtc_state *new_crtc_state = NULL; + int ret; + + new_crtc = analogix_dp_get_new_crtc(dp, old_state); + if (!new_crtc) + goto out; + + new_crtc_state = drm_atomic_get_new_crtc_state(old_state, new_crtc); + if (!new_crtc_state) + goto out; + + /* Don't do a full disable on PSR transitions */ + if (new_crtc_state->self_refresh_active) + return; + +out: + old_crtc = analogix_dp_get_old_crtc(dp, old_state); + if (old_crtc) { + old_crtc_state = drm_atomic_get_old_crtc_state(old_state, + old_crtc); + + /* When moving from PSR to fully disabled, exit PSR first. */ + if (old_crtc_state && old_crtc_state->self_refresh_active) { + ret = analogix_dp_disable_psr(dp); + if (ret) + DRM_ERROR("Failed to disable psr (%d)\n", ret); + } + } + + analogix_dp_bridge_disable(bridge); +} + +static void +analogix_dp_bridge_atomic_post_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct drm_atomic_state *old_state = old_bridge_state->base.state; + struct analogix_dp_device *dp = bridge->driver_private; + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + int ret; + + crtc = analogix_dp_get_new_crtc(dp, old_state); + if (!crtc) + return; + + new_crtc_state = drm_atomic_get_new_crtc_state(old_state, crtc); + if (!new_crtc_state || !new_crtc_state->self_refresh_active) + return; + + ret = analogix_dp_enable_psr(dp); + if (ret) + DRM_ERROR("Failed to enable psr (%d)\n", ret); +} + +static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *orig_mode, + const struct drm_display_mode *mode) +{ + struct analogix_dp_device *dp = bridge->driver_private; + struct drm_display_info *display_info = &dp->connector.display_info; + struct video_info *video = &dp->video_info; + struct device_node *dp_node = dp->dev->of_node; + int vic; + + /* Input video interlaces & hsync pol & vsync pol */ + video->interlaced = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); + video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC); + video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC); + + /* Input video dynamic_range & colorimetry */ + vic = drm_match_cea_mode(mode); + if ((vic == 6) || (vic == 7) || (vic == 21) || (vic == 22) || + (vic == 2) || (vic == 3) || (vic == 17) || (vic == 18)) { + video->dynamic_range = CEA; + video->ycbcr_coeff = COLOR_YCBCR601; + } else if (vic) { + video->dynamic_range = CEA; + video->ycbcr_coeff = COLOR_YCBCR709; + } else { + video->dynamic_range = VESA; + video->ycbcr_coeff = COLOR_YCBCR709; + } + + /* Input vide bpc and color_formats */ + switch (display_info->bpc) { + case 12: + video->color_depth = COLOR_12; + break; + case 10: + video->color_depth = COLOR_10; + break; + case 8: + video->color_depth = COLOR_8; + break; + case 6: + video->color_depth = COLOR_6; + break; + default: + video->color_depth = COLOR_8; + break; + } + if (display_info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + video->color_space = COLOR_YCBCR444; + else if (display_info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + video->color_space = COLOR_YCBCR422; + else + video->color_space = COLOR_RGB; + + /* + * NOTE: those property parsing code is used for providing backward + * compatibility for samsung platform. + * Due to we used the "of_property_read_u32" interfaces, when this + * property isn't present, the "video_info" can keep the original + * values and wouldn't be modified. + */ + of_property_read_u32(dp_node, "samsung,color-space", + &video->color_space); + of_property_read_u32(dp_node, "samsung,dynamic-range", + &video->dynamic_range); + of_property_read_u32(dp_node, "samsung,ycbcr-coeff", + &video->ycbcr_coeff); + of_property_read_u32(dp_node, "samsung,color-depth", + &video->color_depth); + if (of_property_read_bool(dp_node, "hsync-active-high")) + video->h_sync_polarity = true; + if (of_property_read_bool(dp_node, "vsync-active-high")) + video->v_sync_polarity = true; + if (of_property_read_bool(dp_node, "interlaced")) + video->interlaced = true; +} + +static const struct drm_bridge_funcs analogix_dp_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_pre_enable = analogix_dp_bridge_atomic_pre_enable, + .atomic_enable = analogix_dp_bridge_atomic_enable, + .atomic_disable = analogix_dp_bridge_atomic_disable, + .atomic_post_disable = analogix_dp_bridge_atomic_post_disable, + .mode_set = analogix_dp_bridge_mode_set, + .attach = analogix_dp_bridge_attach, +}; + +static int analogix_dp_create_bridge(struct drm_device *drm_dev, + struct analogix_dp_device *dp) +{ + struct drm_bridge *bridge; + int ret; + + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + DRM_ERROR("failed to allocate for drm bridge\n"); + return -ENOMEM; + } + + dp->bridge = bridge; + + bridge->driver_private = dp; + bridge->funcs = &analogix_dp_bridge_funcs; + + ret = drm_bridge_attach(dp->encoder, bridge, NULL, 0); + if (ret) { + DRM_ERROR("failed to attach drm bridge\n"); + return -EINVAL; + } + + return 0; +} + +static int analogix_dp_dt_parse_pdata(struct analogix_dp_device *dp) +{ + struct device_node *dp_node = dp->dev->of_node; + struct video_info *video_info = &dp->video_info; + + switch (dp->plat_data->dev_type) { + case RK3288_DP: + case RK3399_EDP: + /* + * Like Rk3288 DisplayPort TRM indicate that "Main link + * containing 4 physical lanes of 2.7/1.62 Gbps/lane". + */ + video_info->max_link_rate = 0x0A; + video_info->max_lane_count = 0x04; + break; + case EXYNOS_DP: + /* + * NOTE: those property parseing code is used for + * providing backward compatibility for samsung platform. + */ + of_property_read_u32(dp_node, "samsung,link-rate", + &video_info->max_link_rate); + of_property_read_u32(dp_node, "samsung,lane-count", + &video_info->max_lane_count); + break; + } + + return 0; +} + +static ssize_t analogix_dpaux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct analogix_dp_device *dp = to_dp(aux); + int ret; + + pm_runtime_get_sync(dp->dev); + + ret = analogix_dp_detect_hpd(dp); + if (ret) + goto out; + + ret = analogix_dp_transfer(dp, msg); +out: + pm_runtime_put(dp->dev); + + return ret; +} + +struct analogix_dp_device * +analogix_dp_probe(struct device *dev, struct analogix_dp_plat_data *plat_data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct analogix_dp_device *dp; + struct resource *res; + unsigned int irq_flags; + int ret; + + if (!plat_data) { + dev_err(dev, "Invalided input plat_data\n"); + return ERR_PTR(-EINVAL); + } + + dp = devm_kzalloc(dev, sizeof(struct analogix_dp_device), GFP_KERNEL); + if (!dp) + return ERR_PTR(-ENOMEM); + + dp->dev = &pdev->dev; + dp->dpms_mode = DRM_MODE_DPMS_OFF; + + mutex_init(&dp->panel_lock); + dp->panel_is_modeset = false; + + /* + * platform dp driver need containor_of the plat_data to get + * the driver private data, so we need to store the point of + * plat_data, not the context of plat_data. + */ + dp->plat_data = plat_data; + + ret = analogix_dp_dt_parse_pdata(dp); + if (ret) + return ERR_PTR(ret); + + dp->phy = devm_phy_get(dp->dev, "dp"); + if (IS_ERR(dp->phy)) { + dev_err(dp->dev, "no DP phy configured\n"); + ret = PTR_ERR(dp->phy); + if (ret) { + /* + * phy itself is not enabled, so we can move forward + * assigning NULL to phy pointer. + */ + if (ret == -ENOSYS || ret == -ENODEV) + dp->phy = NULL; + else + return ERR_PTR(ret); + } + } + + dp->clock = devm_clk_get(&pdev->dev, "dp"); + if (IS_ERR(dp->clock)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return ERR_CAST(dp->clock); + } + + clk_prepare_enable(dp->clock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + dp->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dp->reg_base)) { + ret = PTR_ERR(dp->reg_base); + goto err_disable_clk; + } + + dp->force_hpd = of_property_read_bool(dev->of_node, "force-hpd"); + + /* Try two different names */ + dp->hpd_gpiod = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN); + if (!dp->hpd_gpiod) + dp->hpd_gpiod = devm_gpiod_get_optional(dev, "samsung,hpd", + GPIOD_IN); + if (IS_ERR(dp->hpd_gpiod)) { + dev_err(dev, "error getting HDP GPIO: %ld\n", + PTR_ERR(dp->hpd_gpiod)); + ret = PTR_ERR(dp->hpd_gpiod); + goto err_disable_clk; + } + + if (dp->hpd_gpiod) { + /* + * Set up the hotplug GPIO from the device tree as an interrupt. + * Simply specifying a different interrupt in the device tree + * doesn't work since we handle hotplug rather differently when + * using a GPIO. We also need the actual GPIO specifier so + * that we can get the current state of the GPIO. + */ + dp->irq = gpiod_to_irq(dp->hpd_gpiod); + irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; + } else { + dp->irq = platform_get_irq(pdev, 0); + irq_flags = 0; + } + + if (dp->irq == -ENXIO) { + dev_err(&pdev->dev, "failed to get irq\n"); + ret = -ENODEV; + goto err_disable_clk; + } + + ret = devm_request_threaded_irq(&pdev->dev, dp->irq, + analogix_dp_hardirq, + analogix_dp_irq_thread, + irq_flags, "analogix-dp", dp); + if (ret) { + dev_err(&pdev->dev, "failed to request irq\n"); + goto err_disable_clk; + } + disable_irq(dp->irq); + + return dp; + +err_disable_clk: + clk_disable_unprepare(dp->clock); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(analogix_dp_probe); + +int analogix_dp_bind(struct analogix_dp_device *dp, struct drm_device *drm_dev) +{ + int ret; + + dp->drm_dev = drm_dev; + dp->encoder = dp->plat_data->encoder; + + dp->aux.name = "DP-AUX"; + dp->aux.transfer = analogix_dpaux_transfer; + dp->aux.dev = dp->dev; + + ret = drm_dp_aux_register(&dp->aux); + if (ret) + return ret; + + pm_runtime_enable(dp->dev); + + ret = analogix_dp_create_bridge(drm_dev, dp); + if (ret) { + DRM_ERROR("failed to create bridge (%d)\n", ret); + goto err_disable_pm_runtime; + } + + return 0; + +err_disable_pm_runtime: + pm_runtime_disable(dp->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(analogix_dp_bind); + +void analogix_dp_unbind(struct analogix_dp_device *dp) +{ + analogix_dp_bridge_disable(dp->bridge); + dp->connector.funcs->destroy(&dp->connector); + + if (dp->plat_data->panel) { + if (drm_panel_unprepare(dp->plat_data->panel)) + DRM_ERROR("failed to turnoff the panel\n"); + } + + drm_dp_aux_unregister(&dp->aux); + pm_runtime_disable(dp->dev); +} +EXPORT_SYMBOL_GPL(analogix_dp_unbind); + +void analogix_dp_remove(struct analogix_dp_device *dp) +{ + clk_disable_unprepare(dp->clock); +} +EXPORT_SYMBOL_GPL(analogix_dp_remove); + +#ifdef CONFIG_PM +int analogix_dp_suspend(struct analogix_dp_device *dp) +{ + clk_disable_unprepare(dp->clock); + return 0; +} +EXPORT_SYMBOL_GPL(analogix_dp_suspend); + +int analogix_dp_resume(struct analogix_dp_device *dp) +{ + int ret; + + ret = clk_prepare_enable(dp->clock); + if (ret < 0) { + DRM_ERROR("Failed to prepare_enable the clock clk [%d]\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(analogix_dp_resume); +#endif + +int analogix_dp_start_crc(struct drm_connector *connector) +{ + struct analogix_dp_device *dp = to_dp(connector); + + if (!connector->state->crtc) { + DRM_ERROR("Connector %s doesn't currently have a CRTC.\n", + connector->name); + return -EINVAL; + } + + return drm_dp_start_crc(&dp->aux, connector->state->crtc); +} +EXPORT_SYMBOL_GPL(analogix_dp_start_crc); + +int analogix_dp_stop_crc(struct drm_connector *connector) +{ + struct analogix_dp_device *dp = to_dp(connector); + + return drm_dp_stop_crc(&dp->aux); +} +EXPORT_SYMBOL_GPL(analogix_dp_stop_crc); + +MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); +MODULE_DESCRIPTION("Analogix DP Core Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h new file mode 100644 index 000000000..c051502d7 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h @@ -0,0 +1,259 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Header file for Analogix DP (Display Port) core interface driver. + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + */ + +#ifndef _ANALOGIX_DP_CORE_H +#define _ANALOGIX_DP_CORE_H + +#include <drm/drm_crtc.h> +#include <drm/drm_dp_helper.h> + +#define DP_TIMEOUT_LOOP_COUNT 100 +#define MAX_CR_LOOP 5 +#define MAX_EQ_LOOP 5 +#define MAX_PLL_LOCK_LOOP 5 + +/* Training takes 22ms if AUX channel comm fails. Use this as retry interval */ +#define DP_TIMEOUT_TRAINING_US 22000 +#define DP_TIMEOUT_PSR_LOOP_MS 300 + +/* DP_MAX_LANE_COUNT */ +#define DPCD_ENHANCED_FRAME_CAP(x) (((x) >> 7) & 0x1) +#define DPCD_MAX_LANE_COUNT(x) ((x) & 0x1f) + +/* DP_LANE_COUNT_SET */ +#define DPCD_LANE_COUNT_SET(x) ((x) & 0x1f) + +/* DP_TRAINING_LANE0_SET */ +#define DPCD_PRE_EMPHASIS_SET(x) (((x) & 0x3) << 3) +#define DPCD_PRE_EMPHASIS_GET(x) (((x) >> 3) & 0x3) +#define DPCD_VOLTAGE_SWING_SET(x) (((x) & 0x3) << 0) +#define DPCD_VOLTAGE_SWING_GET(x) (((x) >> 0) & 0x3) + +struct gpio_desc; + +enum link_lane_count_type { + LANE_COUNT1 = 1, + LANE_COUNT2 = 2, + LANE_COUNT4 = 4 +}; + +enum link_training_state { + START, + CLOCK_RECOVERY, + EQUALIZER_TRAINING, + FINISHED, + FAILED +}; + +enum voltage_swing_level { + VOLTAGE_LEVEL_0, + VOLTAGE_LEVEL_1, + VOLTAGE_LEVEL_2, + VOLTAGE_LEVEL_3, +}; + +enum pre_emphasis_level { + PRE_EMPHASIS_LEVEL_0, + PRE_EMPHASIS_LEVEL_1, + PRE_EMPHASIS_LEVEL_2, + PRE_EMPHASIS_LEVEL_3, +}; + +enum pattern_set { + PRBS7, + D10_2, + TRAINING_PTN1, + TRAINING_PTN2, + DP_NONE +}; + +enum color_space { + COLOR_RGB, + COLOR_YCBCR422, + COLOR_YCBCR444 +}; + +enum color_depth { + COLOR_6, + COLOR_8, + COLOR_10, + COLOR_12 +}; + +enum color_coefficient { + COLOR_YCBCR601, + COLOR_YCBCR709 +}; + +enum dynamic_range { + VESA, + CEA +}; + +enum pll_status { + PLL_UNLOCKED, + PLL_LOCKED +}; + +enum clock_recovery_m_value_type { + CALCULATED_M, + REGISTER_M +}; + +enum video_timing_recognition_type { + VIDEO_TIMING_FROM_CAPTURE, + VIDEO_TIMING_FROM_REGISTER +}; + +enum analog_power_block { + AUX_BLOCK, + CH0_BLOCK, + CH1_BLOCK, + CH2_BLOCK, + CH3_BLOCK, + ANALOG_TOTAL, + POWER_ALL +}; + +enum dp_irq_type { + DP_IRQ_TYPE_HP_CABLE_IN = BIT(0), + DP_IRQ_TYPE_HP_CABLE_OUT = BIT(1), + DP_IRQ_TYPE_HP_CHANGE = BIT(2), + DP_IRQ_TYPE_UNKNOWN = BIT(3), +}; + +struct video_info { + char *name; + + bool h_sync_polarity; + bool v_sync_polarity; + bool interlaced; + + enum color_space color_space; + enum dynamic_range dynamic_range; + enum color_coefficient ycbcr_coeff; + enum color_depth color_depth; + + int max_link_rate; + enum link_lane_count_type max_lane_count; +}; + +struct link_train { + int eq_loop; + int cr_loop[4]; + + u8 link_rate; + u8 lane_count; + u8 training_lane[4]; + + enum link_training_state lt_state; +}; + +struct analogix_dp_device { + struct drm_encoder *encoder; + struct device *dev; + struct drm_device *drm_dev; + struct drm_connector connector; + struct drm_bridge *bridge; + struct drm_dp_aux aux; + struct clk *clock; + unsigned int irq; + void __iomem *reg_base; + + struct video_info video_info; + struct link_train link_train; + struct phy *phy; + int dpms_mode; + struct gpio_desc *hpd_gpiod; + bool force_hpd; + bool fast_train_enable; + bool psr_supported; + + struct mutex panel_lock; + bool panel_is_modeset; + + struct analogix_dp_plat_data *plat_data; +}; + +/* analogix_dp_reg.c */ +void analogix_dp_enable_video_mute(struct analogix_dp_device *dp, bool enable); +void analogix_dp_stop_video(struct analogix_dp_device *dp); +void analogix_dp_lane_swap(struct analogix_dp_device *dp, bool enable); +void analogix_dp_init_analog_param(struct analogix_dp_device *dp); +void analogix_dp_init_interrupt(struct analogix_dp_device *dp); +void analogix_dp_reset(struct analogix_dp_device *dp); +void analogix_dp_swreset(struct analogix_dp_device *dp); +void analogix_dp_config_interrupt(struct analogix_dp_device *dp); +void analogix_dp_mute_hpd_interrupt(struct analogix_dp_device *dp); +void analogix_dp_unmute_hpd_interrupt(struct analogix_dp_device *dp); +enum pll_status analogix_dp_get_pll_lock_status(struct analogix_dp_device *dp); +void analogix_dp_set_pll_power_down(struct analogix_dp_device *dp, bool enable); +void analogix_dp_set_analog_power_down(struct analogix_dp_device *dp, + enum analog_power_block block, + bool enable); +int analogix_dp_init_analog_func(struct analogix_dp_device *dp); +void analogix_dp_init_hpd(struct analogix_dp_device *dp); +void analogix_dp_force_hpd(struct analogix_dp_device *dp); +enum dp_irq_type analogix_dp_get_irq_type(struct analogix_dp_device *dp); +void analogix_dp_clear_hotplug_interrupts(struct analogix_dp_device *dp); +void analogix_dp_reset_aux(struct analogix_dp_device *dp); +void analogix_dp_init_aux(struct analogix_dp_device *dp); +int analogix_dp_get_plug_in_status(struct analogix_dp_device *dp); +void analogix_dp_enable_sw_function(struct analogix_dp_device *dp); +void analogix_dp_set_link_bandwidth(struct analogix_dp_device *dp, u32 bwtype); +void analogix_dp_get_link_bandwidth(struct analogix_dp_device *dp, u32 *bwtype); +void analogix_dp_set_lane_count(struct analogix_dp_device *dp, u32 count); +void analogix_dp_get_lane_count(struct analogix_dp_device *dp, u32 *count); +void analogix_dp_enable_enhanced_mode(struct analogix_dp_device *dp, + bool enable); +void analogix_dp_set_training_pattern(struct analogix_dp_device *dp, + enum pattern_set pattern); +void analogix_dp_set_lane0_pre_emphasis(struct analogix_dp_device *dp, + u32 level); +void analogix_dp_set_lane1_pre_emphasis(struct analogix_dp_device *dp, + u32 level); +void analogix_dp_set_lane2_pre_emphasis(struct analogix_dp_device *dp, + u32 level); +void analogix_dp_set_lane3_pre_emphasis(struct analogix_dp_device *dp, + u32 level); +void analogix_dp_set_lane0_link_training(struct analogix_dp_device *dp, + u32 training_lane); +void analogix_dp_set_lane1_link_training(struct analogix_dp_device *dp, + u32 training_lane); +void analogix_dp_set_lane2_link_training(struct analogix_dp_device *dp, + u32 training_lane); +void analogix_dp_set_lane3_link_training(struct analogix_dp_device *dp, + u32 training_lane); +u32 analogix_dp_get_lane0_link_training(struct analogix_dp_device *dp); +u32 analogix_dp_get_lane1_link_training(struct analogix_dp_device *dp); +u32 analogix_dp_get_lane2_link_training(struct analogix_dp_device *dp); +u32 analogix_dp_get_lane3_link_training(struct analogix_dp_device *dp); +void analogix_dp_reset_macro(struct analogix_dp_device *dp); +void analogix_dp_init_video(struct analogix_dp_device *dp); + +void analogix_dp_set_video_color_format(struct analogix_dp_device *dp); +int analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp); +void analogix_dp_set_video_cr_mn(struct analogix_dp_device *dp, + enum clock_recovery_m_value_type type, + u32 m_value, + u32 n_value); +void analogix_dp_set_video_timing_mode(struct analogix_dp_device *dp, u32 type); +void analogix_dp_enable_video_master(struct analogix_dp_device *dp, + bool enable); +void analogix_dp_start_video(struct analogix_dp_device *dp); +int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp); +void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp); +void analogix_dp_enable_scrambling(struct analogix_dp_device *dp); +void analogix_dp_disable_scrambling(struct analogix_dp_device *dp); +void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp); +int analogix_dp_send_psr_spd(struct analogix_dp_device *dp, + struct dp_sdp *vsc, bool blocking); +ssize_t analogix_dp_transfer(struct analogix_dp_device *dp, + struct drm_dp_aux_msg *msg); + +#endif /* _ANALOGIX_DP_CORE_H */ diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c new file mode 100644 index 000000000..cab3f5c4e --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c @@ -0,0 +1,1242 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Analogix DP (Display port) core register interface driver. + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/io.h> +#include <linux/iopoll.h> + +#include <drm/bridge/analogix_dp.h> + +#include "analogix_dp_core.h" +#include "analogix_dp_reg.h" + +#define COMMON_INT_MASK_1 0 +#define COMMON_INT_MASK_2 0 +#define COMMON_INT_MASK_3 0 +#define COMMON_INT_MASK_4 (HOTPLUG_CHG | HPD_LOST | PLUG) +#define INT_STA_MASK INT_HPD + +void analogix_dp_enable_video_mute(struct analogix_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + reg |= HDCP_VIDEO_MUTE; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + reg &= ~HDCP_VIDEO_MUTE; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + } +} + +void analogix_dp_stop_video(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + reg &= ~VIDEO_EN; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); +} + +void analogix_dp_lane_swap(struct analogix_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) + reg = LANE3_MAP_LOGIC_LANE_0 | LANE2_MAP_LOGIC_LANE_1 | + LANE1_MAP_LOGIC_LANE_2 | LANE0_MAP_LOGIC_LANE_3; + else + reg = LANE3_MAP_LOGIC_LANE_3 | LANE2_MAP_LOGIC_LANE_2 | + LANE1_MAP_LOGIC_LANE_1 | LANE0_MAP_LOGIC_LANE_0; + + writel(reg, dp->reg_base + ANALOGIX_DP_LANE_MAP); +} + +void analogix_dp_init_analog_param(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = TX_TERMINAL_CTRL_50_OHM; + writel(reg, dp->reg_base + ANALOGIX_DP_ANALOG_CTL_1); + + reg = SEL_24M | TX_DVDD_BIT_1_0625V; + writel(reg, dp->reg_base + ANALOGIX_DP_ANALOG_CTL_2); + + if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) { + reg = REF_CLK_24M; + if (dp->plat_data->dev_type == RK3288_DP) + reg ^= REF_CLK_MASK; + + writel(reg, dp->reg_base + ANALOGIX_DP_PLL_REG_1); + writel(0x95, dp->reg_base + ANALOGIX_DP_PLL_REG_2); + writel(0x40, dp->reg_base + ANALOGIX_DP_PLL_REG_3); + writel(0x58, dp->reg_base + ANALOGIX_DP_PLL_REG_4); + writel(0x22, dp->reg_base + ANALOGIX_DP_PLL_REG_5); + } + + reg = DRIVE_DVDD_BIT_1_0625V | VCO_BIT_600_MICRO; + writel(reg, dp->reg_base + ANALOGIX_DP_ANALOG_CTL_3); + + reg = PD_RING_OSC | AUX_TERMINAL_CTRL_50_OHM | + TX_CUR1_2X | TX_CUR_16_MA; + writel(reg, dp->reg_base + ANALOGIX_DP_PLL_FILTER_CTL_1); + + reg = CH3_AMP_400_MV | CH2_AMP_400_MV | + CH1_AMP_400_MV | CH0_AMP_400_MV; + writel(reg, dp->reg_base + ANALOGIX_DP_TX_AMP_TUNING_CTL); +} + +void analogix_dp_init_interrupt(struct analogix_dp_device *dp) +{ + /* Set interrupt pin assertion polarity as high */ + writel(INT_POL1 | INT_POL0, dp->reg_base + ANALOGIX_DP_INT_CTL); + + /* Clear pending regisers */ + writel(0xff, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_1); + writel(0x4f, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_2); + writel(0xe0, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_3); + writel(0xe7, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_4); + writel(0x63, dp->reg_base + ANALOGIX_DP_INT_STA); + + /* 0:mask,1: unmask */ + writel(0x00, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_1); + writel(0x00, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_2); + writel(0x00, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_3); + writel(0x00, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + writel(0x00, dp->reg_base + ANALOGIX_DP_INT_STA_MASK); +} + +void analogix_dp_reset(struct analogix_dp_device *dp) +{ + u32 reg; + + analogix_dp_stop_video(dp); + analogix_dp_enable_video_mute(dp, 0); + + if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) + reg = RK_VID_CAP_FUNC_EN_N | RK_VID_FIFO_FUNC_EN_N | + SW_FUNC_EN_N; + else + reg = MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N | + AUD_FIFO_FUNC_EN_N | AUD_FUNC_EN_N | + HDCP_FUNC_EN_N | SW_FUNC_EN_N; + + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_1); + + reg = SSC_FUNC_EN_N | AUX_FUNC_EN_N | + SERDES_FIFO_FUNC_EN_N | + LS_CLK_DOMAIN_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + + usleep_range(20, 30); + + analogix_dp_lane_swap(dp, 0); + + writel(0x0, dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + writel(0x40, dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + writel(0x0, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + writel(0x0, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + + writel(0x0, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + writel(0x0, dp->reg_base + ANALOGIX_DP_HDCP_CTL); + + writel(0x5e, dp->reg_base + ANALOGIX_DP_HPD_DEGLITCH_L); + writel(0x1a, dp->reg_base + ANALOGIX_DP_HPD_DEGLITCH_H); + + writel(0x10, dp->reg_base + ANALOGIX_DP_LINK_DEBUG_CTL); + + writel(0x0, dp->reg_base + ANALOGIX_DP_PHY_TEST); + + writel(0x0, dp->reg_base + ANALOGIX_DP_VIDEO_FIFO_THRD); + writel(0x20, dp->reg_base + ANALOGIX_DP_AUDIO_MARGIN); + + writel(0x4, dp->reg_base + ANALOGIX_DP_M_VID_GEN_FILTER_TH); + writel(0x2, dp->reg_base + ANALOGIX_DP_M_AUD_GEN_FILTER_TH); + + writel(0x00000101, dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); +} + +void analogix_dp_swreset(struct analogix_dp_device *dp) +{ + writel(RESET_DP_TX, dp->reg_base + ANALOGIX_DP_TX_SW_RESET); +} + +void analogix_dp_config_interrupt(struct analogix_dp_device *dp) +{ + u32 reg; + + /* 0: mask, 1: unmask */ + reg = COMMON_INT_MASK_1; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_1); + + reg = COMMON_INT_MASK_2; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_2); + + reg = COMMON_INT_MASK_3; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_3); + + reg = COMMON_INT_MASK_4; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + + reg = INT_STA_MASK; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA_MASK); +} + +void analogix_dp_mute_hpd_interrupt(struct analogix_dp_device *dp) +{ + u32 reg; + + /* 0: mask, 1: unmask */ + reg = readl(dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + reg &= ~COMMON_INT_MASK_4; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + + reg = readl(dp->reg_base + ANALOGIX_DP_INT_STA_MASK); + reg &= ~INT_STA_MASK; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA_MASK); +} + +void analogix_dp_unmute_hpd_interrupt(struct analogix_dp_device *dp) +{ + u32 reg; + + /* 0: mask, 1: unmask */ + reg = COMMON_INT_MASK_4; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + + reg = INT_STA_MASK; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA_MASK); +} + +enum pll_status analogix_dp_get_pll_lock_status(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_DEBUG_CTL); + if (reg & PLL_LOCK) + return PLL_LOCKED; + else + return PLL_UNLOCKED; +} + +void analogix_dp_set_pll_power_down(struct analogix_dp_device *dp, bool enable) +{ + u32 reg; + u32 mask = DP_PLL_PD; + u32 pd_addr = ANALOGIX_DP_PLL_CTL; + + if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) { + pd_addr = ANALOGIX_DP_PD; + mask = RK_PLL_PD; + } + + reg = readl(dp->reg_base + pd_addr); + if (enable) + reg |= mask; + else + reg &= ~mask; + writel(reg, dp->reg_base + pd_addr); +} + +void analogix_dp_set_analog_power_down(struct analogix_dp_device *dp, + enum analog_power_block block, + bool enable) +{ + u32 reg; + u32 phy_pd_addr = ANALOGIX_DP_PHY_PD; + u32 mask; + + if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) + phy_pd_addr = ANALOGIX_DP_PD; + + switch (block) { + case AUX_BLOCK: + if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) + mask = RK_AUX_PD; + else + mask = AUX_PD; + + reg = readl(dp->reg_base + phy_pd_addr); + if (enable) + reg |= mask; + else + reg &= ~mask; + writel(reg, dp->reg_base + phy_pd_addr); + break; + case CH0_BLOCK: + mask = CH0_PD; + reg = readl(dp->reg_base + phy_pd_addr); + + if (enable) + reg |= mask; + else + reg &= ~mask; + writel(reg, dp->reg_base + phy_pd_addr); + break; + case CH1_BLOCK: + mask = CH1_PD; + reg = readl(dp->reg_base + phy_pd_addr); + + if (enable) + reg |= mask; + else + reg &= ~mask; + writel(reg, dp->reg_base + phy_pd_addr); + break; + case CH2_BLOCK: + mask = CH2_PD; + reg = readl(dp->reg_base + phy_pd_addr); + + if (enable) + reg |= mask; + else + reg &= ~mask; + writel(reg, dp->reg_base + phy_pd_addr); + break; + case CH3_BLOCK: + mask = CH3_PD; + reg = readl(dp->reg_base + phy_pd_addr); + + if (enable) + reg |= mask; + else + reg &= ~mask; + writel(reg, dp->reg_base + phy_pd_addr); + break; + case ANALOG_TOTAL: + /* + * There is no bit named DP_PHY_PD, so We used DP_INC_BG + * to power off everything instead of DP_PHY_PD in + * Rockchip + */ + if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) + mask = DP_INC_BG; + else + mask = DP_PHY_PD; + + reg = readl(dp->reg_base + phy_pd_addr); + if (enable) + reg |= mask; + else + reg &= ~mask; + + writel(reg, dp->reg_base + phy_pd_addr); + if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) + usleep_range(10, 15); + break; + case POWER_ALL: + if (enable) { + reg = DP_ALL_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } else { + reg = DP_ALL_PD; + writel(reg, dp->reg_base + phy_pd_addr); + usleep_range(10, 15); + reg &= ~DP_INC_BG; + writel(reg, dp->reg_base + phy_pd_addr); + usleep_range(10, 15); + + writel(0x00, dp->reg_base + phy_pd_addr); + } + break; + default: + break; + } +} + +int analogix_dp_init_analog_func(struct analogix_dp_device *dp) +{ + u32 reg; + int timeout_loop = 0; + + analogix_dp_set_analog_power_down(dp, POWER_ALL, 0); + + reg = PLL_LOCK_CHG; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_1); + + reg = readl(dp->reg_base + ANALOGIX_DP_DEBUG_CTL); + reg &= ~(F_PLL_LOCK | PLL_LOCK_CTRL); + writel(reg, dp->reg_base + ANALOGIX_DP_DEBUG_CTL); + + /* Power up PLL */ + if (analogix_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { + analogix_dp_set_pll_power_down(dp, 0); + + while (analogix_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { + timeout_loop++; + if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { + dev_err(dp->dev, "failed to get pll lock status\n"); + return -ETIMEDOUT; + } + usleep_range(10, 20); + } + } + + /* Enable Serdes FIFO function and Link symbol clock domain module */ + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + reg &= ~(SERDES_FIFO_FUNC_EN_N | LS_CLK_DOMAIN_FUNC_EN_N + | AUX_FUNC_EN_N); + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + return 0; +} + +void analogix_dp_clear_hotplug_interrupts(struct analogix_dp_device *dp) +{ + u32 reg; + + if (dp->hpd_gpiod) + return; + + reg = HOTPLUG_CHG | HPD_LOST | PLUG; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_4); + + reg = INT_HPD; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA); +} + +void analogix_dp_init_hpd(struct analogix_dp_device *dp) +{ + u32 reg; + + if (dp->hpd_gpiod) + return; + + analogix_dp_clear_hotplug_interrupts(dp); + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + reg &= ~(F_HPD | HPD_CTRL); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); +} + +void analogix_dp_force_hpd(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + reg = (F_HPD | HPD_CTRL); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); +} + +enum dp_irq_type analogix_dp_get_irq_type(struct analogix_dp_device *dp) +{ + u32 reg; + + if (dp->hpd_gpiod) { + reg = gpiod_get_value(dp->hpd_gpiod); + if (reg) + return DP_IRQ_TYPE_HP_CABLE_IN; + else + return DP_IRQ_TYPE_HP_CABLE_OUT; + } else { + /* Parse hotplug interrupt status register */ + reg = readl(dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_4); + + if (reg & PLUG) + return DP_IRQ_TYPE_HP_CABLE_IN; + + if (reg & HPD_LOST) + return DP_IRQ_TYPE_HP_CABLE_OUT; + + if (reg & HOTPLUG_CHG) + return DP_IRQ_TYPE_HP_CHANGE; + + return DP_IRQ_TYPE_UNKNOWN; + } +} + +void analogix_dp_reset_aux(struct analogix_dp_device *dp) +{ + u32 reg; + + /* Disable AUX channel module */ + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + reg |= AUX_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_2); +} + +void analogix_dp_init_aux(struct analogix_dp_device *dp) +{ + u32 reg; + + /* Clear inerrupts related to AUX channel */ + reg = RPLY_RECEIV | AUX_ERR; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA); + + analogix_dp_set_analog_power_down(dp, AUX_BLOCK, true); + usleep_range(10, 11); + analogix_dp_set_analog_power_down(dp, AUX_BLOCK, false); + + analogix_dp_reset_aux(dp); + + /* AUX_BIT_PERIOD_EXPECTED_DELAY doesn't apply to Rockchip IP */ + if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) + reg = 0; + else + reg = AUX_BIT_PERIOD_EXPECTED_DELAY(3); + + /* Disable AUX transaction H/W retry */ + reg |= AUX_HW_RETRY_COUNT_SEL(0) | + AUX_HW_RETRY_INTERVAL_600_MICROSECONDS; + + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_HW_RETRY_CTL); + + /* Receive AUX Channel DEFER commands equal to DEFFER_COUNT*64 */ + reg = DEFER_CTRL_EN | DEFER_COUNT(1); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_DEFER_CTL); + + /* Enable AUX channel module */ + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + reg &= ~AUX_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_2); +} + +int analogix_dp_get_plug_in_status(struct analogix_dp_device *dp) +{ + u32 reg; + + if (dp->hpd_gpiod) { + if (gpiod_get_value(dp->hpd_gpiod)) + return 0; + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + if (reg & HPD_STATUS) + return 0; + } + + return -EINVAL; +} + +void analogix_dp_enable_sw_function(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_1); + reg &= ~SW_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_1); +} + +int analogix_dp_start_aux_transaction(struct analogix_dp_device *dp) +{ + int reg; + int retval = 0; + int timeout_loop = 0; + + /* Enable AUX CH operation */ + reg = readl(dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_2); + reg |= AUX_EN; + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_2); + + /* Is AUX CH command reply received? */ + reg = readl(dp->reg_base + ANALOGIX_DP_INT_STA); + while (!(reg & RPLY_RECEIV)) { + timeout_loop++; + if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { + dev_err(dp->dev, "AUX CH command reply failed!\n"); + return -ETIMEDOUT; + } + reg = readl(dp->reg_base + ANALOGIX_DP_INT_STA); + usleep_range(10, 11); + } + + /* Clear interrupt source for AUX CH command reply */ + writel(RPLY_RECEIV, dp->reg_base + ANALOGIX_DP_INT_STA); + + /* Clear interrupt source for AUX CH access error */ + reg = readl(dp->reg_base + ANALOGIX_DP_INT_STA); + if (reg & AUX_ERR) { + writel(AUX_ERR, dp->reg_base + ANALOGIX_DP_INT_STA); + return -EREMOTEIO; + } + + /* Check AUX CH error access status */ + reg = readl(dp->reg_base + ANALOGIX_DP_AUX_CH_STA); + if ((reg & AUX_STATUS_MASK) != 0) { + dev_err(dp->dev, "AUX CH error happens: %d\n\n", + reg & AUX_STATUS_MASK); + return -EREMOTEIO; + } + + return retval; +} + +int analogix_dp_write_byte_to_dpcd(struct analogix_dp_device *dp, + unsigned int reg_addr, + unsigned char data) +{ + u32 reg; + int i; + int retval; + + for (i = 0; i < 3; i++) { + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + ANALOGIX_DP_BUFFER_DATA_CTL); + + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(reg_addr); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(reg_addr); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(reg_addr); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_19_16); + + /* Write data buffer */ + reg = (unsigned int)data; + writel(reg, dp->reg_base + ANALOGIX_DP_BUF_DATA_0); + + /* + * Set DisplayPort transaction and write 1 byte + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_WRITE; + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = analogix_dp_start_aux_transaction(dp); + if (retval == 0) + break; + + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", __func__); + } + + return retval; +} + +void analogix_dp_set_link_bandwidth(struct analogix_dp_device *dp, u32 bwtype) +{ + u32 reg; + + reg = bwtype; + if ((bwtype == DP_LINK_BW_2_7) || (bwtype == DP_LINK_BW_1_62)) + writel(reg, dp->reg_base + ANALOGIX_DP_LINK_BW_SET); +} + +void analogix_dp_get_link_bandwidth(struct analogix_dp_device *dp, u32 *bwtype) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LINK_BW_SET); + *bwtype = reg; +} + +void analogix_dp_set_lane_count(struct analogix_dp_device *dp, u32 count) +{ + u32 reg; + + reg = count; + writel(reg, dp->reg_base + ANALOGIX_DP_LANE_COUNT_SET); +} + +void analogix_dp_get_lane_count(struct analogix_dp_device *dp, u32 *count) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LANE_COUNT_SET); + *count = reg; +} + +void analogix_dp_enable_enhanced_mode(struct analogix_dp_device *dp, + bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg |= ENHANCED; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg &= ~ENHANCED; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + } +} + +void analogix_dp_set_training_pattern(struct analogix_dp_device *dp, + enum pattern_set pattern) +{ + u32 reg; + + switch (pattern) { + case PRBS7: + reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_PRBS7; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + case D10_2: + reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_D10_2; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + case TRAINING_PTN1: + reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN1; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + case TRAINING_PTN2: + reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN2; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + case DP_NONE: + reg = SCRAMBLING_ENABLE | + LINK_QUAL_PATTERN_SET_DISABLE | + SW_TRAINING_PATTERN_SET_NORMAL; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + default: + break; + } +} + +void analogix_dp_set_lane0_pre_emphasis(struct analogix_dp_device *dp, + u32 level) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LN0_LINK_TRAINING_CTL); + reg &= ~PRE_EMPHASIS_SET_MASK; + reg |= level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + ANALOGIX_DP_LN0_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane1_pre_emphasis(struct analogix_dp_device *dp, + u32 level) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LN1_LINK_TRAINING_CTL); + reg &= ~PRE_EMPHASIS_SET_MASK; + reg |= level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + ANALOGIX_DP_LN1_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane2_pre_emphasis(struct analogix_dp_device *dp, + u32 level) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LN2_LINK_TRAINING_CTL); + reg &= ~PRE_EMPHASIS_SET_MASK; + reg |= level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + ANALOGIX_DP_LN2_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane3_pre_emphasis(struct analogix_dp_device *dp, + u32 level) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LN3_LINK_TRAINING_CTL); + reg &= ~PRE_EMPHASIS_SET_MASK; + reg |= level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + ANALOGIX_DP_LN3_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane0_link_training(struct analogix_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + ANALOGIX_DP_LN0_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane1_link_training(struct analogix_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + ANALOGIX_DP_LN1_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane2_link_training(struct analogix_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + ANALOGIX_DP_LN2_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane3_link_training(struct analogix_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + ANALOGIX_DP_LN3_LINK_TRAINING_CTL); +} + +u32 analogix_dp_get_lane0_link_training(struct analogix_dp_device *dp) +{ + return readl(dp->reg_base + ANALOGIX_DP_LN0_LINK_TRAINING_CTL); +} + +u32 analogix_dp_get_lane1_link_training(struct analogix_dp_device *dp) +{ + return readl(dp->reg_base + ANALOGIX_DP_LN1_LINK_TRAINING_CTL); +} + +u32 analogix_dp_get_lane2_link_training(struct analogix_dp_device *dp) +{ + return readl(dp->reg_base + ANALOGIX_DP_LN2_LINK_TRAINING_CTL); +} + +u32 analogix_dp_get_lane3_link_training(struct analogix_dp_device *dp) +{ + return readl(dp->reg_base + ANALOGIX_DP_LN3_LINK_TRAINING_CTL); +} + +void analogix_dp_reset_macro(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_PHY_TEST); + reg |= MACRO_RST; + writel(reg, dp->reg_base + ANALOGIX_DP_PHY_TEST); + + /* 10 us is the minimum reset time. */ + usleep_range(10, 20); + + reg &= ~MACRO_RST; + writel(reg, dp->reg_base + ANALOGIX_DP_PHY_TEST); +} + +void analogix_dp_init_video(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = VSYNC_DET | VID_FORMAT_CHG | VID_CLK_CHG; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_1); + + reg = 0x0; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + + reg = CHA_CRI(4) | CHA_CTRL; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + + reg = 0x0; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + + reg = VID_HRES_TH(2) | VID_VRES_TH(0); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_8); +} + +void analogix_dp_set_video_color_format(struct analogix_dp_device *dp) +{ + u32 reg; + + /* Configure the input color depth, color space, dynamic range */ + reg = (dp->video_info.dynamic_range << IN_D_RANGE_SHIFT) | + (dp->video_info.color_depth << IN_BPC_SHIFT) | + (dp->video_info.color_space << IN_COLOR_F_SHIFT); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_2); + + /* Set Input Color YCbCr Coefficients to ITU601 or ITU709 */ + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3); + reg &= ~IN_YC_COEFFI_MASK; + if (dp->video_info.ycbcr_coeff) + reg |= IN_YC_COEFFI_ITU709; + else + reg |= IN_YC_COEFFI_ITU601; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3); +} + +int analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + + if (!(reg & DET_STA)) { + dev_dbg(dp->dev, "Input stream clock not detected.\n"); + return -EINVAL; + } + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + dev_dbg(dp->dev, "wait SYS_CTL_2.\n"); + + if (reg & CHA_STA) { + dev_dbg(dp->dev, "Input stream clk is changing\n"); + return -EINVAL; + } + + return 0; +} + +void analogix_dp_set_video_cr_mn(struct analogix_dp_device *dp, + enum clock_recovery_m_value_type type, + u32 m_value, u32 n_value) +{ + u32 reg; + + if (type == REGISTER_M) { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg |= FIX_M_VID; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg = m_value & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_M_VID_0); + reg = (m_value >> 8) & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_M_VID_1); + reg = (m_value >> 16) & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_M_VID_2); + + reg = n_value & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_N_VID_0); + reg = (n_value >> 8) & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_N_VID_1); + reg = (n_value >> 16) & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_N_VID_2); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg &= ~FIX_M_VID; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + + writel(0x00, dp->reg_base + ANALOGIX_DP_N_VID_0); + writel(0x80, dp->reg_base + ANALOGIX_DP_N_VID_1); + writel(0x00, dp->reg_base + ANALOGIX_DP_N_VID_2); + } +} + +void analogix_dp_set_video_timing_mode(struct analogix_dp_device *dp, u32 type) +{ + u32 reg; + + if (type == VIDEO_TIMING_FROM_CAPTURE) { + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg &= ~FORMAT_SEL; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg |= FORMAT_SEL; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + } +} + +void analogix_dp_enable_video_master(struct analogix_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); + reg &= ~VIDEO_MODE_MASK; + reg |= VIDEO_MASTER_MODE_EN | VIDEO_MODE_MASTER_MODE; + writel(reg, dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); + reg &= ~VIDEO_MODE_MASK; + reg |= VIDEO_MODE_SLAVE_MODE; + writel(reg, dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); + } +} + +void analogix_dp_start_video(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + reg |= VIDEO_EN; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); +} + +int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + if (!(reg & STRM_VALID)) { + dev_dbg(dp->dev, "Input video stream is not detected.\n"); + return -EINVAL; + } + + return 0; +} + +void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_1); + if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) { + reg &= ~(RK_VID_CAP_FUNC_EN_N | RK_VID_FIFO_FUNC_EN_N); + } else { + reg &= ~(MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N); + reg |= MASTER_VID_FUNC_EN_N; + } + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_1); + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg &= ~INTERACE_SCAN_CFG; + reg |= (dp->video_info.interlaced << 2); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg &= ~VSYNC_POLARITY_CFG; + reg |= (dp->video_info.v_sync_polarity << 1); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg &= ~HSYNC_POLARITY_CFG; + reg |= (dp->video_info.h_sync_polarity << 0); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + + reg = AUDIO_MODE_SPDIF_MODE | VIDEO_MODE_SLAVE_MODE; + writel(reg, dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); +} + +void analogix_dp_enable_scrambling(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + reg &= ~SCRAMBLING_DISABLE; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); +} + +void analogix_dp_disable_scrambling(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + reg |= SCRAMBLING_DISABLE; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); +} + +void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp) +{ + writel(PSR_VID_CRC_ENABLE, dp->reg_base + ANALOGIX_DP_CRC_CON); +} + +static ssize_t analogix_dp_get_psr_status(struct analogix_dp_device *dp) +{ + ssize_t val; + u8 status; + + val = drm_dp_dpcd_readb(&dp->aux, DP_PSR_STATUS, &status); + if (val < 0) { + dev_err(dp->dev, "PSR_STATUS read failed ret=%zd", val); + return val; + } + return status; +} + +int analogix_dp_send_psr_spd(struct analogix_dp_device *dp, + struct dp_sdp *vsc, bool blocking) +{ + unsigned int val; + int ret; + ssize_t psr_status; + + /* don't send info frame */ + val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + val &= ~IF_EN; + writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + + /* configure single frame update mode */ + writel(PSR_FRAME_UP_TYPE_BURST | PSR_CRC_SEL_HARDWARE, + dp->reg_base + ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL); + + /* configure VSC HB0~HB3 */ + writel(vsc->sdp_header.HB0, dp->reg_base + ANALOGIX_DP_SPD_HB0); + writel(vsc->sdp_header.HB1, dp->reg_base + ANALOGIX_DP_SPD_HB1); + writel(vsc->sdp_header.HB2, dp->reg_base + ANALOGIX_DP_SPD_HB2); + writel(vsc->sdp_header.HB3, dp->reg_base + ANALOGIX_DP_SPD_HB3); + + /* configure reused VSC PB0~PB3, magic number from vendor */ + writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_PB0); + writel(0x16, dp->reg_base + ANALOGIX_DP_SPD_PB1); + writel(0xCE, dp->reg_base + ANALOGIX_DP_SPD_PB2); + writel(0x5D, dp->reg_base + ANALOGIX_DP_SPD_PB3); + + /* configure DB0 / DB1 values */ + writel(vsc->db[0], dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB0); + writel(vsc->db[1], dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB1); + + /* set reuse spd inforframe */ + val = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3); + val |= REUSE_SPD_EN; + writel(val, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3); + + /* mark info frame update */ + val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + val = (val | IF_UP) & ~IF_EN; + writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + + /* send info frame */ + val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + val |= IF_EN; + writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + + if (!blocking) + return 0; + + /* + * db[1]!=0: entering PSR, wait for fully active remote frame buffer. + * db[1]==0: exiting PSR, wait for either + * (a) ACTIVE_RESYNC - the sink "must display the + * incoming active frames from the Source device with no visible + * glitches and/or artifacts", even though timings may still be + * re-synchronizing; or + * (b) INACTIVE - the transition is fully complete. + */ + ret = readx_poll_timeout(analogix_dp_get_psr_status, dp, psr_status, + psr_status >= 0 && + ((vsc->db[1] && psr_status == DP_PSR_SINK_ACTIVE_RFB) || + (!vsc->db[1] && (psr_status == DP_PSR_SINK_ACTIVE_RESYNC || + psr_status == DP_PSR_SINK_INACTIVE))), + 1500, DP_TIMEOUT_PSR_LOOP_MS * 1000); + if (ret) { + dev_warn(dp->dev, "Failed to apply PSR %d\n", ret); + return ret; + } + return 0; +} + +ssize_t analogix_dp_transfer(struct analogix_dp_device *dp, + struct drm_dp_aux_msg *msg) +{ + u32 reg; + u32 status_reg; + u8 *buffer = msg->buffer; + unsigned int i; + int num_transferred = 0; + int ret; + + /* Buffer size of AUX CH is 16 bytes */ + if (WARN_ON(msg->size > 16)) + return -E2BIG; + + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + ANALOGIX_DP_BUFFER_DATA_CTL); + + switch (msg->request & ~DP_AUX_I2C_MOT) { + case DP_AUX_I2C_WRITE: + reg = AUX_TX_COMM_WRITE | AUX_TX_COMM_I2C_TRANSACTION; + if (msg->request & DP_AUX_I2C_MOT) + reg |= AUX_TX_COMM_MOT; + break; + + case DP_AUX_I2C_READ: + reg = AUX_TX_COMM_READ | AUX_TX_COMM_I2C_TRANSACTION; + if (msg->request & DP_AUX_I2C_MOT) + reg |= AUX_TX_COMM_MOT; + break; + + case DP_AUX_NATIVE_WRITE: + reg = AUX_TX_COMM_WRITE | AUX_TX_COMM_DP_TRANSACTION; + break; + + case DP_AUX_NATIVE_READ: + reg = AUX_TX_COMM_READ | AUX_TX_COMM_DP_TRANSACTION; + break; + + default: + return -EINVAL; + } + + reg |= AUX_LENGTH(msg->size); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_1); + + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(msg->address); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(msg->address); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(msg->address); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_19_16); + + if (!(msg->request & DP_AUX_I2C_READ)) { + for (i = 0; i < msg->size; i++) { + reg = buffer[i]; + writel(reg, dp->reg_base + ANALOGIX_DP_BUF_DATA_0 + + 4 * i); + num_transferred++; + } + } + + /* Enable AUX CH operation */ + reg = AUX_EN; + + /* Zero-sized messages specify address-only transactions. */ + if (msg->size < 1) + reg |= ADDR_ONLY; + + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_2); + + ret = readx_poll_timeout(readl, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_2, + reg, !(reg & AUX_EN), 25, 500 * 1000); + if (ret) { + dev_err(dp->dev, "AUX CH enable timeout!\n"); + goto aux_error; + } + + /* TODO: Wait for an interrupt instead of looping? */ + /* Is AUX CH command reply received? */ + ret = readx_poll_timeout(readl, dp->reg_base + ANALOGIX_DP_INT_STA, + reg, reg & RPLY_RECEIV, 10, 20 * 1000); + if (ret) { + dev_err(dp->dev, "AUX CH cmd reply timeout!\n"); + goto aux_error; + } + + /* Clear interrupt source for AUX CH command reply */ + writel(RPLY_RECEIV, dp->reg_base + ANALOGIX_DP_INT_STA); + + /* Clear interrupt source for AUX CH access error */ + reg = readl(dp->reg_base + ANALOGIX_DP_INT_STA); + status_reg = readl(dp->reg_base + ANALOGIX_DP_AUX_CH_STA); + if ((reg & AUX_ERR) || (status_reg & AUX_STATUS_MASK)) { + writel(AUX_ERR, dp->reg_base + ANALOGIX_DP_INT_STA); + + dev_warn(dp->dev, "AUX CH error happened: %#x (%d)\n", + status_reg & AUX_STATUS_MASK, !!(reg & AUX_ERR)); + goto aux_error; + } + + if (msg->request & DP_AUX_I2C_READ) { + for (i = 0; i < msg->size; i++) { + reg = readl(dp->reg_base + ANALOGIX_DP_BUF_DATA_0 + + 4 * i); + buffer[i] = (unsigned char)reg; + num_transferred++; + } + } + + /* Check if Rx sends defer */ + reg = readl(dp->reg_base + ANALOGIX_DP_AUX_RX_COMM); + if (reg == AUX_RX_COMM_AUX_DEFER) + msg->reply = DP_AUX_NATIVE_REPLY_DEFER; + else if (reg == AUX_RX_COMM_I2C_DEFER) + msg->reply = DP_AUX_I2C_REPLY_DEFER; + else if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_WRITE || + (msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_READ) + msg->reply = DP_AUX_I2C_REPLY_ACK; + else if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_NATIVE_WRITE || + (msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_NATIVE_READ) + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + + return num_transferred > 0 ? num_transferred : -EBUSY; + +aux_error: + /* if aux err happen, reset aux */ + analogix_dp_init_aux(dp); + + return -EREMOTEIO; +} diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h new file mode 100644 index 000000000..e284ee8da --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h @@ -0,0 +1,417 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Register definition file for Analogix DP core driver + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + */ + +#ifndef _ANALOGIX_DP_REG_H +#define _ANALOGIX_DP_REG_H + +#define ANALOGIX_DP_TX_SW_RESET 0x14 +#define ANALOGIX_DP_FUNC_EN_1 0x18 +#define ANALOGIX_DP_FUNC_EN_2 0x1C +#define ANALOGIX_DP_VIDEO_CTL_1 0x20 +#define ANALOGIX_DP_VIDEO_CTL_2 0x24 +#define ANALOGIX_DP_VIDEO_CTL_3 0x28 + +#define ANALOGIX_DP_VIDEO_CTL_8 0x3C +#define ANALOGIX_DP_VIDEO_CTL_10 0x44 + +#define ANALOGIX_DP_SPDIF_AUDIO_CTL_0 0xD8 + +#define ANALOGIX_DP_PLL_REG_1 0xfc +#define ANALOGIX_DP_PLL_REG_2 0x9e4 +#define ANALOGIX_DP_PLL_REG_3 0x9e8 +#define ANALOGIX_DP_PLL_REG_4 0x9ec +#define ANALOGIX_DP_PLL_REG_5 0xa00 + +#define ANALOGIX_DP_PD 0x12c + +#define ANALOGIX_DP_IF_TYPE 0x244 +#define ANALOGIX_DP_IF_PKT_DB1 0x254 +#define ANALOGIX_DP_IF_PKT_DB2 0x258 +#define ANALOGIX_DP_SPD_HB0 0x2F8 +#define ANALOGIX_DP_SPD_HB1 0x2FC +#define ANALOGIX_DP_SPD_HB2 0x300 +#define ANALOGIX_DP_SPD_HB3 0x304 +#define ANALOGIX_DP_SPD_PB0 0x308 +#define ANALOGIX_DP_SPD_PB1 0x30C +#define ANALOGIX_DP_SPD_PB2 0x310 +#define ANALOGIX_DP_SPD_PB3 0x314 +#define ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL 0x318 +#define ANALOGIX_DP_VSC_SHADOW_DB0 0x31C +#define ANALOGIX_DP_VSC_SHADOW_DB1 0x320 + +#define ANALOGIX_DP_LANE_MAP 0x35C + +#define ANALOGIX_DP_ANALOG_CTL_1 0x370 +#define ANALOGIX_DP_ANALOG_CTL_2 0x374 +#define ANALOGIX_DP_ANALOG_CTL_3 0x378 +#define ANALOGIX_DP_PLL_FILTER_CTL_1 0x37C +#define ANALOGIX_DP_TX_AMP_TUNING_CTL 0x380 + +#define ANALOGIX_DP_AUX_HW_RETRY_CTL 0x390 + +#define ANALOGIX_DP_COMMON_INT_STA_1 0x3C4 +#define ANALOGIX_DP_COMMON_INT_STA_2 0x3C8 +#define ANALOGIX_DP_COMMON_INT_STA_3 0x3CC +#define ANALOGIX_DP_COMMON_INT_STA_4 0x3D0 +#define ANALOGIX_DP_INT_STA 0x3DC +#define ANALOGIX_DP_COMMON_INT_MASK_1 0x3E0 +#define ANALOGIX_DP_COMMON_INT_MASK_2 0x3E4 +#define ANALOGIX_DP_COMMON_INT_MASK_3 0x3E8 +#define ANALOGIX_DP_COMMON_INT_MASK_4 0x3EC +#define ANALOGIX_DP_INT_STA_MASK 0x3F8 +#define ANALOGIX_DP_INT_CTL 0x3FC + +#define ANALOGIX_DP_SYS_CTL_1 0x600 +#define ANALOGIX_DP_SYS_CTL_2 0x604 +#define ANALOGIX_DP_SYS_CTL_3 0x608 +#define ANALOGIX_DP_SYS_CTL_4 0x60C + +#define ANALOGIX_DP_PKT_SEND_CTL 0x640 +#define ANALOGIX_DP_HDCP_CTL 0x648 + +#define ANALOGIX_DP_LINK_BW_SET 0x680 +#define ANALOGIX_DP_LANE_COUNT_SET 0x684 +#define ANALOGIX_DP_TRAINING_PTN_SET 0x688 +#define ANALOGIX_DP_LN0_LINK_TRAINING_CTL 0x68C +#define ANALOGIX_DP_LN1_LINK_TRAINING_CTL 0x690 +#define ANALOGIX_DP_LN2_LINK_TRAINING_CTL 0x694 +#define ANALOGIX_DP_LN3_LINK_TRAINING_CTL 0x698 + +#define ANALOGIX_DP_DEBUG_CTL 0x6C0 +#define ANALOGIX_DP_HPD_DEGLITCH_L 0x6C4 +#define ANALOGIX_DP_HPD_DEGLITCH_H 0x6C8 +#define ANALOGIX_DP_LINK_DEBUG_CTL 0x6E0 + +#define ANALOGIX_DP_M_VID_0 0x700 +#define ANALOGIX_DP_M_VID_1 0x704 +#define ANALOGIX_DP_M_VID_2 0x708 +#define ANALOGIX_DP_N_VID_0 0x70C +#define ANALOGIX_DP_N_VID_1 0x710 +#define ANALOGIX_DP_N_VID_2 0x714 + +#define ANALOGIX_DP_PLL_CTL 0x71C +#define ANALOGIX_DP_PHY_PD 0x720 +#define ANALOGIX_DP_PHY_TEST 0x724 + +#define ANALOGIX_DP_VIDEO_FIFO_THRD 0x730 +#define ANALOGIX_DP_AUDIO_MARGIN 0x73C + +#define ANALOGIX_DP_M_VID_GEN_FILTER_TH 0x764 +#define ANALOGIX_DP_M_AUD_GEN_FILTER_TH 0x778 +#define ANALOGIX_DP_AUX_CH_STA 0x780 +#define ANALOGIX_DP_AUX_CH_DEFER_CTL 0x788 +#define ANALOGIX_DP_AUX_RX_COMM 0x78C +#define ANALOGIX_DP_BUFFER_DATA_CTL 0x790 +#define ANALOGIX_DP_AUX_CH_CTL_1 0x794 +#define ANALOGIX_DP_AUX_ADDR_7_0 0x798 +#define ANALOGIX_DP_AUX_ADDR_15_8 0x79C +#define ANALOGIX_DP_AUX_ADDR_19_16 0x7A0 +#define ANALOGIX_DP_AUX_CH_CTL_2 0x7A4 + +#define ANALOGIX_DP_BUF_DATA_0 0x7C0 + +#define ANALOGIX_DP_SOC_GENERAL_CTL 0x800 + +#define ANALOGIX_DP_CRC_CON 0x890 + +/* ANALOGIX_DP_TX_SW_RESET */ +#define RESET_DP_TX (0x1 << 0) + +/* ANALOGIX_DP_FUNC_EN_1 */ +#define MASTER_VID_FUNC_EN_N (0x1 << 7) +#define RK_VID_CAP_FUNC_EN_N (0x1 << 6) +#define SLAVE_VID_FUNC_EN_N (0x1 << 5) +#define RK_VID_FIFO_FUNC_EN_N (0x1 << 5) +#define AUD_FIFO_FUNC_EN_N (0x1 << 4) +#define AUD_FUNC_EN_N (0x1 << 3) +#define HDCP_FUNC_EN_N (0x1 << 2) +#define CRC_FUNC_EN_N (0x1 << 1) +#define SW_FUNC_EN_N (0x1 << 0) + +/* ANALOGIX_DP_FUNC_EN_2 */ +#define SSC_FUNC_EN_N (0x1 << 7) +#define AUX_FUNC_EN_N (0x1 << 2) +#define SERDES_FIFO_FUNC_EN_N (0x1 << 1) +#define LS_CLK_DOMAIN_FUNC_EN_N (0x1 << 0) + +/* ANALOGIX_DP_VIDEO_CTL_1 */ +#define VIDEO_EN (0x1 << 7) +#define HDCP_VIDEO_MUTE (0x1 << 6) + +/* ANALOGIX_DP_VIDEO_CTL_1 */ +#define IN_D_RANGE_MASK (0x1 << 7) +#define IN_D_RANGE_SHIFT (7) +#define IN_D_RANGE_CEA (0x1 << 7) +#define IN_D_RANGE_VESA (0x0 << 7) +#define IN_BPC_MASK (0x7 << 4) +#define IN_BPC_SHIFT (4) +#define IN_BPC_12_BITS (0x3 << 4) +#define IN_BPC_10_BITS (0x2 << 4) +#define IN_BPC_8_BITS (0x1 << 4) +#define IN_BPC_6_BITS (0x0 << 4) +#define IN_COLOR_F_MASK (0x3 << 0) +#define IN_COLOR_F_SHIFT (0) +#define IN_COLOR_F_YCBCR444 (0x2 << 0) +#define IN_COLOR_F_YCBCR422 (0x1 << 0) +#define IN_COLOR_F_RGB (0x0 << 0) + +/* ANALOGIX_DP_VIDEO_CTL_3 */ +#define IN_YC_COEFFI_MASK (0x1 << 7) +#define IN_YC_COEFFI_SHIFT (7) +#define IN_YC_COEFFI_ITU709 (0x1 << 7) +#define IN_YC_COEFFI_ITU601 (0x0 << 7) +#define VID_CHK_UPDATE_TYPE_MASK (0x1 << 4) +#define VID_CHK_UPDATE_TYPE_SHIFT (4) +#define VID_CHK_UPDATE_TYPE_1 (0x1 << 4) +#define VID_CHK_UPDATE_TYPE_0 (0x0 << 4) +#define REUSE_SPD_EN (0x1 << 3) + +/* ANALOGIX_DP_VIDEO_CTL_8 */ +#define VID_HRES_TH(x) (((x) & 0xf) << 4) +#define VID_VRES_TH(x) (((x) & 0xf) << 0) + +/* ANALOGIX_DP_VIDEO_CTL_10 */ +#define FORMAT_SEL (0x1 << 4) +#define INTERACE_SCAN_CFG (0x1 << 2) +#define VSYNC_POLARITY_CFG (0x1 << 1) +#define HSYNC_POLARITY_CFG (0x1 << 0) + +/* ANALOGIX_DP_PLL_REG_1 */ +#define REF_CLK_24M (0x1 << 0) +#define REF_CLK_27M (0x0 << 0) +#define REF_CLK_MASK (0x1 << 0) + +/* ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL */ +#define PSR_FRAME_UP_TYPE_BURST (0x1 << 0) +#define PSR_FRAME_UP_TYPE_SINGLE (0x0 << 0) +#define PSR_CRC_SEL_HARDWARE (0x1 << 1) +#define PSR_CRC_SEL_MANUALLY (0x0 << 1) + +/* ANALOGIX_DP_LANE_MAP */ +#define LANE3_MAP_LOGIC_LANE_0 (0x0 << 6) +#define LANE3_MAP_LOGIC_LANE_1 (0x1 << 6) +#define LANE3_MAP_LOGIC_LANE_2 (0x2 << 6) +#define LANE3_MAP_LOGIC_LANE_3 (0x3 << 6) +#define LANE2_MAP_LOGIC_LANE_0 (0x0 << 4) +#define LANE2_MAP_LOGIC_LANE_1 (0x1 << 4) +#define LANE2_MAP_LOGIC_LANE_2 (0x2 << 4) +#define LANE2_MAP_LOGIC_LANE_3 (0x3 << 4) +#define LANE1_MAP_LOGIC_LANE_0 (0x0 << 2) +#define LANE1_MAP_LOGIC_LANE_1 (0x1 << 2) +#define LANE1_MAP_LOGIC_LANE_2 (0x2 << 2) +#define LANE1_MAP_LOGIC_LANE_3 (0x3 << 2) +#define LANE0_MAP_LOGIC_LANE_0 (0x0 << 0) +#define LANE0_MAP_LOGIC_LANE_1 (0x1 << 0) +#define LANE0_MAP_LOGIC_LANE_2 (0x2 << 0) +#define LANE0_MAP_LOGIC_LANE_3 (0x3 << 0) + +/* ANALOGIX_DP_ANALOG_CTL_1 */ +#define TX_TERMINAL_CTRL_50_OHM (0x1 << 4) + +/* ANALOGIX_DP_ANALOG_CTL_2 */ +#define SEL_24M (0x1 << 3) +#define TX_DVDD_BIT_1_0625V (0x4 << 0) + +/* ANALOGIX_DP_ANALOG_CTL_3 */ +#define DRIVE_DVDD_BIT_1_0625V (0x4 << 5) +#define VCO_BIT_600_MICRO (0x5 << 0) + +/* ANALOGIX_DP_PLL_FILTER_CTL_1 */ +#define PD_RING_OSC (0x1 << 6) +#define AUX_TERMINAL_CTRL_50_OHM (0x2 << 4) +#define TX_CUR1_2X (0x1 << 2) +#define TX_CUR_16_MA (0x3 << 0) + +/* ANALOGIX_DP_TX_AMP_TUNING_CTL */ +#define CH3_AMP_400_MV (0x0 << 24) +#define CH2_AMP_400_MV (0x0 << 16) +#define CH1_AMP_400_MV (0x0 << 8) +#define CH0_AMP_400_MV (0x0 << 0) + +/* ANALOGIX_DP_AUX_HW_RETRY_CTL */ +#define AUX_BIT_PERIOD_EXPECTED_DELAY(x) (((x) & 0x7) << 8) +#define AUX_HW_RETRY_INTERVAL_MASK (0x3 << 3) +#define AUX_HW_RETRY_INTERVAL_600_MICROSECONDS (0x0 << 3) +#define AUX_HW_RETRY_INTERVAL_800_MICROSECONDS (0x1 << 3) +#define AUX_HW_RETRY_INTERVAL_1000_MICROSECONDS (0x2 << 3) +#define AUX_HW_RETRY_INTERVAL_1800_MICROSECONDS (0x3 << 3) +#define AUX_HW_RETRY_COUNT_SEL(x) (((x) & 0x7) << 0) + +/* ANALOGIX_DP_COMMON_INT_STA_1 */ +#define VSYNC_DET (0x1 << 7) +#define PLL_LOCK_CHG (0x1 << 6) +#define SPDIF_ERR (0x1 << 5) +#define SPDIF_UNSTBL (0x1 << 4) +#define VID_FORMAT_CHG (0x1 << 3) +#define AUD_CLK_CHG (0x1 << 2) +#define VID_CLK_CHG (0x1 << 1) +#define SW_INT (0x1 << 0) + +/* ANALOGIX_DP_COMMON_INT_STA_2 */ +#define ENC_EN_CHG (0x1 << 6) +#define HW_BKSV_RDY (0x1 << 3) +#define HW_SHA_DONE (0x1 << 2) +#define HW_AUTH_STATE_CHG (0x1 << 1) +#define HW_AUTH_DONE (0x1 << 0) + +/* ANALOGIX_DP_COMMON_INT_STA_3 */ +#define AFIFO_UNDER (0x1 << 7) +#define AFIFO_OVER (0x1 << 6) +#define R0_CHK_FLAG (0x1 << 5) + +/* ANALOGIX_DP_COMMON_INT_STA_4 */ +#define PSR_ACTIVE (0x1 << 7) +#define PSR_INACTIVE (0x1 << 6) +#define SPDIF_BI_PHASE_ERR (0x1 << 5) +#define HOTPLUG_CHG (0x1 << 2) +#define HPD_LOST (0x1 << 1) +#define PLUG (0x1 << 0) + +/* ANALOGIX_DP_INT_STA */ +#define INT_HPD (0x1 << 6) +#define HW_TRAINING_FINISH (0x1 << 5) +#define RPLY_RECEIV (0x1 << 1) +#define AUX_ERR (0x1 << 0) + +/* ANALOGIX_DP_INT_CTL */ +#define SOFT_INT_CTRL (0x1 << 2) +#define INT_POL1 (0x1 << 1) +#define INT_POL0 (0x1 << 0) + +/* ANALOGIX_DP_SYS_CTL_1 */ +#define DET_STA (0x1 << 2) +#define FORCE_DET (0x1 << 1) +#define DET_CTRL (0x1 << 0) + +/* ANALOGIX_DP_SYS_CTL_2 */ +#define CHA_CRI(x) (((x) & 0xf) << 4) +#define CHA_STA (0x1 << 2) +#define FORCE_CHA (0x1 << 1) +#define CHA_CTRL (0x1 << 0) + +/* ANALOGIX_DP_SYS_CTL_3 */ +#define HPD_STATUS (0x1 << 6) +#define F_HPD (0x1 << 5) +#define HPD_CTRL (0x1 << 4) +#define HDCP_RDY (0x1 << 3) +#define STRM_VALID (0x1 << 2) +#define F_VALID (0x1 << 1) +#define VALID_CTRL (0x1 << 0) + +/* ANALOGIX_DP_SYS_CTL_4 */ +#define FIX_M_AUD (0x1 << 4) +#define ENHANCED (0x1 << 3) +#define FIX_M_VID (0x1 << 2) +#define M_VID_UPDATE_CTRL (0x3 << 0) + +/* ANALOGIX_DP_TRAINING_PTN_SET */ +#define SCRAMBLER_TYPE (0x1 << 9) +#define HW_LINK_TRAINING_PATTERN (0x1 << 8) +#define SCRAMBLING_DISABLE (0x1 << 5) +#define SCRAMBLING_ENABLE (0x0 << 5) +#define LINK_QUAL_PATTERN_SET_MASK (0x3 << 2) +#define LINK_QUAL_PATTERN_SET_PRBS7 (0x3 << 2) +#define LINK_QUAL_PATTERN_SET_D10_2 (0x1 << 2) +#define LINK_QUAL_PATTERN_SET_DISABLE (0x0 << 2) +#define SW_TRAINING_PATTERN_SET_MASK (0x3 << 0) +#define SW_TRAINING_PATTERN_SET_PTN2 (0x2 << 0) +#define SW_TRAINING_PATTERN_SET_PTN1 (0x1 << 0) +#define SW_TRAINING_PATTERN_SET_NORMAL (0x0 << 0) + +/* ANALOGIX_DP_LN0_LINK_TRAINING_CTL */ +#define PRE_EMPHASIS_SET_MASK (0x3 << 3) +#define PRE_EMPHASIS_SET_SHIFT (3) + +/* ANALOGIX_DP_DEBUG_CTL */ +#define PLL_LOCK (0x1 << 4) +#define F_PLL_LOCK (0x1 << 3) +#define PLL_LOCK_CTRL (0x1 << 2) +#define PN_INV (0x1 << 0) + +/* ANALOGIX_DP_PLL_CTL */ +#define DP_PLL_PD (0x1 << 7) +#define DP_PLL_RESET (0x1 << 6) +#define DP_PLL_LOOP_BIT_DEFAULT (0x1 << 4) +#define DP_PLL_REF_BIT_1_1250V (0x5 << 0) +#define DP_PLL_REF_BIT_1_2500V (0x7 << 0) + +/* ANALOGIX_DP_PHY_PD */ +#define DP_INC_BG (0x1 << 7) +#define DP_EXP_BG (0x1 << 6) +#define DP_PHY_PD (0x1 << 5) +#define RK_AUX_PD (0x1 << 5) +#define AUX_PD (0x1 << 4) +#define RK_PLL_PD (0x1 << 4) +#define CH3_PD (0x1 << 3) +#define CH2_PD (0x1 << 2) +#define CH1_PD (0x1 << 1) +#define CH0_PD (0x1 << 0) +#define DP_ALL_PD (0xff) + +/* ANALOGIX_DP_PHY_TEST */ +#define MACRO_RST (0x1 << 5) +#define CH1_TEST (0x1 << 1) +#define CH0_TEST (0x1 << 0) + +/* ANALOGIX_DP_AUX_CH_STA */ +#define AUX_BUSY (0x1 << 4) +#define AUX_STATUS_MASK (0xf << 0) + +/* ANALOGIX_DP_AUX_CH_DEFER_CTL */ +#define DEFER_CTRL_EN (0x1 << 7) +#define DEFER_COUNT(x) (((x) & 0x7f) << 0) + +/* ANALOGIX_DP_AUX_RX_COMM */ +#define AUX_RX_COMM_I2C_DEFER (0x2 << 2) +#define AUX_RX_COMM_AUX_DEFER (0x2 << 0) + +/* ANALOGIX_DP_BUFFER_DATA_CTL */ +#define BUF_CLR (0x1 << 7) +#define BUF_DATA_COUNT(x) (((x) & 0x1f) << 0) + +/* ANALOGIX_DP_AUX_CH_CTL_1 */ +#define AUX_LENGTH(x) (((x - 1) & 0xf) << 4) +#define AUX_TX_COMM_MASK (0xf << 0) +#define AUX_TX_COMM_DP_TRANSACTION (0x1 << 3) +#define AUX_TX_COMM_I2C_TRANSACTION (0x0 << 3) +#define AUX_TX_COMM_MOT (0x1 << 2) +#define AUX_TX_COMM_WRITE (0x0 << 0) +#define AUX_TX_COMM_READ (0x1 << 0) + +/* ANALOGIX_DP_AUX_ADDR_7_0 */ +#define AUX_ADDR_7_0(x) (((x) >> 0) & 0xff) + +/* ANALOGIX_DP_AUX_ADDR_15_8 */ +#define AUX_ADDR_15_8(x) (((x) >> 8) & 0xff) + +/* ANALOGIX_DP_AUX_ADDR_19_16 */ +#define AUX_ADDR_19_16(x) (((x) >> 16) & 0x0f) + +/* ANALOGIX_DP_AUX_CH_CTL_2 */ +#define ADDR_ONLY (0x1 << 1) +#define AUX_EN (0x1 << 0) + +/* ANALOGIX_DP_SOC_GENERAL_CTL */ +#define AUDIO_MODE_SPDIF_MODE (0x1 << 8) +#define AUDIO_MODE_MASTER_MODE (0x0 << 8) +#define MASTER_VIDEO_INTERLACE_EN (0x1 << 4) +#define VIDEO_MASTER_CLK_SEL (0x1 << 2) +#define VIDEO_MASTER_MODE_EN (0x1 << 1) +#define VIDEO_MODE_MASK (0x1 << 0) +#define VIDEO_MODE_SLAVE_MODE (0x1 << 0) +#define VIDEO_MODE_MASTER_MODE (0x0 << 0) + +/* ANALOGIX_DP_PKT_SEND_CTL */ +#define IF_UP (0x1 << 4) +#define IF_EN (0x1 << 0) + +/* ANALOGIX_DP_CRC_CON */ +#define PSR_VID_CRC_FLUSH (0x1 << 2) +#define PSR_VID_CRC_ENABLE (0x1 << 0) + +#endif /* _ANALOGIX_DP_REG_H */ diff --git a/drivers/gpu/drm/bridge/cadence/Kconfig b/drivers/gpu/drm/bridge/cadence/Kconfig new file mode 100644 index 000000000..ef8c230e0 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/Kconfig @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_CDNS_MHDP8546 + tristate "Cadence DPI/DP bridge" + select DRM_KMS_HELPER + select DRM_PANEL_BRIDGE + depends on OF + help + Support Cadence DPI to DP bridge. This is an internal + bridge and is meant to be directly embedded in a SoC. + It takes a DPI stream as input and outputs it encoded + in DP format. + +if DRM_CDNS_MHDP8546 + +config DRM_CDNS_MHDP8546_J721E + depends on ARCH_K3 || COMPILE_TEST + bool "J721E Cadence DPI/DP wrapper support" + default y + help + Support J721E Cadence DPI/DP wrapper. This is a wrapper + which adds support for J721E related platform ops. It + initializes the J721E Display Port and sets up the + clock and data muxes. +endif diff --git a/drivers/gpu/drm/bridge/cadence/Makefile b/drivers/gpu/drm/bridge/cadence/Makefile new file mode 100644 index 000000000..8f647991b --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_DRM_CDNS_MHDP8546) += cdns-mhdp8546.o +cdns-mhdp8546-y := cdns-mhdp8546-core.o +cdns-mhdp8546-$(CONFIG_DRM_CDNS_MHDP8546_J721E) += cdns-mhdp8546-j721e.o diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c new file mode 100644 index 000000000..f56ff97c9 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c @@ -0,0 +1,2532 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence MHDP8546 DP bridge driver. + * + * Copyright (C) 2020 Cadence Design Systems, Inc. + * + * Authors: Quentin Schulz <quentin.schulz@free-electrons.com> + * Swapnil Jakhade <sjakhade@cadence.com> + * Yuti Amonkar <yamonkar@cadence.com> + * Tomi Valkeinen <tomi.valkeinen@ti.com> + * Jyri Sarha <jsarha@ti.com> + * + * TODO: + * - Implement optimized mailbox communication using mailbox interrupts + * - Add support for power management + * - Add support for features like audio, MST and fast link training + * - Implement request_fw_cancel to handle HW_STATE + * - Fix asynchronous loading of firmware implementation + * - Add DRM helper function for cdns_mhdp_lower_link_rate + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/firmware.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/phy/phy-dp.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_dp_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include <asm/unaligned.h> + +#include "cdns-mhdp8546-core.h" + +#include "cdns-mhdp8546-j721e.h" + +static int cdns_mhdp_mailbox_read(struct cdns_mhdp_device *mhdp) +{ + int ret, empty; + + WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex)); + + ret = readx_poll_timeout(readl, mhdp->regs + CDNS_MAILBOX_EMPTY, + empty, !empty, MAILBOX_RETRY_US, + MAILBOX_TIMEOUT_US); + if (ret < 0) + return ret; + + return readl(mhdp->regs + CDNS_MAILBOX_RX_DATA) & 0xff; +} + +static int cdns_mhdp_mailbox_write(struct cdns_mhdp_device *mhdp, u8 val) +{ + int ret, full; + + WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex)); + + ret = readx_poll_timeout(readl, mhdp->regs + CDNS_MAILBOX_FULL, + full, !full, MAILBOX_RETRY_US, + MAILBOX_TIMEOUT_US); + if (ret < 0) + return ret; + + writel(val, mhdp->regs + CDNS_MAILBOX_TX_DATA); + + return 0; +} + +static int cdns_mhdp_mailbox_recv_header(struct cdns_mhdp_device *mhdp, + u8 module_id, u8 opcode, + u16 req_size) +{ + u32 mbox_size, i; + u8 header[4]; + int ret; + + /* read the header of the message */ + for (i = 0; i < sizeof(header); i++) { + ret = cdns_mhdp_mailbox_read(mhdp); + if (ret < 0) + return ret; + + header[i] = ret; + } + + mbox_size = get_unaligned_be16(header + 2); + + if (opcode != header[0] || module_id != header[1] || + req_size != mbox_size) { + /* + * If the message in mailbox is not what we want, we need to + * clear the mailbox by reading its contents. + */ + for (i = 0; i < mbox_size; i++) + if (cdns_mhdp_mailbox_read(mhdp) < 0) + break; + + return -EINVAL; + } + + return 0; +} + +static int cdns_mhdp_mailbox_recv_data(struct cdns_mhdp_device *mhdp, + u8 *buff, u16 buff_size) +{ + u32 i; + int ret; + + for (i = 0; i < buff_size; i++) { + ret = cdns_mhdp_mailbox_read(mhdp); + if (ret < 0) + return ret; + + buff[i] = ret; + } + + return 0; +} + +static int cdns_mhdp_mailbox_send(struct cdns_mhdp_device *mhdp, u8 module_id, + u8 opcode, u16 size, u8 *message) +{ + u8 header[4]; + int ret, i; + + header[0] = opcode; + header[1] = module_id; + put_unaligned_be16(size, header + 2); + + for (i = 0; i < sizeof(header); i++) { + ret = cdns_mhdp_mailbox_write(mhdp, header[i]); + if (ret) + return ret; + } + + for (i = 0; i < size; i++) { + ret = cdns_mhdp_mailbox_write(mhdp, message[i]); + if (ret) + return ret; + } + + return 0; +} + +static +int cdns_mhdp_reg_read(struct cdns_mhdp_device *mhdp, u32 addr, u32 *value) +{ + u8 msg[4], resp[8]; + int ret; + + put_unaligned_be32(addr, msg); + + mutex_lock(&mhdp->mbox_mutex); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL, + GENERAL_REGISTER_READ, + sizeof(msg), msg); + if (ret) + goto out; + + ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_GENERAL, + GENERAL_REGISTER_READ, + sizeof(resp)); + if (ret) + goto out; + + ret = cdns_mhdp_mailbox_recv_data(mhdp, resp, sizeof(resp)); + if (ret) + goto out; + + /* Returned address value should be the same as requested */ + if (memcmp(msg, resp, sizeof(msg))) { + ret = -EINVAL; + goto out; + } + + *value = get_unaligned_be32(resp + 4); + +out: + mutex_unlock(&mhdp->mbox_mutex); + if (ret) { + dev_err(mhdp->dev, "Failed to read register\n"); + *value = 0; + } + + return ret; +} + +static +int cdns_mhdp_reg_write(struct cdns_mhdp_device *mhdp, u16 addr, u32 val) +{ + u8 msg[6]; + int ret; + + put_unaligned_be16(addr, msg); + put_unaligned_be32(val, msg + 2); + + mutex_lock(&mhdp->mbox_mutex); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_WRITE_REGISTER, sizeof(msg), msg); + + mutex_unlock(&mhdp->mbox_mutex); + + return ret; +} + +static +int cdns_mhdp_reg_write_bit(struct cdns_mhdp_device *mhdp, u16 addr, + u8 start_bit, u8 bits_no, u32 val) +{ + u8 field[8]; + int ret; + + put_unaligned_be16(addr, field); + field[2] = start_bit; + field[3] = bits_no; + put_unaligned_be32(val, field + 4); + + mutex_lock(&mhdp->mbox_mutex); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_WRITE_FIELD, sizeof(field), field); + + mutex_unlock(&mhdp->mbox_mutex); + + return ret; +} + +static +int cdns_mhdp_dpcd_read(struct cdns_mhdp_device *mhdp, + u32 addr, u8 *data, u16 len) +{ + u8 msg[5], reg[5]; + int ret; + + put_unaligned_be16(len, msg); + put_unaligned_be24(addr, msg + 2); + + mutex_lock(&mhdp->mbox_mutex); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_READ_DPCD, sizeof(msg), msg); + if (ret) + goto out; + + ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_DP_TX, + DPTX_READ_DPCD, + sizeof(reg) + len); + if (ret) + goto out; + + ret = cdns_mhdp_mailbox_recv_data(mhdp, reg, sizeof(reg)); + if (ret) + goto out; + + ret = cdns_mhdp_mailbox_recv_data(mhdp, data, len); + +out: + mutex_unlock(&mhdp->mbox_mutex); + + return ret; +} + +static +int cdns_mhdp_dpcd_write(struct cdns_mhdp_device *mhdp, u32 addr, u8 value) +{ + u8 msg[6], reg[5]; + int ret; + + put_unaligned_be16(1, msg); + put_unaligned_be24(addr, msg + 2); + msg[5] = value; + + mutex_lock(&mhdp->mbox_mutex); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_WRITE_DPCD, sizeof(msg), msg); + if (ret) + goto out; + + ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_DP_TX, + DPTX_WRITE_DPCD, sizeof(reg)); + if (ret) + goto out; + + ret = cdns_mhdp_mailbox_recv_data(mhdp, reg, sizeof(reg)); + if (ret) + goto out; + + if (addr != get_unaligned_be24(reg + 2)) + ret = -EINVAL; + +out: + mutex_unlock(&mhdp->mbox_mutex); + + if (ret) + dev_err(mhdp->dev, "dpcd write failed: %d\n", ret); + return ret; +} + +static +int cdns_mhdp_set_firmware_active(struct cdns_mhdp_device *mhdp, bool enable) +{ + u8 msg[5]; + int ret, i; + + msg[0] = GENERAL_MAIN_CONTROL; + msg[1] = MB_MODULE_ID_GENERAL; + msg[2] = 0; + msg[3] = 1; + msg[4] = enable ? FW_ACTIVE : FW_STANDBY; + + mutex_lock(&mhdp->mbox_mutex); + + for (i = 0; i < sizeof(msg); i++) { + ret = cdns_mhdp_mailbox_write(mhdp, msg[i]); + if (ret) + goto out; + } + + /* read the firmware state */ + ret = cdns_mhdp_mailbox_recv_data(mhdp, msg, sizeof(msg)); + if (ret) + goto out; + + ret = 0; + +out: + mutex_unlock(&mhdp->mbox_mutex); + + if (ret < 0) + dev_err(mhdp->dev, "set firmware active failed\n"); + return ret; +} + +static +int cdns_mhdp_get_hpd_status(struct cdns_mhdp_device *mhdp) +{ + u8 status; + int ret; + + mutex_lock(&mhdp->mbox_mutex); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_HPD_STATE, 0, NULL); + if (ret) + goto err_get_hpd; + + ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_DP_TX, + DPTX_HPD_STATE, + sizeof(status)); + if (ret) + goto err_get_hpd; + + ret = cdns_mhdp_mailbox_recv_data(mhdp, &status, sizeof(status)); + if (ret) + goto err_get_hpd; + + mutex_unlock(&mhdp->mbox_mutex); + + dev_dbg(mhdp->dev, "%s: HPD %splugged\n", __func__, + status ? "" : "un"); + + return status; + +err_get_hpd: + mutex_unlock(&mhdp->mbox_mutex); + + return ret; +} + +static +int cdns_mhdp_get_edid_block(void *data, u8 *edid, + unsigned int block, size_t length) +{ + struct cdns_mhdp_device *mhdp = data; + u8 msg[2], reg[2], i; + int ret; + + mutex_lock(&mhdp->mbox_mutex); + + for (i = 0; i < 4; i++) { + msg[0] = block / 2; + msg[1] = block % 2; + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_GET_EDID, sizeof(msg), msg); + if (ret) + continue; + + ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_DP_TX, + DPTX_GET_EDID, + sizeof(reg) + length); + if (ret) + continue; + + ret = cdns_mhdp_mailbox_recv_data(mhdp, reg, sizeof(reg)); + if (ret) + continue; + + ret = cdns_mhdp_mailbox_recv_data(mhdp, edid, length); + if (ret) + continue; + + if (reg[0] == length && reg[1] == block / 2) + break; + } + + mutex_unlock(&mhdp->mbox_mutex); + + if (ret) + dev_err(mhdp->dev, "get block[%d] edid failed: %d\n", + block, ret); + + return ret; +} + +static +int cdns_mhdp_read_hpd_event(struct cdns_mhdp_device *mhdp) +{ + u8 event = 0; + int ret; + + mutex_lock(&mhdp->mbox_mutex); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_READ_EVENT, 0, NULL); + if (ret) + goto out; + + ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_DP_TX, + DPTX_READ_EVENT, sizeof(event)); + if (ret < 0) + goto out; + + ret = cdns_mhdp_mailbox_recv_data(mhdp, &event, sizeof(event)); +out: + mutex_unlock(&mhdp->mbox_mutex); + + if (ret < 0) + return ret; + + dev_dbg(mhdp->dev, "%s: %s%s%s%s\n", __func__, + (event & DPTX_READ_EVENT_HPD_TO_HIGH) ? "TO_HIGH " : "", + (event & DPTX_READ_EVENT_HPD_TO_LOW) ? "TO_LOW " : "", + (event & DPTX_READ_EVENT_HPD_PULSE) ? "PULSE " : "", + (event & DPTX_READ_EVENT_HPD_STATE) ? "HPD_STATE " : ""); + + return event; +} + +static +int cdns_mhdp_adjust_lt(struct cdns_mhdp_device *mhdp, unsigned int nlanes, + unsigned int udelay, const u8 *lanes_data, + u8 link_status[DP_LINK_STATUS_SIZE]) +{ + u8 payload[7]; + u8 hdr[5]; /* For DPCD read response header */ + u32 addr; + int ret; + + if (nlanes != 4 && nlanes != 2 && nlanes != 1) { + dev_err(mhdp->dev, "invalid number of lanes: %u\n", nlanes); + ret = -EINVAL; + goto out; + } + + payload[0] = nlanes; + put_unaligned_be16(udelay, payload + 1); + memcpy(payload + 3, lanes_data, nlanes); + + mutex_lock(&mhdp->mbox_mutex); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_ADJUST_LT, + sizeof(payload), payload); + if (ret) + goto out; + + /* Yes, read the DPCD read command response */ + ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_DP_TX, + DPTX_READ_DPCD, + sizeof(hdr) + DP_LINK_STATUS_SIZE); + if (ret) + goto out; + + ret = cdns_mhdp_mailbox_recv_data(mhdp, hdr, sizeof(hdr)); + if (ret) + goto out; + + addr = get_unaligned_be24(hdr + 2); + if (addr != DP_LANE0_1_STATUS) + goto out; + + ret = cdns_mhdp_mailbox_recv_data(mhdp, link_status, + DP_LINK_STATUS_SIZE); + +out: + mutex_unlock(&mhdp->mbox_mutex); + + if (ret) + dev_err(mhdp->dev, "Failed to adjust Link Training.\n"); + + return ret; +} + +/** + * cdns_mhdp_link_power_up() - power up a DisplayPort link + * @aux: DisplayPort AUX channel + * @link: pointer to a structure containing the link configuration + * + * Returns 0 on success or a negative error code on failure. + */ +static +int cdns_mhdp_link_power_up(struct drm_dp_aux *aux, struct cdns_mhdp_link *link) +{ + u8 value; + int err; + + /* DP_SET_POWER register is only available on DPCD v1.1 and later */ + if (link->revision < 0x11) + return 0; + + err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value); + if (err < 0) + return err; + + value &= ~DP_SET_POWER_MASK; + value |= DP_SET_POWER_D0; + + err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value); + if (err < 0) + return err; + + /* + * According to the DP 1.1 specification, a "Sink Device must exit the + * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink + * Control Field" (register 0x600). + */ + usleep_range(1000, 2000); + + return 0; +} + +/** + * cdns_mhdp_link_power_down() - power down a DisplayPort link + * @aux: DisplayPort AUX channel + * @link: pointer to a structure containing the link configuration + * + * Returns 0 on success or a negative error code on failure. + */ +static +int cdns_mhdp_link_power_down(struct drm_dp_aux *aux, + struct cdns_mhdp_link *link) +{ + u8 value; + int err; + + /* DP_SET_POWER register is only available on DPCD v1.1 and later */ + if (link->revision < 0x11) + return 0; + + err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value); + if (err < 0) + return err; + + value &= ~DP_SET_POWER_MASK; + value |= DP_SET_POWER_D3; + + err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value); + if (err < 0) + return err; + + return 0; +} + +/** + * cdns_mhdp_link_configure() - configure a DisplayPort link + * @aux: DisplayPort AUX channel + * @link: pointer to a structure containing the link configuration + * + * Returns 0 on success or a negative error code on failure. + */ +static +int cdns_mhdp_link_configure(struct drm_dp_aux *aux, + struct cdns_mhdp_link *link) +{ + u8 values[2]; + int err; + + values[0] = drm_dp_link_rate_to_bw_code(link->rate); + values[1] = link->num_lanes; + + if (link->capabilities & DP_LINK_CAP_ENHANCED_FRAMING) + values[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + + err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, values, sizeof(values)); + if (err < 0) + return err; + + return 0; +} + +static unsigned int cdns_mhdp_max_link_rate(struct cdns_mhdp_device *mhdp) +{ + return min(mhdp->host.link_rate, mhdp->sink.link_rate); +} + +static u8 cdns_mhdp_max_num_lanes(struct cdns_mhdp_device *mhdp) +{ + return min(mhdp->sink.lanes_cnt, mhdp->host.lanes_cnt); +} + +static u8 cdns_mhdp_eq_training_pattern_supported(struct cdns_mhdp_device *mhdp) +{ + return fls(mhdp->host.pattern_supp & mhdp->sink.pattern_supp); +} + +static bool cdns_mhdp_get_ssc_supported(struct cdns_mhdp_device *mhdp) +{ + /* Check if SSC is supported by both sides */ + return mhdp->host.ssc && mhdp->sink.ssc; +} + +static enum drm_connector_status cdns_mhdp_detect(struct cdns_mhdp_device *mhdp) +{ + dev_dbg(mhdp->dev, "%s: %d\n", __func__, mhdp->plugged); + + if (mhdp->plugged) + return connector_status_connected; + else + return connector_status_disconnected; +} + +static int cdns_mhdp_check_fw_version(struct cdns_mhdp_device *mhdp) +{ + u32 major_num, minor_num, revision; + u32 fw_ver, lib_ver; + + fw_ver = (readl(mhdp->regs + CDNS_VER_H) << 8) + | readl(mhdp->regs + CDNS_VER_L); + + lib_ver = (readl(mhdp->regs + CDNS_LIB_H_ADDR) << 8) + | readl(mhdp->regs + CDNS_LIB_L_ADDR); + + if (lib_ver < 33984) { + /* + * Older FW versions with major number 1, used to store FW + * version information by storing repository revision number + * in registers. This is for identifying these FW versions. + */ + major_num = 1; + minor_num = 2; + if (fw_ver == 26098) { + revision = 15; + } else if (lib_ver == 0 && fw_ver == 0) { + revision = 17; + } else { + dev_err(mhdp->dev, "Unsupported FW version: fw_ver = %u, lib_ver = %u\n", + fw_ver, lib_ver); + return -ENODEV; + } + } else { + /* To identify newer FW versions with major number 2 onwards. */ + major_num = fw_ver / 10000; + minor_num = (fw_ver / 100) % 100; + revision = (fw_ver % 10000) % 100; + } + + dev_dbg(mhdp->dev, "FW version: v%u.%u.%u\n", major_num, minor_num, + revision); + return 0; +} + +static int cdns_mhdp_fw_activate(const struct firmware *fw, + struct cdns_mhdp_device *mhdp) +{ + unsigned int reg; + int ret; + + /* Release uCPU reset and stall it. */ + writel(CDNS_CPU_STALL, mhdp->regs + CDNS_APB_CTRL); + + memcpy_toio(mhdp->regs + CDNS_MHDP_IMEM, fw->data, fw->size); + + /* Leave debug mode, release stall */ + writel(0, mhdp->regs + CDNS_APB_CTRL); + + /* + * Wait for the KEEP_ALIVE "message" on the first 8 bits. + * Updated each sched "tick" (~2ms) + */ + ret = readl_poll_timeout(mhdp->regs + CDNS_KEEP_ALIVE, reg, + reg & CDNS_KEEP_ALIVE_MASK, 500, + CDNS_KEEP_ALIVE_TIMEOUT); + if (ret) { + dev_err(mhdp->dev, + "device didn't give any life sign: reg %d\n", reg); + return ret; + } + + ret = cdns_mhdp_check_fw_version(mhdp); + if (ret) + return ret; + + /* Init events to 0 as it's not cleared by FW at boot but on read */ + readl(mhdp->regs + CDNS_SW_EVENT0); + readl(mhdp->regs + CDNS_SW_EVENT1); + readl(mhdp->regs + CDNS_SW_EVENT2); + readl(mhdp->regs + CDNS_SW_EVENT3); + + /* Activate uCPU */ + ret = cdns_mhdp_set_firmware_active(mhdp, true); + if (ret) + return ret; + + spin_lock(&mhdp->start_lock); + + mhdp->hw_state = MHDP_HW_READY; + + /* + * Here we must keep the lock while enabling the interrupts + * since it would otherwise be possible that interrupt enable + * code is executed after the bridge is detached. The similar + * situation is not possible in attach()/detach() callbacks + * since the hw_state changes from MHDP_HW_READY to + * MHDP_HW_STOPPED happens only due to driver removal when + * bridge should already be detached. + */ + if (mhdp->bridge_attached) + writel(~(u32)CDNS_APB_INT_MASK_SW_EVENT_INT, + mhdp->regs + CDNS_APB_INT_MASK); + + spin_unlock(&mhdp->start_lock); + + wake_up(&mhdp->fw_load_wq); + dev_dbg(mhdp->dev, "DP FW activated\n"); + + return 0; +} + +static void cdns_mhdp_fw_cb(const struct firmware *fw, void *context) +{ + struct cdns_mhdp_device *mhdp = context; + bool bridge_attached; + int ret; + + dev_dbg(mhdp->dev, "firmware callback\n"); + + if (!fw || !fw->data) { + dev_err(mhdp->dev, "%s: No firmware.\n", __func__); + return; + } + + ret = cdns_mhdp_fw_activate(fw, mhdp); + + release_firmware(fw); + + if (ret) + return; + + /* + * XXX how to make sure the bridge is still attached when + * calling drm_kms_helper_hotplug_event() after releasing + * the lock? We should not hold the spin lock when + * calling drm_kms_helper_hotplug_event() since it may + * cause a dead lock. FB-dev console calls detect from the + * same thread just down the call stack started here. + */ + spin_lock(&mhdp->start_lock); + bridge_attached = mhdp->bridge_attached; + spin_unlock(&mhdp->start_lock); + if (bridge_attached) { + if (mhdp->connector.dev) + drm_kms_helper_hotplug_event(mhdp->bridge.dev); + else + drm_bridge_hpd_notify(&mhdp->bridge, cdns_mhdp_detect(mhdp)); + } +} + +static int cdns_mhdp_load_firmware(struct cdns_mhdp_device *mhdp) +{ + int ret; + + ret = request_firmware_nowait(THIS_MODULE, true, FW_NAME, mhdp->dev, + GFP_KERNEL, mhdp, cdns_mhdp_fw_cb); + if (ret) { + dev_err(mhdp->dev, "failed to load firmware (%s), ret: %d\n", + FW_NAME, ret); + return ret; + } + + return 0; +} + +static ssize_t cdns_mhdp_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(aux->dev); + int ret; + + if (msg->request != DP_AUX_NATIVE_WRITE && + msg->request != DP_AUX_NATIVE_READ) + return -EOPNOTSUPP; + + if (msg->request == DP_AUX_NATIVE_WRITE) { + const u8 *buf = msg->buffer; + unsigned int i; + + for (i = 0; i < msg->size; ++i) { + ret = cdns_mhdp_dpcd_write(mhdp, + msg->address + i, buf[i]); + if (!ret) + continue; + + dev_err(mhdp->dev, + "Failed to write DPCD addr %u\n", + msg->address + i); + + return ret; + } + } else { + ret = cdns_mhdp_dpcd_read(mhdp, msg->address, + msg->buffer, msg->size); + if (ret) { + dev_err(mhdp->dev, + "Failed to read DPCD addr %u\n", + msg->address); + + return ret; + } + } + + return msg->size; +} + +static int cdns_mhdp_link_training_init(struct cdns_mhdp_device *mhdp) +{ + union phy_configure_opts phy_cfg; + u32 reg32; + int ret; + + drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_DISABLE); + + /* Reset PHY configuration */ + reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1); + if (!mhdp->host.scrambler) + reg32 |= CDNS_PHY_SCRAMBLER_BYPASS; + + cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_ENHNCD, + mhdp->sink.enhanced & mhdp->host.enhanced); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_LANE_EN, + CDNS_DP_LANE_EN_LANES(mhdp->link.num_lanes)); + + cdns_mhdp_link_configure(&mhdp->aux, &mhdp->link); + phy_cfg.dp.link_rate = mhdp->link.rate / 100; + phy_cfg.dp.lanes = mhdp->link.num_lanes; + + memset(phy_cfg.dp.voltage, 0, sizeof(phy_cfg.dp.voltage)); + memset(phy_cfg.dp.pre, 0, sizeof(phy_cfg.dp.pre)); + + phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp); + phy_cfg.dp.set_lanes = true; + phy_cfg.dp.set_rate = true; + phy_cfg.dp.set_voltages = true; + ret = phy_configure(mhdp->phy, &phy_cfg); + if (ret) { + dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n", + __func__, ret); + return ret; + } + + cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, + CDNS_PHY_COMMON_CONFIG | + CDNS_PHY_TRAINING_EN | + CDNS_PHY_TRAINING_TYPE(1) | + CDNS_PHY_SCRAMBLER_BYPASS); + + drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_1 | DP_LINK_SCRAMBLING_DISABLE); + + return 0; +} + +static void cdns_mhdp_get_adjust_train(struct cdns_mhdp_device *mhdp, + u8 link_status[DP_LINK_STATUS_SIZE], + u8 lanes_data[CDNS_DP_MAX_NUM_LANES], + union phy_configure_opts *phy_cfg) +{ + u8 adjust, max_pre_emph, max_volt_swing; + u8 set_volt, set_pre; + unsigned int i; + + max_pre_emph = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis) + << DP_TRAIN_PRE_EMPHASIS_SHIFT; + max_volt_swing = CDNS_VOLT_SWING(mhdp->host.volt_swing); + + for (i = 0; i < mhdp->link.num_lanes; i++) { + /* Check if Voltage swing and pre-emphasis are within limits */ + adjust = drm_dp_get_adjust_request_voltage(link_status, i); + set_volt = min(adjust, max_volt_swing); + + adjust = drm_dp_get_adjust_request_pre_emphasis(link_status, i); + set_pre = min(adjust, max_pre_emph) + >> DP_TRAIN_PRE_EMPHASIS_SHIFT; + + /* + * Voltage swing level and pre-emphasis level combination is + * not allowed: leaving pre-emphasis as-is, and adjusting + * voltage swing. + */ + if (set_volt + set_pre > 3) + set_volt = 3 - set_pre; + + phy_cfg->dp.voltage[i] = set_volt; + lanes_data[i] = set_volt; + + if (set_volt == max_volt_swing) + lanes_data[i] |= DP_TRAIN_MAX_SWING_REACHED; + + phy_cfg->dp.pre[i] = set_pre; + lanes_data[i] |= (set_pre << DP_TRAIN_PRE_EMPHASIS_SHIFT); + + if (set_pre == (max_pre_emph >> DP_TRAIN_PRE_EMPHASIS_SHIFT)) + lanes_data[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + } +} + +static +void cdns_mhdp_set_adjust_request_voltage(u8 link_status[DP_LINK_STATUS_SIZE], + unsigned int lane, u8 volt) +{ + unsigned int s = ((lane & 1) ? + DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT : + DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT); + unsigned int idx = DP_ADJUST_REQUEST_LANE0_1 - DP_LANE0_1_STATUS + (lane >> 1); + + link_status[idx] &= ~(DP_ADJUST_VOLTAGE_SWING_LANE0_MASK << s); + link_status[idx] |= volt << s; +} + +static +void cdns_mhdp_set_adjust_request_pre_emphasis(u8 link_status[DP_LINK_STATUS_SIZE], + unsigned int lane, u8 pre_emphasis) +{ + unsigned int s = ((lane & 1) ? + DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT : + DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT); + unsigned int idx = DP_ADJUST_REQUEST_LANE0_1 - DP_LANE0_1_STATUS + (lane >> 1); + + link_status[idx] &= ~(DP_ADJUST_PRE_EMPHASIS_LANE0_MASK << s); + link_status[idx] |= pre_emphasis << s; +} + +static void cdns_mhdp_adjust_requested_eq(struct cdns_mhdp_device *mhdp, + u8 link_status[DP_LINK_STATUS_SIZE]) +{ + u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis); + u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing); + unsigned int i; + u8 volt, pre; + + for (i = 0; i < mhdp->link.num_lanes; i++) { + volt = drm_dp_get_adjust_request_voltage(link_status, i); + pre = drm_dp_get_adjust_request_pre_emphasis(link_status, i); + if (volt + pre > 3) + cdns_mhdp_set_adjust_request_voltage(link_status, i, + 3 - pre); + if (mhdp->host.volt_swing & CDNS_FORCE_VOLT_SWING) + cdns_mhdp_set_adjust_request_voltage(link_status, i, + max_volt); + if (mhdp->host.pre_emphasis & CDNS_FORCE_PRE_EMPHASIS) + cdns_mhdp_set_adjust_request_pre_emphasis(link_status, + i, max_pre); + } +} + +static void cdns_mhdp_print_lt_status(const char *prefix, + struct cdns_mhdp_device *mhdp, + union phy_configure_opts *phy_cfg) +{ + char vs[8] = "0/0/0/0"; + char pe[8] = "0/0/0/0"; + unsigned int i; + + for (i = 0; i < mhdp->link.num_lanes; i++) { + vs[i * 2] = '0' + phy_cfg->dp.voltage[i]; + pe[i * 2] = '0' + phy_cfg->dp.pre[i]; + } + + vs[i * 2 - 1] = '\0'; + pe[i * 2 - 1] = '\0'; + + dev_dbg(mhdp->dev, "%s, %u lanes, %u Mbps, vs %s, pe %s\n", + prefix, + mhdp->link.num_lanes, mhdp->link.rate / 100, + vs, pe); +} + +static bool cdns_mhdp_link_training_channel_eq(struct cdns_mhdp_device *mhdp, + u8 eq_tps, + unsigned int training_interval) +{ + u8 lanes_data[CDNS_DP_MAX_NUM_LANES], fail_counter_short = 0; + u8 link_status[DP_LINK_STATUS_SIZE]; + union phy_configure_opts phy_cfg; + u32 reg32; + int ret; + bool r; + + dev_dbg(mhdp->dev, "Starting EQ phase\n"); + + /* Enable link training TPS[eq_tps] in PHY */ + reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_EN | + CDNS_PHY_TRAINING_TYPE(eq_tps); + if (eq_tps != 4) + reg32 |= CDNS_PHY_SCRAMBLER_BYPASS; + cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32); + + drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET, + (eq_tps != 4) ? eq_tps | DP_LINK_SCRAMBLING_DISABLE : + CDNS_DP_TRAINING_PATTERN_4); + + drm_dp_dpcd_read_link_status(&mhdp->aux, link_status); + + do { + cdns_mhdp_get_adjust_train(mhdp, link_status, lanes_data, + &phy_cfg); + phy_cfg.dp.lanes = mhdp->link.num_lanes; + phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp); + phy_cfg.dp.set_lanes = false; + phy_cfg.dp.set_rate = false; + phy_cfg.dp.set_voltages = true; + ret = phy_configure(mhdp->phy, &phy_cfg); + if (ret) { + dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n", + __func__, ret); + goto err; + } + + cdns_mhdp_adjust_lt(mhdp, mhdp->link.num_lanes, + training_interval, lanes_data, link_status); + + r = drm_dp_clock_recovery_ok(link_status, mhdp->link.num_lanes); + if (!r) + goto err; + + if (drm_dp_channel_eq_ok(link_status, mhdp->link.num_lanes)) { + cdns_mhdp_print_lt_status("EQ phase ok", mhdp, + &phy_cfg); + return true; + } + + fail_counter_short++; + + cdns_mhdp_adjust_requested_eq(mhdp, link_status); + } while (fail_counter_short < 5); + +err: + cdns_mhdp_print_lt_status("EQ phase failed", mhdp, &phy_cfg); + + return false; +} + +static void cdns_mhdp_adjust_requested_cr(struct cdns_mhdp_device *mhdp, + u8 link_status[DP_LINK_STATUS_SIZE], + u8 *req_volt, u8 *req_pre) +{ + const u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing); + const u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis); + unsigned int i; + + for (i = 0; i < mhdp->link.num_lanes; i++) { + u8 val; + + val = mhdp->host.volt_swing & CDNS_FORCE_VOLT_SWING ? + max_volt : req_volt[i]; + cdns_mhdp_set_adjust_request_voltage(link_status, i, val); + + val = mhdp->host.pre_emphasis & CDNS_FORCE_PRE_EMPHASIS ? + max_pre : req_pre[i]; + cdns_mhdp_set_adjust_request_pre_emphasis(link_status, i, val); + } +} + +static +void cdns_mhdp_validate_cr(struct cdns_mhdp_device *mhdp, bool *cr_done, + bool *same_before_adjust, bool *max_swing_reached, + u8 before_cr[CDNS_DP_MAX_NUM_LANES], + u8 after_cr[DP_LINK_STATUS_SIZE], u8 *req_volt, + u8 *req_pre) +{ + const u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing); + const u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis); + bool same_pre, same_volt; + unsigned int i; + u8 adjust; + + *same_before_adjust = false; + *max_swing_reached = false; + *cr_done = drm_dp_clock_recovery_ok(after_cr, mhdp->link.num_lanes); + + for (i = 0; i < mhdp->link.num_lanes; i++) { + adjust = drm_dp_get_adjust_request_voltage(after_cr, i); + req_volt[i] = min(adjust, max_volt); + + adjust = drm_dp_get_adjust_request_pre_emphasis(after_cr, i) >> + DP_TRAIN_PRE_EMPHASIS_SHIFT; + req_pre[i] = min(adjust, max_pre); + + same_pre = (before_cr[i] & DP_TRAIN_PRE_EMPHASIS_MASK) == + req_pre[i] << DP_TRAIN_PRE_EMPHASIS_SHIFT; + same_volt = (before_cr[i] & DP_TRAIN_VOLTAGE_SWING_MASK) == + req_volt[i]; + if (same_pre && same_volt) + *same_before_adjust = true; + + /* 3.1.5.2 in DP Standard v1.4. Table 3-1 */ + if (!*cr_done && req_volt[i] + req_pre[i] >= 3) { + *max_swing_reached = true; + return; + } + } +} + +static bool cdns_mhdp_link_training_cr(struct cdns_mhdp_device *mhdp) +{ + u8 lanes_data[CDNS_DP_MAX_NUM_LANES], + fail_counter_short = 0, fail_counter_cr_long = 0; + u8 link_status[DP_LINK_STATUS_SIZE]; + bool cr_done; + union phy_configure_opts phy_cfg; + int ret; + + dev_dbg(mhdp->dev, "Starting CR phase\n"); + + ret = cdns_mhdp_link_training_init(mhdp); + if (ret) + goto err; + + drm_dp_dpcd_read_link_status(&mhdp->aux, link_status); + + do { + u8 requested_adjust_volt_swing[CDNS_DP_MAX_NUM_LANES] = {}; + u8 requested_adjust_pre_emphasis[CDNS_DP_MAX_NUM_LANES] = {}; + bool same_before_adjust, max_swing_reached; + + cdns_mhdp_get_adjust_train(mhdp, link_status, lanes_data, + &phy_cfg); + phy_cfg.dp.lanes = mhdp->link.num_lanes; + phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp); + phy_cfg.dp.set_lanes = false; + phy_cfg.dp.set_rate = false; + phy_cfg.dp.set_voltages = true; + ret = phy_configure(mhdp->phy, &phy_cfg); + if (ret) { + dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n", + __func__, ret); + goto err; + } + + cdns_mhdp_adjust_lt(mhdp, mhdp->link.num_lanes, 100, + lanes_data, link_status); + + cdns_mhdp_validate_cr(mhdp, &cr_done, &same_before_adjust, + &max_swing_reached, lanes_data, + link_status, + requested_adjust_volt_swing, + requested_adjust_pre_emphasis); + + if (max_swing_reached) { + dev_err(mhdp->dev, "CR: max swing reached\n"); + goto err; + } + + if (cr_done) { + cdns_mhdp_print_lt_status("CR phase ok", mhdp, + &phy_cfg); + return true; + } + + /* Not all CR_DONE bits set */ + fail_counter_cr_long++; + + if (same_before_adjust) { + fail_counter_short++; + continue; + } + + fail_counter_short = 0; + /* + * Voltage swing/pre-emphasis adjust requested + * during CR phase + */ + cdns_mhdp_adjust_requested_cr(mhdp, link_status, + requested_adjust_volt_swing, + requested_adjust_pre_emphasis); + } while (fail_counter_short < 5 && fail_counter_cr_long < 10); + +err: + cdns_mhdp_print_lt_status("CR phase failed", mhdp, &phy_cfg); + + return false; +} + +static void cdns_mhdp_lower_link_rate(struct cdns_mhdp_link *link) +{ + switch (drm_dp_link_rate_to_bw_code(link->rate)) { + case DP_LINK_BW_2_7: + link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_1_62); + break; + case DP_LINK_BW_5_4: + link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_2_7); + break; + case DP_LINK_BW_8_1: + link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_5_4); + break; + } +} + +static int cdns_mhdp_link_training(struct cdns_mhdp_device *mhdp, + unsigned int training_interval) +{ + u32 reg32; + const u8 eq_tps = cdns_mhdp_eq_training_pattern_supported(mhdp); + int ret; + + while (1) { + if (!cdns_mhdp_link_training_cr(mhdp)) { + if (drm_dp_link_rate_to_bw_code(mhdp->link.rate) != + DP_LINK_BW_1_62) { + dev_dbg(mhdp->dev, + "Reducing link rate during CR phase\n"); + cdns_mhdp_lower_link_rate(&mhdp->link); + + continue; + } else if (mhdp->link.num_lanes > 1) { + dev_dbg(mhdp->dev, + "Reducing lanes number during CR phase\n"); + mhdp->link.num_lanes >>= 1; + mhdp->link.rate = cdns_mhdp_max_link_rate(mhdp); + + continue; + } + + dev_err(mhdp->dev, + "Link training failed during CR phase\n"); + goto err; + } + + if (cdns_mhdp_link_training_channel_eq(mhdp, eq_tps, + training_interval)) + break; + + if (mhdp->link.num_lanes > 1) { + dev_dbg(mhdp->dev, + "Reducing lanes number during EQ phase\n"); + mhdp->link.num_lanes >>= 1; + + continue; + } else if (drm_dp_link_rate_to_bw_code(mhdp->link.rate) != + DP_LINK_BW_1_62) { + dev_dbg(mhdp->dev, + "Reducing link rate during EQ phase\n"); + cdns_mhdp_lower_link_rate(&mhdp->link); + mhdp->link.num_lanes = cdns_mhdp_max_num_lanes(mhdp); + + continue; + } + + dev_err(mhdp->dev, "Link training failed during EQ phase\n"); + goto err; + } + + dev_dbg(mhdp->dev, "Link training ok. Lanes: %u, Rate %u Mbps\n", + mhdp->link.num_lanes, mhdp->link.rate / 100); + + drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET, + mhdp->host.scrambler ? 0 : + DP_LINK_SCRAMBLING_DISABLE); + + ret = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, ®32); + if (ret < 0) { + dev_err(mhdp->dev, + "Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n", + ret); + return ret; + } + reg32 &= ~GENMASK(1, 0); + reg32 |= CDNS_DP_NUM_LANES(mhdp->link.num_lanes); + reg32 |= CDNS_DP_WR_FAILING_EDGE_VSYNC; + reg32 |= CDNS_DP_FRAMER_EN; + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, reg32); + + /* Reset PHY config */ + reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1); + if (!mhdp->host.scrambler) + reg32 |= CDNS_PHY_SCRAMBLER_BYPASS; + cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32); + + return 0; +err: + /* Reset PHY config */ + reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1); + if (!mhdp->host.scrambler) + reg32 |= CDNS_PHY_SCRAMBLER_BYPASS; + cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32); + + drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_DISABLE); + + return -EIO; +} + +static u32 cdns_mhdp_get_training_interval_us(struct cdns_mhdp_device *mhdp, + u32 interval) +{ + if (interval == 0) + return 400; + if (interval < 5) + return 4000 << (interval - 1); + dev_err(mhdp->dev, + "wrong training interval returned by DPCD: %d\n", interval); + return 0; +} + +static void cdns_mhdp_fill_host_caps(struct cdns_mhdp_device *mhdp) +{ + unsigned int link_rate; + + /* Get source capabilities based on PHY attributes */ + + mhdp->host.lanes_cnt = mhdp->phy->attrs.bus_width; + if (!mhdp->host.lanes_cnt) + mhdp->host.lanes_cnt = 4; + + link_rate = mhdp->phy->attrs.max_link_rate; + if (!link_rate) + link_rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_8_1); + else + /* PHY uses Mb/s, DRM uses tens of kb/s. */ + link_rate *= 100; + + mhdp->host.link_rate = link_rate; + mhdp->host.volt_swing = CDNS_VOLT_SWING(3); + mhdp->host.pre_emphasis = CDNS_PRE_EMPHASIS(3); + mhdp->host.pattern_supp = CDNS_SUPPORT_TPS(1) | + CDNS_SUPPORT_TPS(2) | CDNS_SUPPORT_TPS(3) | + CDNS_SUPPORT_TPS(4); + mhdp->host.lane_mapping = CDNS_LANE_MAPPING_NORMAL; + mhdp->host.fast_link = false; + mhdp->host.enhanced = true; + mhdp->host.scrambler = true; + mhdp->host.ssc = false; +} + +static void cdns_mhdp_fill_sink_caps(struct cdns_mhdp_device *mhdp, + u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + mhdp->sink.link_rate = mhdp->link.rate; + mhdp->sink.lanes_cnt = mhdp->link.num_lanes; + mhdp->sink.enhanced = !!(mhdp->link.capabilities & + DP_LINK_CAP_ENHANCED_FRAMING); + + /* Set SSC support */ + mhdp->sink.ssc = !!(dpcd[DP_MAX_DOWNSPREAD] & + DP_MAX_DOWNSPREAD_0_5); + + /* Set TPS support */ + mhdp->sink.pattern_supp = CDNS_SUPPORT_TPS(1) | CDNS_SUPPORT_TPS(2); + if (drm_dp_tps3_supported(dpcd)) + mhdp->sink.pattern_supp |= CDNS_SUPPORT_TPS(3); + if (drm_dp_tps4_supported(dpcd)) + mhdp->sink.pattern_supp |= CDNS_SUPPORT_TPS(4); + + /* Set fast link support */ + mhdp->sink.fast_link = !!(dpcd[DP_MAX_DOWNSPREAD] & + DP_NO_AUX_HANDSHAKE_LINK_TRAINING); +} + +static int cdns_mhdp_link_up(struct cdns_mhdp_device *mhdp) +{ + u8 dpcd[DP_RECEIVER_CAP_SIZE], amp[2]; + u32 resp, interval, interval_us; + u8 ext_cap_chk = 0; + unsigned int addr; + int err; + + WARN_ON(!mutex_is_locked(&mhdp->link_mutex)); + + drm_dp_dpcd_readb(&mhdp->aux, DP_TRAINING_AUX_RD_INTERVAL, + &ext_cap_chk); + + if (ext_cap_chk & DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT) + addr = DP_DP13_DPCD_REV; + else + addr = DP_DPCD_REV; + + err = drm_dp_dpcd_read(&mhdp->aux, addr, dpcd, DP_RECEIVER_CAP_SIZE); + if (err < 0) { + dev_err(mhdp->dev, "Failed to read receiver capabilities\n"); + return err; + } + + mhdp->link.revision = dpcd[0]; + mhdp->link.rate = drm_dp_bw_code_to_link_rate(dpcd[1]); + mhdp->link.num_lanes = dpcd[2] & DP_MAX_LANE_COUNT_MASK; + + if (dpcd[2] & DP_ENHANCED_FRAME_CAP) + mhdp->link.capabilities |= DP_LINK_CAP_ENHANCED_FRAMING; + + dev_dbg(mhdp->dev, "Set sink device power state via DPCD\n"); + cdns_mhdp_link_power_up(&mhdp->aux, &mhdp->link); + + cdns_mhdp_fill_sink_caps(mhdp, dpcd); + + mhdp->link.rate = cdns_mhdp_max_link_rate(mhdp); + mhdp->link.num_lanes = cdns_mhdp_max_num_lanes(mhdp); + + /* Disable framer for link training */ + err = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp); + if (err < 0) { + dev_err(mhdp->dev, + "Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n", + err); + return err; + } + + resp &= ~CDNS_DP_FRAMER_EN; + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp); + + /* Spread AMP if required, enable 8b/10b coding */ + amp[0] = cdns_mhdp_get_ssc_supported(mhdp) ? DP_SPREAD_AMP_0_5 : 0; + amp[1] = DP_SET_ANSI_8B10B; + drm_dp_dpcd_write(&mhdp->aux, DP_DOWNSPREAD_CTRL, amp, 2); + + if (mhdp->host.fast_link & mhdp->sink.fast_link) { + dev_err(mhdp->dev, "fastlink not supported\n"); + return -EOPNOTSUPP; + } + + interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] & DP_TRAINING_AUX_RD_MASK; + interval_us = cdns_mhdp_get_training_interval_us(mhdp, interval); + if (!interval_us || + cdns_mhdp_link_training(mhdp, interval_us)) { + dev_err(mhdp->dev, "Link training failed. Exiting.\n"); + return -EIO; + } + + mhdp->link_up = true; + + return 0; +} + +static void cdns_mhdp_link_down(struct cdns_mhdp_device *mhdp) +{ + WARN_ON(!mutex_is_locked(&mhdp->link_mutex)); + + if (mhdp->plugged) + cdns_mhdp_link_power_down(&mhdp->aux, &mhdp->link); + + mhdp->link_up = false; +} + +static struct edid *cdns_mhdp_get_edid(struct cdns_mhdp_device *mhdp, + struct drm_connector *connector) +{ + if (!mhdp->plugged) + return NULL; + + return drm_do_get_edid(connector, cdns_mhdp_get_edid_block, mhdp); +} + +static int cdns_mhdp_get_modes(struct drm_connector *connector) +{ + struct cdns_mhdp_device *mhdp = connector_to_mhdp(connector); + struct edid *edid; + int num_modes; + + if (!mhdp->plugged) + return 0; + + edid = cdns_mhdp_get_edid(mhdp, connector); + if (!edid) { + dev_err(mhdp->dev, "Failed to read EDID\n"); + return 0; + } + + drm_connector_update_edid_property(connector, edid); + num_modes = drm_add_edid_modes(connector, edid); + kfree(edid); + + /* + * HACK: Warn about unsupported display formats until we deal + * with them correctly. + */ + if (connector->display_info.color_formats && + !(connector->display_info.color_formats & + mhdp->display_fmt.color_format)) + dev_warn(mhdp->dev, + "%s: No supported color_format found (0x%08x)\n", + __func__, connector->display_info.color_formats); + + if (connector->display_info.bpc && + connector->display_info.bpc < mhdp->display_fmt.bpc) + dev_warn(mhdp->dev, "%s: Display bpc only %d < %d\n", + __func__, connector->display_info.bpc, + mhdp->display_fmt.bpc); + + return num_modes; +} + +static int cdns_mhdp_connector_detect(struct drm_connector *conn, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct cdns_mhdp_device *mhdp = connector_to_mhdp(conn); + + return cdns_mhdp_detect(mhdp); +} + +static u32 cdns_mhdp_get_bpp(struct cdns_mhdp_display_fmt *fmt) +{ + u32 bpp; + + if (fmt->y_only) + return fmt->bpc; + + switch (fmt->color_format) { + case DRM_COLOR_FORMAT_RGB444: + case DRM_COLOR_FORMAT_YCRCB444: + bpp = fmt->bpc * 3; + break; + case DRM_COLOR_FORMAT_YCRCB422: + bpp = fmt->bpc * 2; + break; + case DRM_COLOR_FORMAT_YCRCB420: + bpp = fmt->bpc * 3 / 2; + break; + default: + bpp = fmt->bpc * 3; + WARN_ON(1); + } + return bpp; +} + +static +bool cdns_mhdp_bandwidth_ok(struct cdns_mhdp_device *mhdp, + const struct drm_display_mode *mode, + unsigned int lanes, unsigned int rate) +{ + u32 max_bw, req_bw, bpp; + + /* + * mode->clock is expressed in kHz. Multiplying by bpp and dividing by 8 + * we get the number of kB/s. DisplayPort applies a 8b-10b encoding, the + * value thus equals the bandwidth in 10kb/s units, which matches the + * units of the rate parameter. + */ + + bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt); + req_bw = mode->clock * bpp / 8; + max_bw = lanes * rate; + if (req_bw > max_bw) { + dev_dbg(mhdp->dev, + "Unsupported Mode: %s, Req BW: %u, Available Max BW:%u\n", + mode->name, req_bw, max_bw); + + return false; + } + + return true; +} + +static +enum drm_mode_status cdns_mhdp_mode_valid(struct drm_connector *conn, + struct drm_display_mode *mode) +{ + struct cdns_mhdp_device *mhdp = connector_to_mhdp(conn); + + mutex_lock(&mhdp->link_mutex); + + if (!cdns_mhdp_bandwidth_ok(mhdp, mode, mhdp->link.num_lanes, + mhdp->link.rate)) { + mutex_unlock(&mhdp->link_mutex); + return MODE_CLOCK_HIGH; + } + + mutex_unlock(&mhdp->link_mutex); + return MODE_OK; +} + +static const struct drm_connector_helper_funcs cdns_mhdp_conn_helper_funcs = { + .detect_ctx = cdns_mhdp_connector_detect, + .get_modes = cdns_mhdp_get_modes, + .mode_valid = cdns_mhdp_mode_valid, +}; + +static const struct drm_connector_funcs cdns_mhdp_conn_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .reset = drm_atomic_helper_connector_reset, + .destroy = drm_connector_cleanup, +}; + +static int cdns_mhdp_connector_init(struct cdns_mhdp_device *mhdp) +{ + u32 bus_format = MEDIA_BUS_FMT_RGB121212_1X36; + struct drm_connector *conn = &mhdp->connector; + struct drm_bridge *bridge = &mhdp->bridge; + int ret; + + if (!bridge->encoder) { + dev_err(mhdp->dev, "Parent encoder object not found"); + return -ENODEV; + } + + conn->polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(bridge->dev, conn, &cdns_mhdp_conn_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + dev_err(mhdp->dev, "Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_helper_add(conn, &cdns_mhdp_conn_helper_funcs); + + ret = drm_display_info_set_bus_formats(&conn->display_info, + &bus_format, 1); + if (ret) + return ret; + + ret = drm_connector_attach_encoder(conn, bridge->encoder); + if (ret) { + dev_err(mhdp->dev, "Failed to attach connector to encoder\n"); + return ret; + } + + return 0; +} + +static int cdns_mhdp_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge); + bool hw_ready; + int ret; + + dev_dbg(mhdp->dev, "%s\n", __func__); + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + ret = cdns_mhdp_connector_init(mhdp); + if (ret) + return ret; + } + + spin_lock(&mhdp->start_lock); + + mhdp->bridge_attached = true; + hw_ready = mhdp->hw_state == MHDP_HW_READY; + + spin_unlock(&mhdp->start_lock); + + /* Enable SW event interrupts */ + if (hw_ready) + writel(~(u32)CDNS_APB_INT_MASK_SW_EVENT_INT, + mhdp->regs + CDNS_APB_INT_MASK); + + return 0; +} + +static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp, + const struct drm_display_mode *mode) +{ + unsigned int dp_framer_sp = 0, msa_horizontal_1, + msa_vertical_1, bnd_hsync2vsync, hsync2vsync_pol_ctrl, + misc0 = 0, misc1 = 0, pxl_repr, + front_porch, back_porch, msa_h0, msa_v0, hsync, vsync, + dp_vertical_1; + u8 stream_id = mhdp->stream_id; + u32 bpp, bpc, pxlfmt, framer; + int ret; + + pxlfmt = mhdp->display_fmt.color_format; + bpc = mhdp->display_fmt.bpc; + + /* + * If YCBCR supported and stream not SD, use ITU709 + * Need to handle ITU version with YCBCR420 when supported + */ + if ((pxlfmt == DRM_COLOR_FORMAT_YCRCB444 || + pxlfmt == DRM_COLOR_FORMAT_YCRCB422) && mode->crtc_vdisplay >= 720) + misc0 = DP_YCBCR_COEFFICIENTS_ITU709; + + bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt); + + switch (pxlfmt) { + case DRM_COLOR_FORMAT_RGB444: + pxl_repr = CDNS_DP_FRAMER_RGB << CDNS_DP_FRAMER_PXL_FORMAT; + misc0 |= DP_COLOR_FORMAT_RGB; + break; + case DRM_COLOR_FORMAT_YCRCB444: + pxl_repr = CDNS_DP_FRAMER_YCBCR444 << CDNS_DP_FRAMER_PXL_FORMAT; + misc0 |= DP_COLOR_FORMAT_YCbCr444 | DP_TEST_DYNAMIC_RANGE_CEA; + break; + case DRM_COLOR_FORMAT_YCRCB422: + pxl_repr = CDNS_DP_FRAMER_YCBCR422 << CDNS_DP_FRAMER_PXL_FORMAT; + misc0 |= DP_COLOR_FORMAT_YCbCr422 | DP_TEST_DYNAMIC_RANGE_CEA; + break; + case DRM_COLOR_FORMAT_YCRCB420: + pxl_repr = CDNS_DP_FRAMER_YCBCR420 << CDNS_DP_FRAMER_PXL_FORMAT; + break; + default: + pxl_repr = CDNS_DP_FRAMER_Y_ONLY << CDNS_DP_FRAMER_PXL_FORMAT; + } + + switch (bpc) { + case 6: + misc0 |= DP_TEST_BIT_DEPTH_6; + pxl_repr |= CDNS_DP_FRAMER_6_BPC; + break; + case 8: + misc0 |= DP_TEST_BIT_DEPTH_8; + pxl_repr |= CDNS_DP_FRAMER_8_BPC; + break; + case 10: + misc0 |= DP_TEST_BIT_DEPTH_10; + pxl_repr |= CDNS_DP_FRAMER_10_BPC; + break; + case 12: + misc0 |= DP_TEST_BIT_DEPTH_12; + pxl_repr |= CDNS_DP_FRAMER_12_BPC; + break; + case 16: + misc0 |= DP_TEST_BIT_DEPTH_16; + pxl_repr |= CDNS_DP_FRAMER_16_BPC; + break; + } + + bnd_hsync2vsync = CDNS_IP_BYPASS_V_INTERFACE; + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + bnd_hsync2vsync |= CDNS_IP_DET_INTERLACE_FORMAT; + + cdns_mhdp_reg_write(mhdp, CDNS_BND_HSYNC2VSYNC(stream_id), + bnd_hsync2vsync); + + hsync2vsync_pol_ctrl = 0; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + hsync2vsync_pol_ctrl |= CDNS_H2V_HSYNC_POL_ACTIVE_LOW; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + hsync2vsync_pol_ctrl |= CDNS_H2V_VSYNC_POL_ACTIVE_LOW; + cdns_mhdp_reg_write(mhdp, CDNS_HSYNC2VSYNC_POL_CTRL(stream_id), + hsync2vsync_pol_ctrl); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_PXL_REPR(stream_id), pxl_repr); + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + dp_framer_sp |= CDNS_DP_FRAMER_INTERLACE; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + dp_framer_sp |= CDNS_DP_FRAMER_HSYNC_POL_LOW; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + dp_framer_sp |= CDNS_DP_FRAMER_VSYNC_POL_LOW; + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_SP(stream_id), dp_framer_sp); + + front_porch = mode->crtc_hsync_start - mode->crtc_hdisplay; + back_porch = mode->crtc_htotal - mode->crtc_hsync_end; + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRONT_BACK_PORCH(stream_id), + CDNS_DP_FRONT_PORCH(front_porch) | + CDNS_DP_BACK_PORCH(back_porch)); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_BYTE_COUNT(stream_id), + mode->crtc_hdisplay * bpp / 8); + + msa_h0 = mode->crtc_htotal - mode->crtc_hsync_start; + cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_0(stream_id), + CDNS_DP_MSAH0_H_TOTAL(mode->crtc_htotal) | + CDNS_DP_MSAH0_HSYNC_START(msa_h0)); + + hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; + msa_horizontal_1 = CDNS_DP_MSAH1_HSYNC_WIDTH(hsync) | + CDNS_DP_MSAH1_HDISP_WIDTH(mode->crtc_hdisplay); + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + msa_horizontal_1 |= CDNS_DP_MSAH1_HSYNC_POL_LOW; + cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_1(stream_id), + msa_horizontal_1); + + msa_v0 = mode->crtc_vtotal - mode->crtc_vsync_start; + cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_0(stream_id), + CDNS_DP_MSAV0_V_TOTAL(mode->crtc_vtotal) | + CDNS_DP_MSAV0_VSYNC_START(msa_v0)); + + vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; + msa_vertical_1 = CDNS_DP_MSAV1_VSYNC_WIDTH(vsync) | + CDNS_DP_MSAV1_VDISP_WIDTH(mode->crtc_vdisplay); + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + msa_vertical_1 |= CDNS_DP_MSAV1_VSYNC_POL_LOW; + cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_1(stream_id), + msa_vertical_1); + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && + mode->crtc_vtotal % 2 == 0) + misc1 = DP_TEST_INTERLACED; + if (mhdp->display_fmt.y_only) + misc1 |= CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY; + /* Use VSC SDP for Y420 */ + if (pxlfmt == DRM_COLOR_FORMAT_YCRCB420) + misc1 = CDNS_DP_TEST_VSC_SDP; + + cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_MISC(stream_id), + misc0 | (misc1 << 8)); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_HORIZONTAL(stream_id), + CDNS_DP_H_HSYNC_WIDTH(hsync) | + CDNS_DP_H_H_TOTAL(mode->crtc_hdisplay)); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_0(stream_id), + CDNS_DP_V0_VHEIGHT(mode->crtc_vdisplay) | + CDNS_DP_V0_VSTART(msa_v0)); + + dp_vertical_1 = CDNS_DP_V1_VTOTAL(mode->crtc_vtotal); + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && + mode->crtc_vtotal % 2 == 0) + dp_vertical_1 |= CDNS_DP_V1_VTOTAL_EVEN; + + cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_1(stream_id), dp_vertical_1); + + cdns_mhdp_reg_write_bit(mhdp, CDNS_DP_VB_ID(stream_id), 2, 1, + (mode->flags & DRM_MODE_FLAG_INTERLACE) ? + CDNS_DP_VB_ID_INTERLACED : 0); + + ret = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &framer); + if (ret < 0) { + dev_err(mhdp->dev, + "Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n", + ret); + return; + } + framer |= CDNS_DP_FRAMER_EN; + framer &= ~CDNS_DP_NO_VIDEO_MODE; + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, framer); +} + +static void cdns_mhdp_sst_enable(struct cdns_mhdp_device *mhdp, + const struct drm_display_mode *mode) +{ + u32 rate, vs, required_bandwidth, available_bandwidth; + s32 line_thresh1, line_thresh2, line_thresh = 0; + int pxlclock = mode->crtc_clock; + u32 tu_size = 64; + u32 bpp; + + /* Get rate in MSymbols per second per lane */ + rate = mhdp->link.rate / 1000; + + bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt); + + required_bandwidth = pxlclock * bpp / 8; + available_bandwidth = mhdp->link.num_lanes * rate; + + vs = tu_size * required_bandwidth / available_bandwidth; + vs /= 1000; + + if (vs == tu_size) + vs = tu_size - 1; + + line_thresh1 = ((vs + 1) << 5) * 8 / bpp; + line_thresh2 = (pxlclock << 5) / 1000 / rate * (vs + 1) - (1 << 5); + line_thresh = line_thresh1 - line_thresh2 / (s32)mhdp->link.num_lanes; + line_thresh = (line_thresh >> 5) + 2; + + mhdp->stream_id = 0; + + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_TU, + CDNS_DP_FRAMER_TU_VS(vs) | + CDNS_DP_FRAMER_TU_SIZE(tu_size) | + CDNS_DP_FRAMER_TU_CNT_RST_EN); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_LINE_THRESH(0), + line_thresh & GENMASK(5, 0)); + + cdns_mhdp_reg_write(mhdp, CDNS_DP_STREAM_CONFIG_2(0), + CDNS_DP_SC2_TU_VS_DIFF((tu_size - vs > 3) ? + 0 : tu_size - vs)); + + cdns_mhdp_configure_video(mhdp, mode); +} + +static void cdns_mhdp_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state) +{ + struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge); + struct drm_atomic_state *state = bridge_state->base.state; + struct cdns_mhdp_bridge_state *mhdp_state; + struct drm_crtc_state *crtc_state; + struct drm_connector *connector; + struct drm_connector_state *conn_state; + struct drm_bridge_state *new_state; + const struct drm_display_mode *mode; + u32 resp; + int ret; + + dev_dbg(mhdp->dev, "bridge enable\n"); + + mutex_lock(&mhdp->link_mutex); + + if (mhdp->plugged && !mhdp->link_up) { + ret = cdns_mhdp_link_up(mhdp); + if (ret < 0) + goto out; + } + + if (mhdp->info && mhdp->info->ops && mhdp->info->ops->enable) + mhdp->info->ops->enable(mhdp); + + /* Enable VIF clock for stream 0 */ + ret = cdns_mhdp_reg_read(mhdp, CDNS_DPTX_CAR, &resp); + if (ret < 0) { + dev_err(mhdp->dev, "Failed to read CDNS_DPTX_CAR %d\n", ret); + goto out; + } + + cdns_mhdp_reg_write(mhdp, CDNS_DPTX_CAR, + resp | CDNS_VIF_CLK_EN | CDNS_VIF_CLK_RSTN); + + connector = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + if (WARN_ON(!connector)) + goto out; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (WARN_ON(!conn_state)) + goto out; + + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + if (WARN_ON(!crtc_state)) + goto out; + + mode = &crtc_state->adjusted_mode; + + new_state = drm_atomic_get_new_bridge_state(state, bridge); + if (WARN_ON(!new_state)) + goto out; + + if (!cdns_mhdp_bandwidth_ok(mhdp, mode, mhdp->link.num_lanes, + mhdp->link.rate)) { + ret = -EINVAL; + goto out; + } + + cdns_mhdp_sst_enable(mhdp, mode); + + mhdp_state = to_cdns_mhdp_bridge_state(new_state); + + mhdp_state->current_mode = drm_mode_duplicate(bridge->dev, mode); + drm_mode_set_name(mhdp_state->current_mode); + + dev_dbg(mhdp->dev, "%s: Enabling mode %s\n", __func__, mode->name); + + mhdp->bridge_enabled = true; + +out: + mutex_unlock(&mhdp->link_mutex); + if (ret < 0) + schedule_work(&mhdp->modeset_retry_work); +} + +static void cdns_mhdp_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state) +{ + struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge); + u32 resp; + + dev_dbg(mhdp->dev, "%s\n", __func__); + + mutex_lock(&mhdp->link_mutex); + + mhdp->bridge_enabled = false; + cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp); + resp &= ~CDNS_DP_FRAMER_EN; + resp |= CDNS_DP_NO_VIDEO_MODE; + cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp); + + cdns_mhdp_link_down(mhdp); + + /* Disable VIF clock for stream 0 */ + cdns_mhdp_reg_read(mhdp, CDNS_DPTX_CAR, &resp); + cdns_mhdp_reg_write(mhdp, CDNS_DPTX_CAR, + resp & ~(CDNS_VIF_CLK_EN | CDNS_VIF_CLK_RSTN)); + + if (mhdp->info && mhdp->info->ops && mhdp->info->ops->disable) + mhdp->info->ops->disable(mhdp); + + mutex_unlock(&mhdp->link_mutex); +} + +static void cdns_mhdp_detach(struct drm_bridge *bridge) +{ + struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge); + + dev_dbg(mhdp->dev, "%s\n", __func__); + + spin_lock(&mhdp->start_lock); + + mhdp->bridge_attached = false; + + spin_unlock(&mhdp->start_lock); + + writel(~0, mhdp->regs + CDNS_APB_INT_MASK); +} + +static struct drm_bridge_state * +cdns_mhdp_bridge_atomic_duplicate_state(struct drm_bridge *bridge) +{ + struct cdns_mhdp_bridge_state *state; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_bridge_duplicate_state(bridge, &state->base); + + return &state->base; +} + +static void +cdns_mhdp_bridge_atomic_destroy_state(struct drm_bridge *bridge, + struct drm_bridge_state *state) +{ + struct cdns_mhdp_bridge_state *cdns_mhdp_state; + + cdns_mhdp_state = to_cdns_mhdp_bridge_state(state); + + if (cdns_mhdp_state->current_mode) { + drm_mode_destroy(bridge->dev, cdns_mhdp_state->current_mode); + cdns_mhdp_state->current_mode = NULL; + } + + kfree(cdns_mhdp_state); +} + +static struct drm_bridge_state * +cdns_mhdp_bridge_atomic_reset(struct drm_bridge *bridge) +{ + struct cdns_mhdp_bridge_state *cdns_mhdp_state; + + cdns_mhdp_state = kzalloc(sizeof(*cdns_mhdp_state), GFP_KERNEL); + if (!cdns_mhdp_state) + return NULL; + + __drm_atomic_helper_bridge_reset(bridge, &cdns_mhdp_state->base); + + return &cdns_mhdp_state->base; +} + +static int cdns_mhdp_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge); + const struct drm_display_mode *mode = &crtc_state->adjusted_mode; + + mutex_lock(&mhdp->link_mutex); + + if (!cdns_mhdp_bandwidth_ok(mhdp, mode, mhdp->link.num_lanes, + mhdp->link.rate)) { + dev_err(mhdp->dev, "%s: Not enough BW for %s (%u lanes at %u Mbps)\n", + __func__, mode->name, mhdp->link.num_lanes, + mhdp->link.rate / 100); + mutex_unlock(&mhdp->link_mutex); + return -EINVAL; + } + + mutex_unlock(&mhdp->link_mutex); + return 0; +} + +static enum drm_connector_status cdns_mhdp_bridge_detect(struct drm_bridge *bridge) +{ + struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge); + + return cdns_mhdp_detect(mhdp); +} + +static struct edid *cdns_mhdp_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge); + + return cdns_mhdp_get_edid(mhdp, connector); +} + +static void cdns_mhdp_bridge_hpd_enable(struct drm_bridge *bridge) +{ + struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge); + + /* Enable SW event interrupts */ + if (mhdp->bridge_attached) + writel(~(u32)CDNS_APB_INT_MASK_SW_EVENT_INT, + mhdp->regs + CDNS_APB_INT_MASK); +} + +static void cdns_mhdp_bridge_hpd_disable(struct drm_bridge *bridge) +{ + struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge); + + writel(CDNS_APB_INT_MASK_SW_EVENT_INT, mhdp->regs + CDNS_APB_INT_MASK); +} + +static const struct drm_bridge_funcs cdns_mhdp_bridge_funcs = { + .atomic_enable = cdns_mhdp_atomic_enable, + .atomic_disable = cdns_mhdp_atomic_disable, + .atomic_check = cdns_mhdp_atomic_check, + .attach = cdns_mhdp_attach, + .detach = cdns_mhdp_detach, + .atomic_duplicate_state = cdns_mhdp_bridge_atomic_duplicate_state, + .atomic_destroy_state = cdns_mhdp_bridge_atomic_destroy_state, + .atomic_reset = cdns_mhdp_bridge_atomic_reset, + .detect = cdns_mhdp_bridge_detect, + .get_edid = cdns_mhdp_bridge_get_edid, + .hpd_enable = cdns_mhdp_bridge_hpd_enable, + .hpd_disable = cdns_mhdp_bridge_hpd_disable, +}; + +static bool cdns_mhdp_detect_hpd(struct cdns_mhdp_device *mhdp, bool *hpd_pulse) +{ + int hpd_event, hpd_status; + + *hpd_pulse = false; + + hpd_event = cdns_mhdp_read_hpd_event(mhdp); + + /* Getting event bits failed, bail out */ + if (hpd_event < 0) { + dev_warn(mhdp->dev, "%s: read event failed: %d\n", + __func__, hpd_event); + return false; + } + + hpd_status = cdns_mhdp_get_hpd_status(mhdp); + if (hpd_status < 0) { + dev_warn(mhdp->dev, "%s: get hpd status failed: %d\n", + __func__, hpd_status); + return false; + } + + if (hpd_event & DPTX_READ_EVENT_HPD_PULSE) + *hpd_pulse = true; + + return !!hpd_status; +} + +static int cdns_mhdp_update_link_status(struct cdns_mhdp_device *mhdp) +{ + struct cdns_mhdp_bridge_state *cdns_bridge_state; + struct drm_display_mode *current_mode; + bool old_plugged = mhdp->plugged; + struct drm_bridge_state *state; + u8 status[DP_LINK_STATUS_SIZE]; + bool hpd_pulse; + int ret = 0; + + mutex_lock(&mhdp->link_mutex); + + mhdp->plugged = cdns_mhdp_detect_hpd(mhdp, &hpd_pulse); + + if (!mhdp->plugged) { + cdns_mhdp_link_down(mhdp); + mhdp->link.rate = mhdp->host.link_rate; + mhdp->link.num_lanes = mhdp->host.lanes_cnt; + goto out; + } + + /* + * If we get a HPD pulse event and we were and still are connected, + * check the link status. If link status is ok, there's nothing to do + * as we don't handle DP interrupts. If link status is bad, continue + * with full link setup. + */ + if (hpd_pulse && old_plugged == mhdp->plugged) { + ret = drm_dp_dpcd_read_link_status(&mhdp->aux, status); + + /* + * If everything looks fine, just return, as we don't handle + * DP IRQs. + */ + if (ret > 0 && + drm_dp_channel_eq_ok(status, mhdp->link.num_lanes) && + drm_dp_clock_recovery_ok(status, mhdp->link.num_lanes)) + goto out; + + /* If link is bad, mark link as down so that we do a new LT */ + mhdp->link_up = false; + } + + if (!mhdp->link_up) { + ret = cdns_mhdp_link_up(mhdp); + if (ret < 0) + goto out; + } + + if (mhdp->bridge_enabled) { + state = drm_priv_to_bridge_state(mhdp->bridge.base.state); + if (!state) { + ret = -EINVAL; + goto out; + } + + cdns_bridge_state = to_cdns_mhdp_bridge_state(state); + if (!cdns_bridge_state) { + ret = -EINVAL; + goto out; + } + + current_mode = cdns_bridge_state->current_mode; + if (!current_mode) { + ret = -EINVAL; + goto out; + } + + if (!cdns_mhdp_bandwidth_ok(mhdp, current_mode, mhdp->link.num_lanes, + mhdp->link.rate)) { + ret = -EINVAL; + goto out; + } + + dev_dbg(mhdp->dev, "%s: Enabling mode %s\n", __func__, + current_mode->name); + + cdns_mhdp_sst_enable(mhdp, current_mode); + } +out: + mutex_unlock(&mhdp->link_mutex); + return ret; +} + +static void cdns_mhdp_modeset_retry_fn(struct work_struct *work) +{ + struct cdns_mhdp_device *mhdp; + struct drm_connector *conn; + + mhdp = container_of(work, typeof(*mhdp), modeset_retry_work); + + conn = &mhdp->connector; + + /* Grab the locks before changing connector property */ + mutex_lock(&conn->dev->mode_config.mutex); + + /* + * Set connector link status to BAD and send a Uevent to notify + * userspace to do a modeset. + */ + drm_connector_set_link_status_property(conn, DRM_MODE_LINK_STATUS_BAD); + mutex_unlock(&conn->dev->mode_config.mutex); + + /* Send Hotplug uevent so userspace can reprobe */ + drm_kms_helper_hotplug_event(mhdp->bridge.dev); +} + +static irqreturn_t cdns_mhdp_irq_handler(int irq, void *data) +{ + struct cdns_mhdp_device *mhdp = data; + u32 apb_stat, sw_ev0; + bool bridge_attached; + int ret; + + apb_stat = readl(mhdp->regs + CDNS_APB_INT_STATUS); + if (!(apb_stat & CDNS_APB_INT_MASK_SW_EVENT_INT)) + return IRQ_NONE; + + sw_ev0 = readl(mhdp->regs + CDNS_SW_EVENT0); + + /* + * Calling drm_kms_helper_hotplug_event() when not attached + * to drm device causes an oops because the drm_bridge->dev + * is NULL. See cdns_mhdp_fw_cb() comments for details about the + * problems related drm_kms_helper_hotplug_event() call. + */ + spin_lock(&mhdp->start_lock); + bridge_attached = mhdp->bridge_attached; + spin_unlock(&mhdp->start_lock); + + if (bridge_attached && (sw_ev0 & CDNS_DPTX_HPD)) { + ret = cdns_mhdp_update_link_status(mhdp); + if (mhdp->connector.dev) { + if (ret < 0) + schedule_work(&mhdp->modeset_retry_work); + else + drm_kms_helper_hotplug_event(mhdp->bridge.dev); + } else { + drm_bridge_hpd_notify(&mhdp->bridge, cdns_mhdp_detect(mhdp)); + } + } + + return IRQ_HANDLED; +} + +static int cdns_mhdp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cdns_mhdp_device *mhdp; + unsigned long rate; + struct clk *clk; + int ret; + int irq; + + mhdp = devm_kzalloc(dev, sizeof(*mhdp), GFP_KERNEL); + if (!mhdp) + return -ENOMEM; + + clk = devm_clk_get(dev, NULL); + if (IS_ERR(clk)) { + dev_err(dev, "couldn't get clk: %ld\n", PTR_ERR(clk)); + return PTR_ERR(clk); + } + + mhdp->clk = clk; + mhdp->dev = dev; + mutex_init(&mhdp->mbox_mutex); + mutex_init(&mhdp->link_mutex); + spin_lock_init(&mhdp->start_lock); + + drm_dp_aux_init(&mhdp->aux); + mhdp->aux.dev = dev; + mhdp->aux.transfer = cdns_mhdp_transfer; + + mhdp->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mhdp->regs)) { + dev_err(dev, "Failed to get memory resource\n"); + return PTR_ERR(mhdp->regs); + } + + mhdp->phy = devm_of_phy_get_by_index(dev, pdev->dev.of_node, 0); + if (IS_ERR(mhdp->phy)) { + dev_err(dev, "no PHY configured\n"); + return PTR_ERR(mhdp->phy); + } + + platform_set_drvdata(pdev, mhdp); + + mhdp->info = of_device_get_match_data(dev); + + clk_prepare_enable(clk); + + pm_runtime_enable(dev); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "pm_runtime_resume_and_get failed\n"); + pm_runtime_disable(dev); + goto clk_disable; + } + + if (mhdp->info && mhdp->info->ops && mhdp->info->ops->init) { + ret = mhdp->info->ops->init(mhdp); + if (ret != 0) { + dev_err(dev, "MHDP platform initialization failed: %d\n", + ret); + goto runtime_put; + } + } + + rate = clk_get_rate(clk); + writel(rate % 1000000, mhdp->regs + CDNS_SW_CLK_L); + writel(rate / 1000000, mhdp->regs + CDNS_SW_CLK_H); + + dev_dbg(dev, "func clk rate %lu Hz\n", rate); + + writel(~0, mhdp->regs + CDNS_APB_INT_MASK); + + irq = platform_get_irq(pdev, 0); + ret = devm_request_threaded_irq(mhdp->dev, irq, NULL, + cdns_mhdp_irq_handler, IRQF_ONESHOT, + "mhdp8546", mhdp); + if (ret) { + dev_err(dev, "cannot install IRQ %d\n", irq); + ret = -EIO; + goto plat_fini; + } + + cdns_mhdp_fill_host_caps(mhdp); + + /* Initialize link rate and num of lanes to host values */ + mhdp->link.rate = mhdp->host.link_rate; + mhdp->link.num_lanes = mhdp->host.lanes_cnt; + + /* The only currently supported format */ + mhdp->display_fmt.y_only = false; + mhdp->display_fmt.color_format = DRM_COLOR_FORMAT_RGB444; + mhdp->display_fmt.bpc = 8; + + mhdp->bridge.of_node = pdev->dev.of_node; + mhdp->bridge.funcs = &cdns_mhdp_bridge_funcs; + mhdp->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD; + mhdp->bridge.type = DRM_MODE_CONNECTOR_DisplayPort; + if (mhdp->info) + mhdp->bridge.timings = mhdp->info->timings; + + ret = phy_init(mhdp->phy); + if (ret) { + dev_err(mhdp->dev, "Failed to initialize PHY: %d\n", ret); + goto plat_fini; + } + + /* Initialize the work for modeset in case of link train failure */ + INIT_WORK(&mhdp->modeset_retry_work, cdns_mhdp_modeset_retry_fn); + + init_waitqueue_head(&mhdp->fw_load_wq); + + ret = cdns_mhdp_load_firmware(mhdp); + if (ret) + goto phy_exit; + + drm_bridge_add(&mhdp->bridge); + + return 0; + +phy_exit: + phy_exit(mhdp->phy); +plat_fini: + if (mhdp->info && mhdp->info->ops && mhdp->info->ops->exit) + mhdp->info->ops->exit(mhdp); +runtime_put: + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); +clk_disable: + clk_disable_unprepare(mhdp->clk); + + return ret; +} + +static int cdns_mhdp_remove(struct platform_device *pdev) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(&pdev->dev); + unsigned long timeout = msecs_to_jiffies(100); + bool stop_fw = false; + int ret; + + drm_bridge_remove(&mhdp->bridge); + + ret = wait_event_timeout(mhdp->fw_load_wq, + mhdp->hw_state == MHDP_HW_READY, + timeout); + if (ret == 0) + dev_err(mhdp->dev, "%s: Timeout waiting for fw loading\n", + __func__); + else + stop_fw = true; + + spin_lock(&mhdp->start_lock); + mhdp->hw_state = MHDP_HW_STOPPED; + spin_unlock(&mhdp->start_lock); + + if (stop_fw) + ret = cdns_mhdp_set_firmware_active(mhdp, false); + + phy_exit(mhdp->phy); + + if (mhdp->info && mhdp->info->ops && mhdp->info->ops->exit) + mhdp->info->ops->exit(mhdp); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + cancel_work_sync(&mhdp->modeset_retry_work); + flush_scheduled_work(); + + clk_disable_unprepare(mhdp->clk); + + return ret; +} + +static const struct of_device_id mhdp_ids[] = { + { .compatible = "cdns,mhdp8546", }, +#ifdef CONFIG_DRM_CDNS_MHDP8546_J721E + { .compatible = "ti,j721e-mhdp8546", + .data = &(const struct cdns_mhdp_platform_info) { + .timings = &mhdp_ti_j721e_bridge_timings, + .ops = &mhdp_ti_j721e_ops, + }, + }, +#endif + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mhdp_ids); + +static struct platform_driver mhdp_driver = { + .driver = { + .name = "cdns-mhdp8546", + .of_match_table = of_match_ptr(mhdp_ids), + }, + .probe = cdns_mhdp_probe, + .remove = cdns_mhdp_remove, +}; +module_platform_driver(mhdp_driver); + +MODULE_FIRMWARE(FW_NAME); + +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>"); +MODULE_AUTHOR("Swapnil Jakhade <sjakhade@cadence.com>"); +MODULE_AUTHOR("Yuti Amonkar <yamonkar@cadence.com>"); +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_AUTHOR("Jyri Sarha <jsarha@ti.com>"); +MODULE_DESCRIPTION("Cadence MHDP8546 DP bridge driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cdns-mhdp8546"); diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h new file mode 100644 index 000000000..5897a85e3 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h @@ -0,0 +1,400 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence MHDP8546 DP bridge driver. + * + * Copyright (C) 2020 Cadence Design Systems, Inc. + * + * Author: Quentin Schulz <quentin.schulz@free-electrons.com> + * Swapnil Jakhade <sjakhade@cadence.com> + */ + +#ifndef CDNS_MHDP8546_CORE_H +#define CDNS_MHDP8546_CORE_H + +#include <linux/bits.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_dp_helper.h> + +struct clk; +struct device; +struct phy; + +/* Register offsets */ +#define CDNS_APB_CTRL 0x00000 +#define CDNS_CPU_STALL BIT(3) + +#define CDNS_MAILBOX_FULL 0x00008 +#define CDNS_MAILBOX_EMPTY 0x0000c +#define CDNS_MAILBOX_TX_DATA 0x00010 +#define CDNS_MAILBOX_RX_DATA 0x00014 +#define CDNS_KEEP_ALIVE 0x00018 +#define CDNS_KEEP_ALIVE_MASK GENMASK(7, 0) + +#define CDNS_VER_L 0x0001C +#define CDNS_VER_H 0x00020 +#define CDNS_LIB_L_ADDR 0x00024 +#define CDNS_LIB_H_ADDR 0x00028 + +#define CDNS_MB_INT_MASK 0x00034 +#define CDNS_MB_INT_STATUS 0x00038 + +#define CDNS_SW_CLK_L 0x0003c +#define CDNS_SW_CLK_H 0x00040 + +#define CDNS_SW_EVENT0 0x00044 +#define CDNS_DPTX_HPD BIT(0) + +#define CDNS_SW_EVENT1 0x00048 +#define CDNS_SW_EVENT2 0x0004c +#define CDNS_SW_EVENT3 0x00050 + +#define CDNS_APB_INT_MASK 0x0006C +#define CDNS_APB_INT_MASK_MAILBOX_INT BIT(0) +#define CDNS_APB_INT_MASK_SW_EVENT_INT BIT(1) + +#define CDNS_APB_INT_STATUS 0x00070 + +#define CDNS_DPTX_CAR 0x00904 +#define CDNS_VIF_CLK_EN BIT(0) +#define CDNS_VIF_CLK_RSTN BIT(1) + +#define CDNS_SOURCE_VIDEO_IF(s) (0x00b00 + ((s) * 0x20)) +#define CDNS_BND_HSYNC2VSYNC(s) (CDNS_SOURCE_VIDEO_IF(s) + \ + 0x00) +#define CDNS_IP_DTCT_WIN GENMASK(11, 0) +#define CDNS_IP_DET_INTERLACE_FORMAT BIT(12) +#define CDNS_IP_BYPASS_V_INTERFACE BIT(13) + +#define CDNS_HSYNC2VSYNC_POL_CTRL(s) (CDNS_SOURCE_VIDEO_IF(s) + \ + 0x10) +#define CDNS_H2V_HSYNC_POL_ACTIVE_LOW BIT(1) +#define CDNS_H2V_VSYNC_POL_ACTIVE_LOW BIT(2) + +#define CDNS_DPTX_PHY_CONFIG 0x02000 +#define CDNS_PHY_TRAINING_EN BIT(0) +#define CDNS_PHY_TRAINING_TYPE(x) (((x) & GENMASK(3, 0)) << 1) +#define CDNS_PHY_SCRAMBLER_BYPASS BIT(5) +#define CDNS_PHY_ENCODER_BYPASS BIT(6) +#define CDNS_PHY_SKEW_BYPASS BIT(7) +#define CDNS_PHY_TRAINING_AUTO BIT(8) +#define CDNS_PHY_LANE0_SKEW(x) (((x) & GENMASK(2, 0)) << 9) +#define CDNS_PHY_LANE1_SKEW(x) (((x) & GENMASK(2, 0)) << 12) +#define CDNS_PHY_LANE2_SKEW(x) (((x) & GENMASK(2, 0)) << 15) +#define CDNS_PHY_LANE3_SKEW(x) (((x) & GENMASK(2, 0)) << 18) +#define CDNS_PHY_COMMON_CONFIG (CDNS_PHY_LANE1_SKEW(1) | \ + CDNS_PHY_LANE2_SKEW(2) | \ + CDNS_PHY_LANE3_SKEW(3)) +#define CDNS_PHY_10BIT_EN BIT(21) + +#define CDNS_DP_FRAMER_GLOBAL_CONFIG 0x02200 +#define CDNS_DP_NUM_LANES(x) ((x) - 1) +#define CDNS_DP_MST_EN BIT(2) +#define CDNS_DP_FRAMER_EN BIT(3) +#define CDNS_DP_RATE_GOVERNOR_EN BIT(4) +#define CDNS_DP_NO_VIDEO_MODE BIT(5) +#define CDNS_DP_DISABLE_PHY_RST BIT(6) +#define CDNS_DP_WR_FAILING_EDGE_VSYNC BIT(7) + +#define CDNS_DP_FRAMER_TU 0x02208 +#define CDNS_DP_FRAMER_TU_SIZE(x) (((x) & GENMASK(6, 0)) << 8) +#define CDNS_DP_FRAMER_TU_VS(x) ((x) & GENMASK(5, 0)) +#define CDNS_DP_FRAMER_TU_CNT_RST_EN BIT(15) + +#define CDNS_DP_MTPH_CONTROL 0x02264 +#define CDNS_DP_MTPH_ECF_EN BIT(0) +#define CDNS_DP_MTPH_ACT_EN BIT(1) +#define CDNS_DP_MTPH_LVP_EN BIT(2) + +#define CDNS_DP_MTPH_STATUS 0x0226C +#define CDNS_DP_MTPH_ACT_STATUS BIT(0) + +#define CDNS_DP_LANE_EN 0x02300 +#define CDNS_DP_LANE_EN_LANES(x) GENMASK((x) - 1, 0) + +#define CDNS_DP_ENHNCD 0x02304 + +#define CDNS_DPTX_STREAM(s) (0x03000 + (s) * 0x80) +#define CDNS_DP_MSA_HORIZONTAL_0(s) (CDNS_DPTX_STREAM(s) + 0x00) +#define CDNS_DP_MSAH0_H_TOTAL(x) (x) +#define CDNS_DP_MSAH0_HSYNC_START(x) ((x) << 16) + +#define CDNS_DP_MSA_HORIZONTAL_1(s) (CDNS_DPTX_STREAM(s) + 0x04) +#define CDNS_DP_MSAH1_HSYNC_WIDTH(x) (x) +#define CDNS_DP_MSAH1_HSYNC_POL_LOW BIT(15) +#define CDNS_DP_MSAH1_HDISP_WIDTH(x) ((x) << 16) + +#define CDNS_DP_MSA_VERTICAL_0(s) (CDNS_DPTX_STREAM(s) + 0x08) +#define CDNS_DP_MSAV0_V_TOTAL(x) (x) +#define CDNS_DP_MSAV0_VSYNC_START(x) ((x) << 16) + +#define CDNS_DP_MSA_VERTICAL_1(s) (CDNS_DPTX_STREAM(s) + 0x0c) +#define CDNS_DP_MSAV1_VSYNC_WIDTH(x) (x) +#define CDNS_DP_MSAV1_VSYNC_POL_LOW BIT(15) +#define CDNS_DP_MSAV1_VDISP_WIDTH(x) ((x) << 16) + +#define CDNS_DP_MSA_MISC(s) (CDNS_DPTX_STREAM(s) + 0x10) +#define CDNS_DP_STREAM_CONFIG(s) (CDNS_DPTX_STREAM(s) + 0x14) +#define CDNS_DP_STREAM_CONFIG_2(s) (CDNS_DPTX_STREAM(s) + 0x2c) +#define CDNS_DP_SC2_TU_VS_DIFF(x) ((x) << 8) + +#define CDNS_DP_HORIZONTAL(s) (CDNS_DPTX_STREAM(s) + 0x30) +#define CDNS_DP_H_HSYNC_WIDTH(x) (x) +#define CDNS_DP_H_H_TOTAL(x) ((x) << 16) + +#define CDNS_DP_VERTICAL_0(s) (CDNS_DPTX_STREAM(s) + 0x34) +#define CDNS_DP_V0_VHEIGHT(x) (x) +#define CDNS_DP_V0_VSTART(x) ((x) << 16) + +#define CDNS_DP_VERTICAL_1(s) (CDNS_DPTX_STREAM(s) + 0x38) +#define CDNS_DP_V1_VTOTAL(x) (x) +#define CDNS_DP_V1_VTOTAL_EVEN BIT(16) + +#define CDNS_DP_MST_SLOT_ALLOCATE(s) (CDNS_DPTX_STREAM(s) + 0x44) +#define CDNS_DP_S_ALLOC_START_SLOT(x) (x) +#define CDNS_DP_S_ALLOC_END_SLOT(x) ((x) << 8) + +#define CDNS_DP_RATE_GOVERNING(s) (CDNS_DPTX_STREAM(s) + 0x48) +#define CDNS_DP_RG_TARG_AV_SLOTS_Y(x) (x) +#define CDNS_DP_RG_TARG_AV_SLOTS_X(x) ((x) << 4) +#define CDNS_DP_RG_ENABLE BIT(10) + +#define CDNS_DP_FRAMER_PXL_REPR(s) (CDNS_DPTX_STREAM(s) + 0x4c) +#define CDNS_DP_FRAMER_6_BPC BIT(0) +#define CDNS_DP_FRAMER_8_BPC BIT(1) +#define CDNS_DP_FRAMER_10_BPC BIT(2) +#define CDNS_DP_FRAMER_12_BPC BIT(3) +#define CDNS_DP_FRAMER_16_BPC BIT(4) +#define CDNS_DP_FRAMER_PXL_FORMAT 0x8 +#define CDNS_DP_FRAMER_RGB BIT(0) +#define CDNS_DP_FRAMER_YCBCR444 BIT(1) +#define CDNS_DP_FRAMER_YCBCR422 BIT(2) +#define CDNS_DP_FRAMER_YCBCR420 BIT(3) +#define CDNS_DP_FRAMER_Y_ONLY BIT(4) + +#define CDNS_DP_FRAMER_SP(s) (CDNS_DPTX_STREAM(s) + 0x50) +#define CDNS_DP_FRAMER_VSYNC_POL_LOW BIT(0) +#define CDNS_DP_FRAMER_HSYNC_POL_LOW BIT(1) +#define CDNS_DP_FRAMER_INTERLACE BIT(2) + +#define CDNS_DP_LINE_THRESH(s) (CDNS_DPTX_STREAM(s) + 0x64) +#define CDNS_DP_ACTIVE_LINE_THRESH(x) (x) + +#define CDNS_DP_VB_ID(s) (CDNS_DPTX_STREAM(s) + 0x68) +#define CDNS_DP_VB_ID_INTERLACED BIT(2) +#define CDNS_DP_VB_ID_COMPRESSED BIT(6) + +#define CDNS_DP_FRONT_BACK_PORCH(s) (CDNS_DPTX_STREAM(s) + 0x78) +#define CDNS_DP_BACK_PORCH(x) (x) +#define CDNS_DP_FRONT_PORCH(x) ((x) << 16) + +#define CDNS_DP_BYTE_COUNT(s) (CDNS_DPTX_STREAM(s) + 0x7c) +#define CDNS_DP_BYTE_COUNT_BYTES_IN_CHUNK_SHIFT 16 + +/* mailbox */ +#define MAILBOX_RETRY_US 1000 +#define MAILBOX_TIMEOUT_US 2000000 + +#define MB_OPCODE_ID 0 +#define MB_MODULE_ID 1 +#define MB_SIZE_MSB_ID 2 +#define MB_SIZE_LSB_ID 3 +#define MB_DATA_ID 4 + +#define MB_MODULE_ID_DP_TX 0x01 +#define MB_MODULE_ID_HDCP_TX 0x07 +#define MB_MODULE_ID_HDCP_RX 0x08 +#define MB_MODULE_ID_HDCP_GENERAL 0x09 +#define MB_MODULE_ID_GENERAL 0x0a + +/* firmware and opcodes */ +#define FW_NAME "cadence/mhdp8546.bin" +#define CDNS_MHDP_IMEM 0x10000 + +#define GENERAL_MAIN_CONTROL 0x01 +#define GENERAL_TEST_ECHO 0x02 +#define GENERAL_BUS_SETTINGS 0x03 +#define GENERAL_TEST_ACCESS 0x04 +#define GENERAL_REGISTER_READ 0x07 + +#define DPTX_SET_POWER_MNG 0x00 +#define DPTX_GET_EDID 0x02 +#define DPTX_READ_DPCD 0x03 +#define DPTX_WRITE_DPCD 0x04 +#define DPTX_ENABLE_EVENT 0x05 +#define DPTX_WRITE_REGISTER 0x06 +#define DPTX_READ_REGISTER 0x07 +#define DPTX_WRITE_FIELD 0x08 +#define DPTX_READ_EVENT 0x0a +#define DPTX_GET_LAST_AUX_STAUS 0x0e +#define DPTX_HPD_STATE 0x11 +#define DPTX_ADJUST_LT 0x12 + +#define FW_STANDBY 0 +#define FW_ACTIVE 1 + +/* HPD */ +#define DPTX_READ_EVENT_HPD_TO_HIGH BIT(0) +#define DPTX_READ_EVENT_HPD_TO_LOW BIT(1) +#define DPTX_READ_EVENT_HPD_PULSE BIT(2) +#define DPTX_READ_EVENT_HPD_STATE BIT(3) + +/* general */ +#define CDNS_DP_TRAINING_PATTERN_4 0x7 + +#define CDNS_KEEP_ALIVE_TIMEOUT 2000 + +#define CDNS_VOLT_SWING(x) ((x) & GENMASK(1, 0)) +#define CDNS_FORCE_VOLT_SWING BIT(2) + +#define CDNS_PRE_EMPHASIS(x) ((x) & GENMASK(1, 0)) +#define CDNS_FORCE_PRE_EMPHASIS BIT(2) + +#define CDNS_SUPPORT_TPS(x) BIT((x) - 1) + +#define CDNS_FAST_LINK_TRAINING BIT(0) + +#define CDNS_LANE_MAPPING_TYPE_C_LANE_0(x) ((x) & GENMASK(1, 0)) +#define CDNS_LANE_MAPPING_TYPE_C_LANE_1(x) ((x) & GENMASK(3, 2)) +#define CDNS_LANE_MAPPING_TYPE_C_LANE_2(x) ((x) & GENMASK(5, 4)) +#define CDNS_LANE_MAPPING_TYPE_C_LANE_3(x) ((x) & GENMASK(7, 6)) +#define CDNS_LANE_MAPPING_NORMAL 0xe4 +#define CDNS_LANE_MAPPING_FLIPPED 0x1b + +#define CDNS_DP_MAX_NUM_LANES 4 +#define CDNS_DP_TEST_VSC_SDP BIT(6) /* 1.3+ */ +#define CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY BIT(7) + +#define CDNS_MHDP_MAX_STREAMS 4 + +#define DP_LINK_CAP_ENHANCED_FRAMING BIT(0) + +struct cdns_mhdp_link { + unsigned char revision; + unsigned int rate; + unsigned int num_lanes; + unsigned long capabilities; +}; + +struct cdns_mhdp_host { + unsigned int link_rate; + u8 lanes_cnt; + u8 volt_swing; + u8 pre_emphasis; + u8 pattern_supp; + u8 lane_mapping; + bool fast_link; + bool enhanced; + bool scrambler; + bool ssc; +}; + +struct cdns_mhdp_sink { + unsigned int link_rate; + u8 lanes_cnt; + u8 pattern_supp; + bool fast_link; + bool enhanced; + bool ssc; +}; + +struct cdns_mhdp_display_fmt { + u32 color_format; + u32 bpc; + bool y_only; +}; + +/* + * These enums present MHDP hw initialization state + * Legal state transitions are: + * MHDP_HW_READY <-> MHDP_HW_STOPPED + */ +enum mhdp_hw_state { + MHDP_HW_READY = 1, /* HW ready, FW active */ + MHDP_HW_STOPPED /* Driver removal FW to be stopped */ +}; + +struct cdns_mhdp_device; + +struct mhdp_platform_ops { + int (*init)(struct cdns_mhdp_device *mhdp); + void (*exit)(struct cdns_mhdp_device *mhdp); + void (*enable)(struct cdns_mhdp_device *mhdp); + void (*disable)(struct cdns_mhdp_device *mhdp); +}; + +struct cdns_mhdp_bridge_state { + struct drm_bridge_state base; + struct drm_display_mode *current_mode; +}; + +struct cdns_mhdp_platform_info { + const struct drm_bridge_timings *timings; + const struct mhdp_platform_ops *ops; +}; + +#define to_cdns_mhdp_bridge_state(s) \ + container_of(s, struct cdns_mhdp_bridge_state, base) + +struct cdns_mhdp_device { + void __iomem *regs; + void __iomem *j721e_regs; + + struct device *dev; + struct clk *clk; + struct phy *phy; + + const struct cdns_mhdp_platform_info *info; + + /* This is to protect mailbox communications with the firmware */ + struct mutex mbox_mutex; + + /* + * "link_mutex" protects the access to all the link parameters + * including the link training process. Link training will be + * invoked both from threaded interrupt handler and from atomic + * callbacks when link_up is not set. So this mutex protects + * flags such as link_up, bridge_enabled, link.num_lanes, + * link.rate etc. + */ + struct mutex link_mutex; + + struct drm_connector connector; + struct drm_bridge bridge; + + struct cdns_mhdp_link link; + struct drm_dp_aux aux; + + struct cdns_mhdp_host host; + struct cdns_mhdp_sink sink; + struct cdns_mhdp_display_fmt display_fmt; + u8 stream_id; + + bool link_up; + bool plugged; + + /* + * "start_lock" protects the access to bridge_attached and + * hw_state data members that control the delayed firmware + * loading and attaching the bridge. They are accessed from + * both the DRM core and cdns_mhdp_fw_cb(). In most cases just + * protecting the data members is enough, but the irq mask + * setting needs to be protected when enabling the FW. + */ + spinlock_t start_lock; + bool bridge_attached; + bool bridge_enabled; + enum mhdp_hw_state hw_state; + wait_queue_head_t fw_load_wq; + + /* Work struct to schedule a uevent on link train failure */ + struct work_struct modeset_retry_work; +}; + +#define connector_to_mhdp(x) container_of(x, struct cdns_mhdp_device, connector) +#define bridge_to_mhdp(x) container_of(x, struct cdns_mhdp_device, bridge) + +#endif diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-j721e.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-j721e.c new file mode 100644 index 000000000..dfe1b5951 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-j721e.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TI j721e Cadence MHDP8546 DP wrapper + * + * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Jyri Sarha <jsarha@ti.com> + */ + +#include <linux/io.h> +#include <linux/platform_device.h> + +#include "cdns-mhdp8546-j721e.h" + +#define REVISION 0x00 +#define DPTX_IPCFG 0x04 +#define ECC_MEM_CFG 0x08 +#define DPTX_DSC_CFG 0x0c +#define DPTX_SRC_CFG 0x10 +#define DPTX_VIF_SECURE_MODE_CFG 0x14 +#define DPTX_VIF_CONN_STATUS 0x18 +#define PHY_CLK_STATUS 0x1c + +#define DPTX_SRC_AIF_EN BIT(16) +#define DPTX_SRC_VIF_3_IN30B BIT(11) +#define DPTX_SRC_VIF_2_IN30B BIT(10) +#define DPTX_SRC_VIF_1_IN30B BIT(9) +#define DPTX_SRC_VIF_0_IN30B BIT(8) +#define DPTX_SRC_VIF_3_SEL_DPI5 BIT(7) +#define DPTX_SRC_VIF_3_SEL_DPI3 0 +#define DPTX_SRC_VIF_2_SEL_DPI4 BIT(6) +#define DPTX_SRC_VIF_2_SEL_DPI2 0 +#define DPTX_SRC_VIF_1_SEL_DPI3 BIT(5) +#define DPTX_SRC_VIF_1_SEL_DPI1 0 +#define DPTX_SRC_VIF_0_SEL_DPI2 BIT(4) +#define DPTX_SRC_VIF_0_SEL_DPI0 0 +#define DPTX_SRC_VIF_3_EN BIT(3) +#define DPTX_SRC_VIF_2_EN BIT(2) +#define DPTX_SRC_VIF_1_EN BIT(1) +#define DPTX_SRC_VIF_0_EN BIT(0) + +/* TODO turn DPTX_IPCFG fw_mem_clk_en at pm_runtime_suspend. */ + +static int cdns_mhdp_j721e_init(struct cdns_mhdp_device *mhdp) +{ + struct platform_device *pdev = to_platform_device(mhdp->dev); + + mhdp->j721e_regs = devm_platform_ioremap_resource(pdev, 1); + return PTR_ERR_OR_ZERO(mhdp->j721e_regs); +} + +static void cdns_mhdp_j721e_enable(struct cdns_mhdp_device *mhdp) +{ + /* + * Enable VIF_0 and select DPI2 as its input. DSS0 DPI0 is connected + * to eDP DPI2. This is the only supported SST configuration on + * J721E. + */ + writel(DPTX_SRC_VIF_0_EN | DPTX_SRC_VIF_0_SEL_DPI2, + mhdp->j721e_regs + DPTX_SRC_CFG); +} + +static void cdns_mhdp_j721e_disable(struct cdns_mhdp_device *mhdp) +{ + /* Put everything to defaults */ + writel(0, mhdp->j721e_regs + DPTX_DSC_CFG); +} + +const struct mhdp_platform_ops mhdp_ti_j721e_ops = { + .init = cdns_mhdp_j721e_init, + .enable = cdns_mhdp_j721e_enable, + .disable = cdns_mhdp_j721e_disable, +}; + +const struct drm_bridge_timings mhdp_ti_j721e_bridge_timings = { + .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_DE_HIGH, +}; diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-j721e.h b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-j721e.h new file mode 100644 index 000000000..97d20d115 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-j721e.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * TI j721e Cadence MHDP8546 DP wrapper + * + * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Jyri Sarha <jsarha@ti.com> + */ + +#ifndef CDNS_MHDP8546_J721E_H +#define CDNS_MHDP8546_J721E_H + +#include "cdns-mhdp8546-core.h" + +struct mhdp_platform_ops; + +extern const struct mhdp_platform_ops mhdp_ti_j721e_ops; +extern const struct drm_bridge_timings mhdp_ti_j721e_bridge_timings; + +#endif /* !CDNS_MHDP8546_J721E_H */ diff --git a/drivers/gpu/drm/bridge/cdns-dsi.c b/drivers/gpu/drm/bridge/cdns-dsi.c new file mode 100644 index 000000000..0ced08d81 --- /dev/null +++ b/drivers/gpu/drm/bridge/cdns-dsi.c @@ -0,0 +1,1306 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright: 2017 Cadence Design Systems, Inc. + * + * Author: Boris Brezillon <boris.brezillon@bootlin.com> + */ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_drv.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> +#include <video/mipi_display.h> + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> + +#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h> + +#define IP_CONF 0x0 +#define SP_HS_FIFO_DEPTH(x) (((x) & GENMASK(30, 26)) >> 26) +#define SP_LP_FIFO_DEPTH(x) (((x) & GENMASK(25, 21)) >> 21) +#define VRS_FIFO_DEPTH(x) (((x) & GENMASK(20, 16)) >> 16) +#define DIRCMD_FIFO_DEPTH(x) (((x) & GENMASK(15, 13)) >> 13) +#define SDI_IFACE_32 BIT(12) +#define INTERNAL_DATAPATH_32 (0 << 10) +#define INTERNAL_DATAPATH_16 (1 << 10) +#define INTERNAL_DATAPATH_8 (3 << 10) +#define INTERNAL_DATAPATH_SIZE ((x) & GENMASK(11, 10)) +#define NUM_IFACE(x) ((((x) & GENMASK(9, 8)) >> 8) + 1) +#define MAX_LANE_NB(x) (((x) & GENMASK(7, 6)) >> 6) +#define RX_FIFO_DEPTH(x) ((x) & GENMASK(5, 0)) + +#define MCTL_MAIN_DATA_CTL 0x4 +#define TE_MIPI_POLLING_EN BIT(25) +#define TE_HW_POLLING_EN BIT(24) +#define DISP_EOT_GEN BIT(18) +#define HOST_EOT_GEN BIT(17) +#define DISP_GEN_CHECKSUM BIT(16) +#define DISP_GEN_ECC BIT(15) +#define BTA_EN BIT(14) +#define READ_EN BIT(13) +#define REG_TE_EN BIT(12) +#define IF_TE_EN(x) BIT(8 + (x)) +#define TVG_SEL BIT(6) +#define VID_EN BIT(5) +#define IF_VID_SELECT(x) ((x) << 2) +#define IF_VID_SELECT_MASK GENMASK(3, 2) +#define IF_VID_MODE BIT(1) +#define LINK_EN BIT(0) + +#define MCTL_MAIN_PHY_CTL 0x8 +#define HS_INVERT_DAT(x) BIT(19 + ((x) * 2)) +#define SWAP_PINS_DAT(x) BIT(18 + ((x) * 2)) +#define HS_INVERT_CLK BIT(17) +#define SWAP_PINS_CLK BIT(16) +#define HS_SKEWCAL_EN BIT(15) +#define WAIT_BURST_TIME(x) ((x) << 10) +#define DATA_ULPM_EN(x) BIT(6 + (x)) +#define CLK_ULPM_EN BIT(5) +#define CLK_CONTINUOUS BIT(4) +#define DATA_LANE_EN(x) BIT((x) - 1) + +#define MCTL_MAIN_EN 0xc +#define DATA_FORCE_STOP BIT(17) +#define CLK_FORCE_STOP BIT(16) +#define IF_EN(x) BIT(13 + (x)) +#define DATA_LANE_ULPM_REQ(l) BIT(9 + (l)) +#define CLK_LANE_ULPM_REQ BIT(8) +#define DATA_LANE_START(x) BIT(4 + (x)) +#define CLK_LANE_EN BIT(3) +#define PLL_START BIT(0) + +#define MCTL_DPHY_CFG0 0x10 +#define DPHY_C_RSTB BIT(20) +#define DPHY_D_RSTB(x) GENMASK(15 + (x), 16) +#define DPHY_PLL_PDN BIT(10) +#define DPHY_CMN_PDN BIT(9) +#define DPHY_C_PDN BIT(8) +#define DPHY_D_PDN(x) GENMASK(3 + (x), 4) +#define DPHY_ALL_D_PDN GENMASK(7, 4) +#define DPHY_PLL_PSO BIT(1) +#define DPHY_CMN_PSO BIT(0) + +#define MCTL_DPHY_TIMEOUT1 0x14 +#define HSTX_TIMEOUT(x) ((x) << 4) +#define HSTX_TIMEOUT_MAX GENMASK(17, 0) +#define CLK_DIV(x) (x) +#define CLK_DIV_MAX GENMASK(3, 0) + +#define MCTL_DPHY_TIMEOUT2 0x18 +#define LPRX_TIMEOUT(x) (x) + +#define MCTL_ULPOUT_TIME 0x1c +#define DATA_LANE_ULPOUT_TIME(x) ((x) << 9) +#define CLK_LANE_ULPOUT_TIME(x) (x) + +#define MCTL_3DVIDEO_CTL 0x20 +#define VID_VSYNC_3D_EN BIT(7) +#define VID_VSYNC_3D_LR BIT(5) +#define VID_VSYNC_3D_SECOND_EN BIT(4) +#define VID_VSYNC_3DFORMAT_LINE (0 << 2) +#define VID_VSYNC_3DFORMAT_FRAME (1 << 2) +#define VID_VSYNC_3DFORMAT_PIXEL (2 << 2) +#define VID_VSYNC_3DMODE_OFF 0 +#define VID_VSYNC_3DMODE_PORTRAIT 1 +#define VID_VSYNC_3DMODE_LANDSCAPE 2 + +#define MCTL_MAIN_STS 0x24 +#define MCTL_MAIN_STS_CTL 0x130 +#define MCTL_MAIN_STS_CLR 0x150 +#define MCTL_MAIN_STS_FLAG 0x170 +#define HS_SKEWCAL_DONE BIT(11) +#define IF_UNTERM_PKT_ERR(x) BIT(8 + (x)) +#define LPRX_TIMEOUT_ERR BIT(7) +#define HSTX_TIMEOUT_ERR BIT(6) +#define DATA_LANE_RDY(l) BIT(2 + (l)) +#define CLK_LANE_RDY BIT(1) +#define PLL_LOCKED BIT(0) + +#define MCTL_DPHY_ERR 0x28 +#define MCTL_DPHY_ERR_CTL1 0x148 +#define MCTL_DPHY_ERR_CLR 0x168 +#define MCTL_DPHY_ERR_FLAG 0x188 +#define ERR_CONT_LP(x, l) BIT(18 + ((x) * 4) + (l)) +#define ERR_CONTROL(l) BIT(14 + (l)) +#define ERR_SYNESC(l) BIT(10 + (l)) +#define ERR_ESC(l) BIT(6 + (l)) + +#define MCTL_DPHY_ERR_CTL2 0x14c +#define ERR_CONT_LP_EDGE(x, l) BIT(12 + ((x) * 4) + (l)) +#define ERR_CONTROL_EDGE(l) BIT(8 + (l)) +#define ERR_SYN_ESC_EDGE(l) BIT(4 + (l)) +#define ERR_ESC_EDGE(l) BIT(0 + (l)) + +#define MCTL_LANE_STS 0x2c +#define PPI_C_TX_READY_HS BIT(18) +#define DPHY_PLL_LOCK BIT(17) +#define PPI_D_RX_ULPS_ESC(x) (((x) & GENMASK(15, 12)) >> 12) +#define LANE_STATE_START 0 +#define LANE_STATE_IDLE 1 +#define LANE_STATE_WRITE 2 +#define LANE_STATE_ULPM 3 +#define LANE_STATE_READ 4 +#define DATA_LANE_STATE(l, val) \ + (((val) >> (2 + 2 * (l) + ((l) ? 1 : 0))) & GENMASK((l) ? 1 : 2, 0)) +#define CLK_LANE_STATE_HS 2 +#define CLK_LANE_STATE(val) ((val) & GENMASK(1, 0)) + +#define DSC_MODE_CTL 0x30 +#define DSC_MODE_EN BIT(0) + +#define DSC_CMD_SEND 0x34 +#define DSC_SEND_PPS BIT(0) +#define DSC_EXECUTE_QUEUE BIT(1) + +#define DSC_PPS_WRDAT 0x38 + +#define DSC_MODE_STS 0x3c +#define DSC_PPS_DONE BIT(1) +#define DSC_EXEC_DONE BIT(2) + +#define CMD_MODE_CTL 0x70 +#define IF_LP_EN(x) BIT(9 + (x)) +#define IF_VCHAN_ID(x, c) ((c) << ((x) * 2)) + +#define CMD_MODE_CTL2 0x74 +#define TE_TIMEOUT(x) ((x) << 11) +#define FILL_VALUE(x) ((x) << 3) +#define ARB_IF_WITH_HIGHEST_PRIORITY(x) ((x) << 1) +#define ARB_ROUND_ROBIN_MODE BIT(0) + +#define CMD_MODE_STS 0x78 +#define CMD_MODE_STS_CTL 0x134 +#define CMD_MODE_STS_CLR 0x154 +#define CMD_MODE_STS_FLAG 0x174 +#define ERR_IF_UNDERRUN(x) BIT(4 + (x)) +#define ERR_UNWANTED_READ BIT(3) +#define ERR_TE_MISS BIT(2) +#define ERR_NO_TE BIT(1) +#define CSM_RUNNING BIT(0) + +#define DIRECT_CMD_SEND 0x80 + +#define DIRECT_CMD_MAIN_SETTINGS 0x84 +#define TRIGGER_VAL(x) ((x) << 25) +#define CMD_LP_EN BIT(24) +#define CMD_SIZE(x) ((x) << 16) +#define CMD_VCHAN_ID(x) ((x) << 14) +#define CMD_DATATYPE(x) ((x) << 8) +#define CMD_LONG BIT(3) +#define WRITE_CMD 0 +#define READ_CMD 1 +#define TE_REQ 4 +#define TRIGGER_REQ 5 +#define BTA_REQ 6 + +#define DIRECT_CMD_STS 0x88 +#define DIRECT_CMD_STS_CTL 0x138 +#define DIRECT_CMD_STS_CLR 0x158 +#define DIRECT_CMD_STS_FLAG 0x178 +#define RCVD_ACK_VAL(val) ((val) >> 16) +#define RCVD_TRIGGER_VAL(val) (((val) & GENMASK(14, 11)) >> 11) +#define READ_COMPLETED_WITH_ERR BIT(10) +#define BTA_FINISHED BIT(9) +#define BTA_COMPLETED BIT(8) +#define TE_RCVD BIT(7) +#define TRIGGER_RCVD BIT(6) +#define ACK_WITH_ERR_RCVD BIT(5) +#define ACK_RCVD BIT(4) +#define READ_COMPLETED BIT(3) +#define TRIGGER_COMPLETED BIT(2) +#define WRITE_COMPLETED BIT(1) +#define SENDING_CMD BIT(0) + +#define DIRECT_CMD_STOP_READ 0x8c + +#define DIRECT_CMD_WRDATA 0x90 + +#define DIRECT_CMD_FIFO_RST 0x94 + +#define DIRECT_CMD_RDDATA 0xa0 + +#define DIRECT_CMD_RD_PROPS 0xa4 +#define RD_DCS BIT(18) +#define RD_VCHAN_ID(val) (((val) >> 16) & GENMASK(1, 0)) +#define RD_SIZE(val) ((val) & GENMASK(15, 0)) + +#define DIRECT_CMD_RD_STS 0xa8 +#define DIRECT_CMD_RD_STS_CTL 0x13c +#define DIRECT_CMD_RD_STS_CLR 0x15c +#define DIRECT_CMD_RD_STS_FLAG 0x17c +#define ERR_EOT_WITH_ERR BIT(8) +#define ERR_MISSING_EOT BIT(7) +#define ERR_WRONG_LENGTH BIT(6) +#define ERR_OVERSIZE BIT(5) +#define ERR_RECEIVE BIT(4) +#define ERR_UNDECODABLE BIT(3) +#define ERR_CHECKSUM BIT(2) +#define ERR_UNCORRECTABLE BIT(1) +#define ERR_FIXED BIT(0) + +#define VID_MAIN_CTL 0xb0 +#define VID_IGNORE_MISS_VSYNC BIT(31) +#define VID_FIELD_SW BIT(28) +#define VID_INTERLACED_EN BIT(27) +#define RECOVERY_MODE(x) ((x) << 25) +#define RECOVERY_MODE_NEXT_HSYNC 0 +#define RECOVERY_MODE_NEXT_STOP_POINT 2 +#define RECOVERY_MODE_NEXT_VSYNC 3 +#define REG_BLKEOL_MODE(x) ((x) << 23) +#define REG_BLKLINE_MODE(x) ((x) << 21) +#define REG_BLK_MODE_NULL_PKT 0 +#define REG_BLK_MODE_BLANKING_PKT 1 +#define REG_BLK_MODE_LP 2 +#define SYNC_PULSE_HORIZONTAL BIT(20) +#define SYNC_PULSE_ACTIVE BIT(19) +#define BURST_MODE BIT(18) +#define VID_PIXEL_MODE_MASK GENMASK(17, 14) +#define VID_PIXEL_MODE_RGB565 (0 << 14) +#define VID_PIXEL_MODE_RGB666_PACKED (1 << 14) +#define VID_PIXEL_MODE_RGB666 (2 << 14) +#define VID_PIXEL_MODE_RGB888 (3 << 14) +#define VID_PIXEL_MODE_RGB101010 (4 << 14) +#define VID_PIXEL_MODE_RGB121212 (5 << 14) +#define VID_PIXEL_MODE_YUV420 (8 << 14) +#define VID_PIXEL_MODE_YUV422_PACKED (9 << 14) +#define VID_PIXEL_MODE_YUV422 (10 << 14) +#define VID_PIXEL_MODE_YUV422_24B (11 << 14) +#define VID_PIXEL_MODE_DSC_COMP (12 << 14) +#define VID_DATATYPE(x) ((x) << 8) +#define VID_VIRTCHAN_ID(iface, x) ((x) << (4 + (iface) * 2)) +#define STOP_MODE(x) ((x) << 2) +#define START_MODE(x) (x) + +#define VID_VSIZE1 0xb4 +#define VFP_LEN(x) ((x) << 12) +#define VBP_LEN(x) ((x) << 6) +#define VSA_LEN(x) (x) + +#define VID_VSIZE2 0xb8 +#define VACT_LEN(x) (x) + +#define VID_HSIZE1 0xc0 +#define HBP_LEN(x) ((x) << 16) +#define HSA_LEN(x) (x) + +#define VID_HSIZE2 0xc4 +#define HFP_LEN(x) ((x) << 16) +#define HACT_LEN(x) (x) + +#define VID_BLKSIZE1 0xcc +#define BLK_EOL_PKT_LEN(x) ((x) << 15) +#define BLK_LINE_EVENT_PKT_LEN(x) (x) + +#define VID_BLKSIZE2 0xd0 +#define BLK_LINE_PULSE_PKT_LEN(x) (x) + +#define VID_PKT_TIME 0xd8 +#define BLK_EOL_DURATION(x) (x) + +#define VID_DPHY_TIME 0xdc +#define REG_WAKEUP_TIME(x) ((x) << 17) +#define REG_LINE_DURATION(x) (x) + +#define VID_ERR_COLOR1 0xe0 +#define COL_GREEN(x) ((x) << 12) +#define COL_RED(x) (x) + +#define VID_ERR_COLOR2 0xe4 +#define PAD_VAL(x) ((x) << 12) +#define COL_BLUE(x) (x) + +#define VID_VPOS 0xe8 +#define LINE_VAL(val) (((val) & GENMASK(14, 2)) >> 2) +#define LINE_POS(val) ((val) & GENMASK(1, 0)) + +#define VID_HPOS 0xec +#define HORIZ_VAL(val) (((val) & GENMASK(17, 3)) >> 3) +#define HORIZ_POS(val) ((val) & GENMASK(2, 0)) + +#define VID_MODE_STS 0xf0 +#define VID_MODE_STS_CTL 0x140 +#define VID_MODE_STS_CLR 0x160 +#define VID_MODE_STS_FLAG 0x180 +#define VSG_RECOVERY BIT(10) +#define ERR_VRS_WRONG_LEN BIT(9) +#define ERR_LONG_READ BIT(8) +#define ERR_LINE_WRITE BIT(7) +#define ERR_BURST_WRITE BIT(6) +#define ERR_SMALL_HEIGHT BIT(5) +#define ERR_SMALL_LEN BIT(4) +#define ERR_MISSING_VSYNC BIT(3) +#define ERR_MISSING_HSYNC BIT(2) +#define ERR_MISSING_DATA BIT(1) +#define VSG_RUNNING BIT(0) + +#define VID_VCA_SETTING1 0xf4 +#define BURST_LP BIT(16) +#define MAX_BURST_LIMIT(x) (x) + +#define VID_VCA_SETTING2 0xf8 +#define MAX_LINE_LIMIT(x) ((x) << 16) +#define EXACT_BURST_LIMIT(x) (x) + +#define TVG_CTL 0xfc +#define TVG_STRIPE_SIZE(x) ((x) << 5) +#define TVG_MODE_MASK GENMASK(4, 3) +#define TVG_MODE_SINGLE_COLOR (0 << 3) +#define TVG_MODE_VSTRIPES (2 << 3) +#define TVG_MODE_HSTRIPES (3 << 3) +#define TVG_STOPMODE_MASK GENMASK(2, 1) +#define TVG_STOPMODE_EOF (0 << 1) +#define TVG_STOPMODE_EOL (1 << 1) +#define TVG_STOPMODE_NOW (2 << 1) +#define TVG_RUN BIT(0) + +#define TVG_IMG_SIZE 0x100 +#define TVG_NBLINES(x) ((x) << 16) +#define TVG_LINE_SIZE(x) (x) + +#define TVG_COLOR1 0x104 +#define TVG_COL1_GREEN(x) ((x) << 12) +#define TVG_COL1_RED(x) (x) + +#define TVG_COLOR1_BIS 0x108 +#define TVG_COL1_BLUE(x) (x) + +#define TVG_COLOR2 0x10c +#define TVG_COL2_GREEN(x) ((x) << 12) +#define TVG_COL2_RED(x) (x) + +#define TVG_COLOR2_BIS 0x110 +#define TVG_COL2_BLUE(x) (x) + +#define TVG_STS 0x114 +#define TVG_STS_CTL 0x144 +#define TVG_STS_CLR 0x164 +#define TVG_STS_FLAG 0x184 +#define TVG_STS_RUNNING BIT(0) + +#define STS_CTL_EDGE(e) ((e) << 16) + +#define DPHY_LANES_MAP 0x198 +#define DAT_REMAP_CFG(b, l) ((l) << ((b) * 8)) + +#define DPI_IRQ_EN 0x1a0 +#define DPI_IRQ_CLR 0x1a4 +#define DPI_IRQ_STS 0x1a8 +#define PIXEL_BUF_OVERFLOW BIT(0) + +#define DPI_CFG 0x1ac +#define DPI_CFG_FIFO_DEPTH(x) ((x) >> 16) +#define DPI_CFG_FIFO_LEVEL(x) ((x) & GENMASK(15, 0)) + +#define TEST_GENERIC 0x1f0 +#define TEST_STATUS(x) ((x) >> 16) +#define TEST_CTRL(x) (x) + +#define ID_REG 0x1fc +#define REV_VENDOR_ID(x) (((x) & GENMASK(31, 20)) >> 20) +#define REV_PRODUCT_ID(x) (((x) & GENMASK(19, 12)) >> 12) +#define REV_HW(x) (((x) & GENMASK(11, 8)) >> 8) +#define REV_MAJOR(x) (((x) & GENMASK(7, 4)) >> 4) +#define REV_MINOR(x) ((x) & GENMASK(3, 0)) + +#define DSI_OUTPUT_PORT 0 +#define DSI_INPUT_PORT(inputid) (1 + (inputid)) + +#define DSI_HBP_FRAME_OVERHEAD 12 +#define DSI_HSA_FRAME_OVERHEAD 14 +#define DSI_HFP_FRAME_OVERHEAD 6 +#define DSI_HSS_VSS_VSE_FRAME_OVERHEAD 4 +#define DSI_BLANKING_FRAME_OVERHEAD 6 +#define DSI_NULL_FRAME_OVERHEAD 6 +#define DSI_EOT_PKT_SIZE 4 + +struct cdns_dsi_output { + struct mipi_dsi_device *dev; + struct drm_panel *panel; + struct drm_bridge *bridge; + union phy_configure_opts phy_opts; +}; + +enum cdns_dsi_input_id { + CDNS_SDI_INPUT, + CDNS_DPI_INPUT, + CDNS_DSC_INPUT, +}; + +struct cdns_dsi_cfg { + unsigned int hfp; + unsigned int hsa; + unsigned int hbp; + unsigned int hact; + unsigned int htotal; +}; + +struct cdns_dsi_input { + enum cdns_dsi_input_id id; + struct drm_bridge bridge; +}; + +struct cdns_dsi { + struct mipi_dsi_host base; + void __iomem *regs; + struct cdns_dsi_input input; + struct cdns_dsi_output output; + unsigned int direct_cmd_fifo_depth; + unsigned int rx_fifo_depth; + struct completion direct_cmd_comp; + struct clk *dsi_p_clk; + struct reset_control *dsi_p_rst; + struct clk *dsi_sys_clk; + bool link_initialized; + struct phy *dphy; +}; + +static inline struct cdns_dsi *input_to_dsi(struct cdns_dsi_input *input) +{ + return container_of(input, struct cdns_dsi, input); +} + +static inline struct cdns_dsi *to_cdns_dsi(struct mipi_dsi_host *host) +{ + return container_of(host, struct cdns_dsi, base); +} + +static inline struct cdns_dsi_input * +bridge_to_cdns_dsi_input(struct drm_bridge *bridge) +{ + return container_of(bridge, struct cdns_dsi_input, bridge); +} + +static unsigned int mode_to_dpi_hfp(const struct drm_display_mode *mode, + bool mode_valid_check) +{ + if (mode_valid_check) + return mode->hsync_start - mode->hdisplay; + + return mode->crtc_hsync_start - mode->crtc_hdisplay; +} + +static unsigned int dpi_to_dsi_timing(unsigned int dpi_timing, + unsigned int dpi_bpp, + unsigned int dsi_pkt_overhead) +{ + unsigned int dsi_timing = DIV_ROUND_UP(dpi_timing * dpi_bpp, 8); + + if (dsi_timing < dsi_pkt_overhead) + dsi_timing = 0; + else + dsi_timing -= dsi_pkt_overhead; + + return dsi_timing; +} + +static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi, + const struct drm_display_mode *mode, + struct cdns_dsi_cfg *dsi_cfg, + bool mode_valid_check) +{ + struct cdns_dsi_output *output = &dsi->output; + unsigned int tmp; + bool sync_pulse = false; + int bpp; + + memset(dsi_cfg, 0, sizeof(*dsi_cfg)); + + if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + sync_pulse = true; + + bpp = mipi_dsi_pixel_format_to_bpp(output->dev->format); + + if (mode_valid_check) + tmp = mode->htotal - + (sync_pulse ? mode->hsync_end : mode->hsync_start); + else + tmp = mode->crtc_htotal - + (sync_pulse ? + mode->crtc_hsync_end : mode->crtc_hsync_start); + + dsi_cfg->hbp = dpi_to_dsi_timing(tmp, bpp, DSI_HBP_FRAME_OVERHEAD); + + if (sync_pulse) { + if (mode_valid_check) + tmp = mode->hsync_end - mode->hsync_start; + else + tmp = mode->crtc_hsync_end - mode->crtc_hsync_start; + + dsi_cfg->hsa = dpi_to_dsi_timing(tmp, bpp, + DSI_HSA_FRAME_OVERHEAD); + } + + dsi_cfg->hact = dpi_to_dsi_timing(mode_valid_check ? + mode->hdisplay : mode->crtc_hdisplay, + bpp, 0); + dsi_cfg->hfp = dpi_to_dsi_timing(mode_to_dpi_hfp(mode, mode_valid_check), + bpp, DSI_HFP_FRAME_OVERHEAD); + + return 0; +} + +static int cdns_dsi_adjust_phy_config(struct cdns_dsi *dsi, + struct cdns_dsi_cfg *dsi_cfg, + struct phy_configure_opts_mipi_dphy *phy_cfg, + const struct drm_display_mode *mode, + bool mode_valid_check) +{ + struct cdns_dsi_output *output = &dsi->output; + unsigned long long dlane_bps; + unsigned long adj_dsi_htotal; + unsigned long dsi_htotal; + unsigned long dpi_htotal; + unsigned long dpi_hz; + unsigned int dsi_hfp_ext; + unsigned int lanes = output->dev->lanes; + + dsi_htotal = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD; + if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + dsi_htotal += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD; + + dsi_htotal += dsi_cfg->hact; + dsi_htotal += dsi_cfg->hfp + DSI_HFP_FRAME_OVERHEAD; + + /* + * Make sure DSI htotal is aligned on a lane boundary when calculating + * the expected data rate. This is done by extending HFP in case of + * misalignment. + */ + adj_dsi_htotal = dsi_htotal; + if (dsi_htotal % lanes) + adj_dsi_htotal += lanes - (dsi_htotal % lanes); + + dpi_hz = (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000; + dlane_bps = (unsigned long long)dpi_hz * adj_dsi_htotal; + + /* data rate in bytes/sec is not an integer, refuse the mode. */ + dpi_htotal = mode_valid_check ? mode->htotal : mode->crtc_htotal; + if (do_div(dlane_bps, lanes * dpi_htotal)) + return -EINVAL; + + /* data rate was in bytes/sec, convert to bits/sec. */ + phy_cfg->hs_clk_rate = dlane_bps * 8; + + dsi_hfp_ext = adj_dsi_htotal - dsi_htotal; + dsi_cfg->hfp += dsi_hfp_ext; + dsi_cfg->htotal = dsi_htotal + dsi_hfp_ext; + + return 0; +} + +static int cdns_dsi_check_conf(struct cdns_dsi *dsi, + const struct drm_display_mode *mode, + struct cdns_dsi_cfg *dsi_cfg, + bool mode_valid_check) +{ + struct cdns_dsi_output *output = &dsi->output; + struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy; + unsigned long dsi_hss_hsa_hse_hbp; + unsigned int nlanes = output->dev->lanes; + int ret; + + ret = cdns_dsi_mode2cfg(dsi, mode, dsi_cfg, mode_valid_check); + if (ret) + return ret; + + phy_mipi_dphy_get_default_config(mode->crtc_clock * 1000, + mipi_dsi_pixel_format_to_bpp(output->dev->format), + nlanes, phy_cfg); + + ret = cdns_dsi_adjust_phy_config(dsi, dsi_cfg, phy_cfg, mode, mode_valid_check); + if (ret) + return ret; + + ret = phy_validate(dsi->dphy, PHY_MODE_MIPI_DPHY, 0, &output->phy_opts); + if (ret) + return ret; + + dsi_hss_hsa_hse_hbp = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD; + if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + dsi_hss_hsa_hse_hbp += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD; + + /* + * Make sure DPI(HFP) > DSI(HSS+HSA+HSE+HBP) to guarantee that the FIFO + * is empty before we start a receiving a new line on the DPI + * interface. + */ + if ((u64)phy_cfg->hs_clk_rate * + mode_to_dpi_hfp(mode, mode_valid_check) * nlanes < + (u64)dsi_hss_hsa_hse_hbp * + (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000) + return -EINVAL; + + return 0; +} + +static int cdns_dsi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge); + struct cdns_dsi *dsi = input_to_dsi(input); + struct cdns_dsi_output *output = &dsi->output; + + if (!drm_core_check_feature(bridge->dev, DRIVER_ATOMIC)) { + dev_err(dsi->base.dev, + "cdns-dsi driver is only compatible with DRM devices supporting atomic updates"); + return -ENOTSUPP; + } + + return drm_bridge_attach(bridge->encoder, output->bridge, bridge, + flags); +} + +static enum drm_mode_status +cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge); + struct cdns_dsi *dsi = input_to_dsi(input); + struct cdns_dsi_output *output = &dsi->output; + struct cdns_dsi_cfg dsi_cfg; + int bpp, ret; + + /* + * VFP_DSI should be less than VFP_DPI and VFP_DSI should be at + * least 1. + */ + if (mode->vtotal - mode->vsync_end < 2) + return MODE_V_ILLEGAL; + + /* VSA_DSI = VSA_DPI and must be at least 2. */ + if (mode->vsync_end - mode->vsync_start < 2) + return MODE_V_ILLEGAL; + + /* HACT must be 32-bits aligned. */ + bpp = mipi_dsi_pixel_format_to_bpp(output->dev->format); + if ((mode->hdisplay * bpp) % 32) + return MODE_H_ILLEGAL; + + ret = cdns_dsi_check_conf(dsi, mode, &dsi_cfg, true); + if (ret) + return MODE_BAD; + + return MODE_OK; +} + +static void cdns_dsi_bridge_disable(struct drm_bridge *bridge) +{ + struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge); + struct cdns_dsi *dsi = input_to_dsi(input); + u32 val; + + val = readl(dsi->regs + MCTL_MAIN_DATA_CTL); + val &= ~(IF_VID_SELECT_MASK | IF_VID_MODE | VID_EN | HOST_EOT_GEN | + DISP_EOT_GEN); + writel(val, dsi->regs + MCTL_MAIN_DATA_CTL); + + val = readl(dsi->regs + MCTL_MAIN_EN) & ~IF_EN(input->id); + writel(val, dsi->regs + MCTL_MAIN_EN); + pm_runtime_put(dsi->base.dev); +} + +static void cdns_dsi_hs_init(struct cdns_dsi *dsi) +{ + struct cdns_dsi_output *output = &dsi->output; + u32 status; + + /* + * Power all internal DPHY blocks down and maintain their reset line + * asserted before changing the DPHY config. + */ + writel(DPHY_CMN_PSO | DPHY_PLL_PSO | DPHY_ALL_D_PDN | DPHY_C_PDN | + DPHY_CMN_PDN | DPHY_PLL_PDN, + dsi->regs + MCTL_DPHY_CFG0); + + phy_init(dsi->dphy); + phy_set_mode(dsi->dphy, PHY_MODE_MIPI_DPHY); + phy_configure(dsi->dphy, &output->phy_opts); + phy_power_on(dsi->dphy); + + /* Activate the PLL and wait until it's locked. */ + writel(PLL_LOCKED, dsi->regs + MCTL_MAIN_STS_CLR); + writel(DPHY_CMN_PSO | DPHY_ALL_D_PDN | DPHY_C_PDN | DPHY_CMN_PDN, + dsi->regs + MCTL_DPHY_CFG0); + WARN_ON_ONCE(readl_poll_timeout(dsi->regs + MCTL_MAIN_STS, status, + status & PLL_LOCKED, 100, 100)); + /* De-assert data and clock reset lines. */ + writel(DPHY_CMN_PSO | DPHY_ALL_D_PDN | DPHY_C_PDN | DPHY_CMN_PDN | + DPHY_D_RSTB(output->dev->lanes) | DPHY_C_RSTB, + dsi->regs + MCTL_DPHY_CFG0); +} + +static void cdns_dsi_init_link(struct cdns_dsi *dsi) +{ + struct cdns_dsi_output *output = &dsi->output; + unsigned long sysclk_period, ulpout; + u32 val; + int i; + + if (dsi->link_initialized) + return; + + val = 0; + for (i = 1; i < output->dev->lanes; i++) + val |= DATA_LANE_EN(i); + + if (!(output->dev->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)) + val |= CLK_CONTINUOUS; + + writel(val, dsi->regs + MCTL_MAIN_PHY_CTL); + + /* ULPOUT should be set to 1ms and is expressed in sysclk cycles. */ + sysclk_period = NSEC_PER_SEC / clk_get_rate(dsi->dsi_sys_clk); + ulpout = DIV_ROUND_UP(NSEC_PER_MSEC, sysclk_period); + writel(CLK_LANE_ULPOUT_TIME(ulpout) | DATA_LANE_ULPOUT_TIME(ulpout), + dsi->regs + MCTL_ULPOUT_TIME); + + writel(LINK_EN, dsi->regs + MCTL_MAIN_DATA_CTL); + + val = CLK_LANE_EN | PLL_START; + for (i = 0; i < output->dev->lanes; i++) + val |= DATA_LANE_START(i); + + writel(val, dsi->regs + MCTL_MAIN_EN); + + dsi->link_initialized = true; +} + +static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) +{ + struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge); + struct cdns_dsi *dsi = input_to_dsi(input); + struct cdns_dsi_output *output = &dsi->output; + struct drm_display_mode *mode; + struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy; + unsigned long tx_byte_period; + struct cdns_dsi_cfg dsi_cfg; + u32 tmp, reg_wakeup, div; + int nlanes; + + if (WARN_ON(pm_runtime_get_sync(dsi->base.dev) < 0)) + return; + + mode = &bridge->encoder->crtc->state->adjusted_mode; + nlanes = output->dev->lanes; + + WARN_ON_ONCE(cdns_dsi_check_conf(dsi, mode, &dsi_cfg, false)); + + cdns_dsi_hs_init(dsi); + cdns_dsi_init_link(dsi); + + writel(HBP_LEN(dsi_cfg.hbp) | HSA_LEN(dsi_cfg.hsa), + dsi->regs + VID_HSIZE1); + writel(HFP_LEN(dsi_cfg.hfp) | HACT_LEN(dsi_cfg.hact), + dsi->regs + VID_HSIZE2); + + writel(VBP_LEN(mode->crtc_vtotal - mode->crtc_vsync_end - 1) | + VFP_LEN(mode->crtc_vsync_start - mode->crtc_vdisplay) | + VSA_LEN(mode->crtc_vsync_end - mode->crtc_vsync_start + 1), + dsi->regs + VID_VSIZE1); + writel(mode->crtc_vdisplay, dsi->regs + VID_VSIZE2); + + tmp = dsi_cfg.htotal - + (dsi_cfg.hsa + DSI_BLANKING_FRAME_OVERHEAD + + DSI_HSA_FRAME_OVERHEAD); + writel(BLK_LINE_PULSE_PKT_LEN(tmp), dsi->regs + VID_BLKSIZE2); + if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + writel(MAX_LINE_LIMIT(tmp - DSI_NULL_FRAME_OVERHEAD), + dsi->regs + VID_VCA_SETTING2); + + tmp = dsi_cfg.htotal - + (DSI_HSS_VSS_VSE_FRAME_OVERHEAD + DSI_BLANKING_FRAME_OVERHEAD); + writel(BLK_LINE_EVENT_PKT_LEN(tmp), dsi->regs + VID_BLKSIZE1); + if (!(output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)) + writel(MAX_LINE_LIMIT(tmp - DSI_NULL_FRAME_OVERHEAD), + dsi->regs + VID_VCA_SETTING2); + + tmp = DIV_ROUND_UP(dsi_cfg.htotal, nlanes) - + DIV_ROUND_UP(dsi_cfg.hsa, nlanes); + + if (!(output->dev->mode_flags & MIPI_DSI_MODE_EOT_PACKET)) + tmp -= DIV_ROUND_UP(DSI_EOT_PKT_SIZE, nlanes); + + tx_byte_period = DIV_ROUND_DOWN_ULL((u64)NSEC_PER_SEC * 8, + phy_cfg->hs_clk_rate); + reg_wakeup = (phy_cfg->hs_prepare + phy_cfg->hs_zero) / tx_byte_period; + writel(REG_WAKEUP_TIME(reg_wakeup) | REG_LINE_DURATION(tmp), + dsi->regs + VID_DPHY_TIME); + + /* + * HSTX and LPRX timeouts are both expressed in TX byte clk cycles and + * both should be set to at least the time it takes to transmit a + * frame. + */ + tmp = NSEC_PER_SEC / drm_mode_vrefresh(mode); + tmp /= tx_byte_period; + + for (div = 0; div <= CLK_DIV_MAX; div++) { + if (tmp <= HSTX_TIMEOUT_MAX) + break; + + tmp >>= 1; + } + + if (tmp > HSTX_TIMEOUT_MAX) + tmp = HSTX_TIMEOUT_MAX; + + writel(CLK_DIV(div) | HSTX_TIMEOUT(tmp), + dsi->regs + MCTL_DPHY_TIMEOUT1); + + writel(LPRX_TIMEOUT(tmp), dsi->regs + MCTL_DPHY_TIMEOUT2); + + if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO) { + switch (output->dev->format) { + case MIPI_DSI_FMT_RGB888: + tmp = VID_PIXEL_MODE_RGB888 | + VID_DATATYPE(MIPI_DSI_PACKED_PIXEL_STREAM_24); + break; + + case MIPI_DSI_FMT_RGB666: + tmp = VID_PIXEL_MODE_RGB666 | + VID_DATATYPE(MIPI_DSI_PIXEL_STREAM_3BYTE_18); + break; + + case MIPI_DSI_FMT_RGB666_PACKED: + tmp = VID_PIXEL_MODE_RGB666_PACKED | + VID_DATATYPE(MIPI_DSI_PACKED_PIXEL_STREAM_18); + break; + + case MIPI_DSI_FMT_RGB565: + tmp = VID_PIXEL_MODE_RGB565 | + VID_DATATYPE(MIPI_DSI_PACKED_PIXEL_STREAM_16); + break; + + default: + dev_err(dsi->base.dev, "Unsupported DSI format\n"); + return; + } + + if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + tmp |= SYNC_PULSE_ACTIVE | SYNC_PULSE_HORIZONTAL; + + tmp |= REG_BLKLINE_MODE(REG_BLK_MODE_BLANKING_PKT) | + REG_BLKEOL_MODE(REG_BLK_MODE_BLANKING_PKT) | + RECOVERY_MODE(RECOVERY_MODE_NEXT_HSYNC) | + VID_IGNORE_MISS_VSYNC; + + writel(tmp, dsi->regs + VID_MAIN_CTL); + } + + tmp = readl(dsi->regs + MCTL_MAIN_DATA_CTL); + tmp &= ~(IF_VID_SELECT_MASK | HOST_EOT_GEN | IF_VID_MODE); + + if (!(output->dev->mode_flags & MIPI_DSI_MODE_EOT_PACKET)) + tmp |= HOST_EOT_GEN; + + if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO) + tmp |= IF_VID_MODE | IF_VID_SELECT(input->id) | VID_EN; + + writel(tmp, dsi->regs + MCTL_MAIN_DATA_CTL); + + tmp = readl(dsi->regs + MCTL_MAIN_EN) | IF_EN(input->id); + writel(tmp, dsi->regs + MCTL_MAIN_EN); +} + +static const struct drm_bridge_funcs cdns_dsi_bridge_funcs = { + .attach = cdns_dsi_bridge_attach, + .mode_valid = cdns_dsi_bridge_mode_valid, + .disable = cdns_dsi_bridge_disable, + .enable = cdns_dsi_bridge_enable, +}; + +static int cdns_dsi_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *dev) +{ + struct cdns_dsi *dsi = to_cdns_dsi(host); + struct cdns_dsi_output *output = &dsi->output; + struct cdns_dsi_input *input = &dsi->input; + struct drm_bridge *bridge; + struct drm_panel *panel; + struct device_node *np; + int ret; + + /* + * We currently do not support connecting several DSI devices to the + * same host. In order to support that we'd need the DRM bridge + * framework to allow dynamic reconfiguration of the bridge chain. + */ + if (output->dev) + return -EBUSY; + + /* We do not support burst mode yet. */ + if (dev->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) + return -ENOTSUPP; + + /* + * The host <-> device link might be described using an OF-graph + * representation, in this case we extract the device of_node from + * this representation, otherwise we use dsidev->dev.of_node which + * should have been filled by the core. + */ + np = of_graph_get_remote_node(dsi->base.dev->of_node, DSI_OUTPUT_PORT, + dev->channel); + if (!np) + np = of_node_get(dev->dev.of_node); + + panel = of_drm_find_panel(np); + if (!IS_ERR(panel)) { + bridge = drm_panel_bridge_add_typed(panel, + DRM_MODE_CONNECTOR_DSI); + } else { + bridge = of_drm_find_bridge(dev->dev.of_node); + if (!bridge) + bridge = ERR_PTR(-EINVAL); + } + + of_node_put(np); + + if (IS_ERR(bridge)) { + ret = PTR_ERR(bridge); + dev_err(host->dev, "failed to add DSI device %s (err = %d)", + dev->name, ret); + return ret; + } + + output->dev = dev; + output->bridge = bridge; + output->panel = panel; + + /* + * The DSI output has been properly configured, we can now safely + * register the input to the bridge framework so that it can take place + * in a display pipeline. + */ + drm_bridge_add(&input->bridge); + + return 0; +} + +static int cdns_dsi_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *dev) +{ + struct cdns_dsi *dsi = to_cdns_dsi(host); + struct cdns_dsi_output *output = &dsi->output; + struct cdns_dsi_input *input = &dsi->input; + + drm_bridge_remove(&input->bridge); + if (output->panel) + drm_panel_bridge_remove(output->bridge); + + return 0; +} + +static irqreturn_t cdns_dsi_interrupt(int irq, void *data) +{ + struct cdns_dsi *dsi = data; + irqreturn_t ret = IRQ_NONE; + u32 flag, ctl; + + flag = readl(dsi->regs + DIRECT_CMD_STS_FLAG); + if (flag) { + ctl = readl(dsi->regs + DIRECT_CMD_STS_CTL); + ctl &= ~flag; + writel(ctl, dsi->regs + DIRECT_CMD_STS_CTL); + complete(&dsi->direct_cmd_comp); + ret = IRQ_HANDLED; + } + + return ret; +} + +static ssize_t cdns_dsi_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct cdns_dsi *dsi = to_cdns_dsi(host); + u32 cmd, sts, val, wait = WRITE_COMPLETED, ctl = 0; + struct mipi_dsi_packet packet; + int ret, i, tx_len, rx_len; + + ret = pm_runtime_resume_and_get(host->dev); + if (ret < 0) + return ret; + + cdns_dsi_init_link(dsi); + + ret = mipi_dsi_create_packet(&packet, msg); + if (ret) + goto out; + + tx_len = msg->tx_buf ? msg->tx_len : 0; + rx_len = msg->rx_buf ? msg->rx_len : 0; + + /* For read operations, the maximum TX len is 2. */ + if (rx_len && tx_len > 2) { + ret = -ENOTSUPP; + goto out; + } + + /* TX len is limited by the CMD FIFO depth. */ + if (tx_len > dsi->direct_cmd_fifo_depth) { + ret = -ENOTSUPP; + goto out; + } + + /* RX len is limited by the RX FIFO depth. */ + if (rx_len > dsi->rx_fifo_depth) { + ret = -ENOTSUPP; + goto out; + } + + cmd = CMD_SIZE(tx_len) | CMD_VCHAN_ID(msg->channel) | + CMD_DATATYPE(msg->type); + + if (msg->flags & MIPI_DSI_MSG_USE_LPM) + cmd |= CMD_LP_EN; + + if (mipi_dsi_packet_format_is_long(msg->type)) + cmd |= CMD_LONG; + + if (rx_len) { + cmd |= READ_CMD; + wait = READ_COMPLETED_WITH_ERR | READ_COMPLETED; + ctl = READ_EN | BTA_EN; + } else if (msg->flags & MIPI_DSI_MSG_REQ_ACK) { + cmd |= BTA_REQ; + wait = ACK_WITH_ERR_RCVD | ACK_RCVD; + ctl = BTA_EN; + } + + writel(readl(dsi->regs + MCTL_MAIN_DATA_CTL) | ctl, + dsi->regs + MCTL_MAIN_DATA_CTL); + + writel(cmd, dsi->regs + DIRECT_CMD_MAIN_SETTINGS); + + for (i = 0; i < tx_len; i += 4) { + const u8 *buf = msg->tx_buf; + int j; + + val = 0; + for (j = 0; j < 4 && j + i < tx_len; j++) + val |= (u32)buf[i + j] << (8 * j); + + writel(val, dsi->regs + DIRECT_CMD_WRDATA); + } + + /* Clear status flags before sending the command. */ + writel(wait, dsi->regs + DIRECT_CMD_STS_CLR); + writel(wait, dsi->regs + DIRECT_CMD_STS_CTL); + reinit_completion(&dsi->direct_cmd_comp); + writel(0, dsi->regs + DIRECT_CMD_SEND); + + wait_for_completion_timeout(&dsi->direct_cmd_comp, + msecs_to_jiffies(1000)); + + sts = readl(dsi->regs + DIRECT_CMD_STS); + writel(wait, dsi->regs + DIRECT_CMD_STS_CLR); + writel(0, dsi->regs + DIRECT_CMD_STS_CTL); + + writel(readl(dsi->regs + MCTL_MAIN_DATA_CTL) & ~ctl, + dsi->regs + MCTL_MAIN_DATA_CTL); + + /* We did not receive the events we were waiting for. */ + if (!(sts & wait)) { + ret = -ETIMEDOUT; + goto out; + } + + /* 'READ' or 'WRITE with ACK' failed. */ + if (sts & (READ_COMPLETED_WITH_ERR | ACK_WITH_ERR_RCVD)) { + ret = -EIO; + goto out; + } + + for (i = 0; i < rx_len; i += 4) { + u8 *buf = msg->rx_buf; + int j; + + val = readl(dsi->regs + DIRECT_CMD_RDDATA); + for (j = 0; j < 4 && j + i < rx_len; j++) + buf[i + j] = val >> (8 * j); + } + +out: + pm_runtime_put(host->dev); + return ret; +} + +static const struct mipi_dsi_host_ops cdns_dsi_ops = { + .attach = cdns_dsi_attach, + .detach = cdns_dsi_detach, + .transfer = cdns_dsi_transfer, +}; + +static int __maybe_unused cdns_dsi_resume(struct device *dev) +{ + struct cdns_dsi *dsi = dev_get_drvdata(dev); + + reset_control_deassert(dsi->dsi_p_rst); + clk_prepare_enable(dsi->dsi_p_clk); + clk_prepare_enable(dsi->dsi_sys_clk); + + return 0; +} + +static int __maybe_unused cdns_dsi_suspend(struct device *dev) +{ + struct cdns_dsi *dsi = dev_get_drvdata(dev); + + clk_disable_unprepare(dsi->dsi_sys_clk); + clk_disable_unprepare(dsi->dsi_p_clk); + reset_control_assert(dsi->dsi_p_rst); + dsi->link_initialized = false; + return 0; +} + +static UNIVERSAL_DEV_PM_OPS(cdns_dsi_pm_ops, cdns_dsi_suspend, cdns_dsi_resume, + NULL); + +static int cdns_dsi_drm_probe(struct platform_device *pdev) +{ + struct cdns_dsi *dsi; + struct cdns_dsi_input *input; + struct resource *res; + int ret, irq; + u32 val; + + dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + + platform_set_drvdata(pdev, dsi); + + input = &dsi->input; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dsi->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dsi->regs)) + return PTR_ERR(dsi->regs); + + dsi->dsi_p_clk = devm_clk_get(&pdev->dev, "dsi_p_clk"); + if (IS_ERR(dsi->dsi_p_clk)) + return PTR_ERR(dsi->dsi_p_clk); + + dsi->dsi_p_rst = devm_reset_control_get_optional_exclusive(&pdev->dev, + "dsi_p_rst"); + if (IS_ERR(dsi->dsi_p_rst)) + return PTR_ERR(dsi->dsi_p_rst); + + dsi->dsi_sys_clk = devm_clk_get(&pdev->dev, "dsi_sys_clk"); + if (IS_ERR(dsi->dsi_sys_clk)) + return PTR_ERR(dsi->dsi_sys_clk); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + dsi->dphy = devm_phy_get(&pdev->dev, "dphy"); + if (IS_ERR(dsi->dphy)) + return PTR_ERR(dsi->dphy); + + ret = clk_prepare_enable(dsi->dsi_p_clk); + if (ret) + return ret; + + val = readl(dsi->regs + ID_REG); + if (REV_VENDOR_ID(val) != 0xcad) { + dev_err(&pdev->dev, "invalid vendor id\n"); + ret = -EINVAL; + goto err_disable_pclk; + } + + val = readl(dsi->regs + IP_CONF); + dsi->direct_cmd_fifo_depth = 1 << (DIRCMD_FIFO_DEPTH(val) + 2); + dsi->rx_fifo_depth = RX_FIFO_DEPTH(val); + init_completion(&dsi->direct_cmd_comp); + + writel(0, dsi->regs + MCTL_MAIN_DATA_CTL); + writel(0, dsi->regs + MCTL_MAIN_EN); + writel(0, dsi->regs + MCTL_MAIN_PHY_CTL); + + /* + * We only support the DPI input, so force input->id to + * CDNS_DPI_INPUT. + */ + input->id = CDNS_DPI_INPUT; + input->bridge.funcs = &cdns_dsi_bridge_funcs; + input->bridge.of_node = pdev->dev.of_node; + + /* Mask all interrupts before registering the IRQ handler. */ + writel(0, dsi->regs + MCTL_MAIN_STS_CTL); + writel(0, dsi->regs + MCTL_DPHY_ERR_CTL1); + writel(0, dsi->regs + CMD_MODE_STS_CTL); + writel(0, dsi->regs + DIRECT_CMD_STS_CTL); + writel(0, dsi->regs + DIRECT_CMD_RD_STS_CTL); + writel(0, dsi->regs + VID_MODE_STS_CTL); + writel(0, dsi->regs + TVG_STS_CTL); + writel(0, dsi->regs + DPI_IRQ_EN); + ret = devm_request_irq(&pdev->dev, irq, cdns_dsi_interrupt, 0, + dev_name(&pdev->dev), dsi); + if (ret) + goto err_disable_pclk; + + pm_runtime_enable(&pdev->dev); + dsi->base.dev = &pdev->dev; + dsi->base.ops = &cdns_dsi_ops; + + ret = mipi_dsi_host_register(&dsi->base); + if (ret) + goto err_disable_runtime_pm; + + clk_disable_unprepare(dsi->dsi_p_clk); + + return 0; + +err_disable_runtime_pm: + pm_runtime_disable(&pdev->dev); + +err_disable_pclk: + clk_disable_unprepare(dsi->dsi_p_clk); + + return ret; +} + +static int cdns_dsi_drm_remove(struct platform_device *pdev) +{ + struct cdns_dsi *dsi = platform_get_drvdata(pdev); + + mipi_dsi_host_unregister(&dsi->base); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id cdns_dsi_of_match[] = { + { .compatible = "cdns,dsi" }, + { }, +}; +MODULE_DEVICE_TABLE(of, cdns_dsi_of_match); + +static struct platform_driver cdns_dsi_platform_driver = { + .probe = cdns_dsi_drm_probe, + .remove = cdns_dsi_drm_remove, + .driver = { + .name = "cdns-dsi", + .of_match_table = cdns_dsi_of_match, + .pm = &cdns_dsi_pm_ops, + }, +}; +module_platform_driver(cdns_dsi_platform_driver); + +MODULE_AUTHOR("Boris Brezillon <boris.brezillon@bootlin.com>"); +MODULE_DESCRIPTION("Cadence DSI driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cdns-dsi"); + diff --git a/drivers/gpu/drm/bridge/chrontel-ch7033.c b/drivers/gpu/drm/bridge/chrontel-ch7033.c new file mode 100644 index 000000000..486f405c2 --- /dev/null +++ b/drivers/gpu/drm/bridge/chrontel-ch7033.c @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Chrontel CH7033 Video Encoder Driver + * + * Copyright (C) 2019,2020 Lubomir Rintel + */ + +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +/* Page 0, Register 0x07 */ +enum { + DRI_PD = BIT(3), + IO_PD = BIT(5), +}; + +/* Page 0, Register 0x08 */ +enum { + DRI_PDDRI = GENMASK(7, 4), + PDDAC = GENMASK(3, 1), + PANEN = BIT(0), +}; + +/* Page 0, Register 0x09 */ +enum { + DPD = BIT(7), + GCKOFF = BIT(6), + TV_BP = BIT(5), + SCLPD = BIT(4), + SDPD = BIT(3), + VGA_PD = BIT(2), + HDBKPD = BIT(1), + HDMI_PD = BIT(0), +}; + +/* Page 0, Register 0x0a */ +enum { + MEMINIT = BIT(7), + MEMIDLE = BIT(6), + MEMPD = BIT(5), + STOP = BIT(4), + LVDS_PD = BIT(3), + HD_DVIB = BIT(2), + HDCP_PD = BIT(1), + MCU_PD = BIT(0), +}; + +/* Page 0, Register 0x18 */ +enum { + IDF = GENMASK(7, 4), + INTEN = BIT(3), + SWAP = GENMASK(2, 0), +}; + +enum { + BYTE_SWAP_RGB = 0, + BYTE_SWAP_RBG = 1, + BYTE_SWAP_GRB = 2, + BYTE_SWAP_GBR = 3, + BYTE_SWAP_BRG = 4, + BYTE_SWAP_BGR = 5, +}; + +/* Page 0, Register 0x19 */ +enum { + HPO_I = BIT(5), + VPO_I = BIT(4), + DEPO_I = BIT(3), + CRYS_EN = BIT(2), + GCLKFREQ = GENMASK(2, 0), +}; + +/* Page 0, Register 0x2e */ +enum { + HFLIP = BIT(7), + VFLIP = BIT(6), + DEPO_O = BIT(5), + HPO_O = BIT(4), + VPO_O = BIT(3), + TE = GENMASK(2, 0), +}; + +/* Page 0, Register 0x2b */ +enum { + SWAPS = GENMASK(7, 4), + VFMT = GENMASK(3, 0), +}; + +/* Page 0, Register 0x54 */ +enum { + COMP_BP = BIT(7), + DAC_EN_T = BIT(6), + HWO_HDMI_HI = GENMASK(5, 3), + HOO_HDMI_HI = GENMASK(2, 0), +}; + +/* Page 0, Register 0x57 */ +enum { + FLDSEN = BIT(7), + VWO_HDMI_HI = GENMASK(5, 3), + VOO_HDMI_HI = GENMASK(2, 0), +}; + +/* Page 0, Register 0x7e */ +enum { + HDMI_LVDS_SEL = BIT(7), + DE_GEN = BIT(6), + PWM_INDEX_HI = BIT(5), + USE_DE = BIT(4), + R_INT = GENMASK(3, 0), +}; + +/* Page 1, Register 0x07 */ +enum { + BPCKSEL = BIT(7), + DRI_CMFB_EN = BIT(6), + CEC_PUEN = BIT(5), + CEC_T = BIT(3), + CKINV = BIT(2), + CK_TVINV = BIT(1), + DRI_CKS2 = BIT(0), +}; + +/* Page 1, Register 0x08 */ +enum { + DACG = BIT(6), + DACKTST = BIT(5), + DEDGEB = BIT(4), + SYO = BIT(3), + DRI_IT_LVDS = GENMASK(2, 1), + DISPON = BIT(0), +}; + +/* Page 1, Register 0x0c */ +enum { + DRI_PLL_CP = GENMASK(7, 6), + DRI_PLL_DIVSEL = BIT(5), + DRI_PLL_N1_1 = BIT(4), + DRI_PLL_N1_0 = BIT(3), + DRI_PLL_N3_1 = BIT(2), + DRI_PLL_N3_0 = BIT(1), + DRI_PLL_CKTSTEN = BIT(0), +}; + +/* Page 1, Register 0x6b */ +enum { + VCO3CS = GENMASK(7, 6), + ICPGBK2_0 = GENMASK(5, 3), + DRI_VCO357SC = BIT(2), + PDPLL2 = BIT(1), + DRI_PD_SER = BIT(0), +}; + +/* Page 1, Register 0x6c */ +enum { + PLL2N11 = GENMASK(7, 4), + PLL2N5_4 = BIT(3), + PLL2N5_TOP = BIT(2), + DRI_PLL_PD = BIT(1), + PD_I2CM = BIT(0), +}; + +/* Page 3, Register 0x28 */ +enum { + DIFF_EN = GENMASK(7, 6), + CORREC_EN = GENMASK(5, 4), + VGACLK_BP = BIT(3), + HM_LV_SEL = BIT(2), + HD_VGA_SEL = BIT(1), +}; + +/* Page 3, Register 0x2a */ +enum { + LVDSCLK_BP = BIT(7), + HDTVCLK_BP = BIT(6), + HDMICLK_BP = BIT(5), + HDTV_BP = BIT(4), + HDMI_BP = BIT(3), + THRWL = GENMASK(2, 0), +}; + +/* Page 4, Register 0x52 */ +enum { + PGM_ARSTB = BIT(7), + MCU_ARSTB = BIT(6), + MCU_RETB = BIT(2), + RESETIB = BIT(1), + RESETDB = BIT(0), +}; + +struct ch7033_priv { + struct regmap *regmap; + struct drm_bridge *next_bridge; + struct drm_bridge bridge; + struct drm_connector connector; +}; + +#define conn_to_ch7033_priv(x) \ + container_of(x, struct ch7033_priv, connector) +#define bridge_to_ch7033_priv(x) \ + container_of(x, struct ch7033_priv, bridge) + + +static enum drm_connector_status ch7033_connector_detect( + struct drm_connector *connector, bool force) +{ + struct ch7033_priv *priv = conn_to_ch7033_priv(connector); + + return drm_bridge_detect(priv->next_bridge); +} + +static const struct drm_connector_funcs ch7033_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ch7033_connector_detect, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int ch7033_connector_get_modes(struct drm_connector *connector) +{ + struct ch7033_priv *priv = conn_to_ch7033_priv(connector); + struct edid *edid; + int ret; + + edid = drm_bridge_get_edid(priv->next_bridge, connector); + drm_connector_update_edid_property(connector, edid); + if (edid) { + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + } else { + ret = drm_add_modes_noedid(connector, 1920, 1080); + drm_set_preferred_mode(connector, 1024, 768); + } + + return ret; +} + +static struct drm_encoder *ch7033_connector_best_encoder( + struct drm_connector *connector) +{ + struct ch7033_priv *priv = conn_to_ch7033_priv(connector); + + return priv->bridge.encoder; +} + +static const struct drm_connector_helper_funcs ch7033_connector_helper_funcs = { + .get_modes = ch7033_connector_get_modes, + .best_encoder = ch7033_connector_best_encoder, +}; + +static void ch7033_hpd_event(void *arg, enum drm_connector_status status) +{ + struct ch7033_priv *priv = arg; + + if (priv->bridge.dev) + drm_helper_hpd_irq_event(priv->connector.dev); +} + +static int ch7033_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); + struct drm_connector *connector = &priv->connector; + int ret; + + ret = drm_bridge_attach(bridge->encoder, priv->next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ret; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return 0; + + if (priv->next_bridge->ops & DRM_BRIDGE_OP_DETECT) { + connector->polled = DRM_CONNECTOR_POLL_HPD; + } else { + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + } + + if (priv->next_bridge->ops & DRM_BRIDGE_OP_HPD) { + drm_bridge_hpd_enable(priv->next_bridge, ch7033_hpd_event, + priv); + } + + drm_connector_helper_add(connector, + &ch7033_connector_helper_funcs); + ret = drm_connector_init_with_ddc(bridge->dev, &priv->connector, + &ch7033_connector_funcs, + priv->next_bridge->type, + priv->next_bridge->ddc); + if (ret) { + DRM_ERROR("Failed to initialize connector\n"); + return ret; + } + + return drm_connector_attach_encoder(&priv->connector, bridge->encoder); +} + +static void ch7033_bridge_detach(struct drm_bridge *bridge) +{ + struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); + + if (priv->next_bridge->ops & DRM_BRIDGE_OP_HPD) + drm_bridge_hpd_disable(priv->next_bridge); + drm_connector_cleanup(&priv->connector); +} + +static enum drm_mode_status ch7033_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->clock > 165000) + return MODE_CLOCK_HIGH; + if (mode->hdisplay >= 1920) + return MODE_BAD_HVALUE; + if (mode->vdisplay >= 1080) + return MODE_BAD_VVALUE; + return MODE_OK; +} + +static void ch7033_bridge_disable(struct drm_bridge *bridge) +{ + struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); + + regmap_write(priv->regmap, 0x03, 0x04); + regmap_update_bits(priv->regmap, 0x52, RESETDB, 0x00); +} + +static void ch7033_bridge_enable(struct drm_bridge *bridge) +{ + struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); + + regmap_write(priv->regmap, 0x03, 0x04); + regmap_update_bits(priv->regmap, 0x52, RESETDB, RESETDB); +} + +static void ch7033_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); + int hbporch = mode->hsync_start - mode->hdisplay; + int hsynclen = mode->hsync_end - mode->hsync_start; + int vbporch = mode->vsync_start - mode->vdisplay; + int vsynclen = mode->vsync_end - mode->vsync_start; + + /* + * Page 4 + */ + regmap_write(priv->regmap, 0x03, 0x04); + + /* Turn everything off to set all the registers to their defaults. */ + regmap_write(priv->regmap, 0x52, 0x00); + /* Bring I/O block up. */ + regmap_write(priv->regmap, 0x52, RESETIB); + + /* + * Page 0 + */ + regmap_write(priv->regmap, 0x03, 0x00); + + /* Bring up parts we need from the power down. */ + regmap_update_bits(priv->regmap, 0x07, DRI_PD | IO_PD, 0); + regmap_update_bits(priv->regmap, 0x08, DRI_PDDRI | PDDAC | PANEN, 0); + regmap_update_bits(priv->regmap, 0x09, DPD | GCKOFF | + HDMI_PD | VGA_PD, 0); + regmap_update_bits(priv->regmap, 0x0a, HD_DVIB, 0); + + /* Horizontal input timing. */ + regmap_write(priv->regmap, 0x0b, (mode->htotal >> 8) << 3 | + (mode->hdisplay >> 8)); + regmap_write(priv->regmap, 0x0c, mode->hdisplay); + regmap_write(priv->regmap, 0x0d, mode->htotal); + regmap_write(priv->regmap, 0x0e, (hsynclen >> 8) << 3 | + (hbporch >> 8)); + regmap_write(priv->regmap, 0x0f, hbporch); + regmap_write(priv->regmap, 0x10, hsynclen); + + /* Vertical input timing. */ + regmap_write(priv->regmap, 0x11, (mode->vtotal >> 8) << 3 | + (mode->vdisplay >> 8)); + regmap_write(priv->regmap, 0x12, mode->vdisplay); + regmap_write(priv->regmap, 0x13, mode->vtotal); + regmap_write(priv->regmap, 0x14, ((vsynclen >> 8) << 3) | + (vbporch >> 8)); + regmap_write(priv->regmap, 0x15, vbporch); + regmap_write(priv->regmap, 0x16, vsynclen); + + /* Input color swap. */ + regmap_update_bits(priv->regmap, 0x18, SWAP, BYTE_SWAP_BGR); + + /* Input clock and sync polarity. */ + regmap_update_bits(priv->regmap, 0x19, 0x1, mode->clock >> 16); + regmap_update_bits(priv->regmap, 0x19, HPO_I | VPO_I | GCLKFREQ, + (mode->flags & DRM_MODE_FLAG_PHSYNC) ? HPO_I : 0 | + (mode->flags & DRM_MODE_FLAG_PVSYNC) ? VPO_I : 0 | + mode->clock >> 16); + regmap_write(priv->regmap, 0x1a, mode->clock >> 8); + regmap_write(priv->regmap, 0x1b, mode->clock); + + /* Horizontal output timing. */ + regmap_write(priv->regmap, 0x1f, (mode->htotal >> 8) << 3 | + (mode->hdisplay >> 8)); + regmap_write(priv->regmap, 0x20, mode->hdisplay); + regmap_write(priv->regmap, 0x21, mode->htotal); + + /* Vertical output timing. */ + regmap_write(priv->regmap, 0x25, (mode->vtotal >> 8) << 3 | + (mode->vdisplay >> 8)); + regmap_write(priv->regmap, 0x26, mode->vdisplay); + regmap_write(priv->regmap, 0x27, mode->vtotal); + + /* VGA channel bypass */ + regmap_update_bits(priv->regmap, 0x2b, VFMT, 9); + + /* Output sync polarity. */ + regmap_update_bits(priv->regmap, 0x2e, HPO_O | VPO_O, + (mode->flags & DRM_MODE_FLAG_PHSYNC) ? HPO_O : 0 | + (mode->flags & DRM_MODE_FLAG_PVSYNC) ? VPO_O : 0); + + /* HDMI horizontal output timing. */ + regmap_update_bits(priv->regmap, 0x54, HWO_HDMI_HI | HOO_HDMI_HI, + (hsynclen >> 8) << 3 | + (hbporch >> 8)); + regmap_write(priv->regmap, 0x55, hbporch); + regmap_write(priv->regmap, 0x56, hsynclen); + + /* HDMI vertical output timing. */ + regmap_update_bits(priv->regmap, 0x57, VWO_HDMI_HI | VOO_HDMI_HI, + (vsynclen >> 8) << 3 | + (vbporch >> 8)); + regmap_write(priv->regmap, 0x58, vbporch); + regmap_write(priv->regmap, 0x59, vsynclen); + + /* Pick HDMI, not LVDS. */ + regmap_update_bits(priv->regmap, 0x7e, HDMI_LVDS_SEL, HDMI_LVDS_SEL); + + /* + * Page 1 + */ + regmap_write(priv->regmap, 0x03, 0x01); + + /* No idea what these do, but VGA is wobbly and blinky without them. */ + regmap_update_bits(priv->regmap, 0x07, CKINV, CKINV); + regmap_update_bits(priv->regmap, 0x08, DISPON, DISPON); + + /* DRI PLL */ + regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_DIVSEL, DRI_PLL_DIVSEL); + if (mode->clock <= 40000) { + regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_N1_1 | + DRI_PLL_N1_0 | + DRI_PLL_N3_1 | + DRI_PLL_N3_0, + 0); + } else if (mode->clock < 80000) { + regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_N1_1 | + DRI_PLL_N1_0 | + DRI_PLL_N3_1 | + DRI_PLL_N3_0, + DRI_PLL_N3_0 | + DRI_PLL_N1_0); + } else { + regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_N1_1 | + DRI_PLL_N1_0 | + DRI_PLL_N3_1 | + DRI_PLL_N3_0, + DRI_PLL_N3_1 | + DRI_PLL_N1_1); + } + + /* This seems to be color calibration for VGA. */ + regmap_write(priv->regmap, 0x64, 0x29); /* LSB Blue */ + regmap_write(priv->regmap, 0x65, 0x29); /* LSB Green */ + regmap_write(priv->regmap, 0x66, 0x29); /* LSB Red */ + regmap_write(priv->regmap, 0x67, 0x00); /* MSB Blue */ + regmap_write(priv->regmap, 0x68, 0x00); /* MSB Green */ + regmap_write(priv->regmap, 0x69, 0x00); /* MSB Red */ + + regmap_update_bits(priv->regmap, 0x6b, DRI_PD_SER, 0x00); + regmap_update_bits(priv->regmap, 0x6c, DRI_PLL_PD, 0x00); + + /* + * Page 3 + */ + regmap_write(priv->regmap, 0x03, 0x03); + + /* More bypasses and apparently another HDMI/LVDS selector. */ + regmap_update_bits(priv->regmap, 0x28, VGACLK_BP | HM_LV_SEL, + VGACLK_BP | HM_LV_SEL); + regmap_update_bits(priv->regmap, 0x2a, HDMICLK_BP | HDMI_BP, + HDMICLK_BP | HDMI_BP); + + /* + * Page 4 + */ + regmap_write(priv->regmap, 0x03, 0x04); + + /* Output clock. */ + regmap_write(priv->regmap, 0x10, mode->clock >> 16); + regmap_write(priv->regmap, 0x11, mode->clock >> 8); + regmap_write(priv->regmap, 0x12, mode->clock); +} + +static const struct drm_bridge_funcs ch7033_bridge_funcs = { + .attach = ch7033_bridge_attach, + .detach = ch7033_bridge_detach, + .mode_valid = ch7033_bridge_mode_valid, + .disable = ch7033_bridge_disable, + .enable = ch7033_bridge_enable, + .mode_set = ch7033_bridge_mode_set, +}; + +static const struct regmap_config ch7033_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x7f, +}; + +static int ch7033_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ch7033_priv *priv; + unsigned int val; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + + ret = drm_of_find_panel_or_bridge(dev->of_node, 1, -1, NULL, + &priv->next_bridge); + if (ret) + return ret; + + priv->regmap = devm_regmap_init_i2c(client, &ch7033_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(&client->dev, "regmap init failed\n"); + return PTR_ERR(priv->regmap); + } + + ret = regmap_read(priv->regmap, 0x00, &val); + if (ret < 0) { + dev_err(&client->dev, "error reading the model id: %d\n", ret); + return ret; + } + if ((val & 0xf7) != 0x56) { + dev_err(&client->dev, "the device is not a ch7033\n"); + return -ENODEV; + } + + regmap_write(priv->regmap, 0x03, 0x04); + ret = regmap_read(priv->regmap, 0x51, &val); + if (ret < 0) { + dev_err(&client->dev, "error reading the model id: %d\n", ret); + return ret; + } + if ((val & 0x0f) != 3) { + dev_err(&client->dev, "unknown revision %u\n", val); + return -ENODEV; + } + + INIT_LIST_HEAD(&priv->bridge.list); + priv->bridge.funcs = &ch7033_bridge_funcs; + priv->bridge.of_node = dev->of_node; + drm_bridge_add(&priv->bridge); + + dev_info(dev, "Chrontel CH7033 Video Encoder\n"); + return 0; +} + +static int ch7033_remove(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ch7033_priv *priv = dev_get_drvdata(dev); + + drm_bridge_remove(&priv->bridge); + + return 0; +} + +static const struct of_device_id ch7033_dt_ids[] = { + { .compatible = "chrontel,ch7033", }, + { } +}; +MODULE_DEVICE_TABLE(of, ch7033_dt_ids); + +static const struct i2c_device_id ch7033_ids[] = { + { "ch7033", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ch7033_ids); + +static struct i2c_driver ch7033_driver = { + .probe = ch7033_probe, + .remove = ch7033_remove, + .driver = { + .name = "ch7033", + .of_match_table = of_match_ptr(ch7033_dt_ids), + }, + .id_table = ch7033_ids, +}; + +module_i2c_driver(ch7033_driver); + +MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); +MODULE_DESCRIPTION("Chrontel CH7033 Video Encoder Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c new file mode 100644 index 000000000..544a47335 --- /dev/null +++ b/drivers/gpu/drm/bridge/display-connector.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_edid.h> + +struct display_connector { + struct drm_bridge bridge; + + struct gpio_desc *hpd_gpio; + int hpd_irq; +}; + +static inline struct display_connector * +to_display_connector(struct drm_bridge *bridge) +{ + return container_of(bridge, struct display_connector, bridge); +} + +static int display_connector_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL; +} + +static enum drm_connector_status +display_connector_detect(struct drm_bridge *bridge) +{ + struct display_connector *conn = to_display_connector(bridge); + + if (conn->hpd_gpio) { + if (gpiod_get_value_cansleep(conn->hpd_gpio)) + return connector_status_connected; + else + return connector_status_disconnected; + } + + if (conn->bridge.ddc && drm_probe_ddc(conn->bridge.ddc)) + return connector_status_connected; + + switch (conn->bridge.type) { + case DRM_MODE_CONNECTOR_DVIA: + case DRM_MODE_CONNECTOR_DVID: + case DRM_MODE_CONNECTOR_DVII: + case DRM_MODE_CONNECTOR_HDMIA: + case DRM_MODE_CONNECTOR_HDMIB: + /* + * For DVI and HDMI connectors a DDC probe failure indicates + * that no cable is connected. + */ + return connector_status_disconnected; + + case DRM_MODE_CONNECTOR_Composite: + case DRM_MODE_CONNECTOR_SVIDEO: + case DRM_MODE_CONNECTOR_VGA: + default: + /* + * Composite and S-Video connectors have no other detection + * mean than the HPD GPIO. For VGA connectors, even if we have + * an I2C bus, we can't assume that the cable is disconnected + * if drm_probe_ddc fails, as some cables don't wire the DDC + * pins. + */ + return connector_status_unknown; + } +} + +static struct edid *display_connector_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct display_connector *conn = to_display_connector(bridge); + + return drm_get_edid(connector, conn->bridge.ddc); +} + +static const struct drm_bridge_funcs display_connector_bridge_funcs = { + .attach = display_connector_attach, + .detect = display_connector_detect, + .get_edid = display_connector_get_edid, +}; + +static irqreturn_t display_connector_hpd_irq(int irq, void *arg) +{ + struct display_connector *conn = arg; + struct drm_bridge *bridge = &conn->bridge; + + drm_bridge_hpd_notify(bridge, display_connector_detect(bridge)); + + return IRQ_HANDLED; +} + +static int display_connector_probe(struct platform_device *pdev) +{ + struct display_connector *conn; + unsigned int type; + const char *label = NULL; + int ret; + + conn = devm_kzalloc(&pdev->dev, sizeof(*conn), GFP_KERNEL); + if (!conn) + return -ENOMEM; + + platform_set_drvdata(pdev, conn); + + type = (uintptr_t)of_device_get_match_data(&pdev->dev); + + /* Get the exact connector type. */ + switch (type) { + case DRM_MODE_CONNECTOR_DVII: { + bool analog, digital; + + analog = of_property_read_bool(pdev->dev.of_node, "analog"); + digital = of_property_read_bool(pdev->dev.of_node, "digital"); + if (analog && !digital) { + conn->bridge.type = DRM_MODE_CONNECTOR_DVIA; + } else if (!analog && digital) { + conn->bridge.type = DRM_MODE_CONNECTOR_DVID; + } else if (analog && digital) { + conn->bridge.type = DRM_MODE_CONNECTOR_DVII; + } else { + dev_err(&pdev->dev, "DVI connector with no type\n"); + return -EINVAL; + } + break; + } + + case DRM_MODE_CONNECTOR_HDMIA: { + const char *hdmi_type; + + ret = of_property_read_string(pdev->dev.of_node, "type", + &hdmi_type); + if (ret < 0) { + dev_err(&pdev->dev, "HDMI connector with no type\n"); + return -EINVAL; + } + + if (!strcmp(hdmi_type, "a") || !strcmp(hdmi_type, "c") || + !strcmp(hdmi_type, "d") || !strcmp(hdmi_type, "e")) { + conn->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + } else if (!strcmp(hdmi_type, "b")) { + conn->bridge.type = DRM_MODE_CONNECTOR_HDMIB; + } else { + dev_err(&pdev->dev, + "Unsupported HDMI connector type '%s'\n", + hdmi_type); + return -EINVAL; + } + + break; + } + + default: + conn->bridge.type = type; + break; + } + + /* All the supported connector types support interlaced modes. */ + conn->bridge.interlace_allowed = true; + + /* Get the optional connector label. */ + of_property_read_string(pdev->dev.of_node, "label", &label); + + /* + * Get the HPD GPIO for DVI and HDMI connectors. If the GPIO can provide + * edge interrupts, register an interrupt handler. + */ + if (type == DRM_MODE_CONNECTOR_DVII || + type == DRM_MODE_CONNECTOR_HDMIA) { + conn->hpd_gpio = devm_gpiod_get_optional(&pdev->dev, "hpd", + GPIOD_IN); + if (IS_ERR(conn->hpd_gpio)) { + if (PTR_ERR(conn->hpd_gpio) != -EPROBE_DEFER) + dev_err(&pdev->dev, + "Unable to retrieve HPD GPIO\n"); + return PTR_ERR(conn->hpd_gpio); + } + + conn->hpd_irq = gpiod_to_irq(conn->hpd_gpio); + } else { + conn->hpd_irq = -EINVAL; + } + + if (conn->hpd_irq >= 0) { + ret = devm_request_threaded_irq(&pdev->dev, conn->hpd_irq, + NULL, display_connector_hpd_irq, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "HPD", conn); + if (ret) { + dev_info(&pdev->dev, + "Failed to request HPD edge interrupt, falling back to polling\n"); + conn->hpd_irq = -EINVAL; + } + } + + /* Retrieve the DDC I2C adapter for DVI, HDMI and VGA connectors. */ + if (type == DRM_MODE_CONNECTOR_DVII || + type == DRM_MODE_CONNECTOR_HDMIA || + type == DRM_MODE_CONNECTOR_VGA) { + struct device_node *phandle; + + phandle = of_parse_phandle(pdev->dev.of_node, "ddc-i2c-bus", 0); + if (phandle) { + conn->bridge.ddc = of_get_i2c_adapter_by_node(phandle); + of_node_put(phandle); + if (!conn->bridge.ddc) + return -EPROBE_DEFER; + } else { + dev_dbg(&pdev->dev, + "No I2C bus specified, disabling EDID readout\n"); + } + } + + conn->bridge.funcs = &display_connector_bridge_funcs; + conn->bridge.of_node = pdev->dev.of_node; + + if (conn->bridge.ddc) + conn->bridge.ops |= DRM_BRIDGE_OP_EDID + | DRM_BRIDGE_OP_DETECT; + if (conn->hpd_gpio) + conn->bridge.ops |= DRM_BRIDGE_OP_DETECT; + if (conn->hpd_irq >= 0) + conn->bridge.ops |= DRM_BRIDGE_OP_HPD; + + dev_dbg(&pdev->dev, + "Found %s display connector '%s' %s DDC bus and %s HPD GPIO (ops 0x%x)\n", + drm_get_connector_type_name(conn->bridge.type), + label ? label : "<unlabelled>", + conn->bridge.ddc ? "with" : "without", + conn->hpd_gpio ? "with" : "without", + conn->bridge.ops); + + drm_bridge_add(&conn->bridge); + + return 0; +} + +static int display_connector_remove(struct platform_device *pdev) +{ + struct display_connector *conn = platform_get_drvdata(pdev); + + drm_bridge_remove(&conn->bridge); + + if (!IS_ERR(conn->bridge.ddc)) + i2c_put_adapter(conn->bridge.ddc); + + return 0; +} + +static const struct of_device_id display_connector_match[] = { + { + .compatible = "composite-video-connector", + .data = (void *)DRM_MODE_CONNECTOR_Composite, + }, { + .compatible = "dvi-connector", + .data = (void *)DRM_MODE_CONNECTOR_DVII, + }, { + .compatible = "hdmi-connector", + .data = (void *)DRM_MODE_CONNECTOR_HDMIA, + }, { + .compatible = "svideo-connector", + .data = (void *)DRM_MODE_CONNECTOR_SVIDEO, + }, { + .compatible = "vga-connector", + .data = (void *)DRM_MODE_CONNECTOR_VGA, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, display_connector_match); + +static struct platform_driver display_connector_driver = { + .probe = display_connector_probe, + .remove = display_connector_remove, + .driver = { + .name = "display-connector", + .of_match_table = display_connector_match, + }, +}; +module_platform_driver(display_connector_driver); + +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_DESCRIPTION("Display connector driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/lontium-lt9611.c b/drivers/gpu/drm/bridge/lontium-lt9611.c new file mode 100644 index 000000000..660e05fa4 --- /dev/null +++ b/drivers/gpu/drm/bridge/lontium-lt9611.c @@ -0,0 +1,1248 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2019-2020. Linaro Limited. + */ + +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#include <sound/hdmi-codec.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#define EDID_SEG_SIZE 256 +#define EDID_LEN 32 +#define EDID_LOOP 8 +#define KEY_DDC_ACCS_DONE 0x02 +#define DDC_NO_ACK 0x50 + +#define LT9611_4LANES 0 + +struct lt9611 { + struct device *dev; + struct drm_bridge bridge; + struct drm_connector connector; + + struct regmap *regmap; + + struct device_node *dsi0_node; + struct device_node *dsi1_node; + struct mipi_dsi_device *dsi0; + struct mipi_dsi_device *dsi1; + struct platform_device *audio_pdev; + + bool ac_mode; + + struct gpio_desc *reset_gpio; + struct gpio_desc *enable_gpio; + + bool power_on; + bool sleep; + + struct regulator_bulk_data supplies[2]; + + struct i2c_client *client; + + enum drm_connector_status status; + + u8 edid_buf[EDID_SEG_SIZE]; + u32 vic; +}; + +#define LT9611_PAGE_CONTROL 0xff + +static const struct regmap_range_cfg lt9611_ranges[] = { + { + .name = "register_range", + .range_min = 0, + .range_max = 0x85ff, + .selector_reg = LT9611_PAGE_CONTROL, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x100, + }, +}; + +static const struct regmap_config lt9611_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xffff, + .ranges = lt9611_ranges, + .num_ranges = ARRAY_SIZE(lt9611_ranges), +}; + +struct lt9611_mode { + u16 hdisplay; + u16 vdisplay; + u8 vrefresh; + u8 lanes; + u8 intfs; +}; + +static struct lt9611_mode lt9611_modes[] = { + { 3840, 2160, 30, 4, 2 }, /* 3840x2160 24bit 30Hz 4Lane 2ports */ + { 1920, 1080, 60, 4, 1 }, /* 1080P 24bit 60Hz 4lane 1port */ + { 1920, 1080, 30, 3, 1 }, /* 1080P 24bit 30Hz 3lane 1port */ + { 1920, 1080, 24, 3, 1 }, + { 720, 480, 60, 4, 1 }, + { 720, 576, 50, 2, 1 }, + { 640, 480, 60, 2, 1 }, +}; + +static struct lt9611 *bridge_to_lt9611(struct drm_bridge *bridge) +{ + return container_of(bridge, struct lt9611, bridge); +} + +static struct lt9611 *connector_to_lt9611(struct drm_connector *connector) +{ + return container_of(connector, struct lt9611, connector); +} + +static int lt9611_mipi_input_analog(struct lt9611 *lt9611) +{ + const struct reg_sequence reg_cfg[] = { + { 0x8106, 0x40 }, /* port A rx current */ + { 0x810a, 0xfe }, /* port A ldo voltage set */ + { 0x810b, 0xbf }, /* enable port A lprx */ + { 0x8111, 0x40 }, /* port B rx current */ + { 0x8115, 0xfe }, /* port B ldo voltage set */ + { 0x8116, 0xbf }, /* enable port B lprx */ + + { 0x811c, 0x03 }, /* PortA clk lane no-LP mode */ + { 0x8120, 0x03 }, /* PortB clk lane with-LP mode */ + }; + + return regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg)); +} + +static int lt9611_mipi_input_digital(struct lt9611 *lt9611, + const struct drm_display_mode *mode) +{ + struct reg_sequence reg_cfg[] = { + { 0x8300, LT9611_4LANES }, + { 0x830a, 0x00 }, + { 0x824f, 0x80 }, + { 0x8250, 0x10 }, + { 0x8302, 0x0a }, + { 0x8306, 0x0a }, + }; + + if (mode->hdisplay == 3840) + reg_cfg[1].def = 0x03; + + return regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg)); +} + +static void lt9611_mipi_video_setup(struct lt9611 *lt9611, + const struct drm_display_mode *mode) +{ + u32 h_total, hactive, hsync_len, hfront_porch, hsync_porch; + u32 v_total, vactive, vsync_len, vfront_porch, vsync_porch; + + h_total = mode->htotal; + v_total = mode->vtotal; + + hactive = mode->hdisplay; + hsync_len = mode->hsync_end - mode->hsync_start; + hfront_porch = mode->hsync_start - mode->hdisplay; + hsync_porch = hsync_len + mode->htotal - mode->hsync_end; + + vactive = mode->vdisplay; + vsync_len = mode->vsync_end - mode->vsync_start; + vfront_porch = mode->vsync_start - mode->vdisplay; + vsync_porch = vsync_len + mode->vtotal - mode->vsync_end; + + regmap_write(lt9611->regmap, 0x830d, (u8)(v_total / 256)); + regmap_write(lt9611->regmap, 0x830e, (u8)(v_total % 256)); + + regmap_write(lt9611->regmap, 0x830f, (u8)(vactive / 256)); + regmap_write(lt9611->regmap, 0x8310, (u8)(vactive % 256)); + + regmap_write(lt9611->regmap, 0x8311, (u8)(h_total / 256)); + regmap_write(lt9611->regmap, 0x8312, (u8)(h_total % 256)); + + regmap_write(lt9611->regmap, 0x8313, (u8)(hactive / 256)); + regmap_write(lt9611->regmap, 0x8314, (u8)(hactive % 256)); + + regmap_write(lt9611->regmap, 0x8315, (u8)(vsync_len % 256)); + regmap_write(lt9611->regmap, 0x8316, (u8)(hsync_len % 256)); + + regmap_write(lt9611->regmap, 0x8317, (u8)(vfront_porch % 256)); + + regmap_write(lt9611->regmap, 0x8318, (u8)(vsync_porch % 256)); + + regmap_write(lt9611->regmap, 0x8319, (u8)(hfront_porch % 256)); + + regmap_write(lt9611->regmap, 0x831a, (u8)(hsync_porch / 256) | + ((hfront_porch / 256) << 4)); + regmap_write(lt9611->regmap, 0x831b, (u8)(hsync_porch % 256)); +} + +static void lt9611_pcr_setup(struct lt9611 *lt9611, const struct drm_display_mode *mode, unsigned int postdiv) +{ + unsigned int pcr_m = mode->clock * 5 * postdiv / 27000; + const struct reg_sequence reg_cfg[] = { + { 0x830b, 0x01 }, + { 0x830c, 0x10 }, + { 0x8348, 0x00 }, + { 0x8349, 0x81 }, + + /* stage 1 */ + { 0x8321, 0x4a }, + { 0x8324, 0x71 }, + { 0x8325, 0x30 }, + { 0x832a, 0x01 }, + + /* stage 2 */ + { 0x834a, 0x40 }, + + /* MK limit */ + { 0x832d, 0x38 }, + { 0x8331, 0x08 }, + }; + const struct reg_sequence reg_cfg2[] = { + { 0x830b, 0x03 }, + { 0x830c, 0xd0 }, + { 0x8348, 0x03 }, + { 0x8349, 0xe0 }, + { 0x8324, 0x72 }, + { 0x8325, 0x00 }, + { 0x832a, 0x01 }, + { 0x834a, 0x10 }, + }; + u8 pol = 0x10; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + pol |= 0x2; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + pol |= 0x1; + regmap_write(lt9611->regmap, 0x831d, pol); + + if (mode->hdisplay == 3840) + regmap_multi_reg_write(lt9611->regmap, reg_cfg2, ARRAY_SIZE(reg_cfg2)); + else + regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg)); + + regmap_write(lt9611->regmap, 0x8326, pcr_m); + + /* pcr rst */ + regmap_write(lt9611->regmap, 0x8011, 0x5a); + regmap_write(lt9611->regmap, 0x8011, 0xfa); +} + +static int lt9611_pll_setup(struct lt9611 *lt9611, const struct drm_display_mode *mode, unsigned int *postdiv) +{ + unsigned int pclk = mode->clock; + const struct reg_sequence reg_cfg[] = { + /* txpll init */ + { 0x8123, 0x40 }, + { 0x8124, 0x64 }, + { 0x8125, 0x80 }, + { 0x8126, 0x55 }, + { 0x812c, 0x37 }, + { 0x812f, 0x01 }, + { 0x8126, 0x55 }, + { 0x8127, 0x66 }, + { 0x8128, 0x88 }, + { 0x812a, 0x20 }, + }; + + regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg)); + + if (pclk > 150000) { + regmap_write(lt9611->regmap, 0x812d, 0x88); + *postdiv = 1; + } else if (pclk > 70000) { + regmap_write(lt9611->regmap, 0x812d, 0x99); + *postdiv = 2; + } else { + regmap_write(lt9611->regmap, 0x812d, 0xaa); + *postdiv = 4; + } + + /* + * first divide pclk by 2 first + * - write divide by 64k to 19:16 bits which means shift by 17 + * - write divide by 256 to 15:8 bits which means shift by 9 + * - write remainder to 7:0 bits, which means shift by 1 + */ + regmap_write(lt9611->regmap, 0x82e3, pclk >> 17); /* pclk[19:16] */ + regmap_write(lt9611->regmap, 0x82e4, pclk >> 9); /* pclk[15:8] */ + regmap_write(lt9611->regmap, 0x82e5, pclk >> 1); /* pclk[7:0] */ + + regmap_write(lt9611->regmap, 0x82de, 0x20); + regmap_write(lt9611->regmap, 0x82de, 0xe0); + + regmap_write(lt9611->regmap, 0x8016, 0xf1); + regmap_write(lt9611->regmap, 0x8016, 0xf3); + + return 0; +} + +static int lt9611_read_video_check(struct lt9611 *lt9611, unsigned int reg) +{ + unsigned int temp, temp2; + int ret; + + ret = regmap_read(lt9611->regmap, reg, &temp); + if (ret) + return ret; + temp <<= 8; + ret = regmap_read(lt9611->regmap, reg + 1, &temp2); + if (ret) + return ret; + + return (temp + temp2); +} + +static int lt9611_video_check(struct lt9611 *lt9611) +{ + u32 v_total, vactive, hactive_a, hactive_b, h_total_sysclk; + int temp; + + /* top module video check */ + + /* vactive */ + temp = lt9611_read_video_check(lt9611, 0x8282); + if (temp < 0) + goto end; + vactive = temp; + + /* v_total */ + temp = lt9611_read_video_check(lt9611, 0x826c); + if (temp < 0) + goto end; + v_total = temp; + + /* h_total_sysclk */ + temp = lt9611_read_video_check(lt9611, 0x8286); + if (temp < 0) + goto end; + h_total_sysclk = temp; + + /* hactive_a */ + temp = lt9611_read_video_check(lt9611, 0x8382); + if (temp < 0) + goto end; + hactive_a = temp / 3; + + /* hactive_b */ + temp = lt9611_read_video_check(lt9611, 0x8386); + if (temp < 0) + goto end; + hactive_b = temp / 3; + + dev_info(lt9611->dev, + "video check: hactive_a=%d, hactive_b=%d, vactive=%d, v_total=%d, h_total_sysclk=%d\n", + hactive_a, hactive_b, vactive, v_total, h_total_sysclk); + + return 0; + +end: + dev_err(lt9611->dev, "read video check error\n"); + return temp; +} + +static void lt9611_hdmi_tx_digital(struct lt9611 *lt9611) +{ + regmap_write(lt9611->regmap, 0x8443, 0x46 - lt9611->vic); + regmap_write(lt9611->regmap, 0x8447, lt9611->vic); + regmap_write(lt9611->regmap, 0x843d, 0x0a); /* UD1 infoframe */ + + regmap_write(lt9611->regmap, 0x82d6, 0x8c); + regmap_write(lt9611->regmap, 0x82d7, 0x04); +} + +static void lt9611_hdmi_tx_phy(struct lt9611 *lt9611) +{ + struct reg_sequence reg_cfg[] = { + { 0x8130, 0x6a }, + { 0x8131, 0x44 }, /* HDMI DC mode */ + { 0x8132, 0x4a }, + { 0x8133, 0x0b }, + { 0x8134, 0x00 }, + { 0x8135, 0x00 }, + { 0x8136, 0x00 }, + { 0x8137, 0x44 }, + { 0x813f, 0x0f }, + { 0x8140, 0xa0 }, + { 0x8141, 0xa0 }, + { 0x8142, 0xa0 }, + { 0x8143, 0xa0 }, + { 0x8144, 0x0a }, + }; + + /* HDMI AC mode */ + if (lt9611->ac_mode) + reg_cfg[2].def = 0x73; + + regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg)); +} + +static irqreturn_t lt9611_irq_thread_handler(int irq, void *dev_id) +{ + struct lt9611 *lt9611 = dev_id; + unsigned int irq_flag0 = 0; + unsigned int irq_flag3 = 0; + + regmap_read(lt9611->regmap, 0x820f, &irq_flag3); + regmap_read(lt9611->regmap, 0x820c, &irq_flag0); + + /* hpd changed low */ + if (irq_flag3 & 0x80) { + dev_info(lt9611->dev, "hdmi cable disconnected\n"); + + regmap_write(lt9611->regmap, 0x8207, 0xbf); + regmap_write(lt9611->regmap, 0x8207, 0x3f); + } + + /* hpd changed high */ + if (irq_flag3 & 0x40) { + dev_info(lt9611->dev, "hdmi cable connected\n"); + + regmap_write(lt9611->regmap, 0x8207, 0x7f); + regmap_write(lt9611->regmap, 0x8207, 0x3f); + } + + if (irq_flag3 & 0xc0 && lt9611->bridge.dev) + drm_kms_helper_hotplug_event(lt9611->bridge.dev); + + /* video input changed */ + if (irq_flag0 & 0x01) { + dev_info(lt9611->dev, "video input changed\n"); + regmap_write(lt9611->regmap, 0x829e, 0xff); + regmap_write(lt9611->regmap, 0x829e, 0xf7); + regmap_write(lt9611->regmap, 0x8204, 0xff); + regmap_write(lt9611->regmap, 0x8204, 0xfe); + } + + return IRQ_HANDLED; +} + +static void lt9611_enable_hpd_interrupts(struct lt9611 *lt9611) +{ + unsigned int val; + + regmap_read(lt9611->regmap, 0x8203, &val); + + val &= ~0xc0; + regmap_write(lt9611->regmap, 0x8203, val); + regmap_write(lt9611->regmap, 0x8207, 0xff); /* clear */ + regmap_write(lt9611->regmap, 0x8207, 0x3f); +} + +static void lt9611_sleep_setup(struct lt9611 *lt9611) +{ + const struct reg_sequence sleep_setup[] = { + { 0x8024, 0x76 }, + { 0x8023, 0x01 }, + { 0x8157, 0x03 }, /* set addr pin as output */ + { 0x8149, 0x0b }, + + { 0x8102, 0x48 }, /* MIPI Rx power down */ + { 0x8123, 0x80 }, + { 0x8130, 0x00 }, + { 0x8011, 0x0a }, + }; + + regmap_multi_reg_write(lt9611->regmap, + sleep_setup, ARRAY_SIZE(sleep_setup)); + lt9611->sleep = true; +} + +static int lt9611_power_on(struct lt9611 *lt9611) +{ + int ret; + const struct reg_sequence seq[] = { + /* LT9611_System_Init */ + { 0x8101, 0x18 }, /* sel xtal clock */ + + /* timer for frequency meter */ + { 0x821b, 0x69 }, /* timer 2 */ + { 0x821c, 0x78 }, + { 0x82cb, 0x69 }, /* timer 1 */ + { 0x82cc, 0x78 }, + + /* irq init */ + { 0x8251, 0x01 }, + { 0x8258, 0x0a }, /* hpd irq */ + { 0x8259, 0x80 }, /* hpd debounce width */ + { 0x829e, 0xf7 }, /* video check irq */ + + /* power consumption for work */ + { 0x8004, 0xf0 }, + { 0x8006, 0xf0 }, + { 0x800a, 0x80 }, + { 0x800b, 0x40 }, + { 0x800d, 0xef }, + { 0x8011, 0xfa }, + }; + + if (lt9611->power_on) + return 0; + + ret = regmap_multi_reg_write(lt9611->regmap, seq, ARRAY_SIZE(seq)); + if (!ret) + lt9611->power_on = true; + + return ret; +} + +static int lt9611_power_off(struct lt9611 *lt9611) +{ + int ret; + + ret = regmap_write(lt9611->regmap, 0x8130, 0x6a); + if (!ret) + lt9611->power_on = false; + + return ret; +} + +static void lt9611_reset(struct lt9611 *lt9611) +{ + gpiod_set_value_cansleep(lt9611->reset_gpio, 1); + msleep(20); + + gpiod_set_value_cansleep(lt9611->reset_gpio, 0); + msleep(20); + + gpiod_set_value_cansleep(lt9611->reset_gpio, 1); + msleep(100); +} + +static void lt9611_assert_5v(struct lt9611 *lt9611) +{ + if (!lt9611->enable_gpio) + return; + + gpiod_set_value_cansleep(lt9611->enable_gpio, 1); + msleep(20); +} + +static int lt9611_regulator_init(struct lt9611 *lt9611) +{ + int ret; + + lt9611->supplies[0].supply = "vdd"; + lt9611->supplies[1].supply = "vcc"; + + ret = devm_regulator_bulk_get(lt9611->dev, 2, lt9611->supplies); + if (ret < 0) + return ret; + + return regulator_set_load(lt9611->supplies[0].consumer, 300000); +} + +static int lt9611_regulator_enable(struct lt9611 *lt9611) +{ + int ret; + + ret = regulator_enable(lt9611->supplies[0].consumer); + if (ret < 0) + return ret; + + usleep_range(1000, 10000); + + ret = regulator_enable(lt9611->supplies[1].consumer); + if (ret < 0) { + regulator_disable(lt9611->supplies[0].consumer); + return ret; + } + + return 0; +} + +static struct lt9611_mode *lt9611_find_mode(const struct drm_display_mode *mode) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(lt9611_modes); i++) { + if (lt9611_modes[i].hdisplay == mode->hdisplay && + lt9611_modes[i].vdisplay == mode->vdisplay && + lt9611_modes[i].vrefresh == drm_mode_vrefresh(mode)) { + return <9611_modes[i]; + } + } + + return NULL; +} + +/* connector funcs */ +static enum drm_connector_status +lt9611_connector_detect(struct drm_connector *connector, bool force) +{ + struct lt9611 *lt9611 = connector_to_lt9611(connector); + unsigned int reg_val = 0; + int connected = 0; + + regmap_read(lt9611->regmap, 0x825e, ®_val); + connected = (reg_val & BIT(2)); + + lt9611->status = connected ? connector_status_connected : + connector_status_disconnected; + + return lt9611->status; +} + +static int lt9611_read_edid(struct lt9611 *lt9611) +{ + unsigned int temp; + int ret = 0; + int i, j; + + /* memset to clear old buffer, if any */ + memset(lt9611->edid_buf, 0, sizeof(lt9611->edid_buf)); + + regmap_write(lt9611->regmap, 0x8503, 0xc9); + + /* 0xA0 is EDID device address */ + regmap_write(lt9611->regmap, 0x8504, 0xa0); + /* 0x00 is EDID offset address */ + regmap_write(lt9611->regmap, 0x8505, 0x00); + + /* length for read */ + regmap_write(lt9611->regmap, 0x8506, EDID_LEN); + regmap_write(lt9611->regmap, 0x8514, 0x7f); + + for (i = 0; i < EDID_LOOP; i++) { + /* offset address */ + regmap_write(lt9611->regmap, 0x8505, i * EDID_LEN); + regmap_write(lt9611->regmap, 0x8507, 0x36); + regmap_write(lt9611->regmap, 0x8507, 0x31); + regmap_write(lt9611->regmap, 0x8507, 0x37); + usleep_range(5000, 10000); + + regmap_read(lt9611->regmap, 0x8540, &temp); + + if (temp & KEY_DDC_ACCS_DONE) { + for (j = 0; j < EDID_LEN; j++) { + regmap_read(lt9611->regmap, 0x8583, &temp); + lt9611->edid_buf[i * EDID_LEN + j] = temp; + } + + } else if (temp & DDC_NO_ACK) { /* DDC No Ack or Abitration lost */ + dev_err(lt9611->dev, "read edid failed: no ack\n"); + ret = -EIO; + goto end; + + } else { + dev_err(lt9611->dev, "read edid failed: access not done\n"); + ret = -EIO; + goto end; + } + } + +end: + regmap_write(lt9611->regmap, 0x8507, 0x1f); + return ret; +} + +static int +lt9611_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len) +{ + struct lt9611 *lt9611 = data; + int ret; + + if (len > 128) + return -EINVAL; + + /* supports up to 1 extension block */ + /* TODO: add support for more extension blocks */ + if (block > 1) + return -EINVAL; + + if (block == 0) { + ret = lt9611_read_edid(lt9611); + if (ret) { + dev_err(lt9611->dev, "edid read failed\n"); + return ret; + } + } + + block %= 2; + memcpy(buf, lt9611->edid_buf + (block * 128), len); + + return 0; +} + +static int lt9611_connector_get_modes(struct drm_connector *connector) +{ + struct lt9611 *lt9611 = connector_to_lt9611(connector); + unsigned int count; + struct edid *edid; + + lt9611_power_on(lt9611); + edid = drm_do_get_edid(connector, lt9611_get_edid_block, lt9611); + drm_connector_update_edid_property(connector, edid); + count = drm_add_edid_modes(connector, edid); + kfree(edid); + + return count; +} + +static enum drm_mode_status +lt9611_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct lt9611_mode *lt9611_mode = lt9611_find_mode(mode); + + return lt9611_mode ? MODE_OK : MODE_BAD; +} + +/* bridge funcs */ +static void lt9611_bridge_enable(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + + if (lt9611_power_on(lt9611)) { + dev_err(lt9611->dev, "power on failed\n"); + return; + } + + lt9611_mipi_input_analog(lt9611); + lt9611_hdmi_tx_digital(lt9611); + lt9611_hdmi_tx_phy(lt9611); + + msleep(500); + + lt9611_video_check(lt9611); + + /* Enable HDMI output */ + regmap_write(lt9611->regmap, 0x8130, 0xea); +} + +static void lt9611_bridge_disable(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + int ret; + + /* Disable HDMI output */ + ret = regmap_write(lt9611->regmap, 0x8130, 0x6a); + if (ret) { + dev_err(lt9611->dev, "video on failed\n"); + return; + } + + if (lt9611_power_off(lt9611)) { + dev_err(lt9611->dev, "power on failed\n"); + return; + } +} + +static struct +drm_connector_helper_funcs lt9611_bridge_connector_helper_funcs = { + .get_modes = lt9611_connector_get_modes, + .mode_valid = lt9611_connector_mode_valid, +}; + +static const struct drm_connector_funcs lt9611_bridge_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = lt9611_connector_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static struct mipi_dsi_device *lt9611_attach_dsi(struct lt9611 *lt9611, + struct device_node *dsi_node) +{ + const struct mipi_dsi_device_info info = { "lt9611", 0, lt9611->dev->of_node}; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *host; + int ret; + + host = of_find_mipi_dsi_host_by_node(dsi_node); + if (!host) { + dev_err(lt9611->dev, "failed to find dsi host\n"); + return ERR_PTR(-EPROBE_DEFER); + } + + dsi = mipi_dsi_device_register_full(host, &info); + if (IS_ERR(dsi)) { + dev_err(lt9611->dev, "failed to create dsi device\n"); + return dsi; + } + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_VIDEO_HSE; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(lt9611->dev, "failed to attach dsi to host\n"); + mipi_dsi_device_unregister(dsi); + return ERR_PTR(ret); + } + + return dsi; +} + +static void lt9611_bridge_detach(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + + if (lt9611->dsi1) { + mipi_dsi_detach(lt9611->dsi1); + mipi_dsi_device_unregister(lt9611->dsi1); + } + + mipi_dsi_detach(lt9611->dsi0); + mipi_dsi_device_unregister(lt9611->dsi0); +} + +static int lt9611_connector_init(struct drm_bridge *bridge, struct lt9611 *lt9611) +{ + int ret; + + ret = drm_connector_init(bridge->dev, <9611->connector, + <9611_bridge_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_helper_add(<9611->connector, + <9611_bridge_connector_helper_funcs); + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + drm_connector_attach_encoder(<9611->connector, bridge->encoder); + + return 0; +} + +static int lt9611_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + int ret; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + ret = lt9611_connector_init(bridge, lt9611); + if (ret < 0) + return ret; + } + + /* Attach primary DSI */ + lt9611->dsi0 = lt9611_attach_dsi(lt9611, lt9611->dsi0_node); + if (IS_ERR(lt9611->dsi0)) + return PTR_ERR(lt9611->dsi0); + + /* Attach secondary DSI, if specified */ + if (lt9611->dsi1_node) { + lt9611->dsi1 = lt9611_attach_dsi(lt9611, lt9611->dsi1_node); + if (IS_ERR(lt9611->dsi1)) { + ret = PTR_ERR(lt9611->dsi1); + goto err_unregister_dsi0; + } + } + + return 0; + +err_unregister_dsi0: + lt9611_bridge_detach(bridge); + drm_connector_cleanup(<9611->connector); + mipi_dsi_device_unregister(lt9611->dsi0); + + return ret; +} + +static enum drm_mode_status lt9611_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct lt9611_mode *lt9611_mode = lt9611_find_mode(mode); + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + + if (!lt9611_mode) + return MODE_BAD; + else if (lt9611_mode->intfs > 1 && !lt9611->dsi1) + return MODE_PANEL; + else + return MODE_OK; +} + +static void lt9611_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + static const struct reg_sequence reg_cfg[] = { + { 0x8102, 0x12 }, + { 0x8123, 0x40 }, + { 0x8130, 0xea }, + { 0x8011, 0xfa }, + }; + + if (!lt9611->sleep) + return; + + regmap_multi_reg_write(lt9611->regmap, + reg_cfg, ARRAY_SIZE(reg_cfg)); + + lt9611->sleep = false; +} + +static void lt9611_bridge_post_disable(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + + lt9611_sleep_setup(lt9611); +} + +static void lt9611_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adj_mode) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + struct hdmi_avi_infoframe avi_frame; + unsigned int postdiv; + int ret; + + lt9611_bridge_pre_enable(bridge); + + lt9611_mipi_input_digital(lt9611, mode); + lt9611_pll_setup(lt9611, mode, &postdiv); + lt9611_mipi_video_setup(lt9611, mode); + lt9611_pcr_setup(lt9611, mode, postdiv); + + ret = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame, + <9611->connector, + mode); + if (!ret) + lt9611->vic = avi_frame.video_code; +} + +static enum drm_connector_status lt9611_bridge_detect(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + unsigned int reg_val = 0; + int connected; + + regmap_read(lt9611->regmap, 0x825e, ®_val); + connected = reg_val & BIT(2); + + lt9611->status = connected ? connector_status_connected : + connector_status_disconnected; + + return lt9611->status; +} + +static struct edid *lt9611_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + + lt9611_power_on(lt9611); + return drm_do_get_edid(connector, lt9611_get_edid_block, lt9611); +} + +static void lt9611_bridge_hpd_enable(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + + lt9611_enable_hpd_interrupts(lt9611); +} + +static const struct drm_bridge_funcs lt9611_bridge_funcs = { + .attach = lt9611_bridge_attach, + .detach = lt9611_bridge_detach, + .mode_valid = lt9611_bridge_mode_valid, + .enable = lt9611_bridge_enable, + .disable = lt9611_bridge_disable, + .post_disable = lt9611_bridge_post_disable, + .mode_set = lt9611_bridge_mode_set, + .detect = lt9611_bridge_detect, + .get_edid = lt9611_bridge_get_edid, + .hpd_enable = lt9611_bridge_hpd_enable, +}; + +static int lt9611_parse_dt(struct device *dev, + struct lt9611 *lt9611) +{ + lt9611->dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1); + if (!lt9611->dsi0_node) { + dev_err(lt9611->dev, "failed to get remote node for primary dsi\n"); + return -ENODEV; + } + + lt9611->dsi1_node = of_graph_get_remote_node(dev->of_node, 1, -1); + + lt9611->ac_mode = of_property_read_bool(dev->of_node, "lt,ac-mode"); + + return 0; +} + +static int lt9611_gpio_init(struct lt9611 *lt9611) +{ + struct device *dev = lt9611->dev; + + lt9611->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(lt9611->reset_gpio)) { + dev_err(dev, "failed to acquire reset gpio\n"); + return PTR_ERR(lt9611->reset_gpio); + } + + lt9611->enable_gpio = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(lt9611->enable_gpio)) { + dev_err(dev, "failed to acquire enable gpio\n"); + return PTR_ERR(lt9611->enable_gpio); + } + + return 0; +} + +static int lt9611_read_device_rev(struct lt9611 *lt9611) +{ + unsigned int rev; + int ret; + + regmap_write(lt9611->regmap, 0x80ee, 0x01); + ret = regmap_read(lt9611->regmap, 0x8002, &rev); + if (ret) + dev_err(lt9611->dev, "failed to read revision: %d\n", ret); + else + dev_info(lt9611->dev, "LT9611 revision: 0x%x\n", rev); + + return ret; +} + +static int lt9611_hdmi_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct lt9611 *lt9611 = data; + + if (hparms->sample_rate == 48000) + regmap_write(lt9611->regmap, 0x840f, 0x2b); + else if (hparms->sample_rate == 96000) + regmap_write(lt9611->regmap, 0x840f, 0xab); + else + return -EINVAL; + + regmap_write(lt9611->regmap, 0x8435, 0x00); + regmap_write(lt9611->regmap, 0x8436, 0x18); + regmap_write(lt9611->regmap, 0x8437, 0x00); + + return 0; +} + +static int lt9611_audio_startup(struct device *dev, void *data) +{ + struct lt9611 *lt9611 = data; + + regmap_write(lt9611->regmap, 0x82d6, 0x8c); + regmap_write(lt9611->regmap, 0x82d7, 0x04); + + regmap_write(lt9611->regmap, 0x8406, 0x08); + regmap_write(lt9611->regmap, 0x8407, 0x10); + + regmap_write(lt9611->regmap, 0x8434, 0xd5); + + return 0; +} + +static void lt9611_audio_shutdown(struct device *dev, void *data) +{ + struct lt9611 *lt9611 = data; + + regmap_write(lt9611->regmap, 0x8406, 0x00); + regmap_write(lt9611->regmap, 0x8407, 0x00); +} + +static int lt9611_hdmi_i2s_get_dai_id(struct snd_soc_component *component, + struct device_node *endpoint) +{ + struct of_endpoint of_ep; + int ret; + + ret = of_graph_parse_endpoint(endpoint, &of_ep); + if (ret < 0) + return ret; + + /* + * HDMI sound should be located as reg = <2> + * Then, it is sound port 0 + */ + if (of_ep.port == 2) + return 0; + + return -EINVAL; +} + +static const struct hdmi_codec_ops lt9611_codec_ops = { + .hw_params = lt9611_hdmi_hw_params, + .audio_shutdown = lt9611_audio_shutdown, + .audio_startup = lt9611_audio_startup, + .get_dai_id = lt9611_hdmi_i2s_get_dai_id, +}; + +static struct hdmi_codec_pdata codec_data = { + .ops = <9611_codec_ops, + .max_i2s_channels = 8, + .i2s = 1, +}; + +static int lt9611_audio_init(struct device *dev, struct lt9611 *lt9611) +{ + codec_data.data = lt9611; + lt9611->audio_pdev = + platform_device_register_data(dev, HDMI_CODEC_DRV_NAME, + PLATFORM_DEVID_AUTO, + &codec_data, sizeof(codec_data)); + + return PTR_ERR_OR_ZERO(lt9611->audio_pdev); +} + +static void lt9611_audio_exit(struct lt9611 *lt9611) +{ + if (lt9611->audio_pdev) { + platform_device_unregister(lt9611->audio_pdev); + lt9611->audio_pdev = NULL; + } +} + +static int lt9611_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lt9611 *lt9611; + struct device *dev = &client->dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(dev, "device doesn't support I2C\n"); + return -ENODEV; + } + + lt9611 = devm_kzalloc(dev, sizeof(*lt9611), GFP_KERNEL); + if (!lt9611) + return -ENOMEM; + + lt9611->dev = &client->dev; + lt9611->client = client; + lt9611->sleep = false; + + lt9611->regmap = devm_regmap_init_i2c(client, <9611_regmap_config); + if (IS_ERR(lt9611->regmap)) { + dev_err(lt9611->dev, "regmap i2c init failed\n"); + return PTR_ERR(lt9611->regmap); + } + + ret = lt9611_parse_dt(&client->dev, lt9611); + if (ret) { + dev_err(dev, "failed to parse device tree\n"); + return ret; + } + + ret = lt9611_gpio_init(lt9611); + if (ret < 0) + goto err_of_put; + + ret = lt9611_regulator_init(lt9611); + if (ret < 0) + goto err_of_put; + + lt9611_assert_5v(lt9611); + + ret = lt9611_regulator_enable(lt9611); + if (ret) + goto err_of_put; + + lt9611_reset(lt9611); + + ret = lt9611_read_device_rev(lt9611); + if (ret) { + dev_err(dev, "failed to read chip rev\n"); + goto err_disable_regulators; + } + + ret = devm_request_threaded_irq(dev, client->irq, NULL, + lt9611_irq_thread_handler, + IRQF_ONESHOT, "lt9611", lt9611); + if (ret) { + dev_err(dev, "failed to request irq\n"); + goto err_disable_regulators; + } + + i2c_set_clientdata(client, lt9611); + + lt9611->bridge.funcs = <9611_bridge_funcs; + lt9611->bridge.of_node = client->dev.of_node; + lt9611->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD | DRM_BRIDGE_OP_MODES; + lt9611->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + drm_bridge_add(<9611->bridge); + + lt9611_enable_hpd_interrupts(lt9611); + + return lt9611_audio_init(dev, lt9611); + +err_disable_regulators: + regulator_bulk_disable(ARRAY_SIZE(lt9611->supplies), lt9611->supplies); + +err_of_put: + of_node_put(lt9611->dsi1_node); + of_node_put(lt9611->dsi0_node); + + return ret; +} + +static int lt9611_remove(struct i2c_client *client) +{ + struct lt9611 *lt9611 = i2c_get_clientdata(client); + + disable_irq(client->irq); + lt9611_audio_exit(lt9611); + drm_bridge_remove(<9611->bridge); + + regulator_bulk_disable(ARRAY_SIZE(lt9611->supplies), lt9611->supplies); + + of_node_put(lt9611->dsi1_node); + of_node_put(lt9611->dsi0_node); + + return 0; +} + +static struct i2c_device_id lt9611_id[] = { + { "lontium,lt9611", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, lt9611_id); + +static const struct of_device_id lt9611_match_table[] = { + { .compatible = "lontium,lt9611" }, + { } +}; +MODULE_DEVICE_TABLE(of, lt9611_match_table); + +static struct i2c_driver lt9611_driver = { + .driver = { + .name = "lt9611", + .of_match_table = lt9611_match_table, + }, + .probe = lt9611_probe, + .remove = lt9611_remove, + .id_table = lt9611_id, +}; +module_i2c_driver(lt9611_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/lvds-codec.c b/drivers/gpu/drm/bridge/lvds-codec.c new file mode 100644 index 000000000..f52ccffc1 --- /dev/null +++ b/drivers/gpu/drm/bridge/lvds-codec.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2019 Renesas Electronics Corporation + * Copyright (C) 2016 Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_panel.h> + +struct lvds_codec { + struct device *dev; + struct drm_bridge bridge; + struct drm_bridge *panel_bridge; + struct regulator *vcc; + struct gpio_desc *powerdown_gpio; + u32 connector_type; +}; + +static inline struct lvds_codec *to_lvds_codec(struct drm_bridge *bridge) +{ + return container_of(bridge, struct lvds_codec, bridge); +} + +static int lvds_codec_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct lvds_codec *lvds_codec = to_lvds_codec(bridge); + + return drm_bridge_attach(bridge->encoder, lvds_codec->panel_bridge, + bridge, flags); +} + +static void lvds_codec_enable(struct drm_bridge *bridge) +{ + struct lvds_codec *lvds_codec = to_lvds_codec(bridge); + int ret; + + ret = regulator_enable(lvds_codec->vcc); + if (ret) { + dev_err(lvds_codec->dev, + "Failed to enable regulator \"vcc\": %d\n", ret); + return; + } + + if (lvds_codec->powerdown_gpio) + gpiod_set_value_cansleep(lvds_codec->powerdown_gpio, 0); +} + +static void lvds_codec_disable(struct drm_bridge *bridge) +{ + struct lvds_codec *lvds_codec = to_lvds_codec(bridge); + int ret; + + if (lvds_codec->powerdown_gpio) + gpiod_set_value_cansleep(lvds_codec->powerdown_gpio, 1); + + ret = regulator_disable(lvds_codec->vcc); + if (ret) + dev_err(lvds_codec->dev, + "Failed to disable regulator \"vcc\": %d\n", ret); +} + +static const struct drm_bridge_funcs funcs = { + .attach = lvds_codec_attach, + .enable = lvds_codec_enable, + .disable = lvds_codec_disable, +}; + +static int lvds_codec_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *panel_node; + struct drm_panel *panel; + struct lvds_codec *lvds_codec; + int ret; + + lvds_codec = devm_kzalloc(dev, sizeof(*lvds_codec), GFP_KERNEL); + if (!lvds_codec) + return -ENOMEM; + + lvds_codec->dev = &pdev->dev; + lvds_codec->connector_type = (uintptr_t)of_device_get_match_data(dev); + + lvds_codec->vcc = devm_regulator_get(lvds_codec->dev, "power"); + if (IS_ERR(lvds_codec->vcc)) { + ret = PTR_ERR(lvds_codec->vcc); + if (ret != -EPROBE_DEFER) + dev_err(lvds_codec->dev, + "Unable to get \"vcc\" supply: %d\n", ret); + return ret; + } + + lvds_codec->powerdown_gpio = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(lvds_codec->powerdown_gpio)) + return dev_err_probe(dev, PTR_ERR(lvds_codec->powerdown_gpio), + "powerdown GPIO failure\n"); + + /* Locate the panel DT node. */ + panel_node = of_graph_get_remote_node(dev->of_node, 1, 0); + if (!panel_node) { + dev_dbg(dev, "panel DT node not found\n"); + return -ENXIO; + } + + panel = of_drm_find_panel(panel_node); + of_node_put(panel_node); + if (IS_ERR(panel)) { + dev_dbg(dev, "panel not found, deferring probe\n"); + return PTR_ERR(panel); + } + + lvds_codec->panel_bridge = + devm_drm_panel_bridge_add_typed(dev, panel, + lvds_codec->connector_type); + if (IS_ERR(lvds_codec->panel_bridge)) + return PTR_ERR(lvds_codec->panel_bridge); + + /* + * The panel_bridge bridge is attached to the panel's of_node, + * but we need a bridge attached to our of_node for our user + * to look up. + */ + lvds_codec->bridge.of_node = dev->of_node; + lvds_codec->bridge.funcs = &funcs; + drm_bridge_add(&lvds_codec->bridge); + + platform_set_drvdata(pdev, lvds_codec); + + return 0; +} + +static int lvds_codec_remove(struct platform_device *pdev) +{ + struct lvds_codec *lvds_codec = platform_get_drvdata(pdev); + + drm_bridge_remove(&lvds_codec->bridge); + + return 0; +} + +static const struct of_device_id lvds_codec_match[] = { + { + .compatible = "lvds-decoder", + .data = (void *)DRM_MODE_CONNECTOR_DPI, + }, + { + .compatible = "lvds-encoder", + .data = (void *)DRM_MODE_CONNECTOR_LVDS, + }, + { + .compatible = "thine,thc63lvdm83d", + .data = (void *)DRM_MODE_CONNECTOR_LVDS, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, lvds_codec_match); + +static struct platform_driver lvds_codec_driver = { + .probe = lvds_codec_probe, + .remove = lvds_codec_remove, + .driver = { + .name = "lvds-codec", + .of_match_table = lvds_codec_match, + }, +}; +module_platform_driver(lvds_codec_driver); + +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_DESCRIPTION("LVDS encoders and decoders"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c new file mode 100644 index 000000000..e41afcc53 --- /dev/null +++ b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c @@ -0,0 +1,465 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for MegaChips STDP4028 with GE B850v3 firmware (LVDS-DP) + * Driver for MegaChips STDP2690 with GE B850v3 firmware (DP-DP++) + + * Copyright (c) 2017, Collabora Ltd. + * Copyright (c) 2017, General Electric Company + + + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ + * display bridge of the GE B850v3. There are two physical bridges on the video + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The + * physical bridges are automatically configured by the input video signal, and + * the driver has no access to the video processing pipeline. The driver is + * only needed to read EDID from the STDP2690 and to handle HPD events from the + * STDP4028. The driver communicates with both bridges over i2c. The video + * signal pipeline is as follows: + * + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_edid.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#define EDID_EXT_BLOCK_CNT 0x7E + +#define STDP4028_IRQ_OUT_CONF_REG 0x02 +#define STDP4028_DPTX_IRQ_EN_REG 0x3C +#define STDP4028_DPTX_IRQ_STS_REG 0x3D +#define STDP4028_DPTX_STS_REG 0x3E + +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 + +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 +#define STDP4028_DPTX_IRQ_CONFIG \ + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) + +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 +#define STDP4028_DPTX_LINK_STS 0x1000 +#define STDP4028_CON_STATE_CONNECTED \ + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) + +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 +#define STDP4028_DPTX_LINK_CH_STS 0x2000 +#define STDP4028_DPTX_IRQ_CLEAR \ + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) + +static DEFINE_MUTEX(ge_b850v3_lvds_dev_mutex); + +struct ge_b850v3_lvds { + struct drm_connector connector; + struct drm_bridge bridge; + struct i2c_client *stdp4028_i2c; + struct i2c_client *stdp2690_i2c; +}; + +static struct ge_b850v3_lvds *ge_b850v3_lvds_ptr; + +static u8 *stdp2690_get_edid(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + unsigned char start = 0x00; + unsigned int total_size; + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = block, + } + }; + + if (!block) + return NULL; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID.\n"); + goto err; + } + + if (!drm_edid_block_valid(block, 0, false, NULL)) { + DRM_ERROR("Invalid EDID data\n"); + goto err; + } + + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; + if (total_size > EDID_LENGTH) { + kfree(block); + block = kmalloc(total_size, GFP_KERNEL); + if (!block) + return NULL; + + /* Yes, read the entire buffer, and do not skip the first + * EDID_LENGTH bytes. + */ + start = 0x00; + msgs[1].len = total_size; + msgs[1].buf = block; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID extension blocks.\n"); + goto err; + } + if (!drm_edid_block_valid(block, 1, false, NULL)) { + DRM_ERROR("Invalid EDID data\n"); + goto err; + } + } + + return block; + +err: + kfree(block); + return NULL; +} + +static struct edid *ge_b850v3_lvds_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct i2c_client *client; + + client = ge_b850v3_lvds_ptr->stdp2690_i2c; + + return (struct edid *)stdp2690_get_edid(client); +} + +static int ge_b850v3_lvds_get_modes(struct drm_connector *connector) +{ + struct edid *edid; + int num_modes; + + edid = ge_b850v3_lvds_get_edid(&ge_b850v3_lvds_ptr->bridge, connector); + + drm_connector_update_edid_property(connector, edid); + num_modes = drm_add_edid_modes(connector, edid); + kfree(edid); + + return num_modes; +} + +static enum drm_mode_status ge_b850v3_lvds_mode_valid( + struct drm_connector *connector, struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static const struct +drm_connector_helper_funcs ge_b850v3_lvds_connector_helper_funcs = { + .get_modes = ge_b850v3_lvds_get_modes, + .mode_valid = ge_b850v3_lvds_mode_valid, +}; + +static enum drm_connector_status ge_b850v3_lvds_bridge_detect(struct drm_bridge *bridge) +{ + struct i2c_client *stdp4028_i2c = + ge_b850v3_lvds_ptr->stdp4028_i2c; + s32 link_state; + + link_state = i2c_smbus_read_word_data(stdp4028_i2c, + STDP4028_DPTX_STS_REG); + + if (link_state == STDP4028_CON_STATE_CONNECTED) + return connector_status_connected; + + if (link_state == 0) + return connector_status_disconnected; + + return connector_status_unknown; +} + +static enum drm_connector_status ge_b850v3_lvds_detect(struct drm_connector *connector, + bool force) +{ + return ge_b850v3_lvds_bridge_detect(&ge_b850v3_lvds_ptr->bridge); +} + +static const struct drm_connector_funcs ge_b850v3_lvds_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ge_b850v3_lvds_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int ge_b850v3_lvds_create_connector(struct drm_bridge *bridge) +{ + struct drm_connector *connector = &ge_b850v3_lvds_ptr->connector; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, + &ge_b850v3_lvds_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, + &ge_b850v3_lvds_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + return drm_connector_attach_encoder(connector, bridge->encoder); +} + +static irqreturn_t ge_b850v3_lvds_irq_handler(int irq, void *dev_id) +{ + struct i2c_client *stdp4028_i2c + = ge_b850v3_lvds_ptr->stdp4028_i2c; + + i2c_smbus_write_word_data(stdp4028_i2c, + STDP4028_DPTX_IRQ_STS_REG, + STDP4028_DPTX_IRQ_CLEAR); + + if (ge_b850v3_lvds_ptr->bridge.dev) + drm_kms_helper_hotplug_event(ge_b850v3_lvds_ptr->bridge.dev); + + return IRQ_HANDLED; +} + +static int ge_b850v3_lvds_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct i2c_client *stdp4028_i2c + = ge_b850v3_lvds_ptr->stdp4028_i2c; + + /* Configures the bridge to re-enable interrupts after each ack. */ + i2c_smbus_write_word_data(stdp4028_i2c, + STDP4028_IRQ_OUT_CONF_REG, + STDP4028_DPTX_DP_IRQ_EN); + + /* Enable interrupts */ + i2c_smbus_write_word_data(stdp4028_i2c, + STDP4028_DPTX_IRQ_EN_REG, + STDP4028_DPTX_IRQ_CONFIG); + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return 0; + + return ge_b850v3_lvds_create_connector(bridge); +} + +static const struct drm_bridge_funcs ge_b850v3_lvds_funcs = { + .attach = ge_b850v3_lvds_attach, + .detect = ge_b850v3_lvds_bridge_detect, + .get_edid = ge_b850v3_lvds_get_edid, +}; + +static int ge_b850v3_lvds_init(struct device *dev) +{ + mutex_lock(&ge_b850v3_lvds_dev_mutex); + + if (ge_b850v3_lvds_ptr) + goto success; + + ge_b850v3_lvds_ptr = devm_kzalloc(dev, + sizeof(*ge_b850v3_lvds_ptr), + GFP_KERNEL); + + if (!ge_b850v3_lvds_ptr) { + mutex_unlock(&ge_b850v3_lvds_dev_mutex); + return -ENOMEM; + } + +success: + mutex_unlock(&ge_b850v3_lvds_dev_mutex); + return 0; +} + +static void ge_b850v3_lvds_remove(void) +{ + mutex_lock(&ge_b850v3_lvds_dev_mutex); + /* + * This check is to avoid both the drivers + * removing the bridge in their remove() function + */ + if (!ge_b850v3_lvds_ptr || + !ge_b850v3_lvds_ptr->stdp2690_i2c || + !ge_b850v3_lvds_ptr->stdp4028_i2c) + goto out; + + drm_bridge_remove(&ge_b850v3_lvds_ptr->bridge); + + ge_b850v3_lvds_ptr = NULL; +out: + mutex_unlock(&ge_b850v3_lvds_dev_mutex); +} + +static int ge_b850v3_register(void) +{ + struct i2c_client *stdp4028_i2c = ge_b850v3_lvds_ptr->stdp4028_i2c; + struct device *dev = &stdp4028_i2c->dev; + + /* drm bridge initialization */ + ge_b850v3_lvds_ptr->bridge.funcs = &ge_b850v3_lvds_funcs; + ge_b850v3_lvds_ptr->bridge.ops = DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID; + ge_b850v3_lvds_ptr->bridge.type = DRM_MODE_CONNECTOR_DisplayPort; + ge_b850v3_lvds_ptr->bridge.of_node = dev->of_node; + drm_bridge_add(&ge_b850v3_lvds_ptr->bridge); + + /* Clear pending interrupts since power up. */ + i2c_smbus_write_word_data(stdp4028_i2c, + STDP4028_DPTX_IRQ_STS_REG, + STDP4028_DPTX_IRQ_CLEAR); + + if (!stdp4028_i2c->irq) + return 0; + + return devm_request_threaded_irq(&stdp4028_i2c->dev, + stdp4028_i2c->irq, NULL, + ge_b850v3_lvds_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ge-b850v3-lvds-dp", ge_b850v3_lvds_ptr); +} + +static int stdp4028_ge_b850v3_fw_probe(struct i2c_client *stdp4028_i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &stdp4028_i2c->dev; + int ret; + + ret = ge_b850v3_lvds_init(dev); + + if (ret) + return ret; + + ge_b850v3_lvds_ptr->stdp4028_i2c = stdp4028_i2c; + i2c_set_clientdata(stdp4028_i2c, ge_b850v3_lvds_ptr); + + /* Only register after both bridges are probed */ + if (!ge_b850v3_lvds_ptr->stdp2690_i2c) + return 0; + + return ge_b850v3_register(); +} + +static int stdp4028_ge_b850v3_fw_remove(struct i2c_client *stdp4028_i2c) +{ + ge_b850v3_lvds_remove(); + + return 0; +} + +static const struct i2c_device_id stdp4028_ge_b850v3_fw_i2c_table[] = { + {"stdp4028_ge_fw", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, stdp4028_ge_b850v3_fw_i2c_table); + +static const struct of_device_id stdp4028_ge_b850v3_fw_match[] = { + { .compatible = "megachips,stdp4028-ge-b850v3-fw" }, + {}, +}; +MODULE_DEVICE_TABLE(of, stdp4028_ge_b850v3_fw_match); + +static struct i2c_driver stdp4028_ge_b850v3_fw_driver = { + .id_table = stdp4028_ge_b850v3_fw_i2c_table, + .probe = stdp4028_ge_b850v3_fw_probe, + .remove = stdp4028_ge_b850v3_fw_remove, + .driver = { + .name = "stdp4028-ge-b850v3-fw", + .of_match_table = stdp4028_ge_b850v3_fw_match, + }, +}; + +static int stdp2690_ge_b850v3_fw_probe(struct i2c_client *stdp2690_i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &stdp2690_i2c->dev; + int ret; + + ret = ge_b850v3_lvds_init(dev); + + if (ret) + return ret; + + ge_b850v3_lvds_ptr->stdp2690_i2c = stdp2690_i2c; + i2c_set_clientdata(stdp2690_i2c, ge_b850v3_lvds_ptr); + + /* Only register after both bridges are probed */ + if (!ge_b850v3_lvds_ptr->stdp4028_i2c) + return 0; + + return ge_b850v3_register(); +} + +static int stdp2690_ge_b850v3_fw_remove(struct i2c_client *stdp2690_i2c) +{ + ge_b850v3_lvds_remove(); + + return 0; +} + +static const struct i2c_device_id stdp2690_ge_b850v3_fw_i2c_table[] = { + {"stdp2690_ge_fw", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, stdp2690_ge_b850v3_fw_i2c_table); + +static const struct of_device_id stdp2690_ge_b850v3_fw_match[] = { + { .compatible = "megachips,stdp2690-ge-b850v3-fw" }, + {}, +}; +MODULE_DEVICE_TABLE(of, stdp2690_ge_b850v3_fw_match); + +static struct i2c_driver stdp2690_ge_b850v3_fw_driver = { + .id_table = stdp2690_ge_b850v3_fw_i2c_table, + .probe = stdp2690_ge_b850v3_fw_probe, + .remove = stdp2690_ge_b850v3_fw_remove, + .driver = { + .name = "stdp2690-ge-b850v3-fw", + .of_match_table = stdp2690_ge_b850v3_fw_match, + }, +}; + +static int __init stdpxxxx_ge_b850v3_init(void) +{ + int ret; + + ret = i2c_add_driver(&stdp4028_ge_b850v3_fw_driver); + if (ret) + return ret; + + ret = i2c_add_driver(&stdp2690_ge_b850v3_fw_driver); + if (ret) + i2c_del_driver(&stdp4028_ge_b850v3_fw_driver); + + return ret; +} +module_init(stdpxxxx_ge_b850v3_init); + +static void __exit stdpxxxx_ge_b850v3_exit(void) +{ + i2c_del_driver(&stdp2690_ge_b850v3_fw_driver); + i2c_del_driver(&stdp4028_ge_b850v3_fw_driver); +} +module_exit(stdpxxxx_ge_b850v3_exit); + +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c new file mode 100644 index 000000000..b68d33598 --- /dev/null +++ b/drivers/gpu/drm/bridge/nwl-dsi.c @@ -0,0 +1,1225 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * i.MX8 NWL MIPI DSI host driver + * + * Copyright (C) 2017 NXP + * Copyright (C) 2020 Purism SPC + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/math64.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/mux/consumer.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/sys_soc.h> +#include <linux/time64.h> + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> + +#include <video/mipi_display.h> + +#include "nwl-dsi.h" + +#define DRV_NAME "nwl-dsi" + +/* i.MX8 NWL quirks */ +/* i.MX8MQ errata E11418 */ +#define E11418_HS_MODE_QUIRK BIT(0) + +#define NWL_DSI_MIPI_FIFO_TIMEOUT msecs_to_jiffies(500) + +enum transfer_direction { + DSI_PACKET_SEND, + DSI_PACKET_RECEIVE, +}; + +#define NWL_DSI_ENDPOINT_LCDIF 0 +#define NWL_DSI_ENDPOINT_DCSS 1 + +struct nwl_dsi_plat_clk_config { + const char *id; + struct clk *clk; + bool present; +}; + +struct nwl_dsi_transfer { + const struct mipi_dsi_msg *msg; + struct mipi_dsi_packet packet; + struct completion completed; + + int status; /* status of transmission */ + enum transfer_direction direction; + bool need_bta; + u8 cmd; + u16 rx_word_count; + size_t tx_len; /* in bytes */ + size_t rx_len; /* in bytes */ +}; + +struct nwl_dsi { + struct drm_bridge bridge; + struct mipi_dsi_host dsi_host; + struct drm_bridge *panel_bridge; + struct device *dev; + struct phy *phy; + union phy_configure_opts phy_cfg; + unsigned int quirks; + + struct regmap *regmap; + int irq; + /* + * The DSI host controller needs this reset sequence according to NWL: + * 1. Deassert pclk reset to get access to DSI regs + * 2. Configure DSI Host and DPHY and enable DPHY + * 3. Deassert ESC and BYTE resets to allow host TX operations) + * 4. Send DSI cmds to configure peripheral (handled by panel drv) + * 5. Deassert DPI reset so DPI receives pixels and starts sending + * DSI data + * + * TODO: Since panel_bridges do their DSI setup in enable we + * currently have 4. and 5. swapped. + */ + struct reset_control *rst_byte; + struct reset_control *rst_esc; + struct reset_control *rst_dpi; + struct reset_control *rst_pclk; + struct mux_control *mux; + + /* DSI clocks */ + struct clk *phy_ref_clk; + struct clk *rx_esc_clk; + struct clk *tx_esc_clk; + struct clk *core_clk; + /* + * hardware bug: the i.MX8MQ needs this clock on during reset + * even when not using LCDIF. + */ + struct clk *lcdif_clk; + + /* dsi lanes */ + u32 lanes; + enum mipi_dsi_pixel_format format; + struct drm_display_mode mode; + unsigned long dsi_mode_flags; + int error; + + struct nwl_dsi_transfer *xfer; +}; + +static const struct regmap_config nwl_dsi_regmap_config = { + .reg_bits = 16, + .val_bits = 32, + .reg_stride = 4, + .max_register = NWL_DSI_IRQ_MASK2, + .name = DRV_NAME, +}; + +static inline struct nwl_dsi *bridge_to_dsi(struct drm_bridge *bridge) +{ + return container_of(bridge, struct nwl_dsi, bridge); +} + +static int nwl_dsi_clear_error(struct nwl_dsi *dsi) +{ + int ret = dsi->error; + + dsi->error = 0; + return ret; +} + +static void nwl_dsi_write(struct nwl_dsi *dsi, unsigned int reg, u32 val) +{ + int ret; + + if (dsi->error) + return; + + ret = regmap_write(dsi->regmap, reg, val); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, + "Failed to write NWL DSI reg 0x%x: %d\n", reg, + ret); + dsi->error = ret; + } +} + +static u32 nwl_dsi_read(struct nwl_dsi *dsi, u32 reg) +{ + unsigned int val; + int ret; + + if (dsi->error) + return 0; + + ret = regmap_read(dsi->regmap, reg, &val); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to read NWL DSI reg 0x%x: %d\n", + reg, ret); + dsi->error = ret; + } + return val; +} + +static int nwl_dsi_get_dpi_pixel_format(enum mipi_dsi_pixel_format format) +{ + switch (format) { + case MIPI_DSI_FMT_RGB565: + return NWL_DSI_PIXEL_FORMAT_16; + case MIPI_DSI_FMT_RGB666: + return NWL_DSI_PIXEL_FORMAT_18L; + case MIPI_DSI_FMT_RGB666_PACKED: + return NWL_DSI_PIXEL_FORMAT_18; + case MIPI_DSI_FMT_RGB888: + return NWL_DSI_PIXEL_FORMAT_24; + default: + return -EINVAL; + } +} + +/* + * ps2bc - Picoseconds to byte clock cycles + */ +static u32 ps2bc(struct nwl_dsi *dsi, unsigned long long ps) +{ + u32 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + + return DIV64_U64_ROUND_UP(ps * dsi->mode.clock * bpp, + dsi->lanes * 8ULL * NSEC_PER_SEC); +} + +/* + * ui2bc - UI time periods to byte clock cycles + */ +static u32 ui2bc(struct nwl_dsi *dsi, unsigned long long ui) +{ + u32 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + + return DIV64_U64_ROUND_UP(ui * dsi->lanes, + dsi->mode.clock * 1000 * bpp); +} + +/* + * us2bc - micro seconds to lp clock cycles + */ +static u32 us2lp(u32 lp_clk_rate, unsigned long us) +{ + return DIV_ROUND_UP(us * lp_clk_rate, USEC_PER_SEC); +} + +static int nwl_dsi_config_host(struct nwl_dsi *dsi) +{ + u32 cycles; + struct phy_configure_opts_mipi_dphy *cfg = &dsi->phy_cfg.mipi_dphy; + + if (dsi->lanes < 1 || dsi->lanes > 4) + return -EINVAL; + + DRM_DEV_DEBUG_DRIVER(dsi->dev, "DSI Lanes %d\n", dsi->lanes); + nwl_dsi_write(dsi, NWL_DSI_CFG_NUM_LANES, dsi->lanes - 1); + + if (dsi->dsi_mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) { + nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK, 0x01); + nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x01); + } else { + nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK, 0x00); + nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x00); + } + + /* values in byte clock cycles */ + cycles = ui2bc(dsi, cfg->clk_pre); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_t_pre: 0x%x\n", cycles); + nwl_dsi_write(dsi, NWL_DSI_CFG_T_PRE, cycles); + cycles = ps2bc(dsi, cfg->lpx + cfg->clk_prepare + cfg->clk_zero); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap (pre): 0x%x\n", cycles); + cycles += ui2bc(dsi, cfg->clk_pre); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_t_post: 0x%x\n", cycles); + nwl_dsi_write(dsi, NWL_DSI_CFG_T_POST, cycles); + cycles = ps2bc(dsi, cfg->hs_exit); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap: 0x%x\n", cycles); + nwl_dsi_write(dsi, NWL_DSI_CFG_TX_GAP, cycles); + + nwl_dsi_write(dsi, NWL_DSI_CFG_EXTRA_CMDS_AFTER_EOTP, 0x01); + nwl_dsi_write(dsi, NWL_DSI_CFG_HTX_TO_COUNT, 0x00); + nwl_dsi_write(dsi, NWL_DSI_CFG_LRX_H_TO_COUNT, 0x00); + nwl_dsi_write(dsi, NWL_DSI_CFG_BTA_H_TO_COUNT, 0x00); + /* In LP clock cycles */ + cycles = us2lp(cfg->lp_clk_rate, cfg->wakeup); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_twakeup: 0x%x\n", cycles); + nwl_dsi_write(dsi, NWL_DSI_CFG_TWAKEUP, cycles); + + return nwl_dsi_clear_error(dsi); +} + +static int nwl_dsi_config_dpi(struct nwl_dsi *dsi) +{ + u32 mode; + int color_format; + bool burst_mode; + int hfront_porch, hback_porch, vfront_porch, vback_porch; + int hsync_len, vsync_len; + + hfront_porch = dsi->mode.hsync_start - dsi->mode.hdisplay; + hsync_len = dsi->mode.hsync_end - dsi->mode.hsync_start; + hback_porch = dsi->mode.htotal - dsi->mode.hsync_end; + + vfront_porch = dsi->mode.vsync_start - dsi->mode.vdisplay; + vsync_len = dsi->mode.vsync_end - dsi->mode.vsync_start; + vback_porch = dsi->mode.vtotal - dsi->mode.vsync_end; + + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hfront_porch = %d\n", hfront_porch); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hback_porch = %d\n", hback_porch); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hsync_len = %d\n", hsync_len); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hdisplay = %d\n", dsi->mode.hdisplay); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vfront_porch = %d\n", vfront_porch); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vback_porch = %d\n", vback_porch); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vsync_len = %d\n", vsync_len); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vactive = %d\n", dsi->mode.vdisplay); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "clock = %d kHz\n", dsi->mode.clock); + + color_format = nwl_dsi_get_dpi_pixel_format(dsi->format); + if (color_format < 0) { + DRM_DEV_ERROR(dsi->dev, "Invalid color format 0x%x\n", + dsi->format); + return color_format; + } + DRM_DEV_DEBUG_DRIVER(dsi->dev, "pixel fmt = %d\n", dsi->format); + + nwl_dsi_write(dsi, NWL_DSI_INTERFACE_COLOR_CODING, NWL_DSI_DPI_24_BIT); + nwl_dsi_write(dsi, NWL_DSI_PIXEL_FORMAT, color_format); + /* + * Adjusting input polarity based on the video mode results in + * a black screen so always pick active low: + */ + nwl_dsi_write(dsi, NWL_DSI_VSYNC_POLARITY, + NWL_DSI_VSYNC_POLARITY_ACTIVE_LOW); + nwl_dsi_write(dsi, NWL_DSI_HSYNC_POLARITY, + NWL_DSI_HSYNC_POLARITY_ACTIVE_LOW); + + burst_mode = (dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_BURST) && + !(dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE); + + if (burst_mode) { + nwl_dsi_write(dsi, NWL_DSI_VIDEO_MODE, NWL_DSI_VM_BURST_MODE); + nwl_dsi_write(dsi, NWL_DSI_PIXEL_FIFO_SEND_LEVEL, 256); + } else { + mode = ((dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) ? + NWL_DSI_VM_BURST_MODE_WITH_SYNC_PULSES : + NWL_DSI_VM_NON_BURST_MODE_WITH_SYNC_EVENTS); + nwl_dsi_write(dsi, NWL_DSI_VIDEO_MODE, mode); + nwl_dsi_write(dsi, NWL_DSI_PIXEL_FIFO_SEND_LEVEL, + dsi->mode.hdisplay); + } + + nwl_dsi_write(dsi, NWL_DSI_HFP, hfront_porch); + nwl_dsi_write(dsi, NWL_DSI_HBP, hback_porch); + nwl_dsi_write(dsi, NWL_DSI_HSA, hsync_len); + + nwl_dsi_write(dsi, NWL_DSI_ENABLE_MULT_PKTS, 0x0); + nwl_dsi_write(dsi, NWL_DSI_BLLP_MODE, 0x1); + nwl_dsi_write(dsi, NWL_DSI_USE_NULL_PKT_BLLP, 0x0); + nwl_dsi_write(dsi, NWL_DSI_VC, 0x0); + + nwl_dsi_write(dsi, NWL_DSI_PIXEL_PAYLOAD_SIZE, dsi->mode.hdisplay); + nwl_dsi_write(dsi, NWL_DSI_VACTIVE, dsi->mode.vdisplay - 1); + nwl_dsi_write(dsi, NWL_DSI_VBP, vback_porch); + nwl_dsi_write(dsi, NWL_DSI_VFP, vfront_porch); + + return nwl_dsi_clear_error(dsi); +} + +static int nwl_dsi_init_interrupts(struct nwl_dsi *dsi) +{ + u32 irq_enable; + + nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, 0xffffffff); + nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK2, 0x7); + + irq_enable = ~(u32)(NWL_DSI_TX_PKT_DONE_MASK | + NWL_DSI_RX_PKT_HDR_RCVD_MASK | + NWL_DSI_TX_FIFO_OVFLW_MASK | + NWL_DSI_HS_TX_TIMEOUT_MASK); + + nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, irq_enable); + + return nwl_dsi_clear_error(dsi); +} + +static int nwl_dsi_host_attach(struct mipi_dsi_host *dsi_host, + struct mipi_dsi_device *device) +{ + struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi, dsi_host); + struct device *dev = dsi->dev; + + DRM_DEV_INFO(dev, "lanes=%u, format=0x%x flags=0x%lx\n", device->lanes, + device->format, device->mode_flags); + + if (device->lanes < 1 || device->lanes > 4) + return -EINVAL; + + dsi->lanes = device->lanes; + dsi->format = device->format; + dsi->dsi_mode_flags = device->mode_flags; + + return 0; +} + +static bool nwl_dsi_read_packet(struct nwl_dsi *dsi, u32 status) +{ + struct device *dev = dsi->dev; + struct nwl_dsi_transfer *xfer = dsi->xfer; + int err; + u8 *payload = xfer->msg->rx_buf; + u32 val; + u16 word_count; + u8 channel; + u8 data_type; + + xfer->status = 0; + + if (xfer->rx_word_count == 0) { + if (!(status & NWL_DSI_RX_PKT_HDR_RCVD)) + return false; + /* Get the RX header and parse it */ + val = nwl_dsi_read(dsi, NWL_DSI_RX_PKT_HEADER); + err = nwl_dsi_clear_error(dsi); + if (err) + xfer->status = err; + word_count = NWL_DSI_WC(val); + channel = NWL_DSI_RX_VC(val); + data_type = NWL_DSI_RX_DT(val); + + if (channel != xfer->msg->channel) { + DRM_DEV_ERROR(dev, + "[%02X] Channel mismatch (%u != %u)\n", + xfer->cmd, channel, xfer->msg->channel); + xfer->status = -EINVAL; + return true; + } + + switch (data_type) { + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + if (xfer->msg->rx_len > 1) { + /* read second byte */ + payload[1] = word_count >> 8; + ++xfer->rx_len; + } + fallthrough; + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + if (xfer->msg->rx_len > 0) { + /* read first byte */ + payload[0] = word_count & 0xff; + ++xfer->rx_len; + } + xfer->status = xfer->rx_len; + return true; + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + word_count &= 0xff; + DRM_DEV_ERROR(dev, "[%02X] DSI error report: 0x%02x\n", + xfer->cmd, word_count); + xfer->status = -EPROTO; + return true; + } + + if (word_count > xfer->msg->rx_len) { + DRM_DEV_ERROR(dev, + "[%02X] Receive buffer too small: %zu (< %u)\n", + xfer->cmd, xfer->msg->rx_len, word_count); + xfer->status = -EINVAL; + return true; + } + + xfer->rx_word_count = word_count; + } else { + /* Set word_count from previous header read */ + word_count = xfer->rx_word_count; + } + + /* If RX payload is not yet received, wait for it */ + if (!(status & NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD)) + return false; + + /* Read the RX payload */ + while (word_count >= 4) { + val = nwl_dsi_read(dsi, NWL_DSI_RX_PAYLOAD); + payload[0] = (val >> 0) & 0xff; + payload[1] = (val >> 8) & 0xff; + payload[2] = (val >> 16) & 0xff; + payload[3] = (val >> 24) & 0xff; + payload += 4; + xfer->rx_len += 4; + word_count -= 4; + } + + if (word_count > 0) { + val = nwl_dsi_read(dsi, NWL_DSI_RX_PAYLOAD); + switch (word_count) { + case 3: + payload[2] = (val >> 16) & 0xff; + ++xfer->rx_len; + fallthrough; + case 2: + payload[1] = (val >> 8) & 0xff; + ++xfer->rx_len; + fallthrough; + case 1: + payload[0] = (val >> 0) & 0xff; + ++xfer->rx_len; + break; + } + } + + xfer->status = xfer->rx_len; + err = nwl_dsi_clear_error(dsi); + if (err) + xfer->status = err; + + return true; +} + +static void nwl_dsi_finish_transmission(struct nwl_dsi *dsi, u32 status) +{ + struct nwl_dsi_transfer *xfer = dsi->xfer; + bool end_packet = false; + + if (!xfer) + return; + + if (xfer->direction == DSI_PACKET_SEND && + status & NWL_DSI_TX_PKT_DONE) { + xfer->status = xfer->tx_len; + end_packet = true; + } else if (status & NWL_DSI_DPHY_DIRECTION && + ((status & (NWL_DSI_RX_PKT_HDR_RCVD | + NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD)))) { + end_packet = nwl_dsi_read_packet(dsi, status); + } + + if (end_packet) + complete(&xfer->completed); +} + +static void nwl_dsi_begin_transmission(struct nwl_dsi *dsi) +{ + struct nwl_dsi_transfer *xfer = dsi->xfer; + struct mipi_dsi_packet *pkt = &xfer->packet; + const u8 *payload; + size_t length; + u16 word_count; + u8 hs_mode; + u32 val; + u32 hs_workaround = 0; + + /* Send the payload, if any */ + length = pkt->payload_length; + payload = pkt->payload; + + while (length >= 4) { + val = *(u32 *)payload; + hs_workaround |= !(val & 0xFFFF00); + nwl_dsi_write(dsi, NWL_DSI_TX_PAYLOAD, val); + payload += 4; + length -= 4; + } + /* Send the rest of the payload */ + val = 0; + switch (length) { + case 3: + val |= payload[2] << 16; + fallthrough; + case 2: + val |= payload[1] << 8; + hs_workaround |= !(val & 0xFFFF00); + fallthrough; + case 1: + val |= payload[0]; + nwl_dsi_write(dsi, NWL_DSI_TX_PAYLOAD, val); + break; + } + xfer->tx_len = pkt->payload_length; + + /* + * Send the header + * header[0] = Virtual Channel + Data Type + * header[1] = Word Count LSB (LP) or first param (SP) + * header[2] = Word Count MSB (LP) or second param (SP) + */ + word_count = pkt->header[1] | (pkt->header[2] << 8); + if (hs_workaround && (dsi->quirks & E11418_HS_MODE_QUIRK)) { + DRM_DEV_DEBUG_DRIVER(dsi->dev, + "Using hs mode workaround for cmd 0x%x\n", + xfer->cmd); + hs_mode = 1; + } else { + hs_mode = (xfer->msg->flags & MIPI_DSI_MSG_USE_LPM) ? 0 : 1; + } + val = NWL_DSI_WC(word_count) | NWL_DSI_TX_VC(xfer->msg->channel) | + NWL_DSI_TX_DT(xfer->msg->type) | NWL_DSI_HS_SEL(hs_mode) | + NWL_DSI_BTA_TX(xfer->need_bta); + nwl_dsi_write(dsi, NWL_DSI_PKT_CONTROL, val); + + /* Send packet command */ + nwl_dsi_write(dsi, NWL_DSI_SEND_PACKET, 0x1); +} + +static ssize_t nwl_dsi_host_transfer(struct mipi_dsi_host *dsi_host, + const struct mipi_dsi_msg *msg) +{ + struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi, dsi_host); + struct nwl_dsi_transfer xfer; + ssize_t ret = 0; + + /* Create packet to be sent */ + dsi->xfer = &xfer; + ret = mipi_dsi_create_packet(&xfer.packet, msg); + if (ret < 0) { + dsi->xfer = NULL; + return ret; + } + + if ((msg->type & MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM || + msg->type & MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM || + msg->type & MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM || + msg->type & MIPI_DSI_DCS_READ) && + msg->rx_len > 0 && msg->rx_buf) + xfer.direction = DSI_PACKET_RECEIVE; + else + xfer.direction = DSI_PACKET_SEND; + + xfer.need_bta = (xfer.direction == DSI_PACKET_RECEIVE); + xfer.need_bta |= (msg->flags & MIPI_DSI_MSG_REQ_ACK) ? 1 : 0; + xfer.msg = msg; + xfer.status = -ETIMEDOUT; + xfer.rx_word_count = 0; + xfer.rx_len = 0; + xfer.cmd = 0x00; + if (msg->tx_len > 0) + xfer.cmd = ((u8 *)(msg->tx_buf))[0]; + init_completion(&xfer.completed); + + ret = clk_prepare_enable(dsi->rx_esc_clk); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to enable rx_esc clk: %zd\n", + ret); + return ret; + } + DRM_DEV_DEBUG_DRIVER(dsi->dev, "Enabled rx_esc clk @%lu Hz\n", + clk_get_rate(dsi->rx_esc_clk)); + + /* Initiate the DSI packet transmision */ + nwl_dsi_begin_transmission(dsi); + + if (!wait_for_completion_timeout(&xfer.completed, + NWL_DSI_MIPI_FIFO_TIMEOUT)) { + DRM_DEV_ERROR(dsi_host->dev, "[%02X] DSI transfer timed out\n", + xfer.cmd); + ret = -ETIMEDOUT; + } else { + ret = xfer.status; + } + + clk_disable_unprepare(dsi->rx_esc_clk); + + return ret; +} + +static const struct mipi_dsi_host_ops nwl_dsi_host_ops = { + .attach = nwl_dsi_host_attach, + .transfer = nwl_dsi_host_transfer, +}; + +static irqreturn_t nwl_dsi_irq_handler(int irq, void *data) +{ + u32 irq_status; + struct nwl_dsi *dsi = data; + + irq_status = nwl_dsi_read(dsi, NWL_DSI_IRQ_STATUS); + + if (irq_status & NWL_DSI_TX_FIFO_OVFLW) + DRM_DEV_ERROR_RATELIMITED(dsi->dev, "tx fifo overflow\n"); + + if (irq_status & NWL_DSI_HS_TX_TIMEOUT) + DRM_DEV_ERROR_RATELIMITED(dsi->dev, "HS tx timeout\n"); + + if (irq_status & NWL_DSI_TX_PKT_DONE || + irq_status & NWL_DSI_RX_PKT_HDR_RCVD || + irq_status & NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD) + nwl_dsi_finish_transmission(dsi, irq_status); + + return IRQ_HANDLED; +} + +static int nwl_dsi_enable(struct nwl_dsi *dsi) +{ + struct device *dev = dsi->dev; + union phy_configure_opts *phy_cfg = &dsi->phy_cfg; + int ret; + + if (!dsi->lanes) { + DRM_DEV_ERROR(dev, "Need DSI lanes: %d\n", dsi->lanes); + return -EINVAL; + } + + ret = phy_init(dsi->phy); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to init DSI phy: %d\n", ret); + return ret; + } + + ret = phy_configure(dsi->phy, phy_cfg); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to configure DSI phy: %d\n", ret); + goto uninit_phy; + } + + ret = clk_prepare_enable(dsi->tx_esc_clk); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to enable tx_esc clk: %d\n", + ret); + goto uninit_phy; + } + DRM_DEV_DEBUG_DRIVER(dsi->dev, "Enabled tx_esc clk @%lu Hz\n", + clk_get_rate(dsi->tx_esc_clk)); + + ret = nwl_dsi_config_host(dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to set up DSI: %d", ret); + goto disable_clock; + } + + ret = nwl_dsi_config_dpi(dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to set up DPI: %d", ret); + goto disable_clock; + } + + ret = phy_power_on(dsi->phy); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to power on DPHY (%d)\n", ret); + goto disable_clock; + } + + ret = nwl_dsi_init_interrupts(dsi); + if (ret < 0) + goto power_off_phy; + + return ret; + +power_off_phy: + phy_power_off(dsi->phy); +disable_clock: + clk_disable_unprepare(dsi->tx_esc_clk); +uninit_phy: + phy_exit(dsi->phy); + + return ret; +} + +static int nwl_dsi_disable(struct nwl_dsi *dsi) +{ + struct device *dev = dsi->dev; + + DRM_DEV_DEBUG_DRIVER(dev, "Disabling clocks and phy\n"); + + phy_power_off(dsi->phy); + phy_exit(dsi->phy); + + /* Disabling the clock before the phy breaks enabling dsi again */ + clk_disable_unprepare(dsi->tx_esc_clk); + + return 0; +} + +static void +nwl_dsi_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + int ret; + + nwl_dsi_disable(dsi); + + ret = reset_control_assert(dsi->rst_dpi); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to assert DPI: %d\n", ret); + return; + } + ret = reset_control_assert(dsi->rst_byte); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to assert ESC: %d\n", ret); + return; + } + ret = reset_control_assert(dsi->rst_esc); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to assert BYTE: %d\n", ret); + return; + } + ret = reset_control_assert(dsi->rst_pclk); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to assert PCLK: %d\n", ret); + return; + } + + clk_disable_unprepare(dsi->core_clk); + clk_disable_unprepare(dsi->lcdif_clk); + + pm_runtime_put(dsi->dev); +} + +static int nwl_dsi_get_dphy_params(struct nwl_dsi *dsi, + const struct drm_display_mode *mode, + union phy_configure_opts *phy_opts) +{ + unsigned long rate; + int ret; + + if (dsi->lanes < 1 || dsi->lanes > 4) + return -EINVAL; + + /* + * So far the DPHY spec minimal timings work for both mixel + * dphy and nwl dsi host + */ + ret = phy_mipi_dphy_get_default_config(mode->clock * 1000, + mipi_dsi_pixel_format_to_bpp(dsi->format), dsi->lanes, + &phy_opts->mipi_dphy); + if (ret < 0) + return ret; + + rate = clk_get_rate(dsi->tx_esc_clk); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "LP clk is @%lu Hz\n", rate); + phy_opts->mipi_dphy.lp_clk_rate = rate; + + return 0; +} + +static enum drm_mode_status +nwl_dsi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + + if (mode->clock * bpp > 15000000 * dsi->lanes) + return MODE_CLOCK_HIGH; + + if (mode->clock * bpp < 80000 * dsi->lanes) + return MODE_CLOCK_LOW; + + return MODE_OK; +} + +static int nwl_dsi_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + + /* At least LCDIF + NWL needs active high sync */ + adjusted_mode->flags |= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC); + adjusted_mode->flags &= ~(DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC); + + /* Do a full modeset if crtc_state->active is changed to be true. */ + if (crtc_state->active_changed && crtc_state->active) + crtc_state->mode_changed = true; + + return 0; +} + +static void +nwl_dsi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + struct device *dev = dsi->dev; + union phy_configure_opts new_cfg; + unsigned long phy_ref_rate; + int ret; + + ret = nwl_dsi_get_dphy_params(dsi, adjusted_mode, &new_cfg); + if (ret < 0) + return; + + /* + * If hs clock is unchanged, we're all good - all parameters are + * derived from it atm. + */ + if (new_cfg.mipi_dphy.hs_clk_rate == dsi->phy_cfg.mipi_dphy.hs_clk_rate) + return; + + phy_ref_rate = clk_get_rate(dsi->phy_ref_clk); + DRM_DEV_DEBUG_DRIVER(dev, "PHY at ref rate: %lu\n", phy_ref_rate); + /* Save the new desired phy config */ + memcpy(&dsi->phy_cfg, &new_cfg, sizeof(new_cfg)); + + memcpy(&dsi->mode, adjusted_mode, sizeof(dsi->mode)); + drm_mode_debug_printmodeline(adjusted_mode); +} + +static void +nwl_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + int ret; + + pm_runtime_get_sync(dsi->dev); + + if (clk_prepare_enable(dsi->lcdif_clk) < 0) + return; + if (clk_prepare_enable(dsi->core_clk) < 0) + return; + + /* Step 1 from DSI reset-out instructions */ + ret = reset_control_deassert(dsi->rst_pclk); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to deassert PCLK: %d\n", ret); + return; + } + + /* Step 2 from DSI reset-out instructions */ + nwl_dsi_enable(dsi); + + /* Step 3 from DSI reset-out instructions */ + ret = reset_control_deassert(dsi->rst_esc); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to deassert ESC: %d\n", ret); + return; + } + ret = reset_control_deassert(dsi->rst_byte); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to deassert BYTE: %d\n", ret); + return; + } +} + +static void +nwl_dsi_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + int ret; + + /* Step 5 from DSI reset-out instructions */ + ret = reset_control_deassert(dsi->rst_dpi); + if (ret < 0) + DRM_DEV_ERROR(dsi->dev, "Failed to deassert DPI: %d\n", ret); +} + +static int nwl_dsi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + struct drm_bridge *panel_bridge; + struct drm_panel *panel; + int ret; + + ret = drm_of_find_panel_or_bridge(dsi->dev->of_node, 1, 0, &panel, + &panel_bridge); + if (ret) + return ret; + + if (panel) { + panel_bridge = drm_panel_bridge_add(panel); + if (IS_ERR(panel_bridge)) + return PTR_ERR(panel_bridge); + } + dsi->panel_bridge = panel_bridge; + + if (!dsi->panel_bridge) + return -EPROBE_DEFER; + + return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge, + flags); +} + +static void nwl_dsi_bridge_detach(struct drm_bridge *bridge) +{ struct nwl_dsi *dsi = bridge_to_dsi(bridge); + + drm_of_panel_bridge_remove(dsi->dev->of_node, 1, 0); +} + +static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_check = nwl_dsi_bridge_atomic_check, + .atomic_pre_enable = nwl_dsi_bridge_atomic_pre_enable, + .atomic_enable = nwl_dsi_bridge_atomic_enable, + .atomic_disable = nwl_dsi_bridge_atomic_disable, + .mode_set = nwl_dsi_bridge_mode_set, + .mode_valid = nwl_dsi_bridge_mode_valid, + .attach = nwl_dsi_bridge_attach, + .detach = nwl_dsi_bridge_detach, +}; + +static int nwl_dsi_parse_dt(struct nwl_dsi *dsi) +{ + struct platform_device *pdev = to_platform_device(dsi->dev); + struct clk *clk; + void __iomem *base; + int ret; + + dsi->phy = devm_phy_get(dsi->dev, "dphy"); + if (IS_ERR(dsi->phy)) { + ret = PTR_ERR(dsi->phy); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dsi->dev, "Could not get PHY: %d\n", ret); + return ret; + } + + clk = devm_clk_get(dsi->dev, "lcdif"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, "Failed to get lcdif clock: %d\n", + ret); + return ret; + } + dsi->lcdif_clk = clk; + + clk = devm_clk_get(dsi->dev, "core"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, "Failed to get core clock: %d\n", + ret); + return ret; + } + dsi->core_clk = clk; + + clk = devm_clk_get(dsi->dev, "phy_ref"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, "Failed to get phy_ref clock: %d\n", + ret); + return ret; + } + dsi->phy_ref_clk = clk; + + clk = devm_clk_get(dsi->dev, "rx_esc"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, "Failed to get rx_esc clock: %d\n", + ret); + return ret; + } + dsi->rx_esc_clk = clk; + + clk = devm_clk_get(dsi->dev, "tx_esc"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, "Failed to get tx_esc clock: %d\n", + ret); + return ret; + } + dsi->tx_esc_clk = clk; + + dsi->mux = devm_mux_control_get(dsi->dev, NULL); + if (IS_ERR(dsi->mux)) { + ret = PTR_ERR(dsi->mux); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dsi->dev, "Failed to get mux: %d\n", ret); + return ret; + } + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + dsi->regmap = + devm_regmap_init_mmio(dsi->dev, base, &nwl_dsi_regmap_config); + if (IS_ERR(dsi->regmap)) { + ret = PTR_ERR(dsi->regmap); + DRM_DEV_ERROR(dsi->dev, "Failed to create NWL DSI regmap: %d\n", + ret); + return ret; + } + + dsi->irq = platform_get_irq(pdev, 0); + if (dsi->irq < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to get device IRQ: %d\n", + dsi->irq); + return dsi->irq; + } + + dsi->rst_pclk = devm_reset_control_get_exclusive(dsi->dev, "pclk"); + if (IS_ERR(dsi->rst_pclk)) { + DRM_DEV_ERROR(dsi->dev, "Failed to get pclk reset: %ld\n", + PTR_ERR(dsi->rst_pclk)); + return PTR_ERR(dsi->rst_pclk); + } + dsi->rst_byte = devm_reset_control_get_exclusive(dsi->dev, "byte"); + if (IS_ERR(dsi->rst_byte)) { + DRM_DEV_ERROR(dsi->dev, "Failed to get byte reset: %ld\n", + PTR_ERR(dsi->rst_byte)); + return PTR_ERR(dsi->rst_byte); + } + dsi->rst_esc = devm_reset_control_get_exclusive(dsi->dev, "esc"); + if (IS_ERR(dsi->rst_esc)) { + DRM_DEV_ERROR(dsi->dev, "Failed to get esc reset: %ld\n", + PTR_ERR(dsi->rst_esc)); + return PTR_ERR(dsi->rst_esc); + } + dsi->rst_dpi = devm_reset_control_get_exclusive(dsi->dev, "dpi"); + if (IS_ERR(dsi->rst_dpi)) { + DRM_DEV_ERROR(dsi->dev, "Failed to get dpi reset: %ld\n", + PTR_ERR(dsi->rst_dpi)); + return PTR_ERR(dsi->rst_dpi); + } + return 0; +} + +static int nwl_dsi_select_input(struct nwl_dsi *dsi) +{ + struct device_node *remote; + u32 use_dcss = 1; + int ret; + + remote = of_graph_get_remote_node(dsi->dev->of_node, 0, + NWL_DSI_ENDPOINT_LCDIF); + if (remote) { + use_dcss = 0; + } else { + remote = of_graph_get_remote_node(dsi->dev->of_node, 0, + NWL_DSI_ENDPOINT_DCSS); + if (!remote) { + DRM_DEV_ERROR(dsi->dev, + "No valid input endpoint found\n"); + return -EINVAL; + } + } + + DRM_DEV_INFO(dsi->dev, "Using %s as input source\n", + (use_dcss) ? "DCSS" : "LCDIF"); + ret = mux_control_try_select(dsi->mux, use_dcss); + if (ret < 0) + DRM_DEV_ERROR(dsi->dev, "Failed to select input: %d\n", ret); + + of_node_put(remote); + return ret; +} + +static int nwl_dsi_deselect_input(struct nwl_dsi *dsi) +{ + int ret; + + ret = mux_control_deselect(dsi->mux); + if (ret < 0) + DRM_DEV_ERROR(dsi->dev, "Failed to deselect input: %d\n", ret); + + return ret; +} + +static const struct drm_bridge_timings nwl_dsi_timings = { + .input_bus_flags = DRM_BUS_FLAG_DE_LOW, +}; + +static const struct of_device_id nwl_dsi_dt_ids[] = { + { .compatible = "fsl,imx8mq-nwl-dsi", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, nwl_dsi_dt_ids); + +static const struct soc_device_attribute nwl_dsi_quirks_match[] = { + { .soc_id = "i.MX8MQ", .revision = "2.0", + .data = (void *)E11418_HS_MODE_QUIRK }, + { /* sentinel. */ }, +}; + +static int nwl_dsi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct soc_device_attribute *attr; + struct nwl_dsi *dsi; + int ret; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + + dsi->dev = dev; + + ret = nwl_dsi_parse_dt(dsi); + if (ret) + return ret; + + ret = devm_request_irq(dev, dsi->irq, nwl_dsi_irq_handler, 0, + dev_name(dev), dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to request IRQ %d: %d\n", dsi->irq, + ret); + return ret; + } + + dsi->dsi_host.ops = &nwl_dsi_host_ops; + dsi->dsi_host.dev = dev; + ret = mipi_dsi_host_register(&dsi->dsi_host); + if (ret) { + DRM_DEV_ERROR(dev, "Failed to register MIPI host: %d\n", ret); + return ret; + } + + attr = soc_device_match(nwl_dsi_quirks_match); + if (attr) + dsi->quirks = (uintptr_t)attr->data; + + dsi->bridge.driver_private = dsi; + dsi->bridge.funcs = &nwl_dsi_bridge_funcs; + dsi->bridge.of_node = dev->of_node; + dsi->bridge.timings = &nwl_dsi_timings; + + dev_set_drvdata(dev, dsi); + pm_runtime_enable(dev); + + ret = nwl_dsi_select_input(dsi); + if (ret < 0) { + pm_runtime_disable(dev); + mipi_dsi_host_unregister(&dsi->dsi_host); + return ret; + } + + drm_bridge_add(&dsi->bridge); + return 0; +} + +static int nwl_dsi_remove(struct platform_device *pdev) +{ + struct nwl_dsi *dsi = platform_get_drvdata(pdev); + + nwl_dsi_deselect_input(dsi); + mipi_dsi_host_unregister(&dsi->dsi_host); + drm_bridge_remove(&dsi->bridge); + pm_runtime_disable(&pdev->dev); + return 0; +} + +static struct platform_driver nwl_dsi_driver = { + .probe = nwl_dsi_probe, + .remove = nwl_dsi_remove, + .driver = { + .of_match_table = nwl_dsi_dt_ids, + .name = DRV_NAME, + }, +}; + +module_platform_driver(nwl_dsi_driver); + +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_AUTHOR("Purism SPC"); +MODULE_DESCRIPTION("Northwest Logic MIPI-DSI driver"); +MODULE_LICENSE("GPL"); /* GPLv2 or later */ diff --git a/drivers/gpu/drm/bridge/nwl-dsi.h b/drivers/gpu/drm/bridge/nwl-dsi.h new file mode 100644 index 000000000..a247a8a11 --- /dev/null +++ b/drivers/gpu/drm/bridge/nwl-dsi.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * NWL MIPI DSI host driver + * + * Copyright (C) 2017 NXP + * Copyright (C) 2019 Purism SPC + */ +#ifndef __NWL_DSI_H__ +#define __NWL_DSI_H__ + +/* DSI HOST registers */ +#define NWL_DSI_CFG_NUM_LANES 0x0 +#define NWL_DSI_CFG_NONCONTINUOUS_CLK 0x4 +#define NWL_DSI_CFG_T_PRE 0x8 +#define NWL_DSI_CFG_T_POST 0xc +#define NWL_DSI_CFG_TX_GAP 0x10 +#define NWL_DSI_CFG_AUTOINSERT_EOTP 0x14 +#define NWL_DSI_CFG_EXTRA_CMDS_AFTER_EOTP 0x18 +#define NWL_DSI_CFG_HTX_TO_COUNT 0x1c +#define NWL_DSI_CFG_LRX_H_TO_COUNT 0x20 +#define NWL_DSI_CFG_BTA_H_TO_COUNT 0x24 +#define NWL_DSI_CFG_TWAKEUP 0x28 +#define NWL_DSI_CFG_STATUS_OUT 0x2c +#define NWL_DSI_RX_ERROR_STATUS 0x30 + +/* DSI DPI registers */ +#define NWL_DSI_PIXEL_PAYLOAD_SIZE 0x200 +#define NWL_DSI_PIXEL_FIFO_SEND_LEVEL 0x204 +#define NWL_DSI_INTERFACE_COLOR_CODING 0x208 +#define NWL_DSI_PIXEL_FORMAT 0x20c +#define NWL_DSI_VSYNC_POLARITY 0x210 +#define NWL_DSI_VSYNC_POLARITY_ACTIVE_LOW 0 +#define NWL_DSI_VSYNC_POLARITY_ACTIVE_HIGH BIT(1) + +#define NWL_DSI_HSYNC_POLARITY 0x214 +#define NWL_DSI_HSYNC_POLARITY_ACTIVE_LOW 0 +#define NWL_DSI_HSYNC_POLARITY_ACTIVE_HIGH BIT(1) + +#define NWL_DSI_VIDEO_MODE 0x218 +#define NWL_DSI_HFP 0x21c +#define NWL_DSI_HBP 0x220 +#define NWL_DSI_HSA 0x224 +#define NWL_DSI_ENABLE_MULT_PKTS 0x228 +#define NWL_DSI_VBP 0x22c +#define NWL_DSI_VFP 0x230 +#define NWL_DSI_BLLP_MODE 0x234 +#define NWL_DSI_USE_NULL_PKT_BLLP 0x238 +#define NWL_DSI_VACTIVE 0x23c +#define NWL_DSI_VC 0x240 + +/* DSI APB PKT control */ +#define NWL_DSI_TX_PAYLOAD 0x280 +#define NWL_DSI_PKT_CONTROL 0x284 +#define NWL_DSI_SEND_PACKET 0x288 +#define NWL_DSI_PKT_STATUS 0x28c +#define NWL_DSI_PKT_FIFO_WR_LEVEL 0x290 +#define NWL_DSI_PKT_FIFO_RD_LEVEL 0x294 +#define NWL_DSI_RX_PAYLOAD 0x298 +#define NWL_DSI_RX_PKT_HEADER 0x29c + +/* DSI IRQ handling */ +#define NWL_DSI_IRQ_STATUS 0x2a0 +#define NWL_DSI_SM_NOT_IDLE BIT(0) +#define NWL_DSI_TX_PKT_DONE BIT(1) +#define NWL_DSI_DPHY_DIRECTION BIT(2) +#define NWL_DSI_TX_FIFO_OVFLW BIT(3) +#define NWL_DSI_TX_FIFO_UDFLW BIT(4) +#define NWL_DSI_RX_FIFO_OVFLW BIT(5) +#define NWL_DSI_RX_FIFO_UDFLW BIT(6) +#define NWL_DSI_RX_PKT_HDR_RCVD BIT(7) +#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD BIT(8) +#define NWL_DSI_BTA_TIMEOUT BIT(29) +#define NWL_DSI_LP_RX_TIMEOUT BIT(30) +#define NWL_DSI_HS_TX_TIMEOUT BIT(31) + +#define NWL_DSI_IRQ_STATUS2 0x2a4 +#define NWL_DSI_SINGLE_BIT_ECC_ERR BIT(0) +#define NWL_DSI_MULTI_BIT_ECC_ERR BIT(1) +#define NWL_DSI_CRC_ERR BIT(2) + +#define NWL_DSI_IRQ_MASK 0x2a8 +#define NWL_DSI_SM_NOT_IDLE_MASK BIT(0) +#define NWL_DSI_TX_PKT_DONE_MASK BIT(1) +#define NWL_DSI_DPHY_DIRECTION_MASK BIT(2) +#define NWL_DSI_TX_FIFO_OVFLW_MASK BIT(3) +#define NWL_DSI_TX_FIFO_UDFLW_MASK BIT(4) +#define NWL_DSI_RX_FIFO_OVFLW_MASK BIT(5) +#define NWL_DSI_RX_FIFO_UDFLW_MASK BIT(6) +#define NWL_DSI_RX_PKT_HDR_RCVD_MASK BIT(7) +#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD_MASK BIT(8) +#define NWL_DSI_BTA_TIMEOUT_MASK BIT(29) +#define NWL_DSI_LP_RX_TIMEOUT_MASK BIT(30) +#define NWL_DSI_HS_TX_TIMEOUT_MASK BIT(31) + +#define NWL_DSI_IRQ_MASK2 0x2ac +#define NWL_DSI_SINGLE_BIT_ECC_ERR_MASK BIT(0) +#define NWL_DSI_MULTI_BIT_ECC_ERR_MASK BIT(1) +#define NWL_DSI_CRC_ERR_MASK BIT(2) + +/* + * PKT_CONTROL format: + * [15: 0] - word count + * [17:16] - virtual channel + * [23:18] - data type + * [24] - LP or HS select (0 - LP, 1 - HS) + * [25] - perform BTA after packet is sent + * [26] - perform BTA only, no packet tx + */ +#define NWL_DSI_WC(x) FIELD_PREP(GENMASK(15, 0), (x)) +#define NWL_DSI_TX_VC(x) FIELD_PREP(GENMASK(17, 16), (x)) +#define NWL_DSI_TX_DT(x) FIELD_PREP(GENMASK(23, 18), (x)) +#define NWL_DSI_HS_SEL(x) FIELD_PREP(GENMASK(24, 24), (x)) +#define NWL_DSI_BTA_TX(x) FIELD_PREP(GENMASK(25, 25), (x)) +#define NWL_DSI_BTA_NO_TX(x) FIELD_PREP(GENMASK(26, 26), (x)) + +/* + * RX_PKT_HEADER format: + * [15: 0] - word count + * [21:16] - data type + * [23:22] - virtual channel + */ +#define NWL_DSI_RX_DT(x) FIELD_GET(GENMASK(21, 16), (x)) +#define NWL_DSI_RX_VC(x) FIELD_GET(GENMASK(23, 22), (x)) + +/* DSI Video mode */ +#define NWL_DSI_VM_BURST_MODE_WITH_SYNC_PULSES 0 +#define NWL_DSI_VM_NON_BURST_MODE_WITH_SYNC_EVENTS BIT(0) +#define NWL_DSI_VM_BURST_MODE BIT(1) + +/* * DPI color coding */ +#define NWL_DSI_DPI_16_BIT_565_PACKED 0 +#define NWL_DSI_DPI_16_BIT_565_ALIGNED 1 +#define NWL_DSI_DPI_16_BIT_565_SHIFTED 2 +#define NWL_DSI_DPI_18_BIT_PACKED 3 +#define NWL_DSI_DPI_18_BIT_ALIGNED 4 +#define NWL_DSI_DPI_24_BIT 5 + +/* * DPI Pixel format */ +#define NWL_DSI_PIXEL_FORMAT_16 0 +#define NWL_DSI_PIXEL_FORMAT_18 BIT(0) +#define NWL_DSI_PIXEL_FORMAT_18L BIT(1) +#define NWL_DSI_PIXEL_FORMAT_24 (BIT(0) | BIT(1)) + +#endif /* __NWL_DSI_H__ */ diff --git a/drivers/gpu/drm/bridge/nxp-ptn3460.c b/drivers/gpu/drm/bridge/nxp-ptn3460.c new file mode 100644 index 000000000..e941c1132 --- /dev/null +++ b/drivers/gpu/drm/bridge/nxp-ptn3460.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP PTN3460 DP/LVDS bridge driver + * + * Copyright (C) 2013 Google, Inc. + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#define PTN3460_EDID_ADDR 0x0 +#define PTN3460_EDID_EMULATION_ADDR 0x84 +#define PTN3460_EDID_ENABLE_EMULATION 0 +#define PTN3460_EDID_EMULATION_SELECTION 1 +#define PTN3460_EDID_SRAM_LOAD_ADDR 0x85 + +struct ptn3460_bridge { + struct drm_connector connector; + struct i2c_client *client; + struct drm_bridge bridge; + struct drm_bridge *panel_bridge; + struct gpio_desc *gpio_pd_n; + struct gpio_desc *gpio_rst_n; + u32 edid_emulation; + bool enabled; +}; + +static inline struct ptn3460_bridge * + bridge_to_ptn3460(struct drm_bridge *bridge) +{ + return container_of(bridge, struct ptn3460_bridge, bridge); +} + +static inline struct ptn3460_bridge * + connector_to_ptn3460(struct drm_connector *connector) +{ + return container_of(connector, struct ptn3460_bridge, connector); +} + +static int ptn3460_read_bytes(struct ptn3460_bridge *ptn_bridge, char addr, + u8 *buf, int len) +{ + int ret; + + ret = i2c_master_send(ptn_bridge->client, &addr, 1); + if (ret <= 0) { + DRM_ERROR("Failed to send i2c command, ret=%d\n", ret); + return ret; + } + + ret = i2c_master_recv(ptn_bridge->client, buf, len); + if (ret <= 0) { + DRM_ERROR("Failed to recv i2c data, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int ptn3460_write_byte(struct ptn3460_bridge *ptn_bridge, char addr, + char val) +{ + int ret; + char buf[2]; + + buf[0] = addr; + buf[1] = val; + + ret = i2c_master_send(ptn_bridge->client, buf, ARRAY_SIZE(buf)); + if (ret <= 0) { + DRM_ERROR("Failed to send i2c command, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int ptn3460_select_edid(struct ptn3460_bridge *ptn_bridge) +{ + int ret; + char val; + + /* Load the selected edid into SRAM (accessed at PTN3460_EDID_ADDR) */ + ret = ptn3460_write_byte(ptn_bridge, PTN3460_EDID_SRAM_LOAD_ADDR, + ptn_bridge->edid_emulation); + if (ret) { + DRM_ERROR("Failed to transfer EDID to sram, ret=%d\n", ret); + return ret; + } + + /* Enable EDID emulation and select the desired EDID */ + val = 1 << PTN3460_EDID_ENABLE_EMULATION | + ptn_bridge->edid_emulation << PTN3460_EDID_EMULATION_SELECTION; + + ret = ptn3460_write_byte(ptn_bridge, PTN3460_EDID_EMULATION_ADDR, val); + if (ret) { + DRM_ERROR("Failed to write EDID value, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static void ptn3460_pre_enable(struct drm_bridge *bridge) +{ + struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge); + int ret; + + if (ptn_bridge->enabled) + return; + + gpiod_set_value(ptn_bridge->gpio_pd_n, 1); + + gpiod_set_value(ptn_bridge->gpio_rst_n, 0); + usleep_range(10, 20); + gpiod_set_value(ptn_bridge->gpio_rst_n, 1); + + /* + * There's a bug in the PTN chip where it falsely asserts hotplug before + * it is fully functional. We're forced to wait for the maximum start up + * time specified in the chip's datasheet to make sure we're really up. + */ + msleep(90); + + ret = ptn3460_select_edid(ptn_bridge); + if (ret) + DRM_ERROR("Select EDID failed ret=%d\n", ret); + + ptn_bridge->enabled = true; +} + +static void ptn3460_disable(struct drm_bridge *bridge) +{ + struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge); + + if (!ptn_bridge->enabled) + return; + + ptn_bridge->enabled = false; + + gpiod_set_value(ptn_bridge->gpio_rst_n, 1); + gpiod_set_value(ptn_bridge->gpio_pd_n, 0); +} + + +static struct edid *ptn3460_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge); + bool power_off; + u8 *edid; + int ret; + + power_off = !ptn_bridge->enabled; + ptn3460_pre_enable(&ptn_bridge->bridge); + + edid = kmalloc(EDID_LENGTH, GFP_KERNEL); + if (!edid) { + DRM_ERROR("Failed to allocate EDID\n"); + goto out; + } + + ret = ptn3460_read_bytes(ptn_bridge, PTN3460_EDID_ADDR, edid, + EDID_LENGTH); + if (ret) { + kfree(edid); + edid = NULL; + goto out; + } + +out: + if (power_off) + ptn3460_disable(&ptn_bridge->bridge); + + return (struct edid *)edid; +} + +static int ptn3460_connector_get_modes(struct drm_connector *connector) +{ + struct ptn3460_bridge *ptn_bridge = connector_to_ptn3460(connector); + struct edid *edid; + int num_modes; + + edid = ptn3460_get_edid(&ptn_bridge->bridge, connector); + drm_connector_update_edid_property(connector, edid); + num_modes = drm_add_edid_modes(connector, edid); + kfree(edid); + + return num_modes; +} + +static const struct drm_connector_helper_funcs ptn3460_connector_helper_funcs = { + .get_modes = ptn3460_connector_get_modes, +}; + +static const struct drm_connector_funcs ptn3460_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int ptn3460_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge); + int ret; + + /* Let this driver create connector if requested */ + ret = drm_bridge_attach(bridge->encoder, ptn_bridge->panel_bridge, + bridge, flags | DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret < 0) + return ret; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return 0; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + ptn_bridge->connector.polled = DRM_CONNECTOR_POLL_HPD; + ret = drm_connector_init(bridge->dev, &ptn_bridge->connector, + &ptn3460_connector_funcs, DRM_MODE_CONNECTOR_LVDS); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + drm_connector_helper_add(&ptn_bridge->connector, + &ptn3460_connector_helper_funcs); + drm_connector_register(&ptn_bridge->connector); + drm_connector_attach_encoder(&ptn_bridge->connector, + bridge->encoder); + + drm_helper_hpd_irq_event(ptn_bridge->connector.dev); + + return ret; +} + +static const struct drm_bridge_funcs ptn3460_bridge_funcs = { + .pre_enable = ptn3460_pre_enable, + .disable = ptn3460_disable, + .attach = ptn3460_bridge_attach, + .get_edid = ptn3460_get_edid, +}; + +static int ptn3460_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ptn3460_bridge *ptn_bridge; + struct drm_bridge *panel_bridge; + struct drm_panel *panel; + int ret; + + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) { + return -ENOMEM; + } + + ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0, &panel, NULL); + if (ret) + return ret; + + panel_bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(panel_bridge)) + return PTR_ERR(panel_bridge); + + ptn_bridge->panel_bridge = panel_bridge; + ptn_bridge->client = client; + + ptn_bridge->gpio_pd_n = devm_gpiod_get(&client->dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(ptn_bridge->gpio_pd_n)) { + ret = PTR_ERR(ptn_bridge->gpio_pd_n); + dev_err(dev, "cannot get gpio_pd_n %d\n", ret); + return ret; + } + + /* + * Request the reset pin low to avoid the bridge being + * initialized prematurely + */ + ptn_bridge->gpio_rst_n = devm_gpiod_get(&client->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ptn_bridge->gpio_rst_n)) { + ret = PTR_ERR(ptn_bridge->gpio_rst_n); + DRM_ERROR("cannot get gpio_rst_n %d\n", ret); + return ret; + } + + ret = of_property_read_u32(dev->of_node, "edid-emulation", + &ptn_bridge->edid_emulation); + if (ret) { + dev_err(dev, "Can't read EDID emulation value\n"); + return ret; + } + + ptn_bridge->bridge.funcs = &ptn3460_bridge_funcs; + ptn_bridge->bridge.ops = DRM_BRIDGE_OP_EDID; + ptn_bridge->bridge.type = DRM_MODE_CONNECTOR_LVDS; + ptn_bridge->bridge.of_node = dev->of_node; + drm_bridge_add(&ptn_bridge->bridge); + + i2c_set_clientdata(client, ptn_bridge); + + return 0; +} + +static int ptn3460_remove(struct i2c_client *client) +{ + struct ptn3460_bridge *ptn_bridge = i2c_get_clientdata(client); + + drm_bridge_remove(&ptn_bridge->bridge); + + return 0; +} + +static const struct i2c_device_id ptn3460_i2c_table[] = { + {"ptn3460", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ptn3460_i2c_table); + +static const struct of_device_id ptn3460_match[] = { + { .compatible = "nxp,ptn3460" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ptn3460_match); + +static struct i2c_driver ptn3460_driver = { + .id_table = ptn3460_i2c_table, + .probe = ptn3460_probe, + .remove = ptn3460_remove, + .driver = { + .name = "nxp,ptn3460", + .of_match_table = ptn3460_match, + }, +}; +module_i2c_driver(ptn3460_driver); + +MODULE_AUTHOR("Sean Paul <seanpaul@chromium.org>"); +MODULE_DESCRIPTION("NXP ptn3460 eDP-LVDS converter driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c new file mode 100644 index 000000000..c916f4b89 --- /dev/null +++ b/drivers/gpu/drm/bridge/panel.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 Laurent Pinchart <laurent.pinchart@ideasonboard.com> + * Copyright (C) 2017 Broadcom + */ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_encoder.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +struct panel_bridge { + struct drm_bridge bridge; + struct drm_connector connector; + struct drm_panel *panel; + u32 connector_type; +}; + +static inline struct panel_bridge * +drm_bridge_to_panel_bridge(struct drm_bridge *bridge) +{ + return container_of(bridge, struct panel_bridge, bridge); +} + +static inline struct panel_bridge * +drm_connector_to_panel_bridge(struct drm_connector *connector) +{ + return container_of(connector, struct panel_bridge, connector); +} + +static int panel_bridge_connector_get_modes(struct drm_connector *connector) +{ + struct panel_bridge *panel_bridge = + drm_connector_to_panel_bridge(connector); + + return drm_panel_get_modes(panel_bridge->panel, connector); +} + +static const struct drm_connector_helper_funcs +panel_bridge_connector_helper_funcs = { + .get_modes = panel_bridge_connector_get_modes, +}; + +static const struct drm_connector_funcs panel_bridge_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int panel_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge); + struct drm_connector *connector = &panel_bridge->connector; + int ret; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return 0; + + if (!bridge->encoder) { + DRM_ERROR("Missing encoder\n"); + return -ENODEV; + } + + drm_connector_helper_add(connector, + &panel_bridge_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, + &panel_bridge_connector_funcs, + panel_bridge->connector_type); + if (ret) { + DRM_ERROR("Failed to initialize connector\n"); + return ret; + } + + drm_connector_attach_encoder(&panel_bridge->connector, + bridge->encoder); + + return 0; +} + +static void panel_bridge_detach(struct drm_bridge *bridge) +{ + struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge); + struct drm_connector *connector = &panel_bridge->connector; + + /* + * Cleanup the connector if we know it was initialized. + * + * FIXME: This wouldn't be needed if the panel_bridge structure was + * allocated with drmm_kzalloc(). This might be tricky since the + * drm_device pointer can only be retrieved when the bridge is attached. + */ + if (connector->dev) + drm_connector_cleanup(connector); +} + +static void panel_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge); + + drm_panel_prepare(panel_bridge->panel); +} + +static void panel_bridge_enable(struct drm_bridge *bridge) +{ + struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge); + + drm_panel_enable(panel_bridge->panel); +} + +static void panel_bridge_disable(struct drm_bridge *bridge) +{ + struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge); + + drm_panel_disable(panel_bridge->panel); +} + +static void panel_bridge_post_disable(struct drm_bridge *bridge) +{ + struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge); + + drm_panel_unprepare(panel_bridge->panel); +} + +static int panel_bridge_get_modes(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge); + + return drm_panel_get_modes(panel_bridge->panel, connector); +} + +static const struct drm_bridge_funcs panel_bridge_bridge_funcs = { + .attach = panel_bridge_attach, + .detach = panel_bridge_detach, + .pre_enable = panel_bridge_pre_enable, + .enable = panel_bridge_enable, + .disable = panel_bridge_disable, + .post_disable = panel_bridge_post_disable, + .get_modes = panel_bridge_get_modes, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt, +}; + +/** + * drm_panel_bridge_add - Creates a &drm_bridge and &drm_connector that + * just calls the appropriate functions from &drm_panel. + * + * @panel: The drm_panel being wrapped. Must be non-NULL. + * + * For drivers converting from directly using drm_panel: The expected + * usage pattern is that during either encoder module probe or DSI + * host attach, a drm_panel will be looked up through + * drm_of_find_panel_or_bridge(). drm_panel_bridge_add() is used to + * wrap that panel in the new bridge, and the result can then be + * passed to drm_bridge_attach(). The drm_panel_prepare() and related + * functions can be dropped from the encoder driver (they're now + * called by the KMS helpers before calling into the encoder), along + * with connector creation. When done with the bridge (after + * drm_mode_config_cleanup() if the bridge has already been attached), then + * drm_panel_bridge_remove() to free it. + * + * The connector type is set to @panel->connector_type, which must be set to a + * known type. Calling this function with a panel whose connector type is + * DRM_MODE_CONNECTOR_Unknown will return ERR_PTR(-EINVAL). + * + * See devm_drm_panel_bridge_add() for an automatically managed version of this + * function. + */ +struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel) +{ + if (WARN_ON(panel->connector_type == DRM_MODE_CONNECTOR_Unknown)) + return ERR_PTR(-EINVAL); + + return drm_panel_bridge_add_typed(panel, panel->connector_type); +} +EXPORT_SYMBOL(drm_panel_bridge_add); + +/** + * drm_panel_bridge_add_typed - Creates a &drm_bridge and &drm_connector with + * an explicit connector type. + * @panel: The drm_panel being wrapped. Must be non-NULL. + * @connector_type: The connector type (DRM_MODE_CONNECTOR_*) + * + * This is just like drm_panel_bridge_add(), but forces the connector type to + * @connector_type instead of infering it from the panel. + * + * This function is deprecated and should not be used in new drivers. Use + * drm_panel_bridge_add() instead, and fix panel drivers as necessary if they + * don't report a connector type. + */ +struct drm_bridge *drm_panel_bridge_add_typed(struct drm_panel *panel, + u32 connector_type) +{ + struct panel_bridge *panel_bridge; + + if (!panel) + return ERR_PTR(-EINVAL); + + panel_bridge = devm_kzalloc(panel->dev, sizeof(*panel_bridge), + GFP_KERNEL); + if (!panel_bridge) + return ERR_PTR(-ENOMEM); + + panel_bridge->connector_type = connector_type; + panel_bridge->panel = panel; + + panel_bridge->bridge.funcs = &panel_bridge_bridge_funcs; +#ifdef CONFIG_OF + panel_bridge->bridge.of_node = panel->dev->of_node; +#endif + panel_bridge->bridge.ops = DRM_BRIDGE_OP_MODES; + panel_bridge->bridge.type = connector_type; + + drm_bridge_add(&panel_bridge->bridge); + + return &panel_bridge->bridge; +} +EXPORT_SYMBOL(drm_panel_bridge_add_typed); + +/** + * drm_panel_bridge_remove - Unregisters and frees a drm_bridge + * created by drm_panel_bridge_add(). + * + * @bridge: The drm_bridge being freed. + */ +void drm_panel_bridge_remove(struct drm_bridge *bridge) +{ + struct panel_bridge *panel_bridge; + + if (!bridge) + return; + + if (bridge->funcs != &panel_bridge_bridge_funcs) + return; + + panel_bridge = drm_bridge_to_panel_bridge(bridge); + + drm_bridge_remove(bridge); + devm_kfree(panel_bridge->panel->dev, bridge); +} +EXPORT_SYMBOL(drm_panel_bridge_remove); + +static void devm_drm_panel_bridge_release(struct device *dev, void *res) +{ + struct drm_bridge **bridge = res; + + drm_panel_bridge_remove(*bridge); +} + +/** + * devm_drm_panel_bridge_add - Creates a managed &drm_bridge and &drm_connector + * that just calls the appropriate functions from &drm_panel. + * @dev: device to tie the bridge lifetime to + * @panel: The drm_panel being wrapped. Must be non-NULL. + * + * This is the managed version of drm_panel_bridge_add() which automatically + * calls drm_panel_bridge_remove() when @dev is unbound. + */ +struct drm_bridge *devm_drm_panel_bridge_add(struct device *dev, + struct drm_panel *panel) +{ + if (WARN_ON(panel->connector_type == DRM_MODE_CONNECTOR_Unknown)) + return ERR_PTR(-EINVAL); + + return devm_drm_panel_bridge_add_typed(dev, panel, + panel->connector_type); +} +EXPORT_SYMBOL(devm_drm_panel_bridge_add); + +/** + * devm_drm_panel_bridge_add_typed - Creates a managed &drm_bridge and + * &drm_connector with an explicit connector type. + * @dev: device to tie the bridge lifetime to + * @panel: The drm_panel being wrapped. Must be non-NULL. + * @connector_type: The connector type (DRM_MODE_CONNECTOR_*) + * + * This is just like devm_drm_panel_bridge_add(), but forces the connector type + * to @connector_type instead of infering it from the panel. + * + * This function is deprecated and should not be used in new drivers. Use + * devm_drm_panel_bridge_add() instead, and fix panel drivers as necessary if + * they don't report a connector type. + */ +struct drm_bridge *devm_drm_panel_bridge_add_typed(struct device *dev, + struct drm_panel *panel, + u32 connector_type) +{ + struct drm_bridge **ptr, *bridge; + + ptr = devres_alloc(devm_drm_panel_bridge_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + bridge = drm_panel_bridge_add_typed(panel, connector_type); + if (!IS_ERR(bridge)) { + *ptr = bridge; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return bridge; +} +EXPORT_SYMBOL(devm_drm_panel_bridge_add_typed); + +/** + * drm_panel_bridge_connector - return the connector for the panel bridge + * @bridge: The drm_bridge. + * + * drm_panel_bridge creates the connector. + * This function gives external access to the connector. + * + * Returns: Pointer to drm_connector + */ +struct drm_connector *drm_panel_bridge_connector(struct drm_bridge *bridge) +{ + struct panel_bridge *panel_bridge; + + panel_bridge = drm_bridge_to_panel_bridge(bridge); + + return &panel_bridge->connector; +} +EXPORT_SYMBOL(drm_panel_bridge_connector); diff --git a/drivers/gpu/drm/bridge/parade-ps8622.c b/drivers/gpu/drm/bridge/parade-ps8622.c new file mode 100644 index 000000000..614b19f0f --- /dev/null +++ b/drivers/gpu/drm/bridge/parade-ps8622.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Parade PS8622 eDP/LVDS bridge driver + * + * Copyright (C) 2014 Google, Inc. + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pm.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +/* Brightness scale on the Parade chip */ +#define PS8622_MAX_BRIGHTNESS 0xff + +/* Timings taken from the version 1.7 datasheet for the PS8622/PS8625 */ +#define PS8622_POWER_RISE_T1_MIN_US 10 +#define PS8622_POWER_RISE_T1_MAX_US 10000 +#define PS8622_RST_HIGH_T2_MIN_US 3000 +#define PS8622_RST_HIGH_T2_MAX_US 30000 +#define PS8622_PWMO_END_T12_MS 200 +#define PS8622_POWER_FALL_T16_MAX_US 10000 +#define PS8622_POWER_OFF_T17_MS 500 + +#if ((PS8622_RST_HIGH_T2_MIN_US + PS8622_POWER_RISE_T1_MAX_US) > \ + (PS8622_RST_HIGH_T2_MAX_US + PS8622_POWER_RISE_T1_MIN_US)) +#error "T2.min + T1.max must be less than T2.max + T1.min" +#endif + +struct ps8622_bridge { + struct i2c_client *client; + struct drm_bridge bridge; + struct drm_bridge *panel_bridge; + struct regulator *v12; + struct backlight_device *bl; + + struct gpio_desc *gpio_slp; + struct gpio_desc *gpio_rst; + + u32 max_lane_count; + u32 lane_count; + + bool enabled; +}; + +static inline struct ps8622_bridge * + bridge_to_ps8622(struct drm_bridge *bridge) +{ + return container_of(bridge, struct ps8622_bridge, bridge); +} + +static int ps8622_set(struct i2c_client *client, u8 page, u8 reg, u8 val) +{ + int ret; + struct i2c_adapter *adap = client->adapter; + struct i2c_msg msg; + u8 data[] = {reg, val}; + + msg.addr = client->addr + page; + msg.flags = 0; + msg.len = sizeof(data); + msg.buf = data; + + ret = i2c_transfer(adap, &msg, 1); + if (ret != 1) + pr_warn("PS8622 I2C write (0x%02x,0x%02x,0x%02x) failed: %d\n", + client->addr + page, reg, val, ret); + return !(ret == 1); +} + +static int ps8622_send_config(struct ps8622_bridge *ps8622) +{ + struct i2c_client *cl = ps8622->client; + int err = 0; + + /* HPD low */ + err = ps8622_set(cl, 0x02, 0xa1, 0x01); + if (err) + goto error; + + /* SW setting: [1:0] SW output 1.2V voltage is lower to 96% */ + err = ps8622_set(cl, 0x04, 0x14, 0x01); + if (err) + goto error; + + /* RCO SS setting: [5:4] = b01 0.5%, b10 1%, b11 1.5% */ + err = ps8622_set(cl, 0x04, 0xe3, 0x20); + if (err) + goto error; + + /* [7] RCO SS enable */ + err = ps8622_set(cl, 0x04, 0xe2, 0x80); + if (err) + goto error; + + /* RPHY Setting + * [3:2] CDR tune wait cycle before measure for fine tune + * b00: 1us b01: 0.5us b10:2us, b11: 4us + */ + err = ps8622_set(cl, 0x04, 0x8a, 0x0c); + if (err) + goto error; + + /* [3] RFD always on */ + err = ps8622_set(cl, 0x04, 0x89, 0x08); + if (err) + goto error; + + /* CTN lock in/out: 20000ppm/80000ppm. Lock out 2 times. */ + err = ps8622_set(cl, 0x04, 0x71, 0x2d); + if (err) + goto error; + + /* 2.7G CDR settings: NOF=40LSB for HBR CDR setting */ + err = ps8622_set(cl, 0x04, 0x7d, 0x07); + if (err) + goto error; + + /* [1:0] Fmin=+4bands */ + err = ps8622_set(cl, 0x04, 0x7b, 0x00); + if (err) + goto error; + + /* [7:5] DCO_FTRNG=+-40% */ + err = ps8622_set(cl, 0x04, 0x7a, 0xfd); + if (err) + goto error; + + /* 1.62G CDR settings: [5:2]NOF=64LSB [1:0]DCO scale is 2/5 */ + err = ps8622_set(cl, 0x04, 0xc0, 0x12); + if (err) + goto error; + + /* Gitune=-37% */ + err = ps8622_set(cl, 0x04, 0xc1, 0x92); + if (err) + goto error; + + /* Fbstep=100% */ + err = ps8622_set(cl, 0x04, 0xc2, 0x1c); + if (err) + goto error; + + /* [7] LOS signal disable */ + err = ps8622_set(cl, 0x04, 0x32, 0x80); + if (err) + goto error; + + /* RPIO Setting: [7:4] LVDS driver bias current : 75% (250mV swing) */ + err = ps8622_set(cl, 0x04, 0x00, 0xb0); + if (err) + goto error; + + /* [7:6] Right-bar GPIO output strength is 8mA */ + err = ps8622_set(cl, 0x04, 0x15, 0x40); + if (err) + goto error; + + /* EQ Training State Machine Setting, RCO calibration start */ + err = ps8622_set(cl, 0x04, 0x54, 0x10); + if (err) + goto error; + + /* Logic, needs more than 10 I2C command */ + /* [4:0] MAX_LANE_COUNT set to max supported lanes */ + err = ps8622_set(cl, 0x01, 0x02, 0x80 | ps8622->max_lane_count); + if (err) + goto error; + + /* [4:0] LANE_COUNT_SET set to chosen lane count */ + err = ps8622_set(cl, 0x01, 0x21, 0x80 | ps8622->lane_count); + if (err) + goto error; + + err = ps8622_set(cl, 0x00, 0x52, 0x20); + if (err) + goto error; + + /* HPD CP toggle enable */ + err = ps8622_set(cl, 0x00, 0xf1, 0x03); + if (err) + goto error; + + err = ps8622_set(cl, 0x00, 0x62, 0x41); + if (err) + goto error; + + /* Counter number, add 1ms counter delay */ + err = ps8622_set(cl, 0x00, 0xf6, 0x01); + if (err) + goto error; + + /* [6]PWM function control by DPCD0040f[7], default is PWM block */ + err = ps8622_set(cl, 0x00, 0x77, 0x06); + if (err) + goto error; + + /* 04h Adjust VTotal toleranceto fix the 30Hz no display issue */ + err = ps8622_set(cl, 0x00, 0x4c, 0x04); + if (err) + goto error; + + /* DPCD00400='h00, Parade OUI ='h001cf8 */ + err = ps8622_set(cl, 0x01, 0xc0, 0x00); + if (err) + goto error; + + /* DPCD00401='h1c */ + err = ps8622_set(cl, 0x01, 0xc1, 0x1c); + if (err) + goto error; + + /* DPCD00402='hf8 */ + err = ps8622_set(cl, 0x01, 0xc2, 0xf8); + if (err) + goto error; + + /* DPCD403~408 = ASCII code, D2SLV5='h4432534c5635 */ + err = ps8622_set(cl, 0x01, 0xc3, 0x44); + if (err) + goto error; + + /* DPCD404 */ + err = ps8622_set(cl, 0x01, 0xc4, 0x32); + if (err) + goto error; + + /* DPCD405 */ + err = ps8622_set(cl, 0x01, 0xc5, 0x53); + if (err) + goto error; + + /* DPCD406 */ + err = ps8622_set(cl, 0x01, 0xc6, 0x4c); + if (err) + goto error; + + /* DPCD407 */ + err = ps8622_set(cl, 0x01, 0xc7, 0x56); + if (err) + goto error; + + /* DPCD408 */ + err = ps8622_set(cl, 0x01, 0xc8, 0x35); + if (err) + goto error; + + /* DPCD40A, Initial Code major revision '01' */ + err = ps8622_set(cl, 0x01, 0xca, 0x01); + if (err) + goto error; + + /* DPCD40B, Initial Code minor revision '05' */ + err = ps8622_set(cl, 0x01, 0xcb, 0x05); + if (err) + goto error; + + + if (ps8622->bl) { + /* DPCD720, internal PWM */ + err = ps8622_set(cl, 0x01, 0xa5, 0xa0); + if (err) + goto error; + + /* FFh for 100% brightness, 0h for 0% brightness */ + err = ps8622_set(cl, 0x01, 0xa7, + ps8622->bl->props.brightness); + if (err) + goto error; + } else { + /* DPCD720, external PWM */ + err = ps8622_set(cl, 0x01, 0xa5, 0x80); + if (err) + goto error; + } + + /* Set LVDS output as 6bit-VESA mapping, single LVDS channel */ + err = ps8622_set(cl, 0x01, 0xcc, 0x13); + if (err) + goto error; + + /* Enable SSC set by register */ + err = ps8622_set(cl, 0x02, 0xb1, 0x20); + if (err) + goto error; + + /* Set SSC enabled and +/-1% central spreading */ + err = ps8622_set(cl, 0x04, 0x10, 0x16); + if (err) + goto error; + + /* Logic end */ + /* MPU Clock source: LC => RCO */ + err = ps8622_set(cl, 0x04, 0x59, 0x60); + if (err) + goto error; + + /* LC -> RCO */ + err = ps8622_set(cl, 0x04, 0x54, 0x14); + if (err) + goto error; + + /* HPD high */ + err = ps8622_set(cl, 0x02, 0xa1, 0x91); + +error: + return err ? -EIO : 0; +} + +static int ps8622_backlight_update(struct backlight_device *bl) +{ + struct ps8622_bridge *ps8622 = dev_get_drvdata(&bl->dev); + int ret, brightness = bl->props.brightness; + + if (bl->props.power != FB_BLANK_UNBLANK || + bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) + brightness = 0; + + if (!ps8622->enabled) + return -EINVAL; + + ret = ps8622_set(ps8622->client, 0x01, 0xa7, brightness); + + return ret; +} + +static const struct backlight_ops ps8622_backlight_ops = { + .update_status = ps8622_backlight_update, +}; + +static void ps8622_pre_enable(struct drm_bridge *bridge) +{ + struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge); + int ret; + + if (ps8622->enabled) + return; + + gpiod_set_value(ps8622->gpio_rst, 0); + + if (ps8622->v12) { + ret = regulator_enable(ps8622->v12); + if (ret) + DRM_ERROR("fails to enable ps8622->v12"); + } + + gpiod_set_value(ps8622->gpio_slp, 1); + + /* + * T1 is the range of time that it takes for the power to rise after we + * enable the lcd/ps8622 fet. T2 is the range of time in which the + * data sheet specifies we should deassert the reset pin. + * + * If it takes T1.max for the power to rise, we need to wait atleast + * T2.min before deasserting the reset pin. If it takes T1.min for the + * power to rise, we need to wait at most T2.max before deasserting the + * reset pin. + */ + usleep_range(PS8622_RST_HIGH_T2_MIN_US + PS8622_POWER_RISE_T1_MAX_US, + PS8622_RST_HIGH_T2_MAX_US + PS8622_POWER_RISE_T1_MIN_US); + + gpiod_set_value(ps8622->gpio_rst, 1); + + /* wait 20ms after RST high */ + usleep_range(20000, 30000); + + ret = ps8622_send_config(ps8622); + if (ret) { + DRM_ERROR("Failed to send config to bridge (%d)\n", ret); + return; + } + + ps8622->enabled = true; +} + +static void ps8622_disable(struct drm_bridge *bridge) +{ + /* Delay after panel is disabled */ + msleep(PS8622_PWMO_END_T12_MS); +} + +static void ps8622_post_disable(struct drm_bridge *bridge) +{ + struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge); + + if (!ps8622->enabled) + return; + + ps8622->enabled = false; + + /* + * This doesn't matter if the regulators are turned off, but something + * else might keep them on. In that case, we want to assert the slp gpio + * to lower power. + */ + gpiod_set_value(ps8622->gpio_slp, 0); + + if (ps8622->v12) + regulator_disable(ps8622->v12); + + /* + * Sleep for at least the amount of time that it takes the power rail to + * fall to prevent asserting the rst gpio from doing anything. + */ + usleep_range(PS8622_POWER_FALL_T16_MAX_US, + 2 * PS8622_POWER_FALL_T16_MAX_US); + gpiod_set_value(ps8622->gpio_rst, 0); + + msleep(PS8622_POWER_OFF_T17_MS); +} + +static int ps8622_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge); + + return drm_bridge_attach(ps8622->bridge.encoder, ps8622->panel_bridge, + &ps8622->bridge, flags); +} + +static const struct drm_bridge_funcs ps8622_bridge_funcs = { + .pre_enable = ps8622_pre_enable, + .disable = ps8622_disable, + .post_disable = ps8622_post_disable, + .attach = ps8622_attach, +}; + +static const struct of_device_id ps8622_devices[] = { + {.compatible = "parade,ps8622",}, + {.compatible = "parade,ps8625",}, + {} +}; +MODULE_DEVICE_TABLE(of, ps8622_devices); + +static int ps8622_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ps8622_bridge *ps8622; + struct drm_bridge *panel_bridge; + struct drm_panel *panel; + int ret; + + ps8622 = devm_kzalloc(dev, sizeof(*ps8622), GFP_KERNEL); + if (!ps8622) + return -ENOMEM; + + ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0, &panel, NULL); + if (ret) + return ret; + + panel_bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(panel_bridge)) + return PTR_ERR(panel_bridge); + + ps8622->panel_bridge = panel_bridge; + ps8622->client = client; + + ps8622->v12 = devm_regulator_get(dev, "vdd12"); + if (IS_ERR(ps8622->v12)) { + dev_info(dev, "no 1.2v regulator found for PS8622\n"); + ps8622->v12 = NULL; + } + + ps8622->gpio_slp = devm_gpiod_get(dev, "sleep", GPIOD_OUT_HIGH); + if (IS_ERR(ps8622->gpio_slp)) { + ret = PTR_ERR(ps8622->gpio_slp); + dev_err(dev, "cannot get gpio_slp %d\n", ret); + return ret; + } + + /* + * Assert the reset pin high to avoid the bridge being + * initialized prematurely + */ + ps8622->gpio_rst = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ps8622->gpio_rst)) { + ret = PTR_ERR(ps8622->gpio_rst); + dev_err(dev, "cannot get gpio_rst %d\n", ret); + return ret; + } + + ps8622->max_lane_count = id->driver_data; + + if (of_property_read_u32(dev->of_node, "lane-count", + &ps8622->lane_count)) { + ps8622->lane_count = ps8622->max_lane_count; + } else if (ps8622->lane_count > ps8622->max_lane_count) { + dev_info(dev, "lane-count property is too high," + "using max_lane_count\n"); + ps8622->lane_count = ps8622->max_lane_count; + } + + if (!of_find_property(dev->of_node, "use-external-pwm", NULL)) { + ps8622->bl = backlight_device_register("ps8622-backlight", + dev, ps8622, &ps8622_backlight_ops, + NULL); + if (IS_ERR(ps8622->bl)) { + DRM_ERROR("failed to register backlight\n"); + ret = PTR_ERR(ps8622->bl); + ps8622->bl = NULL; + return ret; + } + ps8622->bl->props.max_brightness = PS8622_MAX_BRIGHTNESS; + ps8622->bl->props.brightness = PS8622_MAX_BRIGHTNESS; + } + + ps8622->bridge.funcs = &ps8622_bridge_funcs; + ps8622->bridge.type = DRM_MODE_CONNECTOR_LVDS; + ps8622->bridge.of_node = dev->of_node; + drm_bridge_add(&ps8622->bridge); + + i2c_set_clientdata(client, ps8622); + + return 0; +} + +static int ps8622_remove(struct i2c_client *client) +{ + struct ps8622_bridge *ps8622 = i2c_get_clientdata(client); + + backlight_device_unregister(ps8622->bl); + drm_bridge_remove(&ps8622->bridge); + + return 0; +} + +static const struct i2c_device_id ps8622_i2c_table[] = { + /* Device type, max_lane_count */ + {"ps8622", 1}, + {"ps8625", 2}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ps8622_i2c_table); + +static struct i2c_driver ps8622_driver = { + .id_table = ps8622_i2c_table, + .probe = ps8622_probe, + .remove = ps8622_remove, + .driver = { + .name = "ps8622", + .of_match_table = ps8622_devices, + }, +}; +module_i2c_driver(ps8622_driver); + +MODULE_AUTHOR("Vincent Palatin <vpalatin@chromium.org>"); +MODULE_DESCRIPTION("Parade ps8622/ps8625 eDP-LVDS converter driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c new file mode 100644 index 000000000..924851010 --- /dev/null +++ b/drivers/gpu/drm/bridge/parade-ps8640.c @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016 MediaTek Inc. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> + +#define PAGE2_GPIO_H 0xa7 +#define PS_GPIO9 BIT(1) +#define PAGE2_I2C_BYPASS 0xea +#define I2C_BYPASS_EN 0xd0 +#define PAGE2_MCS_EN 0xf3 +#define MCS_EN BIT(0) +#define PAGE3_SET_ADD 0xfe +#define VDO_CTL_ADD 0x13 +#define VDO_DIS 0x18 +#define VDO_EN 0x1c +#define DP_NUM_LANES 4 + +/* + * PS8640 uses multiple addresses: + * page[0]: for DP control + * page[1]: for VIDEO Bridge + * page[2]: for control top + * page[3]: for DSI Link Control1 + * page[4]: for MIPI Phy + * page[5]: for VPLL + * page[6]: for DSI Link Control2 + * page[7]: for SPI ROM mapping + */ +enum page_addr_offset { + PAGE0_DP_CNTL = 0, + PAGE1_VDO_BDG, + PAGE2_TOP_CNTL, + PAGE3_DSI_CNTL1, + PAGE4_MIPI_PHY, + PAGE5_VPLL, + PAGE6_DSI_CNTL2, + PAGE7_SPI_CNTL, + MAX_DEVS +}; + +enum ps8640_vdo_control { + DISABLE = VDO_DIS, + ENABLE = VDO_EN, +}; + +struct ps8640 { + struct drm_bridge bridge; + struct drm_bridge *panel_bridge; + struct mipi_dsi_device *dsi; + struct i2c_client *page[MAX_DEVS]; + struct regulator_bulk_data supplies[2]; + struct gpio_desc *gpio_reset; + struct gpio_desc *gpio_powerdown; + bool powered; +}; + +static inline struct ps8640 *bridge_to_ps8640(struct drm_bridge *e) +{ + return container_of(e, struct ps8640, bridge); +} + +static int ps8640_bridge_vdo_control(struct ps8640 *ps_bridge, + const enum ps8640_vdo_control ctrl) +{ + struct i2c_client *client = ps_bridge->page[PAGE3_DSI_CNTL1]; + u8 vdo_ctrl_buf[] = { VDO_CTL_ADD, ctrl }; + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, PAGE3_SET_ADD, + sizeof(vdo_ctrl_buf), + vdo_ctrl_buf); + if (ret < 0) { + DRM_ERROR("failed to %sable VDO: %d\n", + ctrl == ENABLE ? "en" : "dis", ret); + return ret; + } + + return 0; +} + +static void ps8640_bridge_poweron(struct ps8640 *ps_bridge) +{ + struct i2c_client *client = ps_bridge->page[PAGE2_TOP_CNTL]; + unsigned long timeout; + int ret, status; + + if (ps_bridge->powered) + return; + + ret = regulator_bulk_enable(ARRAY_SIZE(ps_bridge->supplies), + ps_bridge->supplies); + if (ret < 0) { + DRM_ERROR("cannot enable regulators %d\n", ret); + return; + } + + gpiod_set_value(ps_bridge->gpio_powerdown, 0); + gpiod_set_value(ps_bridge->gpio_reset, 1); + usleep_range(2000, 2500); + gpiod_set_value(ps_bridge->gpio_reset, 0); + + /* + * Wait for the ps8640 embedded MCU to be ready + * First wait 200ms and then check the MCU ready flag every 20ms + */ + msleep(200); + + timeout = jiffies + msecs_to_jiffies(200) + 1; + + while (time_is_after_jiffies(timeout)) { + status = i2c_smbus_read_byte_data(client, PAGE2_GPIO_H); + if (status < 0) { + DRM_ERROR("failed read PAGE2_GPIO_H: %d\n", status); + goto err_regulators_disable; + } + if ((status & PS_GPIO9) == PS_GPIO9) + break; + + msleep(20); + } + + msleep(50); + + /* + * The Manufacturer Command Set (MCS) is a device dependent interface + * intended for factory programming of the display module default + * parameters. Once the display module is configured, the MCS shall be + * disabled by the manufacturer. Once disabled, all MCS commands are + * ignored by the display interface. + */ + status = i2c_smbus_read_byte_data(client, PAGE2_MCS_EN); + if (status < 0) { + DRM_ERROR("failed read PAGE2_MCS_EN: %d\n", status); + goto err_regulators_disable; + } + + ret = i2c_smbus_write_byte_data(client, PAGE2_MCS_EN, + status & ~MCS_EN); + if (ret < 0) { + DRM_ERROR("failed write PAGE2_MCS_EN: %d\n", ret); + goto err_regulators_disable; + } + + /* Switch access edp panel's edid through i2c */ + ret = i2c_smbus_write_byte_data(client, PAGE2_I2C_BYPASS, + I2C_BYPASS_EN); + if (ret < 0) { + DRM_ERROR("failed write PAGE2_I2C_BYPASS: %d\n", ret); + goto err_regulators_disable; + } + + ps_bridge->powered = true; + + return; + +err_regulators_disable: + regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies), + ps_bridge->supplies); +} + +static void ps8640_bridge_poweroff(struct ps8640 *ps_bridge) +{ + int ret; + + if (!ps_bridge->powered) + return; + + gpiod_set_value(ps_bridge->gpio_reset, 1); + gpiod_set_value(ps_bridge->gpio_powerdown, 1); + ret = regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies), + ps_bridge->supplies); + if (ret < 0) + DRM_ERROR("cannot disable regulators %d\n", ret); + + ps_bridge->powered = false; +} + +static void ps8640_pre_enable(struct drm_bridge *bridge) +{ + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge); + int ret; + + ps8640_bridge_poweron(ps_bridge); + + ret = ps8640_bridge_vdo_control(ps_bridge, ENABLE); + if (ret < 0) + ps8640_bridge_poweroff(ps_bridge); +} + +static void ps8640_post_disable(struct drm_bridge *bridge) +{ + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge); + + ps8640_bridge_vdo_control(ps_bridge, DISABLE); + ps8640_bridge_poweroff(ps_bridge); +} + +static int ps8640_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge); + struct device *dev = &ps_bridge->page[0]->dev; + struct device_node *in_ep, *dsi_node; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *host; + int ret; + const struct mipi_dsi_device_info info = { .type = "ps8640", + .channel = 0, + .node = NULL, + }; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) + return -EINVAL; + + /* port@0 is ps8640 dsi input port */ + in_ep = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1); + if (!in_ep) + return -ENODEV; + + dsi_node = of_graph_get_remote_port_parent(in_ep); + of_node_put(in_ep); + if (!dsi_node) + return -ENODEV; + + host = of_find_mipi_dsi_host_by_node(dsi_node); + of_node_put(dsi_node); + if (!host) + return -ENODEV; + + dsi = mipi_dsi_device_register_full(host, &info); + if (IS_ERR(dsi)) { + dev_err(dev, "failed to create dsi device\n"); + ret = PTR_ERR(dsi); + return ret; + } + + ps_bridge->dsi = dsi; + + dsi->host = host; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->lanes = DP_NUM_LANES; + ret = mipi_dsi_attach(dsi); + if (ret) + goto err_dsi_attach; + + /* Attach the panel-bridge to the dsi bridge */ + return drm_bridge_attach(bridge->encoder, ps_bridge->panel_bridge, + &ps_bridge->bridge, flags); + +err_dsi_attach: + mipi_dsi_device_unregister(dsi); + return ret; +} + +static struct edid *ps8640_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge); + bool poweroff = !ps_bridge->powered; + struct edid *edid; + + /* + * When we end calling get_edid() triggered by an ioctl, i.e + * + * drm_mode_getconnector (ioctl) + * -> drm_helper_probe_single_connector_modes + * -> drm_bridge_connector_get_modes + * -> ps8640_bridge_get_edid + * + * We need to make sure that what we need is enabled before reading + * EDID, for this chip, we need to do a full poweron, otherwise it will + * fail. + */ + drm_bridge_chain_pre_enable(bridge); + + edid = drm_get_edid(connector, + ps_bridge->page[PAGE0_DP_CNTL]->adapter); + + /* + * If we call the get_edid() function without having enabled the chip + * before, return the chip to its original power state. + */ + if (poweroff) + drm_bridge_chain_post_disable(bridge); + + return edid; +} + +static const struct drm_bridge_funcs ps8640_bridge_funcs = { + .attach = ps8640_bridge_attach, + .get_edid = ps8640_bridge_get_edid, + .post_disable = ps8640_post_disable, + .pre_enable = ps8640_pre_enable, +}; + +static int ps8640_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device_node *np = dev->of_node; + struct ps8640 *ps_bridge; + struct drm_panel *panel; + int ret; + u32 i; + + ps_bridge = devm_kzalloc(dev, sizeof(*ps_bridge), GFP_KERNEL); + if (!ps_bridge) + return -ENOMEM; + + /* port@1 is ps8640 output port */ + ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL); + if (ret < 0) + return ret; + if (!panel) + return -ENODEV; + + ps_bridge->panel_bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(ps_bridge->panel_bridge)) + return PTR_ERR(ps_bridge->panel_bridge); + + ps_bridge->supplies[0].supply = "vdd12"; + ps_bridge->supplies[1].supply = "vdd33"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ps_bridge->supplies), + ps_bridge->supplies); + if (ret) + return ret; + + ps_bridge->gpio_powerdown = devm_gpiod_get(&client->dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(ps_bridge->gpio_powerdown)) + return PTR_ERR(ps_bridge->gpio_powerdown); + + /* + * Assert the reset to avoid the bridge being initialized prematurely + */ + ps_bridge->gpio_reset = devm_gpiod_get(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(ps_bridge->gpio_reset)) + return PTR_ERR(ps_bridge->gpio_reset); + + ps_bridge->bridge.funcs = &ps8640_bridge_funcs; + ps_bridge->bridge.of_node = dev->of_node; + ps_bridge->bridge.ops = DRM_BRIDGE_OP_EDID; + ps_bridge->bridge.type = DRM_MODE_CONNECTOR_eDP; + + ps_bridge->page[PAGE0_DP_CNTL] = client; + + for (i = 1; i < ARRAY_SIZE(ps_bridge->page); i++) { + ps_bridge->page[i] = devm_i2c_new_dummy_device(&client->dev, + client->adapter, + client->addr + i); + if (IS_ERR(ps_bridge->page[i])) { + dev_err(dev, "failed i2c dummy device, address %02x\n", + client->addr + i); + return PTR_ERR(ps_bridge->page[i]); + } + } + + i2c_set_clientdata(client, ps_bridge); + + drm_bridge_add(&ps_bridge->bridge); + + return 0; +} + +static int ps8640_remove(struct i2c_client *client) +{ + struct ps8640 *ps_bridge = i2c_get_clientdata(client); + + drm_bridge_remove(&ps_bridge->bridge); + + return 0; +} + +static const struct of_device_id ps8640_match[] = { + { .compatible = "parade,ps8640" }, + { } +}; +MODULE_DEVICE_TABLE(of, ps8640_match); + +static struct i2c_driver ps8640_driver = { + .probe_new = ps8640_probe, + .remove = ps8640_remove, + .driver = { + .name = "ps8640", + .of_match_table = ps8640_match, + }, +}; +module_i2c_driver(ps8640_driver); + +MODULE_AUTHOR("Jitao Shi <jitao.shi@mediatek.com>"); +MODULE_AUTHOR("CK Hu <ck.hu@mediatek.com>"); +MODULE_AUTHOR("Enric Balletbo i Serra <enric.balletbo@collabora.com>"); +MODULE_DESCRIPTION("PARADE ps8640 DSI-eDP converter driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c new file mode 100644 index 000000000..89558e581 --- /dev/null +++ b/drivers/gpu/drm/bridge/sii902x.c @@ -0,0 +1,1115 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Renesas Electronics + * + * Copyright (C) 2016 Atmel + * Bo Shen <voice.shen@atmel.com> + * + * Authors: Bo Shen <voice.shen@atmel.com> + * Boris Brezillon <boris.brezillon@free-electrons.com> + * Wu, Songjun <Songjun.Wu@atmel.com> + * + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +#include <linux/gpio/consumer.h> +#include <linux/i2c-mux.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/clk.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_drv.h> +#include <drm/drm_edid.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include <sound/hdmi-codec.h> + +#define SII902X_TPI_VIDEO_DATA 0x0 + +#define SII902X_TPI_PIXEL_REPETITION 0x8 +#define SII902X_TPI_AVI_PIXEL_REP_BUS_24BIT BIT(5) +#define SII902X_TPI_AVI_PIXEL_REP_RISING_EDGE BIT(4) +#define SII902X_TPI_AVI_PIXEL_REP_4X 3 +#define SII902X_TPI_AVI_PIXEL_REP_2X 1 +#define SII902X_TPI_AVI_PIXEL_REP_NONE 0 +#define SII902X_TPI_CLK_RATIO_HALF (0 << 6) +#define SII902X_TPI_CLK_RATIO_1X (1 << 6) +#define SII902X_TPI_CLK_RATIO_2X (2 << 6) +#define SII902X_TPI_CLK_RATIO_4X (3 << 6) + +#define SII902X_TPI_AVI_IN_FORMAT 0x9 +#define SII902X_TPI_AVI_INPUT_BITMODE_12BIT BIT(7) +#define SII902X_TPI_AVI_INPUT_DITHER BIT(6) +#define SII902X_TPI_AVI_INPUT_RANGE_LIMITED (2 << 2) +#define SII902X_TPI_AVI_INPUT_RANGE_FULL (1 << 2) +#define SII902X_TPI_AVI_INPUT_RANGE_AUTO (0 << 2) +#define SII902X_TPI_AVI_INPUT_COLORSPACE_BLACK (3 << 0) +#define SII902X_TPI_AVI_INPUT_COLORSPACE_YUV422 (2 << 0) +#define SII902X_TPI_AVI_INPUT_COLORSPACE_YUV444 (1 << 0) +#define SII902X_TPI_AVI_INPUT_COLORSPACE_RGB (0 << 0) + +#define SII902X_TPI_AVI_INFOFRAME 0x0c + +#define SII902X_SYS_CTRL_DATA 0x1a +#define SII902X_SYS_CTRL_PWR_DWN BIT(4) +#define SII902X_SYS_CTRL_AV_MUTE BIT(3) +#define SII902X_SYS_CTRL_DDC_BUS_REQ BIT(2) +#define SII902X_SYS_CTRL_DDC_BUS_GRTD BIT(1) +#define SII902X_SYS_CTRL_OUTPUT_MODE BIT(0) +#define SII902X_SYS_CTRL_OUTPUT_HDMI 1 +#define SII902X_SYS_CTRL_OUTPUT_DVI 0 + +#define SII902X_REG_CHIPID(n) (0x1b + (n)) + +#define SII902X_PWR_STATE_CTRL 0x1e +#define SII902X_AVI_POWER_STATE_MSK GENMASK(1, 0) +#define SII902X_AVI_POWER_STATE_D(l) ((l) & SII902X_AVI_POWER_STATE_MSK) + +/* Audio */ +#define SII902X_TPI_I2S_ENABLE_MAPPING_REG 0x1f +#define SII902X_TPI_I2S_CONFIG_FIFO0 (0 << 0) +#define SII902X_TPI_I2S_CONFIG_FIFO1 (1 << 0) +#define SII902X_TPI_I2S_CONFIG_FIFO2 (2 << 0) +#define SII902X_TPI_I2S_CONFIG_FIFO3 (3 << 0) +#define SII902X_TPI_I2S_LEFT_RIGHT_SWAP (1 << 2) +#define SII902X_TPI_I2S_AUTO_DOWNSAMPLE (1 << 3) +#define SII902X_TPI_I2S_SELECT_SD0 (0 << 4) +#define SII902X_TPI_I2S_SELECT_SD1 (1 << 4) +#define SII902X_TPI_I2S_SELECT_SD2 (2 << 4) +#define SII902X_TPI_I2S_SELECT_SD3 (3 << 4) +#define SII902X_TPI_I2S_FIFO_ENABLE (1 << 7) + +#define SII902X_TPI_I2S_INPUT_CONFIG_REG 0x20 +#define SII902X_TPI_I2S_FIRST_BIT_SHIFT_YES (0 << 0) +#define SII902X_TPI_I2S_FIRST_BIT_SHIFT_NO (1 << 0) +#define SII902X_TPI_I2S_SD_DIRECTION_MSB_FIRST (0 << 1) +#define SII902X_TPI_I2S_SD_DIRECTION_LSB_FIRST (1 << 1) +#define SII902X_TPI_I2S_SD_JUSTIFY_LEFT (0 << 2) +#define SII902X_TPI_I2S_SD_JUSTIFY_RIGHT (1 << 2) +#define SII902X_TPI_I2S_WS_POLARITY_LOW (0 << 3) +#define SII902X_TPI_I2S_WS_POLARITY_HIGH (1 << 3) +#define SII902X_TPI_I2S_MCLK_MULTIPLIER_128 (0 << 4) +#define SII902X_TPI_I2S_MCLK_MULTIPLIER_256 (1 << 4) +#define SII902X_TPI_I2S_MCLK_MULTIPLIER_384 (2 << 4) +#define SII902X_TPI_I2S_MCLK_MULTIPLIER_512 (3 << 4) +#define SII902X_TPI_I2S_MCLK_MULTIPLIER_768 (4 << 4) +#define SII902X_TPI_I2S_MCLK_MULTIPLIER_1024 (5 << 4) +#define SII902X_TPI_I2S_MCLK_MULTIPLIER_1152 (6 << 4) +#define SII902X_TPI_I2S_MCLK_MULTIPLIER_192 (7 << 4) +#define SII902X_TPI_I2S_SCK_EDGE_FALLING (0 << 7) +#define SII902X_TPI_I2S_SCK_EDGE_RISING (1 << 7) + +#define SII902X_TPI_I2S_STRM_HDR_BASE 0x21 +#define SII902X_TPI_I2S_STRM_HDR_SIZE 5 + +#define SII902X_TPI_AUDIO_CONFIG_BYTE2_REG 0x26 +#define SII902X_TPI_AUDIO_CODING_STREAM_HEADER (0 << 0) +#define SII902X_TPI_AUDIO_CODING_PCM (1 << 0) +#define SII902X_TPI_AUDIO_CODING_AC3 (2 << 0) +#define SII902X_TPI_AUDIO_CODING_MPEG1 (3 << 0) +#define SII902X_TPI_AUDIO_CODING_MP3 (4 << 0) +#define SII902X_TPI_AUDIO_CODING_MPEG2 (5 << 0) +#define SII902X_TPI_AUDIO_CODING_AAC (6 << 0) +#define SII902X_TPI_AUDIO_CODING_DTS (7 << 0) +#define SII902X_TPI_AUDIO_CODING_ATRAC (8 << 0) +#define SII902X_TPI_AUDIO_MUTE_DISABLE (0 << 4) +#define SII902X_TPI_AUDIO_MUTE_ENABLE (1 << 4) +#define SII902X_TPI_AUDIO_LAYOUT_2_CHANNELS (0 << 5) +#define SII902X_TPI_AUDIO_LAYOUT_8_CHANNELS (1 << 5) +#define SII902X_TPI_AUDIO_INTERFACE_DISABLE (0 << 6) +#define SII902X_TPI_AUDIO_INTERFACE_SPDIF (1 << 6) +#define SII902X_TPI_AUDIO_INTERFACE_I2S (2 << 6) + +#define SII902X_TPI_AUDIO_CONFIG_BYTE3_REG 0x27 +#define SII902X_TPI_AUDIO_FREQ_STREAM (0 << 3) +#define SII902X_TPI_AUDIO_FREQ_32KHZ (1 << 3) +#define SII902X_TPI_AUDIO_FREQ_44KHZ (2 << 3) +#define SII902X_TPI_AUDIO_FREQ_48KHZ (3 << 3) +#define SII902X_TPI_AUDIO_FREQ_88KHZ (4 << 3) +#define SII902X_TPI_AUDIO_FREQ_96KHZ (5 << 3) +#define SII902X_TPI_AUDIO_FREQ_176KHZ (6 << 3) +#define SII902X_TPI_AUDIO_FREQ_192KHZ (7 << 3) +#define SII902X_TPI_AUDIO_SAMPLE_SIZE_STREAM (0 << 6) +#define SII902X_TPI_AUDIO_SAMPLE_SIZE_16 (1 << 6) +#define SII902X_TPI_AUDIO_SAMPLE_SIZE_20 (2 << 6) +#define SII902X_TPI_AUDIO_SAMPLE_SIZE_24 (3 << 6) + +#define SII902X_TPI_AUDIO_CONFIG_BYTE4_REG 0x28 + +#define SII902X_INT_ENABLE 0x3c +#define SII902X_INT_STATUS 0x3d +#define SII902X_HOTPLUG_EVENT BIT(0) +#define SII902X_PLUGGED_STATUS BIT(2) + +#define SII902X_REG_TPI_RQB 0xc7 + +/* Indirect internal register access */ +#define SII902X_IND_SET_PAGE 0xbc +#define SII902X_IND_OFFSET 0xbd +#define SII902X_IND_VALUE 0xbe + +#define SII902X_TPI_MISC_INFOFRAME_BASE 0xbf +#define SII902X_TPI_MISC_INFOFRAME_END 0xde +#define SII902X_TPI_MISC_INFOFRAME_SIZE \ + (SII902X_TPI_MISC_INFOFRAME_END - SII902X_TPI_MISC_INFOFRAME_BASE) + +#define SII902X_I2C_BUS_ACQUISITION_TIMEOUT_MS 500 + +#define SII902X_AUDIO_PORT_INDEX 3 + +struct sii902x { + struct i2c_client *i2c; + struct regmap *regmap; + struct drm_bridge bridge; + struct drm_connector connector; + struct gpio_desc *reset_gpio; + struct i2c_mux_core *i2cmux; + struct regulator_bulk_data supplies[2]; + /* + * Mutex protects audio and video functions from interfering + * each other, by keeping their i2c command sequences atomic. + */ + struct mutex mutex; + struct sii902x_audio { + struct platform_device *pdev; + struct clk *mclk; + u32 i2s_fifo_sequence[4]; + } audio; +}; + +static int sii902x_read_unlocked(struct i2c_client *i2c, u8 reg, u8 *val) +{ + union i2c_smbus_data data; + int ret; + + ret = __i2c_smbus_xfer(i2c->adapter, i2c->addr, i2c->flags, + I2C_SMBUS_READ, reg, I2C_SMBUS_BYTE_DATA, &data); + + if (ret < 0) + return ret; + + *val = data.byte; + return 0; +} + +static int sii902x_write_unlocked(struct i2c_client *i2c, u8 reg, u8 val) +{ + union i2c_smbus_data data; + + data.byte = val; + + return __i2c_smbus_xfer(i2c->adapter, i2c->addr, i2c->flags, + I2C_SMBUS_WRITE, reg, I2C_SMBUS_BYTE_DATA, + &data); +} + +static int sii902x_update_bits_unlocked(struct i2c_client *i2c, u8 reg, u8 mask, + u8 val) +{ + int ret; + u8 status; + + ret = sii902x_read_unlocked(i2c, reg, &status); + if (ret) + return ret; + status &= ~mask; + status |= val & mask; + return sii902x_write_unlocked(i2c, reg, status); +} + +static inline struct sii902x *bridge_to_sii902x(struct drm_bridge *bridge) +{ + return container_of(bridge, struct sii902x, bridge); +} + +static inline struct sii902x *connector_to_sii902x(struct drm_connector *con) +{ + return container_of(con, struct sii902x, connector); +} + +static void sii902x_reset(struct sii902x *sii902x) +{ + if (!sii902x->reset_gpio) + return; + + gpiod_set_value(sii902x->reset_gpio, 1); + + /* The datasheet says treset-min = 100us. Make it 150us to be sure. */ + usleep_range(150, 200); + + gpiod_set_value(sii902x->reset_gpio, 0); +} + +static enum drm_connector_status +sii902x_connector_detect(struct drm_connector *connector, bool force) +{ + struct sii902x *sii902x = connector_to_sii902x(connector); + unsigned int status; + + mutex_lock(&sii902x->mutex); + + regmap_read(sii902x->regmap, SII902X_INT_STATUS, &status); + + mutex_unlock(&sii902x->mutex); + + return (status & SII902X_PLUGGED_STATUS) ? + connector_status_connected : connector_status_disconnected; +} + +static const struct drm_connector_funcs sii902x_connector_funcs = { + .detect = sii902x_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int sii902x_get_modes(struct drm_connector *connector) +{ + struct sii902x *sii902x = connector_to_sii902x(connector); + u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + u8 output_mode = SII902X_SYS_CTRL_OUTPUT_DVI; + struct edid *edid; + int num = 0, ret; + + mutex_lock(&sii902x->mutex); + + edid = drm_get_edid(connector, sii902x->i2cmux->adapter[0]); + drm_connector_update_edid_property(connector, edid); + if (edid) { + if (drm_detect_hdmi_monitor(edid)) + output_mode = SII902X_SYS_CTRL_OUTPUT_HDMI; + + num = drm_add_edid_modes(connector, edid); + kfree(edid); + } + + ret = drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + if (ret) + goto error_out; + + ret = regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, + SII902X_SYS_CTRL_OUTPUT_MODE, output_mode); + if (ret) + goto error_out; + + ret = num; + +error_out: + mutex_unlock(&sii902x->mutex); + + return ret; +} + +static enum drm_mode_status sii902x_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + /* TODO: check mode */ + + return MODE_OK; +} + +static const struct drm_connector_helper_funcs sii902x_connector_helper_funcs = { + .get_modes = sii902x_get_modes, + .mode_valid = sii902x_mode_valid, +}; + +static void sii902x_bridge_disable(struct drm_bridge *bridge) +{ + struct sii902x *sii902x = bridge_to_sii902x(bridge); + + mutex_lock(&sii902x->mutex); + + regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, + SII902X_SYS_CTRL_PWR_DWN, + SII902X_SYS_CTRL_PWR_DWN); + + mutex_unlock(&sii902x->mutex); +} + +static void sii902x_bridge_enable(struct drm_bridge *bridge) +{ + struct sii902x *sii902x = bridge_to_sii902x(bridge); + + mutex_lock(&sii902x->mutex); + + regmap_update_bits(sii902x->regmap, SII902X_PWR_STATE_CTRL, + SII902X_AVI_POWER_STATE_MSK, + SII902X_AVI_POWER_STATE_D(0)); + regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, + SII902X_SYS_CTRL_PWR_DWN, 0); + + mutex_unlock(&sii902x->mutex); +} + +static void sii902x_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adj) +{ + struct sii902x *sii902x = bridge_to_sii902x(bridge); + struct regmap *regmap = sii902x->regmap; + u8 buf[HDMI_INFOFRAME_SIZE(AVI)]; + struct hdmi_avi_infoframe frame; + u16 pixel_clock_10kHz = adj->clock / 10; + int ret; + + buf[0] = pixel_clock_10kHz & 0xff; + buf[1] = pixel_clock_10kHz >> 8; + buf[2] = drm_mode_vrefresh(adj); + buf[3] = 0x00; + buf[4] = adj->hdisplay; + buf[5] = adj->hdisplay >> 8; + buf[6] = adj->vdisplay; + buf[7] = adj->vdisplay >> 8; + buf[8] = SII902X_TPI_CLK_RATIO_1X | SII902X_TPI_AVI_PIXEL_REP_NONE | + SII902X_TPI_AVI_PIXEL_REP_BUS_24BIT; + buf[9] = SII902X_TPI_AVI_INPUT_RANGE_AUTO | + SII902X_TPI_AVI_INPUT_COLORSPACE_RGB; + + mutex_lock(&sii902x->mutex); + + ret = regmap_bulk_write(regmap, SII902X_TPI_VIDEO_DATA, buf, 10); + if (ret) + goto out; + + ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, + &sii902x->connector, adj); + if (ret < 0) { + DRM_ERROR("couldn't fill AVI infoframe\n"); + goto out; + } + + ret = hdmi_avi_infoframe_pack(&frame, buf, sizeof(buf)); + if (ret < 0) { + DRM_ERROR("failed to pack AVI infoframe: %d\n", ret); + goto out; + } + + /* Do not send the infoframe header, but keep the CRC field. */ + regmap_bulk_write(regmap, SII902X_TPI_AVI_INFOFRAME, + buf + HDMI_INFOFRAME_HEADER_SIZE - 1, + HDMI_AVI_INFOFRAME_SIZE + 1); + +out: + mutex_unlock(&sii902x->mutex); +} + +static int sii902x_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct sii902x *sii902x = bridge_to_sii902x(bridge); + struct drm_device *drm = bridge->dev; + int ret; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + DRM_ERROR("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + + drm_connector_helper_add(&sii902x->connector, + &sii902x_connector_helper_funcs); + + if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) { + dev_err(&sii902x->i2c->dev, + "sii902x driver is only compatible with DRM devices supporting atomic updates\n"); + return -ENOTSUPP; + } + + ret = drm_connector_init(drm, &sii902x->connector, + &sii902x_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) + return ret; + + if (sii902x->i2c->irq > 0) + sii902x->connector.polled = DRM_CONNECTOR_POLL_HPD; + else + sii902x->connector.polled = DRM_CONNECTOR_POLL_CONNECT; + + drm_connector_attach_encoder(&sii902x->connector, bridge->encoder); + + return 0; +} + +static const struct drm_bridge_funcs sii902x_bridge_funcs = { + .attach = sii902x_bridge_attach, + .mode_set = sii902x_bridge_mode_set, + .disable = sii902x_bridge_disable, + .enable = sii902x_bridge_enable, +}; + +static int sii902x_mute(struct sii902x *sii902x, bool mute) +{ + struct device *dev = &sii902x->i2c->dev; + unsigned int val = mute ? SII902X_TPI_AUDIO_MUTE_ENABLE : + SII902X_TPI_AUDIO_MUTE_DISABLE; + + dev_dbg(dev, "%s: %s\n", __func__, mute ? "Muted" : "Unmuted"); + + return regmap_update_bits(sii902x->regmap, + SII902X_TPI_AUDIO_CONFIG_BYTE2_REG, + SII902X_TPI_AUDIO_MUTE_ENABLE, val); +} + +static const int sii902x_mclk_div_table[] = { + 128, 256, 384, 512, 768, 1024, 1152, 192 }; + +static int sii902x_select_mclk_div(u8 *i2s_config_reg, unsigned int rate, + unsigned int mclk) +{ + int div = mclk / rate; + int distance = 100000; + u8 i, nearest = 0; + + for (i = 0; i < ARRAY_SIZE(sii902x_mclk_div_table); i++) { + unsigned int d = abs(div - sii902x_mclk_div_table[i]); + + if (d >= distance) + continue; + + nearest = i; + distance = d; + if (d == 0) + break; + } + + *i2s_config_reg |= nearest << 4; + + return sii902x_mclk_div_table[nearest]; +} + +static const struct sii902x_sample_freq { + u32 freq; + u8 val; +} sii902x_sample_freq[] = { + { .freq = 32000, .val = SII902X_TPI_AUDIO_FREQ_32KHZ }, + { .freq = 44000, .val = SII902X_TPI_AUDIO_FREQ_44KHZ }, + { .freq = 48000, .val = SII902X_TPI_AUDIO_FREQ_48KHZ }, + { .freq = 88000, .val = SII902X_TPI_AUDIO_FREQ_88KHZ }, + { .freq = 96000, .val = SII902X_TPI_AUDIO_FREQ_96KHZ }, + { .freq = 176000, .val = SII902X_TPI_AUDIO_FREQ_176KHZ }, + { .freq = 192000, .val = SII902X_TPI_AUDIO_FREQ_192KHZ }, +}; + +static int sii902x_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct sii902x *sii902x = dev_get_drvdata(dev); + u8 i2s_config_reg = SII902X_TPI_I2S_SD_DIRECTION_MSB_FIRST; + u8 config_byte2_reg = (SII902X_TPI_AUDIO_INTERFACE_I2S | + SII902X_TPI_AUDIO_MUTE_ENABLE | + SII902X_TPI_AUDIO_CODING_PCM); + u8 config_byte3_reg = 0; + u8 infoframe_buf[HDMI_INFOFRAME_SIZE(AUDIO)]; + unsigned long mclk_rate; + int i, ret; + + if (daifmt->bit_clk_master || daifmt->frame_clk_master) { + dev_dbg(dev, "%s: I2S master mode not supported\n", __func__); + return -EINVAL; + } + + switch (daifmt->fmt) { + case HDMI_I2S: + i2s_config_reg |= SII902X_TPI_I2S_FIRST_BIT_SHIFT_YES | + SII902X_TPI_I2S_SD_JUSTIFY_LEFT; + break; + case HDMI_RIGHT_J: + i2s_config_reg |= SII902X_TPI_I2S_SD_JUSTIFY_RIGHT; + break; + case HDMI_LEFT_J: + i2s_config_reg |= SII902X_TPI_I2S_SD_JUSTIFY_LEFT; + break; + default: + dev_dbg(dev, "%s: Unsupported i2s format %u\n", __func__, + daifmt->fmt); + return -EINVAL; + } + + if (daifmt->bit_clk_inv) + i2s_config_reg |= SII902X_TPI_I2S_SCK_EDGE_FALLING; + else + i2s_config_reg |= SII902X_TPI_I2S_SCK_EDGE_RISING; + + if (daifmt->frame_clk_inv) + i2s_config_reg |= SII902X_TPI_I2S_WS_POLARITY_LOW; + else + i2s_config_reg |= SII902X_TPI_I2S_WS_POLARITY_HIGH; + + if (params->channels > 2) + config_byte2_reg |= SII902X_TPI_AUDIO_LAYOUT_8_CHANNELS; + else + config_byte2_reg |= SII902X_TPI_AUDIO_LAYOUT_2_CHANNELS; + + switch (params->sample_width) { + case 16: + config_byte3_reg |= SII902X_TPI_AUDIO_SAMPLE_SIZE_16; + break; + case 20: + config_byte3_reg |= SII902X_TPI_AUDIO_SAMPLE_SIZE_20; + break; + case 24: + case 32: + config_byte3_reg |= SII902X_TPI_AUDIO_SAMPLE_SIZE_24; + break; + default: + dev_err(dev, "%s: Unsupported sample width %u\n", __func__, + params->sample_width); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(sii902x_sample_freq); i++) { + if (params->sample_rate == sii902x_sample_freq[i].freq) { + config_byte3_reg |= sii902x_sample_freq[i].val; + break; + } + } + + ret = clk_prepare_enable(sii902x->audio.mclk); + if (ret) { + dev_err(dev, "Enabling mclk failed: %d\n", ret); + return ret; + } + + if (sii902x->audio.mclk) { + mclk_rate = clk_get_rate(sii902x->audio.mclk); + ret = sii902x_select_mclk_div(&i2s_config_reg, + params->sample_rate, mclk_rate); + if (mclk_rate != ret * params->sample_rate) + dev_dbg(dev, "Inaccurate reference clock (%ld/%d != %u)\n", + mclk_rate, ret, params->sample_rate); + } + + mutex_lock(&sii902x->mutex); + + ret = regmap_write(sii902x->regmap, + SII902X_TPI_AUDIO_CONFIG_BYTE2_REG, + config_byte2_reg); + if (ret < 0) + goto out; + + ret = regmap_write(sii902x->regmap, SII902X_TPI_I2S_INPUT_CONFIG_REG, + i2s_config_reg); + if (ret) + goto out; + + for (i = 0; i < ARRAY_SIZE(sii902x->audio.i2s_fifo_sequence) && + sii902x->audio.i2s_fifo_sequence[i]; i++) + regmap_write(sii902x->regmap, + SII902X_TPI_I2S_ENABLE_MAPPING_REG, + sii902x->audio.i2s_fifo_sequence[i]); + + ret = regmap_write(sii902x->regmap, SII902X_TPI_AUDIO_CONFIG_BYTE3_REG, + config_byte3_reg); + if (ret) + goto out; + + ret = regmap_bulk_write(sii902x->regmap, SII902X_TPI_I2S_STRM_HDR_BASE, + params->iec.status, + min((size_t) SII902X_TPI_I2S_STRM_HDR_SIZE, + sizeof(params->iec.status))); + if (ret) + goto out; + + ret = hdmi_audio_infoframe_pack(¶ms->cea, infoframe_buf, + sizeof(infoframe_buf)); + if (ret < 0) { + dev_err(dev, "%s: Failed to pack audio infoframe: %d\n", + __func__, ret); + goto out; + } + + ret = regmap_bulk_write(sii902x->regmap, + SII902X_TPI_MISC_INFOFRAME_BASE, + infoframe_buf, + min(ret, SII902X_TPI_MISC_INFOFRAME_SIZE)); + if (ret) + goto out; + + /* Decode Level 0 Packets */ + ret = regmap_write(sii902x->regmap, SII902X_IND_SET_PAGE, 0x02); + if (ret) + goto out; + + ret = regmap_write(sii902x->regmap, SII902X_IND_OFFSET, 0x24); + if (ret) + goto out; + + ret = regmap_write(sii902x->regmap, SII902X_IND_VALUE, 0x02); + if (ret) + goto out; + + dev_dbg(dev, "%s: hdmi audio enabled\n", __func__); +out: + mutex_unlock(&sii902x->mutex); + + if (ret) { + clk_disable_unprepare(sii902x->audio.mclk); + dev_err(dev, "%s: hdmi audio enable failed: %d\n", __func__, + ret); + } + + return ret; +} + +static void sii902x_audio_shutdown(struct device *dev, void *data) +{ + struct sii902x *sii902x = dev_get_drvdata(dev); + + mutex_lock(&sii902x->mutex); + + regmap_write(sii902x->regmap, SII902X_TPI_AUDIO_CONFIG_BYTE2_REG, + SII902X_TPI_AUDIO_INTERFACE_DISABLE); + + mutex_unlock(&sii902x->mutex); + + clk_disable_unprepare(sii902x->audio.mclk); +} + +static int sii902x_audio_mute(struct device *dev, void *data, + bool enable, int direction) +{ + struct sii902x *sii902x = dev_get_drvdata(dev); + + mutex_lock(&sii902x->mutex); + + sii902x_mute(sii902x, enable); + + mutex_unlock(&sii902x->mutex); + + return 0; +} + +static int sii902x_audio_get_eld(struct device *dev, void *data, + uint8_t *buf, size_t len) +{ + struct sii902x *sii902x = dev_get_drvdata(dev); + + mutex_lock(&sii902x->mutex); + + memcpy(buf, sii902x->connector.eld, + min(sizeof(sii902x->connector.eld), len)); + + mutex_unlock(&sii902x->mutex); + + return 0; +} + +static int sii902x_audio_get_dai_id(struct snd_soc_component *component, + struct device_node *endpoint) +{ + struct of_endpoint of_ep; + int ret; + + ret = of_graph_parse_endpoint(endpoint, &of_ep); + if (ret < 0) + return ret; + + /* + * HDMI sound should be located at reg = <3> + * Return expected DAI index 0. + */ + if (of_ep.port == SII902X_AUDIO_PORT_INDEX) + return 0; + + return -EINVAL; +} + +static const struct hdmi_codec_ops sii902x_audio_codec_ops = { + .hw_params = sii902x_audio_hw_params, + .audio_shutdown = sii902x_audio_shutdown, + .mute_stream = sii902x_audio_mute, + .get_eld = sii902x_audio_get_eld, + .get_dai_id = sii902x_audio_get_dai_id, + .no_capture_mute = 1, +}; + +static int sii902x_audio_codec_init(struct sii902x *sii902x, + struct device *dev) +{ + static const u8 audio_fifo_id[] = { + SII902X_TPI_I2S_CONFIG_FIFO0, + SII902X_TPI_I2S_CONFIG_FIFO1, + SII902X_TPI_I2S_CONFIG_FIFO2, + SII902X_TPI_I2S_CONFIG_FIFO3, + }; + static const u8 i2s_lane_id[] = { + SII902X_TPI_I2S_SELECT_SD0, + SII902X_TPI_I2S_SELECT_SD1, + SII902X_TPI_I2S_SELECT_SD2, + SII902X_TPI_I2S_SELECT_SD3, + }; + struct hdmi_codec_pdata codec_data = { + .ops = &sii902x_audio_codec_ops, + .i2s = 1, /* Only i2s support for now. */ + .spdif = 0, + .max_i2s_channels = 0, + }; + u8 lanes[4]; + int num_lanes, i; + + if (!of_property_read_bool(dev->of_node, "#sound-dai-cells")) { + dev_dbg(dev, "%s: No \"#sound-dai-cells\", no audio\n", + __func__); + return 0; + } + + num_lanes = of_property_read_variable_u8_array(dev->of_node, + "sil,i2s-data-lanes", + lanes, 1, + ARRAY_SIZE(lanes)); + + if (num_lanes == -EINVAL) { + dev_dbg(dev, + "%s: No \"sil,i2s-data-lanes\", use default <0>\n", + __func__); + num_lanes = 1; + lanes[0] = 0; + } else if (num_lanes < 0) { + dev_err(dev, + "%s: Error gettin \"sil,i2s-data-lanes\": %d\n", + __func__, num_lanes); + return num_lanes; + } + codec_data.max_i2s_channels = 2 * num_lanes; + + for (i = 0; i < num_lanes; i++) + sii902x->audio.i2s_fifo_sequence[i] |= audio_fifo_id[i] | + i2s_lane_id[lanes[i]] | SII902X_TPI_I2S_FIFO_ENABLE; + + sii902x->audio.mclk = devm_clk_get_optional(dev, "mclk"); + if (IS_ERR(sii902x->audio.mclk)) { + dev_err(dev, "%s: No clock (audio mclk) found: %ld\n", + __func__, PTR_ERR(sii902x->audio.mclk)); + return PTR_ERR(sii902x->audio.mclk); + } + + sii902x->audio.pdev = platform_device_register_data( + dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO, + &codec_data, sizeof(codec_data)); + + return PTR_ERR_OR_ZERO(sii902x->audio.pdev); +} + +static const struct regmap_range sii902x_volatile_ranges[] = { + { .range_min = 0, .range_max = 0xff }, +}; + +static const struct regmap_access_table sii902x_volatile_table = { + .yes_ranges = sii902x_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(sii902x_volatile_ranges), +}; + +static const struct regmap_config sii902x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .disable_locking = true, /* struct sii902x mutex should be enough */ + .max_register = SII902X_TPI_MISC_INFOFRAME_END, + .volatile_table = &sii902x_volatile_table, + .cache_type = REGCACHE_NONE, +}; + +static irqreturn_t sii902x_interrupt(int irq, void *data) +{ + struct sii902x *sii902x = data; + unsigned int status = 0; + + mutex_lock(&sii902x->mutex); + + regmap_read(sii902x->regmap, SII902X_INT_STATUS, &status); + regmap_write(sii902x->regmap, SII902X_INT_STATUS, status); + + mutex_unlock(&sii902x->mutex); + + if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev) + drm_helper_hpd_irq_event(sii902x->bridge.dev); + + return IRQ_HANDLED; +} + +/* + * The purpose of sii902x_i2c_bypass_select is to enable the pass through + * mode of the HDMI transmitter. Do not use regmap from within this function, + * only use sii902x_*_unlocked functions to read/modify/write registers. + * We are holding the parent adapter lock here, keep this in mind before + * adding more i2c transactions. + * + * Also, since SII902X_SYS_CTRL_DATA is used with regmap_update_bits elsewhere + * in this driver, we need to make sure that we only touch 0x1A[2:1] from + * within sii902x_i2c_bypass_select and sii902x_i2c_bypass_deselect, and that + * we leave the remaining bits as we have found them. + */ +static int sii902x_i2c_bypass_select(struct i2c_mux_core *mux, u32 chan_id) +{ + struct sii902x *sii902x = i2c_mux_priv(mux); + struct device *dev = &sii902x->i2c->dev; + unsigned long timeout; + u8 status; + int ret; + + ret = sii902x_update_bits_unlocked(sii902x->i2c, SII902X_SYS_CTRL_DATA, + SII902X_SYS_CTRL_DDC_BUS_REQ, + SII902X_SYS_CTRL_DDC_BUS_REQ); + if (ret) + return ret; + + timeout = jiffies + + msecs_to_jiffies(SII902X_I2C_BUS_ACQUISITION_TIMEOUT_MS); + do { + ret = sii902x_read_unlocked(sii902x->i2c, SII902X_SYS_CTRL_DATA, + &status); + if (ret) + return ret; + } while (!(status & SII902X_SYS_CTRL_DDC_BUS_GRTD) && + time_before(jiffies, timeout)); + + if (!(status & SII902X_SYS_CTRL_DDC_BUS_GRTD)) { + dev_err(dev, "Failed to acquire the i2c bus\n"); + return -ETIMEDOUT; + } + + return sii902x_write_unlocked(sii902x->i2c, SII902X_SYS_CTRL_DATA, + status); +} + +/* + * The purpose of sii902x_i2c_bypass_deselect is to disable the pass through + * mode of the HDMI transmitter. Do not use regmap from within this function, + * only use sii902x_*_unlocked functions to read/modify/write registers. + * We are holding the parent adapter lock here, keep this in mind before + * adding more i2c transactions. + * + * Also, since SII902X_SYS_CTRL_DATA is used with regmap_update_bits elsewhere + * in this driver, we need to make sure that we only touch 0x1A[2:1] from + * within sii902x_i2c_bypass_select and sii902x_i2c_bypass_deselect, and that + * we leave the remaining bits as we have found them. + */ +static int sii902x_i2c_bypass_deselect(struct i2c_mux_core *mux, u32 chan_id) +{ + struct sii902x *sii902x = i2c_mux_priv(mux); + struct device *dev = &sii902x->i2c->dev; + unsigned long timeout; + unsigned int retries; + u8 status; + int ret; + + /* + * When the HDMI transmitter is in pass through mode, we need an + * (undocumented) additional delay between STOP and START conditions + * to guarantee the bus won't get stuck. + */ + udelay(30); + + /* + * Sometimes the I2C bus can stall after failure to use the + * EDID channel. Retry a few times to see if things clear + * up, else continue anyway. + */ + retries = 5; + do { + ret = sii902x_read_unlocked(sii902x->i2c, SII902X_SYS_CTRL_DATA, + &status); + retries--; + } while (ret && retries); + if (ret) { + dev_err(dev, "failed to read status (%d)\n", ret); + return ret; + } + + ret = sii902x_update_bits_unlocked(sii902x->i2c, SII902X_SYS_CTRL_DATA, + SII902X_SYS_CTRL_DDC_BUS_REQ | + SII902X_SYS_CTRL_DDC_BUS_GRTD, 0); + if (ret) + return ret; + + timeout = jiffies + + msecs_to_jiffies(SII902X_I2C_BUS_ACQUISITION_TIMEOUT_MS); + do { + ret = sii902x_read_unlocked(sii902x->i2c, SII902X_SYS_CTRL_DATA, + &status); + if (ret) + return ret; + } while (status & (SII902X_SYS_CTRL_DDC_BUS_REQ | + SII902X_SYS_CTRL_DDC_BUS_GRTD) && + time_before(jiffies, timeout)); + + if (status & (SII902X_SYS_CTRL_DDC_BUS_REQ | + SII902X_SYS_CTRL_DDC_BUS_GRTD)) { + dev_err(dev, "failed to release the i2c bus\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static const struct drm_bridge_timings default_sii902x_timings = { + .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE + | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE + | DRM_BUS_FLAG_DE_HIGH, +}; + +static int sii902x_init(struct sii902x *sii902x) +{ + struct device *dev = &sii902x->i2c->dev; + unsigned int status = 0; + u8 chipid[4]; + int ret; + + sii902x_reset(sii902x); + + ret = regmap_write(sii902x->regmap, SII902X_REG_TPI_RQB, 0x0); + if (ret) + return ret; + + ret = regmap_bulk_read(sii902x->regmap, SII902X_REG_CHIPID(0), + &chipid, 4); + if (ret) { + dev_err(dev, "regmap_read failed %d\n", ret); + return ret; + } + + if (chipid[0] != 0xb0) { + dev_err(dev, "Invalid chipid: %02x (expecting 0xb0)\n", + chipid[0]); + return -EINVAL; + } + + /* Clear all pending interrupts */ + regmap_read(sii902x->regmap, SII902X_INT_STATUS, &status); + regmap_write(sii902x->regmap, SII902X_INT_STATUS, status); + + if (sii902x->i2c->irq > 0) { + regmap_write(sii902x->regmap, SII902X_INT_ENABLE, + SII902X_HOTPLUG_EVENT); + + ret = devm_request_threaded_irq(dev, sii902x->i2c->irq, NULL, + sii902x_interrupt, + IRQF_ONESHOT, dev_name(dev), + sii902x); + if (ret) + return ret; + } + + sii902x->bridge.funcs = &sii902x_bridge_funcs; + sii902x->bridge.of_node = dev->of_node; + sii902x->bridge.timings = &default_sii902x_timings; + drm_bridge_add(&sii902x->bridge); + + sii902x_audio_codec_init(sii902x, dev); + + i2c_set_clientdata(sii902x->i2c, sii902x); + + sii902x->i2cmux = i2c_mux_alloc(sii902x->i2c->adapter, dev, + 1, 0, I2C_MUX_GATE, + sii902x_i2c_bypass_select, + sii902x_i2c_bypass_deselect); + if (!sii902x->i2cmux) + return -ENOMEM; + + sii902x->i2cmux->priv = sii902x; + return i2c_mux_add_adapter(sii902x->i2cmux, 0, 0, 0); +} + +static int sii902x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct sii902x *sii902x; + int ret; + + ret = i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA); + if (!ret) { + dev_err(dev, "I2C adapter not suitable\n"); + return -EIO; + } + + sii902x = devm_kzalloc(dev, sizeof(*sii902x), GFP_KERNEL); + if (!sii902x) + return -ENOMEM; + + sii902x->i2c = client; + sii902x->regmap = devm_regmap_init_i2c(client, &sii902x_regmap_config); + if (IS_ERR(sii902x->regmap)) + return PTR_ERR(sii902x->regmap); + + sii902x->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(sii902x->reset_gpio)) { + dev_err(dev, "Failed to retrieve/request reset gpio: %ld\n", + PTR_ERR(sii902x->reset_gpio)); + return PTR_ERR(sii902x->reset_gpio); + } + + mutex_init(&sii902x->mutex); + + sii902x->supplies[0].supply = "iovcc"; + sii902x->supplies[1].supply = "cvcc12"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(sii902x->supplies), + sii902x->supplies); + if (ret < 0) + return ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(sii902x->supplies), + sii902x->supplies); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to enable supplies"); + return ret; + } + + ret = sii902x_init(sii902x); + if (ret < 0) { + regulator_bulk_disable(ARRAY_SIZE(sii902x->supplies), + sii902x->supplies); + } + + return ret; +} + +static int sii902x_remove(struct i2c_client *client) + +{ + struct sii902x *sii902x = i2c_get_clientdata(client); + + i2c_mux_del_adapters(sii902x->i2cmux); + drm_bridge_remove(&sii902x->bridge); + regulator_bulk_disable(ARRAY_SIZE(sii902x->supplies), + sii902x->supplies); + + return 0; +} + +static const struct of_device_id sii902x_dt_ids[] = { + { .compatible = "sil,sii9022", }, + { } +}; +MODULE_DEVICE_TABLE(of, sii902x_dt_ids); + +static const struct i2c_device_id sii902x_i2c_ids[] = { + { "sii9022", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, sii902x_i2c_ids); + +static struct i2c_driver sii902x_driver = { + .probe = sii902x_probe, + .remove = sii902x_remove, + .driver = { + .name = "sii902x", + .of_match_table = sii902x_dt_ids, + }, + .id_table = sii902x_i2c_ids, +}; +module_i2c_driver(sii902x_driver); + +MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); +MODULE_DESCRIPTION("SII902x RGB -> HDMI bridges"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/sii9234.c b/drivers/gpu/drm/bridge/sii9234.c new file mode 100644 index 000000000..15c98a7bd --- /dev/null +++ b/drivers/gpu/drm/bridge/sii9234.c @@ -0,0 +1,972 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2017 Samsung Electronics + * + * Authors: + * Tomasz Stanislawski <t.stanislaws@samsung.com> + * Maciej Purski <m.purski@samsung.com> + * + * Based on sii9234 driver created by: + * Adam Hampson <ahampson@sta.samsung.com> + * Erik Gilling <konkers@android.com> + * Shankar Bandal <shankar.b@samsung.com> + * Dharam Kumar <dharam.kr@samsung.com> + */ +#include <drm/bridge/mhl.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_edid.h> + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#define CBUS_DEVCAP_OFFSET 0x80 + +#define SII9234_MHL_VERSION 0x11 +#define SII9234_SCRATCHPAD_SIZE 0x10 +#define SII9234_INT_STAT_SIZE 0x33 + +#define BIT_TMDS_CCTRL_TMDS_OE BIT(4) +#define MHL_HPD_OUT_OVR_EN BIT(4) +#define MHL_HPD_OUT_OVR_VAL BIT(5) +#define MHL_INIT_TIMEOUT 0x0C + +/* MHL Tx registers and bits */ +#define MHL_TX_SRST 0x05 +#define MHL_TX_SYSSTAT_REG 0x09 +#define MHL_TX_INTR1_REG 0x71 +#define MHL_TX_INTR4_REG 0x74 +#define MHL_TX_INTR1_ENABLE_REG 0x75 +#define MHL_TX_INTR4_ENABLE_REG 0x78 +#define MHL_TX_INT_CTRL_REG 0x79 +#define MHL_TX_TMDS_CCTRL 0x80 +#define MHL_TX_DISC_CTRL1_REG 0x90 +#define MHL_TX_DISC_CTRL2_REG 0x91 +#define MHL_TX_DISC_CTRL3_REG 0x92 +#define MHL_TX_DISC_CTRL4_REG 0x93 +#define MHL_TX_DISC_CTRL5_REG 0x94 +#define MHL_TX_DISC_CTRL6_REG 0x95 +#define MHL_TX_DISC_CTRL7_REG 0x96 +#define MHL_TX_DISC_CTRL8_REG 0x97 +#define MHL_TX_STAT2_REG 0x99 +#define MHL_TX_MHLTX_CTL1_REG 0xA0 +#define MHL_TX_MHLTX_CTL2_REG 0xA1 +#define MHL_TX_MHLTX_CTL4_REG 0xA3 +#define MHL_TX_MHLTX_CTL6_REG 0xA5 +#define MHL_TX_MHLTX_CTL7_REG 0xA6 + +#define RSEN_STATUS BIT(2) +#define HPD_CHANGE_INT BIT(6) +#define RSEN_CHANGE_INT BIT(5) +#define RGND_READY_INT BIT(6) +#define VBUS_LOW_INT BIT(5) +#define CBUS_LKOUT_INT BIT(4) +#define MHL_DISC_FAIL_INT BIT(3) +#define MHL_EST_INT BIT(2) +#define HPD_CHANGE_INT_MASK BIT(6) +#define RSEN_CHANGE_INT_MASK BIT(5) + +#define RGND_READY_MASK BIT(6) +#define CBUS_LKOUT_MASK BIT(4) +#define MHL_DISC_FAIL_MASK BIT(3) +#define MHL_EST_MASK BIT(2) + +#define SKIP_GND BIT(6) + +#define ATT_THRESH_SHIFT 0x04 +#define ATT_THRESH_MASK (0x03 << ATT_THRESH_SHIFT) +#define USB_D_OEN BIT(3) +#define DEGLITCH_TIME_MASK 0x07 +#define DEGLITCH_TIME_2MS 0 +#define DEGLITCH_TIME_4MS 1 +#define DEGLITCH_TIME_8MS 2 +#define DEGLITCH_TIME_16MS 3 +#define DEGLITCH_TIME_40MS 4 +#define DEGLITCH_TIME_50MS 5 +#define DEGLITCH_TIME_60MS 6 +#define DEGLITCH_TIME_128MS 7 + +#define USB_D_OVR BIT(7) +#define USB_ID_OVR BIT(6) +#define DVRFLT_SEL BIT(5) +#define BLOCK_RGND_INT BIT(4) +#define SKIP_DEG BIT(3) +#define CI2CA_POL BIT(2) +#define CI2CA_WKUP BIT(1) +#define SINGLE_ATT BIT(0) + +#define USB_D_ODN BIT(5) +#define VBUS_CHECK BIT(2) +#define RGND_INTP_MASK 0x03 +#define RGND_INTP_OPEN 0 +#define RGND_INTP_2K 1 +#define RGND_INTP_1K 2 +#define RGND_INTP_SHORT 3 + +/* HDMI registers */ +#define HDMI_RX_TMDS0_CCTRL1_REG 0x10 +#define HDMI_RX_TMDS_CLK_EN_REG 0x11 +#define HDMI_RX_TMDS_CH_EN_REG 0x12 +#define HDMI_RX_PLL_CALREFSEL_REG 0x17 +#define HDMI_RX_PLL_VCOCAL_REG 0x1A +#define HDMI_RX_EQ_DATA0_REG 0x22 +#define HDMI_RX_EQ_DATA1_REG 0x23 +#define HDMI_RX_EQ_DATA2_REG 0x24 +#define HDMI_RX_EQ_DATA3_REG 0x25 +#define HDMI_RX_EQ_DATA4_REG 0x26 +#define HDMI_RX_TMDS_ZONE_CTRL_REG 0x4C +#define HDMI_RX_TMDS_MODE_CTRL_REG 0x4D + +/* CBUS registers */ +#define CBUS_INT_STATUS_1_REG 0x08 +#define CBUS_INTR1_ENABLE_REG 0x09 +#define CBUS_MSC_REQ_ABORT_REASON_REG 0x0D +#define CBUS_INT_STATUS_2_REG 0x1E +#define CBUS_INTR2_ENABLE_REG 0x1F +#define CBUS_LINK_CONTROL_2_REG 0x31 +#define CBUS_MHL_STATUS_REG_0 0xB0 +#define CBUS_MHL_STATUS_REG_1 0xB1 + +#define BIT_CBUS_RESET BIT(3) +#define SET_HPD_DOWNSTREAM BIT(6) + +/* TPI registers */ +#define TPI_DPD_REG 0x3D + +/* Timeouts in msec */ +#define T_SRC_VBUS_CBUS_TO_STABLE 200 +#define T_SRC_CBUS_FLOAT 100 +#define T_SRC_CBUS_DEGLITCH 2 +#define T_SRC_RXSENSE_DEGLITCH 110 + +#define MHL1_MAX_CLK 75000 /* in kHz */ + +#define I2C_TPI_ADDR 0x3D +#define I2C_HDMI_ADDR 0x49 +#define I2C_CBUS_ADDR 0x64 + +enum sii9234_state { + ST_OFF, + ST_D3, + ST_RGND_INIT, + ST_RGND_1K, + ST_RSEN_HIGH, + ST_MHL_ESTABLISHED, + ST_FAILURE_DISCOVERY, + ST_FAILURE, +}; + +struct sii9234 { + struct i2c_client *client[4]; + struct drm_bridge bridge; + struct device *dev; + struct gpio_desc *gpio_reset; + int i2c_error; + struct regulator_bulk_data supplies[4]; + + struct mutex lock; /* Protects fields below and device registers */ + enum sii9234_state state; +}; + +enum sii9234_client_id { + I2C_MHL, + I2C_TPI, + I2C_HDMI, + I2C_CBUS, +}; + +static const char * const sii9234_client_name[] = { + [I2C_MHL] = "MHL", + [I2C_TPI] = "TPI", + [I2C_HDMI] = "HDMI", + [I2C_CBUS] = "CBUS", +}; + +static int sii9234_writeb(struct sii9234 *ctx, int id, int offset, + int value) +{ + int ret; + struct i2c_client *client = ctx->client[id]; + + if (ctx->i2c_error) + return ctx->i2c_error; + + ret = i2c_smbus_write_byte_data(client, offset, value); + if (ret < 0) + dev_err(ctx->dev, "writeb: %4s[0x%02x] <- 0x%02x\n", + sii9234_client_name[id], offset, value); + ctx->i2c_error = ret; + + return ret; +} + +static int sii9234_writebm(struct sii9234 *ctx, int id, int offset, + int value, int mask) +{ + int ret; + struct i2c_client *client = ctx->client[id]; + + if (ctx->i2c_error) + return ctx->i2c_error; + + ret = i2c_smbus_write_byte(client, offset); + if (ret < 0) { + dev_err(ctx->dev, "writebm: %4s[0x%02x] <- 0x%02x\n", + sii9234_client_name[id], offset, value); + ctx->i2c_error = ret; + return ret; + } + + ret = i2c_smbus_read_byte(client); + if (ret < 0) { + dev_err(ctx->dev, "writebm: %4s[0x%02x] <- 0x%02x\n", + sii9234_client_name[id], offset, value); + ctx->i2c_error = ret; + return ret; + } + + value = (value & mask) | (ret & ~mask); + + ret = i2c_smbus_write_byte_data(client, offset, value); + if (ret < 0) { + dev_err(ctx->dev, "writebm: %4s[0x%02x] <- 0x%02x\n", + sii9234_client_name[id], offset, value); + ctx->i2c_error = ret; + } + + return ret; +} + +static int sii9234_readb(struct sii9234 *ctx, int id, int offset) +{ + int ret; + struct i2c_client *client = ctx->client[id]; + + if (ctx->i2c_error) + return ctx->i2c_error; + + ret = i2c_smbus_write_byte(client, offset); + if (ret < 0) { + dev_err(ctx->dev, "readb: %4s[0x%02x]\n", + sii9234_client_name[id], offset); + ctx->i2c_error = ret; + return ret; + } + + ret = i2c_smbus_read_byte(client); + if (ret < 0) { + dev_err(ctx->dev, "readb: %4s[0x%02x]\n", + sii9234_client_name[id], offset); + ctx->i2c_error = ret; + } + + return ret; +} + +static int sii9234_clear_error(struct sii9234 *ctx) +{ + int ret = ctx->i2c_error; + + ctx->i2c_error = 0; + + return ret; +} + +#define mhl_tx_writeb(sii9234, offset, value) \ + sii9234_writeb(sii9234, I2C_MHL, offset, value) +#define mhl_tx_writebm(sii9234, offset, value, mask) \ + sii9234_writebm(sii9234, I2C_MHL, offset, value, mask) +#define mhl_tx_readb(sii9234, offset) \ + sii9234_readb(sii9234, I2C_MHL, offset) +#define cbus_writeb(sii9234, offset, value) \ + sii9234_writeb(sii9234, I2C_CBUS, offset, value) +#define cbus_writebm(sii9234, offset, value, mask) \ + sii9234_writebm(sii9234, I2C_CBUS, offset, value, mask) +#define cbus_readb(sii9234, offset) \ + sii9234_readb(sii9234, I2C_CBUS, offset) +#define hdmi_writeb(sii9234, offset, value) \ + sii9234_writeb(sii9234, I2C_HDMI, offset, value) +#define hdmi_writebm(sii9234, offset, value, mask) \ + sii9234_writebm(sii9234, I2C_HDMI, offset, value, mask) +#define hdmi_readb(sii9234, offset) \ + sii9234_readb(sii9234, I2C_HDMI, offset) +#define tpi_writeb(sii9234, offset, value) \ + sii9234_writeb(sii9234, I2C_TPI, offset, value) +#define tpi_writebm(sii9234, offset, value, mask) \ + sii9234_writebm(sii9234, I2C_TPI, offset, value, mask) +#define tpi_readb(sii9234, offset) \ + sii9234_readb(sii9234, I2C_TPI, offset) + +static u8 sii9234_tmds_control(struct sii9234 *ctx, bool enable) +{ + mhl_tx_writebm(ctx, MHL_TX_TMDS_CCTRL, enable ? ~0 : 0, + BIT_TMDS_CCTRL_TMDS_OE); + mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, enable ? ~0 : 0, + MHL_HPD_OUT_OVR_EN | MHL_HPD_OUT_OVR_VAL); + return sii9234_clear_error(ctx); +} + +static int sii9234_cbus_reset(struct sii9234 *ctx) +{ + int i; + + mhl_tx_writebm(ctx, MHL_TX_SRST, ~0, BIT_CBUS_RESET); + msleep(T_SRC_CBUS_DEGLITCH); + mhl_tx_writebm(ctx, MHL_TX_SRST, 0, BIT_CBUS_RESET); + + for (i = 0; i < 4; i++) { + /* + * Enable WRITE_STAT interrupt for writes to all + * 4 MSC Status registers. + */ + cbus_writeb(ctx, 0xE0 + i, 0xF2); + /* + * Enable SET_INT interrupt for writes to all + * 4 MSC Interrupt registers. + */ + cbus_writeb(ctx, 0xF0 + i, 0xF2); + } + + return sii9234_clear_error(ctx); +} + +/* Require to chek mhl imformation of samsung in cbus_init_register */ +static int sii9234_cbus_init(struct sii9234 *ctx) +{ + cbus_writeb(ctx, 0x07, 0xF2); + cbus_writeb(ctx, 0x40, 0x03); + cbus_writeb(ctx, 0x42, 0x06); + cbus_writeb(ctx, 0x36, 0x0C); + cbus_writeb(ctx, 0x3D, 0xFD); + cbus_writeb(ctx, 0x1C, 0x01); + cbus_writeb(ctx, 0x1D, 0x0F); + cbus_writeb(ctx, 0x44, 0x02); + /* Setup our devcap */ + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_DEV_STATE, 0x00); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_MHL_VERSION, + SII9234_MHL_VERSION); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_CAT, + MHL_DCAP_CAT_SOURCE); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_ADOPTER_ID_H, 0x01); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_ADOPTER_ID_L, 0x41); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_VID_LINK_MODE, + MHL_DCAP_VID_LINK_RGB444 | MHL_DCAP_VID_LINK_YCBCR444); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_VIDEO_TYPE, + MHL_DCAP_VT_GRAPHICS); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_LOG_DEV_MAP, + MHL_DCAP_LD_GUI); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_BANDWIDTH, 0x0F); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_FEATURE_FLAG, + MHL_DCAP_FEATURE_RCP_SUPPORT | MHL_DCAP_FEATURE_RAP_SUPPORT + | MHL_DCAP_FEATURE_SP_SUPPORT); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_DEVICE_ID_H, 0x0); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_DEVICE_ID_L, 0x0); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_SCRATCHPAD_SIZE, + SII9234_SCRATCHPAD_SIZE); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_INT_STAT_SIZE, + SII9234_INT_STAT_SIZE); + cbus_writeb(ctx, CBUS_DEVCAP_OFFSET + MHL_DCAP_RESERVED, 0); + cbus_writebm(ctx, 0x31, 0x0C, 0x0C); + cbus_writeb(ctx, 0x30, 0x01); + cbus_writebm(ctx, 0x3C, 0x30, 0x38); + cbus_writebm(ctx, 0x22, 0x0D, 0x0F); + cbus_writebm(ctx, 0x2E, 0x15, 0x15); + cbus_writeb(ctx, CBUS_INTR1_ENABLE_REG, 0); + cbus_writeb(ctx, CBUS_INTR2_ENABLE_REG, 0); + + return sii9234_clear_error(ctx); +} + +static void force_usb_id_switch_open(struct sii9234 *ctx) +{ + /* Disable CBUS discovery */ + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, 0, 0x01); + /* Force USB ID switch to open */ + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, ~0, USB_ID_OVR); + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL3_REG, ~0, 0x86); + /* Force upstream HPD to 0 when not in MHL mode. */ + mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, 0, 0x30); +} + +static void release_usb_id_switch_open(struct sii9234 *ctx) +{ + msleep(T_SRC_CBUS_FLOAT); + /* Clear USB ID switch to open */ + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, 0, USB_ID_OVR); + /* Enable CBUS discovery */ + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, ~0, 0x01); +} + +static int sii9234_power_init(struct sii9234 *ctx) +{ + /* Force the SiI9234 into the D0 state. */ + tpi_writeb(ctx, TPI_DPD_REG, 0x3F); + /* Enable TxPLL Clock */ + hdmi_writeb(ctx, HDMI_RX_TMDS_CLK_EN_REG, 0x01); + /* Enable Tx Clock Path & Equalizer */ + hdmi_writeb(ctx, HDMI_RX_TMDS_CH_EN_REG, 0x15); + /* Power Up TMDS */ + mhl_tx_writeb(ctx, 0x08, 0x35); + return sii9234_clear_error(ctx); +} + +static int sii9234_hdmi_init(struct sii9234 *ctx) +{ + hdmi_writeb(ctx, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1); + hdmi_writeb(ctx, HDMI_RX_PLL_CALREFSEL_REG, 0x03); + hdmi_writeb(ctx, HDMI_RX_PLL_VCOCAL_REG, 0x20); + hdmi_writeb(ctx, HDMI_RX_EQ_DATA0_REG, 0x8A); + hdmi_writeb(ctx, HDMI_RX_EQ_DATA1_REG, 0x6A); + hdmi_writeb(ctx, HDMI_RX_EQ_DATA2_REG, 0xAA); + hdmi_writeb(ctx, HDMI_RX_EQ_DATA3_REG, 0xCA); + hdmi_writeb(ctx, HDMI_RX_EQ_DATA4_REG, 0xEA); + hdmi_writeb(ctx, HDMI_RX_TMDS_ZONE_CTRL_REG, 0xA0); + hdmi_writeb(ctx, HDMI_RX_TMDS_MODE_CTRL_REG, 0x00); + mhl_tx_writeb(ctx, MHL_TX_TMDS_CCTRL, 0x34); + hdmi_writeb(ctx, 0x45, 0x44); + hdmi_writeb(ctx, 0x31, 0x0A); + hdmi_writeb(ctx, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1); + + return sii9234_clear_error(ctx); +} + +static int sii9234_mhl_tx_ctl_int(struct sii9234 *ctx) +{ + mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL1_REG, 0xD0); + mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL2_REG, 0xFC); + mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL4_REG, 0xEB); + mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL7_REG, 0x0C); + + return sii9234_clear_error(ctx); +} + +static int sii9234_reset(struct sii9234 *ctx) +{ + int ret; + + sii9234_clear_error(ctx); + + ret = sii9234_power_init(ctx); + if (ret < 0) + return ret; + ret = sii9234_cbus_reset(ctx); + if (ret < 0) + return ret; + ret = sii9234_hdmi_init(ctx); + if (ret < 0) + return ret; + ret = sii9234_mhl_tx_ctl_int(ctx); + if (ret < 0) + return ret; + + /* Enable HDCP Compliance safety */ + mhl_tx_writeb(ctx, 0x2B, 0x01); + /* CBUS discovery cycle time for each drive and float = 150us */ + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, 0x04, 0x06); + /* Clear bit 6 (reg_skip_rgnd) */ + mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL2_REG, (1 << 7) /* Reserved */ + | 2 << ATT_THRESH_SHIFT | DEGLITCH_TIME_50MS); + /* + * Changed from 66 to 65 for 94[1:0] = 01 = 5k reg_cbusmhl_pup_sel + * 1.8V CBUS VTH & GND threshold + * to meet CTS 3.3.7.2 spec + */ + mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL5_REG, 0x77); + cbus_writebm(ctx, CBUS_LINK_CONTROL_2_REG, ~0, MHL_INIT_TIMEOUT); + mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL6_REG, 0xA0); + /* RGND & single discovery attempt (RGND blocking) */ + mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL6_REG, BLOCK_RGND_INT | + DVRFLT_SEL | SINGLE_ATT); + /* Use VBUS path of discovery state machine */ + mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL8_REG, 0); + /* 0x92[3] sets the CBUS / ID switch */ + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, ~0, USB_ID_OVR); + /* + * To allow RGND engine to operate correctly. + * When moving the chip from D2 to D0 (power up, init regs) + * the values should be + * 94[1:0] = 01 reg_cbusmhl_pup_sel[1:0] should be set for 5k + * 93[7:6] = 10 reg_cbusdisc_pup_sel[1:0] should be + * set for 10k (default) + * 93[5:4] = 00 reg_cbusidle_pup_sel[1:0] = open (default) + */ + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL3_REG, ~0, 0x86); + /* + * Change from CC to 8C to match 5K + * to meet CTS 3.3.72 spec + */ + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL4_REG, ~0, 0x8C); + /* Configure the interrupt as active high */ + mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, 0, 0x06); + + msleep(25); + + /* Release usb_id switch */ + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, 0, USB_ID_OVR); + mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL1_REG, 0x27); + + ret = sii9234_clear_error(ctx); + if (ret < 0) + return ret; + ret = sii9234_cbus_init(ctx); + if (ret < 0) + return ret; + + /* Enable Auto soft reset on SCDT = 0 */ + mhl_tx_writeb(ctx, 0x05, 0x04); + /* HDMI Transcode mode enable */ + mhl_tx_writeb(ctx, 0x0D, 0x1C); + mhl_tx_writeb(ctx, MHL_TX_INTR4_ENABLE_REG, + RGND_READY_MASK | CBUS_LKOUT_MASK + | MHL_DISC_FAIL_MASK | MHL_EST_MASK); + mhl_tx_writeb(ctx, MHL_TX_INTR1_ENABLE_REG, 0x60); + + /* This point is very important before measure RGND impedance */ + force_usb_id_switch_open(ctx); + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL4_REG, 0, 0xF0); + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL5_REG, 0, 0x03); + release_usb_id_switch_open(ctx); + + /* Force upstream HPD to 0 when not in MHL mode */ + mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, 0, 1 << 5); + mhl_tx_writebm(ctx, MHL_TX_INT_CTRL_REG, ~0, 1 << 4); + + return sii9234_clear_error(ctx); +} + +static int sii9234_goto_d3(struct sii9234 *ctx) +{ + int ret; + + dev_dbg(ctx->dev, "sii9234: detection started d3\n"); + + ret = sii9234_reset(ctx); + if (ret < 0) + goto exit; + + hdmi_writeb(ctx, 0x01, 0x03); + tpi_writebm(ctx, TPI_DPD_REG, 0, 1); + /* I2C above is expected to fail because power goes down */ + sii9234_clear_error(ctx); + + ctx->state = ST_D3; + + return 0; + exit: + dev_err(ctx->dev, "%s failed\n", __func__); + return -1; +} + +static int sii9234_hw_on(struct sii9234 *ctx) +{ + return regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); +} + +static void sii9234_hw_off(struct sii9234 *ctx) +{ + gpiod_set_value(ctx->gpio_reset, 1); + msleep(20); + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); +} + +static void sii9234_hw_reset(struct sii9234 *ctx) +{ + gpiod_set_value(ctx->gpio_reset, 1); + msleep(20); + gpiod_set_value(ctx->gpio_reset, 0); +} + +static void sii9234_cable_in(struct sii9234 *ctx) +{ + int ret; + + mutex_lock(&ctx->lock); + if (ctx->state != ST_OFF) + goto unlock; + ret = sii9234_hw_on(ctx); + if (ret < 0) + goto unlock; + + sii9234_hw_reset(ctx); + sii9234_goto_d3(ctx); + /* To avoid irq storm, when hw is in meta state */ + enable_irq(to_i2c_client(ctx->dev)->irq); + +unlock: + mutex_unlock(&ctx->lock); +} + +static void sii9234_cable_out(struct sii9234 *ctx) +{ + mutex_lock(&ctx->lock); + + if (ctx->state == ST_OFF) + goto unlock; + + disable_irq(to_i2c_client(ctx->dev)->irq); + tpi_writeb(ctx, TPI_DPD_REG, 0); + /* Turn on&off hpd festure for only QCT HDMI */ + sii9234_hw_off(ctx); + + ctx->state = ST_OFF; + +unlock: + mutex_unlock(&ctx->lock); +} + +static enum sii9234_state sii9234_rgnd_ready_irq(struct sii9234 *ctx) +{ + int value; + + if (ctx->state == ST_D3) { + int ret; + + dev_dbg(ctx->dev, "RGND_READY_INT\n"); + sii9234_hw_reset(ctx); + + ret = sii9234_reset(ctx); + if (ret < 0) { + dev_err(ctx->dev, "sii9234_reset() failed\n"); + return ST_FAILURE; + } + + return ST_RGND_INIT; + } + + /* Got interrupt in inappropriate state */ + if (ctx->state != ST_RGND_INIT) + return ST_FAILURE; + + value = mhl_tx_readb(ctx, MHL_TX_STAT2_REG); + if (sii9234_clear_error(ctx)) + return ST_FAILURE; + + if ((value & RGND_INTP_MASK) != RGND_INTP_1K) { + dev_warn(ctx->dev, "RGND is not 1k\n"); + return ST_RGND_INIT; + } + dev_dbg(ctx->dev, "RGND 1K!!\n"); + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL4_REG, ~0, 0x8C); + mhl_tx_writeb(ctx, MHL_TX_DISC_CTRL5_REG, 0x77); + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL6_REG, ~0, 0x05); + if (sii9234_clear_error(ctx)) + return ST_FAILURE; + + msleep(T_SRC_VBUS_CBUS_TO_STABLE); + return ST_RGND_1K; +} + +static enum sii9234_state sii9234_mhl_established(struct sii9234 *ctx) +{ + dev_dbg(ctx->dev, "mhl est interrupt\n"); + + /* Discovery override */ + mhl_tx_writeb(ctx, MHL_TX_MHLTX_CTL1_REG, 0x10); + /* Increase DDC translation layer timer (byte mode) */ + cbus_writeb(ctx, 0x07, 0x32); + cbus_writebm(ctx, 0x44, ~0, 1 << 1); + /* Keep the discovery enabled. Need RGND interrupt */ + mhl_tx_writebm(ctx, MHL_TX_DISC_CTRL1_REG, ~0, 1); + mhl_tx_writeb(ctx, MHL_TX_INTR1_ENABLE_REG, + RSEN_CHANGE_INT_MASK | HPD_CHANGE_INT_MASK); + + if (sii9234_clear_error(ctx)) + return ST_FAILURE; + + return ST_MHL_ESTABLISHED; +} + +static enum sii9234_state sii9234_hpd_change(struct sii9234 *ctx) +{ + int value; + + value = cbus_readb(ctx, CBUS_MSC_REQ_ABORT_REASON_REG); + if (sii9234_clear_error(ctx)) + return ST_FAILURE; + + if (value & SET_HPD_DOWNSTREAM) { + /* Downstream HPD High, Enable TMDS */ + sii9234_tmds_control(ctx, true); + } else { + /* Downstream HPD Low, Disable TMDS */ + sii9234_tmds_control(ctx, false); + } + + return ctx->state; +} + +static enum sii9234_state sii9234_rsen_change(struct sii9234 *ctx) +{ + int value; + + /* Work_around code to handle wrong interrupt */ + if (ctx->state != ST_RGND_1K) { + dev_err(ctx->dev, "RSEN_HIGH without RGND_1K\n"); + return ST_FAILURE; + } + value = mhl_tx_readb(ctx, MHL_TX_SYSSTAT_REG); + if (value < 0) + return ST_FAILURE; + + if (value & RSEN_STATUS) { + dev_dbg(ctx->dev, "MHL cable connected.. RSEN High\n"); + return ST_RSEN_HIGH; + } + dev_dbg(ctx->dev, "RSEN lost\n"); + /* + * Once RSEN loss is confirmed,we need to check + * based on cable status and chip power status,whether + * it is SINK Loss(HDMI cable not connected, TV Off) + * or MHL cable disconnection + * TODO: Define the below mhl_disconnection() + */ + msleep(T_SRC_RXSENSE_DEGLITCH); + value = mhl_tx_readb(ctx, MHL_TX_SYSSTAT_REG); + if (value < 0) + return ST_FAILURE; + dev_dbg(ctx->dev, "sys_stat: %x\n", value); + + if (value & RSEN_STATUS) { + dev_dbg(ctx->dev, "RSEN recovery\n"); + return ST_RSEN_HIGH; + } + dev_dbg(ctx->dev, "RSEN Really LOW\n"); + /* To meet CTS 3.3.22.2 spec */ + sii9234_tmds_control(ctx, false); + force_usb_id_switch_open(ctx); + release_usb_id_switch_open(ctx); + + return ST_FAILURE; +} + +static irqreturn_t sii9234_irq_thread(int irq, void *data) +{ + struct sii9234 *ctx = data; + int intr1, intr4; + int intr1_en, intr4_en; + int cbus_intr1, cbus_intr2; + + dev_dbg(ctx->dev, "%s\n", __func__); + + mutex_lock(&ctx->lock); + + intr1 = mhl_tx_readb(ctx, MHL_TX_INTR1_REG); + intr4 = mhl_tx_readb(ctx, MHL_TX_INTR4_REG); + intr1_en = mhl_tx_readb(ctx, MHL_TX_INTR1_ENABLE_REG); + intr4_en = mhl_tx_readb(ctx, MHL_TX_INTR4_ENABLE_REG); + cbus_intr1 = cbus_readb(ctx, CBUS_INT_STATUS_1_REG); + cbus_intr2 = cbus_readb(ctx, CBUS_INT_STATUS_2_REG); + + if (sii9234_clear_error(ctx)) + goto done; + + dev_dbg(ctx->dev, "irq %02x/%02x %02x/%02x %02x/%02x\n", + intr1, intr1_en, intr4, intr4_en, cbus_intr1, cbus_intr2); + + if (intr4 & RGND_READY_INT) + ctx->state = sii9234_rgnd_ready_irq(ctx); + if (intr1 & RSEN_CHANGE_INT) + ctx->state = sii9234_rsen_change(ctx); + if (intr4 & MHL_EST_INT) + ctx->state = sii9234_mhl_established(ctx); + if (intr1 & HPD_CHANGE_INT) + ctx->state = sii9234_hpd_change(ctx); + if (intr4 & CBUS_LKOUT_INT) + ctx->state = ST_FAILURE; + if (intr4 & MHL_DISC_FAIL_INT) + ctx->state = ST_FAILURE_DISCOVERY; + + done: + /* Clean interrupt status and pending flags */ + mhl_tx_writeb(ctx, MHL_TX_INTR1_REG, intr1); + mhl_tx_writeb(ctx, MHL_TX_INTR4_REG, intr4); + cbus_writeb(ctx, CBUS_MHL_STATUS_REG_0, 0xFF); + cbus_writeb(ctx, CBUS_MHL_STATUS_REG_1, 0xFF); + cbus_writeb(ctx, CBUS_INT_STATUS_1_REG, cbus_intr1); + cbus_writeb(ctx, CBUS_INT_STATUS_2_REG, cbus_intr2); + + sii9234_clear_error(ctx); + + if (ctx->state == ST_FAILURE) { + dev_dbg(ctx->dev, "try to reset after failure\n"); + sii9234_hw_reset(ctx); + sii9234_goto_d3(ctx); + } + + if (ctx->state == ST_FAILURE_DISCOVERY) { + dev_err(ctx->dev, "discovery failed, no power for MHL?\n"); + tpi_writebm(ctx, TPI_DPD_REG, 0, 1); + ctx->state = ST_D3; + } + + mutex_unlock(&ctx->lock); + + return IRQ_HANDLED; +} + +static int sii9234_init_resources(struct sii9234 *ctx, + struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + int ret; + + if (!ctx->dev->of_node) { + dev_err(ctx->dev, "not DT device\n"); + return -ENODEV; + } + + ctx->gpio_reset = devm_gpiod_get(ctx->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->gpio_reset)) { + dev_err(ctx->dev, "failed to get reset gpio from DT\n"); + return PTR_ERR(ctx->gpio_reset); + } + + ctx->supplies[0].supply = "avcc12"; + ctx->supplies[1].supply = "avcc33"; + ctx->supplies[2].supply = "iovcc18"; + ctx->supplies[3].supply = "cvcc12"; + ret = devm_regulator_bulk_get(ctx->dev, 4, ctx->supplies); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(ctx->dev, "regulator_bulk failed\n"); + return ret; + } + + ctx->client[I2C_MHL] = client; + + ctx->client[I2C_TPI] = devm_i2c_new_dummy_device(&client->dev, adapter, + I2C_TPI_ADDR); + if (IS_ERR(ctx->client[I2C_TPI])) { + dev_err(ctx->dev, "failed to create TPI client\n"); + return PTR_ERR(ctx->client[I2C_TPI]); + } + + ctx->client[I2C_HDMI] = devm_i2c_new_dummy_device(&client->dev, adapter, + I2C_HDMI_ADDR); + if (IS_ERR(ctx->client[I2C_HDMI])) { + dev_err(ctx->dev, "failed to create HDMI RX client\n"); + return PTR_ERR(ctx->client[I2C_HDMI]); + } + + ctx->client[I2C_CBUS] = devm_i2c_new_dummy_device(&client->dev, adapter, + I2C_CBUS_ADDR); + if (IS_ERR(ctx->client[I2C_CBUS])) { + dev_err(ctx->dev, "failed to create CBUS client\n"); + return PTR_ERR(ctx->client[I2C_CBUS]); + } + + return 0; +} + +static inline struct sii9234 *bridge_to_sii9234(struct drm_bridge *bridge) +{ + return container_of(bridge, struct sii9234, bridge); +} + +static enum drm_mode_status sii9234_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->clock > MHL1_MAX_CLK) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static const struct drm_bridge_funcs sii9234_bridge_funcs = { + .mode_valid = sii9234_mode_valid, +}; + +static int sii9234_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct sii9234 *ctx; + struct device *dev = &client->dev; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->dev = dev; + mutex_init(&ctx->lock); + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(dev, "I2C adapter lacks SMBUS feature\n"); + return -EIO; + } + + if (!client->irq) { + dev_err(dev, "no irq provided\n"); + return -EINVAL; + } + + irq_set_status_flags(client->irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(dev, client->irq, NULL, + sii9234_irq_thread, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "sii9234", ctx); + if (ret < 0) { + dev_err(dev, "failed to install IRQ handler\n"); + return ret; + } + + ret = sii9234_init_resources(ctx, client); + if (ret < 0) + return ret; + + i2c_set_clientdata(client, ctx); + + ctx->bridge.funcs = &sii9234_bridge_funcs; + ctx->bridge.of_node = dev->of_node; + drm_bridge_add(&ctx->bridge); + + sii9234_cable_in(ctx); + + return 0; +} + +static int sii9234_remove(struct i2c_client *client) +{ + struct sii9234 *ctx = i2c_get_clientdata(client); + + sii9234_cable_out(ctx); + drm_bridge_remove(&ctx->bridge); + + return 0; +} + +static const struct of_device_id sii9234_dt_match[] = { + { .compatible = "sil,sii9234" }, + { }, +}; +MODULE_DEVICE_TABLE(of, sii9234_dt_match); + +static const struct i2c_device_id sii9234_id[] = { + { "SII9234", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, sii9234_id); + +static struct i2c_driver sii9234_driver = { + .driver = { + .name = "sii9234", + .of_match_table = sii9234_dt_match, + }, + .probe = sii9234_probe, + .remove = sii9234_remove, + .id_table = sii9234_id, +}; + +module_i2c_driver(sii9234_driver); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c new file mode 100644 index 000000000..ab0bce4a9 --- /dev/null +++ b/drivers/gpu/drm/bridge/sil-sii8620.c @@ -0,0 +1,2390 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Silicon Image SiI8620 HDMI/MHL bridge driver + * + * Copyright (C) 2015, Samsung Electronics Co., Ltd. + * Andrzej Hajda <a.hajda@samsung.com> + */ + +#include <asm/unaligned.h> + +#include <drm/bridge/mhl.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_edid.h> +#include <drm/drm_encoder.h> + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/extcon.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <media/rc-core.h> + +#include "sil-sii8620.h" + +#define SII8620_BURST_BUF_LEN 288 +#define VAL_RX_HDMI_CTRL2_DEFVAL VAL_RX_HDMI_CTRL2_IDLE_CNT(3) + +#define MHL1_MAX_PCLK 75000 +#define MHL1_MAX_PCLK_PP_MODE 150000 +#define MHL3_MAX_PCLK 200000 +#define MHL3_MAX_PCLK_PP_MODE 300000 + +enum sii8620_mode { + CM_DISCONNECTED, + CM_DISCOVERY, + CM_MHL1, + CM_MHL3, + CM_ECBUS_S +}; + +enum sii8620_sink_type { + SINK_NONE, + SINK_HDMI, + SINK_DVI +}; + +enum sii8620_mt_state { + MT_STATE_READY, + MT_STATE_BUSY, + MT_STATE_DONE +}; + +struct sii8620 { + struct drm_bridge bridge; + struct device *dev; + struct rc_dev *rc_dev; + struct clk *clk_xtal; + struct gpio_desc *gpio_reset; + struct gpio_desc *gpio_int; + struct regulator_bulk_data supplies[2]; + struct mutex lock; /* context lock, protects fields below */ + int error; + unsigned int use_packed_pixel:1; + enum sii8620_mode mode; + enum sii8620_sink_type sink_type; + u8 cbus_status; + u8 stat[MHL_DST_SIZE]; + u8 xstat[MHL_XDS_SIZE]; + u8 devcap[MHL_DCAP_SIZE]; + u8 xdevcap[MHL_XDC_SIZE]; + bool feature_complete; + bool devcap_read; + bool sink_detected; + struct edid *edid; + unsigned int gen2_write_burst:1; + enum sii8620_mt_state mt_state; + struct extcon_dev *extcon; + struct notifier_block extcon_nb; + struct work_struct extcon_wq; + int cable_state; + struct list_head mt_queue; + struct { + int r_size; + int r_count; + int rx_ack; + int rx_count; + u8 rx_buf[32]; + int tx_count; + u8 tx_buf[32]; + } burst; +}; + +struct sii8620_mt_msg; + +typedef void (*sii8620_mt_msg_cb)(struct sii8620 *ctx, + struct sii8620_mt_msg *msg); + +typedef void (*sii8620_cb)(struct sii8620 *ctx, int ret); + +struct sii8620_mt_msg { + struct list_head node; + u8 reg[4]; + u8 ret; + sii8620_mt_msg_cb send; + sii8620_mt_msg_cb recv; + sii8620_cb continuation; +}; + +static const u8 sii8620_i2c_page[] = { + 0x39, /* Main System */ + 0x3d, /* TDM and HSIC */ + 0x49, /* TMDS Receiver, MHL EDID */ + 0x4d, /* eMSC, HDCP, HSIC */ + 0x5d, /* MHL Spec */ + 0x64, /* MHL CBUS */ + 0x59, /* Hardware TPI (Transmitter Programming Interface) */ + 0x61, /* eCBUS-S, eCBUS-D */ +}; + +static void sii8620_fetch_edid(struct sii8620 *ctx); +static void sii8620_set_upstream_edid(struct sii8620 *ctx); +static void sii8620_enable_hpd(struct sii8620 *ctx); +static void sii8620_mhl_disconnected(struct sii8620 *ctx); +static void sii8620_disconnect(struct sii8620 *ctx); + +static int sii8620_clear_error(struct sii8620 *ctx) +{ + int ret = ctx->error; + + ctx->error = 0; + return ret; +} + +static void sii8620_read_buf(struct sii8620 *ctx, u16 addr, u8 *buf, int len) +{ + struct device *dev = ctx->dev; + struct i2c_client *client = to_i2c_client(dev); + u8 data = addr; + struct i2c_msg msg[] = { + { + .addr = sii8620_i2c_page[addr >> 8], + .flags = client->flags, + .len = 1, + .buf = &data + }, + { + .addr = sii8620_i2c_page[addr >> 8], + .flags = client->flags | I2C_M_RD, + .len = len, + .buf = buf + }, + }; + int ret; + + if (ctx->error) + return; + + ret = i2c_transfer(client->adapter, msg, 2); + dev_dbg(dev, "read at %04x: %*ph, %d\n", addr, len, buf, ret); + + if (ret != 2) { + dev_err(dev, "Read at %#06x of %d bytes failed with code %d.\n", + addr, len, ret); + ctx->error = ret < 0 ? ret : -EIO; + } +} + +static u8 sii8620_readb(struct sii8620 *ctx, u16 addr) +{ + u8 ret = 0; + + sii8620_read_buf(ctx, addr, &ret, 1); + return ret; +} + +static void sii8620_write_buf(struct sii8620 *ctx, u16 addr, const u8 *buf, + int len) +{ + struct device *dev = ctx->dev; + struct i2c_client *client = to_i2c_client(dev); + u8 data[2]; + struct i2c_msg msg = { + .addr = sii8620_i2c_page[addr >> 8], + .flags = client->flags, + .len = len + 1, + }; + int ret; + + if (ctx->error) + return; + + if (len > 1) { + msg.buf = kmalloc(len + 1, GFP_KERNEL); + if (!msg.buf) { + ctx->error = -ENOMEM; + return; + } + memcpy(msg.buf + 1, buf, len); + } else { + msg.buf = data; + msg.buf[1] = *buf; + } + + msg.buf[0] = addr; + + ret = i2c_transfer(client->adapter, &msg, 1); + dev_dbg(dev, "write at %04x: %*ph, %d\n", addr, len, buf, ret); + + if (ret != 1) { + dev_err(dev, "Write at %#06x of %*ph failed with code %d.\n", + addr, len, buf, ret); + ctx->error = ret ?: -EIO; + } + + if (len > 1) + kfree(msg.buf); +} + +#define sii8620_write(ctx, addr, arr...) \ +({\ + u8 d[] = { arr }; \ + sii8620_write_buf(ctx, addr, d, ARRAY_SIZE(d)); \ +}) + +static void __sii8620_write_seq(struct sii8620 *ctx, const u16 *seq, int len) +{ + int i; + + for (i = 0; i < len; i += 2) + sii8620_write(ctx, seq[i], seq[i + 1]); +} + +#define sii8620_write_seq(ctx, seq...) \ +({\ + const u16 d[] = { seq }; \ + __sii8620_write_seq(ctx, d, ARRAY_SIZE(d)); \ +}) + +#define sii8620_write_seq_static(ctx, seq...) \ +({\ + static const u16 d[] = { seq }; \ + __sii8620_write_seq(ctx, d, ARRAY_SIZE(d)); \ +}) + +static void sii8620_setbits(struct sii8620 *ctx, u16 addr, u8 mask, u8 val) +{ + val = (val & mask) | (sii8620_readb(ctx, addr) & ~mask); + sii8620_write(ctx, addr, val); +} + +static inline bool sii8620_is_mhl3(struct sii8620 *ctx) +{ + return ctx->mode >= CM_MHL3; +} + +static void sii8620_mt_cleanup(struct sii8620 *ctx) +{ + struct sii8620_mt_msg *msg, *n; + + list_for_each_entry_safe(msg, n, &ctx->mt_queue, node) { + list_del(&msg->node); + kfree(msg); + } + ctx->mt_state = MT_STATE_READY; +} + +static void sii8620_mt_work(struct sii8620 *ctx) +{ + struct sii8620_mt_msg *msg; + + if (ctx->error) + return; + if (ctx->mt_state == MT_STATE_BUSY || list_empty(&ctx->mt_queue)) + return; + + if (ctx->mt_state == MT_STATE_DONE) { + ctx->mt_state = MT_STATE_READY; + msg = list_first_entry(&ctx->mt_queue, struct sii8620_mt_msg, + node); + list_del(&msg->node); + if (msg->recv) + msg->recv(ctx, msg); + if (msg->continuation) + msg->continuation(ctx, msg->ret); + kfree(msg); + } + + if (ctx->mt_state != MT_STATE_READY || list_empty(&ctx->mt_queue)) + return; + + ctx->mt_state = MT_STATE_BUSY; + msg = list_first_entry(&ctx->mt_queue, struct sii8620_mt_msg, node); + if (msg->send) + msg->send(ctx, msg); +} + +static void sii8620_enable_gen2_write_burst(struct sii8620 *ctx) +{ + u8 ctrl = BIT_MDT_RCV_CTRL_MDT_RCV_EN; + + if (ctx->gen2_write_burst) + return; + + if (ctx->mode >= CM_MHL1) + ctrl |= BIT_MDT_RCV_CTRL_MDT_DELAY_RCV_EN; + + sii8620_write_seq(ctx, + REG_MDT_RCV_TIMEOUT, 100, + REG_MDT_RCV_CTRL, ctrl + ); + ctx->gen2_write_burst = 1; +} + +static void sii8620_disable_gen2_write_burst(struct sii8620 *ctx) +{ + if (!ctx->gen2_write_burst) + return; + + sii8620_write_seq_static(ctx, + REG_MDT_XMIT_CTRL, 0, + REG_MDT_RCV_CTRL, 0 + ); + ctx->gen2_write_burst = 0; +} + +static void sii8620_start_gen2_write_burst(struct sii8620 *ctx) +{ + sii8620_write_seq_static(ctx, + REG_MDT_INT_1_MASK, BIT_MDT_RCV_TIMEOUT + | BIT_MDT_RCV_SM_ABORT_PKT_RCVD | BIT_MDT_RCV_SM_ERROR + | BIT_MDT_XMIT_TIMEOUT | BIT_MDT_XMIT_SM_ABORT_PKT_RCVD + | BIT_MDT_XMIT_SM_ERROR, + REG_MDT_INT_0_MASK, BIT_MDT_XFIFO_EMPTY + | BIT_MDT_IDLE_AFTER_HAWB_DISABLE + | BIT_MDT_RFIFO_DATA_RDY + ); + sii8620_enable_gen2_write_burst(ctx); +} + +static void sii8620_mt_msc_cmd_send(struct sii8620 *ctx, + struct sii8620_mt_msg *msg) +{ + if (msg->reg[0] == MHL_SET_INT && + msg->reg[1] == MHL_INT_REG(RCHANGE) && + msg->reg[2] == MHL_INT_RC_FEAT_REQ) + sii8620_enable_gen2_write_burst(ctx); + else + sii8620_disable_gen2_write_burst(ctx); + + switch (msg->reg[0]) { + case MHL_WRITE_STAT: + case MHL_SET_INT: + sii8620_write_buf(ctx, REG_MSC_CMD_OR_OFFSET, msg->reg + 1, 2); + sii8620_write(ctx, REG_MSC_COMMAND_START, + BIT_MSC_COMMAND_START_WRITE_STAT); + break; + case MHL_MSC_MSG: + sii8620_write_buf(ctx, REG_MSC_CMD_OR_OFFSET, msg->reg, 3); + sii8620_write(ctx, REG_MSC_COMMAND_START, + BIT_MSC_COMMAND_START_MSC_MSG); + break; + case MHL_READ_DEVCAP_REG: + case MHL_READ_XDEVCAP_REG: + sii8620_write(ctx, REG_MSC_CMD_OR_OFFSET, msg->reg[1]); + sii8620_write(ctx, REG_MSC_COMMAND_START, + BIT_MSC_COMMAND_START_READ_DEVCAP); + break; + default: + dev_err(ctx->dev, "%s: command %#x not supported\n", __func__, + msg->reg[0]); + } +} + +static struct sii8620_mt_msg *sii8620_mt_msg_new(struct sii8620 *ctx) +{ + struct sii8620_mt_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL); + + if (!msg) + ctx->error = -ENOMEM; + else + list_add_tail(&msg->node, &ctx->mt_queue); + + return msg; +} + +static void sii8620_mt_set_cont(struct sii8620 *ctx, sii8620_cb cont) +{ + struct sii8620_mt_msg *msg; + + if (ctx->error) + return; + + if (list_empty(&ctx->mt_queue)) { + ctx->error = -EINVAL; + return; + } + msg = list_last_entry(&ctx->mt_queue, struct sii8620_mt_msg, node); + msg->continuation = cont; +} + +static void sii8620_mt_msc_cmd(struct sii8620 *ctx, u8 cmd, u8 arg1, u8 arg2) +{ + struct sii8620_mt_msg *msg = sii8620_mt_msg_new(ctx); + + if (!msg) + return; + + msg->reg[0] = cmd; + msg->reg[1] = arg1; + msg->reg[2] = arg2; + msg->send = sii8620_mt_msc_cmd_send; +} + +static void sii8620_mt_write_stat(struct sii8620 *ctx, u8 reg, u8 val) +{ + sii8620_mt_msc_cmd(ctx, MHL_WRITE_STAT, reg, val); +} + +static inline void sii8620_mt_set_int(struct sii8620 *ctx, u8 irq, u8 mask) +{ + sii8620_mt_msc_cmd(ctx, MHL_SET_INT, irq, mask); +} + +static void sii8620_mt_msc_msg(struct sii8620 *ctx, u8 cmd, u8 data) +{ + sii8620_mt_msc_cmd(ctx, MHL_MSC_MSG, cmd, data); +} + +static void sii8620_mt_rap(struct sii8620 *ctx, u8 code) +{ + sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RAP, code); +} + +static void sii8620_mt_rcpk(struct sii8620 *ctx, u8 code) +{ + sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RCPK, code); +} + +static void sii8620_mt_rcpe(struct sii8620 *ctx, u8 code) +{ + sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RCPE, code); +} + +static void sii8620_mt_read_devcap_send(struct sii8620 *ctx, + struct sii8620_mt_msg *msg) +{ + u8 ctrl = BIT_EDID_CTRL_DEVCAP_SELECT_DEVCAP + | BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO + | BIT_EDID_CTRL_EDID_MODE_EN; + + if (msg->reg[0] == MHL_READ_XDEVCAP) + ctrl |= BIT_EDID_CTRL_XDEVCAP_EN; + + sii8620_write_seq(ctx, + REG_INTR9_MASK, BIT_INTR9_DEVCAP_DONE, + REG_EDID_CTRL, ctrl, + REG_TPI_CBUS_START, BIT_TPI_CBUS_START_GET_DEVCAP_START + ); +} + +/* copy src to dst and set changed bits in src */ +static void sii8620_update_array(u8 *dst, u8 *src, int count) +{ + while (--count >= 0) { + *src ^= *dst; + *dst++ ^= *src++; + } +} + +static void sii8620_identify_sink(struct sii8620 *ctx) +{ + static const char * const sink_str[] = { + [SINK_NONE] = "NONE", + [SINK_HDMI] = "HDMI", + [SINK_DVI] = "DVI" + }; + + char sink_name[20]; + struct device *dev = ctx->dev; + + if (!ctx->sink_detected || !ctx->devcap_read) + return; + + sii8620_fetch_edid(ctx); + if (!ctx->edid) { + dev_err(ctx->dev, "Cannot fetch EDID\n"); + sii8620_mhl_disconnected(ctx); + return; + } + sii8620_set_upstream_edid(ctx); + + if (drm_detect_hdmi_monitor(ctx->edid)) + ctx->sink_type = SINK_HDMI; + else + ctx->sink_type = SINK_DVI; + + drm_edid_get_monitor_name(ctx->edid, sink_name, ARRAY_SIZE(sink_name)); + + dev_info(dev, "detected sink(type: %s): %s\n", + sink_str[ctx->sink_type], sink_name); +} + +static void sii8620_mr_devcap(struct sii8620 *ctx) +{ + u8 dcap[MHL_DCAP_SIZE]; + struct device *dev = ctx->dev; + + sii8620_read_buf(ctx, REG_EDID_FIFO_RD_DATA, dcap, MHL_DCAP_SIZE); + if (ctx->error < 0) + return; + + dev_info(dev, "detected dongle MHL %d.%d, ChipID %02x%02x:%02x%02x\n", + dcap[MHL_DCAP_MHL_VERSION] / 16, + dcap[MHL_DCAP_MHL_VERSION] % 16, + dcap[MHL_DCAP_ADOPTER_ID_H], dcap[MHL_DCAP_ADOPTER_ID_L], + dcap[MHL_DCAP_DEVICE_ID_H], dcap[MHL_DCAP_DEVICE_ID_L]); + sii8620_update_array(ctx->devcap, dcap, MHL_DCAP_SIZE); + ctx->devcap_read = true; + sii8620_identify_sink(ctx); +} + +static void sii8620_mr_xdevcap(struct sii8620 *ctx) +{ + sii8620_read_buf(ctx, REG_EDID_FIFO_RD_DATA, ctx->xdevcap, + MHL_XDC_SIZE); +} + +static void sii8620_mt_read_devcap_recv(struct sii8620 *ctx, + struct sii8620_mt_msg *msg) +{ + u8 ctrl = BIT_EDID_CTRL_DEVCAP_SELECT_DEVCAP + | BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO + | BIT_EDID_CTRL_EDID_MODE_EN; + + if (msg->reg[0] == MHL_READ_XDEVCAP) + ctrl |= BIT_EDID_CTRL_XDEVCAP_EN; + + sii8620_write_seq(ctx, + REG_INTR9_MASK, BIT_INTR9_DEVCAP_DONE | BIT_INTR9_EDID_DONE + | BIT_INTR9_EDID_ERROR, + REG_EDID_CTRL, ctrl, + REG_EDID_FIFO_ADDR, 0 + ); + + if (msg->reg[0] == MHL_READ_XDEVCAP) + sii8620_mr_xdevcap(ctx); + else + sii8620_mr_devcap(ctx); +} + +static void sii8620_mt_read_devcap(struct sii8620 *ctx, bool xdevcap) +{ + struct sii8620_mt_msg *msg = sii8620_mt_msg_new(ctx); + + if (!msg) + return; + + msg->reg[0] = xdevcap ? MHL_READ_XDEVCAP : MHL_READ_DEVCAP; + msg->send = sii8620_mt_read_devcap_send; + msg->recv = sii8620_mt_read_devcap_recv; +} + +static void sii8620_mt_read_devcap_reg_recv(struct sii8620 *ctx, + struct sii8620_mt_msg *msg) +{ + u8 reg = msg->reg[1] & 0x7f; + + if (msg->reg[1] & 0x80) + ctx->xdevcap[reg] = msg->ret; + else + ctx->devcap[reg] = msg->ret; +} + +static void sii8620_mt_read_devcap_reg(struct sii8620 *ctx, u8 reg) +{ + struct sii8620_mt_msg *msg = sii8620_mt_msg_new(ctx); + + if (!msg) + return; + + msg->reg[0] = (reg & 0x80) ? MHL_READ_XDEVCAP_REG : MHL_READ_DEVCAP_REG; + msg->reg[1] = reg; + msg->send = sii8620_mt_msc_cmd_send; + msg->recv = sii8620_mt_read_devcap_reg_recv; +} + +static inline void sii8620_mt_read_xdevcap_reg(struct sii8620 *ctx, u8 reg) +{ + sii8620_mt_read_devcap_reg(ctx, reg | 0x80); +} + +static void *sii8620_burst_get_tx_buf(struct sii8620 *ctx, int len) +{ + u8 *buf = &ctx->burst.tx_buf[ctx->burst.tx_count]; + int size = len + 2; + + if (ctx->burst.tx_count + size >= ARRAY_SIZE(ctx->burst.tx_buf)) { + dev_err(ctx->dev, "TX-BLK buffer exhausted\n"); + ctx->error = -EINVAL; + return NULL; + } + + ctx->burst.tx_count += size; + buf[1] = len; + + return buf + 2; +} + +static u8 *sii8620_burst_get_rx_buf(struct sii8620 *ctx, int len) +{ + u8 *buf = &ctx->burst.rx_buf[ctx->burst.rx_count]; + int size = len + 1; + + if (ctx->burst.rx_count + size >= ARRAY_SIZE(ctx->burst.rx_buf)) { + dev_err(ctx->dev, "RX-BLK buffer exhausted\n"); + ctx->error = -EINVAL; + return NULL; + } + + ctx->burst.rx_count += size; + buf[0] = len; + + return buf + 1; +} + +static void sii8620_burst_send(struct sii8620 *ctx) +{ + int tx_left = ctx->burst.tx_count; + u8 *d = ctx->burst.tx_buf; + + while (tx_left > 0) { + int len = d[1] + 2; + + if (ctx->burst.r_count + len > ctx->burst.r_size) + break; + d[0] = min(ctx->burst.rx_ack, 255); + ctx->burst.rx_ack -= d[0]; + sii8620_write_buf(ctx, REG_EMSC_XMIT_WRITE_PORT, d, len); + ctx->burst.r_count += len; + tx_left -= len; + d += len; + } + + ctx->burst.tx_count = tx_left; + + while (ctx->burst.rx_ack > 0) { + u8 b[2] = { min(ctx->burst.rx_ack, 255), 0 }; + + if (ctx->burst.r_count + 2 > ctx->burst.r_size) + break; + ctx->burst.rx_ack -= b[0]; + sii8620_write_buf(ctx, REG_EMSC_XMIT_WRITE_PORT, b, 2); + ctx->burst.r_count += 2; + } +} + +static void sii8620_burst_receive(struct sii8620 *ctx) +{ + u8 buf[3], *d; + int count; + + sii8620_read_buf(ctx, REG_EMSCRFIFOBCNTL, buf, 2); + count = get_unaligned_le16(buf); + while (count > 0) { + int len = min(count, 3); + + sii8620_read_buf(ctx, REG_EMSC_RCV_READ_PORT, buf, len); + count -= len; + ctx->burst.rx_ack += len - 1; + ctx->burst.r_count -= buf[1]; + if (ctx->burst.r_count < 0) + ctx->burst.r_count = 0; + + if (len < 3 || !buf[2]) + continue; + + len = buf[2]; + d = sii8620_burst_get_rx_buf(ctx, len); + if (!d) + continue; + sii8620_read_buf(ctx, REG_EMSC_RCV_READ_PORT, d, len); + count -= len; + ctx->burst.rx_ack += len; + } +} + +static void sii8620_burst_tx_rbuf_info(struct sii8620 *ctx, int size) +{ + struct mhl_burst_blk_rcv_buffer_info *d = + sii8620_burst_get_tx_buf(ctx, sizeof(*d)); + if (!d) + return; + + d->id = cpu_to_be16(MHL_BURST_ID_BLK_RCV_BUFFER_INFO); + d->size = cpu_to_le16(size); +} + +static u8 sii8620_checksum(void *ptr, int size) +{ + u8 *d = ptr, sum = 0; + + while (size--) + sum += *d++; + + return sum; +} + +static void sii8620_mhl_burst_hdr_set(struct mhl3_burst_header *h, + enum mhl_burst_id id) +{ + h->id = cpu_to_be16(id); + h->total_entries = 1; + h->sequence_index = 1; +} + +static void sii8620_burst_tx_bits_per_pixel_fmt(struct sii8620 *ctx, u8 fmt) +{ + struct mhl_burst_bits_per_pixel_fmt *d; + const int size = sizeof(*d) + sizeof(d->desc[0]); + + d = sii8620_burst_get_tx_buf(ctx, size); + if (!d) + return; + + sii8620_mhl_burst_hdr_set(&d->hdr, MHL_BURST_ID_BITS_PER_PIXEL_FMT); + d->num_entries = 1; + d->desc[0].stream_id = 0; + d->desc[0].pixel_format = fmt; + d->hdr.checksum -= sii8620_checksum(d, size); +} + +static void sii8620_burst_rx_all(struct sii8620 *ctx) +{ + u8 *d = ctx->burst.rx_buf; + int count = ctx->burst.rx_count; + + while (count-- > 0) { + int len = *d++; + int id = get_unaligned_be16(&d[0]); + + switch (id) { + case MHL_BURST_ID_BLK_RCV_BUFFER_INFO: + ctx->burst.r_size = get_unaligned_le16(&d[2]); + break; + default: + break; + } + count -= len; + d += len; + } + ctx->burst.rx_count = 0; +} + +static void sii8620_fetch_edid(struct sii8620 *ctx) +{ + u8 lm_ddc, ddc_cmd, int3, cbus; + unsigned long timeout; + int fetched, i; + int edid_len = EDID_LENGTH; + u8 *edid; + + sii8620_readb(ctx, REG_CBUS_STATUS); + lm_ddc = sii8620_readb(ctx, REG_LM_DDC); + ddc_cmd = sii8620_readb(ctx, REG_DDC_CMD); + + sii8620_write_seq(ctx, + REG_INTR9_MASK, 0, + REG_EDID_CTRL, BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO, + REG_HDCP2X_POLL_CS, 0x71, + REG_HDCP2X_CTRL_0, BIT_HDCP2X_CTRL_0_HDCP2X_HDCPTX, + REG_LM_DDC, lm_ddc | BIT_LM_DDC_SW_TPI_EN_DISABLED, + ); + + for (i = 0; i < 256; ++i) { + u8 ddc_stat = sii8620_readb(ctx, REG_DDC_STATUS); + + if (!(ddc_stat & BIT_DDC_STATUS_DDC_I2C_IN_PROG)) + break; + sii8620_write(ctx, REG_DDC_STATUS, + BIT_DDC_STATUS_DDC_FIFO_EMPTY); + } + + sii8620_write(ctx, REG_DDC_ADDR, 0x50 << 1); + + edid = kmalloc(EDID_LENGTH, GFP_KERNEL); + if (!edid) { + ctx->error = -ENOMEM; + return; + } + +#define FETCH_SIZE 16 + for (fetched = 0; fetched < edid_len; fetched += FETCH_SIZE) { + sii8620_readb(ctx, REG_DDC_STATUS); + sii8620_write_seq(ctx, + REG_DDC_CMD, ddc_cmd | VAL_DDC_CMD_DDC_CMD_ABORT, + REG_DDC_CMD, ddc_cmd | VAL_DDC_CMD_DDC_CMD_CLEAR_FIFO, + REG_DDC_STATUS, BIT_DDC_STATUS_DDC_FIFO_EMPTY + ); + sii8620_write_seq(ctx, + REG_DDC_SEGM, fetched >> 8, + REG_DDC_OFFSET, fetched & 0xff, + REG_DDC_DIN_CNT1, FETCH_SIZE, + REG_DDC_DIN_CNT2, 0, + REG_DDC_CMD, ddc_cmd | VAL_DDC_CMD_ENH_DDC_READ_NO_ACK + ); + + int3 = 0; + timeout = jiffies + msecs_to_jiffies(200); + for (;;) { + cbus = sii8620_readb(ctx, REG_CBUS_STATUS); + if (~cbus & BIT_CBUS_STATUS_CBUS_CONNECTED) { + kfree(edid); + edid = NULL; + goto end; + } + if (int3 & BIT_DDC_CMD_DONE) { + if (sii8620_readb(ctx, REG_DDC_DOUT_CNT) + >= FETCH_SIZE) + break; + } else { + int3 = sii8620_readb(ctx, REG_INTR3); + } + if (time_is_before_jiffies(timeout)) { + ctx->error = -ETIMEDOUT; + dev_err(ctx->dev, "timeout during EDID read\n"); + kfree(edid); + edid = NULL; + goto end; + } + usleep_range(10, 20); + } + + sii8620_read_buf(ctx, REG_DDC_DATA, edid + fetched, FETCH_SIZE); + if (fetched + FETCH_SIZE == EDID_LENGTH) { + u8 ext = ((struct edid *)edid)->extensions; + + if (ext) { + u8 *new_edid; + + edid_len += ext * EDID_LENGTH; + new_edid = krealloc(edid, edid_len, GFP_KERNEL); + if (!new_edid) { + kfree(edid); + ctx->error = -ENOMEM; + return; + } + edid = new_edid; + } + } + } + + sii8620_write_seq(ctx, + REG_INTR3_MASK, BIT_DDC_CMD_DONE, + REG_LM_DDC, lm_ddc + ); + +end: + kfree(ctx->edid); + ctx->edid = (struct edid *)edid; +} + +static void sii8620_set_upstream_edid(struct sii8620 *ctx) +{ + sii8620_setbits(ctx, REG_DPD, BIT_DPD_PDNRX12 | BIT_DPD_PDIDCK_N + | BIT_DPD_PD_MHL_CLK_N, 0xff); + + sii8620_write_seq_static(ctx, + REG_RX_HDMI_CTRL3, 0x00, + REG_PKT_FILTER_0, 0xFF, + REG_PKT_FILTER_1, 0xFF, + REG_ALICE0_BW_I2C, 0x06 + ); + + sii8620_setbits(ctx, REG_RX_HDMI_CLR_BUFFER, + BIT_RX_HDMI_CLR_BUFFER_VSI_CLR_EN, 0xff); + + sii8620_write_seq_static(ctx, + REG_EDID_CTRL, BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO + | BIT_EDID_CTRL_EDID_MODE_EN, + REG_EDID_FIFO_ADDR, 0, + ); + + sii8620_write_buf(ctx, REG_EDID_FIFO_WR_DATA, (u8 *)ctx->edid, + (ctx->edid->extensions + 1) * EDID_LENGTH); + + sii8620_write_seq_static(ctx, + REG_EDID_CTRL, BIT_EDID_CTRL_EDID_PRIME_VALID + | BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO + | BIT_EDID_CTRL_EDID_MODE_EN, + REG_INTR5_MASK, BIT_INTR_SCDT_CHANGE, + REG_INTR9_MASK, 0 + ); +} + +static void sii8620_xtal_set_rate(struct sii8620 *ctx) +{ + static const struct { + unsigned int rate; + u8 div; + u8 tp1; + } rates[] = { + { 19200, 0x04, 0x53 }, + { 20000, 0x04, 0x62 }, + { 24000, 0x05, 0x75 }, + { 30000, 0x06, 0x92 }, + { 38400, 0x0c, 0xbc }, + }; + unsigned long rate = clk_get_rate(ctx->clk_xtal) / 1000; + int i; + + for (i = 0; i < ARRAY_SIZE(rates) - 1; ++i) + if (rate <= rates[i].rate) + break; + + if (rate != rates[i].rate) + dev_err(ctx->dev, "xtal clock rate(%lukHz) not supported, setting MHL for %ukHz.\n", + rate, rates[i].rate); + + sii8620_write(ctx, REG_DIV_CTL_MAIN, rates[i].div); + sii8620_write(ctx, REG_HDCP2X_TP1, rates[i].tp1); +} + +static int sii8620_hw_on(struct sii8620 *ctx) +{ + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret) + return ret; + + usleep_range(10000, 20000); + ret = clk_prepare_enable(ctx->clk_xtal); + if (ret) + return ret; + + msleep(100); + gpiod_set_value(ctx->gpio_reset, 0); + msleep(100); + + return 0; +} + +static int sii8620_hw_off(struct sii8620 *ctx) +{ + clk_disable_unprepare(ctx->clk_xtal); + gpiod_set_value(ctx->gpio_reset, 1); + return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); +} + +static void sii8620_cbus_reset(struct sii8620 *ctx) +{ + sii8620_write(ctx, REG_PWD_SRST, BIT_PWD_SRST_CBUS_RST + | BIT_PWD_SRST_CBUS_RST_SW_EN); + usleep_range(10000, 20000); + sii8620_write(ctx, REG_PWD_SRST, BIT_PWD_SRST_CBUS_RST_SW_EN); +} + +static void sii8620_set_auto_zone(struct sii8620 *ctx) +{ + if (ctx->mode != CM_MHL1) { + sii8620_write_seq_static(ctx, + REG_TX_ZONE_CTL1, 0x0, + REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X + | BIT_MHL_PLL_CTL0_CRYSTAL_CLK_SEL + | BIT_MHL_PLL_CTL0_ZONE_MASK_OE + ); + } else { + sii8620_write_seq_static(ctx, + REG_TX_ZONE_CTL1, VAL_TX_ZONE_CTL1_TX_ZONE_CTRL_MODE, + REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X + | BIT_MHL_PLL_CTL0_ZONE_MASK_OE + ); + } +} + +static void sii8620_stop_video(struct sii8620 *ctx) +{ + u8 val; + + sii8620_write_seq_static(ctx, + REG_TPI_INTR_EN, 0, + REG_HDCP2X_INTR0_MASK, 0, + REG_TPI_COPP_DATA2, 0, + REG_TPI_INTR_ST0, ~0, + ); + + switch (ctx->sink_type) { + case SINK_DVI: + val = BIT_TPI_SC_REG_TMDS_OE_POWER_DOWN + | BIT_TPI_SC_TPI_AV_MUTE; + break; + case SINK_HDMI: + default: + val = BIT_TPI_SC_REG_TMDS_OE_POWER_DOWN + | BIT_TPI_SC_TPI_AV_MUTE + | BIT_TPI_SC_TPI_OUTPUT_MODE_0_HDMI; + break; + } + + sii8620_write(ctx, REG_TPI_SC, val); +} + +static void sii8620_set_format(struct sii8620 *ctx) +{ + u8 out_fmt; + + if (sii8620_is_mhl3(ctx)) { + sii8620_setbits(ctx, REG_M3_P0CTRL, + BIT_M3_P0CTRL_MHL3_P0_PIXEL_MODE_PACKED, + ctx->use_packed_pixel ? ~0 : 0); + } else { + if (ctx->use_packed_pixel) { + sii8620_write_seq_static(ctx, + REG_VID_MODE, BIT_VID_MODE_M1080P, + REG_MHL_TOP_CTL, BIT_MHL_TOP_CTL_MHL_PP_SEL | 1, + REG_MHLTX_CTL6, 0x60 + ); + } else { + sii8620_write_seq_static(ctx, + REG_VID_MODE, 0, + REG_MHL_TOP_CTL, 1, + REG_MHLTX_CTL6, 0xa0 + ); + } + } + + if (ctx->use_packed_pixel) + out_fmt = VAL_TPI_FORMAT(YCBCR422, FULL); + else + out_fmt = VAL_TPI_FORMAT(RGB, FULL); + + sii8620_write_seq(ctx, + REG_TPI_INPUT, VAL_TPI_FORMAT(RGB, FULL), + REG_TPI_OUTPUT, out_fmt, + ); +} + +static int mhl3_infoframe_init(struct mhl3_infoframe *frame) +{ + memset(frame, 0, sizeof(*frame)); + + frame->version = 3; + frame->hev_format = -1; + return 0; +} + +static ssize_t mhl3_infoframe_pack(struct mhl3_infoframe *frame, + void *buffer, size_t size) +{ + const int frm_len = HDMI_INFOFRAME_HEADER_SIZE + MHL3_INFOFRAME_SIZE; + u8 *ptr = buffer; + + if (size < frm_len) + return -ENOSPC; + + memset(buffer, 0, size); + ptr[0] = HDMI_INFOFRAME_TYPE_VENDOR; + ptr[1] = frame->version; + ptr[2] = MHL3_INFOFRAME_SIZE; + ptr[4] = MHL3_IEEE_OUI & 0xff; + ptr[5] = (MHL3_IEEE_OUI >> 8) & 0xff; + ptr[6] = (MHL3_IEEE_OUI >> 16) & 0xff; + ptr[7] = frame->video_format & 0x3; + ptr[7] |= (frame->format_type & 0x7) << 2; + ptr[7] |= frame->sep_audio ? BIT(5) : 0; + if (frame->hev_format >= 0) { + ptr[9] = 1; + ptr[10] = (frame->hev_format >> 8) & 0xff; + ptr[11] = frame->hev_format & 0xff; + } + if (frame->av_delay) { + bool sign = frame->av_delay < 0; + int delay = sign ? -frame->av_delay : frame->av_delay; + + ptr[12] = (delay >> 16) & 0xf; + if (sign) + ptr[12] |= BIT(4); + ptr[13] = (delay >> 8) & 0xff; + ptr[14] = delay & 0xff; + } + ptr[3] -= sii8620_checksum(buffer, frm_len); + return frm_len; +} + +static void sii8620_set_infoframes(struct sii8620 *ctx, + struct drm_display_mode *mode) +{ + struct mhl3_infoframe mhl_frm; + union hdmi_infoframe frm; + u8 buf[31]; + int ret; + + ret = drm_hdmi_avi_infoframe_from_display_mode(&frm.avi, + NULL, mode); + if (ctx->use_packed_pixel) + frm.avi.colorspace = HDMI_COLORSPACE_YUV422; + + if (!ret) + ret = hdmi_avi_infoframe_pack(&frm.avi, buf, ARRAY_SIZE(buf)); + if (ret > 0) + sii8620_write_buf(ctx, REG_TPI_AVI_CHSUM, buf + 3, ret - 3); + + if (!sii8620_is_mhl3(ctx) || !ctx->use_packed_pixel) { + sii8620_write(ctx, REG_TPI_SC, + BIT_TPI_SC_TPI_OUTPUT_MODE_0_HDMI); + sii8620_write(ctx, REG_PKT_FILTER_0, + BIT_PKT_FILTER_0_DROP_CEA_GAMUT_PKT | + BIT_PKT_FILTER_0_DROP_MPEG_PKT | + BIT_PKT_FILTER_0_DROP_GCP_PKT, + BIT_PKT_FILTER_1_DROP_GEN_PKT); + return; + } + + sii8620_write(ctx, REG_PKT_FILTER_0, + BIT_PKT_FILTER_0_DROP_CEA_GAMUT_PKT | + BIT_PKT_FILTER_0_DROP_MPEG_PKT | + BIT_PKT_FILTER_0_DROP_AVI_PKT | + BIT_PKT_FILTER_0_DROP_GCP_PKT, + BIT_PKT_FILTER_1_VSI_OVERRIDE_DIS | + BIT_PKT_FILTER_1_DROP_GEN_PKT | + BIT_PKT_FILTER_1_DROP_VSIF_PKT); + + sii8620_write(ctx, REG_TPI_INFO_FSEL, BIT_TPI_INFO_FSEL_EN + | BIT_TPI_INFO_FSEL_RPT | VAL_TPI_INFO_FSEL_VSI); + ret = mhl3_infoframe_init(&mhl_frm); + if (!ret) + ret = mhl3_infoframe_pack(&mhl_frm, buf, ARRAY_SIZE(buf)); + sii8620_write_buf(ctx, REG_TPI_INFO_B0, buf, ret); +} + +static void sii8620_start_video(struct sii8620 *ctx) +{ + struct drm_display_mode *mode = + &ctx->bridge.encoder->crtc->state->adjusted_mode; + + if (!sii8620_is_mhl3(ctx)) + sii8620_stop_video(ctx); + + if (ctx->sink_type == SINK_DVI && !sii8620_is_mhl3(ctx)) { + sii8620_write(ctx, REG_RX_HDMI_CTRL2, + VAL_RX_HDMI_CTRL2_DEFVAL); + sii8620_write(ctx, REG_TPI_SC, 0); + return; + } + + sii8620_write_seq_static(ctx, + REG_RX_HDMI_CTRL2, VAL_RX_HDMI_CTRL2_DEFVAL + | BIT_RX_HDMI_CTRL2_USE_AV_MUTE, + REG_VID_OVRRD, BIT_VID_OVRRD_PP_AUTO_DISABLE + | BIT_VID_OVRRD_M1080P_OVRRD); + sii8620_set_format(ctx); + + if (!sii8620_is_mhl3(ctx)) { + u8 link_mode = MHL_DST_LM_PATH_ENABLED; + + if (ctx->use_packed_pixel) + link_mode |= MHL_DST_LM_CLK_MODE_PACKED_PIXEL; + else + link_mode |= MHL_DST_LM_CLK_MODE_NORMAL; + + sii8620_mt_write_stat(ctx, MHL_DST_REG(LINK_MODE), link_mode); + sii8620_set_auto_zone(ctx); + } else { + static const struct { + int max_clk; + u8 zone; + u8 link_rate; + u8 rrp_decode; + } clk_spec[] = { + { 150000, VAL_TX_ZONE_CTL3_TX_ZONE_1_5GBPS, + MHL_XDS_LINK_RATE_1_5_GBPS, 0x38 }, + { 300000, VAL_TX_ZONE_CTL3_TX_ZONE_3GBPS, + MHL_XDS_LINK_RATE_3_0_GBPS, 0x40 }, + { 600000, VAL_TX_ZONE_CTL3_TX_ZONE_6GBPS, + MHL_XDS_LINK_RATE_6_0_GBPS, 0x40 }, + }; + u8 p0_ctrl = BIT_M3_P0CTRL_MHL3_P0_PORT_EN; + int clk = mode->clock * (ctx->use_packed_pixel ? 2 : 3); + int i; + + for (i = 0; i < ARRAY_SIZE(clk_spec) - 1; ++i) + if (clk < clk_spec[i].max_clk) + break; + + if (100 * clk >= 98 * clk_spec[i].max_clk) + p0_ctrl |= BIT_M3_P0CTRL_MHL3_P0_UNLIMIT_EN; + + sii8620_burst_tx_bits_per_pixel_fmt(ctx, ctx->use_packed_pixel); + sii8620_burst_send(ctx); + sii8620_write_seq(ctx, + REG_MHL_DP_CTL0, 0xf0, + REG_MHL3_TX_ZONE_CTL, clk_spec[i].zone); + sii8620_setbits(ctx, REG_M3_P0CTRL, + BIT_M3_P0CTRL_MHL3_P0_PORT_EN + | BIT_M3_P0CTRL_MHL3_P0_UNLIMIT_EN, p0_ctrl); + sii8620_setbits(ctx, REG_M3_POSTM, MSK_M3_POSTM_RRP_DECODE, + clk_spec[i].rrp_decode); + sii8620_write_seq_static(ctx, + REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE + | BIT_M3_CTRL_H2M_SWRST, + REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE + ); + sii8620_mt_write_stat(ctx, MHL_XDS_REG(AVLINK_MODE_CONTROL), + clk_spec[i].link_rate); + } + + sii8620_set_infoframes(ctx, mode); +} + +static void sii8620_disable_hpd(struct sii8620 *ctx) +{ + sii8620_setbits(ctx, REG_EDID_CTRL, BIT_EDID_CTRL_EDID_PRIME_VALID, 0); + sii8620_write_seq_static(ctx, + REG_HPD_CTRL, BIT_HPD_CTRL_HPD_OUT_OVR_EN, + REG_INTR8_MASK, 0 + ); +} + +static void sii8620_enable_hpd(struct sii8620 *ctx) +{ + sii8620_setbits(ctx, REG_TMDS_CSTAT_P3, + BIT_TMDS_CSTAT_P3_SCDT_CLR_AVI_DIS + | BIT_TMDS_CSTAT_P3_CLR_AVI, ~0); + sii8620_write_seq_static(ctx, + REG_HPD_CTRL, BIT_HPD_CTRL_HPD_OUT_OVR_EN + | BIT_HPD_CTRL_HPD_HIGH, + ); +} + +static void sii8620_mhl_discover(struct sii8620 *ctx) +{ + sii8620_write_seq_static(ctx, + REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT + | BIT_DISC_CTRL9_DISC_PULSE_PROCEED, + REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_5K, VAL_PUP_20K), + REG_CBUS_DISC_INTR0_MASK, BIT_MHL3_EST_INT + | BIT_MHL_EST_INT + | BIT_NOT_MHL_EST_INT + | BIT_CBUS_MHL3_DISCON_INT + | BIT_CBUS_MHL12_DISCON_INT + | BIT_RGND_READY_INT, + REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X + | BIT_MHL_PLL_CTL0_CRYSTAL_CLK_SEL + | BIT_MHL_PLL_CTL0_ZONE_MASK_OE, + REG_MHL_DP_CTL0, BIT_MHL_DP_CTL0_DP_OE + | BIT_MHL_DP_CTL0_TX_OE_OVR, + REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE, + REG_MHL_DP_CTL1, 0xA2, + REG_MHL_DP_CTL2, 0x03, + REG_MHL_DP_CTL3, 0x35, + REG_MHL_DP_CTL5, 0x02, + REG_MHL_DP_CTL6, 0x02, + REG_MHL_DP_CTL7, 0x03, + REG_COC_CTLC, 0xFF, + REG_DPD, BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 + | BIT_DPD_OSC_EN | BIT_DPD_PWRON_HSIC, + REG_COC_INTR_MASK, BIT_COC_PLL_LOCK_STATUS_CHANGE + | BIT_COC_CALIBRATION_DONE, + REG_CBUS_INT_1_MASK, BIT_CBUS_MSC_ABORT_RCVD + | BIT_CBUS_CMD_ABORT, + REG_CBUS_INT_0_MASK, BIT_CBUS_MSC_MT_DONE + | BIT_CBUS_HPD_CHG + | BIT_CBUS_MSC_MR_WRITE_STAT + | BIT_CBUS_MSC_MR_MSC_MSG + | BIT_CBUS_MSC_MR_WRITE_BURST + | BIT_CBUS_MSC_MR_SET_INT + | BIT_CBUS_MSC_MT_DONE_NACK + ); +} + +static void sii8620_peer_specific_init(struct sii8620 *ctx) +{ + if (sii8620_is_mhl3(ctx)) + sii8620_write_seq_static(ctx, + REG_SYS_CTRL1, BIT_SYS_CTRL1_BLOCK_DDC_BY_HPD, + REG_EMSCINTRMASK1, + BIT_EMSCINTR1_EMSC_TRAINING_COMMA_ERR + ); + else + sii8620_write_seq_static(ctx, + REG_HDCP2X_INTR0_MASK, 0x00, + REG_EMSCINTRMASK1, 0x00, + REG_HDCP2X_INTR0, 0xFF, + REG_INTR1, 0xFF, + REG_SYS_CTRL1, BIT_SYS_CTRL1_BLOCK_DDC_BY_HPD + | BIT_SYS_CTRL1_TX_CTRL_HDMI + ); +} + +#define SII8620_MHL_VERSION 0x32 +#define SII8620_SCRATCHPAD_SIZE 16 +#define SII8620_INT_STAT_SIZE 0x33 + +static void sii8620_set_dev_cap(struct sii8620 *ctx) +{ + static const u8 devcap[MHL_DCAP_SIZE] = { + [MHL_DCAP_MHL_VERSION] = SII8620_MHL_VERSION, + [MHL_DCAP_CAT] = MHL_DCAP_CAT_SOURCE | MHL_DCAP_CAT_POWER, + [MHL_DCAP_ADOPTER_ID_H] = 0x01, + [MHL_DCAP_ADOPTER_ID_L] = 0x41, + [MHL_DCAP_VID_LINK_MODE] = MHL_DCAP_VID_LINK_RGB444 + | MHL_DCAP_VID_LINK_PPIXEL + | MHL_DCAP_VID_LINK_16BPP, + [MHL_DCAP_AUD_LINK_MODE] = MHL_DCAP_AUD_LINK_2CH, + [MHL_DCAP_VIDEO_TYPE] = MHL_DCAP_VT_GRAPHICS, + [MHL_DCAP_LOG_DEV_MAP] = MHL_DCAP_LD_GUI, + [MHL_DCAP_BANDWIDTH] = 0x0f, + [MHL_DCAP_FEATURE_FLAG] = MHL_DCAP_FEATURE_RCP_SUPPORT + | MHL_DCAP_FEATURE_RAP_SUPPORT + | MHL_DCAP_FEATURE_SP_SUPPORT, + [MHL_DCAP_SCRATCHPAD_SIZE] = SII8620_SCRATCHPAD_SIZE, + [MHL_DCAP_INT_STAT_SIZE] = SII8620_INT_STAT_SIZE, + }; + static const u8 xdcap[MHL_XDC_SIZE] = { + [MHL_XDC_ECBUS_SPEEDS] = MHL_XDC_ECBUS_S_075 + | MHL_XDC_ECBUS_S_8BIT, + [MHL_XDC_TMDS_SPEEDS] = MHL_XDC_TMDS_150 + | MHL_XDC_TMDS_300 | MHL_XDC_TMDS_600, + [MHL_XDC_ECBUS_ROLES] = MHL_XDC_DEV_HOST, + [MHL_XDC_LOG_DEV_MAPX] = MHL_XDC_LD_PHONE, + }; + + sii8620_write_buf(ctx, REG_MHL_DEVCAP_0, devcap, ARRAY_SIZE(devcap)); + sii8620_write_buf(ctx, REG_MHL_EXTDEVCAP_0, xdcap, ARRAY_SIZE(xdcap)); +} + +static void sii8620_mhl_init(struct sii8620 *ctx) +{ + sii8620_write_seq_static(ctx, + REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_OFF, VAL_PUP_20K), + REG_CBUS_MSC_COMPAT_CTRL, + BIT_CBUS_MSC_COMPAT_CTRL_XDEVCAP_EN, + ); + + sii8620_peer_specific_init(ctx); + + sii8620_disable_hpd(ctx); + + sii8620_write_seq_static(ctx, + REG_EDID_CTRL, BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO, + REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT + | BIT_DISC_CTRL9_WAKE_PULSE_BYPASS, + REG_TMDS0_CCTRL1, 0x90, + REG_TMDS_CLK_EN, 0x01, + REG_TMDS_CH_EN, 0x11, + REG_BGR_BIAS, 0x87, + REG_ALICE0_ZONE_CTRL, 0xE8, + REG_ALICE0_MODE_CTRL, 0x04, + ); + sii8620_setbits(ctx, REG_LM_DDC, BIT_LM_DDC_SW_TPI_EN_DISABLED, 0); + sii8620_write_seq_static(ctx, + REG_TPI_HW_OPT3, 0x76, + REG_TMDS_CCTRL, BIT_TMDS_CCTRL_TMDS_OE, + REG_TPI_DTD_B2, 79, + ); + sii8620_set_dev_cap(ctx); + sii8620_write_seq_static(ctx, + REG_MDT_XMIT_TIMEOUT, 100, + REG_MDT_XMIT_CTRL, 0x03, + REG_MDT_XFIFO_STAT, 0x00, + REG_MDT_RCV_TIMEOUT, 100, + REG_CBUS_LINK_CTRL_8, 0x1D, + ); + + sii8620_start_gen2_write_burst(ctx); + sii8620_write_seq_static(ctx, + REG_BIST_CTRL, 0x00, + REG_COC_CTL1, 0x10, + REG_COC_CTL2, 0x18, + REG_COC_CTLF, 0x07, + REG_COC_CTL11, 0xF8, + REG_COC_CTL17, 0x61, + REG_COC_CTL18, 0x46, + REG_COC_CTL19, 0x15, + REG_COC_CTL1A, 0x01, + REG_MHL_COC_CTL3, BIT_MHL_COC_CTL3_COC_AECHO_EN, + REG_MHL_COC_CTL4, 0x2D, + REG_MHL_COC_CTL5, 0xF9, + REG_MSC_HEARTBEAT_CTRL, 0x27, + ); + sii8620_disable_gen2_write_burst(ctx); + + sii8620_mt_write_stat(ctx, MHL_DST_REG(VERSION), SII8620_MHL_VERSION); + sii8620_mt_write_stat(ctx, MHL_DST_REG(CONNECTED_RDY), + MHL_DST_CONN_DCAP_RDY | MHL_DST_CONN_XDEVCAPP_SUPP + | MHL_DST_CONN_POW_STAT); + sii8620_mt_set_int(ctx, MHL_INT_REG(RCHANGE), MHL_INT_RC_DCAP_CHG); +} + +static void sii8620_emsc_enable(struct sii8620 *ctx) +{ + u8 reg; + + sii8620_setbits(ctx, REG_GENCTL, BIT_GENCTL_EMSC_EN + | BIT_GENCTL_CLR_EMSC_RFIFO + | BIT_GENCTL_CLR_EMSC_XFIFO, ~0); + sii8620_setbits(ctx, REG_GENCTL, BIT_GENCTL_CLR_EMSC_RFIFO + | BIT_GENCTL_CLR_EMSC_XFIFO, 0); + sii8620_setbits(ctx, REG_COMMECNT, BIT_COMMECNT_I2C_TO_EMSC_EN, ~0); + reg = sii8620_readb(ctx, REG_EMSCINTR); + sii8620_write(ctx, REG_EMSCINTR, reg); + sii8620_write(ctx, REG_EMSCINTRMASK, BIT_EMSCINTR_SPI_DVLD); +} + +static int sii8620_wait_for_fsm_state(struct sii8620 *ctx, u8 state) +{ + int i; + + for (i = 0; i < 10; ++i) { + u8 s = sii8620_readb(ctx, REG_COC_STAT_0); + + if ((s & MSK_COC_STAT_0_FSM_STATE) == state) + return 0; + if (!(s & BIT_COC_STAT_0_PLL_LOCKED)) + return -EBUSY; + usleep_range(4000, 6000); + } + return -ETIMEDOUT; +} + +static void sii8620_set_mode(struct sii8620 *ctx, enum sii8620_mode mode) +{ + int ret; + + if (ctx->mode == mode) + return; + + switch (mode) { + case CM_MHL1: + sii8620_write_seq_static(ctx, + REG_CBUS_MSC_COMPAT_CTRL, 0x02, + REG_M3_CTRL, VAL_M3_CTRL_MHL1_2_VALUE, + REG_DPD, BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 + | BIT_DPD_OSC_EN, + REG_COC_INTR_MASK, 0 + ); + ctx->mode = mode; + break; + case CM_MHL3: + sii8620_write(ctx, REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE); + ctx->mode = mode; + return; + case CM_ECBUS_S: + sii8620_emsc_enable(ctx); + sii8620_write_seq_static(ctx, + REG_TTXSPINUMS, 4, + REG_TRXSPINUMS, 4, + REG_TTXHSICNUMS, 0x14, + REG_TRXHSICNUMS, 0x14, + REG_TTXTOTNUMS, 0x18, + REG_TRXTOTNUMS, 0x18, + REG_PWD_SRST, BIT_PWD_SRST_COC_DOC_RST + | BIT_PWD_SRST_CBUS_RST_SW_EN, + REG_MHL_COC_CTL1, 0xbd, + REG_PWD_SRST, BIT_PWD_SRST_CBUS_RST_SW_EN, + REG_COC_CTLB, 0x01, + REG_COC_CTL0, 0x5c, + REG_COC_CTL14, 0x03, + REG_COC_CTL15, 0x80, + REG_MHL_DP_CTL6, BIT_MHL_DP_CTL6_DP_TAP1_SGN + | BIT_MHL_DP_CTL6_DP_TAP1_EN + | BIT_MHL_DP_CTL6_DT_PREDRV_FEEDCAP_EN, + REG_MHL_DP_CTL8, 0x03 + ); + ret = sii8620_wait_for_fsm_state(ctx, 0x03); + sii8620_write_seq_static(ctx, + REG_COC_CTL14, 0x00, + REG_COC_CTL15, 0x80 + ); + if (!ret) + sii8620_write(ctx, REG_CBUS3_CNVT, 0x85); + else + sii8620_disconnect(ctx); + return; + case CM_DISCONNECTED: + ctx->mode = mode; + break; + default: + dev_err(ctx->dev, "%s mode %d not supported\n", __func__, mode); + break; + } + + sii8620_set_auto_zone(ctx); + + if (mode != CM_MHL1) + return; + + sii8620_write_seq_static(ctx, + REG_MHL_DP_CTL0, 0xBC, + REG_MHL_DP_CTL1, 0xBB, + REG_MHL_DP_CTL3, 0x48, + REG_MHL_DP_CTL5, 0x39, + REG_MHL_DP_CTL2, 0x2A, + REG_MHL_DP_CTL6, 0x2A, + REG_MHL_DP_CTL7, 0x08 + ); +} + +static void sii8620_hpd_unplugged(struct sii8620 *ctx) +{ + sii8620_disable_hpd(ctx); + ctx->sink_type = SINK_NONE; + ctx->sink_detected = false; + ctx->feature_complete = false; + kfree(ctx->edid); + ctx->edid = NULL; +} + +static void sii8620_disconnect(struct sii8620 *ctx) +{ + sii8620_disable_gen2_write_burst(ctx); + sii8620_stop_video(ctx); + msleep(100); + sii8620_cbus_reset(ctx); + sii8620_set_mode(ctx, CM_DISCONNECTED); + sii8620_write_seq_static(ctx, + REG_TX_ZONE_CTL1, 0, + REG_MHL_PLL_CTL0, 0x07, + REG_COC_CTL0, 0x40, + REG_CBUS3_CNVT, 0x84, + REG_COC_CTL14, 0x00, + REG_COC_CTL0, 0x40, + REG_HRXCTRL3, 0x07, + REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X + | BIT_MHL_PLL_CTL0_CRYSTAL_CLK_SEL + | BIT_MHL_PLL_CTL0_ZONE_MASK_OE, + REG_MHL_DP_CTL0, BIT_MHL_DP_CTL0_DP_OE + | BIT_MHL_DP_CTL0_TX_OE_OVR, + REG_MHL_DP_CTL1, 0xBB, + REG_MHL_DP_CTL3, 0x48, + REG_MHL_DP_CTL5, 0x3F, + REG_MHL_DP_CTL2, 0x2F, + REG_MHL_DP_CTL6, 0x2A, + REG_MHL_DP_CTL7, 0x03 + ); + sii8620_hpd_unplugged(ctx); + sii8620_write_seq_static(ctx, + REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE, + REG_MHL_COC_CTL1, 0x07, + REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_OFF, VAL_PUP_20K), + REG_DISC_CTRL8, 0x00, + REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT + | BIT_DISC_CTRL9_WAKE_PULSE_BYPASS, + REG_INT_CTRL, 0x00, + REG_MSC_HEARTBEAT_CTRL, 0x27, + REG_DISC_CTRL1, 0x25, + REG_CBUS_DISC_INTR0, (u8)~BIT_RGND_READY_INT, + REG_CBUS_DISC_INTR0_MASK, BIT_RGND_READY_INT, + REG_MDT_INT_1, 0xff, + REG_MDT_INT_1_MASK, 0x00, + REG_MDT_INT_0, 0xff, + REG_MDT_INT_0_MASK, 0x00, + REG_COC_INTR, 0xff, + REG_COC_INTR_MASK, 0x00, + REG_TRXINTH, 0xff, + REG_TRXINTMH, 0x00, + REG_CBUS_INT_0, 0xff, + REG_CBUS_INT_0_MASK, 0x00, + REG_CBUS_INT_1, 0xff, + REG_CBUS_INT_1_MASK, 0x00, + REG_EMSCINTR, 0xff, + REG_EMSCINTRMASK, 0x00, + REG_EMSCINTR1, 0xff, + REG_EMSCINTRMASK1, 0x00, + REG_INTR8, 0xff, + REG_INTR8_MASK, 0x00, + REG_TPI_INTR_ST0, 0xff, + REG_TPI_INTR_EN, 0x00, + REG_HDCP2X_INTR0, 0xff, + REG_HDCP2X_INTR0_MASK, 0x00, + REG_INTR9, 0xff, + REG_INTR9_MASK, 0x00, + REG_INTR3, 0xff, + REG_INTR3_MASK, 0x00, + REG_INTR5, 0xff, + REG_INTR5_MASK, 0x00, + REG_INTR2, 0xff, + REG_INTR2_MASK, 0x00, + ); + memset(ctx->stat, 0, sizeof(ctx->stat)); + memset(ctx->xstat, 0, sizeof(ctx->xstat)); + memset(ctx->devcap, 0, sizeof(ctx->devcap)); + memset(ctx->xdevcap, 0, sizeof(ctx->xdevcap)); + ctx->devcap_read = false; + ctx->cbus_status = 0; + sii8620_mt_cleanup(ctx); +} + +static void sii8620_mhl_disconnected(struct sii8620 *ctx) +{ + sii8620_write_seq_static(ctx, + REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_OFF, VAL_PUP_20K), + REG_CBUS_MSC_COMPAT_CTRL, + BIT_CBUS_MSC_COMPAT_CTRL_XDEVCAP_EN + ); + sii8620_disconnect(ctx); +} + +static void sii8620_irq_disc(struct sii8620 *ctx) +{ + u8 stat = sii8620_readb(ctx, REG_CBUS_DISC_INTR0); + + if (stat & VAL_CBUS_MHL_DISCON) + sii8620_mhl_disconnected(ctx); + + if (stat & BIT_RGND_READY_INT) { + u8 stat2 = sii8620_readb(ctx, REG_DISC_STAT2); + + if ((stat2 & MSK_DISC_STAT2_RGND) == VAL_RGND_1K) { + sii8620_mhl_discover(ctx); + } else { + sii8620_write_seq_static(ctx, + REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT + | BIT_DISC_CTRL9_NOMHL_EST + | BIT_DISC_CTRL9_WAKE_PULSE_BYPASS, + REG_CBUS_DISC_INTR0_MASK, BIT_RGND_READY_INT + | BIT_CBUS_MHL3_DISCON_INT + | BIT_CBUS_MHL12_DISCON_INT + | BIT_NOT_MHL_EST_INT + ); + } + } + if (stat & BIT_MHL_EST_INT) + sii8620_mhl_init(ctx); + + sii8620_write(ctx, REG_CBUS_DISC_INTR0, stat); +} + +static void sii8620_read_burst(struct sii8620 *ctx) +{ + u8 buf[17]; + + sii8620_read_buf(ctx, REG_MDT_RCV_READ_PORT, buf, ARRAY_SIZE(buf)); + sii8620_write(ctx, REG_MDT_RCV_CTRL, BIT_MDT_RCV_CTRL_MDT_RCV_EN | + BIT_MDT_RCV_CTRL_MDT_DELAY_RCV_EN | + BIT_MDT_RCV_CTRL_MDT_RFIFO_CLR_CUR); + sii8620_readb(ctx, REG_MDT_RFIFO_STAT); +} + +static void sii8620_irq_g2wb(struct sii8620 *ctx) +{ + u8 stat = sii8620_readb(ctx, REG_MDT_INT_0); + + if (stat & BIT_MDT_IDLE_AFTER_HAWB_DISABLE) + if (sii8620_is_mhl3(ctx)) + sii8620_mt_set_int(ctx, MHL_INT_REG(RCHANGE), + MHL_INT_RC_FEAT_COMPLETE); + + if (stat & BIT_MDT_RFIFO_DATA_RDY) + sii8620_read_burst(ctx); + + if (stat & BIT_MDT_XFIFO_EMPTY) + sii8620_write(ctx, REG_MDT_XMIT_CTRL, 0); + + sii8620_write(ctx, REG_MDT_INT_0, stat); +} + +static void sii8620_status_dcap_ready(struct sii8620 *ctx) +{ + enum sii8620_mode mode; + + mode = ctx->stat[MHL_DST_VERSION] >= 0x30 ? CM_MHL3 : CM_MHL1; + if (mode > ctx->mode) + sii8620_set_mode(ctx, mode); + sii8620_peer_specific_init(ctx); + sii8620_write(ctx, REG_INTR9_MASK, BIT_INTR9_DEVCAP_DONE + | BIT_INTR9_EDID_DONE | BIT_INTR9_EDID_ERROR); +} + +static void sii8620_status_changed_path(struct sii8620 *ctx) +{ + u8 link_mode; + + if (ctx->use_packed_pixel) + link_mode = MHL_DST_LM_CLK_MODE_PACKED_PIXEL; + else + link_mode = MHL_DST_LM_CLK_MODE_NORMAL; + + if (ctx->stat[MHL_DST_LINK_MODE] & MHL_DST_LM_PATH_ENABLED) + link_mode |= MHL_DST_LM_PATH_ENABLED; + + sii8620_mt_write_stat(ctx, MHL_DST_REG(LINK_MODE), + link_mode); +} + +static void sii8620_msc_mr_write_stat(struct sii8620 *ctx) +{ + u8 st[MHL_DST_SIZE], xst[MHL_XDS_SIZE]; + + sii8620_read_buf(ctx, REG_MHL_STAT_0, st, MHL_DST_SIZE); + sii8620_read_buf(ctx, REG_MHL_EXTSTAT_0, xst, MHL_XDS_SIZE); + + sii8620_update_array(ctx->stat, st, MHL_DST_SIZE); + sii8620_update_array(ctx->xstat, xst, MHL_XDS_SIZE); + + if (ctx->stat[MHL_DST_CONNECTED_RDY] & st[MHL_DST_CONNECTED_RDY] & + MHL_DST_CONN_DCAP_RDY) { + sii8620_status_dcap_ready(ctx); + + if (!sii8620_is_mhl3(ctx)) + sii8620_mt_read_devcap(ctx, false); + } + + if (st[MHL_DST_LINK_MODE] & MHL_DST_LM_PATH_ENABLED) + sii8620_status_changed_path(ctx); +} + +static void sii8620_ecbus_up(struct sii8620 *ctx, int ret) +{ + if (ret < 0) + return; + + sii8620_set_mode(ctx, CM_ECBUS_S); +} + +static void sii8620_got_ecbus_speed(struct sii8620 *ctx, int ret) +{ + if (ret < 0) + return; + + sii8620_mt_write_stat(ctx, MHL_XDS_REG(CURR_ECBUS_MODE), + MHL_XDS_ECBUS_S | MHL_XDS_SLOT_MODE_8BIT); + sii8620_mt_rap(ctx, MHL_RAP_CBUS_MODE_UP); + sii8620_mt_set_cont(ctx, sii8620_ecbus_up); +} + +static void sii8620_mhl_burst_emsc_support_set(struct mhl_burst_emsc_support *d, + enum mhl_burst_id id) +{ + sii8620_mhl_burst_hdr_set(&d->hdr, MHL_BURST_ID_EMSC_SUPPORT); + d->num_entries = 1; + d->burst_id[0] = cpu_to_be16(id); +} + +static void sii8620_send_features(struct sii8620 *ctx) +{ + u8 buf[16]; + + sii8620_write(ctx, REG_MDT_XMIT_CTRL, BIT_MDT_XMIT_CTRL_EN + | BIT_MDT_XMIT_CTRL_FIXED_BURST_LEN); + sii8620_mhl_burst_emsc_support_set((void *)buf, + MHL_BURST_ID_HID_PAYLOAD); + sii8620_write_buf(ctx, REG_MDT_XMIT_WRITE_PORT, buf, ARRAY_SIZE(buf)); +} + +static bool sii8620_rcp_consume(struct sii8620 *ctx, u8 scancode) +{ + bool pressed = !(scancode & MHL_RCP_KEY_RELEASED_MASK); + + scancode &= MHL_RCP_KEY_ID_MASK; + + if (!IS_ENABLED(CONFIG_RC_CORE) || !ctx->rc_dev) + return false; + + if (pressed) + rc_keydown(ctx->rc_dev, RC_PROTO_CEC, scancode, 0); + else + rc_keyup(ctx->rc_dev); + + return true; +} + +static void sii8620_msc_mr_set_int(struct sii8620 *ctx) +{ + u8 ints[MHL_INT_SIZE]; + + sii8620_read_buf(ctx, REG_MHL_INT_0, ints, MHL_INT_SIZE); + sii8620_write_buf(ctx, REG_MHL_INT_0, ints, MHL_INT_SIZE); + + if (ints[MHL_INT_RCHANGE] & MHL_INT_RC_DCAP_CHG) { + switch (ctx->mode) { + case CM_MHL3: + sii8620_mt_read_xdevcap_reg(ctx, MHL_XDC_ECBUS_SPEEDS); + sii8620_mt_set_cont(ctx, sii8620_got_ecbus_speed); + break; + case CM_ECBUS_S: + sii8620_mt_read_devcap(ctx, true); + break; + default: + break; + } + } + if (ints[MHL_INT_RCHANGE] & MHL_INT_RC_FEAT_REQ) + sii8620_send_features(ctx); + if (ints[MHL_INT_RCHANGE] & MHL_INT_RC_FEAT_COMPLETE) { + ctx->feature_complete = true; + if (ctx->edid) + sii8620_enable_hpd(ctx); + } +} + +static struct sii8620_mt_msg *sii8620_msc_msg_first(struct sii8620 *ctx) +{ + struct device *dev = ctx->dev; + + if (list_empty(&ctx->mt_queue)) { + dev_err(dev, "unexpected MSC MT response\n"); + return NULL; + } + + return list_first_entry(&ctx->mt_queue, struct sii8620_mt_msg, node); +} + +static void sii8620_msc_mt_done(struct sii8620 *ctx) +{ + struct sii8620_mt_msg *msg = sii8620_msc_msg_first(ctx); + + if (!msg) + return; + + msg->ret = sii8620_readb(ctx, REG_MSC_MT_RCVD_DATA0); + ctx->mt_state = MT_STATE_DONE; +} + +static void sii8620_msc_mr_msc_msg(struct sii8620 *ctx) +{ + struct sii8620_mt_msg *msg; + u8 buf[2]; + + sii8620_read_buf(ctx, REG_MSC_MR_MSC_MSG_RCVD_1ST_DATA, buf, 2); + + switch (buf[0]) { + case MHL_MSC_MSG_RAPK: + msg = sii8620_msc_msg_first(ctx); + if (!msg) + return; + msg->ret = buf[1]; + ctx->mt_state = MT_STATE_DONE; + break; + case MHL_MSC_MSG_RCP: + if (!sii8620_rcp_consume(ctx, buf[1])) + sii8620_mt_rcpe(ctx, + MHL_RCPE_STATUS_INEFFECTIVE_KEY_CODE); + sii8620_mt_rcpk(ctx, buf[1]); + break; + default: + dev_err(ctx->dev, "%s message type %d,%d not supported", + __func__, buf[0], buf[1]); + } +} + +static void sii8620_irq_msc(struct sii8620 *ctx) +{ + u8 stat = sii8620_readb(ctx, REG_CBUS_INT_0); + + if (stat & ~BIT_CBUS_HPD_CHG) + sii8620_write(ctx, REG_CBUS_INT_0, stat & ~BIT_CBUS_HPD_CHG); + + if (stat & BIT_CBUS_HPD_CHG) { + u8 cbus_stat = sii8620_readb(ctx, REG_CBUS_STATUS); + + if ((cbus_stat ^ ctx->cbus_status) & BIT_CBUS_STATUS_CBUS_HPD) { + sii8620_write(ctx, REG_CBUS_INT_0, BIT_CBUS_HPD_CHG); + } else { + stat ^= BIT_CBUS_STATUS_CBUS_HPD; + cbus_stat ^= BIT_CBUS_STATUS_CBUS_HPD; + } + ctx->cbus_status = cbus_stat; + } + + if (stat & BIT_CBUS_MSC_MR_WRITE_STAT) + sii8620_msc_mr_write_stat(ctx); + + if (stat & BIT_CBUS_HPD_CHG) { + if (ctx->cbus_status & BIT_CBUS_STATUS_CBUS_HPD) { + ctx->sink_detected = true; + sii8620_identify_sink(ctx); + } else { + sii8620_hpd_unplugged(ctx); + } + } + + if (stat & BIT_CBUS_MSC_MR_SET_INT) + sii8620_msc_mr_set_int(ctx); + + if (stat & BIT_CBUS_MSC_MT_DONE) + sii8620_msc_mt_done(ctx); + + if (stat & BIT_CBUS_MSC_MR_MSC_MSG) + sii8620_msc_mr_msc_msg(ctx); +} + +static void sii8620_irq_coc(struct sii8620 *ctx) +{ + u8 stat = sii8620_readb(ctx, REG_COC_INTR); + + if (stat & BIT_COC_CALIBRATION_DONE) { + u8 cstat = sii8620_readb(ctx, REG_COC_STAT_0); + + cstat &= BIT_COC_STAT_0_PLL_LOCKED | MSK_COC_STAT_0_FSM_STATE; + if (cstat == (BIT_COC_STAT_0_PLL_LOCKED | 0x02)) { + sii8620_write_seq_static(ctx, + REG_COC_CTLB, 0, + REG_TRXINTMH, BIT_TDM_INTR_SYNC_DATA + | BIT_TDM_INTR_SYNC_WAIT + ); + } + } + + sii8620_write(ctx, REG_COC_INTR, stat); +} + +static void sii8620_irq_merr(struct sii8620 *ctx) +{ + u8 stat = sii8620_readb(ctx, REG_CBUS_INT_1); + + sii8620_write(ctx, REG_CBUS_INT_1, stat); +} + +static void sii8620_irq_edid(struct sii8620 *ctx) +{ + u8 stat = sii8620_readb(ctx, REG_INTR9); + + sii8620_write(ctx, REG_INTR9, stat); + + if (stat & BIT_INTR9_DEVCAP_DONE) + ctx->mt_state = MT_STATE_DONE; +} + +static void sii8620_irq_scdt(struct sii8620 *ctx) +{ + u8 stat = sii8620_readb(ctx, REG_INTR5); + + if (stat & BIT_INTR_SCDT_CHANGE) { + u8 cstat = sii8620_readb(ctx, REG_TMDS_CSTAT_P3); + + if (cstat & BIT_TMDS_CSTAT_P3_SCDT) + sii8620_start_video(ctx); + } + + sii8620_write(ctx, REG_INTR5, stat); +} + +static void sii8620_got_xdevcap(struct sii8620 *ctx, int ret) +{ + if (ret < 0) + return; + + sii8620_mt_read_devcap(ctx, false); +} + +static void sii8620_irq_tdm(struct sii8620 *ctx) +{ + u8 stat = sii8620_readb(ctx, REG_TRXINTH); + u8 tdm = sii8620_readb(ctx, REG_TRXSTA2); + + if ((tdm & MSK_TDM_SYNCHRONIZED) == VAL_TDM_SYNCHRONIZED) { + ctx->mode = CM_ECBUS_S; + ctx->burst.rx_ack = 0; + ctx->burst.r_size = SII8620_BURST_BUF_LEN; + sii8620_burst_tx_rbuf_info(ctx, SII8620_BURST_BUF_LEN); + sii8620_mt_read_devcap(ctx, true); + sii8620_mt_set_cont(ctx, sii8620_got_xdevcap); + } else { + sii8620_write_seq_static(ctx, + REG_MHL_PLL_CTL2, 0, + REG_MHL_PLL_CTL2, BIT_MHL_PLL_CTL2_CLKDETECT_EN + ); + } + + sii8620_write(ctx, REG_TRXINTH, stat); +} + +static void sii8620_irq_block(struct sii8620 *ctx) +{ + u8 stat = sii8620_readb(ctx, REG_EMSCINTR); + + if (stat & BIT_EMSCINTR_SPI_DVLD) { + u8 bstat = sii8620_readb(ctx, REG_SPIBURSTSTAT); + + if (bstat & BIT_SPIBURSTSTAT_EMSC_NORMAL_MODE) + sii8620_burst_receive(ctx); + } + + sii8620_write(ctx, REG_EMSCINTR, stat); +} + +static void sii8620_irq_ddc(struct sii8620 *ctx) +{ + u8 stat = sii8620_readb(ctx, REG_INTR3); + + if (stat & BIT_DDC_CMD_DONE) { + sii8620_write(ctx, REG_INTR3_MASK, 0); + if (sii8620_is_mhl3(ctx) && !ctx->feature_complete) + sii8620_mt_set_int(ctx, MHL_INT_REG(RCHANGE), + MHL_INT_RC_FEAT_REQ); + else + sii8620_enable_hpd(ctx); + } + sii8620_write(ctx, REG_INTR3, stat); +} + +/* endian agnostic, non-volatile version of test_bit */ +static bool sii8620_test_bit(unsigned int nr, const u8 *addr) +{ + return 1 & (addr[nr / BITS_PER_BYTE] >> (nr % BITS_PER_BYTE)); +} + +static irqreturn_t sii8620_irq_thread(int irq, void *data) +{ + static const struct { + int bit; + void (*handler)(struct sii8620 *ctx); + } irq_vec[] = { + { BIT_FAST_INTR_STAT_DISC, sii8620_irq_disc }, + { BIT_FAST_INTR_STAT_G2WB, sii8620_irq_g2wb }, + { BIT_FAST_INTR_STAT_COC, sii8620_irq_coc }, + { BIT_FAST_INTR_STAT_TDM, sii8620_irq_tdm }, + { BIT_FAST_INTR_STAT_MSC, sii8620_irq_msc }, + { BIT_FAST_INTR_STAT_MERR, sii8620_irq_merr }, + { BIT_FAST_INTR_STAT_BLOCK, sii8620_irq_block }, + { BIT_FAST_INTR_STAT_EDID, sii8620_irq_edid }, + { BIT_FAST_INTR_STAT_DDC, sii8620_irq_ddc }, + { BIT_FAST_INTR_STAT_SCDT, sii8620_irq_scdt }, + }; + struct sii8620 *ctx = data; + u8 stats[LEN_FAST_INTR_STAT]; + int i, ret; + + mutex_lock(&ctx->lock); + + sii8620_read_buf(ctx, REG_FAST_INTR_STAT, stats, ARRAY_SIZE(stats)); + for (i = 0; i < ARRAY_SIZE(irq_vec); ++i) + if (sii8620_test_bit(irq_vec[i].bit, stats)) + irq_vec[i].handler(ctx); + + sii8620_burst_rx_all(ctx); + sii8620_mt_work(ctx); + sii8620_burst_send(ctx); + + ret = sii8620_clear_error(ctx); + if (ret) { + dev_err(ctx->dev, "Error during IRQ handling, %d.\n", ret); + sii8620_mhl_disconnected(ctx); + } + mutex_unlock(&ctx->lock); + + return IRQ_HANDLED; +} + +static void sii8620_cable_in(struct sii8620 *ctx) +{ + struct device *dev = ctx->dev; + u8 ver[5]; + int ret; + + ret = sii8620_hw_on(ctx); + if (ret) { + dev_err(dev, "Error powering on, %d.\n", ret); + return; + } + + sii8620_read_buf(ctx, REG_VND_IDL, ver, ARRAY_SIZE(ver)); + ret = sii8620_clear_error(ctx); + if (ret) { + dev_err(dev, "Error accessing I2C bus, %d.\n", ret); + return; + } + + dev_info(dev, "ChipID %02x%02x:%02x%02x rev %02x.\n", ver[1], ver[0], + ver[3], ver[2], ver[4]); + + sii8620_write(ctx, REG_DPD, + BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 | BIT_DPD_OSC_EN); + + sii8620_xtal_set_rate(ctx); + sii8620_disconnect(ctx); + + sii8620_write_seq_static(ctx, + REG_MHL_CBUS_CTL0, VAL_MHL_CBUS_CTL0_CBUS_DRV_SEL_STRONG + | VAL_MHL_CBUS_CTL0_CBUS_RGND_VBIAS_734, + REG_MHL_CBUS_CTL1, VAL_MHL_CBUS_CTL1_1115_OHM, + REG_DPD, BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 | BIT_DPD_OSC_EN, + ); + + ret = sii8620_clear_error(ctx); + if (ret) { + dev_err(dev, "Error accessing I2C bus, %d.\n", ret); + return; + } + + enable_irq(to_i2c_client(ctx->dev)->irq); +} + +static void sii8620_init_rcp_input_dev(struct sii8620 *ctx) +{ + struct rc_dev *rc_dev; + int ret; + + if (!IS_ENABLED(CONFIG_RC_CORE)) + return; + + rc_dev = rc_allocate_device(RC_DRIVER_SCANCODE); + if (!rc_dev) { + dev_err(ctx->dev, "Failed to allocate RC device\n"); + ctx->error = -ENOMEM; + return; + } + + rc_dev->input_phys = "sii8620/input0"; + rc_dev->input_id.bustype = BUS_VIRTUAL; + rc_dev->map_name = RC_MAP_CEC; + rc_dev->allowed_protocols = RC_PROTO_BIT_CEC; + rc_dev->driver_name = "sii8620"; + rc_dev->device_name = "sii8620"; + + ret = rc_register_device(rc_dev); + + if (ret) { + dev_err(ctx->dev, "Failed to register RC device\n"); + ctx->error = ret; + rc_free_device(rc_dev); + return; + } + ctx->rc_dev = rc_dev; +} + +static void sii8620_cable_out(struct sii8620 *ctx) +{ + disable_irq(to_i2c_client(ctx->dev)->irq); + sii8620_hw_off(ctx); +} + +static void sii8620_extcon_work(struct work_struct *work) +{ + struct sii8620 *ctx = + container_of(work, struct sii8620, extcon_wq); + int state = extcon_get_state(ctx->extcon, EXTCON_DISP_MHL); + + if (state == ctx->cable_state) + return; + + ctx->cable_state = state; + + if (state > 0) + sii8620_cable_in(ctx); + else + sii8620_cable_out(ctx); +} + +static int sii8620_extcon_notifier(struct notifier_block *self, + unsigned long event, void *ptr) +{ + struct sii8620 *ctx = + container_of(self, struct sii8620, extcon_nb); + + schedule_work(&ctx->extcon_wq); + + return NOTIFY_DONE; +} + +static int sii8620_extcon_init(struct sii8620 *ctx) +{ + struct extcon_dev *edev; + struct device_node *musb, *muic; + int ret; + + /* get micro-USB connector node */ + musb = of_graph_get_remote_node(ctx->dev->of_node, 1, -1); + /* next get micro-USB Interface Controller node */ + muic = of_get_next_parent(musb); + + if (!muic) { + dev_info(ctx->dev, "no extcon found, switching to 'always on' mode\n"); + return 0; + } + + edev = extcon_find_edev_by_node(muic); + of_node_put(muic); + if (IS_ERR(edev)) { + if (PTR_ERR(edev) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_err(ctx->dev, "Invalid or missing extcon\n"); + return PTR_ERR(edev); + } + + ctx->extcon = edev; + ctx->extcon_nb.notifier_call = sii8620_extcon_notifier; + INIT_WORK(&ctx->extcon_wq, sii8620_extcon_work); + ret = extcon_register_notifier(edev, EXTCON_DISP_MHL, &ctx->extcon_nb); + if (ret) { + dev_err(ctx->dev, "failed to register notifier for MHL\n"); + return ret; + } + + return 0; +} + +static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge) +{ + return container_of(bridge, struct sii8620, bridge); +} + +static int sii8620_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct sii8620 *ctx = bridge_to_sii8620(bridge); + + sii8620_init_rcp_input_dev(ctx); + + return sii8620_clear_error(ctx); +} + +static void sii8620_detach(struct drm_bridge *bridge) +{ + struct sii8620 *ctx = bridge_to_sii8620(bridge); + + if (!IS_ENABLED(CONFIG_RC_CORE)) + return; + + rc_unregister_device(ctx->rc_dev); +} + +static int sii8620_is_packing_required(struct sii8620 *ctx, + const struct drm_display_mode *mode) +{ + int max_pclk, max_pclk_pp_mode; + + if (sii8620_is_mhl3(ctx)) { + max_pclk = MHL3_MAX_PCLK; + max_pclk_pp_mode = MHL3_MAX_PCLK_PP_MODE; + } else { + max_pclk = MHL1_MAX_PCLK; + max_pclk_pp_mode = MHL1_MAX_PCLK_PP_MODE; + } + + if (mode->clock < max_pclk) + return 0; + else if (mode->clock < max_pclk_pp_mode) + return 1; + else + return -1; +} + +static enum drm_mode_status sii8620_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct sii8620 *ctx = bridge_to_sii8620(bridge); + int pack_required = sii8620_is_packing_required(ctx, mode); + bool can_pack = ctx->devcap[MHL_DCAP_VID_LINK_MODE] & + MHL_DCAP_VID_LINK_PPIXEL; + + switch (pack_required) { + case 0: + return MODE_OK; + case 1: + return (can_pack) ? MODE_OK : MODE_CLOCK_HIGH; + default: + return MODE_CLOCK_HIGH; + } +} + +static bool sii8620_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct sii8620 *ctx = bridge_to_sii8620(bridge); + + mutex_lock(&ctx->lock); + + ctx->use_packed_pixel = sii8620_is_packing_required(ctx, adjusted_mode); + + mutex_unlock(&ctx->lock); + + return true; +} + +static const struct drm_bridge_funcs sii8620_bridge_funcs = { + .attach = sii8620_attach, + .detach = sii8620_detach, + .mode_fixup = sii8620_mode_fixup, + .mode_valid = sii8620_mode_valid, +}; + +static int sii8620_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct sii8620 *ctx; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->dev = dev; + mutex_init(&ctx->lock); + INIT_LIST_HEAD(&ctx->mt_queue); + + ctx->clk_xtal = devm_clk_get(dev, "xtal"); + if (IS_ERR(ctx->clk_xtal)) + return dev_err_probe(dev, PTR_ERR(ctx->clk_xtal), + "failed to get xtal clock from DT\n"); + + if (!client->irq) { + dev_err(dev, "no irq provided\n"); + return -EINVAL; + } + irq_set_status_flags(client->irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(dev, client->irq, NULL, + sii8620_irq_thread, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "sii8620", ctx); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to install IRQ handler\n"); + + ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->gpio_reset)) + return dev_err_probe(dev, PTR_ERR(ctx->gpio_reset), + "failed to get reset gpio from DT\n"); + + ctx->supplies[0].supply = "cvcc10"; + ctx->supplies[1].supply = "iovcc18"; + ret = devm_regulator_bulk_get(dev, 2, ctx->supplies); + if (ret) + return ret; + + ret = sii8620_extcon_init(ctx); + if (ret < 0) { + dev_err(ctx->dev, "failed to initialize EXTCON\n"); + return ret; + } + + i2c_set_clientdata(client, ctx); + + ctx->bridge.funcs = &sii8620_bridge_funcs; + ctx->bridge.of_node = dev->of_node; + drm_bridge_add(&ctx->bridge); + + if (!ctx->extcon) + sii8620_cable_in(ctx); + + return 0; +} + +static int sii8620_remove(struct i2c_client *client) +{ + struct sii8620 *ctx = i2c_get_clientdata(client); + + if (ctx->extcon) { + extcon_unregister_notifier(ctx->extcon, EXTCON_DISP_MHL, + &ctx->extcon_nb); + flush_work(&ctx->extcon_wq); + if (ctx->cable_state > 0) + sii8620_cable_out(ctx); + } else { + sii8620_cable_out(ctx); + } + drm_bridge_remove(&ctx->bridge); + + return 0; +} + +static const struct of_device_id sii8620_dt_match[] = { + { .compatible = "sil,sii8620" }, + { }, +}; +MODULE_DEVICE_TABLE(of, sii8620_dt_match); + +static const struct i2c_device_id sii8620_id[] = { + { "sii8620", 0 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, sii8620_id); +static struct i2c_driver sii8620_driver = { + .driver = { + .name = "sii8620", + .of_match_table = of_match_ptr(sii8620_dt_match), + }, + .probe = sii8620_probe, + .remove = sii8620_remove, + .id_table = sii8620_id, +}; + +module_i2c_driver(sii8620_driver); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/sil-sii8620.h b/drivers/gpu/drm/bridge/sil-sii8620.h new file mode 100644 index 000000000..79d61caf3 --- /dev/null +++ b/drivers/gpu/drm/bridge/sil-sii8620.h @@ -0,0 +1,1532 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Registers of Silicon Image SiI8620 Mobile HD Transmitter + * + * Copyright (C) 2015, Samsung Electronics Co., Ltd. + * Andrzej Hajda <a.hajda@samsung.com> + * + * Based on MHL driver for Android devices. + * Copyright (C) 2013-2014 Silicon Image, Inc. + */ + +#ifndef __SIL_SII8620_H__ +#define __SIL_SII8620_H__ + +/* Vendor ID Low byte, default value: 0x01 */ +#define REG_VND_IDL 0x0000 + +/* Vendor ID High byte, default value: 0x00 */ +#define REG_VND_IDH 0x0001 + +/* Device ID Low byte, default value: 0x60 */ +#define REG_DEV_IDL 0x0002 + +/* Device ID High byte, default value: 0x86 */ +#define REG_DEV_IDH 0x0003 + +/* Device Revision, default value: 0x10 */ +#define REG_DEV_REV 0x0004 + +/* OTP DBYTE510, default value: 0x00 */ +#define REG_OTP_DBYTE510 0x0006 + +/* System Control #1, default value: 0x00 */ +#define REG_SYS_CTRL1 0x0008 +#define BIT_SYS_CTRL1_OTPVMUTEOVR_SET BIT(7) +#define BIT_SYS_CTRL1_VSYNCPIN BIT(6) +#define BIT_SYS_CTRL1_OTPADROPOVR_SET BIT(5) +#define BIT_SYS_CTRL1_BLOCK_DDC_BY_HPD BIT(4) +#define BIT_SYS_CTRL1_OTP2XVOVR_EN BIT(3) +#define BIT_SYS_CTRL1_OTP2XAOVR_EN BIT(2) +#define BIT_SYS_CTRL1_TX_CTRL_HDMI BIT(1) +#define BIT_SYS_CTRL1_OTPAMUTEOVR_SET BIT(0) + +/* System Control DPD, default value: 0x90 */ +#define REG_DPD 0x000b +#define BIT_DPD_PWRON_PLL BIT(7) +#define BIT_DPD_PDNTX12 BIT(6) +#define BIT_DPD_PDNRX12 BIT(5) +#define BIT_DPD_OSC_EN BIT(4) +#define BIT_DPD_PWRON_HSIC BIT(3) +#define BIT_DPD_PDIDCK_N BIT(2) +#define BIT_DPD_PD_MHL_CLK_N BIT(1) + +/* Dual link Control, default value: 0x00 */ +#define REG_DCTL 0x000d +#define BIT_DCTL_TDM_LCLK_PHASE BIT(7) +#define BIT_DCTL_HSIC_CLK_PHASE BIT(6) +#define BIT_DCTL_CTS_TCK_PHASE BIT(5) +#define BIT_DCTL_EXT_DDC_SEL BIT(4) +#define BIT_DCTL_TRANSCODE BIT(3) +#define BIT_DCTL_HSIC_RX_STROBE_PHASE BIT(2) +#define BIT_DCTL_HSIC_TX_BIST_START_SEL BIT(1) +#define BIT_DCTL_TCLKNX_PHASE BIT(0) + +/* PWD Software Reset, default value: 0x20 */ +#define REG_PWD_SRST 0x000e +#define BIT_PWD_SRST_COC_DOC_RST BIT(7) +#define BIT_PWD_SRST_CBUS_RST_SW BIT(6) +#define BIT_PWD_SRST_CBUS_RST_SW_EN BIT(5) +#define BIT_PWD_SRST_MHLFIFO_RST BIT(4) +#define BIT_PWD_SRST_CBUS_RST BIT(3) +#define BIT_PWD_SRST_SW_RST_AUTO BIT(2) +#define BIT_PWD_SRST_HDCP2X_SW_RST BIT(1) +#define BIT_PWD_SRST_SW_RST BIT(0) + +/* AKSV_1, default value: 0x00 */ +#define REG_AKSV_1 0x001d + +/* Video H Resolution #1, default value: 0x00 */ +#define REG_H_RESL 0x003a + +/* Video Mode, default value: 0x00 */ +#define REG_VID_MODE 0x004a +#define BIT_VID_MODE_M1080P BIT(6) + +/* Video Input Mode, default value: 0xc0 */ +#define REG_VID_OVRRD 0x0051 +#define BIT_VID_OVRRD_PP_AUTO_DISABLE BIT(7) +#define BIT_VID_OVRRD_M1080P_OVRRD BIT(6) +#define BIT_VID_OVRRD_MINIVSYNC_ON BIT(5) +#define BIT_VID_OVRRD_3DCONV_EN_FRAME_PACK BIT(4) +#define BIT_VID_OVRRD_ENABLE_AUTO_PATH_EN BIT(3) +#define BIT_VID_OVRRD_ENRGB2YCBCR_OVRRD BIT(2) +#define BIT_VID_OVRRD_ENDOWNSAMPLE_OVRRD BIT(0) + +/* I2C Address reassignment, default value: 0x00 */ +#define REG_PAGE_MHLSPEC_ADDR 0x0057 +#define REG_PAGE7_ADDR 0x0058 +#define REG_PAGE8_ADDR 0x005c + +/* Fast Interrupt Status, default value: 0x00 */ +#define REG_FAST_INTR_STAT 0x005f +#define LEN_FAST_INTR_STAT 7 +#define BIT_FAST_INTR_STAT_TIMR 8 +#define BIT_FAST_INTR_STAT_INT2 9 +#define BIT_FAST_INTR_STAT_DDC 10 +#define BIT_FAST_INTR_STAT_SCDT 11 +#define BIT_FAST_INTR_STAT_INFR 13 +#define BIT_FAST_INTR_STAT_EDID 14 +#define BIT_FAST_INTR_STAT_HDCP 15 +#define BIT_FAST_INTR_STAT_MSC 16 +#define BIT_FAST_INTR_STAT_MERR 17 +#define BIT_FAST_INTR_STAT_G2WB 18 +#define BIT_FAST_INTR_STAT_G2WB_ERR 19 +#define BIT_FAST_INTR_STAT_DISC 28 +#define BIT_FAST_INTR_STAT_BLOCK 30 +#define BIT_FAST_INTR_STAT_LTRN 31 +#define BIT_FAST_INTR_STAT_HDCP2 32 +#define BIT_FAST_INTR_STAT_TDM 42 +#define BIT_FAST_INTR_STAT_COC 51 + +/* GPIO Control, default value: 0x15 */ +#define REG_GPIO_CTRL1 0x006e +#define BIT_CTRL1_GPIO_I_8 BIT(5) +#define BIT_CTRL1_GPIO_OEN_8 BIT(4) +#define BIT_CTRL1_GPIO_I_7 BIT(3) +#define BIT_CTRL1_GPIO_OEN_7 BIT(2) +#define BIT_CTRL1_GPIO_I_6 BIT(1) +#define BIT_CTRL1_GPIO_OEN_6 BIT(0) + +/* Interrupt Control, default value: 0x06 */ +#define REG_INT_CTRL 0x006f +#define BIT_INT_CTRL_SOFTWARE_WP BIT(7) +#define BIT_INT_CTRL_INTR_OD BIT(2) +#define BIT_INT_CTRL_INTR_POLARITY BIT(1) + +/* Interrupt State, default value: 0x00 */ +#define REG_INTR_STATE 0x0070 +#define BIT_INTR_STATE_INTR_STATE BIT(0) + +/* Interrupt Source #1, default value: 0x00 */ +#define REG_INTR1 0x0071 + +/* Interrupt Source #2, default value: 0x00 */ +#define REG_INTR2 0x0072 + +/* Interrupt Source #3, default value: 0x01 */ +#define REG_INTR3 0x0073 +#define BIT_DDC_CMD_DONE BIT(3) + +/* Interrupt Source #5, default value: 0x00 */ +#define REG_INTR5 0x0074 + +/* Interrupt #1 Mask, default value: 0x00 */ +#define REG_INTR1_MASK 0x0075 + +/* Interrupt #2 Mask, default value: 0x00 */ +#define REG_INTR2_MASK 0x0076 + +/* Interrupt #3 Mask, default value: 0x00 */ +#define REG_INTR3_MASK 0x0077 + +/* Interrupt #5 Mask, default value: 0x00 */ +#define REG_INTR5_MASK 0x0078 +#define BIT_INTR_SCDT_CHANGE BIT(0) + +/* Hot Plug Connection Control, default value: 0x45 */ +#define REG_HPD_CTRL 0x0079 +#define BIT_HPD_CTRL_HPD_DS_SIGNAL BIT(7) +#define BIT_HPD_CTRL_HPD_OUT_OD_EN BIT(6) +#define BIT_HPD_CTRL_HPD_HIGH BIT(5) +#define BIT_HPD_CTRL_HPD_OUT_OVR_EN BIT(4) +#define BIT_HPD_CTRL_GPIO_I_1 BIT(3) +#define BIT_HPD_CTRL_GPIO_OEN_1 BIT(2) +#define BIT_HPD_CTRL_GPIO_I_0 BIT(1) +#define BIT_HPD_CTRL_GPIO_OEN_0 BIT(0) + +/* GPIO Control, default value: 0x55 */ +#define REG_GPIO_CTRL 0x007a +#define BIT_CTRL_GPIO_I_5 BIT(7) +#define BIT_CTRL_GPIO_OEN_5 BIT(6) +#define BIT_CTRL_GPIO_I_4 BIT(5) +#define BIT_CTRL_GPIO_OEN_4 BIT(4) +#define BIT_CTRL_GPIO_I_3 BIT(3) +#define BIT_CTRL_GPIO_OEN_3 BIT(2) +#define BIT_CTRL_GPIO_I_2 BIT(1) +#define BIT_CTRL_GPIO_OEN_2 BIT(0) + +/* Interrupt Source 7, default value: 0x00 */ +#define REG_INTR7 0x007b + +/* Interrupt Source 8, default value: 0x00 */ +#define REG_INTR8 0x007c + +/* Interrupt #7 Mask, default value: 0x00 */ +#define REG_INTR7_MASK 0x007d + +/* Interrupt #8 Mask, default value: 0x00 */ +#define REG_INTR8_MASK 0x007e +#define BIT_CEA_NEW_VSI BIT(2) +#define BIT_CEA_NEW_AVI BIT(1) + +/* IEEE, default value: 0x10 */ +#define REG_TMDS_CCTRL 0x0080 +#define BIT_TMDS_CCTRL_TMDS_OE BIT(4) + +/* TMDS Control #4, default value: 0x02 */ +#define REG_TMDS_CTRL4 0x0085 +#define BIT_TMDS_CTRL4_SCDT_CKDT_SEL BIT(1) +#define BIT_TMDS_CTRL4_TX_EN_BY_SCDT BIT(0) + +/* BIST CNTL, default value: 0x00 */ +#define REG_BIST_CTRL 0x00bb +#define BIT_RXBIST_VGB_EN BIT(7) +#define BIT_TXBIST_VGB_EN BIT(6) +#define BIT_BIST_START_SEL BIT(5) +#define BIT_BIST_START_BIT BIT(4) +#define BIT_BIST_ALWAYS_ON BIT(3) +#define BIT_BIST_TRANS BIT(2) +#define BIT_BIST_RESET BIT(1) +#define BIT_BIST_EN BIT(0) + +/* BIST DURATION0, default value: 0x00 */ +#define REG_BIST_TEST_SEL 0x00bd +#define MSK_BIST_TEST_SEL_BIST_PATT_SEL 0x0f + +/* BIST VIDEO_MODE, default value: 0x00 */ +#define REG_BIST_VIDEO_MODE 0x00be +#define MSK_BIST_VIDEO_MODE_BIST_VIDEO_MODE_3_0 0x0f + +/* BIST DURATION0, default value: 0x00 */ +#define REG_BIST_DURATION_0 0x00bf + +/* BIST DURATION1, default value: 0x00 */ +#define REG_BIST_DURATION_1 0x00c0 + +/* BIST DURATION2, default value: 0x00 */ +#define REG_BIST_DURATION_2 0x00c1 + +/* BIST 8BIT_PATTERN, default value: 0x00 */ +#define REG_BIST_8BIT_PATTERN 0x00c2 + +/* LM DDC, default value: 0x80 */ +#define REG_LM_DDC 0x00c7 +#define BIT_LM_DDC_SW_TPI_EN_DISABLED BIT(7) + +#define BIT_LM_DDC_VIDEO_MUTE_EN BIT(5) +#define BIT_LM_DDC_DDC_TPI_SW BIT(2) +#define BIT_LM_DDC_DDC_GRANT BIT(1) +#define BIT_LM_DDC_DDC_GPU_REQUEST BIT(0) + +/* DDC I2C Manual, default value: 0x03 */ +#define REG_DDC_MANUAL 0x00ec +#define BIT_DDC_MANUAL_MAN_DDC BIT(7) +#define BIT_DDC_MANUAL_VP_SEL BIT(6) +#define BIT_DDC_MANUAL_DSDA BIT(5) +#define BIT_DDC_MANUAL_DSCL BIT(4) +#define BIT_DDC_MANUAL_GCP_HW_CTL_EN BIT(3) +#define BIT_DDC_MANUAL_DDCM_ABORT_WP BIT(2) +#define BIT_DDC_MANUAL_IO_DSDA BIT(1) +#define BIT_DDC_MANUAL_IO_DSCL BIT(0) + +/* DDC I2C Target Slave Address, default value: 0x00 */ +#define REG_DDC_ADDR 0x00ed +#define MSK_DDC_ADDR_DDC_ADDR 0xfe + +/* DDC I2C Target Segment Address, default value: 0x00 */ +#define REG_DDC_SEGM 0x00ee + +/* DDC I2C Target Offset Address, default value: 0x00 */ +#define REG_DDC_OFFSET 0x00ef + +/* DDC I2C Data In count #1, default value: 0x00 */ +#define REG_DDC_DIN_CNT1 0x00f0 + +/* DDC I2C Data In count #2, default value: 0x00 */ +#define REG_DDC_DIN_CNT2 0x00f1 +#define MSK_DDC_DIN_CNT2_DDC_DIN_CNT_9_8 0x03 + +/* DDC I2C Status, default value: 0x04 */ +#define REG_DDC_STATUS 0x00f2 +#define BIT_DDC_STATUS_DDC_BUS_LOW BIT(6) +#define BIT_DDC_STATUS_DDC_NO_ACK BIT(5) +#define BIT_DDC_STATUS_DDC_I2C_IN_PROG BIT(4) +#define BIT_DDC_STATUS_DDC_FIFO_FULL BIT(3) +#define BIT_DDC_STATUS_DDC_FIFO_EMPTY BIT(2) +#define BIT_DDC_STATUS_DDC_FIFO_READ_IN_SUE BIT(1) +#define BIT_DDC_STATUS_DDC_FIFO_WRITE_IN_USE BIT(0) + +/* DDC I2C Command, default value: 0x70 */ +#define REG_DDC_CMD 0x00f3 +#define BIT_DDC_CMD_HDCP_DDC_EN BIT(6) +#define BIT_DDC_CMD_SDA_DEL_EN BIT(5) +#define BIT_DDC_CMD_DDC_FLT_EN BIT(4) + +#define MSK_DDC_CMD_DDC_CMD 0x0f +#define VAL_DDC_CMD_ENH_DDC_READ_NO_ACK 0x04 +#define VAL_DDC_CMD_DDC_CMD_CLEAR_FIFO 0x09 +#define VAL_DDC_CMD_DDC_CMD_ABORT 0x0f + +/* DDC I2C FIFO Data In/Out, default value: 0x00 */ +#define REG_DDC_DATA 0x00f4 + +/* DDC I2C Data Out Counter, default value: 0x00 */ +#define REG_DDC_DOUT_CNT 0x00f5 +#define BIT_DDC_DOUT_CNT_DDC_DELAY_CNT_8 BIT(7) +#define MSK_DDC_DOUT_CNT_DDC_DATA_OUT_CNT 0x1f + +/* DDC I2C Delay Count, default value: 0x14 */ +#define REG_DDC_DELAY_CNT 0x00f6 + +/* Test Control, default value: 0x80 */ +#define REG_TEST_TXCTRL 0x00f7 +#define BIT_TEST_TXCTRL_RCLK_REF_SEL BIT(7) +#define BIT_TEST_TXCTRL_PCLK_REF_SEL BIT(6) +#define MSK_TEST_TXCTRL_BYPASS_PLL_CLK 0x3c +#define BIT_TEST_TXCTRL_HDMI_MODE BIT(1) +#define BIT_TEST_TXCTRL_TST_PLLCK BIT(0) + +/* CBUS Address, default value: 0x00 */ +#define REG_PAGE_CBUS_ADDR 0x00f8 + +/* I2C Device Address re-assignment */ +#define REG_PAGE1_ADDR 0x00fc +#define REG_PAGE2_ADDR 0x00fd +#define REG_PAGE3_ADDR 0x00fe +#define REG_HW_TPI_ADDR 0x00ff + +/* USBT CTRL0, default value: 0x00 */ +#define REG_UTSRST 0x0100 +#define BIT_UTSRST_FC_SRST BIT(5) +#define BIT_UTSRST_KEEPER_SRST BIT(4) +#define BIT_UTSRST_HTX_SRST BIT(3) +#define BIT_UTSRST_TRX_SRST BIT(2) +#define BIT_UTSRST_TTX_SRST BIT(1) +#define BIT_UTSRST_HRX_SRST BIT(0) + +/* HSIC RX Control3, default value: 0x07 */ +#define REG_HRXCTRL3 0x0104 +#define MSK_HRXCTRL3_HRX_AFFCTRL 0xf0 +#define BIT_HRXCTRL3_HRX_OUT_EN BIT(2) +#define BIT_HRXCTRL3_STATUS_EN BIT(1) +#define BIT_HRXCTRL3_HRX_STAY_RESET BIT(0) + +/* HSIC RX INT Registers */ +#define REG_HRXINTL 0x0111 +#define REG_HRXINTH 0x0112 + +/* TDM TX NUMBITS, default value: 0x0c */ +#define REG_TTXNUMB 0x0116 +#define MSK_TTXNUMB_TTX_AFFCTRL_3_0 0xf0 +#define BIT_TTXNUMB_TTX_COM1_AT_SYNC_WAIT BIT(3) +#define MSK_TTXNUMB_TTX_NUMBPS 0x07 + +/* TDM TX NUMSPISYM, default value: 0x04 */ +#define REG_TTXSPINUMS 0x0117 + +/* TDM TX NUMHSICSYM, default value: 0x14 */ +#define REG_TTXHSICNUMS 0x0118 + +/* TDM TX NUMTOTSYM, default value: 0x18 */ +#define REG_TTXTOTNUMS 0x0119 + +/* TDM TX INT Low, default value: 0x00 */ +#define REG_TTXINTL 0x0136 +#define BIT_TTXINTL_TTX_INTR7 BIT(7) +#define BIT_TTXINTL_TTX_INTR6 BIT(6) +#define BIT_TTXINTL_TTX_INTR5 BIT(5) +#define BIT_TTXINTL_TTX_INTR4 BIT(4) +#define BIT_TTXINTL_TTX_INTR3 BIT(3) +#define BIT_TTXINTL_TTX_INTR2 BIT(2) +#define BIT_TTXINTL_TTX_INTR1 BIT(1) +#define BIT_TTXINTL_TTX_INTR0 BIT(0) + +/* TDM TX INT High, default value: 0x00 */ +#define REG_TTXINTH 0x0137 +#define BIT_TTXINTH_TTX_INTR15 BIT(7) +#define BIT_TTXINTH_TTX_INTR14 BIT(6) +#define BIT_TTXINTH_TTX_INTR13 BIT(5) +#define BIT_TTXINTH_TTX_INTR12 BIT(4) +#define BIT_TTXINTH_TTX_INTR11 BIT(3) +#define BIT_TTXINTH_TTX_INTR10 BIT(2) +#define BIT_TTXINTH_TTX_INTR9 BIT(1) +#define BIT_TTXINTH_TTX_INTR8 BIT(0) + +/* TDM RX Control, default value: 0x1c */ +#define REG_TRXCTRL 0x013b +#define BIT_TRXCTRL_TRX_CLR_WVALLOW BIT(4) +#define BIT_TRXCTRL_TRX_FROM_SE_COC BIT(3) +#define MSK_TRXCTRL_TRX_NUMBPS_2_0 0x07 + +/* TDM RX NUMSPISYM, default value: 0x04 */ +#define REG_TRXSPINUMS 0x013c + +/* TDM RX NUMHSICSYM, default value: 0x14 */ +#define REG_TRXHSICNUMS 0x013d + +/* TDM RX NUMTOTSYM, default value: 0x18 */ +#define REG_TRXTOTNUMS 0x013e + +/* TDM RX Status 2nd, default value: 0x00 */ +#define REG_TRXSTA2 0x015c +#define MSK_TDM_SYNCHRONIZED 0xc0 +#define VAL_TDM_SYNCHRONIZED 0x80 + +/* TDM RX INT Low, default value: 0x00 */ +#define REG_TRXINTL 0x0163 + +/* TDM RX INT High, default value: 0x00 */ +#define REG_TRXINTH 0x0164 +#define BIT_TDM_INTR_SYNC_DATA BIT(0) +#define BIT_TDM_INTR_SYNC_WAIT BIT(1) + +/* TDM RX INTMASK High, default value: 0x00 */ +#define REG_TRXINTMH 0x0166 + +/* HSIC TX CRTL, default value: 0x00 */ +#define REG_HTXCTRL 0x0169 +#define BIT_HTXCTRL_HTX_ALLSBE_SOP BIT(4) +#define BIT_HTXCTRL_HTX_RGDINV_USB BIT(3) +#define BIT_HTXCTRL_HTX_RSPTDM_BUSY BIT(2) +#define BIT_HTXCTRL_HTX_DRVCONN1 BIT(1) +#define BIT_HTXCTRL_HTX_DRVRST1 BIT(0) + +/* HSIC TX INT Low, default value: 0x00 */ +#define REG_HTXINTL 0x017d + +/* HSIC TX INT High, default value: 0x00 */ +#define REG_HTXINTH 0x017e + +/* HSIC Keeper, default value: 0x00 */ +#define REG_KEEPER 0x0181 +#define MSK_KEEPER_MODE 0x03 +#define VAL_KEEPER_MODE_HOST 0 +#define VAL_KEEPER_MODE_DEVICE 2 + +/* HSIC Flow Control General, default value: 0x02 */ +#define REG_FCGC 0x0183 +#define BIT_FCGC_HSIC_HOSTMODE BIT(1) +#define BIT_FCGC_HSIC_ENABLE BIT(0) + +/* HSIC Flow Control CTR13, default value: 0xfc */ +#define REG_FCCTR13 0x0191 + +/* HSIC Flow Control CTR14, default value: 0xff */ +#define REG_FCCTR14 0x0192 + +/* HSIC Flow Control CTR15, default value: 0xff */ +#define REG_FCCTR15 0x0193 + +/* HSIC Flow Control CTR50, default value: 0x03 */ +#define REG_FCCTR50 0x01b6 + +/* HSIC Flow Control INTR0, default value: 0x00 */ +#define REG_FCINTR0 0x01ec +#define REG_FCINTR1 0x01ed +#define REG_FCINTR2 0x01ee +#define REG_FCINTR3 0x01ef +#define REG_FCINTR4 0x01f0 +#define REG_FCINTR5 0x01f1 +#define REG_FCINTR6 0x01f2 +#define REG_FCINTR7 0x01f3 + +/* TDM Low Latency, default value: 0x20 */ +#define REG_TDMLLCTL 0x01fc +#define MSK_TDMLLCTL_TRX_LL_SEL_MANUAL 0xc0 +#define MSK_TDMLLCTL_TRX_LL_SEL_MODE 0x30 +#define MSK_TDMLLCTL_TTX_LL_SEL_MANUAL 0x0c +#define BIT_TDMLLCTL_TTX_LL_TIE_LOW BIT(1) +#define BIT_TDMLLCTL_TTX_LL_SEL_MODE BIT(0) + +/* TMDS 0 Clock Control, default value: 0x10 */ +#define REG_TMDS0_CCTRL1 0x0210 +#define MSK_TMDS0_CCTRL1_TEST_SEL 0xc0 +#define MSK_TMDS0_CCTRL1_CLK1X_CTL 0x30 + +/* TMDS Clock Enable, default value: 0x00 */ +#define REG_TMDS_CLK_EN 0x0211 +#define BIT_TMDS_CLK_EN_CLK_EN BIT(0) + +/* TMDS Channel Enable, default value: 0x00 */ +#define REG_TMDS_CH_EN 0x0212 +#define BIT_TMDS_CH_EN_CH0_EN BIT(4) +#define BIT_TMDS_CH_EN_CH12_EN BIT(0) + +/* BGR_BIAS, default value: 0x07 */ +#define REG_BGR_BIAS 0x0215 +#define BIT_BGR_BIAS_BGR_EN BIT(7) +#define MSK_BGR_BIAS_BIAS_BGR_D 0x0f + +/* TMDS 0 Digital I2C BW, default value: 0x0a */ +#define REG_ALICE0_BW_I2C 0x0231 + +/* TMDS 0 Digital Zone Control, default value: 0xe0 */ +#define REG_ALICE0_ZONE_CTRL 0x024c +#define BIT_ALICE0_ZONE_CTRL_ICRST_N BIT(7) +#define BIT_ALICE0_ZONE_CTRL_USE_INT_DIV20 BIT(6) +#define MSK_ALICE0_ZONE_CTRL_SZONE_I2C 0x30 +#define MSK_ALICE0_ZONE_CTRL_ZONE_CTRL 0x0f + +/* TMDS 0 Digital PLL Mode Control, default value: 0x00 */ +#define REG_ALICE0_MODE_CTRL 0x024d +#define MSK_ALICE0_MODE_CTRL_PLL_MODE_I2C 0x0c +#define MSK_ALICE0_MODE_CTRL_DIV20_CTRL 0x03 + +/* MHL Tx Control 6th, default value: 0xa0 */ +#define REG_MHLTX_CTL6 0x0285 +#define MSK_MHLTX_CTL6_EMI_SEL 0xe0 +#define MSK_MHLTX_CTL6_TX_CLK_SHAPE_9_8 0x03 + +/* Packet Filter0, default value: 0x00 */ +#define REG_PKT_FILTER_0 0x0290 +#define BIT_PKT_FILTER_0_DROP_CEA_GAMUT_PKT BIT(7) +#define BIT_PKT_FILTER_0_DROP_CEA_CP_PKT BIT(6) +#define BIT_PKT_FILTER_0_DROP_MPEG_PKT BIT(5) +#define BIT_PKT_FILTER_0_DROP_SPIF_PKT BIT(4) +#define BIT_PKT_FILTER_0_DROP_AIF_PKT BIT(3) +#define BIT_PKT_FILTER_0_DROP_AVI_PKT BIT(2) +#define BIT_PKT_FILTER_0_DROP_CTS_PKT BIT(1) +#define BIT_PKT_FILTER_0_DROP_GCP_PKT BIT(0) + +/* Packet Filter1, default value: 0x00 */ +#define REG_PKT_FILTER_1 0x0291 +#define BIT_PKT_FILTER_1_VSI_OVERRIDE_DIS BIT(7) +#define BIT_PKT_FILTER_1_AVI_OVERRIDE_DIS BIT(6) +#define BIT_PKT_FILTER_1_DROP_AUDIO_PKT BIT(3) +#define BIT_PKT_FILTER_1_DROP_GEN2_PKT BIT(2) +#define BIT_PKT_FILTER_1_DROP_GEN_PKT BIT(1) +#define BIT_PKT_FILTER_1_DROP_VSIF_PKT BIT(0) + +/* TMDS Clock Status, default value: 0x10 */ +#define REG_TMDS_CSTAT_P3 0x02a0 +#define BIT_TMDS_CSTAT_P3_RX_HDMI_CP_CLR_MUTE BIT(7) +#define BIT_TMDS_CSTAT_P3_RX_HDMI_CP_SET_MUTE BIT(6) +#define BIT_TMDS_CSTAT_P3_RX_HDMI_CP_NEW_CP BIT(5) +#define BIT_TMDS_CSTAT_P3_CLR_AVI BIT(3) +#define BIT_TMDS_CSTAT_P3_SCDT_CLR_AVI_DIS BIT(2) +#define BIT_TMDS_CSTAT_P3_SCDT BIT(1) +#define BIT_TMDS_CSTAT_P3_CKDT BIT(0) + +/* RX_HDMI Control, default value: 0x10 */ +#define REG_RX_HDMI_CTRL0 0x02a1 +#define BIT_RX_HDMI_CTRL0_BYP_DVIFILT_SYNC BIT(5) +#define BIT_RX_HDMI_CTRL0_HDMI_MODE_EN_ITSELF_CLR BIT(4) +#define BIT_RX_HDMI_CTRL0_HDMI_MODE_SW_VALUE BIT(3) +#define BIT_RX_HDMI_CTRL0_HDMI_MODE_OVERWRITE BIT(2) +#define BIT_RX_HDMI_CTRL0_RX_HDMI_HDMI_MODE_EN BIT(1) +#define BIT_RX_HDMI_CTRL0_RX_HDMI_HDMI_MODE BIT(0) + +/* RX_HDMI Control, default value: 0x38 */ +#define REG_RX_HDMI_CTRL2 0x02a3 +#define MSK_RX_HDMI_CTRL2_IDLE_CNT 0xf0 +#define VAL_RX_HDMI_CTRL2_IDLE_CNT(n) ((n) << 4) +#define BIT_RX_HDMI_CTRL2_USE_AV_MUTE BIT(3) +#define BIT_RX_HDMI_CTRL2_VSI_MON_SEL_VSI BIT(0) + +/* RX_HDMI Control, default value: 0x0f */ +#define REG_RX_HDMI_CTRL3 0x02a4 +#define MSK_RX_HDMI_CTRL3_PP_MODE_CLK_EN 0x0f + +/* rx_hdmi Clear Buffer, default value: 0x00 */ +#define REG_RX_HDMI_CLR_BUFFER 0x02ac +#define MSK_RX_HDMI_CLR_BUFFER_AIF4VSI_CMP 0xc0 +#define BIT_RX_HDMI_CLR_BUFFER_USE_AIF4VSI BIT(5) +#define BIT_RX_HDMI_CLR_BUFFER_VSI_CLR_W_AVI BIT(4) +#define BIT_RX_HDMI_CLR_BUFFER_VSI_IEEE_ID_CHK_EN BIT(3) +#define BIT_RX_HDMI_CLR_BUFFER_SWAP_VSI_IEEE_ID BIT(2) +#define BIT_RX_HDMI_CLR_BUFFER_AIF_CLR_EN BIT(1) +#define BIT_RX_HDMI_CLR_BUFFER_VSI_CLR_EN BIT(0) + +/* RX_HDMI VSI Header1, default value: 0x00 */ +#define REG_RX_HDMI_MON_PKT_HEADER1 0x02b8 + +/* RX_HDMI VSI MHL Monitor, default value: 0x3c */ +#define REG_RX_HDMI_VSIF_MHL_MON 0x02d7 + +#define MSK_RX_HDMI_VSIF_MHL_MON_RX_HDMI_MHL_3D_FORMAT 0x3c +#define MSK_RX_HDMI_VSIF_MHL_MON_RX_HDMI_MHL_VID_FORMAT 0x03 + +/* Interrupt Source 9, default value: 0x00 */ +#define REG_INTR9 0x02e0 +#define BIT_INTR9_EDID_ERROR BIT(6) +#define BIT_INTR9_EDID_DONE BIT(5) +#define BIT_INTR9_DEVCAP_DONE BIT(4) + +/* Interrupt 9 Mask, default value: 0x00 */ +#define REG_INTR9_MASK 0x02e1 + +/* TPI CBUS Start, default value: 0x00 */ +#define REG_TPI_CBUS_START 0x02e2 +#define BIT_TPI_CBUS_START_RCP_REQ_START BIT(7) +#define BIT_TPI_CBUS_START_RCPK_REPLY_START BIT(6) +#define BIT_TPI_CBUS_START_RCPE_REPLY_START BIT(5) +#define BIT_TPI_CBUS_START_PUT_LINK_MODE_START BIT(4) +#define BIT_TPI_CBUS_START_PUT_DCAPCHG_START BIT(3) +#define BIT_TPI_CBUS_START_PUT_DCAPRDY_START BIT(2) +#define BIT_TPI_CBUS_START_GET_EDID_START_0 BIT(1) +#define BIT_TPI_CBUS_START_GET_DEVCAP_START BIT(0) + +/* EDID Control, default value: 0x10 */ +#define REG_EDID_CTRL 0x02e3 +#define BIT_EDID_CTRL_EDID_PRIME_VALID BIT(7) +#define BIT_EDID_CTRL_XDEVCAP_EN BIT(6) +#define BIT_EDID_CTRL_DEVCAP_SELECT_DEVCAP BIT(5) +#define BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO BIT(4) +#define BIT_EDID_CTRL_EDID_FIFO_ACCESS_ALWAYS_EN BIT(3) +#define BIT_EDID_CTRL_EDID_FIFO_BLOCK_SEL BIT(2) +#define BIT_EDID_CTRL_INVALID_BKSV BIT(1) +#define BIT_EDID_CTRL_EDID_MODE_EN BIT(0) + +/* EDID FIFO Addr, default value: 0x00 */ +#define REG_EDID_FIFO_ADDR 0x02e9 + +/* EDID FIFO Write Data, default value: 0x00 */ +#define REG_EDID_FIFO_WR_DATA 0x02ea + +/* EDID/DEVCAP FIFO Internal Addr, default value: 0x00 */ +#define REG_EDID_FIFO_ADDR_MON 0x02eb + +/* EDID FIFO Read Data, default value: 0x00 */ +#define REG_EDID_FIFO_RD_DATA 0x02ec + +/* EDID DDC Segment Pointer, default value: 0x00 */ +#define REG_EDID_START_EXT 0x02ed + +/* TX IP BIST CNTL and Status, default value: 0x00 */ +#define REG_TX_IP_BIST_CNTLSTA 0x02f2 +#define BIT_TX_IP_BIST_CNTLSTA_TXBIST_QUARTER_CLK_SEL BIT(6) +#define BIT_TX_IP_BIST_CNTLSTA_TXBIST_DONE BIT(5) +#define BIT_TX_IP_BIST_CNTLSTA_TXBIST_ON BIT(4) +#define BIT_TX_IP_BIST_CNTLSTA_TXBIST_RUN BIT(3) +#define BIT_TX_IP_BIST_CNTLSTA_TXCLK_HALF_SEL BIT(2) +#define BIT_TX_IP_BIST_CNTLSTA_TXBIST_EN BIT(1) +#define BIT_TX_IP_BIST_CNTLSTA_TXBIST_SEL BIT(0) + +/* TX IP BIST INST LOW, default value: 0x00 */ +#define REG_TX_IP_BIST_INST_LOW 0x02f3 +#define REG_TX_IP_BIST_INST_HIGH 0x02f4 + +/* TX IP BIST PATTERN LOW, default value: 0x00 */ +#define REG_TX_IP_BIST_PAT_LOW 0x02f5 +#define REG_TX_IP_BIST_PAT_HIGH 0x02f6 + +/* TX IP BIST CONFIGURE LOW, default value: 0x00 */ +#define REG_TX_IP_BIST_CONF_LOW 0x02f7 +#define REG_TX_IP_BIST_CONF_HIGH 0x02f8 + +/* E-MSC General Control, default value: 0x80 */ +#define REG_GENCTL 0x0300 +#define BIT_GENCTL_SPEC_TRANS_DIS BIT(7) +#define BIT_GENCTL_DIS_XMIT_ERR_STATE BIT(6) +#define BIT_GENCTL_SPI_MISO_EDGE BIT(5) +#define BIT_GENCTL_SPI_MOSI_EDGE BIT(4) +#define BIT_GENCTL_CLR_EMSC_RFIFO BIT(3) +#define BIT_GENCTL_CLR_EMSC_XFIFO BIT(2) +#define BIT_GENCTL_START_TRAIN_SEQ BIT(1) +#define BIT_GENCTL_EMSC_EN BIT(0) + +/* E-MSC Comma ErrorCNT, default value: 0x03 */ +#define REG_COMMECNT 0x0305 +#define BIT_COMMECNT_I2C_TO_EMSC_EN BIT(7) +#define MSK_COMMECNT_COMMA_CHAR_ERR_CNT 0x0f + +/* E-MSC RFIFO ByteCnt, default value: 0x00 */ +#define REG_EMSCRFIFOBCNTL 0x031a +#define REG_EMSCRFIFOBCNTH 0x031b + +/* SPI Burst Cnt Status, default value: 0x00 */ +#define REG_SPIBURSTCNT 0x031e + +/* SPI Burst Status and SWRST, default value: 0x00 */ +#define REG_SPIBURSTSTAT 0x0322 +#define BIT_SPIBURSTSTAT_SPI_HDCPRST BIT(7) +#define BIT_SPIBURSTSTAT_SPI_CBUSRST BIT(6) +#define BIT_SPIBURSTSTAT_SPI_SRST BIT(5) +#define BIT_SPIBURSTSTAT_EMSC_NORMAL_MODE BIT(0) + +/* E-MSC 1st Interrupt, default value: 0x00 */ +#define REG_EMSCINTR 0x0323 +#define BIT_EMSCINTR_EMSC_XFIFO_EMPTY BIT(7) +#define BIT_EMSCINTR_EMSC_XMIT_ACK_TOUT BIT(6) +#define BIT_EMSCINTR_EMSC_RFIFO_READ_ERR BIT(5) +#define BIT_EMSCINTR_EMSC_XFIFO_WRITE_ERR BIT(4) +#define BIT_EMSCINTR_EMSC_COMMA_CHAR_ERR BIT(3) +#define BIT_EMSCINTR_EMSC_XMIT_DONE BIT(2) +#define BIT_EMSCINTR_EMSC_XMIT_GNT_TOUT BIT(1) +#define BIT_EMSCINTR_SPI_DVLD BIT(0) + +/* E-MSC Interrupt Mask, default value: 0x00 */ +#define REG_EMSCINTRMASK 0x0324 + +/* I2C E-MSC XMIT FIFO Write Port, default value: 0x00 */ +#define REG_EMSC_XMIT_WRITE_PORT 0x032a + +/* I2C E-MSC RCV FIFO Write Port, default value: 0x00 */ +#define REG_EMSC_RCV_READ_PORT 0x032b + +/* E-MSC 2nd Interrupt, default value: 0x00 */ +#define REG_EMSCINTR1 0x032c +#define BIT_EMSCINTR1_EMSC_TRAINING_COMMA_ERR BIT(0) + +/* E-MSC Interrupt Mask, default value: 0x00 */ +#define REG_EMSCINTRMASK1 0x032d +#define BIT_EMSCINTRMASK1_EMSC_INTRMASK1_0 BIT(0) + +/* MHL Top Ctl, default value: 0x00 */ +#define REG_MHL_TOP_CTL 0x0330 +#define BIT_MHL_TOP_CTL_MHL3_DOC_SEL BIT(7) +#define BIT_MHL_TOP_CTL_MHL_PP_SEL BIT(6) +#define MSK_MHL_TOP_CTL_IF_TIMING_CTL 0x03 + +/* MHL DataPath 1st Ctl, default value: 0xbc */ +#define REG_MHL_DP_CTL0 0x0331 +#define BIT_MHL_DP_CTL0_DP_OE BIT(7) +#define BIT_MHL_DP_CTL0_TX_OE_OVR BIT(6) +#define MSK_MHL_DP_CTL0_TX_OE 0x3f + +/* MHL DataPath 2nd Ctl, default value: 0xbb */ +#define REG_MHL_DP_CTL1 0x0332 +#define MSK_MHL_DP_CTL1_CK_SWING_CTL 0xf0 +#define MSK_MHL_DP_CTL1_DT_SWING_CTL 0x0f + +/* MHL DataPath 3rd Ctl, default value: 0x2f */ +#define REG_MHL_DP_CTL2 0x0333 +#define BIT_MHL_DP_CTL2_CLK_BYPASS_EN BIT(7) +#define MSK_MHL_DP_CTL2_DAMP_TERM_SEL 0x30 +#define MSK_MHL_DP_CTL2_CK_TERM_SEL 0x0c +#define MSK_MHL_DP_CTL2_DT_TERM_SEL 0x03 + +/* MHL DataPath 4th Ctl, default value: 0x48 */ +#define REG_MHL_DP_CTL3 0x0334 +#define MSK_MHL_DP_CTL3_DT_DRV_VNBC_CTL 0xf0 +#define MSK_MHL_DP_CTL3_DT_DRV_VNB_CTL 0x0f + +/* MHL DataPath 5th Ctl, default value: 0x48 */ +#define REG_MHL_DP_CTL4 0x0335 +#define MSK_MHL_DP_CTL4_CK_DRV_VNBC_CTL 0xf0 +#define MSK_MHL_DP_CTL4_CK_DRV_VNB_CTL 0x0f + +/* MHL DataPath 6th Ctl, default value: 0x3f */ +#define REG_MHL_DP_CTL5 0x0336 +#define BIT_MHL_DP_CTL5_RSEN_EN_OVR BIT(7) +#define BIT_MHL_DP_CTL5_RSEN_EN BIT(6) +#define MSK_MHL_DP_CTL5_DAMP_TERM_VGS_CTL 0x30 +#define MSK_MHL_DP_CTL5_CK_TERM_VGS_CTL 0x0c +#define MSK_MHL_DP_CTL5_DT_TERM_VGS_CTL 0x03 + +/* MHL PLL 1st Ctl, default value: 0x05 */ +#define REG_MHL_PLL_CTL0 0x0337 +#define BIT_MHL_PLL_CTL0_AUD_CLK_EN BIT(7) + +#define MSK_MHL_PLL_CTL0_AUD_CLK_RATIO 0x70 +#define VAL_MHL_PLL_CTL0_AUD_CLK_RATIO_5_10 0x70 +#define VAL_MHL_PLL_CTL0_AUD_CLK_RATIO_5_6 0x60 +#define VAL_MHL_PLL_CTL0_AUD_CLK_RATIO_5_4 0x50 +#define VAL_MHL_PLL_CTL0_AUD_CLK_RATIO_5_2 0x40 +#define VAL_MHL_PLL_CTL0_AUD_CLK_RATIO_5_5 0x30 +#define VAL_MHL_PLL_CTL0_AUD_CLK_RATIO_5_3 0x20 +#define VAL_MHL_PLL_CTL0_AUD_CLK_RATIO_5_2_PRIME 0x10 +#define VAL_MHL_PLL_CTL0_AUD_CLK_RATIO_5_1 0x00 + +#define MSK_MHL_PLL_CTL0_HDMI_CLK_RATIO 0x0c +#define VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_4X 0x0c +#define VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_2X 0x08 +#define VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X 0x04 +#define VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_HALF_X 0x00 + +#define BIT_MHL_PLL_CTL0_CRYSTAL_CLK_SEL BIT(1) +#define BIT_MHL_PLL_CTL0_ZONE_MASK_OE BIT(0) + +/* MHL PLL 3rd Ctl, default value: 0x80 */ +#define REG_MHL_PLL_CTL2 0x0339 +#define BIT_MHL_PLL_CTL2_CLKDETECT_EN BIT(7) +#define BIT_MHL_PLL_CTL2_MEAS_FVCO BIT(3) +#define BIT_MHL_PLL_CTL2_PLL_FAST_LOCK BIT(2) +#define MSK_MHL_PLL_CTL2_PLL_LF_SEL 0x03 + +/* MHL CBUS 1st Ctl, default value: 0x12 */ +#define REG_MHL_CBUS_CTL0 0x0340 +#define BIT_MHL_CBUS_CTL0_CBUS_RGND_TEST_MODE BIT(7) + +#define MSK_MHL_CBUS_CTL0_CBUS_RGND_VTH_CTL 0x30 +#define VAL_MHL_CBUS_CTL0_CBUS_RGND_VBIAS_734 0x00 +#define VAL_MHL_CBUS_CTL0_CBUS_RGND_VBIAS_747 0x10 +#define VAL_MHL_CBUS_CTL0_CBUS_RGND_VBIAS_740 0x20 +#define VAL_MHL_CBUS_CTL0_CBUS_RGND_VBIAS_754 0x30 + +#define MSK_MHL_CBUS_CTL0_CBUS_RES_TEST_SEL 0x0c + +#define MSK_MHL_CBUS_CTL0_CBUS_DRV_SEL 0x03 +#define VAL_MHL_CBUS_CTL0_CBUS_DRV_SEL_WEAKEST 0x00 +#define VAL_MHL_CBUS_CTL0_CBUS_DRV_SEL_WEAK 0x01 +#define VAL_MHL_CBUS_CTL0_CBUS_DRV_SEL_STRONG 0x02 +#define VAL_MHL_CBUS_CTL0_CBUS_DRV_SEL_STRONGEST 0x03 + +/* MHL CBUS 2nd Ctl, default value: 0x03 */ +#define REG_MHL_CBUS_CTL1 0x0341 +#define MSK_MHL_CBUS_CTL1_CBUS_RGND_RES_CTL 0x07 +#define VAL_MHL_CBUS_CTL1_0888_OHM 0x00 +#define VAL_MHL_CBUS_CTL1_1115_OHM 0x04 +#define VAL_MHL_CBUS_CTL1_1378_OHM 0x07 + +/* MHL CoC 1st Ctl, default value: 0xc3 */ +#define REG_MHL_COC_CTL0 0x0342 +#define BIT_MHL_COC_CTL0_COC_BIAS_EN BIT(7) +#define MSK_MHL_COC_CTL0_COC_BIAS_CTL 0x70 +#define MSK_MHL_COC_CTL0_COC_TERM_CTL 0x07 + +/* MHL CoC 2nd Ctl, default value: 0x87 */ +#define REG_MHL_COC_CTL1 0x0343 +#define BIT_MHL_COC_CTL1_COC_EN BIT(7) +#define MSK_MHL_COC_CTL1_COC_DRV_CTL 0x3f + +/* MHL CoC 4th Ctl, default value: 0x00 */ +#define REG_MHL_COC_CTL3 0x0345 +#define BIT_MHL_COC_CTL3_COC_AECHO_EN BIT(0) + +/* MHL CoC 5th Ctl, default value: 0x28 */ +#define REG_MHL_COC_CTL4 0x0346 +#define MSK_MHL_COC_CTL4_COC_IF_CTL 0xf0 +#define MSK_MHL_COC_CTL4_COC_SLEW_CTL 0x0f + +/* MHL CoC 6th Ctl, default value: 0x0d */ +#define REG_MHL_COC_CTL5 0x0347 + +/* MHL DoC 1st Ctl, default value: 0x18 */ +#define REG_MHL_DOC_CTL0 0x0349 +#define BIT_MHL_DOC_CTL0_DOC_RXDATA_EN BIT(7) +#define MSK_MHL_DOC_CTL0_DOC_DM_TERM 0x38 +#define MSK_MHL_DOC_CTL0_DOC_OPMODE 0x06 +#define BIT_MHL_DOC_CTL0_DOC_RXBIAS_EN BIT(0) + +/* MHL DataPath 7th Ctl, default value: 0x2a */ +#define REG_MHL_DP_CTL6 0x0350 +#define BIT_MHL_DP_CTL6_DP_TAP2_SGN BIT(5) +#define BIT_MHL_DP_CTL6_DP_TAP2_EN BIT(4) +#define BIT_MHL_DP_CTL6_DP_TAP1_SGN BIT(3) +#define BIT_MHL_DP_CTL6_DP_TAP1_EN BIT(2) +#define BIT_MHL_DP_CTL6_DT_PREDRV_FEEDCAP_EN BIT(1) +#define BIT_MHL_DP_CTL6_DP_PRE_POST_SEL BIT(0) + +/* MHL DataPath 8th Ctl, default value: 0x06 */ +#define REG_MHL_DP_CTL7 0x0351 +#define MSK_MHL_DP_CTL7_DT_DRV_VBIAS_CASCTL 0xf0 +#define MSK_MHL_DP_CTL7_DT_DRV_IREF_CTL 0x0f + +#define REG_MHL_DP_CTL8 0x0352 + +/* Tx Zone Ctl1, default value: 0x00 */ +#define REG_TX_ZONE_CTL1 0x0361 +#define VAL_TX_ZONE_CTL1_TX_ZONE_CTRL_MODE 0x08 + +/* MHL3 Tx Zone Ctl, default value: 0x00 */ +#define REG_MHL3_TX_ZONE_CTL 0x0364 +#define BIT_MHL3_TX_ZONE_CTL_MHL2_INTPLT_ZONE_MANU_EN BIT(7) +#define MSK_MHL3_TX_ZONE_CTL_MHL3_TX_ZONE 0x03 + +#define MSK_TX_ZONE_CTL3_TX_ZONE 0x03 +#define VAL_TX_ZONE_CTL3_TX_ZONE_6GBPS 0x00 +#define VAL_TX_ZONE_CTL3_TX_ZONE_3GBPS 0x01 +#define VAL_TX_ZONE_CTL3_TX_ZONE_1_5GBPS 0x02 + +/* HDCP Polling Control and Status, default value: 0x70 */ +#define REG_HDCP2X_POLL_CS 0x0391 + +#define BIT_HDCP2X_POLL_CS_HDCP2X_MSG_SZ_CLR_OPTION BIT(6) +#define BIT_HDCP2X_POLL_CS_HDCP2X_RPT_READY_CLR_OPTION BIT(5) +#define BIT_HDCP2X_POLL_CS_HDCP2X_REAUTH_REQ_CLR_OPTION BIT(4) +#define MSK_HDCP2X_POLL_CS_ 0x0c +#define BIT_HDCP2X_POLL_CS_HDCP2X_DIS_POLL_GNT BIT(1) +#define BIT_HDCP2X_POLL_CS_HDCP2X_DIS_POLL_EN BIT(0) + +/* HDCP Interrupt 0, default value: 0x00 */ +#define REG_HDCP2X_INTR0 0x0398 + +/* HDCP Interrupt 0 Mask, default value: 0x00 */ +#define REG_HDCP2X_INTR0_MASK 0x0399 + +/* HDCP General Control 0, default value: 0x02 */ +#define REG_HDCP2X_CTRL_0 0x03a0 +#define BIT_HDCP2X_CTRL_0_HDCP2X_ENCRYPT_EN BIT(7) +#define BIT_HDCP2X_CTRL_0_HDCP2X_POLINT_SEL BIT(6) +#define BIT_HDCP2X_CTRL_0_HDCP2X_POLINT_OVR BIT(5) +#define BIT_HDCP2X_CTRL_0_HDCP2X_PRECOMPUTE BIT(4) +#define BIT_HDCP2X_CTRL_0_HDCP2X_HDMIMODE BIT(3) +#define BIT_HDCP2X_CTRL_0_HDCP2X_REPEATER BIT(2) +#define BIT_HDCP2X_CTRL_0_HDCP2X_HDCPTX BIT(1) +#define BIT_HDCP2X_CTRL_0_HDCP2X_EN BIT(0) + +/* HDCP General Control 1, default value: 0x08 */ +#define REG_HDCP2X_CTRL_1 0x03a1 +#define MSK_HDCP2X_CTRL_1_HDCP2X_REAUTH_MSK_3_0 0xf0 +#define BIT_HDCP2X_CTRL_1_HDCP2X_HPD_SW BIT(3) +#define BIT_HDCP2X_CTRL_1_HDCP2X_HPD_OVR BIT(2) +#define BIT_HDCP2X_CTRL_1_HDCP2X_CTL3MSK BIT(1) +#define BIT_HDCP2X_CTRL_1_HDCP2X_REAUTH_SW BIT(0) + +/* HDCP Misc Control, default value: 0x00 */ +#define REG_HDCP2X_MISC_CTRL 0x03a5 +#define BIT_HDCP2X_MISC_CTRL_HDCP2X_RPT_SMNG_XFER_START BIT(4) +#define BIT_HDCP2X_MISC_CTRL_HDCP2X_RPT_SMNG_WR_START BIT(3) +#define BIT_HDCP2X_MISC_CTRL_HDCP2X_RPT_SMNG_WR BIT(2) +#define BIT_HDCP2X_MISC_CTRL_HDCP2X_RPT_RCVID_RD_START BIT(1) +#define BIT_HDCP2X_MISC_CTRL_HDCP2X_RPT_RCVID_RD BIT(0) + +/* HDCP RPT SMNG K, default value: 0x00 */ +#define REG_HDCP2X_RPT_SMNG_K 0x03a6 + +/* HDCP RPT SMNG In, default value: 0x00 */ +#define REG_HDCP2X_RPT_SMNG_IN 0x03a7 + +/* HDCP Auth Status, default value: 0x00 */ +#define REG_HDCP2X_AUTH_STAT 0x03aa + +/* HDCP RPT RCVID Out, default value: 0x00 */ +#define REG_HDCP2X_RPT_RCVID_OUT 0x03ac + +/* HDCP TP1, default value: 0x62 */ +#define REG_HDCP2X_TP1 0x03b4 + +/* HDCP GP Out 0, default value: 0x00 */ +#define REG_HDCP2X_GP_OUT0 0x03c7 + +/* HDCP Repeater RCVR ID 0, default value: 0x00 */ +#define REG_HDCP2X_RPT_RCVR_ID0 0x03d1 + +/* HDCP DDCM Status, default value: 0x00 */ +#define REG_HDCP2X_DDCM_STS 0x03d8 +#define MSK_HDCP2X_DDCM_STS_HDCP2X_DDCM_ERR_STS_3_0 0xf0 +#define MSK_HDCP2X_DDCM_STS_HDCP2X_DDCM_CTL_CS_3_0 0x0f + +/* HDMI2MHL3 Control, default value: 0x0a */ +#define REG_M3_CTRL 0x03e0 +#define BIT_M3_CTRL_H2M_SWRST BIT(4) +#define BIT_M3_CTRL_SW_MHL3_SEL BIT(3) +#define BIT_M3_CTRL_M3AV_EN BIT(2) +#define BIT_M3_CTRL_ENC_TMDS BIT(1) +#define BIT_M3_CTRL_MHL3_MASTER_EN BIT(0) + +#define VAL_M3_CTRL_MHL1_2_VALUE (BIT_M3_CTRL_SW_MHL3_SEL \ + | BIT_M3_CTRL_ENC_TMDS) +#define VAL_M3_CTRL_MHL3_VALUE (BIT_M3_CTRL_SW_MHL3_SEL \ + | BIT_M3_CTRL_M3AV_EN \ + | BIT_M3_CTRL_ENC_TMDS \ + | BIT_M3_CTRL_MHL3_MASTER_EN) + +/* HDMI2MHL3 Port0 Control, default value: 0x04 */ +#define REG_M3_P0CTRL 0x03e1 +#define BIT_M3_P0CTRL_MHL3_P0_HDCP_ENC_EN BIT(4) +#define BIT_M3_P0CTRL_MHL3_P0_UNLIMIT_EN BIT(3) +#define BIT_M3_P0CTRL_MHL3_P0_HDCP_EN BIT(2) +#define BIT_M3_P0CTRL_MHL3_P0_PIXEL_MODE_PACKED BIT(1) +#define BIT_M3_P0CTRL_MHL3_P0_PORT_EN BIT(0) + +#define REG_M3_POSTM 0x03e2 +#define MSK_M3_POSTM_RRP_DECODE 0xf8 +#define MSK_M3_POSTM_MHL3_P0_STM_ID 0x07 + +/* HDMI2MHL3 Scramble Control, default value: 0x41 */ +#define REG_M3_SCTRL 0x03e6 +#define MSK_M3_SCTRL_MHL3_SR_LENGTH 0xf0 +#define BIT_M3_SCTRL_MHL3_SCRAMBLER_EN BIT(0) + +/* HSIC Div Ctl, default value: 0x05 */ +#define REG_DIV_CTL_MAIN 0x03f2 +#define MSK_DIV_CTL_MAIN_PRE_DIV_CTL_MAIN 0x1c +#define MSK_DIV_CTL_MAIN_FB_DIV_CTL_MAIN 0x03 + +/* MHL Capability 1st Byte, default value: 0x00 */ +#define REG_MHL_DEVCAP_0 0x0400 + +/* MHL Interrupt 1st Byte, default value: 0x00 */ +#define REG_MHL_INT_0 0x0420 + +/* Device Status 1st byte, default value: 0x00 */ +#define REG_MHL_STAT_0 0x0430 + +/* CBUS Scratch Pad 1st Byte, default value: 0x00 */ +#define REG_MHL_SCRPAD_0 0x0440 + +/* MHL Extended Capability 1st Byte, default value: 0x00 */ +#define REG_MHL_EXTDEVCAP_0 0x0480 + +/* Device Extended Status 1st byte, default value: 0x00 */ +#define REG_MHL_EXTSTAT_0 0x0490 + +/* TPI DTD Byte2, default value: 0x00 */ +#define REG_TPI_DTD_B2 0x0602 + +#define VAL_TPI_QUAN_RANGE_LIMITED 0x01 +#define VAL_TPI_QUAN_RANGE_FULL 0x02 +#define VAL_TPI_FORMAT_RGB 0x00 +#define VAL_TPI_FORMAT_YCBCR444 0x01 +#define VAL_TPI_FORMAT_YCBCR422 0x02 +#define VAL_TPI_FORMAT_INTERNAL_RGB 0x03 +#define VAL_TPI_FORMAT(_fmt, _qr) \ + (VAL_TPI_FORMAT_##_fmt | (VAL_TPI_QUAN_RANGE_##_qr << 2)) + +/* Input Format, default value: 0x00 */ +#define REG_TPI_INPUT 0x0609 +#define BIT_TPI_INPUT_EXTENDEDBITMODE BIT(7) +#define BIT_TPI_INPUT_ENDITHER BIT(6) +#define MSK_TPI_INPUT_INPUT_QUAN_RANGE 0x0c +#define MSK_TPI_INPUT_INPUT_FORMAT 0x03 + +/* Output Format, default value: 0x00 */ +#define REG_TPI_OUTPUT 0x060a +#define BIT_TPI_OUTPUT_CSCMODE709 BIT(4) +#define MSK_TPI_OUTPUT_OUTPUT_QUAN_RANGE 0x0c +#define MSK_TPI_OUTPUT_OUTPUT_FORMAT 0x03 + +/* TPI AVI Check Sum, default value: 0x00 */ +#define REG_TPI_AVI_CHSUM 0x060c + +/* TPI System Control, default value: 0x00 */ +#define REG_TPI_SC 0x061a +#define BIT_TPI_SC_TPI_UPDATE_FLG BIT(7) +#define BIT_TPI_SC_TPI_REAUTH_CTL BIT(6) +#define BIT_TPI_SC_TPI_OUTPUT_MODE_1 BIT(5) +#define BIT_TPI_SC_REG_TMDS_OE_POWER_DOWN BIT(4) +#define BIT_TPI_SC_TPI_AV_MUTE BIT(3) +#define BIT_TPI_SC_DDC_GPU_REQUEST BIT(2) +#define BIT_TPI_SC_DDC_TPI_SW BIT(1) +#define BIT_TPI_SC_TPI_OUTPUT_MODE_0_HDMI BIT(0) + +/* TPI COPP Query Data, default value: 0x00 */ +#define REG_TPI_COPP_DATA1 0x0629 +#define BIT_TPI_COPP_DATA1_COPP_GPROT BIT(7) +#define BIT_TPI_COPP_DATA1_COPP_LPROT BIT(6) +#define MSK_TPI_COPP_DATA1_COPP_LINK_STATUS 0x30 +#define VAL_TPI_COPP_LINK_STATUS_NORMAL 0x00 +#define VAL_TPI_COPP_LINK_STATUS_LINK_LOST 0x10 +#define VAL_TPI_COPP_LINK_STATUS_RENEGOTIATION_REQ 0x20 +#define VAL_TPI_COPP_LINK_STATUS_LINK_SUSPENDED 0x30 +#define BIT_TPI_COPP_DATA1_COPP_HDCP_REP BIT(3) +#define BIT_TPI_COPP_DATA1_COPP_CONNTYPE_0 BIT(2) +#define BIT_TPI_COPP_DATA1_COPP_PROTYPE BIT(1) +#define BIT_TPI_COPP_DATA1_COPP_CONNTYPE_1 BIT(0) + +/* TPI COPP Control Data, default value: 0x00 */ +#define REG_TPI_COPP_DATA2 0x062a +#define BIT_TPI_COPP_DATA2_INTR_ENCRYPTION BIT(5) +#define BIT_TPI_COPP_DATA2_KSV_FORWARD BIT(4) +#define BIT_TPI_COPP_DATA2_INTERM_RI_CHECK_EN BIT(3) +#define BIT_TPI_COPP_DATA2_DOUBLE_RI_CHECK BIT(2) +#define BIT_TPI_COPP_DATA2_DDC_SHORT_RI_RD BIT(1) +#define BIT_TPI_COPP_DATA2_COPP_PROTLEVEL BIT(0) + +/* TPI Interrupt Enable, default value: 0x00 */ +#define REG_TPI_INTR_EN 0x063c + +/* TPI Interrupt Status Low Byte, default value: 0x00 */ +#define REG_TPI_INTR_ST0 0x063d +#define BIT_TPI_INTR_ST0_TPI_AUTH_CHNGE_STAT BIT(7) +#define BIT_TPI_INTR_ST0_TPI_V_RDY_STAT BIT(6) +#define BIT_TPI_INTR_ST0_TPI_COPP_CHNGE_STAT BIT(5) +#define BIT_TPI_INTR_ST0_KSV_FIFO_FIRST_STAT BIT(3) +#define BIT_TPI_INTR_ST0_READ_BKSV_BCAPS_DONE_STAT BIT(2) +#define BIT_TPI_INTR_ST0_READ_BKSV_BCAPS_ERR_STAT BIT(1) +#define BIT_TPI_INTR_ST0_READ_BKSV_ERR_STAT BIT(0) + +/* TPI DS BCAPS Status, default value: 0x00 */ +#define REG_TPI_DS_BCAPS 0x0644 + +/* TPI BStatus1, default value: 0x00 */ +#define REG_TPI_BSTATUS1 0x0645 +#define BIT_TPI_BSTATUS1_DS_DEV_EXCEED BIT(7) +#define MSK_TPI_BSTATUS1_DS_DEV_CNT 0x7f + +/* TPI BStatus2, default value: 0x10 */ +#define REG_TPI_BSTATUS2 0x0646 +#define MSK_TPI_BSTATUS2_DS_BSTATUS 0xe0 +#define BIT_TPI_BSTATUS2_DS_HDMI_MODE BIT(4) +#define BIT_TPI_BSTATUS2_DS_CASC_EXCEED BIT(3) +#define MSK_TPI_BSTATUS2_DS_DEPTH 0x07 + +/* TPI HW Optimization Control #3, default value: 0x00 */ +#define REG_TPI_HW_OPT3 0x06bb +#define BIT_TPI_HW_OPT3_DDC_DEBUG BIT(7) +#define BIT_TPI_HW_OPT3_RI_CHECK_SKIP BIT(3) +#define BIT_TPI_HW_OPT3_TPI_DDC_BURST_MODE BIT(2) +#define MSK_TPI_HW_OPT3_TPI_DDC_REQ_LEVEL 0x03 + +/* TPI Info Frame Select, default value: 0x00 */ +#define REG_TPI_INFO_FSEL 0x06bf +#define BIT_TPI_INFO_FSEL_EN BIT(7) +#define BIT_TPI_INFO_FSEL_RPT BIT(6) +#define BIT_TPI_INFO_FSEL_READ_FLAG BIT(5) +#define MSK_TPI_INFO_FSEL_PKT 0x07 +#define VAL_TPI_INFO_FSEL_AVI 0x00 +#define VAL_TPI_INFO_FSEL_SPD 0x01 +#define VAL_TPI_INFO_FSEL_AUD 0x02 +#define VAL_TPI_INFO_FSEL_MPG 0x03 +#define VAL_TPI_INFO_FSEL_GEN 0x04 +#define VAL_TPI_INFO_FSEL_GEN2 0x05 +#define VAL_TPI_INFO_FSEL_VSI 0x06 + +/* TPI Info Byte #0, default value: 0x00 */ +#define REG_TPI_INFO_B0 0x06c0 + +/* CoC Status, default value: 0x00 */ +#define REG_COC_STAT_0 0x0700 +#define BIT_COC_STAT_0_PLL_LOCKED BIT(7) +#define MSK_COC_STAT_0_FSM_STATE 0x0f + +#define REG_COC_STAT_1 0x0701 +#define REG_COC_STAT_2 0x0702 +#define REG_COC_STAT_3 0x0703 +#define REG_COC_STAT_4 0x0704 +#define REG_COC_STAT_5 0x0705 + +/* CoC 1st Ctl, default value: 0x40 */ +#define REG_COC_CTL0 0x0710 + +/* CoC 2nd Ctl, default value: 0x0a */ +#define REG_COC_CTL1 0x0711 +#define MSK_COC_CTL1_COC_CTRL1_7_6 0xc0 +#define MSK_COC_CTL1_COC_CTRL1_5_0 0x3f + +/* CoC 3rd Ctl, default value: 0x14 */ +#define REG_COC_CTL2 0x0712 +#define MSK_COC_CTL2_COC_CTRL2_7_6 0xc0 +#define MSK_COC_CTL2_COC_CTRL2_5_0 0x3f + +/* CoC 4th Ctl, default value: 0x40 */ +#define REG_COC_CTL3 0x0713 +#define BIT_COC_CTL3_COC_CTRL3_7 BIT(7) +#define MSK_COC_CTL3_COC_CTRL3_6_0 0x7f + +/* CoC 7th Ctl, default value: 0x00 */ +#define REG_COC_CTL6 0x0716 +#define BIT_COC_CTL6_COC_CTRL6_7 BIT(7) +#define BIT_COC_CTL6_COC_CTRL6_6 BIT(6) +#define MSK_COC_CTL6_COC_CTRL6_5_0 0x3f + +/* CoC 8th Ctl, default value: 0x06 */ +#define REG_COC_CTL7 0x0717 +#define BIT_COC_CTL7_COC_CTRL7_7 BIT(7) +#define BIT_COC_CTL7_COC_CTRL7_6 BIT(6) +#define BIT_COC_CTL7_COC_CTRL7_5 BIT(5) +#define MSK_COC_CTL7_COC_CTRL7_4_3 0x18 +#define MSK_COC_CTL7_COC_CTRL7_2_0 0x07 + +/* CoC 10th Ctl, default value: 0x00 */ +#define REG_COC_CTL9 0x0719 + +/* CoC 11th Ctl, default value: 0x00 */ +#define REG_COC_CTLA 0x071a + +/* CoC 12th Ctl, default value: 0x00 */ +#define REG_COC_CTLB 0x071b + +/* CoC 13th Ctl, default value: 0x0f */ +#define REG_COC_CTLC 0x071c + +/* CoC 14th Ctl, default value: 0x0a */ +#define REG_COC_CTLD 0x071d +#define BIT_COC_CTLD_COC_CTRLD_7 BIT(7) +#define MSK_COC_CTLD_COC_CTRLD_6_0 0x7f + +/* CoC 15th Ctl, default value: 0x0a */ +#define REG_COC_CTLE 0x071e +#define BIT_COC_CTLE_COC_CTRLE_7 BIT(7) +#define MSK_COC_CTLE_COC_CTRLE_6_0 0x7f + +/* CoC 16th Ctl, default value: 0x00 */ +#define REG_COC_CTLF 0x071f +#define MSK_COC_CTLF_COC_CTRLF_7_3 0xf8 +#define MSK_COC_CTLF_COC_CTRLF_2_0 0x07 + +/* CoC 18th Ctl, default value: 0x32 */ +#define REG_COC_CTL11 0x0721 +#define MSK_COC_CTL11_COC_CTRL11_7_4 0xf0 +#define MSK_COC_CTL11_COC_CTRL11_3_0 0x0f + +/* CoC 21st Ctl, default value: 0x00 */ +#define REG_COC_CTL14 0x0724 +#define MSK_COC_CTL14_COC_CTRL14_7_4 0xf0 +#define MSK_COC_CTL14_COC_CTRL14_3_0 0x0f + +/* CoC 22nd Ctl, default value: 0x00 */ +#define REG_COC_CTL15 0x0725 +#define BIT_COC_CTL15_COC_CTRL15_7 BIT(7) +#define MSK_COC_CTL15_COC_CTRL15_6_4 0x70 +#define MSK_COC_CTL15_COC_CTRL15_3_0 0x0f + +/* CoC Interrupt, default value: 0x00 */ +#define REG_COC_INTR 0x0726 + +/* CoC Interrupt Mask, default value: 0x00 */ +#define REG_COC_INTR_MASK 0x0727 +#define BIT_COC_PLL_LOCK_STATUS_CHANGE BIT(0) +#define BIT_COC_CALIBRATION_DONE BIT(1) + +/* CoC Misc Ctl, default value: 0x00 */ +#define REG_COC_MISC_CTL0 0x0728 +#define BIT_COC_MISC_CTL0_FSM_MON BIT(7) + +/* CoC 24th Ctl, default value: 0x00 */ +#define REG_COC_CTL17 0x072a +#define MSK_COC_CTL17_COC_CTRL17_7_4 0xf0 +#define MSK_COC_CTL17_COC_CTRL17_3_0 0x0f + +/* CoC 25th Ctl, default value: 0x00 */ +#define REG_COC_CTL18 0x072b +#define MSK_COC_CTL18_COC_CTRL18_7_4 0xf0 +#define MSK_COC_CTL18_COC_CTRL18_3_0 0x0f + +/* CoC 26th Ctl, default value: 0x00 */ +#define REG_COC_CTL19 0x072c +#define MSK_COC_CTL19_COC_CTRL19_7_4 0xf0 +#define MSK_COC_CTL19_COC_CTRL19_3_0 0x0f + +/* CoC 27th Ctl, default value: 0x00 */ +#define REG_COC_CTL1A 0x072d +#define MSK_COC_CTL1A_COC_CTRL1A_7_2 0xfc +#define MSK_COC_CTL1A_COC_CTRL1A_1_0 0x03 + +/* DoC 9th Status, default value: 0x00 */ +#define REG_DOC_STAT_8 0x0740 + +/* DoC 10th Status, default value: 0x00 */ +#define REG_DOC_STAT_9 0x0741 + +/* DoC 5th CFG, default value: 0x00 */ +#define REG_DOC_CFG4 0x074e +#define MSK_DOC_CFG4_DBG_STATE_DOC_FSM 0x0f + +/* DoC 1st Ctl, default value: 0x40 */ +#define REG_DOC_CTL0 0x0751 + +/* DoC 7th Ctl, default value: 0x00 */ +#define REG_DOC_CTL6 0x0757 +#define BIT_DOC_CTL6_DOC_CTRL6_7 BIT(7) +#define BIT_DOC_CTL6_DOC_CTRL6_6 BIT(6) +#define MSK_DOC_CTL6_DOC_CTRL6_5_4 0x30 +#define MSK_DOC_CTL6_DOC_CTRL6_3_0 0x0f + +/* DoC 8th Ctl, default value: 0x00 */ +#define REG_DOC_CTL7 0x0758 +#define BIT_DOC_CTL7_DOC_CTRL7_7 BIT(7) +#define BIT_DOC_CTL7_DOC_CTRL7_6 BIT(6) +#define BIT_DOC_CTL7_DOC_CTRL7_5 BIT(5) +#define MSK_DOC_CTL7_DOC_CTRL7_4_3 0x18 +#define MSK_DOC_CTL7_DOC_CTRL7_2_0 0x07 + +/* DoC 9th Ctl, default value: 0x00 */ +#define REG_DOC_CTL8 0x076c +#define BIT_DOC_CTL8_DOC_CTRL8_7 BIT(7) +#define MSK_DOC_CTL8_DOC_CTRL8_6_4 0x70 +#define MSK_DOC_CTL8_DOC_CTRL8_3_2 0x0c +#define MSK_DOC_CTL8_DOC_CTRL8_1_0 0x03 + +/* DoC 10th Ctl, default value: 0x00 */ +#define REG_DOC_CTL9 0x076d + +/* DoC 11th Ctl, default value: 0x00 */ +#define REG_DOC_CTLA 0x076e + +/* DoC 15th Ctl, default value: 0x00 */ +#define REG_DOC_CTLE 0x0772 +#define BIT_DOC_CTLE_DOC_CTRLE_7 BIT(7) +#define BIT_DOC_CTLE_DOC_CTRLE_6 BIT(6) +#define MSK_DOC_CTLE_DOC_CTRLE_5_4 0x30 +#define MSK_DOC_CTLE_DOC_CTRLE_3_0 0x0f + +/* Interrupt Mask 1st, default value: 0x00 */ +#define REG_MHL_INT_0_MASK 0x0580 + +/* Interrupt Mask 2nd, default value: 0x00 */ +#define REG_MHL_INT_1_MASK 0x0581 + +/* Interrupt Mask 3rd, default value: 0x00 */ +#define REG_MHL_INT_2_MASK 0x0582 + +/* Interrupt Mask 4th, default value: 0x00 */ +#define REG_MHL_INT_3_MASK 0x0583 + +/* MDT Receive Time Out, default value: 0x00 */ +#define REG_MDT_RCV_TIMEOUT 0x0584 + +/* MDT Transmit Time Out, default value: 0x00 */ +#define REG_MDT_XMIT_TIMEOUT 0x0585 + +/* MDT Receive Control, default value: 0x00 */ +#define REG_MDT_RCV_CTRL 0x0586 +#define BIT_MDT_RCV_CTRL_MDT_RCV_EN BIT(7) +#define BIT_MDT_RCV_CTRL_MDT_DELAY_RCV_EN BIT(6) +#define BIT_MDT_RCV_CTRL_MDT_RFIFO_OVER_WR_EN BIT(4) +#define BIT_MDT_RCV_CTRL_MDT_XFIFO_OVER_WR_EN BIT(3) +#define BIT_MDT_RCV_CTRL_MDT_DISABLE BIT(2) +#define BIT_MDT_RCV_CTRL_MDT_RFIFO_CLR_ALL BIT(1) +#define BIT_MDT_RCV_CTRL_MDT_RFIFO_CLR_CUR BIT(0) + +/* MDT Receive Read Port, default value: 0x00 */ +#define REG_MDT_RCV_READ_PORT 0x0587 + +/* MDT Transmit Control, default value: 0x70 */ +#define REG_MDT_XMIT_CTRL 0x0588 +#define BIT_MDT_XMIT_CTRL_EN BIT(7) +#define BIT_MDT_XMIT_CTRL_CMD_MERGE_EN BIT(6) +#define BIT_MDT_XMIT_CTRL_FIXED_BURST_LEN BIT(5) +#define BIT_MDT_XMIT_CTRL_FIXED_AID BIT(4) +#define BIT_MDT_XMIT_CTRL_SINGLE_RUN_EN BIT(3) +#define BIT_MDT_XMIT_CTRL_CLR_ABORT_WAIT BIT(2) +#define BIT_MDT_XMIT_CTRL_XFIFO_CLR_ALL BIT(1) +#define BIT_MDT_XMIT_CTRL_XFIFO_CLR_CUR BIT(0) + +/* MDT Receive WRITE Port, default value: 0x00 */ +#define REG_MDT_XMIT_WRITE_PORT 0x0589 + +/* MDT RFIFO Status, default value: 0x00 */ +#define REG_MDT_RFIFO_STAT 0x058a +#define MSK_MDT_RFIFO_STAT_MDT_RFIFO_CNT 0xe0 +#define MSK_MDT_RFIFO_STAT_MDT_RFIFO_CUR_BYTE_CNT 0x1f + +/* MDT XFIFO Status, default value: 0x80 */ +#define REG_MDT_XFIFO_STAT 0x058b +#define MSK_MDT_XFIFO_STAT_MDT_XFIFO_LEVEL_AVAIL 0xe0 +#define BIT_MDT_XFIFO_STAT_MDT_XMIT_PRE_HS_EN BIT(4) +#define MSK_MDT_XFIFO_STAT_MDT_WRITE_BURST_LEN 0x0f + +/* MDT Interrupt 0, default value: 0x0c */ +#define REG_MDT_INT_0 0x058c +#define BIT_MDT_RFIFO_DATA_RDY BIT(0) +#define BIT_MDT_IDLE_AFTER_HAWB_DISABLE BIT(2) +#define BIT_MDT_XFIFO_EMPTY BIT(3) + +/* MDT Interrupt 0 Mask, default value: 0x00 */ +#define REG_MDT_INT_0_MASK 0x058d + +/* MDT Interrupt 1, default value: 0x00 */ +#define REG_MDT_INT_1 0x058e +#define BIT_MDT_RCV_TIMEOUT BIT(0) +#define BIT_MDT_RCV_SM_ABORT_PKT_RCVD BIT(1) +#define BIT_MDT_RCV_SM_ERROR BIT(2) +#define BIT_MDT_XMIT_TIMEOUT BIT(5) +#define BIT_MDT_XMIT_SM_ABORT_PKT_RCVD BIT(6) +#define BIT_MDT_XMIT_SM_ERROR BIT(7) + +/* MDT Interrupt 1 Mask, default value: 0x00 */ +#define REG_MDT_INT_1_MASK 0x058f + +/* CBUS Vendor ID, default value: 0x01 */ +#define REG_CBUS_VENDOR_ID 0x0590 + +/* CBUS Connection Status, default value: 0x00 */ +#define REG_CBUS_STATUS 0x0591 +#define BIT_CBUS_STATUS_MHL_CABLE_PRESENT BIT(4) +#define BIT_CBUS_STATUS_MSC_HB_SUCCESS BIT(3) +#define BIT_CBUS_STATUS_CBUS_HPD BIT(2) +#define BIT_CBUS_STATUS_MHL_MODE BIT(1) +#define BIT_CBUS_STATUS_CBUS_CONNECTED BIT(0) + +/* CBUS Interrupt 1st, default value: 0x00 */ +#define REG_CBUS_INT_0 0x0592 +#define BIT_CBUS_MSC_MT_DONE_NACK BIT(7) +#define BIT_CBUS_MSC_MR_SET_INT BIT(6) +#define BIT_CBUS_MSC_MR_WRITE_BURST BIT(5) +#define BIT_CBUS_MSC_MR_MSC_MSG BIT(4) +#define BIT_CBUS_MSC_MR_WRITE_STAT BIT(3) +#define BIT_CBUS_HPD_CHG BIT(2) +#define BIT_CBUS_MSC_MT_DONE BIT(1) +#define BIT_CBUS_CNX_CHG BIT(0) + +/* CBUS Interrupt Mask 1st, default value: 0x00 */ +#define REG_CBUS_INT_0_MASK 0x0593 + +/* CBUS Interrupt 2nd, default value: 0x00 */ +#define REG_CBUS_INT_1 0x0594 +#define BIT_CBUS_CMD_ABORT BIT(6) +#define BIT_CBUS_MSC_ABORT_RCVD BIT(3) +#define BIT_CBUS_DDC_ABORT BIT(2) +#define BIT_CBUS_CEC_ABORT BIT(1) + +/* CBUS Interrupt Mask 2nd, default value: 0x00 */ +#define REG_CBUS_INT_1_MASK 0x0595 + +/* CBUS DDC Abort Interrupt, default value: 0x00 */ +#define REG_DDC_ABORT_INT 0x0598 + +/* CBUS DDC Abort Interrupt Mask, default value: 0x00 */ +#define REG_DDC_ABORT_INT_MASK 0x0599 + +/* CBUS MSC Requester Abort Interrupt, default value: 0x00 */ +#define REG_MSC_MT_ABORT_INT 0x059a + +/* CBUS MSC Requester Abort Interrupt Mask, default value: 0x00 */ +#define REG_MSC_MT_ABORT_INT_MASK 0x059b + +/* CBUS MSC Responder Abort Interrupt, default value: 0x00 */ +#define REG_MSC_MR_ABORT_INT 0x059c + +/* CBUS MSC Responder Abort Interrupt Mask, default value: 0x00 */ +#define REG_MSC_MR_ABORT_INT_MASK 0x059d + +/* CBUS RX DISCOVERY interrupt, default value: 0x00 */ +#define REG_CBUS_RX_DISC_INT0 0x059e + +/* CBUS RX DISCOVERY Interrupt Mask, default value: 0x00 */ +#define REG_CBUS_RX_DISC_INT0_MASK 0x059f + +/* CBUS_Link_Layer Control #8, default value: 0x00 */ +#define REG_CBUS_LINK_CTRL_8 0x05a7 + +/* MDT State Machine Status, default value: 0x00 */ +#define REG_MDT_SM_STAT 0x05b5 +#define MSK_MDT_SM_STAT_MDT_RCV_STATE 0xf0 +#define MSK_MDT_SM_STAT_MDT_XMIT_STATE 0x0f + +/* CBUS MSC command trigger, default value: 0x00 */ +#define REG_MSC_COMMAND_START 0x05b8 +#define BIT_MSC_COMMAND_START_DEBUG BIT(5) +#define BIT_MSC_COMMAND_START_WRITE_BURST BIT(4) +#define BIT_MSC_COMMAND_START_WRITE_STAT BIT(3) +#define BIT_MSC_COMMAND_START_READ_DEVCAP BIT(2) +#define BIT_MSC_COMMAND_START_MSC_MSG BIT(1) +#define BIT_MSC_COMMAND_START_PEER BIT(0) + +/* CBUS MSC Command/Offset, default value: 0x00 */ +#define REG_MSC_CMD_OR_OFFSET 0x05b9 + +/* CBUS MSC Transmit Data */ +#define REG_MSC_1ST_TRANSMIT_DATA 0x05ba +#define REG_MSC_2ND_TRANSMIT_DATA 0x05bb + +/* CBUS MSC Requester Received Data */ +#define REG_MSC_MT_RCVD_DATA0 0x05bc +#define REG_MSC_MT_RCVD_DATA1 0x05bd + +/* CBUS MSC Responder MSC_MSG Received Data */ +#define REG_MSC_MR_MSC_MSG_RCVD_1ST_DATA 0x05bf +#define REG_MSC_MR_MSC_MSG_RCVD_2ND_DATA 0x05c0 + +/* CBUS MSC Heartbeat Control, default value: 0x27 */ +#define REG_MSC_HEARTBEAT_CTRL 0x05c4 +#define BIT_MSC_HEARTBEAT_CTRL_MSC_HB_EN BIT(7) +#define MSK_MSC_HEARTBEAT_CTRL_MSC_HB_FAIL_LIMIT 0x70 +#define MSK_MSC_HEARTBEAT_CTRL_MSC_HB_PERIOD_MSB 0x0f + +/* CBUS MSC Compatibility Control, default value: 0x02 */ +#define REG_CBUS_MSC_COMPAT_CTRL 0x05c7 +#define BIT_CBUS_MSC_COMPAT_CTRL_XDEVCAP_EN BIT(7) +#define BIT_CBUS_MSC_COMPAT_CTRL_DISABLE_MSC_ON_CBUS BIT(6) +#define BIT_CBUS_MSC_COMPAT_CTRL_DISABLE_DDC_ON_CBUS BIT(5) +#define BIT_CBUS_MSC_COMPAT_CTRL_DISABLE_GET_DDC_ERRORCODE BIT(3) +#define BIT_CBUS_MSC_COMPAT_CTRL_DISABLE_GET_VS1_ERRORCODE BIT(2) + +/* CBUS3 Converter Control, default value: 0x24 */ +#define REG_CBUS3_CNVT 0x05dc +#define MSK_CBUS3_CNVT_CBUS3_RETRYLMT 0xf0 +#define MSK_CBUS3_CNVT_CBUS3_PEERTOUT_SEL 0x0c +#define BIT_CBUS3_CNVT_TEARCBUS_EN BIT(1) +#define BIT_CBUS3_CNVT_CBUS3CNVT_EN BIT(0) + +/* Discovery Control1, default value: 0x24 */ +#define REG_DISC_CTRL1 0x05e0 +#define BIT_DISC_CTRL1_CBUS_INTR_EN BIT(7) +#define BIT_DISC_CTRL1_HB_ONLY BIT(6) +#define MSK_DISC_CTRL1_DISC_ATT 0x30 +#define MSK_DISC_CTRL1_DISC_CYC 0x0c +#define BIT_DISC_CTRL1_DISC_EN BIT(0) + +#define VAL_PUP_OFF 0 +#define VAL_PUP_20K 1 +#define VAL_PUP_5K 2 + +/* Discovery Control4, default value: 0x80 */ +#define REG_DISC_CTRL4 0x05e3 +#define MSK_DISC_CTRL4_CBUSDISC_PUP_SEL 0xc0 +#define MSK_DISC_CTRL4_CBUSIDLE_PUP_SEL 0x30 +#define VAL_DISC_CTRL4(pup_disc, pup_idle) (((pup_disc) << 6) | (pup_idle << 4)) + +/* Discovery Control5, default value: 0x03 */ +#define REG_DISC_CTRL5 0x05e4 +#define BIT_DISC_CTRL5_DSM_OVRIDE BIT(3) +#define MSK_DISC_CTRL5_CBUSMHL_PUP_SEL 0x03 + +/* Discovery Control8, default value: 0x81 */ +#define REG_DISC_CTRL8 0x05e7 +#define BIT_DISC_CTRL8_NOMHLINT_CLR_BYPASS BIT(7) +#define BIT_DISC_CTRL8_DELAY_CBUS_INTR_EN BIT(0) + +/* Discovery Control9, default value: 0x54 */ +#define REG_DISC_CTRL9 0x05e8 +#define BIT_DISC_CTRL9_MHL3_RSEN_BYP BIT(7) +#define BIT_DISC_CTRL9_MHL3DISC_EN BIT(6) +#define BIT_DISC_CTRL9_WAKE_DRVFLT BIT(4) +#define BIT_DISC_CTRL9_NOMHL_EST BIT(3) +#define BIT_DISC_CTRL9_DISC_PULSE_PROCEED BIT(2) +#define BIT_DISC_CTRL9_WAKE_PULSE_BYPASS BIT(1) +#define BIT_DISC_CTRL9_VBUS_OUTPUT_CAPABILITY_SRC BIT(0) + +/* Discovery Status1, default value: 0x00 */ +#define REG_DISC_STAT1 0x05eb +#define BIT_DISC_STAT1_PSM_OVRIDE BIT(5) +#define MSK_DISC_STAT1_DISC_SM 0x0f + +/* Discovery Status2, default value: 0x00 */ +#define REG_DISC_STAT2 0x05ec +#define BIT_DISC_STAT2_CBUS_OE_POL BIT(6) +#define BIT_DISC_STAT2_CBUS_SATUS BIT(5) +#define BIT_DISC_STAT2_RSEN BIT(4) + +#define MSK_DISC_STAT2_MHL_VRSN 0x0c +#define VAL_DISC_STAT2_DEFAULT 0x00 +#define VAL_DISC_STAT2_MHL1_2 0x04 +#define VAL_DISC_STAT2_MHL3 0x08 +#define VAL_DISC_STAT2_RESERVED 0x0c + +#define MSK_DISC_STAT2_RGND 0x03 +#define VAL_RGND_OPEN 0x00 +#define VAL_RGND_2K 0x01 +#define VAL_RGND_1K 0x02 +#define VAL_RGND_SHORT 0x03 + +/* Interrupt CBUS_reg1 INTR0, default value: 0x00 */ +#define REG_CBUS_DISC_INTR0 0x05ed +#define BIT_RGND_READY_INT BIT(6) +#define BIT_CBUS_MHL12_DISCON_INT BIT(5) +#define BIT_CBUS_MHL3_DISCON_INT BIT(4) +#define BIT_NOT_MHL_EST_INT BIT(3) +#define BIT_MHL_EST_INT BIT(2) +#define BIT_MHL3_EST_INT BIT(1) +#define VAL_CBUS_MHL_DISCON (BIT_CBUS_MHL12_DISCON_INT \ + | BIT_CBUS_MHL3_DISCON_INT \ + | BIT_NOT_MHL_EST_INT) + +/* Interrupt CBUS_reg1 INTR0 Mask, default value: 0x00 */ +#define REG_CBUS_DISC_INTR0_MASK 0x05ee + +#endif /* __SIL_SII8620_H__ */ diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c new file mode 100644 index 000000000..d974282c1 --- /dev/null +++ b/drivers/gpu/drm/bridge/simple-bridge.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2015-2016 Free Electrons + * Copyright (C) 2015-2016 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + */ + +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +struct simple_bridge_info { + const struct drm_bridge_timings *timings; + unsigned int connector_type; +}; + +struct simple_bridge { + struct drm_bridge bridge; + struct drm_connector connector; + + const struct simple_bridge_info *info; + + struct drm_bridge *next_bridge; + struct regulator *vdd; + struct gpio_desc *enable; +}; + +static inline struct simple_bridge * +drm_bridge_to_simple_bridge(struct drm_bridge *bridge) +{ + return container_of(bridge, struct simple_bridge, bridge); +} + +static inline struct simple_bridge * +drm_connector_to_simple_bridge(struct drm_connector *connector) +{ + return container_of(connector, struct simple_bridge, connector); +} + +static int simple_bridge_get_modes(struct drm_connector *connector) +{ + struct simple_bridge *sbridge = drm_connector_to_simple_bridge(connector); + struct edid *edid; + int ret; + + if (sbridge->next_bridge->ops & DRM_BRIDGE_OP_EDID) { + edid = drm_bridge_get_edid(sbridge->next_bridge, connector); + if (!edid) + DRM_INFO("EDID read failed. Fallback to standard modes\n"); + } else { + edid = NULL; + } + + if (!edid) { + /* + * In case we cannot retrieve the EDIDs (missing or broken DDC + * bus from the next bridge), fallback on the XGA standards and + * prefer a mode pretty much anyone can handle. + */ + ret = drm_add_modes_noedid(connector, 1920, 1200); + drm_set_preferred_mode(connector, 1024, 768); + return ret; + } + + drm_connector_update_edid_property(connector, edid); + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + + return ret; +} + +static const struct drm_connector_helper_funcs simple_bridge_con_helper_funcs = { + .get_modes = simple_bridge_get_modes, +}; + +static enum drm_connector_status +simple_bridge_connector_detect(struct drm_connector *connector, bool force) +{ + struct simple_bridge *sbridge = drm_connector_to_simple_bridge(connector); + + return drm_bridge_detect(sbridge->next_bridge); +} + +static const struct drm_connector_funcs simple_bridge_con_funcs = { + .detect = simple_bridge_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int simple_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge); + int ret; + + ret = drm_bridge_attach(bridge->encoder, sbridge->next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret < 0) + return ret; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return 0; + + if (!bridge->encoder) { + DRM_ERROR("Missing encoder\n"); + return -ENODEV; + } + + drm_connector_helper_add(&sbridge->connector, + &simple_bridge_con_helper_funcs); + ret = drm_connector_init_with_ddc(bridge->dev, &sbridge->connector, + &simple_bridge_con_funcs, + sbridge->info->connector_type, + sbridge->next_bridge->ddc); + if (ret) { + DRM_ERROR("Failed to initialize connector\n"); + return ret; + } + + drm_connector_attach_encoder(&sbridge->connector, bridge->encoder); + + return 0; +} + +static void simple_bridge_enable(struct drm_bridge *bridge) +{ + struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge); + int ret; + + if (sbridge->vdd) { + ret = regulator_enable(sbridge->vdd); + if (ret) + DRM_ERROR("Failed to enable vdd regulator: %d\n", ret); + } + + gpiod_set_value_cansleep(sbridge->enable, 1); +} + +static void simple_bridge_disable(struct drm_bridge *bridge) +{ + struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge); + + gpiod_set_value_cansleep(sbridge->enable, 0); + + if (sbridge->vdd) + regulator_disable(sbridge->vdd); +} + +static const struct drm_bridge_funcs simple_bridge_bridge_funcs = { + .attach = simple_bridge_attach, + .enable = simple_bridge_enable, + .disable = simple_bridge_disable, +}; + +static int simple_bridge_probe(struct platform_device *pdev) +{ + struct simple_bridge *sbridge; + struct device_node *remote; + + sbridge = devm_kzalloc(&pdev->dev, sizeof(*sbridge), GFP_KERNEL); + if (!sbridge) + return -ENOMEM; + platform_set_drvdata(pdev, sbridge); + + sbridge->info = of_device_get_match_data(&pdev->dev); + + /* Get the next bridge in the pipeline. */ + remote = of_graph_get_remote_node(pdev->dev.of_node, 1, -1); + if (!remote) + return -EINVAL; + + sbridge->next_bridge = of_drm_find_bridge(remote); + of_node_put(remote); + + if (!sbridge->next_bridge) { + dev_dbg(&pdev->dev, "Next bridge not found, deferring probe\n"); + return -EPROBE_DEFER; + } + + /* Get the regulator and GPIO resources. */ + sbridge->vdd = devm_regulator_get_optional(&pdev->dev, "vdd"); + if (IS_ERR(sbridge->vdd)) { + int ret = PTR_ERR(sbridge->vdd); + if (ret == -EPROBE_DEFER) + return -EPROBE_DEFER; + sbridge->vdd = NULL; + dev_dbg(&pdev->dev, "No vdd regulator found: %d\n", ret); + } + + sbridge->enable = devm_gpiod_get_optional(&pdev->dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(sbridge->enable)) { + if (PTR_ERR(sbridge->enable) != -EPROBE_DEFER) + dev_err(&pdev->dev, "Unable to retrieve enable GPIO\n"); + return PTR_ERR(sbridge->enable); + } + + /* Register the bridge. */ + sbridge->bridge.funcs = &simple_bridge_bridge_funcs; + sbridge->bridge.of_node = pdev->dev.of_node; + sbridge->bridge.timings = sbridge->info->timings; + + drm_bridge_add(&sbridge->bridge); + + return 0; +} + +static int simple_bridge_remove(struct platform_device *pdev) +{ + struct simple_bridge *sbridge = platform_get_drvdata(pdev); + + drm_bridge_remove(&sbridge->bridge); + + return 0; +} + +/* + * We assume the ADV7123 DAC is the "default" for historical reasons + * Information taken from the ADV7123 datasheet, revision D. + * NOTE: the ADV7123EP seems to have other timings and need a new timings + * set if used. + */ +static const struct drm_bridge_timings default_bridge_timings = { + /* Timing specifications, datasheet page 7 */ + .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, + .setup_time_ps = 500, + .hold_time_ps = 1500, +}; + +/* + * Information taken from the THS8134, THS8134A, THS8134B datasheet named + * "SLVS205D", dated May 1990, revised March 2000. + */ +static const struct drm_bridge_timings ti_ths8134_bridge_timings = { + /* From timing diagram, datasheet page 9 */ + .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, + /* From datasheet, page 12 */ + .setup_time_ps = 3000, + /* I guess this means latched input */ + .hold_time_ps = 0, +}; + +/* + * Information taken from the THS8135 datasheet named "SLAS343B", dated + * May 2001, revised April 2013. + */ +static const struct drm_bridge_timings ti_ths8135_bridge_timings = { + /* From timing diagram, datasheet page 14 */ + .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, + /* From datasheet, page 16 */ + .setup_time_ps = 2000, + .hold_time_ps = 500, +}; + +static const struct of_device_id simple_bridge_match[] = { + { + .compatible = "dumb-vga-dac", + .data = &(const struct simple_bridge_info) { + .connector_type = DRM_MODE_CONNECTOR_VGA, + }, + }, { + .compatible = "adi,adv7123", + .data = &(const struct simple_bridge_info) { + .timings = &default_bridge_timings, + .connector_type = DRM_MODE_CONNECTOR_VGA, + }, + }, { + .compatible = "ti,opa362", + .data = &(const struct simple_bridge_info) { + .connector_type = DRM_MODE_CONNECTOR_Composite, + }, + }, { + .compatible = "ti,ths8135", + .data = &(const struct simple_bridge_info) { + .timings = &ti_ths8135_bridge_timings, + .connector_type = DRM_MODE_CONNECTOR_VGA, + }, + }, { + .compatible = "ti,ths8134", + .data = &(const struct simple_bridge_info) { + .timings = &ti_ths8134_bridge_timings, + .connector_type = DRM_MODE_CONNECTOR_VGA, + }, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, simple_bridge_match); + +static struct platform_driver simple_bridge_driver = { + .probe = simple_bridge_probe, + .remove = simple_bridge_remove, + .driver = { + .name = "simple-bridge", + .of_match_table = simple_bridge_match, + }, +}; +module_platform_driver(simple_bridge_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Simple DRM bridge driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig new file mode 100644 index 000000000..21a1be3ce --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_DW_HDMI + tristate + select DRM_KMS_HELPER + select REGMAP_MMIO + select CEC_CORE if CEC_NOTIFIER + +config DRM_DW_HDMI_AHB_AUDIO + tristate "Synopsys Designware AHB Audio interface" + depends on DRM_DW_HDMI && SND + select SND_PCM + select SND_PCM_ELD + select SND_PCM_IEC958 + help + Support the AHB Audio interface which is part of the Synopsys + Designware HDMI block. This is used in conjunction with + the i.MX6 HDMI driver. + +config DRM_DW_HDMI_I2S_AUDIO + tristate "Synopsys Designware I2S Audio interface" + depends on SND_SOC + depends on DRM_DW_HDMI + select SND_SOC_HDMI_CODEC + help + Support the I2S Audio interface which is part of the Synopsys + Designware HDMI block. + +config DRM_DW_HDMI_CEC + tristate "Synopsis Designware CEC interface" + depends on DRM_DW_HDMI + select CEC_CORE + select CEC_NOTIFIER + help + Support the CE interface which is part of the Synopsys + Designware HDMI block. + +config DRM_DW_MIPI_DSI + tristate + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL_BRIDGE diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile new file mode 100644 index 000000000..91d746ad5 --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o +obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o +obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o +obj-$(CONFIG_DRM_DW_HDMI_CEC) += dw-hdmi-cec.o + +obj-$(CONFIG_DRM_DW_MIPI_DSI) += dw-mipi-dsi.o diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c new file mode 100644 index 000000000..7d2ed0ed2 --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c @@ -0,0 +1,640 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * DesignWare HDMI audio driver + * + * Written and tested against the Designware HDMI Tx found in iMX6. + */ +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <drm/bridge/dw_hdmi.h> +#include <drm/drm_edid.h> + +#include <sound/asoundef.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_drm_eld.h> +#include <sound/pcm_iec958.h> + +#include "dw-hdmi-audio.h" + +#define DRIVER_NAME "dw-hdmi-ahb-audio" + +/* Provide some bits rather than bit offsets */ +enum { + HDMI_AHB_DMA_CONF0_SW_FIFO_RST = BIT(7), + HDMI_AHB_DMA_CONF0_EN_HLOCK = BIT(3), + HDMI_AHB_DMA_START_START = BIT(0), + HDMI_AHB_DMA_STOP_STOP = BIT(0), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_ERROR = BIT(5), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_LOST = BIT(4), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_RETRY = BIT(3), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE = BIT(2), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFFULL = BIT(1), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFEMPTY = BIT(0), + HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL = + HDMI_IH_MUTE_AHBDMAAUD_STAT0_ERROR | + HDMI_IH_MUTE_AHBDMAAUD_STAT0_LOST | + HDMI_IH_MUTE_AHBDMAAUD_STAT0_RETRY | + HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE | + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFFULL | + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFEMPTY, + HDMI_IH_AHBDMAAUD_STAT0_ERROR = BIT(5), + HDMI_IH_AHBDMAAUD_STAT0_LOST = BIT(4), + HDMI_IH_AHBDMAAUD_STAT0_RETRY = BIT(3), + HDMI_IH_AHBDMAAUD_STAT0_DONE = BIT(2), + HDMI_IH_AHBDMAAUD_STAT0_BUFFFULL = BIT(1), + HDMI_IH_AHBDMAAUD_STAT0_BUFFEMPTY = BIT(0), + HDMI_IH_AHBDMAAUD_STAT0_ALL = + HDMI_IH_AHBDMAAUD_STAT0_ERROR | + HDMI_IH_AHBDMAAUD_STAT0_LOST | + HDMI_IH_AHBDMAAUD_STAT0_RETRY | + HDMI_IH_AHBDMAAUD_STAT0_DONE | + HDMI_IH_AHBDMAAUD_STAT0_BUFFFULL | + HDMI_IH_AHBDMAAUD_STAT0_BUFFEMPTY, + HDMI_AHB_DMA_CONF0_INCR16 = 2 << 1, + HDMI_AHB_DMA_CONF0_INCR8 = 1 << 1, + HDMI_AHB_DMA_CONF0_INCR4 = 0, + HDMI_AHB_DMA_CONF0_BURST_MODE = BIT(0), + HDMI_AHB_DMA_MASK_DONE = BIT(7), + + HDMI_REVISION_ID = 0x0001, + HDMI_IH_AHBDMAAUD_STAT0 = 0x0109, + HDMI_IH_MUTE_AHBDMAAUD_STAT0 = 0x0189, + HDMI_AHB_DMA_CONF0 = 0x3600, + HDMI_AHB_DMA_START = 0x3601, + HDMI_AHB_DMA_STOP = 0x3602, + HDMI_AHB_DMA_THRSLD = 0x3603, + HDMI_AHB_DMA_STRADDR0 = 0x3604, + HDMI_AHB_DMA_STPADDR0 = 0x3608, + HDMI_AHB_DMA_MASK = 0x3614, + HDMI_AHB_DMA_POL = 0x3615, + HDMI_AHB_DMA_CONF1 = 0x3616, + HDMI_AHB_DMA_BUFFPOL = 0x361a, +}; + +struct dw_hdmi_channel_conf { + u8 conf1; + u8 ca; +}; + +/* + * The default mapping of ALSA channels to HDMI channels and speaker + * allocation bits. Note that we can't do channel remapping here - + * channels must be in the same order. + * + * Mappings for alsa-lib pcm/surround*.conf files: + * + * Front Sur4.0 Sur4.1 Sur5.0 Sur5.1 Sur7.1 + * Channels 2 4 6 6 6 8 + * + * Our mapping from ALSA channel to CEA686D speaker name and HDMI channel: + * + * Number of ALSA channels + * ALSA Channel 2 3 4 5 6 7 8 + * 0 FL:0 = = = = = = + * 1 FR:1 = = = = = = + * 2 FC:3 RL:4 LFE:2 = = = + * 3 RR:5 RL:4 FC:3 = = + * 4 RR:5 RL:4 = = + * 5 RR:5 = = + * 6 RC:6 = + * 7 RLC/FRC RLC/FRC + */ +static struct dw_hdmi_channel_conf default_hdmi_channel_config[7] = { + { 0x03, 0x00 }, /* FL,FR */ + { 0x0b, 0x02 }, /* FL,FR,FC */ + { 0x33, 0x08 }, /* FL,FR,RL,RR */ + { 0x37, 0x09 }, /* FL,FR,LFE,RL,RR */ + { 0x3f, 0x0b }, /* FL,FR,LFE,FC,RL,RR */ + { 0x7f, 0x0f }, /* FL,FR,LFE,FC,RL,RR,RC */ + { 0xff, 0x13 }, /* FL,FR,LFE,FC,RL,RR,[FR]RC,[FR]LC */ +}; + +struct snd_dw_hdmi { + struct snd_card *card; + struct snd_pcm *pcm; + spinlock_t lock; + struct dw_hdmi_audio_data data; + struct snd_pcm_substream *substream; + void (*reformat)(struct snd_dw_hdmi *, size_t, size_t); + void *buf_src; + void *buf_dst; + dma_addr_t buf_addr; + unsigned buf_offset; + unsigned buf_period; + unsigned buf_size; + unsigned channels; + u8 revision; + u8 iec_offset; + u8 cs[192][8]; +}; + +static void dw_hdmi_writel(u32 val, void __iomem *ptr) +{ + writeb_relaxed(val, ptr); + writeb_relaxed(val >> 8, ptr + 1); + writeb_relaxed(val >> 16, ptr + 2); + writeb_relaxed(val >> 24, ptr + 3); +} + +/* + * Convert to hardware format: The userspace buffer contains IEC958 samples, + * with the PCUV bits in bits 31..28 and audio samples in bits 27..4. We + * need these to be in bits 27..24, with the IEC B bit in bit 28, and audio + * samples in 23..0. + * + * Default preamble in bits 3..0: 8 = block start, 4 = even 2 = odd + * + * Ideally, we could do with having the data properly formatted in userspace. + */ +static void dw_hdmi_reformat_iec958(struct snd_dw_hdmi *dw, + size_t offset, size_t bytes) +{ + u32 *src = dw->buf_src + offset; + u32 *dst = dw->buf_dst + offset; + u32 *end = dw->buf_src + offset + bytes; + + do { + u32 b, sample = *src++; + + b = (sample & 8) << (28 - 3); + + sample >>= 4; + + *dst++ = sample | b; + } while (src < end); +} + +static u32 parity(u32 sample) +{ + sample ^= sample >> 16; + sample ^= sample >> 8; + sample ^= sample >> 4; + sample ^= sample >> 2; + sample ^= sample >> 1; + return (sample & 1) << 27; +} + +static void dw_hdmi_reformat_s24(struct snd_dw_hdmi *dw, + size_t offset, size_t bytes) +{ + u32 *src = dw->buf_src + offset; + u32 *dst = dw->buf_dst + offset; + u32 *end = dw->buf_src + offset + bytes; + + do { + unsigned i; + u8 *cs; + + cs = dw->cs[dw->iec_offset++]; + if (dw->iec_offset >= 192) + dw->iec_offset = 0; + + i = dw->channels; + do { + u32 sample = *src++; + + sample &= ~0xff000000; + sample |= *cs++ << 24; + sample |= parity(sample & ~0xf8000000); + + *dst++ = sample; + } while (--i); + } while (src < end); +} + +static void dw_hdmi_create_cs(struct snd_dw_hdmi *dw, + struct snd_pcm_runtime *runtime) +{ + u8 cs[4]; + unsigned ch, i, j; + + snd_pcm_create_iec958_consumer(runtime, cs, sizeof(cs)); + + memset(dw->cs, 0, sizeof(dw->cs)); + + for (ch = 0; ch < 8; ch++) { + cs[2] &= ~IEC958_AES2_CON_CHANNEL; + cs[2] |= (ch + 1) << 4; + + for (i = 0; i < ARRAY_SIZE(cs); i++) { + unsigned c = cs[i]; + + for (j = 0; j < 8; j++, c >>= 1) + dw->cs[i * 8 + j][ch] = (c & 1) << 2; + } + } + dw->cs[0][0] |= BIT(4); +} + +static void dw_hdmi_start_dma(struct snd_dw_hdmi *dw) +{ + void __iomem *base = dw->data.base; + unsigned offset = dw->buf_offset; + unsigned period = dw->buf_period; + u32 start, stop; + + dw->reformat(dw, offset, period); + + /* Clear all irqs before enabling irqs and starting DMA */ + writeb_relaxed(HDMI_IH_AHBDMAAUD_STAT0_ALL, + base + HDMI_IH_AHBDMAAUD_STAT0); + + start = dw->buf_addr + offset; + stop = start + period - 1; + + /* Setup the hardware start/stop addresses */ + dw_hdmi_writel(start, base + HDMI_AHB_DMA_STRADDR0); + dw_hdmi_writel(stop, base + HDMI_AHB_DMA_STPADDR0); + + writeb_relaxed((u8)~HDMI_AHB_DMA_MASK_DONE, base + HDMI_AHB_DMA_MASK); + writeb(HDMI_AHB_DMA_START_START, base + HDMI_AHB_DMA_START); + + offset += period; + if (offset >= dw->buf_size) + offset = 0; + dw->buf_offset = offset; +} + +static void dw_hdmi_stop_dma(struct snd_dw_hdmi *dw) +{ + /* Disable interrupts before disabling DMA */ + writeb_relaxed(~0, dw->data.base + HDMI_AHB_DMA_MASK); + writeb_relaxed(HDMI_AHB_DMA_STOP_STOP, dw->data.base + HDMI_AHB_DMA_STOP); +} + +static irqreturn_t snd_dw_hdmi_irq(int irq, void *data) +{ + struct snd_dw_hdmi *dw = data; + struct snd_pcm_substream *substream; + unsigned stat; + + stat = readb_relaxed(dw->data.base + HDMI_IH_AHBDMAAUD_STAT0); + if (!stat) + return IRQ_NONE; + + writeb_relaxed(stat, dw->data.base + HDMI_IH_AHBDMAAUD_STAT0); + + substream = dw->substream; + if (stat & HDMI_IH_AHBDMAAUD_STAT0_DONE && substream) { + snd_pcm_period_elapsed(substream); + + spin_lock(&dw->lock); + if (dw->substream) + dw_hdmi_start_dma(dw); + spin_unlock(&dw->lock); + } + + return IRQ_HANDLED; +} + +static const struct snd_pcm_hardware dw_hdmi_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = 1024 * 1024, + .period_bytes_min = 256, + .period_bytes_max = 8192, /* ERR004323: must limit to 8k */ + .periods_min = 2, + .periods_max = 16, + .fifo_size = 0, +}; + +static int dw_hdmi_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dw_hdmi *dw = substream->private_data; + void __iomem *base = dw->data.base; + u8 *eld; + int ret; + + runtime->hw = dw_hdmi_hw; + + eld = dw->data.get_eld(dw->data.hdmi); + if (eld) { + ret = snd_pcm_hw_constraint_eld(runtime, eld); + if (ret < 0) + return ret; + } + + ret = snd_pcm_limit_hw_rates(runtime); + if (ret < 0) + return ret; + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + /* Limit the buffer size to the size of the preallocated buffer */ + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + 0, substream->dma_buffer.bytes); + if (ret < 0) + return ret; + + /* Clear FIFO */ + writeb_relaxed(HDMI_AHB_DMA_CONF0_SW_FIFO_RST, + base + HDMI_AHB_DMA_CONF0); + + /* Configure interrupt polarities */ + writeb_relaxed(~0, base + HDMI_AHB_DMA_POL); + writeb_relaxed(~0, base + HDMI_AHB_DMA_BUFFPOL); + + /* Keep interrupts masked, and clear any pending */ + writeb_relaxed(~0, base + HDMI_AHB_DMA_MASK); + writeb_relaxed(~0, base + HDMI_IH_AHBDMAAUD_STAT0); + + ret = request_irq(dw->data.irq, snd_dw_hdmi_irq, IRQF_SHARED, + "dw-hdmi-audio", dw); + if (ret) + return ret; + + /* Un-mute done interrupt */ + writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL & + ~HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE, + base + HDMI_IH_MUTE_AHBDMAAUD_STAT0); + + return 0; +} + +static int dw_hdmi_close(struct snd_pcm_substream *substream) +{ + struct snd_dw_hdmi *dw = substream->private_data; + + /* Mute all interrupts */ + writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL, + dw->data.base + HDMI_IH_MUTE_AHBDMAAUD_STAT0); + + free_irq(dw->data.irq, dw); + + return 0; +} + +static int dw_hdmi_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int dw_hdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + /* Allocate the PCM runtime buffer, which is exposed to userspace. */ + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(params)); +} + +static int dw_hdmi_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dw_hdmi *dw = substream->private_data; + u8 threshold, conf0, conf1, ca; + + /* Setup as per 3.0.5 FSL 4.1.0 BSP */ + switch (dw->revision) { + case 0x0a: + conf0 = HDMI_AHB_DMA_CONF0_BURST_MODE | + HDMI_AHB_DMA_CONF0_INCR4; + if (runtime->channels == 2) + threshold = 126; + else + threshold = 124; + break; + case 0x1a: + conf0 = HDMI_AHB_DMA_CONF0_BURST_MODE | + HDMI_AHB_DMA_CONF0_INCR8; + threshold = 128; + break; + default: + /* NOTREACHED */ + return -EINVAL; + } + + dw_hdmi_set_sample_rate(dw->data.hdmi, runtime->rate); + + /* Minimum number of bytes in the fifo. */ + runtime->hw.fifo_size = threshold * 32; + + conf0 |= HDMI_AHB_DMA_CONF0_EN_HLOCK; + conf1 = default_hdmi_channel_config[runtime->channels - 2].conf1; + ca = default_hdmi_channel_config[runtime->channels - 2].ca; + + writeb_relaxed(threshold, dw->data.base + HDMI_AHB_DMA_THRSLD); + writeb_relaxed(conf0, dw->data.base + HDMI_AHB_DMA_CONF0); + writeb_relaxed(conf1, dw->data.base + HDMI_AHB_DMA_CONF1); + + dw_hdmi_set_channel_count(dw->data.hdmi, runtime->channels); + dw_hdmi_set_channel_allocation(dw->data.hdmi, ca); + + switch (runtime->format) { + case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: + dw->reformat = dw_hdmi_reformat_iec958; + break; + case SNDRV_PCM_FORMAT_S24_LE: + dw_hdmi_create_cs(dw, runtime); + dw->reformat = dw_hdmi_reformat_s24; + break; + } + dw->iec_offset = 0; + dw->channels = runtime->channels; + dw->buf_src = runtime->dma_area; + dw->buf_dst = substream->dma_buffer.area; + dw->buf_addr = substream->dma_buffer.addr; + dw->buf_period = snd_pcm_lib_period_bytes(substream); + dw->buf_size = snd_pcm_lib_buffer_bytes(substream); + + return 0; +} + +static int dw_hdmi_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dw_hdmi *dw = substream->private_data; + unsigned long flags; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + spin_lock_irqsave(&dw->lock, flags); + dw->buf_offset = 0; + dw->substream = substream; + dw_hdmi_start_dma(dw); + dw_hdmi_audio_enable(dw->data.hdmi); + spin_unlock_irqrestore(&dw->lock, flags); + substream->runtime->delay = substream->runtime->period_size; + break; + + case SNDRV_PCM_TRIGGER_STOP: + spin_lock_irqsave(&dw->lock, flags); + dw->substream = NULL; + dw_hdmi_stop_dma(dw); + dw_hdmi_audio_disable(dw->data.hdmi); + spin_unlock_irqrestore(&dw->lock, flags); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t dw_hdmi_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dw_hdmi *dw = substream->private_data; + + /* + * We are unable to report the exact hardware position as + * reading the 32-bit DMA position using 8-bit reads is racy. + */ + return bytes_to_frames(runtime, dw->buf_offset); +} + +static const struct snd_pcm_ops snd_dw_hdmi_ops = { + .open = dw_hdmi_open, + .close = dw_hdmi_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dw_hdmi_hw_params, + .hw_free = dw_hdmi_hw_free, + .prepare = dw_hdmi_prepare, + .trigger = dw_hdmi_trigger, + .pointer = dw_hdmi_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static int snd_dw_hdmi_probe(struct platform_device *pdev) +{ + const struct dw_hdmi_audio_data *data = pdev->dev.platform_data; + struct device *dev = pdev->dev.parent; + struct snd_dw_hdmi *dw; + struct snd_card *card; + struct snd_pcm *pcm; + unsigned revision; + int ret; + + writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL, + data->base + HDMI_IH_MUTE_AHBDMAAUD_STAT0); + revision = readb_relaxed(data->base + HDMI_REVISION_ID); + if (revision != 0x0a && revision != 0x1a) { + dev_err(dev, "dw-hdmi-audio: unknown revision 0x%02x\n", + revision); + return -ENXIO; + } + + ret = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, sizeof(struct snd_dw_hdmi), &card); + if (ret < 0) + return ret; + + strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver)); + strlcpy(card->shortname, "DW-HDMI", sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "%s rev 0x%02x, irq %d", card->shortname, revision, + data->irq); + + dw = card->private_data; + dw->card = card; + dw->data = *data; + dw->revision = revision; + + spin_lock_init(&dw->lock); + + ret = snd_pcm_new(card, "DW HDMI", 0, 1, 0, &pcm); + if (ret < 0) + goto err; + + dw->pcm = pcm; + pcm->private_data = dw; + strlcpy(pcm->name, DRIVER_NAME, sizeof(pcm->name)); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_dw_hdmi_ops); + + /* + * To support 8-channel 96kHz audio reliably, we need 512k + * to satisfy alsa with our restricted period (ERR004323). + */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + dev, 128 * 1024, 1024 * 1024); + + ret = snd_card_register(card); + if (ret < 0) + goto err; + + platform_set_drvdata(pdev, dw); + + return 0; + +err: + snd_card_free(card); + return ret; +} + +static int snd_dw_hdmi_remove(struct platform_device *pdev) +{ + struct snd_dw_hdmi *dw = platform_get_drvdata(pdev); + + snd_card_free(dw->card); + + return 0; +} + +#if defined(CONFIG_PM_SLEEP) && defined(IS_NOT_BROKEN) +/* + * This code is fine, but requires implementation in the dw_hdmi_trigger() + * method which is currently missing as I have no way to test this. + */ +static int snd_dw_hdmi_suspend(struct device *dev) +{ + struct snd_dw_hdmi *dw = dev_get_drvdata(dev); + + snd_power_change_state(dw->card, SNDRV_CTL_POWER_D3cold); + + return 0; +} + +static int snd_dw_hdmi_resume(struct device *dev) +{ + struct snd_dw_hdmi *dw = dev_get_drvdata(dev); + + snd_power_change_state(dw->card, SNDRV_CTL_POWER_D0); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(snd_dw_hdmi_pm, snd_dw_hdmi_suspend, + snd_dw_hdmi_resume); +#define PM_OPS &snd_dw_hdmi_pm +#else +#define PM_OPS NULL +#endif + +static struct platform_driver snd_dw_hdmi_driver = { + .probe = snd_dw_hdmi_probe, + .remove = snd_dw_hdmi_remove, + .driver = { + .name = DRIVER_NAME, + .pm = PM_OPS, + }, +}; + +module_platform_driver(snd_dw_hdmi_driver); + +MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>"); +MODULE_DESCRIPTION("Synopsis Designware HDMI AHB ALSA interface"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h new file mode 100644 index 000000000..f72d27208 --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef DW_HDMI_AUDIO_H +#define DW_HDMI_AUDIO_H + +struct dw_hdmi; + +struct dw_hdmi_audio_data { + phys_addr_t phys; + void __iomem *base; + int irq; + struct dw_hdmi *hdmi; + u8 *(*get_eld)(struct dw_hdmi *hdmi); +}; + +struct dw_hdmi_i2s_audio_data { + struct dw_hdmi *hdmi; + + void (*write)(struct dw_hdmi *hdmi, u8 val, int offset); + u8 (*read)(struct dw_hdmi *hdmi, int offset); + u8 *(*get_eld)(struct dw_hdmi *hdmi); +}; + +#endif diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c new file mode 100644 index 000000000..70ab4fbdc --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Designware HDMI CEC driver + * + * Copyright (C) 2015-2017 Russell King. + */ +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> + +#include <drm/drm_edid.h> + +#include <media/cec.h> +#include <media/cec-notifier.h> + +#include "dw-hdmi-cec.h" + +enum { + HDMI_IH_CEC_STAT0 = 0x0106, + HDMI_IH_MUTE_CEC_STAT0 = 0x0186, + + HDMI_CEC_CTRL = 0x7d00, + CEC_CTRL_START = BIT(0), + CEC_CTRL_FRAME_TYP = 3 << 1, + CEC_CTRL_RETRY = 0 << 1, + CEC_CTRL_NORMAL = 1 << 1, + CEC_CTRL_IMMED = 2 << 1, + + HDMI_CEC_STAT = 0x7d01, + CEC_STAT_DONE = BIT(0), + CEC_STAT_EOM = BIT(1), + CEC_STAT_NACK = BIT(2), + CEC_STAT_ARBLOST = BIT(3), + CEC_STAT_ERROR_INIT = BIT(4), + CEC_STAT_ERROR_FOLL = BIT(5), + CEC_STAT_WAKEUP = BIT(6), + + HDMI_CEC_MASK = 0x7d02, + HDMI_CEC_POLARITY = 0x7d03, + HDMI_CEC_INT = 0x7d04, + HDMI_CEC_ADDR_L = 0x7d05, + HDMI_CEC_ADDR_H = 0x7d06, + HDMI_CEC_TX_CNT = 0x7d07, + HDMI_CEC_RX_CNT = 0x7d08, + HDMI_CEC_TX_DATA0 = 0x7d10, + HDMI_CEC_RX_DATA0 = 0x7d20, + HDMI_CEC_LOCK = 0x7d30, + HDMI_CEC_WKUPCTRL = 0x7d31, +}; + +struct dw_hdmi_cec { + struct dw_hdmi *hdmi; + const struct dw_hdmi_cec_ops *ops; + u32 addresses; + struct cec_adapter *adap; + struct cec_msg rx_msg; + unsigned int tx_status; + bool tx_done; + bool rx_done; + struct cec_notifier *notify; + int irq; +}; + +static void dw_hdmi_write(struct dw_hdmi_cec *cec, u8 val, int offset) +{ + cec->ops->write(cec->hdmi, val, offset); +} + +static u8 dw_hdmi_read(struct dw_hdmi_cec *cec, int offset) +{ + return cec->ops->read(cec->hdmi, offset); +} + +static int dw_hdmi_cec_log_addr(struct cec_adapter *adap, u8 logical_addr) +{ + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); + + if (logical_addr == CEC_LOG_ADDR_INVALID) + cec->addresses = 0; + else + cec->addresses |= BIT(logical_addr) | BIT(15); + + dw_hdmi_write(cec, cec->addresses & 255, HDMI_CEC_ADDR_L); + dw_hdmi_write(cec, cec->addresses >> 8, HDMI_CEC_ADDR_H); + + return 0; +} + +static int dw_hdmi_cec_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); + unsigned int i, ctrl; + + switch (signal_free_time) { + case CEC_SIGNAL_FREE_TIME_RETRY: + ctrl = CEC_CTRL_RETRY; + break; + case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR: + default: + ctrl = CEC_CTRL_NORMAL; + break; + case CEC_SIGNAL_FREE_TIME_NEXT_XFER: + ctrl = CEC_CTRL_IMMED; + break; + } + + for (i = 0; i < msg->len; i++) + dw_hdmi_write(cec, msg->msg[i], HDMI_CEC_TX_DATA0 + i); + + dw_hdmi_write(cec, msg->len, HDMI_CEC_TX_CNT); + dw_hdmi_write(cec, ctrl | CEC_CTRL_START, HDMI_CEC_CTRL); + + return 0; +} + +static irqreturn_t dw_hdmi_cec_hardirq(int irq, void *data) +{ + struct cec_adapter *adap = data; + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); + unsigned int stat = dw_hdmi_read(cec, HDMI_IH_CEC_STAT0); + irqreturn_t ret = IRQ_HANDLED; + + if (stat == 0) + return IRQ_NONE; + + dw_hdmi_write(cec, stat, HDMI_IH_CEC_STAT0); + + if (stat & CEC_STAT_ERROR_INIT) { + cec->tx_status = CEC_TX_STATUS_ERROR; + cec->tx_done = true; + ret = IRQ_WAKE_THREAD; + } else if (stat & CEC_STAT_DONE) { + cec->tx_status = CEC_TX_STATUS_OK; + cec->tx_done = true; + ret = IRQ_WAKE_THREAD; + } else if (stat & CEC_STAT_NACK) { + cec->tx_status = CEC_TX_STATUS_NACK; + cec->tx_done = true; + ret = IRQ_WAKE_THREAD; + } + + if (stat & CEC_STAT_EOM) { + unsigned int len, i; + + len = dw_hdmi_read(cec, HDMI_CEC_RX_CNT); + if (len > sizeof(cec->rx_msg.msg)) + len = sizeof(cec->rx_msg.msg); + + for (i = 0; i < len; i++) + cec->rx_msg.msg[i] = + dw_hdmi_read(cec, HDMI_CEC_RX_DATA0 + i); + + dw_hdmi_write(cec, 0, HDMI_CEC_LOCK); + + cec->rx_msg.len = len; + smp_wmb(); + cec->rx_done = true; + + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +static irqreturn_t dw_hdmi_cec_thread(int irq, void *data) +{ + struct cec_adapter *adap = data; + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); + + if (cec->tx_done) { + cec->tx_done = false; + cec_transmit_attempt_done(adap, cec->tx_status); + } + if (cec->rx_done) { + cec->rx_done = false; + smp_rmb(); + cec_received_msg(adap, &cec->rx_msg); + } + return IRQ_HANDLED; +} + +static int dw_hdmi_cec_enable(struct cec_adapter *adap, bool enable) +{ + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); + + if (!enable) { + dw_hdmi_write(cec, ~0, HDMI_CEC_MASK); + dw_hdmi_write(cec, ~0, HDMI_IH_MUTE_CEC_STAT0); + dw_hdmi_write(cec, 0, HDMI_CEC_POLARITY); + + cec->ops->disable(cec->hdmi); + } else { + unsigned int irqs; + + dw_hdmi_write(cec, 0, HDMI_CEC_CTRL); + dw_hdmi_write(cec, ~0, HDMI_IH_CEC_STAT0); + dw_hdmi_write(cec, 0, HDMI_CEC_LOCK); + + dw_hdmi_cec_log_addr(cec->adap, CEC_LOG_ADDR_INVALID); + + cec->ops->enable(cec->hdmi); + + irqs = CEC_STAT_ERROR_INIT | CEC_STAT_NACK | CEC_STAT_EOM | + CEC_STAT_DONE; + dw_hdmi_write(cec, irqs, HDMI_CEC_POLARITY); + dw_hdmi_write(cec, ~irqs, HDMI_CEC_MASK); + dw_hdmi_write(cec, ~irqs, HDMI_IH_MUTE_CEC_STAT0); + } + return 0; +} + +static const struct cec_adap_ops dw_hdmi_cec_ops = { + .adap_enable = dw_hdmi_cec_enable, + .adap_log_addr = dw_hdmi_cec_log_addr, + .adap_transmit = dw_hdmi_cec_transmit, +}; + +static void dw_hdmi_cec_del(void *data) +{ + struct dw_hdmi_cec *cec = data; + + cec_delete_adapter(cec->adap); +} + +static int dw_hdmi_cec_probe(struct platform_device *pdev) +{ + struct dw_hdmi_cec_data *data = dev_get_platdata(&pdev->dev); + struct dw_hdmi_cec *cec; + int ret; + + if (!data) + return -ENXIO; + + /* + * Our device is just a convenience - we want to link to the real + * hardware device here, so that userspace can see the association + * between the HDMI hardware and its associated CEC chardev. + */ + cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL); + if (!cec) + return -ENOMEM; + + cec->irq = data->irq; + cec->ops = data->ops; + cec->hdmi = data->hdmi; + + platform_set_drvdata(pdev, cec); + + dw_hdmi_write(cec, 0, HDMI_CEC_TX_CNT); + dw_hdmi_write(cec, ~0, HDMI_CEC_MASK); + dw_hdmi_write(cec, ~0, HDMI_IH_MUTE_CEC_STAT0); + dw_hdmi_write(cec, 0, HDMI_CEC_POLARITY); + + cec->adap = cec_allocate_adapter(&dw_hdmi_cec_ops, cec, "dw_hdmi", + CEC_CAP_DEFAULTS | + CEC_CAP_CONNECTOR_INFO, + CEC_MAX_LOG_ADDRS); + if (IS_ERR(cec->adap)) + return PTR_ERR(cec->adap); + + /* override the module pointer */ + cec->adap->owner = THIS_MODULE; + + ret = devm_add_action(&pdev->dev, dw_hdmi_cec_del, cec); + if (ret) { + cec_delete_adapter(cec->adap); + return ret; + } + + ret = devm_request_threaded_irq(&pdev->dev, cec->irq, + dw_hdmi_cec_hardirq, + dw_hdmi_cec_thread, IRQF_SHARED, + "dw-hdmi-cec", cec->adap); + if (ret < 0) + return ret; + + cec->notify = cec_notifier_cec_adap_register(pdev->dev.parent, + NULL, cec->adap); + if (!cec->notify) + return -ENOMEM; + + ret = cec_register_adapter(cec->adap, pdev->dev.parent); + if (ret < 0) { + cec_notifier_cec_adap_unregister(cec->notify, cec->adap); + return ret; + } + + /* + * CEC documentation says we must not call cec_delete_adapter + * after a successful call to cec_register_adapter(). + */ + devm_remove_action(&pdev->dev, dw_hdmi_cec_del, cec); + + return 0; +} + +static int dw_hdmi_cec_remove(struct platform_device *pdev) +{ + struct dw_hdmi_cec *cec = platform_get_drvdata(pdev); + + cec_notifier_cec_adap_unregister(cec->notify, cec->adap); + cec_unregister_adapter(cec->adap); + + return 0; +} + +static struct platform_driver dw_hdmi_cec_driver = { + .probe = dw_hdmi_cec_probe, + .remove = dw_hdmi_cec_remove, + .driver = { + .name = "dw-hdmi-cec", + }, +}; +module_platform_driver(dw_hdmi_cec_driver); + +MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>"); +MODULE_DESCRIPTION("Synopsys Designware HDMI CEC driver for i.MX"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS(PLATFORM_MODULE_PREFIX "dw-hdmi-cec"); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.h new file mode 100644 index 000000000..cf4dc121a --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.h @@ -0,0 +1,19 @@ +#ifndef DW_HDMI_CEC_H +#define DW_HDMI_CEC_H + +struct dw_hdmi; + +struct dw_hdmi_cec_ops { + void (*write)(struct dw_hdmi *hdmi, u8 val, int offset); + u8 (*read)(struct dw_hdmi *hdmi, int offset); + void (*enable)(struct dw_hdmi *hdmi); + void (*disable)(struct dw_hdmi *hdmi); +}; + +struct dw_hdmi_cec_data { + struct dw_hdmi *hdmi; + const struct dw_hdmi_cec_ops *ops; + int irq; +}; + +#endif diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c new file mode 100644 index 000000000..968241605 --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dw-hdmi-i2s-audio.c + * + * Copyright (c) 2017 Renesas Solutions Corp. + * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + */ + +#include <linux/dma-mapping.h> +#include <linux/module.h> + +#include <drm/bridge/dw_hdmi.h> +#include <drm/drm_crtc.h> + +#include <sound/hdmi-codec.h> + +#include "dw-hdmi.h" +#include "dw-hdmi-audio.h" + +#define DRIVER_NAME "dw-hdmi-i2s-audio" + +static inline void hdmi_write(struct dw_hdmi_i2s_audio_data *audio, + u8 val, int offset) +{ + struct dw_hdmi *hdmi = audio->hdmi; + + audio->write(hdmi, val, offset); +} + +static inline u8 hdmi_read(struct dw_hdmi_i2s_audio_data *audio, int offset) +{ + struct dw_hdmi *hdmi = audio->hdmi; + + return audio->read(hdmi, offset); +} + +static int dw_hdmi_i2s_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct dw_hdmi_i2s_audio_data *audio = data; + struct dw_hdmi *hdmi = audio->hdmi; + u8 conf0 = 0; + u8 conf1 = 0; + u8 inputclkfs = 0; + + /* it cares I2S only */ + if (fmt->bit_clk_master | fmt->frame_clk_master) { + dev_err(dev, "unsupported clock settings\n"); + return -EINVAL; + } + + /* Reset the FIFOs before applying new params */ + hdmi_write(audio, HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0); + hdmi_write(audio, (u8)~HDMI_MC_SWRSTZ_I2SSWRST_REQ, HDMI_MC_SWRSTZ); + + inputclkfs = HDMI_AUD_INPUTCLKFS_64FS; + conf0 = (HDMI_AUD_CONF0_I2S_SELECT | HDMI_AUD_CONF0_I2S_EN0); + + /* Enable the required i2s lanes */ + switch (hparms->channels) { + case 7 ... 8: + conf0 |= HDMI_AUD_CONF0_I2S_EN3; + fallthrough; + case 5 ... 6: + conf0 |= HDMI_AUD_CONF0_I2S_EN2; + fallthrough; + case 3 ... 4: + conf0 |= HDMI_AUD_CONF0_I2S_EN1; + /* Fall-thru */ + } + + switch (hparms->sample_width) { + case 16: + conf1 = HDMI_AUD_CONF1_WIDTH_16; + break; + case 24: + case 32: + conf1 = HDMI_AUD_CONF1_WIDTH_24; + break; + } + + switch (fmt->fmt) { + case HDMI_I2S: + conf1 |= HDMI_AUD_CONF1_MODE_I2S; + break; + case HDMI_RIGHT_J: + conf1 |= HDMI_AUD_CONF1_MODE_RIGHT_J; + break; + case HDMI_LEFT_J: + conf1 |= HDMI_AUD_CONF1_MODE_LEFT_J; + break; + case HDMI_DSP_A: + conf1 |= HDMI_AUD_CONF1_MODE_BURST_1; + break; + case HDMI_DSP_B: + conf1 |= HDMI_AUD_CONF1_MODE_BURST_2; + break; + default: + dev_err(dev, "unsupported format\n"); + return -EINVAL; + } + + dw_hdmi_set_sample_rate(hdmi, hparms->sample_rate); + dw_hdmi_set_channel_status(hdmi, hparms->iec.status); + dw_hdmi_set_channel_count(hdmi, hparms->channels); + dw_hdmi_set_channel_allocation(hdmi, hparms->cea.channel_allocation); + + hdmi_write(audio, inputclkfs, HDMI_AUD_INPUTCLKFS); + hdmi_write(audio, conf0, HDMI_AUD_CONF0); + hdmi_write(audio, conf1, HDMI_AUD_CONF1); + + return 0; +} + +static int dw_hdmi_i2s_audio_startup(struct device *dev, void *data) +{ + struct dw_hdmi_i2s_audio_data *audio = data; + struct dw_hdmi *hdmi = audio->hdmi; + + dw_hdmi_audio_enable(hdmi); + + return 0; +} + +static void dw_hdmi_i2s_audio_shutdown(struct device *dev, void *data) +{ + struct dw_hdmi_i2s_audio_data *audio = data; + struct dw_hdmi *hdmi = audio->hdmi; + + dw_hdmi_audio_disable(hdmi); +} + +static int dw_hdmi_i2s_get_eld(struct device *dev, void *data, uint8_t *buf, + size_t len) +{ + struct dw_hdmi_i2s_audio_data *audio = data; + u8 *eld; + + eld = audio->get_eld(audio->hdmi); + if (eld) + memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len)); + else + /* Pass en empty ELD if connector not available */ + memset(buf, 0, len); + + return 0; +} + +static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component, + struct device_node *endpoint) +{ + struct of_endpoint of_ep; + int ret; + + ret = of_graph_parse_endpoint(endpoint, &of_ep); + if (ret < 0) + return ret; + + /* + * HDMI sound should be located as reg = <2> + * Then, it is sound port 0 + */ + if (of_ep.port == 2) + return 0; + + return -EINVAL; +} + +static int dw_hdmi_i2s_hook_plugged_cb(struct device *dev, void *data, + hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + struct dw_hdmi_i2s_audio_data *audio = data; + struct dw_hdmi *hdmi = audio->hdmi; + + return dw_hdmi_set_plugged_cb(hdmi, fn, codec_dev); +} + +static struct hdmi_codec_ops dw_hdmi_i2s_ops = { + .hw_params = dw_hdmi_i2s_hw_params, + .audio_startup = dw_hdmi_i2s_audio_startup, + .audio_shutdown = dw_hdmi_i2s_audio_shutdown, + .get_eld = dw_hdmi_i2s_get_eld, + .get_dai_id = dw_hdmi_i2s_get_dai_id, + .hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb, +}; + +static int snd_dw_hdmi_probe(struct platform_device *pdev) +{ + struct dw_hdmi_i2s_audio_data *audio = pdev->dev.platform_data; + struct platform_device_info pdevinfo; + struct hdmi_codec_pdata pdata; + struct platform_device *platform; + + pdata.ops = &dw_hdmi_i2s_ops; + pdata.i2s = 1; + pdata.max_i2s_channels = 8; + pdata.data = audio; + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = pdev->dev.parent; + pdevinfo.id = PLATFORM_DEVID_AUTO; + pdevinfo.name = HDMI_CODEC_DRV_NAME; + pdevinfo.data = &pdata; + pdevinfo.size_data = sizeof(pdata); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + + platform = platform_device_register_full(&pdevinfo); + if (IS_ERR(platform)) + return PTR_ERR(platform); + + dev_set_drvdata(&pdev->dev, platform); + + return 0; +} + +static int snd_dw_hdmi_remove(struct platform_device *pdev) +{ + struct platform_device *platform = dev_get_drvdata(&pdev->dev); + + platform_device_unregister(platform); + + return 0; +} + +static struct platform_driver snd_dw_hdmi_driver = { + .probe = snd_dw_hdmi_probe, + .remove = snd_dw_hdmi_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; +module_platform_driver(snd_dw_hdmi_driver); + +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); +MODULE_DESCRIPTION("Synopsis Designware HDMI I2S ALSA SoC interface"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c new file mode 100644 index 000000000..2c3c743df --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -0,0 +1,3533 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DesignWare High-Definition Multimedia Interface (HDMI) driver + * + * Copyright (C) 2013-2015 Mentor Graphics Inc. + * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. + * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de> + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/hdmi.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/pinctrl/consumer.h> +#include <linux/regmap.h> +#include <linux/dma-mapping.h> +#include <linux/spinlock.h> + +#include <media/cec-notifier.h> + +#include <uapi/linux/media-bus-format.h> +#include <uapi/linux/videodev2.h> + +#include <drm/bridge/dw_hdmi.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_scdc_helper.h> + +#include "dw-hdmi-audio.h" +#include "dw-hdmi-cec.h" +#include "dw-hdmi.h" + +#define DDC_CI_ADDR 0x37 +#define DDC_SEGMENT_ADDR 0x30 + +#define HDMI_EDID_LEN 512 + +/* DW-HDMI Controller >= 0x200a are at least compliant with SCDC version 1 */ +#define SCDC_MIN_SOURCE_VERSION 0x1 + +#define HDMI14_MAX_TMDSCLK 340000000 + +enum hdmi_datamap { + RGB444_8B = 0x01, + RGB444_10B = 0x03, + RGB444_12B = 0x05, + RGB444_16B = 0x07, + YCbCr444_8B = 0x09, + YCbCr444_10B = 0x0B, + YCbCr444_12B = 0x0D, + YCbCr444_16B = 0x0F, + YCbCr422_8B = 0x16, + YCbCr422_10B = 0x14, + YCbCr422_12B = 0x12, +}; + +static const u16 csc_coeff_default[3][4] = { + { 0x2000, 0x0000, 0x0000, 0x0000 }, + { 0x0000, 0x2000, 0x0000, 0x0000 }, + { 0x0000, 0x0000, 0x2000, 0x0000 } +}; + +static const u16 csc_coeff_rgb_out_eitu601[3][4] = { + { 0x2000, 0x6926, 0x74fd, 0x010e }, + { 0x2000, 0x2cdd, 0x0000, 0x7e9a }, + { 0x2000, 0x0000, 0x38b4, 0x7e3b } +}; + +static const u16 csc_coeff_rgb_out_eitu709[3][4] = { + { 0x2000, 0x7106, 0x7a02, 0x00a7 }, + { 0x2000, 0x3264, 0x0000, 0x7e6d }, + { 0x2000, 0x0000, 0x3b61, 0x7e25 } +}; + +static const u16 csc_coeff_rgb_in_eitu601[3][4] = { + { 0x2591, 0x1322, 0x074b, 0x0000 }, + { 0x6535, 0x2000, 0x7acc, 0x0200 }, + { 0x6acd, 0x7534, 0x2000, 0x0200 } +}; + +static const u16 csc_coeff_rgb_in_eitu709[3][4] = { + { 0x2dc5, 0x0d9b, 0x049e, 0x0000 }, + { 0x62f0, 0x2000, 0x7d11, 0x0200 }, + { 0x6756, 0x78ab, 0x2000, 0x0200 } +}; + +static const u16 csc_coeff_rgb_full_to_rgb_limited[3][4] = { + { 0x1b7c, 0x0000, 0x0000, 0x0020 }, + { 0x0000, 0x1b7c, 0x0000, 0x0020 }, + { 0x0000, 0x0000, 0x1b7c, 0x0020 } +}; + +struct hdmi_vmode { + bool mdataenablepolarity; + + unsigned int mpixelclock; + unsigned int mpixelrepetitioninput; + unsigned int mpixelrepetitionoutput; + unsigned int mtmdsclock; +}; + +struct hdmi_data_info { + unsigned int enc_in_bus_format; + unsigned int enc_out_bus_format; + unsigned int enc_in_encoding; + unsigned int enc_out_encoding; + unsigned int pix_repet_factor; + unsigned int hdcp_enable; + struct hdmi_vmode video_mode; + bool rgb_limited_range; +}; + +struct dw_hdmi_i2c { + struct i2c_adapter adap; + + struct mutex lock; /* used to serialize data transfers */ + struct completion cmp; + u8 stat; + + u8 slave_reg; + bool is_regaddr; + bool is_segment; +}; + +struct dw_hdmi_phy_data { + enum dw_hdmi_phy_type type; + const char *name; + unsigned int gen; + bool has_svsret; + int (*configure)(struct dw_hdmi *hdmi, + const struct dw_hdmi_plat_data *pdata, + unsigned long mpixelclock); +}; + +struct dw_hdmi { + struct drm_connector connector; + struct drm_bridge bridge; + + unsigned int version; + + struct platform_device *audio; + struct platform_device *cec; + struct device *dev; + struct clk *isfr_clk; + struct clk *iahb_clk; + struct clk *cec_clk; + struct dw_hdmi_i2c *i2c; + + struct hdmi_data_info hdmi_data; + const struct dw_hdmi_plat_data *plat_data; + + int vic; + + u8 edid[HDMI_EDID_LEN]; + + struct { + const struct dw_hdmi_phy_ops *ops; + const char *name; + void *data; + bool enabled; + } phy; + + struct drm_display_mode previous_mode; + + struct i2c_adapter *ddc; + void __iomem *regs; + bool sink_is_hdmi; + bool sink_has_audio; + + struct pinctrl *pinctrl; + struct pinctrl_state *default_state; + struct pinctrl_state *unwedge_state; + + struct mutex mutex; /* for state below and previous_mode */ + enum drm_connector_force force; /* mutex-protected force state */ + struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */ + bool disabled; /* DRM has disabled our bridge */ + bool bridge_is_on; /* indicates the bridge is on */ + bool rxsense; /* rxsense state */ + u8 phy_mask; /* desired phy int mask settings */ + u8 mc_clkdis; /* clock disable register */ + + spinlock_t audio_lock; + struct mutex audio_mutex; + unsigned int sample_rate; + unsigned int audio_cts; + unsigned int audio_n; + bool audio_enable; + + unsigned int reg_shift; + struct regmap *regm; + void (*enable_audio)(struct dw_hdmi *hdmi); + void (*disable_audio)(struct dw_hdmi *hdmi); + + struct mutex cec_notifier_mutex; + struct cec_notifier *cec_notifier; + + hdmi_codec_plugged_cb plugged_cb; + struct device *codec_dev; + enum drm_connector_status last_connector_result; +}; + +#define HDMI_IH_PHY_STAT0_RX_SENSE \ + (HDMI_IH_PHY_STAT0_RX_SENSE0 | HDMI_IH_PHY_STAT0_RX_SENSE1 | \ + HDMI_IH_PHY_STAT0_RX_SENSE2 | HDMI_IH_PHY_STAT0_RX_SENSE3) + +#define HDMI_PHY_RX_SENSE \ + (HDMI_PHY_RX_SENSE0 | HDMI_PHY_RX_SENSE1 | \ + HDMI_PHY_RX_SENSE2 | HDMI_PHY_RX_SENSE3) + +static inline void hdmi_writeb(struct dw_hdmi *hdmi, u8 val, int offset) +{ + regmap_write(hdmi->regm, offset << hdmi->reg_shift, val); +} + +static inline u8 hdmi_readb(struct dw_hdmi *hdmi, int offset) +{ + unsigned int val = 0; + + regmap_read(hdmi->regm, offset << hdmi->reg_shift, &val); + + return val; +} + +static void handle_plugged_change(struct dw_hdmi *hdmi, bool plugged) +{ + if (hdmi->plugged_cb && hdmi->codec_dev) + hdmi->plugged_cb(hdmi->codec_dev, plugged); +} + +int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + bool plugged; + + mutex_lock(&hdmi->mutex); + hdmi->plugged_cb = fn; + hdmi->codec_dev = codec_dev; + plugged = hdmi->last_connector_result == connector_status_connected; + handle_plugged_change(hdmi, plugged); + mutex_unlock(&hdmi->mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_plugged_cb); + +static void hdmi_modb(struct dw_hdmi *hdmi, u8 data, u8 mask, unsigned reg) +{ + regmap_update_bits(hdmi->regm, reg << hdmi->reg_shift, mask, data); +} + +static void hdmi_mask_writeb(struct dw_hdmi *hdmi, u8 data, unsigned int reg, + u8 shift, u8 mask) +{ + hdmi_modb(hdmi, data << shift, mask, reg); +} + +static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi) +{ + hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL, + HDMI_PHY_I2CM_INT_ADDR); + + hdmi_writeb(hdmi, HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL | + HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL, + HDMI_PHY_I2CM_CTLINT_ADDR); + + /* Software reset */ + hdmi_writeb(hdmi, 0x00, HDMI_I2CM_SOFTRSTZ); + + /* Set Standard Mode speed (determined to be 100KHz on iMX6) */ + hdmi_writeb(hdmi, 0x00, HDMI_I2CM_DIV); + + /* Set done, not acknowledged and arbitration interrupt polarities */ + hdmi_writeb(hdmi, HDMI_I2CM_INT_DONE_POL, HDMI_I2CM_INT); + hdmi_writeb(hdmi, HDMI_I2CM_CTLINT_NAC_POL | HDMI_I2CM_CTLINT_ARB_POL, + HDMI_I2CM_CTLINT); + + /* Clear DONE and ERROR interrupts */ + hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE, + HDMI_IH_I2CM_STAT0); + + /* Mute DONE and ERROR interrupts */ + hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE, + HDMI_IH_MUTE_I2CM_STAT0); +} + +static bool dw_hdmi_i2c_unwedge(struct dw_hdmi *hdmi) +{ + /* If no unwedge state then give up */ + if (!hdmi->unwedge_state) + return false; + + dev_info(hdmi->dev, "Attempting to unwedge stuck i2c bus\n"); + + /* + * This is a huge hack to workaround a problem where the dw_hdmi i2c + * bus could sometimes get wedged. Once wedged there doesn't appear + * to be any way to unwedge it (including the HDMI_I2CM_SOFTRSTZ) + * other than pulsing the SDA line. + * + * We appear to be able to pulse the SDA line (in the eyes of dw_hdmi) + * by: + * 1. Remux the pin as a GPIO output, driven low. + * 2. Wait a little while. 1 ms seems to work, but we'll do 10. + * 3. Immediately jump to remux the pin as dw_hdmi i2c again. + * + * At the moment of remuxing, the line will still be low due to its + * recent stint as an output, but then it will be pulled high by the + * (presumed) external pullup. dw_hdmi seems to see this as a rising + * edge and that seems to get it out of its jam. + * + * This wedging was only ever seen on one TV, and only on one of + * its HDMI ports. It happened when the TV was powered on while the + * device was plugged in. A scope trace shows the TV bringing both SDA + * and SCL low, then bringing them both back up at roughly the same + * time. Presumably this confuses dw_hdmi because it saw activity but + * no real STOP (maybe it thinks there's another master on the bus?). + * Giving it a clean rising edge of SDA while SCL is already high + * presumably makes dw_hdmi see a STOP which seems to bring dw_hdmi out + * of its stupor. + * + * Note that after coming back alive, transfers seem to immediately + * resume, so if we unwedge due to a timeout we should wait a little + * longer for our transfer to finish, since it might have just started + * now. + */ + pinctrl_select_state(hdmi->pinctrl, hdmi->unwedge_state); + msleep(10); + pinctrl_select_state(hdmi->pinctrl, hdmi->default_state); + + return true; +} + +static int dw_hdmi_i2c_wait(struct dw_hdmi *hdmi) +{ + struct dw_hdmi_i2c *i2c = hdmi->i2c; + int stat; + + stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); + if (!stat) { + /* If we can't unwedge, return timeout */ + if (!dw_hdmi_i2c_unwedge(hdmi)) + return -EAGAIN; + + /* We tried to unwedge; give it another chance */ + stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); + if (!stat) + return -EAGAIN; + } + + /* Check for error condition on the bus */ + if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR) + return -EIO; + + return 0; +} + +static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi, + unsigned char *buf, unsigned int length) +{ + struct dw_hdmi_i2c *i2c = hdmi->i2c; + int ret; + + if (!i2c->is_regaddr) { + dev_dbg(hdmi->dev, "set read register address to 0\n"); + i2c->slave_reg = 0x00; + i2c->is_regaddr = true; + } + + while (length--) { + reinit_completion(&i2c->cmp); + + hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS); + if (i2c->is_segment) + hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ_EXT, + HDMI_I2CM_OPERATION); + else + hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ, + HDMI_I2CM_OPERATION); + + ret = dw_hdmi_i2c_wait(hdmi); + if (ret) + return ret; + + *buf++ = hdmi_readb(hdmi, HDMI_I2CM_DATAI); + } + i2c->is_segment = false; + + return 0; +} + +static int dw_hdmi_i2c_write(struct dw_hdmi *hdmi, + unsigned char *buf, unsigned int length) +{ + struct dw_hdmi_i2c *i2c = hdmi->i2c; + int ret; + + if (!i2c->is_regaddr) { + /* Use the first write byte as register address */ + i2c->slave_reg = buf[0]; + length--; + buf++; + i2c->is_regaddr = true; + } + + while (length--) { + reinit_completion(&i2c->cmp); + + hdmi_writeb(hdmi, *buf++, HDMI_I2CM_DATAO); + hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS); + hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_WRITE, + HDMI_I2CM_OPERATION); + + ret = dw_hdmi_i2c_wait(hdmi); + if (ret) + return ret; + } + + return 0; +} + +static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct dw_hdmi *hdmi = i2c_get_adapdata(adap); + struct dw_hdmi_i2c *i2c = hdmi->i2c; + u8 addr = msgs[0].addr; + int i, ret = 0; + + if (addr == DDC_CI_ADDR) + /* + * The internal I2C controller does not support the multi-byte + * read and write operations needed for DDC/CI. + * TOFIX: Blacklist the DDC/CI address until we filter out + * unsupported I2C operations. + */ + return -EOPNOTSUPP; + + dev_dbg(hdmi->dev, "xfer: num: %d, addr: %#x\n", num, addr); + + for (i = 0; i < num; i++) { + if (msgs[i].len == 0) { + dev_dbg(hdmi->dev, + "unsupported transfer %d/%d, no data\n", + i + 1, num); + return -EOPNOTSUPP; + } + } + + mutex_lock(&i2c->lock); + + /* Unmute DONE and ERROR interrupts */ + hdmi_writeb(hdmi, 0x00, HDMI_IH_MUTE_I2CM_STAT0); + + /* Set slave device address taken from the first I2C message */ + hdmi_writeb(hdmi, addr, HDMI_I2CM_SLAVE); + + /* Set slave device register address on transfer */ + i2c->is_regaddr = false; + + /* Set segment pointer for I2C extended read mode operation */ + i2c->is_segment = false; + + for (i = 0; i < num; i++) { + dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: %#x\n", + i + 1, num, msgs[i].len, msgs[i].flags); + if (msgs[i].addr == DDC_SEGMENT_ADDR && msgs[i].len == 1) { + i2c->is_segment = true; + hdmi_writeb(hdmi, DDC_SEGMENT_ADDR, HDMI_I2CM_SEGADDR); + hdmi_writeb(hdmi, *msgs[i].buf, HDMI_I2CM_SEGPTR); + } else { + if (msgs[i].flags & I2C_M_RD) + ret = dw_hdmi_i2c_read(hdmi, msgs[i].buf, + msgs[i].len); + else + ret = dw_hdmi_i2c_write(hdmi, msgs[i].buf, + msgs[i].len); + } + if (ret < 0) + break; + } + + if (!ret) + ret = num; + + /* Mute DONE and ERROR interrupts */ + hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE, + HDMI_IH_MUTE_I2CM_STAT0); + + mutex_unlock(&i2c->lock); + + return ret; +} + +static u32 dw_hdmi_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm dw_hdmi_algorithm = { + .master_xfer = dw_hdmi_i2c_xfer, + .functionality = dw_hdmi_i2c_func, +}; + +static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi) +{ + struct i2c_adapter *adap; + struct dw_hdmi_i2c *i2c; + int ret; + + i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return ERR_PTR(-ENOMEM); + + mutex_init(&i2c->lock); + init_completion(&i2c->cmp); + + adap = &i2c->adap; + adap->class = I2C_CLASS_DDC; + adap->owner = THIS_MODULE; + adap->dev.parent = hdmi->dev; + adap->algo = &dw_hdmi_algorithm; + strlcpy(adap->name, "DesignWare HDMI", sizeof(adap->name)); + i2c_set_adapdata(adap, hdmi); + + ret = i2c_add_adapter(adap); + if (ret) { + dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name); + devm_kfree(hdmi->dev, i2c); + return ERR_PTR(ret); + } + + hdmi->i2c = i2c; + + dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name); + + return adap; +} + +static void hdmi_set_cts_n(struct dw_hdmi *hdmi, unsigned int cts, + unsigned int n) +{ + /* Must be set/cleared first */ + hdmi_modb(hdmi, 0, HDMI_AUD_CTS3_CTS_MANUAL, HDMI_AUD_CTS3); + + /* nshift factor = 0 */ + hdmi_modb(hdmi, 0, HDMI_AUD_CTS3_N_SHIFT_MASK, HDMI_AUD_CTS3); + + /* Use automatic CTS generation mode when CTS is not set */ + if (cts) + hdmi_writeb(hdmi, ((cts >> 16) & + HDMI_AUD_CTS3_AUDCTS19_16_MASK) | + HDMI_AUD_CTS3_CTS_MANUAL, + HDMI_AUD_CTS3); + else + hdmi_writeb(hdmi, 0, HDMI_AUD_CTS3); + hdmi_writeb(hdmi, (cts >> 8) & 0xff, HDMI_AUD_CTS2); + hdmi_writeb(hdmi, cts & 0xff, HDMI_AUD_CTS1); + + hdmi_writeb(hdmi, (n >> 16) & 0x0f, HDMI_AUD_N3); + hdmi_writeb(hdmi, (n >> 8) & 0xff, HDMI_AUD_N2); + hdmi_writeb(hdmi, n & 0xff, HDMI_AUD_N1); +} + +static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk) +{ + unsigned int n = (128 * freq) / 1000; + unsigned int mult = 1; + + while (freq > 48000) { + mult *= 2; + freq /= 2; + } + + switch (freq) { + case 32000: + if (pixel_clk == 25175000) + n = 4576; + else if (pixel_clk == 27027000) + n = 4096; + else if (pixel_clk == 74176000 || pixel_clk == 148352000) + n = 11648; + else + n = 4096; + n *= mult; + break; + + case 44100: + if (pixel_clk == 25175000) + n = 7007; + else if (pixel_clk == 74176000) + n = 17836; + else if (pixel_clk == 148352000) + n = 8918; + else + n = 6272; + n *= mult; + break; + + case 48000: + if (pixel_clk == 25175000) + n = 6864; + else if (pixel_clk == 27027000) + n = 6144; + else if (pixel_clk == 74176000) + n = 11648; + else if (pixel_clk == 148352000) + n = 5824; + else + n = 6144; + n *= mult; + break; + + default: + break; + } + + return n; +} + +/* + * When transmitting IEC60958 linear PCM audio, these registers allow to + * configure the channel status information of all the channel status + * bits in the IEC60958 frame. For the moment this configuration is only + * used when the I2S audio interface, General Purpose Audio (GPA), + * or AHB audio DMA (AHBAUDDMA) interface is active + * (for S/PDIF interface this information comes from the stream). + */ +void dw_hdmi_set_channel_status(struct dw_hdmi *hdmi, + u8 *channel_status) +{ + /* + * Set channel status register for frequency and word length. + * Use default values for other registers. + */ + hdmi_writeb(hdmi, channel_status[3], HDMI_FC_AUDSCHNLS7); + hdmi_writeb(hdmi, channel_status[4], HDMI_FC_AUDSCHNLS8); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_channel_status); + +static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi, + unsigned long pixel_clk, unsigned int sample_rate) +{ + unsigned long ftdms = pixel_clk; + unsigned int n, cts; + u8 config3; + u64 tmp; + + n = hdmi_compute_n(sample_rate, pixel_clk); + + config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID); + + /* Only compute CTS when using internal AHB audio */ + if (config3 & HDMI_CONFIG3_AHBAUDDMA) { + /* + * Compute the CTS value from the N value. Note that CTS and N + * can be up to 20 bits in total, so we need 64-bit math. Also + * note that our TDMS clock is not fully accurate; it is + * accurate to kHz. This can introduce an unnecessary remainder + * in the calculation below, so we don't try to warn about that. + */ + tmp = (u64)ftdms * n; + do_div(tmp, 128 * sample_rate); + cts = tmp; + + dev_dbg(hdmi->dev, "%s: fs=%uHz ftdms=%lu.%03luMHz N=%d cts=%d\n", + __func__, sample_rate, + ftdms / 1000000, (ftdms / 1000) % 1000, + n, cts); + } else { + cts = 0; + } + + spin_lock_irq(&hdmi->audio_lock); + hdmi->audio_n = n; + hdmi->audio_cts = cts; + hdmi_set_cts_n(hdmi, cts, hdmi->audio_enable ? n : 0); + spin_unlock_irq(&hdmi->audio_lock); +} + +static void hdmi_init_clk_regenerator(struct dw_hdmi *hdmi) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi_set_clk_regenerator(hdmi, 74250000, hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); +} + +static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock, + hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); +} + +void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi->sample_rate = rate; + hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock, + hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_rate); + +void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt) +{ + u8 layout; + + mutex_lock(&hdmi->audio_mutex); + + /* + * For >2 channel PCM audio, we need to select layout 1 + * and set an appropriate channel map. + */ + if (cnt > 2) + layout = HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT1; + else + layout = HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT0; + + hdmi_modb(hdmi, layout, HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_MASK, + HDMI_FC_AUDSCONF); + + /* Set the audio infoframes channel count */ + hdmi_modb(hdmi, (cnt - 1) << HDMI_FC_AUDICONF0_CC_OFFSET, + HDMI_FC_AUDICONF0_CC_MASK, HDMI_FC_AUDICONF0); + + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_channel_count); + +void dw_hdmi_set_channel_allocation(struct dw_hdmi *hdmi, unsigned int ca) +{ + mutex_lock(&hdmi->audio_mutex); + + hdmi_writeb(hdmi, ca, HDMI_FC_AUDICONF2); + + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_channel_allocation); + +static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi, bool enable) +{ + if (enable) + hdmi->mc_clkdis &= ~HDMI_MC_CLKDIS_AUDCLK_DISABLE; + else + hdmi->mc_clkdis |= HDMI_MC_CLKDIS_AUDCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); +} + +static u8 *hdmi_audio_get_eld(struct dw_hdmi *hdmi) +{ + if (!hdmi->curr_conn) + return NULL; + + return hdmi->curr_conn->eld; +} + +static void dw_hdmi_ahb_audio_enable(struct dw_hdmi *hdmi) +{ + hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); +} + +static void dw_hdmi_ahb_audio_disable(struct dw_hdmi *hdmi) +{ + hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0); +} + +static void dw_hdmi_i2s_audio_enable(struct dw_hdmi *hdmi) +{ + hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); + hdmi_enable_audio_clk(hdmi, true); +} + +static void dw_hdmi_i2s_audio_disable(struct dw_hdmi *hdmi) +{ + hdmi_enable_audio_clk(hdmi, false); +} + +void dw_hdmi_audio_enable(struct dw_hdmi *hdmi) +{ + unsigned long flags; + + spin_lock_irqsave(&hdmi->audio_lock, flags); + hdmi->audio_enable = true; + if (hdmi->enable_audio) + hdmi->enable_audio(hdmi); + spin_unlock_irqrestore(&hdmi->audio_lock, flags); +} +EXPORT_SYMBOL_GPL(dw_hdmi_audio_enable); + +void dw_hdmi_audio_disable(struct dw_hdmi *hdmi) +{ + unsigned long flags; + + spin_lock_irqsave(&hdmi->audio_lock, flags); + hdmi->audio_enable = false; + if (hdmi->disable_audio) + hdmi->disable_audio(hdmi); + spin_unlock_irqrestore(&hdmi->audio_lock, flags); +} +EXPORT_SYMBOL_GPL(dw_hdmi_audio_disable); + +static bool hdmi_bus_fmt_is_rgb(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_RGB161616_1X48: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv444(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_YUV16_1X48: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv422(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_UYVY12_1X24: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv420(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + return true; + + default: + return false; + } +} + +static int hdmi_bus_fmt_color_depth(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + return 8; + + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + return 10; + + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_UYVY12_1X24: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + return 12; + + case MEDIA_BUS_FMT_RGB161616_1X48: + case MEDIA_BUS_FMT_YUV16_1X48: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + return 16; + + default: + return 0; + } +} + +/* + * this submodule is responsible for the video data synchronization. + * for example, for RGB 4:4:4 input, the data map is defined as + * pin{47~40} <==> R[7:0] + * pin{31~24} <==> G[7:0] + * pin{15~8} <==> B[7:0] + */ +static void hdmi_video_sample(struct dw_hdmi *hdmi) +{ + int color_format = 0; + u8 val; + + switch (hdmi->hdmi_data.enc_in_bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + color_format = 0x01; + break; + case MEDIA_BUS_FMT_RGB101010_1X30: + color_format = 0x03; + break; + case MEDIA_BUS_FMT_RGB121212_1X36: + color_format = 0x05; + break; + case MEDIA_BUS_FMT_RGB161616_1X48: + color_format = 0x07; + break; + + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + color_format = 0x09; + break; + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + color_format = 0x0B; + break; + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + color_format = 0x0D; + break; + case MEDIA_BUS_FMT_YUV16_1X48: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + color_format = 0x0F; + break; + + case MEDIA_BUS_FMT_UYVY8_1X16: + color_format = 0x16; + break; + case MEDIA_BUS_FMT_UYVY10_1X20: + color_format = 0x14; + break; + case MEDIA_BUS_FMT_UYVY12_1X24: + color_format = 0x12; + break; + + default: + return; + } + + val = HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE | + ((color_format << HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET) & + HDMI_TX_INVID0_VIDEO_MAPPING_MASK); + hdmi_writeb(hdmi, val, HDMI_TX_INVID0); + + /* Enable TX stuffing: When DE is inactive, fix the output data to 0 */ + val = HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE | + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE | + HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE; + hdmi_writeb(hdmi, val, HDMI_TX_INSTUFFING); + hdmi_writeb(hdmi, 0x0, HDMI_TX_GYDATA0); + hdmi_writeb(hdmi, 0x0, HDMI_TX_GYDATA1); + hdmi_writeb(hdmi, 0x0, HDMI_TX_RCRDATA0); + hdmi_writeb(hdmi, 0x0, HDMI_TX_RCRDATA1); + hdmi_writeb(hdmi, 0x0, HDMI_TX_BCBDATA0); + hdmi_writeb(hdmi, 0x0, HDMI_TX_BCBDATA1); +} + +static int is_color_space_conversion(struct dw_hdmi *hdmi) +{ + struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data; + bool is_input_rgb, is_output_rgb; + + is_input_rgb = hdmi_bus_fmt_is_rgb(hdmi_data->enc_in_bus_format); + is_output_rgb = hdmi_bus_fmt_is_rgb(hdmi_data->enc_out_bus_format); + + return (is_input_rgb != is_output_rgb) || + (is_input_rgb && is_output_rgb && hdmi_data->rgb_limited_range); +} + +static int is_color_space_decimation(struct dw_hdmi *hdmi) +{ + if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) + return 0; + + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_in_bus_format) || + hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_in_bus_format)) + return 1; + + return 0; +} + +static int is_color_space_interpolation(struct dw_hdmi *hdmi) +{ + if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_in_bus_format)) + return 0; + + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format) || + hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) + return 1; + + return 0; +} + +static bool is_csc_needed(struct dw_hdmi *hdmi) +{ + return is_color_space_conversion(hdmi) || + is_color_space_decimation(hdmi) || + is_color_space_interpolation(hdmi); +} + +static void dw_hdmi_update_csc_coeffs(struct dw_hdmi *hdmi) +{ + const u16 (*csc_coeff)[3][4] = &csc_coeff_default; + bool is_input_rgb, is_output_rgb; + unsigned i; + u32 csc_scale = 1; + + is_input_rgb = hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_in_bus_format); + is_output_rgb = hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format); + + if (!is_input_rgb && is_output_rgb) { + if (hdmi->hdmi_data.enc_out_encoding == V4L2_YCBCR_ENC_601) + csc_coeff = &csc_coeff_rgb_out_eitu601; + else + csc_coeff = &csc_coeff_rgb_out_eitu709; + } else if (is_input_rgb && !is_output_rgb) { + if (hdmi->hdmi_data.enc_out_encoding == V4L2_YCBCR_ENC_601) + csc_coeff = &csc_coeff_rgb_in_eitu601; + else + csc_coeff = &csc_coeff_rgb_in_eitu709; + csc_scale = 0; + } else if (is_input_rgb && is_output_rgb && + hdmi->hdmi_data.rgb_limited_range) { + csc_coeff = &csc_coeff_rgb_full_to_rgb_limited; + } + + /* The CSC registers are sequential, alternating MSB then LSB */ + for (i = 0; i < ARRAY_SIZE(csc_coeff_default[0]); i++) { + u16 coeff_a = (*csc_coeff)[0][i]; + u16 coeff_b = (*csc_coeff)[1][i]; + u16 coeff_c = (*csc_coeff)[2][i]; + + hdmi_writeb(hdmi, coeff_a & 0xff, HDMI_CSC_COEF_A1_LSB + i * 2); + hdmi_writeb(hdmi, coeff_a >> 8, HDMI_CSC_COEF_A1_MSB + i * 2); + hdmi_writeb(hdmi, coeff_b & 0xff, HDMI_CSC_COEF_B1_LSB + i * 2); + hdmi_writeb(hdmi, coeff_b >> 8, HDMI_CSC_COEF_B1_MSB + i * 2); + hdmi_writeb(hdmi, coeff_c & 0xff, HDMI_CSC_COEF_C1_LSB + i * 2); + hdmi_writeb(hdmi, coeff_c >> 8, HDMI_CSC_COEF_C1_MSB + i * 2); + } + + hdmi_modb(hdmi, csc_scale, HDMI_CSC_SCALE_CSCSCALE_MASK, + HDMI_CSC_SCALE); +} + +static void hdmi_video_csc(struct dw_hdmi *hdmi) +{ + int color_depth = 0; + int interpolation = HDMI_CSC_CFG_INTMODE_DISABLE; + int decimation = 0; + + /* YCC422 interpolation to 444 mode */ + if (is_color_space_interpolation(hdmi)) + interpolation = HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA1; + else if (is_color_space_decimation(hdmi)) + decimation = HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA3; + + switch (hdmi_bus_fmt_color_depth(hdmi->hdmi_data.enc_out_bus_format)) { + case 8: + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_24BPP; + break; + case 10: + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_30BPP; + break; + case 12: + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_36BPP; + break; + case 16: + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_48BPP; + break; + + default: + return; + } + + /* Configure the CSC registers */ + hdmi_writeb(hdmi, interpolation | decimation, HDMI_CSC_CFG); + hdmi_modb(hdmi, color_depth, HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK, + HDMI_CSC_SCALE); + + dw_hdmi_update_csc_coeffs(hdmi); +} + +/* + * HDMI video packetizer is used to packetize the data. + * for example, if input is YCC422 mode or repeater is used, + * data should be repacked this module can be bypassed. + */ +static void hdmi_video_packetize(struct dw_hdmi *hdmi) +{ + unsigned int color_depth = 0; + unsigned int remap_size = HDMI_VP_REMAP_YCC422_16bit; + unsigned int output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_PP; + struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data; + u8 val, vp_conf; + + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format) || + hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format) || + hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) { + switch (hdmi_bus_fmt_color_depth( + hdmi->hdmi_data.enc_out_bus_format)) { + case 8: + color_depth = 4; + output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; + break; + case 10: + color_depth = 5; + break; + case 12: + color_depth = 6; + break; + case 16: + color_depth = 7; + break; + default: + output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; + } + } else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) { + switch (hdmi_bus_fmt_color_depth( + hdmi->hdmi_data.enc_out_bus_format)) { + case 0: + case 8: + remap_size = HDMI_VP_REMAP_YCC422_16bit; + break; + case 10: + remap_size = HDMI_VP_REMAP_YCC422_20bit; + break; + case 12: + remap_size = HDMI_VP_REMAP_YCC422_24bit; + break; + + default: + return; + } + output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422; + } else { + return; + } + + /* set the packetizer registers */ + val = ((color_depth << HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET) & + HDMI_VP_PR_CD_COLOR_DEPTH_MASK) | + ((hdmi_data->pix_repet_factor << + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_OFFSET) & + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK); + hdmi_writeb(hdmi, val, HDMI_VP_PR_CD); + + hdmi_modb(hdmi, HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE, + HDMI_VP_STUFF_PR_STUFFING_MASK, HDMI_VP_STUFF); + + /* Data from pixel repeater block */ + if (hdmi_data->pix_repet_factor > 1) { + vp_conf = HDMI_VP_CONF_PR_EN_ENABLE | + HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER; + } else { /* data from packetizer block */ + vp_conf = HDMI_VP_CONF_PR_EN_DISABLE | + HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER; + } + + hdmi_modb(hdmi, vp_conf, + HDMI_VP_CONF_PR_EN_MASK | + HDMI_VP_CONF_BYPASS_SELECT_MASK, HDMI_VP_CONF); + + hdmi_modb(hdmi, 1 << HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET, + HDMI_VP_STUFF_IDEFAULT_PHASE_MASK, HDMI_VP_STUFF); + + hdmi_writeb(hdmi, remap_size, HDMI_VP_REMAP); + + if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_PP) { + vp_conf = HDMI_VP_CONF_BYPASS_EN_DISABLE | + HDMI_VP_CONF_PP_EN_ENABLE | + HDMI_VP_CONF_YCC422_EN_DISABLE; + } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422) { + vp_conf = HDMI_VP_CONF_BYPASS_EN_DISABLE | + HDMI_VP_CONF_PP_EN_DISABLE | + HDMI_VP_CONF_YCC422_EN_ENABLE; + } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS) { + vp_conf = HDMI_VP_CONF_BYPASS_EN_ENABLE | + HDMI_VP_CONF_PP_EN_DISABLE | + HDMI_VP_CONF_YCC422_EN_DISABLE; + } else { + return; + } + + hdmi_modb(hdmi, vp_conf, + HDMI_VP_CONF_BYPASS_EN_MASK | HDMI_VP_CONF_PP_EN_ENMASK | + HDMI_VP_CONF_YCC422_EN_MASK, HDMI_VP_CONF); + + hdmi_modb(hdmi, HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE | + HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE, + HDMI_VP_STUFF_PP_STUFFING_MASK | + HDMI_VP_STUFF_YCC422_STUFFING_MASK, HDMI_VP_STUFF); + + hdmi_modb(hdmi, output_select, HDMI_VP_CONF_OUTPUT_SELECTOR_MASK, + HDMI_VP_CONF); +} + +/* ----------------------------------------------------------------------------- + * Synopsys PHY Handling + */ + +static inline void hdmi_phy_test_clear(struct dw_hdmi *hdmi, + unsigned char bit) +{ + hdmi_modb(hdmi, bit << HDMI_PHY_TST0_TSTCLR_OFFSET, + HDMI_PHY_TST0_TSTCLR_MASK, HDMI_PHY_TST0); +} + +static bool hdmi_phy_wait_i2c_done(struct dw_hdmi *hdmi, int msec) +{ + u32 val; + + while ((val = hdmi_readb(hdmi, HDMI_IH_I2CMPHY_STAT0) & 0x3) == 0) { + if (msec-- == 0) + return false; + udelay(1000); + } + hdmi_writeb(hdmi, val, HDMI_IH_I2CMPHY_STAT0); + + return true; +} + +void dw_hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data, + unsigned char addr) +{ + hdmi_writeb(hdmi, 0xFF, HDMI_IH_I2CMPHY_STAT0); + hdmi_writeb(hdmi, addr, HDMI_PHY_I2CM_ADDRESS_ADDR); + hdmi_writeb(hdmi, (unsigned char)(data >> 8), + HDMI_PHY_I2CM_DATAO_1_ADDR); + hdmi_writeb(hdmi, (unsigned char)(data >> 0), + HDMI_PHY_I2CM_DATAO_0_ADDR); + hdmi_writeb(hdmi, HDMI_PHY_I2CM_OPERATION_ADDR_WRITE, + HDMI_PHY_I2CM_OPERATION_ADDR); + hdmi_phy_wait_i2c_done(hdmi, 1000); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_write); + +/* Filter out invalid setups to avoid configuring SCDC and scrambling */ +static bool dw_hdmi_support_scdc(struct dw_hdmi *hdmi, + const struct drm_display_info *display) +{ + /* Completely disable SCDC support for older controllers */ + if (hdmi->version < 0x200a) + return false; + + /* Disable if no DDC bus */ + if (!hdmi->ddc) + return false; + + /* Disable if SCDC is not supported, or if an HF-VSDB block is absent */ + if (!display->hdmi.scdc.supported || + !display->hdmi.scdc.scrambling.supported) + return false; + + /* + * Disable if display only support low TMDS rates and scrambling + * for low rates is not supported either + */ + if (!display->hdmi.scdc.scrambling.low_rates && + display->max_tmds_clock <= 340000) + return false; + + return true; +} + +/* + * HDMI2.0 Specifies the following procedure for High TMDS Bit Rates: + * - The Source shall suspend transmission of the TMDS clock and data + * - The Source shall write to the TMDS_Bit_Clock_Ratio bit to change it + * from a 0 to a 1 or from a 1 to a 0 + * - The Source shall allow a minimum of 1 ms and a maximum of 100 ms from + * the time the TMDS_Bit_Clock_Ratio bit is written until resuming + * transmission of TMDS clock and data + * + * To respect the 100ms maximum delay, the dw_hdmi_set_high_tmds_clock_ratio() + * helper should called right before enabling the TMDS Clock and Data in + * the PHY configuration callback. + */ +void dw_hdmi_set_high_tmds_clock_ratio(struct dw_hdmi *hdmi, + const struct drm_display_info *display) +{ + unsigned long mtmdsclock = hdmi->hdmi_data.video_mode.mtmdsclock; + + /* Control for TMDS Bit Period/TMDS Clock-Period Ratio */ + if (dw_hdmi_support_scdc(hdmi, display)) { + if (mtmdsclock > HDMI14_MAX_TMDSCLK) + drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 1); + else + drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 0); + } +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_high_tmds_clock_ratio); + +static void dw_hdmi_phy_enable_powerdown(struct dw_hdmi *hdmi, bool enable) +{ + hdmi_mask_writeb(hdmi, !enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_PDZ_OFFSET, + HDMI_PHY_CONF0_PDZ_MASK); +} + +static void dw_hdmi_phy_enable_tmds(struct dw_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_ENTMDS_OFFSET, + HDMI_PHY_CONF0_ENTMDS_MASK); +} + +static void dw_hdmi_phy_enable_svsret(struct dw_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_SVSRET_OFFSET, + HDMI_PHY_CONF0_SVSRET_MASK); +} + +void dw_hdmi_phy_gen2_pddq(struct dw_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET, + HDMI_PHY_CONF0_GEN2_PDDQ_MASK); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen2_pddq); + +void dw_hdmi_phy_gen2_txpwron(struct dw_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET, + HDMI_PHY_CONF0_GEN2_TXPWRON_MASK); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen2_txpwron); + +static void dw_hdmi_phy_sel_data_en_pol(struct dw_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_SELDATAENPOL_OFFSET, + HDMI_PHY_CONF0_SELDATAENPOL_MASK); +} + +static void dw_hdmi_phy_sel_interface_control(struct dw_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_SELDIPIF_OFFSET, + HDMI_PHY_CONF0_SELDIPIF_MASK); +} + +void dw_hdmi_phy_reset(struct dw_hdmi *hdmi) +{ + /* PHY reset. The reset signal is active high on Gen2 PHYs. */ + hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ); + hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_reset); + +void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address) +{ + hdmi_phy_test_clear(hdmi, 1); + hdmi_writeb(hdmi, address, HDMI_PHY_I2CM_SLAVE_ADDR); + hdmi_phy_test_clear(hdmi, 0); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_set_addr); + +static void dw_hdmi_phy_power_off(struct dw_hdmi *hdmi) +{ + const struct dw_hdmi_phy_data *phy = hdmi->phy.data; + unsigned int i; + u16 val; + + if (phy->gen == 1) { + dw_hdmi_phy_enable_tmds(hdmi, 0); + dw_hdmi_phy_enable_powerdown(hdmi, true); + return; + } + + dw_hdmi_phy_gen2_txpwron(hdmi, 0); + + /* + * Wait for TX_PHY_LOCK to be deasserted to indicate that the PHY went + * to low power mode. + */ + for (i = 0; i < 5; ++i) { + val = hdmi_readb(hdmi, HDMI_PHY_STAT0); + if (!(val & HDMI_PHY_TX_PHY_LOCK)) + break; + + usleep_range(1000, 2000); + } + + if (val & HDMI_PHY_TX_PHY_LOCK) + dev_warn(hdmi->dev, "PHY failed to power down\n"); + else + dev_dbg(hdmi->dev, "PHY powered down in %u iterations\n", i); + + dw_hdmi_phy_gen2_pddq(hdmi, 1); +} + +static int dw_hdmi_phy_power_on(struct dw_hdmi *hdmi) +{ + const struct dw_hdmi_phy_data *phy = hdmi->phy.data; + unsigned int i; + u8 val; + + if (phy->gen == 1) { + dw_hdmi_phy_enable_powerdown(hdmi, false); + + /* Toggle TMDS enable. */ + dw_hdmi_phy_enable_tmds(hdmi, 0); + dw_hdmi_phy_enable_tmds(hdmi, 1); + return 0; + } + + dw_hdmi_phy_gen2_txpwron(hdmi, 1); + dw_hdmi_phy_gen2_pddq(hdmi, 0); + + /* Wait for PHY PLL lock */ + for (i = 0; i < 5; ++i) { + val = hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK; + if (val) + break; + + usleep_range(1000, 2000); + } + + if (!val) { + dev_err(hdmi->dev, "PHY PLL failed to lock\n"); + return -ETIMEDOUT; + } + + dev_dbg(hdmi->dev, "PHY PLL locked %u iterations\n", i); + return 0; +} + +/* + * PHY configuration function for the DWC HDMI 3D TX PHY. Based on the available + * information the DWC MHL PHY has the same register layout and is thus also + * supported by this function. + */ +static int hdmi_phy_configure_dwc_hdmi_3d_tx(struct dw_hdmi *hdmi, + const struct dw_hdmi_plat_data *pdata, + unsigned long mpixelclock) +{ + const struct dw_hdmi_mpll_config *mpll_config = pdata->mpll_cfg; + const struct dw_hdmi_curr_ctrl *curr_ctrl = pdata->cur_ctr; + const struct dw_hdmi_phy_config *phy_config = pdata->phy_config; + + /* TOFIX Will need 420 specific PHY configuration tables */ + + /* PLL/MPLL Cfg - always match on final entry */ + for (; mpll_config->mpixelclock != ~0UL; mpll_config++) + if (mpixelclock <= mpll_config->mpixelclock) + break; + + for (; curr_ctrl->mpixelclock != ~0UL; curr_ctrl++) + if (mpixelclock <= curr_ctrl->mpixelclock) + break; + + for (; phy_config->mpixelclock != ~0UL; phy_config++) + if (mpixelclock <= phy_config->mpixelclock) + break; + + if (mpll_config->mpixelclock == ~0UL || + curr_ctrl->mpixelclock == ~0UL || + phy_config->mpixelclock == ~0UL) + return -EINVAL; + + dw_hdmi_phy_i2c_write(hdmi, mpll_config->res[0].cpce, + HDMI_3D_TX_PHY_CPCE_CTRL); + dw_hdmi_phy_i2c_write(hdmi, mpll_config->res[0].gmp, + HDMI_3D_TX_PHY_GMPCTRL); + dw_hdmi_phy_i2c_write(hdmi, curr_ctrl->curr[0], + HDMI_3D_TX_PHY_CURRCTRL); + + dw_hdmi_phy_i2c_write(hdmi, 0, HDMI_3D_TX_PHY_PLLPHBYCTRL); + dw_hdmi_phy_i2c_write(hdmi, HDMI_3D_TX_PHY_MSM_CTRL_CKO_SEL_FB_CLK, + HDMI_3D_TX_PHY_MSM_CTRL); + + dw_hdmi_phy_i2c_write(hdmi, phy_config->term, HDMI_3D_TX_PHY_TXTERM); + dw_hdmi_phy_i2c_write(hdmi, phy_config->sym_ctr, + HDMI_3D_TX_PHY_CKSYMTXCTRL); + dw_hdmi_phy_i2c_write(hdmi, phy_config->vlev_ctr, + HDMI_3D_TX_PHY_VLEVCTRL); + + /* Override and disable clock termination. */ + dw_hdmi_phy_i2c_write(hdmi, HDMI_3D_TX_PHY_CKCALCTRL_OVERRIDE, + HDMI_3D_TX_PHY_CKCALCTRL); + + return 0; +} + +static int hdmi_phy_configure(struct dw_hdmi *hdmi, + const struct drm_display_info *display) +{ + const struct dw_hdmi_phy_data *phy = hdmi->phy.data; + const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; + unsigned long mpixelclock = hdmi->hdmi_data.video_mode.mpixelclock; + unsigned long mtmdsclock = hdmi->hdmi_data.video_mode.mtmdsclock; + int ret; + + dw_hdmi_phy_power_off(hdmi); + + dw_hdmi_set_high_tmds_clock_ratio(hdmi, display); + + /* Leave low power consumption mode by asserting SVSRET. */ + if (phy->has_svsret) + dw_hdmi_phy_enable_svsret(hdmi, 1); + + dw_hdmi_phy_reset(hdmi); + + hdmi_writeb(hdmi, HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST); + + dw_hdmi_phy_i2c_set_addr(hdmi, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2); + + /* Write to the PHY as configured by the platform */ + if (pdata->configure_phy) + ret = pdata->configure_phy(hdmi, pdata->priv_data, mpixelclock); + else + ret = phy->configure(hdmi, pdata, mpixelclock); + if (ret) { + dev_err(hdmi->dev, "PHY configuration failed (clock %lu)\n", + mpixelclock); + return ret; + } + + /* Wait for resuming transmission of TMDS clock and data */ + if (mtmdsclock > HDMI14_MAX_TMDSCLK) + msleep(100); + + return dw_hdmi_phy_power_on(hdmi); +} + +static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, + const struct drm_display_info *display, + const struct drm_display_mode *mode) +{ + int i, ret; + + /* HDMI Phy spec says to do the phy initialization sequence twice */ + for (i = 0; i < 2; i++) { + dw_hdmi_phy_sel_data_en_pol(hdmi, 1); + dw_hdmi_phy_sel_interface_control(hdmi, 0); + + ret = hdmi_phy_configure(hdmi, display); + if (ret) + return ret; + } + + return 0; +} + +static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data) +{ + dw_hdmi_phy_power_off(hdmi); +} + +enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi, + void *data) +{ + return hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD ? + connector_status_connected : connector_status_disconnected; +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_read_hpd); + +void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data, + bool force, bool disabled, bool rxsense) +{ + u8 old_mask = hdmi->phy_mask; + + if (force || disabled || !rxsense) + hdmi->phy_mask |= HDMI_PHY_RX_SENSE; + else + hdmi->phy_mask &= ~HDMI_PHY_RX_SENSE; + + if (old_mask != hdmi->phy_mask) + hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_update_hpd); + +void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data) +{ + /* + * Configure the PHY RX SENSE and HPD interrupts polarities and clear + * any pending interrupt. + */ + hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE, HDMI_PHY_POL0); + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, + HDMI_IH_PHY_STAT0); + + /* Enable cable hot plug irq. */ + hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0); + + /* Clear and unmute interrupts. */ + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, + HDMI_IH_PHY_STAT0); + hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE), + HDMI_IH_MUTE_PHY_STAT0); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_setup_hpd); + +static const struct dw_hdmi_phy_ops dw_hdmi_synopsys_phy_ops = { + .init = dw_hdmi_phy_init, + .disable = dw_hdmi_phy_disable, + .read_hpd = dw_hdmi_phy_read_hpd, + .update_hpd = dw_hdmi_phy_update_hpd, + .setup_hpd = dw_hdmi_phy_setup_hpd, +}; + +/* ----------------------------------------------------------------------------- + * HDMI TX Setup + */ + +static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi) +{ + u8 de; + + if (hdmi->hdmi_data.video_mode.mdataenablepolarity) + de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH; + else + de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW; + + /* disable rx detect */ + hdmi_modb(hdmi, HDMI_A_HDCPCFG0_RXDETECT_DISABLE, + HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0); + + hdmi_modb(hdmi, de, HDMI_A_VIDPOLCFG_DATAENPOL_MASK, HDMI_A_VIDPOLCFG); + + hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1); +} + +static void hdmi_config_AVI(struct dw_hdmi *hdmi, + const struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + struct hdmi_avi_infoframe frame; + u8 val; + + /* Initialise info frame from DRM mode */ + drm_hdmi_avi_infoframe_from_display_mode(&frame, connector, mode); + + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) { + drm_hdmi_avi_infoframe_quant_range(&frame, connector, mode, + hdmi->hdmi_data.rgb_limited_range ? + HDMI_QUANTIZATION_RANGE_LIMITED : + HDMI_QUANTIZATION_RANGE_FULL); + } else { + frame.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT; + frame.ycc_quantization_range = + HDMI_YCC_QUANTIZATION_RANGE_LIMITED; + } + + if (hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) + frame.colorspace = HDMI_COLORSPACE_YUV444; + else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) + frame.colorspace = HDMI_COLORSPACE_YUV422; + else if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) + frame.colorspace = HDMI_COLORSPACE_YUV420; + else + frame.colorspace = HDMI_COLORSPACE_RGB; + + /* Set up colorimetry */ + if (!hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) { + switch (hdmi->hdmi_data.enc_out_encoding) { + case V4L2_YCBCR_ENC_601: + if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_XV601) + frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; + else + frame.colorimetry = HDMI_COLORIMETRY_ITU_601; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + break; + case V4L2_YCBCR_ENC_709: + if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_XV709) + frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; + else + frame.colorimetry = HDMI_COLORIMETRY_ITU_709; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_709; + break; + default: /* Carries no data */ + frame.colorimetry = HDMI_COLORIMETRY_ITU_601; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + break; + } + } else { + frame.colorimetry = HDMI_COLORIMETRY_NONE; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + } + + /* + * The Designware IP uses a different byte format from standard + * AVI info frames, though generally the bits are in the correct + * bytes. + */ + + /* + * AVI data byte 1 differences: Colorspace in bits 0,1 rather than 5,6, + * scan info in bits 4,5 rather than 0,1 and active aspect present in + * bit 6 rather than 4. + */ + val = (frame.scan_mode & 3) << 4 | (frame.colorspace & 3); + if (frame.active_aspect & 15) + val |= HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT; + if (frame.top_bar || frame.bottom_bar) + val |= HDMI_FC_AVICONF0_BAR_DATA_HORIZ_BAR; + if (frame.left_bar || frame.right_bar) + val |= HDMI_FC_AVICONF0_BAR_DATA_VERT_BAR; + hdmi_writeb(hdmi, val, HDMI_FC_AVICONF0); + + /* AVI data byte 2 differences: none */ + val = ((frame.colorimetry & 0x3) << 6) | + ((frame.picture_aspect & 0x3) << 4) | + (frame.active_aspect & 0xf); + hdmi_writeb(hdmi, val, HDMI_FC_AVICONF1); + + /* AVI data byte 3 differences: none */ + val = ((frame.extended_colorimetry & 0x7) << 4) | + ((frame.quantization_range & 0x3) << 2) | + (frame.nups & 0x3); + if (frame.itc) + val |= HDMI_FC_AVICONF2_IT_CONTENT_VALID; + hdmi_writeb(hdmi, val, HDMI_FC_AVICONF2); + + /* AVI data byte 4 differences: none */ + val = frame.video_code & 0x7f; + hdmi_writeb(hdmi, val, HDMI_FC_AVIVID); + + /* AVI Data Byte 5- set up input and output pixel repetition */ + val = (((hdmi->hdmi_data.video_mode.mpixelrepetitioninput + 1) << + HDMI_FC_PRCONF_INCOMING_PR_FACTOR_OFFSET) & + HDMI_FC_PRCONF_INCOMING_PR_FACTOR_MASK) | + ((hdmi->hdmi_data.video_mode.mpixelrepetitionoutput << + HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_OFFSET) & + HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_MASK); + hdmi_writeb(hdmi, val, HDMI_FC_PRCONF); + + /* + * AVI data byte 5 differences: content type in 0,1 rather than 4,5, + * ycc range in bits 2,3 rather than 6,7 + */ + val = ((frame.ycc_quantization_range & 0x3) << 2) | + (frame.content_type & 0x3); + hdmi_writeb(hdmi, val, HDMI_FC_AVICONF3); + + /* AVI Data Bytes 6-13 */ + hdmi_writeb(hdmi, frame.top_bar & 0xff, HDMI_FC_AVIETB0); + hdmi_writeb(hdmi, (frame.top_bar >> 8) & 0xff, HDMI_FC_AVIETB1); + hdmi_writeb(hdmi, frame.bottom_bar & 0xff, HDMI_FC_AVISBB0); + hdmi_writeb(hdmi, (frame.bottom_bar >> 8) & 0xff, HDMI_FC_AVISBB1); + hdmi_writeb(hdmi, frame.left_bar & 0xff, HDMI_FC_AVIELB0); + hdmi_writeb(hdmi, (frame.left_bar >> 8) & 0xff, HDMI_FC_AVIELB1); + hdmi_writeb(hdmi, frame.right_bar & 0xff, HDMI_FC_AVISRB0); + hdmi_writeb(hdmi, (frame.right_bar >> 8) & 0xff, HDMI_FC_AVISRB1); +} + +static void hdmi_config_vendor_specific_infoframe(struct dw_hdmi *hdmi, + const struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + struct hdmi_vendor_infoframe frame; + u8 buffer[10]; + ssize_t err; + + err = drm_hdmi_vendor_infoframe_from_display_mode(&frame, connector, + mode); + if (err < 0) + /* + * Going into that statement does not means vendor infoframe + * fails. It just informed us that vendor infoframe is not + * needed for the selected mode. Only 4k or stereoscopic 3D + * mode requires vendor infoframe. So just simply return. + */ + return; + + err = hdmi_vendor_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(hdmi->dev, "Failed to pack vendor infoframe: %zd\n", + err); + return; + } + hdmi_mask_writeb(hdmi, 0, HDMI_FC_DATAUTO0, HDMI_FC_DATAUTO0_VSD_OFFSET, + HDMI_FC_DATAUTO0_VSD_MASK); + + /* Set the length of HDMI vendor specific InfoFrame payload */ + hdmi_writeb(hdmi, buffer[2], HDMI_FC_VSDSIZE); + + /* Set 24bit IEEE Registration Identifier */ + hdmi_writeb(hdmi, buffer[4], HDMI_FC_VSDIEEEID0); + hdmi_writeb(hdmi, buffer[5], HDMI_FC_VSDIEEEID1); + hdmi_writeb(hdmi, buffer[6], HDMI_FC_VSDIEEEID2); + + /* Set HDMI_Video_Format and HDMI_VIC/3D_Structure */ + hdmi_writeb(hdmi, buffer[7], HDMI_FC_VSDPAYLOAD0); + hdmi_writeb(hdmi, buffer[8], HDMI_FC_VSDPAYLOAD1); + + if (frame.s3d_struct >= HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF) + hdmi_writeb(hdmi, buffer[9], HDMI_FC_VSDPAYLOAD2); + + /* Packet frame interpolation */ + hdmi_writeb(hdmi, 1, HDMI_FC_DATAUTO1); + + /* Auto packets per frame and line spacing */ + hdmi_writeb(hdmi, 0x11, HDMI_FC_DATAUTO2); + + /* Configures the Frame Composer On RDRB mode */ + hdmi_mask_writeb(hdmi, 1, HDMI_FC_DATAUTO0, HDMI_FC_DATAUTO0_VSD_OFFSET, + HDMI_FC_DATAUTO0_VSD_MASK); +} + +static void hdmi_config_drm_infoframe(struct dw_hdmi *hdmi, + const struct drm_connector *connector) +{ + const struct drm_connector_state *conn_state = connector->state; + struct hdmi_drm_infoframe frame; + u8 buffer[30]; + ssize_t err; + int i; + + if (!hdmi->plat_data->use_drm_infoframe) + return; + + hdmi_modb(hdmi, HDMI_FC_PACKET_TX_EN_DRM_DISABLE, + HDMI_FC_PACKET_TX_EN_DRM_MASK, HDMI_FC_PACKET_TX_EN); + + err = drm_hdmi_infoframe_set_hdr_metadata(&frame, conn_state); + if (err < 0) + return; + + err = hdmi_drm_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(hdmi->dev, "Failed to pack drm infoframe: %zd\n", err); + return; + } + + hdmi_writeb(hdmi, frame.version, HDMI_FC_DRM_HB0); + hdmi_writeb(hdmi, frame.length, HDMI_FC_DRM_HB1); + + for (i = 0; i < frame.length; i++) + hdmi_writeb(hdmi, buffer[4 + i], HDMI_FC_DRM_PB0 + i); + + hdmi_writeb(hdmi, 1, HDMI_FC_DRM_UP); + hdmi_modb(hdmi, HDMI_FC_PACKET_TX_EN_DRM_ENABLE, + HDMI_FC_PACKET_TX_EN_DRM_MASK, HDMI_FC_PACKET_TX_EN); +} + +static void hdmi_av_composer(struct dw_hdmi *hdmi, + const struct drm_display_info *display, + const struct drm_display_mode *mode) +{ + u8 inv_val, bytes; + const struct drm_hdmi_info *hdmi_info = &display->hdmi; + struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode; + int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len; + unsigned int vdisplay, hdisplay; + + vmode->mpixelclock = mode->clock * 1000; + + dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock); + + vmode->mtmdsclock = vmode->mpixelclock; + + if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) { + switch (hdmi_bus_fmt_color_depth( + hdmi->hdmi_data.enc_out_bus_format)) { + case 16: + vmode->mtmdsclock = vmode->mpixelclock * 2; + break; + case 12: + vmode->mtmdsclock = vmode->mpixelclock * 3 / 2; + break; + case 10: + vmode->mtmdsclock = vmode->mpixelclock * 5 / 4; + break; + } + } + + if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) + vmode->mtmdsclock /= 2; + + dev_dbg(hdmi->dev, "final tmdsclock = %d\n", vmode->mtmdsclock); + + /* Set up HDMI_FC_INVIDCONF */ + inv_val = (hdmi->hdmi_data.hdcp_enable || + (dw_hdmi_support_scdc(hdmi, display) && + (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK || + hdmi_info->scdc.scrambling.low_rates)) ? + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE : + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE); + + inv_val |= mode->flags & DRM_MODE_FLAG_PVSYNC ? + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW; + + inv_val |= mode->flags & DRM_MODE_FLAG_PHSYNC ? + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW; + + inv_val |= (vmode->mdataenablepolarity ? + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_LOW); + + if (hdmi->vic == 39) + inv_val |= HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH; + else + inv_val |= mode->flags & DRM_MODE_FLAG_INTERLACE ? + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW; + + inv_val |= mode->flags & DRM_MODE_FLAG_INTERLACE ? + HDMI_FC_INVIDCONF_IN_I_P_INTERLACED : + HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE; + + inv_val |= hdmi->sink_is_hdmi ? + HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE : + HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE; + + hdmi_writeb(hdmi, inv_val, HDMI_FC_INVIDCONF); + + hdisplay = mode->hdisplay; + hblank = mode->htotal - mode->hdisplay; + h_de_hs = mode->hsync_start - mode->hdisplay; + hsync_len = mode->hsync_end - mode->hsync_start; + + /* + * When we're setting a YCbCr420 mode, we need + * to adjust the horizontal timing to suit. + */ + if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) { + hdisplay /= 2; + hblank /= 2; + h_de_hs /= 2; + hsync_len /= 2; + } + + vdisplay = mode->vdisplay; + vblank = mode->vtotal - mode->vdisplay; + v_de_vs = mode->vsync_start - mode->vdisplay; + vsync_len = mode->vsync_end - mode->vsync_start; + + /* + * When we're setting an interlaced mode, we need + * to adjust the vertical timing to suit. + */ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + vdisplay /= 2; + vblank /= 2; + v_de_vs /= 2; + vsync_len /= 2; + } + + /* Scrambling Control */ + if (dw_hdmi_support_scdc(hdmi, display)) { + if (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK || + hdmi_info->scdc.scrambling.low_rates) { + /* + * HDMI2.0 Specifies the following procedure: + * After the Source Device has determined that + * SCDC_Present is set (=1), the Source Device should + * write the accurate Version of the Source Device + * to the Source Version field in the SCDCS. + * Source Devices compliant shall set the + * Source Version = 1. + */ + drm_scdc_readb(hdmi->ddc, SCDC_SINK_VERSION, + &bytes); + drm_scdc_writeb(hdmi->ddc, SCDC_SOURCE_VERSION, + min_t(u8, bytes, SCDC_MIN_SOURCE_VERSION)); + + /* Enabled Scrambling in the Sink */ + drm_scdc_set_scrambling(hdmi->ddc, 1); + + /* + * To activate the scrambler feature, you must ensure + * that the quasi-static configuration bit + * fc_invidconf.HDCP_keepout is set at configuration + * time, before the required mc_swrstzreq.tmdsswrst_req + * reset request is issued. + */ + hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, + HDMI_MC_SWRSTZ); + hdmi_writeb(hdmi, 1, HDMI_FC_SCRAMBLER_CTRL); + } else { + hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL); + hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, + HDMI_MC_SWRSTZ); + drm_scdc_set_scrambling(hdmi->ddc, 0); + } + } + + /* Set up horizontal active pixel width */ + hdmi_writeb(hdmi, hdisplay >> 8, HDMI_FC_INHACTV1); + hdmi_writeb(hdmi, hdisplay, HDMI_FC_INHACTV0); + + /* Set up vertical active lines */ + hdmi_writeb(hdmi, vdisplay >> 8, HDMI_FC_INVACTV1); + hdmi_writeb(hdmi, vdisplay, HDMI_FC_INVACTV0); + + /* Set up horizontal blanking pixel region width */ + hdmi_writeb(hdmi, hblank >> 8, HDMI_FC_INHBLANK1); + hdmi_writeb(hdmi, hblank, HDMI_FC_INHBLANK0); + + /* Set up vertical blanking pixel region width */ + hdmi_writeb(hdmi, vblank, HDMI_FC_INVBLANK); + + /* Set up HSYNC active edge delay width (in pixel clks) */ + hdmi_writeb(hdmi, h_de_hs >> 8, HDMI_FC_HSYNCINDELAY1); + hdmi_writeb(hdmi, h_de_hs, HDMI_FC_HSYNCINDELAY0); + + /* Set up VSYNC active edge delay (in lines) */ + hdmi_writeb(hdmi, v_de_vs, HDMI_FC_VSYNCINDELAY); + + /* Set up HSYNC active pulse width (in pixel clks) */ + hdmi_writeb(hdmi, hsync_len >> 8, HDMI_FC_HSYNCINWIDTH1); + hdmi_writeb(hdmi, hsync_len, HDMI_FC_HSYNCINWIDTH0); + + /* Set up VSYNC active edge delay (in lines) */ + hdmi_writeb(hdmi, vsync_len, HDMI_FC_VSYNCINWIDTH); +} + +/* HDMI Initialization Step B.4 */ +static void dw_hdmi_enable_video_path(struct dw_hdmi *hdmi) +{ + /* control period minimum duration */ + hdmi_writeb(hdmi, 12, HDMI_FC_CTRLDUR); + hdmi_writeb(hdmi, 32, HDMI_FC_EXCTRLDUR); + hdmi_writeb(hdmi, 1, HDMI_FC_EXCTRLSPAC); + + /* Set to fill TMDS data channels */ + hdmi_writeb(hdmi, 0x0B, HDMI_FC_CH0PREAM); + hdmi_writeb(hdmi, 0x16, HDMI_FC_CH1PREAM); + hdmi_writeb(hdmi, 0x21, HDMI_FC_CH2PREAM); + + /* Enable pixel clock and tmds data path */ + hdmi->mc_clkdis |= HDMI_MC_CLKDIS_HDCPCLK_DISABLE | + HDMI_MC_CLKDIS_CSCCLK_DISABLE | + HDMI_MC_CLKDIS_AUDCLK_DISABLE | + HDMI_MC_CLKDIS_PREPCLK_DISABLE | + HDMI_MC_CLKDIS_TMDSCLK_DISABLE; + hdmi->mc_clkdis &= ~HDMI_MC_CLKDIS_PIXELCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + + hdmi->mc_clkdis &= ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + + /* Enable csc path */ + if (is_csc_needed(hdmi)) { + hdmi->mc_clkdis &= ~HDMI_MC_CLKDIS_CSCCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + + hdmi_writeb(hdmi, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH, + HDMI_MC_FLOWCTRL); + } else { + hdmi->mc_clkdis |= HDMI_MC_CLKDIS_CSCCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + + hdmi_writeb(hdmi, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS, + HDMI_MC_FLOWCTRL); + } +} + +/* Workaround to clear the overflow condition */ +static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi) +{ + unsigned int count; + unsigned int i; + u8 val; + + /* + * Under some circumstances the Frame Composer arithmetic unit can miss + * an FC register write due to being busy processing the previous one. + * The issue can be worked around by issuing a TMDS software reset and + * then write one of the FC registers several times. + * + * The number of iterations matters and depends on the HDMI TX revision + * (and possibly on the platform). So far i.MX6Q (v1.30a), i.MX6DL + * (v1.31a) and multiple Allwinner SoCs (v1.32a) have been identified + * as needing the workaround, with 4 iterations for v1.30a and 1 + * iteration for others. + * The Amlogic Meson GX SoCs (v2.01a) have been identified as needing + * the workaround with a single iteration. + * The Rockchip RK3288 SoC (v2.00a) and RK3328/RK3399 SoCs (v2.11a) have + * been identified as needing the workaround with a single iteration. + */ + + switch (hdmi->version) { + case 0x130a: + count = 4; + break; + case 0x131a: + case 0x132a: + case 0x200a: + case 0x201a: + case 0x211a: + case 0x212a: + count = 1; + break; + default: + return; + } + + /* TMDS software reset */ + hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, HDMI_MC_SWRSTZ); + + val = hdmi_readb(hdmi, HDMI_FC_INVIDCONF); + for (i = 0; i < count; i++) + hdmi_writeb(hdmi, val, HDMI_FC_INVIDCONF); +} + +static void hdmi_disable_overflow_interrupts(struct dw_hdmi *hdmi) +{ + hdmi_writeb(hdmi, HDMI_IH_MUTE_FC_STAT2_OVERFLOW_MASK, + HDMI_IH_MUTE_FC_STAT2); +} + +static int dw_hdmi_setup(struct dw_hdmi *hdmi, + const struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + int ret; + + hdmi_disable_overflow_interrupts(hdmi); + + hdmi->vic = drm_match_cea_mode(mode); + + if (!hdmi->vic) { + dev_dbg(hdmi->dev, "Non-CEA mode used in HDMI\n"); + } else { + dev_dbg(hdmi->dev, "CEA mode used vic=%d\n", hdmi->vic); + } + + if ((hdmi->vic == 6) || (hdmi->vic == 7) || + (hdmi->vic == 21) || (hdmi->vic == 22) || + (hdmi->vic == 2) || (hdmi->vic == 3) || + (hdmi->vic == 17) || (hdmi->vic == 18)) + hdmi->hdmi_data.enc_out_encoding = V4L2_YCBCR_ENC_601; + else + hdmi->hdmi_data.enc_out_encoding = V4L2_YCBCR_ENC_709; + + hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0; + hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0; + + if (hdmi->hdmi_data.enc_in_bus_format == MEDIA_BUS_FMT_FIXED) + hdmi->hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + /* TOFIX: Get input encoding from plat data or fallback to none */ + if (hdmi->plat_data->input_bus_encoding) + hdmi->hdmi_data.enc_in_encoding = + hdmi->plat_data->input_bus_encoding; + else + hdmi->hdmi_data.enc_in_encoding = V4L2_YCBCR_ENC_DEFAULT; + + if (hdmi->hdmi_data.enc_out_bus_format == MEDIA_BUS_FMT_FIXED) + hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + hdmi->hdmi_data.rgb_limited_range = hdmi->sink_is_hdmi && + drm_default_rgb_quant_range(mode) == + HDMI_QUANTIZATION_RANGE_LIMITED; + + hdmi->hdmi_data.pix_repet_factor = 0; + hdmi->hdmi_data.hdcp_enable = 0; + hdmi->hdmi_data.video_mode.mdataenablepolarity = true; + + /* HDMI Initialization Step B.1 */ + hdmi_av_composer(hdmi, &connector->display_info, mode); + + /* HDMI Initializateion Step B.2 */ + ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, + &connector->display_info, + &hdmi->previous_mode); + if (ret) + return ret; + hdmi->phy.enabled = true; + + /* HDMI Initialization Step B.3 */ + dw_hdmi_enable_video_path(hdmi); + + if (hdmi->sink_has_audio) { + dev_dbg(hdmi->dev, "sink has audio support\n"); + + /* HDMI Initialization Step E - Configure audio */ + hdmi_clk_regenerator_update_pixel_clock(hdmi); + hdmi_enable_audio_clk(hdmi, hdmi->audio_enable); + } + + /* not for DVI mode */ + if (hdmi->sink_is_hdmi) { + dev_dbg(hdmi->dev, "%s HDMI mode\n", __func__); + + /* HDMI Initialization Step F - Configure AVI InfoFrame */ + hdmi_config_AVI(hdmi, connector, mode); + hdmi_config_vendor_specific_infoframe(hdmi, connector, mode); + hdmi_config_drm_infoframe(hdmi, connector); + } else { + dev_dbg(hdmi->dev, "%s DVI mode\n", __func__); + } + + hdmi_video_packetize(hdmi); + hdmi_video_csc(hdmi); + hdmi_video_sample(hdmi); + hdmi_tx_hdcp_config(hdmi); + + dw_hdmi_clear_overflow(hdmi); + + return 0; +} + +static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi) +{ + u8 ih_mute; + + /* + * Boot up defaults are: + * HDMI_IH_MUTE = 0x03 (disabled) + * HDMI_IH_MUTE_* = 0x00 (enabled) + * + * Disable top level interrupt bits in HDMI block + */ + ih_mute = hdmi_readb(hdmi, HDMI_IH_MUTE) | + HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT | + HDMI_IH_MUTE_MUTE_ALL_INTERRUPT; + + hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE); + + /* by default mask all interrupts */ + hdmi_writeb(hdmi, 0xff, HDMI_VP_MASK); + hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK0); + hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK1); + hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK2); + hdmi_writeb(hdmi, 0xff, HDMI_PHY_MASK0); + hdmi_writeb(hdmi, 0xff, HDMI_PHY_I2CM_INT_ADDR); + hdmi_writeb(hdmi, 0xff, HDMI_PHY_I2CM_CTLINT_ADDR); + hdmi_writeb(hdmi, 0xff, HDMI_AUD_INT); + hdmi_writeb(hdmi, 0xff, HDMI_AUD_SPDIFINT); + hdmi_writeb(hdmi, 0xff, HDMI_AUD_HBR_MASK); + hdmi_writeb(hdmi, 0xff, HDMI_GP_MASK); + hdmi_writeb(hdmi, 0xff, HDMI_A_APIINTMSK); + hdmi_writeb(hdmi, 0xff, HDMI_I2CM_INT); + hdmi_writeb(hdmi, 0xff, HDMI_I2CM_CTLINT); + + /* Disable interrupts in the IH_MUTE_* registers */ + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT1); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT2); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_AS_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_PHY_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_I2CM_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_CEC_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_VP_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_I2CMPHY_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0); + + /* Enable top level interrupt bits in HDMI block */ + ih_mute &= ~(HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT | + HDMI_IH_MUTE_MUTE_ALL_INTERRUPT); + hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE); +} + +static void dw_hdmi_poweron(struct dw_hdmi *hdmi) +{ + hdmi->bridge_is_on = true; + + /* + * The curr_conn field is guaranteed to be valid here, as this function + * is only be called when !hdmi->disabled. + */ + dw_hdmi_setup(hdmi, hdmi->curr_conn, &hdmi->previous_mode); +} + +static void dw_hdmi_poweroff(struct dw_hdmi *hdmi) +{ + if (hdmi->phy.enabled) { + hdmi->phy.ops->disable(hdmi, hdmi->phy.data); + hdmi->phy.enabled = false; + } + + hdmi->bridge_is_on = false; +} + +static void dw_hdmi_update_power(struct dw_hdmi *hdmi) +{ + int force = hdmi->force; + + if (hdmi->disabled) { + force = DRM_FORCE_OFF; + } else if (force == DRM_FORCE_UNSPECIFIED) { + if (hdmi->rxsense) + force = DRM_FORCE_ON; + else + force = DRM_FORCE_OFF; + } + + if (force == DRM_FORCE_OFF) { + if (hdmi->bridge_is_on) + dw_hdmi_poweroff(hdmi); + } else { + if (!hdmi->bridge_is_on) + dw_hdmi_poweron(hdmi); + } +} + +/* + * Adjust the detection of RXSENSE according to whether we have a forced + * connection mode enabled, or whether we have been disabled. There is + * no point processing RXSENSE interrupts if we have a forced connection + * state, or DRM has us disabled. + * + * We also disable rxsense interrupts when we think we're disconnected + * to avoid floating TDMS signals giving false rxsense interrupts. + * + * Note: we still need to listen for HPD interrupts even when DRM has us + * disabled so that we can detect a connect event. + */ +static void dw_hdmi_update_phy_mask(struct dw_hdmi *hdmi) +{ + if (hdmi->phy.ops->update_hpd) + hdmi->phy.ops->update_hpd(hdmi, hdmi->phy.data, + hdmi->force, hdmi->disabled, + hdmi->rxsense); +} + +static enum drm_connector_status dw_hdmi_detect(struct dw_hdmi *hdmi) +{ + enum drm_connector_status result; + + result = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + + mutex_lock(&hdmi->mutex); + if (result != hdmi->last_connector_result) { + dev_dbg(hdmi->dev, "read_hpd result: %d", result); + handle_plugged_change(hdmi, + result == connector_status_connected); + hdmi->last_connector_result = result; + } + mutex_unlock(&hdmi->mutex); + + return result; +} + +static struct edid *dw_hdmi_get_edid(struct dw_hdmi *hdmi, + struct drm_connector *connector) +{ + struct edid *edid; + + if (!hdmi->ddc) + return NULL; + + edid = drm_get_edid(connector, hdmi->ddc); + if (!edid) { + dev_dbg(hdmi->dev, "failed to get edid\n"); + return NULL; + } + + dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n", + edid->width_cm, edid->height_cm); + + hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid); + hdmi->sink_has_audio = drm_detect_monitor_audio(edid); + + return edid; +} + +/* ----------------------------------------------------------------------------- + * DRM Connector Operations + */ + +static enum drm_connector_status +dw_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, + connector); + return dw_hdmi_detect(hdmi); +} + +static int dw_hdmi_connector_get_modes(struct drm_connector *connector) +{ + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, + connector); + struct edid *edid; + int ret; + + edid = dw_hdmi_get_edid(hdmi, connector); + if (!edid) + return 0; + + drm_connector_update_edid_property(connector, edid); + cec_notifier_set_phys_addr_from_edid(hdmi->cec_notifier, edid); + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + + return ret; +} + +static bool hdr_metadata_equal(const struct drm_connector_state *old_state, + const struct drm_connector_state *new_state) +{ + struct drm_property_blob *old_blob = old_state->hdr_output_metadata; + struct drm_property_blob *new_blob = new_state->hdr_output_metadata; + + if (!old_blob || !new_blob) + return old_blob == new_blob; + + if (old_blob->length != new_blob->length) + return false; + + return !memcmp(old_blob->data, new_blob->data, old_blob->length); +} + +static int dw_hdmi_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct drm_connector_state *old_state = + drm_atomic_get_old_connector_state(state, connector); + struct drm_connector_state *new_state = + drm_atomic_get_new_connector_state(state, connector); + struct drm_crtc *crtc = new_state->crtc; + struct drm_crtc_state *crtc_state; + + if (!crtc) + return 0; + + if (!hdr_metadata_equal(old_state, new_state)) { + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + crtc_state->mode_changed = true; + } + + return 0; +} + +static void dw_hdmi_connector_force(struct drm_connector *connector) +{ + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, + connector); + + mutex_lock(&hdmi->mutex); + hdmi->force = connector->force; + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + mutex_unlock(&hdmi->mutex); +} + +static const struct drm_connector_funcs dw_hdmi_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = dw_hdmi_connector_detect, + .destroy = drm_connector_cleanup, + .force = dw_hdmi_connector_force, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = { + .get_modes = dw_hdmi_connector_get_modes, + .atomic_check = dw_hdmi_connector_atomic_check, +}; + +static int dw_hdmi_connector_create(struct dw_hdmi *hdmi) +{ + struct drm_connector *connector = &hdmi->connector; + struct cec_connector_info conn_info; + struct cec_notifier *notifier; + + if (hdmi->version >= 0x200a) + connector->ycbcr_420_allowed = + hdmi->plat_data->ycbcr_420_allowed; + else + connector->ycbcr_420_allowed = false; + + connector->interlace_allowed = 1; + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, &dw_hdmi_connector_helper_funcs); + + drm_connector_init_with_ddc(hdmi->bridge.dev, connector, + &dw_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA, + hdmi->ddc); + + /* + * drm_connector_attach_max_bpc_property() requires the + * connector to have a state. + */ + drm_atomic_helper_connector_reset(connector); + + drm_connector_attach_max_bpc_property(connector, 8, 16); + + if (hdmi->version >= 0x200a && hdmi->plat_data->use_drm_infoframe) + drm_object_attach_property(&connector->base, + connector->dev->mode_config.hdr_output_metadata_property, 0); + + drm_connector_attach_encoder(connector, hdmi->bridge.encoder); + + cec_fill_conn_info_from_drm(&conn_info, connector); + + notifier = cec_notifier_conn_register(hdmi->dev, NULL, &conn_info); + if (!notifier) + return -ENOMEM; + + mutex_lock(&hdmi->cec_notifier_mutex); + hdmi->cec_notifier = notifier; + mutex_unlock(&hdmi->cec_notifier_mutex); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * DRM Bridge Operations + */ + +/* + * Possible output formats : + * - MEDIA_BUS_FMT_UYYVYY16_0_5X48, + * - MEDIA_BUS_FMT_UYYVYY12_0_5X36, + * - MEDIA_BUS_FMT_UYYVYY10_0_5X30, + * - MEDIA_BUS_FMT_UYYVYY8_0_5X24, + * - MEDIA_BUS_FMT_YUV16_1X48, + * - MEDIA_BUS_FMT_RGB161616_1X48, + * - MEDIA_BUS_FMT_UYVY12_1X24, + * - MEDIA_BUS_FMT_YUV12_1X36, + * - MEDIA_BUS_FMT_RGB121212_1X36, + * - MEDIA_BUS_FMT_UYVY10_1X20, + * - MEDIA_BUS_FMT_YUV10_1X30, + * - MEDIA_BUS_FMT_RGB101010_1X30, + * - MEDIA_BUS_FMT_UYVY8_1X16, + * - MEDIA_BUS_FMT_YUV8_1X24, + * - MEDIA_BUS_FMT_RGB888_1X24, + */ + +/* Can return a maximum of 11 possible output formats for a mode/connector */ +#define MAX_OUTPUT_SEL_FORMATS 11 + +static u32 *dw_hdmi_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + unsigned int *num_output_fmts) +{ + struct drm_connector *conn = conn_state->connector; + struct drm_display_info *info = &conn->display_info; + struct drm_display_mode *mode = &crtc_state->mode; + u8 max_bpc = conn_state->max_requested_bpc; + bool is_hdmi2_sink = info->hdmi.scdc.supported || + (info->color_formats & DRM_COLOR_FORMAT_YCRCB420); + u32 *output_fmts; + unsigned int i = 0; + + *num_output_fmts = 0; + + output_fmts = kcalloc(MAX_OUTPUT_SEL_FORMATS, sizeof(*output_fmts), + GFP_KERNEL); + if (!output_fmts) + return NULL; + + /* If dw-hdmi is the first or only bridge, avoid negociating with ourselves */ + if (list_is_singular(&bridge->encoder->bridge_chain) || + list_is_first(&bridge->chain_node, &bridge->encoder->bridge_chain)) { + *num_output_fmts = 1; + output_fmts[0] = MEDIA_BUS_FMT_FIXED; + + return output_fmts; + } + + /* + * If the current mode enforces 4:2:0, force the output but format + * to 4:2:0 and do not add the YUV422/444/RGB formats + */ + if (conn->ycbcr_420_allowed && + (drm_mode_is_420_only(info, mode) || + (is_hdmi2_sink && drm_mode_is_420_also(info, mode)))) { + + /* Order bus formats from 16bit to 8bit if supported */ + if (max_bpc >= 16 && info->bpc == 16 && + (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48)) + output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY16_0_5X48; + + if (max_bpc >= 12 && info->bpc >= 12 && + (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36)) + output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY12_0_5X36; + + if (max_bpc >= 10 && info->bpc >= 10 && + (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30)) + output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY10_0_5X30; + + /* Default 8bit fallback */ + output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY8_0_5X24; + + *num_output_fmts = i; + + return output_fmts; + } + + /* + * Order bus formats from 16bit to 8bit and from YUV422 to RGB + * if supported. In any case the default RGB888 format is added + */ + + /* Default 8bit RGB fallback */ + output_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + + if (max_bpc >= 16 && info->bpc == 16) { + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48; + + output_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48; + } + + if (max_bpc >= 12 && info->bpc >= 12) { + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + output_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + + output_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + } + + if (max_bpc >= 10 && info->bpc >= 10) { + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + output_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + + output_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + } + + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + output_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + + *num_output_fmts = i; + + return output_fmts; +} + +/* + * Possible input formats : + * - MEDIA_BUS_FMT_RGB888_1X24 + * - MEDIA_BUS_FMT_YUV8_1X24 + * - MEDIA_BUS_FMT_UYVY8_1X16 + * - MEDIA_BUS_FMT_UYYVYY8_0_5X24 + * - MEDIA_BUS_FMT_RGB101010_1X30 + * - MEDIA_BUS_FMT_YUV10_1X30 + * - MEDIA_BUS_FMT_UYVY10_1X20 + * - MEDIA_BUS_FMT_UYYVYY10_0_5X30 + * - MEDIA_BUS_FMT_RGB121212_1X36 + * - MEDIA_BUS_FMT_YUV12_1X36 + * - MEDIA_BUS_FMT_UYVY12_1X24 + * - MEDIA_BUS_FMT_UYYVYY12_0_5X36 + * - MEDIA_BUS_FMT_RGB161616_1X48 + * - MEDIA_BUS_FMT_YUV16_1X48 + * - MEDIA_BUS_FMT_UYYVYY16_0_5X48 + */ + +/* Can return a maximum of 3 possible input formats for an output format */ +#define MAX_INPUT_SEL_FORMATS 3 + +static u32 *dw_hdmi_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) +{ + u32 *input_fmts; + unsigned int i = 0; + + *num_input_fmts = 0; + + input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts), + GFP_KERNEL); + if (!input_fmts) + return NULL; + + switch (output_fmt) { + /* If MEDIA_BUS_FMT_FIXED is tested, return default bus format */ + case MEDIA_BUS_FMT_FIXED: + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + break; + /* 8bit */ + case MEDIA_BUS_FMT_RGB888_1X24: + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + break; + case MEDIA_BUS_FMT_YUV8_1X24: + input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + break; + + /* 10bit */ + case MEDIA_BUS_FMT_RGB101010_1X30: + input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + break; + case MEDIA_BUS_FMT_YUV10_1X30: + input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + break; + case MEDIA_BUS_FMT_UYVY10_1X20: + input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + break; + + /* 12bit */ + case MEDIA_BUS_FMT_RGB121212_1X36: + input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + break; + case MEDIA_BUS_FMT_YUV12_1X36: + input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + break; + case MEDIA_BUS_FMT_UYVY12_1X24: + input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + break; + + /* 16bit */ + case MEDIA_BUS_FMT_RGB161616_1X48: + input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48; + input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48; + break; + case MEDIA_BUS_FMT_YUV16_1X48: + input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48; + input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48; + break; + + /*YUV 4:2:0 */ + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + input_fmts[i++] = output_fmt; + break; + } + + *num_input_fmts = i; + + if (*num_input_fmts == 0) { + kfree(input_fmts); + input_fmts = NULL; + } + + return input_fmts; +} + +static int dw_hdmi_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + hdmi->hdmi_data.enc_out_bus_format = + bridge_state->output_bus_cfg.format; + + hdmi->hdmi_data.enc_in_bus_format = + bridge_state->input_bus_cfg.format; + + dev_dbg(hdmi->dev, "input format 0x%04x, output format 0x%04x\n", + bridge_state->input_bus_cfg.format, + bridge_state->output_bus_cfg.format); + + return 0; +} + +static int dw_hdmi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return 0; + + return dw_hdmi_connector_create(hdmi); +} + +static void dw_hdmi_bridge_detach(struct drm_bridge *bridge) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + mutex_lock(&hdmi->cec_notifier_mutex); + cec_notifier_conn_unregister(hdmi->cec_notifier); + hdmi->cec_notifier = NULL; + mutex_unlock(&hdmi->cec_notifier_mutex); +} + +static enum drm_mode_status +dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; + enum drm_mode_status mode_status = MODE_OK; + + /* We don't support double-clocked modes */ + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + return MODE_BAD; + + if (pdata->mode_valid) + mode_status = pdata->mode_valid(hdmi, pdata->priv_data, info, + mode); + + return mode_status; +} + +static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *orig_mode, + const struct drm_display_mode *mode) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + mutex_lock(&hdmi->mutex); + + /* Store the display mode for plugin/DKMS poweron events */ + memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode)); + + mutex_unlock(&hdmi->mutex); +} + +static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + mutex_lock(&hdmi->mutex); + hdmi->disabled = true; + hdmi->curr_conn = NULL; + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + mutex_unlock(&hdmi->mutex); +} + +static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + struct drm_atomic_state *state = old_state->base.state; + struct drm_connector *connector; + + connector = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + + mutex_lock(&hdmi->mutex); + hdmi->disabled = false; + hdmi->curr_conn = connector; + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + mutex_unlock(&hdmi->mutex); +} + +static enum drm_connector_status dw_hdmi_bridge_detect(struct drm_bridge *bridge) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + return dw_hdmi_detect(hdmi); +} + +static struct edid *dw_hdmi_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + return dw_hdmi_get_edid(hdmi, connector); +} + +static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .attach = dw_hdmi_bridge_attach, + .detach = dw_hdmi_bridge_detach, + .atomic_check = dw_hdmi_bridge_atomic_check, + .atomic_get_output_bus_fmts = dw_hdmi_bridge_atomic_get_output_bus_fmts, + .atomic_get_input_bus_fmts = dw_hdmi_bridge_atomic_get_input_bus_fmts, + .atomic_enable = dw_hdmi_bridge_atomic_enable, + .atomic_disable = dw_hdmi_bridge_atomic_disable, + .mode_set = dw_hdmi_bridge_mode_set, + .mode_valid = dw_hdmi_bridge_mode_valid, + .detect = dw_hdmi_bridge_detect, + .get_edid = dw_hdmi_bridge_get_edid, +}; + +/* ----------------------------------------------------------------------------- + * IRQ Handling + */ + +static irqreturn_t dw_hdmi_i2c_irq(struct dw_hdmi *hdmi) +{ + struct dw_hdmi_i2c *i2c = hdmi->i2c; + unsigned int stat; + + stat = hdmi_readb(hdmi, HDMI_IH_I2CM_STAT0); + if (!stat) + return IRQ_NONE; + + hdmi_writeb(hdmi, stat, HDMI_IH_I2CM_STAT0); + + i2c->stat = stat; + + complete(&i2c->cmp); + + return IRQ_HANDLED; +} + +static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) +{ + struct dw_hdmi *hdmi = dev_id; + u8 intr_stat; + irqreturn_t ret = IRQ_NONE; + + if (hdmi->i2c) + ret = dw_hdmi_i2c_irq(hdmi); + + intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); + if (intr_stat) { + hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); + return IRQ_WAKE_THREAD; + } + + return ret; +} + +void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense) +{ + mutex_lock(&hdmi->mutex); + + if (!hdmi->force) { + /* + * If the RX sense status indicates we're disconnected, + * clear the software rxsense status. + */ + if (!rx_sense) + hdmi->rxsense = false; + + /* + * Only set the software rxsense status when both + * rxsense and hpd indicates we're connected. + * This avoids what seems to be bad behaviour in + * at least iMX6S versions of the phy. + */ + if (hpd) + hdmi->rxsense = true; + + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + } + mutex_unlock(&hdmi->mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_setup_rx_sense); + +static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) +{ + struct dw_hdmi *hdmi = dev_id; + u8 intr_stat, phy_int_pol, phy_pol_mask, phy_stat; + enum drm_connector_status status = connector_status_unknown; + + intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); + phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0); + phy_stat = hdmi_readb(hdmi, HDMI_PHY_STAT0); + + phy_pol_mask = 0; + if (intr_stat & HDMI_IH_PHY_STAT0_HPD) + phy_pol_mask |= HDMI_PHY_HPD; + if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE0) + phy_pol_mask |= HDMI_PHY_RX_SENSE0; + if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE1) + phy_pol_mask |= HDMI_PHY_RX_SENSE1; + if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE2) + phy_pol_mask |= HDMI_PHY_RX_SENSE2; + if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE3) + phy_pol_mask |= HDMI_PHY_RX_SENSE3; + + if (phy_pol_mask) + hdmi_modb(hdmi, ~phy_int_pol, phy_pol_mask, HDMI_PHY_POL0); + + /* + * RX sense tells us whether the TDMS transmitters are detecting + * load - in other words, there's something listening on the + * other end of the link. Use this to decide whether we should + * power on the phy as HPD may be toggled by the sink to merely + * ask the source to re-read the EDID. + */ + if (intr_stat & + (HDMI_IH_PHY_STAT0_RX_SENSE | HDMI_IH_PHY_STAT0_HPD)) { + dw_hdmi_setup_rx_sense(hdmi, + phy_stat & HDMI_PHY_HPD, + phy_stat & HDMI_PHY_RX_SENSE); + + if ((phy_stat & (HDMI_PHY_RX_SENSE | HDMI_PHY_HPD)) == 0) { + mutex_lock(&hdmi->cec_notifier_mutex); + cec_notifier_phys_addr_invalidate(hdmi->cec_notifier); + mutex_unlock(&hdmi->cec_notifier_mutex); + } + + if (phy_stat & HDMI_PHY_HPD) + status = connector_status_connected; + + if (!(phy_stat & (HDMI_PHY_HPD | HDMI_PHY_RX_SENSE))) + status = connector_status_disconnected; + } + + if (status != connector_status_unknown) { + dev_dbg(hdmi->dev, "EVENT=%s\n", + status == connector_status_connected ? + "plugin" : "plugout"); + + if (hdmi->bridge.dev) { + drm_helper_hpd_irq_event(hdmi->bridge.dev); + drm_bridge_hpd_notify(&hdmi->bridge, status); + } + } + + hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); + hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE), + HDMI_IH_MUTE_PHY_STAT0); + + return IRQ_HANDLED; +} + +static const struct dw_hdmi_phy_data dw_hdmi_phys[] = { + { + .type = DW_HDMI_PHY_DWC_HDMI_TX_PHY, + .name = "DWC HDMI TX PHY", + .gen = 1, + }, { + .type = DW_HDMI_PHY_DWC_MHL_PHY_HEAC, + .name = "DWC MHL PHY + HEAC PHY", + .gen = 2, + .has_svsret = true, + .configure = hdmi_phy_configure_dwc_hdmi_3d_tx, + }, { + .type = DW_HDMI_PHY_DWC_MHL_PHY, + .name = "DWC MHL PHY", + .gen = 2, + .has_svsret = true, + .configure = hdmi_phy_configure_dwc_hdmi_3d_tx, + }, { + .type = DW_HDMI_PHY_DWC_HDMI_3D_TX_PHY_HEAC, + .name = "DWC HDMI 3D TX PHY + HEAC PHY", + .gen = 2, + .configure = hdmi_phy_configure_dwc_hdmi_3d_tx, + }, { + .type = DW_HDMI_PHY_DWC_HDMI_3D_TX_PHY, + .name = "DWC HDMI 3D TX PHY", + .gen = 2, + .configure = hdmi_phy_configure_dwc_hdmi_3d_tx, + }, { + .type = DW_HDMI_PHY_DWC_HDMI20_TX_PHY, + .name = "DWC HDMI 2.0 TX PHY", + .gen = 2, + .has_svsret = true, + .configure = hdmi_phy_configure_dwc_hdmi_3d_tx, + }, { + .type = DW_HDMI_PHY_VENDOR_PHY, + .name = "Vendor PHY", + } +}; + +static int dw_hdmi_detect_phy(struct dw_hdmi *hdmi) +{ + unsigned int i; + u8 phy_type; + + phy_type = hdmi->plat_data->phy_force_vendor ? + DW_HDMI_PHY_VENDOR_PHY : + hdmi_readb(hdmi, HDMI_CONFIG2_ID); + + if (phy_type == DW_HDMI_PHY_VENDOR_PHY) { + /* Vendor PHYs require support from the glue layer. */ + if (!hdmi->plat_data->phy_ops || !hdmi->plat_data->phy_name) { + dev_err(hdmi->dev, + "Vendor HDMI PHY not supported by glue layer\n"); + return -ENODEV; + } + + hdmi->phy.ops = hdmi->plat_data->phy_ops; + hdmi->phy.data = hdmi->plat_data->phy_data; + hdmi->phy.name = hdmi->plat_data->phy_name; + return 0; + } + + /* Synopsys PHYs are handled internally. */ + for (i = 0; i < ARRAY_SIZE(dw_hdmi_phys); ++i) { + if (dw_hdmi_phys[i].type == phy_type) { + hdmi->phy.ops = &dw_hdmi_synopsys_phy_ops; + hdmi->phy.name = dw_hdmi_phys[i].name; + hdmi->phy.data = (void *)&dw_hdmi_phys[i]; + + if (!dw_hdmi_phys[i].configure && + !hdmi->plat_data->configure_phy) { + dev_err(hdmi->dev, "%s requires platform support\n", + hdmi->phy.name); + return -ENODEV; + } + + return 0; + } + } + + dev_err(hdmi->dev, "Unsupported HDMI PHY type (%02x)\n", phy_type); + return -ENODEV; +} + +static void dw_hdmi_cec_enable(struct dw_hdmi *hdmi) +{ + mutex_lock(&hdmi->mutex); + hdmi->mc_clkdis &= ~HDMI_MC_CLKDIS_CECCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + mutex_unlock(&hdmi->mutex); +} + +static void dw_hdmi_cec_disable(struct dw_hdmi *hdmi) +{ + mutex_lock(&hdmi->mutex); + hdmi->mc_clkdis |= HDMI_MC_CLKDIS_CECCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + mutex_unlock(&hdmi->mutex); +} + +static const struct dw_hdmi_cec_ops dw_hdmi_cec_ops = { + .write = hdmi_writeb, + .read = hdmi_readb, + .enable = dw_hdmi_cec_enable, + .disable = dw_hdmi_cec_disable, +}; + +static const struct regmap_config hdmi_regmap_8bit_config = { + .reg_bits = 32, + .val_bits = 8, + .reg_stride = 1, + .max_register = HDMI_I2CM_FS_SCL_LCNT_0_ADDR, +}; + +static const struct regmap_config hdmi_regmap_32bit_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = HDMI_I2CM_FS_SCL_LCNT_0_ADDR << 2, +}; + +static void dw_hdmi_init_hw(struct dw_hdmi *hdmi) +{ + initialize_hdmi_ih_mutes(hdmi); + + /* + * Reset HDMI DDC I2C master controller and mute I2CM interrupts. + * Even if we are using a separate i2c adapter doing this doesn't + * hurt. + */ + dw_hdmi_i2c_init(hdmi); + + if (hdmi->phy.ops->setup_hpd) + hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data); +} + +/* ----------------------------------------------------------------------------- + * Probe/remove API, used from platforms based on the DRM bridge API. + */ +struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, + const struct dw_hdmi_plat_data *plat_data) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct platform_device_info pdevinfo; + struct device_node *ddc_node; + struct dw_hdmi_cec_data cec; + struct dw_hdmi *hdmi; + struct resource *iores = NULL; + int irq; + int ret; + u32 val = 1; + u8 prod_id0; + u8 prod_id1; + u8 config0; + u8 config3; + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return ERR_PTR(-ENOMEM); + + hdmi->plat_data = plat_data; + hdmi->dev = dev; + hdmi->sample_rate = 48000; + hdmi->disabled = true; + hdmi->rxsense = true; + hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE); + hdmi->mc_clkdis = 0x7f; + hdmi->last_connector_result = connector_status_disconnected; + + mutex_init(&hdmi->mutex); + mutex_init(&hdmi->audio_mutex); + mutex_init(&hdmi->cec_notifier_mutex); + spin_lock_init(&hdmi->audio_lock); + + ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0); + if (ddc_node) { + hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!hdmi->ddc) { + dev_dbg(hdmi->dev, "failed to read ddc node\n"); + return ERR_PTR(-EPROBE_DEFER); + } + + } else { + dev_dbg(hdmi->dev, "no ddc property found\n"); + } + + if (!plat_data->regm) { + const struct regmap_config *reg_config; + + of_property_read_u32(np, "reg-io-width", &val); + switch (val) { + case 4: + reg_config = &hdmi_regmap_32bit_config; + hdmi->reg_shift = 2; + break; + case 1: + reg_config = &hdmi_regmap_8bit_config; + break; + default: + dev_err(dev, "reg-io-width must be 1 or 4\n"); + return ERR_PTR(-EINVAL); + } + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdmi->regs = devm_ioremap_resource(dev, iores); + if (IS_ERR(hdmi->regs)) { + ret = PTR_ERR(hdmi->regs); + goto err_res; + } + + hdmi->regm = devm_regmap_init_mmio(dev, hdmi->regs, reg_config); + if (IS_ERR(hdmi->regm)) { + dev_err(dev, "Failed to configure regmap\n"); + ret = PTR_ERR(hdmi->regm); + goto err_res; + } + } else { + hdmi->regm = plat_data->regm; + } + + hdmi->isfr_clk = devm_clk_get(hdmi->dev, "isfr"); + if (IS_ERR(hdmi->isfr_clk)) { + ret = PTR_ERR(hdmi->isfr_clk); + dev_err(hdmi->dev, "Unable to get HDMI isfr clk: %d\n", ret); + goto err_res; + } + + ret = clk_prepare_enable(hdmi->isfr_clk); + if (ret) { + dev_err(hdmi->dev, "Cannot enable HDMI isfr clock: %d\n", ret); + goto err_res; + } + + hdmi->iahb_clk = devm_clk_get(hdmi->dev, "iahb"); + if (IS_ERR(hdmi->iahb_clk)) { + ret = PTR_ERR(hdmi->iahb_clk); + dev_err(hdmi->dev, "Unable to get HDMI iahb clk: %d\n", ret); + goto err_isfr; + } + + ret = clk_prepare_enable(hdmi->iahb_clk); + if (ret) { + dev_err(hdmi->dev, "Cannot enable HDMI iahb clock: %d\n", ret); + goto err_isfr; + } + + hdmi->cec_clk = devm_clk_get(hdmi->dev, "cec"); + if (PTR_ERR(hdmi->cec_clk) == -ENOENT) { + hdmi->cec_clk = NULL; + } else if (IS_ERR(hdmi->cec_clk)) { + ret = PTR_ERR(hdmi->cec_clk); + if (ret != -EPROBE_DEFER) + dev_err(hdmi->dev, "Cannot get HDMI cec clock: %d\n", + ret); + + hdmi->cec_clk = NULL; + goto err_iahb; + } else { + ret = clk_prepare_enable(hdmi->cec_clk); + if (ret) { + dev_err(hdmi->dev, "Cannot enable HDMI cec clock: %d\n", + ret); + goto err_iahb; + } + } + + /* Product and revision IDs */ + hdmi->version = (hdmi_readb(hdmi, HDMI_DESIGN_ID) << 8) + | (hdmi_readb(hdmi, HDMI_REVISION_ID) << 0); + prod_id0 = hdmi_readb(hdmi, HDMI_PRODUCT_ID0); + prod_id1 = hdmi_readb(hdmi, HDMI_PRODUCT_ID1); + + if (prod_id0 != HDMI_PRODUCT_ID0_HDMI_TX || + (prod_id1 & ~HDMI_PRODUCT_ID1_HDCP) != HDMI_PRODUCT_ID1_HDMI_TX) { + dev_err(dev, "Unsupported HDMI controller (%04x:%02x:%02x)\n", + hdmi->version, prod_id0, prod_id1); + ret = -ENODEV; + goto err_iahb; + } + + ret = dw_hdmi_detect_phy(hdmi); + if (ret < 0) + goto err_iahb; + + dev_info(dev, "Detected HDMI TX controller v%x.%03x %s HDCP (%s)\n", + hdmi->version >> 12, hdmi->version & 0xfff, + prod_id1 & HDMI_PRODUCT_ID1_HDCP ? "with" : "without", + hdmi->phy.name); + + dw_hdmi_init_hw(hdmi); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto err_iahb; + } + + ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq, + dw_hdmi_irq, IRQF_SHARED, + dev_name(dev), hdmi); + if (ret) + goto err_iahb; + + /* + * To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator + * N and cts values before enabling phy + */ + hdmi_init_clk_regenerator(hdmi); + + /* If DDC bus is not specified, try to register HDMI I2C bus */ + if (!hdmi->ddc) { + /* Look for (optional) stuff related to unwedging */ + hdmi->pinctrl = devm_pinctrl_get(dev); + if (!IS_ERR(hdmi->pinctrl)) { + hdmi->unwedge_state = + pinctrl_lookup_state(hdmi->pinctrl, "unwedge"); + hdmi->default_state = + pinctrl_lookup_state(hdmi->pinctrl, "default"); + + if (IS_ERR(hdmi->default_state) || + IS_ERR(hdmi->unwedge_state)) { + if (!IS_ERR(hdmi->unwedge_state)) + dev_warn(dev, + "Unwedge requires default pinctrl\n"); + hdmi->default_state = NULL; + hdmi->unwedge_state = NULL; + } + } + + hdmi->ddc = dw_hdmi_i2c_adapter(hdmi); + if (IS_ERR(hdmi->ddc)) + hdmi->ddc = NULL; + } + + hdmi->bridge.driver_private = hdmi; + hdmi->bridge.funcs = &dw_hdmi_bridge_funcs; + hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID + | DRM_BRIDGE_OP_HPD; +#ifdef CONFIG_OF + hdmi->bridge.of_node = pdev->dev.of_node; +#endif + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = dev; + pdevinfo.id = PLATFORM_DEVID_AUTO; + + config0 = hdmi_readb(hdmi, HDMI_CONFIG0_ID); + config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID); + + if (iores && config3 & HDMI_CONFIG3_AHBAUDDMA) { + struct dw_hdmi_audio_data audio; + + audio.phys = iores->start; + audio.base = hdmi->regs; + audio.irq = irq; + audio.hdmi = hdmi; + audio.get_eld = hdmi_audio_get_eld; + hdmi->enable_audio = dw_hdmi_ahb_audio_enable; + hdmi->disable_audio = dw_hdmi_ahb_audio_disable; + + pdevinfo.name = "dw-hdmi-ahb-audio"; + pdevinfo.data = &audio; + pdevinfo.size_data = sizeof(audio); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + hdmi->audio = platform_device_register_full(&pdevinfo); + } else if (config0 & HDMI_CONFIG0_I2S) { + struct dw_hdmi_i2s_audio_data audio; + + audio.hdmi = hdmi; + audio.get_eld = hdmi_audio_get_eld; + audio.write = hdmi_writeb; + audio.read = hdmi_readb; + hdmi->enable_audio = dw_hdmi_i2s_audio_enable; + hdmi->disable_audio = dw_hdmi_i2s_audio_disable; + + pdevinfo.name = "dw-hdmi-i2s-audio"; + pdevinfo.data = &audio; + pdevinfo.size_data = sizeof(audio); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + hdmi->audio = platform_device_register_full(&pdevinfo); + } + + if (config0 & HDMI_CONFIG0_CEC) { + cec.hdmi = hdmi; + cec.ops = &dw_hdmi_cec_ops; + cec.irq = irq; + + pdevinfo.name = "dw-hdmi-cec"; + pdevinfo.data = &cec; + pdevinfo.size_data = sizeof(cec); + pdevinfo.dma_mask = 0; + + hdmi->cec = platform_device_register_full(&pdevinfo); + } + + drm_bridge_add(&hdmi->bridge); + + return hdmi; + +err_iahb: + clk_disable_unprepare(hdmi->iahb_clk); + if (hdmi->cec_clk) + clk_disable_unprepare(hdmi->cec_clk); +err_isfr: + clk_disable_unprepare(hdmi->isfr_clk); +err_res: + i2c_put_adapter(hdmi->ddc); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(dw_hdmi_probe); + +void dw_hdmi_remove(struct dw_hdmi *hdmi) +{ + drm_bridge_remove(&hdmi->bridge); + + if (hdmi->audio && !IS_ERR(hdmi->audio)) + platform_device_unregister(hdmi->audio); + if (!IS_ERR(hdmi->cec)) + platform_device_unregister(hdmi->cec); + + /* Disable all interrupts */ + hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); + + clk_disable_unprepare(hdmi->iahb_clk); + clk_disable_unprepare(hdmi->isfr_clk); + if (hdmi->cec_clk) + clk_disable_unprepare(hdmi->cec_clk); + + if (hdmi->i2c) + i2c_del_adapter(&hdmi->i2c->adap); + else + i2c_put_adapter(hdmi->ddc); +} +EXPORT_SYMBOL_GPL(dw_hdmi_remove); + +/* ----------------------------------------------------------------------------- + * Bind/unbind API, used from platforms based on the component framework. + */ +struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev, + struct drm_encoder *encoder, + const struct dw_hdmi_plat_data *plat_data) +{ + struct dw_hdmi *hdmi; + int ret; + + hdmi = dw_hdmi_probe(pdev, plat_data); + if (IS_ERR(hdmi)) + return hdmi; + + ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0); + if (ret) { + dw_hdmi_remove(hdmi); + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ERR_PTR(ret); + } + + return hdmi; +} +EXPORT_SYMBOL_GPL(dw_hdmi_bind); + +void dw_hdmi_unbind(struct dw_hdmi *hdmi) +{ + dw_hdmi_remove(hdmi); +} +EXPORT_SYMBOL_GPL(dw_hdmi_unbind); + +void dw_hdmi_resume(struct dw_hdmi *hdmi) +{ + dw_hdmi_init_hw(hdmi); +} +EXPORT_SYMBOL_GPL(dw_hdmi_resume); + +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); +MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>"); +MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>"); +MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>"); +MODULE_DESCRIPTION("DW HDMI transmitter driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:dw-hdmi"); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h new file mode 100644 index 000000000..1999db05b --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h @@ -0,0 +1,1156 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. + */ + +#ifndef __DW_HDMI_H__ +#define __DW_HDMI_H__ + +/* Identification Registers */ +#define HDMI_DESIGN_ID 0x0000 +#define HDMI_REVISION_ID 0x0001 +#define HDMI_PRODUCT_ID0 0x0002 +#define HDMI_PRODUCT_ID1 0x0003 +#define HDMI_CONFIG0_ID 0x0004 +#define HDMI_CONFIG1_ID 0x0005 +#define HDMI_CONFIG2_ID 0x0006 +#define HDMI_CONFIG3_ID 0x0007 + +/* Interrupt Registers */ +#define HDMI_IH_FC_STAT0 0x0100 +#define HDMI_IH_FC_STAT1 0x0101 +#define HDMI_IH_FC_STAT2 0x0102 +#define HDMI_IH_AS_STAT0 0x0103 +#define HDMI_IH_PHY_STAT0 0x0104 +#define HDMI_IH_I2CM_STAT0 0x0105 +#define HDMI_IH_CEC_STAT0 0x0106 +#define HDMI_IH_VP_STAT0 0x0107 +#define HDMI_IH_I2CMPHY_STAT0 0x0108 +#define HDMI_IH_AHBDMAAUD_STAT0 0x0109 + +#define HDMI_IH_MUTE_FC_STAT0 0x0180 +#define HDMI_IH_MUTE_FC_STAT1 0x0181 +#define HDMI_IH_MUTE_FC_STAT2 0x0182 +#define HDMI_IH_MUTE_AS_STAT0 0x0183 +#define HDMI_IH_MUTE_PHY_STAT0 0x0184 +#define HDMI_IH_MUTE_I2CM_STAT0 0x0185 +#define HDMI_IH_MUTE_CEC_STAT0 0x0186 +#define HDMI_IH_MUTE_VP_STAT0 0x0187 +#define HDMI_IH_MUTE_I2CMPHY_STAT0 0x0188 +#define HDMI_IH_MUTE_AHBDMAAUD_STAT0 0x0189 +#define HDMI_IH_MUTE 0x01FF + +/* Video Sample Registers */ +#define HDMI_TX_INVID0 0x0200 +#define HDMI_TX_INSTUFFING 0x0201 +#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 + +/* Video Packetizer Registers */ +#define HDMI_VP_STATUS 0x0800 +#define HDMI_VP_PR_CD 0x0801 +#define HDMI_VP_STUFF 0x0802 +#define HDMI_VP_REMAP 0x0803 +#define HDMI_VP_CONF 0x0804 +#define HDMI_VP_STAT 0x0805 +#define HDMI_VP_INT 0x0806 +#define HDMI_VP_MASK 0x0807 +#define HDMI_VP_POL 0x0808 + +/* Frame Composer Registers */ +#define HDMI_FC_INVIDCONF 0x1000 +#define HDMI_FC_INHACTV0 0x1001 +#define HDMI_FC_INHACTV1 0x1002 +#define HDMI_FC_INHBLANK0 0x1003 +#define HDMI_FC_INHBLANK1 0x1004 +#define HDMI_FC_INVACTV0 0x1005 +#define HDMI_FC_INVACTV1 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_INFREQ0 0x100E +#define HDMI_FC_INFREQ1 0x100F +#define HDMI_FC_INFREQ2 0x1010 +#define HDMI_FC_CTRLDUR 0x1011 +#define HDMI_FC_EXCTRLDUR 0x1012 +#define HDMI_FC_EXCTRLSPAC 0x1013 +#define HDMI_FC_CH0PREAM 0x1014 +#define HDMI_FC_CH1PREAM 0x1015 +#define HDMI_FC_CH2PREAM 0x1016 +#define HDMI_FC_AVICONF3 0x1017 +#define HDMI_FC_GCP 0x1018 +#define HDMI_FC_AVICONF0 0x1019 +#define HDMI_FC_AVICONF1 0x101A +#define HDMI_FC_AVICONF2 0x101B +#define HDMI_FC_AVIVID 0x101C +#define HDMI_FC_AVIETB0 0x101D +#define HDMI_FC_AVIETB1 0x101E +#define HDMI_FC_AVISBB0 0x101F +#define HDMI_FC_AVISBB1 0x1020 +#define HDMI_FC_AVIELB0 0x1021 +#define HDMI_FC_AVIELB1 0x1022 +#define HDMI_FC_AVISRB0 0x1023 +#define HDMI_FC_AVISRB1 0x1024 +#define HDMI_FC_AUDICONF0 0x1025 +#define HDMI_FC_AUDICONF1 0x1026 +#define HDMI_FC_AUDICONF2 0x1027 +#define HDMI_FC_AUDICONF3 0x1028 +#define HDMI_FC_VSDIEEEID0 0x1029 +#define HDMI_FC_VSDSIZE 0x102A +#define HDMI_FC_VSDIEEEID1 0x1030 +#define HDMI_FC_VSDIEEEID2 0x1031 +#define HDMI_FC_VSDPAYLOAD0 0x1032 +#define HDMI_FC_VSDPAYLOAD1 0x1033 +#define HDMI_FC_VSDPAYLOAD2 0x1034 +#define HDMI_FC_VSDPAYLOAD3 0x1035 +#define HDMI_FC_VSDPAYLOAD4 0x1036 +#define HDMI_FC_VSDPAYLOAD5 0x1037 +#define HDMI_FC_VSDPAYLOAD6 0x1038 +#define HDMI_FC_VSDPAYLOAD7 0x1039 +#define HDMI_FC_VSDPAYLOAD8 0x103A +#define HDMI_FC_VSDPAYLOAD9 0x103B +#define HDMI_FC_VSDPAYLOAD10 0x103C +#define HDMI_FC_VSDPAYLOAD11 0x103D +#define HDMI_FC_VSDPAYLOAD12 0x103E +#define HDMI_FC_VSDPAYLOAD13 0x103F +#define HDMI_FC_VSDPAYLOAD14 0x1040 +#define HDMI_FC_VSDPAYLOAD15 0x1041 +#define HDMI_FC_VSDPAYLOAD16 0x1042 +#define HDMI_FC_VSDPAYLOAD17 0x1043 +#define HDMI_FC_VSDPAYLOAD18 0x1044 +#define HDMI_FC_VSDPAYLOAD19 0x1045 +#define HDMI_FC_VSDPAYLOAD20 0x1046 +#define HDMI_FC_VSDPAYLOAD21 0x1047 +#define HDMI_FC_VSDPAYLOAD22 0x1048 +#define HDMI_FC_VSDPAYLOAD23 0x1049 +#define HDMI_FC_SPDVENDORNAME0 0x104A +#define HDMI_FC_SPDVENDORNAME1 0x104B +#define HDMI_FC_SPDVENDORNAME2 0x104C +#define HDMI_FC_SPDVENDORNAME3 0x104D +#define HDMI_FC_SPDVENDORNAME4 0x104E +#define HDMI_FC_SPDVENDORNAME5 0x104F +#define HDMI_FC_SPDVENDORNAME6 0x1050 +#define HDMI_FC_SPDVENDORNAME7 0x1051 +#define HDMI_FC_SDPPRODUCTNAME0 0x1052 +#define HDMI_FC_SDPPRODUCTNAME1 0x1053 +#define HDMI_FC_SDPPRODUCTNAME2 0x1054 +#define HDMI_FC_SDPPRODUCTNAME3 0x1055 +#define HDMI_FC_SDPPRODUCTNAME4 0x1056 +#define HDMI_FC_SDPPRODUCTNAME5 0x1057 +#define HDMI_FC_SDPPRODUCTNAME6 0x1058 +#define HDMI_FC_SDPPRODUCTNAME7 0x1059 +#define HDMI_FC_SDPPRODUCTNAME8 0x105A +#define HDMI_FC_SDPPRODUCTNAME9 0x105B +#define HDMI_FC_SDPPRODUCTNAME10 0x105C +#define HDMI_FC_SDPPRODUCTNAME11 0x105D +#define HDMI_FC_SDPPRODUCTNAME12 0x105E +#define HDMI_FC_SDPPRODUCTNAME13 0x105F +#define HDMI_FC_SDPPRODUCTNAME14 0x1060 +#define HDMI_FC_SPDPRODUCTNAME15 0x1061 +#define HDMI_FC_SPDDEVICEINF 0x1062 +#define HDMI_FC_AUDSCONF 0x1063 +#define HDMI_FC_AUDSSTAT 0x1064 +#define HDMI_FC_AUDSCHNLS7 0x106e +#define HDMI_FC_AUDSCHNLS8 0x106f +#define HDMI_FC_DATACH0FILL 0x1070 +#define HDMI_FC_DATACH1FILL 0x1071 +#define HDMI_FC_DATACH2FILL 0x1072 +#define HDMI_FC_CTRLQHIGH 0x1073 +#define HDMI_FC_CTRLQLOW 0x1074 +#define HDMI_FC_ACP0 0x1075 +#define HDMI_FC_ACP28 0x1076 +#define HDMI_FC_ACP27 0x1077 +#define HDMI_FC_ACP26 0x1078 +#define HDMI_FC_ACP25 0x1079 +#define HDMI_FC_ACP24 0x107A +#define HDMI_FC_ACP23 0x107B +#define HDMI_FC_ACP22 0x107C +#define HDMI_FC_ACP21 0x107D +#define HDMI_FC_ACP20 0x107E +#define HDMI_FC_ACP19 0x107F +#define HDMI_FC_ACP18 0x1080 +#define HDMI_FC_ACP17 0x1081 +#define HDMI_FC_ACP16 0x1082 +#define HDMI_FC_ACP15 0x1083 +#define HDMI_FC_ACP14 0x1084 +#define HDMI_FC_ACP13 0x1085 +#define HDMI_FC_ACP12 0x1086 +#define HDMI_FC_ACP11 0x1087 +#define HDMI_FC_ACP10 0x1088 +#define HDMI_FC_ACP9 0x1089 +#define HDMI_FC_ACP8 0x108A +#define HDMI_FC_ACP7 0x108B +#define HDMI_FC_ACP6 0x108C +#define HDMI_FC_ACP5 0x108D +#define HDMI_FC_ACP4 0x108E +#define HDMI_FC_ACP3 0x108F +#define HDMI_FC_ACP2 0x1090 +#define HDMI_FC_ACP1 0x1091 +#define HDMI_FC_ISCR1_0 0x1092 +#define HDMI_FC_ISCR1_16 0x1093 +#define HDMI_FC_ISCR1_15 0x1094 +#define HDMI_FC_ISCR1_14 0x1095 +#define HDMI_FC_ISCR1_13 0x1096 +#define HDMI_FC_ISCR1_12 0x1097 +#define HDMI_FC_ISCR1_11 0x1098 +#define HDMI_FC_ISCR1_10 0x1099 +#define HDMI_FC_ISCR1_9 0x109A +#define HDMI_FC_ISCR1_8 0x109B +#define HDMI_FC_ISCR1_7 0x109C +#define HDMI_FC_ISCR1_6 0x109D +#define HDMI_FC_ISCR1_5 0x109E +#define HDMI_FC_ISCR1_4 0x109F +#define HDMI_FC_ISCR1_3 0x10A0 +#define HDMI_FC_ISCR1_2 0x10A1 +#define HDMI_FC_ISCR1_1 0x10A2 +#define HDMI_FC_ISCR2_15 0x10A3 +#define HDMI_FC_ISCR2_14 0x10A4 +#define HDMI_FC_ISCR2_13 0x10A5 +#define HDMI_FC_ISCR2_12 0x10A6 +#define HDMI_FC_ISCR2_11 0x10A7 +#define HDMI_FC_ISCR2_10 0x10A8 +#define HDMI_FC_ISCR2_9 0x10A9 +#define HDMI_FC_ISCR2_8 0x10AA +#define HDMI_FC_ISCR2_7 0x10AB +#define HDMI_FC_ISCR2_6 0x10AC +#define HDMI_FC_ISCR2_5 0x10AD +#define HDMI_FC_ISCR2_4 0x10AE +#define HDMI_FC_ISCR2_3 0x10AF +#define HDMI_FC_ISCR2_2 0x10B0 +#define HDMI_FC_ISCR2_1 0x10B1 +#define HDMI_FC_ISCR2_0 0x10B2 +#define HDMI_FC_DATAUTO0 0x10B3 +#define HDMI_FC_DATAUTO1 0x10B4 +#define HDMI_FC_DATAUTO2 0x10B5 +#define HDMI_FC_DATMAN 0x10B6 +#define HDMI_FC_DATAUTO3 0x10B7 +#define HDMI_FC_RDRB0 0x10B8 +#define HDMI_FC_RDRB1 0x10B9 +#define HDMI_FC_RDRB2 0x10BA +#define HDMI_FC_RDRB3 0x10BB +#define HDMI_FC_RDRB4 0x10BC +#define HDMI_FC_RDRB5 0x10BD +#define HDMI_FC_RDRB6 0x10BE +#define HDMI_FC_RDRB7 0x10BF +#define HDMI_FC_STAT0 0x10D0 +#define HDMI_FC_INT0 0x10D1 +#define HDMI_FC_MASK0 0x10D2 +#define HDMI_FC_POL0 0x10D3 +#define HDMI_FC_STAT1 0x10D4 +#define HDMI_FC_INT1 0x10D5 +#define HDMI_FC_MASK1 0x10D6 +#define HDMI_FC_POL1 0x10D7 +#define HDMI_FC_STAT2 0x10D8 +#define HDMI_FC_INT2 0x10D9 +#define HDMI_FC_MASK2 0x10DA +#define HDMI_FC_POL2 0x10DB +#define HDMI_FC_PRCONF 0x10E0 +#define HDMI_FC_SCRAMBLER_CTRL 0x10E1 +#define HDMI_FC_PACKET_TX_EN 0x10E3 + +#define HDMI_FC_GMD_STAT 0x1100 +#define HDMI_FC_GMD_EN 0x1101 +#define HDMI_FC_GMD_UP 0x1102 +#define HDMI_FC_GMD_CONF 0x1103 +#define HDMI_FC_GMD_HB 0x1104 +#define HDMI_FC_GMD_PB0 0x1105 +#define HDMI_FC_GMD_PB1 0x1106 +#define HDMI_FC_GMD_PB2 0x1107 +#define HDMI_FC_GMD_PB3 0x1108 +#define HDMI_FC_GMD_PB4 0x1109 +#define HDMI_FC_GMD_PB5 0x110A +#define HDMI_FC_GMD_PB6 0x110B +#define HDMI_FC_GMD_PB7 0x110C +#define HDMI_FC_GMD_PB8 0x110D +#define HDMI_FC_GMD_PB9 0x110E +#define HDMI_FC_GMD_PB10 0x110F +#define HDMI_FC_GMD_PB11 0x1110 +#define HDMI_FC_GMD_PB12 0x1111 +#define HDMI_FC_GMD_PB13 0x1112 +#define HDMI_FC_GMD_PB14 0x1113 +#define HDMI_FC_GMD_PB15 0x1114 +#define HDMI_FC_GMD_PB16 0x1115 +#define HDMI_FC_GMD_PB17 0x1116 +#define HDMI_FC_GMD_PB18 0x1117 +#define HDMI_FC_GMD_PB19 0x1118 +#define HDMI_FC_GMD_PB20 0x1119 +#define HDMI_FC_GMD_PB21 0x111A +#define HDMI_FC_GMD_PB22 0x111B +#define HDMI_FC_GMD_PB23 0x111C +#define HDMI_FC_GMD_PB24 0x111D +#define HDMI_FC_GMD_PB25 0x111E +#define HDMI_FC_GMD_PB26 0x111F +#define HDMI_FC_GMD_PB27 0x1120 + +#define HDMI_FC_DRM_UP 0x1167 +#define HDMI_FC_DRM_HB0 0x1168 +#define HDMI_FC_DRM_HB1 0x1169 +#define HDMI_FC_DRM_PB0 0x116A +#define HDMI_FC_DRM_PB1 0x116B +#define HDMI_FC_DRM_PB2 0x116C +#define HDMI_FC_DRM_PB3 0x116D +#define HDMI_FC_DRM_PB4 0x116E +#define HDMI_FC_DRM_PB5 0x116F +#define HDMI_FC_DRM_PB6 0x1170 +#define HDMI_FC_DRM_PB7 0x1171 +#define HDMI_FC_DRM_PB8 0x1172 +#define HDMI_FC_DRM_PB9 0x1173 +#define HDMI_FC_DRM_PB10 0x1174 +#define HDMI_FC_DRM_PB11 0x1175 +#define HDMI_FC_DRM_PB12 0x1176 +#define HDMI_FC_DRM_PB13 0x1177 +#define HDMI_FC_DRM_PB14 0x1178 +#define HDMI_FC_DRM_PB15 0x1179 +#define HDMI_FC_DRM_PB16 0x117A +#define HDMI_FC_DRM_PB17 0x117B +#define HDMI_FC_DRM_PB18 0x117C +#define HDMI_FC_DRM_PB19 0x117D +#define HDMI_FC_DRM_PB20 0x117E +#define HDMI_FC_DRM_PB21 0x117F +#define HDMI_FC_DRM_PB22 0x1180 +#define HDMI_FC_DRM_PB23 0x1181 +#define HDMI_FC_DRM_PB24 0x1182 +#define HDMI_FC_DRM_PB25 0x1183 +#define HDMI_FC_DRM_PB26 0x1184 + +#define HDMI_FC_DBGFORCE 0x1200 +#define HDMI_FC_DBGAUD0CH0 0x1201 +#define HDMI_FC_DBGAUD1CH0 0x1202 +#define HDMI_FC_DBGAUD2CH0 0x1203 +#define HDMI_FC_DBGAUD0CH1 0x1204 +#define HDMI_FC_DBGAUD1CH1 0x1205 +#define HDMI_FC_DBGAUD2CH1 0x1206 +#define HDMI_FC_DBGAUD0CH2 0x1207 +#define HDMI_FC_DBGAUD1CH2 0x1208 +#define HDMI_FC_DBGAUD2CH2 0x1209 +#define HDMI_FC_DBGAUD0CH3 0x120A +#define HDMI_FC_DBGAUD1CH3 0x120B +#define HDMI_FC_DBGAUD2CH3 0x120C +#define HDMI_FC_DBGAUD0CH4 0x120D +#define HDMI_FC_DBGAUD1CH4 0x120E +#define HDMI_FC_DBGAUD2CH4 0x120F +#define HDMI_FC_DBGAUD0CH5 0x1210 +#define HDMI_FC_DBGAUD1CH5 0x1211 +#define HDMI_FC_DBGAUD2CH5 0x1212 +#define HDMI_FC_DBGAUD0CH6 0x1213 +#define HDMI_FC_DBGAUD1CH6 0x1214 +#define HDMI_FC_DBGAUD2CH6 0x1215 +#define HDMI_FC_DBGAUD0CH7 0x1216 +#define HDMI_FC_DBGAUD1CH7 0x1217 +#define HDMI_FC_DBGAUD2CH7 0x1218 +#define HDMI_FC_DBGTMDS0 0x1219 +#define HDMI_FC_DBGTMDS1 0x121A +#define HDMI_FC_DBGTMDS2 0x121B + +/* HDMI Source PHY Registers */ +#define HDMI_PHY_CONF0 0x3000 +#define HDMI_PHY_TST0 0x3001 +#define HDMI_PHY_TST1 0x3002 +#define HDMI_PHY_TST2 0x3003 +#define HDMI_PHY_STAT0 0x3004 +#define HDMI_PHY_INT0 0x3005 +#define HDMI_PHY_MASK0 0x3006 +#define HDMI_PHY_POL0 0x3007 + +/* HDMI Master PHY Registers */ +#define HDMI_PHY_I2CM_SLAVE_ADDR 0x3020 +#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_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 + +/* Audio Sampler Registers */ +#define HDMI_AUD_CONF0 0x3100 +#define HDMI_AUD_CONF1 0x3101 +#define HDMI_AUD_INT 0x3102 +#define HDMI_AUD_CONF2 0x3103 +#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_SPDIFINT 0x3302 +#define HDMI_AUD_CONF0_HBR 0x3400 +#define HDMI_AUD_HBR_STATUS 0x3401 +#define HDMI_AUD_HBR_INT 0x3402 +#define HDMI_AUD_HBR_POL 0x3403 +#define HDMI_AUD_HBR_MASK 0x3404 + +/* + * Generic Parallel Audio Interface Registers + * Not used as GPAUD interface is not enabled in hw + */ +#define HDMI_GP_CONF0 0x3500 +#define HDMI_GP_CONF1 0x3501 +#define HDMI_GP_CONF2 0x3502 +#define HDMI_GP_STAT 0x3503 +#define HDMI_GP_INT 0x3504 +#define HDMI_GP_MASK 0x3505 +#define HDMI_GP_POL 0x3506 + +/* Audio DMA Registers */ +#define HDMI_AHB_DMA_CONF0 0x3600 +#define HDMI_AHB_DMA_START 0x3601 +#define HDMI_AHB_DMA_STOP 0x3602 +#define HDMI_AHB_DMA_THRSLD 0x3603 +#define HDMI_AHB_DMA_STRADDR0 0x3604 +#define HDMI_AHB_DMA_STRADDR1 0x3605 +#define HDMI_AHB_DMA_STRADDR2 0x3606 +#define HDMI_AHB_DMA_STRADDR3 0x3607 +#define HDMI_AHB_DMA_STPADDR0 0x3608 +#define HDMI_AHB_DMA_STPADDR1 0x3609 +#define HDMI_AHB_DMA_STPADDR2 0x360a +#define HDMI_AHB_DMA_STPADDR3 0x360b +#define HDMI_AHB_DMA_BSTADDR0 0x360c +#define HDMI_AHB_DMA_BSTADDR1 0x360d +#define HDMI_AHB_DMA_BSTADDR2 0x360e +#define HDMI_AHB_DMA_BSTADDR3 0x360f +#define HDMI_AHB_DMA_MBLENGTH0 0x3610 +#define HDMI_AHB_DMA_MBLENGTH1 0x3611 +#define HDMI_AHB_DMA_STAT 0x3612 +#define HDMI_AHB_DMA_INT 0x3613 +#define HDMI_AHB_DMA_MASK 0x3614 +#define HDMI_AHB_DMA_POL 0x3615 +#define HDMI_AHB_DMA_CONF1 0x3616 +#define HDMI_AHB_DMA_BUFFSTAT 0x3617 +#define HDMI_AHB_DMA_BUFFINT 0x3618 +#define HDMI_AHB_DMA_BUFFMASK 0x3619 +#define HDMI_AHB_DMA_BUFFPOL 0x361a + +/* Main Controller Registers */ +#define HDMI_MC_SFRDIV 0x4000 +#define HDMI_MC_CLKDIS 0x4001 +#define HDMI_MC_SWRSTZ 0x4002 +#define HDMI_MC_OPCTRL 0x4003 +#define HDMI_MC_FLOWCTRL 0x4004 +#define HDMI_MC_PHYRSTZ 0x4005 +#define HDMI_MC_LOCKONCLOCK 0x4006 +#define HDMI_MC_HEACPHY_RST 0x4007 + +/* Color Space Converter Registers */ +#define HDMI_CSC_CFG 0x4100 +#define HDMI_CSC_SCALE 0x4101 +#define HDMI_CSC_COEF_A1_MSB 0x4102 +#define HDMI_CSC_COEF_A1_LSB 0x4103 +#define HDMI_CSC_COEF_A2_MSB 0x4104 +#define HDMI_CSC_COEF_A2_LSB 0x4105 +#define HDMI_CSC_COEF_A3_MSB 0x4106 +#define HDMI_CSC_COEF_A3_LSB 0x4107 +#define HDMI_CSC_COEF_A4_MSB 0x4108 +#define HDMI_CSC_COEF_A4_LSB 0x4109 +#define HDMI_CSC_COEF_B1_MSB 0x410A +#define HDMI_CSC_COEF_B1_LSB 0x410B +#define HDMI_CSC_COEF_B2_MSB 0x410C +#define HDMI_CSC_COEF_B2_LSB 0x410D +#define HDMI_CSC_COEF_B3_MSB 0x410E +#define HDMI_CSC_COEF_B3_LSB 0x410F +#define HDMI_CSC_COEF_B4_MSB 0x4110 +#define HDMI_CSC_COEF_B4_LSB 0x4111 +#define HDMI_CSC_COEF_C1_MSB 0x4112 +#define HDMI_CSC_COEF_C1_LSB 0x4113 +#define HDMI_CSC_COEF_C2_MSB 0x4114 +#define HDMI_CSC_COEF_C2_LSB 0x4115 +#define HDMI_CSC_COEF_C3_MSB 0x4116 +#define HDMI_CSC_COEF_C3_LSB 0x4117 +#define HDMI_CSC_COEF_C4_MSB 0x4118 +#define HDMI_CSC_COEF_C4_LSB 0x4119 + +/* HDCP Encryption Engine Registers */ +#define HDMI_A_HDCPCFG0 0x5000 +#define HDMI_A_HDCPCFG1 0x5001 +#define HDMI_A_HDCPOBS0 0x5002 +#define HDMI_A_HDCPOBS1 0x5003 +#define HDMI_A_HDCPOBS2 0x5004 +#define HDMI_A_HDCPOBS3 0x5005 +#define HDMI_A_APIINTCLR 0x5006 +#define HDMI_A_APIINTSTAT 0x5007 +#define HDMI_A_APIINTMSK 0x5008 +#define HDMI_A_VIDPOLCFG 0x5009 +#define HDMI_A_OESSWCFG 0x500A +#define HDMI_A_TIMER1SETUP0 0x500B +#define HDMI_A_TIMER1SETUP1 0x500C +#define HDMI_A_TIMER2SETUP0 0x500D +#define HDMI_A_TIMER2SETUP1 0x500E +#define HDMI_A_100MSCFG 0x500F +#define HDMI_A_2SCFG0 0x5010 +#define HDMI_A_2SCFG1 0x5011 +#define HDMI_A_5SCFG0 0x5012 +#define HDMI_A_5SCFG1 0x5013 +#define HDMI_A_SRMVERLSB 0x5014 +#define HDMI_A_SRMVERMSB 0x5015 +#define HDMI_A_SRMCTRL 0x5016 +#define HDMI_A_SFRSETUP 0x5017 +#define HDMI_A_I2CHSETUP 0x5018 +#define HDMI_A_INTSETUP 0x5019 +#define HDMI_A_PRESETUP 0x501A +#define HDMI_A_SRM_BASE 0x5020 + +/* I2C Master Registers (E-DDC) */ +#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_INT 0x7E05 +#define HDMI_I2CM_CTLINT 0x7E06 +#define HDMI_I2CM_DIV 0x7E07 +#define HDMI_I2CM_SEGADDR 0x7E08 +#define HDMI_I2CM_SOFTRSTZ 0x7E09 +#define HDMI_I2CM_SEGPTR 0x7E0A +#define HDMI_I2CM_SS_SCL_HCNT_1_ADDR 0x7E0B +#define HDMI_I2CM_SS_SCL_HCNT_0_ADDR 0x7E0C +#define HDMI_I2CM_SS_SCL_LCNT_1_ADDR 0x7E0D +#define HDMI_I2CM_SS_SCL_LCNT_0_ADDR 0x7E0E +#define HDMI_I2CM_FS_SCL_HCNT_1_ADDR 0x7E0F +#define HDMI_I2CM_FS_SCL_HCNT_0_ADDR 0x7E10 +#define HDMI_I2CM_FS_SCL_LCNT_1_ADDR 0x7E11 +#define HDMI_I2CM_FS_SCL_LCNT_0_ADDR 0x7E12 + +enum { +/* PRODUCT_ID0 field values */ + HDMI_PRODUCT_ID0_HDMI_TX = 0xa0, + +/* PRODUCT_ID1 field values */ + HDMI_PRODUCT_ID1_HDCP = 0xc0, + HDMI_PRODUCT_ID1_HDMI_RX = 0x02, + HDMI_PRODUCT_ID1_HDMI_TX = 0x01, + +/* CONFIG0_ID field values */ + HDMI_CONFIG0_I2S = 0x10, + HDMI_CONFIG0_CEC = 0x02, + +/* CONFIG1_ID field values */ + HDMI_CONFIG1_AHB = 0x01, + +/* CONFIG3_ID field values */ + HDMI_CONFIG3_AHBAUDDMA = 0x02, + HDMI_CONFIG3_GPAUD = 0x01, + +/* IH_FC_INT2 field values */ + HDMI_IH_FC_INT2_OVERFLOW_MASK = 0x03, + HDMI_IH_FC_INT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_IH_FC_INT2_HIGH_PRIORITY_OVERFLOW = 0x01, + +/* IH_FC_STAT2 field values */ + HDMI_IH_FC_STAT2_OVERFLOW_MASK = 0x03, + HDMI_IH_FC_STAT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_IH_FC_STAT2_HIGH_PRIORITY_OVERFLOW = 0x01, + +/* IH_PHY_STAT0 field values */ + HDMI_IH_PHY_STAT0_RX_SENSE3 = 0x20, + HDMI_IH_PHY_STAT0_RX_SENSE2 = 0x10, + HDMI_IH_PHY_STAT0_RX_SENSE1 = 0x8, + HDMI_IH_PHY_STAT0_RX_SENSE0 = 0x4, + HDMI_IH_PHY_STAT0_TX_PHY_LOCK = 0x2, + HDMI_IH_PHY_STAT0_HPD = 0x1, + +/* IH_I2CM_STAT0 and IH_MUTE_I2CM_STAT0 field values */ + HDMI_IH_I2CM_STAT0_DONE = 0x2, + HDMI_IH_I2CM_STAT0_ERROR = 0x1, + +/* IH_MUTE_I2CMPHY_STAT0 field values */ + HDMI_IH_MUTE_I2CMPHY_STAT0_I2CMPHYDONE = 0x2, + HDMI_IH_MUTE_I2CMPHY_STAT0_I2CMPHYERROR = 0x1, + +/* IH_AHBDMAAUD_STAT0 field values */ + HDMI_IH_AHBDMAAUD_STAT0_ERROR = 0x20, + HDMI_IH_AHBDMAAUD_STAT0_LOST = 0x10, + HDMI_IH_AHBDMAAUD_STAT0_RETRY = 0x08, + HDMI_IH_AHBDMAAUD_STAT0_DONE = 0x04, + HDMI_IH_AHBDMAAUD_STAT0_BUFFFULL = 0x02, + HDMI_IH_AHBDMAAUD_STAT0_BUFFEMPTY = 0x01, + +/* IH_MUTE_FC_STAT2 field values */ + HDMI_IH_MUTE_FC_STAT2_OVERFLOW_MASK = 0x03, + HDMI_IH_MUTE_FC_STAT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_IH_MUTE_FC_STAT2_HIGH_PRIORITY_OVERFLOW = 0x01, + +/* IH_MUTE_AHBDMAAUD_STAT0 field values */ + HDMI_IH_MUTE_AHBDMAAUD_STAT0_ERROR = 0x20, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_LOST = 0x10, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_RETRY = 0x08, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE = 0x04, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFFULL = 0x02, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFEMPTY = 0x01, + +/* IH_MUTE field values */ + HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT = 0x2, + HDMI_IH_MUTE_MUTE_ALL_INTERRUPT = 0x1, + +/* TX_INVID0 field values */ + HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_MASK = 0x80, + HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_ENABLE = 0x80, + HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE = 0x00, + HDMI_TX_INVID0_VIDEO_MAPPING_MASK = 0x1F, + HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET = 0, + +/* TX_INSTUFFING field values */ + HDMI_TX_INSTUFFING_BDBDATA_STUFFING_MASK = 0x4, + HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE = 0x4, + HDMI_TX_INSTUFFING_BDBDATA_STUFFING_DISABLE = 0x0, + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_MASK = 0x2, + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE = 0x2, + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_DISABLE = 0x0, + HDMI_TX_INSTUFFING_GYDATA_STUFFING_MASK = 0x1, + HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE = 0x1, + HDMI_TX_INSTUFFING_GYDATA_STUFFING_DISABLE = 0x0, + +/* VP_PR_CD field values */ + HDMI_VP_PR_CD_COLOR_DEPTH_MASK = 0xF0, + HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET = 4, + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK = 0x0F, + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_OFFSET = 0, + +/* VP_STUFF field values */ + HDMI_VP_STUFF_IDEFAULT_PHASE_MASK = 0x20, + HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET = 5, + HDMI_VP_STUFF_IFIX_PP_TO_LAST_MASK = 0x10, + HDMI_VP_STUFF_IFIX_PP_TO_LAST_OFFSET = 4, + HDMI_VP_STUFF_ICX_GOTO_P0_ST_MASK = 0x8, + HDMI_VP_STUFF_ICX_GOTO_P0_ST_OFFSET = 3, + HDMI_VP_STUFF_YCC422_STUFFING_MASK = 0x4, + HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE = 0x4, + HDMI_VP_STUFF_YCC422_STUFFING_DIRECT_MODE = 0x0, + HDMI_VP_STUFF_PP_STUFFING_MASK = 0x2, + HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE = 0x2, + HDMI_VP_STUFF_PP_STUFFING_DIRECT_MODE = 0x0, + HDMI_VP_STUFF_PR_STUFFING_MASK = 0x1, + HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE = 0x1, + HDMI_VP_STUFF_PR_STUFFING_DIRECT_MODE = 0x0, + +/* VP_CONF field values */ + HDMI_VP_CONF_BYPASS_EN_MASK = 0x40, + HDMI_VP_CONF_BYPASS_EN_ENABLE = 0x40, + HDMI_VP_CONF_BYPASS_EN_DISABLE = 0x00, + HDMI_VP_CONF_PP_EN_ENMASK = 0x20, + HDMI_VP_CONF_PP_EN_ENABLE = 0x20, + HDMI_VP_CONF_PP_EN_DISABLE = 0x00, + HDMI_VP_CONF_PR_EN_MASK = 0x10, + HDMI_VP_CONF_PR_EN_ENABLE = 0x10, + HDMI_VP_CONF_PR_EN_DISABLE = 0x00, + HDMI_VP_CONF_YCC422_EN_MASK = 0x8, + HDMI_VP_CONF_YCC422_EN_ENABLE = 0x8, + HDMI_VP_CONF_YCC422_EN_DISABLE = 0x0, + HDMI_VP_CONF_BYPASS_SELECT_MASK = 0x4, + HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER = 0x4, + HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER = 0x0, + HDMI_VP_CONF_OUTPUT_SELECTOR_MASK = 0x3, + HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS = 0x3, + HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422 = 0x1, + HDMI_VP_CONF_OUTPUT_SELECTOR_PP = 0x0, + +/* VP_REMAP field values */ + HDMI_VP_REMAP_MASK = 0x3, + HDMI_VP_REMAP_YCC422_24bit = 0x2, + HDMI_VP_REMAP_YCC422_20bit = 0x1, + HDMI_VP_REMAP_YCC422_16bit = 0x0, + +/* FC_INVIDCONF field values */ + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_MASK = 0x80, + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE = 0x80, + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE = 0x00, + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_MASK = 0x40, + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH = 0x40, + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW = 0x00, + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_MASK = 0x20, + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH = 0x20, + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW = 0x00, + HDMI_FC_INVIDCONF_DE_IN_POLARITY_MASK = 0x10, + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH = 0x10, + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_LOW = 0x00, + HDMI_FC_INVIDCONF_DVI_MODEZ_MASK = 0x8, + HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE = 0x8, + HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE = 0x0, + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_MASK = 0x2, + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH = 0x2, + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW = 0x0, + HDMI_FC_INVIDCONF_IN_I_P_MASK = 0x1, + HDMI_FC_INVIDCONF_IN_I_P_INTERLACED = 0x1, + HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE = 0x0, + +/* FC_AUDICONF0 field values */ + HDMI_FC_AUDICONF0_CC_OFFSET = 4, + HDMI_FC_AUDICONF0_CC_MASK = 0x70, + HDMI_FC_AUDICONF0_CT_OFFSET = 0, + HDMI_FC_AUDICONF0_CT_MASK = 0xF, + +/* FC_AUDICONF1 field values */ + HDMI_FC_AUDICONF1_SS_OFFSET = 3, + HDMI_FC_AUDICONF1_SS_MASK = 0x18, + HDMI_FC_AUDICONF1_SF_OFFSET = 0, + HDMI_FC_AUDICONF1_SF_MASK = 0x7, + +/* FC_AUDICONF3 field values */ + HDMI_FC_AUDICONF3_LFEPBL_OFFSET = 5, + HDMI_FC_AUDICONF3_LFEPBL_MASK = 0x60, + HDMI_FC_AUDICONF3_DM_INH_OFFSET = 4, + HDMI_FC_AUDICONF3_DM_INH_MASK = 0x10, + HDMI_FC_AUDICONF3_LSV_OFFSET = 0, + HDMI_FC_AUDICONF3_LSV_MASK = 0xF, + +/* FC_AUDSCHNLS0 field values */ + HDMI_FC_AUDSCHNLS0_CGMSA_OFFSET = 4, + HDMI_FC_AUDSCHNLS0_CGMSA_MASK = 0x30, + HDMI_FC_AUDSCHNLS0_COPYRIGHT_OFFSET = 0, + HDMI_FC_AUDSCHNLS0_COPYRIGHT_MASK = 0x01, + +/* FC_AUDSCHNLS3-6 field values */ + HDMI_FC_AUDSCHNLS3_OIEC_CH0_OFFSET = 0, + HDMI_FC_AUDSCHNLS3_OIEC_CH0_MASK = 0x0f, + HDMI_FC_AUDSCHNLS3_OIEC_CH1_OFFSET = 4, + HDMI_FC_AUDSCHNLS3_OIEC_CH1_MASK = 0xf0, + HDMI_FC_AUDSCHNLS4_OIEC_CH2_OFFSET = 0, + HDMI_FC_AUDSCHNLS4_OIEC_CH2_MASK = 0x0f, + HDMI_FC_AUDSCHNLS4_OIEC_CH3_OFFSET = 4, + HDMI_FC_AUDSCHNLS4_OIEC_CH3_MASK = 0xf0, + + HDMI_FC_AUDSCHNLS5_OIEC_CH0_OFFSET = 0, + HDMI_FC_AUDSCHNLS5_OIEC_CH0_MASK = 0x0f, + HDMI_FC_AUDSCHNLS5_OIEC_CH1_OFFSET = 4, + HDMI_FC_AUDSCHNLS5_OIEC_CH1_MASK = 0xf0, + HDMI_FC_AUDSCHNLS6_OIEC_CH2_OFFSET = 0, + HDMI_FC_AUDSCHNLS6_OIEC_CH2_MASK = 0x0f, + HDMI_FC_AUDSCHNLS6_OIEC_CH3_OFFSET = 4, + HDMI_FC_AUDSCHNLS6_OIEC_CH3_MASK = 0xf0, + +/* HDMI_FC_AUDSCHNLS7 field values */ + HDMI_FC_AUDSCHNLS7_ACCURACY_OFFSET = 4, + HDMI_FC_AUDSCHNLS7_ACCURACY_MASK = 0x30, + +/* HDMI_FC_AUDSCHNLS8 field values */ + HDMI_FC_AUDSCHNLS8_ORIGSAMPFREQ_MASK = 0xf0, + HDMI_FC_AUDSCHNLS8_ORIGSAMPFREQ_OFFSET = 4, + HDMI_FC_AUDSCHNLS8_WORDLEGNTH_MASK = 0x0f, + HDMI_FC_AUDSCHNLS8_WORDLEGNTH_OFFSET = 0, + +/* FC_AUDSCONF field values */ + HDMI_FC_AUDSCONF_AUD_PACKET_SAMPFIT_MASK = 0xF0, + HDMI_FC_AUDSCONF_AUD_PACKET_SAMPFIT_OFFSET = 4, + HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_MASK = 0x1, + HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_OFFSET = 0, + HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT1 = 0x1, + HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT0 = 0x0, + +/* FC_STAT2 field values */ + HDMI_FC_STAT2_OVERFLOW_MASK = 0x03, + HDMI_FC_STAT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_FC_STAT2_HIGH_PRIORITY_OVERFLOW = 0x01, + +/* FC_INT2 field values */ + HDMI_FC_INT2_OVERFLOW_MASK = 0x03, + HDMI_FC_INT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_FC_INT2_HIGH_PRIORITY_OVERFLOW = 0x01, + +/* FC_MASK2 field values */ + HDMI_FC_MASK2_OVERFLOW_MASK = 0x03, + HDMI_FC_MASK2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_FC_MASK2_HIGH_PRIORITY_OVERFLOW = 0x01, + +/* FC_PRCONF field values */ + HDMI_FC_PRCONF_INCOMING_PR_FACTOR_MASK = 0xF0, + HDMI_FC_PRCONF_INCOMING_PR_FACTOR_OFFSET = 4, + HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_MASK = 0x0F, + HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_OFFSET = 0, + +/* FC_PACKET_TX_EN field values */ + HDMI_FC_PACKET_TX_EN_DRM_MASK = 0x80, + HDMI_FC_PACKET_TX_EN_DRM_ENABLE = 0x80, + HDMI_FC_PACKET_TX_EN_DRM_DISABLE = 0x00, + +/* FC_AVICONF0-FC_AVICONF3 field values */ + HDMI_FC_AVICONF0_PIX_FMT_MASK = 0x03, + HDMI_FC_AVICONF0_PIX_FMT_RGB = 0x00, + HDMI_FC_AVICONF0_PIX_FMT_YCBCR422 = 0x01, + HDMI_FC_AVICONF0_PIX_FMT_YCBCR444 = 0x02, + HDMI_FC_AVICONF0_ACTIVE_FMT_MASK = 0x40, + HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT = 0x40, + HDMI_FC_AVICONF0_ACTIVE_FMT_NO_INFO = 0x00, + HDMI_FC_AVICONF0_BAR_DATA_MASK = 0x0C, + HDMI_FC_AVICONF0_BAR_DATA_NO_DATA = 0x00, + HDMI_FC_AVICONF0_BAR_DATA_VERT_BAR = 0x04, + HDMI_FC_AVICONF0_BAR_DATA_HORIZ_BAR = 0x08, + HDMI_FC_AVICONF0_BAR_DATA_VERT_HORIZ_BAR = 0x0C, + HDMI_FC_AVICONF0_SCAN_INFO_MASK = 0x30, + HDMI_FC_AVICONF0_SCAN_INFO_OVERSCAN = 0x10, + HDMI_FC_AVICONF0_SCAN_INFO_UNDERSCAN = 0x20, + HDMI_FC_AVICONF0_SCAN_INFO_NODATA = 0x00, + + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_MASK = 0x0F, + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_USE_CODED = 0x08, + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_4_3 = 0x09, + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_16_9 = 0x0A, + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_14_9 = 0x0B, + HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_MASK = 0x30, + HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_NO_DATA = 0x00, + HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_4_3 = 0x10, + HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_16_9 = 0x20, + HDMI_FC_AVICONF1_COLORIMETRY_MASK = 0xC0, + HDMI_FC_AVICONF1_COLORIMETRY_NO_DATA = 0x00, + HDMI_FC_AVICONF1_COLORIMETRY_SMPTE = 0x40, + HDMI_FC_AVICONF1_COLORIMETRY_ITUR = 0x80, + HDMI_FC_AVICONF1_COLORIMETRY_EXTENDED_INFO = 0xC0, + + HDMI_FC_AVICONF2_SCALING_MASK = 0x03, + HDMI_FC_AVICONF2_SCALING_NONE = 0x00, + HDMI_FC_AVICONF2_SCALING_HORIZ = 0x01, + HDMI_FC_AVICONF2_SCALING_VERT = 0x02, + HDMI_FC_AVICONF2_SCALING_HORIZ_VERT = 0x03, + HDMI_FC_AVICONF2_RGB_QUANT_MASK = 0x0C, + HDMI_FC_AVICONF2_RGB_QUANT_DEFAULT = 0x00, + HDMI_FC_AVICONF2_RGB_QUANT_LIMITED_RANGE = 0x04, + HDMI_FC_AVICONF2_RGB_QUANT_FULL_RANGE = 0x08, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_MASK = 0x70, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601 = 0x00, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC709 = 0x10, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_SYCC601 = 0x20, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_ADOBE_YCC601 = 0x30, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_ADOBE_RGB = 0x40, + HDMI_FC_AVICONF2_IT_CONTENT_MASK = 0x80, + HDMI_FC_AVICONF2_IT_CONTENT_NO_DATA = 0x00, + HDMI_FC_AVICONF2_IT_CONTENT_VALID = 0x80, + + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_MASK = 0x03, + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_GRAPHICS = 0x00, + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_PHOTO = 0x01, + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_CINEMA = 0x02, + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_GAME = 0x03, + HDMI_FC_AVICONF3_QUANT_RANGE_MASK = 0x0C, + HDMI_FC_AVICONF3_QUANT_RANGE_LIMITED = 0x00, + HDMI_FC_AVICONF3_QUANT_RANGE_FULL = 0x04, + +/* FC_DBGFORCE field values */ + HDMI_FC_DBGFORCE_FORCEAUDIO = 0x10, + HDMI_FC_DBGFORCE_FORCEVIDEO = 0x1, + +/* FC_DATAUTO0 field values */ + HDMI_FC_DATAUTO0_VSD_MASK = 0x08, + HDMI_FC_DATAUTO0_VSD_OFFSET = 3, + +/* PHY_CONF0 field values */ + HDMI_PHY_CONF0_PDZ_MASK = 0x80, + HDMI_PHY_CONF0_PDZ_OFFSET = 7, + HDMI_PHY_CONF0_ENTMDS_MASK = 0x40, + HDMI_PHY_CONF0_ENTMDS_OFFSET = 6, + HDMI_PHY_CONF0_SVSRET_MASK = 0x20, + HDMI_PHY_CONF0_SVSRET_OFFSET = 5, + HDMI_PHY_CONF0_GEN2_PDDQ_MASK = 0x10, + HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET = 4, + HDMI_PHY_CONF0_GEN2_TXPWRON_MASK = 0x8, + HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET = 3, + HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_MASK = 0x4, + HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_OFFSET = 2, + HDMI_PHY_CONF0_SELDATAENPOL_MASK = 0x2, + HDMI_PHY_CONF0_SELDATAENPOL_OFFSET = 1, + HDMI_PHY_CONF0_SELDIPIF_MASK = 0x1, + HDMI_PHY_CONF0_SELDIPIF_OFFSET = 0, + +/* PHY_TST0 field values */ + HDMI_PHY_TST0_TSTCLR_MASK = 0x20, + HDMI_PHY_TST0_TSTCLR_OFFSET = 5, + HDMI_PHY_TST0_TSTEN_MASK = 0x10, + HDMI_PHY_TST0_TSTEN_OFFSET = 4, + HDMI_PHY_TST0_TSTCLK_MASK = 0x1, + HDMI_PHY_TST0_TSTCLK_OFFSET = 0, + +/* PHY_STAT0 field values */ + HDMI_PHY_RX_SENSE3 = 0x80, + HDMI_PHY_RX_SENSE2 = 0x40, + HDMI_PHY_RX_SENSE1 = 0x20, + HDMI_PHY_RX_SENSE0 = 0x10, + HDMI_PHY_HPD = 0x02, + HDMI_PHY_TX_PHY_LOCK = 0x01, + +/* PHY_I2CM_SLAVE_ADDR field values */ + HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2 = 0x69, + HDMI_PHY_I2CM_SLAVE_ADDR_HEAC_PHY = 0x49, + +/* PHY_I2CM_OPERATION_ADDR field values */ + HDMI_PHY_I2CM_OPERATION_ADDR_WRITE = 0x10, + HDMI_PHY_I2CM_OPERATION_ADDR_READ = 0x1, + +/* HDMI_PHY_I2CM_INT_ADDR */ + HDMI_PHY_I2CM_INT_ADDR_DONE_POL = 0x08, + HDMI_PHY_I2CM_INT_ADDR_DONE_MASK = 0x04, + +/* HDMI_PHY_I2CM_CTLINT_ADDR */ + HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL = 0x80, + HDMI_PHY_I2CM_CTLINT_ADDR_NAC_MASK = 0x40, + HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL = 0x08, + HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_MASK = 0x04, + +/* AUD_CONF0 field values */ + HDMI_AUD_CONF0_SW_RESET = 0x80, + HDMI_AUD_CONF0_I2S_SELECT = 0x20, + HDMI_AUD_CONF0_I2S_EN3 = 0x08, + HDMI_AUD_CONF0_I2S_EN2 = 0x04, + HDMI_AUD_CONF0_I2S_EN1 = 0x02, + HDMI_AUD_CONF0_I2S_EN0 = 0x01, + +/* AUD_CONF1 field values */ + HDMI_AUD_CONF1_MODE_I2S = 0x00, + HDMI_AUD_CONF1_MODE_RIGHT_J = 0x20, + HDMI_AUD_CONF1_MODE_LEFT_J = 0x40, + HDMI_AUD_CONF1_MODE_BURST_1 = 0x60, + HDMI_AUD_CONF1_MODE_BURST_2 = 0x80, + HDMI_AUD_CONF1_WIDTH_16 = 0x10, + HDMI_AUD_CONF1_WIDTH_24 = 0x18, + +/* AUD_CTS3 field values */ + HDMI_AUD_CTS3_N_SHIFT_OFFSET = 5, + HDMI_AUD_CTS3_N_SHIFT_MASK = 0xe0, + HDMI_AUD_CTS3_N_SHIFT_1 = 0, + HDMI_AUD_CTS3_N_SHIFT_16 = 0x20, + HDMI_AUD_CTS3_N_SHIFT_32 = 0x40, + HDMI_AUD_CTS3_N_SHIFT_64 = 0x60, + HDMI_AUD_CTS3_N_SHIFT_128 = 0x80, + HDMI_AUD_CTS3_N_SHIFT_256 = 0xa0, + /* note that the CTS3 MANUAL bit has been removed + from our part. Can't set it, will read as 0. */ + HDMI_AUD_CTS3_CTS_MANUAL = 0x10, + HDMI_AUD_CTS3_AUDCTS19_16_MASK = 0x0f, + +/* HDMI_AUD_INPUTCLKFS field values */ + HDMI_AUD_INPUTCLKFS_128FS = 0, + HDMI_AUD_INPUTCLKFS_256FS = 1, + HDMI_AUD_INPUTCLKFS_512FS = 2, + HDMI_AUD_INPUTCLKFS_64FS = 4, + +/* AHB_DMA_CONF0 field values */ + HDMI_AHB_DMA_CONF0_SW_FIFO_RST_OFFSET = 7, + HDMI_AHB_DMA_CONF0_SW_FIFO_RST_MASK = 0x80, + HDMI_AHB_DMA_CONF0_HBR = 0x10, + HDMI_AHB_DMA_CONF0_EN_HLOCK_OFFSET = 3, + HDMI_AHB_DMA_CONF0_EN_HLOCK_MASK = 0x08, + HDMI_AHB_DMA_CONF0_INCR_TYPE_OFFSET = 1, + HDMI_AHB_DMA_CONF0_INCR_TYPE_MASK = 0x06, + HDMI_AHB_DMA_CONF0_INCR4 = 0x0, + HDMI_AHB_DMA_CONF0_INCR8 = 0x2, + HDMI_AHB_DMA_CONF0_INCR16 = 0x4, + HDMI_AHB_DMA_CONF0_BURST_MODE = 0x1, + +/* HDMI_AHB_DMA_START field values */ + HDMI_AHB_DMA_START_START_OFFSET = 0, + HDMI_AHB_DMA_START_START_MASK = 0x01, + +/* HDMI_AHB_DMA_STOP field values */ + HDMI_AHB_DMA_STOP_STOP_OFFSET = 0, + HDMI_AHB_DMA_STOP_STOP_MASK = 0x01, + +/* AHB_DMA_STAT, AHB_DMA_INT, AHB_DMA_MASK, AHB_DMA_POL field values */ + HDMI_AHB_DMA_DONE = 0x80, + HDMI_AHB_DMA_RETRY_SPLIT = 0x40, + HDMI_AHB_DMA_LOSTOWNERSHIP = 0x20, + HDMI_AHB_DMA_ERROR = 0x10, + HDMI_AHB_DMA_FIFO_THREMPTY = 0x04, + HDMI_AHB_DMA_FIFO_FULL = 0x02, + HDMI_AHB_DMA_FIFO_EMPTY = 0x01, + +/* AHB_DMA_BUFFSTAT, AHB_DMA_BUFFINT,AHB_DMA_BUFFMASK,AHB_DMA_BUFFPOL values */ + HDMI_AHB_DMA_BUFFSTAT_FULL = 0x02, + HDMI_AHB_DMA_BUFFSTAT_EMPTY = 0x01, + +/* MC_CLKDIS field values */ + HDMI_MC_CLKDIS_HDCPCLK_DISABLE = 0x40, + HDMI_MC_CLKDIS_CECCLK_DISABLE = 0x20, + HDMI_MC_CLKDIS_CSCCLK_DISABLE = 0x10, + HDMI_MC_CLKDIS_AUDCLK_DISABLE = 0x8, + HDMI_MC_CLKDIS_PREPCLK_DISABLE = 0x4, + HDMI_MC_CLKDIS_TMDSCLK_DISABLE = 0x2, + HDMI_MC_CLKDIS_PIXELCLK_DISABLE = 0x1, + +/* MC_SWRSTZ field values */ + HDMI_MC_SWRSTZ_I2SSWRST_REQ = 0x08, + HDMI_MC_SWRSTZ_TMDSSWRST_REQ = 0x02, + +/* MC_FLOWCTRL field values */ + HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_MASK = 0x1, + HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH = 0x1, + HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS = 0x0, + +/* MC_PHYRSTZ field values */ + HDMI_MC_PHYRSTZ_PHYRSTZ = 0x01, + +/* MC_HEACPHY_RST field values */ + HDMI_MC_HEACPHY_RST_ASSERT = 0x1, + HDMI_MC_HEACPHY_RST_DEASSERT = 0x0, + +/* CSC_CFG field values */ + HDMI_CSC_CFG_INTMODE_MASK = 0x30, + HDMI_CSC_CFG_INTMODE_OFFSET = 4, + HDMI_CSC_CFG_INTMODE_DISABLE = 0x00, + HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA1 = 0x10, + HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA2 = 0x20, + HDMI_CSC_CFG_DECMODE_MASK = 0x3, + HDMI_CSC_CFG_DECMODE_OFFSET = 0, + HDMI_CSC_CFG_DECMODE_DISABLE = 0x0, + HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA1 = 0x1, + HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA2 = 0x2, + HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA3 = 0x3, + +/* CSC_SCALE field values */ + HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK = 0xF0, + HDMI_CSC_SCALE_CSC_COLORDE_PTH_24BPP = 0x00, + HDMI_CSC_SCALE_CSC_COLORDE_PTH_30BPP = 0x50, + HDMI_CSC_SCALE_CSC_COLORDE_PTH_36BPP = 0x60, + HDMI_CSC_SCALE_CSC_COLORDE_PTH_48BPP = 0x70, + HDMI_CSC_SCALE_CSCSCALE_MASK = 0x03, + +/* A_HDCPCFG0 field values */ + HDMI_A_HDCPCFG0_ELVENA_MASK = 0x80, + HDMI_A_HDCPCFG0_ELVENA_ENABLE = 0x80, + HDMI_A_HDCPCFG0_ELVENA_DISABLE = 0x00, + HDMI_A_HDCPCFG0_I2CFASTMODE_MASK = 0x40, + HDMI_A_HDCPCFG0_I2CFASTMODE_ENABLE = 0x40, + HDMI_A_HDCPCFG0_I2CFASTMODE_DISABLE = 0x00, + HDMI_A_HDCPCFG0_BYPENCRYPTION_MASK = 0x20, + HDMI_A_HDCPCFG0_BYPENCRYPTION_ENABLE = 0x20, + HDMI_A_HDCPCFG0_BYPENCRYPTION_DISABLE = 0x00, + HDMI_A_HDCPCFG0_SYNCRICHECK_MASK = 0x10, + HDMI_A_HDCPCFG0_SYNCRICHECK_ENABLE = 0x10, + HDMI_A_HDCPCFG0_SYNCRICHECK_DISABLE = 0x00, + HDMI_A_HDCPCFG0_AVMUTE_MASK = 0x8, + HDMI_A_HDCPCFG0_AVMUTE_ENABLE = 0x8, + HDMI_A_HDCPCFG0_AVMUTE_DISABLE = 0x0, + HDMI_A_HDCPCFG0_RXDETECT_MASK = 0x4, + HDMI_A_HDCPCFG0_RXDETECT_ENABLE = 0x4, + HDMI_A_HDCPCFG0_RXDETECT_DISABLE = 0x0, + HDMI_A_HDCPCFG0_EN11FEATURE_MASK = 0x2, + HDMI_A_HDCPCFG0_EN11FEATURE_ENABLE = 0x2, + HDMI_A_HDCPCFG0_EN11FEATURE_DISABLE = 0x0, + HDMI_A_HDCPCFG0_HDMIDVI_MASK = 0x1, + HDMI_A_HDCPCFG0_HDMIDVI_HDMI = 0x1, + HDMI_A_HDCPCFG0_HDMIDVI_DVI = 0x0, + +/* A_HDCPCFG1 field values */ + HDMI_A_HDCPCFG1_DISSHA1CHECK_MASK = 0x8, + HDMI_A_HDCPCFG1_DISSHA1CHECK_DISABLE = 0x8, + HDMI_A_HDCPCFG1_DISSHA1CHECK_ENABLE = 0x0, + HDMI_A_HDCPCFG1_PH2UPSHFTENC_MASK = 0x4, + HDMI_A_HDCPCFG1_PH2UPSHFTENC_ENABLE = 0x4, + HDMI_A_HDCPCFG1_PH2UPSHFTENC_DISABLE = 0x0, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK = 0x2, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE = 0x2, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_ENABLE = 0x0, + HDMI_A_HDCPCFG1_SWRESET_MASK = 0x1, + HDMI_A_HDCPCFG1_SWRESET_ASSERT = 0x0, + +/* A_VIDPOLCFG field values */ + HDMI_A_VIDPOLCFG_UNENCRYPTCONF_MASK = 0x60, + HDMI_A_VIDPOLCFG_UNENCRYPTCONF_OFFSET = 5, + HDMI_A_VIDPOLCFG_DATAENPOL_MASK = 0x10, + HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH = 0x10, + HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW = 0x0, + HDMI_A_VIDPOLCFG_VSYNCPOL_MASK = 0x8, + HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_HIGH = 0x8, + HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_LOW = 0x0, + HDMI_A_VIDPOLCFG_HSYNCPOL_MASK = 0x2, + HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_HIGH = 0x2, + HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_LOW = 0x0, + +/* I2CM_OPERATION field values */ + HDMI_I2CM_OPERATION_WRITE = 0x10, + HDMI_I2CM_OPERATION_READ_EXT = 0x2, + HDMI_I2CM_OPERATION_READ = 0x1, + +/* I2CM_INT field values */ + HDMI_I2CM_INT_DONE_POL = 0x8, + HDMI_I2CM_INT_DONE_MASK = 0x4, + +/* I2CM_CTLINT field values */ + HDMI_I2CM_CTLINT_NAC_POL = 0x80, + HDMI_I2CM_CTLINT_NAC_MASK = 0x40, + HDMI_I2CM_CTLINT_ARB_POL = 0x8, + HDMI_I2CM_CTLINT_ARB_MASK = 0x4, +}; + +/* + * HDMI 3D TX PHY registers + */ +#define HDMI_3D_TX_PHY_PWRCTRL 0x00 +#define HDMI_3D_TX_PHY_SERDIVCTRL 0x01 +#define HDMI_3D_TX_PHY_SERCKCTRL 0x02 +#define HDMI_3D_TX_PHY_SERCKKILLCTRL 0x03 +#define HDMI_3D_TX_PHY_TXRESCTRL 0x04 +#define HDMI_3D_TX_PHY_CKCALCTRL 0x05 +#define HDMI_3D_TX_PHY_CPCE_CTRL 0x06 +#define HDMI_3D_TX_PHY_TXCLKMEASCTRL 0x07 +#define HDMI_3D_TX_PHY_TXMEASCTRL 0x08 +#define HDMI_3D_TX_PHY_CKSYMTXCTRL 0x09 +#define HDMI_3D_TX_PHY_CMPSEQCTRL 0x0a +#define HDMI_3D_TX_PHY_CMPPWRCTRL 0x0b +#define HDMI_3D_TX_PHY_CMPMODECTRL 0x0c +#define HDMI_3D_TX_PHY_MEASCTRL 0x0d +#define HDMI_3D_TX_PHY_VLEVCTRL 0x0e +#define HDMI_3D_TX_PHY_D2ACTRL 0x0f +#define HDMI_3D_TX_PHY_CURRCTRL 0x10 +#define HDMI_3D_TX_PHY_DRVANACTRL 0x11 +#define HDMI_3D_TX_PHY_PLLMEASCTRL 0x12 +#define HDMI_3D_TX_PHY_PLLPHBYCTRL 0x13 +#define HDMI_3D_TX_PHY_GRP_CTRL 0x14 +#define HDMI_3D_TX_PHY_GMPCTRL 0x15 +#define HDMI_3D_TX_PHY_MPLLMEASCTRL 0x16 +#define HDMI_3D_TX_PHY_MSM_CTRL 0x17 +#define HDMI_3D_TX_PHY_SCRPB_STATUS 0x18 +#define HDMI_3D_TX_PHY_TXTERM 0x19 +#define HDMI_3D_TX_PHY_PTRPT_ENBL 0x1a +#define HDMI_3D_TX_PHY_PATTERNGEN 0x1b +#define HDMI_3D_TX_PHY_SDCAP_MODE 0x1c +#define HDMI_3D_TX_PHY_SCOPEMODE 0x1d +#define HDMI_3D_TX_PHY_DIGTXMODE 0x1e +#define HDMI_3D_TX_PHY_STR_STATUS 0x1f +#define HDMI_3D_TX_PHY_SCOPECNT0 0x20 +#define HDMI_3D_TX_PHY_SCOPECNT1 0x21 +#define HDMI_3D_TX_PHY_SCOPECNT2 0x22 +#define HDMI_3D_TX_PHY_SCOPECNTCLK 0x23 +#define HDMI_3D_TX_PHY_SCOPESAMPLE 0x24 +#define HDMI_3D_TX_PHY_SCOPECNTMSB01 0x25 +#define HDMI_3D_TX_PHY_SCOPECNTMSB2CK 0x26 + +/* HDMI_3D_TX_PHY_CKCALCTRL values */ +#define HDMI_3D_TX_PHY_CKCALCTRL_OVERRIDE BIT(15) + +/* HDMI_3D_TX_PHY_MSM_CTRL values */ +#define HDMI_3D_TX_PHY_MSM_CTRL_MPLL_PH_SEL_CK BIT(13) +#define HDMI_3D_TX_PHY_MSM_CTRL_CKO_SEL_CLK_REF_MPLL (0 << 1) +#define HDMI_3D_TX_PHY_MSM_CTRL_CKO_SEL_OFF (1 << 1) +#define HDMI_3D_TX_PHY_MSM_CTRL_CKO_SEL_PCLK (2 << 1) +#define HDMI_3D_TX_PHY_MSM_CTRL_CKO_SEL_FB_CLK (3 << 1) +#define HDMI_3D_TX_PHY_MSM_CTRL_SCOPE_CK_SEL BIT(0) + +/* HDMI_3D_TX_PHY_PTRPT_ENBL values */ +#define HDMI_3D_TX_PHY_PTRPT_ENBL_OVERRIDE BIT(15) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_PG_SKIP_BIT2 BIT(8) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_PG_SKIP_BIT1 BIT(7) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_PG_SKIP_BIT0 BIT(6) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_CK_REF_ENB BIT(5) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_RCAL_ENB BIT(4) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_TX_CLK_ALIGN_ENB BIT(3) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_TX_READY BIT(2) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_CKO_WORD_ENB BIT(1) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_REFCLK_ENB BIT(0) + +#endif /* __DW_HDMI_H__ */ diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c new file mode 100644 index 000000000..376fa6eb4 --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c @@ -0,0 +1,1254 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd + * Copyright (C) STMicroelectronics SA 2017 + * + * Modified by Philippe Cornu <philippe.cornu@st.com> + * This generic Synopsys DesignWare MIPI DSI host driver is based on the + * Rockchip version from rockchip/dw-mipi-dsi.c with phy & bridge APIs. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/debugfs.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> + +#include <video/mipi_display.h> + +#include <drm/bridge/dw_mipi_dsi.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> + +#define HWVER_131 0x31333100 /* IP version 1.31 */ + +#define DSI_VERSION 0x00 +#define VERSION GENMASK(31, 8) + +#define DSI_PWR_UP 0x04 +#define RESET 0 +#define POWERUP BIT(0) + +#define DSI_CLKMGR_CFG 0x08 +#define TO_CLK_DIVISION(div) (((div) & 0xff) << 8) +#define TX_ESC_CLK_DIVISION(div) ((div) & 0xff) + +#define DSI_DPI_VCID 0x0c +#define DPI_VCID(vcid) ((vcid) & 0x3) + +#define DSI_DPI_COLOR_CODING 0x10 +#define LOOSELY18_EN BIT(8) +#define DPI_COLOR_CODING_16BIT_1 0x0 +#define DPI_COLOR_CODING_16BIT_2 0x1 +#define DPI_COLOR_CODING_16BIT_3 0x2 +#define DPI_COLOR_CODING_18BIT_1 0x3 +#define DPI_COLOR_CODING_18BIT_2 0x4 +#define DPI_COLOR_CODING_24BIT 0x5 + +#define DSI_DPI_CFG_POL 0x14 +#define COLORM_ACTIVE_LOW BIT(4) +#define SHUTD_ACTIVE_LOW BIT(3) +#define HSYNC_ACTIVE_LOW BIT(2) +#define VSYNC_ACTIVE_LOW BIT(1) +#define DATAEN_ACTIVE_LOW BIT(0) + +#define DSI_DPI_LP_CMD_TIM 0x18 +#define OUTVACT_LPCMD_TIME(p) (((p) & 0xff) << 16) +#define INVACT_LPCMD_TIME(p) ((p) & 0xff) + +#define DSI_DBI_VCID 0x1c +#define DSI_DBI_CFG 0x20 +#define DSI_DBI_PARTITIONING_EN 0x24 +#define DSI_DBI_CMDSIZE 0x28 + +#define DSI_PCKHDL_CFG 0x2c +#define CRC_RX_EN BIT(4) +#define ECC_RX_EN BIT(3) +#define BTA_EN BIT(2) +#define EOTP_RX_EN BIT(1) +#define EOTP_TX_EN BIT(0) + +#define DSI_GEN_VCID 0x30 + +#define DSI_MODE_CFG 0x34 +#define ENABLE_VIDEO_MODE 0 +#define ENABLE_CMD_MODE BIT(0) + +#define DSI_VID_MODE_CFG 0x38 +#define ENABLE_LOW_POWER (0x3f << 8) +#define ENABLE_LOW_POWER_MASK (0x3f << 8) +#define VID_MODE_TYPE_NON_BURST_SYNC_PULSES 0x0 +#define VID_MODE_TYPE_NON_BURST_SYNC_EVENTS 0x1 +#define VID_MODE_TYPE_BURST 0x2 +#define VID_MODE_TYPE_MASK 0x3 +#define ENABLE_LOW_POWER_CMD BIT(15) +#define VID_MODE_VPG_ENABLE BIT(16) +#define VID_MODE_VPG_MODE BIT(20) +#define VID_MODE_VPG_HORIZONTAL BIT(24) + +#define DSI_VID_PKT_SIZE 0x3c +#define VID_PKT_SIZE(p) ((p) & 0x3fff) + +#define DSI_VID_NUM_CHUNKS 0x40 +#define VID_NUM_CHUNKS(c) ((c) & 0x1fff) + +#define DSI_VID_NULL_SIZE 0x44 +#define VID_NULL_SIZE(b) ((b) & 0x1fff) + +#define DSI_VID_HSA_TIME 0x48 +#define DSI_VID_HBP_TIME 0x4c +#define DSI_VID_HLINE_TIME 0x50 +#define DSI_VID_VSA_LINES 0x54 +#define DSI_VID_VBP_LINES 0x58 +#define DSI_VID_VFP_LINES 0x5c +#define DSI_VID_VACTIVE_LINES 0x60 +#define DSI_EDPI_CMD_SIZE 0x64 + +#define DSI_CMD_MODE_CFG 0x68 +#define MAX_RD_PKT_SIZE_LP BIT(24) +#define DCS_LW_TX_LP BIT(19) +#define DCS_SR_0P_TX_LP BIT(18) +#define DCS_SW_1P_TX_LP BIT(17) +#define DCS_SW_0P_TX_LP BIT(16) +#define GEN_LW_TX_LP BIT(14) +#define GEN_SR_2P_TX_LP BIT(13) +#define GEN_SR_1P_TX_LP BIT(12) +#define GEN_SR_0P_TX_LP BIT(11) +#define GEN_SW_2P_TX_LP BIT(10) +#define GEN_SW_1P_TX_LP BIT(9) +#define GEN_SW_0P_TX_LP BIT(8) +#define ACK_RQST_EN BIT(1) +#define TEAR_FX_EN BIT(0) + +#define CMD_MODE_ALL_LP (MAX_RD_PKT_SIZE_LP | \ + DCS_LW_TX_LP | \ + DCS_SR_0P_TX_LP | \ + DCS_SW_1P_TX_LP | \ + DCS_SW_0P_TX_LP | \ + GEN_LW_TX_LP | \ + GEN_SR_2P_TX_LP | \ + GEN_SR_1P_TX_LP | \ + GEN_SR_0P_TX_LP | \ + GEN_SW_2P_TX_LP | \ + GEN_SW_1P_TX_LP | \ + GEN_SW_0P_TX_LP) + +#define DSI_GEN_HDR 0x6c +#define DSI_GEN_PLD_DATA 0x70 + +#define DSI_CMD_PKT_STATUS 0x74 +#define GEN_RD_CMD_BUSY BIT(6) +#define GEN_PLD_R_FULL BIT(5) +#define GEN_PLD_R_EMPTY BIT(4) +#define GEN_PLD_W_FULL BIT(3) +#define GEN_PLD_W_EMPTY BIT(2) +#define GEN_CMD_FULL BIT(1) +#define GEN_CMD_EMPTY BIT(0) + +#define DSI_TO_CNT_CFG 0x78 +#define HSTX_TO_CNT(p) (((p) & 0xffff) << 16) +#define LPRX_TO_CNT(p) ((p) & 0xffff) + +#define DSI_HS_RD_TO_CNT 0x7c +#define DSI_LP_RD_TO_CNT 0x80 +#define DSI_HS_WR_TO_CNT 0x84 +#define DSI_LP_WR_TO_CNT 0x88 +#define DSI_BTA_TO_CNT 0x8c + +#define DSI_LPCLK_CTRL 0x94 +#define AUTO_CLKLANE_CTRL BIT(1) +#define PHY_TXREQUESTCLKHS BIT(0) + +#define DSI_PHY_TMR_LPCLK_CFG 0x98 +#define PHY_CLKHS2LP_TIME(lbcc) (((lbcc) & 0x3ff) << 16) +#define PHY_CLKLP2HS_TIME(lbcc) ((lbcc) & 0x3ff) + +#define DSI_PHY_TMR_CFG 0x9c +#define PHY_HS2LP_TIME(lbcc) (((lbcc) & 0xff) << 24) +#define PHY_LP2HS_TIME(lbcc) (((lbcc) & 0xff) << 16) +#define MAX_RD_TIME(lbcc) ((lbcc) & 0x7fff) +#define PHY_HS2LP_TIME_V131(lbcc) (((lbcc) & 0x3ff) << 16) +#define PHY_LP2HS_TIME_V131(lbcc) ((lbcc) & 0x3ff) + +#define DSI_PHY_RSTZ 0xa0 +#define PHY_DISFORCEPLL 0 +#define PHY_ENFORCEPLL BIT(3) +#define PHY_DISABLECLK 0 +#define PHY_ENABLECLK BIT(2) +#define PHY_RSTZ 0 +#define PHY_UNRSTZ BIT(1) +#define PHY_SHUTDOWNZ 0 +#define PHY_UNSHUTDOWNZ BIT(0) + +#define DSI_PHY_IF_CFG 0xa4 +#define PHY_STOP_WAIT_TIME(cycle) (((cycle) & 0xff) << 8) +#define N_LANES(n) (((n) - 1) & 0x3) + +#define DSI_PHY_ULPS_CTRL 0xa8 +#define DSI_PHY_TX_TRIGGERS 0xac + +#define DSI_PHY_STATUS 0xb0 +#define PHY_STOP_STATE_CLK_LANE BIT(2) +#define PHY_LOCK BIT(0) + +#define DSI_PHY_TST_CTRL0 0xb4 +#define PHY_TESTCLK BIT(1) +#define PHY_UNTESTCLK 0 +#define PHY_TESTCLR BIT(0) +#define PHY_UNTESTCLR 0 + +#define DSI_PHY_TST_CTRL1 0xb8 +#define PHY_TESTEN BIT(16) +#define PHY_UNTESTEN 0 +#define PHY_TESTDOUT(n) (((n) & 0xff) << 8) +#define PHY_TESTDIN(n) ((n) & 0xff) + +#define DSI_INT_ST0 0xbc +#define DSI_INT_ST1 0xc0 +#define DSI_INT_MSK0 0xc4 +#define DSI_INT_MSK1 0xc8 + +#define DSI_PHY_TMR_RD_CFG 0xf4 +#define MAX_RD_TIME_V131(lbcc) ((lbcc) & 0x7fff) + +#define PHY_STATUS_TIMEOUT_US 10000 +#define CMD_PKT_STATUS_TIMEOUT_US 20000 + +#ifdef CONFIG_DEBUG_FS +#define VPG_DEFS(name, dsi) \ + ((void __force *)&((*dsi).vpg_defs.name)) + +#define REGISTER(name, mask, dsi) \ + { #name, VPG_DEFS(name, dsi), mask, dsi } + +struct debugfs_entries { + const char *name; + bool *reg; + u32 mask; + struct dw_mipi_dsi *dsi; +}; +#endif /* CONFIG_DEBUG_FS */ + +struct dw_mipi_dsi { + struct drm_bridge bridge; + struct mipi_dsi_host dsi_host; + struct drm_bridge *panel_bridge; + struct device *dev; + void __iomem *base; + + struct clk *pclk; + + unsigned int lane_mbps; /* per lane */ + u32 channel; + u32 lanes; + u32 format; + unsigned long mode_flags; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; + struct debugfs_entries *debugfs_vpg; + struct { + bool vpg; + bool vpg_horizontal; + bool vpg_ber_pattern; + } vpg_defs; +#endif /* CONFIG_DEBUG_FS */ + + struct dw_mipi_dsi *master; /* dual-dsi master ptr */ + struct dw_mipi_dsi *slave; /* dual-dsi slave ptr */ + + const struct dw_mipi_dsi_plat_data *plat_data; +}; + +/* + * Check if either a link to a master or slave is present + */ +static inline bool dw_mipi_is_dual_mode(struct dw_mipi_dsi *dsi) +{ + return dsi->slave || dsi->master; +} + +/* + * The controller should generate 2 frames before + * preparing the peripheral. + */ +static void dw_mipi_dsi_wait_for_two_frames(const struct drm_display_mode *mode) +{ + int refresh, two_frames; + + refresh = drm_mode_vrefresh(mode); + two_frames = DIV_ROUND_UP(MSEC_PER_SEC, refresh) * 2; + msleep(two_frames); +} + +static inline struct dw_mipi_dsi *host_to_dsi(struct mipi_dsi_host *host) +{ + return container_of(host, struct dw_mipi_dsi, dsi_host); +} + +static inline struct dw_mipi_dsi *bridge_to_dsi(struct drm_bridge *bridge) +{ + return container_of(bridge, struct dw_mipi_dsi, bridge); +} + +static inline void dsi_write(struct dw_mipi_dsi *dsi, u32 reg, u32 val) +{ + writel(val, dsi->base + reg); +} + +static inline u32 dsi_read(struct dw_mipi_dsi *dsi, u32 reg) +{ + return readl(dsi->base + reg); +} + +static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct dw_mipi_dsi *dsi = host_to_dsi(host); + const struct dw_mipi_dsi_plat_data *pdata = dsi->plat_data; + struct drm_bridge *bridge; + struct drm_panel *panel; + int ret; + + if (device->lanes > dsi->plat_data->max_data_lanes) { + dev_err(dsi->dev, "the number of data lanes(%u) is too many\n", + device->lanes); + return -EINVAL; + } + + dsi->lanes = device->lanes; + dsi->channel = device->channel; + dsi->format = device->format; + dsi->mode_flags = device->mode_flags; + + ret = drm_of_find_panel_or_bridge(host->dev->of_node, 1, 0, + &panel, &bridge); + if (ret) + return ret; + + if (panel) { + bridge = drm_panel_bridge_add_typed(panel, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + } + + dsi->panel_bridge = bridge; + + drm_bridge_add(&dsi->bridge); + + if (pdata->host_ops && pdata->host_ops->attach) { + ret = pdata->host_ops->attach(pdata->priv_data, device); + if (ret < 0) + return ret; + } + + return 0; +} + +static int dw_mipi_dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct dw_mipi_dsi *dsi = host_to_dsi(host); + const struct dw_mipi_dsi_plat_data *pdata = dsi->plat_data; + int ret; + + if (pdata->host_ops && pdata->host_ops->detach) { + ret = pdata->host_ops->detach(pdata->priv_data, device); + if (ret < 0) + return ret; + } + + drm_of_panel_bridge_remove(host->dev->of_node, 1, 0); + + drm_bridge_remove(&dsi->bridge); + + return 0; +} + +static void dw_mipi_message_config(struct dw_mipi_dsi *dsi, + const struct mipi_dsi_msg *msg) +{ + bool lpm = msg->flags & MIPI_DSI_MSG_USE_LPM; + u32 val = 0; + + /* + * TODO dw drv improvements + * largest packet sizes during hfp or during vsa/vpb/vfp + * should be computed according to byte lane, lane number and only + * if sending lp cmds in high speed is enable (PHY_TXREQUESTCLKHS) + */ + dsi_write(dsi, DSI_DPI_LP_CMD_TIM, OUTVACT_LPCMD_TIME(16) + | INVACT_LPCMD_TIME(4)); + + if (msg->flags & MIPI_DSI_MSG_REQ_ACK) + val |= ACK_RQST_EN; + if (lpm) + val |= CMD_MODE_ALL_LP; + + dsi_write(dsi, DSI_CMD_MODE_CFG, val); + + val = dsi_read(dsi, DSI_VID_MODE_CFG); + if (lpm) + val |= ENABLE_LOW_POWER_CMD; + else + val &= ~ENABLE_LOW_POWER_CMD; + dsi_write(dsi, DSI_VID_MODE_CFG, val); +} + +static int dw_mipi_dsi_gen_pkt_hdr_write(struct dw_mipi_dsi *dsi, u32 hdr_val) +{ + int ret; + u32 val, mask; + + ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS, + val, !(val & GEN_CMD_FULL), 1000, + CMD_PKT_STATUS_TIMEOUT_US); + if (ret) { + dev_err(dsi->dev, "failed to get available command FIFO\n"); + return ret; + } + + dsi_write(dsi, DSI_GEN_HDR, hdr_val); + + mask = GEN_CMD_EMPTY | GEN_PLD_W_EMPTY; + ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS, + val, (val & mask) == mask, + 1000, CMD_PKT_STATUS_TIMEOUT_US); + if (ret) { + dev_err(dsi->dev, "failed to write command FIFO\n"); + return ret; + } + + return 0; +} + +static int dw_mipi_dsi_write(struct dw_mipi_dsi *dsi, + const struct mipi_dsi_packet *packet) +{ + const u8 *tx_buf = packet->payload; + int len = packet->payload_length, pld_data_bytes = sizeof(u32), ret; + __le32 word; + u32 val; + + while (len) { + if (len < pld_data_bytes) { + word = 0; + memcpy(&word, tx_buf, len); + dsi_write(dsi, DSI_GEN_PLD_DATA, le32_to_cpu(word)); + len = 0; + } else { + memcpy(&word, tx_buf, pld_data_bytes); + dsi_write(dsi, DSI_GEN_PLD_DATA, le32_to_cpu(word)); + tx_buf += pld_data_bytes; + len -= pld_data_bytes; + } + + ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS, + val, !(val & GEN_PLD_W_FULL), 1000, + CMD_PKT_STATUS_TIMEOUT_US); + if (ret) { + dev_err(dsi->dev, + "failed to get available write payload FIFO\n"); + return ret; + } + } + + word = 0; + memcpy(&word, packet->header, sizeof(packet->header)); + return dw_mipi_dsi_gen_pkt_hdr_write(dsi, le32_to_cpu(word)); +} + +static int dw_mipi_dsi_read(struct dw_mipi_dsi *dsi, + const struct mipi_dsi_msg *msg) +{ + int i, j, ret, len = msg->rx_len; + u8 *buf = msg->rx_buf; + u32 val; + + /* Wait end of the read operation */ + ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS, + val, !(val & GEN_RD_CMD_BUSY), + 1000, CMD_PKT_STATUS_TIMEOUT_US); + if (ret) { + dev_err(dsi->dev, "Timeout during read operation\n"); + return ret; + } + + for (i = 0; i < len; i += 4) { + /* Read fifo must not be empty before all bytes are read */ + ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS, + val, !(val & GEN_PLD_R_EMPTY), + 1000, CMD_PKT_STATUS_TIMEOUT_US); + if (ret) { + dev_err(dsi->dev, "Read payload FIFO is empty\n"); + return ret; + } + + val = dsi_read(dsi, DSI_GEN_PLD_DATA); + for (j = 0; j < 4 && j + i < len; j++) + buf[i + j] = val >> (8 * j); + } + + return ret; +} + +static ssize_t dw_mipi_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct dw_mipi_dsi *dsi = host_to_dsi(host); + struct mipi_dsi_packet packet; + int ret, nb_bytes; + + ret = mipi_dsi_create_packet(&packet, msg); + if (ret) { + dev_err(dsi->dev, "failed to create packet: %d\n", ret); + return ret; + } + + dw_mipi_message_config(dsi, msg); + if (dsi->slave) + dw_mipi_message_config(dsi->slave, msg); + + ret = dw_mipi_dsi_write(dsi, &packet); + if (ret) + return ret; + if (dsi->slave) { + ret = dw_mipi_dsi_write(dsi->slave, &packet); + if (ret) + return ret; + } + + if (msg->rx_buf && msg->rx_len) { + ret = dw_mipi_dsi_read(dsi, msg); + if (ret) + return ret; + nb_bytes = msg->rx_len; + } else { + nb_bytes = packet.size; + } + + return nb_bytes; +} + +static const struct mipi_dsi_host_ops dw_mipi_dsi_host_ops = { + .attach = dw_mipi_dsi_host_attach, + .detach = dw_mipi_dsi_host_detach, + .transfer = dw_mipi_dsi_host_transfer, +}; + +static void dw_mipi_dsi_video_mode_config(struct dw_mipi_dsi *dsi) +{ + u32 val; + + /* + * TODO dw drv improvements + * enabling low power is panel-dependent, we should use the + * panel configuration here... + */ + val = ENABLE_LOW_POWER; + + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) + val |= VID_MODE_TYPE_BURST; + else if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + val |= VID_MODE_TYPE_NON_BURST_SYNC_PULSES; + else + val |= VID_MODE_TYPE_NON_BURST_SYNC_EVENTS; + +#ifdef CONFIG_DEBUG_FS + if (dsi->vpg_defs.vpg) { + val |= VID_MODE_VPG_ENABLE; + val |= dsi->vpg_defs.vpg_horizontal ? + VID_MODE_VPG_HORIZONTAL : 0; + val |= dsi->vpg_defs.vpg_ber_pattern ? VID_MODE_VPG_MODE : 0; + } +#endif /* CONFIG_DEBUG_FS */ + + dsi_write(dsi, DSI_VID_MODE_CFG, val); +} + +static void dw_mipi_dsi_set_mode(struct dw_mipi_dsi *dsi, + unsigned long mode_flags) +{ + u32 val; + + dsi_write(dsi, DSI_PWR_UP, RESET); + + if (mode_flags & MIPI_DSI_MODE_VIDEO) { + dsi_write(dsi, DSI_MODE_CFG, ENABLE_VIDEO_MODE); + dw_mipi_dsi_video_mode_config(dsi); + } else { + dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE); + } + + val = PHY_TXREQUESTCLKHS; + if (dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) + val |= AUTO_CLKLANE_CTRL; + dsi_write(dsi, DSI_LPCLK_CTRL, val); + + dsi_write(dsi, DSI_PWR_UP, POWERUP); +} + +static void dw_mipi_dsi_disable(struct dw_mipi_dsi *dsi) +{ + dsi_write(dsi, DSI_PWR_UP, RESET); + dsi_write(dsi, DSI_PHY_RSTZ, PHY_RSTZ); +} + +static void dw_mipi_dsi_init(struct dw_mipi_dsi *dsi) +{ + const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops; + unsigned int esc_rate; /* in MHz */ + u32 esc_clk_division; + int ret; + + /* + * The maximum permitted escape clock is 20MHz and it is derived from + * lanebyteclk, which is running at "lane_mbps / 8". + */ + if (phy_ops->get_esc_clk_rate) { + ret = phy_ops->get_esc_clk_rate(dsi->plat_data->priv_data, + &esc_rate); + if (ret) + DRM_DEBUG_DRIVER("Phy get_esc_clk_rate() failed\n"); + } else + esc_rate = 20; /* Default to 20MHz */ + + /* + * We want : + * (lane_mbps >> 3) / esc_clk_division < X + * which is: + * (lane_mbps >> 3) / X > esc_clk_division + */ + esc_clk_division = (dsi->lane_mbps >> 3) / esc_rate + 1; + + dsi_write(dsi, DSI_PWR_UP, RESET); + + /* + * TODO dw drv improvements + * timeout clock division should be computed with the + * high speed transmission counter timeout and byte lane... + */ + dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVISION(10) | + TX_ESC_CLK_DIVISION(esc_clk_division)); +} + +static void dw_mipi_dsi_dpi_config(struct dw_mipi_dsi *dsi, + const struct drm_display_mode *mode) +{ + u32 val = 0, color = 0; + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB888: + color = DPI_COLOR_CODING_24BIT; + break; + case MIPI_DSI_FMT_RGB666: + color = DPI_COLOR_CODING_18BIT_2 | LOOSELY18_EN; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + color = DPI_COLOR_CODING_18BIT_1; + break; + case MIPI_DSI_FMT_RGB565: + color = DPI_COLOR_CODING_16BIT_1; + break; + } + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + val |= VSYNC_ACTIVE_LOW; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + val |= HSYNC_ACTIVE_LOW; + + dsi_write(dsi, DSI_DPI_VCID, DPI_VCID(dsi->channel)); + dsi_write(dsi, DSI_DPI_COLOR_CODING, color); + dsi_write(dsi, DSI_DPI_CFG_POL, val); +} + +static void dw_mipi_dsi_packet_handler_config(struct dw_mipi_dsi *dsi) +{ + dsi_write(dsi, DSI_PCKHDL_CFG, CRC_RX_EN | ECC_RX_EN | BTA_EN); +} + +static void dw_mipi_dsi_video_packet_config(struct dw_mipi_dsi *dsi, + const struct drm_display_mode *mode) +{ + /* + * TODO dw drv improvements + * only burst mode is supported here. For non-burst video modes, + * we should compute DSI_VID_PKT_SIZE, DSI_VCCR.NUMC & + * DSI_VNPCR.NPSIZE... especially because this driver supports + * non-burst video modes, see dw_mipi_dsi_video_mode_config()... + */ + + dsi_write(dsi, DSI_VID_PKT_SIZE, + dw_mipi_is_dual_mode(dsi) ? + VID_PKT_SIZE(mode->hdisplay / 2) : + VID_PKT_SIZE(mode->hdisplay)); +} + +static void dw_mipi_dsi_command_mode_config(struct dw_mipi_dsi *dsi) +{ + /* + * TODO dw drv improvements + * compute high speed transmission counter timeout according + * to the timeout clock division (TO_CLK_DIVISION) and byte lane... + */ + dsi_write(dsi, DSI_TO_CNT_CFG, HSTX_TO_CNT(1000) | LPRX_TO_CNT(1000)); + /* + * TODO dw drv improvements + * the Bus-Turn-Around Timeout Counter should be computed + * according to byte lane... + */ + dsi_write(dsi, DSI_BTA_TO_CNT, 0xd00); + dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE); +} + +/* Get lane byte clock cycles. */ +static u32 dw_mipi_dsi_get_hcomponent_lbcc(struct dw_mipi_dsi *dsi, + const struct drm_display_mode *mode, + u32 hcomponent) +{ + u32 frac, lbcc; + + lbcc = hcomponent * dsi->lane_mbps * MSEC_PER_SEC / 8; + + frac = lbcc % mode->clock; + lbcc = lbcc / mode->clock; + if (frac) + lbcc++; + + return lbcc; +} + +static void dw_mipi_dsi_line_timer_config(struct dw_mipi_dsi *dsi, + const struct drm_display_mode *mode) +{ + u32 htotal, hsa, hbp, lbcc; + + htotal = mode->htotal; + hsa = mode->hsync_end - mode->hsync_start; + hbp = mode->htotal - mode->hsync_end; + + /* + * TODO dw drv improvements + * computations below may be improved... + */ + lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, mode, htotal); + dsi_write(dsi, DSI_VID_HLINE_TIME, lbcc); + + lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, mode, hsa); + dsi_write(dsi, DSI_VID_HSA_TIME, lbcc); + + lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, mode, hbp); + dsi_write(dsi, DSI_VID_HBP_TIME, lbcc); +} + +static void dw_mipi_dsi_vertical_timing_config(struct dw_mipi_dsi *dsi, + const struct drm_display_mode *mode) +{ + u32 vactive, vsa, vfp, vbp; + + vactive = mode->vdisplay; + vsa = mode->vsync_end - mode->vsync_start; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + + dsi_write(dsi, DSI_VID_VACTIVE_LINES, vactive); + dsi_write(dsi, DSI_VID_VSA_LINES, vsa); + dsi_write(dsi, DSI_VID_VFP_LINES, vfp); + dsi_write(dsi, DSI_VID_VBP_LINES, vbp); +} + +static void dw_mipi_dsi_dphy_timing_config(struct dw_mipi_dsi *dsi) +{ + const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops; + struct dw_mipi_dsi_dphy_timing timing; + u32 hw_version; + int ret; + + ret = phy_ops->get_timing(dsi->plat_data->priv_data, + dsi->lane_mbps, &timing); + if (ret) + DRM_DEV_ERROR(dsi->dev, "Retrieving phy timings failed\n"); + + /* + * TODO dw drv improvements + * data & clock lane timers should be computed according to panel + * blankings and to the automatic clock lane control mode... + * note: DSI_PHY_TMR_CFG.MAX_RD_TIME should be in line with + * DSI_CMD_MODE_CFG.MAX_RD_PKT_SIZE_LP (see CMD_MODE_ALL_LP) + */ + + hw_version = dsi_read(dsi, DSI_VERSION) & VERSION; + + if (hw_version >= HWVER_131) { + dsi_write(dsi, DSI_PHY_TMR_CFG, + PHY_HS2LP_TIME_V131(timing.data_hs2lp) | + PHY_LP2HS_TIME_V131(timing.data_lp2hs)); + dsi_write(dsi, DSI_PHY_TMR_RD_CFG, MAX_RD_TIME_V131(10000)); + } else { + dsi_write(dsi, DSI_PHY_TMR_CFG, + PHY_HS2LP_TIME(timing.data_hs2lp) | + PHY_LP2HS_TIME(timing.data_lp2hs) | + MAX_RD_TIME(10000)); + } + + dsi_write(dsi, DSI_PHY_TMR_LPCLK_CFG, + PHY_CLKHS2LP_TIME(timing.clk_hs2lp) | + PHY_CLKLP2HS_TIME(timing.clk_lp2hs)); +} + +static void dw_mipi_dsi_dphy_interface_config(struct dw_mipi_dsi *dsi) +{ + /* + * TODO dw drv improvements + * stop wait time should be the maximum between host dsi + * and panel stop wait times + */ + dsi_write(dsi, DSI_PHY_IF_CFG, PHY_STOP_WAIT_TIME(0x20) | + N_LANES(dsi->lanes)); +} + +static void dw_mipi_dsi_dphy_init(struct dw_mipi_dsi *dsi) +{ + /* Clear PHY state */ + dsi_write(dsi, DSI_PHY_RSTZ, PHY_DISFORCEPLL | PHY_DISABLECLK + | PHY_RSTZ | PHY_SHUTDOWNZ); + dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLR); + dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLR); + dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLR); +} + +static void dw_mipi_dsi_dphy_enable(struct dw_mipi_dsi *dsi) +{ + u32 val; + int ret; + + dsi_write(dsi, DSI_PHY_RSTZ, PHY_ENFORCEPLL | PHY_ENABLECLK | + PHY_UNRSTZ | PHY_UNSHUTDOWNZ); + + ret = readl_poll_timeout(dsi->base + DSI_PHY_STATUS, val, + val & PHY_LOCK, 1000, PHY_STATUS_TIMEOUT_US); + if (ret) + DRM_DEBUG_DRIVER("failed to wait phy lock state\n"); + + ret = readl_poll_timeout(dsi->base + DSI_PHY_STATUS, + val, val & PHY_STOP_STATE_CLK_LANE, 1000, + PHY_STATUS_TIMEOUT_US); + if (ret) + DRM_DEBUG_DRIVER("failed to wait phy clk lane stop state\n"); +} + +static void dw_mipi_dsi_clear_err(struct dw_mipi_dsi *dsi) +{ + dsi_read(dsi, DSI_INT_ST0); + dsi_read(dsi, DSI_INT_ST1); + dsi_write(dsi, DSI_INT_MSK0, 0); + dsi_write(dsi, DSI_INT_MSK1, 0); +} + +static void dw_mipi_dsi_bridge_post_disable(struct drm_bridge *bridge) +{ + struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); + const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops; + + /* + * Switch to command mode before panel-bridge post_disable & + * panel unprepare. + * Note: panel-bridge disable & panel disable has been called + * before by the drm framework. + */ + dw_mipi_dsi_set_mode(dsi, 0); + + /* + * TODO Only way found to call panel-bridge post_disable & + * panel unprepare before the dsi "final" disable... + * This needs to be fixed in the drm_bridge framework and the API + * needs to be updated to manage our own call chains... + */ + if (dsi->panel_bridge->funcs->post_disable) + dsi->panel_bridge->funcs->post_disable(dsi->panel_bridge); + + if (phy_ops->power_off) + phy_ops->power_off(dsi->plat_data->priv_data); + + if (dsi->slave) { + dw_mipi_dsi_disable(dsi->slave); + clk_disable_unprepare(dsi->slave->pclk); + pm_runtime_put(dsi->slave->dev); + } + dw_mipi_dsi_disable(dsi); + + clk_disable_unprepare(dsi->pclk); + pm_runtime_put(dsi->dev); +} + +static unsigned int dw_mipi_dsi_get_lanes(struct dw_mipi_dsi *dsi) +{ + /* this instance is the slave, so add the master's lanes */ + if (dsi->master) + return dsi->master->lanes + dsi->lanes; + + /* this instance is the master, so add the slave's lanes */ + if (dsi->slave) + return dsi->lanes + dsi->slave->lanes; + + /* single-dsi, so no other instance to consider */ + return dsi->lanes; +} + +static void dw_mipi_dsi_mode_set(struct dw_mipi_dsi *dsi, + const struct drm_display_mode *adjusted_mode) +{ + const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops; + void *priv_data = dsi->plat_data->priv_data; + int ret; + u32 lanes = dw_mipi_dsi_get_lanes(dsi); + + clk_prepare_enable(dsi->pclk); + + ret = phy_ops->get_lane_mbps(priv_data, adjusted_mode, dsi->mode_flags, + lanes, dsi->format, &dsi->lane_mbps); + if (ret) + DRM_DEBUG_DRIVER("Phy get_lane_mbps() failed\n"); + + pm_runtime_get_sync(dsi->dev); + dw_mipi_dsi_init(dsi); + dw_mipi_dsi_dpi_config(dsi, adjusted_mode); + dw_mipi_dsi_packet_handler_config(dsi); + dw_mipi_dsi_video_mode_config(dsi); + dw_mipi_dsi_video_packet_config(dsi, adjusted_mode); + dw_mipi_dsi_command_mode_config(dsi); + dw_mipi_dsi_line_timer_config(dsi, adjusted_mode); + dw_mipi_dsi_vertical_timing_config(dsi, adjusted_mode); + + dw_mipi_dsi_dphy_init(dsi); + dw_mipi_dsi_dphy_timing_config(dsi); + dw_mipi_dsi_dphy_interface_config(dsi); + + dw_mipi_dsi_clear_err(dsi); + + ret = phy_ops->init(priv_data); + if (ret) + DRM_DEBUG_DRIVER("Phy init() failed\n"); + + dw_mipi_dsi_dphy_enable(dsi); + + dw_mipi_dsi_wait_for_two_frames(adjusted_mode); + + /* Switch to cmd mode for panel-bridge pre_enable & panel prepare */ + dw_mipi_dsi_set_mode(dsi, 0); + + if (phy_ops->power_on) + phy_ops->power_on(dsi->plat_data->priv_data); +} + +static void dw_mipi_dsi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); + + dw_mipi_dsi_mode_set(dsi, adjusted_mode); + if (dsi->slave) + dw_mipi_dsi_mode_set(dsi->slave, adjusted_mode); +} + +static void dw_mipi_dsi_bridge_enable(struct drm_bridge *bridge) +{ + struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); + + /* Switch to video mode for panel-bridge enable & panel enable */ + dw_mipi_dsi_set_mode(dsi, MIPI_DSI_MODE_VIDEO); + if (dsi->slave) + dw_mipi_dsi_set_mode(dsi->slave, MIPI_DSI_MODE_VIDEO); +} + +static enum drm_mode_status +dw_mipi_dsi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); + const struct dw_mipi_dsi_plat_data *pdata = dsi->plat_data; + enum drm_mode_status mode_status = MODE_OK; + + if (pdata->mode_valid) + mode_status = pdata->mode_valid(pdata->priv_data, mode); + + return mode_status; +} + +static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found\n"); + return -ENODEV; + } + + /* Set the encoder type as caller does not know it */ + bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI; + + /* Attach the panel-bridge to the dsi bridge */ + return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge, + flags); +} + +static const struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = { + .mode_set = dw_mipi_dsi_bridge_mode_set, + .enable = dw_mipi_dsi_bridge_enable, + .post_disable = dw_mipi_dsi_bridge_post_disable, + .mode_valid = dw_mipi_dsi_bridge_mode_valid, + .attach = dw_mipi_dsi_bridge_attach, +}; + +#ifdef CONFIG_DEBUG_FS + +static int dw_mipi_dsi_debugfs_write(void *data, u64 val) +{ + struct debugfs_entries *vpg = data; + struct dw_mipi_dsi *dsi; + u32 mode_cfg; + + if (!vpg) + return -ENODEV; + + dsi = vpg->dsi; + + *vpg->reg = (bool)val; + + mode_cfg = dsi_read(dsi, DSI_VID_MODE_CFG); + + if (*vpg->reg) + mode_cfg |= vpg->mask; + else + mode_cfg &= ~vpg->mask; + + dsi_write(dsi, DSI_VID_MODE_CFG, mode_cfg); + + return 0; +} + +static int dw_mipi_dsi_debugfs_show(void *data, u64 *val) +{ + struct debugfs_entries *vpg = data; + + if (!vpg) + return -ENODEV; + + *val = *vpg->reg; + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(fops_x32, dw_mipi_dsi_debugfs_show, + dw_mipi_dsi_debugfs_write, "%llu\n"); + +static void debugfs_create_files(void *data) +{ + struct dw_mipi_dsi *dsi = data; + struct debugfs_entries debugfs[] = { + REGISTER(vpg, VID_MODE_VPG_ENABLE, dsi), + REGISTER(vpg_horizontal, VID_MODE_VPG_HORIZONTAL, dsi), + REGISTER(vpg_ber_pattern, VID_MODE_VPG_MODE, dsi), + }; + int i; + + dsi->debugfs_vpg = kmemdup(debugfs, sizeof(debugfs), GFP_KERNEL); + if (!dsi->debugfs_vpg) + return; + + for (i = 0; i < ARRAY_SIZE(debugfs); i++) + debugfs_create_file(dsi->debugfs_vpg[i].name, 0644, + dsi->debugfs, &dsi->debugfs_vpg[i], + &fops_x32); +} + +static void dw_mipi_dsi_debugfs_init(struct dw_mipi_dsi *dsi) +{ + dsi->debugfs = debugfs_create_dir(dev_name(dsi->dev), NULL); + if (IS_ERR(dsi->debugfs)) { + dev_err(dsi->dev, "failed to create debugfs root\n"); + return; + } + + debugfs_create_files(dsi); +} + +static void dw_mipi_dsi_debugfs_remove(struct dw_mipi_dsi *dsi) +{ + debugfs_remove_recursive(dsi->debugfs); + kfree(dsi->debugfs_vpg); +} + +#else + +static void dw_mipi_dsi_debugfs_init(struct dw_mipi_dsi *dsi) { } +static void dw_mipi_dsi_debugfs_remove(struct dw_mipi_dsi *dsi) { } + +#endif /* CONFIG_DEBUG_FS */ + +static struct dw_mipi_dsi * +__dw_mipi_dsi_probe(struct platform_device *pdev, + const struct dw_mipi_dsi_plat_data *plat_data) +{ + struct device *dev = &pdev->dev; + struct reset_control *apb_rst; + struct dw_mipi_dsi *dsi; + int ret; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return ERR_PTR(-ENOMEM); + + dsi->dev = dev; + dsi->plat_data = plat_data; + + if (!plat_data->phy_ops->init || !plat_data->phy_ops->get_lane_mbps || + !plat_data->phy_ops->get_timing) { + DRM_ERROR("Phy not properly configured\n"); + return ERR_PTR(-ENODEV); + } + + if (!plat_data->base) { + dsi->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dsi->base)) + return ERR_PTR(-ENODEV); + + } else { + dsi->base = plat_data->base; + } + + dsi->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(dsi->pclk)) { + ret = PTR_ERR(dsi->pclk); + dev_err(dev, "Unable to get pclk: %d\n", ret); + return ERR_PTR(ret); + } + + /* + * Note that the reset was not defined in the initial device tree, so + * we have to be prepared for it not being found. + */ + apb_rst = devm_reset_control_get_optional_exclusive(dev, "apb"); + if (IS_ERR(apb_rst)) { + ret = PTR_ERR(apb_rst); + + if (ret != -EPROBE_DEFER) + dev_err(dev, "Unable to get reset control: %d\n", ret); + + return ERR_PTR(ret); + } + + if (apb_rst) { + ret = clk_prepare_enable(dsi->pclk); + if (ret) { + dev_err(dev, "%s: Failed to enable pclk\n", __func__); + return ERR_PTR(ret); + } + + reset_control_assert(apb_rst); + usleep_range(10, 20); + reset_control_deassert(apb_rst); + + clk_disable_unprepare(dsi->pclk); + } + + dw_mipi_dsi_debugfs_init(dsi); + pm_runtime_enable(dev); + + dsi->dsi_host.ops = &dw_mipi_dsi_host_ops; + dsi->dsi_host.dev = dev; + ret = mipi_dsi_host_register(&dsi->dsi_host); + if (ret) { + dev_err(dev, "Failed to register MIPI host: %d\n", ret); + pm_runtime_disable(dev); + dw_mipi_dsi_debugfs_remove(dsi); + return ERR_PTR(ret); + } + + dsi->bridge.driver_private = dsi; + dsi->bridge.funcs = &dw_mipi_dsi_bridge_funcs; +#ifdef CONFIG_OF + dsi->bridge.of_node = pdev->dev.of_node; +#endif + + return dsi; +} + +static void __dw_mipi_dsi_remove(struct dw_mipi_dsi *dsi) +{ + mipi_dsi_host_unregister(&dsi->dsi_host); + + pm_runtime_disable(dsi->dev); + dw_mipi_dsi_debugfs_remove(dsi); +} + +void dw_mipi_dsi_set_slave(struct dw_mipi_dsi *dsi, struct dw_mipi_dsi *slave) +{ + /* introduce controllers to each other */ + dsi->slave = slave; + dsi->slave->master = dsi; + + /* migrate settings for already attached displays */ + dsi->slave->lanes = dsi->lanes; + dsi->slave->channel = dsi->channel; + dsi->slave->format = dsi->format; + dsi->slave->mode_flags = dsi->mode_flags; +} +EXPORT_SYMBOL_GPL(dw_mipi_dsi_set_slave); + +/* + * Probe/remove API, used from platforms based on the DRM bridge API. + */ +struct dw_mipi_dsi * +dw_mipi_dsi_probe(struct platform_device *pdev, + const struct dw_mipi_dsi_plat_data *plat_data) +{ + return __dw_mipi_dsi_probe(pdev, plat_data); +} +EXPORT_SYMBOL_GPL(dw_mipi_dsi_probe); + +void dw_mipi_dsi_remove(struct dw_mipi_dsi *dsi) +{ + __dw_mipi_dsi_remove(dsi); +} +EXPORT_SYMBOL_GPL(dw_mipi_dsi_remove); + +/* + * Bind/unbind API, used from platforms based on the component framework. + */ +int dw_mipi_dsi_bind(struct dw_mipi_dsi *dsi, struct drm_encoder *encoder) +{ + int ret; + + ret = drm_bridge_attach(encoder, &dsi->bridge, NULL, 0); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(dw_mipi_dsi_bind); + +void dw_mipi_dsi_unbind(struct dw_mipi_dsi *dsi) +{ +} +EXPORT_SYMBOL_GPL(dw_mipi_dsi_unbind); + +MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>"); +MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>"); +MODULE_DESCRIPTION("DW MIPI DSI host controller driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:dw-mipi-dsi"); diff --git a/drivers/gpu/drm/bridge/tc358762.c b/drivers/gpu/drm/bridge/tc358762.c new file mode 100644 index 000000000..21c57d343 --- /dev/null +++ b/drivers/gpu/drm/bridge/tc358762.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Marek Vasut <marex@denx.de> + * + * Based on tc358764.c by + * Andrzej Hajda <a.hajda@samsung.com> + * Maciej Purski <m.purski@samsung.com> + * + * Based on rpi_touchscreen.c by + * Eric Anholt <eric@anholt.net> + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +/* PPI layer registers */ +#define PPI_STARTPPI 0x0104 /* START control bit */ +#define PPI_LPTXTIMECNT 0x0114 /* LPTX timing signal */ +#define PPI_D0S_ATMR 0x0144 +#define PPI_D1S_ATMR 0x0148 +#define PPI_D0S_CLRSIPOCOUNT 0x0164 /* Assertion timer for Lane 0 */ +#define PPI_D1S_CLRSIPOCOUNT 0x0168 /* Assertion timer for Lane 1 */ +#define PPI_START_FUNCTION 1 + +/* DSI layer registers */ +#define DSI_STARTDSI 0x0204 /* START control bit of DSI-TX */ +#define DSI_LANEENABLE 0x0210 /* Enables each lane */ +#define DSI_RX_START 1 + +/* LCDC/DPI Host Registers */ +#define LCDCTRL 0x0420 + +/* SPI Master Registers */ +#define SPICMR 0x0450 +#define SPITCR 0x0454 + +/* System Controller Registers */ +#define SYSCTRL 0x0464 + +/* System registers */ +#define LPX_PERIOD 3 + +/* Lane enable PPI and DSI register bits */ +#define LANEENABLE_CLEN BIT(0) +#define LANEENABLE_L0EN BIT(1) +#define LANEENABLE_L1EN BIT(2) + +struct tc358762 { + struct device *dev; + struct drm_bridge bridge; + struct drm_connector connector; + struct regulator *regulator; + struct drm_bridge *panel_bridge; + bool pre_enabled; + int error; +}; + +static int tc358762_clear_error(struct tc358762 *ctx) +{ + int ret = ctx->error; + + ctx->error = 0; + return ret; +} + +static void tc358762_write(struct tc358762 *ctx, u16 addr, u32 val) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + ssize_t ret; + u8 data[6]; + + if (ctx->error) + return; + + data[0] = addr; + data[1] = addr >> 8; + data[2] = val; + data[3] = val >> 8; + data[4] = val >> 16; + data[5] = val >> 24; + + ret = mipi_dsi_generic_write(dsi, data, sizeof(data)); + if (ret < 0) + ctx->error = ret; +} + +static inline struct tc358762 *bridge_to_tc358762(struct drm_bridge *bridge) +{ + return container_of(bridge, struct tc358762, bridge); +} + +static int tc358762_init(struct tc358762 *ctx) +{ + tc358762_write(ctx, DSI_LANEENABLE, + LANEENABLE_L0EN | LANEENABLE_CLEN); + tc358762_write(ctx, PPI_D0S_CLRSIPOCOUNT, 5); + tc358762_write(ctx, PPI_D1S_CLRSIPOCOUNT, 5); + tc358762_write(ctx, PPI_D0S_ATMR, 0); + tc358762_write(ctx, PPI_D1S_ATMR, 0); + tc358762_write(ctx, PPI_LPTXTIMECNT, LPX_PERIOD); + + tc358762_write(ctx, SPICMR, 0x00); + tc358762_write(ctx, LCDCTRL, 0x00100150); + tc358762_write(ctx, SYSCTRL, 0x040f); + msleep(100); + + tc358762_write(ctx, PPI_STARTPPI, PPI_START_FUNCTION); + tc358762_write(ctx, DSI_STARTDSI, DSI_RX_START); + + msleep(100); + + return tc358762_clear_error(ctx); +} + +static void tc358762_post_disable(struct drm_bridge *bridge) +{ + struct tc358762 *ctx = bridge_to_tc358762(bridge); + int ret; + + /* + * The post_disable hook might be called multiple times. + * We want to avoid regulator imbalance below. + */ + if (!ctx->pre_enabled) + return; + + ctx->pre_enabled = false; + + ret = regulator_disable(ctx->regulator); + if (ret < 0) + dev_err(ctx->dev, "error disabling regulators (%d)\n", ret); +} + +static void tc358762_pre_enable(struct drm_bridge *bridge) +{ + struct tc358762 *ctx = bridge_to_tc358762(bridge); + int ret; + + ret = regulator_enable(ctx->regulator); + if (ret < 0) + dev_err(ctx->dev, "error enabling regulators (%d)\n", ret); + + ret = tc358762_init(ctx); + if (ret < 0) + dev_err(ctx->dev, "error initializing bridge (%d)\n", ret); + + ctx->pre_enabled = true; +} + +static int tc358762_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct tc358762 *ctx = bridge_to_tc358762(bridge); + + return drm_bridge_attach(bridge->encoder, ctx->panel_bridge, + bridge, flags); +} + +static const struct drm_bridge_funcs tc358762_bridge_funcs = { + .post_disable = tc358762_post_disable, + .pre_enable = tc358762_pre_enable, + .attach = tc358762_attach, +}; + +static int tc358762_parse_dt(struct tc358762 *ctx) +{ + struct drm_bridge *panel_bridge; + struct device *dev = ctx->dev; + struct drm_panel *panel; + int ret; + + ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL); + if (ret) + return ret; + + panel_bridge = devm_drm_panel_bridge_add(dev, panel); + + if (IS_ERR(panel_bridge)) + return PTR_ERR(panel_bridge); + + ctx->panel_bridge = panel_bridge; + + return 0; +} + +static int tc358762_configure_regulators(struct tc358762 *ctx) +{ + ctx->regulator = devm_regulator_get(ctx->dev, "vddc"); + if (IS_ERR(ctx->regulator)) + return PTR_ERR(ctx->regulator); + + return 0; +} + +static int tc358762_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct tc358762 *ctx; + int ret; + + ctx = devm_kzalloc(dev, sizeof(struct tc358762), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->dev = dev; + ctx->pre_enabled = false; + + /* TODO: Find out how to get dual-lane mode working */ + dsi->lanes = 1; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_VIDEO_HSE; + + ret = tc358762_parse_dt(ctx); + if (ret < 0) + return ret; + + ret = tc358762_configure_regulators(ctx); + if (ret < 0) + return ret; + + ctx->bridge.funcs = &tc358762_bridge_funcs; + ctx->bridge.type = DRM_MODE_CONNECTOR_DPI; + ctx->bridge.of_node = dev->of_node; + + drm_bridge_add(&ctx->bridge); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + drm_bridge_remove(&ctx->bridge); + dev_err(dev, "failed to attach dsi\n"); + } + + return ret; +} + +static int tc358762_remove(struct mipi_dsi_device *dsi) +{ + struct tc358762 *ctx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_bridge_remove(&ctx->bridge); + + return 0; +} + +static const struct of_device_id tc358762_of_match[] = { + { .compatible = "toshiba,tc358762" }, + { } +}; +MODULE_DEVICE_TABLE(of, tc358762_of_match); + +static struct mipi_dsi_driver tc358762_driver = { + .probe = tc358762_probe, + .remove = tc358762_remove, + .driver = { + .name = "tc358762", + .of_match_table = tc358762_of_match, + }, +}; +module_mipi_dsi_driver(tc358762_driver); + +MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); +MODULE_DESCRIPTION("MIPI-DSI based Driver for TC358762 DSI/DPI Bridge"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c new file mode 100644 index 000000000..ea1445e09 --- /dev/null +++ b/drivers/gpu/drm/bridge/tc358764.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd + * + * Authors: + * Andrzej Hajda <a.hajda@samsung.com> + * Maciej Purski <m.purski@samsung.com> + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#define FLD_MASK(start, end) (((1 << ((start) - (end) + 1)) - 1) << (end)) +#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end)) + +/* PPI layer registers */ +#define PPI_STARTPPI 0x0104 /* START control bit */ +#define PPI_LPTXTIMECNT 0x0114 /* LPTX timing signal */ +#define PPI_LANEENABLE 0x0134 /* Enables each lane */ +#define PPI_TX_RX_TA 0x013C /* BTA timing parameters */ +#define PPI_D0S_CLRSIPOCOUNT 0x0164 /* Assertion timer for Lane 0 */ +#define PPI_D1S_CLRSIPOCOUNT 0x0168 /* Assertion timer for Lane 1 */ +#define PPI_D2S_CLRSIPOCOUNT 0x016C /* Assertion timer for Lane 2 */ +#define PPI_D3S_CLRSIPOCOUNT 0x0170 /* Assertion timer for Lane 3 */ +#define PPI_START_FUNCTION 1 + +/* DSI layer registers */ +#define DSI_STARTDSI 0x0204 /* START control bit of DSI-TX */ +#define DSI_LANEENABLE 0x0210 /* Enables each lane */ +#define DSI_RX_START 1 + +/* Video path registers */ +#define VP_CTRL 0x0450 /* Video Path Control */ +#define VP_CTRL_MSF(v) FLD_VAL(v, 0, 0) /* Magic square in RGB666 */ +#define VP_CTRL_VTGEN(v) FLD_VAL(v, 4, 4) /* Use chip clock for timing */ +#define VP_CTRL_EVTMODE(v) FLD_VAL(v, 5, 5) /* Event mode */ +#define VP_CTRL_RGB888(v) FLD_VAL(v, 8, 8) /* RGB888 mode */ +#define VP_CTRL_VSDELAY(v) FLD_VAL(v, 31, 20) /* VSYNC delay */ +#define VP_CTRL_HSPOL BIT(17) /* Polarity of HSYNC signal */ +#define VP_CTRL_DEPOL BIT(18) /* Polarity of DE signal */ +#define VP_CTRL_VSPOL BIT(19) /* Polarity of VSYNC signal */ +#define VP_HTIM1 0x0454 /* Horizontal Timing Control 1 */ +#define VP_HTIM1_HBP(v) FLD_VAL(v, 24, 16) +#define VP_HTIM1_HSYNC(v) FLD_VAL(v, 8, 0) +#define VP_HTIM2 0x0458 /* Horizontal Timing Control 2 */ +#define VP_HTIM2_HFP(v) FLD_VAL(v, 24, 16) +#define VP_HTIM2_HACT(v) FLD_VAL(v, 10, 0) +#define VP_VTIM1 0x045C /* Vertical Timing Control 1 */ +#define VP_VTIM1_VBP(v) FLD_VAL(v, 23, 16) +#define VP_VTIM1_VSYNC(v) FLD_VAL(v, 7, 0) +#define VP_VTIM2 0x0460 /* Vertical Timing Control 2 */ +#define VP_VTIM2_VFP(v) FLD_VAL(v, 23, 16) +#define VP_VTIM2_VACT(v) FLD_VAL(v, 10, 0) +#define VP_VFUEN 0x0464 /* Video Frame Timing Update Enable */ + +/* LVDS registers */ +#define LV_MX0003 0x0480 /* Mux input bit 0 to 3 */ +#define LV_MX0407 0x0484 /* Mux input bit 4 to 7 */ +#define LV_MX0811 0x0488 /* Mux input bit 8 to 11 */ +#define LV_MX1215 0x048C /* Mux input bit 12 to 15 */ +#define LV_MX1619 0x0490 /* Mux input bit 16 to 19 */ +#define LV_MX2023 0x0494 /* Mux input bit 20 to 23 */ +#define LV_MX2427 0x0498 /* Mux input bit 24 to 27 */ +#define LV_MX(b0, b1, b2, b3) (FLD_VAL(b0, 4, 0) | FLD_VAL(b1, 12, 8) | \ + FLD_VAL(b2, 20, 16) | FLD_VAL(b3, 28, 24)) + +/* Input bit numbers used in mux registers */ +enum { + LVI_R0, + LVI_R1, + LVI_R2, + LVI_R3, + LVI_R4, + LVI_R5, + LVI_R6, + LVI_R7, + LVI_G0, + LVI_G1, + LVI_G2, + LVI_G3, + LVI_G4, + LVI_G5, + LVI_G6, + LVI_G7, + LVI_B0, + LVI_B1, + LVI_B2, + LVI_B3, + LVI_B4, + LVI_B5, + LVI_B6, + LVI_B7, + LVI_HS, + LVI_VS, + LVI_DE, + LVI_L0 +}; + +#define LV_CFG 0x049C /* LVDS Configuration */ +#define LV_PHY0 0x04A0 /* LVDS PHY 0 */ +#define LV_PHY0_RST(v) FLD_VAL(v, 22, 22) /* PHY reset */ +#define LV_PHY0_IS(v) FLD_VAL(v, 15, 14) +#define LV_PHY0_ND(v) FLD_VAL(v, 4, 0) /* Frequency range select */ +#define LV_PHY0_PRBS_ON(v) FLD_VAL(v, 20, 16) /* Clock/Data Flag pins */ + +/* System registers */ +#define SYS_RST 0x0504 /* System Reset */ +#define SYS_ID 0x0580 /* System ID */ + +#define SYS_RST_I2CS BIT(0) /* Reset I2C-Slave controller */ +#define SYS_RST_I2CM BIT(1) /* Reset I2C-Master controller */ +#define SYS_RST_LCD BIT(2) /* Reset LCD controller */ +#define SYS_RST_BM BIT(3) /* Reset Bus Management controller */ +#define SYS_RST_DSIRX BIT(4) /* Reset DSI-RX and App controller */ +#define SYS_RST_REG BIT(5) /* Reset Register module */ + +#define LPX_PERIOD 2 +#define TTA_SURE 3 +#define TTA_GET 0x20000 + +/* Lane enable PPI and DSI register bits */ +#define LANEENABLE_CLEN BIT(0) +#define LANEENABLE_L0EN BIT(1) +#define LANEENABLE_L1EN BIT(2) +#define LANEENABLE_L2EN BIT(3) +#define LANEENABLE_L3EN BIT(4) + +/* LVCFG fields */ +#define LV_CFG_LVEN BIT(0) +#define LV_CFG_LVDLINK BIT(1) +#define LV_CFG_CLKPOL1 BIT(2) +#define LV_CFG_CLKPOL2 BIT(3) + +static const char * const tc358764_supplies[] = { + "vddc", "vddio", "vddlvds" +}; + +struct tc358764 { + struct device *dev; + struct drm_bridge bridge; + struct regulator_bulk_data supplies[ARRAY_SIZE(tc358764_supplies)]; + struct gpio_desc *gpio_reset; + struct drm_bridge *panel_bridge; + int error; +}; + +static int tc358764_clear_error(struct tc358764 *ctx) +{ + int ret = ctx->error; + + ctx->error = 0; + return ret; +} + +static void tc358764_read(struct tc358764 *ctx, u16 addr, u32 *val) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + ssize_t ret; + + if (ctx->error) + return; + + cpu_to_le16s(&addr); + ret = mipi_dsi_generic_read(dsi, &addr, sizeof(addr), val, sizeof(*val)); + if (ret >= 0) + le32_to_cpus(val); + + dev_dbg(ctx->dev, "read: addr=0x%04x data=0x%08x\n", addr, *val); +} + +static void tc358764_write(struct tc358764 *ctx, u16 addr, u32 val) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + ssize_t ret; + u8 data[6]; + + if (ctx->error) + return; + + data[0] = addr; + data[1] = addr >> 8; + data[2] = val; + data[3] = val >> 8; + data[4] = val >> 16; + data[5] = val >> 24; + + ret = mipi_dsi_generic_write(dsi, data, sizeof(data)); + if (ret < 0) + ctx->error = ret; +} + +static inline struct tc358764 *bridge_to_tc358764(struct drm_bridge *bridge) +{ + return container_of(bridge, struct tc358764, bridge); +} + +static int tc358764_init(struct tc358764 *ctx) +{ + u32 v = 0; + + tc358764_read(ctx, SYS_ID, &v); + if (ctx->error) + return tc358764_clear_error(ctx); + dev_info(ctx->dev, "ID: %#x\n", v); + + /* configure PPI counters */ + tc358764_write(ctx, PPI_TX_RX_TA, TTA_GET | TTA_SURE); + tc358764_write(ctx, PPI_LPTXTIMECNT, LPX_PERIOD); + tc358764_write(ctx, PPI_D0S_CLRSIPOCOUNT, 5); + tc358764_write(ctx, PPI_D1S_CLRSIPOCOUNT, 5); + tc358764_write(ctx, PPI_D2S_CLRSIPOCOUNT, 5); + tc358764_write(ctx, PPI_D3S_CLRSIPOCOUNT, 5); + + /* enable four data lanes and clock lane */ + tc358764_write(ctx, PPI_LANEENABLE, LANEENABLE_L3EN | LANEENABLE_L2EN | + LANEENABLE_L1EN | LANEENABLE_L0EN | LANEENABLE_CLEN); + tc358764_write(ctx, DSI_LANEENABLE, LANEENABLE_L3EN | LANEENABLE_L2EN | + LANEENABLE_L1EN | LANEENABLE_L0EN | LANEENABLE_CLEN); + + /* start */ + tc358764_write(ctx, PPI_STARTPPI, PPI_START_FUNCTION); + tc358764_write(ctx, DSI_STARTDSI, DSI_RX_START); + + /* configure video path */ + tc358764_write(ctx, VP_CTRL, VP_CTRL_VSDELAY(15) | VP_CTRL_RGB888(1) | + VP_CTRL_EVTMODE(1) | VP_CTRL_HSPOL | VP_CTRL_VSPOL); + + /* reset PHY */ + tc358764_write(ctx, LV_PHY0, LV_PHY0_RST(1) | + LV_PHY0_PRBS_ON(4) | LV_PHY0_IS(2) | LV_PHY0_ND(6)); + tc358764_write(ctx, LV_PHY0, LV_PHY0_PRBS_ON(4) | LV_PHY0_IS(2) | + LV_PHY0_ND(6)); + + /* reset bridge */ + tc358764_write(ctx, SYS_RST, SYS_RST_LCD); + + /* set bit order */ + tc358764_write(ctx, LV_MX0003, LV_MX(LVI_R0, LVI_R1, LVI_R2, LVI_R3)); + tc358764_write(ctx, LV_MX0407, LV_MX(LVI_R4, LVI_R7, LVI_R5, LVI_G0)); + tc358764_write(ctx, LV_MX0811, LV_MX(LVI_G1, LVI_G2, LVI_G6, LVI_G7)); + tc358764_write(ctx, LV_MX1215, LV_MX(LVI_G3, LVI_G4, LVI_G5, LVI_B0)); + tc358764_write(ctx, LV_MX1619, LV_MX(LVI_B6, LVI_B7, LVI_B1, LVI_B2)); + tc358764_write(ctx, LV_MX2023, LV_MX(LVI_B3, LVI_B4, LVI_B5, LVI_L0)); + tc358764_write(ctx, LV_MX2427, LV_MX(LVI_HS, LVI_VS, LVI_DE, LVI_R6)); + tc358764_write(ctx, LV_CFG, LV_CFG_CLKPOL2 | LV_CFG_CLKPOL1 | + LV_CFG_LVEN); + + return tc358764_clear_error(ctx); +} + +static void tc358764_reset(struct tc358764 *ctx) +{ + gpiod_set_value(ctx->gpio_reset, 1); + usleep_range(1000, 2000); + gpiod_set_value(ctx->gpio_reset, 0); + usleep_range(1000, 2000); +} + +static void tc358764_post_disable(struct drm_bridge *bridge) +{ + struct tc358764 *ctx = bridge_to_tc358764(bridge); + int ret; + + tc358764_reset(ctx); + usleep_range(10000, 15000); + ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) + dev_err(ctx->dev, "error disabling regulators (%d)\n", ret); +} + +static void tc358764_pre_enable(struct drm_bridge *bridge) +{ + struct tc358764 *ctx = bridge_to_tc358764(bridge); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) + dev_err(ctx->dev, "error enabling regulators (%d)\n", ret); + usleep_range(10000, 15000); + tc358764_reset(ctx); + ret = tc358764_init(ctx); + if (ret < 0) + dev_err(ctx->dev, "error initializing bridge (%d)\n", ret); +} + +static int tc358764_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct tc358764 *ctx = bridge_to_tc358764(bridge); + + return drm_bridge_attach(bridge->encoder, ctx->panel_bridge, + bridge, flags); +} + +static const struct drm_bridge_funcs tc358764_bridge_funcs = { + .post_disable = tc358764_post_disable, + .pre_enable = tc358764_pre_enable, + .attach = tc358764_attach, +}; + +static int tc358764_parse_dt(struct tc358764 *ctx) +{ + struct drm_bridge *panel_bridge; + struct device *dev = ctx->dev; + struct drm_panel *panel; + int ret; + + ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->gpio_reset)) { + dev_err(dev, "no reset GPIO pin provided\n"); + return PTR_ERR(ctx->gpio_reset); + } + + ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL); + if (ret) + return ret; + + panel_bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(panel_bridge)) + return PTR_ERR(panel_bridge); + + ctx->panel_bridge = panel_bridge; + return 0; +} + +static int tc358764_configure_regulators(struct tc358764 *ctx) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(ctx->supplies); ++i) + ctx->supplies[i].supply = tc358764_supplies[i]; + + ret = devm_regulator_bulk_get(ctx->dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) + dev_err(ctx->dev, "failed to get regulators: %d\n", ret); + + return ret; +} + +static int tc358764_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct tc358764 *ctx; + int ret; + + ctx = devm_kzalloc(dev, sizeof(struct tc358764), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->dev = dev; + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST + | MIPI_DSI_MODE_VIDEO_AUTO_VERT | MIPI_DSI_MODE_LPM; + + ret = tc358764_parse_dt(ctx); + if (ret < 0) + return ret; + + ret = tc358764_configure_regulators(ctx); + if (ret < 0) + return ret; + + ctx->bridge.funcs = &tc358764_bridge_funcs; + ctx->bridge.type = DRM_MODE_CONNECTOR_LVDS; + ctx->bridge.of_node = dev->of_node; + + drm_bridge_add(&ctx->bridge); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + drm_bridge_remove(&ctx->bridge); + dev_err(dev, "failed to attach dsi\n"); + } + + return ret; +} + +static int tc358764_remove(struct mipi_dsi_device *dsi) +{ + struct tc358764 *ctx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_bridge_remove(&ctx->bridge); + + return 0; +} + +static const struct of_device_id tc358764_of_match[] = { + { .compatible = "toshiba,tc358764" }, + { } +}; +MODULE_DEVICE_TABLE(of, tc358764_of_match); + +static struct mipi_dsi_driver tc358764_driver = { + .probe = tc358764_probe, + .remove = tc358764_remove, + .driver = { + .name = "tc358764", + .owner = THIS_MODULE, + .of_match_table = tc358764_of_match, + }, +}; +module_mipi_dsi_driver(tc358764_driver); + +MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>"); +MODULE_AUTHOR("Maciej Purski <m.purski@samsung.com>"); +MODULE_DESCRIPTION("MIPI-DSI based Driver for TC358764 DSI/LVDS Bridge"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c new file mode 100644 index 000000000..9c905634f --- /dev/null +++ b/drivers/gpu/drm/bridge/tc358767.c @@ -0,0 +1,1765 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * tc358767 eDP bridge driver + * + * Copyright (C) 2016 CogentEmbedded Inc + * Author: Andrey Gusakov <andrey.gusakov@cogentembedded.com> + * + * Copyright (C) 2016 Pengutronix, Philipp Zabel <p.zabel@pengutronix.de> + * + * Copyright (C) 2016 Zodiac Inflight Innovations + * + * Initially based on: drivers/gpu/drm/i2c/tda998x_drv.c + * + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_dp_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +/* Registers */ + +/* Display Parallel Interface */ +#define DPIPXLFMT 0x0440 +#define VS_POL_ACTIVE_LOW (1 << 10) +#define HS_POL_ACTIVE_LOW (1 << 9) +#define DE_POL_ACTIVE_HIGH (0 << 8) +#define SUB_CFG_TYPE_CONFIG1 (0 << 2) /* LSB aligned */ +#define SUB_CFG_TYPE_CONFIG2 (1 << 2) /* Loosely Packed */ +#define SUB_CFG_TYPE_CONFIG3 (2 << 2) /* LSB aligned 8-bit */ +#define DPI_BPP_RGB888 (0 << 0) +#define DPI_BPP_RGB666 (1 << 0) +#define DPI_BPP_RGB565 (2 << 0) + +/* Video Path */ +#define VPCTRL0 0x0450 +#define VSDELAY GENMASK(31, 20) +#define OPXLFMT_RGB666 (0 << 8) +#define OPXLFMT_RGB888 (1 << 8) +#define FRMSYNC_DISABLED (0 << 4) /* Video Timing Gen Disabled */ +#define FRMSYNC_ENABLED (1 << 4) /* Video Timing Gen Enabled */ +#define MSF_DISABLED (0 << 0) /* Magic Square FRC disabled */ +#define MSF_ENABLED (1 << 0) /* Magic Square FRC enabled */ +#define HTIM01 0x0454 +#define HPW GENMASK(8, 0) +#define HBPR GENMASK(24, 16) +#define HTIM02 0x0458 +#define HDISPR GENMASK(10, 0) +#define HFPR GENMASK(24, 16) +#define VTIM01 0x045c +#define VSPR GENMASK(7, 0) +#define VBPR GENMASK(23, 16) +#define VTIM02 0x0460 +#define VFPR GENMASK(23, 16) +#define VDISPR GENMASK(10, 0) +#define VFUEN0 0x0464 +#define VFUEN BIT(0) /* Video Frame Timing Upload */ + +/* System */ +#define TC_IDREG 0x0500 +#define SYSSTAT 0x0508 +#define SYSCTRL 0x0510 +#define DP0_AUDSRC_NO_INPUT (0 << 3) +#define DP0_AUDSRC_I2S_RX (1 << 3) +#define DP0_VIDSRC_NO_INPUT (0 << 0) +#define DP0_VIDSRC_DSI_RX (1 << 0) +#define DP0_VIDSRC_DPI_RX (2 << 0) +#define DP0_VIDSRC_COLOR_BAR (3 << 0) +#define SYSRSTENB 0x050c +#define ENBI2C (1 << 0) +#define ENBLCD0 (1 << 2) +#define ENBBM (1 << 3) +#define ENBDSIRX (1 << 4) +#define ENBREG (1 << 5) +#define ENBHDCP (1 << 8) +#define GPIOM 0x0540 +#define GPIOC 0x0544 +#define GPIOO 0x0548 +#define GPIOI 0x054c +#define INTCTL_G 0x0560 +#define INTSTS_G 0x0564 + +#define INT_SYSERR BIT(16) +#define INT_GPIO_H(x) (1 << (x == 0 ? 2 : 10)) +#define INT_GPIO_LC(x) (1 << (x == 0 ? 3 : 11)) + +#define INT_GP0_LCNT 0x0584 +#define INT_GP1_LCNT 0x0588 + +/* Control */ +#define DP0CTL 0x0600 +#define VID_MN_GEN BIT(6) /* Auto-generate M/N values */ +#define EF_EN BIT(5) /* Enable Enhanced Framing */ +#define VID_EN BIT(1) /* Video transmission enable */ +#define DP_EN BIT(0) /* Enable DPTX function */ + +/* Clocks */ +#define DP0_VIDMNGEN0 0x0610 +#define DP0_VIDMNGEN1 0x0614 +#define DP0_VMNGENSTATUS 0x0618 + +/* Main Channel */ +#define DP0_SECSAMPLE 0x0640 +#define DP0_VIDSYNCDELAY 0x0644 +#define VID_SYNC_DLY GENMASK(15, 0) +#define THRESH_DLY GENMASK(31, 16) + +#define DP0_TOTALVAL 0x0648 +#define H_TOTAL GENMASK(15, 0) +#define V_TOTAL GENMASK(31, 16) +#define DP0_STARTVAL 0x064c +#define H_START GENMASK(15, 0) +#define V_START GENMASK(31, 16) +#define DP0_ACTIVEVAL 0x0650 +#define H_ACT GENMASK(15, 0) +#define V_ACT GENMASK(31, 16) + +#define DP0_SYNCVAL 0x0654 +#define VS_WIDTH GENMASK(30, 16) +#define HS_WIDTH GENMASK(14, 0) +#define SYNCVAL_HS_POL_ACTIVE_LOW (1 << 15) +#define SYNCVAL_VS_POL_ACTIVE_LOW (1 << 31) +#define DP0_MISC 0x0658 +#define TU_SIZE_RECOMMENDED (63) /* LSCLK cycles per TU */ +#define MAX_TU_SYMBOL GENMASK(28, 23) +#define TU_SIZE GENMASK(21, 16) +#define BPC_6 (0 << 5) +#define BPC_8 (1 << 5) + +/* AUX channel */ +#define DP0_AUXCFG0 0x0660 +#define DP0_AUXCFG0_BSIZE GENMASK(11, 8) +#define DP0_AUXCFG0_ADDR_ONLY BIT(4) +#define DP0_AUXCFG1 0x0664 +#define AUX_RX_FILTER_EN BIT(16) + +#define DP0_AUXADDR 0x0668 +#define DP0_AUXWDATA(i) (0x066c + (i) * 4) +#define DP0_AUXRDATA(i) (0x067c + (i) * 4) +#define DP0_AUXSTATUS 0x068c +#define AUX_BYTES GENMASK(15, 8) +#define AUX_STATUS GENMASK(7, 4) +#define AUX_TIMEOUT BIT(1) +#define AUX_BUSY BIT(0) +#define DP0_AUXI2CADR 0x0698 + +/* Link Training */ +#define DP0_SRCCTRL 0x06a0 +#define DP0_SRCCTRL_SCRMBLDIS BIT(13) +#define DP0_SRCCTRL_EN810B BIT(12) +#define DP0_SRCCTRL_NOTP (0 << 8) +#define DP0_SRCCTRL_TP1 (1 << 8) +#define DP0_SRCCTRL_TP2 (2 << 8) +#define DP0_SRCCTRL_LANESKEW BIT(7) +#define DP0_SRCCTRL_SSCG BIT(3) +#define DP0_SRCCTRL_LANES_1 (0 << 2) +#define DP0_SRCCTRL_LANES_2 (1 << 2) +#define DP0_SRCCTRL_BW27 (1 << 1) +#define DP0_SRCCTRL_BW162 (0 << 1) +#define DP0_SRCCTRL_AUTOCORRECT BIT(0) +#define DP0_LTSTAT 0x06d0 +#define LT_LOOPDONE BIT(13) +#define LT_STATUS_MASK (0x1f << 8) +#define LT_CHANNEL1_EQ_BITS (DP_CHANNEL_EQ_BITS << 4) +#define LT_INTERLANE_ALIGN_DONE BIT(3) +#define LT_CHANNEL0_EQ_BITS (DP_CHANNEL_EQ_BITS) +#define DP0_SNKLTCHGREQ 0x06d4 +#define DP0_LTLOOPCTRL 0x06d8 +#define DP0_SNKLTCTRL 0x06e4 + +#define DP1_SRCCTRL 0x07a0 + +/* PHY */ +#define DP_PHY_CTRL 0x0800 +#define DP_PHY_RST BIT(28) /* DP PHY Global Soft Reset */ +#define BGREN BIT(25) /* AUX PHY BGR Enable */ +#define PWR_SW_EN BIT(24) /* PHY Power Switch Enable */ +#define PHY_M1_RST BIT(12) /* Reset PHY1 Main Channel */ +#define PHY_RDY BIT(16) /* PHY Main Channels Ready */ +#define PHY_M0_RST BIT(8) /* Reset PHY0 Main Channel */ +#define PHY_2LANE BIT(2) /* PHY Enable 2 lanes */ +#define PHY_A0_EN BIT(1) /* PHY Aux Channel0 Enable */ +#define PHY_M0_EN BIT(0) /* PHY Main Channel0 Enable */ + +/* PLL */ +#define DP0_PLLCTRL 0x0900 +#define DP1_PLLCTRL 0x0904 /* not defined in DS */ +#define PXL_PLLCTRL 0x0908 +#define PLLUPDATE BIT(2) +#define PLLBYP BIT(1) +#define PLLEN BIT(0) +#define PXL_PLLPARAM 0x0914 +#define IN_SEL_REFCLK (0 << 14) +#define SYS_PLLPARAM 0x0918 +#define REF_FREQ_38M4 (0 << 8) /* 38.4 MHz */ +#define REF_FREQ_19M2 (1 << 8) /* 19.2 MHz */ +#define REF_FREQ_26M (2 << 8) /* 26 MHz */ +#define REF_FREQ_13M (3 << 8) /* 13 MHz */ +#define SYSCLK_SEL_LSCLK (0 << 4) +#define LSCLK_DIV_1 (0 << 0) +#define LSCLK_DIV_2 (1 << 0) + +/* Test & Debug */ +#define TSTCTL 0x0a00 +#define COLOR_R GENMASK(31, 24) +#define COLOR_G GENMASK(23, 16) +#define COLOR_B GENMASK(15, 8) +#define ENI2CFILTER BIT(4) +#define COLOR_BAR_MODE GENMASK(1, 0) +#define COLOR_BAR_MODE_BARS 2 +#define PLL_DBG 0x0a04 + +static bool tc_test_pattern; +module_param_named(test, tc_test_pattern, bool, 0644); + +struct tc_edp_link { + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + unsigned int rate; + u8 num_lanes; + u8 assr; + bool scrambler_dis; + bool spread; +}; + +struct tc_data { + struct device *dev; + struct regmap *regmap; + struct drm_dp_aux aux; + + struct drm_bridge bridge; + struct drm_bridge *panel_bridge; + struct drm_connector connector; + + /* link settings */ + struct tc_edp_link link; + + /* current mode */ + struct drm_display_mode mode; + + u32 rev; + u8 assr; + + struct gpio_desc *sd_gpio; + struct gpio_desc *reset_gpio; + struct clk *refclk; + + /* do we have IRQ */ + bool have_irq; + + /* HPD pin number (0 or 1) or -ENODEV */ + int hpd_pin; +}; + +static inline struct tc_data *aux_to_tc(struct drm_dp_aux *a) +{ + return container_of(a, struct tc_data, aux); +} + +static inline struct tc_data *bridge_to_tc(struct drm_bridge *b) +{ + return container_of(b, struct tc_data, bridge); +} + +static inline struct tc_data *connector_to_tc(struct drm_connector *c) +{ + return container_of(c, struct tc_data, connector); +} + +static inline int tc_poll_timeout(struct tc_data *tc, unsigned int addr, + unsigned int cond_mask, + unsigned int cond_value, + unsigned long sleep_us, u64 timeout_us) +{ + unsigned int val; + + return regmap_read_poll_timeout(tc->regmap, addr, val, + (val & cond_mask) == cond_value, + sleep_us, timeout_us); +} + +static int tc_aux_wait_busy(struct tc_data *tc) +{ + return tc_poll_timeout(tc, DP0_AUXSTATUS, AUX_BUSY, 0, 100, 100000); +} + +static int tc_aux_write_data(struct tc_data *tc, const void *data, + size_t size) +{ + u32 auxwdata[DP_AUX_MAX_PAYLOAD_BYTES / sizeof(u32)] = { 0 }; + int ret, count = ALIGN(size, sizeof(u32)); + + memcpy(auxwdata, data, size); + + ret = regmap_raw_write(tc->regmap, DP0_AUXWDATA(0), auxwdata, count); + if (ret) + return ret; + + return size; +} + +static int tc_aux_read_data(struct tc_data *tc, void *data, size_t size) +{ + u32 auxrdata[DP_AUX_MAX_PAYLOAD_BYTES / sizeof(u32)]; + int ret, count = ALIGN(size, sizeof(u32)); + + ret = regmap_raw_read(tc->regmap, DP0_AUXRDATA(0), auxrdata, count); + if (ret) + return ret; + + memcpy(data, auxrdata, size); + + return size; +} + +static u32 tc_auxcfg0(struct drm_dp_aux_msg *msg, size_t size) +{ + u32 auxcfg0 = msg->request; + + if (size) + auxcfg0 |= FIELD_PREP(DP0_AUXCFG0_BSIZE, size - 1); + else + auxcfg0 |= DP0_AUXCFG0_ADDR_ONLY; + + return auxcfg0; +} + +static ssize_t tc_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct tc_data *tc = aux_to_tc(aux); + size_t size = min_t(size_t, DP_AUX_MAX_PAYLOAD_BYTES - 1, msg->size); + u8 request = msg->request & ~DP_AUX_I2C_MOT; + u32 auxstatus; + int ret; + + ret = tc_aux_wait_busy(tc); + if (ret) + return ret; + + switch (request) { + case DP_AUX_NATIVE_READ: + case DP_AUX_I2C_READ: + break; + case DP_AUX_NATIVE_WRITE: + case DP_AUX_I2C_WRITE: + if (size) { + ret = tc_aux_write_data(tc, msg->buffer, size); + if (ret < 0) + return ret; + } + break; + default: + return -EINVAL; + } + + /* Store address */ + ret = regmap_write(tc->regmap, DP0_AUXADDR, msg->address); + if (ret) + return ret; + /* Start transfer */ + ret = regmap_write(tc->regmap, DP0_AUXCFG0, tc_auxcfg0(msg, size)); + if (ret) + return ret; + + ret = tc_aux_wait_busy(tc); + if (ret) + return ret; + + ret = regmap_read(tc->regmap, DP0_AUXSTATUS, &auxstatus); + if (ret) + return ret; + + if (auxstatus & AUX_TIMEOUT) + return -ETIMEDOUT; + /* + * For some reason address-only DP_AUX_I2C_WRITE (MOT), still + * reports 1 byte transferred in its status. To deal we that + * we ignore aux_bytes field if we know that this was an + * address-only transfer + */ + if (size) + size = FIELD_GET(AUX_BYTES, auxstatus); + msg->reply = FIELD_GET(AUX_STATUS, auxstatus); + + switch (request) { + case DP_AUX_NATIVE_READ: + case DP_AUX_I2C_READ: + if (size) + return tc_aux_read_data(tc, msg->buffer, size); + break; + } + + return size; +} + +static const char * const training_pattern1_errors[] = { + "No errors", + "Aux write error", + "Aux read error", + "Max voltage reached error", + "Loop counter expired error", + "res", "res", "res" +}; + +static const char * const training_pattern2_errors[] = { + "No errors", + "Aux write error", + "Aux read error", + "Clock recovery failed error", + "Loop counter expired error", + "res", "res", "res" +}; + +static u32 tc_srcctrl(struct tc_data *tc) +{ + /* + * No training pattern, skew lane 1 data by two LSCLK cycles with + * respect to lane 0 data, AutoCorrect Mode = 0 + */ + u32 reg = DP0_SRCCTRL_NOTP | DP0_SRCCTRL_LANESKEW | DP0_SRCCTRL_EN810B; + + if (tc->link.scrambler_dis) + reg |= DP0_SRCCTRL_SCRMBLDIS; /* Scrambler Disabled */ + if (tc->link.spread) + reg |= DP0_SRCCTRL_SSCG; /* Spread Spectrum Enable */ + if (tc->link.num_lanes == 2) + reg |= DP0_SRCCTRL_LANES_2; /* Two Main Channel Lanes */ + if (tc->link.rate != 162000) + reg |= DP0_SRCCTRL_BW27; /* 2.7 Gbps link */ + return reg; +} + +static int tc_pllupdate(struct tc_data *tc, unsigned int pllctrl) +{ + int ret; + + ret = regmap_write(tc->regmap, pllctrl, PLLUPDATE | PLLEN); + if (ret) + return ret; + + /* Wait for PLL to lock: up to 2.09 ms, depending on refclk */ + usleep_range(3000, 6000); + + return 0; +} + +static int tc_pxl_pll_en(struct tc_data *tc, u32 refclk, u32 pixelclock) +{ + int ret; + int i_pre, best_pre = 1; + int i_post, best_post = 1; + int div, best_div = 1; + int mul, best_mul = 1; + int delta, best_delta; + int ext_div[] = {1, 2, 3, 5, 7}; + int best_pixelclock = 0; + int vco_hi = 0; + u32 pxl_pllparam; + + dev_dbg(tc->dev, "PLL: requested %d pixelclock, ref %d\n", pixelclock, + refclk); + best_delta = pixelclock; + /* Loop over all possible ext_divs, skipping invalid configurations */ + for (i_pre = 0; i_pre < ARRAY_SIZE(ext_div); i_pre++) { + /* + * refclk / ext_pre_div should be in the 1 to 200 MHz range. + * We don't allow any refclk > 200 MHz, only check lower bounds. + */ + if (refclk / ext_div[i_pre] < 1000000) + continue; + for (i_post = 0; i_post < ARRAY_SIZE(ext_div); i_post++) { + for (div = 1; div <= 16; div++) { + u32 clk; + u64 tmp; + + tmp = pixelclock * ext_div[i_pre] * + ext_div[i_post] * div; + do_div(tmp, refclk); + mul = tmp; + + /* Check limits */ + if ((mul < 1) || (mul > 128)) + continue; + + clk = (refclk / ext_div[i_pre] / div) * mul; + /* + * refclk * mul / (ext_pre_div * pre_div) + * should be in the 150 to 650 MHz range + */ + if ((clk > 650000000) || (clk < 150000000)) + continue; + + clk = clk / ext_div[i_post]; + delta = clk - pixelclock; + + if (abs(delta) < abs(best_delta)) { + best_pre = i_pre; + best_post = i_post; + best_div = div; + best_mul = mul; + best_delta = delta; + best_pixelclock = clk; + } + } + } + } + if (best_pixelclock == 0) { + dev_err(tc->dev, "Failed to calc clock for %d pixelclock\n", + pixelclock); + return -EINVAL; + } + + dev_dbg(tc->dev, "PLL: got %d, delta %d\n", best_pixelclock, + best_delta); + dev_dbg(tc->dev, "PLL: %d / %d / %d * %d / %d\n", refclk, + ext_div[best_pre], best_div, best_mul, ext_div[best_post]); + + /* if VCO >= 300 MHz */ + if (refclk / ext_div[best_pre] / best_div * best_mul >= 300000000) + vco_hi = 1; + /* see DS */ + if (best_div == 16) + best_div = 0; + if (best_mul == 128) + best_mul = 0; + + /* Power up PLL and switch to bypass */ + ret = regmap_write(tc->regmap, PXL_PLLCTRL, PLLBYP | PLLEN); + if (ret) + return ret; + + pxl_pllparam = vco_hi << 24; /* For PLL VCO >= 300 MHz = 1 */ + pxl_pllparam |= ext_div[best_pre] << 20; /* External Pre-divider */ + pxl_pllparam |= ext_div[best_post] << 16; /* External Post-divider */ + pxl_pllparam |= IN_SEL_REFCLK; /* Use RefClk as PLL input */ + pxl_pllparam |= best_div << 8; /* Divider for PLL RefClk */ + pxl_pllparam |= best_mul; /* Multiplier for PLL */ + + ret = regmap_write(tc->regmap, PXL_PLLPARAM, pxl_pllparam); + if (ret) + return ret; + + /* Force PLL parameter update and disable bypass */ + return tc_pllupdate(tc, PXL_PLLCTRL); +} + +static int tc_pxl_pll_dis(struct tc_data *tc) +{ + /* Enable PLL bypass, power down PLL */ + return regmap_write(tc->regmap, PXL_PLLCTRL, PLLBYP); +} + +static int tc_stream_clock_calc(struct tc_data *tc) +{ + /* + * If the Stream clock and Link Symbol clock are + * asynchronous with each other, the value of M changes over + * time. This way of generating link clock and stream + * clock is called Asynchronous Clock mode. The value M + * must change while the value N stays constant. The + * value of N in this Asynchronous Clock mode must be set + * to 2^15 or 32,768. + * + * LSCLK = 1/10 of high speed link clock + * + * f_STRMCLK = M/N * f_LSCLK + * M/N = f_STRMCLK / f_LSCLK + * + */ + return regmap_write(tc->regmap, DP0_VIDMNGEN1, 32768); +} + +static int tc_set_syspllparam(struct tc_data *tc) +{ + unsigned long rate; + u32 pllparam = SYSCLK_SEL_LSCLK | LSCLK_DIV_2; + + rate = clk_get_rate(tc->refclk); + switch (rate) { + case 38400000: + pllparam |= REF_FREQ_38M4; + break; + case 26000000: + pllparam |= REF_FREQ_26M; + break; + case 19200000: + pllparam |= REF_FREQ_19M2; + break; + case 13000000: + pllparam |= REF_FREQ_13M; + break; + default: + dev_err(tc->dev, "Invalid refclk rate: %lu Hz\n", rate); + return -EINVAL; + } + + return regmap_write(tc->regmap, SYS_PLLPARAM, pllparam); +} + +static int tc_aux_link_setup(struct tc_data *tc) +{ + int ret; + u32 dp0_auxcfg1; + + /* Setup DP-PHY / PLL */ + ret = tc_set_syspllparam(tc); + if (ret) + goto err; + + ret = regmap_write(tc->regmap, DP_PHY_CTRL, + BGREN | PWR_SW_EN | PHY_A0_EN); + if (ret) + goto err; + /* + * Initially PLLs are in bypass. Force PLL parameter update, + * disable PLL bypass, enable PLL + */ + ret = tc_pllupdate(tc, DP0_PLLCTRL); + if (ret) + goto err; + + ret = tc_pllupdate(tc, DP1_PLLCTRL); + if (ret) + goto err; + + ret = tc_poll_timeout(tc, DP_PHY_CTRL, PHY_RDY, PHY_RDY, 100, 100000); + if (ret == -ETIMEDOUT) { + dev_err(tc->dev, "Timeout waiting for PHY to become ready"); + return ret; + } else if (ret) { + goto err; + } + + /* Setup AUX link */ + dp0_auxcfg1 = AUX_RX_FILTER_EN; + dp0_auxcfg1 |= 0x06 << 8; /* Aux Bit Period Calculator Threshold */ + dp0_auxcfg1 |= 0x3f << 0; /* Aux Response Timeout Timer */ + + ret = regmap_write(tc->regmap, DP0_AUXCFG1, dp0_auxcfg1); + if (ret) + goto err; + + return 0; +err: + dev_err(tc->dev, "tc_aux_link_setup failed: %d\n", ret); + return ret; +} + +static int tc_get_display_props(struct tc_data *tc) +{ + u8 revision, num_lanes; + unsigned int rate; + int ret; + u8 reg; + + /* Read DP Rx Link Capability */ + ret = drm_dp_dpcd_read(&tc->aux, DP_DPCD_REV, tc->link.dpcd, + DP_RECEIVER_CAP_SIZE); + if (ret < 0) + goto err_dpcd_read; + + revision = tc->link.dpcd[DP_DPCD_REV]; + rate = drm_dp_max_link_rate(tc->link.dpcd); + num_lanes = drm_dp_max_lane_count(tc->link.dpcd); + + if (rate != 162000 && rate != 270000) { + dev_dbg(tc->dev, "Falling to 2.7 Gbps rate\n"); + rate = 270000; + } + + tc->link.rate = rate; + + if (num_lanes > 2) { + dev_dbg(tc->dev, "Falling to 2 lanes\n"); + num_lanes = 2; + } + + tc->link.num_lanes = num_lanes; + + ret = drm_dp_dpcd_readb(&tc->aux, DP_MAX_DOWNSPREAD, ®); + if (ret < 0) + goto err_dpcd_read; + tc->link.spread = reg & DP_MAX_DOWNSPREAD_0_5; + + ret = drm_dp_dpcd_readb(&tc->aux, DP_MAIN_LINK_CHANNEL_CODING, ®); + if (ret < 0) + goto err_dpcd_read; + + tc->link.scrambler_dis = false; + /* read assr */ + ret = drm_dp_dpcd_readb(&tc->aux, DP_EDP_CONFIGURATION_SET, ®); + if (ret < 0) + goto err_dpcd_read; + tc->link.assr = reg & DP_ALTERNATE_SCRAMBLER_RESET_ENABLE; + + dev_dbg(tc->dev, "DPCD rev: %d.%d, rate: %s, lanes: %d, framing: %s\n", + revision >> 4, revision & 0x0f, + (tc->link.rate == 162000) ? "1.62Gbps" : "2.7Gbps", + tc->link.num_lanes, + drm_dp_enhanced_frame_cap(tc->link.dpcd) ? + "enhanced" : "default"); + dev_dbg(tc->dev, "Downspread: %s, scrambler: %s\n", + tc->link.spread ? "0.5%" : "0.0%", + tc->link.scrambler_dis ? "disabled" : "enabled"); + dev_dbg(tc->dev, "Display ASSR: %d, TC358767 ASSR: %d\n", + tc->link.assr, tc->assr); + + return 0; + +err_dpcd_read: + dev_err(tc->dev, "failed to read DPCD: %d\n", ret); + return ret; +} + +static int tc_set_video_mode(struct tc_data *tc, + const struct drm_display_mode *mode) +{ + int ret; + int vid_sync_dly; + int max_tu_symbol; + + int left_margin = mode->htotal - mode->hsync_end; + int right_margin = mode->hsync_start - mode->hdisplay; + int hsync_len = mode->hsync_end - mode->hsync_start; + int upper_margin = mode->vtotal - mode->vsync_end; + int lower_margin = mode->vsync_start - mode->vdisplay; + int vsync_len = mode->vsync_end - mode->vsync_start; + u32 dp0_syncval; + u32 bits_per_pixel = 24; + u32 in_bw, out_bw; + + /* + * Recommended maximum number of symbols transferred in a transfer unit: + * DIV_ROUND_UP((input active video bandwidth in bytes) * tu_size, + * (output active video bandwidth in bytes)) + * Must be less than tu_size. + */ + + in_bw = mode->clock * bits_per_pixel / 8; + out_bw = tc->link.num_lanes * tc->link.rate; + max_tu_symbol = DIV_ROUND_UP(in_bw * TU_SIZE_RECOMMENDED, out_bw); + + dev_dbg(tc->dev, "set mode %dx%d\n", + mode->hdisplay, mode->vdisplay); + dev_dbg(tc->dev, "H margin %d,%d sync %d\n", + left_margin, right_margin, hsync_len); + dev_dbg(tc->dev, "V margin %d,%d sync %d\n", + upper_margin, lower_margin, vsync_len); + dev_dbg(tc->dev, "total: %dx%d\n", mode->htotal, mode->vtotal); + + + /* + * LCD Ctl Frame Size + * datasheet is not clear of vsdelay in case of DPI + * assume we do not need any delay when DPI is a source of + * sync signals + */ + ret = regmap_write(tc->regmap, VPCTRL0, + FIELD_PREP(VSDELAY, 0) | + OPXLFMT_RGB888 | FRMSYNC_DISABLED | MSF_DISABLED); + if (ret) + return ret; + + ret = regmap_write(tc->regmap, HTIM01, + FIELD_PREP(HBPR, ALIGN(left_margin, 2)) | + FIELD_PREP(HPW, ALIGN(hsync_len, 2))); + if (ret) + return ret; + + ret = regmap_write(tc->regmap, HTIM02, + FIELD_PREP(HDISPR, ALIGN(mode->hdisplay, 2)) | + FIELD_PREP(HFPR, ALIGN(right_margin, 2))); + if (ret) + return ret; + + ret = regmap_write(tc->regmap, VTIM01, + FIELD_PREP(VBPR, upper_margin) | + FIELD_PREP(VSPR, vsync_len)); + if (ret) + return ret; + + ret = regmap_write(tc->regmap, VTIM02, + FIELD_PREP(VFPR, lower_margin) | + FIELD_PREP(VDISPR, mode->vdisplay)); + if (ret) + return ret; + + ret = regmap_write(tc->regmap, VFUEN0, VFUEN); /* update settings */ + if (ret) + return ret; + + /* Test pattern settings */ + ret = regmap_write(tc->regmap, TSTCTL, + FIELD_PREP(COLOR_R, 120) | + FIELD_PREP(COLOR_G, 20) | + FIELD_PREP(COLOR_B, 99) | + ENI2CFILTER | + FIELD_PREP(COLOR_BAR_MODE, COLOR_BAR_MODE_BARS)); + if (ret) + return ret; + + /* DP Main Stream Attributes */ + vid_sync_dly = hsync_len + left_margin + mode->hdisplay; + ret = regmap_write(tc->regmap, DP0_VIDSYNCDELAY, + FIELD_PREP(THRESH_DLY, max_tu_symbol) | + FIELD_PREP(VID_SYNC_DLY, vid_sync_dly)); + + ret = regmap_write(tc->regmap, DP0_TOTALVAL, + FIELD_PREP(H_TOTAL, mode->htotal) | + FIELD_PREP(V_TOTAL, mode->vtotal)); + if (ret) + return ret; + + ret = regmap_write(tc->regmap, DP0_STARTVAL, + FIELD_PREP(H_START, left_margin + hsync_len) | + FIELD_PREP(V_START, upper_margin + vsync_len)); + if (ret) + return ret; + + ret = regmap_write(tc->regmap, DP0_ACTIVEVAL, + FIELD_PREP(V_ACT, mode->vdisplay) | + FIELD_PREP(H_ACT, mode->hdisplay)); + if (ret) + return ret; + + dp0_syncval = FIELD_PREP(VS_WIDTH, vsync_len) | + FIELD_PREP(HS_WIDTH, hsync_len); + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + dp0_syncval |= SYNCVAL_VS_POL_ACTIVE_LOW; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + dp0_syncval |= SYNCVAL_HS_POL_ACTIVE_LOW; + + ret = regmap_write(tc->regmap, DP0_SYNCVAL, dp0_syncval); + if (ret) + return ret; + + ret = regmap_write(tc->regmap, DPIPXLFMT, + VS_POL_ACTIVE_LOW | HS_POL_ACTIVE_LOW | + DE_POL_ACTIVE_HIGH | SUB_CFG_TYPE_CONFIG1 | + DPI_BPP_RGB888); + if (ret) + return ret; + + ret = regmap_write(tc->regmap, DP0_MISC, + FIELD_PREP(MAX_TU_SYMBOL, max_tu_symbol) | + FIELD_PREP(TU_SIZE, TU_SIZE_RECOMMENDED) | + BPC_8); + if (ret) + return ret; + + return 0; +} + +static int tc_wait_link_training(struct tc_data *tc) +{ + u32 value; + int ret; + + ret = tc_poll_timeout(tc, DP0_LTSTAT, LT_LOOPDONE, + LT_LOOPDONE, 500, 100000); + if (ret) { + dev_err(tc->dev, "Link training timeout waiting for LT_LOOPDONE!\n"); + return ret; + } + + ret = regmap_read(tc->regmap, DP0_LTSTAT, &value); + if (ret) + return ret; + + return (value >> 8) & 0x7; +} + +static int tc_main_link_enable(struct tc_data *tc) +{ + struct drm_dp_aux *aux = &tc->aux; + struct device *dev = tc->dev; + u32 dp_phy_ctrl; + u32 value; + int ret; + u8 tmp[DP_LINK_STATUS_SIZE]; + + dev_dbg(tc->dev, "link enable\n"); + + ret = regmap_read(tc->regmap, DP0CTL, &value); + if (ret) + return ret; + + if (WARN_ON(value & DP_EN)) { + ret = regmap_write(tc->regmap, DP0CTL, 0); + if (ret) + return ret; + } + + ret = regmap_write(tc->regmap, DP0_SRCCTRL, tc_srcctrl(tc)); + if (ret) + return ret; + /* SSCG and BW27 on DP1 must be set to the same as on DP0 */ + ret = regmap_write(tc->regmap, DP1_SRCCTRL, + (tc->link.spread ? DP0_SRCCTRL_SSCG : 0) | + ((tc->link.rate != 162000) ? DP0_SRCCTRL_BW27 : 0)); + if (ret) + return ret; + + ret = tc_set_syspllparam(tc); + if (ret) + return ret; + + /* Setup Main Link */ + dp_phy_ctrl = BGREN | PWR_SW_EN | PHY_A0_EN | PHY_M0_EN; + if (tc->link.num_lanes == 2) + dp_phy_ctrl |= PHY_2LANE; + + ret = regmap_write(tc->regmap, DP_PHY_CTRL, dp_phy_ctrl); + if (ret) + return ret; + + /* PLL setup */ + ret = tc_pllupdate(tc, DP0_PLLCTRL); + if (ret) + return ret; + + ret = tc_pllupdate(tc, DP1_PLLCTRL); + if (ret) + return ret; + + /* Reset/Enable Main Links */ + dp_phy_ctrl |= DP_PHY_RST | PHY_M1_RST | PHY_M0_RST; + ret = regmap_write(tc->regmap, DP_PHY_CTRL, dp_phy_ctrl); + usleep_range(100, 200); + dp_phy_ctrl &= ~(DP_PHY_RST | PHY_M1_RST | PHY_M0_RST); + ret = regmap_write(tc->regmap, DP_PHY_CTRL, dp_phy_ctrl); + + ret = tc_poll_timeout(tc, DP_PHY_CTRL, PHY_RDY, PHY_RDY, 500, 100000); + if (ret) { + dev_err(dev, "timeout waiting for phy become ready"); + return ret; + } + + /* Set misc: 8 bits per color */ + ret = regmap_update_bits(tc->regmap, DP0_MISC, BPC_8, BPC_8); + if (ret) + return ret; + + /* + * ASSR mode + * on TC358767 side ASSR configured through strap pin + * seems there is no way to change this setting from SW + * + * check is tc configured for same mode + */ + if (tc->assr != tc->link.assr) { + dev_dbg(dev, "Trying to set display to ASSR: %d\n", + tc->assr); + /* try to set ASSR on display side */ + tmp[0] = tc->assr; + ret = drm_dp_dpcd_writeb(aux, DP_EDP_CONFIGURATION_SET, tmp[0]); + if (ret < 0) + goto err_dpcd_read; + /* read back */ + ret = drm_dp_dpcd_readb(aux, DP_EDP_CONFIGURATION_SET, tmp); + if (ret < 0) + goto err_dpcd_read; + + if (tmp[0] != tc->assr) { + dev_dbg(dev, "Failed to switch display ASSR to %d, falling back to unscrambled mode\n", + tc->assr); + /* trying with disabled scrambler */ + tc->link.scrambler_dis = true; + } + } + + /* Setup Link & DPRx Config for Training */ + tmp[0] = drm_dp_link_rate_to_bw_code(tc->link.rate); + tmp[1] = tc->link.num_lanes; + + if (drm_dp_enhanced_frame_cap(tc->link.dpcd)) + tmp[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + + ret = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, tmp, 2); + if (ret < 0) + goto err_dpcd_write; + + /* DOWNSPREAD_CTRL */ + tmp[0] = tc->link.spread ? DP_SPREAD_AMP_0_5 : 0x00; + /* MAIN_LINK_CHANNEL_CODING_SET */ + tmp[1] = DP_SET_ANSI_8B10B; + ret = drm_dp_dpcd_write(aux, DP_DOWNSPREAD_CTRL, tmp, 2); + if (ret < 0) + goto err_dpcd_write; + + /* Reset voltage-swing & pre-emphasis */ + tmp[0] = tmp[1] = DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | + DP_TRAIN_PRE_EMPH_LEVEL_0; + ret = drm_dp_dpcd_write(aux, DP_TRAINING_LANE0_SET, tmp, 2); + if (ret < 0) + goto err_dpcd_write; + + /* Clock-Recovery */ + + /* Set DPCD 0x102 for Training Pattern 1 */ + ret = regmap_write(tc->regmap, DP0_SNKLTCTRL, + DP_LINK_SCRAMBLING_DISABLE | + DP_TRAINING_PATTERN_1); + if (ret) + return ret; + + ret = regmap_write(tc->regmap, DP0_LTLOOPCTRL, + (15 << 28) | /* Defer Iteration Count */ + (15 << 24) | /* Loop Iteration Count */ + (0xd << 0)); /* Loop Timer Delay */ + if (ret) + return ret; + + ret = regmap_write(tc->regmap, DP0_SRCCTRL, + tc_srcctrl(tc) | DP0_SRCCTRL_SCRMBLDIS | + DP0_SRCCTRL_AUTOCORRECT | + DP0_SRCCTRL_TP1); + if (ret) + return ret; + + /* Enable DP0 to start Link Training */ + ret = regmap_write(tc->regmap, DP0CTL, + (drm_dp_enhanced_frame_cap(tc->link.dpcd) ? + EF_EN : 0) | DP_EN); + if (ret) + return ret; + + /* wait */ + + ret = tc_wait_link_training(tc); + if (ret < 0) + return ret; + + if (ret) { + dev_err(tc->dev, "Link training phase 1 failed: %s\n", + training_pattern1_errors[ret]); + return -ENODEV; + } + + /* Channel Equalization */ + + /* Set DPCD 0x102 for Training Pattern 2 */ + ret = regmap_write(tc->regmap, DP0_SNKLTCTRL, + DP_LINK_SCRAMBLING_DISABLE | + DP_TRAINING_PATTERN_2); + if (ret) + return ret; + + ret = regmap_write(tc->regmap, DP0_SRCCTRL, + tc_srcctrl(tc) | DP0_SRCCTRL_SCRMBLDIS | + DP0_SRCCTRL_AUTOCORRECT | + DP0_SRCCTRL_TP2); + if (ret) + return ret; + + /* wait */ + ret = tc_wait_link_training(tc); + if (ret < 0) + return ret; + + if (ret) { + dev_err(tc->dev, "Link training phase 2 failed: %s\n", + training_pattern2_errors[ret]); + return -ENODEV; + } + + /* + * Toshiba's documentation suggests to first clear DPCD 0x102, then + * clear the training pattern bit in DP0_SRCCTRL. Testing shows + * that the link sometimes drops if those steps are done in that order, + * but if the steps are done in reverse order, the link stays up. + * + * So we do the steps differently than documented here. + */ + + /* Clear Training Pattern, set AutoCorrect Mode = 1 */ + ret = regmap_write(tc->regmap, DP0_SRCCTRL, tc_srcctrl(tc) | + DP0_SRCCTRL_AUTOCORRECT); + if (ret) + return ret; + + /* Clear DPCD 0x102 */ + /* Note: Can Not use DP0_SNKLTCTRL (0x06E4) short cut */ + tmp[0] = tc->link.scrambler_dis ? DP_LINK_SCRAMBLING_DISABLE : 0x00; + ret = drm_dp_dpcd_writeb(aux, DP_TRAINING_PATTERN_SET, tmp[0]); + if (ret < 0) + goto err_dpcd_write; + + /* Check link status */ + ret = drm_dp_dpcd_read_link_status(aux, tmp); + if (ret < 0) + goto err_dpcd_read; + + ret = 0; + + value = tmp[0] & DP_CHANNEL_EQ_BITS; + + if (value != DP_CHANNEL_EQ_BITS) { + dev_err(tc->dev, "Lane 0 failed: %x\n", value); + ret = -ENODEV; + } + + if (tc->link.num_lanes == 2) { + value = (tmp[0] >> 4) & DP_CHANNEL_EQ_BITS; + + if (value != DP_CHANNEL_EQ_BITS) { + dev_err(tc->dev, "Lane 1 failed: %x\n", value); + ret = -ENODEV; + } + + if (!(tmp[2] & DP_INTERLANE_ALIGN_DONE)) { + dev_err(tc->dev, "Interlane align failed\n"); + ret = -ENODEV; + } + } + + if (ret) { + dev_err(dev, "0x0202 LANE0_1_STATUS: 0x%02x\n", tmp[0]); + dev_err(dev, "0x0203 LANE2_3_STATUS 0x%02x\n", tmp[1]); + dev_err(dev, "0x0204 LANE_ALIGN_STATUS_UPDATED: 0x%02x\n", tmp[2]); + dev_err(dev, "0x0205 SINK_STATUS: 0x%02x\n", tmp[3]); + dev_err(dev, "0x0206 ADJUST_REQUEST_LANE0_1: 0x%02x\n", tmp[4]); + dev_err(dev, "0x0207 ADJUST_REQUEST_LANE2_3: 0x%02x\n", tmp[5]); + return ret; + } + + return 0; +err_dpcd_read: + dev_err(tc->dev, "Failed to read DPCD: %d\n", ret); + return ret; +err_dpcd_write: + dev_err(tc->dev, "Failed to write DPCD: %d\n", ret); + return ret; +} + +static int tc_main_link_disable(struct tc_data *tc) +{ + int ret; + + dev_dbg(tc->dev, "link disable\n"); + + ret = regmap_write(tc->regmap, DP0_SRCCTRL, 0); + if (ret) + return ret; + + return regmap_write(tc->regmap, DP0CTL, 0); +} + +static int tc_stream_enable(struct tc_data *tc) +{ + int ret; + u32 value; + + dev_dbg(tc->dev, "enable video stream\n"); + + /* PXL PLL setup */ + if (tc_test_pattern) { + ret = tc_pxl_pll_en(tc, clk_get_rate(tc->refclk), + 1000 * tc->mode.clock); + if (ret) + return ret; + } + + ret = tc_set_video_mode(tc, &tc->mode); + if (ret) + return ret; + + /* Set M/N */ + ret = tc_stream_clock_calc(tc); + if (ret) + return ret; + + value = VID_MN_GEN | DP_EN; + if (drm_dp_enhanced_frame_cap(tc->link.dpcd)) + value |= EF_EN; + ret = regmap_write(tc->regmap, DP0CTL, value); + if (ret) + return ret; + /* + * VID_EN assertion should be delayed by at least N * LSCLK + * cycles from the time VID_MN_GEN is enabled in order to + * generate stable values for VID_M. LSCLK is 270 MHz or + * 162 MHz, VID_N is set to 32768 in tc_stream_clock_calc(), + * so a delay of at least 203 us should suffice. + */ + usleep_range(500, 1000); + value |= VID_EN; + ret = regmap_write(tc->regmap, DP0CTL, value); + if (ret) + return ret; + /* Set input interface */ + value = DP0_AUDSRC_NO_INPUT; + if (tc_test_pattern) + value |= DP0_VIDSRC_COLOR_BAR; + else + value |= DP0_VIDSRC_DPI_RX; + ret = regmap_write(tc->regmap, SYSCTRL, value); + if (ret) + return ret; + + return 0; +} + +static int tc_stream_disable(struct tc_data *tc) +{ + int ret; + + dev_dbg(tc->dev, "disable video stream\n"); + + ret = regmap_update_bits(tc->regmap, DP0CTL, VID_EN, 0); + if (ret) + return ret; + + tc_pxl_pll_dis(tc); + + return 0; +} + +static void tc_bridge_enable(struct drm_bridge *bridge) +{ + struct tc_data *tc = bridge_to_tc(bridge); + int ret; + + ret = tc_get_display_props(tc); + if (ret < 0) { + dev_err(tc->dev, "failed to read display props: %d\n", ret); + return; + } + + ret = tc_main_link_enable(tc); + if (ret < 0) { + dev_err(tc->dev, "main link enable error: %d\n", ret); + return; + } + + ret = tc_stream_enable(tc); + if (ret < 0) { + dev_err(tc->dev, "main link stream start error: %d\n", ret); + tc_main_link_disable(tc); + return; + } +} + +static void tc_bridge_disable(struct drm_bridge *bridge) +{ + struct tc_data *tc = bridge_to_tc(bridge); + int ret; + + ret = tc_stream_disable(tc); + if (ret < 0) + dev_err(tc->dev, "main link stream stop error: %d\n", ret); + + ret = tc_main_link_disable(tc); + if (ret < 0) + dev_err(tc->dev, "main link disable error: %d\n", ret); +} + +static bool tc_bridge_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adj) +{ + /* Fixup sync polarities, both hsync and vsync are active low */ + adj->flags = mode->flags; + adj->flags |= (DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC); + adj->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC); + + return true; +} + +static enum drm_mode_status tc_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct tc_data *tc = bridge_to_tc(bridge); + u32 req, avail; + u32 bits_per_pixel = 24; + + /* DPI interface clock limitation: upto 154 MHz */ + if (mode->clock > 154000) + return MODE_CLOCK_HIGH; + + req = mode->clock * bits_per_pixel / 8; + avail = tc->link.num_lanes * tc->link.rate; + + if (req > avail) + return MODE_BAD; + + return MODE_OK; +} + +static void tc_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adj) +{ + struct tc_data *tc = bridge_to_tc(bridge); + + tc->mode = *mode; +} + +static struct edid *tc_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct tc_data *tc = bridge_to_tc(bridge); + + return drm_get_edid(connector, &tc->aux.ddc); +} + +static int tc_connector_get_modes(struct drm_connector *connector) +{ + struct tc_data *tc = connector_to_tc(connector); + int num_modes; + struct edid *edid; + int ret; + + ret = tc_get_display_props(tc); + if (ret < 0) { + dev_err(tc->dev, "failed to read display props: %d\n", ret); + return 0; + } + + if (tc->panel_bridge) { + num_modes = drm_bridge_get_modes(tc->panel_bridge, connector); + if (num_modes > 0) + return num_modes; + } + + edid = tc_get_edid(&tc->bridge, connector); + num_modes = drm_add_edid_modes(connector, edid); + kfree(edid); + + return num_modes; +} + +static const struct drm_connector_helper_funcs tc_connector_helper_funcs = { + .get_modes = tc_connector_get_modes, +}; + +static enum drm_connector_status tc_bridge_detect(struct drm_bridge *bridge) +{ + struct tc_data *tc = bridge_to_tc(bridge); + bool conn; + u32 val; + int ret; + + ret = regmap_read(tc->regmap, GPIOI, &val); + if (ret) + return connector_status_unknown; + + conn = val & BIT(tc->hpd_pin); + + if (conn) + return connector_status_connected; + else + return connector_status_disconnected; +} + +static enum drm_connector_status +tc_connector_detect(struct drm_connector *connector, bool force) +{ + struct tc_data *tc = connector_to_tc(connector); + + if (tc->hpd_pin >= 0) + return tc_bridge_detect(&tc->bridge); + + if (tc->panel_bridge) + return connector_status_connected; + else + return connector_status_unknown; +} + +static const struct drm_connector_funcs tc_connector_funcs = { + .detect = tc_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int tc_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + struct tc_data *tc = bridge_to_tc(bridge); + struct drm_device *drm = bridge->dev; + int ret; + + if (tc->panel_bridge) { + /* If a connector is required then this driver shall create it */ + ret = drm_bridge_attach(tc->bridge.encoder, tc->panel_bridge, + &tc->bridge, flags | DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ret; + } + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return 0; + + /* Create DP/eDP connector */ + drm_connector_helper_add(&tc->connector, &tc_connector_helper_funcs); + ret = drm_connector_init(drm, &tc->connector, &tc_connector_funcs, tc->bridge.type); + if (ret) + return ret; + + /* Don't poll if don't have HPD connected */ + if (tc->hpd_pin >= 0) { + if (tc->have_irq) + tc->connector.polled = DRM_CONNECTOR_POLL_HPD; + else + tc->connector.polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + } + + drm_display_info_set_bus_formats(&tc->connector.display_info, + &bus_format, 1); + tc->connector.display_info.bus_flags = + DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE | + DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE; + drm_connector_attach_encoder(&tc->connector, tc->bridge.encoder); + + return 0; +} + +static const struct drm_bridge_funcs tc_bridge_funcs = { + .attach = tc_bridge_attach, + .mode_valid = tc_mode_valid, + .mode_set = tc_bridge_mode_set, + .enable = tc_bridge_enable, + .disable = tc_bridge_disable, + .mode_fixup = tc_bridge_mode_fixup, + .detect = tc_bridge_detect, + .get_edid = tc_get_edid, +}; + +static bool tc_readable_reg(struct device *dev, unsigned int reg) +{ + return reg != SYSCTRL; +} + +static const struct regmap_range tc_volatile_ranges[] = { + regmap_reg_range(DP0_AUXWDATA(0), DP0_AUXSTATUS), + regmap_reg_range(DP0_LTSTAT, DP0_SNKLTCHGREQ), + regmap_reg_range(DP_PHY_CTRL, DP_PHY_CTRL), + regmap_reg_range(DP0_PLLCTRL, PXL_PLLCTRL), + regmap_reg_range(VFUEN0, VFUEN0), + regmap_reg_range(INTSTS_G, INTSTS_G), + regmap_reg_range(GPIOI, GPIOI), +}; + +static const struct regmap_access_table tc_volatile_table = { + .yes_ranges = tc_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(tc_volatile_ranges), +}; + +static bool tc_writeable_reg(struct device *dev, unsigned int reg) +{ + return (reg != TC_IDREG) && + (reg != DP0_LTSTAT) && + (reg != DP0_SNKLTCHGREQ); +} + +static const struct regmap_config tc_regmap_config = { + .name = "tc358767", + .reg_bits = 16, + .val_bits = 32, + .reg_stride = 4, + .max_register = PLL_DBG, + .cache_type = REGCACHE_RBTREE, + .readable_reg = tc_readable_reg, + .volatile_table = &tc_volatile_table, + .writeable_reg = tc_writeable_reg, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static irqreturn_t tc_irq_handler(int irq, void *arg) +{ + struct tc_data *tc = arg; + u32 val; + int r; + + r = regmap_read(tc->regmap, INTSTS_G, &val); + if (r) + return IRQ_NONE; + + if (!val) + return IRQ_NONE; + + if (val & INT_SYSERR) { + u32 stat = 0; + + regmap_read(tc->regmap, SYSSTAT, &stat); + + dev_err(tc->dev, "syserr %x\n", stat); + } + + if (tc->hpd_pin >= 0 && tc->bridge.dev) { + /* + * H is triggered when the GPIO goes high. + * + * LC is triggered when the GPIO goes low and stays low for + * the duration of LCNT + */ + bool h = val & INT_GPIO_H(tc->hpd_pin); + bool lc = val & INT_GPIO_LC(tc->hpd_pin); + + dev_dbg(tc->dev, "GPIO%d: %s %s\n", tc->hpd_pin, + h ? "H" : "", lc ? "LC" : ""); + + if (h || lc) + drm_kms_helper_hotplug_event(tc->bridge.dev); + } + + regmap_write(tc->regmap, INTSTS_G, val); + + return IRQ_HANDLED; +} + +static int tc_probe_edp_bridge_endpoint(struct tc_data *tc) +{ + struct device *dev = tc->dev; + struct drm_panel *panel; + int ret; + + /* port@2 is the output port */ + ret = drm_of_find_panel_or_bridge(dev->of_node, 2, 0, &panel, NULL); + if (ret && ret != -ENODEV) + return ret; + + if (panel) { + struct drm_bridge *panel_bridge; + + panel_bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(panel_bridge)) + return PTR_ERR(panel_bridge); + + tc->panel_bridge = panel_bridge; + tc->bridge.type = DRM_MODE_CONNECTOR_eDP; + } else { + tc->bridge.type = DRM_MODE_CONNECTOR_DisplayPort; + } + + return 0; +} + +static void tc_clk_disable(void *data) +{ + struct clk *refclk = data; + + clk_disable_unprepare(refclk); +} + +static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct tc_data *tc; + int ret; + + tc = devm_kzalloc(dev, sizeof(*tc), GFP_KERNEL); + if (!tc) + return -ENOMEM; + + tc->dev = dev; + + ret = tc_probe_edp_bridge_endpoint(tc); + if (ret) + return ret; + + tc->refclk = devm_clk_get(dev, "ref"); + if (IS_ERR(tc->refclk)) { + ret = PTR_ERR(tc->refclk); + dev_err(dev, "Failed to get refclk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(tc->refclk); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, tc_clk_disable, tc->refclk); + if (ret) + return ret; + + /* tRSTW = 100 cycles , at 13 MHz that is ~7.69 us */ + usleep_range(10, 15); + + /* Shut down GPIO is optional */ + tc->sd_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH); + if (IS_ERR(tc->sd_gpio)) + return PTR_ERR(tc->sd_gpio); + + if (tc->sd_gpio) { + gpiod_set_value_cansleep(tc->sd_gpio, 0); + usleep_range(5000, 10000); + } + + /* Reset GPIO is optional */ + tc->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(tc->reset_gpio)) + return PTR_ERR(tc->reset_gpio); + + if (tc->reset_gpio) { + gpiod_set_value_cansleep(tc->reset_gpio, 1); + usleep_range(5000, 10000); + } + + tc->regmap = devm_regmap_init_i2c(client, &tc_regmap_config); + if (IS_ERR(tc->regmap)) { + ret = PTR_ERR(tc->regmap); + dev_err(dev, "Failed to initialize regmap: %d\n", ret); + return ret; + } + + ret = of_property_read_u32(dev->of_node, "toshiba,hpd-pin", + &tc->hpd_pin); + if (ret) { + tc->hpd_pin = -ENODEV; + } else { + if (tc->hpd_pin < 0 || tc->hpd_pin > 1) { + dev_err(dev, "failed to parse HPD number\n"); + return -EINVAL; + } + } + + if (client->irq > 0) { + /* enable SysErr */ + regmap_write(tc->regmap, INTCTL_G, INT_SYSERR); + + ret = devm_request_threaded_irq(dev, client->irq, + NULL, tc_irq_handler, + IRQF_ONESHOT, + "tc358767-irq", tc); + if (ret) { + dev_err(dev, "failed to register dp interrupt\n"); + return ret; + } + + tc->have_irq = true; + } + + ret = regmap_read(tc->regmap, TC_IDREG, &tc->rev); + if (ret) { + dev_err(tc->dev, "can not read device ID: %d\n", ret); + return ret; + } + + if ((tc->rev != 0x6601) && (tc->rev != 0x6603)) { + dev_err(tc->dev, "invalid device ID: 0x%08x\n", tc->rev); + return -EINVAL; + } + + tc->assr = (tc->rev == 0x6601); /* Enable ASSR for eDP panels */ + + if (!tc->reset_gpio) { + /* + * If the reset pin isn't present, do a software reset. It isn't + * as thorough as the hardware reset, as we can't reset the I2C + * communication block for obvious reasons, but it's getting the + * chip into a defined state. + */ + regmap_update_bits(tc->regmap, SYSRSTENB, + ENBLCD0 | ENBBM | ENBDSIRX | ENBREG | ENBHDCP, + 0); + regmap_update_bits(tc->regmap, SYSRSTENB, + ENBLCD0 | ENBBM | ENBDSIRX | ENBREG | ENBHDCP, + ENBLCD0 | ENBBM | ENBDSIRX | ENBREG | ENBHDCP); + usleep_range(5000, 10000); + } + + if (tc->hpd_pin >= 0) { + u32 lcnt_reg = tc->hpd_pin == 0 ? INT_GP0_LCNT : INT_GP1_LCNT; + u32 h_lc = INT_GPIO_H(tc->hpd_pin) | INT_GPIO_LC(tc->hpd_pin); + + /* Set LCNT to 2ms */ + regmap_write(tc->regmap, lcnt_reg, + clk_get_rate(tc->refclk) * 2 / 1000); + /* We need the "alternate" mode for HPD */ + regmap_write(tc->regmap, GPIOM, BIT(tc->hpd_pin)); + + if (tc->have_irq) { + /* enable H & LC */ + regmap_update_bits(tc->regmap, INTCTL_G, h_lc, h_lc); + } + } + + ret = tc_aux_link_setup(tc); + if (ret) + return ret; + + /* Register DP AUX channel */ + tc->aux.name = "TC358767 AUX i2c adapter"; + tc->aux.dev = tc->dev; + tc->aux.transfer = tc_aux_transfer; + ret = drm_dp_aux_register(&tc->aux); + if (ret) + return ret; + + tc->bridge.funcs = &tc_bridge_funcs; + if (tc->hpd_pin >= 0) + tc->bridge.ops |= DRM_BRIDGE_OP_DETECT; + tc->bridge.ops |= DRM_BRIDGE_OP_EDID; + + tc->bridge.of_node = dev->of_node; + drm_bridge_add(&tc->bridge); + + i2c_set_clientdata(client, tc); + + return 0; +} + +static int tc_remove(struct i2c_client *client) +{ + struct tc_data *tc = i2c_get_clientdata(client); + + drm_bridge_remove(&tc->bridge); + drm_dp_aux_unregister(&tc->aux); + + return 0; +} + +static const struct i2c_device_id tc358767_i2c_ids[] = { + { "tc358767", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tc358767_i2c_ids); + +static const struct of_device_id tc358767_of_ids[] = { + { .compatible = "toshiba,tc358767", }, + { } +}; +MODULE_DEVICE_TABLE(of, tc358767_of_ids); + +static struct i2c_driver tc358767_driver = { + .driver = { + .name = "tc358767", + .of_match_table = tc358767_of_ids, + }, + .id_table = tc358767_i2c_ids, + .probe = tc_probe, + .remove = tc_remove, +}; +module_i2c_driver(tc358767_driver); + +MODULE_AUTHOR("Andrey Gusakov <andrey.gusakov@cogentembedded.com>"); +MODULE_DESCRIPTION("tc358767 eDP encoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c new file mode 100644 index 000000000..48dab19f3 --- /dev/null +++ b/drivers/gpu/drm/bridge/tc358768.c @@ -0,0 +1,1113 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Texas Instruments Incorporated - https://www.ti.com + * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/media-bus-format.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <video/mipi_display.h> +#include <video/videomode.h> + +/* Global (16-bit addressable) */ +#define TC358768_CHIPID 0x0000 +#define TC358768_SYSCTL 0x0002 +#define TC358768_CONFCTL 0x0004 +#define TC358768_VSDLY 0x0006 +#define TC358768_DATAFMT 0x0008 +#define TC358768_GPIOEN 0x000E +#define TC358768_GPIODIR 0x0010 +#define TC358768_GPIOIN 0x0012 +#define TC358768_GPIOOUT 0x0014 +#define TC358768_PLLCTL0 0x0016 +#define TC358768_PLLCTL1 0x0018 +#define TC358768_CMDBYTE 0x0022 +#define TC358768_PP_MISC 0x0032 +#define TC358768_DSITX_DT 0x0050 +#define TC358768_FIFOSTATUS 0x00F8 + +/* Debug (16-bit addressable) */ +#define TC358768_VBUFCTRL 0x00E0 +#define TC358768_DBG_WIDTH 0x00E2 +#define TC358768_DBG_VBLANK 0x00E4 +#define TC358768_DBG_DATA 0x00E8 + +/* TX PHY (32-bit addressable) */ +#define TC358768_CLW_DPHYCONTTX 0x0100 +#define TC358768_D0W_DPHYCONTTX 0x0104 +#define TC358768_D1W_DPHYCONTTX 0x0108 +#define TC358768_D2W_DPHYCONTTX 0x010C +#define TC358768_D3W_DPHYCONTTX 0x0110 +#define TC358768_CLW_CNTRL 0x0140 +#define TC358768_D0W_CNTRL 0x0144 +#define TC358768_D1W_CNTRL 0x0148 +#define TC358768_D2W_CNTRL 0x014C +#define TC358768_D3W_CNTRL 0x0150 + +/* TX PPI (32-bit addressable) */ +#define TC358768_STARTCNTRL 0x0204 +#define TC358768_DSITXSTATUS 0x0208 +#define TC358768_LINEINITCNT 0x0210 +#define TC358768_LPTXTIMECNT 0x0214 +#define TC358768_TCLK_HEADERCNT 0x0218 +#define TC358768_TCLK_TRAILCNT 0x021C +#define TC358768_THS_HEADERCNT 0x0220 +#define TC358768_TWAKEUP 0x0224 +#define TC358768_TCLK_POSTCNT 0x0228 +#define TC358768_THS_TRAILCNT 0x022C +#define TC358768_HSTXVREGCNT 0x0230 +#define TC358768_HSTXVREGEN 0x0234 +#define TC358768_TXOPTIONCNTRL 0x0238 +#define TC358768_BTACNTRL1 0x023C + +/* TX CTRL (32-bit addressable) */ +#define TC358768_DSI_CONTROL 0x040C +#define TC358768_DSI_STATUS 0x0410 +#define TC358768_DSI_INT 0x0414 +#define TC358768_DSI_INT_ENA 0x0418 +#define TC358768_DSICMD_RDFIFO 0x0430 +#define TC358768_DSI_ACKERR 0x0434 +#define TC358768_DSI_ACKERR_INTENA 0x0438 +#define TC358768_DSI_ACKERR_HALT 0x043c +#define TC358768_DSI_RXERR 0x0440 +#define TC358768_DSI_RXERR_INTENA 0x0444 +#define TC358768_DSI_RXERR_HALT 0x0448 +#define TC358768_DSI_ERR 0x044C +#define TC358768_DSI_ERR_INTENA 0x0450 +#define TC358768_DSI_ERR_HALT 0x0454 +#define TC358768_DSI_CONFW 0x0500 +#define TC358768_DSI_LPCMD 0x0500 +#define TC358768_DSI_RESET 0x0504 +#define TC358768_DSI_INT_CLR 0x050C +#define TC358768_DSI_START 0x0518 + +/* DSITX CTRL (16-bit addressable) */ +#define TC358768_DSICMD_TX 0x0600 +#define TC358768_DSICMD_TYPE 0x0602 +#define TC358768_DSICMD_WC 0x0604 +#define TC358768_DSICMD_WD0 0x0610 +#define TC358768_DSICMD_WD1 0x0612 +#define TC358768_DSICMD_WD2 0x0614 +#define TC358768_DSICMD_WD3 0x0616 +#define TC358768_DSI_EVENT 0x0620 +#define TC358768_DSI_VSW 0x0622 +#define TC358768_DSI_VBPR 0x0624 +#define TC358768_DSI_VACT 0x0626 +#define TC358768_DSI_HSW 0x0628 +#define TC358768_DSI_HBPR 0x062A +#define TC358768_DSI_HACT 0x062C + +/* TC358768_DSI_CONTROL (0x040C) register */ +#define TC358768_DSI_CONTROL_DIS_MODE BIT(15) +#define TC358768_DSI_CONTROL_TXMD BIT(7) +#define TC358768_DSI_CONTROL_HSCKMD BIT(5) +#define TC358768_DSI_CONTROL_EOTDIS BIT(0) + +/* TC358768_DSI_CONFW (0x0500) register */ +#define TC358768_DSI_CONFW_MODE_SET (5 << 29) +#define TC358768_DSI_CONFW_MODE_CLR (6 << 29) +#define TC358768_DSI_CONFW_ADDR_DSI_CONTROL (0x3 << 24) + +static const char * const tc358768_supplies[] = { + "vddc", "vddmipi", "vddio" +}; + +struct tc358768_dsi_output { + struct mipi_dsi_device *dev; + struct drm_panel *panel; + struct drm_bridge *bridge; +}; + +struct tc358768_priv { + struct device *dev; + struct regmap *regmap; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(tc358768_supplies)]; + struct clk *refclk; + int enabled; + int error; + + struct mipi_dsi_host dsi_host; + struct drm_bridge bridge; + struct tc358768_dsi_output output; + + u32 pd_lines; /* number of Parallel Port Input Data Lines */ + u32 dsi_lanes; /* number of DSI Lanes */ + u32 dsi_bpp; /* number of Bits Per Pixel over DSI */ + + /* Parameters for PLL programming */ + u32 fbd; /* PLL feedback divider */ + u32 prd; /* PLL input divider */ + u32 frs; /* PLL Freqency range for HSCK (post divider) */ + + u32 dsiclk; /* pll_clk / 2 */ +}; + +static inline struct tc358768_priv *dsi_host_to_tc358768(struct mipi_dsi_host + *host) +{ + return container_of(host, struct tc358768_priv, dsi_host); +} + +static inline struct tc358768_priv *bridge_to_tc358768(struct drm_bridge + *bridge) +{ + return container_of(bridge, struct tc358768_priv, bridge); +} + +static int tc358768_clear_error(struct tc358768_priv *priv) +{ + int ret = priv->error; + + priv->error = 0; + return ret; +} + +static void tc358768_write(struct tc358768_priv *priv, u32 reg, u32 val) +{ + /* work around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81715 */ + int tmpval = val; + size_t count = 2; + + if (priv->error) + return; + + /* 16-bit register? */ + if (reg < 0x100 || reg >= 0x600) + count = 1; + + priv->error = regmap_bulk_write(priv->regmap, reg, &tmpval, count); +} + +static void tc358768_read(struct tc358768_priv *priv, u32 reg, u32 *val) +{ + size_t count = 2; + + if (priv->error) + return; + + /* 16-bit register? */ + if (reg < 0x100 || reg >= 0x600) { + *val = 0; + count = 1; + } + + priv->error = regmap_bulk_read(priv->regmap, reg, val, count); +} + +static void tc358768_update_bits(struct tc358768_priv *priv, u32 reg, u32 mask, + u32 val) +{ + u32 tmp, orig; + + tc358768_read(priv, reg, &orig); + + if (priv->error) + return; + + tmp = orig & ~mask; + tmp |= val & mask; + if (tmp != orig) + tc358768_write(priv, reg, tmp); +} + +static int tc358768_sw_reset(struct tc358768_priv *priv) +{ + /* Assert Reset */ + tc358768_write(priv, TC358768_SYSCTL, 1); + /* Release Reset, Exit Sleep */ + tc358768_write(priv, TC358768_SYSCTL, 0); + + return tc358768_clear_error(priv); +} + +static void tc358768_hw_enable(struct tc358768_priv *priv) +{ + int ret; + + if (priv->enabled) + return; + + ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); + if (ret < 0) + dev_err(priv->dev, "error enabling regulators (%d)\n", ret); + + if (priv->reset_gpio) + usleep_range(200, 300); + + /* + * The RESX is active low (GPIO_ACTIVE_LOW). + * DEASSERT (value = 0) the reset_gpio to enable the chip + */ + gpiod_set_value_cansleep(priv->reset_gpio, 0); + + /* wait for encoder clocks to stabilize */ + usleep_range(1000, 2000); + + priv->enabled = true; +} + +static void tc358768_hw_disable(struct tc358768_priv *priv) +{ + int ret; + + if (!priv->enabled) + return; + + /* + * The RESX is active low (GPIO_ACTIVE_LOW). + * ASSERT (value = 1) the reset_gpio to disable the chip + */ + gpiod_set_value_cansleep(priv->reset_gpio, 1); + + ret = regulator_bulk_disable(ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret < 0) + dev_err(priv->dev, "error disabling regulators (%d)\n", ret); + + priv->enabled = false; +} + +static u32 tc358768_pll_to_pclk(struct tc358768_priv *priv, u32 pll_clk) +{ + return (u32)div_u64((u64)pll_clk * priv->dsi_lanes, priv->dsi_bpp); +} + +static u32 tc358768_pclk_to_pll(struct tc358768_priv *priv, u32 pclk) +{ + return (u32)div_u64((u64)pclk * priv->dsi_bpp, priv->dsi_lanes); +} + +static int tc358768_calc_pll(struct tc358768_priv *priv, + const struct drm_display_mode *mode, + bool verify_only) +{ + const u32 frs_limits[] = { + 1000000000, + 500000000, + 250000000, + 125000000, + 62500000 + }; + unsigned long refclk; + u32 prd, target_pll, i, max_pll, min_pll; + u32 frs, best_diff, best_pll, best_prd, best_fbd; + + target_pll = tc358768_pclk_to_pll(priv, mode->clock * 1000); + + /* pll_clk = RefClk * [(FBD + 1)/ (PRD + 1)] * [1 / (2^FRS)] */ + + for (i = 0; i < ARRAY_SIZE(frs_limits); i++) + if (target_pll >= frs_limits[i]) + break; + + if (i == ARRAY_SIZE(frs_limits) || i == 0) + return -EINVAL; + + frs = i - 1; + max_pll = frs_limits[i - 1]; + min_pll = frs_limits[i]; + + refclk = clk_get_rate(priv->refclk); + + best_diff = UINT_MAX; + best_pll = 0; + best_prd = 0; + best_fbd = 0; + + for (prd = 0; prd < 16; ++prd) { + u32 divisor = (prd + 1) * (1 << frs); + u32 fbd; + + for (fbd = 0; fbd < 512; ++fbd) { + u32 pll, diff, pll_in; + + pll = (u32)div_u64((u64)refclk * (fbd + 1), divisor); + + if (pll >= max_pll || pll < min_pll) + continue; + + pll_in = (u32)div_u64((u64)refclk, prd + 1); + if (pll_in < 4000000) + continue; + + diff = max(pll, target_pll) - min(pll, target_pll); + + if (diff < best_diff) { + best_diff = diff; + best_pll = pll; + best_prd = prd; + best_fbd = fbd; + + if (best_diff == 0) + goto found; + } + } + } + + if (best_diff == UINT_MAX) { + dev_err(priv->dev, "could not find suitable PLL setup\n"); + return -EINVAL; + } + +found: + if (verify_only) + return 0; + + priv->fbd = best_fbd; + priv->prd = best_prd; + priv->frs = frs; + priv->dsiclk = best_pll / 2; + + return 0; +} + +static int tc358768_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *dev) +{ + struct tc358768_priv *priv = dsi_host_to_tc358768(host); + struct drm_bridge *bridge; + struct drm_panel *panel; + struct device_node *ep; + int ret; + + if (dev->lanes > 4) { + dev_err(priv->dev, "unsupported number of data lanes(%u)\n", + dev->lanes); + return -EINVAL; + } + + /* + * tc358768 supports both Video and Pulse mode, but the driver only + * implements Video (event) mode currently + */ + if (!(dev->mode_flags & MIPI_DSI_MODE_VIDEO)) { + dev_err(priv->dev, "Only MIPI_DSI_MODE_VIDEO is supported\n"); + return -ENOTSUPP; + } + + /* + * tc358768 supports RGB888, RGB666, RGB666_PACKED and RGB565, but only + * RGB888 is verified. + */ + if (dev->format != MIPI_DSI_FMT_RGB888) { + dev_warn(priv->dev, "Only MIPI_DSI_FMT_RGB888 tested!\n"); + return -ENOTSUPP; + } + + ret = drm_of_find_panel_or_bridge(host->dev->of_node, 1, 0, &panel, + &bridge); + if (ret) + return ret; + + if (panel) { + bridge = drm_panel_bridge_add_typed(panel, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + } + + priv->output.dev = dev; + priv->output.bridge = bridge; + priv->output.panel = panel; + + priv->dsi_lanes = dev->lanes; + priv->dsi_bpp = mipi_dsi_pixel_format_to_bpp(dev->format); + + /* get input ep (port0/endpoint0) */ + ret = -EINVAL; + ep = of_graph_get_endpoint_by_regs(host->dev->of_node, 0, 0); + if (ep) { + ret = of_property_read_u32(ep, "data-lines", &priv->pd_lines); + + of_node_put(ep); + } + + if (ret) + priv->pd_lines = priv->dsi_bpp; + + drm_bridge_add(&priv->bridge); + + return 0; +} + +static int tc358768_dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *dev) +{ + struct tc358768_priv *priv = dsi_host_to_tc358768(host); + + drm_bridge_remove(&priv->bridge); + if (priv->output.panel) + drm_panel_bridge_remove(priv->output.bridge); + + return 0; +} + +static ssize_t tc358768_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct tc358768_priv *priv = dsi_host_to_tc358768(host); + struct mipi_dsi_packet packet; + int ret; + + if (!priv->enabled) { + dev_err(priv->dev, "Bridge is not enabled\n"); + return -ENODEV; + } + + if (msg->rx_len) { + dev_warn(priv->dev, "MIPI rx is not supported\n"); + return -ENOTSUPP; + } + + if (msg->tx_len > 8) { + dev_warn(priv->dev, "Maximum 8 byte MIPI tx is supported\n"); + return -ENOTSUPP; + } + + ret = mipi_dsi_create_packet(&packet, msg); + if (ret) + return ret; + + if (mipi_dsi_packet_format_is_short(msg->type)) { + tc358768_write(priv, TC358768_DSICMD_TYPE, + (0x10 << 8) | (packet.header[0] & 0x3f)); + tc358768_write(priv, TC358768_DSICMD_WC, 0); + tc358768_write(priv, TC358768_DSICMD_WD0, + (packet.header[2] << 8) | packet.header[1]); + } else { + int i; + + tc358768_write(priv, TC358768_DSICMD_TYPE, + (0x40 << 8) | (packet.header[0] & 0x3f)); + tc358768_write(priv, TC358768_DSICMD_WC, packet.payload_length); + for (i = 0; i < packet.payload_length; i += 2) { + u16 val = packet.payload[i]; + + if (i + 1 < packet.payload_length) + val |= packet.payload[i + 1] << 8; + + tc358768_write(priv, TC358768_DSICMD_WD0 + i, val); + } + } + + /* start transfer */ + tc358768_write(priv, TC358768_DSICMD_TX, 1); + + ret = tc358768_clear_error(priv); + if (ret) + dev_warn(priv->dev, "Software disable failed: %d\n", ret); + else + ret = packet.size; + + return ret; +} + +static const struct mipi_dsi_host_ops tc358768_dsi_host_ops = { + .attach = tc358768_dsi_host_attach, + .detach = tc358768_dsi_host_detach, + .transfer = tc358768_dsi_host_transfer, +}; + +static int tc358768_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct tc358768_priv *priv = bridge_to_tc358768(bridge); + + if (!drm_core_check_feature(bridge->dev, DRIVER_ATOMIC)) { + dev_err(priv->dev, "needs atomic updates support\n"); + return -ENOTSUPP; + } + + return drm_bridge_attach(bridge->encoder, priv->output.bridge, bridge, + flags); +} + +static enum drm_mode_status +tc358768_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct tc358768_priv *priv = bridge_to_tc358768(bridge); + + if (tc358768_calc_pll(priv, mode, true)) + return MODE_CLOCK_RANGE; + + return MODE_OK; +} + +static void tc358768_bridge_disable(struct drm_bridge *bridge) +{ + struct tc358768_priv *priv = bridge_to_tc358768(bridge); + int ret; + + /* set FrmStop */ + tc358768_update_bits(priv, TC358768_PP_MISC, BIT(15), BIT(15)); + + /* wait at least for one frame */ + msleep(50); + + /* clear PP_en */ + tc358768_update_bits(priv, TC358768_CONFCTL, BIT(6), 0); + + /* set RstPtr */ + tc358768_update_bits(priv, TC358768_PP_MISC, BIT(14), BIT(14)); + + ret = tc358768_clear_error(priv); + if (ret) + dev_warn(priv->dev, "Software disable failed: %d\n", ret); +} + +static void tc358768_bridge_post_disable(struct drm_bridge *bridge) +{ + struct tc358768_priv *priv = bridge_to_tc358768(bridge); + + tc358768_hw_disable(priv); +} + +static int tc358768_setup_pll(struct tc358768_priv *priv, + const struct drm_display_mode *mode) +{ + u32 fbd, prd, frs; + int ret; + + ret = tc358768_calc_pll(priv, mode, false); + if (ret) { + dev_err(priv->dev, "PLL calculation failed: %d\n", ret); + return ret; + } + + fbd = priv->fbd; + prd = priv->prd; + frs = priv->frs; + + dev_dbg(priv->dev, "PLL: refclk %lu, fbd %u, prd %u, frs %u\n", + clk_get_rate(priv->refclk), fbd, prd, frs); + dev_dbg(priv->dev, "PLL: pll_clk: %u, DSIClk %u, DSIByteClk %u\n", + priv->dsiclk * 2, priv->dsiclk, priv->dsiclk / 4); + dev_dbg(priv->dev, "PLL: pclk %u (panel: %u)\n", + tc358768_pll_to_pclk(priv, priv->dsiclk * 2), + mode->clock * 1000); + + /* PRD[15:12] FBD[8:0] */ + tc358768_write(priv, TC358768_PLLCTL0, (prd << 12) | fbd); + + /* FRS[11:10] LBWS[9:8] CKEN[4] RESETB[1] EN[0] */ + tc358768_write(priv, TC358768_PLLCTL1, + (frs << 10) | (0x2 << 8) | BIT(1) | BIT(0)); + + /* wait for lock */ + usleep_range(1000, 2000); + + /* FRS[11:10] LBWS[9:8] CKEN[4] PLL_CKEN[4] RESETB[1] EN[0] */ + tc358768_write(priv, TC358768_PLLCTL1, + (frs << 10) | (0x2 << 8) | BIT(4) | BIT(1) | BIT(0)); + + return tc358768_clear_error(priv); +} + +#define TC358768_PRECISION 1000 +static u32 tc358768_ns_to_cnt(u32 ns, u32 period_nsk) +{ + return (ns * TC358768_PRECISION + period_nsk) / period_nsk; +} + +static u32 tc358768_to_ns(u32 nsk) +{ + return (nsk / TC358768_PRECISION); +} + +static void tc358768_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct tc358768_priv *priv = bridge_to_tc358768(bridge); + struct mipi_dsi_device *dsi_dev = priv->output.dev; + unsigned long mode_flags = dsi_dev->mode_flags; + u32 val, val2, lptxcnt, hact, data_type; + s32 raw_val; + const struct drm_display_mode *mode; + u32 dsibclk_nsk, dsiclk_nsk, ui_nsk, phy_delay_nsk; + u32 dsiclk, dsibclk; + int ret, i; + + if (mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) { + dev_warn_once(priv->dev, "Non-continuous mode unimplemented, falling back to continuous\n"); + mode_flags &= ~MIPI_DSI_CLOCK_NON_CONTINUOUS; + } + + tc358768_hw_enable(priv); + + ret = tc358768_sw_reset(priv); + if (ret) { + dev_err(priv->dev, "Software reset failed: %d\n", ret); + tc358768_hw_disable(priv); + return; + } + + mode = &bridge->encoder->crtc->state->adjusted_mode; + ret = tc358768_setup_pll(priv, mode); + if (ret) { + dev_err(priv->dev, "PLL setup failed: %d\n", ret); + tc358768_hw_disable(priv); + return; + } + + dsiclk = priv->dsiclk; + dsibclk = dsiclk / 4; + + /* Data Format Control Register */ + val = BIT(2) | BIT(1) | BIT(0); /* rdswap_en | dsitx_en | txdt_en */ + switch (dsi_dev->format) { + case MIPI_DSI_FMT_RGB888: + val |= (0x3 << 4); + hact = mode->hdisplay * 3; + data_type = MIPI_DSI_PACKED_PIXEL_STREAM_24; + break; + case MIPI_DSI_FMT_RGB666: + val |= (0x4 << 4); + hact = mode->hdisplay * 3; + data_type = MIPI_DSI_PACKED_PIXEL_STREAM_18; + break; + + case MIPI_DSI_FMT_RGB666_PACKED: + val |= (0x4 << 4) | BIT(3); + hact = mode->hdisplay * 18 / 8; + data_type = MIPI_DSI_PIXEL_STREAM_3BYTE_18; + break; + + case MIPI_DSI_FMT_RGB565: + val |= (0x5 << 4); + hact = mode->hdisplay * 2; + data_type = MIPI_DSI_PACKED_PIXEL_STREAM_16; + break; + default: + dev_err(priv->dev, "Invalid data format (%u)\n", + dsi_dev->format); + tc358768_hw_disable(priv); + return; + } + + /* VSDly[9:0] */ + tc358768_write(priv, TC358768_VSDLY, 1); + + tc358768_write(priv, TC358768_DATAFMT, val); + tc358768_write(priv, TC358768_DSITX_DT, data_type); + + /* Enable D-PHY (HiZ->LP11) */ + tc358768_write(priv, TC358768_CLW_CNTRL, 0x0000); + /* Enable lanes */ + for (i = 0; i < dsi_dev->lanes; i++) + tc358768_write(priv, TC358768_D0W_CNTRL + i * 4, 0x0000); + + /* DSI Timings */ + dsibclk_nsk = (u32)div_u64((u64)1000000000 * TC358768_PRECISION, + dsibclk); + dsiclk_nsk = (u32)div_u64((u64)1000000000 * TC358768_PRECISION, dsiclk); + ui_nsk = dsiclk_nsk / 2; + phy_delay_nsk = dsibclk_nsk + 2 * dsiclk_nsk; + dev_dbg(priv->dev, "dsiclk_nsk: %u\n", dsiclk_nsk); + dev_dbg(priv->dev, "ui_nsk: %u\n", ui_nsk); + dev_dbg(priv->dev, "dsibclk_nsk: %u\n", dsibclk_nsk); + dev_dbg(priv->dev, "phy_delay_nsk: %u\n", phy_delay_nsk); + + /* LP11 > 100us for D-PHY Rx Init */ + val = tc358768_ns_to_cnt(100 * 1000, dsibclk_nsk) - 1; + dev_dbg(priv->dev, "LINEINITCNT: 0x%x\n", val); + tc358768_write(priv, TC358768_LINEINITCNT, val); + + /* LPTimeCnt > 50ns */ + val = tc358768_ns_to_cnt(50, dsibclk_nsk) - 1; + lptxcnt = val; + dev_dbg(priv->dev, "LPTXTIMECNT: 0x%x\n", val); + tc358768_write(priv, TC358768_LPTXTIMECNT, val); + + /* 38ns < TCLK_PREPARE < 95ns */ + val = tc358768_ns_to_cnt(65, dsibclk_nsk) - 1; + /* TCLK_PREPARE + TCLK_ZERO > 300ns */ + val2 = tc358768_ns_to_cnt(300 - tc358768_to_ns(2 * ui_nsk), + dsibclk_nsk) - 2; + val |= val2 << 8; + dev_dbg(priv->dev, "TCLK_HEADERCNT: 0x%x\n", val); + tc358768_write(priv, TC358768_TCLK_HEADERCNT, val); + + /* TCLK_TRAIL > 60ns AND TEOT <= 105 ns + 12*UI */ + raw_val = tc358768_ns_to_cnt(60 + tc358768_to_ns(2 * ui_nsk), dsibclk_nsk) - 5; + val = clamp(raw_val, 0, 127); + dev_dbg(priv->dev, "TCLK_TRAILCNT: 0x%x\n", val); + tc358768_write(priv, TC358768_TCLK_TRAILCNT, val); + + /* 40ns + 4*UI < THS_PREPARE < 85ns + 6*UI */ + val = 50 + tc358768_to_ns(4 * ui_nsk); + val = tc358768_ns_to_cnt(val, dsibclk_nsk) - 1; + /* THS_PREPARE + THS_ZERO > 145ns + 10*UI */ + raw_val = tc358768_ns_to_cnt(145 - tc358768_to_ns(3 * ui_nsk), dsibclk_nsk) - 10; + val2 = clamp(raw_val, 0, 127); + val |= val2 << 8; + dev_dbg(priv->dev, "THS_HEADERCNT: 0x%x\n", val); + tc358768_write(priv, TC358768_THS_HEADERCNT, val); + + /* TWAKEUP > 1ms in lptxcnt steps */ + val = tc358768_ns_to_cnt(1020000, dsibclk_nsk); + val = val / (lptxcnt + 1) - 1; + dev_dbg(priv->dev, "TWAKEUP: 0x%x\n", val); + tc358768_write(priv, TC358768_TWAKEUP, val); + + /* TCLK_POSTCNT > 60ns + 52*UI */ + val = tc358768_ns_to_cnt(60 + tc358768_to_ns(52 * ui_nsk), + dsibclk_nsk) - 3; + dev_dbg(priv->dev, "TCLK_POSTCNT: 0x%x\n", val); + tc358768_write(priv, TC358768_TCLK_POSTCNT, val); + + /* max(60ns + 4*UI, 8*UI) < THS_TRAILCNT < 105ns + 12*UI */ + raw_val = tc358768_ns_to_cnt(60 + tc358768_to_ns(18 * ui_nsk), + dsibclk_nsk) - 4; + val = clamp(raw_val, 0, 15); + dev_dbg(priv->dev, "THS_TRAILCNT: 0x%x\n", val); + tc358768_write(priv, TC358768_THS_TRAILCNT, val); + + val = BIT(0); + for (i = 0; i < dsi_dev->lanes; i++) + val |= BIT(i + 1); + tc358768_write(priv, TC358768_HSTXVREGEN, val); + + tc358768_write(priv, TC358768_TXOPTIONCNTRL, + (mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) ? 0 : BIT(0)); + + /* TXTAGOCNT[26:16] RXTASURECNT[10:0] */ + val = tc358768_to_ns((lptxcnt + 1) * dsibclk_nsk * 4); + val = tc358768_ns_to_cnt(val, dsibclk_nsk) / 4 - 1; + val2 = tc358768_ns_to_cnt(tc358768_to_ns((lptxcnt + 1) * dsibclk_nsk), + dsibclk_nsk) - 2; + val |= val2 << 16; + dev_dbg(priv->dev, "BTACNTRL1: 0x%x\n", val); + tc358768_write(priv, TC358768_BTACNTRL1, val); + + /* START[0] */ + tc358768_write(priv, TC358768_STARTCNTRL, 1); + + /* Set event mode */ + tc358768_write(priv, TC358768_DSI_EVENT, 1); + + /* vsw (+ vbp) */ + tc358768_write(priv, TC358768_DSI_VSW, + mode->vtotal - mode->vsync_start); + /* vbp (not used in event mode) */ + tc358768_write(priv, TC358768_DSI_VBPR, 0); + /* vact */ + tc358768_write(priv, TC358768_DSI_VACT, mode->vdisplay); + + /* (hsw + hbp) * byteclk * ndl / pclk */ + val = (u32)div_u64((mode->htotal - mode->hsync_start) * + ((u64)priv->dsiclk / 4) * priv->dsi_lanes, + mode->clock * 1000); + tc358768_write(priv, TC358768_DSI_HSW, val); + /* hbp (not used in event mode) */ + tc358768_write(priv, TC358768_DSI_HBPR, 0); + /* hact (bytes) */ + tc358768_write(priv, TC358768_DSI_HACT, hact); + + /* VSYNC polarity */ + tc358768_update_bits(priv, TC358768_CONFCTL, BIT(5), + (mode->flags & DRM_MODE_FLAG_PVSYNC) ? BIT(5) : 0); + + /* HSYNC polarity */ + tc358768_update_bits(priv, TC358768_PP_MISC, BIT(0), + (mode->flags & DRM_MODE_FLAG_PHSYNC) ? BIT(0) : 0); + + /* Start DSI Tx */ + tc358768_write(priv, TC358768_DSI_START, 0x1); + + /* Configure DSI_Control register */ + val = TC358768_DSI_CONFW_MODE_CLR | TC358768_DSI_CONFW_ADDR_DSI_CONTROL; + val |= TC358768_DSI_CONTROL_TXMD | TC358768_DSI_CONTROL_HSCKMD | + 0x3 << 1 | TC358768_DSI_CONTROL_EOTDIS; + tc358768_write(priv, TC358768_DSI_CONFW, val); + + val = TC358768_DSI_CONFW_MODE_SET | TC358768_DSI_CONFW_ADDR_DSI_CONTROL; + val |= (dsi_dev->lanes - 1) << 1; + + val |= TC358768_DSI_CONTROL_TXMD; + + if (!(mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)) + val |= TC358768_DSI_CONTROL_HSCKMD; + + if (dsi_dev->mode_flags & MIPI_DSI_MODE_EOT_PACKET) + val |= TC358768_DSI_CONTROL_EOTDIS; + + tc358768_write(priv, TC358768_DSI_CONFW, val); + + val = TC358768_DSI_CONFW_MODE_CLR | TC358768_DSI_CONFW_ADDR_DSI_CONTROL; + val |= TC358768_DSI_CONTROL_DIS_MODE; /* DSI mode */ + tc358768_write(priv, TC358768_DSI_CONFW, val); + + ret = tc358768_clear_error(priv); + if (ret) { + dev_err(priv->dev, "Bridge pre_enable failed: %d\n", ret); + tc358768_bridge_disable(bridge); + tc358768_bridge_post_disable(bridge); + } +} + +static void tc358768_bridge_enable(struct drm_bridge *bridge) +{ + struct tc358768_priv *priv = bridge_to_tc358768(bridge); + int ret; + + if (!priv->enabled) { + dev_err(priv->dev, "Bridge is not enabled\n"); + return; + } + + /* clear FrmStop and RstPtr */ + tc358768_update_bits(priv, TC358768_PP_MISC, 0x3 << 14, 0); + + /* set PP_en */ + tc358768_update_bits(priv, TC358768_CONFCTL, BIT(6), BIT(6)); + + ret = tc358768_clear_error(priv); + if (ret) { + dev_err(priv->dev, "Bridge enable failed: %d\n", ret); + tc358768_bridge_disable(bridge); + tc358768_bridge_post_disable(bridge); + } +} + +#define MAX_INPUT_SEL_FORMATS 1 + +static u32 * +tc358768_atomic_get_input_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) +{ + struct tc358768_priv *priv = bridge_to_tc358768(bridge); + u32 *input_fmts; + + *num_input_fmts = 0; + + input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts), + GFP_KERNEL); + if (!input_fmts) + return NULL; + + switch (priv->pd_lines) { + case 16: + input_fmts[0] = MEDIA_BUS_FMT_RGB565_1X16; + break; + case 18: + input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X18; + break; + default: + case 24: + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; + break; + }; + + *num_input_fmts = MAX_INPUT_SEL_FORMATS; + + return input_fmts; +} + +static const struct drm_bridge_funcs tc358768_bridge_funcs = { + .attach = tc358768_bridge_attach, + .mode_valid = tc358768_bridge_mode_valid, + .pre_enable = tc358768_bridge_pre_enable, + .enable = tc358768_bridge_enable, + .disable = tc358768_bridge_disable, + .post_disable = tc358768_bridge_post_disable, + + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_get_input_bus_fmts = tc358768_atomic_get_input_bus_fmts, +}; + +static const struct drm_bridge_timings default_tc358768_timings = { + .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE + | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE + | DRM_BUS_FLAG_DE_HIGH, +}; + +static bool tc358768_is_reserved_reg(unsigned int reg) +{ + switch (reg) { + case 0x114 ... 0x13f: + case 0x200: + case 0x20c: + case 0x400 ... 0x408: + case 0x41c ... 0x42f: + return true; + default: + return false; + } +} + +static bool tc358768_writeable_reg(struct device *dev, unsigned int reg) +{ + if (tc358768_is_reserved_reg(reg)) + return false; + + switch (reg) { + case TC358768_CHIPID: + case TC358768_FIFOSTATUS: + case TC358768_DSITXSTATUS ... (TC358768_DSITXSTATUS + 2): + case TC358768_DSI_CONTROL ... (TC358768_DSI_INT_ENA + 2): + case TC358768_DSICMD_RDFIFO ... (TC358768_DSI_ERR_HALT + 2): + return false; + default: + return true; + } +} + +static bool tc358768_readable_reg(struct device *dev, unsigned int reg) +{ + if (tc358768_is_reserved_reg(reg)) + return false; + + switch (reg) { + case TC358768_STARTCNTRL: + case TC358768_DSI_CONFW ... (TC358768_DSI_CONFW + 2): + case TC358768_DSI_INT_CLR ... (TC358768_DSI_INT_CLR + 2): + case TC358768_DSI_START ... (TC358768_DSI_START + 2): + case TC358768_DBG_DATA: + return false; + default: + return true; + } +} + +static const struct regmap_config tc358768_regmap_config = { + .name = "tc358768", + .reg_bits = 16, + .val_bits = 16, + .max_register = TC358768_DSI_HACT, + .cache_type = REGCACHE_NONE, + .writeable_reg = tc358768_writeable_reg, + .readable_reg = tc358768_readable_reg, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static const struct i2c_device_id tc358768_i2c_ids[] = { + { "tc358768", 0 }, + { "tc358778", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tc358768_i2c_ids); + +static const struct of_device_id tc358768_of_ids[] = { + { .compatible = "toshiba,tc358768", }, + { .compatible = "toshiba,tc358778", }, + { } +}; +MODULE_DEVICE_TABLE(of, tc358768_of_ids); + +static int tc358768_get_regulators(struct tc358768_priv *priv) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(priv->supplies); ++i) + priv->supplies[i].supply = tc358768_supplies[i]; + + ret = devm_regulator_bulk_get(priv->dev, ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret < 0) + dev_err(priv->dev, "failed to get regulators: %d\n", ret); + + return ret; +} + +static int tc358768_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tc358768_priv *priv; + struct device *dev = &client->dev; + struct device_node *np = dev->of_node; + int ret; + + if (!np) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + priv->dev = dev; + + ret = tc358768_get_regulators(priv); + if (ret) + return ret; + + priv->refclk = devm_clk_get(dev, "refclk"); + if (IS_ERR(priv->refclk)) + return PTR_ERR(priv->refclk); + + /* + * RESX is low active, to disable tc358768 initially (keep in reset) + * the gpio line must be LOW. This is the ASSERTED state of + * GPIO_ACTIVE_LOW (GPIOD_OUT_HIGH == ASSERTED). + */ + priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpio)) + return PTR_ERR(priv->reset_gpio); + + priv->regmap = devm_regmap_init_i2c(client, &tc358768_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(dev, "Failed to init regmap\n"); + return PTR_ERR(priv->regmap); + } + + priv->dsi_host.dev = dev; + priv->dsi_host.ops = &tc358768_dsi_host_ops; + + priv->bridge.funcs = &tc358768_bridge_funcs; + priv->bridge.timings = &default_tc358768_timings; + priv->bridge.of_node = np; + + i2c_set_clientdata(client, priv); + + return mipi_dsi_host_register(&priv->dsi_host); +} + +static int tc358768_i2c_remove(struct i2c_client *client) +{ + struct tc358768_priv *priv = i2c_get_clientdata(client); + + mipi_dsi_host_unregister(&priv->dsi_host); + + return 0; +} + +static struct i2c_driver tc358768_driver = { + .driver = { + .name = "tc358768", + .of_match_table = tc358768_of_ids, + }, + .id_table = tc358768_i2c_ids, + .probe = tc358768_i2c_probe, + .remove = tc358768_i2c_remove, +}; +module_i2c_driver(tc358768_driver); + +MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); +MODULE_DESCRIPTION("TC358768AXBG/TC358778XBG DSI bridge"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/tc358775.c b/drivers/gpu/drm/bridge/tc358775.c new file mode 100644 index 000000000..2272adcc5 --- /dev/null +++ b/drivers/gpu/drm/bridge/tc358775.c @@ -0,0 +1,749 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TC358775 DSI to LVDS bridge driver + * + * Copyright (C) 2020 SMART Wireless Computing + * Author: Vinay Simha BN <simhavcs@gmail.com> + * + */ +/* #define DEBUG */ +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <asm/unaligned.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_dp_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +#define FLD_VAL(val, start, end) FIELD_PREP(GENMASK(start, end), val) + +/* Registers */ + +/* DSI D-PHY Layer Registers */ +#define D0W_DPHYCONTTX 0x0004 /* Data Lane 0 DPHY Tx Control */ +#define CLW_DPHYCONTRX 0x0020 /* Clock Lane DPHY Rx Control */ +#define D0W_DPHYCONTRX 0x0024 /* Data Lane 0 DPHY Rx Control */ +#define D1W_DPHYCONTRX 0x0028 /* Data Lane 1 DPHY Rx Control */ +#define D2W_DPHYCONTRX 0x002C /* Data Lane 2 DPHY Rx Control */ +#define D3W_DPHYCONTRX 0x0030 /* Data Lane 3 DPHY Rx Control */ +#define COM_DPHYCONTRX 0x0038 /* DPHY Rx Common Control */ +#define CLW_CNTRL 0x0040 /* Clock Lane Control */ +#define D0W_CNTRL 0x0044 /* Data Lane 0 Control */ +#define D1W_CNTRL 0x0048 /* Data Lane 1 Control */ +#define D2W_CNTRL 0x004C /* Data Lane 2 Control */ +#define D3W_CNTRL 0x0050 /* Data Lane 3 Control */ +#define DFTMODE_CNTRL 0x0054 /* DFT Mode Control */ + +/* DSI PPI Layer Registers */ +#define PPI_STARTPPI 0x0104 /* START control bit of PPI-TX function. */ +#define PPI_START_FUNCTION 1 + +#define PPI_BUSYPPI 0x0108 +#define PPI_LINEINITCNT 0x0110 /* Line Initialization Wait Counter */ +#define PPI_LPTXTIMECNT 0x0114 +#define PPI_LANEENABLE 0x0134 /* Enables each lane at the PPI layer. */ +#define PPI_TX_RX_TA 0x013C /* DSI Bus Turn Around timing parameters */ + +/* Analog timer function enable */ +#define PPI_CLS_ATMR 0x0140 /* Delay for Clock Lane in LPRX */ +#define PPI_D0S_ATMR 0x0144 /* Delay for Data Lane 0 in LPRX */ +#define PPI_D1S_ATMR 0x0148 /* Delay for Data Lane 1 in LPRX */ +#define PPI_D2S_ATMR 0x014C /* Delay for Data Lane 2 in LPRX */ +#define PPI_D3S_ATMR 0x0150 /* Delay for Data Lane 3 in LPRX */ + +#define PPI_D0S_CLRSIPOCOUNT 0x0164 /* For lane 0 */ +#define PPI_D1S_CLRSIPOCOUNT 0x0168 /* For lane 1 */ +#define PPI_D2S_CLRSIPOCOUNT 0x016C /* For lane 2 */ +#define PPI_D3S_CLRSIPOCOUNT 0x0170 /* For lane 3 */ + +#define CLS_PRE 0x0180 /* Digital Counter inside of PHY IO */ +#define D0S_PRE 0x0184 /* Digital Counter inside of PHY IO */ +#define D1S_PRE 0x0188 /* Digital Counter inside of PHY IO */ +#define D2S_PRE 0x018C /* Digital Counter inside of PHY IO */ +#define D3S_PRE 0x0190 /* Digital Counter inside of PHY IO */ +#define CLS_PREP 0x01A0 /* Digital Counter inside of PHY IO */ +#define D0S_PREP 0x01A4 /* Digital Counter inside of PHY IO */ +#define D1S_PREP 0x01A8 /* Digital Counter inside of PHY IO */ +#define D2S_PREP 0x01AC /* Digital Counter inside of PHY IO */ +#define D3S_PREP 0x01B0 /* Digital Counter inside of PHY IO */ +#define CLS_ZERO 0x01C0 /* Digital Counter inside of PHY IO */ +#define D0S_ZERO 0x01C4 /* Digital Counter inside of PHY IO */ +#define D1S_ZERO 0x01C8 /* Digital Counter inside of PHY IO */ +#define D2S_ZERO 0x01CC /* Digital Counter inside of PHY IO */ +#define D3S_ZERO 0x01D0 /* Digital Counter inside of PHY IO */ + +#define PPI_CLRFLG 0x01E0 /* PRE Counters has reached set values */ +#define PPI_CLRSIPO 0x01E4 /* Clear SIPO values, Slave mode use only. */ +#define HSTIMEOUT 0x01F0 /* HS Rx Time Out Counter */ +#define HSTIMEOUTENABLE 0x01F4 /* Enable HS Rx Time Out Counter */ +#define DSI_STARTDSI 0x0204 /* START control bit of DSI-TX function */ +#define DSI_RX_START 1 + +#define DSI_BUSYDSI 0x0208 +#define DSI_LANEENABLE 0x0210 /* Enables each lane at the Protocol layer. */ +#define DSI_LANESTATUS0 0x0214 /* Displays lane is in HS RX mode. */ +#define DSI_LANESTATUS1 0x0218 /* Displays lane is in ULPS or STOP state */ + +#define DSI_INTSTATUS 0x0220 /* Interrupt Status */ +#define DSI_INTMASK 0x0224 /* Interrupt Mask */ +#define DSI_INTCLR 0x0228 /* Interrupt Clear */ +#define DSI_LPTXTO 0x0230 /* Low Power Tx Time Out Counter */ + +#define DSIERRCNT 0x0300 /* DSI Error Count */ +#define APLCTRL 0x0400 /* Application Layer Control */ +#define RDPKTLN 0x0404 /* Command Read Packet Length */ + +#define VPCTRL 0x0450 /* Video Path Control */ +#define HTIM1 0x0454 /* Horizontal Timing Control 1 */ +#define HTIM2 0x0458 /* Horizontal Timing Control 2 */ +#define VTIM1 0x045C /* Vertical Timing Control 1 */ +#define VTIM2 0x0460 /* Vertical Timing Control 2 */ +#define VFUEN 0x0464 /* Video Frame Timing Update Enable */ +#define VFUEN_EN BIT(0) /* Upload Enable */ + +/* Mux Input Select for LVDS LINK Input */ +#define LV_MX0003 0x0480 /* Bit 0 to 3 */ +#define LV_MX0407 0x0484 /* Bit 4 to 7 */ +#define LV_MX0811 0x0488 /* Bit 8 to 11 */ +#define LV_MX1215 0x048C /* Bit 12 to 15 */ +#define LV_MX1619 0x0490 /* Bit 16 to 19 */ +#define LV_MX2023 0x0494 /* Bit 20 to 23 */ +#define LV_MX2427 0x0498 /* Bit 24 to 27 */ +#define LV_MX(b0, b1, b2, b3) (FLD_VAL(b0, 4, 0) | FLD_VAL(b1, 12, 8) | \ + FLD_VAL(b2, 20, 16) | FLD_VAL(b3, 28, 24)) + +/* Input bit numbers used in mux registers */ +enum { + LVI_R0, + LVI_R1, + LVI_R2, + LVI_R3, + LVI_R4, + LVI_R5, + LVI_R6, + LVI_R7, + LVI_G0, + LVI_G1, + LVI_G2, + LVI_G3, + LVI_G4, + LVI_G5, + LVI_G6, + LVI_G7, + LVI_B0, + LVI_B1, + LVI_B2, + LVI_B3, + LVI_B4, + LVI_B5, + LVI_B6, + LVI_B7, + LVI_HS, + LVI_VS, + LVI_DE, + LVI_L0 +}; + +#define LVCFG 0x049C /* LVDS Configuration */ +#define LVPHY0 0x04A0 /* LVDS PHY 0 */ +#define LV_PHY0_RST(v) FLD_VAL(v, 22, 22) /* PHY reset */ +#define LV_PHY0_IS(v) FLD_VAL(v, 15, 14) +#define LV_PHY0_ND(v) FLD_VAL(v, 4, 0) /* Frequency range select */ +#define LV_PHY0_PRBS_ON(v) FLD_VAL(v, 20, 16) /* Clock/Data Flag pins */ + +#define LVPHY1 0x04A4 /* LVDS PHY 1 */ +#define SYSSTAT 0x0500 /* System Status */ +#define SYSRST 0x0504 /* System Reset */ + +#define SYS_RST_I2CS BIT(0) /* Reset I2C-Slave controller */ +#define SYS_RST_I2CM BIT(1) /* Reset I2C-Master controller */ +#define SYS_RST_LCD BIT(2) /* Reset LCD controller */ +#define SYS_RST_BM BIT(3) /* Reset Bus Management controller */ +#define SYS_RST_DSIRX BIT(4) /* Reset DSI-RX and App controller */ +#define SYS_RST_REG BIT(5) /* Reset Register module */ + +/* GPIO Registers */ +#define GPIOC 0x0520 /* GPIO Control */ +#define GPIOO 0x0524 /* GPIO Output */ +#define GPIOI 0x0528 /* GPIO Input */ + +/* I2C Registers */ +#define I2CTIMCTRL 0x0540 /* I2C IF Timing and Enable Control */ +#define I2CMADDR 0x0544 /* I2C Master Addressing */ +#define WDATAQ 0x0548 /* Write Data Queue */ +#define RDATAQ 0x054C /* Read Data Queue */ + +/* Chip ID and Revision ID Register */ +#define IDREG 0x0580 + +#define LPX_PERIOD 4 +#define TTA_GET 0x40000 +#define TTA_SURE 6 +#define SINGLE_LINK 1 +#define DUAL_LINK 2 + +#define TC358775XBG_ID 0x00007500 + +/* Debug Registers */ +#define DEBUG00 0x05A0 /* Debug */ +#define DEBUG01 0x05A4 /* LVDS Data */ + +#define DSI_CLEN_BIT BIT(0) +#define DIVIDE_BY_3 3 /* PCLK=DCLK/3 */ +#define DIVIDE_BY_6 6 /* PCLK=DCLK/6 */ +#define LVCFG_LVEN_BIT BIT(0) + +#define L0EN BIT(1) + +#define TC358775_VPCTRL_VSDELAY__MASK 0x3FF00000 +#define TC358775_VPCTRL_VSDELAY__SHIFT 20 +static inline u32 TC358775_VPCTRL_VSDELAY(uint32_t val) +{ + return ((val) << TC358775_VPCTRL_VSDELAY__SHIFT) & + TC358775_VPCTRL_VSDELAY__MASK; +} + +#define TC358775_VPCTRL_OPXLFMT__MASK 0x00000100 +#define TC358775_VPCTRL_OPXLFMT__SHIFT 8 +static inline u32 TC358775_VPCTRL_OPXLFMT(uint32_t val) +{ + return ((val) << TC358775_VPCTRL_OPXLFMT__SHIFT) & + TC358775_VPCTRL_OPXLFMT__MASK; +} + +#define TC358775_VPCTRL_MSF__MASK 0x00000001 +#define TC358775_VPCTRL_MSF__SHIFT 0 +static inline u32 TC358775_VPCTRL_MSF(uint32_t val) +{ + return ((val) << TC358775_VPCTRL_MSF__SHIFT) & + TC358775_VPCTRL_MSF__MASK; +} + +#define TC358775_LVCFG_PCLKDIV__MASK 0x000000f0 +#define TC358775_LVCFG_PCLKDIV__SHIFT 4 +static inline u32 TC358775_LVCFG_PCLKDIV(uint32_t val) +{ + return ((val) << TC358775_LVCFG_PCLKDIV__SHIFT) & + TC358775_LVCFG_PCLKDIV__MASK; +} + +#define TC358775_LVCFG_LVDLINK__MASK 0x00000002 +#define TC358775_LVCFG_LVDLINK__SHIFT 0 +static inline u32 TC358775_LVCFG_LVDLINK(uint32_t val) +{ + return ((val) << TC358775_LVCFG_LVDLINK__SHIFT) & + TC358775_LVCFG_LVDLINK__MASK; +} + +enum tc358775_ports { + TC358775_DSI_IN, + TC358775_LVDS_OUT0, + TC358775_LVDS_OUT1, +}; + +struct tc_data { + struct i2c_client *i2c; + struct device *dev; + + struct drm_bridge bridge; + struct drm_bridge *panel_bridge; + + struct device_node *host_node; + struct mipi_dsi_device *dsi; + u8 num_dsi_lanes; + + struct regulator *vdd; + struct regulator *vddio; + struct gpio_desc *reset_gpio; + struct gpio_desc *stby_gpio; + u8 lvds_link; /* single-link or dual-link */ + u8 bpc; +}; + +static inline struct tc_data *bridge_to_tc(struct drm_bridge *b) +{ + return container_of(b, struct tc_data, bridge); +} + +static void tc_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct tc_data *tc = bridge_to_tc(bridge); + struct device *dev = &tc->dsi->dev; + int ret; + + ret = regulator_enable(tc->vddio); + if (ret < 0) + dev_err(dev, "regulator vddio enable failed, %d\n", ret); + usleep_range(10000, 11000); + + ret = regulator_enable(tc->vdd); + if (ret < 0) + dev_err(dev, "regulator vdd enable failed, %d\n", ret); + usleep_range(10000, 11000); + + gpiod_set_value(tc->stby_gpio, 0); + usleep_range(10000, 11000); + + gpiod_set_value(tc->reset_gpio, 0); + usleep_range(10, 20); +} + +static void tc_bridge_post_disable(struct drm_bridge *bridge) +{ + struct tc_data *tc = bridge_to_tc(bridge); + struct device *dev = &tc->dsi->dev; + int ret; + + gpiod_set_value(tc->reset_gpio, 1); + usleep_range(10, 20); + + gpiod_set_value(tc->stby_gpio, 1); + usleep_range(10000, 11000); + + ret = regulator_disable(tc->vdd); + if (ret < 0) + dev_err(dev, "regulator vdd disable failed, %d\n", ret); + usleep_range(10000, 11000); + + ret = regulator_disable(tc->vddio); + if (ret < 0) + dev_err(dev, "regulator vddio disable failed, %d\n", ret); + usleep_range(10000, 11000); +} + +static void d2l_read(struct i2c_client *i2c, u16 addr, u32 *val) +{ + int ret; + u8 buf_addr[2]; + + put_unaligned_be16(addr, buf_addr); + ret = i2c_master_send(i2c, buf_addr, sizeof(buf_addr)); + if (ret < 0) + goto fail; + + ret = i2c_master_recv(i2c, (u8 *)val, sizeof(*val)); + if (ret < 0) + goto fail; + + pr_debug("d2l: I2C : addr:%04x value:%08x\n", addr, *val); + +fail: + dev_err(&i2c->dev, "Error %d reading from subaddress 0x%x\n", + ret, addr); +} + +static void d2l_write(struct i2c_client *i2c, u16 addr, u32 val) +{ + u8 data[6]; + int ret; + + put_unaligned_be16(addr, data); + put_unaligned_le32(val, data + 2); + + ret = i2c_master_send(i2c, data, ARRAY_SIZE(data)); + if (ret < 0) + dev_err(&i2c->dev, "Error %d writing to subaddress 0x%x\n", + ret, addr); +} + +/* helper function to access bus_formats */ +static struct drm_connector *get_connector(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) + if (connector->encoder == encoder) + return connector; + + return NULL; +} + +static void tc_bridge_enable(struct drm_bridge *bridge) +{ + struct tc_data *tc = bridge_to_tc(bridge); + u32 hback_porch, hsync_len, hfront_porch, hactive, htime1, htime2; + u32 vback_porch, vsync_len, vfront_porch, vactive, vtime1, vtime2; + u32 val = 0; + u16 dsiclk, clkdiv, byteclk, t1, t2, t3, vsdelay; + struct drm_display_mode *mode; + struct drm_connector *connector = get_connector(bridge->encoder); + + mode = &bridge->encoder->crtc->state->adjusted_mode; + + hback_porch = mode->htotal - mode->hsync_end; + hsync_len = mode->hsync_end - mode->hsync_start; + vback_porch = mode->vtotal - mode->vsync_end; + vsync_len = mode->vsync_end - mode->vsync_start; + + htime1 = (hback_porch << 16) + hsync_len; + vtime1 = (vback_porch << 16) + vsync_len; + + hfront_porch = mode->hsync_start - mode->hdisplay; + hactive = mode->hdisplay; + vfront_porch = mode->vsync_start - mode->vdisplay; + vactive = mode->vdisplay; + + htime2 = (hfront_porch << 16) + hactive; + vtime2 = (vfront_porch << 16) + vactive; + + d2l_read(tc->i2c, IDREG, &val); + + dev_info(tc->dev, "DSI2LVDS Chip ID.%02x Revision ID. %02x **\n", + (val >> 8) & 0xFF, val & 0xFF); + + d2l_write(tc->i2c, SYSRST, SYS_RST_REG | SYS_RST_DSIRX | SYS_RST_BM | + SYS_RST_LCD | SYS_RST_I2CM | SYS_RST_I2CS); + usleep_range(30000, 40000); + + d2l_write(tc->i2c, PPI_TX_RX_TA, TTA_GET | TTA_SURE); + d2l_write(tc->i2c, PPI_LPTXTIMECNT, LPX_PERIOD); + d2l_write(tc->i2c, PPI_D0S_CLRSIPOCOUNT, 3); + d2l_write(tc->i2c, PPI_D1S_CLRSIPOCOUNT, 3); + d2l_write(tc->i2c, PPI_D2S_CLRSIPOCOUNT, 3); + d2l_write(tc->i2c, PPI_D3S_CLRSIPOCOUNT, 3); + + val = ((L0EN << tc->num_dsi_lanes) - L0EN) | DSI_CLEN_BIT; + d2l_write(tc->i2c, PPI_LANEENABLE, val); + d2l_write(tc->i2c, DSI_LANEENABLE, val); + + d2l_write(tc->i2c, PPI_STARTPPI, PPI_START_FUNCTION); + d2l_write(tc->i2c, DSI_STARTDSI, DSI_RX_START); + + if (tc->bpc == 8) + val = TC358775_VPCTRL_OPXLFMT(1); + else /* bpc = 6; */ + val = TC358775_VPCTRL_MSF(1); + + dsiclk = mode->crtc_clock * 3 * tc->bpc / tc->num_dsi_lanes / 1000; + clkdiv = dsiclk / DIVIDE_BY_3 * tc->lvds_link; + byteclk = dsiclk / 4; + t1 = hactive * (tc->bpc * 3 / 8) / tc->num_dsi_lanes; + t2 = ((100000 / clkdiv)) * (hactive + hback_porch + hsync_len + hfront_porch) / 1000; + t3 = ((t2 * byteclk) / 100) - (hactive * (tc->bpc * 3 / 8) / + tc->num_dsi_lanes); + + vsdelay = (clkdiv * (t1 + t3) / byteclk) - hback_porch - hsync_len - hactive; + + val |= TC358775_VPCTRL_VSDELAY(vsdelay); + d2l_write(tc->i2c, VPCTRL, val); + + d2l_write(tc->i2c, HTIM1, htime1); + d2l_write(tc->i2c, VTIM1, vtime1); + d2l_write(tc->i2c, HTIM2, htime2); + d2l_write(tc->i2c, VTIM2, vtime2); + + d2l_write(tc->i2c, VFUEN, VFUEN_EN); + d2l_write(tc->i2c, SYSRST, SYS_RST_LCD); + d2l_write(tc->i2c, LVPHY0, LV_PHY0_PRBS_ON(4) | LV_PHY0_ND(6)); + + dev_dbg(tc->dev, "bus_formats %04x bpc %d\n", + connector->display_info.bus_formats[0], + tc->bpc); + /* + * Default hardware register settings of tc358775 configured + * with MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA jeida-24 format + */ + if (connector->display_info.bus_formats[0] == + MEDIA_BUS_FMT_RGB888_1X7X4_SPWG) { + /* VESA-24 */ + d2l_write(tc->i2c, LV_MX0003, LV_MX(LVI_R0, LVI_R1, LVI_R2, LVI_R3)); + d2l_write(tc->i2c, LV_MX0407, LV_MX(LVI_R4, LVI_R7, LVI_R5, LVI_G0)); + d2l_write(tc->i2c, LV_MX0811, LV_MX(LVI_G1, LVI_G2, LVI_G6, LVI_G7)); + d2l_write(tc->i2c, LV_MX1215, LV_MX(LVI_G3, LVI_G4, LVI_G5, LVI_B0)); + d2l_write(tc->i2c, LV_MX1619, LV_MX(LVI_B6, LVI_B7, LVI_B1, LVI_B2)); + d2l_write(tc->i2c, LV_MX2023, LV_MX(LVI_B3, LVI_B4, LVI_B5, LVI_L0)); + d2l_write(tc->i2c, LV_MX2427, LV_MX(LVI_HS, LVI_VS, LVI_DE, LVI_R6)); + } else { /* MEDIA_BUS_FMT_RGB666_1X7X3_SPWG - JEIDA-18 */ + d2l_write(tc->i2c, LV_MX0003, LV_MX(LVI_R0, LVI_R1, LVI_R2, LVI_R3)); + d2l_write(tc->i2c, LV_MX0407, LV_MX(LVI_R4, LVI_L0, LVI_R5, LVI_G0)); + d2l_write(tc->i2c, LV_MX0811, LV_MX(LVI_G1, LVI_G2, LVI_L0, LVI_L0)); + d2l_write(tc->i2c, LV_MX1215, LV_MX(LVI_G3, LVI_G4, LVI_G5, LVI_B0)); + d2l_write(tc->i2c, LV_MX1619, LV_MX(LVI_L0, LVI_L0, LVI_B1, LVI_B2)); + d2l_write(tc->i2c, LV_MX2023, LV_MX(LVI_B3, LVI_B4, LVI_B5, LVI_L0)); + d2l_write(tc->i2c, LV_MX2427, LV_MX(LVI_HS, LVI_VS, LVI_DE, LVI_L0)); + } + + d2l_write(tc->i2c, VFUEN, VFUEN_EN); + + val = LVCFG_LVEN_BIT; + if (tc->lvds_link == DUAL_LINK) { + val |= TC358775_LVCFG_LVDLINK(1); + val |= TC358775_LVCFG_PCLKDIV(DIVIDE_BY_6); + } else { + val |= TC358775_LVCFG_PCLKDIV(DIVIDE_BY_3); + } + d2l_write(tc->i2c, LVCFG, val); +} + +static enum drm_mode_status +tc_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct tc_data *tc = bridge_to_tc(bridge); + + /* + * Maximum pixel clock speed 135MHz for single-link + * 270MHz for dual-link + */ + if ((mode->clock > 135000 && tc->lvds_link == SINGLE_LINK) || + (mode->clock > 270000 && tc->lvds_link == DUAL_LINK)) + return MODE_CLOCK_HIGH; + + switch (info->bus_formats[0]) { + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + /* RGB888 */ + tc->bpc = 8; + break; + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + /* RGB666 */ + tc->bpc = 6; + break; + default: + dev_warn(tc->dev, + "unsupported LVDS bus format 0x%04x\n", + info->bus_formats[0]); + return MODE_NOMODE; + } + + return MODE_OK; +} + +static int tc358775_parse_dt(struct device_node *np, struct tc_data *tc) +{ + struct device_node *endpoint; + struct device_node *parent; + struct device_node *remote; + struct property *prop; + int len = 0; + + /* + * To get the data-lanes of dsi, we need to access the dsi0_out of port1 + * of dsi0 endpoint from bridge port0 of d2l_in + */ + endpoint = of_graph_get_endpoint_by_regs(tc->dev->of_node, + TC358775_DSI_IN, -1); + if (endpoint) { + /* dsi0_out node */ + parent = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + if (parent) { + /* dsi0 port 1 */ + endpoint = of_graph_get_endpoint_by_regs(parent, 1, -1); + of_node_put(parent); + if (endpoint) { + prop = of_find_property(endpoint, "data-lanes", + &len); + of_node_put(endpoint); + if (!prop) { + dev_err(tc->dev, + "failed to find data lane\n"); + return -EPROBE_DEFER; + } + } + } + } + + tc->num_dsi_lanes = len / sizeof(u32); + + if (tc->num_dsi_lanes < 1 || tc->num_dsi_lanes > 4) + return -EINVAL; + + tc->host_node = of_graph_get_remote_node(np, 0, 0); + if (!tc->host_node) + return -ENODEV; + + of_node_put(tc->host_node); + + tc->lvds_link = SINGLE_LINK; + endpoint = of_graph_get_endpoint_by_regs(tc->dev->of_node, + TC358775_LVDS_OUT1, -1); + if (endpoint) { + remote = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + + if (remote) { + if (of_device_is_available(remote)) + tc->lvds_link = DUAL_LINK; + of_node_put(remote); + } + } + + dev_dbg(tc->dev, "no.of dsi lanes: %d\n", tc->num_dsi_lanes); + dev_dbg(tc->dev, "operating in %d-link mode\n", tc->lvds_link); + + return 0; +} + +static int tc_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct tc_data *tc = bridge_to_tc(bridge); + struct device *dev = &tc->i2c->dev; + struct mipi_dsi_host *host; + struct mipi_dsi_device *dsi; + int ret; + + const struct mipi_dsi_device_info info = { .type = "tc358775", + .channel = 0, + .node = NULL, + }; + + host = of_find_mipi_dsi_host_by_node(tc->host_node); + if (!host) { + dev_err(dev, "failed to find dsi host\n"); + return -EPROBE_DEFER; + } + + dsi = mipi_dsi_device_register_full(host, &info); + if (IS_ERR(dsi)) { + dev_err(dev, "failed to create dsi device\n"); + ret = PTR_ERR(dsi); + goto err_dsi_device; + } + + tc->dsi = dsi; + + dsi->lanes = tc->num_dsi_lanes; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "failed to attach dsi to host\n"); + goto err_dsi_attach; + } + + /* Attach the panel-bridge to the dsi bridge */ + return drm_bridge_attach(bridge->encoder, tc->panel_bridge, + &tc->bridge, flags); +err_dsi_attach: + mipi_dsi_device_unregister(dsi); +err_dsi_device: + return ret; +} + +static const struct drm_bridge_funcs tc_bridge_funcs = { + .attach = tc_bridge_attach, + .pre_enable = tc_bridge_pre_enable, + .enable = tc_bridge_enable, + .mode_valid = tc_mode_valid, + .post_disable = tc_bridge_post_disable, +}; + +static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct drm_panel *panel; + struct tc_data *tc; + int ret; + + tc = devm_kzalloc(dev, sizeof(*tc), GFP_KERNEL); + if (!tc) + return -ENOMEM; + + tc->dev = dev; + tc->i2c = client; + + ret = drm_of_find_panel_or_bridge(dev->of_node, TC358775_LVDS_OUT0, + 0, &panel, NULL); + if (ret < 0) + return ret; + if (!panel) + return -ENODEV; + + tc->panel_bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(tc->panel_bridge)) + return PTR_ERR(tc->panel_bridge); + + ret = tc358775_parse_dt(dev->of_node, tc); + if (ret) + return ret; + + tc->vddio = devm_regulator_get(dev, "vddio-supply"); + if (IS_ERR(tc->vddio)) { + ret = PTR_ERR(tc->vddio); + dev_err(dev, "vddio-supply not found\n"); + return ret; + } + + tc->vdd = devm_regulator_get(dev, "vdd-supply"); + if (IS_ERR(tc->vdd)) { + ret = PTR_ERR(tc->vdd); + dev_err(dev, "vdd-supply not found\n"); + return ret; + } + + tc->stby_gpio = devm_gpiod_get(dev, "stby", GPIOD_OUT_HIGH); + if (IS_ERR(tc->stby_gpio)) { + ret = PTR_ERR(tc->stby_gpio); + dev_err(dev, "cannot get stby-gpio %d\n", ret); + return ret; + } + + tc->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(tc->reset_gpio)) { + ret = PTR_ERR(tc->reset_gpio); + dev_err(dev, "cannot get reset-gpios %d\n", ret); + return ret; + } + + tc->bridge.funcs = &tc_bridge_funcs; + tc->bridge.of_node = dev->of_node; + drm_bridge_add(&tc->bridge); + + i2c_set_clientdata(client, tc); + + return 0; +} + +static int tc_remove(struct i2c_client *client) +{ + struct tc_data *tc = i2c_get_clientdata(client); + + drm_bridge_remove(&tc->bridge); + + return 0; +} + +static const struct i2c_device_id tc358775_i2c_ids[] = { + { "tc358775", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tc358775_i2c_ids); + +static const struct of_device_id tc358775_of_ids[] = { + { .compatible = "toshiba,tc358775", }, + { } +}; +MODULE_DEVICE_TABLE(of, tc358775_of_ids); + +static struct i2c_driver tc358775_driver = { + .driver = { + .name = "tc358775", + .of_match_table = tc358775_of_ids, + }, + .id_table = tc358775_i2c_ids, + .probe = tc_probe, + .remove = tc_remove, +}; +module_i2c_driver(tc358775_driver); + +MODULE_AUTHOR("Vinay Simha BN <simhavcs@gmail.com>"); +MODULE_DESCRIPTION("TC358775 DSI/LVDS bridge driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/thc63lvd1024.c b/drivers/gpu/drm/bridge/thc63lvd1024.c new file mode 100644 index 000000000..86b06975b --- /dev/null +++ b/drivers/gpu/drm/bridge/thc63lvd1024.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * THC63LVD1024 LVDS to parallel data DRM bridge driver. + * + * Copyright (C) 2018 Jacopo Mondi <jacopo+renesas@jmondi.org> + */ + +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_panel.h> + +enum thc63_ports { + THC63_LVDS_IN0, + THC63_LVDS_IN1, + THC63_RGB_OUT0, + THC63_RGB_OUT1, +}; + +struct thc63_dev { + struct device *dev; + + struct regulator *vcc; + + struct gpio_desc *pdwn; + struct gpio_desc *oe; + + struct drm_bridge bridge; + struct drm_bridge *next; + + struct drm_bridge_timings timings; +}; + +static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge) +{ + return container_of(bridge, struct thc63_dev, bridge); +} + +static int thc63_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct thc63_dev *thc63 = to_thc63(bridge); + + return drm_bridge_attach(bridge->encoder, thc63->next, bridge, flags); +} + +static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct thc63_dev *thc63 = to_thc63(bridge); + unsigned int min_freq; + unsigned int max_freq; + + /* + * The THC63LVD1024 pixel rate range is 8 to 135 MHz in all modes but + * dual-in, single-out where it is 40 to 150 MHz. As dual-in, dual-out + * isn't supported by the driver yet, simply derive the limits from the + * input mode. + */ + if (thc63->timings.dual_link) { + min_freq = 40000; + max_freq = 150000; + } else { + min_freq = 8000; + max_freq = 135000; + } + + if (mode->clock < min_freq) + return MODE_CLOCK_LOW; + + if (mode->clock > max_freq) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static void thc63_enable(struct drm_bridge *bridge) +{ + struct thc63_dev *thc63 = to_thc63(bridge); + int ret; + + ret = regulator_enable(thc63->vcc); + if (ret) { + dev_err(thc63->dev, + "Failed to enable regulator \"vcc\": %d\n", ret); + return; + } + + gpiod_set_value(thc63->pdwn, 0); + gpiod_set_value(thc63->oe, 1); +} + +static void thc63_disable(struct drm_bridge *bridge) +{ + struct thc63_dev *thc63 = to_thc63(bridge); + int ret; + + gpiod_set_value(thc63->oe, 0); + gpiod_set_value(thc63->pdwn, 1); + + ret = regulator_disable(thc63->vcc); + if (ret) + dev_err(thc63->dev, + "Failed to disable regulator \"vcc\": %d\n", ret); +} + +static const struct drm_bridge_funcs thc63_bridge_func = { + .attach = thc63_attach, + .mode_valid = thc63_mode_valid, + .enable = thc63_enable, + .disable = thc63_disable, +}; + +static int thc63_parse_dt(struct thc63_dev *thc63) +{ + struct device_node *endpoint; + struct device_node *remote; + + endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node, + THC63_RGB_OUT0, -1); + if (!endpoint) { + dev_err(thc63->dev, "Missing endpoint in port@%u\n", + THC63_RGB_OUT0); + return -ENODEV; + } + + remote = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + if (!remote) { + dev_err(thc63->dev, "Endpoint in port@%u unconnected\n", + THC63_RGB_OUT0); + return -ENODEV; + } + + if (!of_device_is_available(remote)) { + dev_err(thc63->dev, "port@%u remote endpoint is disabled\n", + THC63_RGB_OUT0); + of_node_put(remote); + return -ENODEV; + } + + thc63->next = of_drm_find_bridge(remote); + of_node_put(remote); + if (!thc63->next) + return -EPROBE_DEFER; + + endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node, + THC63_LVDS_IN1, -1); + if (endpoint) { + remote = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + + if (remote) { + if (of_device_is_available(remote)) + thc63->timings.dual_link = true; + of_node_put(remote); + } + } + + dev_dbg(thc63->dev, "operating in %s-link mode\n", + thc63->timings.dual_link ? "dual" : "single"); + + return 0; +} + +static int thc63_gpio_init(struct thc63_dev *thc63) +{ + thc63->oe = devm_gpiod_get_optional(thc63->dev, "oe", GPIOD_OUT_LOW); + if (IS_ERR(thc63->oe)) { + dev_err(thc63->dev, "Unable to get \"oe-gpios\": %ld\n", + PTR_ERR(thc63->oe)); + return PTR_ERR(thc63->oe); + } + + thc63->pdwn = devm_gpiod_get_optional(thc63->dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(thc63->pdwn)) { + dev_err(thc63->dev, "Unable to get \"powerdown-gpios\": %ld\n", + PTR_ERR(thc63->pdwn)); + return PTR_ERR(thc63->pdwn); + } + + return 0; +} + +static int thc63_probe(struct platform_device *pdev) +{ + struct thc63_dev *thc63; + int ret; + + thc63 = devm_kzalloc(&pdev->dev, sizeof(*thc63), GFP_KERNEL); + if (!thc63) + return -ENOMEM; + + thc63->dev = &pdev->dev; + platform_set_drvdata(pdev, thc63); + + thc63->vcc = devm_regulator_get_optional(thc63->dev, "vcc"); + if (IS_ERR(thc63->vcc)) { + if (PTR_ERR(thc63->vcc) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + dev_err(thc63->dev, "Unable to get \"vcc\" supply: %ld\n", + PTR_ERR(thc63->vcc)); + return PTR_ERR(thc63->vcc); + } + + ret = thc63_gpio_init(thc63); + if (ret) + return ret; + + ret = thc63_parse_dt(thc63); + if (ret) + return ret; + + thc63->bridge.driver_private = thc63; + thc63->bridge.of_node = pdev->dev.of_node; + thc63->bridge.funcs = &thc63_bridge_func; + thc63->bridge.timings = &thc63->timings; + + drm_bridge_add(&thc63->bridge); + + return 0; +} + +static int thc63_remove(struct platform_device *pdev) +{ + struct thc63_dev *thc63 = platform_get_drvdata(pdev); + + drm_bridge_remove(&thc63->bridge); + + return 0; +} + +static const struct of_device_id thc63_match[] = { + { .compatible = "thine,thc63lvd1024", }, + { }, +}; +MODULE_DEVICE_TABLE(of, thc63_match); + +static struct platform_driver thc63_driver = { + .probe = thc63_probe, + .remove = thc63_remove, + .driver = { + .name = "thc63lvd1024", + .of_match_table = thc63_match, + }, +}; +module_platform_driver(thc63_driver); + +MODULE_AUTHOR("Jacopo Mondi <jacopo@jmondi.org>"); +MODULE_DESCRIPTION("Thine THC63LVD1024 LVDS decoder DRM bridge driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c new file mode 100644 index 000000000..77a447a3f --- /dev/null +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -0,0 +1,1297 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * datasheet: https://www.ti.com/lit/ds/symlink/sn65dsi86.pdf + */ + +#include <linux/bits.h> +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/driver.h> +#include <linux/i2c.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_dp_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#define SN_DEVICE_REV_REG 0x08 +#define SN_DPPLL_SRC_REG 0x0A +#define DPPLL_CLK_SRC_DSICLK BIT(0) +#define REFCLK_FREQ_MASK GENMASK(3, 1) +#define REFCLK_FREQ(x) ((x) << 1) +#define DPPLL_SRC_DP_PLL_LOCK BIT(7) +#define SN_PLL_ENABLE_REG 0x0D +#define SN_DSI_LANES_REG 0x10 +#define CHA_DSI_LANES_MASK GENMASK(4, 3) +#define CHA_DSI_LANES(x) ((x) << 3) +#define SN_DSIA_CLK_FREQ_REG 0x12 +#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG 0x20 +#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG 0x24 +#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG 0x2C +#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG 0x2D +#define CHA_HSYNC_POLARITY BIT(7) +#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG 0x30 +#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG 0x31 +#define CHA_VSYNC_POLARITY BIT(7) +#define SN_CHA_HORIZONTAL_BACK_PORCH_REG 0x34 +#define SN_CHA_VERTICAL_BACK_PORCH_REG 0x36 +#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG 0x38 +#define SN_CHA_VERTICAL_FRONT_PORCH_REG 0x3A +#define SN_LN_ASSIGN_REG 0x59 +#define LN_ASSIGN_WIDTH 2 +#define SN_ENH_FRAME_REG 0x5A +#define VSTREAM_ENABLE BIT(3) +#define LN_POLRS_OFFSET 4 +#define LN_POLRS_MASK 0xf0 +#define SN_DATA_FORMAT_REG 0x5B +#define BPP_18_RGB BIT(0) +#define SN_HPD_DISABLE_REG 0x5C +#define HPD_DISABLE BIT(0) +#define SN_GPIO_IO_REG 0x5E +#define SN_GPIO_INPUT_SHIFT 4 +#define SN_GPIO_OUTPUT_SHIFT 0 +#define SN_GPIO_CTRL_REG 0x5F +#define SN_GPIO_MUX_INPUT 0 +#define SN_GPIO_MUX_OUTPUT 1 +#define SN_GPIO_MUX_SPECIAL 2 +#define SN_GPIO_MUX_MASK 0x3 +#define SN_AUX_WDATA_REG(x) (0x64 + (x)) +#define SN_AUX_ADDR_19_16_REG 0x74 +#define SN_AUX_ADDR_15_8_REG 0x75 +#define SN_AUX_ADDR_7_0_REG 0x76 +#define SN_AUX_LENGTH_REG 0x77 +#define SN_AUX_CMD_REG 0x78 +#define AUX_CMD_SEND BIT(0) +#define AUX_CMD_REQ(x) ((x) << 4) +#define SN_AUX_RDATA_REG(x) (0x79 + (x)) +#define SN_SSC_CONFIG_REG 0x93 +#define DP_NUM_LANES_MASK GENMASK(5, 4) +#define DP_NUM_LANES(x) ((x) << 4) +#define SN_DATARATE_CONFIG_REG 0x94 +#define DP_DATARATE_MASK GENMASK(7, 5) +#define DP_DATARATE(x) ((x) << 5) +#define SN_ML_TX_MODE_REG 0x96 +#define ML_TX_MAIN_LINK_OFF 0 +#define ML_TX_NORMAL_MODE BIT(0) +#define SN_AUX_CMD_STATUS_REG 0xF4 +#define AUX_IRQ_STATUS_AUX_RPLY_TOUT BIT(3) +#define AUX_IRQ_STATUS_AUX_SHORT BIT(5) +#define AUX_IRQ_STATUS_NAT_I2C_FAIL BIT(6) + +#define MIN_DSI_CLK_FREQ_MHZ 40 + +/* fudge factor required to account for 8b/10b encoding */ +#define DP_CLK_FUDGE_NUM 10 +#define DP_CLK_FUDGE_DEN 8 + +/* Matches DP_AUX_MAX_PAYLOAD_BYTES (for now) */ +#define SN_AUX_MAX_PAYLOAD_BYTES 16 + +#define SN_REGULATOR_SUPPLY_NUM 4 + +#define SN_MAX_DP_LANES 4 +#define SN_NUM_GPIOS 4 +#define SN_GPIO_PHYSICAL_OFFSET 1 + +/** + * struct ti_sn_bridge - Platform data for ti-sn65dsi86 driver. + * @dev: Pointer to our device. + * @regmap: Regmap for accessing i2c. + * @aux: Our aux channel. + * @bridge: Our bridge. + * @connector: Our connector. + * @debugfs: Used for managing our debugfs. + * @host_node: Remote DSI node. + * @dsi: Our MIPI DSI source. + * @refclk: Our reference clock. + * @panel: Our panel. + * @enable_gpio: The GPIO we toggle to enable the bridge. + * @supplies: Data for bulk enabling/disabling our regulators. + * @dp_lanes: Count of dp_lanes we're using. + * @ln_assign: Value to program to the LN_ASSIGN register. + * @ln_polrs: Value for the 4-bit LN_POLRS field of SN_ENH_FRAME_REG. + * + * @gchip: If we expose our GPIOs, this is used. + * @gchip_output: A cache of whether we've set GPIOs to output. This + * serves double-duty of keeping track of the direction and + * also keeping track of whether we've incremented the + * pm_runtime reference count for this pin, which we do + * whenever a pin is configured as an output. This is a + * bitmap so we can do atomic ops on it without an extra + * lock so concurrent users of our 4 GPIOs don't stomp on + * each other's read-modify-write. + */ +struct ti_sn_bridge { + struct device *dev; + struct regmap *regmap; + struct drm_dp_aux aux; + struct drm_bridge bridge; + struct drm_connector connector; + struct dentry *debugfs; + struct device_node *host_node; + struct mipi_dsi_device *dsi; + struct clk *refclk; + struct drm_panel *panel; + struct gpio_desc *enable_gpio; + struct regulator_bulk_data supplies[SN_REGULATOR_SUPPLY_NUM]; + int dp_lanes; + u8 ln_assign; + u8 ln_polrs; + +#if defined(CONFIG_OF_GPIO) + struct gpio_chip gchip; + DECLARE_BITMAP(gchip_output, SN_NUM_GPIOS); +#endif +}; + +static const struct regmap_range ti_sn_bridge_volatile_ranges[] = { + { .range_min = 0, .range_max = 0xFF }, +}; + +static const struct regmap_access_table ti_sn_bridge_volatile_table = { + .yes_ranges = ti_sn_bridge_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(ti_sn_bridge_volatile_ranges), +}; + +static const struct regmap_config ti_sn_bridge_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_table = &ti_sn_bridge_volatile_table, + .cache_type = REGCACHE_NONE, + .max_register = 0xFF, +}; + +static void ti_sn_bridge_write_u16(struct ti_sn_bridge *pdata, + unsigned int reg, u16 val) +{ + regmap_write(pdata->regmap, reg, val & 0xFF); + regmap_write(pdata->regmap, reg + 1, val >> 8); +} + +static int __maybe_unused ti_sn_bridge_resume(struct device *dev) +{ + struct ti_sn_bridge *pdata = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(SN_REGULATOR_SUPPLY_NUM, pdata->supplies); + if (ret) { + DRM_ERROR("failed to enable supplies %d\n", ret); + return ret; + } + + gpiod_set_value(pdata->enable_gpio, 1); + + return ret; +} + +static int __maybe_unused ti_sn_bridge_suspend(struct device *dev) +{ + struct ti_sn_bridge *pdata = dev_get_drvdata(dev); + int ret; + + gpiod_set_value(pdata->enable_gpio, 0); + + ret = regulator_bulk_disable(SN_REGULATOR_SUPPLY_NUM, pdata->supplies); + if (ret) + DRM_ERROR("failed to disable supplies %d\n", ret); + + return ret; +} + +static const struct dev_pm_ops ti_sn_bridge_pm_ops = { + SET_RUNTIME_PM_OPS(ti_sn_bridge_suspend, ti_sn_bridge_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static int status_show(struct seq_file *s, void *data) +{ + struct ti_sn_bridge *pdata = s->private; + unsigned int reg, val; + + seq_puts(s, "STATUS REGISTERS:\n"); + + pm_runtime_get_sync(pdata->dev); + + /* IRQ Status Registers, see Table 31 in datasheet */ + for (reg = 0xf0; reg <= 0xf8; reg++) { + regmap_read(pdata->regmap, reg, &val); + seq_printf(s, "[0x%02x] = 0x%08x\n", reg, val); + } + + pm_runtime_put(pdata->dev); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(status); + +static void ti_sn_debugfs_init(struct ti_sn_bridge *pdata) +{ + pdata->debugfs = debugfs_create_dir(dev_name(pdata->dev), NULL); + + debugfs_create_file("status", 0600, pdata->debugfs, pdata, + &status_fops); +} + +static void ti_sn_debugfs_remove(struct ti_sn_bridge *pdata) +{ + debugfs_remove_recursive(pdata->debugfs); + pdata->debugfs = NULL; +} + +/* Connector funcs */ +static struct ti_sn_bridge * +connector_to_ti_sn_bridge(struct drm_connector *connector) +{ + return container_of(connector, struct ti_sn_bridge, connector); +} + +static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector) +{ + struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector); + + return drm_panel_get_modes(pdata->panel, connector); +} + +static enum drm_mode_status +ti_sn_bridge_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + /* maximum supported resolution is 4K at 60 fps */ + if (mode->clock > 594000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static struct drm_connector_helper_funcs ti_sn_bridge_connector_helper_funcs = { + .get_modes = ti_sn_bridge_connector_get_modes, + .mode_valid = ti_sn_bridge_connector_mode_valid, +}; + +static enum drm_connector_status +ti_sn_bridge_connector_detect(struct drm_connector *connector, bool force) +{ + /** + * TODO: Currently if drm_panel is present, then always + * return the status as connected. Need to add support to detect + * device state for hot pluggable scenarios. + */ + return connector_status_connected; +} + +static const struct drm_connector_funcs ti_sn_bridge_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ti_sn_bridge_connector_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static struct ti_sn_bridge *bridge_to_ti_sn_bridge(struct drm_bridge *bridge) +{ + return container_of(bridge, struct ti_sn_bridge, bridge); +} + +static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata) +{ + unsigned int i; + const char * const ti_sn_bridge_supply_names[] = { + "vcca", "vcc", "vccio", "vpll", + }; + + for (i = 0; i < SN_REGULATOR_SUPPLY_NUM; i++) + pdata->supplies[i].supply = ti_sn_bridge_supply_names[i]; + + return devm_regulator_bulk_get(pdata->dev, SN_REGULATOR_SUPPLY_NUM, + pdata->supplies); +} + +static int ti_sn_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + int ret, val; + struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge); + struct mipi_dsi_host *host; + struct mipi_dsi_device *dsi; + const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge", + .channel = 0, + .node = NULL, + }; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + DRM_ERROR("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + + ret = drm_connector_init(bridge->dev, &pdata->connector, + &ti_sn_bridge_connector_funcs, + DRM_MODE_CONNECTOR_eDP); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_helper_add(&pdata->connector, + &ti_sn_bridge_connector_helper_funcs); + drm_connector_attach_encoder(&pdata->connector, bridge->encoder); + + /* + * TODO: ideally finding host resource and dsi dev registration needs + * to be done in bridge probe. But some existing DSI host drivers will + * wait for any of the drm_bridge/drm_panel to get added to the global + * bridge/panel list, before completing their probe. So if we do the + * dsi dev registration part in bridge probe, before populating in + * the global bridge list, then it will cause deadlock as dsi host probe + * will never complete, neither our bridge probe. So keeping it here + * will satisfy most of the existing host drivers. Once the host driver + * is fixed we can move the below code to bridge probe safely. + */ + host = of_find_mipi_dsi_host_by_node(pdata->host_node); + if (!host) { + DRM_ERROR("failed to find dsi host\n"); + ret = -ENODEV; + goto err_dsi_host; + } + + dsi = mipi_dsi_device_register_full(host, &info); + if (IS_ERR(dsi)) { + DRM_ERROR("failed to create dsi device\n"); + ret = PTR_ERR(dsi); + goto err_dsi_host; + } + + /* TODO: setting to 4 MIPI lanes always for now */ + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO; + + /* check if continuous dsi clock is required or not */ + pm_runtime_get_sync(pdata->dev); + regmap_read(pdata->regmap, SN_DPPLL_SRC_REG, &val); + pm_runtime_put(pdata->dev); + if (!(val & DPPLL_CLK_SRC_DSICLK)) + dsi->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + DRM_ERROR("failed to attach dsi to host\n"); + goto err_dsi_attach; + } + pdata->dsi = dsi; + + return 0; + +err_dsi_attach: + mipi_dsi_device_unregister(dsi); +err_dsi_host: + drm_connector_cleanup(&pdata->connector); + return ret; +} + +static void ti_sn_bridge_disable(struct drm_bridge *bridge) +{ + struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge); + + drm_panel_disable(pdata->panel); + + /* disable video stream */ + regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, VSTREAM_ENABLE, 0); + /* semi auto link training mode OFF */ + regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0); + /* disable DP PLL */ + regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 0); + + drm_panel_unprepare(pdata->panel); +} + +static u32 ti_sn_bridge_get_dsi_freq(struct ti_sn_bridge *pdata) +{ + u32 bit_rate_khz, clk_freq_khz; + struct drm_display_mode *mode = + &pdata->bridge.encoder->crtc->state->adjusted_mode; + + bit_rate_khz = mode->clock * + mipi_dsi_pixel_format_to_bpp(pdata->dsi->format); + clk_freq_khz = bit_rate_khz / (pdata->dsi->lanes * 2); + + return clk_freq_khz; +} + +/* clk frequencies supported by bridge in Hz in case derived from REFCLK pin */ +static const u32 ti_sn_bridge_refclk_lut[] = { + 12000000, + 19200000, + 26000000, + 27000000, + 38400000, +}; + +/* clk frequencies supported by bridge in Hz in case derived from DACP/N pin */ +static const u32 ti_sn_bridge_dsiclk_lut[] = { + 468000000, + 384000000, + 416000000, + 486000000, + 460800000, +}; + +static void ti_sn_bridge_set_refclk_freq(struct ti_sn_bridge *pdata) +{ + int i; + u32 refclk_rate; + const u32 *refclk_lut; + size_t refclk_lut_size; + + if (pdata->refclk) { + refclk_rate = clk_get_rate(pdata->refclk); + refclk_lut = ti_sn_bridge_refclk_lut; + refclk_lut_size = ARRAY_SIZE(ti_sn_bridge_refclk_lut); + clk_prepare_enable(pdata->refclk); + } else { + refclk_rate = ti_sn_bridge_get_dsi_freq(pdata) * 1000; + refclk_lut = ti_sn_bridge_dsiclk_lut; + refclk_lut_size = ARRAY_SIZE(ti_sn_bridge_dsiclk_lut); + } + + /* for i equals to refclk_lut_size means default frequency */ + for (i = 0; i < refclk_lut_size; i++) + if (refclk_lut[i] == refclk_rate) + break; + + regmap_update_bits(pdata->regmap, SN_DPPLL_SRC_REG, REFCLK_FREQ_MASK, + REFCLK_FREQ(i)); +} + +static void ti_sn_bridge_set_dsi_rate(struct ti_sn_bridge *pdata) +{ + unsigned int bit_rate_mhz, clk_freq_mhz; + unsigned int val; + struct drm_display_mode *mode = + &pdata->bridge.encoder->crtc->state->adjusted_mode; + + /* set DSIA clk frequency */ + bit_rate_mhz = (mode->clock / 1000) * + mipi_dsi_pixel_format_to_bpp(pdata->dsi->format); + clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2); + + /* for each increment in val, frequency increases by 5MHz */ + val = (MIN_DSI_CLK_FREQ_MHZ / 5) + + (((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 5) & 0xFF); + regmap_write(pdata->regmap, SN_DSIA_CLK_FREQ_REG, val); +} + +static unsigned int ti_sn_bridge_get_bpp(struct ti_sn_bridge *pdata) +{ + if (pdata->connector.display_info.bpc <= 6) + return 18; + else + return 24; +} + +/* + * LUT index corresponds to register value and + * LUT values corresponds to dp data rate supported + * by the bridge in Mbps unit. + */ +static const unsigned int ti_sn_bridge_dp_rate_lut[] = { + 0, 1620, 2160, 2430, 2700, 3240, 4320, 5400 +}; + +static int ti_sn_bridge_calc_min_dp_rate_idx(struct ti_sn_bridge *pdata) +{ + unsigned int bit_rate_khz, dp_rate_mhz; + unsigned int i; + struct drm_display_mode *mode = + &pdata->bridge.encoder->crtc->state->adjusted_mode; + + /* Calculate minimum bit rate based on our pixel clock. */ + bit_rate_khz = mode->clock * ti_sn_bridge_get_bpp(pdata); + + /* Calculate minimum DP data rate, taking 80% as per DP spec */ + dp_rate_mhz = DIV_ROUND_UP(bit_rate_khz * DP_CLK_FUDGE_NUM, + 1000 * pdata->dp_lanes * DP_CLK_FUDGE_DEN); + + for (i = 1; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut) - 1; i++) + if (ti_sn_bridge_dp_rate_lut[i] >= dp_rate_mhz) + break; + + return i; +} + +static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata, + bool rate_valid[]) +{ + unsigned int rate_per_200khz; + unsigned int rate_mhz; + u8 dpcd_val; + int ret; + int i, j; + + ret = drm_dp_dpcd_readb(&pdata->aux, DP_EDP_DPCD_REV, &dpcd_val); + if (ret != 1) { + DRM_DEV_ERROR(pdata->dev, + "Can't read eDP rev (%d), assuming 1.1\n", ret); + dpcd_val = DP_EDP_11; + } + + if (dpcd_val >= DP_EDP_14) { + /* eDP 1.4 devices must provide a custom table */ + __le16 sink_rates[DP_MAX_SUPPORTED_RATES]; + + ret = drm_dp_dpcd_read(&pdata->aux, DP_SUPPORTED_LINK_RATES, + sink_rates, sizeof(sink_rates)); + + if (ret != sizeof(sink_rates)) { + DRM_DEV_ERROR(pdata->dev, + "Can't read supported rate table (%d)\n", ret); + + /* By zeroing we'll fall back to DP_MAX_LINK_RATE. */ + memset(sink_rates, 0, sizeof(sink_rates)); + } + + for (i = 0; i < ARRAY_SIZE(sink_rates); i++) { + rate_per_200khz = le16_to_cpu(sink_rates[i]); + + if (!rate_per_200khz) + break; + + rate_mhz = rate_per_200khz * 200 / 1000; + for (j = 0; + j < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); + j++) { + if (ti_sn_bridge_dp_rate_lut[j] == rate_mhz) + rate_valid[j] = true; + } + } + + for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); i++) { + if (rate_valid[i]) + return; + } + DRM_DEV_ERROR(pdata->dev, + "No matching eDP rates in table; falling back\n"); + } + + /* On older versions best we can do is use DP_MAX_LINK_RATE */ + ret = drm_dp_dpcd_readb(&pdata->aux, DP_MAX_LINK_RATE, &dpcd_val); + if (ret != 1) { + DRM_DEV_ERROR(pdata->dev, + "Can't read max rate (%d); assuming 5.4 GHz\n", + ret); + dpcd_val = DP_LINK_BW_5_4; + } + + switch (dpcd_val) { + default: + DRM_DEV_ERROR(pdata->dev, + "Unexpected max rate (%#x); assuming 5.4 GHz\n", + (int)dpcd_val); + fallthrough; + case DP_LINK_BW_5_4: + rate_valid[7] = 1; + fallthrough; + case DP_LINK_BW_2_7: + rate_valid[4] = 1; + fallthrough; + case DP_LINK_BW_1_62: + rate_valid[1] = 1; + break; + } +} + +static void ti_sn_bridge_set_video_timings(struct ti_sn_bridge *pdata) +{ + struct drm_display_mode *mode = + &pdata->bridge.encoder->crtc->state->adjusted_mode; + u8 hsync_polarity = 0, vsync_polarity = 0; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + hsync_polarity = CHA_HSYNC_POLARITY; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + vsync_polarity = CHA_VSYNC_POLARITY; + + ti_sn_bridge_write_u16(pdata, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG, + mode->hdisplay); + ti_sn_bridge_write_u16(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG, + mode->vdisplay); + regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG, + (mode->hsync_end - mode->hsync_start) & 0xFF); + regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG, + (((mode->hsync_end - mode->hsync_start) >> 8) & 0x7F) | + hsync_polarity); + regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG, + (mode->vsync_end - mode->vsync_start) & 0xFF); + regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG, + (((mode->vsync_end - mode->vsync_start) >> 8) & 0x7F) | + vsync_polarity); + + regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_BACK_PORCH_REG, + (mode->htotal - mode->hsync_end) & 0xFF); + regmap_write(pdata->regmap, SN_CHA_VERTICAL_BACK_PORCH_REG, + (mode->vtotal - mode->vsync_end) & 0xFF); + + regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_FRONT_PORCH_REG, + (mode->hsync_start - mode->hdisplay) & 0xFF); + regmap_write(pdata->regmap, SN_CHA_VERTICAL_FRONT_PORCH_REG, + (mode->vsync_start - mode->vdisplay) & 0xFF); + + usleep_range(10000, 10500); /* 10ms delay recommended by spec */ +} + +static unsigned int ti_sn_get_max_lanes(struct ti_sn_bridge *pdata) +{ + u8 data; + int ret; + + ret = drm_dp_dpcd_readb(&pdata->aux, DP_MAX_LANE_COUNT, &data); + if (ret != 1) { + DRM_DEV_ERROR(pdata->dev, + "Can't read lane count (%d); assuming 4\n", ret); + return 4; + } + + return data & DP_LANE_COUNT_MASK; +} + +static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx, + const char **last_err_str) +{ + unsigned int val; + int ret; + + /* set dp clk frequency value */ + regmap_update_bits(pdata->regmap, SN_DATARATE_CONFIG_REG, + DP_DATARATE_MASK, DP_DATARATE(dp_rate_idx)); + + /* enable DP PLL */ + regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 1); + + ret = regmap_read_poll_timeout(pdata->regmap, SN_DPPLL_SRC_REG, val, + val & DPPLL_SRC_DP_PLL_LOCK, 1000, + 50 * 1000); + if (ret) { + *last_err_str = "DP_PLL_LOCK polling failed"; + goto exit; + } + + /* Semi auto link training mode */ + regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0x0A); + ret = regmap_read_poll_timeout(pdata->regmap, SN_ML_TX_MODE_REG, val, + val == ML_TX_MAIN_LINK_OFF || + val == ML_TX_NORMAL_MODE, 1000, + 500 * 1000); + if (ret) { + *last_err_str = "Training complete polling failed"; + } else if (val == ML_TX_MAIN_LINK_OFF) { + *last_err_str = "Link training failed, link is off"; + ret = -EIO; + } + +exit: + /* Disable the PLL if we failed */ + if (ret) + regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 0); + + return ret; +} + +static void ti_sn_bridge_enable(struct drm_bridge *bridge) +{ + struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge); + bool rate_valid[ARRAY_SIZE(ti_sn_bridge_dp_rate_lut)] = { }; + const char *last_err_str = "No supported DP rate"; + int dp_rate_idx; + unsigned int val; + int ret = -EINVAL; + int max_dp_lanes; + + max_dp_lanes = ti_sn_get_max_lanes(pdata); + pdata->dp_lanes = min(pdata->dp_lanes, max_dp_lanes); + + /* DSI_A lane config */ + val = CHA_DSI_LANES(SN_MAX_DP_LANES - pdata->dsi->lanes); + regmap_update_bits(pdata->regmap, SN_DSI_LANES_REG, + CHA_DSI_LANES_MASK, val); + + regmap_write(pdata->regmap, SN_LN_ASSIGN_REG, pdata->ln_assign); + regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, LN_POLRS_MASK, + pdata->ln_polrs << LN_POLRS_OFFSET); + + /* set dsi clk frequency value */ + ti_sn_bridge_set_dsi_rate(pdata); + + /** + * The SN65DSI86 only supports ASSR Display Authentication method and + * this method is enabled by default. An eDP panel must support this + * authentication method. We need to enable this method in the eDP panel + * at DisplayPort address 0x0010A prior to link training. + */ + drm_dp_dpcd_writeb(&pdata->aux, DP_EDP_CONFIGURATION_SET, + DP_ALTERNATE_SCRAMBLER_RESET_ENABLE); + + /* Set the DP output format (18 bpp or 24 bpp) */ + val = (ti_sn_bridge_get_bpp(pdata) == 18) ? BPP_18_RGB : 0; + regmap_update_bits(pdata->regmap, SN_DATA_FORMAT_REG, BPP_18_RGB, val); + + /* DP lane config */ + val = DP_NUM_LANES(min(pdata->dp_lanes, 3)); + regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK, + val); + + ti_sn_bridge_read_valid_rates(pdata, rate_valid); + + /* Train until we run out of rates */ + for (dp_rate_idx = ti_sn_bridge_calc_min_dp_rate_idx(pdata); + dp_rate_idx < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); + dp_rate_idx++) { + if (!rate_valid[dp_rate_idx]) + continue; + + ret = ti_sn_link_training(pdata, dp_rate_idx, &last_err_str); + if (!ret) + break; + } + if (ret) { + DRM_DEV_ERROR(pdata->dev, "%s (%d)\n", last_err_str, ret); + return; + } + + /* config video parameters */ + ti_sn_bridge_set_video_timings(pdata); + + /* enable video stream */ + regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, VSTREAM_ENABLE, + VSTREAM_ENABLE); + + drm_panel_enable(pdata->panel); +} + +static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge); + + pm_runtime_get_sync(pdata->dev); + + /* configure bridge ref_clk */ + ti_sn_bridge_set_refclk_freq(pdata); + + /* + * HPD on this bridge chip is a bit useless. This is an eDP bridge + * so the HPD is an internal signal that's only there to signal that + * the panel is done powering up. ...but the bridge chip debounces + * this signal by between 100 ms and 400 ms (depending on process, + * voltage, and temperate--I measured it at about 200 ms). One + * particular panel asserted HPD 84 ms after it was powered on meaning + * that we saw HPD 284 ms after power on. ...but the same panel said + * that instead of looking at HPD you could just hardcode a delay of + * 200 ms. We'll assume that the panel driver will have the hardcoded + * delay in its prepare and always disable HPD. + * + * If HPD somehow makes sense on some future panel we'll have to + * change this to be conditional on someone specifying that HPD should + * be used. + */ + regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG, HPD_DISABLE, + HPD_DISABLE); + + drm_panel_prepare(pdata->panel); +} + +static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) +{ + struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge); + + if (pdata->refclk) + clk_disable_unprepare(pdata->refclk); + + pm_runtime_put_sync(pdata->dev); +} + +static const struct drm_bridge_funcs ti_sn_bridge_funcs = { + .attach = ti_sn_bridge_attach, + .pre_enable = ti_sn_bridge_pre_enable, + .enable = ti_sn_bridge_enable, + .disable = ti_sn_bridge_disable, + .post_disable = ti_sn_bridge_post_disable, +}; + +static struct ti_sn_bridge *aux_to_ti_sn_bridge(struct drm_dp_aux *aux) +{ + return container_of(aux, struct ti_sn_bridge, aux); +} + +static ssize_t ti_sn_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct ti_sn_bridge *pdata = aux_to_ti_sn_bridge(aux); + u32 request = msg->request & ~DP_AUX_I2C_MOT; + u32 request_val = AUX_CMD_REQ(msg->request); + u8 *buf = (u8 *)msg->buffer; + unsigned int val; + int ret, i; + + if (msg->size > SN_AUX_MAX_PAYLOAD_BYTES) + return -EINVAL; + + switch (request) { + case DP_AUX_NATIVE_WRITE: + case DP_AUX_I2C_WRITE: + case DP_AUX_NATIVE_READ: + case DP_AUX_I2C_READ: + regmap_write(pdata->regmap, SN_AUX_CMD_REG, request_val); + break; + default: + return -EINVAL; + } + + regmap_write(pdata->regmap, SN_AUX_ADDR_19_16_REG, + (msg->address >> 16) & 0xF); + regmap_write(pdata->regmap, SN_AUX_ADDR_15_8_REG, + (msg->address >> 8) & 0xFF); + regmap_write(pdata->regmap, SN_AUX_ADDR_7_0_REG, msg->address & 0xFF); + + regmap_write(pdata->regmap, SN_AUX_LENGTH_REG, msg->size); + + if (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE) { + for (i = 0; i < msg->size; i++) + regmap_write(pdata->regmap, SN_AUX_WDATA_REG(i), + buf[i]); + } + + /* Clear old status bits before start so we don't get confused */ + regmap_write(pdata->regmap, SN_AUX_CMD_STATUS_REG, + AUX_IRQ_STATUS_NAT_I2C_FAIL | + AUX_IRQ_STATUS_AUX_RPLY_TOUT | + AUX_IRQ_STATUS_AUX_SHORT); + + regmap_write(pdata->regmap, SN_AUX_CMD_REG, request_val | AUX_CMD_SEND); + + ret = regmap_read_poll_timeout(pdata->regmap, SN_AUX_CMD_REG, val, + !(val & AUX_CMD_SEND), 200, + 50 * 1000); + if (ret) + return ret; + + ret = regmap_read(pdata->regmap, SN_AUX_CMD_STATUS_REG, &val); + if (ret) + return ret; + else if ((val & AUX_IRQ_STATUS_NAT_I2C_FAIL) + || (val & AUX_IRQ_STATUS_AUX_RPLY_TOUT) + || (val & AUX_IRQ_STATUS_AUX_SHORT)) + return -ENXIO; + + if (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE) + return msg->size; + + for (i = 0; i < msg->size; i++) { + unsigned int val; + ret = regmap_read(pdata->regmap, SN_AUX_RDATA_REG(i), + &val); + if (ret) + return ret; + + WARN_ON(val & ~0xFF); + buf[i] = (u8)(val & 0xFF); + } + + return msg->size; +} + +static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata) +{ + struct device_node *np = pdata->dev->of_node; + + pdata->host_node = of_graph_get_remote_node(np, 0, 0); + + if (!pdata->host_node) { + DRM_ERROR("remote dsi host node not found\n"); + return -ENODEV; + } + + return 0; +} + +#if defined(CONFIG_OF_GPIO) + +static int tn_sn_bridge_of_xlate(struct gpio_chip *chip, + const struct of_phandle_args *gpiospec, + u32 *flags) +{ + if (WARN_ON(gpiospec->args_count < chip->of_gpio_n_cells)) + return -EINVAL; + + if (gpiospec->args[0] > chip->ngpio || gpiospec->args[0] < 1) + return -EINVAL; + + if (flags) + *flags = gpiospec->args[1]; + + return gpiospec->args[0] - SN_GPIO_PHYSICAL_OFFSET; +} + +static int ti_sn_bridge_gpio_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + struct ti_sn_bridge *pdata = gpiochip_get_data(chip); + + /* + * We already have to keep track of the direction because we use + * that to figure out whether we've powered the device. We can + * just return that rather than (maybe) powering up the device + * to ask its direction. + */ + return test_bit(offset, pdata->gchip_output) ? + GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; +} + +static int ti_sn_bridge_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct ti_sn_bridge *pdata = gpiochip_get_data(chip); + unsigned int val; + int ret; + + /* + * When the pin is an input we don't forcibly keep the bridge + * powered--we just power it on to read the pin. NOTE: part of + * the reason this works is that the bridge defaults (when + * powered back on) to all 4 GPIOs being configured as GPIO input. + * Also note that if something else is keeping the chip powered the + * pm_runtime functions are lightweight increments of a refcount. + */ + pm_runtime_get_sync(pdata->dev); + ret = regmap_read(pdata->regmap, SN_GPIO_IO_REG, &val); + pm_runtime_put(pdata->dev); + + if (ret) + return ret; + + return !!(val & BIT(SN_GPIO_INPUT_SHIFT + offset)); +} + +static void ti_sn_bridge_gpio_set(struct gpio_chip *chip, unsigned int offset, + int val) +{ + struct ti_sn_bridge *pdata = gpiochip_get_data(chip); + int ret; + + if (!test_bit(offset, pdata->gchip_output)) { + dev_err(pdata->dev, "Ignoring GPIO set while input\n"); + return; + } + + val &= 1; + ret = regmap_update_bits(pdata->regmap, SN_GPIO_IO_REG, + BIT(SN_GPIO_OUTPUT_SHIFT + offset), + val << (SN_GPIO_OUTPUT_SHIFT + offset)); + if (ret) + dev_warn(pdata->dev, + "Failed to set bridge GPIO %u: %d\n", offset, ret); +} + +static int ti_sn_bridge_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct ti_sn_bridge *pdata = gpiochip_get_data(chip); + int shift = offset * 2; + int ret; + + if (!test_and_clear_bit(offset, pdata->gchip_output)) + return 0; + + ret = regmap_update_bits(pdata->regmap, SN_GPIO_CTRL_REG, + SN_GPIO_MUX_MASK << shift, + SN_GPIO_MUX_INPUT << shift); + if (ret) { + set_bit(offset, pdata->gchip_output); + return ret; + } + + /* + * NOTE: if nobody else is powering the device this may fully power + * it off and when it comes back it will have lost all state, but + * that's OK because the default is input and we're now an input. + */ + pm_runtime_put(pdata->dev); + + return 0; +} + +static int ti_sn_bridge_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int val) +{ + struct ti_sn_bridge *pdata = gpiochip_get_data(chip); + int shift = offset * 2; + int ret; + + if (test_and_set_bit(offset, pdata->gchip_output)) + return 0; + + pm_runtime_get_sync(pdata->dev); + + /* Set value first to avoid glitching */ + ti_sn_bridge_gpio_set(chip, offset, val); + + /* Set direction */ + ret = regmap_update_bits(pdata->regmap, SN_GPIO_CTRL_REG, + SN_GPIO_MUX_MASK << shift, + SN_GPIO_MUX_OUTPUT << shift); + if (ret) { + clear_bit(offset, pdata->gchip_output); + pm_runtime_put(pdata->dev); + } + + return ret; +} + +static void ti_sn_bridge_gpio_free(struct gpio_chip *chip, unsigned int offset) +{ + /* We won't keep pm_runtime if we're input, so switch there on free */ + ti_sn_bridge_gpio_direction_input(chip, offset); +} + +static const char * const ti_sn_bridge_gpio_names[SN_NUM_GPIOS] = { + "GPIO1", "GPIO2", "GPIO3", "GPIO4" +}; + +static int ti_sn_setup_gpio_controller(struct ti_sn_bridge *pdata) +{ + int ret; + + /* Only init if someone is going to use us as a GPIO controller */ + if (!of_property_read_bool(pdata->dev->of_node, "gpio-controller")) + return 0; + + pdata->gchip.label = dev_name(pdata->dev); + pdata->gchip.parent = pdata->dev; + pdata->gchip.owner = THIS_MODULE; + pdata->gchip.of_xlate = tn_sn_bridge_of_xlate; + pdata->gchip.of_gpio_n_cells = 2; + pdata->gchip.free = ti_sn_bridge_gpio_free; + pdata->gchip.get_direction = ti_sn_bridge_gpio_get_direction; + pdata->gchip.direction_input = ti_sn_bridge_gpio_direction_input; + pdata->gchip.direction_output = ti_sn_bridge_gpio_direction_output; + pdata->gchip.get = ti_sn_bridge_gpio_get; + pdata->gchip.set = ti_sn_bridge_gpio_set; + pdata->gchip.can_sleep = true; + pdata->gchip.names = ti_sn_bridge_gpio_names; + pdata->gchip.ngpio = SN_NUM_GPIOS; + pdata->gchip.base = -1; + ret = devm_gpiochip_add_data(pdata->dev, &pdata->gchip, pdata); + if (ret) + dev_err(pdata->dev, "can't add gpio chip\n"); + + return ret; +} + +#else + +static inline int ti_sn_setup_gpio_controller(struct ti_sn_bridge *pdata) +{ + return 0; +} + +#endif + +static void ti_sn_bridge_parse_lanes(struct ti_sn_bridge *pdata, + struct device_node *np) +{ + u32 lane_assignments[SN_MAX_DP_LANES] = { 0, 1, 2, 3 }; + u32 lane_polarities[SN_MAX_DP_LANES] = { }; + struct device_node *endpoint; + u8 ln_assign = 0; + u8 ln_polrs = 0; + int dp_lanes; + int i; + + /* + * Read config from the device tree about lane remapping and lane + * polarities. These are optional and we assume identity map and + * normal polarity if nothing is specified. It's OK to specify just + * data-lanes but not lane-polarities but not vice versa. + * + * Error checking is light (we just make sure we don't crash or + * buffer overrun) and we assume dts is well formed and specifying + * mappings that the hardware supports. + */ + endpoint = of_graph_get_endpoint_by_regs(np, 1, -1); + dp_lanes = of_property_count_u32_elems(endpoint, "data-lanes"); + if (dp_lanes > 0 && dp_lanes <= SN_MAX_DP_LANES) { + of_property_read_u32_array(endpoint, "data-lanes", + lane_assignments, dp_lanes); + of_property_read_u32_array(endpoint, "lane-polarities", + lane_polarities, dp_lanes); + } else { + dp_lanes = SN_MAX_DP_LANES; + } + of_node_put(endpoint); + + /* + * Convert into register format. Loop over all lanes even if + * data-lanes had fewer elements so that we nicely initialize + * the LN_ASSIGN register. + */ + for (i = SN_MAX_DP_LANES - 1; i >= 0; i--) { + ln_assign = ln_assign << LN_ASSIGN_WIDTH | lane_assignments[i]; + ln_polrs = ln_polrs << 1 | lane_polarities[i]; + } + + /* Stash in our struct for when we power on */ + pdata->dp_lanes = dp_lanes; + pdata->ln_assign = ln_assign; + pdata->ln_polrs = ln_polrs; +} + +static int ti_sn_bridge_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ti_sn_bridge *pdata; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + DRM_ERROR("device doesn't support I2C\n"); + return -ENODEV; + } + + pdata = devm_kzalloc(&client->dev, sizeof(struct ti_sn_bridge), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->regmap = devm_regmap_init_i2c(client, + &ti_sn_bridge_regmap_config); + if (IS_ERR(pdata->regmap)) { + DRM_ERROR("regmap i2c init failed\n"); + return PTR_ERR(pdata->regmap); + } + + pdata->dev = &client->dev; + + ret = drm_of_find_panel_or_bridge(pdata->dev->of_node, 1, 0, + &pdata->panel, NULL); + if (ret) { + DRM_ERROR("could not find any panel node\n"); + return ret; + } + + dev_set_drvdata(&client->dev, pdata); + + pdata->enable_gpio = devm_gpiod_get(pdata->dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(pdata->enable_gpio)) { + DRM_ERROR("failed to get enable gpio from DT\n"); + ret = PTR_ERR(pdata->enable_gpio); + return ret; + } + + ti_sn_bridge_parse_lanes(pdata, client->dev.of_node); + + ret = ti_sn_bridge_parse_regulators(pdata); + if (ret) { + DRM_ERROR("failed to parse regulators\n"); + return ret; + } + + pdata->refclk = devm_clk_get(pdata->dev, "refclk"); + if (IS_ERR(pdata->refclk)) { + ret = PTR_ERR(pdata->refclk); + if (ret == -EPROBE_DEFER) + return ret; + DRM_DEBUG_KMS("refclk not found\n"); + pdata->refclk = NULL; + } + + ret = ti_sn_bridge_parse_dsi_host(pdata); + if (ret) + return ret; + + pm_runtime_enable(pdata->dev); + + ret = ti_sn_setup_gpio_controller(pdata); + if (ret) { + pm_runtime_disable(pdata->dev); + return ret; + } + + i2c_set_clientdata(client, pdata); + + pdata->aux.name = "ti-sn65dsi86-aux"; + pdata->aux.dev = pdata->dev; + pdata->aux.transfer = ti_sn_aux_transfer; + drm_dp_aux_register(&pdata->aux); + + pdata->bridge.funcs = &ti_sn_bridge_funcs; + pdata->bridge.of_node = client->dev.of_node; + + drm_bridge_add(&pdata->bridge); + + ti_sn_debugfs_init(pdata); + + return 0; +} + +static int ti_sn_bridge_remove(struct i2c_client *client) +{ + struct ti_sn_bridge *pdata = i2c_get_clientdata(client); + + if (!pdata) + return -EINVAL; + + ti_sn_debugfs_remove(pdata); + + of_node_put(pdata->host_node); + + pm_runtime_disable(pdata->dev); + + if (pdata->dsi) { + mipi_dsi_detach(pdata->dsi); + mipi_dsi_device_unregister(pdata->dsi); + } + + drm_bridge_remove(&pdata->bridge); + + return 0; +} + +static struct i2c_device_id ti_sn_bridge_id[] = { + { "ti,sn65dsi86", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ti_sn_bridge_id); + +static const struct of_device_id ti_sn_bridge_match_table[] = { + {.compatible = "ti,sn65dsi86"}, + {}, +}; +MODULE_DEVICE_TABLE(of, ti_sn_bridge_match_table); + +static struct i2c_driver ti_sn_bridge_driver = { + .driver = { + .name = "ti_sn65dsi86", + .of_match_table = ti_sn_bridge_match_table, + .pm = &ti_sn_bridge_pm_ops, + }, + .probe = ti_sn_bridge_probe, + .remove = ti_sn_bridge_remove, + .id_table = ti_sn_bridge_id, +}; +module_i2c_driver(ti_sn_bridge_driver); + +MODULE_AUTHOR("Sandeep Panda <spanda@codeaurora.org>"); +MODULE_DESCRIPTION("sn65dsi86 DSI to eDP bridge driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c new file mode 100644 index 000000000..ba3fa2a9b --- /dev/null +++ b/drivers/gpu/drm/bridge/ti-tfp410.c @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2016 Texas Instruments + * Author: Jyri Sarha <jsarha@ti.com> + */ + +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#define HOTPLUG_DEBOUNCE_MS 1100 + +struct tfp410 { + struct drm_bridge bridge; + struct drm_connector connector; + + u32 bus_format; + struct delayed_work hpd_work; + struct gpio_desc *powerdown; + + struct drm_bridge_timings timings; + struct drm_bridge *next_bridge; + + struct device *dev; +}; + +static inline struct tfp410 * +drm_bridge_to_tfp410(struct drm_bridge *bridge) +{ + return container_of(bridge, struct tfp410, bridge); +} + +static inline struct tfp410 * +drm_connector_to_tfp410(struct drm_connector *connector) +{ + return container_of(connector, struct tfp410, connector); +} + +static int tfp410_get_modes(struct drm_connector *connector) +{ + struct tfp410 *dvi = drm_connector_to_tfp410(connector); + struct edid *edid; + int ret; + + if (dvi->next_bridge->ops & DRM_BRIDGE_OP_EDID) { + edid = drm_bridge_get_edid(dvi->next_bridge, connector); + if (!edid) + DRM_INFO("EDID read failed. Fallback to standard modes\n"); + } else { + edid = NULL; + } + + if (!edid) { + /* + * No EDID, fallback on the XGA standard modes and prefer a mode + * pretty much anything can handle. + */ + ret = drm_add_modes_noedid(connector, 1920, 1200); + drm_set_preferred_mode(connector, 1024, 768); + return ret; + } + + drm_connector_update_edid_property(connector, edid); + + ret = drm_add_edid_modes(connector, edid); + + kfree(edid); + + return ret; +} + +static const struct drm_connector_helper_funcs tfp410_con_helper_funcs = { + .get_modes = tfp410_get_modes, +}; + +static enum drm_connector_status +tfp410_connector_detect(struct drm_connector *connector, bool force) +{ + struct tfp410 *dvi = drm_connector_to_tfp410(connector); + + return drm_bridge_detect(dvi->next_bridge); +} + +static const struct drm_connector_funcs tfp410_con_funcs = { + .detect = tfp410_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static void tfp410_hpd_work_func(struct work_struct *work) +{ + struct tfp410 *dvi; + + dvi = container_of(work, struct tfp410, hpd_work.work); + + if (dvi->bridge.dev) + drm_helper_hpd_irq_event(dvi->bridge.dev); +} + +static void tfp410_hpd_callback(void *arg, enum drm_connector_status status) +{ + struct tfp410 *dvi = arg; + + mod_delayed_work(system_wq, &dvi->hpd_work, + msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); +} + +static int tfp410_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); + int ret; + + ret = drm_bridge_attach(bridge->encoder, dvi->next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret < 0) + return ret; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return 0; + + if (!bridge->encoder) { + dev_err(dvi->dev, "Missing encoder\n"); + return -ENODEV; + } + + if (dvi->next_bridge->ops & DRM_BRIDGE_OP_DETECT) + dvi->connector.polled = DRM_CONNECTOR_POLL_HPD; + else + dvi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + + if (dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) { + INIT_DELAYED_WORK(&dvi->hpd_work, tfp410_hpd_work_func); + drm_bridge_hpd_enable(dvi->next_bridge, tfp410_hpd_callback, + dvi); + } + + drm_connector_helper_add(&dvi->connector, + &tfp410_con_helper_funcs); + ret = drm_connector_init_with_ddc(bridge->dev, &dvi->connector, + &tfp410_con_funcs, + dvi->next_bridge->type, + dvi->next_bridge->ddc); + if (ret) { + dev_err(dvi->dev, "drm_connector_init_with_ddc() failed: %d\n", + ret); + return ret; + } + + drm_display_info_set_bus_formats(&dvi->connector.display_info, + &dvi->bus_format, 1); + + drm_connector_attach_encoder(&dvi->connector, bridge->encoder); + + return 0; +} + +static void tfp410_detach(struct drm_bridge *bridge) +{ + struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); + + if (dvi->connector.dev && dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) { + drm_bridge_hpd_disable(dvi->next_bridge); + cancel_delayed_work_sync(&dvi->hpd_work); + } +} + +static void tfp410_enable(struct drm_bridge *bridge) +{ + struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); + + gpiod_set_value_cansleep(dvi->powerdown, 0); +} + +static void tfp410_disable(struct drm_bridge *bridge) +{ + struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); + + gpiod_set_value_cansleep(dvi->powerdown, 1); +} + +static enum drm_mode_status tfp410_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->clock < 25000) + return MODE_CLOCK_LOW; + + if (mode->clock > 165000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static const struct drm_bridge_funcs tfp410_bridge_funcs = { + .attach = tfp410_attach, + .detach = tfp410_detach, + .enable = tfp410_enable, + .disable = tfp410_disable, + .mode_valid = tfp410_mode_valid, +}; + +static const struct drm_bridge_timings tfp410_default_timings = { + .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE + | DRM_BUS_FLAG_DE_HIGH, + .setup_time_ps = 1200, + .hold_time_ps = 1300, +}; + +static int tfp410_parse_timings(struct tfp410 *dvi, bool i2c) +{ + struct drm_bridge_timings *timings = &dvi->timings; + struct device_node *ep; + u32 pclk_sample = 0; + u32 bus_width = 24; + u32 deskew = 0; + + /* Start with defaults. */ + *timings = tfp410_default_timings; + + if (i2c) + /* + * In I2C mode timings are configured through the I2C interface. + * As the driver doesn't support I2C configuration yet, we just + * go with the defaults (BSEL=1, DSEL=1, DKEN=0, EDGE=1). + */ + return 0; + + /* + * In non-I2C mode, timings are configured through the BSEL, DSEL, DKEN + * and EDGE pins. They are specified in DT through endpoint properties + * and vendor-specific properties. + */ + ep = of_graph_get_endpoint_by_regs(dvi->dev->of_node, 0, 0); + if (!ep) + return -EINVAL; + + /* Get the sampling edge from the endpoint. */ + of_property_read_u32(ep, "pclk-sample", &pclk_sample); + of_property_read_u32(ep, "bus-width", &bus_width); + of_node_put(ep); + + timings->input_bus_flags = DRM_BUS_FLAG_DE_HIGH; + + switch (pclk_sample) { + case 0: + timings->input_bus_flags |= DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE + | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE; + break; + case 1: + timings->input_bus_flags |= DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE + | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE; + break; + default: + return -EINVAL; + } + + switch (bus_width) { + case 12: + dvi->bus_format = MEDIA_BUS_FMT_RGB888_2X12_LE; + break; + case 24: + dvi->bus_format = MEDIA_BUS_FMT_RGB888_1X24; + break; + default: + return -EINVAL; + } + + /* Get the setup and hold time from vendor-specific properties. */ + of_property_read_u32(dvi->dev->of_node, "ti,deskew", &deskew); + if (deskew > 7) + return -EINVAL; + + timings->setup_time_ps = 1200 - 350 * ((s32)deskew - 4); + timings->hold_time_ps = max(0, 1300 + 350 * ((s32)deskew - 4)); + + return 0; +} + +static int tfp410_init(struct device *dev, bool i2c) +{ + struct device_node *node; + struct tfp410 *dvi; + int ret; + + if (!dev->of_node) { + dev_err(dev, "device-tree data is missing\n"); + return -ENXIO; + } + + dvi = devm_kzalloc(dev, sizeof(*dvi), GFP_KERNEL); + if (!dvi) + return -ENOMEM; + + dvi->dev = dev; + dev_set_drvdata(dev, dvi); + + dvi->bridge.funcs = &tfp410_bridge_funcs; + dvi->bridge.of_node = dev->of_node; + dvi->bridge.timings = &dvi->timings; + dvi->bridge.type = DRM_MODE_CONNECTOR_DVID; + + ret = tfp410_parse_timings(dvi, i2c); + if (ret) + return ret; + + /* Get the next bridge, connected to port@1. */ + node = of_graph_get_remote_node(dev->of_node, 1, -1); + if (!node) + return -ENODEV; + + dvi->next_bridge = of_drm_find_bridge(node); + of_node_put(node); + + if (!dvi->next_bridge) + return -EPROBE_DEFER; + + /* Get the powerdown GPIO. */ + dvi->powerdown = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(dvi->powerdown)) { + dev_err(dev, "failed to parse powerdown gpio\n"); + return PTR_ERR(dvi->powerdown); + } + + /* Register the DRM bridge. */ + drm_bridge_add(&dvi->bridge); + + return 0; +} + +static int tfp410_fini(struct device *dev) +{ + struct tfp410 *dvi = dev_get_drvdata(dev); + + drm_bridge_remove(&dvi->bridge); + + return 0; +} + +static int tfp410_probe(struct platform_device *pdev) +{ + return tfp410_init(&pdev->dev, false); +} + +static int tfp410_remove(struct platform_device *pdev) +{ + return tfp410_fini(&pdev->dev); +} + +static const struct of_device_id tfp410_match[] = { + { .compatible = "ti,tfp410" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tfp410_match); + +static struct platform_driver tfp410_platform_driver = { + .probe = tfp410_probe, + .remove = tfp410_remove, + .driver = { + .name = "tfp410-bridge", + .of_match_table = tfp410_match, + }, +}; + +#if IS_ENABLED(CONFIG_I2C) +/* There is currently no i2c functionality. */ +static int tfp410_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int reg; + + if (!client->dev.of_node || + of_property_read_u32(client->dev.of_node, "reg", ®)) { + dev_err(&client->dev, + "Can't get i2c reg property from device-tree\n"); + return -ENXIO; + } + + return tfp410_init(&client->dev, true); +} + +static int tfp410_i2c_remove(struct i2c_client *client) +{ + return tfp410_fini(&client->dev); +} + +static const struct i2c_device_id tfp410_i2c_ids[] = { + { "tfp410", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tfp410_i2c_ids); + +static struct i2c_driver tfp410_i2c_driver = { + .driver = { + .name = "tfp410", + .of_match_table = of_match_ptr(tfp410_match), + }, + .id_table = tfp410_i2c_ids, + .probe = tfp410_i2c_probe, + .remove = tfp410_i2c_remove, +}; +#endif /* IS_ENABLED(CONFIG_I2C) */ + +static struct { + uint i2c:1; + uint platform:1; +} tfp410_registered_driver; + +static int __init tfp410_module_init(void) +{ + int ret; + +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&tfp410_i2c_driver); + if (ret) + pr_err("%s: registering i2c driver failed: %d", + __func__, ret); + else + tfp410_registered_driver.i2c = 1; +#endif + + ret = platform_driver_register(&tfp410_platform_driver); + if (ret) + pr_err("%s: registering platform driver failed: %d", + __func__, ret); + else + tfp410_registered_driver.platform = 1; + + if (tfp410_registered_driver.i2c || + tfp410_registered_driver.platform) + return 0; + + return ret; +} +module_init(tfp410_module_init); + +static void __exit tfp410_module_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + if (tfp410_registered_driver.i2c) + i2c_del_driver(&tfp410_i2c_driver); +#endif + if (tfp410_registered_driver.platform) + platform_driver_unregister(&tfp410_platform_driver); +} +module_exit(tfp410_module_exit); + +MODULE_AUTHOR("Jyri Sarha <jsarha@ti.com>"); +MODULE_DESCRIPTION("TI TFP410 DVI bridge driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/ti-tpd12s015.c b/drivers/gpu/drm/bridge/ti-tpd12s015.c new file mode 100644 index 000000000..b588fea12 --- /dev/null +++ b/drivers/gpu/drm/bridge/ti-tpd12s015.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TPD12S015 HDMI ESD protection & level shifter chip driver + * + * Copyright (C) 2019 Texas Instruments Incorporated + * + * Based on the omapdrm-specific encoder-opa362 driver + * + * Copyright (C) 2013 Texas Instruments Incorporated + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> + +#include <drm/drm_bridge.h> + +struct tpd12s015_device { + struct drm_bridge bridge; + + struct gpio_desc *ct_cp_hpd_gpio; + struct gpio_desc *ls_oe_gpio; + struct gpio_desc *hpd_gpio; + int hpd_irq; + + struct drm_bridge *next_bridge; +}; + +static inline struct tpd12s015_device *to_tpd12s015(struct drm_bridge *bridge) +{ + return container_of(bridge, struct tpd12s015_device, bridge); +} + +static int tpd12s015_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct tpd12s015_device *tpd = to_tpd12s015(bridge); + int ret; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) + return -EINVAL; + + ret = drm_bridge_attach(bridge->encoder, tpd->next_bridge, + bridge, flags); + if (ret < 0) + return ret; + + gpiod_set_value_cansleep(tpd->ls_oe_gpio, 1); + + /* DC-DC converter needs at max 300us to get to 90% of 5V. */ + usleep_range(300, 1000); + + return 0; +} + +static void tpd12s015_detach(struct drm_bridge *bridge) +{ + struct tpd12s015_device *tpd = to_tpd12s015(bridge); + + gpiod_set_value_cansleep(tpd->ls_oe_gpio, 0); +} + +static enum drm_connector_status tpd12s015_detect(struct drm_bridge *bridge) +{ + struct tpd12s015_device *tpd = to_tpd12s015(bridge); + + if (gpiod_get_value_cansleep(tpd->hpd_gpio)) + return connector_status_connected; + else + return connector_status_disconnected; +} + +static void tpd12s015_hpd_enable(struct drm_bridge *bridge) +{ + struct tpd12s015_device *tpd = to_tpd12s015(bridge); + + gpiod_set_value_cansleep(tpd->ct_cp_hpd_gpio, 1); +} + +static void tpd12s015_hpd_disable(struct drm_bridge *bridge) +{ + struct tpd12s015_device *tpd = to_tpd12s015(bridge); + + gpiod_set_value_cansleep(tpd->ct_cp_hpd_gpio, 0); +} + +static const struct drm_bridge_funcs tpd12s015_bridge_funcs = { + .attach = tpd12s015_attach, + .detach = tpd12s015_detach, + .detect = tpd12s015_detect, + .hpd_enable = tpd12s015_hpd_enable, + .hpd_disable = tpd12s015_hpd_disable, +}; + +static irqreturn_t tpd12s015_hpd_isr(int irq, void *data) +{ + struct tpd12s015_device *tpd = data; + struct drm_bridge *bridge = &tpd->bridge; + + drm_bridge_hpd_notify(bridge, tpd12s015_detect(bridge)); + + return IRQ_HANDLED; +} + +static int tpd12s015_probe(struct platform_device *pdev) +{ + struct tpd12s015_device *tpd; + struct device_node *node; + struct gpio_desc *gpio; + int ret; + + tpd = devm_kzalloc(&pdev->dev, sizeof(*tpd), GFP_KERNEL); + if (!tpd) + return -ENOMEM; + + platform_set_drvdata(pdev, tpd); + + tpd->bridge.funcs = &tpd12s015_bridge_funcs; + tpd->bridge.of_node = pdev->dev.of_node; + tpd->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + tpd->bridge.ops = DRM_BRIDGE_OP_DETECT; + + /* Get the next bridge, connected to port@1. */ + node = of_graph_get_remote_node(pdev->dev.of_node, 1, -1); + if (!node) + return -ENODEV; + + tpd->next_bridge = of_drm_find_bridge(node); + of_node_put(node); + + if (!tpd->next_bridge) + return -EPROBE_DEFER; + + /* Get the control and HPD GPIOs. */ + gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 0, + GPIOD_OUT_LOW); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + + tpd->ct_cp_hpd_gpio = gpio; + + gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 1, + GPIOD_OUT_LOW); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + + tpd->ls_oe_gpio = gpio; + + gpio = devm_gpiod_get_index(&pdev->dev, NULL, 2, GPIOD_IN); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + + tpd->hpd_gpio = gpio; + + /* Register the IRQ if the HPD GPIO is IRQ-capable. */ + tpd->hpd_irq = gpiod_to_irq(tpd->hpd_gpio); + if (tpd->hpd_irq >= 0) { + ret = devm_request_threaded_irq(&pdev->dev, tpd->hpd_irq, NULL, + tpd12s015_hpd_isr, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "tpd12s015 hpd", tpd); + if (ret) + return ret; + + tpd->bridge.ops |= DRM_BRIDGE_OP_HPD; + } + + /* Register the DRM bridge. */ + drm_bridge_add(&tpd->bridge); + + return 0; +} + +static int tpd12s015_remove(struct platform_device *pdev) +{ + struct tpd12s015_device *tpd = platform_get_drvdata(pdev); + + drm_bridge_remove(&tpd->bridge); + + return 0; +} + +static const struct of_device_id tpd12s015_of_match[] = { + { .compatible = "ti,tpd12s015", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, tpd12s015_of_match); + +static struct platform_driver tpd12s015_driver = { + .probe = tpd12s015_probe, + .remove = tpd12s015_remove, + .driver = { + .name = "tpd12s015", + .of_match_table = tpd12s015_of_match, + }, +}; + +module_platform_driver(tpd12s015_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("TPD12S015 HDMI level shifter and ESD protection driver"); +MODULE_LICENSE("GPL"); |