linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/9] Add support for RK3588 DisplayPort Controller
@ 2025-04-03  3:37 Andy Yan
  2025-04-03  3:37 ` [PATCH v3 1/9] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller Andy Yan
                   ` (8 more replies)
  0 siblings, 9 replies; 21+ messages in thread
From: Andy Yan @ 2025-04-03  3:37 UTC (permalink / raw)
  To: dmitry.baryshkov, heiko
  Cc: hjc, mripard, naoki, stephen, cristian.ciocaltea, neil.armstrong,
	Laurent.pinchart, yubing.zhang, krzk+dt, devicetree, dri-devel,
	linux-arm-kernel, linux-kernel, linux-rockchip, robh,
	sebastian.reichel, Andy Yan

From: Andy Yan <andy.yan@rock-chips.com>


There are two DW DPTX based DisplayPort Controller on rk3588 which
are compliant with the DisplayPort Specification Version 1.4 with
the following features:

* DisplayPort 1.4a
* Main Link: 1/2/4 lanes
* Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps
* AUX channel 1Mbps
* Single Stream Transport(SST)
* Multistream Transport (MST)
* Type-C support (alternate mode)
* HDCP 2.2, HDCP 1.3
* Supports up to 8/10 bits per color component
* Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0
* Pixel clock up to 594MHz
* I2S, SPDIF audio interface

The current version of this patch series only supports basic display outputs.
I tested with DP0 in 1080p and 4K@60 YCbCr4:2:0 modes; the ALT/Type-C mode was
tested on Rock 5B, DP1 was tested on Rock 5 ITX by Stephen and Piotr.
HDCP and audio features remain unimplemented.
For RK3588, it's only support SST, while in the upcoming RK3576, it can support
MST output.

This version still has one unresolved issues highlighted by Dmitry during
the last code review(I add TODO for it).
Let's make the driver itself to a usable and good shape first.

Currently, there are three dependencies PATCH[0][1][2], take care if there are someone
want to here a try with this series:

[0]https://lore.kernel.org/linux-rockchip/20250302115257.188774-1-andyshrk@163.com/
[1]https://lore.kernel.org/linux-rockchip/20250312064218.524143-1-andyshrk@163.com/T/#u
[2]https://lore.kernel.org/linux-rockchip/20250312080041.524546-1-andyshrk@163.com/T/#u


Changes in v3:
- Rebase on drm-misc-next
- Switch to common helpers to power up/down dp link
- Only pass parameters to phy that should be set
- Add RA620 into bridge chain.
- Link to v2: https://lore.kernel.org/linux-rockchip/20250312104214.525242-1-andyshrk@163.com/

Changes in v2:
- Fix a character encoding issue
- Fix compile error when build as module
- Add phy init
- Only use one dw_dp_link_train_set
- inline dw_dp_phy_update_vs_emph
- Use dp_sdp
- Check return value of drm_modeset_lock
- Merge code in atomic_pre_enable/mode_fixup to atomic_check
- Return NULL if can't find a supported output format
- Fix max_link_rate from plat_data
- no include uapi path
- switch to drmm_encoder_init
- Sort in alphabetical order
- Link to V1: https://lore.kernel.org/linux-rockchip/20250223113036.74252-1-andyshrk@163.com/

Andy Yan (9):
  dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller
  drm/bridge: synopsys: Add DW DPTX Controller support library
  drm/rockchip: Add RK3588 DPTX output support
  dt-bindings: display: simple-bridge: Add ra620 compatible
  drm/birdge: simple-bridge: Add support for radxa ra620
  arm64: dts: rockchip: Add DP0 for rk3588
  arm64: dts: rockchip: Add DP1 for rk3588
  arm64: dts: rockchip: Enable DisplayPort for rk3588s Cool Pi 4B
  arm64: dts: rockchip: Enable DP2HDMI for ROCK 5 ITX

 .../display/bridge/simple-bridge.yaml         |    1 +
 .../display/rockchip/rockchip,dw-dp.yaml      |  150 ++
 arch/arm64/boot/dts/rockchip/rk3588-base.dtsi |   30 +
 .../arm64/boot/dts/rockchip/rk3588-extra.dtsi |   30 +
 .../boot/dts/rockchip/rk3588-rock-5-itx.dts   |   59 +
 .../boot/dts/rockchip/rk3588s-coolpi-4b.dts   |   37 +
 drivers/gpu/drm/bridge/simple-bridge.c        |    5 +
 drivers/gpu/drm/bridge/synopsys/Kconfig       |    7 +
 drivers/gpu/drm/bridge/synopsys/Makefile      |    1 +
 drivers/gpu/drm/bridge/synopsys/dw-dp.c       | 2053 +++++++++++++++++
 drivers/gpu/drm/rockchip/Kconfig              |    9 +
 drivers/gpu/drm/rockchip/Makefile             |    1 +
 drivers/gpu/drm/rockchip/dw_dp-rockchip.c     |  154 ++
 drivers/gpu/drm/rockchip/rockchip_drm_drv.c   |    1 +
 drivers/gpu/drm/rockchip/rockchip_drm_drv.h   |    1 +
 include/drm/bridge/dw_dp.h                    |   20 +
 16 files changed, 2559 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
 create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c
 create mode 100644 drivers/gpu/drm/rockchip/dw_dp-rockchip.c
 create mode 100644 include/drm/bridge/dw_dp.h

-- 
2.43.0



^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v3 1/9] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller
  2025-04-03  3:37 [PATCH v3 0/9] Add support for RK3588 DisplayPort Controller Andy Yan
@ 2025-04-03  3:37 ` Andy Yan
  2025-04-04 19:36   ` Diederik de Haas
  2025-04-03  3:37 ` [PATCH v3 2/9] drm/bridge: synopsys: Add DW DPTX Controller support library Andy Yan
                   ` (7 subsequent siblings)
  8 siblings, 1 reply; 21+ messages in thread
From: Andy Yan @ 2025-04-03  3:37 UTC (permalink / raw)
  To: dmitry.baryshkov, heiko
  Cc: hjc, mripard, naoki, stephen, cristian.ciocaltea, neil.armstrong,
	Laurent.pinchart, yubing.zhang, krzk+dt, devicetree, dri-devel,
	linux-arm-kernel, linux-kernel, linux-rockchip, robh,
	sebastian.reichel, Andy Yan

From: Andy Yan <andy.yan@rock-chips.com>

The Rockchip RK3588 SoC integrates the Synopsys DesignWare DPTX
controller. And this DPTX controller need share a USBDP PHY with
the USB 3.0 OTG controller during operation.

Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>

---

(no changes since v2)

Changes in v2:
- Link to V1: https://lore.kernel.org/linux-rockchip/20250223113036.74252-1-andyshrk@163.com/
- Fix a character encoding issue

 .../display/rockchip/rockchip,dw-dp.yaml      | 150 ++++++++++++++++++
 1 file changed, 150 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml

diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
new file mode 100644
index 0000000000000..a8a0087179972
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
@@ -0,0 +1,150 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/rockchip/rockchip,dw-dp.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Rockchip DW DisplayPort Transmitter
+
+maintainers:
+  - Andy Yan <andy.yan@rock-chips.com>
+
+description: |
+  The Rockchip RK3588 SoC integrates the Synopsys DesignWare DPTX controller
+  which is compliant with the DisplayPort Specification Version 1.4 with the
+  following features:
+
+  * DisplayPort 1.4a
+  * Main Link: 1/2/4 lanes
+  * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps
+  * AUX channel 1Mbps
+  * Single Stream Transport(SST)
+  * Multistream Transport (MST)
+  * Type-C support (alternate mode)
+  * HDCP 2.2, HDCP 1.3
+  * Supports up to 8/10 bits per color component
+  * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0
+  * Pixel clock up to 594MHz
+  * I2S, SPDIF audio interface
+
+allOf:
+  - $ref: /schemas/sound/dai-common.yaml#
+
+properties:
+  compatible:
+    enum:
+      - rockchip,rk3588-dp
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: Peripheral/APB bus clock
+      - description: DisplayPort AUX clock
+      - description: HDCP clock
+      - description: I2S interface clock
+      - description: SPDIF interfce clock
+
+  clock-names:
+    items:
+      - const: apb
+      - const: aux
+      - const: hdcp
+      - const: i2s
+      - const: spdif
+
+  phys:
+    maxItems: 1
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: Video port for RGB/YUV input.
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: Video port for DP output.
+
+    required:
+      - port@0
+      - port@1
+
+  power-domains:
+    maxItems: 1
+
+  resets:
+    maxItems: 1
+
+  "#sound-dai-cells":
+    const: 0
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - interrupts
+  - phys
+  - ports
+  - resets
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/rockchip,rk3588-cru.h>
+    #include <dt-bindings/phy/phy.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/power/rk3588-power.h>
+    #include <dt-bindings/reset/rockchip,rk3588-cru.h>
+
+    soc {
+      #address-cells = <2>;
+      #size-cells = <2>;
+
+      dp@fde50000 {
+        compatible = "rockchip,rk3588-dp";
+        reg = <0x0 0xfde50000 0x0 0x4000>;
+        interrupts = <GIC_SPI 161 IRQ_TYPE_LEVEL_HIGH 0>;
+        clocks = <&cru PCLK_DP0>, <&cru CLK_AUX16M_0>,
+        <&cru CLK_DP0>, <&cru MCLK_I2S4_8CH_TX>,
+        <&cru MCLK_SPDIF2_DP0>;
+        clock-names = "apb", "aux", "hdcp", "i2s", "spdif";
+        assigned-clocks = <&cru CLK_AUX16M_0>;
+        assigned-clock-rates = <16000000>;
+        resets = <&cru SRST_DP0>;
+        phys = <&usbdp_phy0 PHY_TYPE_DP>;
+        power-domains = <&power RK3588_PD_VO0>;
+        #sound-dai-cells = <0>;
+
+
+        ports {
+          #address-cells = <1>;
+          #size-cells = <0>;
+
+          port@0 {
+            reg = <0>;
+
+            dp0_in_vp2: endpoint {
+              remote-endpoint = <&vp2_out_dp0>;
+            };
+          };
+
+          port@1 {
+            reg = <1>;
+
+            dp0_out_con0: endpoint {
+              remote-endpoint = <&dp_con0_in>;
+            };
+          };
+        };
+      };
+    };
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v3 2/9] drm/bridge: synopsys: Add DW DPTX Controller support library
  2025-04-03  3:37 [PATCH v3 0/9] Add support for RK3588 DisplayPort Controller Andy Yan
  2025-04-03  3:37 ` [PATCH v3 1/9] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller Andy Yan
@ 2025-04-03  3:37 ` Andy Yan
  2025-05-14 12:15   ` Andy Yan
  2025-04-03  3:37 ` [PATCH v3 3/9] drm/rockchip: Add RK3588 DPTX output support Andy Yan
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 21+ messages in thread
From: Andy Yan @ 2025-04-03  3:37 UTC (permalink / raw)
  To: dmitry.baryshkov, heiko
  Cc: hjc, mripard, naoki, stephen, cristian.ciocaltea, neil.armstrong,
	Laurent.pinchart, yubing.zhang, krzk+dt, devicetree, dri-devel,
	linux-arm-kernel, linux-kernel, linux-rockchip, robh,
	sebastian.reichel, Andy Yan

From: Andy Yan <andy.yan@rock-chips.com>

The DW DP TX Controller is compliant with the DisplayPort Specification
Version 1.4 with the following features:

* DisplayPort 1.4a
* Main Link: 1/2/4 lanes
* Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps
* AUX channel 1Mbps
* Single Stream Transport(SST)
* Multistream Transport (MST)
* Type-C support (alternate mode)
* HDCP 2.2, HDCP 1.3
* Supports up to 8/10 bits per color component
* Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0
* Pixel clock up to 594MHz
* I2S, SPDIF audio interface

Add library with common helpers to make it can be shared with
other SoC.

Signed-off-by: Andy Yan <andy.yan@rock-chips.com>

---

Changes in v3:
- Rebase on drm-misc-next
- Switch to common helpers to power up/down dp link
- Only pass parameters to phy that should be set

Changes in v2:
- Fix compile error when build as module
- Add phy init
- Only use one dw_dp_link_train_set
- inline dw_dp_phy_update_vs_emph
- Use dp_sdp
- Check return value of drm_modeset_lock
- Merge code in atomic_pre_enable/mode_fixup to atomic_check
- Return NULL if can't find a supported output format
- Fix max_link_rate from plat_data

 drivers/gpu/drm/bridge/synopsys/Kconfig  |    7 +
 drivers/gpu/drm/bridge/synopsys/Makefile |    1 +
 drivers/gpu/drm/bridge/synopsys/dw-dp.c  | 2053 ++++++++++++++++++++++
 include/drm/bridge/dw_dp.h               |   20 +
 4 files changed, 2081 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c
 create mode 100644 include/drm/bridge/dw_dp.h

diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig
index f3ab2f985f8ca..2c5e532410de9 100644
--- a/drivers/gpu/drm/bridge/synopsys/Kconfig
+++ b/drivers/gpu/drm/bridge/synopsys/Kconfig
@@ -1,4 +1,11 @@
 # SPDX-License-Identifier: GPL-2.0-only
+config DRM_DW_DP
+	tristate
+	select DRM_DISPLAY_HELPER
+	select DRM_DISPLAY_DP_HELPER
+	select DRM_KMS_HELPER
+	select REGMAP_MMIO
+
 config DRM_DW_HDMI
 	tristate
 	select DRM_DISPLAY_HDMI_HELPER
diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile
index 9dc376d220ad7..4dada44029acf 100644
--- a/drivers/gpu/drm/bridge/synopsys/Makefile
+++ b/drivers/gpu/drm/bridge/synopsys/Makefile
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_DRM_DW_DP) += dw-dp.o
 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_GP_AUDIO) += dw-hdmi-gp-audio.o
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
new file mode 100644
index 0000000000000..828b34d676a19
--- /dev/null
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -0,0 +1,2053 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synopsys DesignWare Cores DisplayPort Transmitter Controller
+ *
+ * Copyright (c) 2025 Rockchip Electronics Co., Ltd.
+ *
+ * Author: Andy Yan <andy.yan@rock-chips.com>
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/iopoll.h>
+#include <linux/irq.h>
+#include <linux/media-bus-format.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/phy/phy.h>
+#include <linux/unaligned.h>
+
+#include <drm/bridge/dw_dp.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
+#include <drm/display/drm_dp_helper.h>
+#include <drm/display/drm_hdmi_helper.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_simple_kms_helper.h>
+
+#define DW_DP_VERSION_NUMBER			0x0000
+#define DW_DP_VERSION_TYPE			0x0004
+#define DW_DP_ID					0x0008
+
+#define DW_DP_CONFIG_REG1			0x0100
+#define DW_DP_CONFIG_REG2			0x0104
+#define DW_DP_CONFIG_REG3			0x0108
+
+#define DW_DP_CCTL				0x0200
+#define FORCE_HPD				BIT(4)
+#define DEFAULT_FAST_LINK_TRAIN_EN		BIT(2)
+#define ENHANCE_FRAMING_EN			BIT(1)
+#define SCRAMBLE_DIS				BIT(0)
+#define DW_DP_SOFT_RESET_CTRL			0x0204
+#define VIDEO_RESET				BIT(5)
+#define AUX_RESET				BIT(4)
+#define AUDIO_SAMPLER_RESET			BIT(3)
+#define HDCP_MODULE_RESET			BIT(2)
+#define PHY_SOFT_RESET				BIT(1)
+#define CONTROLLER_RESET			BIT(0)
+
+#define DW_DP_VSAMPLE_CTRL			0x0300
+#define PIXEL_MODE_SELECT			GENMASK(22, 21)
+#define VIDEO_MAPPING				GENMASK(20, 16)
+#define VIDEO_STREAM_ENABLE			BIT(5)
+
+#define DW_DP_VSAMPLE_STUFF_CTRL1		0x0304
+
+#define DW_DP_VSAMPLE_STUFF_CTRL2		0x0308
+
+#define DW_DP_VINPUT_POLARITY_CTRL		0x030c
+#define DE_IN_POLARITY				BIT(2)
+#define HSYNC_IN_POLARITY			BIT(1)
+#define VSYNC_IN_POLARITY			BIT(0)
+
+#define DW_DP_VIDEO_CONFIG1			0x0310
+#define HACTIVE					GENMASK(31, 16)
+#define HBLANK					GENMASK(15, 2)
+#define I_P					BIT(1)
+#define R_V_BLANK_IN_OSC			BIT(0)
+
+#define DW_DP_VIDEO_CONFIG2			0x0314
+#define VBLANK					GENMASK(31, 16)
+#define VACTIVE					GENMASK(15, 0)
+
+#define DW_DP_VIDEO_CONFIG3			0x0318
+#define H_SYNC_WIDTH				GENMASK(31, 16)
+#define H_FRONT_PORCH				GENMASK(15, 0)
+
+#define DW_DP_VIDEO_CONFIG4			0x031c
+#define V_SYNC_WIDTH				GENMASK(31, 16)
+#define V_FRONT_PORCH				GENMASK(15, 0)
+
+#define DW_DP_VIDEO_CONFIG5			0x0320
+#define INIT_THRESHOLD_HI			GENMASK(22, 21)
+#define AVERAGE_BYTES_PER_TU_FRAC		GENMASK(19, 16)
+#define INIT_THRESHOLD				GENMASK(13, 7)
+#define AVERAGE_BYTES_PER_TU			GENMASK(6, 0)
+
+#define DW_DP_VIDEO_MSA1				0x0324
+#define VSTART					GENMASK(31, 16)
+#define HSTART					GENMASK(15, 0)
+
+#define DW_DP_VIDEO_MSA2			0x0328
+#define MISC0					GENMASK(31, 24)
+
+#define DW_DP_VIDEO_MSA3			0x032c
+#define MISC1					GENMASK(31, 24)
+
+#define DW_DP_VIDEO_HBLANK_INTERVAL		0x0330
+#define HBLANK_INTERVAL_EN			BIT(16)
+#define HBLANK_INTERVAL				GENMASK(15, 0)
+
+#define DW_DP_AUD_CONFIG1			0x0400
+#define AUDIO_TIMESTAMP_VERSION_NUM		GENMASK(29, 24)
+#define AUDIO_PACKET_ID				GENMASK(23, 16)
+#define AUDIO_MUTE				BIT(15)
+#define NUM_CHANNELS				GENMASK(14, 12)
+#define HBR_MODE_ENABLE				BIT(10)
+#define AUDIO_DATA_WIDTH			GENMASK(9, 5)
+#define AUDIO_DATA_IN_EN			GENMASK(4, 1)
+#define AUDIO_INF_SELECT			BIT(0)
+
+#define DW_DP_SDP_VERTICAL_CTRL			0x0500
+#define EN_VERTICAL_SDP				BIT(2)
+#define EN_AUDIO_STREAM_SDP			BIT(1)
+#define EN_AUDIO_TIMESTAMP_SDP			BIT(0)
+#define DW_DP_SDP_HORIZONTAL_CTRL		0x0504
+#define EN_HORIZONTAL_SDP			BIT(2)
+#define DW_DP_SDP_STATUS_REGISTER		0x0508
+#define DW_DP_SDP_MANUAL_CTRL			0x050c
+#define DW_DP_SDP_STATUS_EN			0x0510
+
+#define DW_DP_SDP_REGISTER_BANK			0x0600
+#define SDP_REGS				GENMASK(31, 0)
+
+#define DW_DP_PHYIF_CTRL			0x0a00
+#define PHY_WIDTH				BIT(25)
+#define PHY_POWERDOWN				GENMASK(20, 17)
+#define PHY_BUSY				GENMASK(15, 12)
+#define SSC_DIS					BIT(16)
+#define XMIT_ENABLE				GENMASK(11, 8)
+#define PHY_LANES				GENMASK(7, 6)
+#define PHY_RATE				GENMASK(5, 4)
+#define TPS_SEL					GENMASK(3, 0)
+
+#define DW_DP_PHY_TX_EQ				0x0a04
+#define DW_DP_CUSTOMPAT0			0x0a08
+#define DW_DP_CUSTOMPAT1			0x0a0c
+#define DW_DP_CUSTOMPAT2			0x0a10
+#define DW_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET	0x0a14
+#define DW_DP_PHYIF_PWRDOWN_CTRL		0x0a18
+
+#define DW_DP_AUX_CMD				0x0b00
+#define AUX_CMD_TYPE				GENMASK(31, 28)
+#define AUX_ADDR				GENMASK(27, 8)
+#define I2C_ADDR_ONLY				BIT(4)
+#define AUX_LEN_REQ				GENMASK(3, 0)
+
+#define DW_DP_AUX_STATUS			0x0b04
+#define AUX_TIMEOUT				BIT(17)
+#define AUX_BYTES_READ				GENMASK(23, 19)
+#define AUX_STATUS				GENMASK(7, 4)
+
+#define DW_DP_AUX_DATA0				0x0b08
+#define DW_DP_AUX_DATA1				0x0b0c
+#define DW_DP_AUX_DATA2				0x0b10
+#define DW_DP_AUX_DATA3				0x0b14
+
+#define DW_DP_GENERAL_INTERRUPT			0x0d00
+#define VIDEO_FIFO_OVERFLOW_STREAM0		BIT(6)
+#define AUDIO_FIFO_OVERFLOW_STREAM0		BIT(5)
+#define SDP_EVENT_STREAM0			BIT(4)
+#define AUX_CMD_INVALID				BIT(3)
+#define HDCP_EVENT				BIT(2)
+#define AUX_REPLY_EVENT				BIT(1)
+#define HPD_EVENT				BIT(0)
+
+#define DW_DP_GENERAL_INTERRUPT_ENABLE		0x0d04
+#define HDCP_EVENT_EN				BIT(2)
+#define AUX_REPLY_EVENT_EN			BIT(1)
+#define HPD_EVENT_EN				BIT(0)
+
+#define DW_DP_HPD_STATUS			0x0d08
+#define HPD_STATE				GENMASK(11, 9)
+#define HPD_STATUS				BIT(8)
+#define HPD_HOT_UNPLUG				BIT(2)
+#define HPD_HOT_PLUG				BIT(1)
+#define HPD_IRQ					BIT(0)
+
+#define DW_DP_HPD_INTERRUPT_ENABLE		0x0d0c
+#define HPD_UNPLUG_ERR_EN			BIT(3)
+#define HPD_UNPLUG_EN				BIT(2)
+#define HPD_PLUG_EN				BIT(1)
+#define HPD_IRQ_EN				BIT(0)
+
+#define DW_DP_HDCP_CFG				0x0e00
+#define DPCD12PLUS				BIT(7)
+#define CP_IRQ					BIT(6)
+#define BYPENCRYPTION				BIT(5)
+#define HDCP_LOCK				BIT(4)
+#define ENCRYPTIONDISABLE			BIT(3)
+#define ENABLE_HDCP_13				BIT(2)
+#define ENABLE_HDCP				BIT(1)
+
+#define DW_DP_HDCP_OBS				0x0e04
+#define HDCP22_RE_AUTHENTICATION_REQ		BIT(31)
+#define HDCP22_AUTHENTICATION_FAILED		BIT(30)
+#define HDCP22_AUTHENTICATION_SUCCESS		BIT(29)
+#define HDCP22_CAPABLE_SINK			BIT(28)
+#define HDCP22_SINK_CAP_CHECK_COMPLETE		BIT(27)
+#define HDCP22_STATE				GENMASK(26, 24)
+#define HDCP22_BOOTED				BIT(23)
+#define HDCP13_BSTATUS				GENMASK(22, 19)
+#define REPEATER				BIT(18)
+#define HDCP_CAPABLE				BIT(17)
+#define STATEE					GENMASK(16, 14)
+#define STATEOEG				GENMASK(13, 11)
+#define STATER					GENMASK(10, 8)
+#define STATEA					GENMASK(7, 4)
+#define SUBSTATEA				GENMASK(3, 1)
+#define HDCPENGAGED				BIT(0)
+
+#define DW_DP_HDCP_APIINTCLR			0x0e08
+#define DW_DP_HDCP_APIINTSTAT			0x0e0c
+#define DW_DP_HDCP_APIINTMSK			0x0e10
+#define HDCP22_GPIOINT				BIT(8)
+#define HDCP_ENGAGED				BIT(7)
+#define HDCP_FAILED				BIT(6)
+#define KSVSHA1CALCDONEINT			BIT(5)
+#define AUXRESPNACK7TIMES			BIT(4)
+#define AUXRESPTIMEOUT				BIT(3)
+#define AUXRESPDEFER7TIMES			BIT(2)
+#define KSVACCESSINT				BIT(0)
+
+#define DW_DP_HDCP_KSVMEMCTRL			0x0e18
+#define KSVSHA1STATUS				BIT(4)
+#define KSVMEMACCESS				BIT(1)
+#define KSVMEMREQUEST				BIT(0)
+
+#define DW_DP_HDCP_REG_BKSV0			0x3600
+#define DW_DP_HDCP_REG_BKSV1			0x3604
+#define DW_DP_HDCP_REG_ANCONF			0x3608
+#define AN_BYPASS				BIT(0)
+
+#define DW_DP_HDCP_REG_AN0			0x360c
+#define DW_DP_HDCP_REG_AN1			0x3610
+#define DW_DP_HDCP_REG_RMLCTL			0x3614
+#define ODPK_DECRYPT_ENABLE			BIT(0)
+
+#define DW_DP_HDCP_REG_RMLSTS			0x3618
+#define IDPK_WR_OK_STS				BIT(6)
+#define	IDPK_DATA_INDEX				GENMASK(5, 0)
+#define DW_DP_HDCP_REG_SEED			0x361c
+#define DW_DP_HDCP_REG_DPK0			0x3620
+#define DW_DP_HDCP_REG_DPK1			0x3624
+#define DW_DP_HDCP22_GPIOSTS			0x3628
+#define DW_DP_HDCP22_GPIOCHNGSTS		0x362c
+#define DW_DP_HDCP_REG_DPK_CRC			0x3630
+
+#define DW_DP_MAX_REGISTER			DW_DP_HDCP_REG_DPK_CRC
+
+#define SDP_REG_BANK_SIZE			16
+
+struct dw_dp_link_caps {
+	bool enhanced_framing;
+	bool tps3_supported;
+	bool tps4_supported;
+	bool fast_training;
+	bool channel_coding;
+	bool ssc;
+};
+
+struct dw_dp_link_train_set {
+	unsigned int voltage_swing[4];
+	unsigned int pre_emphasis[4];
+	bool voltage_max_reached[4];
+	bool pre_max_reached[4];
+};
+
+struct dw_dp_link_train {
+	struct dw_dp_link_train_set adjust;
+	bool clock_recovered;
+	bool channel_equalized;
+};
+
+struct dw_dp_link {
+	u8 dpcd[DP_RECEIVER_CAP_SIZE];
+	unsigned char revision;
+	unsigned int rate;
+	unsigned int lanes;
+	u8 sink_count;
+	u8 vsc_sdp_supported;
+	struct dw_dp_link_caps caps;
+	struct dw_dp_link_train train;
+	struct drm_dp_desc desc;
+};
+
+struct dw_dp_video {
+	struct drm_display_mode mode;
+	u8 video_mapping;
+	u8 pixel_mode;
+	u8 color_format;
+	u8 bpc;
+	u8 bpp;
+};
+
+struct dw_dp_sdp {
+	struct dp_sdp base;
+	unsigned long flags;
+};
+
+struct dw_dp_hotplug {
+	bool long_hpd;
+};
+
+struct dw_dp {
+	struct drm_bridge bridge;
+	struct device *dev;
+	struct regmap *regmap;
+	struct phy *phy;
+	struct clk *apb_clk;
+	struct clk *aux_clk;
+	struct clk *i2s_clk;
+	struct clk *spdif_clk;
+	struct clk *hdcp_clk;
+	struct reset_control *rstc;
+	struct completion complete;
+	int irq;
+	struct work_struct hpd_work;
+	struct dw_dp_hotplug hotplug;
+	struct mutex irq_lock;
+
+	struct drm_dp_aux aux;
+
+	struct dw_dp_link link;
+	struct dw_dp_video video;
+	struct dw_dp_plat_data plat_data;
+
+	DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE);
+};
+
+enum {
+	DW_DP_RGB_6BIT,
+	DW_DP_RGB_8BIT,
+	DW_DP_RGB_10BIT,
+	DW_DP_RGB_12BIT,
+	DW_DP_RGB_16BIT,
+	DW_DP_YCBCR444_8BIT,
+	DW_DP_YCBCR444_10BIT,
+	DW_DP_YCBCR444_12BIT,
+	DW_DP_YCBCR444_16BIT,
+	DW_DP_YCBCR422_8BIT,
+	DW_DP_YCBCR422_10BIT,
+	DW_DP_YCBCR422_12BIT,
+	DW_DP_YCBCR422_16BIT,
+	DW_DP_YCBCR420_8BIT,
+	DW_DP_YCBCR420_10BIT,
+	DW_DP_YCBCR420_12BIT,
+	DW_DP_YCBCR420_16BIT,
+};
+
+enum {
+	DW_DP_MP_SINGLE_PIXEL,
+	DW_DP_MP_DUAL_PIXEL,
+	DW_DP_MP_QUAD_PIXEL,
+};
+
+enum {
+	DW_DP_SDP_VERTICAL_INTERVAL = BIT(0),
+	DW_DP_SDP_HORIZONTAL_INTERVAL = BIT(1),
+};
+
+enum {
+	DW_DP_HPD_STATE_IDLE,
+	DW_DP_HPD_STATE_UNPLUG,
+	DP_DP_HPD_STATE_TIMEOUT = 4,
+	DW_DP_HPD_STATE_PLUG = 7
+};
+
+enum {
+	DW_DP_PHY_PATTERN_NONE,
+	DW_DP_PHY_PATTERN_TPS_1,
+	DW_DP_PHY_PATTERN_TPS_2,
+	DW_DP_PHY_PATTERN_TPS_3,
+	DW_DP_PHY_PATTERN_TPS_4,
+	DW_DP_PHY_PATTERN_SERM,
+	DW_DP_PHY_PATTERN_PBRS7,
+	DW_DP_PHY_PATTERN_CUSTOM_80BIT,
+	DW_DP_PHY_PATTERN_CP2520_1,
+	DW_DP_PHY_PATTERN_CP2520_2,
+};
+
+struct dw_dp_output_format {
+	u32 bus_format;
+	u32 color_format;
+	u8 video_mapping;
+	u8 bpc;
+	u8 bpp;
+};
+
+static const struct dw_dp_output_format dw_dp_output_formats[] = {
+	{ MEDIA_BUS_FMT_RGB101010_1X30, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_10BIT, 10, 30 },
+	{ MEDIA_BUS_FMT_RGB888_1X24, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_8BIT, 8, 24 },
+	{ MEDIA_BUS_FMT_YUV10_1X30, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_10BIT, 10, 30 },
+	{ MEDIA_BUS_FMT_YUV8_1X24, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_8BIT, 8, 24},
+	{ MEDIA_BUS_FMT_YUYV10_1X20, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_10BIT, 10, 20 },
+	{ MEDIA_BUS_FMT_YUYV8_1X16, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_8BIT, 8, 16 },
+	{ MEDIA_BUS_FMT_UYYVYY10_0_5X30, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_10BIT, 10, 15 },
+	{ MEDIA_BUS_FMT_UYYVYY8_0_5X24, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_8BIT, 8, 12 },
+	{ MEDIA_BUS_FMT_RGB666_1X24_CPADHI, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_6BIT, 6, 18 },
+};
+
+static const struct dw_dp_output_format *dw_dp_get_output_format(u32 bus_format)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++)
+		if (dw_dp_output_formats[i].bus_format == bus_format)
+			return &dw_dp_output_formats[i];
+
+	return NULL;
+}
+
+static inline struct dw_dp *bridge_to_dp(struct drm_bridge *b)
+{
+	return container_of(b, struct dw_dp, bridge);
+}
+
+static inline void dw_dp_phy_set_pattern(struct dw_dp *dp, u32 pattern)
+{
+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, TPS_SEL,
+			   FIELD_PREP(TPS_SEL, pattern));
+}
+
+static void dw_dp_phy_xmit_enable(struct dw_dp *dp, u32 lanes)
+{
+	u32 xmit_enable;
+
+	switch (lanes) {
+	case 4:
+	case 2:
+	case 1:
+		xmit_enable = GENMASK(lanes - 1, 0);
+		break;
+	case 0:
+	default:
+		xmit_enable = 0;
+		break;
+	}
+
+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, XMIT_ENABLE,
+			   FIELD_PREP(XMIT_ENABLE, xmit_enable));
+}
+
+static bool dw_dp_bandwidth_ok(struct dw_dp *dp,
+			       const struct drm_display_mode *mode, u32 bpp,
+			       unsigned int lanes, unsigned int rate)
+{
+	u32 max_bw, req_bw;
+
+	req_bw = mode->clock * bpp / 8;
+	max_bw = lanes * rate;
+	if (req_bw > max_bw)
+		return false;
+
+	return true;
+}
+
+static bool dw_dp_hpd_detect(struct dw_dp *dp)
+{
+	u32 value;
+
+	regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value);
+
+	return FIELD_GET(HPD_STATE, value) == DW_DP_HPD_STATE_PLUG;
+}
+
+static void dw_dp_link_caps_reset(struct dw_dp_link_caps *caps)
+{
+	caps->enhanced_framing = false;
+	caps->tps3_supported = false;
+	caps->tps4_supported = false;
+	caps->fast_training = false;
+	caps->channel_coding = false;
+}
+
+/* TODO: pull these two functions to DRM DP helpers */
+static void dw_dp_link_reset(struct dw_dp_link *link)
+{
+	link->vsc_sdp_supported = 0;
+	link->sink_count = 0;
+	link->revision = 0;
+	link->rate = 0;
+	link->lanes = 0;
+
+	dw_dp_link_caps_reset(&link->caps);
+	memset(link->dpcd, 0, sizeof(link->dpcd));
+}
+
+/* TODO: switch to drm_dp_read_sink_count_cap once we can get connector*/
+static bool dw_dp_has_sink_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
+				 const struct drm_dp_desc *desc)
+{
+	return dpcd[DP_DPCD_REV] >= DP_DPCD_REV_11 &&
+	       dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT &&
+	       !drm_dp_has_quirk(desc, DP_DPCD_QUIRK_NO_SINK_COUNT);
+}
+
+static int dw_dp_link_parse(struct dw_dp *dp)
+{
+	struct dw_dp_link *link = &dp->link;
+	u8 dpcd;
+	int ret;
+
+	dw_dp_link_reset(link);
+
+	ret = drm_dp_read_dpcd_caps(&dp->aux, link->dpcd);
+	if (ret < 0)
+		return ret;
+
+	drm_dp_read_desc(&dp->aux, &link->desc, drm_dp_is_branch(link->dpcd));
+
+	if (dw_dp_has_sink_count(link->dpcd, &link->desc)) {
+		ret = drm_dp_read_sink_count(&dp->aux);
+		if (ret < 0)
+			return ret;
+
+		link->sink_count = ret;
+
+		/* Dongle connected, but no display */
+		if (!link->sink_count)
+			return -ENODEV;
+	}
+
+	ret = drm_dp_dpcd_readb(&dp->aux, DP_DPRX_FEATURE_ENUMERATION_LIST, &dpcd);
+	if (ret < 0)
+		return ret;
+
+	link->vsc_sdp_supported = !!(dpcd & DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED);
+
+	link->revision = link->dpcd[DP_DPCD_REV];
+	link->rate = min_t(u32, min(dp->plat_data.max_link_rate,
+				    dp->phy->attrs.max_link_rate * 100),
+			   drm_dp_max_link_rate(link->dpcd));
+	link->lanes = min_t(u8, phy_get_bus_width(dp->phy),
+			    drm_dp_max_lane_count(link->dpcd));
+
+	link->caps.enhanced_framing = drm_dp_enhanced_frame_cap(link->dpcd);
+	link->caps.tps3_supported = drm_dp_tps3_supported(link->dpcd);
+	link->caps.tps4_supported = drm_dp_tps4_supported(link->dpcd);
+	link->caps.fast_training = drm_dp_fast_training_cap(link->dpcd);
+	link->caps.channel_coding = drm_dp_channel_coding_supported(link->dpcd);
+	link->caps.ssc = !!(link->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5);
+
+	return 0;
+}
+
+static int dw_dp_link_train_update_vs_emph(struct dw_dp *dp)
+{
+	struct dw_dp_link *link = &dp->link;
+	struct dw_dp_link_train_set *train_set = &link->train.adjust;
+	unsigned int lanes = dp->link.lanes;
+	union phy_configure_opts phy_cfg;
+	unsigned int *vs, *pe;
+	int i, ret;
+	u8 buf[4];
+
+	vs = train_set->voltage_swing;
+	pe = train_set->pre_emphasis;
+
+	for (i = 0; i < lanes; i++) {
+		phy_cfg.dp.voltage[i] = vs[i];
+		phy_cfg.dp.pre[i] = pe[i];
+	}
+
+	phy_cfg.dp.set_lanes = false;
+	phy_cfg.dp.set_rate = false;
+	phy_cfg.dp.set_voltages = true;
+
+	ret = phy_configure(dp->phy, &phy_cfg);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < lanes; i++) {
+		buf[i] = (vs[i] << DP_TRAIN_VOLTAGE_SWING_SHIFT) |
+			 (pe[i] << DP_TRAIN_PRE_EMPHASIS_SHIFT);
+		if (train_set->voltage_max_reached[i])
+			buf[i] |= DP_TRAIN_MAX_SWING_REACHED;
+		if (train_set->pre_max_reached[i])
+			buf[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
+	}
+
+	ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, buf, lanes);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int dw_dp_phy_configure(struct dw_dp *dp, unsigned int rate,
+			       unsigned int lanes, bool ssc)
+{
+	union phy_configure_opts phy_cfg;
+	int ret;
+
+	/* Move PHY to P3 */
+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN,
+			   FIELD_PREP(PHY_POWERDOWN, 0x3));
+
+	phy_cfg.dp.lanes = lanes;
+	phy_cfg.dp.link_rate = rate / 100;
+	phy_cfg.dp.ssc = ssc;
+	phy_cfg.dp.set_lanes = true;
+	phy_cfg.dp.set_rate = true;
+	phy_cfg.dp.set_voltages = false;
+	ret = phy_configure(dp->phy, &phy_cfg);
+	if (ret)
+		return ret;
+
+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_LANES,
+			   FIELD_PREP(PHY_LANES, lanes / 2));
+
+	/* Move PHY to P0 */
+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN,
+			   FIELD_PREP(PHY_POWERDOWN, 0x0));
+
+	dw_dp_phy_xmit_enable(dp, lanes);
+
+	return 0;
+}
+
+static int dw_dp_link_configure(struct dw_dp *dp)
+{
+	struct dw_dp_link *link = &dp->link;
+	u8 buf[2];
+	int ret;
+
+	ret = dw_dp_phy_configure(dp, link->rate, link->lanes, link->caps.ssc);
+	if (ret)
+		return ret;
+
+	buf[0] = drm_dp_link_rate_to_bw_code(link->rate);
+	buf[1] = link->lanes;
+
+	if (link->caps.enhanced_framing) {
+		buf[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
+		regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN,
+				   FIELD_PREP(ENHANCE_FRAMING_EN, 1));
+	} else {
+		regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN,
+				   FIELD_PREP(ENHANCE_FRAMING_EN, 0));
+	}
+
+	ret = drm_dp_dpcd_write(&dp->aux, DP_LINK_BW_SET, buf, sizeof(buf));
+	if (ret < 0)
+		return ret;
+
+	buf[0] = link->caps.ssc ? DP_SPREAD_AMP_0_5 : 0;
+	buf[1] = link->caps.channel_coding ? DP_SET_ANSI_8B10B : 0;
+
+	ret = drm_dp_dpcd_write(&dp->aux, DP_DOWNSPREAD_CTRL, buf, sizeof(buf));
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void dw_dp_link_train_init(struct dw_dp_link_train *train)
+{
+	struct dw_dp_link_train_set *adj = &train->adjust;
+	unsigned int i;
+
+	for (i = 0; i < 4; i++) {
+		adj->voltage_swing[i] = 0;
+		adj->pre_emphasis[i] = 0;
+		adj->voltage_max_reached[i] = false;
+		adj->pre_max_reached[i] = false;
+	}
+
+	train->clock_recovered = false;
+	train->channel_equalized = false;
+}
+
+static bool dw_dp_link_train_valid(const struct dw_dp_link_train *train)
+{
+	return train->clock_recovered && train->channel_equalized;
+}
+
+static int dw_dp_link_train_set_pattern(struct dw_dp *dp, u32 pattern)
+{
+	u8 buf = 0;
+	int ret;
+
+	if (pattern && pattern != DP_TRAINING_PATTERN_4) {
+		buf |= DP_LINK_SCRAMBLING_DISABLE;
+
+		regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS,
+				   FIELD_PREP(SCRAMBLE_DIS, 1));
+	} else {
+		regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS,
+				   FIELD_PREP(SCRAMBLE_DIS, 0));
+	}
+
+	switch (pattern) {
+	case DP_TRAINING_PATTERN_DISABLE:
+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_NONE);
+		break;
+	case DP_TRAINING_PATTERN_1:
+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_1);
+		break;
+	case DP_TRAINING_PATTERN_2:
+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_2);
+		break;
+	case DP_TRAINING_PATTERN_3:
+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_3);
+		break;
+	case DP_TRAINING_PATTERN_4:
+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_4);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET,
+				 buf | pattern);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static u8 dw_dp_voltage_max(u8 preemph)
+{
+	switch (preemph & DP_TRAIN_PRE_EMPHASIS_MASK) {
+	case DP_TRAIN_PRE_EMPH_LEVEL_0:
+		return DP_TRAIN_VOLTAGE_SWING_LEVEL_3;
+	case DP_TRAIN_PRE_EMPH_LEVEL_1:
+		return DP_TRAIN_VOLTAGE_SWING_LEVEL_2;
+	case DP_TRAIN_PRE_EMPH_LEVEL_2:
+		return DP_TRAIN_VOLTAGE_SWING_LEVEL_1;
+	case DP_TRAIN_PRE_EMPH_LEVEL_3:
+	default:
+		return DP_TRAIN_VOLTAGE_SWING_LEVEL_0;
+	}
+}
+
+static bool dw_dp_link_get_adjustments(struct dw_dp_link *link,
+				       u8 status[DP_LINK_STATUS_SIZE])
+{
+	struct dw_dp_link_train_set *adj = &link->train.adjust;
+	unsigned int i;
+	bool changed = false;
+	u8 v = 0;
+	u8 p = 0;
+
+	for (i = 0; i < link->lanes; i++) {
+		v = drm_dp_get_adjust_request_voltage(status, i);
+		v >>= DP_TRAIN_VOLTAGE_SWING_SHIFT;
+		p = drm_dp_get_adjust_request_pre_emphasis(status, i);
+		p >>= DP_TRAIN_PRE_EMPHASIS_SHIFT;
+
+		if ((v != adj->voltage_swing[i]) || (p != adj->pre_emphasis[i]))
+			changed = true;
+
+		if (p >=  (DP_TRAIN_PRE_EMPH_LEVEL_3 >> DP_TRAIN_PRE_EMPHASIS_SHIFT)) {
+			adj->pre_emphasis[i] = DP_TRAIN_PRE_EMPH_LEVEL_3 >>
+					       DP_TRAIN_PRE_EMPHASIS_SHIFT;
+			adj->pre_max_reached[i] = true;
+		} else {
+			adj->pre_emphasis[i] = p;
+			adj->pre_max_reached[i] = false;
+		}
+
+		v = min(v, dw_dp_voltage_max(p));
+		if (v >= (DP_TRAIN_VOLTAGE_SWING_LEVEL_3 >> DP_TRAIN_VOLTAGE_SWING_SHIFT)) {
+			adj->voltage_swing[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_3 >>
+						DP_TRAIN_VOLTAGE_SWING_SHIFT;
+			adj->voltage_max_reached[i] = true;
+		} else {
+			adj->voltage_swing[i] = v;
+			adj->voltage_max_reached[i] = false;
+		}
+	}
+
+	return changed;
+}
+
+static int dw_dp_link_clock_recovery(struct dw_dp *dp)
+{
+	struct dw_dp_link *link = &dp->link;
+	u8 status[DP_LINK_STATUS_SIZE];
+	unsigned int tries = 0;
+	int ret;
+	bool adj_changed;
+
+	ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1);
+	if (ret)
+		return ret;
+
+	for (;;) {
+		ret = dw_dp_link_train_update_vs_emph(dp);
+		if (ret)
+			return ret;
+
+		drm_dp_link_train_clock_recovery_delay(&dp->aux, link->dpcd);
+
+		ret = drm_dp_dpcd_read_link_status(&dp->aux, status);
+		if (ret < 0) {
+			dev_err(dp->dev, "failed to read link status: %d\n", ret);
+			return ret;
+		}
+
+		if (drm_dp_clock_recovery_ok(status, link->lanes)) {
+			link->train.clock_recovered = true;
+			break;
+		}
+
+		/*
+		 * According to DP spec 1.4, if current ADJ is the same
+		 * with previous REQ, we need to retry 5 times.
+		 */
+		adj_changed = dw_dp_link_get_adjustments(link, status);
+		if (!adj_changed)
+			tries++;
+		else
+			tries = 0;
+
+		if (tries == 5)
+			break;
+	}
+
+	return 0;
+}
+
+static int dw_dp_link_channel_equalization(struct dw_dp *dp)
+{
+	struct dw_dp_link *link = &dp->link;
+	u8 status[DP_LINK_STATUS_SIZE], pattern;
+	unsigned int tries;
+	int ret;
+
+	if (link->caps.tps4_supported)
+		pattern = DP_TRAINING_PATTERN_4;
+	else if (link->caps.tps3_supported)
+		pattern = DP_TRAINING_PATTERN_3;
+	else
+		pattern = DP_TRAINING_PATTERN_2;
+	ret = dw_dp_link_train_set_pattern(dp, pattern);
+	if (ret)
+		return ret;
+
+	for (tries = 1; tries < 5; tries++) {
+		ret = dw_dp_link_train_update_vs_emph(dp);
+		if (ret)
+			return ret;
+
+		drm_dp_link_train_channel_eq_delay(&dp->aux, link->dpcd);
+
+		ret = drm_dp_dpcd_read_link_status(&dp->aux, status);
+		if (ret < 0)
+			return ret;
+
+		if (!drm_dp_clock_recovery_ok(status, link->lanes)) {
+			dev_err(dp->dev, "clock recovery lost while equalizing channel\n");
+			link->train.clock_recovered = false;
+			break;
+		}
+
+		if (drm_dp_channel_eq_ok(status, link->lanes)) {
+			link->train.channel_equalized = true;
+			break;
+		}
+
+		dw_dp_link_get_adjustments(link, status);
+	}
+
+	return 0;
+}
+
+static int dw_dp_link_downgrade(struct dw_dp *dp)
+{
+	struct dw_dp_link *link = &dp->link;
+	struct dw_dp_video *video = &dp->video;
+
+	switch (link->rate) {
+	case 162000:
+		return -EINVAL;
+	case 270000:
+		link->rate = 162000;
+		break;
+	case 540000:
+		link->rate = 270000;
+		break;
+	case 810000:
+		link->rate = 540000;
+		break;
+	}
+
+	if (!dw_dp_bandwidth_ok(dp, &video->mode, video->bpp, link->lanes,
+				link->rate))
+		return -E2BIG;
+
+	return 0;
+}
+
+static int dw_dp_link_train_full(struct dw_dp *dp)
+{
+	struct dw_dp_link *link = &dp->link;
+	int ret;
+
+retry:
+	dw_dp_link_train_init(&link->train);
+
+	dev_dbg(dp->dev, "full-training link: %u lane%s at %u MHz\n",
+		link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100);
+
+	ret = dw_dp_link_configure(dp);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to configure DP link: %d\n", ret);
+		return ret;
+	}
+
+	ret = dw_dp_link_clock_recovery(dp);
+	if (ret < 0) {
+		dev_err(dp->dev, "clock recovery failed: %d\n", ret);
+		goto out;
+	}
+
+	if (!link->train.clock_recovered) {
+		dev_err(dp->dev, "clock recovery failed, downgrading link\n");
+
+		ret = dw_dp_link_downgrade(dp);
+		if (ret < 0)
+			goto out;
+		else
+			goto retry;
+	}
+
+	dev_dbg(dp->dev, "clock recovery succeeded\n");
+
+	ret = dw_dp_link_channel_equalization(dp);
+	if (ret < 0) {
+		dev_err(dp->dev, "channel equalization failed: %d\n", ret);
+		goto out;
+	}
+
+	if (!link->train.channel_equalized) {
+		dev_err(dp->dev, "channel equalization failed, downgrading link\n");
+
+		ret = dw_dp_link_downgrade(dp);
+		if (ret < 0)
+			goto out;
+		else
+			goto retry;
+	}
+
+	dev_dbg(dp->dev, "channel equalization succeeded\n");
+
+out:
+	dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE);
+	return ret;
+}
+
+static int dw_dp_link_train_fast(struct dw_dp *dp)
+{
+	struct dw_dp_link *link = &dp->link;
+	int ret;
+	u8 status[DP_LINK_STATUS_SIZE];
+	u8 pattern;
+
+	dw_dp_link_train_init(&link->train);
+
+	dev_dbg(dp->dev, "fast-training link: %u lane%s at %u MHz\n",
+		link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100);
+
+	ret = dw_dp_link_configure(dp);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to configure DP link: %d\n", ret);
+		return ret;
+	}
+
+	ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1);
+	if (ret)
+		goto out;
+
+	usleep_range(500, 1000);
+
+	if (link->caps.tps4_supported)
+		pattern = DP_TRAINING_PATTERN_4;
+	else if (link->caps.tps3_supported)
+		pattern = DP_TRAINING_PATTERN_3;
+	else
+		pattern = DP_TRAINING_PATTERN_2;
+	ret = dw_dp_link_train_set_pattern(dp, pattern);
+	if (ret)
+		goto out;
+
+	usleep_range(500, 1000);
+
+	ret = drm_dp_dpcd_read_link_status(&dp->aux, status);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to read link status: %d\n", ret);
+		goto out;
+	}
+
+	if (!drm_dp_clock_recovery_ok(status, link->lanes)) {
+		dev_err(dp->dev, "clock recovery failed\n");
+		ret = -EIO;
+		goto out;
+	}
+
+	if (!drm_dp_channel_eq_ok(status, link->lanes)) {
+		dev_err(dp->dev, "channel equalization failed\n");
+		ret = -EIO;
+		goto out;
+	}
+
+out:
+	dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE);
+	return ret;
+}
+
+static int dw_dp_link_train(struct dw_dp *dp)
+{
+	struct dw_dp_link *link = &dp->link;
+	int ret;
+
+	if (link->caps.fast_training) {
+		if (dw_dp_link_train_valid(&link->train)) {
+			ret = dw_dp_link_train_fast(dp);
+			if (ret < 0)
+				dev_err(dp->dev, "fast link training failed: %d\n", ret);
+			else
+				return 0;
+		}
+	}
+
+	ret = dw_dp_link_train_full(dp);
+	if (ret < 0) {
+		dev_err(dp->dev, "full link training failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp)
+{
+	const u8 *payload = sdp->base.db;
+	u32 reg;
+	int i, nr;
+
+	nr = find_first_zero_bit(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
+	if (nr < SDP_REG_BANK_SIZE)
+		set_bit(nr, dp->sdp_reg_bank);
+	else
+		return -EBUSY;
+
+	reg = DW_DP_SDP_REGISTER_BANK + nr * 9 * 4;
+
+	/* SDP header */
+	regmap_write(dp->regmap, reg, get_unaligned_le32(&sdp->base.sdp_header));
+
+	/* SDP data payload */
+	for (i = 1; i < 9; i++, payload += 4)
+		regmap_write(dp->regmap, reg + i * 4,
+			     FIELD_PREP(SDP_REGS, get_unaligned_le32(payload)));
+
+	if (sdp->flags & DW_DP_SDP_VERTICAL_INTERVAL)
+		regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
+				   EN_VERTICAL_SDP << nr,
+				   EN_VERTICAL_SDP << nr);
+
+	if (sdp->flags & DW_DP_SDP_HORIZONTAL_INTERVAL)
+		regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
+				   EN_HORIZONTAL_SDP << nr,
+				   EN_HORIZONTAL_SDP << nr);
+
+	return 0;
+}
+
+static int dw_dp_send_vsc_sdp(struct dw_dp *dp)
+{
+	struct dw_dp_video *video = &dp->video;
+	struct dw_dp_sdp sdp = {};
+	struct drm_dp_vsc_sdp vsc = {};
+
+	sdp.flags = DW_DP_SDP_VERTICAL_INTERVAL;
+
+	vsc.sdp_type = DP_SDP_VSC;
+	vsc.revision = 0x5;
+	vsc.length = 0x13;
+	vsc.bpc = video->bpc;
+	vsc.content_type = DP_CONTENT_TYPE_NOT_DEFINED;
+
+	switch (video->color_format) {
+	case DRM_COLOR_FORMAT_YCBCR444:
+		vsc.pixelformat = DP_PIXELFORMAT_YUV444;
+		break;
+	case DRM_COLOR_FORMAT_YCBCR420:
+		vsc.pixelformat = DP_PIXELFORMAT_YUV420;
+		break;
+	case DRM_COLOR_FORMAT_YCBCR422:
+		vsc.pixelformat = DP_PIXELFORMAT_YUV422;
+		break;
+	case DRM_COLOR_FORMAT_RGB444:
+	default:
+		vsc.pixelformat = DP_PIXELFORMAT_RGB;
+		break;
+	}
+
+	if (video->color_format == DRM_COLOR_FORMAT_RGB444) {
+		vsc.colorimetry = DP_COLORIMETRY_DEFAULT;
+		vsc.dynamic_range = DP_DYNAMIC_RANGE_VESA;
+	} else {
+		vsc.colorimetry = DP_COLORIMETRY_BT709_YCC;
+		vsc.dynamic_range = DP_DYNAMIC_RANGE_CTA;
+	}
+
+	drm_dp_vsc_sdp_pack(&vsc, &sdp.base);
+
+	return dw_dp_send_sdp(dp, &sdp);
+}
+
+static int dw_dp_video_set_pixel_mode(struct dw_dp *dp, u8 pixel_mode)
+{
+	switch (pixel_mode) {
+	case DW_DP_MP_SINGLE_PIXEL:
+	case DW_DP_MP_DUAL_PIXEL:
+	case DW_DP_MP_QUAD_PIXEL:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, PIXEL_MODE_SELECT,
+			   FIELD_PREP(PIXEL_MODE_SELECT, pixel_mode));
+
+	return 0;
+}
+
+static bool dw_dp_video_need_vsc_sdp(struct dw_dp *dp)
+{
+	struct dw_dp_link *link = &dp->link;
+	struct dw_dp_video *video = &dp->video;
+
+	if (!link->vsc_sdp_supported)
+		return false;
+
+	if (video->color_format == DRM_COLOR_FORMAT_YCBCR420)
+		return true;
+
+	return false;
+}
+
+static int dw_dp_video_set_msa(struct dw_dp *dp, u8 color_format, u8 bpc,
+			       u16 vstart, u16 hstart)
+{
+	u16 misc = 0;
+
+	if (dw_dp_video_need_vsc_sdp(dp))
+		misc |= DP_MSA_MISC_COLOR_VSC_SDP;
+
+	switch (color_format) {
+	case DRM_COLOR_FORMAT_RGB444:
+		misc |= DP_MSA_MISC_COLOR_RGB;
+		break;
+	case DRM_COLOR_FORMAT_YCBCR444:
+		misc |= DP_MSA_MISC_COLOR_YCBCR_444_BT709;
+		break;
+	case DRM_COLOR_FORMAT_YCBCR422:
+		misc |= DP_MSA_MISC_COLOR_YCBCR_422_BT709;
+		break;
+	case DRM_COLOR_FORMAT_YCBCR420:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (bpc) {
+	case 6:
+		misc |= DP_MSA_MISC_6_BPC;
+		break;
+	case 8:
+		misc |= DP_MSA_MISC_8_BPC;
+		break;
+	case 10:
+		misc |= DP_MSA_MISC_10_BPC;
+		break;
+	case 12:
+		misc |= DP_MSA_MISC_12_BPC;
+		break;
+	case 16:
+		misc |= DP_MSA_MISC_16_BPC;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	regmap_write(dp->regmap, DW_DP_VIDEO_MSA1,
+		     FIELD_PREP(VSTART, vstart) | FIELD_PREP(HSTART, hstart));
+	regmap_write(dp->regmap, DW_DP_VIDEO_MSA2, FIELD_PREP(MISC0, misc));
+	regmap_write(dp->regmap, DW_DP_VIDEO_MSA3, FIELD_PREP(MISC1, misc >> 8));
+
+	return 0;
+}
+
+static void dw_dp_video_disable(struct dw_dp *dp)
+{
+	regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE,
+			   FIELD_PREP(VIDEO_STREAM_ENABLE, 0));
+}
+
+static int dw_dp_video_enable(struct dw_dp *dp)
+{
+	struct dw_dp_video *video = &dp->video;
+	struct dw_dp_link *link = &dp->link;
+	struct drm_display_mode *mode = &video->mode;
+	u8 color_format = video->color_format;
+	u8 bpc = video->bpc;
+	u8 pixel_mode = video->pixel_mode;
+	u8 bpp = video->bpp, init_threshold, vic;
+	u32 hactive, hblank, h_sync_width, h_front_porch;
+	u32 vactive, vblank, v_sync_width, v_front_porch;
+	u32 vstart = mode->vtotal - mode->vsync_start;
+	u32 hstart = mode->htotal - mode->hsync_start;
+	u32 peak_stream_bandwidth, link_bandwidth;
+	u32 average_bytes_per_tu, average_bytes_per_tu_frac;
+	u32 ts, hblank_interval;
+	u32 value;
+	int ret;
+
+	ret = dw_dp_video_set_pixel_mode(dp, pixel_mode);
+	if (ret)
+		return ret;
+
+	ret = dw_dp_video_set_msa(dp, color_format, bpc, vstart, hstart);
+	if (ret)
+		return ret;
+
+	regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_MAPPING,
+			   FIELD_PREP(VIDEO_MAPPING, video->video_mapping));
+
+	/* Configure DW_DP_VINPUT_POLARITY_CTRL register */
+	value = 0;
+	if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+		value |= FIELD_PREP(HSYNC_IN_POLARITY, 1);
+	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+		value |= FIELD_PREP(VSYNC_IN_POLARITY, 1);
+	regmap_write(dp->regmap, DW_DP_VINPUT_POLARITY_CTRL, value);
+
+	/* Configure DW_DP_VIDEO_CONFIG1 register */
+	hactive = mode->hdisplay;
+	hblank = mode->htotal - mode->hdisplay;
+	value = FIELD_PREP(HACTIVE, hactive) | FIELD_PREP(HBLANK, hblank);
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		value |= FIELD_PREP(I_P, 1);
+	vic = drm_match_cea_mode(mode);
+	if (vic == 5 || vic == 6 || vic == 7 ||
+	    vic == 10 || vic == 11 || vic == 20 ||
+	    vic == 21 || vic == 22 || vic == 39 ||
+	    vic == 25 || vic == 26 || vic == 40 ||
+	    vic == 44 || vic == 45 || vic == 46 ||
+	    vic == 50 || vic == 51 || vic == 54 ||
+	    vic == 55 || vic == 58 || vic  == 59)
+		value |= R_V_BLANK_IN_OSC;
+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG1, value);
+
+	/* Configure DW_DP_VIDEO_CONFIG2 register */
+	vblank = mode->vtotal - mode->vdisplay;
+	vactive = mode->vdisplay;
+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG2,
+		     FIELD_PREP(VBLANK, vblank) | FIELD_PREP(VACTIVE, vactive));
+
+	/* Configure DW_DP_VIDEO_CONFIG3 register */
+	h_sync_width = mode->hsync_end - mode->hsync_start;
+	h_front_porch = mode->hsync_start - mode->hdisplay;
+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG3,
+		     FIELD_PREP(H_SYNC_WIDTH, h_sync_width) |
+		     FIELD_PREP(H_FRONT_PORCH, h_front_porch));
+
+	/* Configure DW_DP_VIDEO_CONFIG4 register */
+	v_sync_width = mode->vsync_end - mode->vsync_start;
+	v_front_porch = mode->vsync_start - mode->vdisplay;
+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG4,
+		     FIELD_PREP(V_SYNC_WIDTH, v_sync_width) |
+		     FIELD_PREP(V_FRONT_PORCH, v_front_porch));
+
+	/* Configure DW_DP_VIDEO_CONFIG5 register */
+	peak_stream_bandwidth = mode->clock * bpp / 8;
+	link_bandwidth = (link->rate / 1000) * link->lanes;
+	ts = peak_stream_bandwidth * 64 / link_bandwidth;
+	average_bytes_per_tu = ts / 1000;
+	average_bytes_per_tu_frac = ts / 100 - average_bytes_per_tu * 10;
+	if (pixel_mode == DW_DP_MP_SINGLE_PIXEL) {
+		if (average_bytes_per_tu < 6)
+			init_threshold = 32;
+		else if (hblank <= 80 && color_format != DRM_COLOR_FORMAT_YCBCR420)
+			init_threshold = 12;
+		else if (hblank <= 40 && color_format == DRM_COLOR_FORMAT_YCBCR420)
+			init_threshold = 3;
+		else
+			init_threshold = 16;
+	} else {
+		u32 t1 = 0, t2 = 0, t3 = 0;
+
+		switch (bpc) {
+		case 6:
+			t1 = (4 * 1000 / 9) * link->lanes;
+			break;
+		case 8:
+			if (color_format == DRM_COLOR_FORMAT_YCBCR422) {
+				t1 = (1000 / 2) * link->lanes;
+			} else {
+				if (pixel_mode == DW_DP_MP_DUAL_PIXEL)
+					t1 = (1000 / 3) * link->lanes;
+				else
+					t1 = (3000 / 16) * link->lanes;
+			}
+			break;
+		case 10:
+			if (color_format == DRM_COLOR_FORMAT_YCBCR422)
+				t1 = (2000 / 5) * link->lanes;
+			else
+				t1 = (4000 / 15) * link->lanes;
+			break;
+		case 12:
+			if (color_format == DRM_COLOR_FORMAT_YCBCR422) {
+				if (pixel_mode == DW_DP_MP_DUAL_PIXEL)
+					t1 = (1000 / 6) * link->lanes;
+				else
+					t1 = (1000 / 3) * link->lanes;
+			} else {
+				t1 = (2000 / 9) * link->lanes;
+			}
+			break;
+		case 16:
+			if (color_format != DRM_COLOR_FORMAT_YCBCR422 &&
+			    pixel_mode == DW_DP_MP_DUAL_PIXEL)
+				t1 = (1000 / 6) * link->lanes;
+			else
+				t1 = (1000 / 4) * link->lanes;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		if (color_format == DRM_COLOR_FORMAT_YCBCR420)
+			t2 = (link->rate / 4) * 1000 / (mode->clock / 2);
+		else
+			t2 = (link->rate / 4) * 1000 / mode->clock;
+
+		if (average_bytes_per_tu_frac)
+			t3 = average_bytes_per_tu + 1;
+		else
+			t3 = average_bytes_per_tu;
+		init_threshold = t1 * t2 * t3 / (1000 * 1000);
+		if (init_threshold <= 16 || average_bytes_per_tu < 10)
+			init_threshold = 40;
+	}
+
+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG5,
+		     FIELD_PREP(INIT_THRESHOLD_HI, init_threshold >> 6) |
+		     FIELD_PREP(AVERAGE_BYTES_PER_TU_FRAC, average_bytes_per_tu_frac) |
+		     FIELD_PREP(INIT_THRESHOLD, init_threshold) |
+		     FIELD_PREP(AVERAGE_BYTES_PER_TU, average_bytes_per_tu));
+
+	/* Configure DW_DP_VIDEO_HBLANK_INTERVAL register */
+	hblank_interval = hblank * (link->rate / 4) / mode->clock;
+	regmap_write(dp->regmap, DW_DP_VIDEO_HBLANK_INTERVAL,
+		     FIELD_PREP(HBLANK_INTERVAL_EN, 1) |
+		     FIELD_PREP(HBLANK_INTERVAL, hblank_interval));
+
+	/* Video stream enable */
+	regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE,
+			   FIELD_PREP(VIDEO_STREAM_ENABLE, 1));
+
+	if (dw_dp_video_need_vsc_sdp(dp))
+		dw_dp_send_vsc_sdp(dp);
+
+	return 0;
+}
+
+static void dw_dp_hpd_init(struct dw_dp *dp)
+{
+	/* Enable all HPD interrupts */
+	regmap_update_bits(dp->regmap, DW_DP_HPD_INTERRUPT_ENABLE,
+			   HPD_UNPLUG_EN | HPD_PLUG_EN | HPD_IRQ_EN,
+			   FIELD_PREP(HPD_UNPLUG_EN, 1) |
+			   FIELD_PREP(HPD_PLUG_EN, 1) |
+			   FIELD_PREP(HPD_IRQ_EN, 1));
+
+	/* Enable all top-level interrupts */
+	regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE,
+			   HPD_EVENT_EN, FIELD_PREP(HPD_EVENT_EN, 1));
+}
+
+static void dw_dp_aux_init(struct dw_dp *dp)
+{
+	regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE,
+			   AUX_REPLY_EVENT_EN, FIELD_PREP(AUX_REPLY_EVENT_EN, 1));
+}
+
+static void dw_dp_init_hw(struct dw_dp *dp)
+{
+	regmap_update_bits(dp->regmap, DW_DP_CCTL, DEFAULT_FAST_LINK_TRAIN_EN,
+			   FIELD_PREP(DEFAULT_FAST_LINK_TRAIN_EN, 0));
+
+	dw_dp_hpd_init(dp);
+	dw_dp_aux_init(dp);
+}
+
+static int dw_dp_aux_write_data(struct dw_dp *dp, const u8 *buffer, size_t size)
+{
+	size_t i, j;
+
+	for (i = 0; i < DIV_ROUND_UP(size, 4); i++) {
+		size_t num = min_t(size_t, size - i * 4, 4);
+		u32 value = 0;
+
+		for (j = 0; j < num; j++)
+			value |= buffer[i * 4 + j] << (j * 8);
+
+		regmap_write(dp->regmap, DW_DP_AUX_DATA0 + i * 4, value);
+	}
+
+	return size;
+}
+
+static int dw_dp_aux_read_data(struct dw_dp *dp, u8 *buffer, size_t size)
+{
+	size_t i, j;
+
+	for (i = 0; i < DIV_ROUND_UP(size, 4); i++) {
+		size_t num = min_t(size_t, size - i * 4, 4);
+		u32 value;
+
+		regmap_read(dp->regmap, DW_DP_AUX_DATA0 + i * 4, &value);
+
+		for (j = 0; j < num; j++)
+			buffer[i * 4 + j] = value >> (j * 8);
+	}
+
+	return size;
+}
+
+static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux,
+				  struct drm_dp_aux_msg *msg)
+{
+	struct dw_dp *dp = container_of(aux, struct dw_dp, aux);
+	unsigned long timeout = msecs_to_jiffies(10);
+	u32 status, value;
+	ssize_t ret = 0;
+
+	if (WARN_ON(msg->size > 16))
+		return -E2BIG;
+
+	switch (msg->request & ~DP_AUX_I2C_MOT) {
+	case DP_AUX_NATIVE_WRITE:
+	case DP_AUX_I2C_WRITE:
+	case DP_AUX_I2C_WRITE_STATUS_UPDATE:
+		ret = dw_dp_aux_write_data(dp, msg->buffer, msg->size);
+		if (ret < 0)
+			return ret;
+		break;
+	case DP_AUX_NATIVE_READ:
+	case DP_AUX_I2C_READ:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (msg->size > 0)
+		value = FIELD_PREP(AUX_LEN_REQ, msg->size - 1);
+	else
+		value = FIELD_PREP(I2C_ADDR_ONLY, 1);
+	value |= FIELD_PREP(AUX_CMD_TYPE, msg->request);
+	value |= FIELD_PREP(AUX_ADDR, msg->address);
+	regmap_write(dp->regmap, DW_DP_AUX_CMD, value);
+
+	status = wait_for_completion_timeout(&dp->complete, timeout);
+	if (!status) {
+		dev_err(dp->dev, "timeout waiting for AUX reply\n");
+		return -ETIMEDOUT;
+	}
+
+	regmap_read(dp->regmap, DW_DP_AUX_STATUS, &value);
+	if (value & AUX_TIMEOUT)
+		return -ETIMEDOUT;
+
+	msg->reply = FIELD_GET(AUX_STATUS, value);
+
+	if (msg->size > 0 && msg->reply == DP_AUX_NATIVE_REPLY_ACK) {
+		if (msg->request & DP_AUX_I2C_READ) {
+			size_t count = FIELD_GET(AUX_BYTES_READ, value) - 1;
+
+			if (count != msg->size)
+				return -EBUSY;
+
+			ret = dw_dp_aux_read_data(dp, msg->buffer, count);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return ret;
+}
+
+/*
+ * Limits for the video timing for DP:
+ * 1. the hfp should be 2 pixels aligned;
+ * 2. the minimum hsync should be 9 pixel;
+ * 3. the minimum hbp should be 16 pixel;
+ */
+static int dw_dp_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_dp *dp = bridge_to_dp(bridge);
+	struct dw_dp_video *video = &dp->video;
+	struct drm_display_mode *m = &video->mode;
+	struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+	const struct dw_dp_output_format *fmt;
+	int min_hbp = 16;
+	int min_hsync = 9;
+
+	fmt = dw_dp_get_output_format(bridge_state->output_bus_cfg.format);
+	if (!fmt)
+		return -EINVAL;
+
+	video->video_mapping = fmt->video_mapping;
+	video->color_format = fmt->color_format;
+	video->bpc = fmt->bpc;
+	video->bpp = fmt->bpp;
+
+	if ((adjusted_mode->hsync_start - adjusted_mode->hdisplay) & 0x1) {
+		adjusted_mode->hsync_start += 1;
+		dev_warn(dp->dev, "hfp is not 2 pixeel aligned, fixup to aligned hfp\n");
+	}
+	if (adjusted_mode->hsync_end - adjusted_mode->hsync_start < min_hsync) {
+		adjusted_mode->hsync_end = adjusted_mode->hsync_start + min_hsync;
+		dev_warn(dp->dev, "hsync is too narrow, fixup to min hsync:%d\n", min_hsync);
+	}
+	if (adjusted_mode->htotal - adjusted_mode->hsync_end < min_hbp) {
+		adjusted_mode->htotal = adjusted_mode->hsync_end + min_hbp;
+		dev_warn(dp->dev, "hbp is too narrow, fixup to min hbp:%d\n", min_hbp);
+	}
+
+	drm_mode_copy(m, adjusted_mode);
+
+	return 0;
+}
+
+static enum drm_mode_status dw_dp_bridge_mode_valid(struct drm_bridge *bridge,
+						    const struct drm_display_info *info,
+						    const struct drm_display_mode *mode)
+{
+	struct dw_dp *dp = bridge_to_dp(bridge);
+	struct dw_dp_link *link = &dp->link;
+	u32 min_bpp;
+
+	if (info->color_formats & DRM_COLOR_FORMAT_YCBCR420 &&
+	    link->vsc_sdp_supported &&
+	    (drm_mode_is_420_only(info, mode) || drm_mode_is_420_also(info, mode)))
+		min_bpp = 12;
+	else if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422)
+		min_bpp = 16;
+	else if (info->color_formats & DRM_COLOR_FORMAT_RGB444)
+		min_bpp = 18;
+	else
+		min_bpp = 24;
+
+	if (!link->vsc_sdp_supported &&
+	    drm_mode_is_420_only(info, mode))
+		return MODE_NO_420;
+
+	if (!dw_dp_bandwidth_ok(dp, mode, min_bpp, link->lanes, link->rate))
+		return MODE_CLOCK_HIGH;
+
+	return MODE_OK;
+}
+
+static bool dw_dp_needs_link_retrain(struct dw_dp *dp)
+{
+	struct dw_dp_link *link = &dp->link;
+	u8 link_status[DP_LINK_STATUS_SIZE];
+
+	if (!dw_dp_link_train_valid(&link->train))
+		return false;
+
+	if (drm_dp_dpcd_read_link_status(&dp->aux, link_status) < 0)
+		return false;
+
+	/* Retrain if Channel EQ or CR not ok */
+	return !drm_dp_channel_eq_ok(link_status, dp->link.lanes);
+}
+
+static void dw_dp_link_disable(struct dw_dp *dp)
+{
+	struct dw_dp_link *link = &dp->link;
+
+	if (dw_dp_hpd_detect(dp))
+		drm_dp_link_power_down(&dp->aux, dp->link.revision);
+
+	dw_dp_phy_xmit_enable(dp, 0);
+
+	phy_power_off(dp->phy);
+
+	link->train.clock_recovered = false;
+	link->train.channel_equalized = false;
+}
+
+static int dw_dp_link_enable(struct dw_dp *dp)
+{
+	int ret;
+
+	ret = phy_power_on(dp->phy);
+	if (ret)
+		return ret;
+
+	ret = drm_dp_link_power_up(&dp->aux, dp->link.revision);
+	if (ret < 0)
+		return ret;
+
+	ret = dw_dp_link_train(dp);
+
+	return ret;
+}
+
+static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
+				       struct drm_atomic_state *state)
+{
+	struct dw_dp *dp = bridge_to_dp(bridge);
+	struct drm_connector *connector;
+	struct drm_connector_state *conn_state;
+	int ret;
+
+	connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
+	if (!connector) {
+		dev_err(dp->dev, "failed to get connector\n");
+		return;
+	}
+
+	conn_state = drm_atomic_get_new_connector_state(state, connector);
+	if (!conn_state) {
+		dev_err(dp->dev, "failed to get connector state\n");
+		return;
+	}
+
+	set_bit(0, dp->sdp_reg_bank);
+
+	ret = dw_dp_link_enable(dp);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to enable link: %d\n", ret);
+		return;
+	}
+
+	ret = dw_dp_video_enable(dp);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to enable video: %d\n", ret);
+		return;
+	}
+}
+
+static void dw_dp_reset(struct dw_dp *dp)
+{
+	int val;
+
+	disable_irq(dp->irq);
+	regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET,
+			   FIELD_PREP(CONTROLLER_RESET, 1));
+	udelay(10);
+	regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET,
+			   FIELD_PREP(CONTROLLER_RESET, 0));
+
+	dw_dp_init_hw(dp);
+	regmap_read_poll_timeout(dp->regmap, DW_DP_HPD_STATUS, val,
+				 FIELD_GET(HPD_HOT_PLUG, val), 200, 200000);
+	regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG);
+	enable_irq(dp->irq);
+}
+
+static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge,
+					struct drm_atomic_state *state)
+{
+	struct dw_dp *dp = bridge_to_dp(bridge);
+
+	dw_dp_video_disable(dp);
+	dw_dp_link_disable(dp);
+	bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
+	dw_dp_reset(dp);
+}
+
+static bool dw_dp_hpd_detect_link(struct dw_dp *dp)
+{
+	int ret;
+
+	ret = phy_power_on(dp->phy);
+	if (ret < 0)
+		return false;
+	ret = dw_dp_link_parse(dp);
+	phy_power_off(dp->phy);
+
+	return !ret;
+}
+
+static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge)
+{
+	struct dw_dp *dp = bridge_to_dp(bridge);
+
+	if (!dw_dp_hpd_detect(dp))
+		return connector_status_disconnected;
+
+	if (!dw_dp_hpd_detect_link(dp))
+		return connector_status_disconnected;
+
+	return connector_status_connected;
+}
+
+static const struct drm_edid *dw_dp_bridge_edid_read(struct drm_bridge *bridge,
+						     struct drm_connector *connector)
+{
+	struct dw_dp *dp = bridge_to_dp(bridge);
+	const struct drm_edid *edid;
+	int ret;
+
+	ret = phy_power_on(dp->phy);
+	if (ret)
+		return NULL;
+
+	edid = drm_edid_read_ddc(connector, &dp->aux.ddc);
+
+	phy_power_off(dp->phy);
+
+	return edid;
+}
+
+static u32 *dw_dp_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 dw_dp *dp = bridge_to_dp(bridge);
+	struct dw_dp_link *link = &dp->link;
+	struct drm_display_info *di = &conn_state->connector->display_info;
+	struct drm_display_mode mode = crtc_state->mode;
+	const struct dw_dp_output_format *fmt;
+	u32 i, j = 0;
+	u32 *output_fmts;
+
+	*num_output_fmts = 0;
+
+	output_fmts = kcalloc(ARRAY_SIZE(dw_dp_output_formats), sizeof(*output_fmts), GFP_KERNEL);
+	if (!output_fmts)
+		return NULL;
+
+	for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) {
+		fmt = &dw_dp_output_formats[i];
+
+		if (fmt->bpc > conn_state->max_bpc)
+			continue;
+
+		if (!(fmt->color_format & di->color_formats))
+			continue;
+
+		if (fmt->color_format == DRM_COLOR_FORMAT_YCBCR420 &&
+		    !link->vsc_sdp_supported)
+			continue;
+
+		if (fmt->color_format != DRM_COLOR_FORMAT_YCBCR420 &&
+		    drm_mode_is_420_only(di, &mode))
+			continue;
+
+		if (!dw_dp_bandwidth_ok(dp, &mode, fmt->bpp, link->lanes, link->rate))
+			continue;
+
+		output_fmts[j++] = fmt->bus_format;
+	}
+
+	*num_output_fmts = j;
+
+	return output_fmts;
+}
+
+static const struct drm_bridge_funcs dw_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_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
+	.atomic_get_output_bus_fmts = dw_dp_bridge_atomic_get_output_bus_fmts,
+	.atomic_check = dw_dp_bridge_atomic_check,
+	.mode_valid = dw_dp_bridge_mode_valid,
+	.atomic_enable = dw_dp_bridge_atomic_enable,
+	.atomic_disable = dw_dp_bridge_atomic_disable,
+	.detect = dw_dp_bridge_detect,
+	.edid_read = dw_dp_bridge_edid_read,
+};
+
+static int dw_dp_link_retrain(struct dw_dp *dp)
+{
+	struct drm_device *dev = dp->bridge.dev;
+	struct drm_modeset_acquire_ctx ctx;
+	int ret;
+
+	if (!dw_dp_needs_link_retrain(dp))
+		return 0;
+
+	dev_dbg(dp->dev, "Retraining link\n");
+
+	drm_modeset_acquire_init(&ctx, 0);
+	for (;;) {
+		ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx);
+		if (ret != -EDEADLK)
+			break;
+
+		drm_modeset_backoff(&ctx);
+	}
+
+	if (!ret)
+		ret = dw_dp_link_train(dp);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+
+	return ret;
+}
+
+static void dw_dp_hpd_work(struct work_struct *work)
+{
+	struct dw_dp *dp = container_of(work, struct dw_dp, hpd_work);
+	bool long_hpd;
+	int ret;
+
+	mutex_lock(&dp->irq_lock);
+	long_hpd = dp->hotplug.long_hpd;
+	mutex_unlock(&dp->irq_lock);
+
+	dev_dbg(dp->dev, "[drm] Get hpd irq - %s\n", long_hpd ? "long" : "short");
+
+	if (!long_hpd) {
+		if (dw_dp_needs_link_retrain(dp)) {
+			ret = dw_dp_link_retrain(dp);
+			if (ret)
+				dev_warn(dp->dev, "Retrain link failed\n");
+		}
+	} else {
+		drm_helper_hpd_irq_event(dp->bridge.dev);
+	}
+}
+
+static void dw_dp_handle_hpd_event(struct dw_dp *dp)
+{
+	u32 value;
+
+	mutex_lock(&dp->irq_lock);
+	regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value);
+
+	if (value & HPD_IRQ) {
+		dev_dbg(dp->dev, "IRQ from the HPD\n");
+		dp->hotplug.long_hpd = false;
+		regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_IRQ);
+	}
+
+	if (value & HPD_HOT_PLUG) {
+		dev_dbg(dp->dev, "Hot plug detected\n");
+		dp->hotplug.long_hpd = true;
+		regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG);
+	}
+
+	if (value & HPD_HOT_UNPLUG) {
+		dev_dbg(dp->dev, "Unplug detected\n");
+		dp->hotplug.long_hpd = true;
+		regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_UNPLUG);
+	}
+	mutex_unlock(&dp->irq_lock);
+
+	schedule_work(&dp->hpd_work);
+}
+
+static irqreturn_t dw_dp_irq(int irq, void *data)
+{
+	struct dw_dp *dp = data;
+	u32 value;
+
+	regmap_read(dp->regmap, DW_DP_GENERAL_INTERRUPT, &value);
+	if (!value)
+		return IRQ_NONE;
+
+	if (value & HPD_EVENT)
+		dw_dp_handle_hpd_event(dp);
+
+	if (value & AUX_REPLY_EVENT) {
+		regmap_write(dp->regmap, DW_DP_GENERAL_INTERRUPT, AUX_REPLY_EVENT);
+		complete(&dp->complete);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static const struct regmap_range dw_dp_readable_ranges[] = {
+	regmap_reg_range(DW_DP_VERSION_NUMBER, DW_DP_ID),
+	regmap_reg_range(DW_DP_CONFIG_REG1, DW_DP_CONFIG_REG3),
+	regmap_reg_range(DW_DP_CCTL, DW_DP_SOFT_RESET_CTRL),
+	regmap_reg_range(DW_DP_VSAMPLE_CTRL, DW_DP_VIDEO_HBLANK_INTERVAL),
+	regmap_reg_range(DW_DP_AUD_CONFIG1, DW_DP_AUD_CONFIG1),
+	regmap_reg_range(DW_DP_SDP_VERTICAL_CTRL, DW_DP_SDP_STATUS_EN),
+	regmap_reg_range(DW_DP_PHYIF_CTRL, DW_DP_PHYIF_PWRDOWN_CTRL),
+	regmap_reg_range(DW_DP_AUX_CMD, DW_DP_AUX_DATA3),
+	regmap_reg_range(DW_DP_GENERAL_INTERRUPT, DW_DP_HPD_INTERRUPT_ENABLE),
+};
+
+static const struct regmap_access_table dw_dp_readable_table = {
+	.yes_ranges     = dw_dp_readable_ranges,
+	.n_yes_ranges   = ARRAY_SIZE(dw_dp_readable_ranges),
+};
+
+static const struct regmap_config dw_dp_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.max_register = DW_DP_MAX_REGISTER,
+	.rd_table = &dw_dp_readable_table,
+};
+
+static void dw_dp_phy_exit(void *data)
+{
+	struct dw_dp *dp = data;
+
+	phy_exit(dp->phy);
+}
+
+struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
+			 const struct dw_dp_plat_data *plat_data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dw_dp *dp;
+	struct drm_bridge *bridge;
+	void __iomem *res;
+	int ret;
+
+	dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
+	if (!dp)
+		return ERR_PTR(-ENOMEM);
+
+	dp->dev = dev;
+	dp->video.pixel_mode = DW_DP_MP_QUAD_PIXEL;
+
+	dp->plat_data.max_link_rate = plat_data->max_link_rate;
+	bridge = &dp->bridge;
+	mutex_init(&dp->irq_lock);
+	INIT_WORK(&dp->hpd_work, dw_dp_hpd_work);
+	init_completion(&dp->complete);
+
+	res = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(res))
+		return ERR_CAST(res);
+
+	dp->regmap = devm_regmap_init_mmio(dev, res, &dw_dp_regmap_config);
+	if (IS_ERR(dp->regmap)) {
+		dev_err_probe(dev, PTR_ERR(dp->regmap), "failed to create regmap\n");
+		return ERR_CAST(dp->regmap);
+	}
+
+	dp->phy = devm_of_phy_get(dev, dev->of_node, NULL);
+	if (IS_ERR(dp->phy)) {
+		dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n");
+		return ERR_CAST(dp->phy);
+	}
+
+	dp->apb_clk = devm_clk_get_enabled(dev, "apb");
+	if (IS_ERR(dp->apb_clk)) {
+		dev_err_probe(dev, PTR_ERR(dp->apb_clk), "failed to get apb clock\n");
+		return ERR_CAST(dp->apb_clk);
+	}
+
+	dp->aux_clk = devm_clk_get_enabled(dev, "aux");
+	if (IS_ERR(dp->aux_clk)) {
+		dev_err_probe(dev, PTR_ERR(dp->aux_clk), "failed to get aux clock\n");
+		return ERR_CAST(dp->aux_clk);
+	}
+
+	dp->i2s_clk = devm_clk_get(dev, "i2s");
+	if (IS_ERR(dp->i2s_clk)) {
+		dev_err_probe(dev, PTR_ERR(dp->i2s_clk), "failed to get i2s clock\n");
+		return ERR_CAST(dp->i2s_clk);
+	}
+
+	dp->spdif_clk = devm_clk_get(dev, "spdif");
+	if (IS_ERR(dp->spdif_clk)) {
+		dev_err_probe(dev, PTR_ERR(dp->spdif_clk), "failed to get spdif clock\n");
+		return ERR_CAST(dp->spdif_clk);
+	}
+
+	dp->hdcp_clk = devm_clk_get(dev, "hdcp");
+	if (IS_ERR(dp->hdcp_clk)) {
+		dev_err_probe(dev, PTR_ERR(dp->hdcp_clk), "failed to get hdcp clock\n");
+		return ERR_CAST(dp->hdcp_clk);
+	}
+
+	dp->rstc = devm_reset_control_get(dev, NULL);
+	if (IS_ERR(dp->rstc)) {
+		dev_err_probe(dev, PTR_ERR(dp->rstc), "failed to get reset control\n");
+		return ERR_CAST(dp->rstc);
+	}
+
+	bridge->of_node = dev->of_node;
+	bridge->funcs = &dw_dp_bridge_funcs;
+	bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
+	bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
+	bridge->ycbcr_420_allowed = true;
+
+	dp->aux.dev = dev;
+	dp->aux.drm_dev = encoder->dev;
+	dp->aux.name = dev_name(dev);
+	dp->aux.transfer = dw_dp_aux_transfer;
+	ret = drm_dp_aux_register(&dp->aux);
+	if (ret) {
+		dev_err_probe(dev, ret, "Aux register failed\n");
+		return ERR_PTR(ret);
+	}
+
+	ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+	if (ret)
+		dev_err_probe(dev, ret, "Failed to attach bridge\n");
+
+	dw_dp_init_hw(dp);
+
+	ret = phy_init(dp->phy);
+	if (ret) {
+		dev_err_probe(dev, ret, "phy init failed\n");
+		return ERR_PTR(ret);
+	}
+
+	ret = devm_add_action_or_reset(dev, dw_dp_phy_exit, dp);
+	if (ret)
+		return ERR_PTR(ret);
+
+	dp->irq = platform_get_irq(pdev, 0);
+	if (dp->irq < 0)
+		return ERR_PTR(ret);
+
+	ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
+					IRQF_ONESHOT, dev_name(dev), dp);
+	if (ret) {
+		dev_err_probe(dev, ret, "failed to request irq\n");
+		return ERR_PTR(ret);
+	}
+
+	return dp;
+}
+EXPORT_SYMBOL_GPL(dw_dp_bind);
+
+MODULE_AUTHOR("Andy Yan <andyshrk@163.com>");
+MODULE_DESCRIPTION("DW DP Core Library");
+MODULE_LICENSE("GPL");
diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h
new file mode 100644
index 0000000000000..d05df49fd8846
--- /dev/null
+++ b/include/drm/bridge/dw_dp.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2025 Rockchip Electronics Co., Ltd.
+ */
+
+#ifndef __DW_DP__
+#define __DW_DP__
+
+#include <linux/device.h>
+
+struct drm_encoder;
+struct dw_dp;
+
+struct dw_dp_plat_data {
+	u32 max_link_rate;
+};
+
+struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
+			 const struct dw_dp_plat_data *plat_data);
+#endif /* __DW_DP__ */
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v3 3/9] drm/rockchip: Add RK3588 DPTX output support
  2025-04-03  3:37 [PATCH v3 0/9] Add support for RK3588 DisplayPort Controller Andy Yan
  2025-04-03  3:37 ` [PATCH v3 1/9] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller Andy Yan
  2025-04-03  3:37 ` [PATCH v3 2/9] drm/bridge: synopsys: Add DW DPTX Controller support library Andy Yan
@ 2025-04-03  3:37 ` Andy Yan
  2025-04-04  9:06   ` kernel test robot
                     ` (2 more replies)
  2025-04-03  3:37 ` [PATCH v3 4/9] dt-bindings: display: simple-bridge: Add ra620 compatible Andy Yan
                   ` (5 subsequent siblings)
  8 siblings, 3 replies; 21+ messages in thread
From: Andy Yan @ 2025-04-03  3:37 UTC (permalink / raw)
  To: dmitry.baryshkov, heiko
  Cc: hjc, mripard, naoki, stephen, cristian.ciocaltea, neil.armstrong,
	Laurent.pinchart, yubing.zhang, krzk+dt, devicetree, dri-devel,
	linux-arm-kernel, linux-kernel, linux-rockchip, robh,
	sebastian.reichel, Andy Yan, Dmitry Baryshkov

From: Andy Yan <andy.yan@rock-chips.com>

Add driver extension for Synopsys DesignWare DPTX IP used
on Rockchip RK3588 SoC.

Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
Acked-by: Dmitry Baryshkov <lumag@kernel.org>

---

(no changes since v2)

Changes in v2:
- no include uapi path
- switch to drmm_encoder_init

 drivers/gpu/drm/rockchip/Kconfig            |   9 ++
 drivers/gpu/drm/rockchip/Makefile           |   1 +
 drivers/gpu/drm/rockchip/dw_dp-rockchip.c   | 154 ++++++++++++++++++++
 drivers/gpu/drm/rockchip/rockchip_drm_drv.c |   1 +
 drivers/gpu/drm/rockchip/rockchip_drm_drv.h |   1 +
 5 files changed, 166 insertions(+)
 create mode 100644 drivers/gpu/drm/rockchip/dw_dp-rockchip.c

diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
index 26c4410b2407c..00315cc6be5a8 100644
--- a/drivers/gpu/drm/rockchip/Kconfig
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -8,6 +8,7 @@ config DRM_ROCKCHIP
 	select DRM_PANEL
 	select VIDEOMODE_HELPERS
 	select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP
+	select DRM_DW_DP if ROCKCHIP_DW_DP
 	select DRM_DW_HDMI if ROCKCHIP_DW_HDMI
 	select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP
 	select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI
@@ -58,6 +59,14 @@ config ROCKCHIP_CDN_DP
 	  RK3399 based SoC, you should select this
 	  option.
 
+config ROCKCHIP_DW_DP
+	bool "Rockchip specific extensions for Synopsys DW DP"
+	help
+	  This selects support for Rockchip SoC specific extensions
+	  to enable Synopsys DesignWare Cores based DisplayPort transmit
+	  controller support on Rockchip SoC, If you want to enable DP on
+	  rk3588 based SoC, you should select this option.
+
 config ROCKCHIP_DW_HDMI
 	bool "Rockchip specific extensions for Synopsys DW HDMI"
 	help
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
index 2b867cebbc121..097f062399c7a 100644
--- a/drivers/gpu/drm/rockchip/Makefile
+++ b/drivers/gpu/drm/rockchip/Makefile
@@ -14,6 +14,7 @@ rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
 rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI_QP) += dw_hdmi_qp-rockchip.o
 rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi-rockchip.o
 rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI2) += dw-mipi-dsi2-rockchip.o
+rockchipdrm-$(CONFIG_ROCKCHIP_DW_DP) += dw_dp-rockchip.o
 rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
 rockchipdrm-$(CONFIG_ROCKCHIP_LVDS) += rockchip_lvds.o
 rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o
diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
new file mode 100644
index 0000000000000..5ff8a6a54997e
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Rockchip Electronics Co., Ltd.
+ *
+ * Author: Zhang Yubing <yubing.zhang@rock-chips.com>
+ * Author: Andy Yan <andy.yan@rock-chips.com>
+ */
+
+#include <linux/component.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <drm/bridge/dw_dp.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
+#include <drm/drm_of.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include <linux/media-bus-format.h>
+#include <linux/videodev2.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_vop.h"
+
+struct rockchip_dw_dp {
+	struct dw_dp *base;
+	struct device *dev;
+	struct rockchip_encoder encoder;
+};
+
+static inline struct rockchip_dw_dp *encoder_to_dp(struct drm_encoder *encoder)
+{
+	struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
+
+	return container_of(rkencoder, struct rockchip_dw_dp, encoder);
+}
+
+static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder,
+				      struct drm_crtc_state *crtc_state,
+				      struct drm_connector_state *conn_state)
+{
+	struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
+	struct drm_atomic_state *state = conn_state->state;
+	struct drm_display_info *di = &conn_state->connector->display_info;
+	struct drm_bridge *bridge  = drm_bridge_chain_get_first_bridge(encoder);
+	struct drm_bridge_state *bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
+	u32 bus_format = bridge_state->input_bus_cfg.format;
+
+	switch (bus_format) {
+	case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
+	case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
+		s->output_mode = ROCKCHIP_OUT_MODE_YUV420;
+		break;
+	case MEDIA_BUS_FMT_YUYV10_1X20:
+	case MEDIA_BUS_FMT_YUYV8_1X16:
+		s->output_mode = ROCKCHIP_OUT_MODE_S888_DUMMY;
+		break;
+	case MEDIA_BUS_FMT_RGB101010_1X30:
+	case MEDIA_BUS_FMT_RGB888_1X24:
+	case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
+	case MEDIA_BUS_FMT_YUV10_1X30:
+	case MEDIA_BUS_FMT_YUV8_1X24:
+	default:
+		s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+		break;
+	}
+
+	s->output_type = DRM_MODE_CONNECTOR_DisplayPort;
+	s->bus_format = bus_format;
+	s->bus_flags = di->bus_flags;
+	s->color_space = V4L2_COLORSPACE_DEFAULT;
+
+	return 0;
+}
+
+static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = {
+	.atomic_check		= dw_dp_encoder_atomic_check,
+};
+
+static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data)
+{
+	struct dw_dp_plat_data plat_data;
+	struct drm_device *drm_dev = data;
+	struct rockchip_dw_dp *dp;
+	struct drm_encoder *encoder;
+	struct drm_connector *connector;
+	int ret;
+
+	dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
+	if (!dp)
+		return -ENOMEM;
+
+	dp->dev = dev;
+	plat_data.max_link_rate = 810000;
+	encoder = &dp->encoder.encoder;
+	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node);
+	rockchip_drm_encoder_set_crtc_endpoint_id(&dp->encoder, dev->of_node, 0, 0);
+
+	ret = drmm_encoder_init(drm_dev, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL);
+	if (ret)
+		return ret;
+	drm_encoder_helper_add(encoder, &dw_dp_encoder_helper_funcs);
+
+	dp->base = dw_dp_bind(dev, encoder, &plat_data);
+	if (IS_ERR(dp->base)) {
+		ret = PTR_ERR(dp->base);
+		return ret;
+	}
+
+	connector = drm_bridge_connector_init(drm_dev, encoder);
+	if (IS_ERR(connector)) {
+		ret = PTR_ERR(connector);
+		return dev_err_probe(dev, ret, "Failed to init bridge connector");
+	}
+
+	drm_connector_attach_encoder(connector, encoder);
+
+	return 0;
+}
+
+static const struct component_ops dw_dp_rockchip_component_ops = {
+	.bind = dw_dp_rockchip_bind,
+};
+
+static int dw_dp_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+
+	return component_add(dev, &dw_dp_rockchip_component_ops);
+}
+
+static void dw_dp_remove(struct platform_device *pdev)
+{
+	struct rockchip_dw_dp *dp = platform_get_drvdata(pdev);
+
+	component_del(dp->dev, &dw_dp_rockchip_component_ops);
+}
+
+static const struct of_device_id dw_dp_of_match[] = {
+	{ .compatible = "rockchip,rk3588-dp", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, dw_dp_of_match);
+
+struct platform_driver dw_dp_driver = {
+	.probe	= dw_dp_probe,
+	.remove = dw_dp_remove,
+	.driver = {
+		.name = "dw-dp",
+		.of_match_table = dw_dp_of_match,
+	},
+};
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
index ed88788e04dd2..687bb7b252e8e 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
@@ -531,6 +531,7 @@ static int __init rockchip_drm_init(void)
 	ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,
 				CONFIG_ROCKCHIP_ANALOGIX_DP);
 	ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP);
+	ADD_ROCKCHIP_SUB_DRIVER(dw_dp_driver, CONFIG_ROCKCHIP_DW_DP);
 	ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver,
 				CONFIG_ROCKCHIP_DW_HDMI);
 	ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_qp_rockchip_pltfm_driver,
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
index c183e82a42a51..2e86ad00979c4 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
@@ -87,6 +87,7 @@ int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rencoder,
 					      struct device_node *np, int port, int reg);
 int rockchip_drm_endpoint_is_subdriver(struct device_node *ep);
 extern struct platform_driver cdn_dp_driver;
+extern struct platform_driver dw_dp_driver;
 extern struct platform_driver dw_hdmi_rockchip_pltfm_driver;
 extern struct platform_driver dw_hdmi_qp_rockchip_pltfm_driver;
 extern struct platform_driver dw_mipi_dsi_rockchip_driver;
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v3 4/9] dt-bindings: display: simple-bridge: Add ra620 compatible
  2025-04-03  3:37 [PATCH v3 0/9] Add support for RK3588 DisplayPort Controller Andy Yan
                   ` (2 preceding siblings ...)
  2025-04-03  3:37 ` [PATCH v3 3/9] drm/rockchip: Add RK3588 DPTX output support Andy Yan
@ 2025-04-03  3:37 ` Andy Yan
  2025-04-03  6:42   ` Krzysztof Kozlowski
  2025-04-03  3:37 ` [PATCH v3 5/9] drm/birdge: simple-bridge: Add support for radxa ra620 Andy Yan
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 21+ messages in thread
From: Andy Yan @ 2025-04-03  3:37 UTC (permalink / raw)
  To: dmitry.baryshkov, heiko
  Cc: hjc, mripard, naoki, stephen, cristian.ciocaltea, neil.armstrong,
	Laurent.pinchart, yubing.zhang, krzk+dt, devicetree, dri-devel,
	linux-arm-kernel, linux-kernel, linux-rockchip, robh,
	sebastian.reichel, Andy Yan

From: Andy Yan <andy.yan@rock-chips.com>

RA620 is a DP to HDMI bridge converter from RADXA, which first
found be used on ROCK 5 ITX.

This chip can be used without involving software.

Signed-off-by: Andy Yan <andy.yan@rock-chips.com>

---

Changes in v3:
- First introduced in this version.

 .../devicetree/bindings/display/bridge/simple-bridge.yaml        | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/display/bridge/simple-bridge.yaml b/Documentation/devicetree/bindings/display/bridge/simple-bridge.yaml
index 43cf4df9811a5..421f99ca42d9b 100644
--- a/Documentation/devicetree/bindings/display/bridge/simple-bridge.yaml
+++ b/Documentation/devicetree/bindings/display/bridge/simple-bridge.yaml
@@ -28,6 +28,7 @@ properties:
       - enum:
           - adi,adv7123
           - dumb-vga-dac
+          - radxa,ra620
           - ti,opa362
           - ti,ths8134
           - ti,ths8135
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v3 5/9] drm/birdge: simple-bridge: Add support for radxa ra620
  2025-04-03  3:37 [PATCH v3 0/9] Add support for RK3588 DisplayPort Controller Andy Yan
                   ` (3 preceding siblings ...)
  2025-04-03  3:37 ` [PATCH v3 4/9] dt-bindings: display: simple-bridge: Add ra620 compatible Andy Yan
@ 2025-04-03  3:37 ` Andy Yan
  2025-04-03 12:14   ` Dmitry Baryshkov
  2025-04-03  3:37 ` [PATCH v3 6/9] arm64: dts: rockchip: Add DP0 for rk3588 Andy Yan
                   ` (3 subsequent siblings)
  8 siblings, 1 reply; 21+ messages in thread
From: Andy Yan @ 2025-04-03  3:37 UTC (permalink / raw)
  To: dmitry.baryshkov, heiko
  Cc: hjc, mripard, naoki, stephen, cristian.ciocaltea, neil.armstrong,
	Laurent.pinchart, yubing.zhang, krzk+dt, devicetree, dri-devel,
	linux-arm-kernel, linux-kernel, linux-rockchip, robh,
	sebastian.reichel, Andy Yan

From: Andy Yan <andy.yan@rock-chips.com>

The RA620 is an active DP to HDMI converter chip, basically
no software is involved to drive it.

Add it to simple bridge to make it can be find by the drm bridge chain.

Signed-off-by: Andy Yan <andy.yan@rock-chips.com>

---

Changes in v3:
- First introduced in this version.

 drivers/gpu/drm/bridge/simple-bridge.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c
index 70db5b99e5bb8..df2d8106ee755 100644
--- a/drivers/gpu/drm/bridge/simple-bridge.c
+++ b/drivers/gpu/drm/bridge/simple-bridge.c
@@ -261,6 +261,11 @@ static const struct of_device_id simple_bridge_match[] = {
 			.timings = &default_bridge_timings,
 			.connector_type = DRM_MODE_CONNECTOR_VGA,
 		},
+	}, {
+		.compatible = "radxa,ra620",
+		.data = &(const struct simple_bridge_info) {
+			.connector_type = DRM_MODE_CONNECTOR_HDMIA,
+		},
 	}, {
 		.compatible = "ti,opa362",
 		.data = &(const struct simple_bridge_info) {
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v3 6/9] arm64: dts: rockchip: Add DP0 for rk3588
  2025-04-03  3:37 [PATCH v3 0/9] Add support for RK3588 DisplayPort Controller Andy Yan
                   ` (4 preceding siblings ...)
  2025-04-03  3:37 ` [PATCH v3 5/9] drm/birdge: simple-bridge: Add support for radxa ra620 Andy Yan
@ 2025-04-03  3:37 ` Andy Yan
  2025-04-03  3:37 ` [PATCH v3 7/9] arm64: dts: rockchip: Add DP1 " Andy Yan
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 21+ messages in thread
From: Andy Yan @ 2025-04-03  3:37 UTC (permalink / raw)
  To: dmitry.baryshkov, heiko
  Cc: hjc, mripard, naoki, stephen, cristian.ciocaltea, neil.armstrong,
	Laurent.pinchart, yubing.zhang, krzk+dt, devicetree, dri-devel,
	linux-arm-kernel, linux-kernel, linux-rockchip, robh,
	sebastian.reichel, Andy Yan

From: Andy Yan <andy.yan@rock-chips.com>

The DP0 is compliant with the DisplayPort Specification
Version 1.4, and share the USBDP combo PHY0 with USB 3.1
HOST0 controller.

Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
---

(no changes since v1)

 arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 30 +++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
index a25048283410c..e07f0d7460e7a 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
@@ -1433,6 +1433,36 @@ i2s9_8ch: i2s@fddfc000 {
 		status = "disabled";
 	};
 
+	dp0: dp@fde50000 {
+		compatible = "rockchip,rk3588-dp";
+		reg = <0x0 0xfde50000 0x0 0x4000>;
+		interrupts = <GIC_SPI 161 IRQ_TYPE_LEVEL_HIGH 0>;
+		clocks = <&cru PCLK_DP0>, <&cru CLK_AUX16M_0>,
+			 <&cru CLK_DP0>, <&cru MCLK_I2S4_8CH_TX>,
+			 <&cru MCLK_SPDIF2_DP0>;
+		clock-names = "apb", "aux", "hdcp", "i2s", "spdif";
+		assigned-clocks = <&cru CLK_AUX16M_0>;
+		assigned-clock-rates = <16000000>;
+		resets = <&cru SRST_DP0>;
+		phys = <&usbdp_phy0 PHY_TYPE_DP>;
+		power-domains = <&power RK3588_PD_VO0>;
+		#sound-dai-cells = <0>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			dp0_in: port@0 {
+				reg = <0>;
+			};
+
+			dp0_out: port@1 {
+				reg = <1>;
+			};
+		};
+	};
+
 	hdmi0: hdmi@fde80000 {
 		compatible = "rockchip,rk3588-dw-hdmi-qp";
 		reg = <0x0 0xfde80000 0x0 0x20000>;
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v3 7/9] arm64: dts: rockchip: Add DP1 for rk3588
  2025-04-03  3:37 [PATCH v3 0/9] Add support for RK3588 DisplayPort Controller Andy Yan
                   ` (5 preceding siblings ...)
  2025-04-03  3:37 ` [PATCH v3 6/9] arm64: dts: rockchip: Add DP0 for rk3588 Andy Yan
@ 2025-04-03  3:37 ` Andy Yan
  2025-04-03  3:37 ` [PATCH v3 8/9] arm64: dts: rockchip: Enable DisplayPort for rk3588s Cool Pi 4B Andy Yan
  2025-04-03  3:37 ` [PATCH v3 9/9] arm64: dts: rockchip: Enable DP2HDMI for ROCK 5 ITX Andy Yan
  8 siblings, 0 replies; 21+ messages in thread
From: Andy Yan @ 2025-04-03  3:37 UTC (permalink / raw)
  To: dmitry.baryshkov, heiko
  Cc: hjc, mripard, naoki, stephen, cristian.ciocaltea, neil.armstrong,
	Laurent.pinchart, yubing.zhang, krzk+dt, devicetree, dri-devel,
	linux-arm-kernel, linux-kernel, linux-rockchip, robh,
	sebastian.reichel, Andy Yan

From: Andy Yan <andy.yan@rock-chips.com>

The DP1 is compliant with the DisplayPort Specification
Version 1.4, and share the USBDP combo PHY1 with USB 3.1
HOST1 controller.

Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
---

(no changes since v1)

 .../arm64/boot/dts/rockchip/rk3588-extra.dtsi | 30 +++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi
index 83113a1098104..c498bf2da4fe8 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi
@@ -140,6 +140,36 @@ i2s10_8ch: i2s@fde00000 {
 		status = "disabled";
 	};
 
+	dp1: dp@fde60000 {
+		compatible = "rockchip,rk3588-dp";
+		reg = <0x0 0xfde60000 0x0 0x4000>;
+		interrupts = <GIC_SPI 162 IRQ_TYPE_LEVEL_HIGH 0>;
+		clocks = <&cru PCLK_DP1>, <&cru CLK_AUX16M_1>,
+			 <&cru CLK_DP1>, <&cru MCLK_I2S8_8CH_TX>,
+			 <&cru MCLK_SPDIF5_DP1>;
+		clock-names = "apb", "aux", "hdcp", "i2s", "spdif";
+		assigned-clocks = <&cru CLK_AUX16M_1>;
+		assigned-clock-rates = <16000000>;
+		resets = <&cru SRST_DP1>;
+		phys = <&usbdp_phy1 PHY_TYPE_DP>;
+		power-domains = <&power RK3588_PD_VO0>;
+		#sound-dai-cells = <0>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			dp1_in: port@0 {
+				reg = <0>;
+			};
+
+			dp1_out: port@1 {
+				reg = <1>;
+			};
+		};
+	};
+
 	hdmi1: hdmi@fdea0000 {
 		compatible = "rockchip,rk3588-dw-hdmi-qp";
 		reg = <0x0 0xfdea0000 0x0 0x20000>;
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v3 8/9] arm64: dts: rockchip: Enable DisplayPort for rk3588s Cool Pi 4B
  2025-04-03  3:37 [PATCH v3 0/9] Add support for RK3588 DisplayPort Controller Andy Yan
                   ` (6 preceding siblings ...)
  2025-04-03  3:37 ` [PATCH v3 7/9] arm64: dts: rockchip: Add DP1 " Andy Yan
@ 2025-04-03  3:37 ` Andy Yan
  2025-04-03  3:37 ` [PATCH v3 9/9] arm64: dts: rockchip: Enable DP2HDMI for ROCK 5 ITX Andy Yan
  8 siblings, 0 replies; 21+ messages in thread
From: Andy Yan @ 2025-04-03  3:37 UTC (permalink / raw)
  To: dmitry.baryshkov, heiko
  Cc: hjc, mripard, naoki, stephen, cristian.ciocaltea, neil.armstrong,
	Laurent.pinchart, yubing.zhang, krzk+dt, devicetree, dri-devel,
	linux-arm-kernel, linux-kernel, linux-rockchip, robh,
	sebastian.reichel, Andy Yan

From: Andy Yan <andy.yan@rock-chips.com>

Enable the Mini DisplayPort on this board.

Note that ROCKCHIP_VOP2_EP_DP0 is defined as 10 in dt-binding header,
but it will trigger a dtc warning like "graph node unit address error,
expected "a"" if we use it directly after endpoint, so we use "a"
instead here.

Signed-off-by: Andy Yan <andy.yan@rock-chips.com>

---

(no changes since v2)

Changes in v2:
- Sort in alphabetical order

 .../boot/dts/rockchip/rk3588s-coolpi-4b.dts   | 37 +++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts b/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts
index e892dd7c91aae..4f471da1d866a 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts
@@ -39,6 +39,18 @@ chosen {
 		stdout-path = "serial2:1500000n8";
 	};
 
+	dp-con {
+		compatible = "dp-connector";
+		label = "DP OUT";
+		type = "mini";
+
+		port {
+			dp_con_in: endpoint {
+				remote-endpoint = <&dp0_out_con>;
+			};
+		};
+	};
+
 	hdmi-con {
 		compatible = "hdmi-connector";
 		type = "d";
@@ -215,6 +227,24 @@ &cpu_b2 {
 	cpu-supply = <&vdd_cpu_big1_s0>;
 };
 
+&dp0 {
+	status = "okay";
+	pinctrl-0 = <&dp0m0_pins>;
+	pinctrl-names = "default";
+};
+
+&dp0_in {
+	dp0_in_vp2: endpoint {
+		remote-endpoint = <&vp2_out_dp0>;
+	};
+};
+
+&dp0_out {
+	dp0_out_con: endpoint {
+		remote-endpoint = <&dp_con_in>;
+	};
+};
+
 &gpu {
 	mali-supply = <&vdd_gpu_s0>;
 	status = "okay";
@@ -889,3 +919,10 @@ vp0_out_hdmi0: endpoint@ROCKCHIP_VOP2_EP_HDMI0 {
 		remote-endpoint = <&hdmi0_in_vp0>;
 	};
 };
+
+&vp2 {
+	vp2_out_dp0: endpoint@a {
+		reg = <ROCKCHIP_VOP2_EP_DP0>;
+		remote-endpoint = <&dp0_in_vp2>;
+	};
+};
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v3 9/9] arm64: dts: rockchip: Enable DP2HDMI for ROCK 5 ITX
  2025-04-03  3:37 [PATCH v3 0/9] Add support for RK3588 DisplayPort Controller Andy Yan
                   ` (7 preceding siblings ...)
  2025-04-03  3:37 ` [PATCH v3 8/9] arm64: dts: rockchip: Enable DisplayPort for rk3588s Cool Pi 4B Andy Yan
@ 2025-04-03  3:37 ` Andy Yan
  8 siblings, 0 replies; 21+ messages in thread
From: Andy Yan @ 2025-04-03  3:37 UTC (permalink / raw)
  To: dmitry.baryshkov, heiko
  Cc: hjc, mripard, naoki, stephen, cristian.ciocaltea, neil.armstrong,
	Laurent.pinchart, yubing.zhang, krzk+dt, devicetree, dri-devel,
	linux-arm-kernel, linux-kernel, linux-rockchip, robh,
	sebastian.reichel, Andy Yan

From: Andy Yan <andy.yan@rock-chips.com>

The HDMI0(Port next to Headphone Jack) is drived by DP1 on rk3588
via RA620(a dp2hdmi converter).

Add related dt nodes to enable it.

Note: ROCKCHIP_VOP2_EP_DP1 is defined as 11 in dt-binding header,
but it will trigger a dtc warning like "graph node unit address
error, expected "b"" if we use it directly after endpoint, so we
use "b" instead here.

Signed-off-by: Andy Yan <andy.yan@rock-chips.com>

---

Changes in v3:
- Add RA620 into bridge chain.

 .../boot/dts/rockchip/rk3588-rock-5-itx.dts   | 59 +++++++++++++++++++
 1 file changed, 59 insertions(+)

diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5-itx.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5-itx.dts
index 67b8863292487..bb61633a5b995 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5-itx.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5-itx.dts
@@ -57,6 +57,29 @@ analog-sound {
 			  "Headphone", "Headphones";
 	};
 
+	bridge {
+		compatible = "radxa,ra620";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				hdmi_bridge_in: endpoint {
+					remote-endpoint = <&dp1_out_con>;
+				};
+			};
+
+			port@1 {
+				reg = <1>;
+
+				hdmi_bridge_out: endpoint {
+					remote-endpoint = <&hdmi_con_in>;
+				};
+			};
+		};
+	};
+
 	gpio-leds {
 		compatible = "gpio-leds";
 		pinctrl-names = "default";
@@ -73,6 +96,17 @@ hdd-led2 {
 		};
 	};
 
+	hdmi0-con {
+		compatible = "hdmi-connector";
+		type = "a";
+
+		port {
+			hdmi_con_in: endpoint {
+				remote-endpoint = <&hdmi_bridge_out>;
+			};
+		};
+	};
+
 	hdmi1-con {
 		compatible = "hdmi-connector";
 		type = "a";
@@ -268,6 +302,24 @@ &cpu_l3 {
 	cpu-supply = <&vdd_cpu_lit_s0>;
 };
 
+&dp1 {
+	status = "okay";
+	pinctrl-names = "default";
+	pinctrl-0 = <&dp1m0_pins>;
+};
+
+&dp1_in {
+	dp1_in_vp2: endpoint {
+		remote-endpoint = <&vp2_out_dp1>;
+	};
+};
+
+&dp1_out {
+	dp1_out_con: endpoint {
+		remote-endpoint = <&hdmi_bridge_in>;
+	};
+};
+
 &gpu {
 	mali-supply = <&vdd_gpu_s0>;
 	status = "okay";
@@ -1262,3 +1314,10 @@ vp1_out_hdmi1: endpoint@ROCKCHIP_VOP2_EP_HDMI1 {
 		remote-endpoint = <&hdmi1_in_vp1>;
 	};
 };
+
+&vp2 {
+	vp2_out_dp1: endpoint@b {
+		reg = <ROCKCHIP_VOP2_EP_DP1>;
+		remote-endpoint = <&dp1_in_vp2>;
+	};
+};
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH v3 4/9] dt-bindings: display: simple-bridge: Add ra620 compatible
  2025-04-03  3:37 ` [PATCH v3 4/9] dt-bindings: display: simple-bridge: Add ra620 compatible Andy Yan
@ 2025-04-03  6:42   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 21+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-03  6:42 UTC (permalink / raw)
  To: Andy Yan
  Cc: dmitry.baryshkov, heiko, hjc, mripard, naoki, stephen,
	cristian.ciocaltea, neil.armstrong, Laurent.pinchart,
	yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel,
	linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan

On Thu, Apr 03, 2025 at 11:37:32AM +0800, Andy Yan wrote:
> From: Andy Yan <andy.yan@rock-chips.com>
> 
> RA620 is a DP to HDMI bridge converter from RADXA, which first
> found be used on ROCK 5 ITX.
> 
> This chip can be used without involving software.
> 
> Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
> 
> ---
> 
> Changes in v3:
> - First introduced in this version.

Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>

Best regards,
Krzysztof



^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3 5/9] drm/birdge: simple-bridge: Add support for radxa ra620
  2025-04-03  3:37 ` [PATCH v3 5/9] drm/birdge: simple-bridge: Add support for radxa ra620 Andy Yan
@ 2025-04-03 12:14   ` Dmitry Baryshkov
  0 siblings, 0 replies; 21+ messages in thread
From: Dmitry Baryshkov @ 2025-04-03 12:14 UTC (permalink / raw)
  To: Andy Yan
  Cc: heiko, hjc, mripard, naoki, stephen, cristian.ciocaltea,
	neil.armstrong, Laurent.pinchart, yubing.zhang, krzk+dt,
	devicetree, dri-devel, linux-arm-kernel, linux-kernel,
	linux-rockchip, robh, sebastian.reichel, Andy Yan

On Thu, Apr 03, 2025 at 11:37:33AM +0800, Andy Yan wrote:
> From: Andy Yan <andy.yan@rock-chips.com>
> 
> The RA620 is an active DP to HDMI converter chip, basically
> no software is involved to drive it.
> 
> Add it to simple bridge to make it can be find by the drm bridge chain.
> 
> Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
> 
> ---
> 
> Changes in v3:
> - First introduced in this version.
> 
>  drivers/gpu/drm/bridge/simple-bridge.c | 5 +++++
>  1 file changed, 5 insertions(+)
> 

Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>

-- 
With best wishes
Dmitry


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3 3/9] drm/rockchip: Add RK3588 DPTX output support
  2025-04-03  3:37 ` [PATCH v3 3/9] drm/rockchip: Add RK3588 DPTX output support Andy Yan
@ 2025-04-04  9:06   ` kernel test robot
  2025-04-04 12:11   ` kernel test robot
  2025-05-27 16:12   ` Nicolas Frattaroli
  2 siblings, 0 replies; 21+ messages in thread
From: kernel test robot @ 2025-04-04  9:06 UTC (permalink / raw)
  To: Andy Yan, dmitry.baryshkov, heiko
  Cc: llvm, oe-kbuild-all, hjc, mripard, naoki, stephen,
	cristian.ciocaltea, neil.armstrong, Laurent.pinchart,
	yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel,
	linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan,
	Dmitry Baryshkov

Hi Andy,

kernel test robot noticed the following build errors:

[auto build test ERROR on rockchip/for-next]
[also build test ERROR on robh/for-next drm-exynos/exynos-drm-next linus/master v6.14 next-20250404]
[cannot apply to drm/drm-next drm-intel/for-linux-next drm-intel/for-linux-next-fixes drm-misc/drm-misc-next drm-tip/drm-tip]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Andy-Yan/dt-bindings-display-rockchip-Add-schema-for-RK3588-DPTX-Controller/20250403-114203
base:   https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git for-next
patch link:    https://lore.kernel.org/r/20250403033748.245007-4-andyshrk%40163.com
patch subject: [PATCH v3 3/9] drm/rockchip: Add RK3588 DPTX output support
config: s390-allmodconfig (https://download.01.org/0day-ci/archive/20250404/202504041655.rA3EV7B8-lkp@intel.com/config)
compiler: clang version 18.1.8 (https://github.com/llvm/llvm-project 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250404/202504041655.rA3EV7B8-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202504041655.rA3EV7B8-lkp@intel.com/

All errors (new ones prefixed by >>):

>> drivers/gpu/drm/bridge/synopsys/dw-dp.c:1599:3: error: call to undeclared function 'drm_dp_link_power_down'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
    1599 |                 drm_dp_link_power_down(&dp->aux, dp->link.revision);
         |                 ^
>> drivers/gpu/drm/bridge/synopsys/dw-dp.c:1617:8: error: call to undeclared function 'drm_dp_link_power_up'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
    1617 |         ret = drm_dp_link_power_up(&dp->aux, dp->link.revision);
         |               ^
>> drivers/gpu/drm/bridge/synopsys/dw-dp.c:1790:19: error: incompatible function pointer types initializing 'void (*)(struct drm_bridge *, struct drm_bridge_state *)' with an expression of type 'void (struct drm_bridge *, struct drm_atomic_state *)' [-Wincompatible-function-pointer-types]
    1790 |         .atomic_enable = dw_dp_bridge_atomic_enable,
         |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/synopsys/dw-dp.c:1791:20: error: incompatible function pointer types initializing 'void (*)(struct drm_bridge *, struct drm_bridge_state *)' with an expression of type 'void (struct drm_bridge *, struct drm_atomic_state *)' [-Wincompatible-function-pointer-types]
    1791 |         .atomic_disable = dw_dp_bridge_atomic_disable,
         |                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   4 errors generated.


vim +1790 drivers/gpu/drm/bridge/synopsys/dw-dp.c

d366451bed980ac Andy Yan 2025-04-03  1593  
d366451bed980ac Andy Yan 2025-04-03  1594  static void dw_dp_link_disable(struct dw_dp *dp)
d366451bed980ac Andy Yan 2025-04-03  1595  {
d366451bed980ac Andy Yan 2025-04-03  1596  	struct dw_dp_link *link = &dp->link;
d366451bed980ac Andy Yan 2025-04-03  1597  
d366451bed980ac Andy Yan 2025-04-03  1598  	if (dw_dp_hpd_detect(dp))
d366451bed980ac Andy Yan 2025-04-03 @1599  		drm_dp_link_power_down(&dp->aux, dp->link.revision);
d366451bed980ac Andy Yan 2025-04-03  1600  
d366451bed980ac Andy Yan 2025-04-03  1601  	dw_dp_phy_xmit_enable(dp, 0);
d366451bed980ac Andy Yan 2025-04-03  1602  
d366451bed980ac Andy Yan 2025-04-03  1603  	phy_power_off(dp->phy);
d366451bed980ac Andy Yan 2025-04-03  1604  
d366451bed980ac Andy Yan 2025-04-03  1605  	link->train.clock_recovered = false;
d366451bed980ac Andy Yan 2025-04-03  1606  	link->train.channel_equalized = false;
d366451bed980ac Andy Yan 2025-04-03  1607  }
d366451bed980ac Andy Yan 2025-04-03  1608  
d366451bed980ac Andy Yan 2025-04-03  1609  static int dw_dp_link_enable(struct dw_dp *dp)
d366451bed980ac Andy Yan 2025-04-03  1610  {
d366451bed980ac Andy Yan 2025-04-03  1611  	int ret;
d366451bed980ac Andy Yan 2025-04-03  1612  
d366451bed980ac Andy Yan 2025-04-03  1613  	ret = phy_power_on(dp->phy);
d366451bed980ac Andy Yan 2025-04-03  1614  	if (ret)
d366451bed980ac Andy Yan 2025-04-03  1615  		return ret;
d366451bed980ac Andy Yan 2025-04-03  1616  
d366451bed980ac Andy Yan 2025-04-03 @1617  	ret = drm_dp_link_power_up(&dp->aux, dp->link.revision);
d366451bed980ac Andy Yan 2025-04-03  1618  	if (ret < 0)
d366451bed980ac Andy Yan 2025-04-03  1619  		return ret;
d366451bed980ac Andy Yan 2025-04-03  1620  
d366451bed980ac Andy Yan 2025-04-03  1621  	ret = dw_dp_link_train(dp);
d366451bed980ac Andy Yan 2025-04-03  1622  
d366451bed980ac Andy Yan 2025-04-03  1623  	return ret;
d366451bed980ac Andy Yan 2025-04-03  1624  }
d366451bed980ac Andy Yan 2025-04-03  1625  
d366451bed980ac Andy Yan 2025-04-03  1626  static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
d366451bed980ac Andy Yan 2025-04-03  1627  				       struct drm_atomic_state *state)
d366451bed980ac Andy Yan 2025-04-03  1628  {
d366451bed980ac Andy Yan 2025-04-03  1629  	struct dw_dp *dp = bridge_to_dp(bridge);
d366451bed980ac Andy Yan 2025-04-03  1630  	struct drm_connector *connector;
d366451bed980ac Andy Yan 2025-04-03  1631  	struct drm_connector_state *conn_state;
d366451bed980ac Andy Yan 2025-04-03  1632  	int ret;
d366451bed980ac Andy Yan 2025-04-03  1633  
d366451bed980ac Andy Yan 2025-04-03  1634  	connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
d366451bed980ac Andy Yan 2025-04-03  1635  	if (!connector) {
d366451bed980ac Andy Yan 2025-04-03  1636  		dev_err(dp->dev, "failed to get connector\n");
d366451bed980ac Andy Yan 2025-04-03  1637  		return;
d366451bed980ac Andy Yan 2025-04-03  1638  	}
d366451bed980ac Andy Yan 2025-04-03  1639  
d366451bed980ac Andy Yan 2025-04-03  1640  	conn_state = drm_atomic_get_new_connector_state(state, connector);
d366451bed980ac Andy Yan 2025-04-03  1641  	if (!conn_state) {
d366451bed980ac Andy Yan 2025-04-03  1642  		dev_err(dp->dev, "failed to get connector state\n");
d366451bed980ac Andy Yan 2025-04-03  1643  		return;
d366451bed980ac Andy Yan 2025-04-03  1644  	}
d366451bed980ac Andy Yan 2025-04-03  1645  
d366451bed980ac Andy Yan 2025-04-03  1646  	set_bit(0, dp->sdp_reg_bank);
d366451bed980ac Andy Yan 2025-04-03  1647  
d366451bed980ac Andy Yan 2025-04-03  1648  	ret = dw_dp_link_enable(dp);
d366451bed980ac Andy Yan 2025-04-03  1649  	if (ret < 0) {
d366451bed980ac Andy Yan 2025-04-03  1650  		dev_err(dp->dev, "failed to enable link: %d\n", ret);
d366451bed980ac Andy Yan 2025-04-03  1651  		return;
d366451bed980ac Andy Yan 2025-04-03  1652  	}
d366451bed980ac Andy Yan 2025-04-03  1653  
d366451bed980ac Andy Yan 2025-04-03  1654  	ret = dw_dp_video_enable(dp);
d366451bed980ac Andy Yan 2025-04-03  1655  	if (ret < 0) {
d366451bed980ac Andy Yan 2025-04-03  1656  		dev_err(dp->dev, "failed to enable video: %d\n", ret);
d366451bed980ac Andy Yan 2025-04-03  1657  		return;
d366451bed980ac Andy Yan 2025-04-03  1658  	}
d366451bed980ac Andy Yan 2025-04-03  1659  }
d366451bed980ac Andy Yan 2025-04-03  1660  
d366451bed980ac Andy Yan 2025-04-03  1661  static void dw_dp_reset(struct dw_dp *dp)
d366451bed980ac Andy Yan 2025-04-03  1662  {
d366451bed980ac Andy Yan 2025-04-03  1663  	int val;
d366451bed980ac Andy Yan 2025-04-03  1664  
d366451bed980ac Andy Yan 2025-04-03  1665  	disable_irq(dp->irq);
d366451bed980ac Andy Yan 2025-04-03  1666  	regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET,
d366451bed980ac Andy Yan 2025-04-03  1667  			   FIELD_PREP(CONTROLLER_RESET, 1));
d366451bed980ac Andy Yan 2025-04-03  1668  	udelay(10);
d366451bed980ac Andy Yan 2025-04-03  1669  	regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET,
d366451bed980ac Andy Yan 2025-04-03  1670  			   FIELD_PREP(CONTROLLER_RESET, 0));
d366451bed980ac Andy Yan 2025-04-03  1671  
d366451bed980ac Andy Yan 2025-04-03  1672  	dw_dp_init_hw(dp);
d366451bed980ac Andy Yan 2025-04-03  1673  	regmap_read_poll_timeout(dp->regmap, DW_DP_HPD_STATUS, val,
d366451bed980ac Andy Yan 2025-04-03  1674  				 FIELD_GET(HPD_HOT_PLUG, val), 200, 200000);
d366451bed980ac Andy Yan 2025-04-03  1675  	regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG);
d366451bed980ac Andy Yan 2025-04-03  1676  	enable_irq(dp->irq);
d366451bed980ac Andy Yan 2025-04-03  1677  }
d366451bed980ac Andy Yan 2025-04-03  1678  
d366451bed980ac Andy Yan 2025-04-03  1679  static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge,
d366451bed980ac Andy Yan 2025-04-03  1680  					struct drm_atomic_state *state)
d366451bed980ac Andy Yan 2025-04-03  1681  {
d366451bed980ac Andy Yan 2025-04-03  1682  	struct dw_dp *dp = bridge_to_dp(bridge);
d366451bed980ac Andy Yan 2025-04-03  1683  
d366451bed980ac Andy Yan 2025-04-03  1684  	dw_dp_video_disable(dp);
d366451bed980ac Andy Yan 2025-04-03  1685  	dw_dp_link_disable(dp);
d366451bed980ac Andy Yan 2025-04-03  1686  	bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
d366451bed980ac Andy Yan 2025-04-03  1687  	dw_dp_reset(dp);
d366451bed980ac Andy Yan 2025-04-03  1688  }
d366451bed980ac Andy Yan 2025-04-03  1689  
d366451bed980ac Andy Yan 2025-04-03  1690  static bool dw_dp_hpd_detect_link(struct dw_dp *dp)
d366451bed980ac Andy Yan 2025-04-03  1691  {
d366451bed980ac Andy Yan 2025-04-03  1692  	int ret;
d366451bed980ac Andy Yan 2025-04-03  1693  
d366451bed980ac Andy Yan 2025-04-03  1694  	ret = phy_power_on(dp->phy);
d366451bed980ac Andy Yan 2025-04-03  1695  	if (ret < 0)
d366451bed980ac Andy Yan 2025-04-03  1696  		return false;
d366451bed980ac Andy Yan 2025-04-03  1697  	ret = dw_dp_link_parse(dp);
d366451bed980ac Andy Yan 2025-04-03  1698  	phy_power_off(dp->phy);
d366451bed980ac Andy Yan 2025-04-03  1699  
d366451bed980ac Andy Yan 2025-04-03  1700  	return !ret;
d366451bed980ac Andy Yan 2025-04-03  1701  }
d366451bed980ac Andy Yan 2025-04-03  1702  
d366451bed980ac Andy Yan 2025-04-03  1703  static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge)
d366451bed980ac Andy Yan 2025-04-03  1704  {
d366451bed980ac Andy Yan 2025-04-03  1705  	struct dw_dp *dp = bridge_to_dp(bridge);
d366451bed980ac Andy Yan 2025-04-03  1706  
d366451bed980ac Andy Yan 2025-04-03  1707  	if (!dw_dp_hpd_detect(dp))
d366451bed980ac Andy Yan 2025-04-03  1708  		return connector_status_disconnected;
d366451bed980ac Andy Yan 2025-04-03  1709  
d366451bed980ac Andy Yan 2025-04-03  1710  	if (!dw_dp_hpd_detect_link(dp))
d366451bed980ac Andy Yan 2025-04-03  1711  		return connector_status_disconnected;
d366451bed980ac Andy Yan 2025-04-03  1712  
d366451bed980ac Andy Yan 2025-04-03  1713  	return connector_status_connected;
d366451bed980ac Andy Yan 2025-04-03  1714  }
d366451bed980ac Andy Yan 2025-04-03  1715  
d366451bed980ac Andy Yan 2025-04-03  1716  static const struct drm_edid *dw_dp_bridge_edid_read(struct drm_bridge *bridge,
d366451bed980ac Andy Yan 2025-04-03  1717  						     struct drm_connector *connector)
d366451bed980ac Andy Yan 2025-04-03  1718  {
d366451bed980ac Andy Yan 2025-04-03  1719  	struct dw_dp *dp = bridge_to_dp(bridge);
d366451bed980ac Andy Yan 2025-04-03  1720  	const struct drm_edid *edid;
d366451bed980ac Andy Yan 2025-04-03  1721  	int ret;
d366451bed980ac Andy Yan 2025-04-03  1722  
d366451bed980ac Andy Yan 2025-04-03  1723  	ret = phy_power_on(dp->phy);
d366451bed980ac Andy Yan 2025-04-03  1724  	if (ret)
d366451bed980ac Andy Yan 2025-04-03  1725  		return NULL;
d366451bed980ac Andy Yan 2025-04-03  1726  
d366451bed980ac Andy Yan 2025-04-03  1727  	edid = drm_edid_read_ddc(connector, &dp->aux.ddc);
d366451bed980ac Andy Yan 2025-04-03  1728  
d366451bed980ac Andy Yan 2025-04-03  1729  	phy_power_off(dp->phy);
d366451bed980ac Andy Yan 2025-04-03  1730  
d366451bed980ac Andy Yan 2025-04-03  1731  	return edid;
d366451bed980ac Andy Yan 2025-04-03  1732  }
d366451bed980ac Andy Yan 2025-04-03  1733  
d366451bed980ac Andy Yan 2025-04-03  1734  static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
d366451bed980ac Andy Yan 2025-04-03  1735  						    struct drm_bridge_state *bridge_state,
d366451bed980ac Andy Yan 2025-04-03  1736  						    struct drm_crtc_state *crtc_state,
d366451bed980ac Andy Yan 2025-04-03  1737  						    struct drm_connector_state *conn_state,
d366451bed980ac Andy Yan 2025-04-03  1738  						    unsigned int *num_output_fmts)
d366451bed980ac Andy Yan 2025-04-03  1739  {
d366451bed980ac Andy Yan 2025-04-03  1740  	struct dw_dp *dp = bridge_to_dp(bridge);
d366451bed980ac Andy Yan 2025-04-03  1741  	struct dw_dp_link *link = &dp->link;
d366451bed980ac Andy Yan 2025-04-03  1742  	struct drm_display_info *di = &conn_state->connector->display_info;
d366451bed980ac Andy Yan 2025-04-03  1743  	struct drm_display_mode mode = crtc_state->mode;
d366451bed980ac Andy Yan 2025-04-03  1744  	const struct dw_dp_output_format *fmt;
d366451bed980ac Andy Yan 2025-04-03  1745  	u32 i, j = 0;
d366451bed980ac Andy Yan 2025-04-03  1746  	u32 *output_fmts;
d366451bed980ac Andy Yan 2025-04-03  1747  
d366451bed980ac Andy Yan 2025-04-03  1748  	*num_output_fmts = 0;
d366451bed980ac Andy Yan 2025-04-03  1749  
d366451bed980ac Andy Yan 2025-04-03  1750  	output_fmts = kcalloc(ARRAY_SIZE(dw_dp_output_formats), sizeof(*output_fmts), GFP_KERNEL);
d366451bed980ac Andy Yan 2025-04-03  1751  	if (!output_fmts)
d366451bed980ac Andy Yan 2025-04-03  1752  		return NULL;
d366451bed980ac Andy Yan 2025-04-03  1753  
d366451bed980ac Andy Yan 2025-04-03  1754  	for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) {
d366451bed980ac Andy Yan 2025-04-03  1755  		fmt = &dw_dp_output_formats[i];
d366451bed980ac Andy Yan 2025-04-03  1756  
d366451bed980ac Andy Yan 2025-04-03  1757  		if (fmt->bpc > conn_state->max_bpc)
d366451bed980ac Andy Yan 2025-04-03  1758  			continue;
d366451bed980ac Andy Yan 2025-04-03  1759  
d366451bed980ac Andy Yan 2025-04-03  1760  		if (!(fmt->color_format & di->color_formats))
d366451bed980ac Andy Yan 2025-04-03  1761  			continue;
d366451bed980ac Andy Yan 2025-04-03  1762  
d366451bed980ac Andy Yan 2025-04-03  1763  		if (fmt->color_format == DRM_COLOR_FORMAT_YCBCR420 &&
d366451bed980ac Andy Yan 2025-04-03  1764  		    !link->vsc_sdp_supported)
d366451bed980ac Andy Yan 2025-04-03  1765  			continue;
d366451bed980ac Andy Yan 2025-04-03  1766  
d366451bed980ac Andy Yan 2025-04-03  1767  		if (fmt->color_format != DRM_COLOR_FORMAT_YCBCR420 &&
d366451bed980ac Andy Yan 2025-04-03  1768  		    drm_mode_is_420_only(di, &mode))
d366451bed980ac Andy Yan 2025-04-03  1769  			continue;
d366451bed980ac Andy Yan 2025-04-03  1770  
d366451bed980ac Andy Yan 2025-04-03  1771  		if (!dw_dp_bandwidth_ok(dp, &mode, fmt->bpp, link->lanes, link->rate))
d366451bed980ac Andy Yan 2025-04-03  1772  			continue;
d366451bed980ac Andy Yan 2025-04-03  1773  
d366451bed980ac Andy Yan 2025-04-03  1774  		output_fmts[j++] = fmt->bus_format;
d366451bed980ac Andy Yan 2025-04-03  1775  	}
d366451bed980ac Andy Yan 2025-04-03  1776  
d366451bed980ac Andy Yan 2025-04-03  1777  	*num_output_fmts = j;
d366451bed980ac Andy Yan 2025-04-03  1778  
d366451bed980ac Andy Yan 2025-04-03  1779  	return output_fmts;
d366451bed980ac Andy Yan 2025-04-03  1780  }
d366451bed980ac Andy Yan 2025-04-03  1781  
d366451bed980ac Andy Yan 2025-04-03  1782  static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
d366451bed980ac Andy Yan 2025-04-03  1783  	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
d366451bed980ac Andy Yan 2025-04-03  1784  	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
d366451bed980ac Andy Yan 2025-04-03  1785  	.atomic_reset = drm_atomic_helper_bridge_reset,
d366451bed980ac Andy Yan 2025-04-03  1786  	.atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
d366451bed980ac Andy Yan 2025-04-03  1787  	.atomic_get_output_bus_fmts = dw_dp_bridge_atomic_get_output_bus_fmts,
d366451bed980ac Andy Yan 2025-04-03  1788  	.atomic_check = dw_dp_bridge_atomic_check,
d366451bed980ac Andy Yan 2025-04-03  1789  	.mode_valid = dw_dp_bridge_mode_valid,
d366451bed980ac Andy Yan 2025-04-03 @1790  	.atomic_enable = dw_dp_bridge_atomic_enable,
d366451bed980ac Andy Yan 2025-04-03  1791  	.atomic_disable = dw_dp_bridge_atomic_disable,
d366451bed980ac Andy Yan 2025-04-03  1792  	.detect = dw_dp_bridge_detect,
d366451bed980ac Andy Yan 2025-04-03  1793  	.edid_read = dw_dp_bridge_edid_read,
d366451bed980ac Andy Yan 2025-04-03  1794  };
d366451bed980ac Andy Yan 2025-04-03  1795  

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3 3/9] drm/rockchip: Add RK3588 DPTX output support
  2025-04-03  3:37 ` [PATCH v3 3/9] drm/rockchip: Add RK3588 DPTX output support Andy Yan
  2025-04-04  9:06   ` kernel test robot
@ 2025-04-04 12:11   ` kernel test robot
  2025-05-27 16:12   ` Nicolas Frattaroli
  2 siblings, 0 replies; 21+ messages in thread
From: kernel test robot @ 2025-04-04 12:11 UTC (permalink / raw)
  To: Andy Yan, dmitry.baryshkov, heiko
  Cc: oe-kbuild-all, hjc, mripard, naoki, stephen, cristian.ciocaltea,
	neil.armstrong, Laurent.pinchart, yubing.zhang, krzk+dt,
	devicetree, dri-devel, linux-arm-kernel, linux-kernel,
	linux-rockchip, robh, sebastian.reichel, Andy Yan,
	Dmitry Baryshkov

Hi Andy,

kernel test robot noticed the following build errors:

[auto build test ERROR on rockchip/for-next]
[also build test ERROR on robh/for-next drm-exynos/exynos-drm-next linus/master v6.14 next-20250404]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Andy-Yan/dt-bindings-display-rockchip-Add-schema-for-RK3588-DPTX-Controller/20250403-114203
base:   https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git for-next
patch link:    https://lore.kernel.org/r/20250403033748.245007-4-andyshrk%40163.com
patch subject: [PATCH v3 3/9] drm/rockchip: Add RK3588 DPTX output support
config: s390-allyesconfig (https://download.01.org/0day-ci/archive/20250404/202504041920.g5XTp8Xp-lkp@intel.com/config)
compiler: s390-linux-gcc (GCC) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250404/202504041920.g5XTp8Xp-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202504041920.g5XTp8Xp-lkp@intel.com/

All errors (new ones prefixed by >>):

   drivers/gpu/drm/bridge/synopsys/dw-dp.c: In function 'dw_dp_link_disable':
>> drivers/gpu/drm/bridge/synopsys/dw-dp.c:1599:17: error: implicit declaration of function 'drm_dp_link_power_down' [-Wimplicit-function-declaration]
    1599 |                 drm_dp_link_power_down(&dp->aux, dp->link.revision);
         |                 ^~~~~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/synopsys/dw-dp.c: In function 'dw_dp_link_enable':
>> drivers/gpu/drm/bridge/synopsys/dw-dp.c:1617:15: error: implicit declaration of function 'drm_dp_link_power_up' [-Wimplicit-function-declaration]
    1617 |         ret = drm_dp_link_power_up(&dp->aux, dp->link.revision);
         |               ^~~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/synopsys/dw-dp.c: At top level:
>> drivers/gpu/drm/bridge/synopsys/dw-dp.c:1790:26: error: initialization of 'void (*)(struct drm_bridge *, struct drm_bridge_state *)' from incompatible pointer type 'void (*)(struct drm_bridge *, struct drm_atomic_state *)' [-Wincompatible-pointer-types]
    1790 |         .atomic_enable = dw_dp_bridge_atomic_enable,
         |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/synopsys/dw-dp.c:1790:26: note: (near initialization for 'dw_dp_bridge_funcs.atomic_enable')
   drivers/gpu/drm/bridge/synopsys/dw-dp.c:1791:27: error: initialization of 'void (*)(struct drm_bridge *, struct drm_bridge_state *)' from incompatible pointer type 'void (*)(struct drm_bridge *, struct drm_atomic_state *)' [-Wincompatible-pointer-types]
    1791 |         .atomic_disable = dw_dp_bridge_atomic_disable,
         |                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/synopsys/dw-dp.c:1791:27: note: (near initialization for 'dw_dp_bridge_funcs.atomic_disable')


vim +1790 drivers/gpu/drm/bridge/synopsys/dw-dp.c

d366451bed980ac Andy Yan 2025-04-03  1593  
d366451bed980ac Andy Yan 2025-04-03  1594  static void dw_dp_link_disable(struct dw_dp *dp)
d366451bed980ac Andy Yan 2025-04-03  1595  {
d366451bed980ac Andy Yan 2025-04-03  1596  	struct dw_dp_link *link = &dp->link;
d366451bed980ac Andy Yan 2025-04-03  1597  
d366451bed980ac Andy Yan 2025-04-03  1598  	if (dw_dp_hpd_detect(dp))
d366451bed980ac Andy Yan 2025-04-03 @1599  		drm_dp_link_power_down(&dp->aux, dp->link.revision);
d366451bed980ac Andy Yan 2025-04-03  1600  
d366451bed980ac Andy Yan 2025-04-03  1601  	dw_dp_phy_xmit_enable(dp, 0);
d366451bed980ac Andy Yan 2025-04-03  1602  
d366451bed980ac Andy Yan 2025-04-03  1603  	phy_power_off(dp->phy);
d366451bed980ac Andy Yan 2025-04-03  1604  
d366451bed980ac Andy Yan 2025-04-03  1605  	link->train.clock_recovered = false;
d366451bed980ac Andy Yan 2025-04-03  1606  	link->train.channel_equalized = false;
d366451bed980ac Andy Yan 2025-04-03  1607  }
d366451bed980ac Andy Yan 2025-04-03  1608  
d366451bed980ac Andy Yan 2025-04-03  1609  static int dw_dp_link_enable(struct dw_dp *dp)
d366451bed980ac Andy Yan 2025-04-03  1610  {
d366451bed980ac Andy Yan 2025-04-03  1611  	int ret;
d366451bed980ac Andy Yan 2025-04-03  1612  
d366451bed980ac Andy Yan 2025-04-03  1613  	ret = phy_power_on(dp->phy);
d366451bed980ac Andy Yan 2025-04-03  1614  	if (ret)
d366451bed980ac Andy Yan 2025-04-03  1615  		return ret;
d366451bed980ac Andy Yan 2025-04-03  1616  
d366451bed980ac Andy Yan 2025-04-03 @1617  	ret = drm_dp_link_power_up(&dp->aux, dp->link.revision);
d366451bed980ac Andy Yan 2025-04-03  1618  	if (ret < 0)
d366451bed980ac Andy Yan 2025-04-03  1619  		return ret;
d366451bed980ac Andy Yan 2025-04-03  1620  
d366451bed980ac Andy Yan 2025-04-03  1621  	ret = dw_dp_link_train(dp);
d366451bed980ac Andy Yan 2025-04-03  1622  
d366451bed980ac Andy Yan 2025-04-03  1623  	return ret;
d366451bed980ac Andy Yan 2025-04-03  1624  }
d366451bed980ac Andy Yan 2025-04-03  1625  
d366451bed980ac Andy Yan 2025-04-03  1626  static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
d366451bed980ac Andy Yan 2025-04-03  1627  				       struct drm_atomic_state *state)
d366451bed980ac Andy Yan 2025-04-03  1628  {
d366451bed980ac Andy Yan 2025-04-03  1629  	struct dw_dp *dp = bridge_to_dp(bridge);
d366451bed980ac Andy Yan 2025-04-03  1630  	struct drm_connector *connector;
d366451bed980ac Andy Yan 2025-04-03  1631  	struct drm_connector_state *conn_state;
d366451bed980ac Andy Yan 2025-04-03  1632  	int ret;
d366451bed980ac Andy Yan 2025-04-03  1633  
d366451bed980ac Andy Yan 2025-04-03  1634  	connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
d366451bed980ac Andy Yan 2025-04-03  1635  	if (!connector) {
d366451bed980ac Andy Yan 2025-04-03  1636  		dev_err(dp->dev, "failed to get connector\n");
d366451bed980ac Andy Yan 2025-04-03  1637  		return;
d366451bed980ac Andy Yan 2025-04-03  1638  	}
d366451bed980ac Andy Yan 2025-04-03  1639  
d366451bed980ac Andy Yan 2025-04-03  1640  	conn_state = drm_atomic_get_new_connector_state(state, connector);
d366451bed980ac Andy Yan 2025-04-03  1641  	if (!conn_state) {
d366451bed980ac Andy Yan 2025-04-03  1642  		dev_err(dp->dev, "failed to get connector state\n");
d366451bed980ac Andy Yan 2025-04-03  1643  		return;
d366451bed980ac Andy Yan 2025-04-03  1644  	}
d366451bed980ac Andy Yan 2025-04-03  1645  
d366451bed980ac Andy Yan 2025-04-03  1646  	set_bit(0, dp->sdp_reg_bank);
d366451bed980ac Andy Yan 2025-04-03  1647  
d366451bed980ac Andy Yan 2025-04-03  1648  	ret = dw_dp_link_enable(dp);
d366451bed980ac Andy Yan 2025-04-03  1649  	if (ret < 0) {
d366451bed980ac Andy Yan 2025-04-03  1650  		dev_err(dp->dev, "failed to enable link: %d\n", ret);
d366451bed980ac Andy Yan 2025-04-03  1651  		return;
d366451bed980ac Andy Yan 2025-04-03  1652  	}
d366451bed980ac Andy Yan 2025-04-03  1653  
d366451bed980ac Andy Yan 2025-04-03  1654  	ret = dw_dp_video_enable(dp);
d366451bed980ac Andy Yan 2025-04-03  1655  	if (ret < 0) {
d366451bed980ac Andy Yan 2025-04-03  1656  		dev_err(dp->dev, "failed to enable video: %d\n", ret);
d366451bed980ac Andy Yan 2025-04-03  1657  		return;
d366451bed980ac Andy Yan 2025-04-03  1658  	}
d366451bed980ac Andy Yan 2025-04-03  1659  }
d366451bed980ac Andy Yan 2025-04-03  1660  
d366451bed980ac Andy Yan 2025-04-03  1661  static void dw_dp_reset(struct dw_dp *dp)
d366451bed980ac Andy Yan 2025-04-03  1662  {
d366451bed980ac Andy Yan 2025-04-03  1663  	int val;
d366451bed980ac Andy Yan 2025-04-03  1664  
d366451bed980ac Andy Yan 2025-04-03  1665  	disable_irq(dp->irq);
d366451bed980ac Andy Yan 2025-04-03  1666  	regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET,
d366451bed980ac Andy Yan 2025-04-03  1667  			   FIELD_PREP(CONTROLLER_RESET, 1));
d366451bed980ac Andy Yan 2025-04-03  1668  	udelay(10);
d366451bed980ac Andy Yan 2025-04-03  1669  	regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET,
d366451bed980ac Andy Yan 2025-04-03  1670  			   FIELD_PREP(CONTROLLER_RESET, 0));
d366451bed980ac Andy Yan 2025-04-03  1671  
d366451bed980ac Andy Yan 2025-04-03  1672  	dw_dp_init_hw(dp);
d366451bed980ac Andy Yan 2025-04-03  1673  	regmap_read_poll_timeout(dp->regmap, DW_DP_HPD_STATUS, val,
d366451bed980ac Andy Yan 2025-04-03  1674  				 FIELD_GET(HPD_HOT_PLUG, val), 200, 200000);
d366451bed980ac Andy Yan 2025-04-03  1675  	regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG);
d366451bed980ac Andy Yan 2025-04-03  1676  	enable_irq(dp->irq);
d366451bed980ac Andy Yan 2025-04-03  1677  }
d366451bed980ac Andy Yan 2025-04-03  1678  
d366451bed980ac Andy Yan 2025-04-03  1679  static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge,
d366451bed980ac Andy Yan 2025-04-03  1680  					struct drm_atomic_state *state)
d366451bed980ac Andy Yan 2025-04-03  1681  {
d366451bed980ac Andy Yan 2025-04-03  1682  	struct dw_dp *dp = bridge_to_dp(bridge);
d366451bed980ac Andy Yan 2025-04-03  1683  
d366451bed980ac Andy Yan 2025-04-03  1684  	dw_dp_video_disable(dp);
d366451bed980ac Andy Yan 2025-04-03  1685  	dw_dp_link_disable(dp);
d366451bed980ac Andy Yan 2025-04-03  1686  	bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
d366451bed980ac Andy Yan 2025-04-03  1687  	dw_dp_reset(dp);
d366451bed980ac Andy Yan 2025-04-03  1688  }
d366451bed980ac Andy Yan 2025-04-03  1689  
d366451bed980ac Andy Yan 2025-04-03  1690  static bool dw_dp_hpd_detect_link(struct dw_dp *dp)
d366451bed980ac Andy Yan 2025-04-03  1691  {
d366451bed980ac Andy Yan 2025-04-03  1692  	int ret;
d366451bed980ac Andy Yan 2025-04-03  1693  
d366451bed980ac Andy Yan 2025-04-03  1694  	ret = phy_power_on(dp->phy);
d366451bed980ac Andy Yan 2025-04-03  1695  	if (ret < 0)
d366451bed980ac Andy Yan 2025-04-03  1696  		return false;
d366451bed980ac Andy Yan 2025-04-03  1697  	ret = dw_dp_link_parse(dp);
d366451bed980ac Andy Yan 2025-04-03  1698  	phy_power_off(dp->phy);
d366451bed980ac Andy Yan 2025-04-03  1699  
d366451bed980ac Andy Yan 2025-04-03  1700  	return !ret;
d366451bed980ac Andy Yan 2025-04-03  1701  }
d366451bed980ac Andy Yan 2025-04-03  1702  
d366451bed980ac Andy Yan 2025-04-03  1703  static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge)
d366451bed980ac Andy Yan 2025-04-03  1704  {
d366451bed980ac Andy Yan 2025-04-03  1705  	struct dw_dp *dp = bridge_to_dp(bridge);
d366451bed980ac Andy Yan 2025-04-03  1706  
d366451bed980ac Andy Yan 2025-04-03  1707  	if (!dw_dp_hpd_detect(dp))
d366451bed980ac Andy Yan 2025-04-03  1708  		return connector_status_disconnected;
d366451bed980ac Andy Yan 2025-04-03  1709  
d366451bed980ac Andy Yan 2025-04-03  1710  	if (!dw_dp_hpd_detect_link(dp))
d366451bed980ac Andy Yan 2025-04-03  1711  		return connector_status_disconnected;
d366451bed980ac Andy Yan 2025-04-03  1712  
d366451bed980ac Andy Yan 2025-04-03  1713  	return connector_status_connected;
d366451bed980ac Andy Yan 2025-04-03  1714  }
d366451bed980ac Andy Yan 2025-04-03  1715  
d366451bed980ac Andy Yan 2025-04-03  1716  static const struct drm_edid *dw_dp_bridge_edid_read(struct drm_bridge *bridge,
d366451bed980ac Andy Yan 2025-04-03  1717  						     struct drm_connector *connector)
d366451bed980ac Andy Yan 2025-04-03  1718  {
d366451bed980ac Andy Yan 2025-04-03  1719  	struct dw_dp *dp = bridge_to_dp(bridge);
d366451bed980ac Andy Yan 2025-04-03  1720  	const struct drm_edid *edid;
d366451bed980ac Andy Yan 2025-04-03  1721  	int ret;
d366451bed980ac Andy Yan 2025-04-03  1722  
d366451bed980ac Andy Yan 2025-04-03  1723  	ret = phy_power_on(dp->phy);
d366451bed980ac Andy Yan 2025-04-03  1724  	if (ret)
d366451bed980ac Andy Yan 2025-04-03  1725  		return NULL;
d366451bed980ac Andy Yan 2025-04-03  1726  
d366451bed980ac Andy Yan 2025-04-03  1727  	edid = drm_edid_read_ddc(connector, &dp->aux.ddc);
d366451bed980ac Andy Yan 2025-04-03  1728  
d366451bed980ac Andy Yan 2025-04-03  1729  	phy_power_off(dp->phy);
d366451bed980ac Andy Yan 2025-04-03  1730  
d366451bed980ac Andy Yan 2025-04-03  1731  	return edid;
d366451bed980ac Andy Yan 2025-04-03  1732  }
d366451bed980ac Andy Yan 2025-04-03  1733  
d366451bed980ac Andy Yan 2025-04-03  1734  static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
d366451bed980ac Andy Yan 2025-04-03  1735  						    struct drm_bridge_state *bridge_state,
d366451bed980ac Andy Yan 2025-04-03  1736  						    struct drm_crtc_state *crtc_state,
d366451bed980ac Andy Yan 2025-04-03  1737  						    struct drm_connector_state *conn_state,
d366451bed980ac Andy Yan 2025-04-03  1738  						    unsigned int *num_output_fmts)
d366451bed980ac Andy Yan 2025-04-03  1739  {
d366451bed980ac Andy Yan 2025-04-03  1740  	struct dw_dp *dp = bridge_to_dp(bridge);
d366451bed980ac Andy Yan 2025-04-03  1741  	struct dw_dp_link *link = &dp->link;
d366451bed980ac Andy Yan 2025-04-03  1742  	struct drm_display_info *di = &conn_state->connector->display_info;
d366451bed980ac Andy Yan 2025-04-03  1743  	struct drm_display_mode mode = crtc_state->mode;
d366451bed980ac Andy Yan 2025-04-03  1744  	const struct dw_dp_output_format *fmt;
d366451bed980ac Andy Yan 2025-04-03  1745  	u32 i, j = 0;
d366451bed980ac Andy Yan 2025-04-03  1746  	u32 *output_fmts;
d366451bed980ac Andy Yan 2025-04-03  1747  
d366451bed980ac Andy Yan 2025-04-03  1748  	*num_output_fmts = 0;
d366451bed980ac Andy Yan 2025-04-03  1749  
d366451bed980ac Andy Yan 2025-04-03  1750  	output_fmts = kcalloc(ARRAY_SIZE(dw_dp_output_formats), sizeof(*output_fmts), GFP_KERNEL);
d366451bed980ac Andy Yan 2025-04-03  1751  	if (!output_fmts)
d366451bed980ac Andy Yan 2025-04-03  1752  		return NULL;
d366451bed980ac Andy Yan 2025-04-03  1753  
d366451bed980ac Andy Yan 2025-04-03  1754  	for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) {
d366451bed980ac Andy Yan 2025-04-03  1755  		fmt = &dw_dp_output_formats[i];
d366451bed980ac Andy Yan 2025-04-03  1756  
d366451bed980ac Andy Yan 2025-04-03  1757  		if (fmt->bpc > conn_state->max_bpc)
d366451bed980ac Andy Yan 2025-04-03  1758  			continue;
d366451bed980ac Andy Yan 2025-04-03  1759  
d366451bed980ac Andy Yan 2025-04-03  1760  		if (!(fmt->color_format & di->color_formats))
d366451bed980ac Andy Yan 2025-04-03  1761  			continue;
d366451bed980ac Andy Yan 2025-04-03  1762  
d366451bed980ac Andy Yan 2025-04-03  1763  		if (fmt->color_format == DRM_COLOR_FORMAT_YCBCR420 &&
d366451bed980ac Andy Yan 2025-04-03  1764  		    !link->vsc_sdp_supported)
d366451bed980ac Andy Yan 2025-04-03  1765  			continue;
d366451bed980ac Andy Yan 2025-04-03  1766  
d366451bed980ac Andy Yan 2025-04-03  1767  		if (fmt->color_format != DRM_COLOR_FORMAT_YCBCR420 &&
d366451bed980ac Andy Yan 2025-04-03  1768  		    drm_mode_is_420_only(di, &mode))
d366451bed980ac Andy Yan 2025-04-03  1769  			continue;
d366451bed980ac Andy Yan 2025-04-03  1770  
d366451bed980ac Andy Yan 2025-04-03  1771  		if (!dw_dp_bandwidth_ok(dp, &mode, fmt->bpp, link->lanes, link->rate))
d366451bed980ac Andy Yan 2025-04-03  1772  			continue;
d366451bed980ac Andy Yan 2025-04-03  1773  
d366451bed980ac Andy Yan 2025-04-03  1774  		output_fmts[j++] = fmt->bus_format;
d366451bed980ac Andy Yan 2025-04-03  1775  	}
d366451bed980ac Andy Yan 2025-04-03  1776  
d366451bed980ac Andy Yan 2025-04-03  1777  	*num_output_fmts = j;
d366451bed980ac Andy Yan 2025-04-03  1778  
d366451bed980ac Andy Yan 2025-04-03  1779  	return output_fmts;
d366451bed980ac Andy Yan 2025-04-03  1780  }
d366451bed980ac Andy Yan 2025-04-03  1781  
d366451bed980ac Andy Yan 2025-04-03  1782  static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
d366451bed980ac Andy Yan 2025-04-03  1783  	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
d366451bed980ac Andy Yan 2025-04-03  1784  	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
d366451bed980ac Andy Yan 2025-04-03  1785  	.atomic_reset = drm_atomic_helper_bridge_reset,
d366451bed980ac Andy Yan 2025-04-03  1786  	.atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
d366451bed980ac Andy Yan 2025-04-03  1787  	.atomic_get_output_bus_fmts = dw_dp_bridge_atomic_get_output_bus_fmts,
d366451bed980ac Andy Yan 2025-04-03  1788  	.atomic_check = dw_dp_bridge_atomic_check,
d366451bed980ac Andy Yan 2025-04-03  1789  	.mode_valid = dw_dp_bridge_mode_valid,
d366451bed980ac Andy Yan 2025-04-03 @1790  	.atomic_enable = dw_dp_bridge_atomic_enable,
d366451bed980ac Andy Yan 2025-04-03  1791  	.atomic_disable = dw_dp_bridge_atomic_disable,
d366451bed980ac Andy Yan 2025-04-03  1792  	.detect = dw_dp_bridge_detect,
d366451bed980ac Andy Yan 2025-04-03  1793  	.edid_read = dw_dp_bridge_edid_read,
d366451bed980ac Andy Yan 2025-04-03  1794  };
d366451bed980ac Andy Yan 2025-04-03  1795  

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3 1/9] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller
  2025-04-03  3:37 ` [PATCH v3 1/9] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller Andy Yan
@ 2025-04-04 19:36   ` Diederik de Haas
  0 siblings, 0 replies; 21+ messages in thread
From: Diederik de Haas @ 2025-04-04 19:36 UTC (permalink / raw)
  To: Andy Yan, dmitry.baryshkov, heiko
  Cc: neil.armstrong, sebastian.reichel, stephen, dri-devel, hjc,
	mripard, linux-kernel, linux-rockchip, devicetree, yubing.zhang,
	naoki, Laurent.pinchart, Andy Yan, krzk+dt, robh,
	linux-arm-kernel

[-- Attachment #1: Type: text/plain, Size: 2845 bytes --]

On Thu Apr 3, 2025 at 5:37 AM CEST, Andy Yan wrote:
> From: Andy Yan <andy.yan@rock-chips.com>
>
> The Rockchip RK3588 SoC integrates the Synopsys DesignWare DPTX
> controller. And this DPTX controller need share a USBDP PHY with
> the USB 3.0 OTG controller during operation.
>
> Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
> Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
>
> ---
>
> (no changes since v2)
>
> Changes in v2:
> - Link to V1: https://lore.kernel.org/linux-rockchip/20250223113036.74252-1-andyshrk@163.com/
> - Fix a character encoding issue
>
>  .../display/rockchip/rockchip,dw-dp.yaml      | 150 ++++++++++++++++++
>  1 file changed, 150 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
>
> diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
> new file mode 100644
> index 0000000000000..a8a0087179972
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
> @@ -0,0 +1,150 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/rockchip/rockchip,dw-dp.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Rockchip DW DisplayPort Transmitter
> +
> +maintainers:
> +  - Andy Yan <andy.yan@rock-chips.com>
> +
> +description: |
> +  The Rockchip RK3588 SoC integrates the Synopsys DesignWare DPTX controller
> +  which is compliant with the DisplayPort Specification Version 1.4 with the
> +  following features:
> +
> +  * DisplayPort 1.4a
> +  * Main Link: 1/2/4 lanes
> +  * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps
> +  * AUX channel 1Mbps
> +  * Single Stream Transport(SST)
> +  * Multistream Transport (MST)
> +  * Type-C support (alternate mode)
> +  * HDCP 2.2, HDCP 1.3
> +  * Supports up to 8/10 bits per color component
> +  * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0
> +  * Pixel clock up to 594MHz
> +  * I2S, SPDIF audio interface
> +
> +allOf:
> +  - $ref: /schemas/sound/dai-common.yaml#
> +
> +properties:
> +  compatible:
> +    enum:
> +      - rockchip,rk3588-dp
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  clocks:
> +    items:
> +      - description: Peripheral/APB bus clock
> +      - description: DisplayPort AUX clock
> +      - description: HDCP clock
> +      - description: I2S interface clock
> +      - description: SPDIF interfce clock

s/interfce/interface/

Cheers,
  Diederik

> +
> +  clock-names:
> +    items:
> +      - const: apb
> +      - const: aux
> +      - const: hdcp
> +      - const: i2s
> +      - const: spdif

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re:[PATCH v3 2/9] drm/bridge: synopsys: Add DW DPTX Controller support library
  2025-04-03  3:37 ` [PATCH v3 2/9] drm/bridge: synopsys: Add DW DPTX Controller support library Andy Yan
@ 2025-05-14 12:15   ` Andy Yan
  2025-05-24  7:46     ` [PATCH " Dmitry Baryshkov
  0 siblings, 1 reply; 21+ messages in thread
From: Andy Yan @ 2025-05-14 12:15 UTC (permalink / raw)
  To: dmitry.baryshkov, heiko
  Cc: neil.armstrong, sebastian.reichel, stephen, dri-devel, hjc,
	mripard, linux-kernel, linux-rockchip, devicetree, yubing.zhang,
	naoki, Laurent.pinchart, Andy Yan, krzk+dt, robh,
	linux-arm-kernel

Hello Dmitry,

    Would it be convenient for you to continue reviewing this patch at your convenience?
Or let me know your opinion on this patch.
 
   There is still one unresolved issue highlighted by you in the first version: try to use drm_dp_read_sink_count_cap
instead of dw_dp_has_sink_count. But there is no response on my patch try to Pass down connector to drm bridge detect hook[0].
   I don't know how to proceed with this patch at the moment.


[0]https://lore.kernel.org/dri-devel/20250321085345.136380-1-andyshrk@163.com/

Thank you.
  
At 2025-04-03 11:37:30, "Andy Yan" <andyshrk@163.com> wrote:
>From: Andy Yan <andy.yan@rock-chips.com>
>
>The DW DP TX Controller is compliant with the DisplayPort Specification
>Version 1.4 with the following features:
>
>* DisplayPort 1.4a
>* Main Link: 1/2/4 lanes
>* Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps
>* AUX channel 1Mbps
>* Single Stream Transport(SST)
>* Multistream Transport (MST)
>* Type-C support (alternate mode)
>* HDCP 2.2, HDCP 1.3
>* Supports up to 8/10 bits per color component
>* Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0
>* Pixel clock up to 594MHz
>* I2S, SPDIF audio interface
>
>Add library with common helpers to make it can be shared with
>other SoC.
>
>Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
>
>---
>
>Changes in v3:
>- Rebase on drm-misc-next
>- Switch to common helpers to power up/down dp link
>- Only pass parameters to phy that should be set
>
>Changes in v2:
>- Fix compile error when build as module
>- Add phy init
>- Only use one dw_dp_link_train_set
>- inline dw_dp_phy_update_vs_emph
>- Use dp_sdp
>- Check return value of drm_modeset_lock
>- Merge code in atomic_pre_enable/mode_fixup to atomic_check
>- Return NULL if can't find a supported output format
>- Fix max_link_rate from plat_data
>
> drivers/gpu/drm/bridge/synopsys/Kconfig  |    7 +
> drivers/gpu/drm/bridge/synopsys/Makefile |    1 +
> drivers/gpu/drm/bridge/synopsys/dw-dp.c  | 2053 ++++++++++++++++++++++
> include/drm/bridge/dw_dp.h               |   20 +
> 4 files changed, 2081 insertions(+)
> create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c
> create mode 100644 include/drm/bridge/dw_dp.h
>
>diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig
>index f3ab2f985f8ca..2c5e532410de9 100644
>--- a/drivers/gpu/drm/bridge/synopsys/Kconfig
>+++ b/drivers/gpu/drm/bridge/synopsys/Kconfig
>@@ -1,4 +1,11 @@
> # SPDX-License-Identifier: GPL-2.0-only
>+config DRM_DW_DP
>+	tristate
>+	select DRM_DISPLAY_HELPER
>+	select DRM_DISPLAY_DP_HELPER
>+	select DRM_KMS_HELPER
>+	select REGMAP_MMIO
>+
> config DRM_DW_HDMI
> 	tristate
> 	select DRM_DISPLAY_HDMI_HELPER
>diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile
>index 9dc376d220ad7..4dada44029acf 100644
>--- a/drivers/gpu/drm/bridge/synopsys/Makefile
>+++ b/drivers/gpu/drm/bridge/synopsys/Makefile
>@@ -1,4 +1,5 @@
> # SPDX-License-Identifier: GPL-2.0-only
>+obj-$(CONFIG_DRM_DW_DP) += dw-dp.o
> 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_GP_AUDIO) += dw-hdmi-gp-audio.o
>diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
>new file mode 100644
>index 0000000000000..828b34d676a19
>--- /dev/null
>+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
>@@ -0,0 +1,2053 @@
>+// SPDX-License-Identifier: GPL-2.0
>+/*
>+ * Synopsys DesignWare Cores DisplayPort Transmitter Controller
>+ *
>+ * Copyright (c) 2025 Rockchip Electronics Co., Ltd.
>+ *
>+ * Author: Andy Yan <andy.yan@rock-chips.com>
>+ */
>+#include <linux/bitfield.h>
>+#include <linux/clk.h>
>+#include <linux/component.h>
>+#include <linux/iopoll.h>
>+#include <linux/irq.h>
>+#include <linux/media-bus-format.h>
>+#include <linux/of_device.h>
>+#include <linux/of_graph.h>
>+#include <linux/platform_device.h>
>+#include <linux/regmap.h>
>+#include <linux/reset.h>
>+#include <linux/phy/phy.h>
>+#include <linux/unaligned.h>
>+
>+#include <drm/bridge/dw_dp.h>
>+#include <drm/drm_atomic_helper.h>
>+#include <drm/drm_bridge.h>
>+#include <drm/drm_bridge_connector.h>
>+#include <drm/display/drm_dp_helper.h>
>+#include <drm/display/drm_hdmi_helper.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_simple_kms_helper.h>
>+
>+#define DW_DP_VERSION_NUMBER			0x0000
>+#define DW_DP_VERSION_TYPE			0x0004
>+#define DW_DP_ID					0x0008
>+
>+#define DW_DP_CONFIG_REG1			0x0100
>+#define DW_DP_CONFIG_REG2			0x0104
>+#define DW_DP_CONFIG_REG3			0x0108
>+
>+#define DW_DP_CCTL				0x0200
>+#define FORCE_HPD				BIT(4)
>+#define DEFAULT_FAST_LINK_TRAIN_EN		BIT(2)
>+#define ENHANCE_FRAMING_EN			BIT(1)
>+#define SCRAMBLE_DIS				BIT(0)
>+#define DW_DP_SOFT_RESET_CTRL			0x0204
>+#define VIDEO_RESET				BIT(5)
>+#define AUX_RESET				BIT(4)
>+#define AUDIO_SAMPLER_RESET			BIT(3)
>+#define HDCP_MODULE_RESET			BIT(2)
>+#define PHY_SOFT_RESET				BIT(1)
>+#define CONTROLLER_RESET			BIT(0)
>+
>+#define DW_DP_VSAMPLE_CTRL			0x0300
>+#define PIXEL_MODE_SELECT			GENMASK(22, 21)
>+#define VIDEO_MAPPING				GENMASK(20, 16)
>+#define VIDEO_STREAM_ENABLE			BIT(5)
>+
>+#define DW_DP_VSAMPLE_STUFF_CTRL1		0x0304
>+
>+#define DW_DP_VSAMPLE_STUFF_CTRL2		0x0308
>+
>+#define DW_DP_VINPUT_POLARITY_CTRL		0x030c
>+#define DE_IN_POLARITY				BIT(2)
>+#define HSYNC_IN_POLARITY			BIT(1)
>+#define VSYNC_IN_POLARITY			BIT(0)
>+
>+#define DW_DP_VIDEO_CONFIG1			0x0310
>+#define HACTIVE					GENMASK(31, 16)
>+#define HBLANK					GENMASK(15, 2)
>+#define I_P					BIT(1)
>+#define R_V_BLANK_IN_OSC			BIT(0)
>+
>+#define DW_DP_VIDEO_CONFIG2			0x0314
>+#define VBLANK					GENMASK(31, 16)
>+#define VACTIVE					GENMASK(15, 0)
>+
>+#define DW_DP_VIDEO_CONFIG3			0x0318
>+#define H_SYNC_WIDTH				GENMASK(31, 16)
>+#define H_FRONT_PORCH				GENMASK(15, 0)
>+
>+#define DW_DP_VIDEO_CONFIG4			0x031c
>+#define V_SYNC_WIDTH				GENMASK(31, 16)
>+#define V_FRONT_PORCH				GENMASK(15, 0)
>+
>+#define DW_DP_VIDEO_CONFIG5			0x0320
>+#define INIT_THRESHOLD_HI			GENMASK(22, 21)
>+#define AVERAGE_BYTES_PER_TU_FRAC		GENMASK(19, 16)
>+#define INIT_THRESHOLD				GENMASK(13, 7)
>+#define AVERAGE_BYTES_PER_TU			GENMASK(6, 0)
>+
>+#define DW_DP_VIDEO_MSA1				0x0324
>+#define VSTART					GENMASK(31, 16)
>+#define HSTART					GENMASK(15, 0)
>+
>+#define DW_DP_VIDEO_MSA2			0x0328
>+#define MISC0					GENMASK(31, 24)
>+
>+#define DW_DP_VIDEO_MSA3			0x032c
>+#define MISC1					GENMASK(31, 24)
>+
>+#define DW_DP_VIDEO_HBLANK_INTERVAL		0x0330
>+#define HBLANK_INTERVAL_EN			BIT(16)
>+#define HBLANK_INTERVAL				GENMASK(15, 0)
>+
>+#define DW_DP_AUD_CONFIG1			0x0400
>+#define AUDIO_TIMESTAMP_VERSION_NUM		GENMASK(29, 24)
>+#define AUDIO_PACKET_ID				GENMASK(23, 16)
>+#define AUDIO_MUTE				BIT(15)
>+#define NUM_CHANNELS				GENMASK(14, 12)
>+#define HBR_MODE_ENABLE				BIT(10)
>+#define AUDIO_DATA_WIDTH			GENMASK(9, 5)
>+#define AUDIO_DATA_IN_EN			GENMASK(4, 1)
>+#define AUDIO_INF_SELECT			BIT(0)
>+
>+#define DW_DP_SDP_VERTICAL_CTRL			0x0500
>+#define EN_VERTICAL_SDP				BIT(2)
>+#define EN_AUDIO_STREAM_SDP			BIT(1)
>+#define EN_AUDIO_TIMESTAMP_SDP			BIT(0)
>+#define DW_DP_SDP_HORIZONTAL_CTRL		0x0504
>+#define EN_HORIZONTAL_SDP			BIT(2)
>+#define DW_DP_SDP_STATUS_REGISTER		0x0508
>+#define DW_DP_SDP_MANUAL_CTRL			0x050c
>+#define DW_DP_SDP_STATUS_EN			0x0510
>+
>+#define DW_DP_SDP_REGISTER_BANK			0x0600
>+#define SDP_REGS				GENMASK(31, 0)
>+
>+#define DW_DP_PHYIF_CTRL			0x0a00
>+#define PHY_WIDTH				BIT(25)
>+#define PHY_POWERDOWN				GENMASK(20, 17)
>+#define PHY_BUSY				GENMASK(15, 12)
>+#define SSC_DIS					BIT(16)
>+#define XMIT_ENABLE				GENMASK(11, 8)
>+#define PHY_LANES				GENMASK(7, 6)
>+#define PHY_RATE				GENMASK(5, 4)
>+#define TPS_SEL					GENMASK(3, 0)
>+
>+#define DW_DP_PHY_TX_EQ				0x0a04
>+#define DW_DP_CUSTOMPAT0			0x0a08
>+#define DW_DP_CUSTOMPAT1			0x0a0c
>+#define DW_DP_CUSTOMPAT2			0x0a10
>+#define DW_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET	0x0a14
>+#define DW_DP_PHYIF_PWRDOWN_CTRL		0x0a18
>+
>+#define DW_DP_AUX_CMD				0x0b00
>+#define AUX_CMD_TYPE				GENMASK(31, 28)
>+#define AUX_ADDR				GENMASK(27, 8)
>+#define I2C_ADDR_ONLY				BIT(4)
>+#define AUX_LEN_REQ				GENMASK(3, 0)
>+
>+#define DW_DP_AUX_STATUS			0x0b04
>+#define AUX_TIMEOUT				BIT(17)
>+#define AUX_BYTES_READ				GENMASK(23, 19)
>+#define AUX_STATUS				GENMASK(7, 4)
>+
>+#define DW_DP_AUX_DATA0				0x0b08
>+#define DW_DP_AUX_DATA1				0x0b0c
>+#define DW_DP_AUX_DATA2				0x0b10
>+#define DW_DP_AUX_DATA3				0x0b14
>+
>+#define DW_DP_GENERAL_INTERRUPT			0x0d00
>+#define VIDEO_FIFO_OVERFLOW_STREAM0		BIT(6)
>+#define AUDIO_FIFO_OVERFLOW_STREAM0		BIT(5)
>+#define SDP_EVENT_STREAM0			BIT(4)
>+#define AUX_CMD_INVALID				BIT(3)
>+#define HDCP_EVENT				BIT(2)
>+#define AUX_REPLY_EVENT				BIT(1)
>+#define HPD_EVENT				BIT(0)
>+
>+#define DW_DP_GENERAL_INTERRUPT_ENABLE		0x0d04
>+#define HDCP_EVENT_EN				BIT(2)
>+#define AUX_REPLY_EVENT_EN			BIT(1)
>+#define HPD_EVENT_EN				BIT(0)
>+
>+#define DW_DP_HPD_STATUS			0x0d08
>+#define HPD_STATE				GENMASK(11, 9)
>+#define HPD_STATUS				BIT(8)
>+#define HPD_HOT_UNPLUG				BIT(2)
>+#define HPD_HOT_PLUG				BIT(1)
>+#define HPD_IRQ					BIT(0)
>+
>+#define DW_DP_HPD_INTERRUPT_ENABLE		0x0d0c
>+#define HPD_UNPLUG_ERR_EN			BIT(3)
>+#define HPD_UNPLUG_EN				BIT(2)
>+#define HPD_PLUG_EN				BIT(1)
>+#define HPD_IRQ_EN				BIT(0)
>+
>+#define DW_DP_HDCP_CFG				0x0e00
>+#define DPCD12PLUS				BIT(7)
>+#define CP_IRQ					BIT(6)
>+#define BYPENCRYPTION				BIT(5)
>+#define HDCP_LOCK				BIT(4)
>+#define ENCRYPTIONDISABLE			BIT(3)
>+#define ENABLE_HDCP_13				BIT(2)
>+#define ENABLE_HDCP				BIT(1)
>+
>+#define DW_DP_HDCP_OBS				0x0e04
>+#define HDCP22_RE_AUTHENTICATION_REQ		BIT(31)
>+#define HDCP22_AUTHENTICATION_FAILED		BIT(30)
>+#define HDCP22_AUTHENTICATION_SUCCESS		BIT(29)
>+#define HDCP22_CAPABLE_SINK			BIT(28)
>+#define HDCP22_SINK_CAP_CHECK_COMPLETE		BIT(27)
>+#define HDCP22_STATE				GENMASK(26, 24)
>+#define HDCP22_BOOTED				BIT(23)
>+#define HDCP13_BSTATUS				GENMASK(22, 19)
>+#define REPEATER				BIT(18)
>+#define HDCP_CAPABLE				BIT(17)
>+#define STATEE					GENMASK(16, 14)
>+#define STATEOEG				GENMASK(13, 11)
>+#define STATER					GENMASK(10, 8)
>+#define STATEA					GENMASK(7, 4)
>+#define SUBSTATEA				GENMASK(3, 1)
>+#define HDCPENGAGED				BIT(0)
>+
>+#define DW_DP_HDCP_APIINTCLR			0x0e08
>+#define DW_DP_HDCP_APIINTSTAT			0x0e0c
>+#define DW_DP_HDCP_APIINTMSK			0x0e10
>+#define HDCP22_GPIOINT				BIT(8)
>+#define HDCP_ENGAGED				BIT(7)
>+#define HDCP_FAILED				BIT(6)
>+#define KSVSHA1CALCDONEINT			BIT(5)
>+#define AUXRESPNACK7TIMES			BIT(4)
>+#define AUXRESPTIMEOUT				BIT(3)
>+#define AUXRESPDEFER7TIMES			BIT(2)
>+#define KSVACCESSINT				BIT(0)
>+
>+#define DW_DP_HDCP_KSVMEMCTRL			0x0e18
>+#define KSVSHA1STATUS				BIT(4)
>+#define KSVMEMACCESS				BIT(1)
>+#define KSVMEMREQUEST				BIT(0)
>+
>+#define DW_DP_HDCP_REG_BKSV0			0x3600
>+#define DW_DP_HDCP_REG_BKSV1			0x3604
>+#define DW_DP_HDCP_REG_ANCONF			0x3608
>+#define AN_BYPASS				BIT(0)
>+
>+#define DW_DP_HDCP_REG_AN0			0x360c
>+#define DW_DP_HDCP_REG_AN1			0x3610
>+#define DW_DP_HDCP_REG_RMLCTL			0x3614
>+#define ODPK_DECRYPT_ENABLE			BIT(0)
>+
>+#define DW_DP_HDCP_REG_RMLSTS			0x3618
>+#define IDPK_WR_OK_STS				BIT(6)
>+#define	IDPK_DATA_INDEX				GENMASK(5, 0)
>+#define DW_DP_HDCP_REG_SEED			0x361c
>+#define DW_DP_HDCP_REG_DPK0			0x3620
>+#define DW_DP_HDCP_REG_DPK1			0x3624
>+#define DW_DP_HDCP22_GPIOSTS			0x3628
>+#define DW_DP_HDCP22_GPIOCHNGSTS		0x362c
>+#define DW_DP_HDCP_REG_DPK_CRC			0x3630
>+
>+#define DW_DP_MAX_REGISTER			DW_DP_HDCP_REG_DPK_CRC
>+
>+#define SDP_REG_BANK_SIZE			16
>+
>+struct dw_dp_link_caps {
>+	bool enhanced_framing;
>+	bool tps3_supported;
>+	bool tps4_supported;
>+	bool fast_training;
>+	bool channel_coding;
>+	bool ssc;
>+};
>+
>+struct dw_dp_link_train_set {
>+	unsigned int voltage_swing[4];
>+	unsigned int pre_emphasis[4];
>+	bool voltage_max_reached[4];
>+	bool pre_max_reached[4];
>+};
>+
>+struct dw_dp_link_train {
>+	struct dw_dp_link_train_set adjust;
>+	bool clock_recovered;
>+	bool channel_equalized;
>+};
>+
>+struct dw_dp_link {
>+	u8 dpcd[DP_RECEIVER_CAP_SIZE];
>+	unsigned char revision;
>+	unsigned int rate;
>+	unsigned int lanes;
>+	u8 sink_count;
>+	u8 vsc_sdp_supported;
>+	struct dw_dp_link_caps caps;
>+	struct dw_dp_link_train train;
>+	struct drm_dp_desc desc;
>+};
>+
>+struct dw_dp_video {
>+	struct drm_display_mode mode;
>+	u8 video_mapping;
>+	u8 pixel_mode;
>+	u8 color_format;
>+	u8 bpc;
>+	u8 bpp;
>+};
>+
>+struct dw_dp_sdp {
>+	struct dp_sdp base;
>+	unsigned long flags;
>+};
>+
>+struct dw_dp_hotplug {
>+	bool long_hpd;
>+};
>+
>+struct dw_dp {
>+	struct drm_bridge bridge;
>+	struct device *dev;
>+	struct regmap *regmap;
>+	struct phy *phy;
>+	struct clk *apb_clk;
>+	struct clk *aux_clk;
>+	struct clk *i2s_clk;
>+	struct clk *spdif_clk;
>+	struct clk *hdcp_clk;
>+	struct reset_control *rstc;
>+	struct completion complete;
>+	int irq;
>+	struct work_struct hpd_work;
>+	struct dw_dp_hotplug hotplug;
>+	struct mutex irq_lock;
>+
>+	struct drm_dp_aux aux;
>+
>+	struct dw_dp_link link;
>+	struct dw_dp_video video;
>+	struct dw_dp_plat_data plat_data;
>+
>+	DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE);
>+};
>+
>+enum {
>+	DW_DP_RGB_6BIT,
>+	DW_DP_RGB_8BIT,
>+	DW_DP_RGB_10BIT,
>+	DW_DP_RGB_12BIT,
>+	DW_DP_RGB_16BIT,
>+	DW_DP_YCBCR444_8BIT,
>+	DW_DP_YCBCR444_10BIT,
>+	DW_DP_YCBCR444_12BIT,
>+	DW_DP_YCBCR444_16BIT,
>+	DW_DP_YCBCR422_8BIT,
>+	DW_DP_YCBCR422_10BIT,
>+	DW_DP_YCBCR422_12BIT,
>+	DW_DP_YCBCR422_16BIT,
>+	DW_DP_YCBCR420_8BIT,
>+	DW_DP_YCBCR420_10BIT,
>+	DW_DP_YCBCR420_12BIT,
>+	DW_DP_YCBCR420_16BIT,
>+};
>+
>+enum {
>+	DW_DP_MP_SINGLE_PIXEL,
>+	DW_DP_MP_DUAL_PIXEL,
>+	DW_DP_MP_QUAD_PIXEL,
>+};
>+
>+enum {
>+	DW_DP_SDP_VERTICAL_INTERVAL = BIT(0),
>+	DW_DP_SDP_HORIZONTAL_INTERVAL = BIT(1),
>+};
>+
>+enum {
>+	DW_DP_HPD_STATE_IDLE,
>+	DW_DP_HPD_STATE_UNPLUG,
>+	DP_DP_HPD_STATE_TIMEOUT = 4,
>+	DW_DP_HPD_STATE_PLUG = 7
>+};
>+
>+enum {
>+	DW_DP_PHY_PATTERN_NONE,
>+	DW_DP_PHY_PATTERN_TPS_1,
>+	DW_DP_PHY_PATTERN_TPS_2,
>+	DW_DP_PHY_PATTERN_TPS_3,
>+	DW_DP_PHY_PATTERN_TPS_4,
>+	DW_DP_PHY_PATTERN_SERM,
>+	DW_DP_PHY_PATTERN_PBRS7,
>+	DW_DP_PHY_PATTERN_CUSTOM_80BIT,
>+	DW_DP_PHY_PATTERN_CP2520_1,
>+	DW_DP_PHY_PATTERN_CP2520_2,
>+};
>+
>+struct dw_dp_output_format {
>+	u32 bus_format;
>+	u32 color_format;
>+	u8 video_mapping;
>+	u8 bpc;
>+	u8 bpp;
>+};
>+
>+static const struct dw_dp_output_format dw_dp_output_formats[] = {
>+	{ MEDIA_BUS_FMT_RGB101010_1X30, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_10BIT, 10, 30 },
>+	{ MEDIA_BUS_FMT_RGB888_1X24, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_8BIT, 8, 24 },
>+	{ MEDIA_BUS_FMT_YUV10_1X30, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_10BIT, 10, 30 },
>+	{ MEDIA_BUS_FMT_YUV8_1X24, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_8BIT, 8, 24},
>+	{ MEDIA_BUS_FMT_YUYV10_1X20, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_10BIT, 10, 20 },
>+	{ MEDIA_BUS_FMT_YUYV8_1X16, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_8BIT, 8, 16 },
>+	{ MEDIA_BUS_FMT_UYYVYY10_0_5X30, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_10BIT, 10, 15 },
>+	{ MEDIA_BUS_FMT_UYYVYY8_0_5X24, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_8BIT, 8, 12 },
>+	{ MEDIA_BUS_FMT_RGB666_1X24_CPADHI, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_6BIT, 6, 18 },
>+};
>+
>+static const struct dw_dp_output_format *dw_dp_get_output_format(u32 bus_format)
>+{
>+	unsigned int i;
>+
>+	for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++)
>+		if (dw_dp_output_formats[i].bus_format == bus_format)
>+			return &dw_dp_output_formats[i];
>+
>+	return NULL;
>+}
>+
>+static inline struct dw_dp *bridge_to_dp(struct drm_bridge *b)
>+{
>+	return container_of(b, struct dw_dp, bridge);
>+}
>+
>+static inline void dw_dp_phy_set_pattern(struct dw_dp *dp, u32 pattern)
>+{
>+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, TPS_SEL,
>+			   FIELD_PREP(TPS_SEL, pattern));
>+}
>+
>+static void dw_dp_phy_xmit_enable(struct dw_dp *dp, u32 lanes)
>+{
>+	u32 xmit_enable;
>+
>+	switch (lanes) {
>+	case 4:
>+	case 2:
>+	case 1:
>+		xmit_enable = GENMASK(lanes - 1, 0);
>+		break;
>+	case 0:
>+	default:
>+		xmit_enable = 0;
>+		break;
>+	}
>+
>+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, XMIT_ENABLE,
>+			   FIELD_PREP(XMIT_ENABLE, xmit_enable));
>+}
>+
>+static bool dw_dp_bandwidth_ok(struct dw_dp *dp,
>+			       const struct drm_display_mode *mode, u32 bpp,
>+			       unsigned int lanes, unsigned int rate)
>+{
>+	u32 max_bw, req_bw;
>+
>+	req_bw = mode->clock * bpp / 8;
>+	max_bw = lanes * rate;
>+	if (req_bw > max_bw)
>+		return false;
>+
>+	return true;
>+}
>+
>+static bool dw_dp_hpd_detect(struct dw_dp *dp)
>+{
>+	u32 value;
>+
>+	regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value);
>+
>+	return FIELD_GET(HPD_STATE, value) == DW_DP_HPD_STATE_PLUG;
>+}
>+
>+static void dw_dp_link_caps_reset(struct dw_dp_link_caps *caps)
>+{
>+	caps->enhanced_framing = false;
>+	caps->tps3_supported = false;
>+	caps->tps4_supported = false;
>+	caps->fast_training = false;
>+	caps->channel_coding = false;
>+}
>+
>+/* TODO: pull these two functions to DRM DP helpers */
>+static void dw_dp_link_reset(struct dw_dp_link *link)
>+{
>+	link->vsc_sdp_supported = 0;
>+	link->sink_count = 0;
>+	link->revision = 0;
>+	link->rate = 0;
>+	link->lanes = 0;
>+
>+	dw_dp_link_caps_reset(&link->caps);
>+	memset(link->dpcd, 0, sizeof(link->dpcd));
>+}
>+
>+/* TODO: switch to drm_dp_read_sink_count_cap once we can get connector*/
>+static bool dw_dp_has_sink_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
>+				 const struct drm_dp_desc *desc)
>+{
>+	return dpcd[DP_DPCD_REV] >= DP_DPCD_REV_11 &&
>+	       dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT &&
>+	       !drm_dp_has_quirk(desc, DP_DPCD_QUIRK_NO_SINK_COUNT);
>+}
>+
>+static int dw_dp_link_parse(struct dw_dp *dp)
>+{
>+	struct dw_dp_link *link = &dp->link;
>+	u8 dpcd;
>+	int ret;
>+
>+	dw_dp_link_reset(link);
>+
>+	ret = drm_dp_read_dpcd_caps(&dp->aux, link->dpcd);
>+	if (ret < 0)
>+		return ret;
>+
>+	drm_dp_read_desc(&dp->aux, &link->desc, drm_dp_is_branch(link->dpcd));
>+
>+	if (dw_dp_has_sink_count(link->dpcd, &link->desc)) {
>+		ret = drm_dp_read_sink_count(&dp->aux);
>+		if (ret < 0)
>+			return ret;
>+
>+		link->sink_count = ret;
>+
>+		/* Dongle connected, but no display */
>+		if (!link->sink_count)
>+			return -ENODEV;
>+	}
>+
>+	ret = drm_dp_dpcd_readb(&dp->aux, DP_DPRX_FEATURE_ENUMERATION_LIST, &dpcd);
>+	if (ret < 0)
>+		return ret;
>+
>+	link->vsc_sdp_supported = !!(dpcd & DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED);
>+
>+	link->revision = link->dpcd[DP_DPCD_REV];
>+	link->rate = min_t(u32, min(dp->plat_data.max_link_rate,
>+				    dp->phy->attrs.max_link_rate * 100),
>+			   drm_dp_max_link_rate(link->dpcd));
>+	link->lanes = min_t(u8, phy_get_bus_width(dp->phy),
>+			    drm_dp_max_lane_count(link->dpcd));
>+
>+	link->caps.enhanced_framing = drm_dp_enhanced_frame_cap(link->dpcd);
>+	link->caps.tps3_supported = drm_dp_tps3_supported(link->dpcd);
>+	link->caps.tps4_supported = drm_dp_tps4_supported(link->dpcd);
>+	link->caps.fast_training = drm_dp_fast_training_cap(link->dpcd);
>+	link->caps.channel_coding = drm_dp_channel_coding_supported(link->dpcd);
>+	link->caps.ssc = !!(link->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5);
>+
>+	return 0;
>+}
>+
>+static int dw_dp_link_train_update_vs_emph(struct dw_dp *dp)
>+{
>+	struct dw_dp_link *link = &dp->link;
>+	struct dw_dp_link_train_set *train_set = &link->train.adjust;
>+	unsigned int lanes = dp->link.lanes;
>+	union phy_configure_opts phy_cfg;
>+	unsigned int *vs, *pe;
>+	int i, ret;
>+	u8 buf[4];
>+
>+	vs = train_set->voltage_swing;
>+	pe = train_set->pre_emphasis;
>+
>+	for (i = 0; i < lanes; i++) {
>+		phy_cfg.dp.voltage[i] = vs[i];
>+		phy_cfg.dp.pre[i] = pe[i];
>+	}
>+
>+	phy_cfg.dp.set_lanes = false;
>+	phy_cfg.dp.set_rate = false;
>+	phy_cfg.dp.set_voltages = true;
>+
>+	ret = phy_configure(dp->phy, &phy_cfg);
>+	if (ret)
>+		return ret;
>+
>+	for (i = 0; i < lanes; i++) {
>+		buf[i] = (vs[i] << DP_TRAIN_VOLTAGE_SWING_SHIFT) |
>+			 (pe[i] << DP_TRAIN_PRE_EMPHASIS_SHIFT);
>+		if (train_set->voltage_max_reached[i])
>+			buf[i] |= DP_TRAIN_MAX_SWING_REACHED;
>+		if (train_set->pre_max_reached[i])
>+			buf[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
>+	}
>+
>+	ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, buf, lanes);
>+	if (ret < 0)
>+		return ret;
>+
>+	return 0;
>+}
>+
>+static int dw_dp_phy_configure(struct dw_dp *dp, unsigned int rate,
>+			       unsigned int lanes, bool ssc)
>+{
>+	union phy_configure_opts phy_cfg;
>+	int ret;
>+
>+	/* Move PHY to P3 */
>+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN,
>+			   FIELD_PREP(PHY_POWERDOWN, 0x3));
>+
>+	phy_cfg.dp.lanes = lanes;
>+	phy_cfg.dp.link_rate = rate / 100;
>+	phy_cfg.dp.ssc = ssc;
>+	phy_cfg.dp.set_lanes = true;
>+	phy_cfg.dp.set_rate = true;
>+	phy_cfg.dp.set_voltages = false;
>+	ret = phy_configure(dp->phy, &phy_cfg);
>+	if (ret)
>+		return ret;
>+
>+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_LANES,
>+			   FIELD_PREP(PHY_LANES, lanes / 2));
>+
>+	/* Move PHY to P0 */
>+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN,
>+			   FIELD_PREP(PHY_POWERDOWN, 0x0));
>+
>+	dw_dp_phy_xmit_enable(dp, lanes);
>+
>+	return 0;
>+}
>+
>+static int dw_dp_link_configure(struct dw_dp *dp)
>+{
>+	struct dw_dp_link *link = &dp->link;
>+	u8 buf[2];
>+	int ret;
>+
>+	ret = dw_dp_phy_configure(dp, link->rate, link->lanes, link->caps.ssc);
>+	if (ret)
>+		return ret;
>+
>+	buf[0] = drm_dp_link_rate_to_bw_code(link->rate);
>+	buf[1] = link->lanes;
>+
>+	if (link->caps.enhanced_framing) {
>+		buf[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
>+		regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN,
>+				   FIELD_PREP(ENHANCE_FRAMING_EN, 1));
>+	} else {
>+		regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN,
>+				   FIELD_PREP(ENHANCE_FRAMING_EN, 0));
>+	}
>+
>+	ret = drm_dp_dpcd_write(&dp->aux, DP_LINK_BW_SET, buf, sizeof(buf));
>+	if (ret < 0)
>+		return ret;
>+
>+	buf[0] = link->caps.ssc ? DP_SPREAD_AMP_0_5 : 0;
>+	buf[1] = link->caps.channel_coding ? DP_SET_ANSI_8B10B : 0;
>+
>+	ret = drm_dp_dpcd_write(&dp->aux, DP_DOWNSPREAD_CTRL, buf, sizeof(buf));
>+	if (ret < 0)
>+		return ret;
>+
>+	return 0;
>+}
>+
>+static void dw_dp_link_train_init(struct dw_dp_link_train *train)
>+{
>+	struct dw_dp_link_train_set *adj = &train->adjust;
>+	unsigned int i;
>+
>+	for (i = 0; i < 4; i++) {
>+		adj->voltage_swing[i] = 0;
>+		adj->pre_emphasis[i] = 0;
>+		adj->voltage_max_reached[i] = false;
>+		adj->pre_max_reached[i] = false;
>+	}
>+
>+	train->clock_recovered = false;
>+	train->channel_equalized = false;
>+}
>+
>+static bool dw_dp_link_train_valid(const struct dw_dp_link_train *train)
>+{
>+	return train->clock_recovered && train->channel_equalized;
>+}
>+
>+static int dw_dp_link_train_set_pattern(struct dw_dp *dp, u32 pattern)
>+{
>+	u8 buf = 0;
>+	int ret;
>+
>+	if (pattern && pattern != DP_TRAINING_PATTERN_4) {
>+		buf |= DP_LINK_SCRAMBLING_DISABLE;
>+
>+		regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS,
>+				   FIELD_PREP(SCRAMBLE_DIS, 1));
>+	} else {
>+		regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS,
>+				   FIELD_PREP(SCRAMBLE_DIS, 0));
>+	}
>+
>+	switch (pattern) {
>+	case DP_TRAINING_PATTERN_DISABLE:
>+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_NONE);
>+		break;
>+	case DP_TRAINING_PATTERN_1:
>+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_1);
>+		break;
>+	case DP_TRAINING_PATTERN_2:
>+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_2);
>+		break;
>+	case DP_TRAINING_PATTERN_3:
>+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_3);
>+		break;
>+	case DP_TRAINING_PATTERN_4:
>+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_4);
>+		break;
>+	default:
>+		return -EINVAL;
>+	}
>+
>+	ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET,
>+				 buf | pattern);
>+	if (ret < 0)
>+		return ret;
>+
>+	return 0;
>+}
>+
>+static u8 dw_dp_voltage_max(u8 preemph)
>+{
>+	switch (preemph & DP_TRAIN_PRE_EMPHASIS_MASK) {
>+	case DP_TRAIN_PRE_EMPH_LEVEL_0:
>+		return DP_TRAIN_VOLTAGE_SWING_LEVEL_3;
>+	case DP_TRAIN_PRE_EMPH_LEVEL_1:
>+		return DP_TRAIN_VOLTAGE_SWING_LEVEL_2;
>+	case DP_TRAIN_PRE_EMPH_LEVEL_2:
>+		return DP_TRAIN_VOLTAGE_SWING_LEVEL_1;
>+	case DP_TRAIN_PRE_EMPH_LEVEL_3:
>+	default:
>+		return DP_TRAIN_VOLTAGE_SWING_LEVEL_0;
>+	}
>+}
>+
>+static bool dw_dp_link_get_adjustments(struct dw_dp_link *link,
>+				       u8 status[DP_LINK_STATUS_SIZE])
>+{
>+	struct dw_dp_link_train_set *adj = &link->train.adjust;
>+	unsigned int i;
>+	bool changed = false;
>+	u8 v = 0;
>+	u8 p = 0;
>+
>+	for (i = 0; i < link->lanes; i++) {
>+		v = drm_dp_get_adjust_request_voltage(status, i);
>+		v >>= DP_TRAIN_VOLTAGE_SWING_SHIFT;
>+		p = drm_dp_get_adjust_request_pre_emphasis(status, i);
>+		p >>= DP_TRAIN_PRE_EMPHASIS_SHIFT;
>+
>+		if ((v != adj->voltage_swing[i]) || (p != adj->pre_emphasis[i]))
>+			changed = true;
>+
>+		if (p >=  (DP_TRAIN_PRE_EMPH_LEVEL_3 >> DP_TRAIN_PRE_EMPHASIS_SHIFT)) {
>+			adj->pre_emphasis[i] = DP_TRAIN_PRE_EMPH_LEVEL_3 >>
>+					       DP_TRAIN_PRE_EMPHASIS_SHIFT;
>+			adj->pre_max_reached[i] = true;
>+		} else {
>+			adj->pre_emphasis[i] = p;
>+			adj->pre_max_reached[i] = false;
>+		}
>+
>+		v = min(v, dw_dp_voltage_max(p));
>+		if (v >= (DP_TRAIN_VOLTAGE_SWING_LEVEL_3 >> DP_TRAIN_VOLTAGE_SWING_SHIFT)) {
>+			adj->voltage_swing[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_3 >>
>+						DP_TRAIN_VOLTAGE_SWING_SHIFT;
>+			adj->voltage_max_reached[i] = true;
>+		} else {
>+			adj->voltage_swing[i] = v;
>+			adj->voltage_max_reached[i] = false;
>+		}
>+	}
>+
>+	return changed;
>+}
>+
>+static int dw_dp_link_clock_recovery(struct dw_dp *dp)
>+{
>+	struct dw_dp_link *link = &dp->link;
>+	u8 status[DP_LINK_STATUS_SIZE];
>+	unsigned int tries = 0;
>+	int ret;
>+	bool adj_changed;
>+
>+	ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1);
>+	if (ret)
>+		return ret;
>+
>+	for (;;) {
>+		ret = dw_dp_link_train_update_vs_emph(dp);
>+		if (ret)
>+			return ret;
>+
>+		drm_dp_link_train_clock_recovery_delay(&dp->aux, link->dpcd);
>+
>+		ret = drm_dp_dpcd_read_link_status(&dp->aux, status);
>+		if (ret < 0) {
>+			dev_err(dp->dev, "failed to read link status: %d\n", ret);
>+			return ret;
>+		}
>+
>+		if (drm_dp_clock_recovery_ok(status, link->lanes)) {
>+			link->train.clock_recovered = true;
>+			break;
>+		}
>+
>+		/*
>+		 * According to DP spec 1.4, if current ADJ is the same
>+		 * with previous REQ, we need to retry 5 times.
>+		 */
>+		adj_changed = dw_dp_link_get_adjustments(link, status);
>+		if (!adj_changed)
>+			tries++;
>+		else
>+			tries = 0;
>+
>+		if (tries == 5)
>+			break;
>+	}
>+
>+	return 0;
>+}
>+
>+static int dw_dp_link_channel_equalization(struct dw_dp *dp)
>+{
>+	struct dw_dp_link *link = &dp->link;
>+	u8 status[DP_LINK_STATUS_SIZE], pattern;
>+	unsigned int tries;
>+	int ret;
>+
>+	if (link->caps.tps4_supported)
>+		pattern = DP_TRAINING_PATTERN_4;
>+	else if (link->caps.tps3_supported)
>+		pattern = DP_TRAINING_PATTERN_3;
>+	else
>+		pattern = DP_TRAINING_PATTERN_2;
>+	ret = dw_dp_link_train_set_pattern(dp, pattern);
>+	if (ret)
>+		return ret;
>+
>+	for (tries = 1; tries < 5; tries++) {
>+		ret = dw_dp_link_train_update_vs_emph(dp);
>+		if (ret)
>+			return ret;
>+
>+		drm_dp_link_train_channel_eq_delay(&dp->aux, link->dpcd);
>+
>+		ret = drm_dp_dpcd_read_link_status(&dp->aux, status);
>+		if (ret < 0)
>+			return ret;
>+
>+		if (!drm_dp_clock_recovery_ok(status, link->lanes)) {
>+			dev_err(dp->dev, "clock recovery lost while equalizing channel\n");
>+			link->train.clock_recovered = false;
>+			break;
>+		}
>+
>+		if (drm_dp_channel_eq_ok(status, link->lanes)) {
>+			link->train.channel_equalized = true;
>+			break;
>+		}
>+
>+		dw_dp_link_get_adjustments(link, status);
>+	}
>+
>+	return 0;
>+}
>+
>+static int dw_dp_link_downgrade(struct dw_dp *dp)
>+{
>+	struct dw_dp_link *link = &dp->link;
>+	struct dw_dp_video *video = &dp->video;
>+
>+	switch (link->rate) {
>+	case 162000:
>+		return -EINVAL;
>+	case 270000:
>+		link->rate = 162000;
>+		break;
>+	case 540000:
>+		link->rate = 270000;
>+		break;
>+	case 810000:
>+		link->rate = 540000;
>+		break;
>+	}
>+
>+	if (!dw_dp_bandwidth_ok(dp, &video->mode, video->bpp, link->lanes,
>+				link->rate))
>+		return -E2BIG;
>+
>+	return 0;
>+}
>+
>+static int dw_dp_link_train_full(struct dw_dp *dp)
>+{
>+	struct dw_dp_link *link = &dp->link;
>+	int ret;
>+
>+retry:
>+	dw_dp_link_train_init(&link->train);
>+
>+	dev_dbg(dp->dev, "full-training link: %u lane%s at %u MHz\n",
>+		link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100);
>+
>+	ret = dw_dp_link_configure(dp);
>+	if (ret < 0) {
>+		dev_err(dp->dev, "failed to configure DP link: %d\n", ret);
>+		return ret;
>+	}
>+
>+	ret = dw_dp_link_clock_recovery(dp);
>+	if (ret < 0) {
>+		dev_err(dp->dev, "clock recovery failed: %d\n", ret);
>+		goto out;
>+	}
>+
>+	if (!link->train.clock_recovered) {
>+		dev_err(dp->dev, "clock recovery failed, downgrading link\n");
>+
>+		ret = dw_dp_link_downgrade(dp);
>+		if (ret < 0)
>+			goto out;
>+		else
>+			goto retry;
>+	}
>+
>+	dev_dbg(dp->dev, "clock recovery succeeded\n");
>+
>+	ret = dw_dp_link_channel_equalization(dp);
>+	if (ret < 0) {
>+		dev_err(dp->dev, "channel equalization failed: %d\n", ret);
>+		goto out;
>+	}
>+
>+	if (!link->train.channel_equalized) {
>+		dev_err(dp->dev, "channel equalization failed, downgrading link\n");
>+
>+		ret = dw_dp_link_downgrade(dp);
>+		if (ret < 0)
>+			goto out;
>+		else
>+			goto retry;
>+	}
>+
>+	dev_dbg(dp->dev, "channel equalization succeeded\n");
>+
>+out:
>+	dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE);
>+	return ret;
>+}
>+
>+static int dw_dp_link_train_fast(struct dw_dp *dp)
>+{
>+	struct dw_dp_link *link = &dp->link;
>+	int ret;
>+	u8 status[DP_LINK_STATUS_SIZE];
>+	u8 pattern;
>+
>+	dw_dp_link_train_init(&link->train);
>+
>+	dev_dbg(dp->dev, "fast-training link: %u lane%s at %u MHz\n",
>+		link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100);
>+
>+	ret = dw_dp_link_configure(dp);
>+	if (ret < 0) {
>+		dev_err(dp->dev, "failed to configure DP link: %d\n", ret);
>+		return ret;
>+	}
>+
>+	ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1);
>+	if (ret)
>+		goto out;
>+
>+	usleep_range(500, 1000);
>+
>+	if (link->caps.tps4_supported)
>+		pattern = DP_TRAINING_PATTERN_4;
>+	else if (link->caps.tps3_supported)
>+		pattern = DP_TRAINING_PATTERN_3;
>+	else
>+		pattern = DP_TRAINING_PATTERN_2;
>+	ret = dw_dp_link_train_set_pattern(dp, pattern);
>+	if (ret)
>+		goto out;
>+
>+	usleep_range(500, 1000);
>+
>+	ret = drm_dp_dpcd_read_link_status(&dp->aux, status);
>+	if (ret < 0) {
>+		dev_err(dp->dev, "failed to read link status: %d\n", ret);
>+		goto out;
>+	}
>+
>+	if (!drm_dp_clock_recovery_ok(status, link->lanes)) {
>+		dev_err(dp->dev, "clock recovery failed\n");
>+		ret = -EIO;
>+		goto out;
>+	}
>+
>+	if (!drm_dp_channel_eq_ok(status, link->lanes)) {
>+		dev_err(dp->dev, "channel equalization failed\n");
>+		ret = -EIO;
>+		goto out;
>+	}
>+
>+out:
>+	dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE);
>+	return ret;
>+}
>+
>+static int dw_dp_link_train(struct dw_dp *dp)
>+{
>+	struct dw_dp_link *link = &dp->link;
>+	int ret;
>+
>+	if (link->caps.fast_training) {
>+		if (dw_dp_link_train_valid(&link->train)) {
>+			ret = dw_dp_link_train_fast(dp);
>+			if (ret < 0)
>+				dev_err(dp->dev, "fast link training failed: %d\n", ret);
>+			else
>+				return 0;
>+		}
>+	}
>+
>+	ret = dw_dp_link_train_full(dp);
>+	if (ret < 0) {
>+		dev_err(dp->dev, "full link training failed: %d\n", ret);
>+		return ret;
>+	}
>+
>+	return 0;
>+}
>+
>+static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp)
>+{
>+	const u8 *payload = sdp->base.db;
>+	u32 reg;
>+	int i, nr;
>+
>+	nr = find_first_zero_bit(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
>+	if (nr < SDP_REG_BANK_SIZE)
>+		set_bit(nr, dp->sdp_reg_bank);
>+	else
>+		return -EBUSY;
>+
>+	reg = DW_DP_SDP_REGISTER_BANK + nr * 9 * 4;
>+
>+	/* SDP header */
>+	regmap_write(dp->regmap, reg, get_unaligned_le32(&sdp->base.sdp_header));
>+
>+	/* SDP data payload */
>+	for (i = 1; i < 9; i++, payload += 4)
>+		regmap_write(dp->regmap, reg + i * 4,
>+			     FIELD_PREP(SDP_REGS, get_unaligned_le32(payload)));
>+
>+	if (sdp->flags & DW_DP_SDP_VERTICAL_INTERVAL)
>+		regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
>+				   EN_VERTICAL_SDP << nr,
>+				   EN_VERTICAL_SDP << nr);
>+
>+	if (sdp->flags & DW_DP_SDP_HORIZONTAL_INTERVAL)
>+		regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
>+				   EN_HORIZONTAL_SDP << nr,
>+				   EN_HORIZONTAL_SDP << nr);
>+
>+	return 0;
>+}
>+
>+static int dw_dp_send_vsc_sdp(struct dw_dp *dp)
>+{
>+	struct dw_dp_video *video = &dp->video;
>+	struct dw_dp_sdp sdp = {};
>+	struct drm_dp_vsc_sdp vsc = {};
>+
>+	sdp.flags = DW_DP_SDP_VERTICAL_INTERVAL;
>+
>+	vsc.sdp_type = DP_SDP_VSC;
>+	vsc.revision = 0x5;
>+	vsc.length = 0x13;
>+	vsc.bpc = video->bpc;
>+	vsc.content_type = DP_CONTENT_TYPE_NOT_DEFINED;
>+
>+	switch (video->color_format) {
>+	case DRM_COLOR_FORMAT_YCBCR444:
>+		vsc.pixelformat = DP_PIXELFORMAT_YUV444;
>+		break;
>+	case DRM_COLOR_FORMAT_YCBCR420:
>+		vsc.pixelformat = DP_PIXELFORMAT_YUV420;
>+		break;
>+	case DRM_COLOR_FORMAT_YCBCR422:
>+		vsc.pixelformat = DP_PIXELFORMAT_YUV422;
>+		break;
>+	case DRM_COLOR_FORMAT_RGB444:
>+	default:
>+		vsc.pixelformat = DP_PIXELFORMAT_RGB;
>+		break;
>+	}
>+
>+	if (video->color_format == DRM_COLOR_FORMAT_RGB444) {
>+		vsc.colorimetry = DP_COLORIMETRY_DEFAULT;
>+		vsc.dynamic_range = DP_DYNAMIC_RANGE_VESA;
>+	} else {
>+		vsc.colorimetry = DP_COLORIMETRY_BT709_YCC;
>+		vsc.dynamic_range = DP_DYNAMIC_RANGE_CTA;
>+	}
>+
>+	drm_dp_vsc_sdp_pack(&vsc, &sdp.base);
>+
>+	return dw_dp_send_sdp(dp, &sdp);
>+}
>+
>+static int dw_dp_video_set_pixel_mode(struct dw_dp *dp, u8 pixel_mode)
>+{
>+	switch (pixel_mode) {
>+	case DW_DP_MP_SINGLE_PIXEL:
>+	case DW_DP_MP_DUAL_PIXEL:
>+	case DW_DP_MP_QUAD_PIXEL:
>+		break;
>+	default:
>+		return -EINVAL;
>+	}
>+
>+	regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, PIXEL_MODE_SELECT,
>+			   FIELD_PREP(PIXEL_MODE_SELECT, pixel_mode));
>+
>+	return 0;
>+}
>+
>+static bool dw_dp_video_need_vsc_sdp(struct dw_dp *dp)
>+{
>+	struct dw_dp_link *link = &dp->link;
>+	struct dw_dp_video *video = &dp->video;
>+
>+	if (!link->vsc_sdp_supported)
>+		return false;
>+
>+	if (video->color_format == DRM_COLOR_FORMAT_YCBCR420)
>+		return true;
>+
>+	return false;
>+}
>+
>+static int dw_dp_video_set_msa(struct dw_dp *dp, u8 color_format, u8 bpc,
>+			       u16 vstart, u16 hstart)
>+{
>+	u16 misc = 0;
>+
>+	if (dw_dp_video_need_vsc_sdp(dp))
>+		misc |= DP_MSA_MISC_COLOR_VSC_SDP;
>+
>+	switch (color_format) {
>+	case DRM_COLOR_FORMAT_RGB444:
>+		misc |= DP_MSA_MISC_COLOR_RGB;
>+		break;
>+	case DRM_COLOR_FORMAT_YCBCR444:
>+		misc |= DP_MSA_MISC_COLOR_YCBCR_444_BT709;
>+		break;
>+	case DRM_COLOR_FORMAT_YCBCR422:
>+		misc |= DP_MSA_MISC_COLOR_YCBCR_422_BT709;
>+		break;
>+	case DRM_COLOR_FORMAT_YCBCR420:
>+		break;
>+	default:
>+		return -EINVAL;
>+	}
>+
>+	switch (bpc) {
>+	case 6:
>+		misc |= DP_MSA_MISC_6_BPC;
>+		break;
>+	case 8:
>+		misc |= DP_MSA_MISC_8_BPC;
>+		break;
>+	case 10:
>+		misc |= DP_MSA_MISC_10_BPC;
>+		break;
>+	case 12:
>+		misc |= DP_MSA_MISC_12_BPC;
>+		break;
>+	case 16:
>+		misc |= DP_MSA_MISC_16_BPC;
>+		break;
>+	default:
>+		return -EINVAL;
>+	}
>+
>+	regmap_write(dp->regmap, DW_DP_VIDEO_MSA1,
>+		     FIELD_PREP(VSTART, vstart) | FIELD_PREP(HSTART, hstart));
>+	regmap_write(dp->regmap, DW_DP_VIDEO_MSA2, FIELD_PREP(MISC0, misc));
>+	regmap_write(dp->regmap, DW_DP_VIDEO_MSA3, FIELD_PREP(MISC1, misc >> 8));
>+
>+	return 0;
>+}
>+
>+static void dw_dp_video_disable(struct dw_dp *dp)
>+{
>+	regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE,
>+			   FIELD_PREP(VIDEO_STREAM_ENABLE, 0));
>+}
>+
>+static int dw_dp_video_enable(struct dw_dp *dp)
>+{
>+	struct dw_dp_video *video = &dp->video;
>+	struct dw_dp_link *link = &dp->link;
>+	struct drm_display_mode *mode = &video->mode;
>+	u8 color_format = video->color_format;
>+	u8 bpc = video->bpc;
>+	u8 pixel_mode = video->pixel_mode;
>+	u8 bpp = video->bpp, init_threshold, vic;
>+	u32 hactive, hblank, h_sync_width, h_front_porch;
>+	u32 vactive, vblank, v_sync_width, v_front_porch;
>+	u32 vstart = mode->vtotal - mode->vsync_start;
>+	u32 hstart = mode->htotal - mode->hsync_start;
>+	u32 peak_stream_bandwidth, link_bandwidth;
>+	u32 average_bytes_per_tu, average_bytes_per_tu_frac;
>+	u32 ts, hblank_interval;
>+	u32 value;
>+	int ret;
>+
>+	ret = dw_dp_video_set_pixel_mode(dp, pixel_mode);
>+	if (ret)
>+		return ret;
>+
>+	ret = dw_dp_video_set_msa(dp, color_format, bpc, vstart, hstart);
>+	if (ret)
>+		return ret;
>+
>+	regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_MAPPING,
>+			   FIELD_PREP(VIDEO_MAPPING, video->video_mapping));
>+
>+	/* Configure DW_DP_VINPUT_POLARITY_CTRL register */
>+	value = 0;
>+	if (mode->flags & DRM_MODE_FLAG_PHSYNC)
>+		value |= FIELD_PREP(HSYNC_IN_POLARITY, 1);
>+	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
>+		value |= FIELD_PREP(VSYNC_IN_POLARITY, 1);
>+	regmap_write(dp->regmap, DW_DP_VINPUT_POLARITY_CTRL, value);
>+
>+	/* Configure DW_DP_VIDEO_CONFIG1 register */
>+	hactive = mode->hdisplay;
>+	hblank = mode->htotal - mode->hdisplay;
>+	value = FIELD_PREP(HACTIVE, hactive) | FIELD_PREP(HBLANK, hblank);
>+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
>+		value |= FIELD_PREP(I_P, 1);
>+	vic = drm_match_cea_mode(mode);
>+	if (vic == 5 || vic == 6 || vic == 7 ||
>+	    vic == 10 || vic == 11 || vic == 20 ||
>+	    vic == 21 || vic == 22 || vic == 39 ||
>+	    vic == 25 || vic == 26 || vic == 40 ||
>+	    vic == 44 || vic == 45 || vic == 46 ||
>+	    vic == 50 || vic == 51 || vic == 54 ||
>+	    vic == 55 || vic == 58 || vic  == 59)
>+		value |= R_V_BLANK_IN_OSC;
>+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG1, value);
>+
>+	/* Configure DW_DP_VIDEO_CONFIG2 register */
>+	vblank = mode->vtotal - mode->vdisplay;
>+	vactive = mode->vdisplay;
>+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG2,
>+		     FIELD_PREP(VBLANK, vblank) | FIELD_PREP(VACTIVE, vactive));
>+
>+	/* Configure DW_DP_VIDEO_CONFIG3 register */
>+	h_sync_width = mode->hsync_end - mode->hsync_start;
>+	h_front_porch = mode->hsync_start - mode->hdisplay;
>+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG3,
>+		     FIELD_PREP(H_SYNC_WIDTH, h_sync_width) |
>+		     FIELD_PREP(H_FRONT_PORCH, h_front_porch));
>+
>+	/* Configure DW_DP_VIDEO_CONFIG4 register */
>+	v_sync_width = mode->vsync_end - mode->vsync_start;
>+	v_front_porch = mode->vsync_start - mode->vdisplay;
>+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG4,
>+		     FIELD_PREP(V_SYNC_WIDTH, v_sync_width) |
>+		     FIELD_PREP(V_FRONT_PORCH, v_front_porch));
>+
>+	/* Configure DW_DP_VIDEO_CONFIG5 register */
>+	peak_stream_bandwidth = mode->clock * bpp / 8;
>+	link_bandwidth = (link->rate / 1000) * link->lanes;
>+	ts = peak_stream_bandwidth * 64 / link_bandwidth;
>+	average_bytes_per_tu = ts / 1000;
>+	average_bytes_per_tu_frac = ts / 100 - average_bytes_per_tu * 10;
>+	if (pixel_mode == DW_DP_MP_SINGLE_PIXEL) {
>+		if (average_bytes_per_tu < 6)
>+			init_threshold = 32;
>+		else if (hblank <= 80 && color_format != DRM_COLOR_FORMAT_YCBCR420)
>+			init_threshold = 12;
>+		else if (hblank <= 40 && color_format == DRM_COLOR_FORMAT_YCBCR420)
>+			init_threshold = 3;
>+		else
>+			init_threshold = 16;
>+	} else {
>+		u32 t1 = 0, t2 = 0, t3 = 0;
>+
>+		switch (bpc) {
>+		case 6:
>+			t1 = (4 * 1000 / 9) * link->lanes;
>+			break;
>+		case 8:
>+			if (color_format == DRM_COLOR_FORMAT_YCBCR422) {
>+				t1 = (1000 / 2) * link->lanes;
>+			} else {
>+				if (pixel_mode == DW_DP_MP_DUAL_PIXEL)
>+					t1 = (1000 / 3) * link->lanes;
>+				else
>+					t1 = (3000 / 16) * link->lanes;
>+			}
>+			break;
>+		case 10:
>+			if (color_format == DRM_COLOR_FORMAT_YCBCR422)
>+				t1 = (2000 / 5) * link->lanes;
>+			else
>+				t1 = (4000 / 15) * link->lanes;
>+			break;
>+		case 12:
>+			if (color_format == DRM_COLOR_FORMAT_YCBCR422) {
>+				if (pixel_mode == DW_DP_MP_DUAL_PIXEL)
>+					t1 = (1000 / 6) * link->lanes;
>+				else
>+					t1 = (1000 / 3) * link->lanes;
>+			} else {
>+				t1 = (2000 / 9) * link->lanes;
>+			}
>+			break;
>+		case 16:
>+			if (color_format != DRM_COLOR_FORMAT_YCBCR422 &&
>+			    pixel_mode == DW_DP_MP_DUAL_PIXEL)
>+				t1 = (1000 / 6) * link->lanes;
>+			else
>+				t1 = (1000 / 4) * link->lanes;
>+			break;
>+		default:
>+			return -EINVAL;
>+		}
>+
>+		if (color_format == DRM_COLOR_FORMAT_YCBCR420)
>+			t2 = (link->rate / 4) * 1000 / (mode->clock / 2);
>+		else
>+			t2 = (link->rate / 4) * 1000 / mode->clock;
>+
>+		if (average_bytes_per_tu_frac)
>+			t3 = average_bytes_per_tu + 1;
>+		else
>+			t3 = average_bytes_per_tu;
>+		init_threshold = t1 * t2 * t3 / (1000 * 1000);
>+		if (init_threshold <= 16 || average_bytes_per_tu < 10)
>+			init_threshold = 40;
>+	}
>+
>+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG5,
>+		     FIELD_PREP(INIT_THRESHOLD_HI, init_threshold >> 6) |
>+		     FIELD_PREP(AVERAGE_BYTES_PER_TU_FRAC, average_bytes_per_tu_frac) |
>+		     FIELD_PREP(INIT_THRESHOLD, init_threshold) |
>+		     FIELD_PREP(AVERAGE_BYTES_PER_TU, average_bytes_per_tu));
>+
>+	/* Configure DW_DP_VIDEO_HBLANK_INTERVAL register */
>+	hblank_interval = hblank * (link->rate / 4) / mode->clock;
>+	regmap_write(dp->regmap, DW_DP_VIDEO_HBLANK_INTERVAL,
>+		     FIELD_PREP(HBLANK_INTERVAL_EN, 1) |
>+		     FIELD_PREP(HBLANK_INTERVAL, hblank_interval));
>+
>+	/* Video stream enable */
>+	regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE,
>+			   FIELD_PREP(VIDEO_STREAM_ENABLE, 1));
>+
>+	if (dw_dp_video_need_vsc_sdp(dp))
>+		dw_dp_send_vsc_sdp(dp);
>+
>+	return 0;
>+}
>+
>+static void dw_dp_hpd_init(struct dw_dp *dp)
>+{
>+	/* Enable all HPD interrupts */
>+	regmap_update_bits(dp->regmap, DW_DP_HPD_INTERRUPT_ENABLE,
>+			   HPD_UNPLUG_EN | HPD_PLUG_EN | HPD_IRQ_EN,
>+			   FIELD_PREP(HPD_UNPLUG_EN, 1) |
>+			   FIELD_PREP(HPD_PLUG_EN, 1) |
>+			   FIELD_PREP(HPD_IRQ_EN, 1));
>+
>+	/* Enable all top-level interrupts */
>+	regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE,
>+			   HPD_EVENT_EN, FIELD_PREP(HPD_EVENT_EN, 1));
>+}
>+
>+static void dw_dp_aux_init(struct dw_dp *dp)
>+{
>+	regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE,
>+			   AUX_REPLY_EVENT_EN, FIELD_PREP(AUX_REPLY_EVENT_EN, 1));
>+}
>+
>+static void dw_dp_init_hw(struct dw_dp *dp)
>+{
>+	regmap_update_bits(dp->regmap, DW_DP_CCTL, DEFAULT_FAST_LINK_TRAIN_EN,
>+			   FIELD_PREP(DEFAULT_FAST_LINK_TRAIN_EN, 0));
>+
>+	dw_dp_hpd_init(dp);
>+	dw_dp_aux_init(dp);
>+}
>+
>+static int dw_dp_aux_write_data(struct dw_dp *dp, const u8 *buffer, size_t size)
>+{
>+	size_t i, j;
>+
>+	for (i = 0; i < DIV_ROUND_UP(size, 4); i++) {
>+		size_t num = min_t(size_t, size - i * 4, 4);
>+		u32 value = 0;
>+
>+		for (j = 0; j < num; j++)
>+			value |= buffer[i * 4 + j] << (j * 8);
>+
>+		regmap_write(dp->regmap, DW_DP_AUX_DATA0 + i * 4, value);
>+	}
>+
>+	return size;
>+}
>+
>+static int dw_dp_aux_read_data(struct dw_dp *dp, u8 *buffer, size_t size)
>+{
>+	size_t i, j;
>+
>+	for (i = 0; i < DIV_ROUND_UP(size, 4); i++) {
>+		size_t num = min_t(size_t, size - i * 4, 4);
>+		u32 value;
>+
>+		regmap_read(dp->regmap, DW_DP_AUX_DATA0 + i * 4, &value);
>+
>+		for (j = 0; j < num; j++)
>+			buffer[i * 4 + j] = value >> (j * 8);
>+	}
>+
>+	return size;
>+}
>+
>+static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux,
>+				  struct drm_dp_aux_msg *msg)
>+{
>+	struct dw_dp *dp = container_of(aux, struct dw_dp, aux);
>+	unsigned long timeout = msecs_to_jiffies(10);
>+	u32 status, value;
>+	ssize_t ret = 0;
>+
>+	if (WARN_ON(msg->size > 16))
>+		return -E2BIG;
>+
>+	switch (msg->request & ~DP_AUX_I2C_MOT) {
>+	case DP_AUX_NATIVE_WRITE:
>+	case DP_AUX_I2C_WRITE:
>+	case DP_AUX_I2C_WRITE_STATUS_UPDATE:
>+		ret = dw_dp_aux_write_data(dp, msg->buffer, msg->size);
>+		if (ret < 0)
>+			return ret;
>+		break;
>+	case DP_AUX_NATIVE_READ:
>+	case DP_AUX_I2C_READ:
>+		break;
>+	default:
>+		return -EINVAL;
>+	}
>+
>+	if (msg->size > 0)
>+		value = FIELD_PREP(AUX_LEN_REQ, msg->size - 1);
>+	else
>+		value = FIELD_PREP(I2C_ADDR_ONLY, 1);
>+	value |= FIELD_PREP(AUX_CMD_TYPE, msg->request);
>+	value |= FIELD_PREP(AUX_ADDR, msg->address);
>+	regmap_write(dp->regmap, DW_DP_AUX_CMD, value);
>+
>+	status = wait_for_completion_timeout(&dp->complete, timeout);
>+	if (!status) {
>+		dev_err(dp->dev, "timeout waiting for AUX reply\n");
>+		return -ETIMEDOUT;
>+	}
>+
>+	regmap_read(dp->regmap, DW_DP_AUX_STATUS, &value);
>+	if (value & AUX_TIMEOUT)
>+		return -ETIMEDOUT;
>+
>+	msg->reply = FIELD_GET(AUX_STATUS, value);
>+
>+	if (msg->size > 0 && msg->reply == DP_AUX_NATIVE_REPLY_ACK) {
>+		if (msg->request & DP_AUX_I2C_READ) {
>+			size_t count = FIELD_GET(AUX_BYTES_READ, value) - 1;
>+
>+			if (count != msg->size)
>+				return -EBUSY;
>+
>+			ret = dw_dp_aux_read_data(dp, msg->buffer, count);
>+			if (ret < 0)
>+				return ret;
>+		}
>+	}
>+
>+	return ret;
>+}
>+
>+/*
>+ * Limits for the video timing for DP:
>+ * 1. the hfp should be 2 pixels aligned;
>+ * 2. the minimum hsync should be 9 pixel;
>+ * 3. the minimum hbp should be 16 pixel;
>+ */
>+static int dw_dp_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_dp *dp = bridge_to_dp(bridge);
>+	struct dw_dp_video *video = &dp->video;
>+	struct drm_display_mode *m = &video->mode;
>+	struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
>+	const struct dw_dp_output_format *fmt;
>+	int min_hbp = 16;
>+	int min_hsync = 9;
>+
>+	fmt = dw_dp_get_output_format(bridge_state->output_bus_cfg.format);
>+	if (!fmt)
>+		return -EINVAL;
>+
>+	video->video_mapping = fmt->video_mapping;
>+	video->color_format = fmt->color_format;
>+	video->bpc = fmt->bpc;
>+	video->bpp = fmt->bpp;
>+
>+	if ((adjusted_mode->hsync_start - adjusted_mode->hdisplay) & 0x1) {
>+		adjusted_mode->hsync_start += 1;
>+		dev_warn(dp->dev, "hfp is not 2 pixeel aligned, fixup to aligned hfp\n");
>+	}
>+	if (adjusted_mode->hsync_end - adjusted_mode->hsync_start < min_hsync) {
>+		adjusted_mode->hsync_end = adjusted_mode->hsync_start + min_hsync;
>+		dev_warn(dp->dev, "hsync is too narrow, fixup to min hsync:%d\n", min_hsync);
>+	}
>+	if (adjusted_mode->htotal - adjusted_mode->hsync_end < min_hbp) {
>+		adjusted_mode->htotal = adjusted_mode->hsync_end + min_hbp;
>+		dev_warn(dp->dev, "hbp is too narrow, fixup to min hbp:%d\n", min_hbp);
>+	}
>+
>+	drm_mode_copy(m, adjusted_mode);
>+
>+	return 0;
>+}
>+
>+static enum drm_mode_status dw_dp_bridge_mode_valid(struct drm_bridge *bridge,
>+						    const struct drm_display_info *info,
>+						    const struct drm_display_mode *mode)
>+{
>+	struct dw_dp *dp = bridge_to_dp(bridge);
>+	struct dw_dp_link *link = &dp->link;
>+	u32 min_bpp;
>+
>+	if (info->color_formats & DRM_COLOR_FORMAT_YCBCR420 &&
>+	    link->vsc_sdp_supported &&
>+	    (drm_mode_is_420_only(info, mode) || drm_mode_is_420_also(info, mode)))
>+		min_bpp = 12;
>+	else if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422)
>+		min_bpp = 16;
>+	else if (info->color_formats & DRM_COLOR_FORMAT_RGB444)
>+		min_bpp = 18;
>+	else
>+		min_bpp = 24;
>+
>+	if (!link->vsc_sdp_supported &&
>+	    drm_mode_is_420_only(info, mode))
>+		return MODE_NO_420;
>+
>+	if (!dw_dp_bandwidth_ok(dp, mode, min_bpp, link->lanes, link->rate))
>+		return MODE_CLOCK_HIGH;
>+
>+	return MODE_OK;
>+}
>+
>+static bool dw_dp_needs_link_retrain(struct dw_dp *dp)
>+{
>+	struct dw_dp_link *link = &dp->link;
>+	u8 link_status[DP_LINK_STATUS_SIZE];
>+
>+	if (!dw_dp_link_train_valid(&link->train))
>+		return false;
>+
>+	if (drm_dp_dpcd_read_link_status(&dp->aux, link_status) < 0)
>+		return false;
>+
>+	/* Retrain if Channel EQ or CR not ok */
>+	return !drm_dp_channel_eq_ok(link_status, dp->link.lanes);
>+}
>+
>+static void dw_dp_link_disable(struct dw_dp *dp)
>+{
>+	struct dw_dp_link *link = &dp->link;
>+
>+	if (dw_dp_hpd_detect(dp))
>+		drm_dp_link_power_down(&dp->aux, dp->link.revision);
>+
>+	dw_dp_phy_xmit_enable(dp, 0);
>+
>+	phy_power_off(dp->phy);
>+
>+	link->train.clock_recovered = false;
>+	link->train.channel_equalized = false;
>+}
>+
>+static int dw_dp_link_enable(struct dw_dp *dp)
>+{
>+	int ret;
>+
>+	ret = phy_power_on(dp->phy);
>+	if (ret)
>+		return ret;
>+
>+	ret = drm_dp_link_power_up(&dp->aux, dp->link.revision);
>+	if (ret < 0)
>+		return ret;
>+
>+	ret = dw_dp_link_train(dp);
>+
>+	return ret;
>+}
>+
>+static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
>+				       struct drm_atomic_state *state)
>+{
>+	struct dw_dp *dp = bridge_to_dp(bridge);
>+	struct drm_connector *connector;
>+	struct drm_connector_state *conn_state;
>+	int ret;
>+
>+	connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
>+	if (!connector) {
>+		dev_err(dp->dev, "failed to get connector\n");
>+		return;
>+	}
>+
>+	conn_state = drm_atomic_get_new_connector_state(state, connector);
>+	if (!conn_state) {
>+		dev_err(dp->dev, "failed to get connector state\n");
>+		return;
>+	}
>+
>+	set_bit(0, dp->sdp_reg_bank);
>+
>+	ret = dw_dp_link_enable(dp);
>+	if (ret < 0) {
>+		dev_err(dp->dev, "failed to enable link: %d\n", ret);
>+		return;
>+	}
>+
>+	ret = dw_dp_video_enable(dp);
>+	if (ret < 0) {
>+		dev_err(dp->dev, "failed to enable video: %d\n", ret);
>+		return;
>+	}
>+}
>+
>+static void dw_dp_reset(struct dw_dp *dp)
>+{
>+	int val;
>+
>+	disable_irq(dp->irq);
>+	regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET,
>+			   FIELD_PREP(CONTROLLER_RESET, 1));
>+	udelay(10);
>+	regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET,
>+			   FIELD_PREP(CONTROLLER_RESET, 0));
>+
>+	dw_dp_init_hw(dp);
>+	regmap_read_poll_timeout(dp->regmap, DW_DP_HPD_STATUS, val,
>+				 FIELD_GET(HPD_HOT_PLUG, val), 200, 200000);
>+	regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG);
>+	enable_irq(dp->irq);
>+}
>+
>+static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge,
>+					struct drm_atomic_state *state)
>+{
>+	struct dw_dp *dp = bridge_to_dp(bridge);
>+
>+	dw_dp_video_disable(dp);
>+	dw_dp_link_disable(dp);
>+	bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
>+	dw_dp_reset(dp);
>+}
>+
>+static bool dw_dp_hpd_detect_link(struct dw_dp *dp)
>+{
>+	int ret;
>+
>+	ret = phy_power_on(dp->phy);
>+	if (ret < 0)
>+		return false;
>+	ret = dw_dp_link_parse(dp);
>+	phy_power_off(dp->phy);
>+
>+	return !ret;
>+}
>+
>+static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge)
>+{
>+	struct dw_dp *dp = bridge_to_dp(bridge);
>+
>+	if (!dw_dp_hpd_detect(dp))
>+		return connector_status_disconnected;
>+
>+	if (!dw_dp_hpd_detect_link(dp))
>+		return connector_status_disconnected;
>+
>+	return connector_status_connected;
>+}
>+
>+static const struct drm_edid *dw_dp_bridge_edid_read(struct drm_bridge *bridge,
>+						     struct drm_connector *connector)
>+{
>+	struct dw_dp *dp = bridge_to_dp(bridge);
>+	const struct drm_edid *edid;
>+	int ret;
>+
>+	ret = phy_power_on(dp->phy);
>+	if (ret)
>+		return NULL;
>+
>+	edid = drm_edid_read_ddc(connector, &dp->aux.ddc);
>+
>+	phy_power_off(dp->phy);
>+
>+	return edid;
>+}
>+
>+static u32 *dw_dp_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 dw_dp *dp = bridge_to_dp(bridge);
>+	struct dw_dp_link *link = &dp->link;
>+	struct drm_display_info *di = &conn_state->connector->display_info;
>+	struct drm_display_mode mode = crtc_state->mode;
>+	const struct dw_dp_output_format *fmt;
>+	u32 i, j = 0;
>+	u32 *output_fmts;
>+
>+	*num_output_fmts = 0;
>+
>+	output_fmts = kcalloc(ARRAY_SIZE(dw_dp_output_formats), sizeof(*output_fmts), GFP_KERNEL);
>+	if (!output_fmts)
>+		return NULL;
>+
>+	for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) {
>+		fmt = &dw_dp_output_formats[i];
>+
>+		if (fmt->bpc > conn_state->max_bpc)
>+			continue;
>+
>+		if (!(fmt->color_format & di->color_formats))
>+			continue;
>+
>+		if (fmt->color_format == DRM_COLOR_FORMAT_YCBCR420 &&
>+		    !link->vsc_sdp_supported)
>+			continue;
>+
>+		if (fmt->color_format != DRM_COLOR_FORMAT_YCBCR420 &&
>+		    drm_mode_is_420_only(di, &mode))
>+			continue;
>+
>+		if (!dw_dp_bandwidth_ok(dp, &mode, fmt->bpp, link->lanes, link->rate))
>+			continue;
>+
>+		output_fmts[j++] = fmt->bus_format;
>+	}
>+
>+	*num_output_fmts = j;
>+
>+	return output_fmts;
>+}
>+
>+static const struct drm_bridge_funcs dw_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_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
>+	.atomic_get_output_bus_fmts = dw_dp_bridge_atomic_get_output_bus_fmts,
>+	.atomic_check = dw_dp_bridge_atomic_check,
>+	.mode_valid = dw_dp_bridge_mode_valid,
>+	.atomic_enable = dw_dp_bridge_atomic_enable,
>+	.atomic_disable = dw_dp_bridge_atomic_disable,
>+	.detect = dw_dp_bridge_detect,
>+	.edid_read = dw_dp_bridge_edid_read,
>+};
>+
>+static int dw_dp_link_retrain(struct dw_dp *dp)
>+{
>+	struct drm_device *dev = dp->bridge.dev;
>+	struct drm_modeset_acquire_ctx ctx;
>+	int ret;
>+
>+	if (!dw_dp_needs_link_retrain(dp))
>+		return 0;
>+
>+	dev_dbg(dp->dev, "Retraining link\n");
>+
>+	drm_modeset_acquire_init(&ctx, 0);
>+	for (;;) {
>+		ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx);
>+		if (ret != -EDEADLK)
>+			break;
>+
>+		drm_modeset_backoff(&ctx);
>+	}
>+
>+	if (!ret)
>+		ret = dw_dp_link_train(dp);
>+
>+	drm_modeset_drop_locks(&ctx);
>+	drm_modeset_acquire_fini(&ctx);
>+
>+	return ret;
>+}
>+
>+static void dw_dp_hpd_work(struct work_struct *work)
>+{
>+	struct dw_dp *dp = container_of(work, struct dw_dp, hpd_work);
>+	bool long_hpd;
>+	int ret;
>+
>+	mutex_lock(&dp->irq_lock);
>+	long_hpd = dp->hotplug.long_hpd;
>+	mutex_unlock(&dp->irq_lock);
>+
>+	dev_dbg(dp->dev, "[drm] Get hpd irq - %s\n", long_hpd ? "long" : "short");
>+
>+	if (!long_hpd) {
>+		if (dw_dp_needs_link_retrain(dp)) {
>+			ret = dw_dp_link_retrain(dp);
>+			if (ret)
>+				dev_warn(dp->dev, "Retrain link failed\n");
>+		}
>+	} else {
>+		drm_helper_hpd_irq_event(dp->bridge.dev);
>+	}
>+}
>+
>+static void dw_dp_handle_hpd_event(struct dw_dp *dp)
>+{
>+	u32 value;
>+
>+	mutex_lock(&dp->irq_lock);
>+	regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value);
>+
>+	if (value & HPD_IRQ) {
>+		dev_dbg(dp->dev, "IRQ from the HPD\n");
>+		dp->hotplug.long_hpd = false;
>+		regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_IRQ);
>+	}
>+
>+	if (value & HPD_HOT_PLUG) {
>+		dev_dbg(dp->dev, "Hot plug detected\n");
>+		dp->hotplug.long_hpd = true;
>+		regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG);
>+	}
>+
>+	if (value & HPD_HOT_UNPLUG) {
>+		dev_dbg(dp->dev, "Unplug detected\n");
>+		dp->hotplug.long_hpd = true;
>+		regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_UNPLUG);
>+	}
>+	mutex_unlock(&dp->irq_lock);
>+
>+	schedule_work(&dp->hpd_work);
>+}
>+
>+static irqreturn_t dw_dp_irq(int irq, void *data)
>+{
>+	struct dw_dp *dp = data;
>+	u32 value;
>+
>+	regmap_read(dp->regmap, DW_DP_GENERAL_INTERRUPT, &value);
>+	if (!value)
>+		return IRQ_NONE;
>+
>+	if (value & HPD_EVENT)
>+		dw_dp_handle_hpd_event(dp);
>+
>+	if (value & AUX_REPLY_EVENT) {
>+		regmap_write(dp->regmap, DW_DP_GENERAL_INTERRUPT, AUX_REPLY_EVENT);
>+		complete(&dp->complete);
>+	}
>+
>+	return IRQ_HANDLED;
>+}
>+
>+static const struct regmap_range dw_dp_readable_ranges[] = {
>+	regmap_reg_range(DW_DP_VERSION_NUMBER, DW_DP_ID),
>+	regmap_reg_range(DW_DP_CONFIG_REG1, DW_DP_CONFIG_REG3),
>+	regmap_reg_range(DW_DP_CCTL, DW_DP_SOFT_RESET_CTRL),
>+	regmap_reg_range(DW_DP_VSAMPLE_CTRL, DW_DP_VIDEO_HBLANK_INTERVAL),
>+	regmap_reg_range(DW_DP_AUD_CONFIG1, DW_DP_AUD_CONFIG1),
>+	regmap_reg_range(DW_DP_SDP_VERTICAL_CTRL, DW_DP_SDP_STATUS_EN),
>+	regmap_reg_range(DW_DP_PHYIF_CTRL, DW_DP_PHYIF_PWRDOWN_CTRL),
>+	regmap_reg_range(DW_DP_AUX_CMD, DW_DP_AUX_DATA3),
>+	regmap_reg_range(DW_DP_GENERAL_INTERRUPT, DW_DP_HPD_INTERRUPT_ENABLE),
>+};
>+
>+static const struct regmap_access_table dw_dp_readable_table = {
>+	.yes_ranges     = dw_dp_readable_ranges,
>+	.n_yes_ranges   = ARRAY_SIZE(dw_dp_readable_ranges),
>+};
>+
>+static const struct regmap_config dw_dp_regmap_config = {
>+	.reg_bits = 32,
>+	.reg_stride = 4,
>+	.val_bits = 32,
>+	.fast_io = true,
>+	.max_register = DW_DP_MAX_REGISTER,
>+	.rd_table = &dw_dp_readable_table,
>+};
>+
>+static void dw_dp_phy_exit(void *data)
>+{
>+	struct dw_dp *dp = data;
>+
>+	phy_exit(dp->phy);
>+}
>+
>+struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
>+			 const struct dw_dp_plat_data *plat_data)
>+{
>+	struct platform_device *pdev = to_platform_device(dev);
>+	struct dw_dp *dp;
>+	struct drm_bridge *bridge;
>+	void __iomem *res;
>+	int ret;
>+
>+	dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
>+	if (!dp)
>+		return ERR_PTR(-ENOMEM);
>+
>+	dp->dev = dev;
>+	dp->video.pixel_mode = DW_DP_MP_QUAD_PIXEL;
>+
>+	dp->plat_data.max_link_rate = plat_data->max_link_rate;
>+	bridge = &dp->bridge;
>+	mutex_init(&dp->irq_lock);
>+	INIT_WORK(&dp->hpd_work, dw_dp_hpd_work);
>+	init_completion(&dp->complete);
>+
>+	res = devm_platform_ioremap_resource(pdev, 0);
>+	if (IS_ERR(res))
>+		return ERR_CAST(res);
>+
>+	dp->regmap = devm_regmap_init_mmio(dev, res, &dw_dp_regmap_config);
>+	if (IS_ERR(dp->regmap)) {
>+		dev_err_probe(dev, PTR_ERR(dp->regmap), "failed to create regmap\n");
>+		return ERR_CAST(dp->regmap);
>+	}
>+
>+	dp->phy = devm_of_phy_get(dev, dev->of_node, NULL);
>+	if (IS_ERR(dp->phy)) {
>+		dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n");
>+		return ERR_CAST(dp->phy);
>+	}
>+
>+	dp->apb_clk = devm_clk_get_enabled(dev, "apb");
>+	if (IS_ERR(dp->apb_clk)) {
>+		dev_err_probe(dev, PTR_ERR(dp->apb_clk), "failed to get apb clock\n");
>+		return ERR_CAST(dp->apb_clk);
>+	}
>+
>+	dp->aux_clk = devm_clk_get_enabled(dev, "aux");
>+	if (IS_ERR(dp->aux_clk)) {
>+		dev_err_probe(dev, PTR_ERR(dp->aux_clk), "failed to get aux clock\n");
>+		return ERR_CAST(dp->aux_clk);
>+	}
>+
>+	dp->i2s_clk = devm_clk_get(dev, "i2s");
>+	if (IS_ERR(dp->i2s_clk)) {
>+		dev_err_probe(dev, PTR_ERR(dp->i2s_clk), "failed to get i2s clock\n");
>+		return ERR_CAST(dp->i2s_clk);
>+	}
>+
>+	dp->spdif_clk = devm_clk_get(dev, "spdif");
>+	if (IS_ERR(dp->spdif_clk)) {
>+		dev_err_probe(dev, PTR_ERR(dp->spdif_clk), "failed to get spdif clock\n");
>+		return ERR_CAST(dp->spdif_clk);
>+	}
>+
>+	dp->hdcp_clk = devm_clk_get(dev, "hdcp");
>+	if (IS_ERR(dp->hdcp_clk)) {
>+		dev_err_probe(dev, PTR_ERR(dp->hdcp_clk), "failed to get hdcp clock\n");
>+		return ERR_CAST(dp->hdcp_clk);
>+	}
>+
>+	dp->rstc = devm_reset_control_get(dev, NULL);
>+	if (IS_ERR(dp->rstc)) {
>+		dev_err_probe(dev, PTR_ERR(dp->rstc), "failed to get reset control\n");
>+		return ERR_CAST(dp->rstc);
>+	}
>+
>+	bridge->of_node = dev->of_node;
>+	bridge->funcs = &dw_dp_bridge_funcs;
>+	bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
>+	bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
>+	bridge->ycbcr_420_allowed = true;
>+
>+	dp->aux.dev = dev;
>+	dp->aux.drm_dev = encoder->dev;
>+	dp->aux.name = dev_name(dev);
>+	dp->aux.transfer = dw_dp_aux_transfer;
>+	ret = drm_dp_aux_register(&dp->aux);
>+	if (ret) {
>+		dev_err_probe(dev, ret, "Aux register failed\n");
>+		return ERR_PTR(ret);
>+	}
>+
>+	ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
>+	if (ret)
>+		dev_err_probe(dev, ret, "Failed to attach bridge\n");
>+
>+	dw_dp_init_hw(dp);
>+
>+	ret = phy_init(dp->phy);
>+	if (ret) {
>+		dev_err_probe(dev, ret, "phy init failed\n");
>+		return ERR_PTR(ret);
>+	}
>+
>+	ret = devm_add_action_or_reset(dev, dw_dp_phy_exit, dp);
>+	if (ret)
>+		return ERR_PTR(ret);
>+
>+	dp->irq = platform_get_irq(pdev, 0);
>+	if (dp->irq < 0)
>+		return ERR_PTR(ret);
>+
>+	ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
>+					IRQF_ONESHOT, dev_name(dev), dp);
>+	if (ret) {
>+		dev_err_probe(dev, ret, "failed to request irq\n");
>+		return ERR_PTR(ret);
>+	}
>+
>+	return dp;
>+}
>+EXPORT_SYMBOL_GPL(dw_dp_bind);
>+
>+MODULE_AUTHOR("Andy Yan <andyshrk@163.com>");
>+MODULE_DESCRIPTION("DW DP Core Library");
>+MODULE_LICENSE("GPL");
>diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h
>new file mode 100644
>index 0000000000000..d05df49fd8846
>--- /dev/null
>+++ b/include/drm/bridge/dw_dp.h
>@@ -0,0 +1,20 @@
>+/* SPDX-License-Identifier: GPL-2.0-or-later */
>+/*
>+ * Copyright (c) 2025 Rockchip Electronics Co., Ltd.
>+ */
>+
>+#ifndef __DW_DP__
>+#define __DW_DP__
>+
>+#include <linux/device.h>
>+
>+struct drm_encoder;
>+struct dw_dp;
>+
>+struct dw_dp_plat_data {
>+	u32 max_link_rate;
>+};
>+
>+struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
>+			 const struct dw_dp_plat_data *plat_data);
>+#endif /* __DW_DP__ */
>-- 
>2.43.0
>
>
>_______________________________________________
>Linux-rockchip mailing list
>Linux-rockchip@lists.infradead.org
>http://lists.infradead.org/mailman/listinfo/linux-rockchip

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3 2/9] drm/bridge: synopsys: Add DW DPTX Controller support library
  2025-05-14 12:15   ` Andy Yan
@ 2025-05-24  7:46     ` Dmitry Baryshkov
  2025-05-24  7:59       ` Andy Yan
  0 siblings, 1 reply; 21+ messages in thread
From: Dmitry Baryshkov @ 2025-05-24  7:46 UTC (permalink / raw)
  To: Andy Yan
  Cc: heiko, neil.armstrong, sebastian.reichel, stephen, dri-devel, hjc,
	mripard, linux-kernel, linux-rockchip, devicetree, yubing.zhang,
	naoki, Laurent.pinchart, Andy Yan, krzk+dt, robh,
	linux-arm-kernel

On Wed, May 14, 2025 at 08:15:55PM +0800, Andy Yan wrote:
> Hello Dmitry,
> 
>     Would it be convenient for you to continue reviewing this patch at your convenience?
> Or let me know your opinion on this patch.
>  
>    There is still one unresolved issue highlighted by you in the first version: try to use drm_dp_read_sink_count_cap
> instead of dw_dp_has_sink_count. But there is no response on my patch try to Pass down connector to drm bridge detect hook[0].
>    I don't know how to proceed with this patch at the moment.

Please excuse me for the delay. It seems Maxime doesn't like that patch,
but I do not see a viable generic alternative.

> 
> 
> [0]https://lore.kernel.org/dri-devel/20250321085345.136380-1-andyshrk@163.com/
> 
> Thank you.
>   
> At 2025-04-03 11:37:30, "Andy Yan" <andyshrk@163.com> wrote:
> >From: Andy Yan <andy.yan@rock-chips.com>
> >
> >The DW DP TX Controller is compliant with the DisplayPort Specification
> >Version 1.4 with the following features:
> >
> >* DisplayPort 1.4a
> >* Main Link: 1/2/4 lanes
> >* Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps
> >* AUX channel 1Mbps
> >* Single Stream Transport(SST)
> >* Multistream Transport (MST)
> >* Type-C support (alternate mode)
> >* HDCP 2.2, HDCP 1.3
> >* Supports up to 8/10 bits per color component
> >* Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0
> >* Pixel clock up to 594MHz
> >* I2S, SPDIF audio interface
> >
> >Add library with common helpers to make it can be shared with
> >other SoC.
> >
> >Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
> >
> >---
> >
> >Changes in v3:
> >- Rebase on drm-misc-next
> >- Switch to common helpers to power up/down dp link
> >- Only pass parameters to phy that should be set
> >
> >Changes in v2:
> >- Fix compile error when build as module
> >- Add phy init
> >- Only use one dw_dp_link_train_set
> >- inline dw_dp_phy_update_vs_emph
> >- Use dp_sdp
> >- Check return value of drm_modeset_lock
> >- Merge code in atomic_pre_enable/mode_fixup to atomic_check
> >- Return NULL if can't find a supported output format
> >- Fix max_link_rate from plat_data
> >
> > drivers/gpu/drm/bridge/synopsys/Kconfig  |    7 +
> > drivers/gpu/drm/bridge/synopsys/Makefile |    1 +
> > drivers/gpu/drm/bridge/synopsys/dw-dp.c  | 2053 ++++++++++++++++++++++
> > include/drm/bridge/dw_dp.h               |   20 +
> > 4 files changed, 2081 insertions(+)
> > create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c
> > create mode 100644 include/drm/bridge/dw_dp.h
> >
> >diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig
> >index f3ab2f985f8ca..2c5e532410de9 100644
> >--- a/drivers/gpu/drm/bridge/synopsys/Kconfig
> >+++ b/drivers/gpu/drm/bridge/synopsys/Kconfig
> >@@ -1,4 +1,11 @@
> > # SPDX-License-Identifier: GPL-2.0-only
> >+config DRM_DW_DP
> >+	tristate
> >+	select DRM_DISPLAY_HELPER
> >+	select DRM_DISPLAY_DP_HELPER
> >+	select DRM_KMS_HELPER
> >+	select REGMAP_MMIO
> >+
> > config DRM_DW_HDMI
> > 	tristate
> > 	select DRM_DISPLAY_HDMI_HELPER
> >diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile
> >index 9dc376d220ad7..4dada44029acf 100644
> >--- a/drivers/gpu/drm/bridge/synopsys/Makefile
> >+++ b/drivers/gpu/drm/bridge/synopsys/Makefile
> >@@ -1,4 +1,5 @@
> > # SPDX-License-Identifier: GPL-2.0-only
> >+obj-$(CONFIG_DRM_DW_DP) += dw-dp.o
> > 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_GP_AUDIO) += dw-hdmi-gp-audio.o
> >diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> >new file mode 100644
> >index 0000000000000..828b34d676a19
> >--- /dev/null
> >+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
> >@@ -0,0 +1,2053 @@
> >+// SPDX-License-Identifier: GPL-2.0
> >+/*
> >+ * Synopsys DesignWare Cores DisplayPort Transmitter Controller
> >+ *
> >+ * Copyright (c) 2025 Rockchip Electronics Co., Ltd.
> >+ *
> >+ * Author: Andy Yan <andy.yan@rock-chips.com>
> >+ */
> >+#include <linux/bitfield.h>
> >+#include <linux/clk.h>
> >+#include <linux/component.h>
> >+#include <linux/iopoll.h>
> >+#include <linux/irq.h>
> >+#include <linux/media-bus-format.h>
> >+#include <linux/of_device.h>
> >+#include <linux/of_graph.h>
> >+#include <linux/platform_device.h>
> >+#include <linux/regmap.h>
> >+#include <linux/reset.h>
> >+#include <linux/phy/phy.h>
> >+#include <linux/unaligned.h>
> >+
> >+#include <drm/bridge/dw_dp.h>
> >+#include <drm/drm_atomic_helper.h>
> >+#include <drm/drm_bridge.h>
> >+#include <drm/drm_bridge_connector.h>
> >+#include <drm/display/drm_dp_helper.h>
> >+#include <drm/display/drm_hdmi_helper.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_simple_kms_helper.h>
> >+
> >+#define DW_DP_VERSION_NUMBER			0x0000
> >+#define DW_DP_VERSION_TYPE			0x0004
> >+#define DW_DP_ID					0x0008
> >+
> >+#define DW_DP_CONFIG_REG1			0x0100
> >+#define DW_DP_CONFIG_REG2			0x0104
> >+#define DW_DP_CONFIG_REG3			0x0108
> >+
> >+#define DW_DP_CCTL				0x0200
> >+#define FORCE_HPD				BIT(4)
> >+#define DEFAULT_FAST_LINK_TRAIN_EN		BIT(2)
> >+#define ENHANCE_FRAMING_EN			BIT(1)
> >+#define SCRAMBLE_DIS				BIT(0)
> >+#define DW_DP_SOFT_RESET_CTRL			0x0204
> >+#define VIDEO_RESET				BIT(5)
> >+#define AUX_RESET				BIT(4)
> >+#define AUDIO_SAMPLER_RESET			BIT(3)
> >+#define HDCP_MODULE_RESET			BIT(2)
> >+#define PHY_SOFT_RESET				BIT(1)
> >+#define CONTROLLER_RESET			BIT(0)
> >+
> >+#define DW_DP_VSAMPLE_CTRL			0x0300
> >+#define PIXEL_MODE_SELECT			GENMASK(22, 21)
> >+#define VIDEO_MAPPING				GENMASK(20, 16)
> >+#define VIDEO_STREAM_ENABLE			BIT(5)
> >+
> >+#define DW_DP_VSAMPLE_STUFF_CTRL1		0x0304
> >+
> >+#define DW_DP_VSAMPLE_STUFF_CTRL2		0x0308
> >+
> >+#define DW_DP_VINPUT_POLARITY_CTRL		0x030c
> >+#define DE_IN_POLARITY				BIT(2)
> >+#define HSYNC_IN_POLARITY			BIT(1)
> >+#define VSYNC_IN_POLARITY			BIT(0)
> >+
> >+#define DW_DP_VIDEO_CONFIG1			0x0310
> >+#define HACTIVE					GENMASK(31, 16)
> >+#define HBLANK					GENMASK(15, 2)
> >+#define I_P					BIT(1)
> >+#define R_V_BLANK_IN_OSC			BIT(0)
> >+
> >+#define DW_DP_VIDEO_CONFIG2			0x0314
> >+#define VBLANK					GENMASK(31, 16)
> >+#define VACTIVE					GENMASK(15, 0)
> >+
> >+#define DW_DP_VIDEO_CONFIG3			0x0318
> >+#define H_SYNC_WIDTH				GENMASK(31, 16)
> >+#define H_FRONT_PORCH				GENMASK(15, 0)
> >+
> >+#define DW_DP_VIDEO_CONFIG4			0x031c
> >+#define V_SYNC_WIDTH				GENMASK(31, 16)
> >+#define V_FRONT_PORCH				GENMASK(15, 0)
> >+
> >+#define DW_DP_VIDEO_CONFIG5			0x0320
> >+#define INIT_THRESHOLD_HI			GENMASK(22, 21)
> >+#define AVERAGE_BYTES_PER_TU_FRAC		GENMASK(19, 16)
> >+#define INIT_THRESHOLD				GENMASK(13, 7)
> >+#define AVERAGE_BYTES_PER_TU			GENMASK(6, 0)
> >+
> >+#define DW_DP_VIDEO_MSA1				0x0324
> >+#define VSTART					GENMASK(31, 16)
> >+#define HSTART					GENMASK(15, 0)
> >+
> >+#define DW_DP_VIDEO_MSA2			0x0328
> >+#define MISC0					GENMASK(31, 24)
> >+
> >+#define DW_DP_VIDEO_MSA3			0x032c
> >+#define MISC1					GENMASK(31, 24)
> >+
> >+#define DW_DP_VIDEO_HBLANK_INTERVAL		0x0330
> >+#define HBLANK_INTERVAL_EN			BIT(16)
> >+#define HBLANK_INTERVAL				GENMASK(15, 0)
> >+
> >+#define DW_DP_AUD_CONFIG1			0x0400
> >+#define AUDIO_TIMESTAMP_VERSION_NUM		GENMASK(29, 24)
> >+#define AUDIO_PACKET_ID				GENMASK(23, 16)
> >+#define AUDIO_MUTE				BIT(15)
> >+#define NUM_CHANNELS				GENMASK(14, 12)
> >+#define HBR_MODE_ENABLE				BIT(10)
> >+#define AUDIO_DATA_WIDTH			GENMASK(9, 5)
> >+#define AUDIO_DATA_IN_EN			GENMASK(4, 1)
> >+#define AUDIO_INF_SELECT			BIT(0)
> >+
> >+#define DW_DP_SDP_VERTICAL_CTRL			0x0500
> >+#define EN_VERTICAL_SDP				BIT(2)
> >+#define EN_AUDIO_STREAM_SDP			BIT(1)
> >+#define EN_AUDIO_TIMESTAMP_SDP			BIT(0)
> >+#define DW_DP_SDP_HORIZONTAL_CTRL		0x0504
> >+#define EN_HORIZONTAL_SDP			BIT(2)
> >+#define DW_DP_SDP_STATUS_REGISTER		0x0508
> >+#define DW_DP_SDP_MANUAL_CTRL			0x050c
> >+#define DW_DP_SDP_STATUS_EN			0x0510
> >+
> >+#define DW_DP_SDP_REGISTER_BANK			0x0600
> >+#define SDP_REGS				GENMASK(31, 0)
> >+
> >+#define DW_DP_PHYIF_CTRL			0x0a00
> >+#define PHY_WIDTH				BIT(25)
> >+#define PHY_POWERDOWN				GENMASK(20, 17)
> >+#define PHY_BUSY				GENMASK(15, 12)
> >+#define SSC_DIS					BIT(16)
> >+#define XMIT_ENABLE				GENMASK(11, 8)
> >+#define PHY_LANES				GENMASK(7, 6)
> >+#define PHY_RATE				GENMASK(5, 4)
> >+#define TPS_SEL					GENMASK(3, 0)
> >+
> >+#define DW_DP_PHY_TX_EQ				0x0a04
> >+#define DW_DP_CUSTOMPAT0			0x0a08
> >+#define DW_DP_CUSTOMPAT1			0x0a0c
> >+#define DW_DP_CUSTOMPAT2			0x0a10
> >+#define DW_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET	0x0a14
> >+#define DW_DP_PHYIF_PWRDOWN_CTRL		0x0a18
> >+
> >+#define DW_DP_AUX_CMD				0x0b00
> >+#define AUX_CMD_TYPE				GENMASK(31, 28)
> >+#define AUX_ADDR				GENMASK(27, 8)
> >+#define I2C_ADDR_ONLY				BIT(4)
> >+#define AUX_LEN_REQ				GENMASK(3, 0)
> >+
> >+#define DW_DP_AUX_STATUS			0x0b04
> >+#define AUX_TIMEOUT				BIT(17)
> >+#define AUX_BYTES_READ				GENMASK(23, 19)
> >+#define AUX_STATUS				GENMASK(7, 4)
> >+
> >+#define DW_DP_AUX_DATA0				0x0b08
> >+#define DW_DP_AUX_DATA1				0x0b0c
> >+#define DW_DP_AUX_DATA2				0x0b10
> >+#define DW_DP_AUX_DATA3				0x0b14
> >+
> >+#define DW_DP_GENERAL_INTERRUPT			0x0d00
> >+#define VIDEO_FIFO_OVERFLOW_STREAM0		BIT(6)
> >+#define AUDIO_FIFO_OVERFLOW_STREAM0		BIT(5)
> >+#define SDP_EVENT_STREAM0			BIT(4)
> >+#define AUX_CMD_INVALID				BIT(3)
> >+#define HDCP_EVENT				BIT(2)
> >+#define AUX_REPLY_EVENT				BIT(1)
> >+#define HPD_EVENT				BIT(0)
> >+
> >+#define DW_DP_GENERAL_INTERRUPT_ENABLE		0x0d04
> >+#define HDCP_EVENT_EN				BIT(2)
> >+#define AUX_REPLY_EVENT_EN			BIT(1)
> >+#define HPD_EVENT_EN				BIT(0)
> >+
> >+#define DW_DP_HPD_STATUS			0x0d08
> >+#define HPD_STATE				GENMASK(11, 9)
> >+#define HPD_STATUS				BIT(8)
> >+#define HPD_HOT_UNPLUG				BIT(2)
> >+#define HPD_HOT_PLUG				BIT(1)
> >+#define HPD_IRQ					BIT(0)
> >+
> >+#define DW_DP_HPD_INTERRUPT_ENABLE		0x0d0c
> >+#define HPD_UNPLUG_ERR_EN			BIT(3)
> >+#define HPD_UNPLUG_EN				BIT(2)
> >+#define HPD_PLUG_EN				BIT(1)
> >+#define HPD_IRQ_EN				BIT(0)
> >+
> >+#define DW_DP_HDCP_CFG				0x0e00
> >+#define DPCD12PLUS				BIT(7)
> >+#define CP_IRQ					BIT(6)
> >+#define BYPENCRYPTION				BIT(5)
> >+#define HDCP_LOCK				BIT(4)
> >+#define ENCRYPTIONDISABLE			BIT(3)
> >+#define ENABLE_HDCP_13				BIT(2)
> >+#define ENABLE_HDCP				BIT(1)
> >+
> >+#define DW_DP_HDCP_OBS				0x0e04
> >+#define HDCP22_RE_AUTHENTICATION_REQ		BIT(31)
> >+#define HDCP22_AUTHENTICATION_FAILED		BIT(30)
> >+#define HDCP22_AUTHENTICATION_SUCCESS		BIT(29)
> >+#define HDCP22_CAPABLE_SINK			BIT(28)
> >+#define HDCP22_SINK_CAP_CHECK_COMPLETE		BIT(27)
> >+#define HDCP22_STATE				GENMASK(26, 24)
> >+#define HDCP22_BOOTED				BIT(23)
> >+#define HDCP13_BSTATUS				GENMASK(22, 19)
> >+#define REPEATER				BIT(18)
> >+#define HDCP_CAPABLE				BIT(17)
> >+#define STATEE					GENMASK(16, 14)
> >+#define STATEOEG				GENMASK(13, 11)
> >+#define STATER					GENMASK(10, 8)
> >+#define STATEA					GENMASK(7, 4)
> >+#define SUBSTATEA				GENMASK(3, 1)
> >+#define HDCPENGAGED				BIT(0)
> >+
> >+#define DW_DP_HDCP_APIINTCLR			0x0e08
> >+#define DW_DP_HDCP_APIINTSTAT			0x0e0c
> >+#define DW_DP_HDCP_APIINTMSK			0x0e10
> >+#define HDCP22_GPIOINT				BIT(8)
> >+#define HDCP_ENGAGED				BIT(7)
> >+#define HDCP_FAILED				BIT(6)
> >+#define KSVSHA1CALCDONEINT			BIT(5)
> >+#define AUXRESPNACK7TIMES			BIT(4)
> >+#define AUXRESPTIMEOUT				BIT(3)
> >+#define AUXRESPDEFER7TIMES			BIT(2)
> >+#define KSVACCESSINT				BIT(0)
> >+
> >+#define DW_DP_HDCP_KSVMEMCTRL			0x0e18
> >+#define KSVSHA1STATUS				BIT(4)
> >+#define KSVMEMACCESS				BIT(1)
> >+#define KSVMEMREQUEST				BIT(0)
> >+
> >+#define DW_DP_HDCP_REG_BKSV0			0x3600
> >+#define DW_DP_HDCP_REG_BKSV1			0x3604
> >+#define DW_DP_HDCP_REG_ANCONF			0x3608
> >+#define AN_BYPASS				BIT(0)
> >+
> >+#define DW_DP_HDCP_REG_AN0			0x360c
> >+#define DW_DP_HDCP_REG_AN1			0x3610
> >+#define DW_DP_HDCP_REG_RMLCTL			0x3614
> >+#define ODPK_DECRYPT_ENABLE			BIT(0)
> >+
> >+#define DW_DP_HDCP_REG_RMLSTS			0x3618
> >+#define IDPK_WR_OK_STS				BIT(6)
> >+#define	IDPK_DATA_INDEX				GENMASK(5, 0)
> >+#define DW_DP_HDCP_REG_SEED			0x361c
> >+#define DW_DP_HDCP_REG_DPK0			0x3620
> >+#define DW_DP_HDCP_REG_DPK1			0x3624
> >+#define DW_DP_HDCP22_GPIOSTS			0x3628
> >+#define DW_DP_HDCP22_GPIOCHNGSTS		0x362c
> >+#define DW_DP_HDCP_REG_DPK_CRC			0x3630
> >+
> >+#define DW_DP_MAX_REGISTER			DW_DP_HDCP_REG_DPK_CRC
> >+
> >+#define SDP_REG_BANK_SIZE			16
> >+
> >+struct dw_dp_link_caps {
> >+	bool enhanced_framing;
> >+	bool tps3_supported;
> >+	bool tps4_supported;
> >+	bool fast_training;
> >+	bool channel_coding;
> >+	bool ssc;
> >+};
> >+
> >+struct dw_dp_link_train_set {
> >+	unsigned int voltage_swing[4];
> >+	unsigned int pre_emphasis[4];
> >+	bool voltage_max_reached[4];
> >+	bool pre_max_reached[4];
> >+};
> >+
> >+struct dw_dp_link_train {
> >+	struct dw_dp_link_train_set adjust;
> >+	bool clock_recovered;
> >+	bool channel_equalized;
> >+};
> >+
> >+struct dw_dp_link {
> >+	u8 dpcd[DP_RECEIVER_CAP_SIZE];
> >+	unsigned char revision;
> >+	unsigned int rate;
> >+	unsigned int lanes;
> >+	u8 sink_count;
> >+	u8 vsc_sdp_supported;
> >+	struct dw_dp_link_caps caps;
> >+	struct dw_dp_link_train train;
> >+	struct drm_dp_desc desc;
> >+};
> >+
> >+struct dw_dp_video {
> >+	struct drm_display_mode mode;
> >+	u8 video_mapping;
> >+	u8 pixel_mode;
> >+	u8 color_format;
> >+	u8 bpc;
> >+	u8 bpp;
> >+};
> >+
> >+struct dw_dp_sdp {
> >+	struct dp_sdp base;
> >+	unsigned long flags;
> >+};
> >+
> >+struct dw_dp_hotplug {
> >+	bool long_hpd;
> >+};
> >+
> >+struct dw_dp {
> >+	struct drm_bridge bridge;
> >+	struct device *dev;
> >+	struct regmap *regmap;
> >+	struct phy *phy;
> >+	struct clk *apb_clk;
> >+	struct clk *aux_clk;
> >+	struct clk *i2s_clk;
> >+	struct clk *spdif_clk;
> >+	struct clk *hdcp_clk;
> >+	struct reset_control *rstc;
> >+	struct completion complete;
> >+	int irq;
> >+	struct work_struct hpd_work;
> >+	struct dw_dp_hotplug hotplug;
> >+	struct mutex irq_lock;
> >+
> >+	struct drm_dp_aux aux;
> >+
> >+	struct dw_dp_link link;
> >+	struct dw_dp_video video;
> >+	struct dw_dp_plat_data plat_data;
> >+
> >+	DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE);
> >+};
> >+
> >+enum {
> >+	DW_DP_RGB_6BIT,
> >+	DW_DP_RGB_8BIT,
> >+	DW_DP_RGB_10BIT,
> >+	DW_DP_RGB_12BIT,
> >+	DW_DP_RGB_16BIT,
> >+	DW_DP_YCBCR444_8BIT,
> >+	DW_DP_YCBCR444_10BIT,
> >+	DW_DP_YCBCR444_12BIT,
> >+	DW_DP_YCBCR444_16BIT,
> >+	DW_DP_YCBCR422_8BIT,
> >+	DW_DP_YCBCR422_10BIT,
> >+	DW_DP_YCBCR422_12BIT,
> >+	DW_DP_YCBCR422_16BIT,
> >+	DW_DP_YCBCR420_8BIT,
> >+	DW_DP_YCBCR420_10BIT,
> >+	DW_DP_YCBCR420_12BIT,
> >+	DW_DP_YCBCR420_16BIT,
> >+};
> >+
> >+enum {
> >+	DW_DP_MP_SINGLE_PIXEL,
> >+	DW_DP_MP_DUAL_PIXEL,
> >+	DW_DP_MP_QUAD_PIXEL,
> >+};
> >+
> >+enum {
> >+	DW_DP_SDP_VERTICAL_INTERVAL = BIT(0),
> >+	DW_DP_SDP_HORIZONTAL_INTERVAL = BIT(1),
> >+};
> >+
> >+enum {
> >+	DW_DP_HPD_STATE_IDLE,
> >+	DW_DP_HPD_STATE_UNPLUG,
> >+	DP_DP_HPD_STATE_TIMEOUT = 4,
> >+	DW_DP_HPD_STATE_PLUG = 7
> >+};
> >+
> >+enum {
> >+	DW_DP_PHY_PATTERN_NONE,
> >+	DW_DP_PHY_PATTERN_TPS_1,
> >+	DW_DP_PHY_PATTERN_TPS_2,
> >+	DW_DP_PHY_PATTERN_TPS_3,
> >+	DW_DP_PHY_PATTERN_TPS_4,
> >+	DW_DP_PHY_PATTERN_SERM,
> >+	DW_DP_PHY_PATTERN_PBRS7,
> >+	DW_DP_PHY_PATTERN_CUSTOM_80BIT,
> >+	DW_DP_PHY_PATTERN_CP2520_1,
> >+	DW_DP_PHY_PATTERN_CP2520_2,
> >+};
> >+
> >+struct dw_dp_output_format {
> >+	u32 bus_format;
> >+	u32 color_format;
> >+	u8 video_mapping;
> >+	u8 bpc;
> >+	u8 bpp;
> >+};
> >+
> >+static const struct dw_dp_output_format dw_dp_output_formats[] = {
> >+	{ MEDIA_BUS_FMT_RGB101010_1X30, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_10BIT, 10, 30 },
> >+	{ MEDIA_BUS_FMT_RGB888_1X24, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_8BIT, 8, 24 },
> >+	{ MEDIA_BUS_FMT_YUV10_1X30, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_10BIT, 10, 30 },
> >+	{ MEDIA_BUS_FMT_YUV8_1X24, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_8BIT, 8, 24},
> >+	{ MEDIA_BUS_FMT_YUYV10_1X20, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_10BIT, 10, 20 },
> >+	{ MEDIA_BUS_FMT_YUYV8_1X16, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_8BIT, 8, 16 },
> >+	{ MEDIA_BUS_FMT_UYYVYY10_0_5X30, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_10BIT, 10, 15 },
> >+	{ MEDIA_BUS_FMT_UYYVYY8_0_5X24, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_8BIT, 8, 12 },
> >+	{ MEDIA_BUS_FMT_RGB666_1X24_CPADHI, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_6BIT, 6, 18 },
> >+};
> >+
> >+static const struct dw_dp_output_format *dw_dp_get_output_format(u32 bus_format)
> >+{
> >+	unsigned int i;
> >+
> >+	for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++)
> >+		if (dw_dp_output_formats[i].bus_format == bus_format)
> >+			return &dw_dp_output_formats[i];
> >+
> >+	return NULL;
> >+}
> >+
> >+static inline struct dw_dp *bridge_to_dp(struct drm_bridge *b)
> >+{
> >+	return container_of(b, struct dw_dp, bridge);
> >+}
> >+
> >+static inline void dw_dp_phy_set_pattern(struct dw_dp *dp, u32 pattern)
> >+{
> >+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, TPS_SEL,
> >+			   FIELD_PREP(TPS_SEL, pattern));
> >+}
> >+
> >+static void dw_dp_phy_xmit_enable(struct dw_dp *dp, u32 lanes)
> >+{
> >+	u32 xmit_enable;
> >+
> >+	switch (lanes) {
> >+	case 4:
> >+	case 2:
> >+	case 1:
> >+		xmit_enable = GENMASK(lanes - 1, 0);
> >+		break;
> >+	case 0:
> >+	default:
> >+		xmit_enable = 0;
> >+		break;
> >+	}
> >+
> >+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, XMIT_ENABLE,
> >+			   FIELD_PREP(XMIT_ENABLE, xmit_enable));
> >+}
> >+
> >+static bool dw_dp_bandwidth_ok(struct dw_dp *dp,
> >+			       const struct drm_display_mode *mode, u32 bpp,
> >+			       unsigned int lanes, unsigned int rate)
> >+{
> >+	u32 max_bw, req_bw;
> >+
> >+	req_bw = mode->clock * bpp / 8;
> >+	max_bw = lanes * rate;
> >+	if (req_bw > max_bw)
> >+		return false;
> >+
> >+	return true;
> >+}
> >+
> >+static bool dw_dp_hpd_detect(struct dw_dp *dp)
> >+{
> >+	u32 value;
> >+
> >+	regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value);
> >+
> >+	return FIELD_GET(HPD_STATE, value) == DW_DP_HPD_STATE_PLUG;
> >+}
> >+
> >+static void dw_dp_link_caps_reset(struct dw_dp_link_caps *caps)
> >+{
> >+	caps->enhanced_framing = false;
> >+	caps->tps3_supported = false;
> >+	caps->tps4_supported = false;
> >+	caps->fast_training = false;
> >+	caps->channel_coding = false;
> >+}
> >+
> >+/* TODO: pull these two functions to DRM DP helpers */
> >+static void dw_dp_link_reset(struct dw_dp_link *link)
> >+{
> >+	link->vsc_sdp_supported = 0;
> >+	link->sink_count = 0;
> >+	link->revision = 0;
> >+	link->rate = 0;
> >+	link->lanes = 0;
> >+
> >+	dw_dp_link_caps_reset(&link->caps);
> >+	memset(link->dpcd, 0, sizeof(link->dpcd));
> >+}
> >+
> >+/* TODO: switch to drm_dp_read_sink_count_cap once we can get connector*/
> >+static bool dw_dp_has_sink_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
> >+				 const struct drm_dp_desc *desc)
> >+{
> >+	return dpcd[DP_DPCD_REV] >= DP_DPCD_REV_11 &&
> >+	       dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT &&
> >+	       !drm_dp_has_quirk(desc, DP_DPCD_QUIRK_NO_SINK_COUNT);
> >+}
> >+
> >+static int dw_dp_link_parse(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_link *link = &dp->link;
> >+	u8 dpcd;
> >+	int ret;
> >+
> >+	dw_dp_link_reset(link);
> >+
> >+	ret = drm_dp_read_dpcd_caps(&dp->aux, link->dpcd);
> >+	if (ret < 0)
> >+		return ret;
> >+
> >+	drm_dp_read_desc(&dp->aux, &link->desc, drm_dp_is_branch(link->dpcd));
> >+
> >+	if (dw_dp_has_sink_count(link->dpcd, &link->desc)) {
> >+		ret = drm_dp_read_sink_count(&dp->aux);
> >+		if (ret < 0)
> >+			return ret;
> >+
> >+		link->sink_count = ret;
> >+
> >+		/* Dongle connected, but no display */
> >+		if (!link->sink_count)
> >+			return -ENODEV;
> >+	}
> >+
> >+	ret = drm_dp_dpcd_readb(&dp->aux, DP_DPRX_FEATURE_ENUMERATION_LIST, &dpcd);
> >+	if (ret < 0)
> >+		return ret;
> >+
> >+	link->vsc_sdp_supported = !!(dpcd & DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED);
> >+
> >+	link->revision = link->dpcd[DP_DPCD_REV];
> >+	link->rate = min_t(u32, min(dp->plat_data.max_link_rate,
> >+				    dp->phy->attrs.max_link_rate * 100),
> >+			   drm_dp_max_link_rate(link->dpcd));
> >+	link->lanes = min_t(u8, phy_get_bus_width(dp->phy),
> >+			    drm_dp_max_lane_count(link->dpcd));
> >+
> >+	link->caps.enhanced_framing = drm_dp_enhanced_frame_cap(link->dpcd);
> >+	link->caps.tps3_supported = drm_dp_tps3_supported(link->dpcd);
> >+	link->caps.tps4_supported = drm_dp_tps4_supported(link->dpcd);
> >+	link->caps.fast_training = drm_dp_fast_training_cap(link->dpcd);
> >+	link->caps.channel_coding = drm_dp_channel_coding_supported(link->dpcd);
> >+	link->caps.ssc = !!(link->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5);
> >+
> >+	return 0;
> >+}
> >+
> >+static int dw_dp_link_train_update_vs_emph(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_link *link = &dp->link;
> >+	struct dw_dp_link_train_set *train_set = &link->train.adjust;
> >+	unsigned int lanes = dp->link.lanes;
> >+	union phy_configure_opts phy_cfg;
> >+	unsigned int *vs, *pe;
> >+	int i, ret;
> >+	u8 buf[4];
> >+
> >+	vs = train_set->voltage_swing;
> >+	pe = train_set->pre_emphasis;
> >+
> >+	for (i = 0; i < lanes; i++) {
> >+		phy_cfg.dp.voltage[i] = vs[i];
> >+		phy_cfg.dp.pre[i] = pe[i];
> >+	}
> >+
> >+	phy_cfg.dp.set_lanes = false;
> >+	phy_cfg.dp.set_rate = false;
> >+	phy_cfg.dp.set_voltages = true;
> >+
> >+	ret = phy_configure(dp->phy, &phy_cfg);
> >+	if (ret)
> >+		return ret;
> >+
> >+	for (i = 0; i < lanes; i++) {
> >+		buf[i] = (vs[i] << DP_TRAIN_VOLTAGE_SWING_SHIFT) |
> >+			 (pe[i] << DP_TRAIN_PRE_EMPHASIS_SHIFT);
> >+		if (train_set->voltage_max_reached[i])
> >+			buf[i] |= DP_TRAIN_MAX_SWING_REACHED;
> >+		if (train_set->pre_max_reached[i])
> >+			buf[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
> >+	}
> >+
> >+	ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, buf, lanes);
> >+	if (ret < 0)
> >+		return ret;
> >+
> >+	return 0;
> >+}
> >+
> >+static int dw_dp_phy_configure(struct dw_dp *dp, unsigned int rate,
> >+			       unsigned int lanes, bool ssc)
> >+{
> >+	union phy_configure_opts phy_cfg;
> >+	int ret;
> >+
> >+	/* Move PHY to P3 */
> >+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN,
> >+			   FIELD_PREP(PHY_POWERDOWN, 0x3));
> >+
> >+	phy_cfg.dp.lanes = lanes;
> >+	phy_cfg.dp.link_rate = rate / 100;
> >+	phy_cfg.dp.ssc = ssc;
> >+	phy_cfg.dp.set_lanes = true;
> >+	phy_cfg.dp.set_rate = true;
> >+	phy_cfg.dp.set_voltages = false;
> >+	ret = phy_configure(dp->phy, &phy_cfg);
> >+	if (ret)
> >+		return ret;
> >+
> >+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_LANES,
> >+			   FIELD_PREP(PHY_LANES, lanes / 2));
> >+
> >+	/* Move PHY to P0 */
> >+	regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN,
> >+			   FIELD_PREP(PHY_POWERDOWN, 0x0));
> >+
> >+	dw_dp_phy_xmit_enable(dp, lanes);
> >+
> >+	return 0;
> >+}
> >+
> >+static int dw_dp_link_configure(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_link *link = &dp->link;
> >+	u8 buf[2];
> >+	int ret;
> >+
> >+	ret = dw_dp_phy_configure(dp, link->rate, link->lanes, link->caps.ssc);
> >+	if (ret)
> >+		return ret;
> >+
> >+	buf[0] = drm_dp_link_rate_to_bw_code(link->rate);
> >+	buf[1] = link->lanes;
> >+
> >+	if (link->caps.enhanced_framing) {
> >+		buf[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
> >+		regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN,
> >+				   FIELD_PREP(ENHANCE_FRAMING_EN, 1));
> >+	} else {
> >+		regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN,
> >+				   FIELD_PREP(ENHANCE_FRAMING_EN, 0));
> >+	}
> >+
> >+	ret = drm_dp_dpcd_write(&dp->aux, DP_LINK_BW_SET, buf, sizeof(buf));
> >+	if (ret < 0)
> >+		return ret;
> >+
> >+	buf[0] = link->caps.ssc ? DP_SPREAD_AMP_0_5 : 0;
> >+	buf[1] = link->caps.channel_coding ? DP_SET_ANSI_8B10B : 0;
> >+
> >+	ret = drm_dp_dpcd_write(&dp->aux, DP_DOWNSPREAD_CTRL, buf, sizeof(buf));
> >+	if (ret < 0)
> >+		return ret;
> >+
> >+	return 0;
> >+}
> >+
> >+static void dw_dp_link_train_init(struct dw_dp_link_train *train)
> >+{
> >+	struct dw_dp_link_train_set *adj = &train->adjust;
> >+	unsigned int i;
> >+
> >+	for (i = 0; i < 4; i++) {
> >+		adj->voltage_swing[i] = 0;
> >+		adj->pre_emphasis[i] = 0;
> >+		adj->voltage_max_reached[i] = false;
> >+		adj->pre_max_reached[i] = false;
> >+	}
> >+
> >+	train->clock_recovered = false;
> >+	train->channel_equalized = false;
> >+}
> >+
> >+static bool dw_dp_link_train_valid(const struct dw_dp_link_train *train)
> >+{
> >+	return train->clock_recovered && train->channel_equalized;
> >+}
> >+
> >+static int dw_dp_link_train_set_pattern(struct dw_dp *dp, u32 pattern)
> >+{
> >+	u8 buf = 0;
> >+	int ret;
> >+
> >+	if (pattern && pattern != DP_TRAINING_PATTERN_4) {
> >+		buf |= DP_LINK_SCRAMBLING_DISABLE;
> >+
> >+		regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS,
> >+				   FIELD_PREP(SCRAMBLE_DIS, 1));
> >+	} else {
> >+		regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS,
> >+				   FIELD_PREP(SCRAMBLE_DIS, 0));
> >+	}
> >+
> >+	switch (pattern) {
> >+	case DP_TRAINING_PATTERN_DISABLE:
> >+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_NONE);
> >+		break;
> >+	case DP_TRAINING_PATTERN_1:
> >+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_1);
> >+		break;
> >+	case DP_TRAINING_PATTERN_2:
> >+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_2);
> >+		break;
> >+	case DP_TRAINING_PATTERN_3:
> >+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_3);
> >+		break;
> >+	case DP_TRAINING_PATTERN_4:
> >+		dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_4);
> >+		break;
> >+	default:
> >+		return -EINVAL;
> >+	}
> >+
> >+	ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET,
> >+				 buf | pattern);
> >+	if (ret < 0)
> >+		return ret;
> >+
> >+	return 0;
> >+}
> >+
> >+static u8 dw_dp_voltage_max(u8 preemph)
> >+{
> >+	switch (preemph & DP_TRAIN_PRE_EMPHASIS_MASK) {
> >+	case DP_TRAIN_PRE_EMPH_LEVEL_0:
> >+		return DP_TRAIN_VOLTAGE_SWING_LEVEL_3;
> >+	case DP_TRAIN_PRE_EMPH_LEVEL_1:
> >+		return DP_TRAIN_VOLTAGE_SWING_LEVEL_2;
> >+	case DP_TRAIN_PRE_EMPH_LEVEL_2:
> >+		return DP_TRAIN_VOLTAGE_SWING_LEVEL_1;
> >+	case DP_TRAIN_PRE_EMPH_LEVEL_3:
> >+	default:
> >+		return DP_TRAIN_VOLTAGE_SWING_LEVEL_0;
> >+	}
> >+}
> >+
> >+static bool dw_dp_link_get_adjustments(struct dw_dp_link *link,
> >+				       u8 status[DP_LINK_STATUS_SIZE])
> >+{
> >+	struct dw_dp_link_train_set *adj = &link->train.adjust;
> >+	unsigned int i;
> >+	bool changed = false;
> >+	u8 v = 0;
> >+	u8 p = 0;
> >+
> >+	for (i = 0; i < link->lanes; i++) {
> >+		v = drm_dp_get_adjust_request_voltage(status, i);
> >+		v >>= DP_TRAIN_VOLTAGE_SWING_SHIFT;
> >+		p = drm_dp_get_adjust_request_pre_emphasis(status, i);
> >+		p >>= DP_TRAIN_PRE_EMPHASIS_SHIFT;
> >+
> >+		if ((v != adj->voltage_swing[i]) || (p != adj->pre_emphasis[i]))
> >+			changed = true;
> >+
> >+		if (p >=  (DP_TRAIN_PRE_EMPH_LEVEL_3 >> DP_TRAIN_PRE_EMPHASIS_SHIFT)) {
> >+			adj->pre_emphasis[i] = DP_TRAIN_PRE_EMPH_LEVEL_3 >>
> >+					       DP_TRAIN_PRE_EMPHASIS_SHIFT;
> >+			adj->pre_max_reached[i] = true;
> >+		} else {
> >+			adj->pre_emphasis[i] = p;
> >+			adj->pre_max_reached[i] = false;
> >+		}
> >+
> >+		v = min(v, dw_dp_voltage_max(p));
> >+		if (v >= (DP_TRAIN_VOLTAGE_SWING_LEVEL_3 >> DP_TRAIN_VOLTAGE_SWING_SHIFT)) {
> >+			adj->voltage_swing[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_3 >>
> >+						DP_TRAIN_VOLTAGE_SWING_SHIFT;
> >+			adj->voltage_max_reached[i] = true;
> >+		} else {
> >+			adj->voltage_swing[i] = v;
> >+			adj->voltage_max_reached[i] = false;
> >+		}
> >+	}
> >+
> >+	return changed;
> >+}
> >+
> >+static int dw_dp_link_clock_recovery(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_link *link = &dp->link;
> >+	u8 status[DP_LINK_STATUS_SIZE];
> >+	unsigned int tries = 0;
> >+	int ret;
> >+	bool adj_changed;
> >+
> >+	ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1);
> >+	if (ret)
> >+		return ret;
> >+
> >+	for (;;) {
> >+		ret = dw_dp_link_train_update_vs_emph(dp);
> >+		if (ret)
> >+			return ret;
> >+
> >+		drm_dp_link_train_clock_recovery_delay(&dp->aux, link->dpcd);
> >+
> >+		ret = drm_dp_dpcd_read_link_status(&dp->aux, status);
> >+		if (ret < 0) {
> >+			dev_err(dp->dev, "failed to read link status: %d\n", ret);
> >+			return ret;
> >+		}
> >+
> >+		if (drm_dp_clock_recovery_ok(status, link->lanes)) {
> >+			link->train.clock_recovered = true;
> >+			break;
> >+		}
> >+
> >+		/*
> >+		 * According to DP spec 1.4, if current ADJ is the same
> >+		 * with previous REQ, we need to retry 5 times.
> >+		 */
> >+		adj_changed = dw_dp_link_get_adjustments(link, status);
> >+		if (!adj_changed)
> >+			tries++;
> >+		else
> >+			tries = 0;
> >+
> >+		if (tries == 5)
> >+			break;
> >+	}
> >+
> >+	return 0;
> >+}
> >+
> >+static int dw_dp_link_channel_equalization(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_link *link = &dp->link;
> >+	u8 status[DP_LINK_STATUS_SIZE], pattern;
> >+	unsigned int tries;
> >+	int ret;
> >+
> >+	if (link->caps.tps4_supported)
> >+		pattern = DP_TRAINING_PATTERN_4;
> >+	else if (link->caps.tps3_supported)
> >+		pattern = DP_TRAINING_PATTERN_3;
> >+	else
> >+		pattern = DP_TRAINING_PATTERN_2;
> >+	ret = dw_dp_link_train_set_pattern(dp, pattern);
> >+	if (ret)
> >+		return ret;
> >+
> >+	for (tries = 1; tries < 5; tries++) {
> >+		ret = dw_dp_link_train_update_vs_emph(dp);
> >+		if (ret)
> >+			return ret;
> >+
> >+		drm_dp_link_train_channel_eq_delay(&dp->aux, link->dpcd);
> >+
> >+		ret = drm_dp_dpcd_read_link_status(&dp->aux, status);
> >+		if (ret < 0)
> >+			return ret;
> >+
> >+		if (!drm_dp_clock_recovery_ok(status, link->lanes)) {
> >+			dev_err(dp->dev, "clock recovery lost while equalizing channel\n");
> >+			link->train.clock_recovered = false;
> >+			break;
> >+		}
> >+
> >+		if (drm_dp_channel_eq_ok(status, link->lanes)) {
> >+			link->train.channel_equalized = true;
> >+			break;
> >+		}
> >+
> >+		dw_dp_link_get_adjustments(link, status);
> >+	}
> >+
> >+	return 0;
> >+}
> >+
> >+static int dw_dp_link_downgrade(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_link *link = &dp->link;
> >+	struct dw_dp_video *video = &dp->video;
> >+
> >+	switch (link->rate) {
> >+	case 162000:
> >+		return -EINVAL;
> >+	case 270000:
> >+		link->rate = 162000;
> >+		break;
> >+	case 540000:
> >+		link->rate = 270000;
> >+		break;
> >+	case 810000:
> >+		link->rate = 540000;
> >+		break;
> >+	}
> >+
> >+	if (!dw_dp_bandwidth_ok(dp, &video->mode, video->bpp, link->lanes,
> >+				link->rate))
> >+		return -E2BIG;
> >+
> >+	return 0;
> >+}
> >+
> >+static int dw_dp_link_train_full(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_link *link = &dp->link;
> >+	int ret;
> >+
> >+retry:
> >+	dw_dp_link_train_init(&link->train);
> >+
> >+	dev_dbg(dp->dev, "full-training link: %u lane%s at %u MHz\n",
> >+		link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100);
> >+
> >+	ret = dw_dp_link_configure(dp);
> >+	if (ret < 0) {
> >+		dev_err(dp->dev, "failed to configure DP link: %d\n", ret);
> >+		return ret;
> >+	}
> >+
> >+	ret = dw_dp_link_clock_recovery(dp);
> >+	if (ret < 0) {
> >+		dev_err(dp->dev, "clock recovery failed: %d\n", ret);
> >+		goto out;
> >+	}
> >+
> >+	if (!link->train.clock_recovered) {
> >+		dev_err(dp->dev, "clock recovery failed, downgrading link\n");
> >+
> >+		ret = dw_dp_link_downgrade(dp);
> >+		if (ret < 0)
> >+			goto out;
> >+		else
> >+			goto retry;
> >+	}
> >+
> >+	dev_dbg(dp->dev, "clock recovery succeeded\n");
> >+
> >+	ret = dw_dp_link_channel_equalization(dp);
> >+	if (ret < 0) {
> >+		dev_err(dp->dev, "channel equalization failed: %d\n", ret);
> >+		goto out;
> >+	}
> >+
> >+	if (!link->train.channel_equalized) {
> >+		dev_err(dp->dev, "channel equalization failed, downgrading link\n");
> >+
> >+		ret = dw_dp_link_downgrade(dp);
> >+		if (ret < 0)
> >+			goto out;
> >+		else
> >+			goto retry;
> >+	}
> >+
> >+	dev_dbg(dp->dev, "channel equalization succeeded\n");
> >+
> >+out:
> >+	dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE);
> >+	return ret;
> >+}
> >+
> >+static int dw_dp_link_train_fast(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_link *link = &dp->link;
> >+	int ret;
> >+	u8 status[DP_LINK_STATUS_SIZE];
> >+	u8 pattern;
> >+
> >+	dw_dp_link_train_init(&link->train);
> >+
> >+	dev_dbg(dp->dev, "fast-training link: %u lane%s at %u MHz\n",
> >+		link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100);
> >+
> >+	ret = dw_dp_link_configure(dp);
> >+	if (ret < 0) {
> >+		dev_err(dp->dev, "failed to configure DP link: %d\n", ret);
> >+		return ret;
> >+	}
> >+
> >+	ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1);
> >+	if (ret)
> >+		goto out;
> >+
> >+	usleep_range(500, 1000);
> >+
> >+	if (link->caps.tps4_supported)
> >+		pattern = DP_TRAINING_PATTERN_4;
> >+	else if (link->caps.tps3_supported)
> >+		pattern = DP_TRAINING_PATTERN_3;
> >+	else
> >+		pattern = DP_TRAINING_PATTERN_2;
> >+	ret = dw_dp_link_train_set_pattern(dp, pattern);
> >+	if (ret)
> >+		goto out;
> >+
> >+	usleep_range(500, 1000);
> >+
> >+	ret = drm_dp_dpcd_read_link_status(&dp->aux, status);
> >+	if (ret < 0) {
> >+		dev_err(dp->dev, "failed to read link status: %d\n", ret);
> >+		goto out;
> >+	}
> >+
> >+	if (!drm_dp_clock_recovery_ok(status, link->lanes)) {
> >+		dev_err(dp->dev, "clock recovery failed\n");
> >+		ret = -EIO;
> >+		goto out;
> >+	}
> >+
> >+	if (!drm_dp_channel_eq_ok(status, link->lanes)) {
> >+		dev_err(dp->dev, "channel equalization failed\n");
> >+		ret = -EIO;
> >+		goto out;
> >+	}
> >+
> >+out:
> >+	dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE);
> >+	return ret;
> >+}
> >+
> >+static int dw_dp_link_train(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_link *link = &dp->link;
> >+	int ret;
> >+
> >+	if (link->caps.fast_training) {
> >+		if (dw_dp_link_train_valid(&link->train)) {
> >+			ret = dw_dp_link_train_fast(dp);
> >+			if (ret < 0)
> >+				dev_err(dp->dev, "fast link training failed: %d\n", ret);
> >+			else
> >+				return 0;
> >+		}
> >+	}
> >+
> >+	ret = dw_dp_link_train_full(dp);
> >+	if (ret < 0) {
> >+		dev_err(dp->dev, "full link training failed: %d\n", ret);
> >+		return ret;
> >+	}
> >+
> >+	return 0;
> >+}
> >+
> >+static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp)
> >+{
> >+	const u8 *payload = sdp->base.db;
> >+	u32 reg;
> >+	int i, nr;
> >+
> >+	nr = find_first_zero_bit(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
> >+	if (nr < SDP_REG_BANK_SIZE)
> >+		set_bit(nr, dp->sdp_reg_bank);
> >+	else
> >+		return -EBUSY;
> >+
> >+	reg = DW_DP_SDP_REGISTER_BANK + nr * 9 * 4;
> >+
> >+	/* SDP header */
> >+	regmap_write(dp->regmap, reg, get_unaligned_le32(&sdp->base.sdp_header));
> >+
> >+	/* SDP data payload */
> >+	for (i = 1; i < 9; i++, payload += 4)
> >+		regmap_write(dp->regmap, reg + i * 4,
> >+			     FIELD_PREP(SDP_REGS, get_unaligned_le32(payload)));
> >+
> >+	if (sdp->flags & DW_DP_SDP_VERTICAL_INTERVAL)
> >+		regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
> >+				   EN_VERTICAL_SDP << nr,
> >+				   EN_VERTICAL_SDP << nr);
> >+
> >+	if (sdp->flags & DW_DP_SDP_HORIZONTAL_INTERVAL)
> >+		regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
> >+				   EN_HORIZONTAL_SDP << nr,
> >+				   EN_HORIZONTAL_SDP << nr);
> >+
> >+	return 0;
> >+}
> >+
> >+static int dw_dp_send_vsc_sdp(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_video *video = &dp->video;
> >+	struct dw_dp_sdp sdp = {};
> >+	struct drm_dp_vsc_sdp vsc = {};
> >+
> >+	sdp.flags = DW_DP_SDP_VERTICAL_INTERVAL;
> >+
> >+	vsc.sdp_type = DP_SDP_VSC;
> >+	vsc.revision = 0x5;
> >+	vsc.length = 0x13;
> >+	vsc.bpc = video->bpc;
> >+	vsc.content_type = DP_CONTENT_TYPE_NOT_DEFINED;
> >+
> >+	switch (video->color_format) {
> >+	case DRM_COLOR_FORMAT_YCBCR444:
> >+		vsc.pixelformat = DP_PIXELFORMAT_YUV444;
> >+		break;
> >+	case DRM_COLOR_FORMAT_YCBCR420:
> >+		vsc.pixelformat = DP_PIXELFORMAT_YUV420;
> >+		break;
> >+	case DRM_COLOR_FORMAT_YCBCR422:
> >+		vsc.pixelformat = DP_PIXELFORMAT_YUV422;
> >+		break;
> >+	case DRM_COLOR_FORMAT_RGB444:
> >+	default:
> >+		vsc.pixelformat = DP_PIXELFORMAT_RGB;
> >+		break;
> >+	}
> >+
> >+	if (video->color_format == DRM_COLOR_FORMAT_RGB444) {
> >+		vsc.colorimetry = DP_COLORIMETRY_DEFAULT;
> >+		vsc.dynamic_range = DP_DYNAMIC_RANGE_VESA;
> >+	} else {
> >+		vsc.colorimetry = DP_COLORIMETRY_BT709_YCC;
> >+		vsc.dynamic_range = DP_DYNAMIC_RANGE_CTA;
> >+	}
> >+
> >+	drm_dp_vsc_sdp_pack(&vsc, &sdp.base);
> >+
> >+	return dw_dp_send_sdp(dp, &sdp);
> >+}
> >+
> >+static int dw_dp_video_set_pixel_mode(struct dw_dp *dp, u8 pixel_mode)
> >+{
> >+	switch (pixel_mode) {
> >+	case DW_DP_MP_SINGLE_PIXEL:
> >+	case DW_DP_MP_DUAL_PIXEL:
> >+	case DW_DP_MP_QUAD_PIXEL:
> >+		break;
> >+	default:
> >+		return -EINVAL;
> >+	}
> >+
> >+	regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, PIXEL_MODE_SELECT,
> >+			   FIELD_PREP(PIXEL_MODE_SELECT, pixel_mode));
> >+
> >+	return 0;
> >+}
> >+
> >+static bool dw_dp_video_need_vsc_sdp(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_link *link = &dp->link;
> >+	struct dw_dp_video *video = &dp->video;
> >+
> >+	if (!link->vsc_sdp_supported)
> >+		return false;
> >+
> >+	if (video->color_format == DRM_COLOR_FORMAT_YCBCR420)
> >+		return true;
> >+
> >+	return false;
> >+}
> >+
> >+static int dw_dp_video_set_msa(struct dw_dp *dp, u8 color_format, u8 bpc,
> >+			       u16 vstart, u16 hstart)
> >+{
> >+	u16 misc = 0;
> >+
> >+	if (dw_dp_video_need_vsc_sdp(dp))
> >+		misc |= DP_MSA_MISC_COLOR_VSC_SDP;
> >+
> >+	switch (color_format) {
> >+	case DRM_COLOR_FORMAT_RGB444:
> >+		misc |= DP_MSA_MISC_COLOR_RGB;
> >+		break;
> >+	case DRM_COLOR_FORMAT_YCBCR444:
> >+		misc |= DP_MSA_MISC_COLOR_YCBCR_444_BT709;
> >+		break;
> >+	case DRM_COLOR_FORMAT_YCBCR422:
> >+		misc |= DP_MSA_MISC_COLOR_YCBCR_422_BT709;
> >+		break;
> >+	case DRM_COLOR_FORMAT_YCBCR420:
> >+		break;
> >+	default:
> >+		return -EINVAL;
> >+	}
> >+
> >+	switch (bpc) {
> >+	case 6:
> >+		misc |= DP_MSA_MISC_6_BPC;
> >+		break;
> >+	case 8:
> >+		misc |= DP_MSA_MISC_8_BPC;
> >+		break;
> >+	case 10:
> >+		misc |= DP_MSA_MISC_10_BPC;
> >+		break;
> >+	case 12:
> >+		misc |= DP_MSA_MISC_12_BPC;
> >+		break;
> >+	case 16:
> >+		misc |= DP_MSA_MISC_16_BPC;
> >+		break;
> >+	default:
> >+		return -EINVAL;
> >+	}
> >+
> >+	regmap_write(dp->regmap, DW_DP_VIDEO_MSA1,
> >+		     FIELD_PREP(VSTART, vstart) | FIELD_PREP(HSTART, hstart));
> >+	regmap_write(dp->regmap, DW_DP_VIDEO_MSA2, FIELD_PREP(MISC0, misc));
> >+	regmap_write(dp->regmap, DW_DP_VIDEO_MSA3, FIELD_PREP(MISC1, misc >> 8));
> >+
> >+	return 0;
> >+}
> >+
> >+static void dw_dp_video_disable(struct dw_dp *dp)
> >+{
> >+	regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE,
> >+			   FIELD_PREP(VIDEO_STREAM_ENABLE, 0));
> >+}
> >+
> >+static int dw_dp_video_enable(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_video *video = &dp->video;
> >+	struct dw_dp_link *link = &dp->link;
> >+	struct drm_display_mode *mode = &video->mode;
> >+	u8 color_format = video->color_format;
> >+	u8 bpc = video->bpc;
> >+	u8 pixel_mode = video->pixel_mode;
> >+	u8 bpp = video->bpp, init_threshold, vic;
> >+	u32 hactive, hblank, h_sync_width, h_front_porch;
> >+	u32 vactive, vblank, v_sync_width, v_front_porch;
> >+	u32 vstart = mode->vtotal - mode->vsync_start;
> >+	u32 hstart = mode->htotal - mode->hsync_start;
> >+	u32 peak_stream_bandwidth, link_bandwidth;
> >+	u32 average_bytes_per_tu, average_bytes_per_tu_frac;
> >+	u32 ts, hblank_interval;
> >+	u32 value;
> >+	int ret;
> >+
> >+	ret = dw_dp_video_set_pixel_mode(dp, pixel_mode);
> >+	if (ret)
> >+		return ret;
> >+
> >+	ret = dw_dp_video_set_msa(dp, color_format, bpc, vstart, hstart);
> >+	if (ret)
> >+		return ret;
> >+
> >+	regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_MAPPING,
> >+			   FIELD_PREP(VIDEO_MAPPING, video->video_mapping));
> >+
> >+	/* Configure DW_DP_VINPUT_POLARITY_CTRL register */
> >+	value = 0;
> >+	if (mode->flags & DRM_MODE_FLAG_PHSYNC)
> >+		value |= FIELD_PREP(HSYNC_IN_POLARITY, 1);
> >+	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
> >+		value |= FIELD_PREP(VSYNC_IN_POLARITY, 1);
> >+	regmap_write(dp->regmap, DW_DP_VINPUT_POLARITY_CTRL, value);
> >+
> >+	/* Configure DW_DP_VIDEO_CONFIG1 register */
> >+	hactive = mode->hdisplay;
> >+	hblank = mode->htotal - mode->hdisplay;
> >+	value = FIELD_PREP(HACTIVE, hactive) | FIELD_PREP(HBLANK, hblank);
> >+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
> >+		value |= FIELD_PREP(I_P, 1);
> >+	vic = drm_match_cea_mode(mode);
> >+	if (vic == 5 || vic == 6 || vic == 7 ||
> >+	    vic == 10 || vic == 11 || vic == 20 ||
> >+	    vic == 21 || vic == 22 || vic == 39 ||
> >+	    vic == 25 || vic == 26 || vic == 40 ||
> >+	    vic == 44 || vic == 45 || vic == 46 ||
> >+	    vic == 50 || vic == 51 || vic == 54 ||
> >+	    vic == 55 || vic == 58 || vic  == 59)
> >+		value |= R_V_BLANK_IN_OSC;
> >+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG1, value);
> >+
> >+	/* Configure DW_DP_VIDEO_CONFIG2 register */
> >+	vblank = mode->vtotal - mode->vdisplay;
> >+	vactive = mode->vdisplay;
> >+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG2,
> >+		     FIELD_PREP(VBLANK, vblank) | FIELD_PREP(VACTIVE, vactive));
> >+
> >+	/* Configure DW_DP_VIDEO_CONFIG3 register */
> >+	h_sync_width = mode->hsync_end - mode->hsync_start;
> >+	h_front_porch = mode->hsync_start - mode->hdisplay;
> >+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG3,
> >+		     FIELD_PREP(H_SYNC_WIDTH, h_sync_width) |
> >+		     FIELD_PREP(H_FRONT_PORCH, h_front_porch));
> >+
> >+	/* Configure DW_DP_VIDEO_CONFIG4 register */
> >+	v_sync_width = mode->vsync_end - mode->vsync_start;
> >+	v_front_porch = mode->vsync_start - mode->vdisplay;
> >+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG4,
> >+		     FIELD_PREP(V_SYNC_WIDTH, v_sync_width) |
> >+		     FIELD_PREP(V_FRONT_PORCH, v_front_porch));
> >+
> >+	/* Configure DW_DP_VIDEO_CONFIG5 register */
> >+	peak_stream_bandwidth = mode->clock * bpp / 8;
> >+	link_bandwidth = (link->rate / 1000) * link->lanes;
> >+	ts = peak_stream_bandwidth * 64 / link_bandwidth;
> >+	average_bytes_per_tu = ts / 1000;
> >+	average_bytes_per_tu_frac = ts / 100 - average_bytes_per_tu * 10;
> >+	if (pixel_mode == DW_DP_MP_SINGLE_PIXEL) {
> >+		if (average_bytes_per_tu < 6)
> >+			init_threshold = 32;
> >+		else if (hblank <= 80 && color_format != DRM_COLOR_FORMAT_YCBCR420)
> >+			init_threshold = 12;
> >+		else if (hblank <= 40 && color_format == DRM_COLOR_FORMAT_YCBCR420)
> >+			init_threshold = 3;
> >+		else
> >+			init_threshold = 16;
> >+	} else {
> >+		u32 t1 = 0, t2 = 0, t3 = 0;
> >+
> >+		switch (bpc) {
> >+		case 6:
> >+			t1 = (4 * 1000 / 9) * link->lanes;
> >+			break;
> >+		case 8:
> >+			if (color_format == DRM_COLOR_FORMAT_YCBCR422) {
> >+				t1 = (1000 / 2) * link->lanes;
> >+			} else {
> >+				if (pixel_mode == DW_DP_MP_DUAL_PIXEL)
> >+					t1 = (1000 / 3) * link->lanes;
> >+				else
> >+					t1 = (3000 / 16) * link->lanes;
> >+			}
> >+			break;
> >+		case 10:
> >+			if (color_format == DRM_COLOR_FORMAT_YCBCR422)
> >+				t1 = (2000 / 5) * link->lanes;
> >+			else
> >+				t1 = (4000 / 15) * link->lanes;
> >+			break;
> >+		case 12:
> >+			if (color_format == DRM_COLOR_FORMAT_YCBCR422) {
> >+				if (pixel_mode == DW_DP_MP_DUAL_PIXEL)
> >+					t1 = (1000 / 6) * link->lanes;
> >+				else
> >+					t1 = (1000 / 3) * link->lanes;
> >+			} else {
> >+				t1 = (2000 / 9) * link->lanes;
> >+			}
> >+			break;
> >+		case 16:
> >+			if (color_format != DRM_COLOR_FORMAT_YCBCR422 &&
> >+			    pixel_mode == DW_DP_MP_DUAL_PIXEL)
> >+				t1 = (1000 / 6) * link->lanes;
> >+			else
> >+				t1 = (1000 / 4) * link->lanes;
> >+			break;
> >+		default:
> >+			return -EINVAL;
> >+		}
> >+
> >+		if (color_format == DRM_COLOR_FORMAT_YCBCR420)
> >+			t2 = (link->rate / 4) * 1000 / (mode->clock / 2);
> >+		else
> >+			t2 = (link->rate / 4) * 1000 / mode->clock;
> >+
> >+		if (average_bytes_per_tu_frac)
> >+			t3 = average_bytes_per_tu + 1;
> >+		else
> >+			t3 = average_bytes_per_tu;
> >+		init_threshold = t1 * t2 * t3 / (1000 * 1000);
> >+		if (init_threshold <= 16 || average_bytes_per_tu < 10)
> >+			init_threshold = 40;
> >+	}
> >+
> >+	regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG5,
> >+		     FIELD_PREP(INIT_THRESHOLD_HI, init_threshold >> 6) |
> >+		     FIELD_PREP(AVERAGE_BYTES_PER_TU_FRAC, average_bytes_per_tu_frac) |
> >+		     FIELD_PREP(INIT_THRESHOLD, init_threshold) |
> >+		     FIELD_PREP(AVERAGE_BYTES_PER_TU, average_bytes_per_tu));
> >+
> >+	/* Configure DW_DP_VIDEO_HBLANK_INTERVAL register */
> >+	hblank_interval = hblank * (link->rate / 4) / mode->clock;
> >+	regmap_write(dp->regmap, DW_DP_VIDEO_HBLANK_INTERVAL,
> >+		     FIELD_PREP(HBLANK_INTERVAL_EN, 1) |
> >+		     FIELD_PREP(HBLANK_INTERVAL, hblank_interval));
> >+
> >+	/* Video stream enable */
> >+	regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE,
> >+			   FIELD_PREP(VIDEO_STREAM_ENABLE, 1));
> >+
> >+	if (dw_dp_video_need_vsc_sdp(dp))
> >+		dw_dp_send_vsc_sdp(dp);
> >+
> >+	return 0;
> >+}
> >+
> >+static void dw_dp_hpd_init(struct dw_dp *dp)
> >+{
> >+	/* Enable all HPD interrupts */
> >+	regmap_update_bits(dp->regmap, DW_DP_HPD_INTERRUPT_ENABLE,
> >+			   HPD_UNPLUG_EN | HPD_PLUG_EN | HPD_IRQ_EN,
> >+			   FIELD_PREP(HPD_UNPLUG_EN, 1) |
> >+			   FIELD_PREP(HPD_PLUG_EN, 1) |
> >+			   FIELD_PREP(HPD_IRQ_EN, 1));
> >+
> >+	/* Enable all top-level interrupts */
> >+	regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE,
> >+			   HPD_EVENT_EN, FIELD_PREP(HPD_EVENT_EN, 1));
> >+}
> >+
> >+static void dw_dp_aux_init(struct dw_dp *dp)
> >+{
> >+	regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE,
> >+			   AUX_REPLY_EVENT_EN, FIELD_PREP(AUX_REPLY_EVENT_EN, 1));
> >+}
> >+
> >+static void dw_dp_init_hw(struct dw_dp *dp)
> >+{
> >+	regmap_update_bits(dp->regmap, DW_DP_CCTL, DEFAULT_FAST_LINK_TRAIN_EN,
> >+			   FIELD_PREP(DEFAULT_FAST_LINK_TRAIN_EN, 0));
> >+
> >+	dw_dp_hpd_init(dp);
> >+	dw_dp_aux_init(dp);
> >+}
> >+
> >+static int dw_dp_aux_write_data(struct dw_dp *dp, const u8 *buffer, size_t size)
> >+{
> >+	size_t i, j;
> >+
> >+	for (i = 0; i < DIV_ROUND_UP(size, 4); i++) {
> >+		size_t num = min_t(size_t, size - i * 4, 4);
> >+		u32 value = 0;
> >+
> >+		for (j = 0; j < num; j++)
> >+			value |= buffer[i * 4 + j] << (j * 8);
> >+
> >+		regmap_write(dp->regmap, DW_DP_AUX_DATA0 + i * 4, value);
> >+	}
> >+
> >+	return size;
> >+}
> >+
> >+static int dw_dp_aux_read_data(struct dw_dp *dp, u8 *buffer, size_t size)
> >+{
> >+	size_t i, j;
> >+
> >+	for (i = 0; i < DIV_ROUND_UP(size, 4); i++) {
> >+		size_t num = min_t(size_t, size - i * 4, 4);
> >+		u32 value;
> >+
> >+		regmap_read(dp->regmap, DW_DP_AUX_DATA0 + i * 4, &value);
> >+
> >+		for (j = 0; j < num; j++)
> >+			buffer[i * 4 + j] = value >> (j * 8);
> >+	}
> >+
> >+	return size;
> >+}
> >+
> >+static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux,
> >+				  struct drm_dp_aux_msg *msg)
> >+{
> >+	struct dw_dp *dp = container_of(aux, struct dw_dp, aux);
> >+	unsigned long timeout = msecs_to_jiffies(10);
> >+	u32 status, value;
> >+	ssize_t ret = 0;
> >+
> >+	if (WARN_ON(msg->size > 16))
> >+		return -E2BIG;
> >+
> >+	switch (msg->request & ~DP_AUX_I2C_MOT) {
> >+	case DP_AUX_NATIVE_WRITE:
> >+	case DP_AUX_I2C_WRITE:
> >+	case DP_AUX_I2C_WRITE_STATUS_UPDATE:
> >+		ret = dw_dp_aux_write_data(dp, msg->buffer, msg->size);
> >+		if (ret < 0)
> >+			return ret;
> >+		break;
> >+	case DP_AUX_NATIVE_READ:
> >+	case DP_AUX_I2C_READ:
> >+		break;
> >+	default:
> >+		return -EINVAL;
> >+	}
> >+
> >+	if (msg->size > 0)
> >+		value = FIELD_PREP(AUX_LEN_REQ, msg->size - 1);
> >+	else
> >+		value = FIELD_PREP(I2C_ADDR_ONLY, 1);
> >+	value |= FIELD_PREP(AUX_CMD_TYPE, msg->request);
> >+	value |= FIELD_PREP(AUX_ADDR, msg->address);
> >+	regmap_write(dp->regmap, DW_DP_AUX_CMD, value);
> >+
> >+	status = wait_for_completion_timeout(&dp->complete, timeout);
> >+	if (!status) {
> >+		dev_err(dp->dev, "timeout waiting for AUX reply\n");
> >+		return -ETIMEDOUT;
> >+	}
> >+
> >+	regmap_read(dp->regmap, DW_DP_AUX_STATUS, &value);
> >+	if (value & AUX_TIMEOUT)
> >+		return -ETIMEDOUT;
> >+
> >+	msg->reply = FIELD_GET(AUX_STATUS, value);
> >+
> >+	if (msg->size > 0 && msg->reply == DP_AUX_NATIVE_REPLY_ACK) {
> >+		if (msg->request & DP_AUX_I2C_READ) {
> >+			size_t count = FIELD_GET(AUX_BYTES_READ, value) - 1;
> >+
> >+			if (count != msg->size)
> >+				return -EBUSY;
> >+
> >+			ret = dw_dp_aux_read_data(dp, msg->buffer, count);
> >+			if (ret < 0)
> >+				return ret;
> >+		}
> >+	}
> >+
> >+	return ret;
> >+}
> >+
> >+/*
> >+ * Limits for the video timing for DP:
> >+ * 1. the hfp should be 2 pixels aligned;
> >+ * 2. the minimum hsync should be 9 pixel;
> >+ * 3. the minimum hbp should be 16 pixel;
> >+ */
> >+static int dw_dp_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_dp *dp = bridge_to_dp(bridge);
> >+	struct dw_dp_video *video = &dp->video;
> >+	struct drm_display_mode *m = &video->mode;
> >+	struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
> >+	const struct dw_dp_output_format *fmt;
> >+	int min_hbp = 16;
> >+	int min_hsync = 9;
> >+
> >+	fmt = dw_dp_get_output_format(bridge_state->output_bus_cfg.format);
> >+	if (!fmt)
> >+		return -EINVAL;
> >+
> >+	video->video_mapping = fmt->video_mapping;
> >+	video->color_format = fmt->color_format;
> >+	video->bpc = fmt->bpc;
> >+	video->bpp = fmt->bpp;
> >+
> >+	if ((adjusted_mode->hsync_start - adjusted_mode->hdisplay) & 0x1) {
> >+		adjusted_mode->hsync_start += 1;
> >+		dev_warn(dp->dev, "hfp is not 2 pixeel aligned, fixup to aligned hfp\n");
> >+	}
> >+	if (adjusted_mode->hsync_end - adjusted_mode->hsync_start < min_hsync) {
> >+		adjusted_mode->hsync_end = adjusted_mode->hsync_start + min_hsync;
> >+		dev_warn(dp->dev, "hsync is too narrow, fixup to min hsync:%d\n", min_hsync);
> >+	}
> >+	if (adjusted_mode->htotal - adjusted_mode->hsync_end < min_hbp) {
> >+		adjusted_mode->htotal = adjusted_mode->hsync_end + min_hbp;
> >+		dev_warn(dp->dev, "hbp is too narrow, fixup to min hbp:%d\n", min_hbp);
> >+	}
> >+
> >+	drm_mode_copy(m, adjusted_mode);
> >+
> >+	return 0;
> >+}
> >+
> >+static enum drm_mode_status dw_dp_bridge_mode_valid(struct drm_bridge *bridge,
> >+						    const struct drm_display_info *info,
> >+						    const struct drm_display_mode *mode)
> >+{
> >+	struct dw_dp *dp = bridge_to_dp(bridge);
> >+	struct dw_dp_link *link = &dp->link;
> >+	u32 min_bpp;
> >+
> >+	if (info->color_formats & DRM_COLOR_FORMAT_YCBCR420 &&
> >+	    link->vsc_sdp_supported &&
> >+	    (drm_mode_is_420_only(info, mode) || drm_mode_is_420_also(info, mode)))
> >+		min_bpp = 12;
> >+	else if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422)
> >+		min_bpp = 16;
> >+	else if (info->color_formats & DRM_COLOR_FORMAT_RGB444)
> >+		min_bpp = 18;
> >+	else
> >+		min_bpp = 24;
> >+
> >+	if (!link->vsc_sdp_supported &&
> >+	    drm_mode_is_420_only(info, mode))
> >+		return MODE_NO_420;
> >+
> >+	if (!dw_dp_bandwidth_ok(dp, mode, min_bpp, link->lanes, link->rate))
> >+		return MODE_CLOCK_HIGH;
> >+
> >+	return MODE_OK;
> >+}
> >+
> >+static bool dw_dp_needs_link_retrain(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_link *link = &dp->link;
> >+	u8 link_status[DP_LINK_STATUS_SIZE];
> >+
> >+	if (!dw_dp_link_train_valid(&link->train))
> >+		return false;
> >+
> >+	if (drm_dp_dpcd_read_link_status(&dp->aux, link_status) < 0)
> >+		return false;
> >+
> >+	/* Retrain if Channel EQ or CR not ok */
> >+	return !drm_dp_channel_eq_ok(link_status, dp->link.lanes);
> >+}
> >+
> >+static void dw_dp_link_disable(struct dw_dp *dp)
> >+{
> >+	struct dw_dp_link *link = &dp->link;
> >+
> >+	if (dw_dp_hpd_detect(dp))
> >+		drm_dp_link_power_down(&dp->aux, dp->link.revision);
> >+
> >+	dw_dp_phy_xmit_enable(dp, 0);
> >+
> >+	phy_power_off(dp->phy);
> >+
> >+	link->train.clock_recovered = false;
> >+	link->train.channel_equalized = false;
> >+}
> >+
> >+static int dw_dp_link_enable(struct dw_dp *dp)
> >+{
> >+	int ret;
> >+
> >+	ret = phy_power_on(dp->phy);
> >+	if (ret)
> >+		return ret;
> >+
> >+	ret = drm_dp_link_power_up(&dp->aux, dp->link.revision);
> >+	if (ret < 0)
> >+		return ret;
> >+
> >+	ret = dw_dp_link_train(dp);
> >+
> >+	return ret;
> >+}
> >+
> >+static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
> >+				       struct drm_atomic_state *state)
> >+{
> >+	struct dw_dp *dp = bridge_to_dp(bridge);
> >+	struct drm_connector *connector;
> >+	struct drm_connector_state *conn_state;
> >+	int ret;
> >+
> >+	connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
> >+	if (!connector) {
> >+		dev_err(dp->dev, "failed to get connector\n");
> >+		return;
> >+	}
> >+
> >+	conn_state = drm_atomic_get_new_connector_state(state, connector);
> >+	if (!conn_state) {
> >+		dev_err(dp->dev, "failed to get connector state\n");
> >+		return;
> >+	}
> >+
> >+	set_bit(0, dp->sdp_reg_bank);
> >+
> >+	ret = dw_dp_link_enable(dp);
> >+	if (ret < 0) {
> >+		dev_err(dp->dev, "failed to enable link: %d\n", ret);
> >+		return;
> >+	}
> >+
> >+	ret = dw_dp_video_enable(dp);
> >+	if (ret < 0) {
> >+		dev_err(dp->dev, "failed to enable video: %d\n", ret);
> >+		return;
> >+	}
> >+}
> >+
> >+static void dw_dp_reset(struct dw_dp *dp)
> >+{
> >+	int val;
> >+
> >+	disable_irq(dp->irq);
> >+	regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET,
> >+			   FIELD_PREP(CONTROLLER_RESET, 1));
> >+	udelay(10);
> >+	regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET,
> >+			   FIELD_PREP(CONTROLLER_RESET, 0));
> >+
> >+	dw_dp_init_hw(dp);
> >+	regmap_read_poll_timeout(dp->regmap, DW_DP_HPD_STATUS, val,
> >+				 FIELD_GET(HPD_HOT_PLUG, val), 200, 200000);
> >+	regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG);
> >+	enable_irq(dp->irq);
> >+}
> >+
> >+static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge,
> >+					struct drm_atomic_state *state)
> >+{
> >+	struct dw_dp *dp = bridge_to_dp(bridge);
> >+
> >+	dw_dp_video_disable(dp);
> >+	dw_dp_link_disable(dp);
> >+	bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
> >+	dw_dp_reset(dp);
> >+}
> >+
> >+static bool dw_dp_hpd_detect_link(struct dw_dp *dp)
> >+{
> >+	int ret;
> >+
> >+	ret = phy_power_on(dp->phy);
> >+	if (ret < 0)
> >+		return false;
> >+	ret = dw_dp_link_parse(dp);
> >+	phy_power_off(dp->phy);
> >+
> >+	return !ret;
> >+}
> >+
> >+static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge)
> >+{
> >+	struct dw_dp *dp = bridge_to_dp(bridge);
> >+
> >+	if (!dw_dp_hpd_detect(dp))
> >+		return connector_status_disconnected;
> >+
> >+	if (!dw_dp_hpd_detect_link(dp))
> >+		return connector_status_disconnected;
> >+
> >+	return connector_status_connected;
> >+}
> >+
> >+static const struct drm_edid *dw_dp_bridge_edid_read(struct drm_bridge *bridge,
> >+						     struct drm_connector *connector)
> >+{
> >+	struct dw_dp *dp = bridge_to_dp(bridge);
> >+	const struct drm_edid *edid;
> >+	int ret;
> >+
> >+	ret = phy_power_on(dp->phy);
> >+	if (ret)
> >+		return NULL;
> >+
> >+	edid = drm_edid_read_ddc(connector, &dp->aux.ddc);
> >+
> >+	phy_power_off(dp->phy);
> >+
> >+	return edid;
> >+}
> >+
> >+static u32 *dw_dp_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 dw_dp *dp = bridge_to_dp(bridge);
> >+	struct dw_dp_link *link = &dp->link;
> >+	struct drm_display_info *di = &conn_state->connector->display_info;
> >+	struct drm_display_mode mode = crtc_state->mode;
> >+	const struct dw_dp_output_format *fmt;
> >+	u32 i, j = 0;
> >+	u32 *output_fmts;
> >+
> >+	*num_output_fmts = 0;
> >+
> >+	output_fmts = kcalloc(ARRAY_SIZE(dw_dp_output_formats), sizeof(*output_fmts), GFP_KERNEL);
> >+	if (!output_fmts)
> >+		return NULL;
> >+
> >+	for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) {
> >+		fmt = &dw_dp_output_formats[i];
> >+
> >+		if (fmt->bpc > conn_state->max_bpc)
> >+			continue;
> >+
> >+		if (!(fmt->color_format & di->color_formats))
> >+			continue;
> >+
> >+		if (fmt->color_format == DRM_COLOR_FORMAT_YCBCR420 &&
> >+		    !link->vsc_sdp_supported)
> >+			continue;
> >+
> >+		if (fmt->color_format != DRM_COLOR_FORMAT_YCBCR420 &&
> >+		    drm_mode_is_420_only(di, &mode))
> >+			continue;
> >+
> >+		if (!dw_dp_bandwidth_ok(dp, &mode, fmt->bpp, link->lanes, link->rate))
> >+			continue;
> >+
> >+		output_fmts[j++] = fmt->bus_format;
> >+	}
> >+
> >+	*num_output_fmts = j;
> >+
> >+	return output_fmts;
> >+}
> >+
> >+static const struct drm_bridge_funcs dw_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_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
> >+	.atomic_get_output_bus_fmts = dw_dp_bridge_atomic_get_output_bus_fmts,
> >+	.atomic_check = dw_dp_bridge_atomic_check,
> >+	.mode_valid = dw_dp_bridge_mode_valid,
> >+	.atomic_enable = dw_dp_bridge_atomic_enable,
> >+	.atomic_disable = dw_dp_bridge_atomic_disable,
> >+	.detect = dw_dp_bridge_detect,
> >+	.edid_read = dw_dp_bridge_edid_read,
> >+};
> >+
> >+static int dw_dp_link_retrain(struct dw_dp *dp)
> >+{
> >+	struct drm_device *dev = dp->bridge.dev;
> >+	struct drm_modeset_acquire_ctx ctx;
> >+	int ret;
> >+
> >+	if (!dw_dp_needs_link_retrain(dp))
> >+		return 0;
> >+
> >+	dev_dbg(dp->dev, "Retraining link\n");
> >+
> >+	drm_modeset_acquire_init(&ctx, 0);
> >+	for (;;) {
> >+		ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx);
> >+		if (ret != -EDEADLK)
> >+			break;
> >+
> >+		drm_modeset_backoff(&ctx);
> >+	}
> >+
> >+	if (!ret)
> >+		ret = dw_dp_link_train(dp);
> >+
> >+	drm_modeset_drop_locks(&ctx);
> >+	drm_modeset_acquire_fini(&ctx);
> >+
> >+	return ret;
> >+}
> >+
> >+static void dw_dp_hpd_work(struct work_struct *work)
> >+{
> >+	struct dw_dp *dp = container_of(work, struct dw_dp, hpd_work);
> >+	bool long_hpd;
> >+	int ret;
> >+
> >+	mutex_lock(&dp->irq_lock);
> >+	long_hpd = dp->hotplug.long_hpd;
> >+	mutex_unlock(&dp->irq_lock);
> >+
> >+	dev_dbg(dp->dev, "[drm] Get hpd irq - %s\n", long_hpd ? "long" : "short");
> >+
> >+	if (!long_hpd) {
> >+		if (dw_dp_needs_link_retrain(dp)) {
> >+			ret = dw_dp_link_retrain(dp);
> >+			if (ret)
> >+				dev_warn(dp->dev, "Retrain link failed\n");
> >+		}
> >+	} else {
> >+		drm_helper_hpd_irq_event(dp->bridge.dev);
> >+	}
> >+}
> >+
> >+static void dw_dp_handle_hpd_event(struct dw_dp *dp)
> >+{
> >+	u32 value;
> >+
> >+	mutex_lock(&dp->irq_lock);
> >+	regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value);
> >+
> >+	if (value & HPD_IRQ) {
> >+		dev_dbg(dp->dev, "IRQ from the HPD\n");
> >+		dp->hotplug.long_hpd = false;
> >+		regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_IRQ);
> >+	}
> >+
> >+	if (value & HPD_HOT_PLUG) {
> >+		dev_dbg(dp->dev, "Hot plug detected\n");
> >+		dp->hotplug.long_hpd = true;
> >+		regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG);
> >+	}
> >+
> >+	if (value & HPD_HOT_UNPLUG) {
> >+		dev_dbg(dp->dev, "Unplug detected\n");
> >+		dp->hotplug.long_hpd = true;
> >+		regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_UNPLUG);
> >+	}
> >+	mutex_unlock(&dp->irq_lock);
> >+
> >+	schedule_work(&dp->hpd_work);
> >+}
> >+
> >+static irqreturn_t dw_dp_irq(int irq, void *data)
> >+{
> >+	struct dw_dp *dp = data;
> >+	u32 value;
> >+
> >+	regmap_read(dp->regmap, DW_DP_GENERAL_INTERRUPT, &value);
> >+	if (!value)
> >+		return IRQ_NONE;
> >+
> >+	if (value & HPD_EVENT)
> >+		dw_dp_handle_hpd_event(dp);
> >+
> >+	if (value & AUX_REPLY_EVENT) {
> >+		regmap_write(dp->regmap, DW_DP_GENERAL_INTERRUPT, AUX_REPLY_EVENT);
> >+		complete(&dp->complete);
> >+	}
> >+
> >+	return IRQ_HANDLED;
> >+}
> >+
> >+static const struct regmap_range dw_dp_readable_ranges[] = {
> >+	regmap_reg_range(DW_DP_VERSION_NUMBER, DW_DP_ID),
> >+	regmap_reg_range(DW_DP_CONFIG_REG1, DW_DP_CONFIG_REG3),
> >+	regmap_reg_range(DW_DP_CCTL, DW_DP_SOFT_RESET_CTRL),
> >+	regmap_reg_range(DW_DP_VSAMPLE_CTRL, DW_DP_VIDEO_HBLANK_INTERVAL),
> >+	regmap_reg_range(DW_DP_AUD_CONFIG1, DW_DP_AUD_CONFIG1),
> >+	regmap_reg_range(DW_DP_SDP_VERTICAL_CTRL, DW_DP_SDP_STATUS_EN),
> >+	regmap_reg_range(DW_DP_PHYIF_CTRL, DW_DP_PHYIF_PWRDOWN_CTRL),
> >+	regmap_reg_range(DW_DP_AUX_CMD, DW_DP_AUX_DATA3),
> >+	regmap_reg_range(DW_DP_GENERAL_INTERRUPT, DW_DP_HPD_INTERRUPT_ENABLE),
> >+};
> >+
> >+static const struct regmap_access_table dw_dp_readable_table = {
> >+	.yes_ranges     = dw_dp_readable_ranges,
> >+	.n_yes_ranges   = ARRAY_SIZE(dw_dp_readable_ranges),
> >+};
> >+
> >+static const struct regmap_config dw_dp_regmap_config = {
> >+	.reg_bits = 32,
> >+	.reg_stride = 4,
> >+	.val_bits = 32,
> >+	.fast_io = true,
> >+	.max_register = DW_DP_MAX_REGISTER,
> >+	.rd_table = &dw_dp_readable_table,
> >+};
> >+
> >+static void dw_dp_phy_exit(void *data)
> >+{
> >+	struct dw_dp *dp = data;
> >+
> >+	phy_exit(dp->phy);
> >+}
> >+
> >+struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
> >+			 const struct dw_dp_plat_data *plat_data)
> >+{
> >+	struct platform_device *pdev = to_platform_device(dev);
> >+	struct dw_dp *dp;
> >+	struct drm_bridge *bridge;
> >+	void __iomem *res;
> >+	int ret;
> >+
> >+	dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
> >+	if (!dp)
> >+		return ERR_PTR(-ENOMEM);
> >+
> >+	dp->dev = dev;
> >+	dp->video.pixel_mode = DW_DP_MP_QUAD_PIXEL;
> >+
> >+	dp->plat_data.max_link_rate = plat_data->max_link_rate;
> >+	bridge = &dp->bridge;
> >+	mutex_init(&dp->irq_lock);
> >+	INIT_WORK(&dp->hpd_work, dw_dp_hpd_work);
> >+	init_completion(&dp->complete);
> >+
> >+	res = devm_platform_ioremap_resource(pdev, 0);
> >+	if (IS_ERR(res))
> >+		return ERR_CAST(res);
> >+
> >+	dp->regmap = devm_regmap_init_mmio(dev, res, &dw_dp_regmap_config);
> >+	if (IS_ERR(dp->regmap)) {
> >+		dev_err_probe(dev, PTR_ERR(dp->regmap), "failed to create regmap\n");
> >+		return ERR_CAST(dp->regmap);
> >+	}
> >+
> >+	dp->phy = devm_of_phy_get(dev, dev->of_node, NULL);
> >+	if (IS_ERR(dp->phy)) {
> >+		dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n");
> >+		return ERR_CAST(dp->phy);
> >+	}
> >+
> >+	dp->apb_clk = devm_clk_get_enabled(dev, "apb");
> >+	if (IS_ERR(dp->apb_clk)) {
> >+		dev_err_probe(dev, PTR_ERR(dp->apb_clk), "failed to get apb clock\n");
> >+		return ERR_CAST(dp->apb_clk);
> >+	}
> >+
> >+	dp->aux_clk = devm_clk_get_enabled(dev, "aux");
> >+	if (IS_ERR(dp->aux_clk)) {
> >+		dev_err_probe(dev, PTR_ERR(dp->aux_clk), "failed to get aux clock\n");
> >+		return ERR_CAST(dp->aux_clk);
> >+	}
> >+
> >+	dp->i2s_clk = devm_clk_get(dev, "i2s");
> >+	if (IS_ERR(dp->i2s_clk)) {
> >+		dev_err_probe(dev, PTR_ERR(dp->i2s_clk), "failed to get i2s clock\n");
> >+		return ERR_CAST(dp->i2s_clk);
> >+	}
> >+
> >+	dp->spdif_clk = devm_clk_get(dev, "spdif");
> >+	if (IS_ERR(dp->spdif_clk)) {
> >+		dev_err_probe(dev, PTR_ERR(dp->spdif_clk), "failed to get spdif clock\n");
> >+		return ERR_CAST(dp->spdif_clk);
> >+	}
> >+
> >+	dp->hdcp_clk = devm_clk_get(dev, "hdcp");
> >+	if (IS_ERR(dp->hdcp_clk)) {
> >+		dev_err_probe(dev, PTR_ERR(dp->hdcp_clk), "failed to get hdcp clock\n");
> >+		return ERR_CAST(dp->hdcp_clk);
> >+	}
> >+
> >+	dp->rstc = devm_reset_control_get(dev, NULL);
> >+	if (IS_ERR(dp->rstc)) {
> >+		dev_err_probe(dev, PTR_ERR(dp->rstc), "failed to get reset control\n");
> >+		return ERR_CAST(dp->rstc);
> >+	}
> >+
> >+	bridge->of_node = dev->of_node;
> >+	bridge->funcs = &dw_dp_bridge_funcs;
> >+	bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
> >+	bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
> >+	bridge->ycbcr_420_allowed = true;
> >+
> >+	dp->aux.dev = dev;
> >+	dp->aux.drm_dev = encoder->dev;
> >+	dp->aux.name = dev_name(dev);
> >+	dp->aux.transfer = dw_dp_aux_transfer;
> >+	ret = drm_dp_aux_register(&dp->aux);
> >+	if (ret) {
> >+		dev_err_probe(dev, ret, "Aux register failed\n");
> >+		return ERR_PTR(ret);
> >+	}
> >+
> >+	ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
> >+	if (ret)
> >+		dev_err_probe(dev, ret, "Failed to attach bridge\n");
> >+
> >+	dw_dp_init_hw(dp);
> >+
> >+	ret = phy_init(dp->phy);
> >+	if (ret) {
> >+		dev_err_probe(dev, ret, "phy init failed\n");
> >+		return ERR_PTR(ret);
> >+	}
> >+
> >+	ret = devm_add_action_or_reset(dev, dw_dp_phy_exit, dp);
> >+	if (ret)
> >+		return ERR_PTR(ret);
> >+
> >+	dp->irq = platform_get_irq(pdev, 0);
> >+	if (dp->irq < 0)
> >+		return ERR_PTR(ret);
> >+
> >+	ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq,
> >+					IRQF_ONESHOT, dev_name(dev), dp);
> >+	if (ret) {
> >+		dev_err_probe(dev, ret, "failed to request irq\n");
> >+		return ERR_PTR(ret);
> >+	}
> >+
> >+	return dp;
> >+}
> >+EXPORT_SYMBOL_GPL(dw_dp_bind);
> >+
> >+MODULE_AUTHOR("Andy Yan <andyshrk@163.com>");
> >+MODULE_DESCRIPTION("DW DP Core Library");
> >+MODULE_LICENSE("GPL");
> >diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h
> >new file mode 100644
> >index 0000000000000..d05df49fd8846
> >--- /dev/null
> >+++ b/include/drm/bridge/dw_dp.h
> >@@ -0,0 +1,20 @@
> >+/* SPDX-License-Identifier: GPL-2.0-or-later */
> >+/*
> >+ * Copyright (c) 2025 Rockchip Electronics Co., Ltd.
> >+ */
> >+
> >+#ifndef __DW_DP__
> >+#define __DW_DP__
> >+
> >+#include <linux/device.h>
> >+
> >+struct drm_encoder;
> >+struct dw_dp;
> >+
> >+struct dw_dp_plat_data {
> >+	u32 max_link_rate;
> >+};
> >+
> >+struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
> >+			 const struct dw_dp_plat_data *plat_data);
> >+#endif /* __DW_DP__ */
> >-- 
> >2.43.0
> >
> >
> >_______________________________________________
> >Linux-rockchip mailing list
> >Linux-rockchip@lists.infradead.org
> >http://lists.infradead.org/mailman/listinfo/linux-rockchip

-- 
With best wishes
Dmitry


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re:Re: [PATCH v3 2/9] drm/bridge: synopsys: Add DW DPTX Controller support library
  2025-05-24  7:46     ` [PATCH " Dmitry Baryshkov
@ 2025-05-24  7:59       ` Andy Yan
  2025-05-24  8:11         ` Dmitry Baryshkov
  0 siblings, 1 reply; 21+ messages in thread
From: Andy Yan @ 2025-05-24  7:59 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: heiko, neil.armstrong, sebastian.reichel, stephen, dri-devel, hjc,
	mripard, linux-kernel, linux-rockchip, devicetree, yubing.zhang,
	naoki, Laurent.pinchart, Andy Yan, krzk+dt, robh,
	linux-arm-kernel



Hello Dmitry,


在 2025-05-24 15:46:15,"Dmitry Baryshkov" <dmitry.baryshkov@oss.qualcomm.com> 写道:
>On Wed, May 14, 2025 at 08:15:55PM +0800, Andy Yan wrote:
>> Hello Dmitry,
>> 
>>     Would it be convenient for you to continue reviewing this patch at your convenience?
>> Or let me know your opinion on this patch.
>>  
>>    There is still one unresolved issue highlighted by you in the first version: try to use drm_dp_read_sink_count_cap
>> instead of dw_dp_has_sink_count. But there is no response on my patch try to Pass down connector to drm bridge detect hook[0].
>>    I don't know how to proceed with this patch at the moment.
>
>Please excuse me for the delay. It seems Maxime doesn't like that patch,
>but I do not see a viable generic alternative.
>


I still want to express my gratitude for your help. 
So, could we perhaps first keep this part as it is now, and
we can switch to the generic helper in one day when we find a proper way to get the connector ?


>> 
>> 
>> [0]https://lore.kernel.org/dri-devel/20250321085345.136380-1-andyshrk@163.com/
>> 
>> Thank you.
>>   
>> At 2025-04-03 11:37:30, "Andy Yan" <andyshrk@163.com> wrote:
>> >From: Andy Yan <andy.yan@rock-chips.com>
>> >

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3 2/9] drm/bridge: synopsys: Add DW DPTX Controller support library
  2025-05-24  7:59       ` Andy Yan
@ 2025-05-24  8:11         ` Dmitry Baryshkov
  0 siblings, 0 replies; 21+ messages in thread
From: Dmitry Baryshkov @ 2025-05-24  8:11 UTC (permalink / raw)
  To: Andy Yan
  Cc: heiko, neil.armstrong, sebastian.reichel, stephen, dri-devel, hjc,
	mripard, linux-kernel, linux-rockchip, devicetree, yubing.zhang,
	naoki, Laurent.pinchart, Andy Yan, krzk+dt, robh,
	linux-arm-kernel

On Sat, May 24, 2025 at 03:59:52PM +0800, Andy Yan wrote:
> 
> 
> Hello Dmitry,
> 
> 
> 在 2025-05-24 15:46:15,"Dmitry Baryshkov" <dmitry.baryshkov@oss.qualcomm.com> 写道:
> >On Wed, May 14, 2025 at 08:15:55PM +0800, Andy Yan wrote:
> >> Hello Dmitry,
> >> 
> >>     Would it be convenient for you to continue reviewing this patch at your convenience?
> >> Or let me know your opinion on this patch.
> >>  
> >>    There is still one unresolved issue highlighted by you in the first version: try to use drm_dp_read_sink_count_cap
> >> instead of dw_dp_has_sink_count. But there is no response on my patch try to Pass down connector to drm bridge detect hook[0].
> >>    I don't know how to proceed with this patch at the moment.
> >
> >Please excuse me for the delay. It seems Maxime doesn't like that patch,
> >but I do not see a viable generic alternative.
> >
> 
> 
> I still want to express my gratitude for your help. 
> So, could we perhaps first keep this part as it is now, and
> we can switch to the generic helper in one day when we find a proper way to get the connector ?
> 

Yes. I will take a look at the series in one of the forthcoming days.

> 
> >> 
> >> 
> >> [0]https://lore.kernel.org/dri-devel/20250321085345.136380-1-andyshrk@163.com/
> >> 
> >> Thank you.
> >>   
> >> At 2025-04-03 11:37:30, "Andy Yan" <andyshrk@163.com> wrote:
> >> >From: Andy Yan <andy.yan@rock-chips.com>
> >> >

-- 
With best wishes
Dmitry


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3 3/9] drm/rockchip: Add RK3588 DPTX output support
  2025-04-03  3:37 ` [PATCH v3 3/9] drm/rockchip: Add RK3588 DPTX output support Andy Yan
  2025-04-04  9:06   ` kernel test robot
  2025-04-04 12:11   ` kernel test robot
@ 2025-05-27 16:12   ` Nicolas Frattaroli
  2025-05-29 12:15     ` Andy Yan
  2 siblings, 1 reply; 21+ messages in thread
From: Nicolas Frattaroli @ 2025-05-27 16:12 UTC (permalink / raw)
  To: dmitry.baryshkov, heiko, linux-rockchip, Andy Yan
  Cc: neil.armstrong, sebastian.reichel, Dmitry Baryshkov, stephen,
	dri-devel, hjc, mripard, linux-kernel, linux-rockchip, devicetree,
	yubing.zhang, naoki, Laurent.pinchart, Andy Yan, krzk+dt, robh,
	linux-arm-kernel

Hi Andy,

thank you for the driver. I'll leave some review comments inline. I don't
have specific knowledge on the DRM subsystem so my comments will be of more
general nature.

On Thursday, 3 April 2025 05:37:31 Central European Summer Time Andy Yan wrote:
> From: Andy Yan <andy.yan@rock-chips.com>
> 
> Add driver extension for Synopsys DesignWare DPTX IP used
> on Rockchip RK3588 SoC.
> 
> Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
> Acked-by: Dmitry Baryshkov <lumag@kernel.org>
> 
> ---
> 
> (no changes since v2)
> 
> Changes in v2:
> - no include uapi path
> - switch to drmm_encoder_init
> 
>  drivers/gpu/drm/rockchip/Kconfig            |   9 ++
>  drivers/gpu/drm/rockchip/Makefile           |   1 +
>  drivers/gpu/drm/rockchip/dw_dp-rockchip.c   | 154 ++++++++++++++++++++
>  drivers/gpu/drm/rockchip/rockchip_drm_drv.c |   1 +
>  drivers/gpu/drm/rockchip/rockchip_drm_drv.h |   1 +
>  5 files changed, 166 insertions(+)
>  create mode 100644 drivers/gpu/drm/rockchip/dw_dp-rockchip.c
> 
> diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
> index 26c4410b2407c..00315cc6be5a8 100644
> --- a/drivers/gpu/drm/rockchip/Kconfig
> +++ b/drivers/gpu/drm/rockchip/Kconfig
> @@ -8,6 +8,7 @@ config DRM_ROCKCHIP
>  	select DRM_PANEL
>  	select VIDEOMODE_HELPERS
>  	select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP
> +	select DRM_DW_DP if ROCKCHIP_DW_DP
>  	select DRM_DW_HDMI if ROCKCHIP_DW_HDMI
>  	select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP
>  	select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI
> @@ -58,6 +59,14 @@ config ROCKCHIP_CDN_DP
>  	  RK3399 based SoC, you should select this
>  	  option.
>  
> +config ROCKCHIP_DW_DP
> +	bool "Rockchip specific extensions for Synopsys DW DP"
> +	help
> +	  This selects support for Rockchip SoC specific extensions
> +	  to enable Synopsys DesignWare Cores based DisplayPort transmit
> +	  controller support on Rockchip SoC, If you want to enable DP on
> +	  rk3588 based SoC, you should select this option.
> +
>  config ROCKCHIP_DW_HDMI
>  	bool "Rockchip specific extensions for Synopsys DW HDMI"
>  	help
> diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
> index 2b867cebbc121..097f062399c7a 100644
> --- a/drivers/gpu/drm/rockchip/Makefile
> +++ b/drivers/gpu/drm/rockchip/Makefile
> @@ -14,6 +14,7 @@ rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
>  rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI_QP) += dw_hdmi_qp-rockchip.o
>  rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi-rockchip.o
>  rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI2) += dw-mipi-dsi2-rockchip.o
> +rockchipdrm-$(CONFIG_ROCKCHIP_DW_DP) += dw_dp-rockchip.o
>  rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
>  rockchipdrm-$(CONFIG_ROCKCHIP_LVDS) += rockchip_lvds.o
>  rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o
> diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
> new file mode 100644
> index 0000000000000..5ff8a6a54997e
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
> @@ -0,0 +1,154 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2020 Rockchip Electronics Co., Ltd.
> + *
> + * Author: Zhang Yubing <yubing.zhang@rock-chips.com>
> + * Author: Andy Yan <andy.yan@rock-chips.com>
> + */
> +
> +#include <linux/component.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <drm/bridge/dw_dp.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_bridge_connector.h>

I think there's a missing #include <drm/display/drm_dp_helper.h> here. It
gets pulled in implicitly in most configurations, but I think this is what
the s390 build failure from the kernel test robot report is about.

> +#include <drm/drm_of.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_probe_helper.h>
> +#include <drm/drm_simple_kms_helper.h>
> +
> +#include <linux/media-bus-format.h>
> +#include <linux/videodev2.h>
> +
> +#include "rockchip_drm_drv.h"
> +#include "rockchip_drm_vop.h"
> +
> +struct rockchip_dw_dp {
> +	struct dw_dp *base;
> +	struct device *dev;
> +	struct rockchip_encoder encoder;
> +};
> +
> +static inline struct rockchip_dw_dp *encoder_to_dp(struct drm_encoder *encoder)
> +{
> +	struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
> +
> +	return container_of(rkencoder, struct rockchip_dw_dp, encoder);
> +}

This function appears to be unused, and will generate a warning:

  linux/drivers/gpu/drm/rockchip/dw_dp-rockchip.c:33:38: warning: unused
  function 'encoder_to_dp' [-Wunused-function]

I assume it may be used in a follow-up series. I think it's fine to add it
there when it's needed and avoid the warning for now by removing it.

> +
> +static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder,
> +				      struct drm_crtc_state *crtc_state,
> +				      struct drm_connector_state *conn_state)
> +{
> +	struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
> +	struct drm_atomic_state *state = conn_state->state;
> +	struct drm_display_info *di = &conn_state->connector->display_info;
> +	struct drm_bridge *bridge  = drm_bridge_chain_get_first_bridge(encoder);
> +	struct drm_bridge_state *bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
> +	u32 bus_format = bridge_state->input_bus_cfg.format;
> +
> +	switch (bus_format) {
> +	case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
> +	case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
> +		s->output_mode = ROCKCHIP_OUT_MODE_YUV420;
> +		break;
> +	case MEDIA_BUS_FMT_YUYV10_1X20:
> +	case MEDIA_BUS_FMT_YUYV8_1X16:
> +		s->output_mode = ROCKCHIP_OUT_MODE_S888_DUMMY;
> +		break;
> +	case MEDIA_BUS_FMT_RGB101010_1X30:
> +	case MEDIA_BUS_FMT_RGB888_1X24:
> +	case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
> +	case MEDIA_BUS_FMT_YUV10_1X30:
> +	case MEDIA_BUS_FMT_YUV8_1X24:
> +	default:
> +		s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
> +		break;
> +	}
> +
> +	s->output_type = DRM_MODE_CONNECTOR_DisplayPort;
> +	s->bus_format = bus_format;
> +	s->bus_flags = di->bus_flags;
> +	s->color_space = V4L2_COLORSPACE_DEFAULT;

Reading the VOP2 code s->color_space gets read by, it seems this results
in the output always using BT.709 as the colour space in YUV output mode.
Is my understanding of the code correct?

I don't know if DP 1.4 is limited with regards to HDR or if this is just
left for later to implement, but BT.709 in the case of an HDR RGB VOP
input seems wrong, unless VOP2 sets the output color_space to something like
BT.2020 or similar in the case of output_mode == ROCKCHIP_OUT_MODE_AAAA and
I'm not seeing it.

> +
> +	return 0;
> +}
> +
> +static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = {
> +	.atomic_check		= dw_dp_encoder_atomic_check,
> +};
> +
> +static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data)
> +{
> +	struct dw_dp_plat_data plat_data;
> +	struct drm_device *drm_dev = data;
> +	struct rockchip_dw_dp *dp;
> +	struct drm_encoder *encoder;
> +	struct drm_connector *connector;
> +	int ret;
> +
> +	dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
> +	if (!dp)
> +		return -ENOMEM;
> +
> +	dp->dev = dev;
> +	plat_data.max_link_rate = 810000;
> +	encoder = &dp->encoder.encoder;
> +	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node);
> +	rockchip_drm_encoder_set_crtc_endpoint_id(&dp->encoder, dev->of_node, 0, 0);
> +
> +	ret = drmm_encoder_init(drm_dev, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL);
> +	if (ret)
> +		return ret;
> +	drm_encoder_helper_add(encoder, &dw_dp_encoder_helper_funcs);
> +
> +	dp->base = dw_dp_bind(dev, encoder, &plat_data);
> +	if (IS_ERR(dp->base)) {
> +		ret = PTR_ERR(dp->base);
> +		return ret;
> +	}
> +
> +	connector = drm_bridge_connector_init(drm_dev, encoder);
> +	if (IS_ERR(connector)) {
> +		ret = PTR_ERR(connector);
> +		return dev_err_probe(dev, ret, "Failed to init bridge connector");
> +	}
> +
> +	drm_connector_attach_encoder(connector, encoder);
> +
> +	return 0;
> +}
> +
> +static const struct component_ops dw_dp_rockchip_component_ops = {
> +	.bind = dw_dp_rockchip_bind,
> +};
> +
> +static int dw_dp_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +
> +	return component_add(dev, &dw_dp_rockchip_component_ops);
> +}
> +
> +static void dw_dp_remove(struct platform_device *pdev)
> +{
> +	struct rockchip_dw_dp *dp = platform_get_drvdata(pdev);

Does one of the helper functions or something else set drvdata? Otherwise
I don't see how this is ever non-null.

> +
> +	component_del(dp->dev, &dw_dp_rockchip_component_ops);
> +}
> +
> +static const struct of_device_id dw_dp_of_match[] = {
> +	{ .compatible = "rockchip,rk3588-dp", },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, dw_dp_of_match);
> +
> +struct platform_driver dw_dp_driver = {
> +	.probe	= dw_dp_probe,
> +	.remove = dw_dp_remove,
> +	.driver = {
> +		.name = "dw-dp",
> +		.of_match_table = dw_dp_of_match,
> +	},
> +};
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
> index ed88788e04dd2..687bb7b252e8e 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
> @@ -531,6 +531,7 @@ static int __init rockchip_drm_init(void)
>  	ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,
>  				CONFIG_ROCKCHIP_ANALOGIX_DP);
>  	ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP);
> +	ADD_ROCKCHIP_SUB_DRIVER(dw_dp_driver, CONFIG_ROCKCHIP_DW_DP);
>  	ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver,
>  				CONFIG_ROCKCHIP_DW_HDMI);
>  	ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_qp_rockchip_pltfm_driver,
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
> index c183e82a42a51..2e86ad00979c4 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
> @@ -87,6 +87,7 @@ int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rencoder,
>  					      struct device_node *np, int port, int reg);
>  int rockchip_drm_endpoint_is_subdriver(struct device_node *ep);
>  extern struct platform_driver cdn_dp_driver;
> +extern struct platform_driver dw_dp_driver;
>  extern struct platform_driver dw_hdmi_rockchip_pltfm_driver;
>  extern struct platform_driver dw_hdmi_qp_rockchip_pltfm_driver;
>  extern struct platform_driver dw_mipi_dsi_rockchip_driver;
> 

Other than that, the driver looks great, thank you! I've tested it on my
ROCK 5T over DP altmode, where it correctly interfaces with a DP monitor
I have through an alt-mode adapter. So feel free to add a

Tested-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>

Kind regards,
Nicolas Frattaroli





^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re:Re: [PATCH v3 3/9] drm/rockchip: Add RK3588 DPTX output support
  2025-05-27 16:12   ` Nicolas Frattaroli
@ 2025-05-29 12:15     ` Andy Yan
  0 siblings, 0 replies; 21+ messages in thread
From: Andy Yan @ 2025-05-29 12:15 UTC (permalink / raw)
  To: Nicolas Frattaroli
  Cc: dmitry.baryshkov, heiko, linux-rockchip, neil.armstrong,
	sebastian.reichel, Dmitry Baryshkov, stephen, dri-devel, hjc,
	mripard, linux-kernel, devicetree, yubing.zhang, naoki,
	Laurent.pinchart, Andy Yan, krzk+dt, robh, linux-arm-kernel


Hi Nicolas,

At 2025-05-28 00:12:43, "Nicolas Frattaroli" <nicolas.frattaroli@collabora.com> wrote:
>Hi Andy,
>
>thank you for the driver. I'll leave some review comments inline. I don't
>have specific knowledge on the DRM subsystem so my comments will be of more
>general nature.
>

Thank you very much for your review.


>On Thursday, 3 April 2025 05:37:31 Central European Summer Time Andy Yan wrote:
>> From: Andy Yan <andy.yan@rock-chips.com>
>> 
>> Add driver extension for Synopsys DesignWare DPTX IP used
>> on Rockchip RK3588 SoC.
>> 
>> Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
>> Acked-by: Dmitry Baryshkov <lumag@kernel.org>
>> 
>> ---
>> 
>> (no changes since v2)
>> 
>> Changes in v2:
>> - no include uapi path
>> - switch to drmm_encoder_init
>> 
>>  drivers/gpu/drm/rockchip/Kconfig            |   9 ++
>>  drivers/gpu/drm/rockchip/Makefile           |   1 +
>>  drivers/gpu/drm/rockchip/dw_dp-rockchip.c   | 154 ++++++++++++++++++++
>>  drivers/gpu/drm/rockchip/rockchip_drm_drv.c |   1 +
>>  drivers/gpu/drm/rockchip/rockchip_drm_drv.h |   1 +
>>  5 files changed, 166 insertions(+)
>>  create mode 100644 drivers/gpu/drm/rockchip/dw_dp-rockchip.c
>> 
>> diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
>> index 26c4410b2407c..00315cc6be5a8 100644
>> --- a/drivers/gpu/drm/rockchip/Kconfig
>> +++ b/drivers/gpu/drm/rockchip/Kconfig
>> @@ -8,6 +8,7 @@ config DRM_ROCKCHIP
>>  	select DRM_PANEL
>>  	select VIDEOMODE_HELPERS
>>  	select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP
>> +	select DRM_DW_DP if ROCKCHIP_DW_DP
>>  	select DRM_DW_HDMI if ROCKCHIP_DW_HDMI
>>  	select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP
>>  	select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI
>> @@ -58,6 +59,14 @@ config ROCKCHIP_CDN_DP
>>  	  RK3399 based SoC, you should select this
>>  	  option.
>>  
>> +config ROCKCHIP_DW_DP
>> +	bool "Rockchip specific extensions for Synopsys DW DP"
>> +	help
>> +	  This selects support for Rockchip SoC specific extensions
>> +	  to enable Synopsys DesignWare Cores based DisplayPort transmit
>> +	  controller support on Rockchip SoC, If you want to enable DP on
>> +	  rk3588 based SoC, you should select this option.
>> +
>>  config ROCKCHIP_DW_HDMI
>>  	bool "Rockchip specific extensions for Synopsys DW HDMI"
>>  	help
>> diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
>> index 2b867cebbc121..097f062399c7a 100644
>> --- a/drivers/gpu/drm/rockchip/Makefile
>> +++ b/drivers/gpu/drm/rockchip/Makefile
>> @@ -14,6 +14,7 @@ rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
>>  rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI_QP) += dw_hdmi_qp-rockchip.o
>>  rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi-rockchip.o
>>  rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI2) += dw-mipi-dsi2-rockchip.o
>> +rockchipdrm-$(CONFIG_ROCKCHIP_DW_DP) += dw_dp-rockchip.o
>>  rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
>>  rockchipdrm-$(CONFIG_ROCKCHIP_LVDS) += rockchip_lvds.o
>>  rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o
>> diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
>> new file mode 100644
>> index 0000000000000..5ff8a6a54997e
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
>> @@ -0,0 +1,154 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (c) 2020 Rockchip Electronics Co., Ltd.
>> + *
>> + * Author: Zhang Yubing <yubing.zhang@rock-chips.com>
>> + * Author: Andy Yan <andy.yan@rock-chips.com>
>> + */
>> +
>> +#include <linux/component.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <drm/bridge/dw_dp.h>
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_bridge.h>
>> +#include <drm/drm_bridge_connector.h>
>
>I think there's a missing #include <drm/display/drm_dp_helper.h> here. It
>gets pulled in implicitly in most configurations, but I think this is what
>the s390 build failure from the kernel test robot report is about.

I have included this header file in the file dw-dp.c. 
I think the error occurred during the robot test because the following patch[0] had
not been merged yet. Now that this submission has been merged, and dw_dp-rockchip.c
does not call  functions from that header file, so it should no longer report any errors now.

[0]https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/?id=09cdda7a60f45784cebddf1fa2109d6279f9890b



>
>> +#include <drm/drm_of.h>
>> +#include <drm/drm_print.h>
>> +#include <drm/drm_probe_helper.h>
>> +#include <drm/drm_simple_kms_helper.h>
>> +
>> +#include <linux/media-bus-format.h>
>> +#include <linux/videodev2.h>
>> +
>> +#include "rockchip_drm_drv.h"
>> +#include "rockchip_drm_vop.h"
>> +
>> +struct rockchip_dw_dp {
>> +	struct dw_dp *base;
>> +	struct device *dev;
>> +	struct rockchip_encoder encoder;
>> +};
>> +
>> +static inline struct rockchip_dw_dp *encoder_to_dp(struct drm_encoder *encoder)
>> +{
>> +	struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
>> +
>> +	return container_of(rkencoder, struct rockchip_dw_dp, encoder);
>> +}
>
>This function appears to be unused, and will generate a warning:
>
>  linux/drivers/gpu/drm/rockchip/dw_dp-rockchip.c:33:38: warning: unused
>  function 'encoder_to_dp' [-Wunused-function]
>
>I assume it may be used in a follow-up series. I think it's fine to add it
>there when it's needed and avoid the warning for now by removing it.


Thank you for catching it, i will remove it in V4.
>
>> +
>> +static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder,
>> +				      struct drm_crtc_state *crtc_state,
>> +				      struct drm_connector_state *conn_state)
>> +{
>> +	struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
>> +	struct drm_atomic_state *state = conn_state->state;
>> +	struct drm_display_info *di = &conn_state->connector->display_info;
>> +	struct drm_bridge *bridge  = drm_bridge_chain_get_first_bridge(encoder);
>> +	struct drm_bridge_state *bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
>> +	u32 bus_format = bridge_state->input_bus_cfg.format;
>> +
>> +	switch (bus_format) {
>> +	case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
>> +	case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
>> +		s->output_mode = ROCKCHIP_OUT_MODE_YUV420;
>> +		break;
>> +	case MEDIA_BUS_FMT_YUYV10_1X20:
>> +	case MEDIA_BUS_FMT_YUYV8_1X16:
>> +		s->output_mode = ROCKCHIP_OUT_MODE_S888_DUMMY;
>> +		break;
>> +	case MEDIA_BUS_FMT_RGB101010_1X30:
>> +	case MEDIA_BUS_FMT_RGB888_1X24:
>> +	case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
>> +	case MEDIA_BUS_FMT_YUV10_1X30:
>> +	case MEDIA_BUS_FMT_YUV8_1X24:
>> +	default:
>> +		s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
>> +		break;
>> +	}
>> +
>> +	s->output_type = DRM_MODE_CONNECTOR_DisplayPort;
>> +	s->bus_format = bus_format;
>> +	s->bus_flags = di->bus_flags;
>> +	s->color_space = V4L2_COLORSPACE_DEFAULT;
>
>Reading the VOP2 code s->color_space gets read by, it seems this results
>in the output always using BT.709 as the colour space in YUV output mode.
>Is my understanding of the code correct?
>
>I don't know if DP 1.4 is limited with regards to HDR or if this is just
>left for later to implement, but BT.709 in the case of an HDR RGB VOP
>input seems wrong, unless VOP2 sets the output color_space to something like
>BT.2020 or similar in the case of output_mode == ROCKCHIP_OUT_MODE_AAAA and
>I'm not seeing it.

In our downstream code, when DP is in HDR output mode, the color_space will be set to  V4L2_COLORSPACE_BT2020. 
Therefore, when we later support HDR output on the mainline, we can make the same adjustment.


>
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = {
>> +	.atomic_check		= dw_dp_encoder_atomic_check,
>> +};
>> +
>> +static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data)
>> +{
>> +	struct dw_dp_plat_data plat_data;
>> +	struct drm_device *drm_dev = data;
>> +	struct rockchip_dw_dp *dp;
>> +	struct drm_encoder *encoder;
>> +	struct drm_connector *connector;
>> +	int ret;
>> +
>> +	dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
>> +	if (!dp)
>> +		return -ENOMEM;
>> +
>> +	dp->dev = dev;
>> +	plat_data.max_link_rate = 810000;
>> +	encoder = &dp->encoder.encoder;
>> +	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node);
>> +	rockchip_drm_encoder_set_crtc_endpoint_id(&dp->encoder, dev->of_node, 0, 0);
>> +
>> +	ret = drmm_encoder_init(drm_dev, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL);
>> +	if (ret)
>> +		return ret;
>> +	drm_encoder_helper_add(encoder, &dw_dp_encoder_helper_funcs);
>> +
>> +	dp->base = dw_dp_bind(dev, encoder, &plat_data);
>> +	if (IS_ERR(dp->base)) {
>> +		ret = PTR_ERR(dp->base);
>> +		return ret;
>> +	}
>> +
>> +	connector = drm_bridge_connector_init(drm_dev, encoder);
>> +	if (IS_ERR(connector)) {
>> +		ret = PTR_ERR(connector);
>> +		return dev_err_probe(dev, ret, "Failed to init bridge connector");
>> +	}
>> +
>> +	drm_connector_attach_encoder(connector, encoder);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct component_ops dw_dp_rockchip_component_ops = {
>> +	.bind = dw_dp_rockchip_bind,
>> +};
>> +
>> +static int dw_dp_probe(struct platform_device *pdev)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +
>> +	return component_add(dev, &dw_dp_rockchip_component_ops);
>> +}
>> +
>> +static void dw_dp_remove(struct platform_device *pdev)
>> +{
>> +	struct rockchip_dw_dp *dp = platform_get_drvdata(pdev);
>
>Does one of the helper functions or something else set drvdata? Otherwise
>I don't see how this is ever non-null.

Thanks  for catching it, will be fixed in V4.

>
>> +
>> +	component_del(dp->dev, &dw_dp_rockchip_component_ops);
>> +}
>> +
>> +static const struct of_device_id dw_dp_of_match[] = {
>> +	{ .compatible = "rockchip,rk3588-dp", },
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(of, dw_dp_of_match);
>> +
>> +struct platform_driver dw_dp_driver = {
>> +	.probe	= dw_dp_probe,
>> +	.remove = dw_dp_remove,
>> +	.driver = {
>> +		.name = "dw-dp",
>> +		.of_match_table = dw_dp_of_match,
>> +	},
>> +};
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
>> index ed88788e04dd2..687bb7b252e8e 100644
>> --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
>> @@ -531,6 +531,7 @@ static int __init rockchip_drm_init(void)
>>  	ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,
>>  				CONFIG_ROCKCHIP_ANALOGIX_DP);
>>  	ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP);
>> +	ADD_ROCKCHIP_SUB_DRIVER(dw_dp_driver, CONFIG_ROCKCHIP_DW_DP);
>>  	ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver,
>>  				CONFIG_ROCKCHIP_DW_HDMI);
>>  	ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_qp_rockchip_pltfm_driver,
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
>> index c183e82a42a51..2e86ad00979c4 100644
>> --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
>> @@ -87,6 +87,7 @@ int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rencoder,
>>  					      struct device_node *np, int port, int reg);
>>  int rockchip_drm_endpoint_is_subdriver(struct device_node *ep);
>>  extern struct platform_driver cdn_dp_driver;
>> +extern struct platform_driver dw_dp_driver;
>>  extern struct platform_driver dw_hdmi_rockchip_pltfm_driver;
>>  extern struct platform_driver dw_hdmi_qp_rockchip_pltfm_driver;
>>  extern struct platform_driver dw_mipi_dsi_rockchip_driver;
>> 
>
>Other than that, the driver looks great, thank you! I've tested it on my
>ROCK 5T over DP altmode, where it correctly interfaces with a DP monitor
>I have through an alt-mode adapter. So feel free to add a
>
>Tested-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>

Thanks again.

>
>Kind regards,
>Nicolas Frattaroli
>
>
>

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2025-05-29 12:53 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-03  3:37 [PATCH v3 0/9] Add support for RK3588 DisplayPort Controller Andy Yan
2025-04-03  3:37 ` [PATCH v3 1/9] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller Andy Yan
2025-04-04 19:36   ` Diederik de Haas
2025-04-03  3:37 ` [PATCH v3 2/9] drm/bridge: synopsys: Add DW DPTX Controller support library Andy Yan
2025-05-14 12:15   ` Andy Yan
2025-05-24  7:46     ` [PATCH " Dmitry Baryshkov
2025-05-24  7:59       ` Andy Yan
2025-05-24  8:11         ` Dmitry Baryshkov
2025-04-03  3:37 ` [PATCH v3 3/9] drm/rockchip: Add RK3588 DPTX output support Andy Yan
2025-04-04  9:06   ` kernel test robot
2025-04-04 12:11   ` kernel test robot
2025-05-27 16:12   ` Nicolas Frattaroli
2025-05-29 12:15     ` Andy Yan
2025-04-03  3:37 ` [PATCH v3 4/9] dt-bindings: display: simple-bridge: Add ra620 compatible Andy Yan
2025-04-03  6:42   ` Krzysztof Kozlowski
2025-04-03  3:37 ` [PATCH v3 5/9] drm/birdge: simple-bridge: Add support for radxa ra620 Andy Yan
2025-04-03 12:14   ` Dmitry Baryshkov
2025-04-03  3:37 ` [PATCH v3 6/9] arm64: dts: rockchip: Add DP0 for rk3588 Andy Yan
2025-04-03  3:37 ` [PATCH v3 7/9] arm64: dts: rockchip: Add DP1 " Andy Yan
2025-04-03  3:37 ` [PATCH v3 8/9] arm64: dts: rockchip: Enable DisplayPort for rk3588s Cool Pi 4B Andy Yan
2025-04-03  3:37 ` [PATCH v3 9/9] arm64: dts: rockchip: Enable DP2HDMI for ROCK 5 ITX Andy Yan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).