linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [v5 PATCH 0/5] Rockchip Type-C and DisplayPort driver
@ 2016-07-12 15:09 Chris Zhong
  2016-07-12 15:09 ` [v5 PATCH 2/5] Documentation: bindings: add dt doc for Rockchip USB Type-C PHY Chris Zhong
                   ` (3 more replies)
  0 siblings, 4 replies; 11+ messages in thread
From: Chris Zhong @ 2016-07-12 15:09 UTC (permalink / raw)
  To: linux-arm-kernel


Hi all

This series patch is for rockchip Type-C phy and DisplayPort controller
driver.

The USB Type-C PHY is designed to support the USB3 and DP applications.
The PHY basically has two main components: USB3 and DisplyPort. USB3
operates in SuperSpeed mode and the DP can operate at RBR, HBR and HBR2
data rates. The Type-C cable orientation detection and Power Delivery
(PD) is accomplished using a PD PHY or a exernal PD chip.

The DP controller is compliant with DisplayPort Specification,
Version 1.3, This IP is compatible with the rockchip type-c PHY IP.
There is a uCPU in DP controller, it need a firmware to work, please
put the firmware file[0] to /lib/firmware/cdn/dptx.bin. The uCPU in charge
of aux communication and link training, the host use mailbox to
communicate with the ucpu.

The DP contoller has register a notification with extcon API, to get the
alt mode from PD, the PD driver need call the devm_extcon_dev_allocate
to create a extcon device and use extcon_set_state to notify DP
controller. And call extcon_set_cable_property to set orientation.

About the DP audio, cdn-dp registered 2 DAIs: 0 is I2S, 1 is SPDIF.
We can reference them in simple-card.

This series is based on Mark Yao's branch[1] and Chanwoo Choi's
extcon-test branch[2]

I test this patches on the rk3399-evb board, with a fusb302 driver,
this branch has no rk3399.dtsi, so the patch about dts is not included
in this series.

[0]
https://patchwork.kernel.org/patch/9225567/
[1]
https://github.com/markyzq/kernel-drm-rockchip/tree/drm-rockchip-next-2016-05-23
[2]
https://git.kernel.org/cgit/linux/kernel/git/chanwoo/extcon.git/log/?h=extcon-test
- usb: dwc3: omap: Support the changed method to get the state of connector
- usb: chipdata: Support the changed method to get the state of connector
- extcon: Add the support for extcon property according to type of connector
- extcon: Add the extcon_type to group each connector into five category


Changes in v5:
- support get property
- support get property from extcon
- remove PIN ASSIGN A/B support
- alphabetical order
- do not use long, use u32 or u64
- return MODE_CLOCK_HIGH when requested > actual
- Optimized Coding Style
- add a formula to get better tu size and symbol value.

Changes in v4:
- add a #phy-cells node
- select EXTCON
- use phy framework to control the USB3 and DP function
- rename PIN_MAP_ to PIN_ASSIGN_
- add a reset node
- support 2 phys
- use phy framework to control DP phy
- support 2 phys

Changes in v3:
- use compatible: rockchip,rk3399-typec-phy
- use dashes instead of underscores.
- remove the phy framework(Kishon Vijay Abraham I)
- add parentheses around the macro
- use a single space between type and name
- add spaces after opening and before closing braces.
- use u16 for register value
- remove type-c phy header file
- CodingStyle optimization
- use some cable extcon to get type-c port information
- add a extcon to notify Display Port
- add SoC specific compatible string
- remove reg = <1>;
- use EXTCON_DISP_DP and EXTCON_DISP_DP_ALT cable to get dp port state.
- reset spdif before config it
- modify the firmware clk to 100Mhz
- retry load firmware if fw file is requested too early

Changes in v2:
- add some registers description
- select RESET_CONTROLLER
- alphabetic order
- modify some spelling mistakes
- make mode cleaner
- use bool for enable/disable
- check all of the return value
- return a better err number
- use more readx_poll_timeout()
- clk_disable_unprepare(tcphy->clk_ref);
- remove unuse functions, rockchip_typec_phy_power_on/off
- remove unnecessary typecast from void *
- use dts node to distinguish between phys.
- Alphabetic order
- remove excess error message
- use define clk_rate
- check all return value
- remove dev_set_name(dp->dev, "cdn-dp");
- use schedule_delayed_work
- remove never-called functions
- remove some unnecessary ()

Changes in v1:
- add extcon node description
- move the registers in phy driver
- remove the suffix of reset
- update the licence note
- init core clock to 50MHz
- use extcon API
- remove unused global
- add some comments for magic num
- change usleep_range(1000, 2000) tousleep_range(1000, 1050)
- remove __func__ from dev_err
- return err number when get clk failed
- remove ADDR_ADJ define
- use devm_clk_get(&pdev->dev, "tcpdcore")
- add extcon node description
- add #sound-dai-cells description
- use extcon API
- use hdmi-codec for the DP Asoc
- do not initialize the "ret"
- printk a err log when drm_of_encoder_active_endpoint_id
- modify the dclk pin_pol to a single line

Chris Zhong (5):
  extcon: Add Type-C and DP support
  Documentation: bindings: add dt doc for Rockchip USB Type-C PHY
  phy: Add USB Type-C PHY driver for rk3399
  Documentation: bindings: add dt documentation for cdn DP controller
  drm/rockchip: cdn-dp: add cdn DP support for rk3399

 .../bindings/display/rockchip/cdn-dp-rockchip.txt  |  67 ++
 .../devicetree/bindings/phy/phy-rockchip-typec.txt |  77 ++
 drivers/extcon/extcon.c                            |  28 +
 drivers/gpu/drm/rockchip/Kconfig                   |   9 +
 drivers/gpu/drm/rockchip/Makefile                  |   1 +
 drivers/gpu/drm/rockchip/cdn-dp-core.c             | 761 ++++++++++++++++
 drivers/gpu/drm/rockchip/cdn-dp-core.h             | 113 +++
 drivers/gpu/drm/rockchip/cdn-dp-reg.c              | 740 ++++++++++++++++
 drivers/gpu/drm/rockchip/cdn-dp-reg.h              | 409 +++++++++
 drivers/gpu/drm/rockchip/rockchip_drm_vop.c        |   6 +-
 drivers/gpu/drm/rockchip/rockchip_drm_vop.h        |   2 +
 drivers/gpu/drm/rockchip/rockchip_vop_reg.c        |   2 +
 drivers/phy/Kconfig                                |   9 +
 drivers/phy/Makefile                               |   1 +
 drivers/phy/phy-rockchip-typec.c                   | 974 +++++++++++++++++++++
 include/linux/extcon.h                             |  13 +
 16 files changed, 3211 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/display/rockchip/cdn-dp-rockchip.txt
 create mode 100644 Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.c
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.h
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.c
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.h
 create mode 100644 drivers/phy/phy-rockchip-typec.c

-- 
2.6.3

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

* [v5 PATCH 2/5] Documentation: bindings: add dt doc for Rockchip USB Type-C PHY
  2016-07-12 15:09 [v5 PATCH 0/5] Rockchip Type-C and DisplayPort driver Chris Zhong
@ 2016-07-12 15:09 ` Chris Zhong
  2016-07-12 15:09 ` [v5 PATCH 3/5] phy: Add USB Type-C PHY driver for rk3399 Chris Zhong
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 11+ messages in thread
From: Chris Zhong @ 2016-07-12 15:09 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds a binding that describes the Rockchip USB Type-C PHY
for rk3399

Signed-off-by: Chris Zhong <zyw@rock-chips.com>
Reviewed-by: Tomasz Figa <tfiga@chromium.org>
Reviewed-by: Kever Yang <kever.yang@rock-chips.com>
Acked-by: Rob Herring <robh@kernel.org>

---

Changes in v5: None
Changes in v4:
- add a #phy-cells node

Changes in v3:
- use compatible: rockchip,rk3399-typec-phy
- use dashes instead of underscores.

Changes in v2:
- add some registers description

Changes in v1:
- add extcon node description
- move the registers in phy driver
- remove the suffix of reset

 .../devicetree/bindings/phy/phy-rockchip-typec.txt | 77 ++++++++++++++++++++++
 1 file changed, 77 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt

diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt b/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt
new file mode 100644
index 0000000..b1c6ef3f
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt
@@ -0,0 +1,77 @@
+* ROCKCHIP type-c PHY
+---------------------
+
+Required properties:
+ - compatible : must be "rockchip,rk3399-typec-phy"
+ - reg: Address and length of the usb phy control register set
+ - rockchip,grf : phandle to the syscon managing the "general
+   register files"
+ - clocks : phandle + clock specifier for the phy clocks
+ - clock-names : string, clock name, must be "tcpdcore", "tcpdphy-ref";
+ - resets : a list of phandle + reset specifier pairs
+ - reset-names : string reset name, must be:
+		 "uphy", "uphy-pipe", "uphy-tcphy"
+ - extcon : extcon specifier for the Power Delivery
+ - #phy-cells: must be 0. See ./phy-bindings.txt for details.
+
+Note, there are 2 type-c phys for RK3399, and they are almost identical, except
+these registers(description below), every register node contains 3 sections:
+offset, enable bit, write mask bit.
+ - rockchip,typec-conn-dir : the register of type-c connector direction,
+   for type-c phy0, it must be <0xe580 0 16>;
+   for type-c phy1, it must be <0xe58c 0 16>;
+ - rockchip,usb3tousb2-en : the register of type-c force usb3 to usb2 enable
+   control.
+   for type-c phy0, it must be <0xe580 3 19>;
+   for type-c phy1, it must be <0xe58c 3 19>;
+ - rockchip,external-psm : the register of type-c phy external psm clock
+   selection.
+   for type-c phy0, it must be <0xe588 14 30>;
+   for type-c phy1, it must be <0xe594 14 30>;
+ - rockchip,pipe-status : the register of type-c phy pipe status.
+   for type-c phy0, it must be <0xe5c0 0 0>;
+   for type-c phy1, it must be <0xe5c0 16 16>;
+ - rockchip,uphy-dp-sel : the register of type-c phy selection for DP
+   for type-c phy0, it must be <0x6268 19 19>;
+   for type-c phy1, it must be <0x6268 3 19>;
+
+Example:
+	tcphy0: phy at ff7c0000 {
+		compatible = "rockchip,rk3399-typec-phy";
+		reg = <0x0 0xff7c0000 0x0 0x40000>;
+		rockchip,grf = <&grf>;
+		#phy-cells = <0>;
+		extcon = <&fusb0>;
+		clocks = <&cru SCLK_UPHY0_TCPDCORE>,
+			 <&cru SCLK_UPHY0_TCPDPHY_REF>;
+		clock-names = "tcpdcore", "tcpdphy-ref";
+		resets = <&cru SRST_UPHY0>,
+			 <&cru SRST_UPHY0_PIPE_L00>,
+			 <&cru SRST_P_UPHY0_TCPHY>;
+		reset-names = "uphy", "uphy-pipe", "uphy-tcphy";
+		rockchip,typec-conn-dir = <0xe580 0 16>;
+		rockchip,usb3tousb2-en = <0xe580 3 19>;
+		rockchip,external-psm = <0xe588 14 30>;
+		rockchip,pipe-status = <0xe5c0 0 0>;
+		rockchip,uphy-dp-sel = <0x6268 19 19>;
+	};
+
+	tcphy1: phy at ff800000 {
+		compatible = "rockchip,rk3399-typec-phy";
+		reg = <0x0 0xff800000 0x0 0x40000>;
+		rockchip,grf = <&grf>;
+		#phy-cells = <0>;
+		extcon = <&fusb1>;
+		clocks = <&cru SCLK_UPHY1_TCPDCORE>,
+			 <&cru SCLK_UPHY1_TCPDPHY_REF>;
+		clock-names = "tcpdcore", "tcpdphy-ref";
+		resets = <&cru SRST_UPHY1>,
+		         <&cru SRST_UPHY1_PIPE_L00>,
+			 <&cru SRST_P_UPHY1_TCPHY>;
+		reset-names = "uphy", "uphy-pipe", "uphy-tcphy";
+		rockchip,typec-conn-dir = <0xe58c 0 16>;
+		rockchip,usb3tousb2-en = <0xe58c 3 19>;
+		rockchip,external-psm = <0xe594 14 30>;
+		rockchip,pipe-status = <0xe5c0 16 16>;
+		rockchip,uphy-dp-sel = <0x6268 3 19>;
+	};
-- 
2.6.3

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

* [v5 PATCH 3/5] phy: Add USB Type-C PHY driver for rk3399
  2016-07-12 15:09 [v5 PATCH 0/5] Rockchip Type-C and DisplayPort driver Chris Zhong
  2016-07-12 15:09 ` [v5 PATCH 2/5] Documentation: bindings: add dt doc for Rockchip USB Type-C PHY Chris Zhong
@ 2016-07-12 15:09 ` Chris Zhong
  2016-07-12 15:09 ` [v5 PATCH 4/5] Documentation: bindings: add dt documentation for cdn DP controller Chris Zhong
  2016-07-12 15:09 ` [v5 PATCH 5/5] drm/rockchip: cdn-dp: add cdn DP support for rk3399 Chris Zhong
  3 siblings, 0 replies; 11+ messages in thread
From: Chris Zhong @ 2016-07-12 15:09 UTC (permalink / raw)
  To: linux-arm-kernel

Add a PHY provider driver for the rk3399 SoC Type-c PHY. The USB
Type-C PHY is designed to support the USB3 and DP applications. The
PHY basically has two main components: USB3 and DisplyPort. USB3
operates in SuperSpeed mode and the DP can operate at RBR, HBR and
HBR2 data rates.

Signed-off-by: Chris Zhong <zyw@rock-chips.com>
Signed-off-by: Kever Yang <kever.yang@rock-chips.com>

---

Changes in v5:
- support get property from extcon
- remove PIN ASSIGN A/B support

Changes in v4:
- select EXTCON
- use phy framework to control the USB3 and DP function
- rename PIN_MAP_ to PIN_ASSIGN_

Changes in v3:
- remove the phy framework(Kishon Vijay Abraham I)
- add parentheses around the macro
- use a single space between type and name
- add spaces after opening and before closing braces.
- use u16 for register value
- remove type-c phy header file
- CodingStyle optimization
- use some cable extcon to get type-c port information
- add a extcon to notify Display Port

Changes in v2:
- select RESET_CONTROLLER
- alphabetic order
- modify some spelling mistakes
- make mode cleaner
- use bool for enable/disable
- check all of the return value
- return a better err number
- use more readx_poll_timeout()
- clk_disable_unprepare(tcphy->clk_ref);
- remove unuse functions, rockchip_typec_phy_power_on/off
- remove unnecessary typecast from void *
- use dts node to distinguish between phys.

Changes in v1:
- update the licence note
- init core clock to 50MHz
- use extcon API
- remove unused global
- add some comments for magic num
- change usleep_range(1000, 2000) tousleep_range(1000, 1050)
- remove __func__ from dev_err
- return err number when get clk failed
- remove ADDR_ADJ define
- use devm_clk_get(&pdev->dev, "tcpdcore")

 drivers/phy/Kconfig              |   9 +
 drivers/phy/Makefile             |   1 +
 drivers/phy/phy-rockchip-typec.c | 974 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 984 insertions(+)
 create mode 100644 drivers/phy/phy-rockchip-typec.c

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 26566db..83706a5 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -351,6 +351,15 @@ config PHY_ROCKCHIP_DP
 	help
 	  Enable this to support the Rockchip Display Port PHY.
 
+config PHY_ROCKCHIP_TYPEC
+	tristate "Rockchip TYPEC PHY Driver"
+	depends on ARCH_ROCKCHIP && OF
+	select EXTCON
+	select GENERIC_PHY
+	select RESET_CONTROLLER
+	help
+	  Enable this to support the Rockchip USB TYPEC PHY.
+
 config PHY_ST_SPEAR1310_MIPHY
 	tristate "ST SPEAR1310-MIPHY driver"
 	select GENERIC_PHY
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 24596a9..91fa413 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_PHY_QCOM_APQ8064_SATA)	+= phy-qcom-apq8064-sata.o
 obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o
 obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o
 obj-$(CONFIG_PHY_ROCKCHIP_DP)		+= phy-rockchip-dp.o
+obj-$(CONFIG_PHY_ROCKCHIP_TYPEC) += phy-rockchip-typec.o
 obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA)	+= phy-qcom-ipq806x-sata.o
 obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY)	+= phy-spear1310-miphy.o
 obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY)	+= phy-spear1340-miphy.o
diff --git a/drivers/phy/phy-rockchip-typec.c b/drivers/phy/phy-rockchip-typec.c
new file mode 100644
index 0000000..348f9b9
--- /dev/null
+++ b/drivers/phy/phy-rockchip-typec.c
@@ -0,0 +1,974 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ *         Kever Yang <kever.yang@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/extcon.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/mfd/syscon.h>
+#include <linux/phy/phy.h>
+
+#define CMN_SSM_BANDGAP			(0x21 << 2)
+#define CMN_SSM_BIAS			(0x22 << 2)
+#define CMN_PLLSM0_PLLEN		(0x29 << 2)
+#define CMN_PLLSM0_PLLPRE		(0x2a << 2)
+#define CMN_PLLSM0_PLLVREF		(0x2b << 2)
+#define CMN_PLLSM0_PLLLOCK		(0x2c << 2)
+#define CMN_PLLSM1_PLLEN		(0x31 << 2)
+#define CMN_PLLSM1_PLLPRE		(0x32 << 2)
+#define CMN_PLLSM1_PLLVREF		(0x33 << 2)
+#define CMN_PLLSM1_PLLLOCK		(0x34 << 2)
+#define CMN_PLLSM1_USER_DEF_CTRL	(0x37 << 2)
+#define CMN_ICAL_OVRD			(0xc1 << 2)
+#define CMN_PLL0_VCOCAL_OVRD		(0x83 << 2)
+#define CMN_PLL0_VCOCAL_INIT		(0x84 << 2)
+#define CMN_PLL0_VCOCAL_ITER		(0x85 << 2)
+#define CMN_PLL0_LOCK_REFCNT_START	(0x90 << 2)
+#define CMN_PLL0_LOCK_PLLCNT_START	(0x92 << 2)
+#define CMN_PLL0_LOCK_PLLCNT_THR	(0x93 << 2)
+#define CMN_PLL0_INTDIV			(0x94 << 2)
+#define CMN_PLL0_FRACDIV		(0x95 << 2)
+#define CMN_PLL0_HIGH_THR		(0x96 << 2)
+#define CMN_PLL0_DSM_DIAG		(0x97 << 2)
+#define CMN_PLL0_SS_CTRL1		(0x98 << 2)
+#define CMN_PLL0_SS_CTRL2		(0x99 << 2)
+#define CMN_PLL1_VCOCAL_START		(0xa1 << 2)
+#define CMN_PLL1_VCOCAL_OVRD		(0xa3 << 2)
+#define CMN_PLL1_VCOCAL_INIT		(0xa4 << 2)
+#define CMN_PLL1_VCOCAL_ITER		(0xa5 << 2)
+#define CMN_PLL1_LOCK_REFCNT_START	(0xb0 << 2)
+#define CMN_PLL1_LOCK_PLLCNT_START	(0xb2 << 2)
+#define CMN_PLL1_LOCK_PLLCNT_THR	(0xb3 << 2)
+#define CMN_PLL1_INTDIV			(0xb4 << 2)
+#define CMN_PLL1_FRACDIV		(0xb5 << 2)
+#define CMN_PLL1_HIGH_THR		(0xb6 << 2)
+#define CMN_PLL1_DSM_DIAG		(0xb7 << 2)
+#define CMN_PLL1_SS_CTRL1		(0xb8 << 2)
+#define CMN_PLL1_SS_CTRL2		(0xb9 << 2)
+#define CMN_RXCAL_OVRD			(0xd1 << 2)
+#define CMN_TXPUCAL_CTRL		(0xe0 << 2)
+#define CMN_TXPUCAL_OVRD		(0xe1 << 2)
+#define CMN_TXPDCAL_OVRD		(0xf1 << 2)
+#define CMN_DIAG_PLL0_FBH_OVRD		(0x1c0 << 2)
+#define CMN_DIAG_PLL0_FBL_OVRD		(0x1c1 << 2)
+#define CMN_DIAG_PLL0_OVRD		(0x1c2 << 2)
+#define CMN_DIAG_PLL0_V2I_TUNE		(0x1c5 << 2)
+#define CMN_DIAG_PLL0_CP_TUNE		(0x1c6 << 2)
+#define CMN_DIAG_PLL0_LF_PROG		(0x1c7 << 2)
+#define CMN_DIAG_PLL1_FBH_OVRD		(0x1d0 << 2)
+#define CMN_DIAG_PLL1_FBL_OVRD		(0x1d1 << 2)
+#define CMN_DIAG_PLL1_OVRD		(0x1d2 << 2)
+#define CMN_DIAG_PLL1_V2I_TUNE		(0x1d5 << 2)
+#define CMN_DIAG_PLL1_CP_TUNE		(0x1d6 << 2)
+#define CMN_DIAG_PLL1_LF_PROG		(0x1d7 << 2)
+#define CMN_DIAG_PLL1_PTATIS_TUNE1	(0x1d8 << 2)
+#define CMN_DIAG_PLL1_PTATIS_TUNE2	(0x1d9 << 2)
+#define CMN_DIAG_PLL1_INCLK_CTRL	(0x1da << 2)
+#define CMN_DIAG_HSCLK_SEL		(0x1e0 << 2)
+
+#define XCVR_PSM_RCTRL(n)		((0x4001 | ((n) << 9)) << 2)
+#define XCVR_PSM_CAL_TMR(n)		((0x4002 | ((n) << 9)) << 2)
+#define XCVR_PSM_A0IN_TMR(n)		((0x4003 | ((n) << 9)) << 2)
+#define TX_TXCC_CAL_SCLR_MULT(n)	((0x4047 | ((n) << 9)) << 2)
+#define TX_TXCC_CPOST_MULT_00(n)	((0x404c | ((n) << 9)) << 2)
+#define TX_TXCC_CPOST_MULT_01(n)	((0x404d | ((n) << 9)) << 2)
+#define TX_TXCC_CPOST_MULT_10(n)	((0x404e | ((n) << 9)) << 2)
+#define TX_TXCC_CPOST_MULT_11(n)	((0x404f | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_000(n)	((0x4050 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_001(n)	((0x4051 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_010(n)	((0x4052 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_011(n)	((0x4053 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_100(n)	((0x4054 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_101(n)	((0x4055 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_110(n)	((0x4056 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_111(n)	((0x4057 | ((n) << 9)) << 2)
+#define XCVR_DIAG_PLLDRC_CTRL(n)	((0x40e0 | ((n) << 9)) << 2)
+#define XCVR_DIAG_BIDI_CTRL(n)		((0x40e8 | ((n) << 9)) << 2)
+#define XCVR_DIAG_LANE_FCM_EN_MGN(n)	((0x40f2 | ((n) << 9)) << 2)
+#define TX_PSC_A0(n)			((0x4100 | ((n) << 9)) << 2)
+#define TX_PSC_A1(n)			((0x4101 | ((n) << 9)) << 2)
+#define TX_PSC_A2(n)			((0x4102 | ((n) << 9)) << 2)
+#define TX_PSC_A3(n)			((0x4103 | ((n) << 9)) << 2)
+#define TX_RCVDET_CTRL(n)		((0x4120 | ((n) << 9)) << 2)
+#define TX_RCVDET_EN_TMR(n)		((0x4122 | ((n) << 9)) << 2)
+#define TX_RCVDET_ST_TMR(n)		((0x4123 | ((n) << 9)) << 2)
+#define TX_DIAG_TX_DRV(n)		((0x41e1 | ((n) << 9)) << 2)
+#define TX_DIAG_BGREF_PREDRV_DELAY	(0x41e7 << 2)
+#define TX_ANA_CTRL_REG_1		(0x5020 << 2)
+#define TX_ANA_CTRL_REG_2		(0x5021 << 2)
+#define TXDA_COEFF_CALC_CTRL		(0x5022 << 2)
+#define TX_DIG_CTRL_REG_2		(0x5024 << 2)
+#define TXDA_CYA_AUXDA_CYA		(0x5025 << 2)
+#define TX_ANA_CTRL_REG_3		(0x5026 << 2)
+#define TX_ANA_CTRL_REG_4		(0x5027 << 2)
+#define TX_ANA_CTRL_REG_5		(0x5029 << 2)
+
+#define RX_PSC_A0(n)			((0x8000 | ((n) << 9)) << 2)
+#define RX_PSC_A1(n)			((0x8001 | ((n) << 9)) << 2)
+#define RX_PSC_A2(n)			((0x8002 | ((n) << 9)) << 2)
+#define RX_PSC_A3(n)			((0x8003 | ((n) << 9)) << 2)
+#define RX_PSC_CAL(n)			((0x8006 | ((n) << 9)) << 2)
+#define RX_PSC_RDY(n)			((0x8007 | ((n) << 9)) << 2)
+#define RX_IQPI_ILL_CAL_OVRD		(0x8023 << 2)
+#define RX_EPI_ILL_CAL_OVRD		(0x8033 << 2)
+#define RX_SDCAL0_OVRD			(0x8041 << 2)
+#define RX_SDCAL1_OVRD			(0x8049 << 2)
+#define RX_SLC_INIT			(0x806d << 2)
+#define RX_SLC_RUN			(0x806e << 2)
+#define RX_CDRLF_CNFG2			(0x8081 << 2)
+#define RX_SIGDET_HL_FILT_TMR(n)	((0x8090 | ((n) << 9)) << 2)
+#define RX_SLC_IOP0_OVRD		(0x8101 << 2)
+#define RX_SLC_IOP1_OVRD		(0x8105 << 2)
+#define RX_SLC_QOP0_OVRD		(0x8109 << 2)
+#define RX_SLC_QOP1_OVRD		(0x810d << 2)
+#define RX_SLC_EOP0_OVRD		(0x8111 << 2)
+#define RX_SLC_EOP1_OVRD		(0x8115 << 2)
+#define RX_SLC_ION0_OVRD		(0x8119 << 2)
+#define RX_SLC_ION1_OVRD		(0x811d << 2)
+#define RX_SLC_QON0_OVRD		(0x8121 << 2)
+#define RX_SLC_QON1_OVRD		(0x8125 << 2)
+#define RX_SLC_EON0_OVRD		(0x8129 << 2)
+#define RX_SLC_EON1_OVRD		(0x812d << 2)
+#define RX_SLC_IEP0_OVRD		(0x8131 << 2)
+#define RX_SLC_IEP1_OVRD		(0x8135 << 2)
+#define RX_SLC_QEP0_OVRD		(0x8139 << 2)
+#define RX_SLC_QEP1_OVRD		(0x813d << 2)
+#define RX_SLC_EEP0_OVRD		(0x8141 << 2)
+#define RX_SLC_EEP1_OVRD		(0x8145 << 2)
+#define RX_SLC_IEN0_OVRD		(0x8149 << 2)
+#define RX_SLC_IEN1_OVRD		(0x814d << 2)
+#define RX_SLC_QEN0_OVRD		(0x8151 << 2)
+#define RX_SLC_QEN1_OVRD		(0x8155 << 2)
+#define RX_SLC_EEN0_OVRD		(0x8159 << 2)
+#define RX_SLC_EEN1_OVRD		(0x815d << 2)
+#define RX_DIAG_SIGDET_TUNE(n)		((0x81dc | ((n) << 9)) << 2)
+#define RX_DIAG_SC2C_DELAY		(0x81e1 << 2)
+
+#define PMA_LANE_CFG			(0xc000 << 2)
+#define PIPE_CMN_CTRL1			(0xc001 << 2)
+#define PIPE_CMN_CTRL2			(0xc002 << 2)
+#define PIPE_COM_LOCK_CFG1		(0xc003 << 2)
+#define PIPE_COM_LOCK_CFG2		(0xc004 << 2)
+#define PIPE_RCV_DET_INH		(0xc005 << 2)
+#define DP_MODE_CTL			(0xc008 << 2)
+#define DP_CLK_CTL			(0xc009 << 2)
+#define STS				(0xc00F << 2)
+#define PHY_ISO_CMN_CTRL		(0xc010 << 2)
+#define PHY_DP_TX_CTL			(0xc408 << 2)
+#define PMA_CMN_CTRL1			(0xc800 << 2)
+#define PHY_PMA_ISO_CMN_CTRL		(0xc810 << 2)
+#define PHY_ISOLATION_CTRL		(0xc81f << 2)
+#define PHY_PMA_ISO_XCVR_CTRL(n)	((0xcc11 | ((n) << 6)) << 2)
+#define PHY_PMA_ISO_LINK_MODE(n)	((0xcc12 | ((n) << 6)) << 2)
+#define PHY_PMA_ISO_PWRST_CTRL(n)	((0xcc13 | ((n) << 6)) << 2)
+#define PHY_PMA_ISO_TX_DATA_LO(n)	((0xcc14 | ((n) << 6)) << 2)
+#define PHY_PMA_ISO_TX_DATA_HI(n)	((0xcc15 | ((n) << 6)) << 2)
+#define PHY_PMA_ISO_RX_DATA_LO(n)	((0xcc16 | ((n) << 6)) << 2)
+#define PHY_PMA_ISO_RX_DATA_HI(n)	((0xcc17 | ((n) << 6)) << 2)
+#define TX_BIST_CTRL(n)			((0x4140 | ((n) << 9)) << 2)
+#define TX_BIST_UDDWR(n)		((0x4141 | ((n) << 9)) << 2)
+
+#define CLK0_PLL_MASK			0x3
+#define PLL0_DIV1			0
+#define CLK1_PLL_MASK			0x30
+#define PLL1_DIV2			0x30
+
+#define CMN_READY			BIT(0)
+
+#define DP_PLL_CLOCK_ENABLE		BIT(2)
+#define DP_PLL_ENABLE			BIT(0)
+#define DP_PLL_DATA_RATE_RBR		((2 << 12) | (4 << 8))
+#define DP_PLL_DATA_RATE_HBR		((2 << 12) | (4 << 8))
+#define DP_PLL_DATA_RATE_HBR2		((1 << 12) | (2 << 8))
+
+#define GRF_SOC_CON26			0x6268
+#define UPHY_DP_SEL			BIT(3)
+#define UPHY_DP_SEL_MASK		BIT(19)
+#define DPTX_HPD_SEL			(3 << 12)
+#define DPTX_HPD_DEL			(2 << 12)
+#define DPTX_HPD_SEL_MASK		(3 << 28)
+
+#define PHY_MODE_SET_TIMEOUT		1000000
+
+#define PIN_ASSIGN_A			BIT(0)
+#define PIN_ASSIGN_B			BIT(1)
+#define PIN_ASSIGN_C			BIT(2)
+#define PIN_ASSIGN_D			BIT(3)
+#define PIN_ASSIGN_E			BIT(4)
+#define PIN_ASSIGN_F			BIT(5)
+
+#define MODE_DISCONNECT			0
+#define MODE_UFP_USB			BIT(0)
+#define MODE_DFP_USB			BIT(1)
+#define MODE_DFP_DP			BIT(2)
+
+struct usb3phy_reg {
+	u32 offset;
+	u32 enable_bit;
+	u32 write_enable;
+};
+
+struct rockchip_usb3phy_port_cfg {
+	struct usb3phy_reg typec_conn_dir;
+	struct usb3phy_reg usb3tousb2_en;
+	struct usb3phy_reg external_psm;
+	struct usb3phy_reg pipe_status;
+	struct usb3phy_reg uphy_dp_sel;
+};
+
+struct rockchip_typec_phy {
+	struct device *dev;
+	void __iomem *base;
+	struct extcon_dev *extcon;
+	struct phy *phy;
+	struct regmap *grf_regs;
+	struct clk *clk_core;
+	struct clk *clk_ref;
+	struct reset_control *uphy_rst;
+	struct reset_control *pipe_rst;
+	struct reset_control *tcphy_rst;
+	struct rockchip_usb3phy_port_cfg port_cfgs;
+
+	bool flip;
+	u8 mode;
+	u8 pin_assign;
+};
+
+struct phy_reg {
+	u16 value;
+	u32 addr;
+};
+
+struct phy_reg usb_pll_cfg[] = {
+	{ 0xf0,		CMN_PLL0_VCOCAL_INIT },
+	{ 0x18,		CMN_PLL0_VCOCAL_ITER },
+	{ 0xd0,		CMN_PLL0_INTDIV },
+	{ 0x4a4a,	CMN_PLL0_FRACDIV },
+	{ 0x34,		CMN_PLL0_HIGH_THR },
+	{ 0x1ee,	CMN_PLL0_SS_CTRL1 },
+	{ 0x7f03,	CMN_PLL0_SS_CTRL2 },
+	{ 0x20,		CMN_PLL0_DSM_DIAG },
+	{ 0,		CMN_DIAG_PLL0_OVRD },
+	{ 0,		CMN_DIAG_PLL0_FBH_OVRD },
+	{ 0,		CMN_DIAG_PLL0_FBL_OVRD },
+	{ 0x7,		CMN_DIAG_PLL0_V2I_TUNE },
+	{ 0x45,		CMN_DIAG_PLL0_CP_TUNE },
+	{ 0x8,		CMN_DIAG_PLL0_LF_PROG },
+};
+
+struct phy_reg dp_pll_cfg[] = {
+	{ 0xf0,		CMN_PLL1_VCOCAL_INIT },
+	{ 0x18,		CMN_PLL1_VCOCAL_ITER },
+	{ 0x30b9,	CMN_PLL1_VCOCAL_START },
+	{ 0x21c,	CMN_PLL1_INTDIV },
+	{ 0,		CMN_PLL1_FRACDIV },
+	{ 0x5,		CMN_PLL1_HIGH_THR },
+	{ 0x35,		CMN_PLL1_SS_CTRL1 },
+	{ 0x7f1e,	CMN_PLL1_SS_CTRL2 },
+	{ 0x20,		CMN_PLL1_DSM_DIAG },
+	{ 0,		CMN_PLLSM1_USER_DEF_CTRL },
+	{ 0,		CMN_DIAG_PLL1_OVRD },
+	{ 0,		CMN_DIAG_PLL1_FBH_OVRD },
+	{ 0,		CMN_DIAG_PLL1_FBL_OVRD },
+	{ 0x6,		CMN_DIAG_PLL1_V2I_TUNE },
+	{ 0x45,		CMN_DIAG_PLL1_CP_TUNE },
+	{ 0x8,		CMN_DIAG_PLL1_LF_PROG },
+	{ 0x100,	CMN_DIAG_PLL1_PTATIS_TUNE1 },
+	{ 0x7,		CMN_DIAG_PLL1_PTATIS_TUNE2 },
+	{ 0x4,		CMN_DIAG_PLL1_INCLK_CTRL },
+};
+
+static void tcphy_cfg_24m(struct rockchip_typec_phy *tcphy,
+			  u32 num_lanes)
+{
+	u32 i;
+
+	/*
+	 * cmn_ref_clk_sel = 3, select the 24Mhz for clk parent
+	 * cmn_psm_clk_dig_div = 2, set the clk division to 2
+	 */
+	writel(0x830, tcphy->base + PMA_CMN_CTRL1);
+	for (i = 0; i < num_lanes; i++) {
+		/*
+		 * The following PHY configuration assumes a 24 MHz reference
+		 * clock.
+		 */
+		writel(0x90, tcphy->base + XCVR_DIAG_LANE_FCM_EN_MGN(i));
+		writel(0x960, tcphy->base + TX_RCVDET_EN_TMR(i));
+		writel(0x30, tcphy->base + TX_RCVDET_ST_TMR(i));
+	}
+}
+
+static void tcphy_cfg_usb_pll(struct rockchip_typec_phy *tcphy)
+{
+	u16 rdata;
+	u32 i;
+
+	/*
+	 * Selects which PLL clock will be driven on the analog high speed
+	 * clock 0: PLL 0 div 1.
+	 */
+	rdata = readl(tcphy->base + CMN_DIAG_HSCLK_SEL);
+	rdata &= ~CLK0_PLL_MASK;
+	rdata |= PLL0_DIV1;
+	writel(rdata, tcphy->base + CMN_DIAG_HSCLK_SEL);
+
+	/* load the configuration of PLL0 */
+	for (i = 0; i < ARRAY_SIZE(usb_pll_cfg); i++)
+		writel(usb_pll_cfg[i].value, tcphy->base + usb_pll_cfg[i].addr);
+}
+
+static void tcphy_cfg_dp_pll(struct rockchip_typec_phy *tcphy)
+{
+	u16 rdata;
+	u32 i;
+
+	/* set the default mode to RBR */
+	writel(DP_PLL_CLOCK_ENABLE | DP_PLL_ENABLE | DP_PLL_DATA_RATE_RBR,
+	       tcphy->base + DP_CLK_CTL);
+
+	/*
+	 * Selects which PLL clock will be driven on the analog high speed
+	 * clock 1: PLL 1 div 2.
+	 */
+	rdata = readl(tcphy->base + CMN_DIAG_HSCLK_SEL);
+	rdata &= ~CLK1_PLL_MASK;
+	rdata |= PLL1_DIV2;
+	writel(rdata, tcphy->base + CMN_DIAG_HSCLK_SEL);
+
+	/* load the configuration of PLL1 */
+	for (i = 0; i < ARRAY_SIZE(dp_pll_cfg); i++)
+		writel(dp_pll_cfg[i].value, tcphy->base + dp_pll_cfg[i].addr);
+}
+
+static void tcphy_tx_usb_cfg_lane(struct rockchip_typec_phy *tcphy,
+				  u32 lane)
+{
+	writel(0x7799, tcphy->base + TX_PSC_A0(lane));
+	writel(0x7798, tcphy->base + TX_PSC_A1(lane));
+	writel(0x5098, tcphy->base + TX_PSC_A2(lane));
+	writel(0x5098, tcphy->base + TX_PSC_A3(lane));
+	writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_000(lane));
+	writel(0xbf, tcphy->base + XCVR_DIAG_BIDI_CTRL(lane));
+}
+
+static void tcphy_rx_usb_cfg_lane(struct rockchip_typec_phy *tcphy,
+				  u32 lane)
+{
+	writel(0xa6fd, tcphy->base + RX_PSC_A0(lane));
+	writel(0xa6fd, tcphy->base + RX_PSC_A1(lane));
+	writel(0xa410, tcphy->base + RX_PSC_A2(lane));
+	writel(0x2410, tcphy->base + RX_PSC_A3(lane));
+	writel(0x23ff, tcphy->base + RX_PSC_CAL(lane));
+	writel(0x13, tcphy->base + RX_SIGDET_HL_FILT_TMR(lane));
+	writel(0x1004, tcphy->base + RX_DIAG_SIGDET_TUNE(lane));
+	writel(0x2010, tcphy->base + RX_PSC_RDY(lane));
+	writel(0xfb, tcphy->base + XCVR_DIAG_BIDI_CTRL(lane));
+}
+
+static void tcphy_dp_cfg_lane(struct rockchip_typec_phy *tcphy,
+			      u32 lane)
+{
+	u16 rdata;
+
+	writel(0xbefc, tcphy->base + XCVR_PSM_RCTRL(lane));
+	writel(0x6799, tcphy->base + TX_PSC_A0(lane));
+	writel(0x6798, tcphy->base + TX_PSC_A1(lane));
+	writel(0x98, tcphy->base + TX_PSC_A2(lane));
+	writel(0x98, tcphy->base + TX_PSC_A3(lane));
+
+	writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_000(lane));
+	writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_001(lane));
+	writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_010(lane));
+	writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_011(lane));
+	writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_100(lane));
+	writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_101(lane));
+	writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_110(lane));
+	writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_111(lane));
+	writel(0, tcphy->base + TX_TXCC_CPOST_MULT_10(lane));
+	writel(0, tcphy->base + TX_TXCC_CPOST_MULT_01(lane));
+	writel(0, tcphy->base + TX_TXCC_CPOST_MULT_00(lane));
+	writel(0, tcphy->base + TX_TXCC_CPOST_MULT_11(lane));
+
+	writel(0x128, tcphy->base + TX_TXCC_CAL_SCLR_MULT(lane));
+	writel(0x700, tcphy->base + TX_DIAG_TX_DRV(lane));
+
+	rdata = readl(tcphy->base + XCVR_DIAG_PLLDRC_CTRL(lane));
+	rdata = (rdata & 0x8fff) | 0x6000;
+	writel(rdata, tcphy->base + XCVR_DIAG_PLLDRC_CTRL(lane));
+}
+
+static void tcphy_cfg_pin_assign(struct rockchip_typec_phy *tcphy)
+{
+	switch (tcphy->pin_assign) {
+	case PIN_ASSIGN_A:
+		writel(0x19d5, tcphy->base + PMA_LANE_CFG);
+		break;
+	case PIN_ASSIGN_B:
+		writel(0x1500, tcphy->base + PMA_LANE_CFG);
+		break;
+	case PIN_ASSIGN_C:
+	case PIN_ASSIGN_E:
+		writel(0x51d9, tcphy->base + PMA_LANE_CFG);
+		break;
+	case PIN_ASSIGN_D:
+	case PIN_ASSIGN_F:
+		writel(0x5100, tcphy->base + PMA_LANE_CFG);
+		break;
+	};
+}
+
+static inline int property_enable(struct rockchip_typec_phy *tcphy,
+				  const struct usb3phy_reg *reg, bool en)
+{
+	u32 mask = 1 << reg->write_enable;
+	u32 val = en << reg->enable_bit;
+
+	return regmap_write(tcphy->grf_regs, reg->offset, val | mask);
+}
+
+static void tcphy_lanes_config(struct rockchip_typec_phy *tcphy)
+{
+	struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs;
+	u32 i;
+
+	property_enable(tcphy, &cfg->typec_conn_dir, tcphy->flip);
+
+	tcphy_cfg_24m(tcphy, 0x4);
+
+	switch (tcphy->mode) {
+	case MODE_UFP_USB:
+	case MODE_DFP_USB:
+		tcphy_cfg_usb_pll(tcphy);
+		tcphy_cfg_dp_pll(tcphy);
+		if (tcphy->flip) {
+			tcphy_tx_usb_cfg_lane(tcphy, 3);
+			tcphy_rx_usb_cfg_lane(tcphy, 2);
+		} else {
+			tcphy_tx_usb_cfg_lane(tcphy, 0);
+			tcphy_rx_usb_cfg_lane(tcphy, 1);
+		}
+		break;
+	case MODE_DFP_DP:
+		tcphy_cfg_dp_pll(tcphy);
+		for (i = 0; i < 4; i++)
+			tcphy_dp_cfg_lane(tcphy, i);
+		break;
+	case MODE_DFP_USB | MODE_DFP_DP:
+		tcphy_cfg_usb_pll(tcphy);
+		tcphy_cfg_dp_pll(tcphy);
+		if (tcphy->flip) {
+			tcphy_tx_usb_cfg_lane(tcphy, 3);
+			tcphy_rx_usb_cfg_lane(tcphy, 2);
+			tcphy_dp_cfg_lane(tcphy, 0);
+			tcphy_dp_cfg_lane(tcphy, 1);
+		} else {
+			tcphy_tx_usb_cfg_lane(tcphy, 0);
+			tcphy_rx_usb_cfg_lane(tcphy, 1);
+			tcphy_dp_cfg_lane(tcphy, 2);
+			tcphy_dp_cfg_lane(tcphy, 3);
+		}
+		break;
+	}
+}
+
+static void tcphy_dp_aux_calibration(struct rockchip_typec_phy *tcphy)
+{
+	u16 rdata, rdata2, val;
+
+	/* disable txda_cal_latch_en for rewrite the calibration values */
+	rdata = readl(tcphy->base + TX_ANA_CTRL_REG_1);
+	val = rdata & 0xdfff;
+	writel(val, tcphy->base + TX_ANA_CTRL_REG_1);
+
+	/*
+	 * read a resistor calibration code from CMN_TXPUCAL_CTRL[6:0] and
+	 * write it to TX_DIG_CTRL_REG_2[6:0], and delay 1ms to make sure it
+	 * works.
+	 */
+	rdata = readl(tcphy->base + TX_DIG_CTRL_REG_2);
+	rdata = rdata & 0xffc0;
+
+	rdata2 = readl(tcphy->base + CMN_TXPUCAL_CTRL);
+	rdata2 = rdata2 & 0x3f;
+
+	val = rdata | rdata2;
+	writel(val, tcphy->base + TX_DIG_CTRL_REG_2);
+	usleep_range(1000, 1050);
+
+	/*
+	 * Enable signal for latch that sample and holds calibration values.
+	 * Activate this signal for 1 clock cycle to sample new calibration
+	 * values.
+	 */
+	rdata = readl(tcphy->base + TX_ANA_CTRL_REG_1);
+	val = rdata | 0x2000;
+	writel(val, tcphy->base + TX_ANA_CTRL_REG_1);
+	usleep_range(150, 200);
+
+	/* set TX Voltage Level and TX Deemphasis to 0 */
+	writel(0, tcphy->base + PHY_DP_TX_CTL);
+	/* re-enable decap */
+	writel(0x100, tcphy->base + TX_ANA_CTRL_REG_2);
+	writel(0x300, tcphy->base + TX_ANA_CTRL_REG_2);
+	writel(0x2008, tcphy->base + TX_ANA_CTRL_REG_1);
+	writel(0x2018, tcphy->base + TX_ANA_CTRL_REG_1);
+
+	writel(0, tcphy->base + TX_ANA_CTRL_REG_5);
+
+	/*
+	 * Programs txda_drv_ldo_prog[15:0], Sets driver LDO
+	 * voltage 16'h1001 for DP-AUX-TX and RX
+	 */
+	writel(0x1001, tcphy->base + TX_ANA_CTRL_REG_4);
+
+	/* re-enables Bandgap reference for LDO */
+	writel(0x2098, tcphy->base + TX_ANA_CTRL_REG_1);
+	writel(0x2198, tcphy->base + TX_ANA_CTRL_REG_1);
+
+	/*
+	 * re-enables the transmitter pre-driver, driver data selection MUX,
+	 * and receiver detect circuits.
+	 */
+	writel(0x301, tcphy->base + TX_ANA_CTRL_REG_2);
+	writel(0x303, tcphy->base + TX_ANA_CTRL_REG_2);
+
+	/*
+	 * BIT 12: Controls auxda_polarity, which selects the polarity of the
+	 * xcvr:
+	 * 1, Reverses the polarity (If TYPEC, Pulls ups aux_p and pull
+	 * down aux_m)
+	 * 0, Normal polarity (if TYPE_C, pulls up aux_m and pulls down
+	 * aux_p)
+	 */
+	val = 0xa078;
+	if (!tcphy->flip)
+		val |= BIT(12);
+	writel(val, tcphy->base + TX_ANA_CTRL_REG_1);
+
+	writel(0, tcphy->base + TX_ANA_CTRL_REG_3);
+	writel(0, tcphy->base + TX_ANA_CTRL_REG_4);
+	writel(0, tcphy->base + TX_ANA_CTRL_REG_5);
+
+	/*
+	 * Controls low_power_swing_en, set the voltage swing of the driver
+	 * to 400mv. The values	below are peak to peak (differential) values.
+	 */
+	writel(4, tcphy->base + TXDA_COEFF_CALC_CTRL);
+	writel(0, tcphy->base + TXDA_CYA_AUXDA_CYA);
+
+	/* Controls tx_high_z_tm_en */
+	val = readl(tcphy->base + TX_DIG_CTRL_REG_2);
+	val |= BIT(15);
+	writel(val, tcphy->base + TX_DIG_CTRL_REG_2);
+}
+
+static void tcphy_usb3_init(struct rockchip_typec_phy *tcphy)
+{
+	struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs;
+	const struct usb3phy_reg *reg = &cfg->pipe_status;
+	int timeout;
+	unsigned int val;
+
+	/* wait TCPHY for pipe ready */
+	for (timeout = 0; timeout < 100; timeout++) {
+		regmap_read(tcphy->grf_regs, reg->offset, &val);
+		if (!(val & BIT(reg->enable_bit)))
+			return;
+		usleep_range(10, 20);
+	}
+
+	dev_err(tcphy->dev, "wait pipe ready timeout!\n");
+}
+
+static int tcphy_dp_init(struct rockchip_typec_phy *tcphy)
+{
+	struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs;
+	u16 val;
+	int ret;
+
+	property_enable(tcphy, &cfg->uphy_dp_sel, 1);
+
+	ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL,
+				 val, val & BIT(6), 1000, PHY_MODE_SET_TIMEOUT);
+	if (ret < 0) {
+		dev_err(tcphy->dev, "failed to wait TCPHY for DP ready\n");
+		return ret;
+	}
+
+	tcphy_dp_aux_calibration(tcphy);
+
+	if (tcphy->mode & MODE_DFP_USB)
+		writel(0xc101, tcphy->base + DP_MODE_CTL);
+	else
+		writel(0x0101, tcphy->base + DP_MODE_CTL);
+
+	ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL,
+				 val, val & BIT(4), 1000, PHY_MODE_SET_TIMEOUT);
+	if (ret < 0) {
+		dev_err(tcphy->dev, "failed to wait TCPHY enter A0\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int tcphy_phy_init(struct rockchip_typec_phy *tcphy)
+{
+	struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs;
+	u32 val;
+	int ret;
+
+	ret = clk_prepare_enable(tcphy->clk_core);
+	if (ret) {
+		dev_err(tcphy->dev, "Failed to prepare_enable core clock\n");
+		return ret;
+	}
+
+	ret = clk_set_rate(tcphy->clk_core, 50000000);
+	if (ret) {
+		dev_err(tcphy->dev, "set type-c phy core clk rate failed\n");
+		goto err_clk_core;
+	}
+
+	ret = clk_prepare_enable(tcphy->clk_ref);
+	if (ret) {
+		dev_err(tcphy->dev, "Failed to prepare_enable ref clock\n");
+		goto err_clk_core;
+	}
+
+	reset_control_assert(tcphy->tcphy_rst);
+	reset_control_assert(tcphy->uphy_rst);
+	reset_control_assert(tcphy->pipe_rst);
+
+	/* select external psm clock */
+	property_enable(tcphy, &cfg->external_psm, 1);
+	property_enable(tcphy, &cfg->usb3tousb2_en, 0);
+
+	reset_control_deassert(tcphy->tcphy_rst);
+
+	tcphy_lanes_config(tcphy);
+
+	tcphy_cfg_pin_assign(tcphy);
+
+	if (tcphy->mode & MODE_DFP_DP) {
+		if (tcphy->mode & MODE_DFP_USB)
+			writel(0xc104, tcphy->base + DP_MODE_CTL);
+		else
+			writel(0x0104, tcphy->base + DP_MODE_CTL);
+	}
+
+	reset_control_deassert(tcphy->uphy_rst);
+
+	ret = readx_poll_timeout(readl, tcphy->base + PMA_CMN_CTRL1,
+				 val, val & CMN_READY, 10,
+				 PHY_MODE_SET_TIMEOUT);
+	if (ret < 0) {
+		dev_err(tcphy->dev, "wait pma ready timeout\n");
+		goto timeout_pma_ready;
+	}
+
+	reset_control_deassert(tcphy->pipe_rst);
+
+	return 0;
+
+timeout_pma_ready:
+	clk_disable_unprepare(tcphy->clk_ref);
+err_clk_core:
+	clk_disable_unprepare(tcphy->clk_core);
+	return ret;
+}
+
+static void tcphy_phy_deinit(struct rockchip_typec_phy *tcphy)
+{
+	clk_disable_unprepare(tcphy->clk_core);
+	clk_disable_unprepare(tcphy->clk_ref);
+	reset_control_assert(tcphy->tcphy_rst);
+	reset_control_assert(tcphy->uphy_rst);
+	reset_control_assert(tcphy->pipe_rst);
+}
+
+static int rockchip_typec_phy_power_on(struct phy *_phy)
+{
+	struct rockchip_typec_phy *tcphy = phy_get_drvdata(_phy);
+	struct extcon_dev *edev = tcphy->extcon;
+	bool plugged, dfp, ufp, dp;
+	union extcon_property_value property;
+	int ret, id;
+	u8 mode;
+
+	ufp = extcon_get_cable_state_(edev, EXTCON_USB);
+	dfp = extcon_get_cable_state_(edev, EXTCON_USB_HOST);
+	dp = extcon_get_cable_state_(edev, EXTCON_DISP_DP);
+
+	plugged = ufp | dfp | dp;
+
+	if (!plugged) {
+		dev_err(tcphy->dev, "Nothing plugged in\n");
+		return -EINVAL;
+	}
+
+	if (ufp) {
+		mode = MODE_UFP_USB;
+		id = EXTCON_USB;
+	} else if (dfp && !dp) {
+		mode = MODE_DFP_USB;
+		id = EXTCON_USB_HOST;
+	} else if (dfp && dp) {
+		mode = MODE_DFP_USB | MODE_DFP_DP;
+		tcphy->pin_assign = PIN_ASSIGN_D;
+		id = EXTCON_DISP_DP;
+	} else {
+		mode = MODE_DFP_DP;
+		tcphy->pin_assign = PIN_ASSIGN_C;
+		id = EXTCON_DISP_DP;
+	}
+
+	ret = extcon_get_cable_property(edev, id, EXTCON_PROP_TYPEC_POLARITY,
+					&property);
+	if (ret) {
+		dev_err(tcphy->dev, "get property failed\n");
+		return ret;
+	}
+
+	tcphy->flip = property.intval ? 1 : 0;
+
+	if (tcphy->mode == mode)
+		return 0;
+
+	tcphy->mode = mode;
+
+	regmap_write(tcphy->grf_regs, GRF_SOC_CON26,
+		     DPTX_HPD_SEL_MASK | DPTX_HPD_DEL);
+
+	ret = tcphy_phy_init(tcphy);
+	if (ret)
+		return ret;
+
+	if (mode & (MODE_UFP_USB | MODE_DFP_USB))
+		tcphy_usb3_init(tcphy);
+
+	if (mode & MODE_DFP_DP) {
+		ret = tcphy_dp_init(tcphy);
+		if (ret)
+			return ret;
+
+		regmap_write(tcphy->grf_regs, GRF_SOC_CON26,
+			     DPTX_HPD_SEL_MASK | DPTX_HPD_SEL);
+	}
+
+	return 0;
+}
+
+static int rockchip_typec_phy_power_off(struct phy *_phy)
+{
+	struct rockchip_typec_phy *tcphy = phy_get_drvdata(_phy);
+
+	tcphy_phy_deinit(tcphy);
+	tcphy->mode = MODE_DISCONNECT;
+
+	return 0;
+}
+
+static const struct phy_ops rockchip_tcphy_ops = {
+	.power_on	= rockchip_typec_phy_power_on,
+	.power_off	= rockchip_typec_phy_power_off,
+	.owner		= THIS_MODULE,
+};
+
+static int tcphy_get_param(struct device *dev,
+			   struct usb3phy_reg *reg,
+			   const char *name)
+{
+	u32 buffer[3];
+	int ret;
+
+	ret = of_property_read_u32_array(dev->of_node, name, buffer, 3);
+	if (ret) {
+		dev_err(dev, "Can not parse %s\n", name);
+		return ret;
+	}
+
+	reg->offset = buffer[0];
+	reg->enable_bit = buffer[1];
+	reg->write_enable = buffer[2];
+	return 0;
+}
+
+static int tcphy_parse_dt(struct rockchip_typec_phy *tcphy,
+			  struct device *dev)
+{
+	struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs;
+	int ret;
+
+	ret = tcphy_get_param(dev, &cfg->typec_conn_dir,
+			      "rockchip,typec-conn-dir");
+	if (ret) {
+		dev_err(dev, "could not find rockchip,typec-conn-dir node\n");
+		return ret;
+	}
+
+	ret = tcphy_get_param(dev, &cfg->usb3tousb2_en,
+			      "rockchip,usb3tousb2-en");
+	if (ret) {
+		dev_err(dev, "could not find rockchip,usb3tousb2-en node\n");
+		return ret;
+	}
+
+	ret = tcphy_get_param(dev, &cfg->external_psm,
+			      "rockchip,external-psm");
+	if (ret) {
+		dev_err(dev, "could not find rockchip,external-psm node\n");
+		return ret;
+	}
+
+	ret = tcphy_get_param(dev, &cfg->pipe_status,
+			      "rockchip,pipe-status");
+	if (ret) {
+		dev_err(dev, "could not find rockchip,pipe-status node\n");
+		return ret;
+	}
+
+	ret = tcphy_get_param(dev, &cfg->uphy_dp_sel,
+			      "rockchip,uphy-dp-sel");
+	if (ret) {
+		dev_err(dev, "could not find rockchip,uphy-dp-sel node\n");
+		return ret;
+	}
+
+	tcphy->grf_regs = syscon_regmap_lookup_by_phandle(dev->of_node,
+							  "rockchip,grf");
+	if (IS_ERR(tcphy->grf_regs)) {
+		dev_err(dev, "could not find grf dt node\n");
+		return PTR_ERR(tcphy->grf_regs);
+	}
+
+	tcphy->clk_core = devm_clk_get(dev, "tcpdcore");
+	if (IS_ERR(tcphy->clk_core)) {
+		dev_err(dev, "could not get uphy core clock\n");
+		return PTR_ERR(tcphy->clk_core);
+	}
+
+	tcphy->clk_ref = devm_clk_get(dev, "tcpdphy-ref");
+	if (IS_ERR(tcphy->clk_ref)) {
+		dev_err(dev, "could not get uphy ref clock\n");
+		return PTR_ERR(tcphy->clk_ref);
+	}
+
+	tcphy->uphy_rst = devm_reset_control_get(dev, "uphy");
+	if (IS_ERR(tcphy->uphy_rst)) {
+		dev_err(dev, "no uphy_rst reset control found\n");
+		return PTR_ERR(tcphy->uphy_rst);
+	}
+
+	tcphy->pipe_rst = devm_reset_control_get(dev, "uphy-pipe");
+	if (IS_ERR(tcphy->pipe_rst)) {
+		dev_err(dev, "no pipe_rst reset control found\n");
+		return PTR_ERR(tcphy->pipe_rst);
+	}
+
+	tcphy->tcphy_rst = devm_reset_control_get(dev, "uphy-tcphy");
+	if (IS_ERR(tcphy->tcphy_rst)) {
+		dev_err(dev, "no tcphy_rst reset control found\n");
+		return PTR_ERR(tcphy->tcphy_rst);
+	}
+
+	return 0;
+}
+
+static int rockchip_typec_phy_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rockchip_typec_phy *tcphy;
+	struct phy_provider *phy_provider;
+	struct resource *res;
+	int ret;
+
+	tcphy = devm_kzalloc(dev, sizeof(*tcphy), GFP_KERNEL);
+	if (!tcphy)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	tcphy->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(tcphy->base)) {
+		dev_err(dev, "failed to remap phy regs\n");
+		return PTR_ERR(tcphy->base);
+	}
+
+	ret = tcphy_parse_dt(tcphy, dev);
+	if (ret)
+		return ret;
+
+	tcphy->dev = dev;
+	platform_set_drvdata(pdev, tcphy);
+
+	tcphy->mode = MODE_DISCONNECT;
+
+	tcphy->extcon = extcon_get_edev_by_phandle(dev, 0);
+	if (IS_ERR(tcphy->extcon)) {
+		if (PTR_ERR(tcphy->extcon) != -EPROBE_DEFER)
+			dev_err(dev, "Invalid or missing extcon\n");
+		return PTR_ERR(tcphy->extcon);
+	}
+
+	tcphy->phy = devm_phy_create(dev, NULL, &rockchip_tcphy_ops);
+	if (IS_ERR(tcphy->phy)) {
+		dev_err(dev, "failed to create Tepy-C phy\n");
+		return PTR_ERR(tcphy->phy);
+	}
+
+	phy_set_drvdata(tcphy->phy, tcphy);
+
+	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+	if (IS_ERR(phy_provider)) {
+		dev_err(dev, "Failed to register phy provider\n");
+		return PTR_ERR(phy_provider);
+	}
+
+	return 0;
+}
+
+static const struct of_device_id rockchip_typec_phy_dt_ids[] = {
+	{ .compatible = "rockchip,rk3399-typec-phy" },
+	{}
+};
+
+MODULE_DEVICE_TABLE(of, rockchip_typec_phy_dt_ids);
+
+static struct platform_driver rockchip_typec_phy_driver = {
+	.probe		= rockchip_typec_phy_probe,
+	.driver		= {
+		.name	= "rockchip-typec-phy",
+		.of_match_table = rockchip_typec_phy_dt_ids,
+	},
+};
+
+module_platform_driver(rockchip_typec_phy_driver);
+
+MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
+MODULE_AUTHOR("Kever Yang <kever.yang@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip USB TYPE-C PHY driver");
+MODULE_LICENSE("GPL v2");
-- 
2.6.3

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

* [v5 PATCH 4/5] Documentation: bindings: add dt documentation for cdn DP controller
  2016-07-12 15:09 [v5 PATCH 0/5] Rockchip Type-C and DisplayPort driver Chris Zhong
  2016-07-12 15:09 ` [v5 PATCH 2/5] Documentation: bindings: add dt doc for Rockchip USB Type-C PHY Chris Zhong
  2016-07-12 15:09 ` [v5 PATCH 3/5] phy: Add USB Type-C PHY driver for rk3399 Chris Zhong
@ 2016-07-12 15:09 ` Chris Zhong
  2016-07-12 15:09 ` [v5 PATCH 5/5] drm/rockchip: cdn-dp: add cdn DP support for rk3399 Chris Zhong
  3 siblings, 0 replies; 11+ messages in thread
From: Chris Zhong @ 2016-07-12 15:09 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds a binding that describes the cdn DP controller for
rk3399.

Signed-off-by: Chris Zhong <zyw@rock-chips.com>
Acked-by: Rob Herring <robh@kernel.org>

---

Changes in v5: None
Changes in v4:
- add a reset node
- support 2 phys

Changes in v3:
- add SoC specific compatible string
- remove reg = <1>;

Changes in v2: None
Changes in v1:
- add extcon node description
- add #sound-dai-cells description

 .../bindings/display/rockchip/cdn-dp-rockchip.txt  | 67 ++++++++++++++++++++++
 1 file changed, 67 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/rockchip/cdn-dp-rockchip.txt

diff --git a/Documentation/devicetree/bindings/display/rockchip/cdn-dp-rockchip.txt b/Documentation/devicetree/bindings/display/rockchip/cdn-dp-rockchip.txt
new file mode 100644
index 0000000..bf0b2ce
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/rockchip/cdn-dp-rockchip.txt
@@ -0,0 +1,67 @@
+Rockchip RK3399 specific extensions to the cdn Display Port
+================================
+
+Required properties:
+- compatible: must be "rockchip,rk3399-cdn-dp"
+
+- reg: physical base address of the controller and length
+
+- clocks: from common clock binding: handle to dp clock.
+
+- clock-names: from common clock binding:
+	       Required elements: "core-clk" "pclk" "spdif"
+
+- resets : a list of phandle + reset specifier pairs
+- reset-names : string reset name, must be:
+		"spdif"
+
+- rockchip,grf: this soc should set GRF regs, so need get grf here.
+
+- ports: contain a port nodes with endpoint definitions as defined in
+	 Documentation/devicetree/bindings/media/video-interfaces.txt.
+	 contained 2 endpoints, connecting to the output of vop.
+
+- phys: from general PHY binding: the phandle for the PHY device.
+
+- extcon: extcon specifier for the Power Delivery
+
+- #sound-dai-cells = it must be 1 if your system is using 2 DAIs: I2S, SPDIF
+
+-------------------------------------------------------------------------------
+
+Example:
+	cdn_dp: dp at fec00000 {
+		compatible = "rockchip,rk3399-cdn-dp";
+		reg = <0x0 0xfec00000 0x0 0x100000>;
+		interrupts = <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&cru SCLK_DP_CORE>, <&cru PCLK_DP_CTRL>,
+			 <&cru SCLK_SPDIF_REC_DPTX>;
+		clock-names = "core-clk", "pclk", "spdif";
+		phys = <&tcphy0>, <&tcphy1>;
+		resets = <&cru SRST_DPTX_SPDIF_REC>;
+		reset-names = "spdif";
+		extcon = <&fusb0>, <&fusb1>;
+		rockchip,grf = <&grf>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		#sound-dai-cells = <1>;
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			dp_in: port {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				dp_in_vopb: endpoint at 0 {
+					reg = <0>;
+					remote-endpoint = <&vopb_out_dp>;
+				};
+
+				dp_in_vopl: endpoint at 1 {
+					reg = <1>;
+					remote-endpoint = <&vopl_out_dp>;
+				};
+			};
+		};
+	};
-- 
2.6.3

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

* [v5 PATCH 5/5] drm/rockchip: cdn-dp: add cdn DP support for rk3399
  2016-07-12 15:09 [v5 PATCH 0/5] Rockchip Type-C and DisplayPort driver Chris Zhong
                   ` (2 preceding siblings ...)
  2016-07-12 15:09 ` [v5 PATCH 4/5] Documentation: bindings: add dt documentation for cdn DP controller Chris Zhong
@ 2016-07-12 15:09 ` Chris Zhong
  2016-07-13 13:59   ` Sean Paul
  3 siblings, 1 reply; 11+ messages in thread
From: Chris Zhong @ 2016-07-12 15:09 UTC (permalink / raw)
  To: linux-arm-kernel

Add support for cdn DP controller which is embedded in the rk3399
SoCs. The DP is compliant with DisplayPort Specification,
Version 1.3, This IP is compatible with the rockchip type-c PHY IP.
There is a uCPU in DP controller, it need a firmware to work,
please put the firmware file to /lib/firmware/cdn/dptx.bin. The
uCPU in charge of aux communication and link training, the host use
mailbox to communicate with the ucpu.
The dclk pin_pol of vop must not be invert for DP.

Signed-off-by: Chris Zhong <zyw@rock-chips.com>

---

Changes in v5:
- alphabetical order
- do not use long, use u32 or u64
- return MODE_CLOCK_HIGH when requested > actual
- Optimized Coding Style
- add a formula to get better tu size and symbol value.

Changes in v4:
- use phy framework to control DP phy
- support 2 phys

Changes in v3:
- use EXTCON_DISP_DP and EXTCON_DISP_DP_ALT cable to get dp port state.
- reset spdif before config it
- modify the firmware clk to 100Mhz
- retry load firmware if fw file is requested too early

Changes in v2:
- Alphabetic order
- remove excess error message
- use define clk_rate
- check all return value
- remove dev_set_name(dp->dev, "cdn-dp");
- use schedule_delayed_work
- remove never-called functions
- remove some unnecessary ()

Changes in v1:
- use extcon API
- use hdmi-codec for the DP Asoc
- do not initialize the "ret"
- printk a err log when drm_of_encoder_active_endpoint_id
- modify the dclk pin_pol to a single line

 drivers/gpu/drm/rockchip/Kconfig            |   9 +
 drivers/gpu/drm/rockchip/Makefile           |   1 +
 drivers/gpu/drm/rockchip/cdn-dp-core.c      | 761 ++++++++++++++++++++++++++++
 drivers/gpu/drm/rockchip/cdn-dp-core.h      | 113 +++++
 drivers/gpu/drm/rockchip/cdn-dp-reg.c       | 740 +++++++++++++++++++++++++++
 drivers/gpu/drm/rockchip/cdn-dp-reg.h       | 409 +++++++++++++++
 drivers/gpu/drm/rockchip/rockchip_drm_vop.c |   6 +-
 drivers/gpu/drm/rockchip/rockchip_drm_vop.h |   2 +
 drivers/gpu/drm/rockchip/rockchip_vop_reg.c |   2 +
 9 files changed, 2042 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.c
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.h
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.c
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.h

diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
index d30bdc3..20da9a8 100644
--- a/drivers/gpu/drm/rockchip/Kconfig
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -25,6 +25,15 @@ config ROCKCHIP_ANALOGIX_DP
 	  for the Analogix Core DP driver. If you want to enable DP
 	  on RK3288 based SoC, you should selet this option.
 
+config ROCKCHIP_CDN_DP
+        tristate "Rockchip cdn DP"
+        depends on DRM_ROCKCHIP
+        help
+	  This selects support for Rockchip SoC specific extensions
+	  for the cdn Dp driver. If you want to enable Dp on
+	  RK3399 based SoC, you should selet this
+	  option.
+
 config ROCKCHIP_DW_HDMI
         tristate "Rockchip specific extensions for Synopsys DW HDMI"
         depends on DRM_ROCKCHIP
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
index 05d0713..abdecd5 100644
--- a/drivers/gpu/drm/rockchip/Makefile
+++ b/drivers/gpu/drm/rockchip/Makefile
@@ -7,6 +7,7 @@ rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
 rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
 
 obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
+obj-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o
 obj-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
 obj-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi.o
 obj-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
new file mode 100644
index 0000000..5b8a15e
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
@@ -0,0 +1,761 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/extcon.h>
+#include <linux/firmware.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/mfd/syscon.h>
+#include <linux/phy/phy.h>
+
+#include <sound/hdmi-codec.h>
+
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include "cdn-dp-core.h"
+#include "cdn-dp-reg.h"
+#include "rockchip_drm_vop.h"
+
+#define connector_to_dp(c) \
+		container_of(c, struct cdn_dp_device, connector)
+
+#define encoder_to_dp(c) \
+		container_of(c, struct cdn_dp_device, encoder)
+
+/* dp grf register offset */
+#define DP_VOP_SEL		0x6224
+#define DP_SEL_VOP_LIT		BIT(12)
+#define DP_CLK_RATE		100000000
+#define WAIT_HPD_STABLE		300
+
+static int cdn_dp_clk_enable(struct cdn_dp_device *dp)
+{
+	int ret;
+
+	ret = clk_prepare_enable(dp->pclk);
+	if (ret < 0) {
+		dev_err(dp->dev, "cannot enable dp pclk %d\n", ret);
+		goto err_pclk;
+	}
+
+	ret = clk_prepare_enable(dp->core_clk);
+	if (ret < 0) {
+		dev_err(dp->dev, "cannot enable core_clk %d\n", ret);
+		goto err_core_clk;
+	}
+
+	ret = clk_set_rate(dp->core_clk, DP_CLK_RATE);
+	if (ret < 0) {
+		dev_err(dp->dev, "cannot set dp core clk to %d %d\n",
+			DP_CLK_RATE, ret);
+		goto err_set_rate;
+	}
+
+	/* notice fw the clk freq value */
+	cdn_dp_set_fw_clk(dp, DP_CLK_RATE);
+
+	return 0;
+
+err_set_rate:
+	clk_disable_unprepare(dp->core_clk);
+err_core_clk:
+	clk_disable_unprepare(dp->pclk);
+err_pclk:
+	return ret;
+}
+
+static enum drm_connector_status
+cdn_dp_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct cdn_dp_device *dp = connector_to_dp(connector);
+
+	return dp->hpd_status ? connector_status_connected :
+				connector_status_disconnected;
+}
+
+static void cdn_dp_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static struct drm_connector_funcs cdn_dp_atomic_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.detect = cdn_dp_connector_detect,
+	.destroy = cdn_dp_connector_destroy,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int cdn_dp_connector_get_modes(struct drm_connector *connector)
+{
+	struct cdn_dp_device *dp = connector_to_dp(connector);
+	struct edid *edid;
+
+	if (!dp->fw_loaded)
+		return 0;
+
+	edid = drm_do_get_edid(connector, cdn_dp_get_edid_block, dp);
+	if (edid) {
+		dev_dbg(dp->dev, "got edid: width[%d] x height[%d]\n",
+			edid->width_cm, edid->height_cm);
+
+		dp->sink_has_audio = drm_detect_monitor_audio(edid);
+		drm_mode_connector_update_edid_property(connector, edid);
+		drm_add_edid_modes(connector, edid);
+		/* Store the ELD */
+		drm_edid_to_eld(connector, edid);
+		kfree(edid);
+	} else {
+		dev_dbg(dp->dev, "failed to get edid\n");
+	}
+
+	return 0;
+}
+
+static struct drm_encoder *
+	cdn_dp_connector_best_encoder(struct drm_connector *connector)
+{
+	struct cdn_dp_device *dp = connector_to_dp(connector);
+
+	return &dp->encoder;
+}
+
+static int cdn_dp_connector_mode_valid(struct drm_connector *connector,
+				       struct drm_display_mode *mode)
+{
+	struct cdn_dp_device *dp = connector_to_dp(connector);
+	struct drm_display_info *display_info = &dp->connector.display_info;
+	u32 requested = mode->clock * display_info->bpc * 3 / 1000;
+	u32 actual, rate;
+
+	rate = drm_dp_bw_code_to_link_rate(dp->link.rate);
+	actual = rate * dp->link.num_lanes / 100;
+
+	/* efficiency is about 0.8 */
+	actual = actual * 8 / 10;
+
+	if (requested > actual) {
+		dev_dbg(dp->dev, "requested=%d, actual=%d, clock=%d\n",
+			requested, actual, mode->clock);
+		return MODE_CLOCK_HIGH;
+	}
+
+	return MODE_OK;
+}
+
+static struct drm_connector_helper_funcs cdn_dp_connector_helper_funcs = {
+	.get_modes = cdn_dp_connector_get_modes,
+	.best_encoder = cdn_dp_connector_best_encoder,
+	.mode_valid = cdn_dp_connector_mode_valid,
+};
+
+static void cdn_dp_commit(struct drm_encoder *encoder)
+{
+	struct cdn_dp_device *dp = encoder_to_dp(encoder);
+
+	if (cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE))
+		return;
+
+	if (cdn_dp_config_video(dp)) {
+		dev_err(dp->dev, "unable to config video\n");
+		return;
+	}
+
+	if (cdn_dp_set_video_status(dp, CONTROL_VIDEO_VALID))
+		return;
+
+	dp->dpms_mode = DRM_MODE_DPMS_ON;
+}
+
+static void cdn_dp_encoder_mode_set(struct drm_encoder *encoder,
+				    struct drm_display_mode *mode,
+				    struct drm_display_mode *adjusted)
+{
+	struct cdn_dp_device *dp = encoder_to_dp(encoder);
+	struct drm_display_info *display_info = &dp->connector.display_info;
+	struct rockchip_crtc_state *state;
+	struct video_info *video = &dp->video_info;
+	int ret, val;
+
+	switch (display_info->bpc) {
+	case 16:
+	case 12:
+	case 10:
+		video->color_depth = 10;
+		break;
+	case 6:
+		video->color_depth = 6;
+		break;
+	default:
+		video->color_depth = 8;
+		break;
+	}
+
+	video->color_fmt = PXL_RGB;
+
+	video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
+	video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
+
+	ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder);
+	if (ret < 0) {
+		dev_err(dp->dev, "Could not get vop id, %d", ret);
+		return;
+	}
+
+	state = to_rockchip_crtc_state(encoder->crtc->state);
+	if (ret) {
+		val = DP_SEL_VOP_LIT | (DP_SEL_VOP_LIT << 16);
+		state->output_mode = ROCKCHIP_OUT_MODE_P888;
+	} else {
+		val = DP_SEL_VOP_LIT << 16;
+		state->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+	}
+	ret = regmap_write(dp->grf, DP_VOP_SEL, val);
+	if (ret != 0)
+		dev_err(dp->dev, "Could not write to GRF: %d\n", ret);
+
+	memcpy(&dp->mode, adjusted, sizeof(*mode));
+}
+
+static void cdn_dp_encoder_enable(struct drm_encoder *encoder)
+{
+	struct cdn_dp_device *dp = encoder_to_dp(encoder);
+
+	if (dp->dpms_mode != DRM_MODE_DPMS_ON)
+		cdn_dp_commit(encoder);
+}
+
+static void cdn_dp_encoder_disable(struct drm_encoder *encoder)
+{
+	struct cdn_dp_device *dp = encoder_to_dp(encoder);
+
+	if (dp->dpms_mode != DRM_MODE_DPMS_OFF) {
+		cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE);
+		dp->dpms_mode = DRM_MODE_DPMS_OFF;
+	}
+}
+
+static int
+cdn_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);
+
+	s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+	s->output_type = DRM_MODE_CONNECTOR_DisplayPort;
+
+	return 0;
+}
+
+static struct drm_encoder_helper_funcs cdn_dp_encoder_helper_funcs = {
+	.mode_set = cdn_dp_encoder_mode_set,
+	.enable = cdn_dp_encoder_enable,
+	.disable = cdn_dp_encoder_disable,
+	.atomic_check = cdn_dp_encoder_atomic_check,
+};
+
+static struct drm_encoder_funcs cdn_dp_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static int cdn_dp_firmware_init(struct cdn_dp_device *dp)
+{
+	int ret;
+	const u32 *iram_data, *dram_data;
+	const struct firmware *fw = dp->fw;
+	const struct cdn_firmware_header *hdr;
+
+	if (dp->fw_loaded)
+		return 0;
+
+	hdr = (struct cdn_firmware_header *)fw->data;
+	if (fw->size != le32_to_cpu(hdr->size_bytes))
+		return -EINVAL;
+
+	iram_data = (const u32 *)(fw->data + hdr->header_size);
+	dram_data = (const u32 *)(fw->data + hdr->header_size + hdr->iram_size);
+
+	ret = cdn_dp_load_firmware(dp, iram_data, hdr->iram_size,
+				   dram_data, hdr->dram_size);
+	if (ret)
+		return ret;
+
+	release_firmware(fw);
+
+	ret = cdn_dp_active(dp, true);
+	if (ret) {
+		dev_err(dp->dev, "active ucpu failed: %d\n", ret);
+		return ret;
+	}
+
+	return cdn_dp_event_config(dp);
+}
+
+static int cdn_dp_init(struct cdn_dp_device *dp)
+{
+	struct device *dev = dp->dev;
+	struct device_node *np = dev->of_node;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct resource *res;
+	int ret;
+
+	dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+	if (IS_ERR(dp->grf)) {
+		dev_err(dev, "cdn-dp needs rockchip,grf property\n");
+		return PTR_ERR(dp->grf);
+	}
+
+	dp->irq = platform_get_irq(pdev, 0);
+	if (dp->irq < 0) {
+		dev_err(dev, "cdn-dp can not get irq\n");
+		return dp->irq;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dp->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(dp->regs)) {
+		dev_err(dev, "ioremap reg failed\n");
+		return PTR_ERR(dp->regs);
+	}
+
+	dp->core_clk = devm_clk_get(dev, "core-clk");
+	if (IS_ERR(dp->core_clk)) {
+		dev_err(dev, "cannot get core_clk_dp\n");
+		return PTR_ERR(dp->core_clk);
+	}
+
+	dp->pclk = devm_clk_get(dev, "pclk");
+	if (IS_ERR(dp->pclk)) {
+		dev_err(dev, "cannot get pclk\n");
+		return PTR_ERR(dp->pclk);
+	}
+
+	dp->spdif_clk = devm_clk_get(dev, "spdif");
+	if (IS_ERR(dp->spdif_clk)) {
+		dev_err(dev, "cannot get spdif_clk\n");
+		return PTR_ERR(dp->spdif_clk);
+	}
+
+	dp->spdif_rst = devm_reset_control_get(dev, "spdif");
+	if (IS_ERR(dp->spdif_rst)) {
+		dev_err(dev, "no spdif reset control found\n");
+		return PTR_ERR(dp->spdif_rst);
+	}
+
+	dp->dpms_mode = DRM_MODE_DPMS_OFF;
+
+	ret = cdn_dp_clk_enable(dp);
+	if (ret < 0)
+		return ret;
+
+	dp_clock_reset_seq(dp);
+
+	return 0;
+}
+
+static int cdn_dp_audio_hw_params(struct device *dev,
+				  struct hdmi_codec_daifmt *daifmt,
+				  struct hdmi_codec_params *params)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	struct audio_info audio = {
+		.sample_width = params->sample_width,
+		.sample_rate = params->sample_rate,
+		.channels = params->channels,
+	};
+	int ret;
+
+	if (!dp->encoder.crtc)
+		return -ENODEV;
+
+	switch (daifmt->fmt) {
+	case HDMI_I2S:
+		audio.format = AFMT_I2S;
+		break;
+	case HDMI_SPDIF:
+		audio.format = AFMT_SPDIF;
+		break;
+	default:
+		dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt);
+		return -EINVAL;
+	}
+
+	ret = cdn_dp_audio_config_set(dp, &audio);
+	if (!ret)
+		dp->audio_info = audio;
+
+	return ret;
+}
+
+static void cdn_dp_audio_shutdown(struct device *dev)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	int ret = cdn_dp_audio_stop(dp, &dp->audio_info);
+
+	if (!ret)
+		dp->audio_info.format = AFMT_UNUSED;
+}
+
+static int cdn_dp_audio_digital_mute(struct device *dev, bool enable)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+
+	return cdn_dp_audio_mute(dp, enable);
+}
+
+static int cdn_dp_audio_get_eld(struct device *dev, uint8_t *buf, size_t len)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	struct drm_mode_config *config = &dp->encoder.dev->mode_config;
+	struct drm_connector *connector;
+	int ret = -ENODEV;
+
+	mutex_lock(&config->mutex);
+	list_for_each_entry(connector, &config->connector_list, head) {
+		if (&dp->encoder == connector->encoder) {
+			memcpy(buf, connector->eld,
+			       min(sizeof(connector->eld), len));
+			ret = 0;
+		}
+	}
+	mutex_unlock(&config->mutex);
+
+	return ret;
+}
+
+static const struct hdmi_codec_ops audio_codec_ops = {
+	.hw_params = cdn_dp_audio_hw_params,
+	.audio_shutdown = cdn_dp_audio_shutdown,
+	.digital_mute = cdn_dp_audio_digital_mute,
+	.get_eld = cdn_dp_audio_get_eld,
+};
+
+static int cdn_dp_audio_codec_init(struct cdn_dp_device *dp,
+				   struct device *dev)
+{
+	struct hdmi_codec_pdata codec_data = {
+		.i2s = 1,
+		.spdif = 1,
+		.ops = &audio_codec_ops,
+		.max_i2s_channels = 8,
+	};
+
+	dp->audio_pdev = platform_device_register_data(
+			 dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO,
+			 &codec_data, sizeof(codec_data));
+
+	return PTR_ERR_OR_ZERO(dp->audio_pdev);
+}
+
+static void cdn_dp_get_state(struct cdn_dp_device *dp, struct extcon_dev *edev)
+{
+	bool dfp, dptx;
+	u8 cap_lanes;
+
+	dfp = extcon_get_cable_state_(edev, EXTCON_USB_HOST);
+	dptx = extcon_get_cable_state_(edev, EXTCON_DISP_DP);
+
+	if (dfp && dptx)
+		cap_lanes = 2;
+	else if (dptx)
+		cap_lanes = 4;
+	else
+		cap_lanes = 0;
+
+	if (cap_lanes != dp->cap_lanes) {
+		dp->cap_lanes = cap_lanes;
+		schedule_delayed_work(&dp->event_wq, 0);
+	}
+}
+
+static int cdn_dp_pd_event(struct notifier_block *nb,
+			   unsigned long event, void *priv)
+{
+	struct cdn_dp_device *dp;
+	struct extcon_dev *edev = priv;
+	u8 i;
+
+	dp = container_of(nb, struct cdn_dp_device, event_nb);
+
+	for (i = 0; i < MAX_PHY; i++) {
+		if (edev == dp->extcon[i]) {
+			dp->port_id = i;
+			break;
+		}
+	}
+
+	cdn_dp_get_state(dp, edev);
+
+	return 0;
+}
+
+static void cdn_dp_pd_event_wq(struct work_struct *work)
+{
+	struct cdn_dp_device *dp;
+	int ret;
+
+	dp = container_of(work, struct cdn_dp_device, event_wq.work);
+
+	ret = request_firmware(&dp->fw, "cdn/dptx.bin", dp->dev);
+	if (ret == -ENOENT && !dp->fw_loaded) {
+		/*
+		 * If can not find the file, retry to load the firmware in 1
+		 * second, if still failed after 1 minute, give up.
+		 */
+		if (dp->fw_retry++ < 60) {
+			schedule_delayed_work(&dp->event_wq,
+					      msecs_to_jiffies(HZ));
+		}
+
+		dev_err(dp->dev, "failed to request firmware %d\n", ret);
+		return;
+	}
+
+	if (!dp->cap_lanes) {
+		if (dp->phy_status[dp->port_id])
+			phy_power_off(dp->phy[dp->port_id]);
+		dp->phy_status[dp->port_id] = 0;
+		dp->hpd_status = false;
+		drm_helper_hpd_irq_event(dp->drm_dev);
+		return;
+	}
+
+	if (!dp->phy_status[dp->port_id]) {
+		ret = phy_power_on(dp->phy[dp->port_id]);
+		if (ret)
+			return;
+
+		msleep(WAIT_HPD_STABLE);
+	}
+
+	dp->phy_status[dp->port_id] = 1;
+
+	ret = cdn_dp_firmware_init(dp);
+	if (ret)
+		return;
+
+	/* read hpd status failed, or the hpd does not exist. */
+	ret = cdn_dp_get_hpd_status(dp);
+	if (ret <= 0)
+		return;
+
+	/*
+	 * Set the capability and start the training process once
+	 * the hpd is detected.
+	 */
+	ret = cdn_dp_set_host_cap(dp);
+	if (ret) {
+		dev_err(dp->dev, "set host capabilities failed\n");
+		return;
+	}
+
+	ret = cdn_dp_training_start(dp);
+	if (ret) {
+		dev_err(dp->dev, "hw lt err:%d\n", ret);
+		return;
+	}
+
+	ret = cdn_dp_get_lt_status(dp);
+	if (ret) {
+		dev_err(dp->dev, "hw lt get status failed\n");
+		return;
+	}
+
+	dev_info(dp->dev, "rate:%d, lanes:%d\n",
+		 dp->link.rate, dp->link.num_lanes);
+
+	dp->hpd_status = true;
+	drm_helper_hpd_irq_event(dp->drm_dev);
+}
+
+static int cdn_dp_bind(struct device *dev, struct device *master,
+		       void *data)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	struct drm_encoder *encoder;
+	struct drm_connector *connector;
+	struct drm_device *drm_dev = data;
+	int ret, i;
+
+	ret = cdn_dp_init(dp);
+	if (ret < 0)
+		return ret;
+
+	dp->drm_dev = drm_dev;
+
+	encoder = &dp->encoder;
+
+	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
+							     dev->of_node);
+	DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs);
+
+	ret = drm_encoder_init(drm_dev, encoder, &cdn_dp_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS, NULL);
+	if (ret) {
+		DRM_ERROR("failed to initialize encoder with drm\n");
+		return ret;
+	}
+
+	drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs);
+
+	connector = &dp->connector;
+	connector->polled = DRM_CONNECTOR_POLL_HPD;
+	connector->dpms = DRM_MODE_DPMS_OFF;
+
+	ret = drm_connector_init(drm_dev, connector,
+				 &cdn_dp_atomic_connector_funcs,
+				 DRM_MODE_CONNECTOR_DisplayPort);
+	if (ret) {
+		DRM_ERROR("failed to initialize connector with drm\n");
+		goto err_free_encoder;
+	}
+
+	drm_connector_helper_add(connector, &cdn_dp_connector_helper_funcs);
+
+	ret = drm_mode_connector_attach_encoder(connector, encoder);
+	if (ret) {
+		DRM_ERROR("failed to attach connector and encoder\n");
+		goto err_free_connector;
+	}
+
+	cdn_dp_audio_codec_init(dp, dev);
+
+	dp->event_nb.notifier_call = cdn_dp_pd_event;
+	INIT_DELAYED_WORK(&dp->event_wq, cdn_dp_pd_event_wq);
+
+	for (i = 0; i < MAX_PHY; i++) {
+		if (IS_ERR(dp->extcon[i]))
+			continue;
+
+		ret = extcon_register_notifier(dp->extcon[i], EXTCON_DISP_DP,
+					       &dp->event_nb);
+		if (ret) {
+			dev_err(dev, "regitster EXTCON_DISP_DP notifier err\n");
+			return ret;
+		}
+
+		cdn_dp_get_state(dp, dp->extcon[i]);
+	}
+
+	return 0;
+
+err_free_connector:
+	drm_connector_cleanup(connector);
+err_free_encoder:
+	drm_encoder_cleanup(encoder);
+	return ret;
+}
+
+static void cdn_dp_unbind(struct device *dev, struct device *master, void *data)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	struct drm_encoder *encoder = &dp->encoder;
+	int i;
+
+	platform_device_unregister(dp->audio_pdev);
+	cdn_dp_encoder_disable(encoder);
+	encoder->funcs->destroy(encoder);
+	drm_connector_unregister(&dp->connector);
+	drm_connector_cleanup(&dp->connector);
+	drm_encoder_cleanup(encoder);
+
+	for (i = 0; i < MAX_PHY; i++) {
+		if (!IS_ERR(dp->extcon[i]))
+			extcon_unregister_notifier(dp->extcon[i], EXTCON_USB,
+						   &dp->event_nb);
+	}
+}
+
+static const struct component_ops cdn_dp_component_ops = {
+	.bind = cdn_dp_bind,
+	.unbind = cdn_dp_unbind,
+};
+
+static int cdn_dp_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct cdn_dp_device *dp;
+	u8 count = 0;
+	int i;
+
+	dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
+	if (!dp)
+		return -ENOMEM;
+	dp->dev = dev;
+
+	for (i = 0; i < MAX_PHY; i++) {
+		dp->extcon[i] = extcon_get_edev_by_phandle(dev, i);
+		dp->phy[i] = devm_of_phy_get_by_index(dev, dev->of_node, i);
+
+		if (!IS_ERR(dp->extcon[i]) && !IS_ERR(dp->phy[i]))
+			count++;
+
+		if (PTR_ERR(dp->extcon[i]) == -EPROBE_DEFER ||
+		    PTR_ERR(dp->phy[i]) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
+	}
+
+	if (!count) {
+		dev_err(dev, "missing extcon or phy\n");
+		return -EINVAL;
+	}
+
+	dev_set_drvdata(dev, dp);
+
+	return component_add(dev, &cdn_dp_component_ops);
+}
+
+static int cdn_dp_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &cdn_dp_component_ops);
+
+	return 0;
+}
+
+static const struct of_device_id cdn_dp_dt_ids[] = {
+	{ .compatible = "rockchip,rk3399-cdn-dp" },
+	{}
+};
+
+MODULE_DEVICE_TABLE(of, cdn_dp_dt_ids);
+
+static struct platform_driver cdn_dp_driver = {
+	.probe = cdn_dp_probe,
+	.remove = cdn_dp_remove,
+	.driver = {
+		   .name = "cdn-dp",
+		   .owner = THIS_MODULE,
+		   .of_match_table = of_match_ptr(cdn_dp_dt_ids),
+	},
+};
+
+module_platform_driver(cdn_dp_driver);
+
+MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
+MODULE_DESCRIPTION("cdn DP Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h
new file mode 100644
index 0000000..31ba22d
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 Chris Zhong <zyw@rock-chips.com>
+ * Copyright (C) 2016 ROCKCHIP, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _ROCKCHIP_EDP_CORE_H
+#define _ROCKCHIP_EDP_CORE_H
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_panel.h>
+#include "rockchip_drm_drv.h"
+#include "cdn-dp-reg.h"
+
+#define MAX_PHY		2
+
+enum AUDIO_FORMAT {
+	AFMT_I2S = 0,
+	AFMT_SPDIF = 1,
+	AFMT_UNUSED,
+};
+
+struct audio_info {
+	enum AUDIO_FORMAT format;
+	int sample_rate;
+	int channels;
+	int sample_width;
+};
+
+struct video_info {
+	bool h_sync_polarity;
+	bool v_sync_polarity;
+	bool interlaced;
+	int color_depth;
+	enum VIC_PXL_ENCODING_FORMAT color_fmt;
+};
+
+struct cdn_firmware_header {
+	u32 size_bytes; /* size of the entire header+image(s) in bytes */
+	u32 header_size; /* size of just the header in bytes */
+	u32 iram_size; /* size of iram */
+	u32 dram_size; /* size of dram */
+};
+
+struct cdn_dp_device {
+	struct device *dev;
+	struct drm_device *drm_dev;
+	struct drm_connector connector;
+	struct drm_encoder encoder;
+	struct drm_display_mode mode;
+	struct platform_device *audio_pdev;
+
+	const struct firmware *fw;	/* cdn dp firmware */
+	unsigned int fw_version;	/* cdn fw version */
+	u32 fw_retry;
+	bool fw_loaded;
+	void __iomem *regs;
+	struct regmap *grf;
+	unsigned int irq;
+	struct clk *core_clk;
+	struct clk *pclk;
+	struct clk *spdif_clk;
+	struct reset_control *spdif_rst;
+	struct audio_info audio_info;
+	struct video_info video_info;
+	struct extcon_dev *extcon[MAX_PHY];
+	struct phy *phy[MAX_PHY];
+	struct notifier_block event_nb;
+	struct delayed_work event_wq;
+
+	u8 port_id;
+	u8 cap_lanes;
+	bool hpd_status;
+	bool phy_status[MAX_PHY];
+
+	int dpms_mode;
+	struct drm_dp_link link;
+	bool sink_has_audio;
+};
+
+void dp_clock_reset_seq(struct cdn_dp_device *dp);
+
+void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, int clk);
+int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
+			 u32 i_size, const u32 *d_mem, u32 d_size);
+int cdn_dp_active(struct cdn_dp_device *dp, bool enable);
+int cdn_dp_set_host_cap(struct cdn_dp_device *dp);
+int cdn_dp_event_config(struct cdn_dp_device *dp);
+int cdn_dp_get_event(struct cdn_dp_device *dp);
+int cdn_dp_get_hpd_status(struct cdn_dp_device *dp);
+int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value);
+int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr);
+int cdn_dp_get_edid_block(void *dp, u8 *edid,
+			  unsigned int block, size_t length);
+int cdn_dp_training_start(struct cdn_dp_device *dp);
+int cdn_dp_get_lt_status(struct cdn_dp_device *dp);
+int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active);
+int cdn_dp_config_video(struct cdn_dp_device *dp);
+int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio);
+int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable);
+int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info *audio);
+
+#endif  /* _ROCKCHIP_EDP_CORE_H */
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.c b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
new file mode 100644
index 0000000..8d5becd
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
@@ -0,0 +1,740 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/reset.h>
+
+#include "cdn-dp-core.h"
+#include "cdn-dp-reg.h"
+
+#define CDN_DP_SPDIF_CLK		200000000
+#define FW_ALIVE_TIMEOUT_US		1000000
+#define MAILBOX_TIMEOUT_US		5000000
+
+void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, int clk)
+{
+	writel(clk / 1000000, dp->regs + SW_CLK_H);
+}
+
+void dp_clock_reset_seq(struct cdn_dp_device *dp)
+{
+	writel(0xfff, dp->regs + SOURCE_DPTX_CAR);
+	writel(0x7, dp->regs + SOURCE_PHY_CAR);
+	writel(0xf, dp->regs + SOURCE_PKT_CAR);
+	writel(0xff, dp->regs + SOURCE_AIF_CAR);
+	writel(0xf, dp->regs + SOURCE_CIPHER_CAR);
+	writel(0x3, dp->regs + SOURCE_CRYPTO_CAR);
+	writel(0, dp->regs + APB_INT_MASK);
+}
+
+static int cdn_dp_mailbox_read(struct cdn_dp_device *dp)
+{
+	int val, ret;
+
+	if (!dp->fw_loaded)
+		return 0;
+
+	ret = readx_poll_timeout(readl, dp->regs + MAILBOX_EMPTY_ADDR,
+				 val, !val, 1000, MAILBOX_TIMEOUT_US);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to read mailbox, keep alive = %x\n",
+			readl(dp->regs + KEEP_ALIVE));
+		return ret;
+	}
+
+	return readl(dp->regs + MAILBOX0_RD_DATA) & 0xff;
+}
+
+static int cdp_dp_mailbox_write(struct cdn_dp_device *dp, u8 val)
+{
+	int ret, full;
+
+	if (!dp->fw_loaded)
+		return 0;
+
+	ret = readx_poll_timeout(readl, dp->regs + MAILBOX_FULL_ADDR,
+				 full, !full, 1000, MAILBOX_TIMEOUT_US);
+	if (ret < 0) {
+		dev_err(dp->dev, "mailbox is full, keep alive = %x\n",
+			readl(dp->regs + KEEP_ALIVE));
+		return ret;
+	}
+
+	writel(val, dp->regs + MAILBOX0_WR_DATA);
+
+	return 0;
+}
+
+static int cdn_dp_mailbox_response(struct cdn_dp_device *dp, u8 module_id,
+				   u8 opcode, u8 *buff, u8 buff_size)
+{
+	int ret, i = 0;
+	u8 header[4];
+
+	/* read the header of the message */
+	while (i < 4) {
+		ret = cdn_dp_mailbox_read(dp);
+		if (ret < 0)
+			return ret;
+
+		header[i++] = ret;
+	}
+
+	if (opcode != header[0] || module_id != header[1] ||
+	    buff_size != ((header[2] << 8) | header[3])) {
+		dev_err(dp->dev, "mailbox response failed");
+
+		/*
+		 * If the message in mailbox is not what we want, we need to
+		 * clear the mailbox by read.
+		 */
+		i = (header[2] << 8) | header[3];
+		while (i--)
+			if (cdn_dp_mailbox_read(dp) < 0)
+				break;
+
+		return -EINVAL;
+	}
+
+	i = 0;
+	while (i < buff_size) {
+		ret = cdn_dp_mailbox_read(dp);
+		if (ret < 0)
+			return ret;
+
+		buff[i++] = ret;
+	}
+
+	return 0;
+}
+
+static int cdn_dp_mailbox_send(struct cdn_dp_device *dp, u8 module_id,
+			       u8 opcode, u16 size, u8 *message)
+{
+	u8 header[4];
+	int ret, i;
+
+	header[0] = opcode;
+	header[1] = module_id;
+	header[2] = size >> 8;
+	header[3] = size & 0xff;
+
+	for (i = 0; i < 4; i++) {
+		ret = cdp_dp_mailbox_write(dp, header[i]);
+		if (ret)
+			return ret;
+	}
+
+	while (size--) {
+		ret = cdp_dp_mailbox_write(dp, *message++);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int cdn_dp_reg_write(struct cdn_dp_device *dp, u16 addr, u32 val)
+{
+	u8 msg[6];
+
+	msg[0] = (addr >> 8) & 0xff;
+	msg[1] = addr & 0xff;
+	msg[2] = (val >> 24) & 0xff;
+	msg[3] = (val >> 16) & 0xff;
+	msg[4] = (val >> 8) & 0xff;
+	msg[5] = val & 0xff;
+	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_REGISTER,
+				   ARRAY_SIZE(msg), msg);
+}
+
+int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
+			 u32 i_size, const u32 *d_mem, u32 d_size)
+{
+	int i, reg, ret;
+
+	/* reset ucpu before load firmware*/
+	writel(APB_IRAM_PATH | APB_DRAM_PATH | APB_XT_RESET,
+	       dp->regs + APB_CTRL);
+
+	for (i = 0; i < i_size; i += 4)
+		writel(*i_mem++, dp->regs + ADDR_IMEM + i);
+
+	for (i = 0; i < d_size; i += 4)
+		writel(*d_mem++, dp->regs + ADDR_DMEM + i);
+
+	/* un-reset ucpu */
+	writel(0, dp->regs + APB_CTRL);
+
+	/* check the keep alive register to make sure fw working */
+	ret = readx_poll_timeout(readl, dp->regs + KEEP_ALIVE,
+				 reg, reg, 2000, FW_ALIVE_TIMEOUT_US);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to loaded the FW reg = %x\n", reg);
+		return -EINVAL;
+	}
+
+	reg = readl(dp->regs + VER_L) & 0xff;
+	dp->fw_version = reg;
+	reg = readl(dp->regs + VER_H) & 0xff;
+	dp->fw_version |= reg << 8;
+	reg = readl(dp->regs + VER_LIB_L_ADDR) & 0xff;
+	dp->fw_version |= reg << 16;
+	reg = readl(dp->regs + VER_LIB_H_ADDR) & 0xff;
+	dp->fw_version |= reg << 24;
+
+	dp->fw_loaded = 1;
+
+	return 0;
+}
+
+int cdn_dp_reg_write_bit(struct cdn_dp_device *dp, u16 addr, u8 start_bit,
+			 u8 bits_no, u32 val)
+{
+	u8 field[8];
+
+	field[0] = (addr >> 8) & 0xff;
+	field[1] = addr & 0xff;
+	field[2] = start_bit;
+	field[3] = bits_no;
+	field[4] = (val >> 24) & 0xff;
+	field[5] = (val >> 16) & 0xff;
+	field[6] = (val >> 8) & 0xff;
+	field[7] = val & 0xff;
+
+	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_FIELD,
+			    sizeof(field), field);
+}
+
+int cdn_dp_active(struct cdn_dp_device *dp, bool enable)
+{
+	u8 active = enable ? 1 : 0;
+	u8 resp;
+	int ret;
+
+	/* set firmware status, 1: avtive; 0: standby */
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_GENERAL,
+				  GENERAL_MAIN_CONTROL, 1, &active);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_GENERAL,
+				      GENERAL_MAIN_CONTROL, &resp, 1);
+	if (ret)
+		return ret;
+
+	return resp ? 0 : -EINVAL;
+}
+
+int cdn_dp_set_host_cap(struct cdn_dp_device *dp)
+{
+	u8 msg[8];
+	int ret;
+
+	msg[0] = DP_LINK_BW_5_4;
+	msg[1] = dp->cap_lanes;
+	msg[2] = VOLTAGE_LEVEL_2;
+	msg[3] = PRE_EMPHASIS_LEVEL_3;
+	msg[4] = PRBS7 | D10_2 | TRAINING_PTN1 | TRAINING_PTN2;
+	msg[5] = FAST_LT_NOT_SUPPORT;
+	msg[6] = LANE_MAPPING_NORMAL;
+	msg[7] = ENHANCED;
+
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
+				  DPTX_SET_HOST_CAPABILITIES,
+				  ARRAY_SIZE(msg), msg);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_reg_write(dp, DP_AUX_SWAP_INVERSION_CONTROL,
+			       AUX_HOST_INVERT);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+int cdn_dp_event_config(struct cdn_dp_device *dp)
+{
+	u8 msg[5] = {0, 0, 0, 0, 0};
+
+	msg[0] = DPTX_EVENT_ENABLE_HPD | DPTX_EVENT_ENABLE_TRAINING;
+
+	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_ENABLE_EVENT,
+				    ARRAY_SIZE(msg), msg);
+}
+
+int cdn_dp_get_event(struct cdn_dp_device *dp)
+{
+	return readl(dp->regs + SW_EVENTS0);
+}
+
+int cdn_dp_get_hpd_status(struct cdn_dp_device *dp)
+{
+	u8 status;
+	int ret;
+
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_HPD_STATE,
+				  0, NULL);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
+				      DPTX_HPD_STATE, &status, 1);
+	if (ret)
+		return ret;
+
+	return status;
+}
+
+int cdn_dp_get_edid_block(void *data, u8 *edid,
+			  unsigned int block, size_t length)
+{
+	struct cdn_dp_device *dp = data;
+	u8 msg[2], reg[EDID_DATA + EDID_BLOCK_SIZE];
+	int ret;
+
+	if (length != EDID_BLOCK_SIZE)
+		return -EINVAL;
+
+	msg[0] = block / 2;
+	msg[1] = block % 2;
+
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_GET_EDID,
+				  2, msg);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
+				      DPTX_GET_EDID, reg,
+				      EDID_DATA + EDID_BLOCK_SIZE);
+	if (ret)
+		return ret;
+
+	if (reg[EDID_LENGTH_BYTE] != EDID_BLOCK_SIZE ||
+	    reg[EDID_SEGMENT_BUMBER] != block / 2) {
+		dev_err(dp->dev, "edid block size err\n");
+		return -EINVAL;
+	}
+
+	memcpy(edid, &reg[EDID_DATA], EDID_BLOCK_SIZE);
+
+	return 0;
+}
+
+int cdn_dp_training_start(struct cdn_dp_device *dp)
+{
+	u8 msg, event[2];
+	unsigned long timeout;
+	int ret;
+
+	msg = LINK_TRAINING_RUN;
+
+	/* start training */
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_TRAINING_CONTROL,
+				  1, &msg);
+	if (ret)
+		return ret;
+
+	/* the whole training should finish in 500ms */
+	timeout = jiffies + msecs_to_jiffies(500);
+	while (1) {
+		msleep(20);
+		ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
+					  DPTX_READ_EVENT, 0, NULL);
+		if (ret)
+			return ret;
+
+		ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
+					      DPTX_READ_EVENT, event, 2);
+		if (ret)
+			return ret;
+
+		if (event[1] & EQ_PHASE_FINISHED)
+			break;
+
+		if (time_after(jiffies, timeout))
+			return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+int cdn_dp_get_lt_status(struct cdn_dp_device *dp)
+{
+	u8 status[10];
+	int ret;
+
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_READ_LINK_STAT,
+				  0, NULL);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
+				      DPTX_READ_LINK_STAT, status, 10);
+	if (ret)
+		return ret;
+
+	dp->link.rate = status[0];
+	dp->link.num_lanes = status[1];
+
+	return 0;
+}
+
+int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active)
+{
+	u8 msg;
+
+	msg = !!active;
+
+	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_SET_VIDEO,
+				   1, &msg);
+}
+
+static int cdn_dp_get_msa_misc(struct video_info *video,
+			       struct drm_display_mode *mode)
+{
+	u8 val0, val1;
+	u32 msa_misc;
+
+	switch (video->color_fmt) {
+	case PXL_RGB:
+	case Y_ONLY:
+		val0 = 0;
+		break;
+	case YCBCR_4_4_4:
+		/* set default color space conversion to BT601 */
+		val0 = 6 + BT_601 * 8;
+		break;
+	case YCBCR_4_2_2:
+		/* set default color space conversion to BT601 */
+		val0 = 5 + BT_601 * 8;
+		break;
+	case YCBCR_4_2_0:
+		val0 = 5;
+		break;
+	};
+
+	switch (video->color_depth) {
+	case 6:
+		val1 = 0;
+		break;
+	case 8:
+		val1 = 1;
+		break;
+	case 10:
+		val1 = 2;
+		break;
+	case 12:
+		val1 = 3;
+		break;
+	case 16:
+		val1 = 4;
+		break;
+	};
+
+	msa_misc = 2 * val0 + 32 * val1 +
+		   ((video->color_fmt == Y_ONLY) ? (1 << 14) : 0);
+
+	return msa_misc;
+}
+
+int cdn_dp_config_video(struct cdn_dp_device *dp)
+{
+	struct video_info *video = &dp->video_info;
+	struct drm_display_mode *mode = &dp->mode;
+	u64 symbol;
+	u32 val, link_rate;
+	u8 bit_per_pix;
+	u8 tu_size_reg = TU_SIZE;
+	int ret;
+
+	bit_per_pix = (video->color_fmt == YCBCR_4_2_2) ?
+		      (video->color_depth * 2) : (video->color_depth * 3);
+
+	link_rate = drm_dp_bw_code_to_link_rate(dp->link.rate);
+
+	val = VIF_BYPASS_INTERLACE;
+
+	ret = cdn_dp_reg_write(dp, BND_HSYNC2VSYNC, val);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_reg_write(dp, HSYNC2VSYNC_POL_CTRL, 0);
+	if (ret)
+		return ret;
+
+	/* get a best tu_size and symbol */
+	do {
+		tu_size_reg += 2;
+		symbol = tu_size_reg * mode->clock * bit_per_pix;
+		symbol /= dp->link.num_lanes * link_rate * 8;
+	} while ((symbol == 1) || (tu_size_reg - symbol < 4));
+
+	val = symbol + (tu_size_reg << 8);
+
+	ret = cdn_dp_reg_write(dp, DP_FRAMER_TU, val);
+	if (ret)
+		return ret;
+
+	switch (video->color_depth) {
+	case 6:
+		val = BCS_6;
+		break;
+	case 8:
+		val = BCS_8;
+		break;
+	case 10:
+		val = BCS_10;
+		break;
+	case 12:
+		val = BCS_12;
+		break;
+	case 16:
+		val = BCS_16;
+		break;
+	};
+
+	val += video->color_fmt << 8;
+	ret = cdn_dp_reg_write(dp, DP_FRAMER_PXL_REPR, val);
+	if (ret)
+		return ret;
+
+	val = video->h_sync_polarity ? DP_FRAMER_SP_HSP : 0;
+	val |= video->v_sync_polarity ? DP_FRAMER_SP_VSP : 0;
+	ret = cdn_dp_reg_write(dp, DP_FRAMER_SP, val);
+	if (ret)
+		return ret;
+
+	val = (mode->hsync_start - mode->hdisplay) << 16;
+	val |= mode->htotal - mode->hsync_end;
+	ret = cdn_dp_reg_write(dp, DP_FRONT_BACK_PORCH, val);
+	if (ret)
+		return ret;
+
+	val = mode->hdisplay * bit_per_pix / 8;
+	ret = cdn_dp_reg_write(dp, DP_BYTE_COUNT, val);
+	if (ret)
+		return ret;
+
+	val = mode->htotal | ((mode->htotal - mode->hsync_start) << 16);
+	ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_0, val);
+	if (ret)
+		return ret;
+
+	val = mode->hsync_end - mode->hsync_start;
+	val |= (mode->hdisplay << 16) | (video->h_sync_polarity << 15);
+	ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_1, val);
+	if (ret)
+		return ret;
+
+	val = mode->vtotal;
+	val |= ((mode->vtotal - mode->vsync_start) << 16);
+
+	ret = cdn_dp_reg_write(dp, MSA_VERTICAL_0, val);
+	if (ret)
+		return ret;
+
+	val = mode->vsync_end - mode->vsync_start;
+	val |= mode->vdisplay << 16;
+	val |= (video->v_sync_polarity << 15);
+	ret = cdn_dp_reg_write(dp, MSA_VERTICAL_1, val);
+	if (ret)
+		return ret;
+
+	val = cdn_dp_get_msa_misc(video, mode);
+	ret = cdn_dp_reg_write(dp, MSA_MISC, val);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_reg_write(dp, STREAM_CONFIG, 1);
+	if (ret)
+		return ret;
+
+	val = mode->hsync_end - mode->hsync_start;
+	val |= (mode->hdisplay << 16);
+	ret = cdn_dp_reg_write(dp, DP_HORIZONTAL, val);
+	if (ret)
+		return ret;
+
+	val = mode->vtotal;
+	val -= (mode->vtotal - mode->vdisplay);
+	val |= (mode->vtotal - mode->vsync_start) << 16;
+
+	ret = cdn_dp_reg_write(dp, DP_VERTICAL_0, val);
+	if (ret)
+		return ret;
+
+	val = mode->vtotal;
+	ret = cdn_dp_reg_write(dp, DP_VERTICAL_1, val);
+	if (ret)
+		return ret;
+
+	val =  0;
+	return cdn_dp_reg_write_bit(dp, DP_VB_ID, 2, 1, val);
+}
+
+int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio)
+{
+	int ret = cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, AUDIO_PACK_EN(0));
+
+	if (ret)
+		return ret;
+	writel(0x1F0707, dp->regs + SPDIF_CTRL_ADDR);
+	writel(0, dp->regs + AUDIO_SRC_CNTL);
+	writel(0, dp->regs + AUDIO_SRC_CNFG);
+	writel(1, dp->regs + AUDIO_SRC_CNTL);
+	writel(0, dp->regs + AUDIO_SRC_CNTL);
+
+	writel(0, dp->regs + SMPL2PKT_CNTL);
+	writel(1, dp->regs + SMPL2PKT_CNTL);
+	writel(0, dp->regs + SMPL2PKT_CNTL);
+
+	writel(1, dp->regs + FIFO_CNTL);
+	writel(0, dp->regs + FIFO_CNTL);
+
+	if (audio->format == AFMT_SPDIF)
+		clk_disable_unprepare(dp->spdif_clk);
+
+	return 0;
+}
+
+int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable)
+{
+	return cdn_dp_reg_write_bit(dp, DP_VB_ID, 4, 1, enable);
+}
+
+int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info *audio)
+{
+	int lanes_param, i2s_port_en_val, val, i;
+	int ret;
+
+	if (audio->channels == 2 && dp->link.num_lanes == 1)
+		lanes_param = 1;
+	else if (audio->channels == 2)
+		lanes_param = 3;
+	else
+		lanes_param = 0;
+
+	if (audio->channels == 2)
+		i2s_port_en_val = 1;
+	else if (audio->channels == 4)
+		i2s_port_en_val = 3;
+	else
+		i2s_port_en_val = 0xf;
+
+	if (audio->format == AFMT_SPDIF) {
+		reset_control_assert(dp->spdif_rst);
+		reset_control_deassert(dp->spdif_rst);
+	}
+
+	ret = cdn_dp_reg_write(dp, CM_LANE_CTRL, 0x8000);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_reg_write(dp, CM_CTRL, 0);
+	if (ret)
+		return ret;
+
+	if (audio->format == AFMT_I2S) {
+		writel(0x0, dp->regs + SPDIF_CTRL_ADDR);
+
+		writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
+
+		val = audio->channels - 1;
+		val |= (audio->channels / 2 - 1) << 5;
+		val |= BIT(8);
+		val |= lanes_param << 11;
+		writel(val, dp->regs + SMPL2PKT_CNFG);
+
+		if (audio->sample_width == 16)
+			val = 0;
+		else if (audio->sample_width == 24)
+			val = 1 << 9;
+		else
+			val = 2 << 9;
+
+		val |= (audio->channels - 1) << 2;
+		val |= i2s_port_en_val << 17;
+		val |= 2 << 11;
+		writel(val, dp->regs + AUDIO_SRC_CNFG);
+
+		for (i = 0; i < (audio->channels + 1) / 2; i++) {
+			if (audio->sample_width == 16)
+				val = (0x08 << 8) | (0x08 << 20);
+			else if (audio->sample_width == 24)
+				val = (0x0b << 8) | (0x0b << 20);
+
+			val |= ((2 * i) << 4) | ((2 * i + 1) << 16);
+			writel(val, dp->regs + STTS_BIT_CH(i));
+		}
+
+		switch (audio->sample_rate) {
+		case 32000:
+			val = SAMPLING_FREQ(3) |
+			      ORIGINAL_SAMP_FREQ(0xc);
+			break;
+		case 44100:
+			val = SAMPLING_FREQ(0) |
+			      ORIGINAL_SAMP_FREQ(0xf);
+			break;
+		case 48000:
+			val = SAMPLING_FREQ(2) |
+			      ORIGINAL_SAMP_FREQ(0xd);
+			break;
+		case 88200:
+			val = SAMPLING_FREQ(8) |
+			      ORIGINAL_SAMP_FREQ(0x7);
+			break;
+		case 96000:
+			val = SAMPLING_FREQ(0xa) |
+			      ORIGINAL_SAMP_FREQ(5);
+			break;
+		case 176400:
+			val = SAMPLING_FREQ(0xc) |
+			      ORIGINAL_SAMP_FREQ(3);
+			break;
+		case 192000:
+			val = SAMPLING_FREQ(0xe) |
+			      ORIGINAL_SAMP_FREQ(1);
+			break;
+		}
+		val |= 4;
+		writel(val, dp->regs + COM_CH_STTS_BITS);
+
+		writel(2, dp->regs + SMPL2PKT_CNTL);
+		writel(2, dp->regs + AUDIO_SRC_CNTL);
+	} else {
+		val = 0x1F0707;
+		writel(val, dp->regs + SPDIF_CTRL_ADDR);
+
+		writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
+
+		val = 0x101 | (3 << 11);
+		writel(val, dp->regs + SMPL2PKT_CNFG);
+		writel(2, dp->regs + SMPL2PKT_CNTL);
+
+		val = 0x3F0707;
+		writel(val, dp->regs + SPDIF_CTRL_ADDR);
+
+		clk_prepare_enable(dp->spdif_clk);
+		clk_set_rate(dp->spdif_clk, CDN_DP_SPDIF_CLK);
+	}
+
+	return cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, AUDIO_PACK_EN(1));
+}
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.h b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
new file mode 100644
index 0000000..b33793e
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _CDN_DP_REG_H
+#define _CDN_DP_REG_H
+
+#include <linux/bitops.h>
+
+#define ADDR_IMEM		0x10000
+#define ADDR_DMEM		0x20000
+
+/* APB CFG addr */
+#define APB_CTRL			0
+#define XT_INT_CTRL			0x04
+#define MAILBOX_FULL_ADDR		0x08
+#define MAILBOX_EMPTY_ADDR		0x0c
+#define MAILBOX0_WR_DATA		0x10
+#define MAILBOX0_RD_DATA		0x14
+#define KEEP_ALIVE			0x18
+#define VER_L				0x1c
+#define VER_H				0x20
+#define VER_LIB_L_ADDR			0x24
+#define VER_LIB_H_ADDR			0x28
+#define SW_DEBUG_L			0x2c
+#define SW_DEBUG_H			0x30
+#define MAILBOX_INT_MASK		0x34
+#define MAILBOX_INT_STATUS		0x38
+#define SW_CLK_L			0x3c
+#define SW_CLK_H			0x40
+#define SW_EVENTS0			0x44
+#define SW_EVENTS1			0x48
+#define SW_EVENTS2			0x4c
+#define SW_EVENTS3			0x50
+#define XT_OCD_CTRL			0x60
+#define APB_INT_MASK			0x6c
+#define APB_STATUS_MASK			0x70
+
+/* audio decoder addr */
+#define AUDIO_SRC_CNTL			0x30000
+#define AUDIO_SRC_CNFG			0x30004
+#define COM_CH_STTS_BITS		0x30008
+#define STTS_BIT_CH(x)			(0x3000c + ((x) << 2))
+#define SPDIF_CTRL_ADDR			0x3004c
+#define SPDIF_CH1_CS_3100_ADDR		0x30050
+#define SPDIF_CH1_CS_6332_ADDR		0x30054
+#define SPDIF_CH1_CS_9564_ADDR		0x30058
+#define SPDIF_CH1_CS_12796_ADDR		0x3005c
+#define SPDIF_CH1_CS_159128_ADDR	0x30060
+#define SPDIF_CH1_CS_191160_ADDR	0x30064
+#define SPDIF_CH2_CS_3100_ADDR		0x30068
+#define SPDIF_CH2_CS_6332_ADDR		0x3006c
+#define SPDIF_CH2_CS_9564_ADDR		0x30070
+#define SPDIF_CH2_CS_12796_ADDR		0x30074
+#define SPDIF_CH2_CS_159128_ADDR	0x30078
+#define SPDIF_CH2_CS_191160_ADDR	0x3007c
+#define SMPL2PKT_CNTL			0x30080
+#define SMPL2PKT_CNFG			0x30084
+#define FIFO_CNTL			0x30088
+#define FIFO_STTS			0x3008c
+
+/* source pif addr */
+#define SOURCE_PIF_WR_ADDR		0x30800
+#define SOURCE_PIF_WR_REQ		0x30804
+#define SOURCE_PIF_RD_ADDR		0x30808
+#define SOURCE_PIF_RD_REQ		0x3080c
+#define SOURCE_PIF_DATA_WR		0x30810
+#define SOURCE_PIF_DATA_RD		0x30814
+#define SOURCE_PIF_FIFO1_FLUSH		0x30818
+#define SOURCE_PIF_FIFO2_FLUSH		0x3081c
+#define SOURCE_PIF_STATUS		0x30820
+#define SOURCE_PIF_INTERRUPT_SOURCE	0x30824
+#define SOURCE_PIF_INTERRUPT_MASK	0x30828
+#define SOURCE_PIF_PKT_ALLOC_REG	0x3082c
+#define SOURCE_PIF_PKT_ALLOC_WR_EN	0x30830
+#define SOURCE_PIF_SW_RESET		0x30834
+
+/* bellow registers need access by mailbox */
+/* source car addr */
+#define SOURCE_HDTX_CAR			0x0900
+#define SOURCE_DPTX_CAR			0x0904
+#define SOURCE_PHY_CAR			0x0908
+#define SOURCE_CEC_CAR			0x090c
+#define SOURCE_CBUS_CAR			0x0910
+#define SOURCE_PKT_CAR			0x0918
+#define SOURCE_AIF_CAR			0x091c
+#define SOURCE_CIPHER_CAR		0x0920
+#define SOURCE_CRYPTO_CAR		0x0924
+
+/* clock meters addr */
+#define CM_CTRL				0x0a00
+#define CM_I2S_CTRL			0x0a04
+#define CM_SPDIF_CTRL			0x0a08
+#define CM_VID_CTRL			0x0a0c
+#define CM_LANE_CTRL			0x0a10
+#define I2S_NM_STABLE			0x0a14
+#define I2S_NCTS_STABLE			0x0a18
+#define SPDIF_NM_STABLE			0x0a1c
+#define SPDIF_NCTS_STABLE		0x0a20
+#define NMVID_MEAS_STABLE		0x0a24
+#define I2S_MEAS			0x0a40
+#define SPDIF_MEAS			0x0a80
+#define NMVID_MEAS			0x0ac0
+
+/* source vif addr */
+#define BND_HSYNC2VSYNC			0x0b00
+#define HSYNC2VSYNC_F1_L1		0x0b04
+#define HSYNC2VSYNC_F2_L1		0x0b08
+#define HSYNC2VSYNC_STATUS		0x0b0c
+#define HSYNC2VSYNC_POL_CTRL		0x0b10
+
+/* dptx phy addr */
+#define DP_TX_PHY_CONFIG_REG		0x2000
+#define DP_TX_PHY_STATUS_REG		0x2004
+#define DP_TX_PHY_SW_RESET		0x2008
+#define DP_TX_PHY_SCRAMBLER_SEED	0x200c
+#define DP_TX_PHY_TRAINING_01_04	0x2010
+#define DP_TX_PHY_TRAINING_05_08	0x2014
+#define DP_TX_PHY_TRAINING_09_10	0x2018
+#define TEST_COR			0x23fc
+
+/* dptx hpd addr */
+#define HPD_IRQ_DET_MIN_TIMER		0x2100
+#define HPD_IRQ_DET_MAX_TIMER		0x2104
+#define HPD_UNPLGED_DET_MIN_TIMER	0x2108
+#define HPD_STABLE_TIMER		0x210c
+#define HPD_FILTER_TIMER		0x2110
+#define HPD_EVENT_MASK			0x211c
+#define HPD_EVENT_DET			0x2120
+
+/* dpyx framer addr */
+#define DP_FRAMER_GLOBAL_CONFIG		0x2200
+#define DP_SW_RESET			0x2204
+#define DP_FRAMER_TU			0x2208
+#define DP_FRAMER_PXL_REPR		0x220c
+#define DP_FRAMER_SP			0x2210
+#define AUDIO_PACK_CONTROL		0x2214
+#define DP_VC_TABLE(x)			(0x2218 + ((x) << 2))
+#define DP_VB_ID			0x2258
+#define DP_MTPH_LVP_CONTROL		0x225c
+#define DP_MTPH_SYMBOL_VALUES		0x2260
+#define DP_MTPH_ECF_CONTROL		0x2264
+#define DP_MTPH_ACT_CONTROL		0x2268
+#define DP_MTPH_STATUS			0x226c
+#define DP_INTERRUPT_SOURCE		0x2270
+#define DP_INTERRUPT_MASK		0x2274
+#define DP_FRONT_BACK_PORCH		0x2278
+#define DP_BYTE_COUNT			0x227c
+
+/* dptx stream addr */
+#define MSA_HORIZONTAL_0		0x2280
+#define MSA_HORIZONTAL_1		0x2284
+#define MSA_VERTICAL_0			0x2288
+#define MSA_VERTICAL_1			0x228c
+#define MSA_MISC			0x2290
+#define STREAM_CONFIG			0x2294
+#define AUDIO_PACK_STATUS		0x2298
+#define VIF_STATUS			0x229c
+#define PCK_STUFF_STATUS_0		0x22a0
+#define PCK_STUFF_STATUS_1		0x22a4
+#define INFO_PACK_STATUS		0x22a8
+#define RATE_GOVERNOR_STATUS		0x22ac
+#define DP_HORIZONTAL			0x22b0
+#define DP_VERTICAL_0			0x22b4
+#define DP_VERTICAL_1			0x22b8
+#define DP_BLOCK_SDP			0x22bc
+
+/* dptx glbl addr */
+#define DPTX_LANE_EN			0x2300
+#define DPTX_ENHNCD			0x2304
+#define DPTX_INT_MASK			0x2308
+#define DPTX_INT_STATUS			0x230c
+
+/* dp aux addr */
+#define DP_AUX_HOST_CONTROL		0x2800
+#define DP_AUX_INTERRUPT_SOURCE		0x2804
+#define DP_AUX_INTERRUPT_MASK		0x2808
+#define DP_AUX_SWAP_INVERSION_CONTROL	0x280c
+#define DP_AUX_SEND_NACK_TRANSACTION	0x2810
+#define DP_AUX_CLEAR_RX			0x2814
+#define DP_AUX_CLEAR_TX			0x2818
+#define DP_AUX_TIMER_STOP		0x281c
+#define DP_AUX_TIMER_CLEAR		0x2820
+#define DP_AUX_RESET_SW			0x2824
+#define DP_AUX_DIVIDE_2M		0x2828
+#define DP_AUX_TX_PREACHARGE_LENGTH	0x282c
+#define DP_AUX_FREQUENCY_1M_MAX		0x2830
+#define DP_AUX_FREQUENCY_1M_MIN		0x2834
+#define DP_AUX_RX_PRE_MIN		0x2838
+#define DP_AUX_RX_PRE_MAX		0x283c
+#define DP_AUX_TIMER_PRESET		0x2840
+#define DP_AUX_NACK_FORMAT		0x2844
+#define DP_AUX_TX_DATA			0x2848
+#define DP_AUX_RX_DATA			0x284c
+#define DP_AUX_TX_STATUS		0x2850
+#define DP_AUX_RX_STATUS		0x2854
+#define DP_AUX_RX_CYCLE_COUNTER		0x2858
+#define DP_AUX_MAIN_STATES		0x285c
+#define DP_AUX_MAIN_TIMER		0x2860
+#define DP_AUX_AFE_OUT			0x2864
+
+/* crypto addr */
+#define CRYPTO_HDCP_REVISION		0x5800
+#define HDCP_CRYPTO_CONFIG		0x5804
+#define CRYPTO_INTERRUPT_SOURCE		0x5808
+#define CRYPTO_INTERRUPT_MASK		0x580c
+#define CRYPTO22_CONFIG			0x5818
+#define CRYPTO22_STATUS			0x581c
+#define SHA_256_DATA_IN			0x583c
+#define SHA_256_DATA_OUT_(x)		(0x5850 + ((x) << 2))
+#define AES_32_KEY_(x)			(0x5870 + ((x) << 2))
+#define AES_32_DATA_IN			0x5880
+#define AES_32_DATA_OUT_(x)		(0x5884 + ((x) << 2))
+#define CRYPTO14_CONFIG			0x58a0
+#define CRYPTO14_STATUS			0x58a4
+#define CRYPTO14_PRNM_OUT		0x58a8
+#define CRYPTO14_KM_0			0x58ac
+#define CRYPTO14_KM_1			0x58b0
+#define CRYPTO14_AN_0			0x58b4
+#define CRYPTO14_AN_1			0x58b8
+#define CRYPTO14_YOUR_KSV_0		0x58bc
+#define CRYPTO14_YOUR_KSV_1		0x58c0
+#define CRYPTO14_MI_0			0x58c4
+#define CRYPTO14_MI_1			0x58c8
+#define CRYPTO14_TI_0			0x58cc
+#define CRYPTO14_KI_0			0x58d0
+#define CRYPTO14_KI_1			0x58d4
+#define CRYPTO14_BLOCKS_NUM		0x58d8
+#define CRYPTO14_KEY_MEM_DATA_0		0x58dc
+#define CRYPTO14_KEY_MEM_DATA_1		0x58e0
+#define CRYPTO14_SHA1_MSG_DATA		0x58e4
+#define CRYPTO14_SHA1_V_VALUE_(x)	(0x58e8 + ((x) << 2))
+#define TRNG_CTRL			0x58fc
+#define TRNG_DATA_RDY			0x5900
+#define TRNG_DATA			0x5904
+
+/* cipher addr */
+#define HDCP_REVISION			0x60000
+#define INTERRUPT_SOURCE		0x60004
+#define INTERRUPT_MASK			0x60008
+#define HDCP_CIPHER_CONFIG		0x6000c
+#define AES_128_KEY_0			0x60010
+#define AES_128_KEY_1			0x60014
+#define AES_128_KEY_2			0x60018
+#define AES_128_KEY_3			0x6001c
+#define AES_128_RANDOM_0		0x60020
+#define AES_128_RANDOM_1		0x60024
+#define CIPHER14_KM_0			0x60028
+#define CIPHER14_KM_1			0x6002c
+#define CIPHER14_STATUS			0x60030
+#define CIPHER14_RI_PJ_STATUS		0x60034
+#define CIPHER_MODE			0x60038
+#define CIPHER14_AN_0			0x6003c
+#define CIPHER14_AN_1			0x60040
+#define CIPHER22_AUTH			0x60044
+#define CIPHER14_R0_DP_STATUS		0x60048
+#define CIPHER14_BOOTSTRAP		0x6004c
+
+#define APB_IRAM_PATH			BIT(2)
+#define APB_DRAM_PATH			BIT(1)
+#define APB_XT_RESET			BIT(0)
+
+/* mailbox */
+#define MB_OPCODE_ID			0
+#define MB_MODULE_ID			1
+#define MB_SIZE_MSB_ID			2
+#define MB_SIZE_LSB_ID			3
+#define MB_DATA_ID			4
+
+#define MB_MODULE_ID_DP_TX		0x01
+#define MB_MODULE_ID_HDCP_TX		0x07
+#define MB_MODULE_ID_HDCP_RX		0x08
+#define MB_MODULE_ID_HDCP_GENERAL	0x09
+#define MB_MODULE_ID_GENERAL		0x0a
+
+/* general opcode */
+#define GENERAL_MAIN_CONTROL            0x01
+#define GENERAL_TEST_ECHO               0x02
+#define GENERAL_BUS_SETTINGS            0x03
+#define GENERAL_TEST_ACCESS             0x04
+
+#define DPTX_SET_POWER_MNG			0x00
+#define DPTX_SET_HOST_CAPABILITIES		0x01
+#define DPTX_GET_EDID				0x02
+#define DPTX_READ_DPCD				0x03
+#define DPTX_WRITE_DPCD				0x04
+#define DPTX_ENABLE_EVENT			0x05
+#define DPTX_WRITE_REGISTER			0x06
+#define DPTX_READ_REGISTER			0x07
+#define DPTX_WRITE_FIELD			0x08
+#define DPTX_TRAINING_CONTROL			0x09
+#define DPTX_READ_EVENT				0x0a
+#define DPTX_READ_LINK_STAT			0x0b
+#define DPTX_SET_VIDEO				0x0c
+#define DPTX_SET_AUDIO				0x0d
+#define DPTX_GET_LAST_AUX_STAUS			0x0e
+#define DPTX_SET_LINK_BREAK_POINT		0x0f
+#define DPTX_FORCE_LANES			0x10
+#define DPTX_HPD_STATE				0x11
+
+#define DPTX_EVENT_ENABLE_HPD			BIT(0)
+#define DPTX_EVENT_ENABLE_TRAINING		BIT(1)
+
+#define LINK_TRAINING_NOT_ACTIVE		0
+#define LINK_TRAINING_RUN			1
+#define LINK_TRAINING_RESTART			2
+
+#define CONTROL_VIDEO_IDLE			0
+#define CONTROL_VIDEO_VALID			1
+
+#define VIF_BYPASS_INTERLACE			BIT(13)
+#define INTERLACE_FMT_DET			BIT(12)
+#define INTERLACE_DTCT_WIN			0x20
+
+#define DP_FRAMER_SP_INTERLACE_EN		BIT(2)
+#define DP_FRAMER_SP_HSP			BIT(1)
+#define DP_FRAMER_SP_VSP			BIT(0)
+
+/* capability */
+#define AUX_HOST_INVERT				3
+#define	FAST_LT_SUPPORT				1
+#define FAST_LT_NOT_SUPPORT			0
+#define LANE_MAPPING_NORMAL			0xe4
+#define LANE_MAPPING_FLIPPED			0x1b
+#define ENHANCED				1
+
+#define	FULL_LT_STARTED				BIT(0)
+#define FASE_LT_STARTED				BIT(1)
+#define CLK_RECOVERY_FINISHED			BIT(2)
+#define EQ_PHASE_FINISHED			BIT(3)
+#define FASE_LT_START_FINISHED			BIT(4)
+#define CLK_RECOVERY_FAILED			BIT(5)
+#define EQ_PHASE_FAILED				BIT(6)
+#define FASE_LT_FAILED				BIT(7)
+
+#define DPTX_HPD_EVENT				BIT(0)
+#define DPTX_TRAINING_EVENT			BIT(1)
+#define HDCP_TX_STATUS_EVENT			BIT(4)
+#define HDCP2_TX_IS_KM_STORED_EVENT		BIT(5)
+#define HDCP2_TX_STORE_KM_EVENT			BIT(6)
+#define HDCP_TX_IS_RECEIVER_ID_VALID_EVENT	BIT(7)
+
+#define EDID_LENGTH_BYTE			0
+#define EDID_SEGMENT_BUMBER			1
+#define EDID_DATA				2
+#define EDID_BLOCK_SIZE				128
+
+#define TU_SIZE					30
+
+/* audio */
+#define AUDIO_PACK_EN(x)			((x) << 8)
+#define SAMPLING_FREQ(x)			((x) << 16)
+#define ORIGINAL_SAMP_FREQ(x)			((x) << 24)
+#define SYNC_WR_TO_CH_ZERO			BIT(1)
+
+enum voltage_swing_level {
+	VOLTAGE_LEVEL_0,
+	VOLTAGE_LEVEL_1,
+	VOLTAGE_LEVEL_2,
+	VOLTAGE_LEVEL_3,
+};
+
+enum pre_emphasis_level {
+	PRE_EMPHASIS_LEVEL_0,
+	PRE_EMPHASIS_LEVEL_1,
+	PRE_EMPHASIS_LEVEL_2,
+	PRE_EMPHASIS_LEVEL_3,
+};
+
+enum pattern_set {
+	PRBS7		= BIT(0),
+	D10_2		= BIT(1),
+	TRAINING_PTN1	= BIT(2),
+	TRAINING_PTN2	= BIT(3),
+	DP_NONE		= BIT(4)
+};
+
+enum VIC_PXL_ENCODING_FORMAT {
+	PXL_RGB = 0x1,
+	YCBCR_4_4_4 = 0x2,
+	YCBCR_4_2_2 = 0x4,
+	YCBCR_4_2_0 = 0x8,
+	Y_ONLY = 0x10,
+};
+
+enum VIC_COLOR_DEPTH {
+	BCS_6 = 0x1,
+	BCS_8 = 0x2,
+	BCS_10 = 0x4,
+	BCS_12 = 0x8,
+	BCS_16 = 0x10,
+};
+
+enum VIC_BT_TYPE {
+	BT_601 = 0x0,
+	BT_709 = 0x1,
+};
+
+#endif /* _CDN_DP_REG_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
index edd7ec2..98302b3 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -969,7 +969,7 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
 		vop_dsp_hold_valid_irq_disable(vop);
 	}
 
-	pin_pol = 0x8;
+	pin_pol = (s->output_type == DRM_MODE_CONNECTOR_DisplayPort) ? 0 : 0x8;
 	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : 1;
 	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? 0 : (1 << 1);
 	VOP_CTRL_SET(vop, pin_pol, pin_pol);
@@ -991,6 +991,10 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
 		VOP_CTRL_SET(vop, mipi_pin_pol, pin_pol);
 		VOP_CTRL_SET(vop, mipi_en, 1);
 		break;
+	case DRM_MODE_CONNECTOR_DisplayPort:
+		VOP_CTRL_SET(vop, dp_pin_pol, pin_pol);
+		VOP_CTRL_SET(vop, dp_en, 1);
+		break;
 	default:
 		DRM_ERROR("unsupport connector_type[%d]\n", s->output_type);
 	}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
index ff4f52e..50a045c 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
@@ -45,6 +45,7 @@ struct vop_ctrl {
 	struct vop_reg edp_en;
 	struct vop_reg hdmi_en;
 	struct vop_reg mipi_en;
+	struct vop_reg dp_en;
 	struct vop_reg out_mode;
 	struct vop_reg dither_down;
 	struct vop_reg dither_up;
@@ -53,6 +54,7 @@ struct vop_ctrl {
 	struct vop_reg hdmi_pin_pol;
 	struct vop_reg edp_pin_pol;
 	struct vop_reg mipi_pin_pol;
+	struct vop_reg dp_pin_pol;
 
 	struct vop_reg htotal_pw;
 	struct vop_reg hact_st_end;
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
index 5b1ae1f..dcf172e 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
@@ -281,6 +281,7 @@ static const struct vop_data rk3288_vop = {
 static const struct vop_ctrl rk3399_ctrl_data = {
 	.standby = VOP_REG(RK3399_SYS_CTRL, 0x1, 22),
 	.gate_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 23),
+	.dp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 11),
 	.rgb_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 12),
 	.hdmi_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 13),
 	.edp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 14),
@@ -290,6 +291,7 @@ static const struct vop_ctrl rk3399_ctrl_data = {
 	.data_blank = VOP_REG(RK3399_DSP_CTRL0, 0x1, 19),
 	.out_mode = VOP_REG(RK3399_DSP_CTRL0, 0xf, 0),
 	.rgb_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 16),
+	.dp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 16),
 	.hdmi_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 20),
 	.edp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 24),
 	.mipi_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 28),
-- 
2.6.3

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

* [v5 PATCH 5/5] drm/rockchip: cdn-dp: add cdn DP support for rk3399
  2016-07-12 15:09 ` [v5 PATCH 5/5] drm/rockchip: cdn-dp: add cdn DP support for rk3399 Chris Zhong
@ 2016-07-13 13:59   ` Sean Paul
  2016-07-14  3:08     ` Chris Zhong
  0 siblings, 1 reply; 11+ messages in thread
From: Sean Paul @ 2016-07-13 13:59 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jul 12, 2016 at 8:09 AM, Chris Zhong <zyw@rock-chips.com> wrote:
> Add support for cdn DP controller which is embedded in the rk3399
> SoCs. The DP is compliant with DisplayPort Specification,
> Version 1.3, This IP is compatible with the rockchip type-c PHY IP.
> There is a uCPU in DP controller, it need a firmware to work,
> please put the firmware file to /lib/firmware/cdn/dptx.bin. The
> uCPU in charge of aux communication and link training, the host use
> mailbox to communicate with the ucpu.
> The dclk pin_pol of vop must not be invert for DP.
>
> Signed-off-by: Chris Zhong <zyw@rock-chips.com>
>
> ---
>
> Changes in v5:
> - alphabetical order
> - do not use long, use u32 or u64
> - return MODE_CLOCK_HIGH when requested > actual
> - Optimized Coding Style
> - add a formula to get better tu size and symbol value.
>
> Changes in v4:
> - use phy framework to control DP phy
> - support 2 phys
>
> Changes in v3:
> - use EXTCON_DISP_DP and EXTCON_DISP_DP_ALT cable to get dp port state.
> - reset spdif before config it
> - modify the firmware clk to 100Mhz
> - retry load firmware if fw file is requested too early
>
> Changes in v2:
> - Alphabetic order
> - remove excess error message
> - use define clk_rate
> - check all return value
> - remove dev_set_name(dp->dev, "cdn-dp");
> - use schedule_delayed_work
> - remove never-called functions
> - remove some unnecessary ()
>
> Changes in v1:
> - use extcon API
> - use hdmi-codec for the DP Asoc
> - do not initialize the "ret"
> - printk a err log when drm_of_encoder_active_endpoint_id
> - modify the dclk pin_pol to a single line
>
>  drivers/gpu/drm/rockchip/Kconfig            |   9 +
>  drivers/gpu/drm/rockchip/Makefile           |   1 +
>  drivers/gpu/drm/rockchip/cdn-dp-core.c      | 761 ++++++++++++++++++++++++++++
>  drivers/gpu/drm/rockchip/cdn-dp-core.h      | 113 +++++
>  drivers/gpu/drm/rockchip/cdn-dp-reg.c       | 740 +++++++++++++++++++++++++++
>  drivers/gpu/drm/rockchip/cdn-dp-reg.h       | 409 +++++++++++++++

Could you explain the file naming convention in the rk driver? We've
got analogix_dp-rockchip.c, dw_hdmi-rockchip.c, dw-mipi-dsi.c, and now
cdn-dp-(core|reg).[ch]. I'm honestly not sure whether these filenames
are consistent with the rest, but bleh.


>  drivers/gpu/drm/rockchip/rockchip_drm_vop.c |   6 +-
>  drivers/gpu/drm/rockchip/rockchip_drm_vop.h |   2 +
>  drivers/gpu/drm/rockchip/rockchip_vop_reg.c |   2 +
>  9 files changed, 2042 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.c
>  create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.h
>  create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.c
>  create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.h
>
> diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
> index d30bdc3..20da9a8 100644
> --- a/drivers/gpu/drm/rockchip/Kconfig
> +++ b/drivers/gpu/drm/rockchip/Kconfig
> @@ -25,6 +25,15 @@ config ROCKCHIP_ANALOGIX_DP
>           for the Analogix Core DP driver. If you want to enable DP
>           on RK3288 based SoC, you should selet this option.
>
> +config ROCKCHIP_CDN_DP
> +        tristate "Rockchip cdn DP"
> +        depends on DRM_ROCKCHIP
> +        help
> +         This selects support for Rockchip SoC specific extensions
> +         for the cdn Dp driver. If you want to enable Dp on

s/Dp/DP/

> +         RK3399 based SoC, you should selet this

s/selet/select/

> +         option.
> +
>  config ROCKCHIP_DW_HDMI
>          tristate "Rockchip specific extensions for Synopsys DW HDMI"
>          depends on DRM_ROCKCHIP
> diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
> index 05d0713..abdecd5 100644
> --- a/drivers/gpu/drm/rockchip/Makefile
> +++ b/drivers/gpu/drm/rockchip/Makefile
> @@ -7,6 +7,7 @@ rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
>  rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
>
>  obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
> +obj-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o
>  obj-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
>  obj-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi.o
>  obj-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
> new file mode 100644
> index 0000000..5b8a15e
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
> @@ -0,0 +1,761 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author: Chris Zhong <zyw@rock-chips.com>
> + *
> + * This software is licensed under the terms of the GNU General Public
> + * License version 2, as published by the Free Software Foundation, and
> + * may be copied, distributed, and modified under those terms.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_dp_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_of.h>
> +
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/extcon.h>
> +#include <linux/firmware.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/phy/phy.h>
> +
> +#include <sound/hdmi-codec.h>
> +
> +#include <video/of_videomode.h>
> +#include <video/videomode.h>

Doesn't seem like you use these.


> +
> +#include "cdn-dp-core.h"
> +#include "cdn-dp-reg.h"
> +#include "rockchip_drm_vop.h"
> +
> +#define connector_to_dp(c) \
> +               container_of(c, struct cdn_dp_device, connector)
> +
> +#define encoder_to_dp(c) \
> +               container_of(c, struct cdn_dp_device, encoder)
> +
> +/* dp grf register offset */
> +#define DP_VOP_SEL             0x6224
> +#define DP_SEL_VOP_LIT         BIT(12)
> +#define DP_CLK_RATE            100000000

If this clock rate never changes, it should probably live somewhere in
the clock driver.

> +#define WAIT_HPD_STABLE                300

Encode the units in these two define names, ie: DP_CLK_RATE_HZ,
WAIT_HPD_STABLE_MS

> +
> +static int cdn_dp_clk_enable(struct cdn_dp_device *dp)
> +{
> +       int ret;
> +
> +       ret = clk_prepare_enable(dp->pclk);
> +       if (ret < 0) {
> +               dev_err(dp->dev, "cannot enable dp pclk %d\n", ret);
> +               goto err_pclk;
> +       }
> +
> +       ret = clk_prepare_enable(dp->core_clk);
> +       if (ret < 0) {
> +               dev_err(dp->dev, "cannot enable core_clk %d\n", ret);
> +               goto err_core_clk;
> +       }
> +
> +       ret = clk_set_rate(dp->core_clk, DP_CLK_RATE);
> +       if (ret < 0) {
> +               dev_err(dp->dev, "cannot set dp core clk to %d %d\n",
> +                       DP_CLK_RATE, ret);
> +               goto err_set_rate;
> +       }
> +
> +       /* notice fw the clk freq value */

This might be clearer if it was rephrased to "update fw with the
current core clk frequency". I still think the rate should be set in
the clock driver, and perhaps you can query the rate to get the
current value.

> +       cdn_dp_set_fw_clk(dp, DP_CLK_RATE);
> +
> +       return 0;
> +
> +err_set_rate:
> +       clk_disable_unprepare(dp->core_clk);
> +err_core_clk:
> +       clk_disable_unprepare(dp->pclk);
> +err_pclk:
> +       return ret;
> +}
> +
> +static enum drm_connector_status
> +cdn_dp_connector_detect(struct drm_connector *connector, bool force)
> +{
> +       struct cdn_dp_device *dp = connector_to_dp(connector);
> +
> +       return dp->hpd_status ? connector_status_connected :
> +                               connector_status_disconnected;

If you just stored hpd_status as drm_connector_status, you could avoid
having to translate the bool.

> +}
> +
> +static void cdn_dp_connector_destroy(struct drm_connector *connector)
> +{
> +       drm_connector_unregister(connector);
> +       drm_connector_cleanup(connector);
> +}
> +
> +static struct drm_connector_funcs cdn_dp_atomic_connector_funcs = {
> +       .dpms = drm_atomic_helper_connector_dpms,
> +       .detect = cdn_dp_connector_detect,
> +       .destroy = cdn_dp_connector_destroy,
> +       .fill_modes = drm_helper_probe_single_connector_modes,
> +       .reset = drm_atomic_helper_connector_reset,
> +       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static int cdn_dp_connector_get_modes(struct drm_connector *connector)
> +{
> +       struct cdn_dp_device *dp = connector_to_dp(connector);
> +       struct edid *edid;
> +
> +       if (!dp->fw_loaded)

Can this actually happen? Seems like we shouldn't be returning
connected from detect if the fw isn't loaded, so this should never be
true. If that is the case, I think BUG_ON or WARN_ON would be more
appropriate here.


> +               return 0;
> +
> +       edid = drm_do_get_edid(connector, cdn_dp_get_edid_block, dp);
> +       if (edid) {
> +               dev_dbg(dp->dev, "got edid: width[%d] x height[%d]\n",
> +                       edid->width_cm, edid->height_cm);
> +
> +               dp->sink_has_audio = drm_detect_monitor_audio(edid);
> +               drm_mode_connector_update_edid_property(connector, edid);
> +               drm_add_edid_modes(connector, edid);
> +               /* Store the ELD */

This comment isn't useful

> +               drm_edid_to_eld(connector, edid);
> +               kfree(edid);
> +       } else {
> +               dev_dbg(dp->dev, "failed to get edid\n");

This seems to warrant a stronger level than dbg, perhaps warn. Also,
should this really return success?

> +       }
> +
> +       return 0;
> +}
> +
> +static struct drm_encoder *
> +       cdn_dp_connector_best_encoder(struct drm_connector *connector)
> +{
> +       struct cdn_dp_device *dp = connector_to_dp(connector);
> +
> +       return &dp->encoder;
> +}
> +
> +static int cdn_dp_connector_mode_valid(struct drm_connector *connector,
> +                                      struct drm_display_mode *mode)
> +{
> +       struct cdn_dp_device *dp = connector_to_dp(connector);
> +       struct drm_display_info *display_info = &dp->connector.display_info;
> +       u32 requested = mode->clock * display_info->bpc * 3 / 1000;
> +       u32 actual, rate;
> +
> +       rate = drm_dp_bw_code_to_link_rate(dp->link.rate);
> +       actual = rate * dp->link.num_lanes / 100;
> +
> +       /* efficiency is about 0.8 */
> +       actual = actual * 8 / 10;
> +
> +       if (requested > actual) {
> +               dev_dbg(dp->dev, "requested=%d, actual=%d, clock=%d\n",
> +                       requested, actual, mode->clock);
> +               return MODE_CLOCK_HIGH;
> +       }

This doesn't seem like something that is likely to happen given the
trained rate should be a function of the display that's attached. Is
that correct?

> +
> +       return MODE_OK;
> +}
> +
> +static struct drm_connector_helper_funcs cdn_dp_connector_helper_funcs = {
> +       .get_modes = cdn_dp_connector_get_modes,
> +       .best_encoder = cdn_dp_connector_best_encoder,
> +       .mode_valid = cdn_dp_connector_mode_valid,
> +};
> +
> +static void cdn_dp_commit(struct drm_encoder *encoder)
> +{
> +       struct cdn_dp_device *dp = encoder_to_dp(encoder);
> +
> +       if (cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE))
> +               return;
> +
> +       if (cdn_dp_config_video(dp)) {
> +               dev_err(dp->dev, "unable to config video\n");
> +               return;
> +       }
> +
> +       if (cdn_dp_set_video_status(dp, CONTROL_VIDEO_VALID))
> +               return;
> +
> +       dp->dpms_mode = DRM_MODE_DPMS_ON;
> +}
> +
> +static void cdn_dp_encoder_mode_set(struct drm_encoder *encoder,
> +                                   struct drm_display_mode *mode,
> +                                   struct drm_display_mode *adjusted)
> +{
> +       struct cdn_dp_device *dp = encoder_to_dp(encoder);
> +       struct drm_display_info *display_info = &dp->connector.display_info;
> +       struct rockchip_crtc_state *state;
> +       struct video_info *video = &dp->video_info;
> +       int ret, val;
> +
> +       switch (display_info->bpc) {
> +       case 16:
> +       case 12:
> +       case 10:
> +               video->color_depth = 10;
> +               break;
> +       case 6:
> +               video->color_depth = 6;
> +               break;
> +       default:
> +               video->color_depth = 8;
> +               break;
> +       }
> +
> +       video->color_fmt = PXL_RGB;
> +
> +       video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
> +       video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
> +
> +       ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder);
> +       if (ret < 0) {
> +               dev_err(dp->dev, "Could not get vop id, %d", ret);
> +               return;
> +       }
> +
> +       state = to_rockchip_crtc_state(encoder->crtc->state);
> +       if (ret) {
> +               val = DP_SEL_VOP_LIT | (DP_SEL_VOP_LIT << 16);
> +               state->output_mode = ROCKCHIP_OUT_MODE_P888;
> +       } else {
> +               val = DP_SEL_VOP_LIT << 16;
> +               state->output_mode = ROCKCHIP_OUT_MODE_AAAA;
> +       }
> +       ret = regmap_write(dp->grf, DP_VOP_SEL, val);
> +       if (ret != 0)
> +               dev_err(dp->dev, "Could not write to GRF: %d\n", ret);
> +
> +       memcpy(&dp->mode, adjusted, sizeof(*mode));
> +}
> +
> +static void cdn_dp_encoder_enable(struct drm_encoder *encoder)
> +{
> +       struct cdn_dp_device *dp = encoder_to_dp(encoder);
> +
> +       if (dp->dpms_mode != DRM_MODE_DPMS_ON)
> +               cdn_dp_commit(encoder);
> +}
> +
> +static void cdn_dp_encoder_disable(struct drm_encoder *encoder)
> +{
> +       struct cdn_dp_device *dp = encoder_to_dp(encoder);
> +
> +       if (dp->dpms_mode != DRM_MODE_DPMS_OFF) {
> +               cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE);
> +               dp->dpms_mode = DRM_MODE_DPMS_OFF;
> +       }
> +}
> +
> +static int
> +cdn_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);
> +
> +       s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
> +       s->output_type = DRM_MODE_CONNECTOR_DisplayPort;
> +
> +       return 0;
> +}
> +
> +static struct drm_encoder_helper_funcs cdn_dp_encoder_helper_funcs = {
> +       .mode_set = cdn_dp_encoder_mode_set,
> +       .enable = cdn_dp_encoder_enable,
> +       .disable = cdn_dp_encoder_disable,
> +       .atomic_check = cdn_dp_encoder_atomic_check,
> +};
> +
> +static struct drm_encoder_funcs cdn_dp_encoder_funcs = {
> +       .destroy = drm_encoder_cleanup,
> +};
> +
> +static int cdn_dp_firmware_init(struct cdn_dp_device *dp)
> +{
> +       int ret;
> +       const u32 *iram_data, *dram_data;
> +       const struct firmware *fw = dp->fw;
> +       const struct cdn_firmware_header *hdr;
> +
> +       if (dp->fw_loaded)
> +               return 0;
> +
> +       hdr = (struct cdn_firmware_header *)fw->data;
> +       if (fw->size != le32_to_cpu(hdr->size_bytes))
> +               return -EINVAL;
> +
> +       iram_data = (const u32 *)(fw->data + hdr->header_size);
> +       dram_data = (const u32 *)(fw->data + hdr->header_size + hdr->iram_size);
> +
> +       ret = cdn_dp_load_firmware(dp, iram_data, hdr->iram_size,
> +                                  dram_data, hdr->dram_size);
> +       if (ret)
> +               return ret;
> +
> +       release_firmware(fw);

Move this out of here and into the same scope as request_firmware so
there are no leaks.

> +
> +       ret = cdn_dp_active(dp, true);
> +       if (ret) {
> +               dev_err(dp->dev, "active ucpu failed: %d\n", ret);
> +               return ret;
> +       }
> +
> +       return cdn_dp_event_config(dp);
> +}
> +
> +static int cdn_dp_init(struct cdn_dp_device *dp)
> +{
> +       struct device *dev = dp->dev;
> +       struct device_node *np = dev->of_node;
> +       struct platform_device *pdev = to_platform_device(dev);
> +       struct resource *res;
> +       int ret;
> +
> +       dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
> +       if (IS_ERR(dp->grf)) {
> +               dev_err(dev, "cdn-dp needs rockchip,grf property\n");
> +               return PTR_ERR(dp->grf);
> +       }
> +
> +       dp->irq = platform_get_irq(pdev, 0);
> +       if (dp->irq < 0) {
> +               dev_err(dev, "cdn-dp can not get irq\n");
> +               return dp->irq;
> +       }
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       dp->regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(dp->regs)) {
> +               dev_err(dev, "ioremap reg failed\n");
> +               return PTR_ERR(dp->regs);
> +       }
> +
> +       dp->core_clk = devm_clk_get(dev, "core-clk");
> +       if (IS_ERR(dp->core_clk)) {
> +               dev_err(dev, "cannot get core_clk_dp\n");
> +               return PTR_ERR(dp->core_clk);
> +       }
> +
> +       dp->pclk = devm_clk_get(dev, "pclk");
> +       if (IS_ERR(dp->pclk)) {
> +               dev_err(dev, "cannot get pclk\n");
> +               return PTR_ERR(dp->pclk);
> +       }
> +
> +       dp->spdif_clk = devm_clk_get(dev, "spdif");
> +       if (IS_ERR(dp->spdif_clk)) {
> +               dev_err(dev, "cannot get spdif_clk\n");
> +               return PTR_ERR(dp->spdif_clk);
> +       }
> +
> +       dp->spdif_rst = devm_reset_control_get(dev, "spdif");
> +       if (IS_ERR(dp->spdif_rst)) {
> +               dev_err(dev, "no spdif reset control found\n");
> +               return PTR_ERR(dp->spdif_rst);
> +       }
> +
> +       dp->dpms_mode = DRM_MODE_DPMS_OFF;
> +
> +       ret = cdn_dp_clk_enable(dp);
> +       if (ret < 0)
> +               return ret;
> +
> +       dp_clock_reset_seq(dp);
> +
> +       return 0;
> +}
> +
> +static int cdn_dp_audio_hw_params(struct device *dev,
> +                                 struct hdmi_codec_daifmt *daifmt,
> +                                 struct hdmi_codec_params *params)
> +{
> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
> +       struct audio_info audio = {
> +               .sample_width = params->sample_width,
> +               .sample_rate = params->sample_rate,
> +               .channels = params->channels,
> +       };
> +       int ret;
> +
> +       if (!dp->encoder.crtc)
> +               return -ENODEV;
> +
> +       switch (daifmt->fmt) {
> +       case HDMI_I2S:
> +               audio.format = AFMT_I2S;
> +               break;
> +       case HDMI_SPDIF:
> +               audio.format = AFMT_SPDIF;
> +               break;
> +       default:
> +               dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt);
> +               return -EINVAL;
> +       }
> +
> +       ret = cdn_dp_audio_config_set(dp, &audio);
> +       if (!ret)
> +               dp->audio_info = audio;
> +
> +       return ret;
> +}
> +
> +static void cdn_dp_audio_shutdown(struct device *dev)
> +{
> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
> +       int ret = cdn_dp_audio_stop(dp, &dp->audio_info);
> +
> +       if (!ret)
> +               dp->audio_info.format = AFMT_UNUSED;
> +}
> +
> +static int cdn_dp_audio_digital_mute(struct device *dev, bool enable)
> +{
> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
> +
> +       return cdn_dp_audio_mute(dp, enable);
> +}
> +
> +static int cdn_dp_audio_get_eld(struct device *dev, uint8_t *buf, size_t len)
> +{
> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
> +       struct drm_mode_config *config = &dp->encoder.dev->mode_config;
> +       struct drm_connector *connector;
> +       int ret = -ENODEV;
> +
> +       mutex_lock(&config->mutex);
> +       list_for_each_entry(connector, &config->connector_list, head) {
> +               if (&dp->encoder == connector->encoder) {
> +                       memcpy(buf, connector->eld,
> +                              min(sizeof(connector->eld), len));
> +                       ret = 0;
> +               }
> +       }
> +       mutex_unlock(&config->mutex);
> +
> +       return ret;
> +}
> +
> +static const struct hdmi_codec_ops audio_codec_ops = {
> +       .hw_params = cdn_dp_audio_hw_params,
> +       .audio_shutdown = cdn_dp_audio_shutdown,
> +       .digital_mute = cdn_dp_audio_digital_mute,
> +       .get_eld = cdn_dp_audio_get_eld,
> +};
> +
> +static int cdn_dp_audio_codec_init(struct cdn_dp_device *dp,
> +                                  struct device *dev)
> +{
> +       struct hdmi_codec_pdata codec_data = {
> +               .i2s = 1,
> +               .spdif = 1,
> +               .ops = &audio_codec_ops,
> +               .max_i2s_channels = 8,
> +       };
> +
> +       dp->audio_pdev = platform_device_register_data(
> +                        dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO,
> +                        &codec_data, sizeof(codec_data));
> +
> +       return PTR_ERR_OR_ZERO(dp->audio_pdev);
> +}
> +
> +static void cdn_dp_get_state(struct cdn_dp_device *dp, struct extcon_dev *edev)
> +{
> +       bool dfp, dptx;
> +       u8 cap_lanes;
> +
> +       dfp = extcon_get_cable_state_(edev, EXTCON_USB_HOST);
> +       dptx = extcon_get_cable_state_(edev, EXTCON_DISP_DP);
> +
> +       if (dfp && dptx)
> +               cap_lanes = 2;
> +       else if (dptx)
> +               cap_lanes = 4;
> +       else
> +               cap_lanes = 0;
> +
> +       if (cap_lanes != dp->cap_lanes) {
> +               dp->cap_lanes = cap_lanes;
> +               schedule_delayed_work(&dp->event_wq, 0);
> +       }

A get function shouldn't be scheduling work. Keep the assignment here
and move the schedule into the pd_event function.

> +}
> +
> +static int cdn_dp_pd_event(struct notifier_block *nb,
> +                          unsigned long event, void *priv)
> +{
> +       struct cdn_dp_device *dp;
> +       struct extcon_dev *edev = priv;
> +       u8 i;
> +
> +       dp = container_of(nb, struct cdn_dp_device, event_nb);
> +
> +       for (i = 0; i < MAX_PHY; i++) {
> +               if (edev == dp->extcon[i]) {
> +                       dp->port_id = i;
> +                       break;
> +               }
> +       }
> +
> +       cdn_dp_get_state(dp, edev);

There's a race here. If the cap_lanes change in between scheduling the
work and the work function running (ie, plug/unplug), the work
function will have out-of-date information. You should call this from
the work function instead.


> +
> +       return 0;
> +}
> +
> +static void cdn_dp_pd_event_wq(struct work_struct *work)
> +{
> +       struct cdn_dp_device *dp;
> +       int ret;
> +
> +       dp = container_of(work, struct cdn_dp_device, event_wq.work);
> +
> +       ret = request_firmware(&dp->fw, "cdn/dptx.bin", dp->dev);

This filename needs to be abstracted into a #define.

You're also failing to call release_firmware in a bunch of cases
below. It needs to be sorted out.


> +       if (ret == -ENOENT && !dp->fw_loaded) {

So the case where ret == -ENOENT and dp->fw_loaded is valid? Or is that a bug?

I'm not convinced you need fw_loaded stored in cdn_dp_device. It seems
like something you could either get rid of (hopefully), or convert to
a local.

> +               /*
> +                * If can not find the file, retry to load the firmware in 1
> +                * second, if still failed after 1 minute, give up.
> +                */
> +               if (dp->fw_retry++ < 60) {
> +                       schedule_delayed_work(&dp->event_wq,
> +                                             msecs_to_jiffies(HZ));

I'd rather do this as an exponential back-off so if it doesn't find
the file, it waits progressively longer.

Perhaps something like:

#define MAX_FW_WAIT_SECS 64

[...]

if (dp->fw_wait <= MAX_FW_WAIT_SECS) {
  schedule_delayed_work(&dp_>event_wq, msecs_to_jiffies(dp->fw_wait * HZ));
  dp->fw_wait *= 2;
}

> +               }
> +
> +               dev_err(dp->dev, "failed to request firmware %d\n", ret);

Print the wait/retries.

> +               return;
> +       }
> +
> +       if (!dp->cap_lanes) {
> +               if (dp->phy_status[dp->port_id])
> +                       phy_power_off(dp->phy[dp->port_id]);
> +               dp->phy_status[dp->port_id] = 0;
> +               dp->hpd_status = false;
> +               drm_helper_hpd_irq_event(dp->drm_dev);
> +               return;
> +       }
> +
> +       if (!dp->phy_status[dp->port_id]) {
> +               ret = phy_power_on(dp->phy[dp->port_id]);
> +               if (ret)

Log something in this case

> +                       return;
> +
> +               msleep(WAIT_HPD_STABLE);

300ms is a long time, do we really need to wait that long?

> +       }
> +
> +       dp->phy_status[dp->port_id] = 1;

Do you really need phy_status? It seems like if things are
well-behaved you shouldn't need it. Of course, if you're turning
on/off multiple times, perhaps there's a bug elsewhere.

> +
> +       ret = cdn_dp_firmware_init(dp);
> +       if (ret)
> +               return;
> +
> +       /* read hpd status failed, or the hpd does not exist. */
> +       ret = cdn_dp_get_hpd_status(dp);
> +       if (ret <= 0)

Log something?

> +               return;
> +
> +       /*
> +        * Set the capability and start the training process once
> +        * the hpd is detected.
> +        */
> +       ret = cdn_dp_set_host_cap(dp);
> +       if (ret) {
> +               dev_err(dp->dev, "set host capabilities failed\n");

print ret

> +               return;
> +       }
> +
> +       ret = cdn_dp_training_start(dp);
> +       if (ret) {
> +               dev_err(dp->dev, "hw lt err:%d\n", ret);

s/lt/link training/

> +               return;
> +       }
> +
> +       ret = cdn_dp_get_lt_status(dp);

Change to cdn_dp_get_training_status

> +       if (ret) {
> +               dev_err(dp->dev, "hw lt get status failed\n");

print ret

> +               return;
> +       }
> +
> +       dev_info(dp->dev, "rate:%d, lanes:%d\n",
> +                dp->link.rate, dp->link.num_lanes);
> +

I think everything from cdn_dp_firmare_init to here should be moved
out of the wq and deferred until modeset time. That way you're
training the link at the same time as you're setting the mode, which
should obviate the need to check the clocks in mode_valid.

> +       dp->hpd_status = true;
> +       drm_helper_hpd_irq_event(dp->drm_dev);
> +}
> +
> +static int cdn_dp_bind(struct device *dev, struct device *master,
> +                      void *data)
> +{
> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
> +       struct drm_encoder *encoder;
> +       struct drm_connector *connector;
> +       struct drm_device *drm_dev = data;
> +       int ret, i;
> +
> +       ret = cdn_dp_init(dp);
> +       if (ret < 0)
> +               return ret;
> +
> +       dp->drm_dev = drm_dev;
> +
> +       encoder = &dp->encoder;
> +
> +       encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
> +                                                            dev->of_node);
> +       DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs);
> +
> +       ret = drm_encoder_init(drm_dev, encoder, &cdn_dp_encoder_funcs,
> +                              DRM_MODE_ENCODER_TMDS, NULL);
> +       if (ret) {
> +               DRM_ERROR("failed to initialize encoder with drm\n");
> +               return ret;
> +       }
> +
> +       drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs);
> +
> +       connector = &dp->connector;
> +       connector->polled = DRM_CONNECTOR_POLL_HPD;
> +       connector->dpms = DRM_MODE_DPMS_OFF;
> +
> +       ret = drm_connector_init(drm_dev, connector,
> +                                &cdn_dp_atomic_connector_funcs,
> +                                DRM_MODE_CONNECTOR_DisplayPort);
> +       if (ret) {
> +               DRM_ERROR("failed to initialize connector with drm\n");
> +               goto err_free_encoder;
> +       }
> +
> +       drm_connector_helper_add(connector, &cdn_dp_connector_helper_funcs);
> +
> +       ret = drm_mode_connector_attach_encoder(connector, encoder);
> +       if (ret) {
> +               DRM_ERROR("failed to attach connector and encoder\n");
> +               goto err_free_connector;
> +       }
> +
> +       cdn_dp_audio_codec_init(dp, dev);
> +
> +       dp->event_nb.notifier_call = cdn_dp_pd_event;
> +       INIT_DELAYED_WORK(&dp->event_wq, cdn_dp_pd_event_wq);
> +
> +       for (i = 0; i < MAX_PHY; i++) {
> +               if (IS_ERR(dp->extcon[i]))
> +                       continue;
> +
> +               ret = extcon_register_notifier(dp->extcon[i], EXTCON_DISP_DP,
> +                                              &dp->event_nb);
> +               if (ret) {
> +                       dev_err(dev, "regitster EXTCON_DISP_DP notifier err\n");
> +                       return ret;
> +               }
> +
> +               cdn_dp_get_state(dp, dp->extcon[i]);
> +       }
> +
> +       return 0;
> +
> +err_free_connector:
> +       drm_connector_cleanup(connector);
> +err_free_encoder:
> +       drm_encoder_cleanup(encoder);
> +       return ret;
> +}
> +
> +static void cdn_dp_unbind(struct device *dev, struct device *master, void *data)
> +{
> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
> +       struct drm_encoder *encoder = &dp->encoder;
> +       int i;
> +
> +       platform_device_unregister(dp->audio_pdev);
> +       cdn_dp_encoder_disable(encoder);
> +       encoder->funcs->destroy(encoder);
> +       drm_connector_unregister(&dp->connector);
> +       drm_connector_cleanup(&dp->connector);

just call connector->destroy() here

> +       drm_encoder_cleanup(encoder);

this is already called by encoder->destroy()

> +
> +       for (i = 0; i < MAX_PHY; i++) {
> +               if (!IS_ERR(dp->extcon[i]))
> +                       extcon_unregister_notifier(dp->extcon[i], EXTCON_USB,
> +                                                  &dp->event_nb);
> +       }
> +}
> +
> +static const struct component_ops cdn_dp_component_ops = {
> +       .bind = cdn_dp_bind,
> +       .unbind = cdn_dp_unbind,
> +};
> +
> +static int cdn_dp_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct cdn_dp_device *dp;
> +       u8 count = 0;
> +       int i;
> +
> +       dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
> +       if (!dp)
> +               return -ENOMEM;
> +       dp->dev = dev;
> +
> +       for (i = 0; i < MAX_PHY; i++) {
> +               dp->extcon[i] = extcon_get_edev_by_phandle(dev, i);
> +               dp->phy[i] = devm_of_phy_get_by_index(dev, dev->of_node, i);
> +
> +               if (!IS_ERR(dp->extcon[i]) && !IS_ERR(dp->phy[i]))
> +                       count++;
> +
> +               if (PTR_ERR(dp->extcon[i]) == -EPROBE_DEFER ||
> +                   PTR_ERR(dp->phy[i]) == -EPROBE_DEFER)
> +                       return -EPROBE_DEFER;
> +       }
> +
> +       if (!count) {
> +               dev_err(dev, "missing extcon or phy\n");
> +               return -EINVAL;
> +       }
> +
> +       dev_set_drvdata(dev, dp);
> +
> +       return component_add(dev, &cdn_dp_component_ops);
> +}
> +
> +static int cdn_dp_remove(struct platform_device *pdev)
> +{
> +       component_del(&pdev->dev, &cdn_dp_component_ops);
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id cdn_dp_dt_ids[] = {
> +       { .compatible = "rockchip,rk3399-cdn-dp" },
> +       {}
> +};
> +
> +MODULE_DEVICE_TABLE(of, cdn_dp_dt_ids);
> +
> +static struct platform_driver cdn_dp_driver = {
> +       .probe = cdn_dp_probe,
> +       .remove = cdn_dp_remove,
> +       .driver = {
> +                  .name = "cdn-dp",
> +                  .owner = THIS_MODULE,
> +                  .of_match_table = of_match_ptr(cdn_dp_dt_ids),
> +       },
> +};
> +
> +module_platform_driver(cdn_dp_driver);
> +
> +MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
> +MODULE_DESCRIPTION("cdn DP Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h
> new file mode 100644
> index 0000000..31ba22d
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h
> @@ -0,0 +1,113 @@
> +/*
> + * Copyright (C) 2016 Chris Zhong <zyw@rock-chips.com>
> + * Copyright (C) 2016 ROCKCHIP, Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#ifndef _ROCKCHIP_EDP_CORE_H
> +#define _ROCKCHIP_EDP_CORE_H

I think the include guard should match the filename

> +
> +#include <drm/drmP.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_dp_helper.h>
> +#include <drm/drm_panel.h>
> +#include "rockchip_drm_drv.h"
> +#include "cdn-dp-reg.h"
> +
> +#define MAX_PHY                2

This should probably be stored in the .data member of of_device_id.

> +
> +enum AUDIO_FORMAT {

enum audio_format

> +       AFMT_I2S = 0,
> +       AFMT_SPDIF = 1,
> +       AFMT_UNUSED,
> +};
> +
> +struct audio_info {
> +       enum AUDIO_FORMAT format;
> +       int sample_rate;
> +       int channels;
> +       int sample_width;
> +};
> +
> +struct video_info {
> +       bool h_sync_polarity;
> +       bool v_sync_polarity;
> +       bool interlaced;
> +       int color_depth;
> +       enum VIC_PXL_ENCODING_FORMAT color_fmt;
> +};
> +
> +struct cdn_firmware_header {
> +       u32 size_bytes; /* size of the entire header+image(s) in bytes */
> +       u32 header_size; /* size of just the header in bytes */
> +       u32 iram_size; /* size of iram */
> +       u32 dram_size; /* size of dram */
> +};
> +
> +struct cdn_dp_device {
> +       struct device *dev;
> +       struct drm_device *drm_dev;
> +       struct drm_connector connector;
> +       struct drm_encoder encoder;
> +       struct drm_display_mode mode;
> +       struct platform_device *audio_pdev;
> +
> +       const struct firmware *fw;      /* cdn dp firmware */
> +       unsigned int fw_version;        /* cdn fw version */
> +       u32 fw_retry;
> +       bool fw_loaded;
> +       void __iomem *regs;
> +       struct regmap *grf;
> +       unsigned int irq;
> +       struct clk *core_clk;
> +       struct clk *pclk;
> +       struct clk *spdif_clk;
> +       struct reset_control *spdif_rst;
> +       struct audio_info audio_info;
> +       struct video_info video_info;
> +       struct extcon_dev *extcon[MAX_PHY];
> +       struct phy *phy[MAX_PHY];
> +       struct notifier_block event_nb;
> +       struct delayed_work event_wq;
> +
> +       u8 port_id;
> +       u8 cap_lanes;
> +       bool hpd_status;
> +       bool phy_status[MAX_PHY];
> +
> +       int dpms_mode;
> +       struct drm_dp_link link;
> +       bool sink_has_audio;
> +};
> +
> +void dp_clock_reset_seq(struct cdn_dp_device *dp);

cdn_ prefix here?

> +
> +void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, int clk);
> +int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
> +                        u32 i_size, const u32 *d_mem, u32 d_size);
> +int cdn_dp_active(struct cdn_dp_device *dp, bool enable);
> +int cdn_dp_set_host_cap(struct cdn_dp_device *dp);
> +int cdn_dp_event_config(struct cdn_dp_device *dp);
> +int cdn_dp_get_event(struct cdn_dp_device *dp);
> +int cdn_dp_get_hpd_status(struct cdn_dp_device *dp);
> +int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value);
> +int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr);
> +int cdn_dp_get_edid_block(void *dp, u8 *edid,
> +                         unsigned int block, size_t length);
> +int cdn_dp_training_start(struct cdn_dp_device *dp);
> +int cdn_dp_get_lt_status(struct cdn_dp_device *dp);
> +int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active);
> +int cdn_dp_config_video(struct cdn_dp_device *dp);
> +int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio);
> +int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable);
> +int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info *audio);
> +

I really don't like that you've declared these in core.h, yet defined
them in reg.c.

> +#endif  /* _ROCKCHIP_EDP_CORE_H */

Make sure you update here too


> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.c b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
> new file mode 100644
> index 0000000..8d5becd
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
> @@ -0,0 +1,740 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author: Chris Zhong <zyw@rock-chips.com>
> + *
> + * This software is licensed under the terms of the GNU General Public
> + * License version 2, as published by the Free Software Foundation, and
> + * may be copied, distributed, and modified under those terms.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/reset.h>
> +
> +#include "cdn-dp-core.h"
> +#include "cdn-dp-reg.h"
> +
> +#define CDN_DP_SPDIF_CLK               200000000
> +#define FW_ALIVE_TIMEOUT_US            1000000
> +#define MAILBOX_TIMEOUT_US             5000000
> +
> +void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, int clk)
> +{
> +       writel(clk / 1000000, dp->regs + SW_CLK_H);
> +}
> +
> +void dp_clock_reset_seq(struct cdn_dp_device *dp)
> +{
> +       writel(0xfff, dp->regs + SOURCE_DPTX_CAR);
> +       writel(0x7, dp->regs + SOURCE_PHY_CAR);
> +       writel(0xf, dp->regs + SOURCE_PKT_CAR);
> +       writel(0xff, dp->regs + SOURCE_AIF_CAR);
> +       writel(0xf, dp->regs + SOURCE_CIPHER_CAR);
> +       writel(0x3, dp->regs + SOURCE_CRYPTO_CAR);
> +       writel(0, dp->regs + APB_INT_MASK);

Pull these hardcoded values out into #defines

> +}
> +
> +static int cdn_dp_mailbox_read(struct cdn_dp_device *dp)
> +{
> +       int val, ret;
> +
> +       if (!dp->fw_loaded)
> +               return 0;

This seems like an error. Return -errno and log a message

> +
> +       ret = readx_poll_timeout(readl, dp->regs + MAILBOX_EMPTY_ADDR,
> +                                val, !val, 1000, MAILBOX_TIMEOUT_US);

Pull the 1000 out into a #define (here and below)

> +       if (ret < 0) {
> +               dev_err(dp->dev, "failed to read mailbox, keep alive = %x\n",
> +                       readl(dp->regs + KEEP_ALIVE));
> +               return ret;
> +       }
> +
> +       return readl(dp->regs + MAILBOX0_RD_DATA) & 0xff;
> +}
> +
> +static int cdp_dp_mailbox_write(struct cdn_dp_device *dp, u8 val)
> +{
> +       int ret, full;
> +
> +       if (!dp->fw_loaded)
> +               return 0;

Same here

> +
> +       ret = readx_poll_timeout(readl, dp->regs + MAILBOX_FULL_ADDR,
> +                                full, !full, 1000, MAILBOX_TIMEOUT_US);
> +       if (ret < 0) {
> +               dev_err(dp->dev, "mailbox is full, keep alive = %x\n",
> +                       readl(dp->regs + KEEP_ALIVE));
> +               return ret;
> +       }
> +
> +       writel(val, dp->regs + MAILBOX0_WR_DATA);
> +
> +       return 0;
> +}
> +
> +static int cdn_dp_mailbox_response(struct cdn_dp_device *dp, u8 module_id,

I think _receive would be a better name than _response

> +                                  u8 opcode, u8 *buff, u8 buff_size)
> +{
> +       int ret, i = 0;
> +       u8 header[4];
> +
> +       /* read the header of the message */
> +       while (i < 4) {

for (i = 0; i < 4; i++) {

> +               ret = cdn_dp_mailbox_read(dp);
> +               if (ret < 0)
> +                       return ret;
> +
> +               header[i++] = ret;

header[i] = ret;

> +       }
> +
> +       if (opcode != header[0] || module_id != header[1] ||
> +           buff_size != ((header[2] << 8) | header[3])) {

pull header[2] << 8 | header[3] out into a local, mbox_size.

> +               dev_err(dp->dev, "mailbox response failed");
> +
> +               /*
> +                * If the message in mailbox is not what we want, we need to
> +                * clear the mailbox by read.
> +                */
> +               i = (header[2] << 8) | header[3];
> +               while (i--)

for (i = 0; i < mbox_size; i++)

> +                       if (cdn_dp_mailbox_read(dp) < 0)
> +                               break;
> +
> +               return -EINVAL;
> +       }
> +
> +       i = 0;
> +       while (i < buff_size) {

for (i = 0; i < buff_size; i++) {

> +               ret = cdn_dp_mailbox_read(dp);
> +               if (ret < 0)
> +                       return ret;
> +
> +               buff[i++] = ret;

 buff[i] = ret;

> +       }
> +
> +       return 0;


Are you always guaranteed to have the entire message in the mailbox?
If not, you might consider supporting partial messages and returning
the length read.

> +}
> +
> +static int cdn_dp_mailbox_send(struct cdn_dp_device *dp, u8 module_id,
> +                              u8 opcode, u16 size, u8 *message)
> +{
> +       u8 header[4];
> +       int ret, i;
> +
> +       header[0] = opcode;
> +       header[1] = module_id;
> +       header[2] = size >> 8;

(size >> 8) & 0xff;

> +       header[3] = size & 0xff;
> +
> +       for (i = 0; i < 4; i++) {
> +               ret = cdp_dp_mailbox_write(dp, header[i]);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       while (size--) {

for (i = 0; i < size; i++) {

> +               ret = cdp_dp_mailbox_write(dp, *message++);

message[i]

> +               if (ret)

Log something here?

> +                       return ret;
> +       }
> +
> +       return 0;

Perhaps consider returning the number of bytes that were sent.

> +}
> +
> +static int cdn_dp_reg_write(struct cdn_dp_device *dp, u16 addr, u32 val)
> +{
> +       u8 msg[6];
> +
> +       msg[0] = (addr >> 8) & 0xff;
> +       msg[1] = addr & 0xff;
> +       msg[2] = (val >> 24) & 0xff;
> +       msg[3] = (val >> 16) & 0xff;
> +       msg[4] = (val >> 8) & 0xff;
> +       msg[5] = val & 0xff;
> +       return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_REGISTER,
> +                                  ARRAY_SIZE(msg), msg);
> +}
> +
> +int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
> +                        u32 i_size, const u32 *d_mem, u32 d_size)
> +{
> +       int i, reg, ret;

reg should be u32

> +
> +       /* reset ucpu before load firmware*/
> +       writel(APB_IRAM_PATH | APB_DRAM_PATH | APB_XT_RESET,
> +              dp->regs + APB_CTRL);
> +
> +       for (i = 0; i < i_size; i += 4)
> +               writel(*i_mem++, dp->regs + ADDR_IMEM + i);
> +
> +       for (i = 0; i < d_size; i += 4)
> +               writel(*d_mem++, dp->regs + ADDR_DMEM + i);
> +
> +       /* un-reset ucpu */
> +       writel(0, dp->regs + APB_CTRL);
> +
> +       /* check the keep alive register to make sure fw working */
> +       ret = readx_poll_timeout(readl, dp->regs + KEEP_ALIVE,
> +                                reg, reg, 2000, FW_ALIVE_TIMEOUT_US);
> +       if (ret < 0) {
> +               dev_err(dp->dev, "failed to loaded the FW reg = %x\n", reg);
> +               return -EINVAL;
> +       }
> +
> +       reg = readl(dp->regs + VER_L) & 0xff;
> +       dp->fw_version = reg;
> +       reg = readl(dp->regs + VER_H) & 0xff;
> +       dp->fw_version |= reg << 8;
> +       reg = readl(dp->regs + VER_LIB_L_ADDR) & 0xff;
> +       dp->fw_version |= reg << 16;
> +       reg = readl(dp->regs + VER_LIB_H_ADDR) & 0xff;
> +       dp->fw_version |= reg << 24;
> +

Are there any sanity checks you can do on the fw_version before returning?

> +       dp->fw_loaded = 1;
> +
> +       return 0;
> +}
> +
> +int cdn_dp_reg_write_bit(struct cdn_dp_device *dp, u16 addr, u8 start_bit,
> +                        u8 bits_no, u32 val)
> +{
> +       u8 field[8];
> +
> +       field[0] = (addr >> 8) & 0xff;
> +       field[1] = addr & 0xff;
> +       field[2] = start_bit;
> +       field[3] = bits_no;
> +       field[4] = (val >> 24) & 0xff;
> +       field[5] = (val >> 16) & 0xff;
> +       field[6] = (val >> 8) & 0xff;
> +       field[7] = val & 0xff;
> +
> +       return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_FIELD,
> +                           sizeof(field), field);
> +}
> +
> +int cdn_dp_active(struct cdn_dp_device *dp, bool enable)
> +{
> +       u8 active = enable ? 1 : 0;
> +       u8 resp;
> +       int ret;
> +
> +       /* set firmware status, 1: avtive; 0: standby */

s/avtive/active/

> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_GENERAL,
> +                                 GENERAL_MAIN_CONTROL, 1, &active);
> +       if (ret)
> +               return ret;
> +
> +       ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_GENERAL,
> +                                     GENERAL_MAIN_CONTROL, &resp, 1);
> +       if (ret)
> +               return ret;
> +
> +       return resp ? 0 : -EINVAL;
> +}
> +
> +int cdn_dp_set_host_cap(struct cdn_dp_device *dp)
> +{
> +       u8 msg[8];
> +       int ret;
> +
> +       msg[0] = DP_LINK_BW_5_4;

Why is this always 5.4GHz? Does the hw support any other rates?

> +       msg[1] = dp->cap_lanes;
> +       msg[2] = VOLTAGE_LEVEL_2;
> +       msg[3] = PRE_EMPHASIS_LEVEL_3;
> +       msg[4] = PRBS7 | D10_2 | TRAINING_PTN1 | TRAINING_PTN2;

You don't need to use training pattern 3 for hbr modes?

> +       msg[5] = FAST_LT_NOT_SUPPORT;
> +       msg[6] = LANE_MAPPING_NORMAL;
> +       msg[7] = ENHANCED;
> +
> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
> +                                 DPTX_SET_HOST_CAPABILITIES,
> +                                 ARRAY_SIZE(msg), msg);

ARRAY_SIZE counts the number of elements, not the size, so I think
you're better off using sizeof instead. This applies to the rest of
the dp_mailbox_send instances as well.

> +       if (ret)
> +               return ret;
> +
> +       ret = cdn_dp_reg_write(dp, DP_AUX_SWAP_INVERSION_CONTROL,
> +                              AUX_HOST_INVERT);
> +       if (ret)
> +               return ret;
> +
> +       return 0;
> +}
> +
> +int cdn_dp_event_config(struct cdn_dp_device *dp)
> +{
> +       u8 msg[5] = {0, 0, 0, 0, 0};

memset(msg, 0, sizeof(msg));

> +
> +       msg[0] = DPTX_EVENT_ENABLE_HPD | DPTX_EVENT_ENABLE_TRAINING;
> +
> +       return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_ENABLE_EVENT,
> +                                   ARRAY_SIZE(msg), msg);
> +}
> +
> +int cdn_dp_get_event(struct cdn_dp_device *dp)

Should return u32

> +{
> +       return readl(dp->regs + SW_EVENTS0);
> +}
> +
> +int cdn_dp_get_hpd_status(struct cdn_dp_device *dp)
> +{
> +       u8 status;
> +       int ret;
> +
> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_HPD_STATE,
> +                                 0, NULL);
> +       if (ret)
> +               return ret;
> +
> +       ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
> +                                     DPTX_HPD_STATE, &status, 1);
> +       if (ret)
> +               return ret;
> +
> +       return status;
> +}
> +
> +int cdn_dp_get_edid_block(void *data, u8 *edid,
> +                         unsigned int block, size_t length)
> +{
> +       struct cdn_dp_device *dp = data;
> +       u8 msg[2], reg[EDID_DATA + EDID_BLOCK_SIZE];
> +       int ret;
> +
> +       if (length != EDID_BLOCK_SIZE)
> +               return -EINVAL;
> +
> +       msg[0] = block / 2;
> +       msg[1] = block % 2;
> +
> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_GET_EDID,
> +                                 2, msg);

sizeof(msg), msg);

> +       if (ret)
> +               return ret;
> +
> +       ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
> +                                     DPTX_GET_EDID, reg,
> +                                     EDID_DATA + EDID_BLOCK_SIZE);
> +       if (ret)
> +               return ret;
> +
> +       if (reg[EDID_LENGTH_BYTE] != EDID_BLOCK_SIZE ||
> +           reg[EDID_SEGMENT_BUMBER] != block / 2) {
> +               dev_err(dp->dev, "edid block size err\n");
> +               return -EINVAL;
> +       }
> +
> +       memcpy(edid, &reg[EDID_DATA], EDID_BLOCK_SIZE);

You can avoid the intermediate reg[] buffer if you restructure the
_response/_receive mailbox function. I'd suggest splitting it into a
_validate_response() function that reads the header and makes sure the
lengths match. The second part would be a _read_response() which
simply reads the specified number of bytes from the mailbox. For
convenience, you could make cdn_dp_mailbox_receive() a helper which
calls both validate and read for times when you're reading the same
number as you're validating.

So in this function, you'd validate the response has EDID_DATA +
EDID_BLOCK_SIZE bytes, then read the EDID_DATA prefix into a local,
then read EDID_BLOCK_DATA directly into edid.

I think you'll also need a _drain function to clear out the mailbox in
case of error (otherwise the next read will fail).

> +
> +       return 0;
> +}
> +
> +int cdn_dp_training_start(struct cdn_dp_device *dp)
> +{
> +       u8 msg, event[2];
> +       unsigned long timeout;
> +       int ret;
> +
> +       msg = LINK_TRAINING_RUN;
> +
> +       /* start training */
> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_TRAINING_CONTROL,
> +                                 1, &msg);
> +       if (ret)
> +               return ret;
> +
> +       /* the whole training should finish in 500ms */
> +       timeout = jiffies + msecs_to_jiffies(500);

Pull 500 out into a #define

> +       while (1) {

while(time_before(jiffies, timeout))

> +               msleep(20);

Pull 20 out into a #define

> +               ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
> +                                         DPTX_READ_EVENT, 0, NULL);
> +               if (ret)
> +                       return ret;
> +
> +               ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
> +                                             DPTX_READ_EVENT, event, 2);
> +               if (ret)
> +                       return ret;
> +
> +               if (event[1] & EQ_PHASE_FINISHED)
> +                       break;

return 0

> +
> +               if (time_after(jiffies, timeout))
> +                       return -ETIMEDOUT;
> +       }
> +
> +       return 0;

return -ETIMEDOUT

> +}
> +
> +int cdn_dp_get_lt_status(struct cdn_dp_device *dp)

s/lt/training/

> +{
> +       u8 status[10];
> +       int ret;
> +
> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_READ_LINK_STAT,
> +                                 0, NULL);
> +       if (ret)
> +               return ret;
> +
> +       ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
> +                                     DPTX_READ_LINK_STAT, status, 10);

s/10/sizeof(status)/

> +       if (ret)
> +               return ret;
> +
> +       dp->link.rate = status[0];
> +       dp->link.num_lanes = status[1];
> +
> +       return 0;
> +}
> +
> +int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active)
> +{
> +       u8 msg;
> +
> +       msg = !!active;
> +
> +       return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_SET_VIDEO,
> +                                  1, &msg);

s/1/sizeof(msg)/

> +}
> +
> +static int cdn_dp_get_msa_misc(struct video_info *video,
> +                              struct drm_display_mode *mode)
> +{
> +       u8 val0, val1;

u8 val[2];

> +       u32 msa_misc;
> +
> +       switch (video->color_fmt) {
> +       case PXL_RGB:
> +       case Y_ONLY:
> +               val0 = 0;
> +               break;

Perhaps this would be better as listed under the default case.

> +       case YCBCR_4_4_4:
> +               /* set default color space conversion to BT601 */
> +               val0 = 6 + BT_601 * 8;
> +               break;
> +       case YCBCR_4_2_2:
> +               /* set default color space conversion to BT601 */
> +               val0 = 5 + BT_601 * 8;
> +               break;
> +       case YCBCR_4_2_0:
> +               val0 = 5;
> +               break;
> +       };
> +
> +       switch (video->color_depth) {
> +       case 6:
> +               val1 = 0;
> +               break;
> +       case 8:
> +               val1 = 1;
> +               break;
> +       case 10:
> +               val1 = 2;
> +               break;
> +       case 12:
> +               val1 = 3;
> +               break;
> +       case 16:
> +               val1 = 4;
> +               break;
> +       };
> +
> +       msa_misc = 2 * val0 + 32 * val1 +
> +                  ((video->color_fmt == Y_ONLY) ? (1 << 14) : 0);

Perhaps a stupid question, but is this documented anywhere? If not,
could you add a comment describing what you're doing?

> +
> +       return msa_misc;
> +}
> +
> +int cdn_dp_config_video(struct cdn_dp_device *dp)
> +{
> +       struct video_info *video = &dp->video_info;
> +       struct drm_display_mode *mode = &dp->mode;
> +       u64 symbol;
> +       u32 val, link_rate;
> +       u8 bit_per_pix;
> +       u8 tu_size_reg = TU_SIZE;
> +       int ret;
> +
> +       bit_per_pix = (video->color_fmt == YCBCR_4_2_2) ?
> +                     (video->color_depth * 2) : (video->color_depth * 3);
> +
> +       link_rate = drm_dp_bw_code_to_link_rate(dp->link.rate);
> +
> +       val = VIF_BYPASS_INTERLACE;
> +

remove extra line

> +       ret = cdn_dp_reg_write(dp, BND_HSYNC2VSYNC, val);
> +       if (ret)
> +               return ret;
> +
> +       ret = cdn_dp_reg_write(dp, HSYNC2VSYNC_POL_CTRL, 0);
> +       if (ret)
> +               return ret;
> +
> +       /* get a best tu_size and symbol */
> +       do {
> +               tu_size_reg += 2;
> +               symbol = tu_size_reg * mode->clock * bit_per_pix;
> +               symbol /= dp->link.num_lanes * link_rate * 8;
> +       } while ((symbol == 1) || (tu_size_reg - symbol < 4));
> +
> +       val = symbol + (tu_size_reg << 8);
> +

remove extra line

> +       ret = cdn_dp_reg_write(dp, DP_FRAMER_TU, val);
> +       if (ret)
> +               return ret;
> +
> +       switch (video->color_depth) {
> +       case 6:
> +               val = BCS_6;
> +               break;
> +       case 8:
> +               val = BCS_8;
> +               break;
> +       case 10:
> +               val = BCS_10;
> +               break;
> +       case 12:
> +               val = BCS_12;
> +               break;
> +       case 16:
> +               val = BCS_16;
> +               break;
> +       };
> +
> +       val += video->color_fmt << 8;
> +       ret = cdn_dp_reg_write(dp, DP_FRAMER_PXL_REPR, val);
> +       if (ret)
> +               return ret;
> +
> +       val = video->h_sync_polarity ? DP_FRAMER_SP_HSP : 0;
> +       val |= video->v_sync_polarity ? DP_FRAMER_SP_VSP : 0;
> +       ret = cdn_dp_reg_write(dp, DP_FRAMER_SP, val);
> +       if (ret)
> +               return ret;
> +
> +       val = (mode->hsync_start - mode->hdisplay) << 16;
> +       val |= mode->htotal - mode->hsync_end;
> +       ret = cdn_dp_reg_write(dp, DP_FRONT_BACK_PORCH, val);
> +       if (ret)
> +               return ret;
> +
> +       val = mode->hdisplay * bit_per_pix / 8;
> +       ret = cdn_dp_reg_write(dp, DP_BYTE_COUNT, val);
> +       if (ret)
> +               return ret;
> +
> +       val = mode->htotal | ((mode->htotal - mode->hsync_start) << 16);
> +       ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_0, val);
> +       if (ret)
> +               return ret;
> +
> +       val = mode->hsync_end - mode->hsync_start;
> +       val |= (mode->hdisplay << 16) | (video->h_sync_polarity << 15);
> +       ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_1, val);
> +       if (ret)
> +               return ret;
> +
> +       val = mode->vtotal;
> +       val |= ((mode->vtotal - mode->vsync_start) << 16);
> +

remove extra line

> +       ret = cdn_dp_reg_write(dp, MSA_VERTICAL_0, val);
> +       if (ret)
> +               return ret;
> +
> +       val = mode->vsync_end - mode->vsync_start;
> +       val |= mode->vdisplay << 16;
> +       val |= (video->v_sync_polarity << 15);

Make this consistent with how you assigned val for MSA_HORIZONTAL_1
(ie: split both or combine both)

> +       ret = cdn_dp_reg_write(dp, MSA_VERTICAL_1, val);
> +       if (ret)
> +               return ret;
> +
> +       val = cdn_dp_get_msa_misc(video, mode);
> +       ret = cdn_dp_reg_write(dp, MSA_MISC, val);
> +       if (ret)
> +               return ret;
> +
> +       ret = cdn_dp_reg_write(dp, STREAM_CONFIG, 1);
> +       if (ret)
> +               return ret;
> +
> +       val = mode->hsync_end - mode->hsync_start;
> +       val |= (mode->hdisplay << 16);
> +       ret = cdn_dp_reg_write(dp, DP_HORIZONTAL, val);
> +       if (ret)
> +               return ret;
> +
> +       val = mode->vtotal;
> +       val -= (mode->vtotal - mode->vdisplay);
> +       val |= (mode->vtotal - mode->vsync_start) << 16;
> +

extra line

> +       ret = cdn_dp_reg_write(dp, DP_VERTICAL_0, val);
> +       if (ret)
> +               return ret;
> +
> +       val = mode->vtotal;
> +       ret = cdn_dp_reg_write(dp, DP_VERTICAL_1, val);
> +       if (ret)
> +               return ret;
> +
> +       val =  0;
> +       return cdn_dp_reg_write_bit(dp, DP_VB_ID, 2, 1, val);
> +}
> +
> +int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio)
> +{
> +       int ret = cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, AUDIO_PACK_EN(0));
> +
remove this empty line

> +       if (ret)
> +               return ret;

insert empy line

> +       writel(0x1F0707, dp->regs + SPDIF_CTRL_ADDR);

pull this magic value into a #define

> +       writel(0, dp->regs + AUDIO_SRC_CNTL);
> +       writel(0, dp->regs + AUDIO_SRC_CNFG);
> +       writel(1, dp->regs + AUDIO_SRC_CNTL);
> +       writel(0, dp->regs + AUDIO_SRC_CNTL);
> +
> +       writel(0, dp->regs + SMPL2PKT_CNTL);
> +       writel(1, dp->regs + SMPL2PKT_CNTL);
> +       writel(0, dp->regs + SMPL2PKT_CNTL);
> +
> +       writel(1, dp->regs + FIFO_CNTL);
> +       writel(0, dp->regs + FIFO_CNTL);

Can you document why you need to toggle these bits as opposed to just
setting them to 0?

> +
> +       if (audio->format == AFMT_SPDIF)
> +               clk_disable_unprepare(dp->spdif_clk);
> +
> +       return 0;
> +}
> +
> +int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable)
> +{
> +       return cdn_dp_reg_write_bit(dp, DP_VB_ID, 4, 1, enable);
> +}
> +
> +int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info *audio)
> +{
> +       int lanes_param, i2s_port_en_val, val, i;

int lanes_param = 3, i2s_port_en_val = 0xf, i;

> +       int ret;
> +
> +       if (audio->channels == 2 && dp->link.num_lanes == 1)
> +               lanes_param = 1;
> +       else if (audio->channels == 2)
> +               lanes_param = 3;
> +       else
> +               lanes_param = 0;
> +
> +       if (audio->channels == 2)
> +               i2s_port_en_val = 1;
> +       else if (audio->channels == 4)
> +               i2s_port_en_val = 3;
> +       else
> +               i2s_port_en_val = 0xf;


I think with the above initialization values, you can replace these 2
conditionals with:

if (audio->channels == 2) {
  if (dp->link.num_lanes == 1)
    lanes_param = 3;
  i2s_port_en_val = 1;
} else if (audio->channels == 4) {
  i2s_port_en_val = 3;
}


> +
> +       if (audio->format == AFMT_SPDIF) {
> +               reset_control_assert(dp->spdif_rst);
> +               reset_control_deassert(dp->spdif_rst);
> +       }
> +
> +       ret = cdn_dp_reg_write(dp, CM_LANE_CTRL, 0x8000);

pull value out into #define (and use BIT(15) if appropriate)

> +       if (ret)
> +               return ret;
> +
> +       ret = cdn_dp_reg_write(dp, CM_CTRL, 0);
> +       if (ret)
> +               return ret;
> +
> +       if (audio->format == AFMT_I2S) {
> +               writel(0x0, dp->regs + SPDIF_CTRL_ADDR);
> +
> +               writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
> +
> +               val = audio->channels - 1;
> +               val |= (audio->channels / 2 - 1) << 5;
> +               val |= BIT(8);
> +               val |= lanes_param << 11;
> +               writel(val, dp->regs + SMPL2PKT_CNFG);
> +
> +               if (audio->sample_width == 16)
> +                       val = 0;
> +               else if (audio->sample_width == 24)
> +                       val = 1 << 9;
> +               else
> +                       val = 2 << 9;
> +
> +               val |= (audio->channels - 1) << 2;
> +               val |= i2s_port_en_val << 17;
> +               val |= 2 << 11;
> +               writel(val, dp->regs + AUDIO_SRC_CNFG);
> +
> +               for (i = 0; i < (audio->channels + 1) / 2; i++) {
> +                       if (audio->sample_width == 16)
> +                               val = (0x08 << 8) | (0x08 << 20);
> +                       else if (audio->sample_width == 24)
> +                               val = (0x0b << 8) | (0x0b << 20);
> +
> +                       val |= ((2 * i) << 4) | ((2 * i + 1) << 16);
> +                       writel(val, dp->regs + STTS_BIT_CH(i));
> +               }
> +
> +               switch (audio->sample_rate) {
> +               case 32000:
> +                       val = SAMPLING_FREQ(3) |
> +                             ORIGINAL_SAMP_FREQ(0xc);
> +                       break;
> +               case 44100:
> +                       val = SAMPLING_FREQ(0) |
> +                             ORIGINAL_SAMP_FREQ(0xf);
> +                       break;
> +               case 48000:
> +                       val = SAMPLING_FREQ(2) |
> +                             ORIGINAL_SAMP_FREQ(0xd);
> +                       break;
> +               case 88200:
> +                       val = SAMPLING_FREQ(8) |
> +                             ORIGINAL_SAMP_FREQ(0x7);
> +                       break;
> +               case 96000:
> +                       val = SAMPLING_FREQ(0xa) |
> +                             ORIGINAL_SAMP_FREQ(5);
> +                       break;
> +               case 176400:
> +                       val = SAMPLING_FREQ(0xc) |
> +                             ORIGINAL_SAMP_FREQ(3);
> +                       break;
> +               case 192000:
> +                       val = SAMPLING_FREQ(0xe) |
> +                             ORIGINAL_SAMP_FREQ(1);
> +                       break;
> +               }
> +               val |= 4;
> +               writel(val, dp->regs + COM_CH_STTS_BITS);
> +
> +               writel(2, dp->regs + SMPL2PKT_CNTL);
> +               writel(2, dp->regs + AUDIO_SRC_CNTL);

Pull all of this code into a new cdn_dp_audio_config_i2s() function
(with the lane_param and i2s_port_en_val assignments) and call it from
here

> +       } else {
> +               val = 0x1F0707;

Magic values need to be in #defines

> +               writel(val, dp->regs + SPDIF_CTRL_ADDR);
> +
> +               writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
> +
> +               val = 0x101 | (3 << 11);

Same here, these mean nothing.

> +               writel(val, dp->regs + SMPL2PKT_CNFG);
> +               writel(2, dp->regs + SMPL2PKT_CNTL);
> +
> +               val = 0x3F0707;

And here. And anywhere else I missed.

> +               writel(val, dp->regs + SPDIF_CTRL_ADDR);
> +
> +               clk_prepare_enable(dp->spdif_clk);
> +               clk_set_rate(dp->spdif_clk, CDN_DP_SPDIF_CLK);
> +       }
> +
> +       return cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, AUDIO_PACK_EN(1));
> +}
> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.h b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
> new file mode 100644
> index 0000000..b33793e
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
> @@ -0,0 +1,409 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author: Chris Zhong <zyw@rock-chips.com>
> + *
> + * This software is licensed under the terms of the GNU General Public
> + * License version 2, as published by the Free Software Foundation, and
> + * may be copied, distributed, and modified under those terms.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#ifndef _CDN_DP_REG_H
> +#define _CDN_DP_REG_H
> +
> +#include <linux/bitops.h>
> +
> +#define ADDR_IMEM              0x10000
> +#define ADDR_DMEM              0x20000
> +
> +/* APB CFG addr */
> +#define APB_CTRL                       0
> +#define XT_INT_CTRL                    0x04
> +#define MAILBOX_FULL_ADDR              0x08
> +#define MAILBOX_EMPTY_ADDR             0x0c
> +#define MAILBOX0_WR_DATA               0x10
> +#define MAILBOX0_RD_DATA               0x14
> +#define KEEP_ALIVE                     0x18
> +#define VER_L                          0x1c
> +#define VER_H                          0x20
> +#define VER_LIB_L_ADDR                 0x24
> +#define VER_LIB_H_ADDR                 0x28
> +#define SW_DEBUG_L                     0x2c
> +#define SW_DEBUG_H                     0x30
> +#define MAILBOX_INT_MASK               0x34
> +#define MAILBOX_INT_STATUS             0x38
> +#define SW_CLK_L                       0x3c
> +#define SW_CLK_H                       0x40
> +#define SW_EVENTS0                     0x44
> +#define SW_EVENTS1                     0x48
> +#define SW_EVENTS2                     0x4c
> +#define SW_EVENTS3                     0x50
> +#define XT_OCD_CTRL                    0x60
> +#define APB_INT_MASK                   0x6c
> +#define APB_STATUS_MASK                        0x70
> +
> +/* audio decoder addr */
> +#define AUDIO_SRC_CNTL                 0x30000
> +#define AUDIO_SRC_CNFG                 0x30004
> +#define COM_CH_STTS_BITS               0x30008

Can you split these up into individual bits?

> +#define STTS_BIT_CH(x)                 (0x3000c + ((x) << 2))
> +#define SPDIF_CTRL_ADDR                        0x3004c
> +#define SPDIF_CH1_CS_3100_ADDR         0x30050
> +#define SPDIF_CH1_CS_6332_ADDR         0x30054
> +#define SPDIF_CH1_CS_9564_ADDR         0x30058
> +#define SPDIF_CH1_CS_12796_ADDR                0x3005c
> +#define SPDIF_CH1_CS_159128_ADDR       0x30060
> +#define SPDIF_CH1_CS_191160_ADDR       0x30064
> +#define SPDIF_CH2_CS_3100_ADDR         0x30068
> +#define SPDIF_CH2_CS_6332_ADDR         0x3006c
> +#define SPDIF_CH2_CS_9564_ADDR         0x30070
> +#define SPDIF_CH2_CS_12796_ADDR                0x30074
> +#define SPDIF_CH2_CS_159128_ADDR       0x30078
> +#define SPDIF_CH2_CS_191160_ADDR       0x3007c
> +#define SMPL2PKT_CNTL                  0x30080
> +#define SMPL2PKT_CNFG                  0x30084
> +#define FIFO_CNTL                      0x30088
> +#define FIFO_STTS                      0x3008c

These cntl and cnfig values should be split up so we know what the
individual bits do

> +
> +/* source pif addr */
> +#define SOURCE_PIF_WR_ADDR             0x30800
> +#define SOURCE_PIF_WR_REQ              0x30804
> +#define SOURCE_PIF_RD_ADDR             0x30808
> +#define SOURCE_PIF_RD_REQ              0x3080c
> +#define SOURCE_PIF_DATA_WR             0x30810
> +#define SOURCE_PIF_DATA_RD             0x30814
> +#define SOURCE_PIF_FIFO1_FLUSH         0x30818
> +#define SOURCE_PIF_FIFO2_FLUSH         0x3081c
> +#define SOURCE_PIF_STATUS              0x30820
> +#define SOURCE_PIF_INTERRUPT_SOURCE    0x30824
> +#define SOURCE_PIF_INTERRUPT_MASK      0x30828

Same here, split these up! Everywhere else applicable too.

> +#define SOURCE_PIF_PKT_ALLOC_REG       0x3082c
> +#define SOURCE_PIF_PKT_ALLOC_WR_EN     0x30830
> +#define SOURCE_PIF_SW_RESET            0x30834
> +
> +/* bellow registers need access by mailbox */
> +/* source car addr */
> +#define SOURCE_HDTX_CAR                        0x0900
> +#define SOURCE_DPTX_CAR                        0x0904
> +#define SOURCE_PHY_CAR                 0x0908
> +#define SOURCE_CEC_CAR                 0x090c
> +#define SOURCE_CBUS_CAR                        0x0910
> +#define SOURCE_PKT_CAR                 0x0918
> +#define SOURCE_AIF_CAR                 0x091c
> +#define SOURCE_CIPHER_CAR              0x0920
> +#define SOURCE_CRYPTO_CAR              0x0924
> +
> +/* clock meters addr */
> +#define CM_CTRL                                0x0a00
> +#define CM_I2S_CTRL                    0x0a04
> +#define CM_SPDIF_CTRL                  0x0a08
> +#define CM_VID_CTRL                    0x0a0c
> +#define CM_LANE_CTRL                   0x0a10
> +#define I2S_NM_STABLE                  0x0a14
> +#define I2S_NCTS_STABLE                        0x0a18
> +#define SPDIF_NM_STABLE                        0x0a1c
> +#define SPDIF_NCTS_STABLE              0x0a20
> +#define NMVID_MEAS_STABLE              0x0a24
> +#define I2S_MEAS                       0x0a40
> +#define SPDIF_MEAS                     0x0a80
> +#define NMVID_MEAS                     0x0ac0
> +
> +/* source vif addr */
> +#define BND_HSYNC2VSYNC                        0x0b00
> +#define HSYNC2VSYNC_F1_L1              0x0b04
> +#define HSYNC2VSYNC_F2_L1              0x0b08
> +#define HSYNC2VSYNC_STATUS             0x0b0c
> +#define HSYNC2VSYNC_POL_CTRL           0x0b10
> +
> +/* dptx phy addr */
> +#define DP_TX_PHY_CONFIG_REG           0x2000
> +#define DP_TX_PHY_STATUS_REG           0x2004
> +#define DP_TX_PHY_SW_RESET             0x2008
> +#define DP_TX_PHY_SCRAMBLER_SEED       0x200c
> +#define DP_TX_PHY_TRAINING_01_04       0x2010
> +#define DP_TX_PHY_TRAINING_05_08       0x2014
> +#define DP_TX_PHY_TRAINING_09_10       0x2018
> +#define TEST_COR                       0x23fc
> +
> +/* dptx hpd addr */
> +#define HPD_IRQ_DET_MIN_TIMER          0x2100
> +#define HPD_IRQ_DET_MAX_TIMER          0x2104
> +#define HPD_UNPLGED_DET_MIN_TIMER      0x2108
> +#define HPD_STABLE_TIMER               0x210c
> +#define HPD_FILTER_TIMER               0x2110
> +#define HPD_EVENT_MASK                 0x211c
> +#define HPD_EVENT_DET                  0x2120
> +
> +/* dpyx framer addr */
> +#define DP_FRAMER_GLOBAL_CONFIG                0x2200
> +#define DP_SW_RESET                    0x2204
> +#define DP_FRAMER_TU                   0x2208
> +#define DP_FRAMER_PXL_REPR             0x220c
> +#define DP_FRAMER_SP                   0x2210
> +#define AUDIO_PACK_CONTROL             0x2214
> +#define DP_VC_TABLE(x)                 (0x2218 + ((x) << 2))
> +#define DP_VB_ID                       0x2258
> +#define DP_MTPH_LVP_CONTROL            0x225c
> +#define DP_MTPH_SYMBOL_VALUES          0x2260
> +#define DP_MTPH_ECF_CONTROL            0x2264
> +#define DP_MTPH_ACT_CONTROL            0x2268
> +#define DP_MTPH_STATUS                 0x226c
> +#define DP_INTERRUPT_SOURCE            0x2270
> +#define DP_INTERRUPT_MASK              0x2274
> +#define DP_FRONT_BACK_PORCH            0x2278
> +#define DP_BYTE_COUNT                  0x227c
> +
> +/* dptx stream addr */
> +#define MSA_HORIZONTAL_0               0x2280
> +#define MSA_HORIZONTAL_1               0x2284
> +#define MSA_VERTICAL_0                 0x2288
> +#define MSA_VERTICAL_1                 0x228c
> +#define MSA_MISC                       0x2290
> +#define STREAM_CONFIG                  0x2294
> +#define AUDIO_PACK_STATUS              0x2298
> +#define VIF_STATUS                     0x229c
> +#define PCK_STUFF_STATUS_0             0x22a0
> +#define PCK_STUFF_STATUS_1             0x22a4
> +#define INFO_PACK_STATUS               0x22a8
> +#define RATE_GOVERNOR_STATUS           0x22ac
> +#define DP_HORIZONTAL                  0x22b0
> +#define DP_VERTICAL_0                  0x22b4
> +#define DP_VERTICAL_1                  0x22b8
> +#define DP_BLOCK_SDP                   0x22bc
> +
> +/* dptx glbl addr */
> +#define DPTX_LANE_EN                   0x2300
> +#define DPTX_ENHNCD                    0x2304
> +#define DPTX_INT_MASK                  0x2308
> +#define DPTX_INT_STATUS                        0x230c
> +
> +/* dp aux addr */
> +#define DP_AUX_HOST_CONTROL            0x2800
> +#define DP_AUX_INTERRUPT_SOURCE                0x2804
> +#define DP_AUX_INTERRUPT_MASK          0x2808
> +#define DP_AUX_SWAP_INVERSION_CONTROL  0x280c
> +#define DP_AUX_SEND_NACK_TRANSACTION   0x2810
> +#define DP_AUX_CLEAR_RX                        0x2814
> +#define DP_AUX_CLEAR_TX                        0x2818
> +#define DP_AUX_TIMER_STOP              0x281c
> +#define DP_AUX_TIMER_CLEAR             0x2820
> +#define DP_AUX_RESET_SW                        0x2824
> +#define DP_AUX_DIVIDE_2M               0x2828
> +#define DP_AUX_TX_PREACHARGE_LENGTH    0x282c
> +#define DP_AUX_FREQUENCY_1M_MAX                0x2830
> +#define DP_AUX_FREQUENCY_1M_MIN                0x2834
> +#define DP_AUX_RX_PRE_MIN              0x2838
> +#define DP_AUX_RX_PRE_MAX              0x283c
> +#define DP_AUX_TIMER_PRESET            0x2840
> +#define DP_AUX_NACK_FORMAT             0x2844
> +#define DP_AUX_TX_DATA                 0x2848
> +#define DP_AUX_RX_DATA                 0x284c
> +#define DP_AUX_TX_STATUS               0x2850
> +#define DP_AUX_RX_STATUS               0x2854
> +#define DP_AUX_RX_CYCLE_COUNTER                0x2858
> +#define DP_AUX_MAIN_STATES             0x285c
> +#define DP_AUX_MAIN_TIMER              0x2860
> +#define DP_AUX_AFE_OUT                 0x2864
> +
> +/* crypto addr */
> +#define CRYPTO_HDCP_REVISION           0x5800
> +#define HDCP_CRYPTO_CONFIG             0x5804
> +#define CRYPTO_INTERRUPT_SOURCE                0x5808
> +#define CRYPTO_INTERRUPT_MASK          0x580c
> +#define CRYPTO22_CONFIG                        0x5818
> +#define CRYPTO22_STATUS                        0x581c
> +#define SHA_256_DATA_IN                        0x583c
> +#define SHA_256_DATA_OUT_(x)           (0x5850 + ((x) << 2))
> +#define AES_32_KEY_(x)                 (0x5870 + ((x) << 2))
> +#define AES_32_DATA_IN                 0x5880
> +#define AES_32_DATA_OUT_(x)            (0x5884 + ((x) << 2))
> +#define CRYPTO14_CONFIG                        0x58a0
> +#define CRYPTO14_STATUS                        0x58a4
> +#define CRYPTO14_PRNM_OUT              0x58a8
> +#define CRYPTO14_KM_0                  0x58ac
> +#define CRYPTO14_KM_1                  0x58b0
> +#define CRYPTO14_AN_0                  0x58b4
> +#define CRYPTO14_AN_1                  0x58b8
> +#define CRYPTO14_YOUR_KSV_0            0x58bc
> +#define CRYPTO14_YOUR_KSV_1            0x58c0
> +#define CRYPTO14_MI_0                  0x58c4
> +#define CRYPTO14_MI_1                  0x58c8
> +#define CRYPTO14_TI_0                  0x58cc
> +#define CRYPTO14_KI_0                  0x58d0
> +#define CRYPTO14_KI_1                  0x58d4
> +#define CRYPTO14_BLOCKS_NUM            0x58d8
> +#define CRYPTO14_KEY_MEM_DATA_0                0x58dc
> +#define CRYPTO14_KEY_MEM_DATA_1                0x58e0
> +#define CRYPTO14_SHA1_MSG_DATA         0x58e4
> +#define CRYPTO14_SHA1_V_VALUE_(x)      (0x58e8 + ((x) << 2))
> +#define TRNG_CTRL                      0x58fc
> +#define TRNG_DATA_RDY                  0x5900
> +#define TRNG_DATA                      0x5904
> +
> +/* cipher addr */
> +#define HDCP_REVISION                  0x60000
> +#define INTERRUPT_SOURCE               0x60004
> +#define INTERRUPT_MASK                 0x60008
> +#define HDCP_CIPHER_CONFIG             0x6000c
> +#define AES_128_KEY_0                  0x60010
> +#define AES_128_KEY_1                  0x60014
> +#define AES_128_KEY_2                  0x60018
> +#define AES_128_KEY_3                  0x6001c
> +#define AES_128_RANDOM_0               0x60020
> +#define AES_128_RANDOM_1               0x60024
> +#define CIPHER14_KM_0                  0x60028
> +#define CIPHER14_KM_1                  0x6002c
> +#define CIPHER14_STATUS                        0x60030
> +#define CIPHER14_RI_PJ_STATUS          0x60034
> +#define CIPHER_MODE                    0x60038
> +#define CIPHER14_AN_0                  0x6003c
> +#define CIPHER14_AN_1                  0x60040
> +#define CIPHER22_AUTH                  0x60044
> +#define CIPHER14_R0_DP_STATUS          0x60048
> +#define CIPHER14_BOOTSTRAP             0x6004c
> +
> +#define APB_IRAM_PATH                  BIT(2)
> +#define APB_DRAM_PATH                  BIT(1)
> +#define APB_XT_RESET                   BIT(0)
> +
> +/* mailbox */
> +#define MB_OPCODE_ID                   0
> +#define MB_MODULE_ID                   1
> +#define MB_SIZE_MSB_ID                 2
> +#define MB_SIZE_LSB_ID                 3
> +#define MB_DATA_ID                     4
> +
> +#define MB_MODULE_ID_DP_TX             0x01
> +#define MB_MODULE_ID_HDCP_TX           0x07
> +#define MB_MODULE_ID_HDCP_RX           0x08
> +#define MB_MODULE_ID_HDCP_GENERAL      0x09
> +#define MB_MODULE_ID_GENERAL           0x0a
> +
> +/* general opcode */
> +#define GENERAL_MAIN_CONTROL            0x01
> +#define GENERAL_TEST_ECHO               0x02
> +#define GENERAL_BUS_SETTINGS            0x03
> +#define GENERAL_TEST_ACCESS             0x04
> +
> +#define DPTX_SET_POWER_MNG                     0x00
> +#define DPTX_SET_HOST_CAPABILITIES             0x01
> +#define DPTX_GET_EDID                          0x02
> +#define DPTX_READ_DPCD                         0x03
> +#define DPTX_WRITE_DPCD                                0x04
> +#define DPTX_ENABLE_EVENT                      0x05
> +#define DPTX_WRITE_REGISTER                    0x06
> +#define DPTX_READ_REGISTER                     0x07
> +#define DPTX_WRITE_FIELD                       0x08
> +#define DPTX_TRAINING_CONTROL                  0x09
> +#define DPTX_READ_EVENT                                0x0a
> +#define DPTX_READ_LINK_STAT                    0x0b
> +#define DPTX_SET_VIDEO                         0x0c
> +#define DPTX_SET_AUDIO                         0x0d
> +#define DPTX_GET_LAST_AUX_STAUS                        0x0e
> +#define DPTX_SET_LINK_BREAK_POINT              0x0f
> +#define DPTX_FORCE_LANES                       0x10
> +#define DPTX_HPD_STATE                         0x11
> +
> +#define DPTX_EVENT_ENABLE_HPD                  BIT(0)
> +#define DPTX_EVENT_ENABLE_TRAINING             BIT(1)
> +
> +#define LINK_TRAINING_NOT_ACTIVE               0
> +#define LINK_TRAINING_RUN                      1
> +#define LINK_TRAINING_RESTART                  2
> +
> +#define CONTROL_VIDEO_IDLE                     0
> +#define CONTROL_VIDEO_VALID                    1
> +
> +#define VIF_BYPASS_INTERLACE                   BIT(13)
> +#define INTERLACE_FMT_DET                      BIT(12)
> +#define INTERLACE_DTCT_WIN                     0x20
> +
> +#define DP_FRAMER_SP_INTERLACE_EN              BIT(2)
> +#define DP_FRAMER_SP_HSP                       BIT(1)
> +#define DP_FRAMER_SP_VSP                       BIT(0)
> +
> +/* capability */
> +#define AUX_HOST_INVERT                                3
> +#define        FAST_LT_SUPPORT                         1
> +#define FAST_LT_NOT_SUPPORT                    0
> +#define LANE_MAPPING_NORMAL                    0xe4
> +#define LANE_MAPPING_FLIPPED                   0x1b
> +#define ENHANCED                               1
> +
> +#define        FULL_LT_STARTED                         BIT(0)
> +#define FASE_LT_STARTED                                BIT(1)
> +#define CLK_RECOVERY_FINISHED                  BIT(2)
> +#define EQ_PHASE_FINISHED                      BIT(3)
> +#define FASE_LT_START_FINISHED                 BIT(4)
> +#define CLK_RECOVERY_FAILED                    BIT(5)
> +#define EQ_PHASE_FAILED                                BIT(6)
> +#define FASE_LT_FAILED                         BIT(7)
> +
> +#define DPTX_HPD_EVENT                         BIT(0)
> +#define DPTX_TRAINING_EVENT                    BIT(1)
> +#define HDCP_TX_STATUS_EVENT                   BIT(4)
> +#define HDCP2_TX_IS_KM_STORED_EVENT            BIT(5)
> +#define HDCP2_TX_STORE_KM_EVENT                        BIT(6)
> +#define HDCP_TX_IS_RECEIVER_ID_VALID_EVENT     BIT(7)
> +
> +#define EDID_LENGTH_BYTE                       0
> +#define EDID_SEGMENT_BUMBER                    1
> +#define EDID_DATA                              2
> +#define EDID_BLOCK_SIZE                                128
> +
> +#define TU_SIZE                                        30
> +
> +/* audio */
> +#define AUDIO_PACK_EN(x)                       ((x) << 8)
> +#define SAMPLING_FREQ(x)                       ((x) << 16)
> +#define ORIGINAL_SAMP_FREQ(x)                  ((x) << 24)
> +#define SYNC_WR_TO_CH_ZERO                     BIT(1)
> +
> +enum voltage_swing_level {
> +       VOLTAGE_LEVEL_0,
> +       VOLTAGE_LEVEL_1,
> +       VOLTAGE_LEVEL_2,
> +       VOLTAGE_LEVEL_3,
> +};
> +
> +enum pre_emphasis_level {
> +       PRE_EMPHASIS_LEVEL_0,
> +       PRE_EMPHASIS_LEVEL_1,
> +       PRE_EMPHASIS_LEVEL_2,
> +       PRE_EMPHASIS_LEVEL_3,
> +};
> +
> +enum pattern_set {
> +       PRBS7           = BIT(0),
> +       D10_2           = BIT(1),
> +       TRAINING_PTN1   = BIT(2),
> +       TRAINING_PTN2   = BIT(3),
> +       DP_NONE         = BIT(4)
> +};
> +
> +enum VIC_PXL_ENCODING_FORMAT {

lowercase name

> +       PXL_RGB = 0x1,
> +       YCBCR_4_4_4 = 0x2,
> +       YCBCR_4_2_2 = 0x4,
> +       YCBCR_4_2_0 = 0x8,
> +       Y_ONLY = 0x10,
> +};
> +
> +enum VIC_COLOR_DEPTH {

and here

> +       BCS_6 = 0x1,
> +       BCS_8 = 0x2,
> +       BCS_10 = 0x4,
> +       BCS_12 = 0x8,
> +       BCS_16 = 0x10,
> +};
> +
> +enum VIC_BT_TYPE {

and here

> +       BT_601 = 0x0,
> +       BT_709 = 0x1,
> +};
> +
> +#endif /* _CDN_DP_REG_H */
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> index edd7ec2..98302b3 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> @@ -969,7 +969,7 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
>                 vop_dsp_hold_valid_irq_disable(vop);
>         }
>
> -       pin_pol = 0x8;
> +       pin_pol = (s->output_type == DRM_MODE_CONNECTOR_DisplayPort) ? 0 : 0x8;

There's no sense checking output_type here and then again in the
switch statement. Instead, pull 0x8 into a #define and only assign it
to pin_pol in the individual case statements below.

>         pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : 1;
>         pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? 0 : (1 << 1);
>         VOP_CTRL_SET(vop, pin_pol, pin_pol);
> @@ -991,6 +991,10 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
>                 VOP_CTRL_SET(vop, mipi_pin_pol, pin_pol);
>                 VOP_CTRL_SET(vop, mipi_en, 1);
>                 break;
> +       case DRM_MODE_CONNECTOR_DisplayPort:
> +               VOP_CTRL_SET(vop, dp_pin_pol, pin_pol);
> +               VOP_CTRL_SET(vop, dp_en, 1);
> +               break;
>         default:
>                 DRM_ERROR("unsupport connector_type[%d]\n", s->output_type);
>         }
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
> index ff4f52e..50a045c 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
> @@ -45,6 +45,7 @@ struct vop_ctrl {
>         struct vop_reg edp_en;
>         struct vop_reg hdmi_en;
>         struct vop_reg mipi_en;
> +       struct vop_reg dp_en;
>         struct vop_reg out_mode;
>         struct vop_reg dither_down;
>         struct vop_reg dither_up;
> @@ -53,6 +54,7 @@ struct vop_ctrl {
>         struct vop_reg hdmi_pin_pol;
>         struct vop_reg edp_pin_pol;
>         struct vop_reg mipi_pin_pol;
> +       struct vop_reg dp_pin_pol;
>
>         struct vop_reg htotal_pw;
>         struct vop_reg hact_st_end;
> diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
> index 5b1ae1f..dcf172e 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
> @@ -281,6 +281,7 @@ static const struct vop_data rk3288_vop = {
>  static const struct vop_ctrl rk3399_ctrl_data = {
>         .standby = VOP_REG(RK3399_SYS_CTRL, 0x1, 22),
>         .gate_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 23),
> +       .dp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 11),
>         .rgb_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 12),
>         .hdmi_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 13),
>         .edp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 14),
> @@ -290,6 +291,7 @@ static const struct vop_ctrl rk3399_ctrl_data = {
>         .data_blank = VOP_REG(RK3399_DSP_CTRL0, 0x1, 19),
>         .out_mode = VOP_REG(RK3399_DSP_CTRL0, 0xf, 0),
>         .rgb_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 16),
> +       .dp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 16),
>         .hdmi_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 20),
>         .edp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 24),
>         .mipi_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 28),
> --
> 2.6.3
>
> _______________________________________________
> dri-devel mailing list
> dri-devel at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [v5 PATCH 5/5] drm/rockchip: cdn-dp: add cdn DP support for rk3399
  2016-07-13 13:59   ` Sean Paul
@ 2016-07-14  3:08     ` Chris Zhong
  2016-07-14 14:02       ` Sean Paul
  0 siblings, 1 reply; 11+ messages in thread
From: Chris Zhong @ 2016-07-14  3:08 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Sean

Thanks for your detailed review. I'm working to modify most of code 
according to comment.
And there is reply for some comment

On 07/13/2016 09:59 PM, Sean Paul wrote:
> On Tue, Jul 12, 2016 at 8:09 AM, Chris Zhong <zyw@rock-chips.com> wrote:
>> Add support for cdn DP controller which is embedded in the rk3399
>> SoCs. The DP is compliant with DisplayPort Specification,
>> Version 1.3, This IP is compatible with the rockchip type-c PHY IP.
>> There is a uCPU in DP controller, it need a firmware to work,
>> please put the firmware file to /lib/firmware/cdn/dptx.bin. The
>> uCPU in charge of aux communication and link training, the host use
>> mailbox to communicate with the ucpu.
>> The dclk pin_pol of vop must not be invert for DP.
>>
>> Signed-off-by: Chris Zhong <zyw@rock-chips.com>
>>
>> ---
>>
>> Changes in v5:
>> - alphabetical order
>> - do not use long, use u32 or u64
>> - return MODE_CLOCK_HIGH when requested > actual
>> - Optimized Coding Style
>> - add a formula to get better tu size and symbol value.
>>
>> Changes in v4:
>> - use phy framework to control DP phy
>> - support 2 phys
>>
>> Changes in v3:
>> - use EXTCON_DISP_DP and EXTCON_DISP_DP_ALT cable to get dp port state.
>> - reset spdif before config it
>> - modify the firmware clk to 100Mhz
>> - retry load firmware if fw file is requested too early
>>
>> Changes in v2:
>> - Alphabetic order
>> - remove excess error message
>> - use define clk_rate
>> - check all return value
>> - remove dev_set_name(dp->dev, "cdn-dp");
>> - use schedule_delayed_work
>> - remove never-called functions
>> - remove some unnecessary ()
>>
>> Changes in v1:
>> - use extcon API
>> - use hdmi-codec for the DP Asoc
>> - do not initialize the "ret"
>> - printk a err log when drm_of_encoder_active_endpoint_id
>> - modify the dclk pin_pol to a single line
>>
>>   drivers/gpu/drm/rockchip/Kconfig            |   9 +
>>   drivers/gpu/drm/rockchip/Makefile           |   1 +
>>   drivers/gpu/drm/rockchip/cdn-dp-core.c      | 761 ++++++++++++++++++++++++++++
>>   drivers/gpu/drm/rockchip/cdn-dp-core.h      | 113 +++++
>>   drivers/gpu/drm/rockchip/cdn-dp-reg.c       | 740 +++++++++++++++++++++++++++
>>   drivers/gpu/drm/rockchip/cdn-dp-reg.h       | 409 +++++++++++++++
> Could you explain the file naming convention in the rk driver? We've
> got analogix_dp-rockchip.c, dw_hdmi-rockchip.c, dw-mipi-dsi.c, and now
> cdn-dp-(core|reg).[ch]. I'm honestly not sure whether these filenames
> are consistent with the rest, but bleh.
>
cdn is the IP vendor's name
dp is the controller's name.

>>   drivers/gpu/drm/rockchip/rockchip_drm_vop.c |   6 +-
>>   drivers/gpu/drm/rockchip/rockchip_drm_vop.h |   2 +
>>   drivers/gpu/drm/rockchip/rockchip_vop_reg.c |   2 +
>>   9 files changed, 2042 insertions(+), 1 deletion(-)
>>   create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.c
>>   create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.h
>>   create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.c
>>   create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.h
>>
>> diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
>> index d30bdc3..20da9a8 100644
>> --- a/drivers/gpu/drm/rockchip/Kconfig
>> +++ b/drivers/gpu/drm/rockchip/Kconfig
>> @@ -25,6 +25,15 @@ config ROCKCHIP_ANALOGIX_DP
>>            for the Analogix Core DP driver. If you want to enable DP
>>            on RK3288 based SoC, you should selet this option.
>>
>> +config ROCKCHIP_CDN_DP
>> +        tristate "Rockchip cdn DP"
>> +        depends on DRM_ROCKCHIP
>> +        help
>> +         This selects support for Rockchip SoC specific extensions
>> +         for the cdn Dp driver. If you want to enable Dp on
> s/Dp/DP/
>
>> +         RK3399 based SoC, you should selet this
> s/selet/select/
>
>> +         option.
>> +
>>   config ROCKCHIP_DW_HDMI
>>           tristate "Rockchip specific extensions for Synopsys DW HDMI"
>>           depends on DRM_ROCKCHIP
>> diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
>> index 05d0713..abdecd5 100644
>> --- a/drivers/gpu/drm/rockchip/Makefile
>> +++ b/drivers/gpu/drm/rockchip/Makefile
>> @@ -7,6 +7,7 @@ rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
>>   rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
>>
>>   obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
>> +obj-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o
>>   obj-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
>>   obj-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi.o
>>   obj-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
>> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
>> new file mode 100644
>> index 0000000..5b8a15e
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
>> @@ -0,0 +1,761 @@
>> +/*
>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>> + * Author: Chris Zhong <zyw@rock-chips.com>
>> + *
>> + * This software is licensed under the terms of the GNU General Public
>> + * License version 2, as published by the Free Software Foundation, and
>> + * may be copied, distributed, and modified under those terms.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + */
>> +
>> +#include <drm/drmP.h>
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_dp_helper.h>
>> +#include <drm/drm_edid.h>
>> +#include <drm/drm_of.h>
>> +
>> +#include <linux/clk.h>
>> +#include <linux/component.h>
>> +#include <linux/extcon.h>
>> +#include <linux/firmware.h>
>> +#include <linux/regmap.h>
>> +#include <linux/reset.h>
>> +#include <linux/mfd/syscon.h>
>> +#include <linux/phy/phy.h>
>> +
>> +#include <sound/hdmi-codec.h>
>> +
>> +#include <video/of_videomode.h>
>> +#include <video/videomode.h>
> Doesn't seem like you use these.
>
>
>> +
>> +#include "cdn-dp-core.h"
>> +#include "cdn-dp-reg.h"
>> +#include "rockchip_drm_vop.h"
>> +
>> +#define connector_to_dp(c) \
>> +               container_of(c, struct cdn_dp_device, connector)
>> +
>> +#define encoder_to_dp(c) \
>> +               container_of(c, struct cdn_dp_device, encoder)
>> +
>> +/* dp grf register offset */
>> +#define DP_VOP_SEL             0x6224
>> +#define DP_SEL_VOP_LIT         BIT(12)
>> +#define DP_CLK_RATE            100000000
> If this clock rate never changes, it should probably live somewhere in
> the clock driver.


>
>> +#define WAIT_HPD_STABLE                300
> Encode the units in these two define names, ie: DP_CLK_RATE_HZ,
> WAIT_HPD_STABLE_MS
>
>> +
>> +static int cdn_dp_clk_enable(struct cdn_dp_device *dp)
>> +{
>> +       int ret;
>> +
>> +       ret = clk_prepare_enable(dp->pclk);
>> +       if (ret < 0) {
>> +               dev_err(dp->dev, "cannot enable dp pclk %d\n", ret);
>> +               goto err_pclk;
>> +       }
>> +
>> +       ret = clk_prepare_enable(dp->core_clk);
>> +       if (ret < 0) {
>> +               dev_err(dp->dev, "cannot enable core_clk %d\n", ret);
>> +               goto err_core_clk;
>> +       }
>> +
>> +       ret = clk_set_rate(dp->core_clk, DP_CLK_RATE);
>> +       if (ret < 0) {
>> +               dev_err(dp->dev, "cannot set dp core clk to %d %d\n",
>> +                       DP_CLK_RATE, ret);
>> +               goto err_set_rate;
>> +       }
>> +
>> +       /* notice fw the clk freq value */
> This might be clearer if it was rephrased to "update fw with the
> current core clk frequency". I still think the rate should be set in
> the clock driver, and perhaps you can query the rate to get the
> current value.
This function is setting the controller register, it let the uCPU know 
what freq it is, then the uCPU use this value to do something.

>
>> +       cdn_dp_set_fw_clk(dp, DP_CLK_RATE);
>> +
>> +       return 0;
>> +
>> +err_set_rate:
>> +       clk_disable_unprepare(dp->core_clk);
>> +err_core_clk:
>> +       clk_disable_unprepare(dp->pclk);
>> +err_pclk:
>> +       return ret;
>> +}
>> +
>> +static enum drm_connector_status
>> +cdn_dp_connector_detect(struct drm_connector *connector, bool force)
>> +{
>> +       struct cdn_dp_device *dp = connector_to_dp(connector);
>> +
>> +       return dp->hpd_status ? connector_status_connected :
>> +                               connector_status_disconnected;
> If you just stored hpd_status as drm_connector_status, you could avoid
> having to translate the bool.
>
>> +}
>> +
>> +static void cdn_dp_connector_destroy(struct drm_connector *connector)
>> +{
>> +       drm_connector_unregister(connector);
>> +       drm_connector_cleanup(connector);
>> +}
>> +
>> +static struct drm_connector_funcs cdn_dp_atomic_connector_funcs = {
>> +       .dpms = drm_atomic_helper_connector_dpms,
>> +       .detect = cdn_dp_connector_detect,
>> +       .destroy = cdn_dp_connector_destroy,
>> +       .fill_modes = drm_helper_probe_single_connector_modes,
>> +       .reset = drm_atomic_helper_connector_reset,
>> +       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
>> +       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>> +};
>> +
>> +static int cdn_dp_connector_get_modes(struct drm_connector *connector)
>> +{
>> +       struct cdn_dp_device *dp = connector_to_dp(connector);
>> +       struct edid *edid;
>> +
>> +       if (!dp->fw_loaded)
> Can this actually happen? Seems like we shouldn't be returning
> connected from detect if the fw isn't loaded, so this should never be
> true. If that is the case, I think BUG_ON or WARN_ON would be more
> appropriate here.
>
>
>> +               return 0;
>> +
>> +       edid = drm_do_get_edid(connector, cdn_dp_get_edid_block, dp);
>> +       if (edid) {
>> +               dev_dbg(dp->dev, "got edid: width[%d] x height[%d]\n",
>> +                       edid->width_cm, edid->height_cm);
>> +
>> +               dp->sink_has_audio = drm_detect_monitor_audio(edid);
>> +               drm_mode_connector_update_edid_property(connector, edid);
>> +               drm_add_edid_modes(connector, edid);
>> +               /* Store the ELD */
> This comment isn't useful
>
>> +               drm_edid_to_eld(connector, edid);
>> +               kfree(edid);
>> +       } else {
>> +               dev_dbg(dp->dev, "failed to get edid\n");
> This seems to warrant a stronger level than dbg, perhaps warn. Also,
> should this really return success?
>
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static struct drm_encoder *
>> +       cdn_dp_connector_best_encoder(struct drm_connector *connector)
>> +{
>> +       struct cdn_dp_device *dp = connector_to_dp(connector);
>> +
>> +       return &dp->encoder;
>> +}
>> +
>> +static int cdn_dp_connector_mode_valid(struct drm_connector *connector,
>> +                                      struct drm_display_mode *mode)
>> +{
>> +       struct cdn_dp_device *dp = connector_to_dp(connector);
>> +       struct drm_display_info *display_info = &dp->connector.display_info;
>> +       u32 requested = mode->clock * display_info->bpc * 3 / 1000;
>> +       u32 actual, rate;
>> +
>> +       rate = drm_dp_bw_code_to_link_rate(dp->link.rate);
>> +       actual = rate * dp->link.num_lanes / 100;
>> +
>> +       /* efficiency is about 0.8 */
>> +       actual = actual * 8 / 10;
>> +
>> +       if (requested > actual) {
>> +               dev_dbg(dp->dev, "requested=%d, actual=%d, clock=%d\n",
>> +                       requested, actual, mode->clock);
>> +               return MODE_CLOCK_HIGH;
>> +       }
> This doesn't seem like something that is likely to happen given the
> trained rate should be a function of the display that's attached. Is
> that correct?
Yes, the link training should be a function of the display. But 
sometimes the edid give
us a excess size for default resolution, it need somewhere to do this 
mode valid checking.
Maybe we can do it at mode_set.

...
>
>> +       hdr = (struct cdn_firmware_header *)fw->data;
>> +       if (fw->size != le32_to_cpu(hdr->size_bytes))
>> +               return -EINVAL;
>> +
>> +       iram_data = (const u32 *)(fw->data + hdr->header_size);
>> +       dram_data = (const u32 *)(fw->data + hdr->header_size + hdr->iram_size);
>> +
>> +       ret = cdn_dp_load_firmware(dp, iram_data, hdr->iram_size,
>> +                                  dram_data, hdr->dram_size);
>> +       if (ret)
>> +               return ret;
>> +
>> +       release_firmware(fw);
> Move this out of here and into the same scope as request_firmware so
> there are no leaks.
>
>> +
>> +       ret = cdn_dp_active(dp, true);
>> +       if (ret) {
>> +               dev_err(dp->dev, "active ucpu failed: %d\n", ret);
>> +               return ret;
>> +       }
>> +
>> +       return cdn_dp_event_config(dp);
>> +}
>> +
>> +static int cdn_dp_init(struct cdn_dp_device *dp)
>> +{
>> +       struct device *dev = dp->dev;
>> +       struct device_node *np = dev->of_node;
>> +       struct platform_device *pdev = to_platform_device(dev);
>> +       struct resource *res;
>> +       int ret;
>> +
>> +       dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
>> +       if (IS_ERR(dp->grf)) {
>> +               dev_err(dev, "cdn-dp needs rockchip,grf property\n");
>> +               return PTR_ERR(dp->grf);
>> +       }
>> +
>> +       dp->irq = platform_get_irq(pdev, 0);
>> +       if (dp->irq < 0) {
>> +               dev_err(dev, "cdn-dp can not get irq\n");
>> +               return dp->irq;
>> +       }
>> +
>> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +       dp->regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(dp->regs)) {
>> +               dev_err(dev, "ioremap reg failed\n");
>> +               return PTR_ERR(dp->regs);
>> +       }
>> +
>> +       dp->core_clk = devm_clk_get(dev, "core-clk");
>> +       if (IS_ERR(dp->core_clk)) {
>> +               dev_err(dev, "cannot get core_clk_dp\n");
>> +               return PTR_ERR(dp->core_clk);
>> +       }
>> +
>> +       dp->pclk = devm_clk_get(dev, "pclk");
>> +       if (IS_ERR(dp->pclk)) {
>> +               dev_err(dev, "cannot get pclk\n");
>> +               return PTR_ERR(dp->pclk);
>> +       }
>> +
>> +       dp->spdif_clk = devm_clk_get(dev, "spdif");
>> +       if (IS_ERR(dp->spdif_clk)) {
>> +               dev_err(dev, "cannot get spdif_clk\n");
>> +               return PTR_ERR(dp->spdif_clk);
>> +       }
>> +
>> +       dp->spdif_rst = devm_reset_control_get(dev, "spdif");
>> +       if (IS_ERR(dp->spdif_rst)) {
>> +               dev_err(dev, "no spdif reset control found\n");
>> +               return PTR_ERR(dp->spdif_rst);
>> +       }
>> +
>> +       dp->dpms_mode = DRM_MODE_DPMS_OFF;
>> +
>> +       ret = cdn_dp_clk_enable(dp);
>> +       if (ret < 0)
>> +               return ret;
>> +
>> +       dp_clock_reset_seq(dp);
>> +
>> +       return 0;
>> +}
>> +
>> +static int cdn_dp_audio_hw_params(struct device *dev,
>> +                                 struct hdmi_codec_daifmt *daifmt,
>> +                                 struct hdmi_codec_params *params)
>> +{
>> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
>> +       struct audio_info audio = {
>> +               .sample_width = params->sample_width,
>> +               .sample_rate = params->sample_rate,
>> +               .channels = params->channels,
>> +       };
>> +       int ret;
>> +
>> +       if (!dp->encoder.crtc)
>> +               return -ENODEV;
>> +
>> +       switch (daifmt->fmt) {
>> +       case HDMI_I2S:
>> +               audio.format = AFMT_I2S;
>> +               break;
>> +       case HDMI_SPDIF:
>> +               audio.format = AFMT_SPDIF;
>> +               break;
>> +       default:
>> +               dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt);
>> +               return -EINVAL;
>> +       }
>> +
>> +       ret = cdn_dp_audio_config_set(dp, &audio);
>> +       if (!ret)
>> +               dp->audio_info = audio;
>> +
>> +       return ret;
>> +}
>> +
>> +static void cdn_dp_audio_shutdown(struct device *dev)
>> +{
>> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
>> +       int ret = cdn_dp_audio_stop(dp, &dp->audio_info);
>> +
>> +       if (!ret)
>> +               dp->audio_info.format = AFMT_UNUSED;
>> +}
>> +
>> +static int cdn_dp_audio_digital_mute(struct device *dev, bool enable)
>> +{
>> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
>> +
>> +       return cdn_dp_audio_mute(dp, enable);
>> +}
>> +
>> +static int cdn_dp_audio_get_eld(struct device *dev, uint8_t *buf, size_t len)
>> +{
>> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
>> +       struct drm_mode_config *config = &dp->encoder.dev->mode_config;
>> +       struct drm_connector *connector;
>> +       int ret = -ENODEV;
>> +
>> +       mutex_lock(&config->mutex);
>> +       list_for_each_entry(connector, &config->connector_list, head) {
>> +               if (&dp->encoder == connector->encoder) {
>> +                       memcpy(buf, connector->eld,
>> +                              min(sizeof(connector->eld), len));
>> +                       ret = 0;
>> +               }
>> +       }
>> +       mutex_unlock(&config->mutex);
>> +
>> +       return ret;
>> +}
>> +
>> +static const struct hdmi_codec_ops audio_codec_ops = {
>> +       .hw_params = cdn_dp_audio_hw_params,
>> +       .audio_shutdown = cdn_dp_audio_shutdown,
>> +       .digital_mute = cdn_dp_audio_digital_mute,
>> +       .get_eld = cdn_dp_audio_get_eld,
>> +};
>> +
>> +static int cdn_dp_audio_codec_init(struct cdn_dp_device *dp,
>> +                                  struct device *dev)
>> +{
>> +       struct hdmi_codec_pdata codec_data = {
>> +               .i2s = 1,
>> +               .spdif = 1,
>> +               .ops = &audio_codec_ops,
>> +               .max_i2s_channels = 8,
>> +       };
>> +
>> +       dp->audio_pdev = platform_device_register_data(
>> +                        dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO,
>> +                        &codec_data, sizeof(codec_data));
>> +
>> +       return PTR_ERR_OR_ZERO(dp->audio_pdev);
>> +}
>> +
>> +static void cdn_dp_get_state(struct cdn_dp_device *dp, struct extcon_dev *edev)
>> +{
>> +       bool dfp, dptx;
>> +       u8 cap_lanes;
>> +
>> +       dfp = extcon_get_cable_state_(edev, EXTCON_USB_HOST);
>> +       dptx = extcon_get_cable_state_(edev, EXTCON_DISP_DP);
>> +
>> +       if (dfp && dptx)
>> +               cap_lanes = 2;
>> +       else if (dptx)
>> +               cap_lanes = 4;
>> +       else
>> +               cap_lanes = 0;
>> +
>> +       if (cap_lanes != dp->cap_lanes) {
>> +               dp->cap_lanes = cap_lanes;
>> +               schedule_delayed_work(&dp->event_wq, 0);
>> +       }
> A get function shouldn't be scheduling work. Keep the assignment here
> and move the schedule into the pd_event function.
>
>> +}
>> +
>> +static int cdn_dp_pd_event(struct notifier_block *nb,
>> +                          unsigned long event, void *priv)
>> +{
>> +       struct cdn_dp_device *dp;
>> +       struct extcon_dev *edev = priv;
>> +       u8 i;
>> +
>> +       dp = container_of(nb, struct cdn_dp_device, event_nb);
>> +
>> +       for (i = 0; i < MAX_PHY; i++) {
>> +               if (edev == dp->extcon[i]) {
>> +                       dp->port_id = i;
>> +                       break;
>> +               }
>> +       }
>> +
>> +       cdn_dp_get_state(dp, edev);
> There's a race here. If the cap_lanes change in between scheduling the
> work and the work function running (ie, plug/unplug), the work
> function will have out-of-date information. You should call this from
> the work function instead.
>
>
>> +
>> +       return 0;
>> +}
>> +
>> +static void cdn_dp_pd_event_wq(struct work_struct *work)
>> +{
>> +       struct cdn_dp_device *dp;
>> +       int ret;
>> +
>> +       dp = container_of(work, struct cdn_dp_device, event_wq.work);
>> +
>> +       ret = request_firmware(&dp->fw, "cdn/dptx.bin", dp->dev);
> This filename needs to be abstracted into a #define.
>
> You're also failing to call release_firmware in a bunch of cases
> below. It needs to be sorted out.
>
>
>> +       if (ret == -ENOENT && !dp->fw_loaded) {
> So the case where ret == -ENOENT and dp->fw_loaded is valid? Or is that a bug?
>
> I'm not convinced you need fw_loaded stored in cdn_dp_device. It seems
> like something you could either get rid of (hopefully), or convert to
> a local.
it is not bug, "ret == -ENOENT" is for file missing, maybe user space is 
not ready.

if dp->fw_loaded it means the firmware has been loaded, do not load again.


>
>> +               /*
>> +                * If can not find the file, retry to load the firmware in 1
>> +                * second, if still failed after 1 minute, give up.
>> +                */
>> +               if (dp->fw_retry++ < 60) {
>> +                       schedule_delayed_work(&dp->event_wq,
>> +                                             msecs_to_jiffies(HZ));
> I'd rather do this as an exponential back-off so if it doesn't find
> the file, it waits progressively longer.
>
> Perhaps something like:
>
> #define MAX_FW_WAIT_SECS 64
>
> [...]
>
> if (dp->fw_wait <= MAX_FW_WAIT_SECS) {
>    schedule_delayed_work(&dp_>event_wq, msecs_to_jiffies(dp->fw_wait * HZ));
>    dp->fw_wait *= 2;
> }
If so, sometimes boot with DP will later than boot and then plug DP cable.


>> +               }
>> +
>> +               dev_err(dp->dev, "failed to request firmware %d\n", ret);
> Print the wait/retries.
>
>> +               return;
>> +       }
>> +
>> +       if (!dp->cap_lanes) {
>> +               if (dp->phy_status[dp->port_id])
>> +                       phy_power_off(dp->phy[dp->port_id]);
>> +               dp->phy_status[dp->port_id] = 0;
>> +               dp->hpd_status = false;
>> +               drm_helper_hpd_irq_event(dp->drm_dev);
>> +               return;
>> +       }
>> +
>> +       if (!dp->phy_status[dp->port_id]) {
>> +               ret = phy_power_on(dp->phy[dp->port_id]);
>> +               if (ret)
> Log something in this case
>
>> +                       return;
>> +
>> +               msleep(WAIT_HPD_STABLE);
> 300ms is a long time, do we really need to wait that long?
The 300ms is a safe value, maybe this time can be reduced

>
>> +       }
>> +
>> +       dp->phy_status[dp->port_id] = 1;
> Do you really need phy_status? It seems like if things are
> well-behaved you shouldn't need it. Of course, if you're turning
> on/off multiple times, perhaps there's a bug elsewhere.
This status is needed, since the phy_power_on would increase the 
power_count, this status can avoid power on multiple times

>
>> +
>> +       ret = cdn_dp_firmware_init(dp);
>> +       if (ret)
>> +               return;
>> +
>> +       /* read hpd status failed, or the hpd does not exist. */
>> +       ret = cdn_dp_get_hpd_status(dp);
>> +       if (ret <= 0)
> Log something?
>
>> +               return;
>> +
>> +       /*
>> +        * Set the capability and start the training process once
>> +        * the hpd is detected.
>> +        */
>> +       ret = cdn_dp_set_host_cap(dp);
>> +       if (ret) {
>> +               dev_err(dp->dev, "set host capabilities failed\n");
> print ret
>
>> +               return;
>> +       }
>> +
>> +       ret = cdn_dp_training_start(dp);
>> +       if (ret) {
>> +               dev_err(dp->dev, "hw lt err:%d\n", ret);
> s/lt/link training/
>
>> +               return;
>> +       }
>> +
>> +       ret = cdn_dp_get_lt_status(dp);
> Change to cdn_dp_get_training_status
>
>> +       if (ret) {
>> +               dev_err(dp->dev, "hw lt get status failed\n");
> print ret
>
>> +               return;
>> +       }
>> +
>> +       dev_info(dp->dev, "rate:%d, lanes:%d\n",
>> +                dp->link.rate, dp->link.num_lanes);
>> +
> I think everything from cdn_dp_firmare_init to here should be moved
> out of the wq and deferred until modeset time. That way you're
> training the link at the same time as you're setting the mode, which
> should obviate the need to check the clocks in mode_valid.

cdn_dp_training_start and cdn_dp_get_lt_status can be move the mode_set,
but the others are need by get_modes, so stay here is better.

>
>> +       dp->hpd_status = true;
>> +       drm_helper_hpd_irq_event(dp->drm_dev);
>> +}
>> +
>> +static int cdn_dp_bind(struct device *dev, struct device *master,
>> +                      void *data)
>> +{
>> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
>> +       struct drm_encoder *encoder;
>> +       struct drm_connector *connector;
>> +       struct drm_device *drm_dev = data;
>> +       int ret, i;
>> +
>> +       ret = cdn_dp_init(dp);
>> +       if (ret < 0)
>> +               return ret;
>> +
>> +       dp->drm_dev = drm_dev;
>> +
>> +       encoder = &dp->encoder;
>> +
>> +       encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
>> +                                                            dev->of_node);
>> +       DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs);
>> +
>> +       ret = drm_encoder_init(drm_dev, encoder, &cdn_dp_encoder_funcs,
>> +                              DRM_MODE_ENCODER_TMDS, NULL);
>> +       if (ret) {
>> +               DRM_ERROR("failed to initialize encoder with drm\n");
>> +               return ret;
>> +       }
>> +
>> +       drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs);
>> +
>> +       connector = &dp->connector;
>> +       connector->polled = DRM_CONNECTOR_POLL_HPD;
>> +       connector->dpms = DRM_MODE_DPMS_OFF;
>> +
>> +       ret = drm_connector_init(drm_dev, connector,
>> +                                &cdn_dp_atomic_connector_funcs,
>> +                                DRM_MODE_CONNECTOR_DisplayPort);
>> +       if (ret) {
>> +               DRM_ERROR("failed to initialize connector with drm\n");
>> +               goto err_free_encoder;
>> +       }
>> +
>> +       drm_connector_helper_add(connector, &cdn_dp_connector_helper_funcs);
>> +
>> +       ret = drm_mode_connector_attach_encoder(connector, encoder);
>> +       if (ret) {
>> +               DRM_ERROR("failed to attach connector and encoder\n");
>> +               goto err_free_connector;
>> +       }
>> +
>> +       cdn_dp_audio_codec_init(dp, dev);
>> +
>> +       dp->event_nb.notifier_call = cdn_dp_pd_event;
>> +       INIT_DELAYED_WORK(&dp->event_wq, cdn_dp_pd_event_wq);
>> +
>> +       for (i = 0; i < MAX_PHY; i++) {
>> +               if (IS_ERR(dp->extcon[i]))
>> +                       continue;
>> +
>> +               ret = extcon_register_notifier(dp->extcon[i], EXTCON_DISP_DP,
>> +                                              &dp->event_nb);
>> +               if (ret) {
>> +                       dev_err(dev, "regitster EXTCON_DISP_DP notifier err\n");
>> +                       return ret;
>> +               }
>> +
>> +               cdn_dp_get_state(dp, dp->extcon[i]);
>> +       }
>> +
>> +       return 0;
>> +
>> +err_free_connector:
>> +       drm_connector_cleanup(connector);
>> +err_free_encoder:
>> +       drm_encoder_cleanup(encoder);
>> +       return ret;
>> +}
>> +
>> +static void cdn_dp_unbind(struct device *dev, struct device *master, void *data)
>> +{
>> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
>> +       struct drm_encoder *encoder = &dp->encoder;
>> +       int i;
>> +
>> +       platform_device_unregister(dp->audio_pdev);
>> +       cdn_dp_encoder_disable(encoder);
>> +       encoder->funcs->destroy(encoder);
>> +       drm_connector_unregister(&dp->connector);
>> +       drm_connector_cleanup(&dp->connector);
> just call connector->destroy() here
>
>> +       drm_encoder_cleanup(encoder);
> this is already called by encoder->destroy()
>
>> +
>> +       for (i = 0; i < MAX_PHY; i++) {
>> +               if (!IS_ERR(dp->extcon[i]))
>> +                       extcon_unregister_notifier(dp->extcon[i], EXTCON_USB,
>> +                                                  &dp->event_nb);
>> +       }
>> +}
>> +
>> +static const struct component_ops cdn_dp_component_ops = {
>> +       .bind = cdn_dp_bind,
>> +       .unbind = cdn_dp_unbind,
>> +};
>> +
>> +static int cdn_dp_probe(struct platform_device *pdev)
>> +{
>> +       struct device *dev = &pdev->dev;
>> +       struct cdn_dp_device *dp;
>> +       u8 count = 0;
>> +       int i;
>> +
>> +       dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
>> +       if (!dp)
>> +               return -ENOMEM;
>> +       dp->dev = dev;
>> +
>> +       for (i = 0; i < MAX_PHY; i++) {
>> +               dp->extcon[i] = extcon_get_edev_by_phandle(dev, i);
>> +               dp->phy[i] = devm_of_phy_get_by_index(dev, dev->of_node, i);
>> +
>> +               if (!IS_ERR(dp->extcon[i]) && !IS_ERR(dp->phy[i]))
>> +                       count++;
>> +
>> +               if (PTR_ERR(dp->extcon[i]) == -EPROBE_DEFER ||
>> +                   PTR_ERR(dp->phy[i]) == -EPROBE_DEFER)
>> +                       return -EPROBE_DEFER;
>> +       }
>> +
>> +       if (!count) {
>> +               dev_err(dev, "missing extcon or phy\n");
>> +               return -EINVAL;
>> +       }
>> +
>> +       dev_set_drvdata(dev, dp);
>> +
>> +       return component_add(dev, &cdn_dp_component_ops);
>> +}
>> +
>> +static int cdn_dp_remove(struct platform_device *pdev)
>> +{
>> +       component_del(&pdev->dev, &cdn_dp_component_ops);
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct of_device_id cdn_dp_dt_ids[] = {
>> +       { .compatible = "rockchip,rk3399-cdn-dp" },
>> +       {}
>> +};
>> +
>> +MODULE_DEVICE_TABLE(of, cdn_dp_dt_ids);
>> +
>> +static struct platform_driver cdn_dp_driver = {
>> +       .probe = cdn_dp_probe,
>> +       .remove = cdn_dp_remove,
>> +       .driver = {
>> +                  .name = "cdn-dp",
>> +                  .owner = THIS_MODULE,
>> +                  .of_match_table = of_match_ptr(cdn_dp_dt_ids),
>> +       },
>> +};
>> +
>> +module_platform_driver(cdn_dp_driver);
>> +
>> +MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
>> +MODULE_DESCRIPTION("cdn DP Driver");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h
>> new file mode 100644
>> index 0000000..31ba22d
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h
>> @@ -0,0 +1,113 @@
>> +/*
>> + * Copyright (C) 2016 Chris Zhong <zyw@rock-chips.com>
>> + * Copyright (C) 2016 ROCKCHIP, Inc.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + */
>> +
>> +#ifndef _ROCKCHIP_EDP_CORE_H
>> +#define _ROCKCHIP_EDP_CORE_H
> I think the include guard should match the filename
>
>> +
>> +#include <drm/drmP.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_dp_helper.h>
>> +#include <drm/drm_panel.h>
>> +#include "rockchip_drm_drv.h"
>> +#include "cdn-dp-reg.h"
>> +
>> +#define MAX_PHY                2
> This should probably be stored in the .data member of of_device_id.
>
>> +
>> +enum AUDIO_FORMAT {
> enum audio_format
>
>> +       AFMT_I2S = 0,
>> +       AFMT_SPDIF = 1,
>> +       AFMT_UNUSED,
>> +};
>> +
>> +struct audio_info {
>> +       enum AUDIO_FORMAT format;
>> +       int sample_rate;
>> +       int channels;
>> +       int sample_width;
>> +};
>> +
>> +struct video_info {
>> +       bool h_sync_polarity;
>> +       bool v_sync_polarity;
>> +       bool interlaced;
>> +       int color_depth;
>> +       enum VIC_PXL_ENCODING_FORMAT color_fmt;
>> +};
>> +
>> +struct cdn_firmware_header {
>> +       u32 size_bytes; /* size of the entire header+image(s) in bytes */
>> +       u32 header_size; /* size of just the header in bytes */
>> +       u32 iram_size; /* size of iram */
>> +       u32 dram_size; /* size of dram */
>> +};
>> +
>> +struct cdn_dp_device {
>> +       struct device *dev;
>> +       struct drm_device *drm_dev;
>> +       struct drm_connector connector;
>> +       struct drm_encoder encoder;
>> +       struct drm_display_mode mode;
>> +       struct platform_device *audio_pdev;
>> +
>> +       const struct firmware *fw;      /* cdn dp firmware */
>> +       unsigned int fw_version;        /* cdn fw version */
>> +       u32 fw_retry;
>> +       bool fw_loaded;
>> +       void __iomem *regs;
>> +       struct regmap *grf;
>> +       unsigned int irq;
>> +       struct clk *core_clk;
>> +       struct clk *pclk;
>> +       struct clk *spdif_clk;
>> +       struct reset_control *spdif_rst;
>> +       struct audio_info audio_info;
>> +       struct video_info video_info;
>> +       struct extcon_dev *extcon[MAX_PHY];
>> +       struct phy *phy[MAX_PHY];
>> +       struct notifier_block event_nb;
>> +       struct delayed_work event_wq;
>> +
>> +       u8 port_id;
>> +       u8 cap_lanes;
>> +       bool hpd_status;
>> +       bool phy_status[MAX_PHY];
>> +
>> +       int dpms_mode;
>> +       struct drm_dp_link link;
>> +       bool sink_has_audio;
>> +};
>> +
>> +void dp_clock_reset_seq(struct cdn_dp_device *dp);
> cdn_ prefix here?
>
>> +
>> +void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, int clk);
>> +int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
>> +                        u32 i_size, const u32 *d_mem, u32 d_size);
>> +int cdn_dp_active(struct cdn_dp_device *dp, bool enable);
>> +int cdn_dp_set_host_cap(struct cdn_dp_device *dp);
>> +int cdn_dp_event_config(struct cdn_dp_device *dp);
>> +int cdn_dp_get_event(struct cdn_dp_device *dp);
>> +int cdn_dp_get_hpd_status(struct cdn_dp_device *dp);
>> +int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value);
>> +int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr);
>> +int cdn_dp_get_edid_block(void *dp, u8 *edid,
>> +                         unsigned int block, size_t length);
>> +int cdn_dp_training_start(struct cdn_dp_device *dp);
>> +int cdn_dp_get_lt_status(struct cdn_dp_device *dp);
>> +int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active);
>> +int cdn_dp_config_video(struct cdn_dp_device *dp);
>> +int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio);
>> +int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable);
>> +int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info *audio);
>> +
> I really don't like that you've declared these in core.h, yet defined
> them in reg.c.
>
>> +#endif  /* _ROCKCHIP_EDP_CORE_H */
> Make sure you update here too
>
>
>> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.c b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
>> new file mode 100644
>> index 0000000..8d5becd
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
>> @@ -0,0 +1,740 @@
>> +/*
>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>> + * Author: Chris Zhong <zyw@rock-chips.com>
>> + *
>> + * This software is licensed under the terms of the GNU General Public
>> + * License version 2, as published by the Free Software Foundation, and
>> + * may be copied, distributed, and modified under those terms.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/device.h>
>> +#include <linux/delay.h>
>> +#include <linux/io.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/reset.h>
>> +
>> +#include "cdn-dp-core.h"
>> +#include "cdn-dp-reg.h"
>> +
>> +#define CDN_DP_SPDIF_CLK               200000000
>> +#define FW_ALIVE_TIMEOUT_US            1000000
>> +#define MAILBOX_TIMEOUT_US             5000000
>> +
>> +void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, int clk)
>> +{
>> +       writel(clk / 1000000, dp->regs + SW_CLK_H);
>> +}
>> +
>> +void dp_clock_reset_seq(struct cdn_dp_device *dp)
>> +{
>> +       writel(0xfff, dp->regs + SOURCE_DPTX_CAR);
>> +       writel(0x7, dp->regs + SOURCE_PHY_CAR);
>> +       writel(0xf, dp->regs + SOURCE_PKT_CAR);
>> +       writel(0xff, dp->regs + SOURCE_AIF_CAR);
>> +       writel(0xf, dp->regs + SOURCE_CIPHER_CAR);
>> +       writel(0x3, dp->regs + SOURCE_CRYPTO_CAR);
>> +       writel(0, dp->regs + APB_INT_MASK);
> Pull these hardcoded values out into #defines
>
>> +}
>> +
>> +static int cdn_dp_mailbox_read(struct cdn_dp_device *dp)
>> +{
>> +       int val, ret;
>> +
>> +       if (!dp->fw_loaded)
>> +               return 0;
> This seems like an error. Return -errno and log a message
The audio will call this function before firmware during probe, return 
err cause audio register failed.

>
>> +
>> +       ret = readx_poll_timeout(readl, dp->regs + MAILBOX_EMPTY_ADDR,
>> +                                val, !val, 1000, MAILBOX_TIMEOUT_US);
> Pull the 1000 out into a #define (here and below)
>
>> +       if (ret < 0) {
>> +               dev_err(dp->dev, "failed to read mailbox, keep alive = %x\n",
>> +                       readl(dp->regs + KEEP_ALIVE));
>> +               return ret;
>> +       }
>> +
>> +       return readl(dp->regs + MAILBOX0_RD_DATA) & 0xff;
>> +}
>> +
>> +static int cdp_dp_mailbox_write(struct cdn_dp_device *dp, u8 val)
>> +{
>> +       int ret, full;
>> +
>> +       if (!dp->fw_loaded)
>> +               return 0;
> Same here
>
>> +
>> +       ret = readx_poll_timeout(readl, dp->regs + MAILBOX_FULL_ADDR,
>> +                                full, !full, 1000, MAILBOX_TIMEOUT_US);
>> +       if (ret < 0) {
>> +               dev_err(dp->dev, "mailbox is full, keep alive = %x\n",
>> +                       readl(dp->regs + KEEP_ALIVE));
>> +               return ret;
>> +       }
>> +
>> +       writel(val, dp->regs + MAILBOX0_WR_DATA);
>> +
>> +       return 0;
>> +}
>> +
>> +static int cdn_dp_mailbox_response(struct cdn_dp_device *dp, u8 module_id,
> I think _receive would be a better name than _response
>
>> +                                  u8 opcode, u8 *buff, u8 buff_size)
>> +{
>> +       int ret, i = 0;
>> +       u8 header[4];
>> +
>> +       /* read the header of the message */
>> +       while (i < 4) {
> for (i = 0; i < 4; i++) {
>
>> +               ret = cdn_dp_mailbox_read(dp);
>> +               if (ret < 0)
>> +                       return ret;
>> +
>> +               header[i++] = ret;
> header[i] = ret;
>
>> +       }
>> +
>> +       if (opcode != header[0] || module_id != header[1] ||
>> +           buff_size != ((header[2] << 8) | header[3])) {
> pull header[2] << 8 | header[3] out into a local, mbox_size.
>
>> +               dev_err(dp->dev, "mailbox response failed");
>> +
>> +               /*
>> +                * If the message in mailbox is not what we want, we need to
>> +                * clear the mailbox by read.
>> +                */
>> +               i = (header[2] << 8) | header[3];
>> +               while (i--)
> for (i = 0; i < mbox_size; i++)
>
>> +                       if (cdn_dp_mailbox_read(dp) < 0)
>> +                               break;
>> +
>> +               return -EINVAL;
>> +       }
>> +
>> +       i = 0;
>> +       while (i < buff_size) {
> for (i = 0; i < buff_size; i++) {
>
>> +               ret = cdn_dp_mailbox_read(dp);
>> +               if (ret < 0)
>> +                       return ret;
>> +
>> +               buff[i++] = ret;
>   buff[i] = ret;
>
>> +       }
>> +
>> +       return 0;
>
> Are you always guaranteed to have the entire message in the mailbox?
> If not, you might consider supporting partial messages and returning
> the length read.
>
>> +}
>> +
>> +static int cdn_dp_mailbox_send(struct cdn_dp_device *dp, u8 module_id,
>> +                              u8 opcode, u16 size, u8 *message)
>> +{
>> +       u8 header[4];
>> +       int ret, i;
>> +
>> +       header[0] = opcode;
>> +       header[1] = module_id;
>> +       header[2] = size >> 8;
> (size >> 8) & 0xff;
>
>> +       header[3] = size & 0xff;
>> +
>> +       for (i = 0; i < 4; i++) {
>> +               ret = cdp_dp_mailbox_write(dp, header[i]);
>> +               if (ret)
>> +                       return ret;
>> +       }
>> +
>> +       while (size--) {
> for (i = 0; i < size; i++) {
>
>> +               ret = cdp_dp_mailbox_write(dp, *message++);
> message[i]
>
>> +               if (ret)
> Log something here?
>
>> +                       return ret;
>> +       }
>> +
>> +       return 0;
> Perhaps consider returning the number of bytes that were sent.
>
>> +}
>> +
>> +static int cdn_dp_reg_write(struct cdn_dp_device *dp, u16 addr, u32 val)
>> +{
>> +       u8 msg[6];
>> +
>> +       msg[0] = (addr >> 8) & 0xff;
>> +       msg[1] = addr & 0xff;
>> +       msg[2] = (val >> 24) & 0xff;
>> +       msg[3] = (val >> 16) & 0xff;
>> +       msg[4] = (val >> 8) & 0xff;
>> +       msg[5] = val & 0xff;
>> +       return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_REGISTER,
>> +                                  ARRAY_SIZE(msg), msg);
>> +}
>> +
>> +int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
>> +                        u32 i_size, const u32 *d_mem, u32 d_size)
>> +{
>> +       int i, reg, ret;
> reg should be u32
>
>> +
>> +       /* reset ucpu before load firmware*/
>> +       writel(APB_IRAM_PATH | APB_DRAM_PATH | APB_XT_RESET,
>> +              dp->regs + APB_CTRL);
>> +
>> +       for (i = 0; i < i_size; i += 4)
>> +               writel(*i_mem++, dp->regs + ADDR_IMEM + i);
>> +
>> +       for (i = 0; i < d_size; i += 4)
>> +               writel(*d_mem++, dp->regs + ADDR_DMEM + i);
>> +
>> +       /* un-reset ucpu */
>> +       writel(0, dp->regs + APB_CTRL);
>> +
>> +       /* check the keep alive register to make sure fw working */
>> +       ret = readx_poll_timeout(readl, dp->regs + KEEP_ALIVE,
>> +                                reg, reg, 2000, FW_ALIVE_TIMEOUT_US);
>> +       if (ret < 0) {
>> +               dev_err(dp->dev, "failed to loaded the FW reg = %x\n", reg);
>> +               return -EINVAL;
>> +       }
>> +
>> +       reg = readl(dp->regs + VER_L) & 0xff;
>> +       dp->fw_version = reg;
>> +       reg = readl(dp->regs + VER_H) & 0xff;
>> +       dp->fw_version |= reg << 8;
>> +       reg = readl(dp->regs + VER_LIB_L_ADDR) & 0xff;
>> +       dp->fw_version |= reg << 16;
>> +       reg = readl(dp->regs + VER_LIB_H_ADDR) & 0xff;
>> +       dp->fw_version |= reg << 24;
>> +
> Are there any sanity checks you can do on the fw_version before returning?
This is first version of firmware, so do not nedd check the number, But 
I think adding a dev_dbg log here is better.

>
>> +       dp->fw_loaded = 1;
>> +
>> +       return 0;
>> +}
>> +
>> +int cdn_dp_reg_write_bit(struct cdn_dp_device *dp, u16 addr, u8 start_bit,
>> +                        u8 bits_no, u32 val)
>> +{
>> +       u8 field[8];
>> +
>> +       field[0] = (addr >> 8) & 0xff;
>> +       field[1] = addr & 0xff;
>> +       field[2] = start_bit;
>> +       field[3] = bits_no;
>> +       field[4] = (val >> 24) & 0xff;
>> +       field[5] = (val >> 16) & 0xff;
>> +       field[6] = (val >> 8) & 0xff;
>> +       field[7] = val & 0xff;
>> +
>> +       return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_FIELD,
>> +                           sizeof(field), field);
>> +}
>> +
>> +int cdn_dp_active(struct cdn_dp_device *dp, bool enable)
>> +{
>> +       u8 active = enable ? 1 : 0;
>> +       u8 resp;
>> +       int ret;
>> +
>> +       /* set firmware status, 1: avtive; 0: standby */
> s/avtive/active/
>
>> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_GENERAL,
>> +                                 GENERAL_MAIN_CONTROL, 1, &active);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_GENERAL,
>> +                                     GENERAL_MAIN_CONTROL, &resp, 1);
>> +       if (ret)
>> +               return ret;
>> +
>> +       return resp ? 0 : -EINVAL;
>> +}
>> +
>> +int cdn_dp_set_host_cap(struct cdn_dp_device *dp)
>> +{
>> +       u8 msg[8];
>> +       int ret;
>> +
>> +       msg[0] = DP_LINK_BW_5_4;
> Why is this always 5.4GHz? Does the hw support any other rates?
It is setting the max rate to 5.4G, not all support rate.

>
>> +       msg[1] = dp->cap_lanes;
>> +       msg[2] = VOLTAGE_LEVEL_2;
>> +       msg[3] = PRE_EMPHASIS_LEVEL_3;
>> +       msg[4] = PRBS7 | D10_2 | TRAINING_PTN1 | TRAINING_PTN2;
> You don't need to use training pattern 3 for hbr modes?
This is the fault of pattern name, it should be:

msg[4] = TPS1 | TPS2 | TPS3 | TPS4;



>
>> +       msg[5] = FAST_LT_NOT_SUPPORT;
>> +       msg[6] = LANE_MAPPING_NORMAL;
>> +       msg[7] = ENHANCED;
>> +
>> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
>> +                                 DPTX_SET_HOST_CAPABILITIES,
>> +                                 ARRAY_SIZE(msg), msg);
> ARRAY_SIZE counts the number of elements, not the size, so I think
> you're better off using sizeof instead. This applies to the rest of
> the dp_mailbox_send instances as well.
>
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = cdn_dp_reg_write(dp, DP_AUX_SWAP_INVERSION_CONTROL,
>> +                              AUX_HOST_INVERT);
>> +       if (ret)
>> +               return ret;
>> +
>> +       return 0;
>> +}
>> +
>> +int cdn_dp_event_config(struct cdn_dp_device *dp)
>> +{
>> +       u8 msg[5] = {0, 0, 0, 0, 0};
> memset(msg, 0, sizeof(msg));
>
>> +
>> +       msg[0] = DPTX_EVENT_ENABLE_HPD | DPTX_EVENT_ENABLE_TRAINING;
>> +
>> +       return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_ENABLE_EVENT,
>> +                                   ARRAY_SIZE(msg), msg);
>> +}
>> +
>> +int cdn_dp_get_event(struct cdn_dp_device *dp)
> Should return u32
>
>> +{
>> +       return readl(dp->regs + SW_EVENTS0);
>> +}
>> +
>> +int cdn_dp_get_hpd_status(struct cdn_dp_device *dp)
>> +{
>> +       u8 status;
>> +       int ret;
>> +
>> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_HPD_STATE,
>> +                                 0, NULL);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
>> +                                     DPTX_HPD_STATE, &status, 1);
>> +       if (ret)
>> +               return ret;
>> +
>> +       return status;
>> +}
>> +
>> +int cdn_dp_get_edid_block(void *data, u8 *edid,
>> +                         unsigned int block, size_t length)
>> +{
>> +       struct cdn_dp_device *dp = data;
>> +       u8 msg[2], reg[EDID_DATA + EDID_BLOCK_SIZE];
>> +       int ret;
>> +
>> +       if (length != EDID_BLOCK_SIZE)
>> +               return -EINVAL;
>> +
>> +       msg[0] = block / 2;
>> +       msg[1] = block % 2;
>> +
>> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_GET_EDID,
>> +                                 2, msg);
> sizeof(msg), msg);
>
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
>> +                                     DPTX_GET_EDID, reg,
>> +                                     EDID_DATA + EDID_BLOCK_SIZE);
>> +       if (ret)
>> +               return ret;
>> +
>> +       if (reg[EDID_LENGTH_BYTE] != EDID_BLOCK_SIZE ||
>> +           reg[EDID_SEGMENT_BUMBER] != block / 2) {
>> +               dev_err(dp->dev, "edid block size err\n");
>> +               return -EINVAL;
>> +       }
>> +
>> +       memcpy(edid, &reg[EDID_DATA], EDID_BLOCK_SIZE);
> You can avoid the intermediate reg[] buffer if you restructure the
> _response/_receive mailbox function. I'd suggest splitting it into a
> _validate_response() function that reads the header and makes sure the
> lengths match. The second part would be a _read_response() which
> simply reads the specified number of bytes from the mailbox. For
> convenience, you could make cdn_dp_mailbox_receive() a helper which
> calls both validate and read for times when you're reading the same
> number as you're validating.
>
> So in this function, you'd validate the response has EDID_DATA +
> EDID_BLOCK_SIZE bytes, then read the EDID_DATA prefix into a local,
> then read EDID_BLOCK_DATA directly into edid.
>
> I think you'll also need a _drain function to clear out the mailbox in
> case of error (otherwise the next read will fail).
>
>> +
>> +       return 0;
>> +}
>> +
>> +int cdn_dp_training_start(struct cdn_dp_device *dp)
>> +{
>> +       u8 msg, event[2];
>> +       unsigned long timeout;
>> +       int ret;
>> +
>> +       msg = LINK_TRAINING_RUN;
>> +
>> +       /* start training */
>> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_TRAINING_CONTROL,
>> +                                 1, &msg);
>> +       if (ret)
>> +               return ret;
>> +
>> +       /* the whole training should finish in 500ms */
>> +       timeout = jiffies + msecs_to_jiffies(500);
> Pull 500 out into a #define
>
>> +       while (1) {
> while(time_before(jiffies, timeout))
>
>> +               msleep(20);
> Pull 20 out into a #define
>
>> +               ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
>> +                                         DPTX_READ_EVENT, 0, NULL);
>> +               if (ret)
>> +                       return ret;
>> +
>> +               ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
>> +                                             DPTX_READ_EVENT, event, 2);
>> +               if (ret)
>> +                       return ret;
>> +
>> +               if (event[1] & EQ_PHASE_FINISHED)
>> +                       break;
> return 0
>
>> +
>> +               if (time_after(jiffies, timeout))
>> +                       return -ETIMEDOUT;
>> +       }
>> +
>> +       return 0;
> return -ETIMEDOUT
>
>> +}
>> +
>> +int cdn_dp_get_lt_status(struct cdn_dp_device *dp)
> s/lt/training/
>
>> +{
>> +       u8 status[10];
>> +       int ret;
>> +
>> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_READ_LINK_STAT,
>> +                                 0, NULL);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
>> +                                     DPTX_READ_LINK_STAT, status, 10);
> s/10/sizeof(status)/
>
>> +       if (ret)
>> +               return ret;
>> +
>> +       dp->link.rate = status[0];
>> +       dp->link.num_lanes = status[1];
>> +
>> +       return 0;
>> +}
>> +
>> +int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active)
>> +{
>> +       u8 msg;
>> +
>> +       msg = !!active;
>> +
>> +       return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_SET_VIDEO,
>> +                                  1, &msg);
> s/1/sizeof(msg)/
>
>> +}
>> +
>> +static int cdn_dp_get_msa_misc(struct video_info *video,
>> +                              struct drm_display_mode *mode)
>> +{
>> +       u8 val0, val1;
> u8 val[2];
>
>> +       u32 msa_misc;
>> +
>> +       switch (video->color_fmt) {
>> +       case PXL_RGB:
>> +       case Y_ONLY:
>> +               val0 = 0;
>> +               break;
> Perhaps this would be better as listed under the default case.
>
>> +       case YCBCR_4_4_4:
>> +               /* set default color space conversion to BT601 */
>> +               val0 = 6 + BT_601 * 8;
>> +               break;
>> +       case YCBCR_4_2_2:
>> +               /* set default color space conversion to BT601 */
>> +               val0 = 5 + BT_601 * 8;
>> +               break;
>> +       case YCBCR_4_2_0:
>> +               val0 = 5;
>> +               break;
>> +       };
>> +
>> +       switch (video->color_depth) {
>> +       case 6:
>> +               val1 = 0;
>> +               break;
>> +       case 8:
>> +               val1 = 1;
>> +               break;
>> +       case 10:
>> +               val1 = 2;
>> +               break;
>> +       case 12:
>> +               val1 = 3;
>> +               break;
>> +       case 16:
>> +               val1 = 4;
>> +               break;
>> +       };
>> +
>> +       msa_misc = 2 * val0 + 32 * val1 +
>> +                  ((video->color_fmt == Y_ONLY) ? (1 << 14) : 0);
> Perhaps a stupid question, but is this documented anywhere? If not,
> could you add a comment describing what you're doing?
>
>> +
>> +       return msa_misc;
>> +}
>> +
>> +int cdn_dp_config_video(struct cdn_dp_device *dp)
>> +{
>> +       struct video_info *video = &dp->video_info;
>> +       struct drm_display_mode *mode = &dp->mode;
>> +       u64 symbol;
>> +       u32 val, link_rate;
>> +       u8 bit_per_pix;
>> +       u8 tu_size_reg = TU_SIZE;
>> +       int ret;
>> +
>> +       bit_per_pix = (video->color_fmt == YCBCR_4_2_2) ?
>> +                     (video->color_depth * 2) : (video->color_depth * 3);
>> +
>> +       link_rate = drm_dp_bw_code_to_link_rate(dp->link.rate);
>> +
>> +       val = VIF_BYPASS_INTERLACE;
>> +
> remove extra line
>
>> +       ret = cdn_dp_reg_write(dp, BND_HSYNC2VSYNC, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = cdn_dp_reg_write(dp, HSYNC2VSYNC_POL_CTRL, 0);
>> +       if (ret)
>> +               return ret;
>> +
>> +       /* get a best tu_size and symbol */
>> +       do {
>> +               tu_size_reg += 2;
>> +               symbol = tu_size_reg * mode->clock * bit_per_pix;
>> +               symbol /= dp->link.num_lanes * link_rate * 8;
>> +       } while ((symbol == 1) || (tu_size_reg - symbol < 4));
>> +
>> +       val = symbol + (tu_size_reg << 8);
>> +
> remove extra line
>
>> +       ret = cdn_dp_reg_write(dp, DP_FRAMER_TU, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       switch (video->color_depth) {
>> +       case 6:
>> +               val = BCS_6;
>> +               break;
>> +       case 8:
>> +               val = BCS_8;
>> +               break;
>> +       case 10:
>> +               val = BCS_10;
>> +               break;
>> +       case 12:
>> +               val = BCS_12;
>> +               break;
>> +       case 16:
>> +               val = BCS_16;
>> +               break;
>> +       };
>> +
>> +       val += video->color_fmt << 8;
>> +       ret = cdn_dp_reg_write(dp, DP_FRAMER_PXL_REPR, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       val = video->h_sync_polarity ? DP_FRAMER_SP_HSP : 0;
>> +       val |= video->v_sync_polarity ? DP_FRAMER_SP_VSP : 0;
>> +       ret = cdn_dp_reg_write(dp, DP_FRAMER_SP, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       val = (mode->hsync_start - mode->hdisplay) << 16;
>> +       val |= mode->htotal - mode->hsync_end;
>> +       ret = cdn_dp_reg_write(dp, DP_FRONT_BACK_PORCH, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       val = mode->hdisplay * bit_per_pix / 8;
>> +       ret = cdn_dp_reg_write(dp, DP_BYTE_COUNT, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       val = mode->htotal | ((mode->htotal - mode->hsync_start) << 16);
>> +       ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_0, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       val = mode->hsync_end - mode->hsync_start;
>> +       val |= (mode->hdisplay << 16) | (video->h_sync_polarity << 15);
>> +       ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_1, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       val = mode->vtotal;
>> +       val |= ((mode->vtotal - mode->vsync_start) << 16);
>> +
> remove extra line
>
>> +       ret = cdn_dp_reg_write(dp, MSA_VERTICAL_0, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       val = mode->vsync_end - mode->vsync_start;
>> +       val |= mode->vdisplay << 16;
>> +       val |= (video->v_sync_polarity << 15);
> Make this consistent with how you assigned val for MSA_HORIZONTAL_1
> (ie: split both or combine both)
>
>> +       ret = cdn_dp_reg_write(dp, MSA_VERTICAL_1, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       val = cdn_dp_get_msa_misc(video, mode);
>> +       ret = cdn_dp_reg_write(dp, MSA_MISC, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = cdn_dp_reg_write(dp, STREAM_CONFIG, 1);
>> +       if (ret)
>> +               return ret;
>> +
>> +       val = mode->hsync_end - mode->hsync_start;
>> +       val |= (mode->hdisplay << 16);
>> +       ret = cdn_dp_reg_write(dp, DP_HORIZONTAL, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       val = mode->vtotal;
>> +       val -= (mode->vtotal - mode->vdisplay);
>> +       val |= (mode->vtotal - mode->vsync_start) << 16;
>> +
> extra line
>
>> +       ret = cdn_dp_reg_write(dp, DP_VERTICAL_0, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       val = mode->vtotal;
>> +       ret = cdn_dp_reg_write(dp, DP_VERTICAL_1, val);
>> +       if (ret)
>> +               return ret;
>> +
>> +       val =  0;
>> +       return cdn_dp_reg_write_bit(dp, DP_VB_ID, 2, 1, val);
>> +}
>> +
>> +int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio)
>> +{
>> +       int ret = cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, AUDIO_PACK_EN(0));
>> +
> remove this empty line
>
>> +       if (ret)
>> +               return ret;
> insert empy line
>
>> +       writel(0x1F0707, dp->regs + SPDIF_CTRL_ADDR);
> pull this magic value into a #define
>
>> +       writel(0, dp->regs + AUDIO_SRC_CNTL);
>> +       writel(0, dp->regs + AUDIO_SRC_CNFG);
>> +       writel(1, dp->regs + AUDIO_SRC_CNTL);
>> +       writel(0, dp->regs + AUDIO_SRC_CNTL);
>> +
>> +       writel(0, dp->regs + SMPL2PKT_CNTL);
>> +       writel(1, dp->regs + SMPL2PKT_CNTL);
>> +       writel(0, dp->regs + SMPL2PKT_CNTL);
>> +
>> +       writel(1, dp->regs + FIFO_CNTL);
>> +       writel(0, dp->regs + FIFO_CNTL);
> Can you document why you need to toggle these bits as opposed to just
> setting them to 0?
>
>> +
>> +       if (audio->format == AFMT_SPDIF)
>> +               clk_disable_unprepare(dp->spdif_clk);
>> +
>> +       return 0;
>> +}
>> +
>> +int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable)
>> +{
>> +       return cdn_dp_reg_write_bit(dp, DP_VB_ID, 4, 1, enable);
>> +}
>> +
>> +int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info *audio)
>> +{
>> +       int lanes_param, i2s_port_en_val, val, i;
> int lanes_param = 3, i2s_port_en_val = 0xf, i;
>
>> +       int ret;
>> +
>> +       if (audio->channels == 2 && dp->link.num_lanes == 1)
>> +               lanes_param = 1;
>> +       else if (audio->channels == 2)
>> +               lanes_param = 3;
>> +       else
>> +               lanes_param = 0;
>> +
>> +       if (audio->channels == 2)
>> +               i2s_port_en_val = 1;
>> +       else if (audio->channels == 4)
>> +               i2s_port_en_val = 3;
>> +       else
>> +               i2s_port_en_val = 0xf;
>
> I think with the above initialization values, you can replace these 2
> conditionals with:
>
> if (audio->channels == 2) {
>    if (dp->link.num_lanes == 1)
>      lanes_param = 3;
>    i2s_port_en_val = 1;
> } else if (audio->channels == 4) {
>    i2s_port_en_val = 3;
> }
>
>
>> +
>> +       if (audio->format == AFMT_SPDIF) {
>> +               reset_control_assert(dp->spdif_rst);
>> +               reset_control_deassert(dp->spdif_rst);
>> +       }
>> +
>> +       ret = cdn_dp_reg_write(dp, CM_LANE_CTRL, 0x8000);
> pull value out into #define (and use BIT(15) if appropriate)
>
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = cdn_dp_reg_write(dp, CM_CTRL, 0);
>> +       if (ret)
>> +               return ret;
>> +
>> +       if (audio->format == AFMT_I2S) {
>> +               writel(0x0, dp->regs + SPDIF_CTRL_ADDR);
>> +
>> +               writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
>> +
>> +               val = audio->channels - 1;
>> +               val |= (audio->channels / 2 - 1) << 5;
>> +               val |= BIT(8);
>> +               val |= lanes_param << 11;
>> +               writel(val, dp->regs + SMPL2PKT_CNFG);
>> +
>> +               if (audio->sample_width == 16)
>> +                       val = 0;
>> +               else if (audio->sample_width == 24)
>> +                       val = 1 << 9;
>> +               else
>> +                       val = 2 << 9;
>> +
>> +               val |= (audio->channels - 1) << 2;
>> +               val |= i2s_port_en_val << 17;
>> +               val |= 2 << 11;
>> +               writel(val, dp->regs + AUDIO_SRC_CNFG);
>> +
>> +               for (i = 0; i < (audio->channels + 1) / 2; i++) {
>> +                       if (audio->sample_width == 16)
>> +                               val = (0x08 << 8) | (0x08 << 20);
>> +                       else if (audio->sample_width == 24)
>> +                               val = (0x0b << 8) | (0x0b << 20);
>> +
>> +                       val |= ((2 * i) << 4) | ((2 * i + 1) << 16);
>> +                       writel(val, dp->regs + STTS_BIT_CH(i));
>> +               }
>> +
>> +               switch (audio->sample_rate) {
>> +               case 32000:
>> +                       val = SAMPLING_FREQ(3) |
>> +                             ORIGINAL_SAMP_FREQ(0xc);
>> +                       break;
>> +               case 44100:
>> +                       val = SAMPLING_FREQ(0) |
>> +                             ORIGINAL_SAMP_FREQ(0xf);
>> +                       break;
>> +               case 48000:
>> +                       val = SAMPLING_FREQ(2) |
>> +                             ORIGINAL_SAMP_FREQ(0xd);
>> +                       break;
>> +               case 88200:
>> +                       val = SAMPLING_FREQ(8) |
>> +                             ORIGINAL_SAMP_FREQ(0x7);
>> +                       break;
>> +               case 96000:
>> +                       val = SAMPLING_FREQ(0xa) |
>> +                             ORIGINAL_SAMP_FREQ(5);
>> +                       break;
>> +               case 176400:
>> +                       val = SAMPLING_FREQ(0xc) |
>> +                             ORIGINAL_SAMP_FREQ(3);
>> +                       break;
>> +               case 192000:
>> +                       val = SAMPLING_FREQ(0xe) |
>> +                             ORIGINAL_SAMP_FREQ(1);
>> +                       break;
>> +               }
>> +               val |= 4;
>> +               writel(val, dp->regs + COM_CH_STTS_BITS);
>> +
>> +               writel(2, dp->regs + SMPL2PKT_CNTL);
>> +               writel(2, dp->regs + AUDIO_SRC_CNTL);
> Pull all of this code into a new cdn_dp_audio_config_i2s() function
> (with the lane_param and i2s_port_en_val assignments) and call it from
> here
>
>> +       } else {
>> +               val = 0x1F0707;
> Magic values need to be in #defines
>
>> +               writel(val, dp->regs + SPDIF_CTRL_ADDR);
>> +
>> +               writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
>> +
>> +               val = 0x101 | (3 << 11);
> Same here, these mean nothing.
>
>> +               writel(val, dp->regs + SMPL2PKT_CNFG);
>> +               writel(2, dp->regs + SMPL2PKT_CNTL);
>> +
>> +               val = 0x3F0707;
> And here. And anywhere else I missed.
>
>> +               writel(val, dp->regs + SPDIF_CTRL_ADDR);
>> +
>> +               clk_prepare_enable(dp->spdif_clk);
>> +               clk_set_rate(dp->spdif_clk, CDN_DP_SPDIF_CLK);
>> +       }
>> +
>> +       return cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, AUDIO_PACK_EN(1));
>> +}
>> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.h b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
>> new file mode 100644
>> index 0000000..b33793e
>> --- /dev/null
>> +++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
>> @@ -0,0 +1,409 @@
>> +/*
>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>> + * Author: Chris Zhong <zyw@rock-chips.com>
>> + *
>> + * This software is licensed under the terms of the GNU General Public
>> + * License version 2, as published by the Free Software Foundation, and
>> + * may be copied, distributed, and modified under those terms.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + */
>> +
>> +#ifndef _CDN_DP_REG_H
>> +#define _CDN_DP_REG_H
>> +
>> +#include <linux/bitops.h>
>> +
>> +#define ADDR_IMEM              0x10000
>> +#define ADDR_DMEM              0x20000
>> +
>> +/* APB CFG addr */
>> +#define APB_CTRL                       0
>> +#define XT_INT_CTRL                    0x04
>> +#define MAILBOX_FULL_ADDR              0x08
>> +#define MAILBOX_EMPTY_ADDR             0x0c
>> +#define MAILBOX0_WR_DATA               0x10
>> +#define MAILBOX0_RD_DATA               0x14
>> +#define KEEP_ALIVE                     0x18
>> +#define VER_L                          0x1c
>> +#define VER_H                          0x20
>> +#define VER_LIB_L_ADDR                 0x24
>> +#define VER_LIB_H_ADDR                 0x28
>> +#define SW_DEBUG_L                     0x2c
>> +#define SW_DEBUG_H                     0x30
>> +#define MAILBOX_INT_MASK               0x34
>> +#define MAILBOX_INT_STATUS             0x38
>> +#define SW_CLK_L                       0x3c
>> +#define SW_CLK_H                       0x40
>> +#define SW_EVENTS0                     0x44
>> +#define SW_EVENTS1                     0x48
>> +#define SW_EVENTS2                     0x4c
>> +#define SW_EVENTS3                     0x50
>> +#define XT_OCD_CTRL                    0x60
>> +#define APB_INT_MASK                   0x6c
>> +#define APB_STATUS_MASK                        0x70
>> +
>> +/* audio decoder addr */
>> +#define AUDIO_SRC_CNTL                 0x30000
>> +#define AUDIO_SRC_CNFG                 0x30004
>> +#define COM_CH_STTS_BITS               0x30008
> Can you split these up into individual bits?
>
>> +#define STTS_BIT_CH(x)                 (0x3000c + ((x) << 2))
>> +#define SPDIF_CTRL_ADDR                        0x3004c
>> +#define SPDIF_CH1_CS_3100_ADDR         0x30050
>> +#define SPDIF_CH1_CS_6332_ADDR         0x30054
>> +#define SPDIF_CH1_CS_9564_ADDR         0x30058
>> +#define SPDIF_CH1_CS_12796_ADDR                0x3005c
>> +#define SPDIF_CH1_CS_159128_ADDR       0x30060
>> +#define SPDIF_CH1_CS_191160_ADDR       0x30064
>> +#define SPDIF_CH2_CS_3100_ADDR         0x30068
>> +#define SPDIF_CH2_CS_6332_ADDR         0x3006c
>> +#define SPDIF_CH2_CS_9564_ADDR         0x30070
>> +#define SPDIF_CH2_CS_12796_ADDR                0x30074
>> +#define SPDIF_CH2_CS_159128_ADDR       0x30078
>> +#define SPDIF_CH2_CS_191160_ADDR       0x3007c
>> +#define SMPL2PKT_CNTL                  0x30080
>> +#define SMPL2PKT_CNFG                  0x30084
>> +#define FIFO_CNTL                      0x30088
>> +#define FIFO_STTS                      0x3008c
> These cntl and cnfig values should be split up so we know what the
> individual bits do
>
>> +
>> +/* source pif addr */
>> +#define SOURCE_PIF_WR_ADDR             0x30800
>> +#define SOURCE_PIF_WR_REQ              0x30804
>> +#define SOURCE_PIF_RD_ADDR             0x30808
>> +#define SOURCE_PIF_RD_REQ              0x3080c
>> +#define SOURCE_PIF_DATA_WR             0x30810
>> +#define SOURCE_PIF_DATA_RD             0x30814
>> +#define SOURCE_PIF_FIFO1_FLUSH         0x30818
>> +#define SOURCE_PIF_FIFO2_FLUSH         0x3081c
>> +#define SOURCE_PIF_STATUS              0x30820
>> +#define SOURCE_PIF_INTERRUPT_SOURCE    0x30824
>> +#define SOURCE_PIF_INTERRUPT_MASK      0x30828
> Same here, split these up! Everywhere else applicable too.
>
>> +#define SOURCE_PIF_PKT_ALLOC_REG       0x3082c
>> +#define SOURCE_PIF_PKT_ALLOC_WR_EN     0x30830
>> +#define SOURCE_PIF_SW_RESET            0x30834
>> +
>> +/* bellow registers need access by mailbox */
>> +/* source car addr */
>> +#define SOURCE_HDTX_CAR                        0x0900
>> +#define SOURCE_DPTX_CAR                        0x0904
>> +#define SOURCE_PHY_CAR                 0x0908
>> +#define SOURCE_CEC_CAR                 0x090c
>> +#define SOURCE_CBUS_CAR                        0x0910
>> +#define SOURCE_PKT_CAR                 0x0918
>> +#define SOURCE_AIF_CAR                 0x091c
>> +#define SOURCE_CIPHER_CAR              0x0920
>> +#define SOURCE_CRYPTO_CAR              0x0924
>> +
>> +/* clock meters addr */
>> +#define CM_CTRL                                0x0a00
>> +#define CM_I2S_CTRL                    0x0a04
>> +#define CM_SPDIF_CTRL                  0x0a08
>> +#define CM_VID_CTRL                    0x0a0c
>> +#define CM_LANE_CTRL                   0x0a10
>> +#define I2S_NM_STABLE                  0x0a14
>> +#define I2S_NCTS_STABLE                        0x0a18
>> +#define SPDIF_NM_STABLE                        0x0a1c
>> +#define SPDIF_NCTS_STABLE              0x0a20
>> +#define NMVID_MEAS_STABLE              0x0a24
>> +#define I2S_MEAS                       0x0a40
>> +#define SPDIF_MEAS                     0x0a80
>> +#define NMVID_MEAS                     0x0ac0
>> +
>> +/* source vif addr */
>> +#define BND_HSYNC2VSYNC                        0x0b00
>> +#define HSYNC2VSYNC_F1_L1              0x0b04
>> +#define HSYNC2VSYNC_F2_L1              0x0b08
>> +#define HSYNC2VSYNC_STATUS             0x0b0c
>> +#define HSYNC2VSYNC_POL_CTRL           0x0b10
>> +
>> +/* dptx phy addr */
>> +#define DP_TX_PHY_CONFIG_REG           0x2000
>> +#define DP_TX_PHY_STATUS_REG           0x2004
>> +#define DP_TX_PHY_SW_RESET             0x2008
>> +#define DP_TX_PHY_SCRAMBLER_SEED       0x200c
>> +#define DP_TX_PHY_TRAINING_01_04       0x2010
>> +#define DP_TX_PHY_TRAINING_05_08       0x2014
>> +#define DP_TX_PHY_TRAINING_09_10       0x2018
>> +#define TEST_COR                       0x23fc
>> +
>> +/* dptx hpd addr */
>> +#define HPD_IRQ_DET_MIN_TIMER          0x2100
>> +#define HPD_IRQ_DET_MAX_TIMER          0x2104
>> +#define HPD_UNPLGED_DET_MIN_TIMER      0x2108
>> +#define HPD_STABLE_TIMER               0x210c
>> +#define HPD_FILTER_TIMER               0x2110
>> +#define HPD_EVENT_MASK                 0x211c
>> +#define HPD_EVENT_DET                  0x2120
>> +
>> +/* dpyx framer addr */
>> +#define DP_FRAMER_GLOBAL_CONFIG                0x2200
>> +#define DP_SW_RESET                    0x2204
>> +#define DP_FRAMER_TU                   0x2208
>> +#define DP_FRAMER_PXL_REPR             0x220c
>> +#define DP_FRAMER_SP                   0x2210
>> +#define AUDIO_PACK_CONTROL             0x2214
>> +#define DP_VC_TABLE(x)                 (0x2218 + ((x) << 2))
>> +#define DP_VB_ID                       0x2258
>> +#define DP_MTPH_LVP_CONTROL            0x225c
>> +#define DP_MTPH_SYMBOL_VALUES          0x2260
>> +#define DP_MTPH_ECF_CONTROL            0x2264
>> +#define DP_MTPH_ACT_CONTROL            0x2268
>> +#define DP_MTPH_STATUS                 0x226c
>> +#define DP_INTERRUPT_SOURCE            0x2270
>> +#define DP_INTERRUPT_MASK              0x2274
>> +#define DP_FRONT_BACK_PORCH            0x2278
>> +#define DP_BYTE_COUNT                  0x227c
>> +
>> +/* dptx stream addr */
>> +#define MSA_HORIZONTAL_0               0x2280
>> +#define MSA_HORIZONTAL_1               0x2284
>> +#define MSA_VERTICAL_0                 0x2288
>> +#define MSA_VERTICAL_1                 0x228c
>> +#define MSA_MISC                       0x2290
>> +#define STREAM_CONFIG                  0x2294
>> +#define AUDIO_PACK_STATUS              0x2298
>> +#define VIF_STATUS                     0x229c
>> +#define PCK_STUFF_STATUS_0             0x22a0
>> +#define PCK_STUFF_STATUS_1             0x22a4
>> +#define INFO_PACK_STATUS               0x22a8
>> +#define RATE_GOVERNOR_STATUS           0x22ac
>> +#define DP_HORIZONTAL                  0x22b0
>> +#define DP_VERTICAL_0                  0x22b4
>> +#define DP_VERTICAL_1                  0x22b8
>> +#define DP_BLOCK_SDP                   0x22bc
>> +
>> +/* dptx glbl addr */
>> +#define DPTX_LANE_EN                   0x2300
>> +#define DPTX_ENHNCD                    0x2304
>> +#define DPTX_INT_MASK                  0x2308
>> +#define DPTX_INT_STATUS                        0x230c
>> +
>> +/* dp aux addr */
>> +#define DP_AUX_HOST_CONTROL            0x2800
>> +#define DP_AUX_INTERRUPT_SOURCE                0x2804
>> +#define DP_AUX_INTERRUPT_MASK          0x2808
>> +#define DP_AUX_SWAP_INVERSION_CONTROL  0x280c
>> +#define DP_AUX_SEND_NACK_TRANSACTION   0x2810
>> +#define DP_AUX_CLEAR_RX                        0x2814
>> +#define DP_AUX_CLEAR_TX                        0x2818
>> +#define DP_AUX_TIMER_STOP              0x281c
>> +#define DP_AUX_TIMER_CLEAR             0x2820
>> +#define DP_AUX_RESET_SW                        0x2824
>> +#define DP_AUX_DIVIDE_2M               0x2828
>> +#define DP_AUX_TX_PREACHARGE_LENGTH    0x282c
>> +#define DP_AUX_FREQUENCY_1M_MAX                0x2830
>> +#define DP_AUX_FREQUENCY_1M_MIN                0x2834
>> +#define DP_AUX_RX_PRE_MIN              0x2838
>> +#define DP_AUX_RX_PRE_MAX              0x283c
>> +#define DP_AUX_TIMER_PRESET            0x2840
>> +#define DP_AUX_NACK_FORMAT             0x2844
>> +#define DP_AUX_TX_DATA                 0x2848
>> +#define DP_AUX_RX_DATA                 0x284c
>> +#define DP_AUX_TX_STATUS               0x2850
>> +#define DP_AUX_RX_STATUS               0x2854
>> +#define DP_AUX_RX_CYCLE_COUNTER                0x2858
>> +#define DP_AUX_MAIN_STATES             0x285c
>> +#define DP_AUX_MAIN_TIMER              0x2860
>> +#define DP_AUX_AFE_OUT                 0x2864
>> +
>> +/* crypto addr */
>> +#define CRYPTO_HDCP_REVISION           0x5800
>> +#define HDCP_CRYPTO_CONFIG             0x5804
>> +#define CRYPTO_INTERRUPT_SOURCE                0x5808
>> +#define CRYPTO_INTERRUPT_MASK          0x580c
>> +#define CRYPTO22_CONFIG                        0x5818
>> +#define CRYPTO22_STATUS                        0x581c
>> +#define SHA_256_DATA_IN                        0x583c
>> +#define SHA_256_DATA_OUT_(x)           (0x5850 + ((x) << 2))
>> +#define AES_32_KEY_(x)                 (0x5870 + ((x) << 2))
>> +#define AES_32_DATA_IN                 0x5880
>> +#define AES_32_DATA_OUT_(x)            (0x5884 + ((x) << 2))
>> +#define CRYPTO14_CONFIG                        0x58a0
>> +#define CRYPTO14_STATUS                        0x58a4
>> +#define CRYPTO14_PRNM_OUT              0x58a8
>> +#define CRYPTO14_KM_0                  0x58ac
>> +#define CRYPTO14_KM_1                  0x58b0
>> +#define CRYPTO14_AN_0                  0x58b4
>> +#define CRYPTO14_AN_1                  0x58b8
>> +#define CRYPTO14_YOUR_KSV_0            0x58bc
>> +#define CRYPTO14_YOUR_KSV_1            0x58c0
>> +#define CRYPTO14_MI_0                  0x58c4
>> +#define CRYPTO14_MI_1                  0x58c8
>> +#define CRYPTO14_TI_0                  0x58cc
>> +#define CRYPTO14_KI_0                  0x58d0
>> +#define CRYPTO14_KI_1                  0x58d4
>> +#define CRYPTO14_BLOCKS_NUM            0x58d8
>> +#define CRYPTO14_KEY_MEM_DATA_0                0x58dc
>> +#define CRYPTO14_KEY_MEM_DATA_1                0x58e0
>> +#define CRYPTO14_SHA1_MSG_DATA         0x58e4
>> +#define CRYPTO14_SHA1_V_VALUE_(x)      (0x58e8 + ((x) << 2))
>> +#define TRNG_CTRL                      0x58fc
>> +#define TRNG_DATA_RDY                  0x5900
>> +#define TRNG_DATA                      0x5904
>> +
>> +/* cipher addr */
>> +#define HDCP_REVISION                  0x60000
>> +#define INTERRUPT_SOURCE               0x60004
>> +#define INTERRUPT_MASK                 0x60008
>> +#define HDCP_CIPHER_CONFIG             0x6000c
>> +#define AES_128_KEY_0                  0x60010
>> +#define AES_128_KEY_1                  0x60014
>> +#define AES_128_KEY_2                  0x60018
>> +#define AES_128_KEY_3                  0x6001c
>> +#define AES_128_RANDOM_0               0x60020
>> +#define AES_128_RANDOM_1               0x60024
>> +#define CIPHER14_KM_0                  0x60028
>> +#define CIPHER14_KM_1                  0x6002c
>> +#define CIPHER14_STATUS                        0x60030
>> +#define CIPHER14_RI_PJ_STATUS          0x60034
>> +#define CIPHER_MODE                    0x60038
>> +#define CIPHER14_AN_0                  0x6003c
>> +#define CIPHER14_AN_1                  0x60040
>> +#define CIPHER22_AUTH                  0x60044
>> +#define CIPHER14_R0_DP_STATUS          0x60048
>> +#define CIPHER14_BOOTSTRAP             0x6004c
>> +
>> +#define APB_IRAM_PATH                  BIT(2)
>> +#define APB_DRAM_PATH                  BIT(1)
>> +#define APB_XT_RESET                   BIT(0)
>> +
>> +/* mailbox */
>> +#define MB_OPCODE_ID                   0
>> +#define MB_MODULE_ID                   1
>> +#define MB_SIZE_MSB_ID                 2
>> +#define MB_SIZE_LSB_ID                 3
>> +#define MB_DATA_ID                     4
>> +
>> +#define MB_MODULE_ID_DP_TX             0x01
>> +#define MB_MODULE_ID_HDCP_TX           0x07
>> +#define MB_MODULE_ID_HDCP_RX           0x08
>> +#define MB_MODULE_ID_HDCP_GENERAL      0x09
>> +#define MB_MODULE_ID_GENERAL           0x0a
>> +
>> +/* general opcode */
>> +#define GENERAL_MAIN_CONTROL            0x01
>> +#define GENERAL_TEST_ECHO               0x02
>> +#define GENERAL_BUS_SETTINGS            0x03
>> +#define GENERAL_TEST_ACCESS             0x04
>> +
>> +#define DPTX_SET_POWER_MNG                     0x00
>> +#define DPTX_SET_HOST_CAPABILITIES             0x01
>> +#define DPTX_GET_EDID                          0x02
>> +#define DPTX_READ_DPCD                         0x03
>> +#define DPTX_WRITE_DPCD                                0x04
>> +#define DPTX_ENABLE_EVENT                      0x05
>> +#define DPTX_WRITE_REGISTER                    0x06
>> +#define DPTX_READ_REGISTER                     0x07
>> +#define DPTX_WRITE_FIELD                       0x08
>> +#define DPTX_TRAINING_CONTROL                  0x09
>> +#define DPTX_READ_EVENT                                0x0a
>> +#define DPTX_READ_LINK_STAT                    0x0b
>> +#define DPTX_SET_VIDEO                         0x0c
>> +#define DPTX_SET_AUDIO                         0x0d
>> +#define DPTX_GET_LAST_AUX_STAUS                        0x0e
>> +#define DPTX_SET_LINK_BREAK_POINT              0x0f
>> +#define DPTX_FORCE_LANES                       0x10
>> +#define DPTX_HPD_STATE                         0x11
>> +
>> +#define DPTX_EVENT_ENABLE_HPD                  BIT(0)
>> +#define DPTX_EVENT_ENABLE_TRAINING             BIT(1)
>> +
>> +#define LINK_TRAINING_NOT_ACTIVE               0
>> +#define LINK_TRAINING_RUN                      1
>> +#define LINK_TRAINING_RESTART                  2
>> +
>> +#define CONTROL_VIDEO_IDLE                     0
>> +#define CONTROL_VIDEO_VALID                    1
>> +
>> +#define VIF_BYPASS_INTERLACE                   BIT(13)
>> +#define INTERLACE_FMT_DET                      BIT(12)
>> +#define INTERLACE_DTCT_WIN                     0x20
>> +
>> +#define DP_FRAMER_SP_INTERLACE_EN              BIT(2)
>> +#define DP_FRAMER_SP_HSP                       BIT(1)
>> +#define DP_FRAMER_SP_VSP                       BIT(0)
>> +
>> +/* capability */
>> +#define AUX_HOST_INVERT                                3
>> +#define        FAST_LT_SUPPORT                         1
>> +#define FAST_LT_NOT_SUPPORT                    0
>> +#define LANE_MAPPING_NORMAL                    0xe4
>> +#define LANE_MAPPING_FLIPPED                   0x1b
>> +#define ENHANCED                               1
>> +
>> +#define        FULL_LT_STARTED                         BIT(0)
>> +#define FASE_LT_STARTED                                BIT(1)
>> +#define CLK_RECOVERY_FINISHED                  BIT(2)
>> +#define EQ_PHASE_FINISHED                      BIT(3)
>> +#define FASE_LT_START_FINISHED                 BIT(4)
>> +#define CLK_RECOVERY_FAILED                    BIT(5)
>> +#define EQ_PHASE_FAILED                                BIT(6)
>> +#define FASE_LT_FAILED                         BIT(7)
>> +
>> +#define DPTX_HPD_EVENT                         BIT(0)
>> +#define DPTX_TRAINING_EVENT                    BIT(1)
>> +#define HDCP_TX_STATUS_EVENT                   BIT(4)
>> +#define HDCP2_TX_IS_KM_STORED_EVENT            BIT(5)
>> +#define HDCP2_TX_STORE_KM_EVENT                        BIT(6)
>> +#define HDCP_TX_IS_RECEIVER_ID_VALID_EVENT     BIT(7)
>> +
>> +#define EDID_LENGTH_BYTE                       0
>> +#define EDID_SEGMENT_BUMBER                    1
>> +#define EDID_DATA                              2
>> +#define EDID_BLOCK_SIZE                                128
>> +
>> +#define TU_SIZE                                        30
>> +
>> +/* audio */
>> +#define AUDIO_PACK_EN(x)                       ((x) << 8)
>> +#define SAMPLING_FREQ(x)                       ((x) << 16)
>> +#define ORIGINAL_SAMP_FREQ(x)                  ((x) << 24)
>> +#define SYNC_WR_TO_CH_ZERO                     BIT(1)
>> +
>> +enum voltage_swing_level {
>> +       VOLTAGE_LEVEL_0,
>> +       VOLTAGE_LEVEL_1,
>> +       VOLTAGE_LEVEL_2,
>> +       VOLTAGE_LEVEL_3,
>> +};
>> +
>> +enum pre_emphasis_level {
>> +       PRE_EMPHASIS_LEVEL_0,
>> +       PRE_EMPHASIS_LEVEL_1,
>> +       PRE_EMPHASIS_LEVEL_2,
>> +       PRE_EMPHASIS_LEVEL_3,
>> +};
>> +
>> +enum pattern_set {
>> +       PRBS7           = BIT(0),
>> +       D10_2           = BIT(1),
>> +       TRAINING_PTN1   = BIT(2),
>> +       TRAINING_PTN2   = BIT(3),
>> +       DP_NONE         = BIT(4)
>> +};
>> +
>> +enum VIC_PXL_ENCODING_FORMAT {
> lowercase name
>
>> +       PXL_RGB = 0x1,
>> +       YCBCR_4_4_4 = 0x2,
>> +       YCBCR_4_2_2 = 0x4,
>> +       YCBCR_4_2_0 = 0x8,
>> +       Y_ONLY = 0x10,
>> +};
>> +
>> +enum VIC_COLOR_DEPTH {
> and here
>
>> +       BCS_6 = 0x1,
>> +       BCS_8 = 0x2,
>> +       BCS_10 = 0x4,
>> +       BCS_12 = 0x8,
>> +       BCS_16 = 0x10,
>> +};
>> +
>> +enum VIC_BT_TYPE {
> and here
>
>> +       BT_601 = 0x0,
>> +       BT_709 = 0x1,
>> +};
>> +
>> +#endif /* _CDN_DP_REG_H */
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
>> index edd7ec2..98302b3 100644
>> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
>> @@ -969,7 +969,7 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
>>                  vop_dsp_hold_valid_irq_disable(vop);
>>          }
>>
>> -       pin_pol = 0x8;
>> +       pin_pol = (s->output_type == DRM_MODE_CONNECTOR_DisplayPort) ? 0 : 0x8;
> There's no sense checking output_type here and then again in the
> switch statement. Instead, pull 0x8 into a #define and only assign it
> to pin_pol in the individual case statements below.
>
>>          pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : 1;
>>          pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? 0 : (1 << 1);
>>          VOP_CTRL_SET(vop, pin_pol, pin_pol);
>> @@ -991,6 +991,10 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
>>                  VOP_CTRL_SET(vop, mipi_pin_pol, pin_pol);
>>                  VOP_CTRL_SET(vop, mipi_en, 1);
>>                  break;
>> +       case DRM_MODE_CONNECTOR_DisplayPort:
>> +               VOP_CTRL_SET(vop, dp_pin_pol, pin_pol);
>> +               VOP_CTRL_SET(vop, dp_en, 1);
>> +               break;
>>          default:
>>                  DRM_ERROR("unsupport connector_type[%d]\n", s->output_type);
>>          }
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
>> index ff4f52e..50a045c 100644
>> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
>> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
>> @@ -45,6 +45,7 @@ struct vop_ctrl {
>>          struct vop_reg edp_en;
>>          struct vop_reg hdmi_en;
>>          struct vop_reg mipi_en;
>> +       struct vop_reg dp_en;
>>          struct vop_reg out_mode;
>>          struct vop_reg dither_down;
>>          struct vop_reg dither_up;
>> @@ -53,6 +54,7 @@ struct vop_ctrl {
>>          struct vop_reg hdmi_pin_pol;
>>          struct vop_reg edp_pin_pol;
>>          struct vop_reg mipi_pin_pol;
>> +       struct vop_reg dp_pin_pol;
>>
>>          struct vop_reg htotal_pw;
>>          struct vop_reg hact_st_end;
>> diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
>> index 5b1ae1f..dcf172e 100644
>> --- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
>> +++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
>> @@ -281,6 +281,7 @@ static const struct vop_data rk3288_vop = {
>>   static const struct vop_ctrl rk3399_ctrl_data = {
>>          .standby = VOP_REG(RK3399_SYS_CTRL, 0x1, 22),
>>          .gate_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 23),
>> +       .dp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 11),
>>          .rgb_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 12),
>>          .hdmi_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 13),
>>          .edp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 14),
>> @@ -290,6 +291,7 @@ static const struct vop_ctrl rk3399_ctrl_data = {
>>          .data_blank = VOP_REG(RK3399_DSP_CTRL0, 0x1, 19),
>>          .out_mode = VOP_REG(RK3399_DSP_CTRL0, 0xf, 0),
>>          .rgb_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 16),
>> +       .dp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 16),
>>          .hdmi_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 20),
>>          .edp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 24),
>>          .mipi_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 28),
>> --
>> 2.6.3
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel at lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel
>
>

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

* [v5 PATCH 5/5] drm/rockchip: cdn-dp: add cdn DP support for rk3399
  2016-07-14  3:08     ` Chris Zhong
@ 2016-07-14 14:02       ` Sean Paul
  2016-07-15  5:52         ` [v5.1 " Chris Zhong
  2016-07-15  8:18         ` [v5.2 " Chris Zhong
  0 siblings, 2 replies; 11+ messages in thread
From: Sean Paul @ 2016-07-14 14:02 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jul 13, 2016 at 8:08 PM, Chris Zhong <zyw@rock-chips.com> wrote:
> Hi Sean
>
> Thanks for your detailed review. I'm working to modify most of code
> according to comment.
> And there is reply for some comment
>
> On 07/13/2016 09:59 PM, Sean Paul wrote:
>>
>> On Tue, Jul 12, 2016 at 8:09 AM, Chris Zhong <zyw@rock-chips.com> wrote:
>>>
>>> Add support for cdn DP controller which is embedded in the rk3399
>>> SoCs. The DP is compliant with DisplayPort Specification,
>>> Version 1.3, This IP is compatible with the rockchip type-c PHY IP.
>>> There is a uCPU in DP controller, it need a firmware to work,
>>> please put the firmware file to /lib/firmware/cdn/dptx.bin. The
>>> uCPU in charge of aux communication and link training, the host use
>>> mailbox to communicate with the ucpu.
>>> The dclk pin_pol of vop must not be invert for DP.
>>>
>>> Signed-off-by: Chris Zhong <zyw@rock-chips.com>
>>>
>>> ---
>>>
>>> Changes in v5:
>>> - alphabetical order
>>> - do not use long, use u32 or u64
>>> - return MODE_CLOCK_HIGH when requested > actual
>>> - Optimized Coding Style
>>> - add a formula to get better tu size and symbol value.
>>>
>>> Changes in v4:
>>> - use phy framework to control DP phy
>>> - support 2 phys
>>>
>>> Changes in v3:
>>> - use EXTCON_DISP_DP and EXTCON_DISP_DP_ALT cable to get dp port state.
>>> - reset spdif before config it
>>> - modify the firmware clk to 100Mhz
>>> - retry load firmware if fw file is requested too early
>>>
>>> Changes in v2:
>>> - Alphabetic order
>>> - remove excess error message
>>> - use define clk_rate
>>> - check all return value
>>> - remove dev_set_name(dp->dev, "cdn-dp");
>>> - use schedule_delayed_work
>>> - remove never-called functions
>>> - remove some unnecessary ()
>>>
>>> Changes in v1:
>>> - use extcon API
>>> - use hdmi-codec for the DP Asoc
>>> - do not initialize the "ret"
>>> - printk a err log when drm_of_encoder_active_endpoint_id
>>> - modify the dclk pin_pol to a single line
>>>
>>>   drivers/gpu/drm/rockchip/Kconfig            |   9 +
>>>   drivers/gpu/drm/rockchip/Makefile           |   1 +
>>>   drivers/gpu/drm/rockchip/cdn-dp-core.c      | 761
>>> ++++++++++++++++++++++++++++
>>>   drivers/gpu/drm/rockchip/cdn-dp-core.h      | 113 +++++
>>>   drivers/gpu/drm/rockchip/cdn-dp-reg.c       | 740
>>> +++++++++++++++++++++++++++
>>>   drivers/gpu/drm/rockchip/cdn-dp-reg.h       | 409 +++++++++++++++
>>
>> Could you explain the file naming convention in the rk driver? We've
>> got analogix_dp-rockchip.c, dw_hdmi-rockchip.c, dw-mipi-dsi.c, and now
>> cdn-dp-(core|reg).[ch]. I'm honestly not sure whether these filenames
>> are consistent with the rest, but bleh.
>>
> cdn is the IP vendor's name
> dp is the controller's name.
>
>>>   drivers/gpu/drm/rockchip/rockchip_drm_vop.c |   6 +-
>>>   drivers/gpu/drm/rockchip/rockchip_drm_vop.h |   2 +
>>>   drivers/gpu/drm/rockchip/rockchip_vop_reg.c |   2 +
>>>   9 files changed, 2042 insertions(+), 1 deletion(-)
>>>   create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.c
>>>   create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.h
>>>   create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.c
>>>   create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.h
>>>
>>> diff --git a/drivers/gpu/drm/rockchip/Kconfig
>>> b/drivers/gpu/drm/rockchip/Kconfig
>>> index d30bdc3..20da9a8 100644
>>> --- a/drivers/gpu/drm/rockchip/Kconfig
>>> +++ b/drivers/gpu/drm/rockchip/Kconfig
>>> @@ -25,6 +25,15 @@ config ROCKCHIP_ANALOGIX_DP
>>>            for the Analogix Core DP driver. If you want to enable DP
>>>            on RK3288 based SoC, you should selet this option.
>>>
>>> +config ROCKCHIP_CDN_DP
>>> +        tristate "Rockchip cdn DP"
>>> +        depends on DRM_ROCKCHIP
>>> +        help
>>> +         This selects support for Rockchip SoC specific extensions
>>> +         for the cdn Dp driver. If you want to enable Dp on
>>
>> s/Dp/DP/
>>
>>> +         RK3399 based SoC, you should selet this
>>
>> s/selet/select/
>>
>>> +         option.
>>> +
>>>   config ROCKCHIP_DW_HDMI
>>>           tristate "Rockchip specific extensions for Synopsys DW HDMI"
>>>           depends on DRM_ROCKCHIP
>>> diff --git a/drivers/gpu/drm/rockchip/Makefile
>>> b/drivers/gpu/drm/rockchip/Makefile
>>> index 05d0713..abdecd5 100644
>>> --- a/drivers/gpu/drm/rockchip/Makefile
>>> +++ b/drivers/gpu/drm/rockchip/Makefile
>>> @@ -7,6 +7,7 @@ rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
>>>   rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
>>>
>>>   obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
>>> +obj-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o
>>>   obj-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
>>>   obj-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi.o
>>>   obj-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
>>> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c
>>> b/drivers/gpu/drm/rockchip/cdn-dp-core.c
>>> new file mode 100644
>>> index 0000000..5b8a15e
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
>>> @@ -0,0 +1,761 @@
>>> +/*
>>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>>> + * Author: Chris Zhong <zyw@rock-chips.com>
>>> + *
>>> + * This software is licensed under the terms of the GNU General Public
>>> + * License version 2, as published by the Free Software Foundation, and
>>> + * may be copied, distributed, and modified under those terms.
>>> + *
>>> + * This program is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> + * GNU General Public License for more details.
>>> + */
>>> +
>>> +#include <drm/drmP.h>
>>> +#include <drm/drm_atomic_helper.h>
>>> +#include <drm/drm_crtc_helper.h>
>>> +#include <drm/drm_dp_helper.h>
>>> +#include <drm/drm_edid.h>
>>> +#include <drm/drm_of.h>
>>> +
>>> +#include <linux/clk.h>
>>> +#include <linux/component.h>
>>> +#include <linux/extcon.h>
>>> +#include <linux/firmware.h>
>>> +#include <linux/regmap.h>
>>> +#include <linux/reset.h>
>>> +#include <linux/mfd/syscon.h>
>>> +#include <linux/phy/phy.h>
>>> +
>>> +#include <sound/hdmi-codec.h>
>>> +
>>> +#include <video/of_videomode.h>
>>> +#include <video/videomode.h>
>>
>> Doesn't seem like you use these.
>>
>>
>>> +
>>> +#include "cdn-dp-core.h"
>>> +#include "cdn-dp-reg.h"
>>> +#include "rockchip_drm_vop.h"
>>> +
>>> +#define connector_to_dp(c) \
>>> +               container_of(c, struct cdn_dp_device, connector)
>>> +
>>> +#define encoder_to_dp(c) \
>>> +               container_of(c, struct cdn_dp_device, encoder)
>>> +
>>> +/* dp grf register offset */
>>> +#define DP_VOP_SEL             0x6224
>>> +#define DP_SEL_VOP_LIT         BIT(12)
>>> +#define DP_CLK_RATE            100000000
>>
>> If this clock rate never changes, it should probably live somewhere in
>> the clock driver.
>
>
>
>>
>>> +#define WAIT_HPD_STABLE                300
>>
>> Encode the units in these two define names, ie: DP_CLK_RATE_HZ,
>> WAIT_HPD_STABLE_MS
>>
>>> +
>>> +static int cdn_dp_clk_enable(struct cdn_dp_device *dp)
>>> +{
>>> +       int ret;
>>> +
>>> +       ret = clk_prepare_enable(dp->pclk);
>>> +       if (ret < 0) {
>>> +               dev_err(dp->dev, "cannot enable dp pclk %d\n", ret);
>>> +               goto err_pclk;
>>> +       }
>>> +
>>> +       ret = clk_prepare_enable(dp->core_clk);
>>> +       if (ret < 0) {
>>> +               dev_err(dp->dev, "cannot enable core_clk %d\n", ret);
>>> +               goto err_core_clk;
>>> +       }
>>> +
>>> +       ret = clk_set_rate(dp->core_clk, DP_CLK_RATE);
>>> +       if (ret < 0) {
>>> +               dev_err(dp->dev, "cannot set dp core clk to %d %d\n",
>>> +                       DP_CLK_RATE, ret);
>>> +               goto err_set_rate;
>>> +       }
>>> +
>>> +       /* notice fw the clk freq value */
>>
>> This might be clearer if it was rephrased to "update fw with the
>> current core clk frequency". I still think the rate should be set in
>> the clock driver, and perhaps you can query the rate to get the
>> current value.
>
> This function is setting the controller register, it let the uCPU know what
> freq it is, then the uCPU use this value to do something.
>

I think that's consistent with my suggestion. Either way, this comment
doesn't make sense, so please either remove it or improve it.

>>
>>> +       cdn_dp_set_fw_clk(dp, DP_CLK_RATE);
>>> +
>>> +       return 0;
>>> +
>>> +err_set_rate:
>>> +       clk_disable_unprepare(dp->core_clk);
>>> +err_core_clk:
>>> +       clk_disable_unprepare(dp->pclk);
>>> +err_pclk:
>>> +       return ret;
>>> +}
>>> +
>>> +static enum drm_connector_status
>>> +cdn_dp_connector_detect(struct drm_connector *connector, bool force)
>>> +{
>>> +       struct cdn_dp_device *dp = connector_to_dp(connector);
>>> +
>>> +       return dp->hpd_status ? connector_status_connected :
>>> +                               connector_status_disconnected;
>>
>> If you just stored hpd_status as drm_connector_status, you could avoid
>> having to translate the bool.
>>
>>> +}
>>> +
>>> +static void cdn_dp_connector_destroy(struct drm_connector *connector)
>>> +{
>>> +       drm_connector_unregister(connector);
>>> +       drm_connector_cleanup(connector);
>>> +}
>>> +
>>> +static struct drm_connector_funcs cdn_dp_atomic_connector_funcs = {
>>> +       .dpms = drm_atomic_helper_connector_dpms,
>>> +       .detect = cdn_dp_connector_detect,
>>> +       .destroy = cdn_dp_connector_destroy,
>>> +       .fill_modes = drm_helper_probe_single_connector_modes,
>>> +       .reset = drm_atomic_helper_connector_reset,
>>> +       .atomic_duplicate_state =
>>> drm_atomic_helper_connector_duplicate_state,
>>> +       .atomic_destroy_state =
>>> drm_atomic_helper_connector_destroy_state,
>>> +};
>>> +
>>> +static int cdn_dp_connector_get_modes(struct drm_connector *connector)
>>> +{
>>> +       struct cdn_dp_device *dp = connector_to_dp(connector);
>>> +       struct edid *edid;
>>> +
>>> +       if (!dp->fw_loaded)
>>
>> Can this actually happen? Seems like we shouldn't be returning
>> connected from detect if the fw isn't loaded, so this should never be
>> true. If that is the case, I think BUG_ON or WARN_ON would be more
>> appropriate here.
>>
>>
>>> +               return 0;
>>> +
>>> +       edid = drm_do_get_edid(connector, cdn_dp_get_edid_block, dp);
>>> +       if (edid) {
>>> +               dev_dbg(dp->dev, "got edid: width[%d] x height[%d]\n",
>>> +                       edid->width_cm, edid->height_cm);
>>> +
>>> +               dp->sink_has_audio = drm_detect_monitor_audio(edid);
>>> +               drm_mode_connector_update_edid_property(connector, edid);
>>> +               drm_add_edid_modes(connector, edid);
>>> +               /* Store the ELD */
>>
>> This comment isn't useful
>>
>>> +               drm_edid_to_eld(connector, edid);
>>> +               kfree(edid);
>>> +       } else {
>>> +               dev_dbg(dp->dev, "failed to get edid\n");
>>
>> This seems to warrant a stronger level than dbg, perhaps warn. Also,
>> should this really return success?
>>
>>> +       }
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static struct drm_encoder *
>>> +       cdn_dp_connector_best_encoder(struct drm_connector *connector)
>>> +{
>>> +       struct cdn_dp_device *dp = connector_to_dp(connector);
>>> +
>>> +       return &dp->encoder;
>>> +}
>>> +
>>> +static int cdn_dp_connector_mode_valid(struct drm_connector *connector,
>>> +                                      struct drm_display_mode *mode)
>>> +{
>>> +       struct cdn_dp_device *dp = connector_to_dp(connector);
>>> +       struct drm_display_info *display_info =
>>> &dp->connector.display_info;
>>> +       u32 requested = mode->clock * display_info->bpc * 3 / 1000;
>>> +       u32 actual, rate;
>>> +
>>> +       rate = drm_dp_bw_code_to_link_rate(dp->link.rate);
>>> +       actual = rate * dp->link.num_lanes / 100;
>>> +
>>> +       /* efficiency is about 0.8 */
>>> +       actual = actual * 8 / 10;
>>> +
>>> +       if (requested > actual) {
>>> +               dev_dbg(dp->dev, "requested=%d, actual=%d, clock=%d\n",
>>> +                       requested, actual, mode->clock);
>>> +               return MODE_CLOCK_HIGH;
>>> +       }
>>
>> This doesn't seem like something that is likely to happen given the
>> trained rate should be a function of the display that's attached. Is
>> that correct?
>
> Yes, the link training should be a function of the display. But sometimes
> the edid give
> us a excess size for default resolution, it need somewhere to do this mode
> valid checking.
> Maybe we can do it at mode_set.
>

That's kind of interesting. You've seen displays which give you a lane
count that is insufficient for the modes it provides in the edid?
You're always training at 5.4 GHz, so I wouldn't expect this.

> ...
>>
>>
>>> +       hdr = (struct cdn_firmware_header *)fw->data;
>>> +       if (fw->size != le32_to_cpu(hdr->size_bytes))
>>> +               return -EINVAL;
>>> +
>>> +       iram_data = (const u32 *)(fw->data + hdr->header_size);
>>> +       dram_data = (const u32 *)(fw->data + hdr->header_size +
>>> hdr->iram_size);
>>> +
>>> +       ret = cdn_dp_load_firmware(dp, iram_data, hdr->iram_size,
>>> +                                  dram_data, hdr->dram_size);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       release_firmware(fw);
>>
>> Move this out of here and into the same scope as request_firmware so
>> there are no leaks.
>>
>>> +
>>> +       ret = cdn_dp_active(dp, true);
>>> +       if (ret) {
>>> +               dev_err(dp->dev, "active ucpu failed: %d\n", ret);
>>> +               return ret;
>>> +       }
>>> +
>>> +       return cdn_dp_event_config(dp);
>>> +}
>>> +
>>> +static int cdn_dp_init(struct cdn_dp_device *dp)
>>> +{
>>> +       struct device *dev = dp->dev;
>>> +       struct device_node *np = dev->of_node;
>>> +       struct platform_device *pdev = to_platform_device(dev);
>>> +       struct resource *res;
>>> +       int ret;
>>> +
>>> +       dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
>>> +       if (IS_ERR(dp->grf)) {
>>> +               dev_err(dev, "cdn-dp needs rockchip,grf property\n");
>>> +               return PTR_ERR(dp->grf);
>>> +       }
>>> +
>>> +       dp->irq = platform_get_irq(pdev, 0);
>>> +       if (dp->irq < 0) {
>>> +               dev_err(dev, "cdn-dp can not get irq\n");
>>> +               return dp->irq;
>>> +       }
>>> +
>>> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>> +       dp->regs = devm_ioremap_resource(dev, res);
>>> +       if (IS_ERR(dp->regs)) {
>>> +               dev_err(dev, "ioremap reg failed\n");
>>> +               return PTR_ERR(dp->regs);
>>> +       }
>>> +
>>> +       dp->core_clk = devm_clk_get(dev, "core-clk");
>>> +       if (IS_ERR(dp->core_clk)) {
>>> +               dev_err(dev, "cannot get core_clk_dp\n");
>>> +               return PTR_ERR(dp->core_clk);
>>> +       }
>>> +
>>> +       dp->pclk = devm_clk_get(dev, "pclk");
>>> +       if (IS_ERR(dp->pclk)) {
>>> +               dev_err(dev, "cannot get pclk\n");
>>> +               return PTR_ERR(dp->pclk);
>>> +       }
>>> +
>>> +       dp->spdif_clk = devm_clk_get(dev, "spdif");
>>> +       if (IS_ERR(dp->spdif_clk)) {
>>> +               dev_err(dev, "cannot get spdif_clk\n");
>>> +               return PTR_ERR(dp->spdif_clk);
>>> +       }
>>> +
>>> +       dp->spdif_rst = devm_reset_control_get(dev, "spdif");
>>> +       if (IS_ERR(dp->spdif_rst)) {
>>> +               dev_err(dev, "no spdif reset control found\n");
>>> +               return PTR_ERR(dp->spdif_rst);
>>> +       }
>>> +
>>> +       dp->dpms_mode = DRM_MODE_DPMS_OFF;
>>> +
>>> +       ret = cdn_dp_clk_enable(dp);
>>> +       if (ret < 0)
>>> +               return ret;
>>> +
>>> +       dp_clock_reset_seq(dp);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int cdn_dp_audio_hw_params(struct device *dev,
>>> +                                 struct hdmi_codec_daifmt *daifmt,
>>> +                                 struct hdmi_codec_params *params)
>>> +{
>>> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
>>> +       struct audio_info audio = {
>>> +               .sample_width = params->sample_width,
>>> +               .sample_rate = params->sample_rate,
>>> +               .channels = params->channels,
>>> +       };
>>> +       int ret;
>>> +
>>> +       if (!dp->encoder.crtc)
>>> +               return -ENODEV;
>>> +
>>> +       switch (daifmt->fmt) {
>>> +       case HDMI_I2S:
>>> +               audio.format = AFMT_I2S;
>>> +               break;
>>> +       case HDMI_SPDIF:
>>> +               audio.format = AFMT_SPDIF;
>>> +               break;
>>> +       default:
>>> +               dev_err(dev, "%s: Invalid format %d\n", __func__,
>>> daifmt->fmt);
>>> +               return -EINVAL;
>>> +       }
>>> +
>>> +       ret = cdn_dp_audio_config_set(dp, &audio);
>>> +       if (!ret)
>>> +               dp->audio_info = audio;
>>> +
>>> +       return ret;
>>> +}
>>> +
>>> +static void cdn_dp_audio_shutdown(struct device *dev)
>>> +{
>>> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
>>> +       int ret = cdn_dp_audio_stop(dp, &dp->audio_info);
>>> +
>>> +       if (!ret)
>>> +               dp->audio_info.format = AFMT_UNUSED;
>>> +}
>>> +
>>> +static int cdn_dp_audio_digital_mute(struct device *dev, bool enable)
>>> +{
>>> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
>>> +
>>> +       return cdn_dp_audio_mute(dp, enable);
>>> +}
>>> +
>>> +static int cdn_dp_audio_get_eld(struct device *dev, uint8_t *buf, size_t
>>> len)
>>> +{
>>> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
>>> +       struct drm_mode_config *config = &dp->encoder.dev->mode_config;
>>> +       struct drm_connector *connector;
>>> +       int ret = -ENODEV;
>>> +
>>> +       mutex_lock(&config->mutex);
>>> +       list_for_each_entry(connector, &config->connector_list, head) {
>>> +               if (&dp->encoder == connector->encoder) {
>>> +                       memcpy(buf, connector->eld,
>>> +                              min(sizeof(connector->eld), len));
>>> +                       ret = 0;
>>> +               }
>>> +       }
>>> +       mutex_unlock(&config->mutex);
>>> +
>>> +       return ret;
>>> +}
>>> +
>>> +static const struct hdmi_codec_ops audio_codec_ops = {
>>> +       .hw_params = cdn_dp_audio_hw_params,
>>> +       .audio_shutdown = cdn_dp_audio_shutdown,
>>> +       .digital_mute = cdn_dp_audio_digital_mute,
>>> +       .get_eld = cdn_dp_audio_get_eld,
>>> +};
>>> +
>>> +static int cdn_dp_audio_codec_init(struct cdn_dp_device *dp,
>>> +                                  struct device *dev)
>>> +{
>>> +       struct hdmi_codec_pdata codec_data = {
>>> +               .i2s = 1,
>>> +               .spdif = 1,
>>> +               .ops = &audio_codec_ops,
>>> +               .max_i2s_channels = 8,
>>> +       };
>>> +
>>> +       dp->audio_pdev = platform_device_register_data(
>>> +                        dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO,
>>> +                        &codec_data, sizeof(codec_data));
>>> +
>>> +       return PTR_ERR_OR_ZERO(dp->audio_pdev);
>>> +}
>>> +
>>> +static void cdn_dp_get_state(struct cdn_dp_device *dp, struct extcon_dev
>>> *edev)
>>> +{
>>> +       bool dfp, dptx;
>>> +       u8 cap_lanes;
>>> +
>>> +       dfp = extcon_get_cable_state_(edev, EXTCON_USB_HOST);
>>> +       dptx = extcon_get_cable_state_(edev, EXTCON_DISP_DP);
>>> +
>>> +       if (dfp && dptx)
>>> +               cap_lanes = 2;
>>> +       else if (dptx)
>>> +               cap_lanes = 4;
>>> +       else
>>> +               cap_lanes = 0;
>>> +
>>> +       if (cap_lanes != dp->cap_lanes) {
>>> +               dp->cap_lanes = cap_lanes;
>>> +               schedule_delayed_work(&dp->event_wq, 0);
>>> +       }
>>
>> A get function shouldn't be scheduling work. Keep the assignment here
>> and move the schedule into the pd_event function.
>>
>>> +}
>>> +
>>> +static int cdn_dp_pd_event(struct notifier_block *nb,
>>> +                          unsigned long event, void *priv)
>>> +{
>>> +       struct cdn_dp_device *dp;
>>> +       struct extcon_dev *edev = priv;
>>> +       u8 i;
>>> +
>>> +       dp = container_of(nb, struct cdn_dp_device, event_nb);
>>> +
>>> +       for (i = 0; i < MAX_PHY; i++) {
>>> +               if (edev == dp->extcon[i]) {
>>> +                       dp->port_id = i;
>>> +                       break;
>>> +               }
>>> +       }
>>> +
>>> +       cdn_dp_get_state(dp, edev);
>>
>> There's a race here. If the cap_lanes change in between scheduling the
>> work and the work function running (ie, plug/unplug), the work
>> function will have out-of-date information. You should call this from
>> the work function instead.
>>
>>
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static void cdn_dp_pd_event_wq(struct work_struct *work)
>>> +{
>>> +       struct cdn_dp_device *dp;
>>> +       int ret;
>>> +
>>> +       dp = container_of(work, struct cdn_dp_device, event_wq.work);
>>> +
>>> +       ret = request_firmware(&dp->fw, "cdn/dptx.bin", dp->dev);
>>
>> This filename needs to be abstracted into a #define.
>>
>> You're also failing to call release_firmware in a bunch of cases
>> below. It needs to be sorted out.
>>
>>
>>> +       if (ret == -ENOENT && !dp->fw_loaded) {
>>
>> So the case where ret == -ENOENT and dp->fw_loaded is valid? Or is that a
>> bug?
>>
>> I'm not convinced you need fw_loaded stored in cdn_dp_device. It seems
>> like something you could either get rid of (hopefully), or convert to
>> a local.
>
> it is not bug, "ret == -ENOENT" is for file missing, maybe user space is not
> ready.
>
> if dp->fw_loaded it means the firmware has been loaded, do not load again.
>

So how can the fw be already loaded and yet the fw file is missing?

>
>>
>>> +               /*
>>> +                * If can not find the file, retry to load the firmware
>>> in 1
>>> +                * second, if still failed after 1 minute, give up.
>>> +                */
>>> +               if (dp->fw_retry++ < 60) {
>>> +                       schedule_delayed_work(&dp->event_wq,
>>> +                                             msecs_to_jiffies(HZ));
>>
>> I'd rather do this as an exponential back-off so if it doesn't find
>> the file, it waits progressively longer.
>>
>> Perhaps something like:
>>
>> #define MAX_FW_WAIT_SECS 64
>>
>> [...]
>>
>> if (dp->fw_wait <= MAX_FW_WAIT_SECS) {
>>    schedule_delayed_work(&dp_>event_wq, msecs_to_jiffies(dp->fw_wait *
>> HZ));
>>    dp->fw_wait *= 2;
>> }
>
> If so, sometimes boot with DP will later than boot and then plug DP cable.
>

Yeah, I realize that. It's a trade-off between waking up every second
vs potentially delaying the external display coming up. I think I'm Ok
with the external display taking a bit longer.


>
>>> +               }
>>> +
>>> +               dev_err(dp->dev, "failed to request firmware %d\n", ret);
>>
>> Print the wait/retries.
>>
>>> +               return;
>>> +       }
>>> +
>>> +       if (!dp->cap_lanes) {
>>> +               if (dp->phy_status[dp->port_id])
>>> +                       phy_power_off(dp->phy[dp->port_id]);
>>> +               dp->phy_status[dp->port_id] = 0;
>>> +               dp->hpd_status = false;
>>> +               drm_helper_hpd_irq_event(dp->drm_dev);
>>> +               return;
>>> +       }
>>> +
>>> +       if (!dp->phy_status[dp->port_id]) {
>>> +               ret = phy_power_on(dp->phy[dp->port_id]);
>>> +               if (ret)
>>
>> Log something in this case
>>
>>> +                       return;
>>> +
>>> +               msleep(WAIT_HPD_STABLE);
>>
>> 300ms is a long time, do we really need to wait that long?
>
> The 300ms is a safe value, maybe this time can be reduced
>

Yeah, the DisplayPort spec states the HPD pulse should be considered a
plug event after 2ms, so I strongly suspect this can be pulled in.

>>
>>> +       }
>>> +
>>> +       dp->phy_status[dp->port_id] = 1;
>>
>> Do you really need phy_status? It seems like if things are
>> well-behaved you shouldn't need it. Of course, if you're turning
>> on/off multiple times, perhaps there's a bug elsewhere.
>
> This status is needed, since the phy_power_on would increase the
> power_count, this status can avoid power on multiple times
>

Yeah, I realize the phy_power is refcounted. Why do you have unmatched
on/off calls?

>>
>>> +
>>> +       ret = cdn_dp_firmware_init(dp);
>>> +       if (ret)
>>> +               return;
>>> +
>>> +       /* read hpd status failed, or the hpd does not exist. */
>>> +       ret = cdn_dp_get_hpd_status(dp);
>>> +       if (ret <= 0)
>>
>> Log something?
>>
>>> +               return;
>>> +
>>> +       /*
>>> +        * Set the capability and start the training process once
>>> +        * the hpd is detected.
>>> +        */
>>> +       ret = cdn_dp_set_host_cap(dp);
>>> +       if (ret) {
>>> +               dev_err(dp->dev, "set host capabilities failed\n");
>>
>> print ret
>>
>>> +               return;
>>> +       }
>>> +
>>> +       ret = cdn_dp_training_start(dp);
>>> +       if (ret) {
>>> +               dev_err(dp->dev, "hw lt err:%d\n", ret);
>>
>> s/lt/link training/
>>
>>> +               return;
>>> +       }
>>> +
>>> +       ret = cdn_dp_get_lt_status(dp);
>>
>> Change to cdn_dp_get_training_status
>>
>>> +       if (ret) {
>>> +               dev_err(dp->dev, "hw lt get status failed\n");
>>
>> print ret
>>
>>> +               return;
>>> +       }
>>> +
>>> +       dev_info(dp->dev, "rate:%d, lanes:%d\n",
>>> +                dp->link.rate, dp->link.num_lanes);
>>> +
>>
>> I think everything from cdn_dp_firmare_init to here should be moved
>> out of the wq and deferred until modeset time. That way you're
>> training the link at the same time as you're setting the mode, which
>> should obviate the need to check the clocks in mode_valid.
>
>
> cdn_dp_training_start and cdn_dp_get_lt_status can be move the mode_set,
> but the others are need by get_modes, so stay here is better.
>

Ok, that sounds like an improvement.

>>
>>> +       dp->hpd_status = true;
>>> +       drm_helper_hpd_irq_event(dp->drm_dev);
>>> +}
>>> +
>>> +static int cdn_dp_bind(struct device *dev, struct device *master,
>>> +                      void *data)
>>> +{
>>> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
>>> +       struct drm_encoder *encoder;
>>> +       struct drm_connector *connector;
>>> +       struct drm_device *drm_dev = data;
>>> +       int ret, i;
>>> +
>>> +       ret = cdn_dp_init(dp);
>>> +       if (ret < 0)
>>> +               return ret;
>>> +
>>> +       dp->drm_dev = drm_dev;
>>> +
>>> +       encoder = &dp->encoder;
>>> +
>>> +       encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
>>> +
>>> dev->of_node);
>>> +       DRM_DEBUG_KMS("possible_crtcs = 0x%x\n",
>>> encoder->possible_crtcs);
>>> +
>>> +       ret = drm_encoder_init(drm_dev, encoder, &cdn_dp_encoder_funcs,
>>> +                              DRM_MODE_ENCODER_TMDS, NULL);
>>> +       if (ret) {
>>> +               DRM_ERROR("failed to initialize encoder with drm\n");
>>> +               return ret;
>>> +       }
>>> +
>>> +       drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs);
>>> +
>>> +       connector = &dp->connector;
>>> +       connector->polled = DRM_CONNECTOR_POLL_HPD;
>>> +       connector->dpms = DRM_MODE_DPMS_OFF;
>>> +
>>> +       ret = drm_connector_init(drm_dev, connector,
>>> +                                &cdn_dp_atomic_connector_funcs,
>>> +                                DRM_MODE_CONNECTOR_DisplayPort);
>>> +       if (ret) {
>>> +               DRM_ERROR("failed to initialize connector with drm\n");
>>> +               goto err_free_encoder;
>>> +       }
>>> +
>>> +       drm_connector_helper_add(connector,
>>> &cdn_dp_connector_helper_funcs);
>>> +
>>> +       ret = drm_mode_connector_attach_encoder(connector, encoder);
>>> +       if (ret) {
>>> +               DRM_ERROR("failed to attach connector and encoder\n");
>>> +               goto err_free_connector;
>>> +       }
>>> +
>>> +       cdn_dp_audio_codec_init(dp, dev);
>>> +
>>> +       dp->event_nb.notifier_call = cdn_dp_pd_event;
>>> +       INIT_DELAYED_WORK(&dp->event_wq, cdn_dp_pd_event_wq);
>>> +
>>> +       for (i = 0; i < MAX_PHY; i++) {
>>> +               if (IS_ERR(dp->extcon[i]))
>>> +                       continue;
>>> +
>>> +               ret = extcon_register_notifier(dp->extcon[i],
>>> EXTCON_DISP_DP,
>>> +                                              &dp->event_nb);
>>> +               if (ret) {
>>> +                       dev_err(dev, "regitster EXTCON_DISP_DP notifier
>>> err\n");
>>> +                       return ret;
>>> +               }
>>> +
>>> +               cdn_dp_get_state(dp, dp->extcon[i]);
>>> +       }
>>> +
>>> +       return 0;
>>> +
>>> +err_free_connector:
>>> +       drm_connector_cleanup(connector);
>>> +err_free_encoder:
>>> +       drm_encoder_cleanup(encoder);
>>> +       return ret;
>>> +}
>>> +
>>> +static void cdn_dp_unbind(struct device *dev, struct device *master,
>>> void *data)
>>> +{
>>> +       struct cdn_dp_device *dp = dev_get_drvdata(dev);
>>> +       struct drm_encoder *encoder = &dp->encoder;
>>> +       int i;
>>> +
>>> +       platform_device_unregister(dp->audio_pdev);
>>> +       cdn_dp_encoder_disable(encoder);
>>> +       encoder->funcs->destroy(encoder);
>>> +       drm_connector_unregister(&dp->connector);
>>> +       drm_connector_cleanup(&dp->connector);
>>
>> just call connector->destroy() here
>>
>>> +       drm_encoder_cleanup(encoder);
>>
>> this is already called by encoder->destroy()
>>
>>> +
>>> +       for (i = 0; i < MAX_PHY; i++) {
>>> +               if (!IS_ERR(dp->extcon[i]))
>>> +                       extcon_unregister_notifier(dp->extcon[i],
>>> EXTCON_USB,
>>> +                                                  &dp->event_nb);
>>> +       }
>>> +}
>>> +
>>> +static const struct component_ops cdn_dp_component_ops = {
>>> +       .bind = cdn_dp_bind,
>>> +       .unbind = cdn_dp_unbind,
>>> +};
>>> +
>>> +static int cdn_dp_probe(struct platform_device *pdev)
>>> +{
>>> +       struct device *dev = &pdev->dev;
>>> +       struct cdn_dp_device *dp;
>>> +       u8 count = 0;
>>> +       int i;
>>> +
>>> +       dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
>>> +       if (!dp)
>>> +               return -ENOMEM;
>>> +       dp->dev = dev;
>>> +
>>> +       for (i = 0; i < MAX_PHY; i++) {
>>> +               dp->extcon[i] = extcon_get_edev_by_phandle(dev, i);
>>> +               dp->phy[i] = devm_of_phy_get_by_index(dev, dev->of_node,
>>> i);
>>> +
>>> +               if (!IS_ERR(dp->extcon[i]) && !IS_ERR(dp->phy[i]))
>>> +                       count++;
>>> +
>>> +               if (PTR_ERR(dp->extcon[i]) == -EPROBE_DEFER ||
>>> +                   PTR_ERR(dp->phy[i]) == -EPROBE_DEFER)
>>> +                       return -EPROBE_DEFER;
>>> +       }
>>> +
>>> +       if (!count) {
>>> +               dev_err(dev, "missing extcon or phy\n");
>>> +               return -EINVAL;
>>> +       }
>>> +
>>> +       dev_set_drvdata(dev, dp);
>>> +
>>> +       return component_add(dev, &cdn_dp_component_ops);
>>> +}
>>> +
>>> +static int cdn_dp_remove(struct platform_device *pdev)
>>> +{
>>> +       component_del(&pdev->dev, &cdn_dp_component_ops);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static const struct of_device_id cdn_dp_dt_ids[] = {
>>> +       { .compatible = "rockchip,rk3399-cdn-dp" },
>>> +       {}
>>> +};
>>> +
>>> +MODULE_DEVICE_TABLE(of, cdn_dp_dt_ids);
>>> +
>>> +static struct platform_driver cdn_dp_driver = {
>>> +       .probe = cdn_dp_probe,
>>> +       .remove = cdn_dp_remove,
>>> +       .driver = {
>>> +                  .name = "cdn-dp",
>>> +                  .owner = THIS_MODULE,
>>> +                  .of_match_table = of_match_ptr(cdn_dp_dt_ids),
>>> +       },
>>> +};
>>> +
>>> +module_platform_driver(cdn_dp_driver);
>>> +
>>> +MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
>>> +MODULE_DESCRIPTION("cdn DP Driver");
>>> +MODULE_LICENSE("GPL v2");
>>> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h
>>> b/drivers/gpu/drm/rockchip/cdn-dp-core.h
>>> new file mode 100644
>>> index 0000000..31ba22d
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h
>>> @@ -0,0 +1,113 @@
>>> +/*
>>> + * Copyright (C) 2016 Chris Zhong <zyw@rock-chips.com>
>>> + * Copyright (C) 2016 ROCKCHIP, Inc.
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License as published by
>>> + * the Free Software Foundation; either version 2 of the License.
>>> + *
>>> + * This program is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> + * GNU General Public License for more details.
>>> + */
>>> +
>>> +#ifndef _ROCKCHIP_EDP_CORE_H
>>> +#define _ROCKCHIP_EDP_CORE_H
>>
>> I think the include guard should match the filename
>>
>>> +
>>> +#include <drm/drmP.h>
>>> +#include <drm/drm_crtc_helper.h>
>>> +#include <drm/drm_dp_helper.h>
>>> +#include <drm/drm_panel.h>
>>> +#include "rockchip_drm_drv.h"
>>> +#include "cdn-dp-reg.h"
>>> +
>>> +#define MAX_PHY                2
>>
>> This should probably be stored in the .data member of of_device_id.
>>
>>> +
>>> +enum AUDIO_FORMAT {
>>
>> enum audio_format
>>
>>> +       AFMT_I2S = 0,
>>> +       AFMT_SPDIF = 1,
>>> +       AFMT_UNUSED,
>>> +};
>>> +
>>> +struct audio_info {
>>> +       enum AUDIO_FORMAT format;
>>> +       int sample_rate;
>>> +       int channels;
>>> +       int sample_width;
>>> +};
>>> +
>>> +struct video_info {
>>> +       bool h_sync_polarity;
>>> +       bool v_sync_polarity;
>>> +       bool interlaced;
>>> +       int color_depth;
>>> +       enum VIC_PXL_ENCODING_FORMAT color_fmt;
>>> +};
>>> +
>>> +struct cdn_firmware_header {
>>> +       u32 size_bytes; /* size of the entire header+image(s) in bytes */
>>> +       u32 header_size; /* size of just the header in bytes */
>>> +       u32 iram_size; /* size of iram */
>>> +       u32 dram_size; /* size of dram */
>>> +};
>>> +
>>> +struct cdn_dp_device {
>>> +       struct device *dev;
>>> +       struct drm_device *drm_dev;
>>> +       struct drm_connector connector;
>>> +       struct drm_encoder encoder;
>>> +       struct drm_display_mode mode;
>>> +       struct platform_device *audio_pdev;
>>> +
>>> +       const struct firmware *fw;      /* cdn dp firmware */
>>> +       unsigned int fw_version;        /* cdn fw version */
>>> +       u32 fw_retry;
>>> +       bool fw_loaded;
>>> +       void __iomem *regs;
>>> +       struct regmap *grf;
>>> +       unsigned int irq;
>>> +       struct clk *core_clk;
>>> +       struct clk *pclk;
>>> +       struct clk *spdif_clk;
>>> +       struct reset_control *spdif_rst;
>>> +       struct audio_info audio_info;
>>> +       struct video_info video_info;
>>> +       struct extcon_dev *extcon[MAX_PHY];
>>> +       struct phy *phy[MAX_PHY];
>>> +       struct notifier_block event_nb;
>>> +       struct delayed_work event_wq;
>>> +
>>> +       u8 port_id;
>>> +       u8 cap_lanes;
>>> +       bool hpd_status;
>>> +       bool phy_status[MAX_PHY];
>>> +
>>> +       int dpms_mode;
>>> +       struct drm_dp_link link;
>>> +       bool sink_has_audio;
>>> +};
>>> +
>>> +void dp_clock_reset_seq(struct cdn_dp_device *dp);
>>
>> cdn_ prefix here?
>>
>>> +
>>> +void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, int clk);
>>> +int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
>>> +                        u32 i_size, const u32 *d_mem, u32 d_size);
>>> +int cdn_dp_active(struct cdn_dp_device *dp, bool enable);
>>> +int cdn_dp_set_host_cap(struct cdn_dp_device *dp);
>>> +int cdn_dp_event_config(struct cdn_dp_device *dp);
>>> +int cdn_dp_get_event(struct cdn_dp_device *dp);
>>> +int cdn_dp_get_hpd_status(struct cdn_dp_device *dp);
>>> +int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value);
>>> +int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr);
>>> +int cdn_dp_get_edid_block(void *dp, u8 *edid,
>>> +                         unsigned int block, size_t length);
>>> +int cdn_dp_training_start(struct cdn_dp_device *dp);
>>> +int cdn_dp_get_lt_status(struct cdn_dp_device *dp);
>>> +int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active);
>>> +int cdn_dp_config_video(struct cdn_dp_device *dp);
>>> +int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info
>>> *audio);
>>> +int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable);
>>> +int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info
>>> *audio);
>>> +
>>
>> I really don't like that you've declared these in core.h, yet defined
>> them in reg.c.
>>
>>> +#endif  /* _ROCKCHIP_EDP_CORE_H */
>>
>> Make sure you update here too
>>
>>
>>> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.c
>>> b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
>>> new file mode 100644
>>> index 0000000..8d5becd
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
>>> @@ -0,0 +1,740 @@
>>> +/*
>>> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
>>> + * Author: Chris Zhong <zyw@rock-chips.com>
>>> + *
>>> + * This software is licensed under the terms of the GNU General Public
>>> + * License version 2, as published by the Free Software Foundation, and
>>> + * may be copied, distributed, and modified under those terms.
>>> + *
>>> + * This program is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> + * GNU General Public License for more details.
>>> + */
>>> +
>>> +#include <linux/clk.h>
>>> +#include <linux/device.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/io.h>
>>> +#include <linux/iopoll.h>
>>> +#include <linux/reset.h>
>>> +
>>> +#include "cdn-dp-core.h"
>>> +#include "cdn-dp-reg.h"
>>> +
>>> +#define CDN_DP_SPDIF_CLK               200000000
>>> +#define FW_ALIVE_TIMEOUT_US            1000000
>>> +#define MAILBOX_TIMEOUT_US             5000000
>>> +
>>> +void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, int clk)
>>> +{
>>> +       writel(clk / 1000000, dp->regs + SW_CLK_H);
>>> +}
>>> +
>>> +void dp_clock_reset_seq(struct cdn_dp_device *dp)
>>> +{
>>> +       writel(0xfff, dp->regs + SOURCE_DPTX_CAR);
>>> +       writel(0x7, dp->regs + SOURCE_PHY_CAR);
>>> +       writel(0xf, dp->regs + SOURCE_PKT_CAR);
>>> +       writel(0xff, dp->regs + SOURCE_AIF_CAR);
>>> +       writel(0xf, dp->regs + SOURCE_CIPHER_CAR);
>>> +       writel(0x3, dp->regs + SOURCE_CRYPTO_CAR);
>>> +       writel(0, dp->regs + APB_INT_MASK);
>>
>> Pull these hardcoded values out into #defines
>>
>>> +}
>>> +
>>> +static int cdn_dp_mailbox_read(struct cdn_dp_device *dp)
>>> +{
>>> +       int val, ret;
>>> +
>>> +       if (!dp->fw_loaded)
>>> +               return 0;
>>
>> This seems like an error. Return -errno and log a message
>
> The audio will call this function before firmware during probe, return err
> cause audio register failed.
>

Hmm, so what happens when you return 0? Isn't that a potentially valid
value that could cause problems on the audio side?

>>
>>> +
>>> +       ret = readx_poll_timeout(readl, dp->regs + MAILBOX_EMPTY_ADDR,
>>> +                                val, !val, 1000, MAILBOX_TIMEOUT_US);
>>
>> Pull the 1000 out into a #define (here and below)
>>
>>> +       if (ret < 0) {
>>> +               dev_err(dp->dev, "failed to read mailbox, keep alive =
>>> %x\n",
>>> +                       readl(dp->regs + KEEP_ALIVE));
>>> +               return ret;
>>> +       }
>>> +
>>> +       return readl(dp->regs + MAILBOX0_RD_DATA) & 0xff;
>>> +}
>>> +
>>> +static int cdp_dp_mailbox_write(struct cdn_dp_device *dp, u8 val)
>>> +{
>>> +       int ret, full;
>>> +
>>> +       if (!dp->fw_loaded)
>>> +               return 0;
>>
>> Same here
>>
>>> +
>>> +       ret = readx_poll_timeout(readl, dp->regs + MAILBOX_FULL_ADDR,
>>> +                                full, !full, 1000, MAILBOX_TIMEOUT_US);
>>> +       if (ret < 0) {
>>> +               dev_err(dp->dev, "mailbox is full, keep alive = %x\n",
>>> +                       readl(dp->regs + KEEP_ALIVE));
>>> +               return ret;
>>> +       }
>>> +
>>> +       writel(val, dp->regs + MAILBOX0_WR_DATA);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int cdn_dp_mailbox_response(struct cdn_dp_device *dp, u8
>>> module_id,
>>
>> I think _receive would be a better name than _response
>>
>>> +                                  u8 opcode, u8 *buff, u8 buff_size)
>>> +{
>>> +       int ret, i = 0;
>>> +       u8 header[4];
>>> +
>>> +       /* read the header of the message */
>>> +       while (i < 4) {
>>
>> for (i = 0; i < 4; i++) {
>>
>>> +               ret = cdn_dp_mailbox_read(dp);
>>> +               if (ret < 0)
>>> +                       return ret;
>>> +
>>> +               header[i++] = ret;
>>
>> header[i] = ret;
>>
>>> +       }
>>> +
>>> +       if (opcode != header[0] || module_id != header[1] ||
>>> +           buff_size != ((header[2] << 8) | header[3])) {
>>
>> pull header[2] << 8 | header[3] out into a local, mbox_size.
>>
>>> +               dev_err(dp->dev, "mailbox response failed");
>>> +
>>> +               /*
>>> +                * If the message in mailbox is not what we want, we need
>>> to
>>> +                * clear the mailbox by read.
>>> +                */
>>> +               i = (header[2] << 8) | header[3];
>>> +               while (i--)
>>
>> for (i = 0; i < mbox_size; i++)
>>
>>> +                       if (cdn_dp_mailbox_read(dp) < 0)
>>> +                               break;
>>> +
>>> +               return -EINVAL;
>>> +       }
>>> +
>>> +       i = 0;
>>> +       while (i < buff_size) {
>>
>> for (i = 0; i < buff_size; i++) {
>>
>>> +               ret = cdn_dp_mailbox_read(dp);
>>> +               if (ret < 0)
>>> +                       return ret;
>>> +
>>> +               buff[i++] = ret;
>>
>>   buff[i] = ret;
>>
>>> +       }
>>> +
>>> +       return 0;
>>
>>
>> Are you always guaranteed to have the entire message in the mailbox?
>> If not, you might consider supporting partial messages and returning
>> the length read.
>>
>>> +}
>>> +
>>> +static int cdn_dp_mailbox_send(struct cdn_dp_device *dp, u8 module_id,
>>> +                              u8 opcode, u16 size, u8 *message)
>>> +{
>>> +       u8 header[4];
>>> +       int ret, i;
>>> +
>>> +       header[0] = opcode;
>>> +       header[1] = module_id;
>>> +       header[2] = size >> 8;
>>
>> (size >> 8) & 0xff;
>>
>>> +       header[3] = size & 0xff;
>>> +
>>> +       for (i = 0; i < 4; i++) {
>>> +               ret = cdp_dp_mailbox_write(dp, header[i]);
>>> +               if (ret)
>>> +                       return ret;
>>> +       }
>>> +
>>> +       while (size--) {
>>
>> for (i = 0; i < size; i++) {
>>
>>> +               ret = cdp_dp_mailbox_write(dp, *message++);
>>
>> message[i]
>>
>>> +               if (ret)
>>
>> Log something here?
>>
>>> +                       return ret;
>>> +       }
>>> +
>>> +       return 0;
>>
>> Perhaps consider returning the number of bytes that were sent.
>>
>>> +}
>>> +
>>> +static int cdn_dp_reg_write(struct cdn_dp_device *dp, u16 addr, u32 val)
>>> +{
>>> +       u8 msg[6];
>>> +
>>> +       msg[0] = (addr >> 8) & 0xff;
>>> +       msg[1] = addr & 0xff;
>>> +       msg[2] = (val >> 24) & 0xff;
>>> +       msg[3] = (val >> 16) & 0xff;
>>> +       msg[4] = (val >> 8) & 0xff;
>>> +       msg[5] = val & 0xff;
>>> +       return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
>>> DPTX_WRITE_REGISTER,
>>> +                                  ARRAY_SIZE(msg), msg);
>>> +}
>>> +
>>> +int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
>>> +                        u32 i_size, const u32 *d_mem, u32 d_size)
>>> +{
>>> +       int i, reg, ret;
>>
>> reg should be u32
>>
>>> +
>>> +       /* reset ucpu before load firmware*/
>>> +       writel(APB_IRAM_PATH | APB_DRAM_PATH | APB_XT_RESET,
>>> +              dp->regs + APB_CTRL);
>>> +
>>> +       for (i = 0; i < i_size; i += 4)
>>> +               writel(*i_mem++, dp->regs + ADDR_IMEM + i);
>>> +
>>> +       for (i = 0; i < d_size; i += 4)
>>> +               writel(*d_mem++, dp->regs + ADDR_DMEM + i);
>>> +
>>> +       /* un-reset ucpu */
>>> +       writel(0, dp->regs + APB_CTRL);
>>> +
>>> +       /* check the keep alive register to make sure fw working */
>>> +       ret = readx_poll_timeout(readl, dp->regs + KEEP_ALIVE,
>>> +                                reg, reg, 2000, FW_ALIVE_TIMEOUT_US);
>>> +       if (ret < 0) {
>>> +               dev_err(dp->dev, "failed to loaded the FW reg = %x\n",
>>> reg);
>>> +               return -EINVAL;
>>> +       }
>>> +
>>> +       reg = readl(dp->regs + VER_L) & 0xff;
>>> +       dp->fw_version = reg;
>>> +       reg = readl(dp->regs + VER_H) & 0xff;
>>> +       dp->fw_version |= reg << 8;
>>> +       reg = readl(dp->regs + VER_LIB_L_ADDR) & 0xff;
>>> +       dp->fw_version |= reg << 16;
>>> +       reg = readl(dp->regs + VER_LIB_H_ADDR) & 0xff;
>>> +       dp->fw_version |= reg << 24;
>>> +
>>
>> Are there any sanity checks you can do on the fw_version before returning?
>
> This is first version of firmware, so do not nedd check the number, But I
> think adding a dev_dbg log here is better.
>

Ok, sounds good.

>>
>>> +       dp->fw_loaded = 1;
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +int cdn_dp_reg_write_bit(struct cdn_dp_device *dp, u16 addr, u8
>>> start_bit,
>>> +                        u8 bits_no, u32 val)
>>> +{
>>> +       u8 field[8];
>>> +
>>> +       field[0] = (addr >> 8) & 0xff;
>>> +       field[1] = addr & 0xff;
>>> +       field[2] = start_bit;
>>> +       field[3] = bits_no;
>>> +       field[4] = (val >> 24) & 0xff;
>>> +       field[5] = (val >> 16) & 0xff;
>>> +       field[6] = (val >> 8) & 0xff;
>>> +       field[7] = val & 0xff;
>>> +
>>> +       return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
>>> DPTX_WRITE_FIELD,
>>> +                           sizeof(field), field);
>>> +}
>>> +
>>> +int cdn_dp_active(struct cdn_dp_device *dp, bool enable)
>>> +{
>>> +       u8 active = enable ? 1 : 0;
>>> +       u8 resp;
>>> +       int ret;
>>> +
>>> +       /* set firmware status, 1: avtive; 0: standby */
>>
>> s/avtive/active/
>>
>>> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_GENERAL,
>>> +                                 GENERAL_MAIN_CONTROL, 1, &active);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_GENERAL,
>>> +                                     GENERAL_MAIN_CONTROL, &resp, 1);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       return resp ? 0 : -EINVAL;
>>> +}
>>> +
>>> +int cdn_dp_set_host_cap(struct cdn_dp_device *dp)
>>> +{
>>> +       u8 msg[8];
>>> +       int ret;
>>> +
>>> +       msg[0] = DP_LINK_BW_5_4;
>>
>> Why is this always 5.4GHz? Does the hw support any other rates?
>
> It is setting the max rate to 5.4G, not all support rate.
>

Ah, so it will automatically downgrade the link speed in the training
process if the sink doesn't support it?

>>
>>> +       msg[1] = dp->cap_lanes;
>>> +       msg[2] = VOLTAGE_LEVEL_2;
>>> +       msg[3] = PRE_EMPHASIS_LEVEL_3;
>>> +       msg[4] = PRBS7 | D10_2 | TRAINING_PTN1 | TRAINING_PTN2;
>>
>> You don't need to use training pattern 3 for hbr modes?
>
> This is the fault of pattern name, it should be:
>
> msg[4] = TPS1 | TPS2 | TPS3 | TPS4;
>
>
>
>>
>>> +       msg[5] = FAST_LT_NOT_SUPPORT;
>>> +       msg[6] = LANE_MAPPING_NORMAL;
>>> +       msg[7] = ENHANCED;
>>> +
>>> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
>>> +                                 DPTX_SET_HOST_CAPABILITIES,
>>> +                                 ARRAY_SIZE(msg), msg);
>>
>> ARRAY_SIZE counts the number of elements, not the size, so I think
>> you're better off using sizeof instead. This applies to the rest of
>> the dp_mailbox_send instances as well.
>>
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = cdn_dp_reg_write(dp, DP_AUX_SWAP_INVERSION_CONTROL,
>>> +                              AUX_HOST_INVERT);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +int cdn_dp_event_config(struct cdn_dp_device *dp)
>>> +{
>>> +       u8 msg[5] = {0, 0, 0, 0, 0};
>>
>> memset(msg, 0, sizeof(msg));
>>
>>> +
>>> +       msg[0] = DPTX_EVENT_ENABLE_HPD | DPTX_EVENT_ENABLE_TRAINING;
>>> +
>>> +       return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
>>> DPTX_ENABLE_EVENT,
>>> +                                   ARRAY_SIZE(msg), msg);
>>> +}
>>> +
>>> +int cdn_dp_get_event(struct cdn_dp_device *dp)
>>
>> Should return u32
>>
>>> +{
>>> +       return readl(dp->regs + SW_EVENTS0);
>>> +}
>>> +
>>> +int cdn_dp_get_hpd_status(struct cdn_dp_device *dp)
>>> +{
>>> +       u8 status;
>>> +       int ret;
>>> +
>>> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_HPD_STATE,
>>> +                                 0, NULL);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
>>> +                                     DPTX_HPD_STATE, &status, 1);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       return status;
>>> +}
>>> +
>>> +int cdn_dp_get_edid_block(void *data, u8 *edid,
>>> +                         unsigned int block, size_t length)
>>> +{
>>> +       struct cdn_dp_device *dp = data;
>>> +       u8 msg[2], reg[EDID_DATA + EDID_BLOCK_SIZE];
>>> +       int ret;
>>> +
>>> +       if (length != EDID_BLOCK_SIZE)
>>> +               return -EINVAL;
>>> +
>>> +       msg[0] = block / 2;
>>> +       msg[1] = block % 2;
>>> +
>>> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_GET_EDID,
>>> +                                 2, msg);
>>
>> sizeof(msg), msg);
>>
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
>>> +                                     DPTX_GET_EDID, reg,
>>> +                                     EDID_DATA + EDID_BLOCK_SIZE);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       if (reg[EDID_LENGTH_BYTE] != EDID_BLOCK_SIZE ||
>>> +           reg[EDID_SEGMENT_BUMBER] != block / 2) {
>>> +               dev_err(dp->dev, "edid block size err\n");
>>> +               return -EINVAL;
>>> +       }
>>> +
>>> +       memcpy(edid, &reg[EDID_DATA], EDID_BLOCK_SIZE);
>>
>> You can avoid the intermediate reg[] buffer if you restructure the
>> _response/_receive mailbox function. I'd suggest splitting it into a
>> _validate_response() function that reads the header and makes sure the
>> lengths match. The second part would be a _read_response() which
>> simply reads the specified number of bytes from the mailbox. For
>> convenience, you could make cdn_dp_mailbox_receive() a helper which
>> calls both validate and read for times when you're reading the same
>> number as you're validating.
>>
>> So in this function, you'd validate the response has EDID_DATA +
>> EDID_BLOCK_SIZE bytes, then read the EDID_DATA prefix into a local,
>> then read EDID_BLOCK_DATA directly into edid.
>>
>> I think you'll also need a _drain function to clear out the mailbox in
>> case of error (otherwise the next read will fail).
>>
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +int cdn_dp_training_start(struct cdn_dp_device *dp)
>>> +{
>>> +       u8 msg, event[2];
>>> +       unsigned long timeout;
>>> +       int ret;
>>> +
>>> +       msg = LINK_TRAINING_RUN;
>>> +
>>> +       /* start training */
>>> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
>>> DPTX_TRAINING_CONTROL,
>>> +                                 1, &msg);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       /* the whole training should finish in 500ms */
>>> +       timeout = jiffies + msecs_to_jiffies(500);
>>
>> Pull 500 out into a #define
>>
>>> +       while (1) {
>>
>> while(time_before(jiffies, timeout))
>>
>>> +               msleep(20);
>>
>> Pull 20 out into a #define
>>
>>> +               ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
>>> +                                         DPTX_READ_EVENT, 0, NULL);
>>> +               if (ret)
>>> +                       return ret;
>>> +
>>> +               ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
>>> +                                             DPTX_READ_EVENT, event, 2);
>>> +               if (ret)
>>> +                       return ret;
>>> +
>>> +               if (event[1] & EQ_PHASE_FINISHED)
>>> +                       break;
>>
>> return 0
>>
>>> +
>>> +               if (time_after(jiffies, timeout))
>>> +                       return -ETIMEDOUT;
>>> +       }
>>> +
>>> +       return 0;
>>
>> return -ETIMEDOUT
>>
>>> +}
>>> +
>>> +int cdn_dp_get_lt_status(struct cdn_dp_device *dp)
>>
>> s/lt/training/
>>
>>> +{
>>> +       u8 status[10];
>>> +       int ret;
>>> +
>>> +       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
>>> DPTX_READ_LINK_STAT,
>>> +                                 0, NULL);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = cdn_dp_mailbox_response(dp, MB_MODULE_ID_DP_TX,
>>> +                                     DPTX_READ_LINK_STAT, status, 10);
>>
>> s/10/sizeof(status)/
>>
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       dp->link.rate = status[0];
>>> +       dp->link.num_lanes = status[1];
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active)
>>> +{
>>> +       u8 msg;
>>> +
>>> +       msg = !!active;
>>> +
>>> +       return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
>>> DPTX_SET_VIDEO,
>>> +                                  1, &msg);
>>
>> s/1/sizeof(msg)/
>>
>>> +}
>>> +
>>> +static int cdn_dp_get_msa_misc(struct video_info *video,
>>> +                              struct drm_display_mode *mode)
>>> +{
>>> +       u8 val0, val1;
>>
>> u8 val[2];
>>
>>> +       u32 msa_misc;
>>> +
>>> +       switch (video->color_fmt) {
>>> +       case PXL_RGB:
>>> +       case Y_ONLY:
>>> +               val0 = 0;
>>> +               break;
>>
>> Perhaps this would be better as listed under the default case.
>>
>>> +       case YCBCR_4_4_4:
>>> +               /* set default color space conversion to BT601 */
>>> +               val0 = 6 + BT_601 * 8;
>>> +               break;
>>> +       case YCBCR_4_2_2:
>>> +               /* set default color space conversion to BT601 */
>>> +               val0 = 5 + BT_601 * 8;
>>> +               break;
>>> +       case YCBCR_4_2_0:
>>> +               val0 = 5;
>>> +               break;
>>> +       };
>>> +
>>> +       switch (video->color_depth) {
>>> +       case 6:
>>> +               val1 = 0;
>>> +               break;
>>> +       case 8:
>>> +               val1 = 1;
>>> +               break;
>>> +       case 10:
>>> +               val1 = 2;
>>> +               break;
>>> +       case 12:
>>> +               val1 = 3;
>>> +               break;
>>> +       case 16:
>>> +               val1 = 4;
>>> +               break;
>>> +       };
>>> +
>>> +       msa_misc = 2 * val0 + 32 * val1 +
>>> +                  ((video->color_fmt == Y_ONLY) ? (1 << 14) : 0);
>>
>> Perhaps a stupid question, but is this documented anywhere? If not,
>> could you add a comment describing what you're doing?
>>
>>> +
>>> +       return msa_misc;
>>> +}
>>> +
>>> +int cdn_dp_config_video(struct cdn_dp_device *dp)
>>> +{
>>> +       struct video_info *video = &dp->video_info;
>>> +       struct drm_display_mode *mode = &dp->mode;
>>> +       u64 symbol;
>>> +       u32 val, link_rate;
>>> +       u8 bit_per_pix;
>>> +       u8 tu_size_reg = TU_SIZE;
>>> +       int ret;
>>> +
>>> +       bit_per_pix = (video->color_fmt == YCBCR_4_2_2) ?
>>> +                     (video->color_depth * 2) : (video->color_depth *
>>> 3);
>>> +
>>> +       link_rate = drm_dp_bw_code_to_link_rate(dp->link.rate);
>>> +
>>> +       val = VIF_BYPASS_INTERLACE;
>>> +
>>
>> remove extra line
>>
>>> +       ret = cdn_dp_reg_write(dp, BND_HSYNC2VSYNC, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = cdn_dp_reg_write(dp, HSYNC2VSYNC_POL_CTRL, 0);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       /* get a best tu_size and symbol */
>>> +       do {
>>> +               tu_size_reg += 2;
>>> +               symbol = tu_size_reg * mode->clock * bit_per_pix;
>>> +               symbol /= dp->link.num_lanes * link_rate * 8;
>>> +       } while ((symbol == 1) || (tu_size_reg - symbol < 4));
>>> +
>>> +       val = symbol + (tu_size_reg << 8);
>>> +
>>
>> remove extra line
>>
>>> +       ret = cdn_dp_reg_write(dp, DP_FRAMER_TU, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       switch (video->color_depth) {
>>> +       case 6:
>>> +               val = BCS_6;
>>> +               break;
>>> +       case 8:
>>> +               val = BCS_8;
>>> +               break;
>>> +       case 10:
>>> +               val = BCS_10;
>>> +               break;
>>> +       case 12:
>>> +               val = BCS_12;
>>> +               break;
>>> +       case 16:
>>> +               val = BCS_16;
>>> +               break;
>>> +       };
>>> +
>>> +       val += video->color_fmt << 8;
>>> +       ret = cdn_dp_reg_write(dp, DP_FRAMER_PXL_REPR, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       val = video->h_sync_polarity ? DP_FRAMER_SP_HSP : 0;
>>> +       val |= video->v_sync_polarity ? DP_FRAMER_SP_VSP : 0;
>>> +       ret = cdn_dp_reg_write(dp, DP_FRAMER_SP, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       val = (mode->hsync_start - mode->hdisplay) << 16;
>>> +       val |= mode->htotal - mode->hsync_end;
>>> +       ret = cdn_dp_reg_write(dp, DP_FRONT_BACK_PORCH, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       val = mode->hdisplay * bit_per_pix / 8;
>>> +       ret = cdn_dp_reg_write(dp, DP_BYTE_COUNT, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       val = mode->htotal | ((mode->htotal - mode->hsync_start) << 16);
>>> +       ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_0, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       val = mode->hsync_end - mode->hsync_start;
>>> +       val |= (mode->hdisplay << 16) | (video->h_sync_polarity << 15);
>>> +       ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_1, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       val = mode->vtotal;
>>> +       val |= ((mode->vtotal - mode->vsync_start) << 16);
>>> +
>>
>> remove extra line
>>
>>> +       ret = cdn_dp_reg_write(dp, MSA_VERTICAL_0, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       val = mode->vsync_end - mode->vsync_start;
>>> +       val |= mode->vdisplay << 16;
>>> +       val |= (video->v_sync_polarity << 15);
>>
>> Make this consistent with how you assigned val for MSA_HORIZONTAL_1
>> (ie: split both or combine both)
>>
>>> +       ret = cdn_dp_reg_write(dp, MSA_VERTICAL_1, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       val = cdn_dp_get_msa_misc(video, mode);
>>> +       ret = cdn_dp_reg_write(dp, MSA_MISC, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       ret = cdn_dp_reg_write(dp, STREAM_CONFIG, 1);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       val = mode->hsync_end - mode->hsync_start;
>>> +       val |= (mode->hdisplay << 16);
>>> +       ret = cdn_dp_reg_write(dp, DP_HORIZONTAL, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       val = mode->vtotal;
>>> +       val -= (mode->vtotal - mode->vdisplay);
>>> +       val |= (mode->vtotal - mode->vsync_start) << 16;
>>> +
>>
>> extra line
>>
>>> +       ret = cdn_dp_reg_write(dp, DP_VERTICAL_0, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       val = mode->vtotal;
>>> +       ret = cdn_dp_reg_write(dp, DP_VERTICAL_1, val);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       val =  0;
>>> +       return cdn_dp_reg_write_bit(dp, DP_VB_ID, 2, 1, val);
>>> +}
>>> +
>>> +int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info
>>> *audio)
>>> +{
>>> +       int ret = cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL,
>>> AUDIO_PACK_EN(0));
>>> +
>>
>> remove this empty line
>>
>>> +       if (ret)
>>> +               return ret;
>>
>> insert empy line
>>
>>> +       writel(0x1F0707, dp->regs + SPDIF_CTRL_ADDR);
>>
>> pull this magic value into a #define
>>
>>> +       writel(0, dp->regs + AUDIO_SRC_CNTL);
>>> +       writel(0, dp->regs + AUDIO_SRC_CNFG);
>>> +       writel(1, dp->regs + AUDIO_SRC_CNTL);
>>> +       writel(0, dp->regs + AUDIO_SRC_CNTL);
>>> +
>>> +       writel(0, dp->regs + SMPL2PKT_CNTL);
>>> +       writel(1, dp->regs + SMPL2PKT_CNTL);
>>> +       writel(0, dp->regs + SMPL2PKT_CNTL);
>>> +
>>> +       writel(1, dp->regs + FIFO_CNTL);
>>> +       writel(0, dp->regs + FIFO_CNTL);
>>
>> Can you document why you need to toggle these bits as opposed to just
>> setting them to 0?
>>
>>> +
>>> +       if (audio->format == AFMT_SPDIF)
>>> +               clk_disable_unprepare(dp->spdif_clk);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable)
>>> +{
>>> +       return cdn_dp_reg_write_bit(dp, DP_VB_ID, 4, 1, enable);
>>> +}
>>> +
>>> +int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info
>>> *audio)
>>> +{
>>> +       int lanes_param, i2s_port_en_val, val, i;
>>
>> int lanes_param = 3, i2s_port_en_val = 0xf, i;
>>
>>> +       int ret;

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

* [v5.1 PATCH 5/5] drm/rockchip: cdn-dp: add cdn DP support for rk3399
  2016-07-14 14:02       ` Sean Paul
@ 2016-07-15  5:52         ` Chris Zhong
  2016-07-15  8:18         ` [v5.2 " Chris Zhong
  1 sibling, 0 replies; 11+ messages in thread
From: Chris Zhong @ 2016-07-15  5:52 UTC (permalink / raw)
  To: linux-arm-kernel

Add support for cdn DP controller which is embedded in the rk3399
SoCs. The DP is compliant with DisplayPort Specification,
Version 1.3, This IP is compatible with the rockchip type-c PHY IP.
There is a uCPU in DP controller, it need a firmware to work,
please put the firmware file to /lib/firmware/cdn/dptx.bin. The
uCPU in charge of aux communication and link training, the host use
mailbox to communicate with the ucpu.
The dclk pin_pol of vop must not be invert for DP.

Signed-off-by: Chris Zhong <zyw@rock-chips.com>

---

Changes in v5.1:
- modify according to Sean Paul's comments

Changes in v5:
- alphabetical order
- do not use long, use u32 or u64
- return MODE_CLOCK_HIGH when requested > actual
- Optimized Coding Style
- add a formula to get better tu size and symbol value.

Changes in v4:
- use phy framework to control DP phy
- support 2 phys

Changes in v3:
- use EXTCON_DISP_DP and EXTCON_DISP_DP_ALT cable to get dp port state.
- reset spdif before config it
- modify the firmware clk to 100Mhz
- retry load firmware if fw file is requested too early

Changes in v2:
- Alphabetic order
- remove excess error message
- use define clk_rate
- check all return value
- remove dev_set_name(dp->dev, "cdn-dp");
- use schedule_delayed_work
- remove never-called functions
- remove some unnecessary ()

Changes in v1:
- use extcon API
- use hdmi-codec for the DP Asoc
- do not initialize the "ret"
- printk a err log when drm_of_encoder_active_endpoint_id
- modify the dclk pin_pol to a single line

 drivers/gpu/drm/rockchip/Kconfig            |   9 +
 drivers/gpu/drm/rockchip/Makefile           |   1 +
 drivers/gpu/drm/rockchip/cdn-dp-core.c      | 840 ++++++++++++++++++++++++++
 drivers/gpu/drm/rockchip/cdn-dp-core.h      |  99 +++
 drivers/gpu/drm/rockchip/cdn-dp-reg.c       | 898 ++++++++++++++++++++++++++++
 drivers/gpu/drm/rockchip/cdn-dp-reg.h       | 479 +++++++++++++++
 drivers/gpu/drm/rockchip/rockchip_drm_vop.c |  11 +-
 drivers/gpu/drm/rockchip/rockchip_drm_vop.h |   9 +
 drivers/gpu/drm/rockchip/rockchip_vop_reg.c |   2 +
 9 files changed, 2345 insertions(+), 3 deletions(-)
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.c
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.h
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.c
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.h

diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
index d30bdc3..a04446a 100644
--- a/drivers/gpu/drm/rockchip/Kconfig
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -25,6 +25,15 @@ config ROCKCHIP_ANALOGIX_DP
 	  for the Analogix Core DP driver. If you want to enable DP
 	  on RK3288 based SoC, you should selet this option.
 
+config ROCKCHIP_CDN_DP
+        tristate "Rockchip cdn DP"
+        depends on DRM_ROCKCHIP
+        help
+	  This selects support for Rockchip SoC specific extensions
+	  for the cdn DP driver. If you want to enable Dp on
+	  RK3399 based SoC, you should select this
+	  option.
+
 config ROCKCHIP_DW_HDMI
         tristate "Rockchip specific extensions for Synopsys DW HDMI"
         depends on DRM_ROCKCHIP
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
index 05d0713..abdecd5 100644
--- a/drivers/gpu/drm/rockchip/Makefile
+++ b/drivers/gpu/drm/rockchip/Makefile
@@ -7,6 +7,7 @@ rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
 rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
 
 obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
+obj-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o
 obj-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
 obj-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi.o
 obj-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
new file mode 100644
index 0000000..a425207
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
@@ -0,0 +1,840 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/extcon.h>
+#include <linux/firmware.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/mfd/syscon.h>
+#include <linux/phy/phy.h>
+
+#include <sound/hdmi-codec.h>
+
+#include "cdn-dp-core.h"
+#include "cdn-dp-reg.h"
+#include "rockchip_drm_vop.h"
+
+#define connector_to_dp(c) \
+		container_of(c, struct cdn_dp_device, connector)
+
+#define encoder_to_dp(c) \
+		container_of(c, struct cdn_dp_device, encoder)
+
+/* dp grf register offset */
+#define DP_VOP_SEL		0x6224
+#define DP_SEL_VOP_LIT		BIT(12)
+#define MAX_FW_WAIT_SECS	64
+#define CDN_DP_FIRMWARE		"rockchip/dptx.bin"
+
+struct cdn_dp_data {
+	u8 max_phy;
+};
+
+struct cdn_dp_data rk3399_cdn_dp = {
+	.max_phy = 2,
+};
+
+static const struct of_device_id cdn_dp_dt_ids[] = {
+	{ .compatible = "rockchip,rk3399-cdn-dp",
+		.data = (void *)&rk3399_cdn_dp },
+	{}
+};
+
+MODULE_DEVICE_TABLE(of, cdn_dp_dt_ids);
+
+static int cdn_dp_clk_enable(struct cdn_dp_device *dp)
+{
+	int ret;
+	u32 rate;
+
+	ret = clk_prepare_enable(dp->pclk);
+	if (ret < 0) {
+		dev_err(dp->dev, "cannot enable dp pclk %d\n", ret);
+		goto err_pclk;
+	}
+
+	ret = clk_prepare_enable(dp->core_clk);
+	if (ret < 0) {
+		dev_err(dp->dev, "cannot enable core_clk %d\n", ret);
+		goto err_core_clk;
+	}
+
+	rate = clk_get_rate(dp->core_clk);
+	if (rate < 0) {
+		dev_err(dp->dev, "get clk rate failed: %d\n", rate);
+		goto err_set_rate;
+	}
+
+	cdn_dp_set_fw_clk(dp, rate);
+
+	return 0;
+
+err_set_rate:
+	clk_disable_unprepare(dp->core_clk);
+err_core_clk:
+	clk_disable_unprepare(dp->pclk);
+err_pclk:
+	return ret;
+}
+
+static enum drm_connector_status
+cdn_dp_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct cdn_dp_device *dp = connector_to_dp(connector);
+
+	return dp->hpd_status;
+}
+
+static void cdn_dp_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static struct drm_connector_funcs cdn_dp_atomic_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.detect = cdn_dp_connector_detect,
+	.destroy = cdn_dp_connector_destroy,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int cdn_dp_connector_get_modes(struct drm_connector *connector)
+{
+	struct cdn_dp_device *dp = connector_to_dp(connector);
+	struct edid *edid;
+	int ret = 0;
+
+	edid = drm_do_get_edid(connector, cdn_dp_get_edid_block, dp);
+	if (edid) {
+		dev_dbg(dp->dev, "got edid: width[%d] x height[%d]\n",
+			edid->width_cm, edid->height_cm);
+
+		dp->sink_has_audio = drm_detect_monitor_audio(edid);
+		ret = drm_add_edid_modes(connector, edid);
+		if (ret) {
+			drm_mode_connector_update_edid_property(connector,
+								edid);
+			drm_edid_to_eld(connector, edid);
+		}
+		kfree(edid);
+	} else {
+		dev_warn(dp->dev, "failed to get edid\n");
+	}
+
+	return ret;
+}
+
+static struct drm_encoder *
+	cdn_dp_connector_best_encoder(struct drm_connector *connector)
+{
+	struct cdn_dp_device *dp = connector_to_dp(connector);
+
+	return &dp->encoder;
+}
+
+static int cdn_dp_connector_mode_valid(struct drm_connector *connector,
+				       struct drm_display_mode *mode)
+{
+	struct cdn_dp_device *dp = connector_to_dp(connector);
+	struct drm_display_info *display_info = &dp->connector.display_info;
+	u32 requested = mode->clock * display_info->bpc * 3 / 1000;
+	u32 sink_max, source_max, rate;
+	u8 lanes, id;
+	u32 actual;
+
+	/* find the running port id */
+	for (id = 0; id < dp->max_phy; id++) {
+		if (dp->phy_status[id] == 1)
+			break;
+	}
+
+	sink_max = drm_dp_max_lane_count(dp->dpcd);
+	source_max = dp->cap_lanes[id];
+	lanes = min(sink_max, source_max);
+
+	sink_max = drm_dp_max_link_rate(dp->dpcd);
+	source_max = drm_dp_bw_code_to_link_rate(CDN_DP_MAX_LINK_RATE);
+	rate = min(sink_max, source_max);
+
+	actual = rate * lanes / 100;
+
+	/* efficiency is about 0.8 */
+	actual = actual * 8 / 10;
+
+	if (requested > actual) {
+		dev_dbg(dp->dev, "requested=%d, actual=%d, clock=%d\n",
+			requested, actual, mode->clock);
+		return MODE_CLOCK_HIGH;
+	}
+
+	return MODE_OK;
+}
+
+static struct drm_connector_helper_funcs cdn_dp_connector_helper_funcs = {
+	.get_modes = cdn_dp_connector_get_modes,
+	.best_encoder = cdn_dp_connector_best_encoder,
+	.mode_valid = cdn_dp_connector_mode_valid,
+};
+
+static void cdn_dp_commit(struct drm_encoder *encoder)
+{
+	struct cdn_dp_device *dp = encoder_to_dp(encoder);
+	int ret;
+
+	ret = cdn_dp_training_start(dp);
+	if (ret) {
+		dev_err(dp->dev, "link training failed: %d\n", ret);
+		return;
+	}
+
+	ret = cdn_dp_get_training_status(dp);
+	if (ret) {
+		dev_err(dp->dev, "get link training status failed: %d\n", ret);
+		return;
+	}
+
+	dev_info(dp->dev, "rate:%d, lanes:%d\n",
+		 dp->link.rate, dp->link.num_lanes);
+
+	if (cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE))
+		return;
+
+	if (cdn_dp_config_video(dp)) {
+		dev_err(dp->dev, "unable to config video\n");
+		return;
+	}
+
+	if (cdn_dp_set_video_status(dp, CONTROL_VIDEO_VALID))
+		return;
+
+	dp->dpms_mode = DRM_MODE_DPMS_ON;
+}
+
+static void cdn_dp_encoder_mode_set(struct drm_encoder *encoder,
+				    struct drm_display_mode *mode,
+				    struct drm_display_mode *adjusted)
+{
+	struct cdn_dp_device *dp = encoder_to_dp(encoder);
+	struct drm_display_info *display_info = &dp->connector.display_info;
+	struct rockchip_crtc_state *state;
+	struct video_info *video = &dp->video_info;
+	int ret, val;
+
+	switch (display_info->bpc) {
+	case 16:
+	case 12:
+	case 10:
+		video->color_depth = 10;
+		break;
+	case 6:
+		video->color_depth = 6;
+		break;
+	default:
+		video->color_depth = 8;
+		break;
+	}
+
+	video->color_fmt = PXL_RGB;
+
+	video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
+	video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
+
+	ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder);
+	if (ret < 0) {
+		dev_err(dp->dev, "Could not get vop id, %d", ret);
+		return;
+	}
+
+	state = to_rockchip_crtc_state(encoder->crtc->state);
+	if (ret) {
+		val = DP_SEL_VOP_LIT | (DP_SEL_VOP_LIT << 16);
+		state->output_mode = ROCKCHIP_OUT_MODE_P888;
+	} else {
+		val = DP_SEL_VOP_LIT << 16;
+		state->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+	}
+	ret = regmap_write(dp->grf, DP_VOP_SEL, val);
+	if (ret != 0)
+		dev_err(dp->dev, "Could not write to GRF: %d\n", ret);
+
+	memcpy(&dp->mode, adjusted, sizeof(*mode));
+}
+
+static void cdn_dp_encoder_enable(struct drm_encoder *encoder)
+{
+	struct cdn_dp_device *dp = encoder_to_dp(encoder);
+
+	if (dp->dpms_mode != DRM_MODE_DPMS_ON)
+		cdn_dp_commit(encoder);
+}
+
+static void cdn_dp_encoder_disable(struct drm_encoder *encoder)
+{
+	struct cdn_dp_device *dp = encoder_to_dp(encoder);
+
+	if (dp->dpms_mode != DRM_MODE_DPMS_OFF) {
+		cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE);
+		dp->dpms_mode = DRM_MODE_DPMS_OFF;
+	}
+}
+
+static int
+cdn_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);
+
+	s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+	s->output_type = DRM_MODE_CONNECTOR_DisplayPort;
+
+	return 0;
+}
+
+static struct drm_encoder_helper_funcs cdn_dp_encoder_helper_funcs = {
+	.mode_set = cdn_dp_encoder_mode_set,
+	.enable = cdn_dp_encoder_enable,
+	.disable = cdn_dp_encoder_disable,
+	.atomic_check = cdn_dp_encoder_atomic_check,
+};
+
+static struct drm_encoder_funcs cdn_dp_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static int cdn_dp_firmware_init(struct cdn_dp_device *dp)
+{
+	int ret;
+	const u32 *iram_data, *dram_data;
+	const struct firmware *fw = dp->fw;
+	const struct cdn_firmware_header *hdr;
+
+	hdr = (struct cdn_firmware_header *)fw->data;
+	if (fw->size != le32_to_cpu(hdr->size_bytes)) {
+		dev_err(dp->dev, "firmware is invalid\n");
+		return -EINVAL;
+	}
+
+	iram_data = (const u32 *)(fw->data + hdr->header_size);
+	dram_data = (const u32 *)(fw->data + hdr->header_size + hdr->iram_size);
+
+	ret = cdn_dp_load_firmware(dp, iram_data, hdr->iram_size,
+				   dram_data, hdr->dram_size);
+	if (ret)
+		return ret;
+
+	dp->fw_loaded = 1;
+
+	ret = cdn_dp_active(dp, true);
+	if (ret) {
+		dev_err(dp->dev, "active ucpu failed: %d\n", ret);
+		return ret;
+	}
+
+	return cdn_dp_event_config(dp);
+}
+
+static int cdn_dp_init(struct cdn_dp_device *dp)
+{
+	struct device *dev = dp->dev;
+	struct device_node *np = dev->of_node;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct resource *res;
+	int ret;
+
+	dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+	if (IS_ERR(dp->grf)) {
+		dev_err(dev, "cdn-dp needs rockchip,grf property\n");
+		return PTR_ERR(dp->grf);
+	}
+
+	dp->irq = platform_get_irq(pdev, 0);
+	if (dp->irq < 0) {
+		dev_err(dev, "cdn-dp can not get irq\n");
+		return dp->irq;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dp->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(dp->regs)) {
+		dev_err(dev, "ioremap reg failed\n");
+		return PTR_ERR(dp->regs);
+	}
+
+	dp->core_clk = devm_clk_get(dev, "core-clk");
+	if (IS_ERR(dp->core_clk)) {
+		dev_err(dev, "cannot get core_clk_dp\n");
+		return PTR_ERR(dp->core_clk);
+	}
+
+	dp->pclk = devm_clk_get(dev, "pclk");
+	if (IS_ERR(dp->pclk)) {
+		dev_err(dev, "cannot get pclk\n");
+		return PTR_ERR(dp->pclk);
+	}
+
+	dp->spdif_clk = devm_clk_get(dev, "spdif");
+	if (IS_ERR(dp->spdif_clk)) {
+		dev_err(dev, "cannot get spdif_clk\n");
+		return PTR_ERR(dp->spdif_clk);
+	}
+
+	dp->spdif_rst = devm_reset_control_get(dev, "spdif");
+	if (IS_ERR(dp->spdif_rst)) {
+		dev_err(dev, "no spdif reset control found\n");
+		return PTR_ERR(dp->spdif_rst);
+	}
+
+	dp->dpms_mode = DRM_MODE_DPMS_OFF;
+
+	ret = cdn_dp_clk_enable(dp);
+	if (ret < 0)
+		return ret;
+
+	cdn_dp_clock_reset(dp);
+
+	return 0;
+}
+
+static int cdn_dp_audio_hw_params(struct device *dev,
+				  struct hdmi_codec_daifmt *daifmt,
+				  struct hdmi_codec_params *params)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	struct audio_info audio = {
+		.sample_width = params->sample_width,
+		.sample_rate = params->sample_rate,
+		.channels = params->channels,
+	};
+	int ret;
+
+	if (!dp->encoder.crtc)
+		return -ENODEV;
+
+	switch (daifmt->fmt) {
+	case HDMI_I2S:
+		audio.format = AFMT_I2S;
+		break;
+	case HDMI_SPDIF:
+		audio.format = AFMT_SPDIF;
+		break;
+	default:
+		dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt);
+		return -EINVAL;
+	}
+
+	ret = cdn_dp_audio_config_set(dp, &audio);
+	if (!ret)
+		dp->audio_info = audio;
+
+	return ret;
+}
+
+static void cdn_dp_audio_shutdown(struct device *dev)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	int ret = cdn_dp_audio_stop(dp, &dp->audio_info);
+
+	if (!ret)
+		dp->audio_info.format = AFMT_UNUSED;
+}
+
+static int cdn_dp_audio_digital_mute(struct device *dev, bool enable)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+
+	return cdn_dp_audio_mute(dp, enable);
+}
+
+static int cdn_dp_audio_get_eld(struct device *dev, uint8_t *buf, size_t len)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	struct drm_mode_config *config = &dp->encoder.dev->mode_config;
+	struct drm_connector *connector;
+	int ret = -ENODEV;
+
+	mutex_lock(&config->mutex);
+	list_for_each_entry(connector, &config->connector_list, head) {
+		if (&dp->encoder == connector->encoder) {
+			memcpy(buf, connector->eld,
+			       min(sizeof(connector->eld), len));
+			ret = 0;
+		}
+	}
+	mutex_unlock(&config->mutex);
+
+	return ret;
+}
+
+static const struct hdmi_codec_ops audio_codec_ops = {
+	.hw_params = cdn_dp_audio_hw_params,
+	.audio_shutdown = cdn_dp_audio_shutdown,
+	.digital_mute = cdn_dp_audio_digital_mute,
+	.get_eld = cdn_dp_audio_get_eld,
+};
+
+static int cdn_dp_audio_codec_init(struct cdn_dp_device *dp,
+				   struct device *dev)
+{
+	struct hdmi_codec_pdata codec_data = {
+		.i2s = 1,
+		.spdif = 1,
+		.ops = &audio_codec_ops,
+		.max_i2s_channels = 8,
+	};
+
+	dp->audio_pdev = platform_device_register_data(
+			 dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO,
+			 &codec_data, sizeof(codec_data));
+
+	return PTR_ERR_OR_ZERO(dp->audio_pdev);
+}
+
+static int cdn_dp_get_cap_lanes(struct cdn_dp_device *dp,
+				struct extcon_dev *edev)
+{
+	bool dfp, dptx;
+	u8 lanes;
+
+	dfp = extcon_get_cable_state_(edev, EXTCON_USB_HOST);
+	dptx = extcon_get_cable_state_(edev, EXTCON_DISP_DP);
+
+	if (dfp && dptx)
+		lanes = 2;
+	else if (dptx)
+		lanes = 4;
+	else
+		lanes = 0;
+
+	return lanes;
+}
+
+static int cdn_dp_pd_event(struct notifier_block *nb,
+			   unsigned long event, void *priv)
+{
+	struct cdn_dp_device *dp;
+	struct extcon_dev *edev = priv;
+	u8 i;
+
+	dp = container_of(nb, struct cdn_dp_device, event_nb);
+
+	for (i = 0; i < dp->max_phy; i++) {
+		if (edev == dp->extcon[i]) {
+			dp->port_id = i;
+			break;
+		}
+	}
+
+	schedule_delayed_work(&dp->event_wq, 0);
+
+	return 0;
+}
+
+static void cdn_dp_pd_event_wq(struct work_struct *work)
+{
+	struct cdn_dp_device *dp = container_of(work, struct cdn_dp_device,
+						event_wq.work);
+	u8 id = dp->port_id, i;
+	u8 old_cap_lanes = dp->cap_lanes[id];
+	int ret;
+
+	dp->cap_lanes[id] = cdn_dp_get_cap_lanes(dp, dp->extcon[id]);
+	if (dp->cap_lanes[id] == old_cap_lanes)
+		return;
+
+	/* if phy is on, need reset it when dp lanes count is changed */
+	if (dp->phy_status[id]) {
+		ret = phy_power_off(dp->phy[id]);
+		if (ret) {
+			dev_err(dp->dev, "phy power off failed: %d", ret);
+			goto err_phy_power_off;
+		}
+		dp->phy_status[id] = 0;
+	}
+
+	/* if other phy is running, do not touch the hpd_status, and return */
+	for (i = 0; i < dp->max_phy; i++) {
+		if (dp->phy_status[i] == 1) {
+			dev_warn(dp->dev, "busy, phy[%d] is running", i);
+			goto err_phy_power_off;
+		}
+	}
+
+	if (!dp->cap_lanes[id]) {
+		dp->hpd_status = connector_status_disconnected;
+		drm_helper_hpd_irq_event(dp->drm_dev);
+		return;
+	}
+
+	if (!dp->fw_loaded) {
+		ret = request_firmware(&dp->fw, CDN_DP_FIRMWARE, dp->dev);
+		if (ret == -ENOENT && dp->fw_wait <= MAX_FW_WAIT_SECS) {
+			u32 time = msecs_to_jiffies(dp->fw_wait * HZ);
+
+			/*
+			 * If can not find the file, retry to load the firmware
+			 * in 1 second, if still failed after 1 minute, give up.
+			 */
+			schedule_delayed_work(&dp->event_wq, time);
+			dp->fw_wait *= 2;
+			dev_warn(dp->dev, "retry to request firmware in %d\n",
+				 dp->fw_wait);
+			goto err_phy_power_off;
+		} else if (ret) {
+			dev_err(dp->dev, "failed to request firmware: %d\n",
+				ret);
+			goto err_phy_power_off;
+		}
+	}
+
+	ret = phy_power_on(dp->phy[id]);
+	if (ret) {
+		dev_err(dp->dev, "phy power on failed: %d", ret);
+		if (!dp->fw_loaded)
+			release_firmware(dp->fw);
+		goto err_phy_power_off;
+	}
+
+	dp->phy_status[id] = 1;
+
+	if (!dp->fw_loaded) {
+		ret = cdn_dp_firmware_init(dp);
+		release_firmware(dp->fw);
+		if (ret) {
+			dev_err(dp->dev, "firmware init failed: %d", ret);
+			goto err_firmware;
+		}
+	}
+
+	/* read hpd status failed, or the hpd does not exist. */
+	ret = cdn_dp_get_hpd_status(dp);
+	if (ret <= 0) {
+		dev_err(dp->dev, "get hpd failed: %d", ret);
+		goto err_firmware;
+	}
+
+	ret = cdn_dp_set_host_cap(dp, dp->cap_lanes[id]);
+	if (ret) {
+		dev_err(dp->dev, "set host capabilities failed: %d\n", ret);
+		goto err_firmware;
+	}
+
+	/*
+	 * Native read with retry for link status and receiver capability reads
+	 * for cases where the sink may still be asleep.
+	 *
+	 * Sinks are *supposed* to come up within 1ms from an off state, but
+	 * we're also supposed to retry 3 times per the spec.
+	 */
+	for (i = 0; i < 3; i++) {
+		ret = cdn_dp_dpcd_read(dp, 0x000, dp->dpcd,
+				       DP_RECEIVER_CAP_SIZE);
+		if (!ret) {
+			dp->hpd_status = connector_status_connected;
+			drm_helper_hpd_irq_event(dp->drm_dev);
+			return;
+		}
+		usleep_range(1000, 1050);
+	}
+
+err_firmware:
+	ret = phy_power_off(dp->phy[id]);
+	if (ret)
+		dev_err(dp->dev, "phy power off failed: %d", ret);
+	else
+		dp->phy_status[id] = 0;
+
+err_phy_power_off:
+	dp->cap_lanes[id] = 0;
+}
+
+static int cdn_dp_bind(struct device *dev, struct device *master,
+		       void *data)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	struct drm_encoder *encoder;
+	struct drm_connector *connector;
+	struct drm_device *drm_dev = data;
+	int ret, i;
+
+	ret = cdn_dp_init(dp);
+	if (ret < 0)
+		return ret;
+
+	dp->drm_dev = drm_dev;
+	dp->hpd_status = connector_status_disconnected;
+
+	encoder = &dp->encoder;
+
+	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
+							     dev->of_node);
+	DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs);
+
+	ret = drm_encoder_init(drm_dev, encoder, &cdn_dp_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS, NULL);
+	if (ret) {
+		DRM_ERROR("failed to initialize encoder with drm\n");
+		return ret;
+	}
+
+	drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs);
+
+	connector = &dp->connector;
+	connector->polled = DRM_CONNECTOR_POLL_HPD;
+	connector->dpms = DRM_MODE_DPMS_OFF;
+
+	ret = drm_connector_init(drm_dev, connector,
+				 &cdn_dp_atomic_connector_funcs,
+				 DRM_MODE_CONNECTOR_DisplayPort);
+	if (ret) {
+		DRM_ERROR("failed to initialize connector with drm\n");
+		goto err_free_encoder;
+	}
+
+	drm_connector_helper_add(connector, &cdn_dp_connector_helper_funcs);
+
+	ret = drm_mode_connector_attach_encoder(connector, encoder);
+	if (ret) {
+		DRM_ERROR("failed to attach connector and encoder\n");
+		goto err_free_connector;
+	}
+
+	cdn_dp_audio_codec_init(dp, dev);
+
+	dp->event_nb.notifier_call = cdn_dp_pd_event;
+	INIT_DELAYED_WORK(&dp->event_wq, cdn_dp_pd_event_wq);
+
+	for (i = 0; i < dp->max_phy; i++) {
+		if (IS_ERR(dp->extcon[i]))
+			continue;
+
+		ret = extcon_register_notifier(dp->extcon[i], EXTCON_DISP_DP,
+					       &dp->event_nb);
+		if (ret) {
+			dev_err(dev, "regitster EXTCON_DISP_DP notifier err\n");
+			return ret;
+		}
+
+		dp->port_id = i;
+		schedule_delayed_work(&dp->event_wq, 0);
+	}
+
+	return 0;
+
+err_free_connector:
+	drm_connector_cleanup(connector);
+err_free_encoder:
+	drm_encoder_cleanup(encoder);
+	return ret;
+}
+
+static void cdn_dp_unbind(struct device *dev, struct device *master, void *data)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	struct drm_encoder *encoder = &dp->encoder;
+	struct drm_connector *connector = &dp->connector;
+	int i;
+
+	platform_device_unregister(dp->audio_pdev);
+	cdn_dp_encoder_disable(encoder);
+	encoder->funcs->destroy(encoder);
+	connector->funcs->destroy(connector);
+
+	for (i = 0; i < dp->max_phy; i++) {
+		if (!IS_ERR(dp->extcon[i]))
+			extcon_unregister_notifier(dp->extcon[i], EXTCON_USB,
+						   &dp->event_nb);
+	}
+}
+
+static const struct component_ops cdn_dp_component_ops = {
+	.bind = cdn_dp_bind,
+	.unbind = cdn_dp_unbind,
+};
+
+static int cdn_dp_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct of_device_id *match;
+	struct cdn_dp_data *dp_data;
+	struct cdn_dp_device *dp;
+	u8 count = 0;
+	int i;
+
+	dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
+	if (!dp)
+		return -ENOMEM;
+	dp->dev = dev;
+
+	match = of_match_node(cdn_dp_dt_ids, pdev->dev.of_node);
+	dp_data = (struct cdn_dp_data *)match->data;
+
+	dp->max_phy = dp_data->max_phy;
+
+	for (i = 0; i < dp->max_phy; i++) {
+		dp->extcon[i] = extcon_get_edev_by_phandle(dev, i);
+		dp->phy[i] = devm_of_phy_get_by_index(dev, dev->of_node, i);
+
+		if (!IS_ERR(dp->extcon[i]) && !IS_ERR(dp->phy[i]))
+			count++;
+
+		if (PTR_ERR(dp->extcon[i]) == -EPROBE_DEFER ||
+		    PTR_ERR(dp->phy[i]) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
+	}
+
+	if (!count) {
+		dev_err(dev, "missing extcon or phy\n");
+		return -EINVAL;
+	}
+
+	dev_set_drvdata(dev, dp);
+
+	return component_add(dev, &cdn_dp_component_ops);
+}
+
+static int cdn_dp_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &cdn_dp_component_ops);
+
+	return 0;
+}
+
+static struct platform_driver cdn_dp_driver = {
+	.probe = cdn_dp_probe,
+	.remove = cdn_dp_remove,
+	.driver = {
+		   .name = "cdn-dp",
+		   .owner = THIS_MODULE,
+		   .of_match_table = of_match_ptr(cdn_dp_dt_ids),
+	},
+};
+
+module_platform_driver(cdn_dp_driver);
+
+MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
+MODULE_DESCRIPTION("cdn DP Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h
new file mode 100644
index 0000000..1c577bf
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 Chris Zhong <zyw@rock-chips.com>
+ * Copyright (C) 2016 ROCKCHIP, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _CDN_DP_CORE_H
+#define _CDN_DP_CORE_H
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_panel.h>
+#include "rockchip_drm_drv.h"
+
+#define MAX_PHY		2
+
+enum audio_format {
+	AFMT_I2S = 0,
+	AFMT_SPDIF = 1,
+	AFMT_UNUSED,
+};
+
+struct audio_info {
+	enum audio_format format;
+	int sample_rate;
+	int channels;
+	int sample_width;
+};
+
+enum vic_pxl_encoding_format {
+	PXL_RGB = 0x1,
+	YCBCR_4_4_4 = 0x2,
+	YCBCR_4_2_2 = 0x4,
+	YCBCR_4_2_0 = 0x8,
+	Y_ONLY = 0x10,
+};
+
+struct video_info {
+	bool h_sync_polarity;
+	bool v_sync_polarity;
+	bool interlaced;
+	int color_depth;
+	enum vic_pxl_encoding_format color_fmt;
+};
+
+struct cdn_firmware_header {
+	u32 size_bytes; /* size of the entire header+image(s) in bytes */
+	u32 header_size; /* size of just the header in bytes */
+	u32 iram_size; /* size of iram */
+	u32 dram_size; /* size of dram */
+};
+
+struct cdn_dp_device {
+	struct device *dev;
+	struct drm_device *drm_dev;
+	struct drm_connector connector;
+	struct drm_encoder encoder;
+	struct drm_display_mode mode;
+	struct platform_device *audio_pdev;
+
+	const struct firmware *fw;	/* cdn dp firmware */
+	unsigned int fw_version;	/* cdn fw version */
+	u32 fw_wait;
+	bool fw_loaded;
+	void __iomem *regs;
+	struct regmap *grf;
+	unsigned int irq;
+	struct clk *core_clk;
+	struct clk *pclk;
+	struct clk *spdif_clk;
+	struct reset_control *spdif_rst;
+	struct audio_info audio_info;
+	struct video_info video_info;
+	struct extcon_dev *extcon[MAX_PHY];
+	struct phy *phy[MAX_PHY];
+	struct notifier_block event_nb;
+	struct delayed_work event_wq;
+	struct drm_dp_link link;
+
+	u8 cap_lanes[MAX_PHY];
+	u8 dpcd[DP_RECEIVER_CAP_SIZE];
+	u8 port_id;
+	u8 max_phy;
+	bool phy_status[MAX_PHY];
+	enum drm_connector_status hpd_status;
+
+	int dpms_mode;
+	bool sink_has_audio;
+};
+#endif  /* _CDN_DP_CORE_H */
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.c b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
new file mode 100644
index 0000000..afc8ae9
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
@@ -0,0 +1,898 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/reset.h>
+
+#include "cdn-dp-core.h"
+#include "cdn-dp-reg.h"
+
+#define CDN_DP_SPDIF_CLK		200000000
+#define FW_ALIVE_TIMEOUT_US		1000000
+#define MAILBOX_RETRY_US		1000
+#define MAILBOX_TIMEOUT_US		5000000
+#define LINK_TRAINING_RETRY_MS		20
+#define LINK_TRAINING_TIMEOUT_MS	500
+
+void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, u32 clk)
+{
+	writel(clk / 1000000, dp->regs + SW_CLK_H);
+}
+
+void cdn_dp_clock_reset(struct cdn_dp_device *dp)
+{
+	u32 val;
+
+	val = DPTX_FRMR_DATA_CLK_RSTN_EN |
+	      DPTX_FRMR_DATA_CLK_EN |
+	      DPTX_PHY_DATA_RSTN_EN |
+	      DPTX_PHY_DATA_CLK_EN |
+	      DPTX_PHY_CHAR_RSTN_EN |
+	      DPTX_PHY_CHAR_CLK_EN |
+	      SOURCE_AUX_SYS_CLK_RSTN_EN |
+	      SOURCE_AUX_SYS_CLK_EN |
+	      DPTX_SYS_CLK_RSTN_EN |
+	      DPTX_SYS_CLK_EN |
+	      CFG_DPTX_VIF_CLK_RSTN_EN |
+	      CFG_DPTX_VIF_CLK_EN;
+	writel(val, dp->regs + SOURCE_DPTX_CAR);
+
+	val = SOURCE_PHY_RSTN_EN | SOURCE_PHY_CLK_EN;
+	writel(val, dp->regs + SOURCE_PHY_CAR);
+
+	val = SOURCE_PKT_SYS_RSTN_EN |
+	      SOURCE_PKT_SYS_CLK_EN |
+	      SOURCE_PKT_DATA_RSTN_EN |
+	      SOURCE_PKT_DATA_CLK_EN;
+	writel(val, dp->regs + SOURCE_PKT_CAR);
+
+	val = SPDIF_CDR_CLK_RSTN_EN |
+	      SPDIF_CDR_CLK_EN |
+	      SOURCE_AIF_SYS_RSTN_EN |
+	      SOURCE_AIF_SYS_CLK_EN |
+	      SOURCE_AIF_CLK_RSTN_EN |
+	      SOURCE_AIF_CLK_EN;
+	writel(val, dp->regs + SOURCE_AIF_CAR);
+
+	val = SOURCE_CIPHER_SYSTEM_CLK_RSTN_EN |
+	      SOURCE_CIPHER_SYS_CLK_EN |
+	      SOURCE_CIPHER_CHAR_CLK_RSTN_EN |
+	      SOURCE_CIPHER_CHAR_CLK_EN;
+	writel(val, dp->regs + SOURCE_CIPHER_CAR);
+
+	val = SOURCE_CRYPTO_SYS_CLK_RSTN_EN |
+	      SOURCE_CRYPTO_SYS_CLK_EN;
+	writel(val, dp->regs + SOURCE_CRYPTO_CAR);
+
+	val = ~(MAILBOX_INT_MASK_BIT | PIF_INT_MASK_BIT) & ALL_INT_MASK;
+	writel(val, dp->regs + APB_INT_MASK);
+}
+
+static int cdn_dp_mailbox_read(struct cdn_dp_device *dp)
+{
+	int val, ret;
+
+	if (!dp->fw_loaded)
+		return -EPERM;
+
+	ret = readx_poll_timeout(readl, dp->regs + MAILBOX_EMPTY_ADDR,
+				 val, !val, MAILBOX_RETRY_US,
+				 MAILBOX_TIMEOUT_US);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to read mailbox, keep alive = %x\n",
+			readl(dp->regs + KEEP_ALIVE));
+		return ret;
+	}
+
+	return readl(dp->regs + MAILBOX0_RD_DATA) & 0xff;
+}
+
+static int cdp_dp_mailbox_write(struct cdn_dp_device *dp, u8 val)
+{
+	int ret, full;
+
+	if (!dp->fw_loaded)
+		return -EPERM;
+
+	ret = readx_poll_timeout(readl, dp->regs + MAILBOX_FULL_ADDR,
+				 full, !full, MAILBOX_RETRY_US,
+				 MAILBOX_TIMEOUT_US);
+	if (ret < 0) {
+		dev_err(dp->dev, "mailbox is full, keep alive = %x\n",
+			readl(dp->regs + KEEP_ALIVE));
+		return ret;
+	}
+
+	writel(val, dp->regs + MAILBOX0_WR_DATA);
+
+	return 0;
+}
+
+static int cdn_dp_mailbox_validate_receive(struct cdn_dp_device *dp,
+					   u8 module_id, u8 opcode,
+					   u8 req_size)
+{
+	u32 mbox_size, i;
+	u8 header[4];
+	int ret;
+
+	/* read the header of the message */
+	for (i = 0; i < 4; i++)  {
+		ret = cdn_dp_mailbox_read(dp);
+		if (ret < 0)
+			return ret;
+
+		header[i] = ret;
+	}
+
+	mbox_size = (header[2] << 8) | header[3];
+
+	if (opcode != header[0] || module_id != header[1] ||
+	    req_size != mbox_size) {
+		dump_stack();
+		dev_err(dp->dev, "mailbox validate failed");
+
+		/*
+		 * If the message in mailbox is not what we want, we need to
+		 * clear the mailbox by read.
+		 */
+		for (i = 0; i < mbox_size; i++)
+			if (cdn_dp_mailbox_read(dp) < 0)
+				break;
+
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int cdn_dp_mailbox_read_receive(struct cdn_dp_device *dp,
+				       u8 *buff, u8 buff_size)
+{
+	u32 i;
+	int ret;
+
+	for (i = 0; i < buff_size; i++) {
+		ret = cdn_dp_mailbox_read(dp);
+		if (ret < 0)
+			return ret;
+
+		buff[i] = ret;
+	}
+
+	return 0;
+}
+
+static int cdn_dp_mailbox_send(struct cdn_dp_device *dp, u8 module_id,
+			       u8 opcode, u16 size, u8 *message)
+{
+	u8 header[4];
+	int ret, i;
+
+	header[0] = opcode;
+	header[1] = module_id;
+	header[2] = (size >> 8) & 0xff;
+	header[3] = size & 0xff;
+
+	for (i = 0; i < 4; i++) {
+		ret = cdp_dp_mailbox_write(dp, header[i]);
+		if (ret)
+			return ret;
+	}
+
+	for (i = 0; i < size; i++) {
+		ret = cdp_dp_mailbox_write(dp, message[i]);
+		if (ret) {
+			dev_err(dp->dev, "mailbox send failed: %d", ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int cdn_dp_reg_write(struct cdn_dp_device *dp, u16 addr, u32 val)
+{
+	u8 msg[6];
+
+	msg[0] = (addr >> 8) & 0xff;
+	msg[1] = addr & 0xff;
+	msg[2] = (val >> 24) & 0xff;
+	msg[3] = (val >> 16) & 0xff;
+	msg[4] = (val >> 8) & 0xff;
+	msg[5] = val & 0xff;
+	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_REGISTER,
+				   sizeof(msg), msg);
+}
+
+int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr, u8 *data, u16 len)
+{
+	u8 msg[5], reg[5];
+	int ret;
+
+	msg[0] = (len >> 8) & 0xff;
+	msg[1] = len & 0xff;
+	msg[2] = (addr >> 16) & 0xff;
+	msg[3] = (addr >> 8) & 0xff;
+	msg[4] = addr & 0xff;
+	cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_READ_DPCD,
+			    sizeof(msg), msg);
+
+	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+					      DPTX_READ_DPCD,
+					      sizeof(reg) + len);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
+	if (ret)
+		return ret;
+
+	return cdn_dp_mailbox_read_receive(dp, data, len);
+}
+
+int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value)
+{
+	u8 msg[6], reg[5];
+	int ret;
+
+	msg[0] = 0;
+	msg[1] = 1;
+	msg[2] = (addr >> 16) & 0xff;
+	msg[3] = (addr >> 8) & 0xff;
+	msg[4] = addr & 0xff;
+	msg[5] = value;
+	cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_DPCD,
+			    sizeof(msg), msg);
+
+	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+					      DPTX_WRITE_DPCD, sizeof(reg));
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
+	if (ret)
+		return ret;
+
+	if (addr != (reg[2] << 16 | reg[3] << 8 | reg[4])) {
+		dev_err(dp->dev, "write dpcd failed\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
+			 u32 i_size, const u32 *d_mem, u32 d_size)
+{
+	int i, ret;
+	u32 reg;
+
+	/* reset ucpu before load firmware*/
+	writel(APB_IRAM_PATH | APB_DRAM_PATH | APB_XT_RESET,
+	       dp->regs + APB_CTRL);
+
+	for (i = 0; i < i_size; i += 4)
+		writel(*i_mem++, dp->regs + ADDR_IMEM + i);
+
+	for (i = 0; i < d_size; i += 4)
+		writel(*d_mem++, dp->regs + ADDR_DMEM + i);
+
+	/* un-reset ucpu */
+	writel(0, dp->regs + APB_CTRL);
+
+	/* check the keep alive register to make sure fw working */
+	ret = readx_poll_timeout(readl, dp->regs + KEEP_ALIVE,
+				 reg, reg, 2000, FW_ALIVE_TIMEOUT_US);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to loaded the FW reg = %x\n", reg);
+		return -EINVAL;
+	}
+
+	reg = readl(dp->regs + VER_L) & 0xff;
+	dp->fw_version = reg;
+	reg = readl(dp->regs + VER_H) & 0xff;
+	dp->fw_version |= reg << 8;
+	reg = readl(dp->regs + VER_LIB_L_ADDR) & 0xff;
+	dp->fw_version |= reg << 16;
+	reg = readl(dp->regs + VER_LIB_H_ADDR) & 0xff;
+	dp->fw_version |= reg << 24;
+
+	dev_dbg(dp->dev, "firmware version: %x\n", dp->fw_version);
+
+	return 0;
+}
+
+int cdn_dp_reg_write_bit(struct cdn_dp_device *dp, u16 addr, u8 start_bit,
+			 u8 bits_no, u32 val)
+{
+	u8 field[8];
+
+	field[0] = (addr >> 8) & 0xff;
+	field[1] = addr & 0xff;
+	field[2] = start_bit;
+	field[3] = bits_no;
+	field[4] = (val >> 24) & 0xff;
+	field[5] = (val >> 16) & 0xff;
+	field[6] = (val >> 8) & 0xff;
+	field[7] = val & 0xff;
+
+	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_FIELD,
+				   sizeof(field), field);
+}
+
+int cdn_dp_active(struct cdn_dp_device *dp, bool enable)
+{
+	u8 active = enable ? 1 : 0;
+	u8 resp;
+	int ret;
+
+	/* set firmware status, 1: active; 0: standby */
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_GENERAL,
+				  GENERAL_MAIN_CONTROL, sizeof(active),
+				  &active);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_GENERAL,
+					      GENERAL_MAIN_CONTROL,
+					      sizeof(resp));
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, &resp, sizeof(resp));
+	if (ret)
+		return ret;
+
+	return resp ? 0 : -EINVAL;
+}
+
+int cdn_dp_set_host_cap(struct cdn_dp_device *dp, u8 lanes)
+{
+	u8 msg[8];
+	int ret;
+
+	msg[0] = CDN_DP_MAX_LINK_RATE;
+	msg[1] = lanes;
+	msg[2] = VOLTAGE_LEVEL_2;
+	msg[3] = PRE_EMPHASIS_LEVEL_3;
+	msg[4] = PTS1 | PTS2 | PTS3 | PTS4;
+	msg[5] = FAST_LT_NOT_SUPPORT;
+	msg[6] = LANE_MAPPING_NORMAL;
+	msg[7] = ENHANCED;
+
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
+				  DPTX_SET_HOST_CAPABILITIES,
+				  sizeof(msg), msg);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_reg_write(dp, DP_AUX_SWAP_INVERSION_CONTROL,
+			       AUX_HOST_INVERT);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+int cdn_dp_event_config(struct cdn_dp_device *dp)
+{
+	u8 msg[5];
+
+	memset(msg, 0, sizeof(msg));
+
+	msg[0] = DPTX_EVENT_ENABLE_HPD | DPTX_EVENT_ENABLE_TRAINING;
+
+	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_ENABLE_EVENT,
+				   sizeof(msg), msg);
+}
+
+u32 cdn_dp_get_event(struct cdn_dp_device *dp)
+{
+	return readl(dp->regs + SW_EVENTS0);
+}
+
+int cdn_dp_get_hpd_status(struct cdn_dp_device *dp)
+{
+	u8 status;
+	int ret;
+
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_HPD_STATE,
+				  0, NULL);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+					      DPTX_HPD_STATE, sizeof(status));
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, &status, sizeof(status));
+	if (ret)
+		return ret;
+
+	return status;
+}
+
+int cdn_dp_get_edid_block(void *data, u8 *edid,
+			  unsigned int block, size_t length)
+{
+	struct cdn_dp_device *dp = data;
+	u8 msg[2], reg[2];
+	int ret;
+
+	msg[0] = block / 2;
+	msg[1] = block % 2;
+
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_GET_EDID,
+				  sizeof(msg), msg);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+					      DPTX_GET_EDID,
+					      sizeof(reg) + length);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, edid, length);
+	if (ret)
+		return ret;
+
+	if (reg[0] != length || reg[1] != block / 2) {
+		dev_err(dp->dev, "edid block size err\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int cdn_dp_training_start(struct cdn_dp_device *dp)
+{
+	u8 msg, event[2];
+	unsigned long timeout;
+	int ret;
+
+	msg = LINK_TRAINING_RUN;
+
+	/* start training */
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_TRAINING_CONTROL,
+				  sizeof(msg), &msg);
+	if (ret)
+		return ret;
+
+	timeout = jiffies + msecs_to_jiffies(LINK_TRAINING_TIMEOUT_MS);
+	while (time_before(jiffies, timeout)) {
+		msleep(LINK_TRAINING_RETRY_MS);
+		ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
+					  DPTX_READ_EVENT, 0, NULL);
+		if (ret)
+			return ret;
+
+		ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+						      DPTX_READ_EVENT,
+						      sizeof(event));
+		if (ret)
+			return ret;
+
+		ret = cdn_dp_mailbox_read_receive(dp, event, sizeof(event));
+		if (ret)
+			return ret;
+
+		if (event[1] & EQ_PHASE_FINISHED)
+			return 0;
+	}
+
+	return -ETIMEDOUT;
+}
+
+int cdn_dp_get_training_status(struct cdn_dp_device *dp)
+{
+	u8 status[10];
+	int ret;
+
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_READ_LINK_STAT,
+				  0, NULL);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+					      DPTX_READ_LINK_STAT,
+					      sizeof(status));
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, status, sizeof(status));
+	if (ret)
+		return ret;
+
+	dp->link.rate = status[0];
+	dp->link.num_lanes = status[1];
+
+	return 0;
+}
+
+int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active)
+{
+	u8 msg;
+
+	msg = !!active;
+
+	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_SET_VIDEO,
+				   sizeof(msg), &msg);
+}
+
+static int cdn_dp_get_msa_misc(struct video_info *video,
+			       struct drm_display_mode *mode)
+{
+	u8 val[2];
+	u32 msa_misc;
+
+	switch (video->color_fmt) {
+	case PXL_RGB:
+	case Y_ONLY:
+		val[0] = 0;
+		break;
+	/* set YUV default color space conversion to BT601 */
+	case YCBCR_4_4_4:
+		val[0] = 6 + BT_601 * 8;
+		break;
+	case YCBCR_4_2_2:
+		val[0] = 5 + BT_601 * 8;
+		break;
+	case YCBCR_4_2_0:
+		val[0] = 5;
+		break;
+	};
+
+	switch (video->color_depth) {
+	case 6:
+		val[1] = 0;
+		break;
+	case 8:
+		val[1] = 1;
+		break;
+	case 10:
+		val[1] = 2;
+		break;
+	case 12:
+		val[1] = 3;
+		break;
+	case 16:
+		val[1] = 4;
+		break;
+	};
+
+	msa_misc = 2 * val[0] + 32 * val[1] +
+		   ((video->color_fmt == Y_ONLY) ? (1 << 14) : 0);
+
+	return msa_misc;
+}
+
+int cdn_dp_config_video(struct cdn_dp_device *dp)
+{
+	struct video_info *video = &dp->video_info;
+	struct drm_display_mode *mode = &dp->mode;
+	u64 symbol;
+	u32 val, link_rate;
+	u8 bit_per_pix;
+	u8 tu_size_reg = TU_SIZE;
+	int ret;
+
+	bit_per_pix = (video->color_fmt == YCBCR_4_2_2) ?
+		      (video->color_depth * 2) : (video->color_depth * 3);
+
+	link_rate = drm_dp_bw_code_to_link_rate(dp->link.rate);
+
+	val = VIF_BYPASS_INTERLACE;
+	ret = cdn_dp_reg_write(dp, BND_HSYNC2VSYNC, val);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_reg_write(dp, HSYNC2VSYNC_POL_CTRL, 0);
+	if (ret)
+		return ret;
+
+	/* get a best tu_size and symbol */
+	do {
+		tu_size_reg += 2;
+		symbol = tu_size_reg * mode->clock * bit_per_pix;
+		symbol /= dp->link.num_lanes * link_rate * 8;
+	} while ((symbol == 1) || (tu_size_reg - symbol < 4));
+
+	val = symbol + (tu_size_reg << 8);
+	ret = cdn_dp_reg_write(dp, DP_FRAMER_TU, val);
+	if (ret)
+		return ret;
+
+	switch (video->color_depth) {
+	case 6:
+		val = BCS_6;
+		break;
+	case 8:
+		val = BCS_8;
+		break;
+	case 10:
+		val = BCS_10;
+		break;
+	case 12:
+		val = BCS_12;
+		break;
+	case 16:
+		val = BCS_16;
+		break;
+	};
+
+	val += video->color_fmt << 8;
+	ret = cdn_dp_reg_write(dp, DP_FRAMER_PXL_REPR, val);
+	if (ret)
+		return ret;
+
+	val = video->h_sync_polarity ? DP_FRAMER_SP_HSP : 0;
+	val |= video->v_sync_polarity ? DP_FRAMER_SP_VSP : 0;
+	ret = cdn_dp_reg_write(dp, DP_FRAMER_SP, val);
+	if (ret)
+		return ret;
+
+	val = (mode->hsync_start - mode->hdisplay) << 16;
+	val |= mode->htotal - mode->hsync_end;
+	ret = cdn_dp_reg_write(dp, DP_FRONT_BACK_PORCH, val);
+	if (ret)
+		return ret;
+
+	val = mode->hdisplay * bit_per_pix / 8;
+	ret = cdn_dp_reg_write(dp, DP_BYTE_COUNT, val);
+	if (ret)
+		return ret;
+
+	val = mode->htotal | ((mode->htotal - mode->hsync_start) << 16);
+	ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_0, val);
+	if (ret)
+		return ret;
+
+	val = mode->hsync_end - mode->hsync_start;
+	val |= (mode->hdisplay << 16) | (video->h_sync_polarity << 15);
+	ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_1, val);
+	if (ret)
+		return ret;
+
+	val = mode->vtotal;
+	val |= ((mode->vtotal - mode->vsync_start) << 16);
+	ret = cdn_dp_reg_write(dp, MSA_VERTICAL_0, val);
+	if (ret)
+		return ret;
+
+	val = mode->vsync_end - mode->vsync_start;
+	val |= mode->vdisplay << 16 | (video->v_sync_polarity << 15);
+	ret = cdn_dp_reg_write(dp, MSA_VERTICAL_1, val);
+	if (ret)
+		return ret;
+
+	val = cdn_dp_get_msa_misc(video, mode);
+	ret = cdn_dp_reg_write(dp, MSA_MISC, val);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_reg_write(dp, STREAM_CONFIG, 1);
+	if (ret)
+		return ret;
+
+	val = mode->hsync_end - mode->hsync_start;
+	val |= (mode->hdisplay << 16);
+	ret = cdn_dp_reg_write(dp, DP_HORIZONTAL, val);
+	if (ret)
+		return ret;
+
+	val = mode->vtotal;
+	val -= (mode->vtotal - mode->vdisplay);
+	val |= (mode->vtotal - mode->vsync_start) << 16;
+	ret = cdn_dp_reg_write(dp, DP_VERTICAL_0, val);
+	if (ret)
+		return ret;
+
+	val = mode->vtotal;
+	ret = cdn_dp_reg_write(dp, DP_VERTICAL_1, val);
+	if (ret)
+		return ret;
+
+	val =  0;
+	return cdn_dp_reg_write_bit(dp, DP_VB_ID, 2, 1, val);
+}
+
+int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio)
+{
+	u32 val;
+	int ret;
+
+	ret = cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, 0);
+	if (ret)
+		return ret;
+
+	val = SPDIF_AVG_SEL | SPDIF_JITTER_BYPASS;
+	val |= SPDIF_FIFO_MID_RANGE(0xe0);
+	val |= SPDIF_JITTER_THRSH(0xe0);
+	val |= SPDIF_JITTER_AVG_WIN(7);
+	writel(val, dp->regs + SPDIF_CTRL_ADDR);
+
+	/* clearn the audio config and reset */
+	writel(0, dp->regs + AUDIO_SRC_CNTL);
+	writel(0, dp->regs + AUDIO_SRC_CNFG);
+	writel(AUDIO_SW_RST, dp->regs + AUDIO_SRC_CNTL);
+	writel(0, dp->regs + AUDIO_SRC_CNTL);
+
+	/* reset smpl2pckt component  */
+	writel(0, dp->regs + SMPL2PKT_CNTL);
+	writel(AUDIO_SW_RST, dp->regs + SMPL2PKT_CNTL);
+	writel(0, dp->regs + SMPL2PKT_CNTL);
+
+	/* reset FIFO */
+	writel(AUDIO_SW_RST, dp->regs + FIFO_CNTL);
+	writel(0, dp->regs + FIFO_CNTL);
+
+	if (audio->format == AFMT_SPDIF)
+		clk_disable_unprepare(dp->spdif_clk);
+
+	return 0;
+}
+
+int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable)
+{
+	return cdn_dp_reg_write_bit(dp, DP_VB_ID, 4, 1, enable);
+}
+
+static void cdn_dp_audio_config_i2s(struct cdn_dp_device *dp,
+				    struct audio_info *audio)
+{
+	int sub_pckt_num = 1, i2s_port_en_val = 0xf, i;
+	u32 val;
+
+	if (audio->channels == 2) {
+		if (dp->link.num_lanes == 1)
+			sub_pckt_num = 2;
+		else
+			sub_pckt_num = 4;
+
+		i2s_port_en_val = 1;
+	} else if (audio->channels == 4) {
+		i2s_port_en_val = 3;
+	}
+
+	writel(0x0, dp->regs + SPDIF_CTRL_ADDR);
+
+	writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
+
+	val = MAX_NUM_CH(audio->channels);
+	val |= NUM_OF_I2S_PORTS(audio->channels);
+	val |= AUDIO_TYPE_LPCM;
+	val |= CFG_SUB_PCKT_NUM(sub_pckt_num);
+	writel(val, dp->regs + SMPL2PKT_CNFG);
+
+	if (audio->sample_width == 16)
+		val = 0;
+	else if (audio->sample_width == 24)
+		val = 1 << 9;
+	else
+		val = 2 << 9;
+
+	val |= AUDIO_CH_NUM(audio->channels);
+	val |= I2S_DEC_PORT_EN(i2s_port_en_val);
+	val |= TRANS_SMPL_WIDTH_32;
+	writel(val, dp->regs + AUDIO_SRC_CNFG);
+
+	for (i = 0; i < (audio->channels + 1) / 2; i++) {
+		if (audio->sample_width == 16)
+			val = (0x08 << 8) | (0x08 << 20);
+		else if (audio->sample_width == 24)
+			val = (0x0b << 8) | (0x0b << 20);
+
+		val |= ((2 * i) << 4) | ((2 * i + 1) << 16);
+		writel(val, dp->regs + STTS_BIT_CH(i));
+	}
+
+	switch (audio->sample_rate) {
+	case 32000:
+		val = SAMPLING_FREQ(3) |
+		      ORIGINAL_SAMP_FREQ(0xc);
+		break;
+	case 44100:
+		val = SAMPLING_FREQ(0) |
+		      ORIGINAL_SAMP_FREQ(0xf);
+		break;
+	case 48000:
+		val = SAMPLING_FREQ(2) |
+		      ORIGINAL_SAMP_FREQ(0xd);
+		break;
+	case 88200:
+		val = SAMPLING_FREQ(8) |
+		      ORIGINAL_SAMP_FREQ(0x7);
+		break;
+	case 96000:
+		val = SAMPLING_FREQ(0xa) |
+		      ORIGINAL_SAMP_FREQ(5);
+		break;
+	case 176400:
+		val = SAMPLING_FREQ(0xc) |
+		      ORIGINAL_SAMP_FREQ(3);
+		break;
+	case 192000:
+		val = SAMPLING_FREQ(0xe) |
+		      ORIGINAL_SAMP_FREQ(1);
+		break;
+	}
+	val |= 4;
+	writel(val, dp->regs + COM_CH_STTS_BITS);
+
+	writel(SMPL2PKT_EN, dp->regs + SMPL2PKT_CNTL);
+	writel(I2S_DEC_START, dp->regs + AUDIO_SRC_CNTL);
+}
+
+static void cdn_dp_audio_config_spdif(struct cdn_dp_device *dp)
+{
+	u32 val;
+
+	val = SPDIF_AVG_SEL | SPDIF_JITTER_BYPASS;
+	val |= SPDIF_FIFO_MID_RANGE(0xe0);
+	val |= SPDIF_JITTER_THRSH(0xe0);
+	val |= SPDIF_JITTER_AVG_WIN(7);
+	writel(val, dp->regs + SPDIF_CTRL_ADDR);
+
+	writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
+
+	val = MAX_NUM_CH(2) | AUDIO_TYPE_LPCM | CFG_SUB_PCKT_NUM(4);
+	writel(val, dp->regs + SMPL2PKT_CNFG);
+	writel(SMPL2PKT_EN, dp->regs + SMPL2PKT_CNTL);
+
+	val = SPDIF_ENABLE | SPDIF_AVG_SEL | SPDIF_JITTER_BYPASS;
+	val |= SPDIF_FIFO_MID_RANGE(0xe0);
+	val |= SPDIF_JITTER_THRSH(0xe0);
+	val |= SPDIF_JITTER_AVG_WIN(7);
+	writel(val, dp->regs + SPDIF_CTRL_ADDR);
+
+	clk_prepare_enable(dp->spdif_clk);
+	clk_set_rate(dp->spdif_clk, CDN_DP_SPDIF_CLK);
+}
+
+int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info *audio)
+{
+	int ret;
+
+	/* reset the spdif clk before config */
+	if (audio->format == AFMT_SPDIF) {
+		reset_control_assert(dp->spdif_rst);
+		reset_control_deassert(dp->spdif_rst);
+	}
+
+	ret = cdn_dp_reg_write(dp, CM_LANE_CTRL, LANE_REF_CYC);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_reg_write(dp, CM_CTRL, 0);
+	if (ret)
+		return ret;
+
+	if (audio->format == AFMT_I2S)
+		cdn_dp_audio_config_i2s(dp, audio);
+	else
+		cdn_dp_audio_config_spdif(dp);
+
+	return cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, AUDIO_PACK_EN);
+}
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.h b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
new file mode 100644
index 0000000..1affeed
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _CDN_DP_REG_H
+#define _CDN_DP_REG_H
+
+#include <linux/bitops.h>
+
+#define ADDR_IMEM		0x10000
+#define ADDR_DMEM		0x20000
+
+/* APB CFG addr */
+#define APB_CTRL			0
+#define XT_INT_CTRL			0x04
+#define MAILBOX_FULL_ADDR		0x08
+#define MAILBOX_EMPTY_ADDR		0x0c
+#define MAILBOX0_WR_DATA		0x10
+#define MAILBOX0_RD_DATA		0x14
+#define KEEP_ALIVE			0x18
+#define VER_L				0x1c
+#define VER_H				0x20
+#define VER_LIB_L_ADDR			0x24
+#define VER_LIB_H_ADDR			0x28
+#define SW_DEBUG_L			0x2c
+#define SW_DEBUG_H			0x30
+#define MAILBOX_INT_MASK		0x34
+#define MAILBOX_INT_STATUS		0x38
+#define SW_CLK_L			0x3c
+#define SW_CLK_H			0x40
+#define SW_EVENTS0			0x44
+#define SW_EVENTS1			0x48
+#define SW_EVENTS2			0x4c
+#define SW_EVENTS3			0x50
+#define XT_OCD_CTRL			0x60
+#define APB_INT_MASK			0x6c
+#define APB_STATUS_MASK			0x70
+
+/* audio decoder addr */
+#define AUDIO_SRC_CNTL			0x30000
+#define AUDIO_SRC_CNFG			0x30004
+#define COM_CH_STTS_BITS		0x30008
+#define STTS_BIT_CH(x)			(0x3000c + ((x) << 2))
+#define SPDIF_CTRL_ADDR			0x3004c
+#define SPDIF_CH1_CS_3100_ADDR		0x30050
+#define SPDIF_CH1_CS_6332_ADDR		0x30054
+#define SPDIF_CH1_CS_9564_ADDR		0x30058
+#define SPDIF_CH1_CS_12796_ADDR		0x3005c
+#define SPDIF_CH1_CS_159128_ADDR	0x30060
+#define SPDIF_CH1_CS_191160_ADDR	0x30064
+#define SPDIF_CH2_CS_3100_ADDR		0x30068
+#define SPDIF_CH2_CS_6332_ADDR		0x3006c
+#define SPDIF_CH2_CS_9564_ADDR		0x30070
+#define SPDIF_CH2_CS_12796_ADDR		0x30074
+#define SPDIF_CH2_CS_159128_ADDR	0x30078
+#define SPDIF_CH2_CS_191160_ADDR	0x3007c
+#define SMPL2PKT_CNTL			0x30080
+#define SMPL2PKT_CNFG			0x30084
+#define FIFO_CNTL			0x30088
+#define FIFO_STTS			0x3008c
+
+/* source pif addr */
+#define SOURCE_PIF_WR_ADDR		0x30800
+#define SOURCE_PIF_WR_REQ		0x30804
+#define SOURCE_PIF_RD_ADDR		0x30808
+#define SOURCE_PIF_RD_REQ		0x3080c
+#define SOURCE_PIF_DATA_WR		0x30810
+#define SOURCE_PIF_DATA_RD		0x30814
+#define SOURCE_PIF_FIFO1_FLUSH		0x30818
+#define SOURCE_PIF_FIFO2_FLUSH		0x3081c
+#define SOURCE_PIF_STATUS		0x30820
+#define SOURCE_PIF_INTERRUPT_SOURCE	0x30824
+#define SOURCE_PIF_INTERRUPT_MASK	0x30828
+#define SOURCE_PIF_PKT_ALLOC_REG	0x3082c
+#define SOURCE_PIF_PKT_ALLOC_WR_EN	0x30830
+#define SOURCE_PIF_SW_RESET		0x30834
+
+/* bellow registers need access by mailbox */
+/* source car addr */
+#define SOURCE_HDTX_CAR			0x0900
+#define SOURCE_DPTX_CAR			0x0904
+#define SOURCE_PHY_CAR			0x0908
+#define SOURCE_CEC_CAR			0x090c
+#define SOURCE_CBUS_CAR			0x0910
+#define SOURCE_PKT_CAR			0x0918
+#define SOURCE_AIF_CAR			0x091c
+#define SOURCE_CIPHER_CAR		0x0920
+#define SOURCE_CRYPTO_CAR		0x0924
+
+/* clock meters addr */
+#define CM_CTRL				0x0a00
+#define CM_I2S_CTRL			0x0a04
+#define CM_SPDIF_CTRL			0x0a08
+#define CM_VID_CTRL			0x0a0c
+#define CM_LANE_CTRL			0x0a10
+#define I2S_NM_STABLE			0x0a14
+#define I2S_NCTS_STABLE			0x0a18
+#define SPDIF_NM_STABLE			0x0a1c
+#define SPDIF_NCTS_STABLE		0x0a20
+#define NMVID_MEAS_STABLE		0x0a24
+#define I2S_MEAS			0x0a40
+#define SPDIF_MEAS			0x0a80
+#define NMVID_MEAS			0x0ac0
+
+/* source vif addr */
+#define BND_HSYNC2VSYNC			0x0b00
+#define HSYNC2VSYNC_F1_L1		0x0b04
+#define HSYNC2VSYNC_F2_L1		0x0b08
+#define HSYNC2VSYNC_STATUS		0x0b0c
+#define HSYNC2VSYNC_POL_CTRL		0x0b10
+
+/* dptx phy addr */
+#define DP_TX_PHY_CONFIG_REG		0x2000
+#define DP_TX_PHY_STATUS_REG		0x2004
+#define DP_TX_PHY_SW_RESET		0x2008
+#define DP_TX_PHY_SCRAMBLER_SEED	0x200c
+#define DP_TX_PHY_TRAINING_01_04	0x2010
+#define DP_TX_PHY_TRAINING_05_08	0x2014
+#define DP_TX_PHY_TRAINING_09_10	0x2018
+#define TEST_COR			0x23fc
+
+/* dptx hpd addr */
+#define HPD_IRQ_DET_MIN_TIMER		0x2100
+#define HPD_IRQ_DET_MAX_TIMER		0x2104
+#define HPD_UNPLGED_DET_MIN_TIMER	0x2108
+#define HPD_STABLE_TIMER		0x210c
+#define HPD_FILTER_TIMER		0x2110
+#define HPD_EVENT_MASK			0x211c
+#define HPD_EVENT_DET			0x2120
+
+/* dpyx framer addr */
+#define DP_FRAMER_GLOBAL_CONFIG		0x2200
+#define DP_SW_RESET			0x2204
+#define DP_FRAMER_TU			0x2208
+#define DP_FRAMER_PXL_REPR		0x220c
+#define DP_FRAMER_SP			0x2210
+#define AUDIO_PACK_CONTROL		0x2214
+#define DP_VC_TABLE(x)			(0x2218 + ((x) << 2))
+#define DP_VB_ID			0x2258
+#define DP_MTPH_LVP_CONTROL		0x225c
+#define DP_MTPH_SYMBOL_VALUES		0x2260
+#define DP_MTPH_ECF_CONTROL		0x2264
+#define DP_MTPH_ACT_CONTROL		0x2268
+#define DP_MTPH_STATUS			0x226c
+#define DP_INTERRUPT_SOURCE		0x2270
+#define DP_INTERRUPT_MASK		0x2274
+#define DP_FRONT_BACK_PORCH		0x2278
+#define DP_BYTE_COUNT			0x227c
+
+/* dptx stream addr */
+#define MSA_HORIZONTAL_0		0x2280
+#define MSA_HORIZONTAL_1		0x2284
+#define MSA_VERTICAL_0			0x2288
+#define MSA_VERTICAL_1			0x228c
+#define MSA_MISC			0x2290
+#define STREAM_CONFIG			0x2294
+#define AUDIO_PACK_STATUS		0x2298
+#define VIF_STATUS			0x229c
+#define PCK_STUFF_STATUS_0		0x22a0
+#define PCK_STUFF_STATUS_1		0x22a4
+#define INFO_PACK_STATUS		0x22a8
+#define RATE_GOVERNOR_STATUS		0x22ac
+#define DP_HORIZONTAL			0x22b0
+#define DP_VERTICAL_0			0x22b4
+#define DP_VERTICAL_1			0x22b8
+#define DP_BLOCK_SDP			0x22bc
+
+/* dptx glbl addr */
+#define DPTX_LANE_EN			0x2300
+#define DPTX_ENHNCD			0x2304
+#define DPTX_INT_MASK			0x2308
+#define DPTX_INT_STATUS			0x230c
+
+/* dp aux addr */
+#define DP_AUX_HOST_CONTROL		0x2800
+#define DP_AUX_INTERRUPT_SOURCE		0x2804
+#define DP_AUX_INTERRUPT_MASK		0x2808
+#define DP_AUX_SWAP_INVERSION_CONTROL	0x280c
+#define DP_AUX_SEND_NACK_TRANSACTION	0x2810
+#define DP_AUX_CLEAR_RX			0x2814
+#define DP_AUX_CLEAR_TX			0x2818
+#define DP_AUX_TIMER_STOP		0x281c
+#define DP_AUX_TIMER_CLEAR		0x2820
+#define DP_AUX_RESET_SW			0x2824
+#define DP_AUX_DIVIDE_2M		0x2828
+#define DP_AUX_TX_PREACHARGE_LENGTH	0x282c
+#define DP_AUX_FREQUENCY_1M_MAX		0x2830
+#define DP_AUX_FREQUENCY_1M_MIN		0x2834
+#define DP_AUX_RX_PRE_MIN		0x2838
+#define DP_AUX_RX_PRE_MAX		0x283c
+#define DP_AUX_TIMER_PRESET		0x2840
+#define DP_AUX_NACK_FORMAT		0x2844
+#define DP_AUX_TX_DATA			0x2848
+#define DP_AUX_RX_DATA			0x284c
+#define DP_AUX_TX_STATUS		0x2850
+#define DP_AUX_RX_STATUS		0x2854
+#define DP_AUX_RX_CYCLE_COUNTER		0x2858
+#define DP_AUX_MAIN_STATES		0x285c
+#define DP_AUX_MAIN_TIMER		0x2860
+#define DP_AUX_AFE_OUT			0x2864
+
+/* crypto addr */
+#define CRYPTO_HDCP_REVISION		0x5800
+#define HDCP_CRYPTO_CONFIG		0x5804
+#define CRYPTO_INTERRUPT_SOURCE		0x5808
+#define CRYPTO_INTERRUPT_MASK		0x580c
+#define CRYPTO22_CONFIG			0x5818
+#define CRYPTO22_STATUS			0x581c
+#define SHA_256_DATA_IN			0x583c
+#define SHA_256_DATA_OUT_(x)		(0x5850 + ((x) << 2))
+#define AES_32_KEY_(x)			(0x5870 + ((x) << 2))
+#define AES_32_DATA_IN			0x5880
+#define AES_32_DATA_OUT_(x)		(0x5884 + ((x) << 2))
+#define CRYPTO14_CONFIG			0x58a0
+#define CRYPTO14_STATUS			0x58a4
+#define CRYPTO14_PRNM_OUT		0x58a8
+#define CRYPTO14_KM_0			0x58ac
+#define CRYPTO14_KM_1			0x58b0
+#define CRYPTO14_AN_0			0x58b4
+#define CRYPTO14_AN_1			0x58b8
+#define CRYPTO14_YOUR_KSV_0		0x58bc
+#define CRYPTO14_YOUR_KSV_1		0x58c0
+#define CRYPTO14_MI_0			0x58c4
+#define CRYPTO14_MI_1			0x58c8
+#define CRYPTO14_TI_0			0x58cc
+#define CRYPTO14_KI_0			0x58d0
+#define CRYPTO14_KI_1			0x58d4
+#define CRYPTO14_BLOCKS_NUM		0x58d8
+#define CRYPTO14_KEY_MEM_DATA_0		0x58dc
+#define CRYPTO14_KEY_MEM_DATA_1		0x58e0
+#define CRYPTO14_SHA1_MSG_DATA		0x58e4
+#define CRYPTO14_SHA1_V_VALUE_(x)	(0x58e8 + ((x) << 2))
+#define TRNG_CTRL			0x58fc
+#define TRNG_DATA_RDY			0x5900
+#define TRNG_DATA			0x5904
+
+/* cipher addr */
+#define HDCP_REVISION			0x60000
+#define INTERRUPT_SOURCE		0x60004
+#define INTERRUPT_MASK			0x60008
+#define HDCP_CIPHER_CONFIG		0x6000c
+#define AES_128_KEY_0			0x60010
+#define AES_128_KEY_1			0x60014
+#define AES_128_KEY_2			0x60018
+#define AES_128_KEY_3			0x6001c
+#define AES_128_RANDOM_0		0x60020
+#define AES_128_RANDOM_1		0x60024
+#define CIPHER14_KM_0			0x60028
+#define CIPHER14_KM_1			0x6002c
+#define CIPHER14_STATUS			0x60030
+#define CIPHER14_RI_PJ_STATUS		0x60034
+#define CIPHER_MODE			0x60038
+#define CIPHER14_AN_0			0x6003c
+#define CIPHER14_AN_1			0x60040
+#define CIPHER22_AUTH			0x60044
+#define CIPHER14_R0_DP_STATUS		0x60048
+#define CIPHER14_BOOTSTRAP		0x6004c
+
+#define DPTX_FRMR_DATA_CLK_RSTN_EN	BIT(11)
+#define DPTX_FRMR_DATA_CLK_EN		BIT(10)
+#define DPTX_PHY_DATA_RSTN_EN		BIT(9)
+#define DPTX_PHY_DATA_CLK_EN		BIT(8)
+#define DPTX_PHY_CHAR_RSTN_EN		BIT(7)
+#define DPTX_PHY_CHAR_CLK_EN		BIT(6)
+#define SOURCE_AUX_SYS_CLK_RSTN_EN	BIT(5)
+#define SOURCE_AUX_SYS_CLK_EN		BIT(4)
+#define DPTX_SYS_CLK_RSTN_EN		BIT(3)
+#define DPTX_SYS_CLK_EN			BIT(2)
+#define CFG_DPTX_VIF_CLK_RSTN_EN	BIT(1)
+#define CFG_DPTX_VIF_CLK_EN		BIT(0)
+
+#define SOURCE_PHY_RSTN_EN		BIT(1)
+#define SOURCE_PHY_CLK_EN		BIT(0)
+
+#define SOURCE_PKT_SYS_RSTN_EN		BIT(3)
+#define SOURCE_PKT_SYS_CLK_EN		BIT(2)
+#define SOURCE_PKT_DATA_RSTN_EN		BIT(1)
+#define SOURCE_PKT_DATA_CLK_EN		BIT(0)
+
+#define SPDIF_CDR_CLK_RSTN_EN		BIT(5)
+#define SPDIF_CDR_CLK_EN		BIT(4)
+#define SOURCE_AIF_SYS_RSTN_EN		BIT(3)
+#define SOURCE_AIF_SYS_CLK_EN		BIT(2)
+#define SOURCE_AIF_CLK_RSTN_EN		BIT(1)
+#define SOURCE_AIF_CLK_EN		BIT(0)
+
+#define SOURCE_CIPHER_SYSTEM_CLK_RSTN_EN	BIT(3)
+#define SOURCE_CIPHER_SYS_CLK_EN		BIT(2)
+#define SOURCE_CIPHER_CHAR_CLK_RSTN_EN		BIT(1)
+#define SOURCE_CIPHER_CHAR_CLK_EN		BIT(0)
+
+#define SOURCE_CRYPTO_SYS_CLK_RSTN_EN	BIT(1)
+#define SOURCE_CRYPTO_SYS_CLK_EN	BIT(0)
+
+#define APB_IRAM_PATH			BIT(2)
+#define APB_DRAM_PATH			BIT(1)
+#define APB_XT_RESET			BIT(0)
+
+#define MAILBOX_INT_MASK_BIT		BIT(1)
+#define PIF_INT_MASK_BIT		BIT(0)
+#define ALL_INT_MASK			3
+
+/* mailbox */
+#define MB_OPCODE_ID			0
+#define MB_MODULE_ID			1
+#define MB_SIZE_MSB_ID			2
+#define MB_SIZE_LSB_ID			3
+#define MB_DATA_ID			4
+
+#define MB_MODULE_ID_DP_TX		0x01
+#define MB_MODULE_ID_HDCP_TX		0x07
+#define MB_MODULE_ID_HDCP_RX		0x08
+#define MB_MODULE_ID_HDCP_GENERAL	0x09
+#define MB_MODULE_ID_GENERAL		0x0a
+
+/* general opcode */
+#define GENERAL_MAIN_CONTROL            0x01
+#define GENERAL_TEST_ECHO               0x02
+#define GENERAL_BUS_SETTINGS            0x03
+#define GENERAL_TEST_ACCESS             0x04
+
+#define DPTX_SET_POWER_MNG			0x00
+#define DPTX_SET_HOST_CAPABILITIES		0x01
+#define DPTX_GET_EDID				0x02
+#define DPTX_READ_DPCD				0x03
+#define DPTX_WRITE_DPCD				0x04
+#define DPTX_ENABLE_EVENT			0x05
+#define DPTX_WRITE_REGISTER			0x06
+#define DPTX_READ_REGISTER			0x07
+#define DPTX_WRITE_FIELD			0x08
+#define DPTX_TRAINING_CONTROL			0x09
+#define DPTX_READ_EVENT				0x0a
+#define DPTX_READ_LINK_STAT			0x0b
+#define DPTX_SET_VIDEO				0x0c
+#define DPTX_SET_AUDIO				0x0d
+#define DPTX_GET_LAST_AUX_STAUS			0x0e
+#define DPTX_SET_LINK_BREAK_POINT		0x0f
+#define DPTX_FORCE_LANES			0x10
+#define DPTX_HPD_STATE				0x11
+
+#define DPTX_EVENT_ENABLE_HPD			BIT(0)
+#define DPTX_EVENT_ENABLE_TRAINING		BIT(1)
+
+#define LINK_TRAINING_NOT_ACTIVE		0
+#define LINK_TRAINING_RUN			1
+#define LINK_TRAINING_RESTART			2
+
+#define CONTROL_VIDEO_IDLE			0
+#define CONTROL_VIDEO_VALID			1
+
+#define VIF_BYPASS_INTERLACE			BIT(13)
+#define INTERLACE_FMT_DET			BIT(12)
+#define INTERLACE_DTCT_WIN			0x20
+
+#define DP_FRAMER_SP_INTERLACE_EN		BIT(2)
+#define DP_FRAMER_SP_HSP			BIT(1)
+#define DP_FRAMER_SP_VSP			BIT(0)
+
+/* capability */
+#define AUX_HOST_INVERT				3
+#define	FAST_LT_SUPPORT				1
+#define FAST_LT_NOT_SUPPORT			0
+#define LANE_MAPPING_NORMAL			0xe4
+#define LANE_MAPPING_FLIPPED			0x1b
+#define ENHANCED				1
+
+#define	FULL_LT_STARTED				BIT(0)
+#define FASE_LT_STARTED				BIT(1)
+#define CLK_RECOVERY_FINISHED			BIT(2)
+#define EQ_PHASE_FINISHED			BIT(3)
+#define FASE_LT_START_FINISHED			BIT(4)
+#define CLK_RECOVERY_FAILED			BIT(5)
+#define EQ_PHASE_FAILED				BIT(6)
+#define FASE_LT_FAILED				BIT(7)
+
+#define DPTX_HPD_EVENT				BIT(0)
+#define DPTX_TRAINING_EVENT			BIT(1)
+#define HDCP_TX_STATUS_EVENT			BIT(4)
+#define HDCP2_TX_IS_KM_STORED_EVENT		BIT(5)
+#define HDCP2_TX_STORE_KM_EVENT			BIT(6)
+#define HDCP_TX_IS_RECEIVER_ID_VALID_EVENT	BIT(7)
+
+#define TU_SIZE					30
+#define CDN_DP_MAX_LINK_RATE			DP_LINK_BW_5_4
+
+/* audio */
+#define AUDIO_PACK_EN				BIT(8)
+#define SAMPLING_FREQ(x)			(((x) & 0xf) << 16)
+#define ORIGINAL_SAMP_FREQ(x)			(((x) & 0xf) << 24)
+#define SYNC_WR_TO_CH_ZERO			BIT(1)
+#define I2S_DEC_START				BIT(1)
+#define AUDIO_SW_RST				BIT(0)
+#define SMPL2PKT_EN				BIT(1)
+#define MAX_NUM_CH(x)				(((x) & 0x1f) - 1)
+#define NUM_OF_I2S_PORTS(x)			((((x) / 2 - 1) & 0x3) << 5)
+#define AUDIO_TYPE_LPCM				(2 << 7)
+#define CFG_SUB_PCKT_NUM(x)			((((x) - 1) & 0x7) << 11)
+#define AUDIO_CH_NUM(x)				((((x) - 1) & 0x1f) << 2)
+#define TRANS_SMPL_WIDTH_16			0
+#define TRANS_SMPL_WIDTH_24			BIT(11)
+#define TRANS_SMPL_WIDTH_32			(2 << 11)
+#define I2S_DEC_PORT_EN(x)			(((x) & 0xf) << 17)
+#define SPDIF_ENABLE				BIT(21)
+#define SPDIF_AVG_SEL				BIT(20)
+#define SPDIF_JITTER_BYPASS			BIT(19)
+#define SPDIF_FIFO_MID_RANGE(x)			(((x) & 0xff) << 11)
+#define SPDIF_JITTER_THRSH(x)			(((x) & 0xff) << 3)
+#define SPDIF_JITTER_AVG_WIN(x)			((x) & 0x7)
+
+/* Refernce cycles when using lane clock as refernce */
+#define LANE_REF_CYC				0x8000
+
+enum voltage_swing_level {
+	VOLTAGE_LEVEL_0,
+	VOLTAGE_LEVEL_1,
+	VOLTAGE_LEVEL_2,
+	VOLTAGE_LEVEL_3,
+};
+
+enum pre_emphasis_level {
+	PRE_EMPHASIS_LEVEL_0,
+	PRE_EMPHASIS_LEVEL_1,
+	PRE_EMPHASIS_LEVEL_2,
+	PRE_EMPHASIS_LEVEL_3,
+};
+
+enum pattern_set {
+	PTS1		= BIT(0),
+	PTS2		= BIT(1),
+	PTS3		= BIT(2),
+	PTS4		= BIT(3),
+	DP_NONE		= BIT(4)
+};
+
+enum vic_color_depth {
+	BCS_6 = 0x1,
+	BCS_8 = 0x2,
+	BCS_10 = 0x4,
+	BCS_12 = 0x8,
+	BCS_16 = 0x10,
+};
+
+enum vic_bt_type {
+	BT_601 = 0x0,
+	BT_709 = 0x1,
+};
+
+void cdn_dp_clock_reset(struct cdn_dp_device *dp);
+
+void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, u32 clk);
+int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
+			 u32 i_size, const u32 *d_mem, u32 d_size);
+int cdn_dp_active(struct cdn_dp_device *dp, bool enable);
+int cdn_dp_set_host_cap(struct cdn_dp_device *dp, u8 lanes);
+int cdn_dp_event_config(struct cdn_dp_device *dp);
+u32 cdn_dp_get_event(struct cdn_dp_device *dp);
+int cdn_dp_get_hpd_status(struct cdn_dp_device *dp);
+int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value);
+int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr, u8 *data, u16 len);
+int cdn_dp_get_edid_block(void *dp, u8 *edid,
+			  unsigned int block, size_t length);
+int cdn_dp_training_start(struct cdn_dp_device *dp);
+int cdn_dp_get_training_status(struct cdn_dp_device *dp);
+int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active);
+int cdn_dp_config_video(struct cdn_dp_device *dp);
+int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio);
+int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable);
+int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info *audio);
+#endif /* _CDN_DP_REG_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
index edd7ec2..ceb4b86 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -969,9 +969,9 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
 		vop_dsp_hold_valid_irq_disable(vop);
 	}
 
-	pin_pol = 0x8;
-	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : 1;
-	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? 0 : (1 << 1);
+	pin_pol = BIT(DCLK_INVERT);
+	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : BIT(HSYNC_POSITIVE);
+	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? 0 : BIT(VSYNC_POSITIVE);
 	VOP_CTRL_SET(vop, pin_pol, pin_pol);
 
 	switch (s->output_type) {
@@ -991,6 +991,11 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
 		VOP_CTRL_SET(vop, mipi_pin_pol, pin_pol);
 		VOP_CTRL_SET(vop, mipi_en, 1);
 		break;
+	case DRM_MODE_CONNECTOR_DisplayPort:
+		pin_pol &= ~BIT(DCLK_INVERT);
+		VOP_CTRL_SET(vop, dp_pin_pol, pin_pol);
+		VOP_CTRL_SET(vop, dp_en, 1);
+		break;
 	default:
 		DRM_ERROR("unsupport connector_type[%d]\n", s->output_type);
 	}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
index ff4f52e..4820a8b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
@@ -45,6 +45,7 @@ struct vop_ctrl {
 	struct vop_reg edp_en;
 	struct vop_reg hdmi_en;
 	struct vop_reg mipi_en;
+	struct vop_reg dp_en;
 	struct vop_reg out_mode;
 	struct vop_reg dither_down;
 	struct vop_reg dither_up;
@@ -53,6 +54,7 @@ struct vop_ctrl {
 	struct vop_reg hdmi_pin_pol;
 	struct vop_reg edp_pin_pol;
 	struct vop_reg mipi_pin_pol;
+	struct vop_reg dp_pin_pol;
 
 	struct vop_reg htotal_pw;
 	struct vop_reg hact_st_end;
@@ -242,6 +244,13 @@ enum scale_down_mode {
 	SCALE_DOWN_AVG = 0x1
 };
 
+enum vop_pol {
+	HSYNC_POSITIVE = 0,
+	VSYNC_POSITIVE = 1,
+	DEN_NEGATIVE   = 2,
+	DCLK_INVERT    = 3
+};
+
 #define FRAC_16_16(mult, div)    (((mult) << 16) / (div))
 #define SCL_FT_DEFAULT_FIXPOINT_SHIFT	12
 #define SCL_MAX_VSKIPLINES		4
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
index 5b1ae1f..dcf172e 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
@@ -281,6 +281,7 @@ static const struct vop_data rk3288_vop = {
 static const struct vop_ctrl rk3399_ctrl_data = {
 	.standby = VOP_REG(RK3399_SYS_CTRL, 0x1, 22),
 	.gate_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 23),
+	.dp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 11),
 	.rgb_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 12),
 	.hdmi_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 13),
 	.edp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 14),
@@ -290,6 +291,7 @@ static const struct vop_ctrl rk3399_ctrl_data = {
 	.data_blank = VOP_REG(RK3399_DSP_CTRL0, 0x1, 19),
 	.out_mode = VOP_REG(RK3399_DSP_CTRL0, 0xf, 0),
 	.rgb_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 16),
+	.dp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 16),
 	.hdmi_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 20),
 	.edp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 24),
 	.mipi_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 28),
-- 
2.6.3

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

* [v5.2 PATCH 5/5] drm/rockchip: cdn-dp: add cdn DP support for rk3399
  2016-07-14 14:02       ` Sean Paul
  2016-07-15  5:52         ` [v5.1 " Chris Zhong
@ 2016-07-15  8:18         ` Chris Zhong
  2016-07-19 16:44           ` Sean Paul
  1 sibling, 1 reply; 11+ messages in thread
From: Chris Zhong @ 2016-07-15  8:18 UTC (permalink / raw)
  To: linux-arm-kernel

Add support for cdn DP controller which is embedded in the rk3399
SoCs. The DP is compliant with DisplayPort Specification,
Version 1.3, This IP is compatible with the rockchip type-c PHY IP.
There is a uCPU in DP controller, it need a firmware to work,
please put the firmware file to /lib/firmware/cdn/dptx.bin. The
uCPU in charge of aux communication and link training, the host use
mailbox to communicate with the ucpu.
The dclk pin_pol of vop must not be invert for DP.

Signed-off-by: Chris Zhong <zyw@rock-chips.com>

---

Changes in v5.2:
- fixed the fw_wait always 0

Changes in v5.1:
- modify according to Sean Paul's comments

Changes in v5:
- alphabetical order
- do not use long, use u32 or u64
- return MODE_CLOCK_HIGH when requested > actual
- Optimized Coding Style
- add a formula to get better tu size and symbol value.

Changes in v4:
- use phy framework to control DP phy
- support 2 phys

Changes in v3:
- use EXTCON_DISP_DP and EXTCON_DISP_DP_ALT cable to get dp port state.
- reset spdif before config it
- modify the firmware clk to 100Mhz
- retry load firmware if fw file is requested too early

Changes in v2:
- Alphabetic order
- remove excess error message
- use define clk_rate
- check all return value
- remove dev_set_name(dp->dev, "cdn-dp");
- use schedule_delayed_work
- remove never-called functions
- remove some unnecessary ()

Changes in v1:
- use extcon API
- use hdmi-codec for the DP Asoc
- do not initialize the "ret"
- printk a err log when drm_of_encoder_active_endpoint_id
- modify the dclk pin_pol to a single line

 drivers/gpu/drm/rockchip/Kconfig            |   9 +
 drivers/gpu/drm/rockchip/Makefile           |   1 +
 drivers/gpu/drm/rockchip/cdn-dp-core.c      | 842 ++++++++++++++++++++++++++
 drivers/gpu/drm/rockchip/cdn-dp-core.h      |  99 +++
 drivers/gpu/drm/rockchip/cdn-dp-reg.c       | 898 ++++++++++++++++++++++++++++
 drivers/gpu/drm/rockchip/cdn-dp-reg.h       | 479 +++++++++++++++
 drivers/gpu/drm/rockchip/rockchip_drm_vop.c |  11 +-
 drivers/gpu/drm/rockchip/rockchip_drm_vop.h |   9 +
 drivers/gpu/drm/rockchip/rockchip_vop_reg.c |   2 +
 9 files changed, 2347 insertions(+), 3 deletions(-)
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.c
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.h
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.c
 create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.h

diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
index d30bdc3..a04446a 100644
--- a/drivers/gpu/drm/rockchip/Kconfig
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -25,6 +25,15 @@ config ROCKCHIP_ANALOGIX_DP
 	  for the Analogix Core DP driver. If you want to enable DP
 	  on RK3288 based SoC, you should selet this option.
 
+config ROCKCHIP_CDN_DP
+        tristate "Rockchip cdn DP"
+        depends on DRM_ROCKCHIP
+        help
+	  This selects support for Rockchip SoC specific extensions
+	  for the cdn DP driver. If you want to enable Dp on
+	  RK3399 based SoC, you should select this
+	  option.
+
 config ROCKCHIP_DW_HDMI
         tristate "Rockchip specific extensions for Synopsys DW HDMI"
         depends on DRM_ROCKCHIP
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
index 05d0713..abdecd5 100644
--- a/drivers/gpu/drm/rockchip/Makefile
+++ b/drivers/gpu/drm/rockchip/Makefile
@@ -7,6 +7,7 @@ rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
 rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
 
 obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
+obj-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o
 obj-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
 obj-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi.o
 obj-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
new file mode 100644
index 0000000..ed38dbc
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
@@ -0,0 +1,842 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/extcon.h>
+#include <linux/firmware.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/mfd/syscon.h>
+#include <linux/phy/phy.h>
+
+#include <sound/hdmi-codec.h>
+
+#include "cdn-dp-core.h"
+#include "cdn-dp-reg.h"
+#include "rockchip_drm_vop.h"
+
+#define connector_to_dp(c) \
+		container_of(c, struct cdn_dp_device, connector)
+
+#define encoder_to_dp(c) \
+		container_of(c, struct cdn_dp_device, encoder)
+
+/* dp grf register offset */
+#define DP_VOP_SEL		0x6224
+#define DP_SEL_VOP_LIT		BIT(12)
+#define MAX_FW_WAIT_SECS	64
+#define CDN_DP_FIRMWARE		"rockchip/dptx.bin"
+
+struct cdn_dp_data {
+	u8 max_phy;
+};
+
+struct cdn_dp_data rk3399_cdn_dp = {
+	.max_phy = 2,
+};
+
+static const struct of_device_id cdn_dp_dt_ids[] = {
+	{ .compatible = "rockchip,rk3399-cdn-dp",
+		.data = (void *)&rk3399_cdn_dp },
+	{}
+};
+
+MODULE_DEVICE_TABLE(of, cdn_dp_dt_ids);
+
+static int cdn_dp_clk_enable(struct cdn_dp_device *dp)
+{
+	int ret;
+	u32 rate;
+
+	ret = clk_prepare_enable(dp->pclk);
+	if (ret < 0) {
+		dev_err(dp->dev, "cannot enable dp pclk %d\n", ret);
+		goto err_pclk;
+	}
+
+	ret = clk_prepare_enable(dp->core_clk);
+	if (ret < 0) {
+		dev_err(dp->dev, "cannot enable core_clk %d\n", ret);
+		goto err_core_clk;
+	}
+
+	rate = clk_get_rate(dp->core_clk);
+	if (rate < 0) {
+		dev_err(dp->dev, "get clk rate failed: %d\n", rate);
+		goto err_set_rate;
+	}
+
+	cdn_dp_set_fw_clk(dp, rate);
+
+	return 0;
+
+err_set_rate:
+	clk_disable_unprepare(dp->core_clk);
+err_core_clk:
+	clk_disable_unprepare(dp->pclk);
+err_pclk:
+	return ret;
+}
+
+static enum drm_connector_status
+cdn_dp_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct cdn_dp_device *dp = connector_to_dp(connector);
+
+	return dp->hpd_status;
+}
+
+static void cdn_dp_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static struct drm_connector_funcs cdn_dp_atomic_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.detect = cdn_dp_connector_detect,
+	.destroy = cdn_dp_connector_destroy,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int cdn_dp_connector_get_modes(struct drm_connector *connector)
+{
+	struct cdn_dp_device *dp = connector_to_dp(connector);
+	struct edid *edid;
+	int ret = 0;
+
+	edid = drm_do_get_edid(connector, cdn_dp_get_edid_block, dp);
+	if (edid) {
+		dev_dbg(dp->dev, "got edid: width[%d] x height[%d]\n",
+			edid->width_cm, edid->height_cm);
+
+		dp->sink_has_audio = drm_detect_monitor_audio(edid);
+		ret = drm_add_edid_modes(connector, edid);
+		if (ret) {
+			drm_mode_connector_update_edid_property(connector,
+								edid);
+			drm_edid_to_eld(connector, edid);
+		}
+		kfree(edid);
+	} else {
+		dev_warn(dp->dev, "failed to get edid\n");
+	}
+
+	return ret;
+}
+
+static struct drm_encoder *
+	cdn_dp_connector_best_encoder(struct drm_connector *connector)
+{
+	struct cdn_dp_device *dp = connector_to_dp(connector);
+
+	return &dp->encoder;
+}
+
+static int cdn_dp_connector_mode_valid(struct drm_connector *connector,
+				       struct drm_display_mode *mode)
+{
+	struct cdn_dp_device *dp = connector_to_dp(connector);
+	struct drm_display_info *display_info = &dp->connector.display_info;
+	u32 requested = mode->clock * display_info->bpc * 3 / 1000;
+	u32 sink_max, source_max, rate;
+	u8 lanes, id;
+	u32 actual;
+
+	/* find the running port id */
+	for (id = 0; id < dp->max_phy; id++) {
+		if (dp->phy_status[id] == 1)
+			break;
+	}
+
+	sink_max = drm_dp_max_lane_count(dp->dpcd);
+	source_max = dp->cap_lanes[id];
+	lanes = min(sink_max, source_max);
+
+	sink_max = drm_dp_max_link_rate(dp->dpcd);
+	source_max = drm_dp_bw_code_to_link_rate(CDN_DP_MAX_LINK_RATE);
+	rate = min(sink_max, source_max);
+
+	actual = rate * lanes / 100;
+
+	/* efficiency is about 0.8 */
+	actual = actual * 8 / 10;
+
+	if (requested > actual) {
+		dev_dbg(dp->dev, "requested=%d, actual=%d, clock=%d\n",
+			requested, actual, mode->clock);
+		return MODE_CLOCK_HIGH;
+	}
+
+	return MODE_OK;
+}
+
+static struct drm_connector_helper_funcs cdn_dp_connector_helper_funcs = {
+	.get_modes = cdn_dp_connector_get_modes,
+	.best_encoder = cdn_dp_connector_best_encoder,
+	.mode_valid = cdn_dp_connector_mode_valid,
+};
+
+static void cdn_dp_commit(struct drm_encoder *encoder)
+{
+	struct cdn_dp_device *dp = encoder_to_dp(encoder);
+	int ret;
+
+	ret = cdn_dp_training_start(dp);
+	if (ret) {
+		dev_err(dp->dev, "link training failed: %d\n", ret);
+		return;
+	}
+
+	ret = cdn_dp_get_training_status(dp);
+	if (ret) {
+		dev_err(dp->dev, "get link training status failed: %d\n", ret);
+		return;
+	}
+
+	dev_info(dp->dev, "rate:%d, lanes:%d\n",
+		 dp->link.rate, dp->link.num_lanes);
+
+	if (cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE))
+		return;
+
+	if (cdn_dp_config_video(dp)) {
+		dev_err(dp->dev, "unable to config video\n");
+		return;
+	}
+
+	if (cdn_dp_set_video_status(dp, CONTROL_VIDEO_VALID))
+		return;
+
+	dp->dpms_mode = DRM_MODE_DPMS_ON;
+}
+
+static void cdn_dp_encoder_mode_set(struct drm_encoder *encoder,
+				    struct drm_display_mode *mode,
+				    struct drm_display_mode *adjusted)
+{
+	struct cdn_dp_device *dp = encoder_to_dp(encoder);
+	struct drm_display_info *display_info = &dp->connector.display_info;
+	struct rockchip_crtc_state *state;
+	struct video_info *video = &dp->video_info;
+	int ret, val;
+
+	switch (display_info->bpc) {
+	case 16:
+	case 12:
+	case 10:
+		video->color_depth = 10;
+		break;
+	case 6:
+		video->color_depth = 6;
+		break;
+	default:
+		video->color_depth = 8;
+		break;
+	}
+
+	video->color_fmt = PXL_RGB;
+
+	video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
+	video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
+
+	ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder);
+	if (ret < 0) {
+		dev_err(dp->dev, "Could not get vop id, %d", ret);
+		return;
+	}
+
+	dev_dbg(dp->dev, "vop %s output to cdn-dp\n", (ret) ? "LIT" : "BIG");
+	state = to_rockchip_crtc_state(encoder->crtc->state);
+	if (ret) {
+		val = DP_SEL_VOP_LIT | (DP_SEL_VOP_LIT << 16);
+		state->output_mode = ROCKCHIP_OUT_MODE_P888;
+	} else {
+		val = DP_SEL_VOP_LIT << 16;
+		state->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+	}
+	ret = regmap_write(dp->grf, DP_VOP_SEL, val);
+	if (ret != 0)
+		dev_err(dp->dev, "Could not write to GRF: %d\n", ret);
+
+	memcpy(&dp->mode, adjusted, sizeof(*mode));
+}
+
+static void cdn_dp_encoder_enable(struct drm_encoder *encoder)
+{
+	struct cdn_dp_device *dp = encoder_to_dp(encoder);
+
+	if (dp->dpms_mode != DRM_MODE_DPMS_ON)
+		cdn_dp_commit(encoder);
+}
+
+static void cdn_dp_encoder_disable(struct drm_encoder *encoder)
+{
+	struct cdn_dp_device *dp = encoder_to_dp(encoder);
+
+	if (dp->dpms_mode != DRM_MODE_DPMS_OFF) {
+		cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE);
+		dp->dpms_mode = DRM_MODE_DPMS_OFF;
+	}
+}
+
+static int
+cdn_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);
+
+	s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+	s->output_type = DRM_MODE_CONNECTOR_DisplayPort;
+
+	return 0;
+}
+
+static struct drm_encoder_helper_funcs cdn_dp_encoder_helper_funcs = {
+	.mode_set = cdn_dp_encoder_mode_set,
+	.enable = cdn_dp_encoder_enable,
+	.disable = cdn_dp_encoder_disable,
+	.atomic_check = cdn_dp_encoder_atomic_check,
+};
+
+static struct drm_encoder_funcs cdn_dp_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static int cdn_dp_firmware_init(struct cdn_dp_device *dp)
+{
+	int ret;
+	const u32 *iram_data, *dram_data;
+	const struct firmware *fw = dp->fw;
+	const struct cdn_firmware_header *hdr;
+
+	hdr = (struct cdn_firmware_header *)fw->data;
+	if (fw->size != le32_to_cpu(hdr->size_bytes)) {
+		dev_err(dp->dev, "firmware is invalid\n");
+		return -EINVAL;
+	}
+
+	iram_data = (const u32 *)(fw->data + hdr->header_size);
+	dram_data = (const u32 *)(fw->data + hdr->header_size + hdr->iram_size);
+
+	ret = cdn_dp_load_firmware(dp, iram_data, hdr->iram_size,
+				   dram_data, hdr->dram_size);
+	if (ret)
+		return ret;
+
+	dp->fw_loaded = 1;
+
+	ret = cdn_dp_active(dp, true);
+	if (ret) {
+		dev_err(dp->dev, "active ucpu failed: %d\n", ret);
+		return ret;
+	}
+
+	return cdn_dp_event_config(dp);
+}
+
+static int cdn_dp_init(struct cdn_dp_device *dp)
+{
+	struct device *dev = dp->dev;
+	struct device_node *np = dev->of_node;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct resource *res;
+	int ret;
+
+	dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+	if (IS_ERR(dp->grf)) {
+		dev_err(dev, "cdn-dp needs rockchip,grf property\n");
+		return PTR_ERR(dp->grf);
+	}
+
+	dp->irq = platform_get_irq(pdev, 0);
+	if (dp->irq < 0) {
+		dev_err(dev, "cdn-dp can not get irq\n");
+		return dp->irq;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dp->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(dp->regs)) {
+		dev_err(dev, "ioremap reg failed\n");
+		return PTR_ERR(dp->regs);
+	}
+
+	dp->core_clk = devm_clk_get(dev, "core-clk");
+	if (IS_ERR(dp->core_clk)) {
+		dev_err(dev, "cannot get core_clk_dp\n");
+		return PTR_ERR(dp->core_clk);
+	}
+
+	dp->pclk = devm_clk_get(dev, "pclk");
+	if (IS_ERR(dp->pclk)) {
+		dev_err(dev, "cannot get pclk\n");
+		return PTR_ERR(dp->pclk);
+	}
+
+	dp->spdif_clk = devm_clk_get(dev, "spdif");
+	if (IS_ERR(dp->spdif_clk)) {
+		dev_err(dev, "cannot get spdif_clk\n");
+		return PTR_ERR(dp->spdif_clk);
+	}
+
+	dp->spdif_rst = devm_reset_control_get(dev, "spdif");
+	if (IS_ERR(dp->spdif_rst)) {
+		dev_err(dev, "no spdif reset control found\n");
+		return PTR_ERR(dp->spdif_rst);
+	}
+
+	dp->dpms_mode = DRM_MODE_DPMS_OFF;
+
+	ret = cdn_dp_clk_enable(dp);
+	if (ret < 0)
+		return ret;
+
+	cdn_dp_clock_reset(dp);
+
+	return 0;
+}
+
+static int cdn_dp_audio_hw_params(struct device *dev,
+				  struct hdmi_codec_daifmt *daifmt,
+				  struct hdmi_codec_params *params)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	struct audio_info audio = {
+		.sample_width = params->sample_width,
+		.sample_rate = params->sample_rate,
+		.channels = params->channels,
+	};
+	int ret;
+
+	if (!dp->encoder.crtc)
+		return -ENODEV;
+
+	switch (daifmt->fmt) {
+	case HDMI_I2S:
+		audio.format = AFMT_I2S;
+		break;
+	case HDMI_SPDIF:
+		audio.format = AFMT_SPDIF;
+		break;
+	default:
+		dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt);
+		return -EINVAL;
+	}
+
+	ret = cdn_dp_audio_config_set(dp, &audio);
+	if (!ret)
+		dp->audio_info = audio;
+
+	return ret;
+}
+
+static void cdn_dp_audio_shutdown(struct device *dev)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	int ret = cdn_dp_audio_stop(dp, &dp->audio_info);
+
+	if (!ret)
+		dp->audio_info.format = AFMT_UNUSED;
+}
+
+static int cdn_dp_audio_digital_mute(struct device *dev, bool enable)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+
+	return cdn_dp_audio_mute(dp, enable);
+}
+
+static int cdn_dp_audio_get_eld(struct device *dev, uint8_t *buf, size_t len)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	struct drm_mode_config *config = &dp->encoder.dev->mode_config;
+	struct drm_connector *connector;
+	int ret = -ENODEV;
+
+	mutex_lock(&config->mutex);
+	list_for_each_entry(connector, &config->connector_list, head) {
+		if (&dp->encoder == connector->encoder) {
+			memcpy(buf, connector->eld,
+			       min(sizeof(connector->eld), len));
+			ret = 0;
+		}
+	}
+	mutex_unlock(&config->mutex);
+
+	return ret;
+}
+
+static const struct hdmi_codec_ops audio_codec_ops = {
+	.hw_params = cdn_dp_audio_hw_params,
+	.audio_shutdown = cdn_dp_audio_shutdown,
+	.digital_mute = cdn_dp_audio_digital_mute,
+	.get_eld = cdn_dp_audio_get_eld,
+};
+
+static int cdn_dp_audio_codec_init(struct cdn_dp_device *dp,
+				   struct device *dev)
+{
+	struct hdmi_codec_pdata codec_data = {
+		.i2s = 1,
+		.spdif = 1,
+		.ops = &audio_codec_ops,
+		.max_i2s_channels = 8,
+	};
+
+	dp->audio_pdev = platform_device_register_data(
+			 dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO,
+			 &codec_data, sizeof(codec_data));
+
+	return PTR_ERR_OR_ZERO(dp->audio_pdev);
+}
+
+static int cdn_dp_get_cap_lanes(struct cdn_dp_device *dp,
+				struct extcon_dev *edev)
+{
+	bool dfp, dptx;
+	u8 lanes;
+
+	dfp = extcon_get_cable_state_(edev, EXTCON_USB_HOST);
+	dptx = extcon_get_cable_state_(edev, EXTCON_DISP_DP);
+
+	if (dfp && dptx)
+		lanes = 2;
+	else if (dptx)
+		lanes = 4;
+	else
+		lanes = 0;
+
+	return lanes;
+}
+
+static int cdn_dp_pd_event(struct notifier_block *nb,
+			   unsigned long event, void *priv)
+{
+	struct cdn_dp_device *dp;
+	struct extcon_dev *edev = priv;
+	u8 i;
+
+	dp = container_of(nb, struct cdn_dp_device, event_nb);
+
+	for (i = 0; i < dp->max_phy; i++) {
+		if (edev == dp->extcon[i]) {
+			dp->port_id = i;
+			break;
+		}
+	}
+
+	schedule_delayed_work(&dp->event_wq, 0);
+
+	return 0;
+}
+
+static void cdn_dp_pd_event_wq(struct work_struct *work)
+{
+	struct cdn_dp_device *dp = container_of(work, struct cdn_dp_device,
+						event_wq.work);
+	u8 id = dp->port_id, i;
+	u8 old_cap_lanes = dp->cap_lanes[id];
+	int ret;
+
+	dp->cap_lanes[id] = cdn_dp_get_cap_lanes(dp, dp->extcon[id]);
+	if (dp->cap_lanes[id] == old_cap_lanes)
+		return;
+
+	/* if phy is on, need reset it when dp lanes count is changed */
+	if (dp->phy_status[id]) {
+		ret = phy_power_off(dp->phy[id]);
+		if (ret) {
+			dev_err(dp->dev, "phy power off failed: %d", ret);
+			goto err_phy_power_off;
+		}
+		dp->phy_status[id] = 0;
+	}
+
+	/* if other phy is running, do not touch the hpd_status, and return */
+	for (i = 0; i < dp->max_phy; i++) {
+		if (dp->phy_status[i] == 1) {
+			dev_warn(dp->dev, "busy, phy[%d] is running", i);
+			goto err_phy_power_off;
+		}
+	}
+
+	if (!dp->cap_lanes[id]) {
+		dp->hpd_status = connector_status_disconnected;
+		drm_helper_hpd_irq_event(dp->drm_dev);
+		return;
+	}
+
+	if (!dp->fw_loaded) {
+		ret = request_firmware(&dp->fw, CDN_DP_FIRMWARE, dp->dev);
+		if (ret == -ENOENT && dp->fw_wait <= MAX_FW_WAIT_SECS) {
+			unsigned long time = msecs_to_jiffies(dp->fw_wait * HZ);
+
+			/*
+			 * If can not find the file, retry to load the firmware
+			 * in 1 second, if still failed after 1 minute, give up.
+			 */
+			schedule_delayed_work(&dp->event_wq, time);
+			dev_warn(dp->dev, "retry to request firmware in %d\n",
+				 dp->fw_wait);
+			dp->fw_wait *= 2;
+			goto err_phy_power_off;
+		} else if (ret) {
+			dev_err(dp->dev, "failed to request firmware: %d\n",
+				ret);
+			goto err_phy_power_off;
+		}
+	}
+
+	ret = phy_power_on(dp->phy[id]);
+	if (ret) {
+		dev_err(dp->dev, "phy power on failed: %d", ret);
+		if (!dp->fw_loaded)
+			release_firmware(dp->fw);
+		goto err_phy_power_off;
+	}
+
+	dp->phy_status[id] = 1;
+
+	if (!dp->fw_loaded) {
+		ret = cdn_dp_firmware_init(dp);
+		release_firmware(dp->fw);
+		if (ret) {
+			dev_err(dp->dev, "firmware init failed: %d", ret);
+			goto err_firmware;
+		}
+	}
+
+	/* read hpd status failed, or the hpd does not exist. */
+	ret = cdn_dp_get_hpd_status(dp);
+	if (ret <= 0) {
+		dev_err(dp->dev, "get hpd failed: %d", ret);
+		goto err_firmware;
+	}
+
+	ret = cdn_dp_set_host_cap(dp, dp->cap_lanes[id]);
+	if (ret) {
+		dev_err(dp->dev, "set host capabilities failed: %d\n", ret);
+		goto err_firmware;
+	}
+
+	/*
+	 * Native read with retry for link status and receiver capability reads
+	 * for cases where the sink may still be asleep.
+	 *
+	 * Sinks are *supposed* to come up within 1ms from an off state, but
+	 * we're also supposed to retry 3 times per the spec.
+	 */
+	for (i = 0; i < 3; i++) {
+		ret = cdn_dp_dpcd_read(dp, 0x000, dp->dpcd,
+				       DP_RECEIVER_CAP_SIZE);
+		if (!ret) {
+			dp->hpd_status = connector_status_connected;
+			drm_helper_hpd_irq_event(dp->drm_dev);
+			return;
+		}
+		usleep_range(1000, 1050);
+	}
+
+err_firmware:
+	ret = phy_power_off(dp->phy[id]);
+	if (ret)
+		dev_err(dp->dev, "phy power off failed: %d", ret);
+	else
+		dp->phy_status[id] = 0;
+
+err_phy_power_off:
+	dp->cap_lanes[id] = 0;
+}
+
+static int cdn_dp_bind(struct device *dev, struct device *master,
+		       void *data)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	struct drm_encoder *encoder;
+	struct drm_connector *connector;
+	struct drm_device *drm_dev = data;
+	int ret, i;
+
+	ret = cdn_dp_init(dp);
+	if (ret < 0)
+		return ret;
+
+	dp->drm_dev = drm_dev;
+	dp->hpd_status = connector_status_disconnected;
+	dp->fw_wait = 1;
+
+	encoder = &dp->encoder;
+
+	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
+							     dev->of_node);
+	DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs);
+
+	ret = drm_encoder_init(drm_dev, encoder, &cdn_dp_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS, NULL);
+	if (ret) {
+		DRM_ERROR("failed to initialize encoder with drm\n");
+		return ret;
+	}
+
+	drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs);
+
+	connector = &dp->connector;
+	connector->polled = DRM_CONNECTOR_POLL_HPD;
+	connector->dpms = DRM_MODE_DPMS_OFF;
+
+	ret = drm_connector_init(drm_dev, connector,
+				 &cdn_dp_atomic_connector_funcs,
+				 DRM_MODE_CONNECTOR_DisplayPort);
+	if (ret) {
+		DRM_ERROR("failed to initialize connector with drm\n");
+		goto err_free_encoder;
+	}
+
+	drm_connector_helper_add(connector, &cdn_dp_connector_helper_funcs);
+
+	ret = drm_mode_connector_attach_encoder(connector, encoder);
+	if (ret) {
+		DRM_ERROR("failed to attach connector and encoder\n");
+		goto err_free_connector;
+	}
+
+	cdn_dp_audio_codec_init(dp, dev);
+
+	dp->event_nb.notifier_call = cdn_dp_pd_event;
+	INIT_DELAYED_WORK(&dp->event_wq, cdn_dp_pd_event_wq);
+
+	for (i = 0; i < dp->max_phy; i++) {
+		if (IS_ERR(dp->extcon[i]))
+			continue;
+
+		ret = extcon_register_notifier(dp->extcon[i], EXTCON_DISP_DP,
+					       &dp->event_nb);
+		if (ret) {
+			dev_err(dev, "regitster EXTCON_DISP_DP notifier err\n");
+			return ret;
+		}
+
+		dp->port_id = i;
+		schedule_delayed_work(&dp->event_wq, 0);
+	}
+
+	return 0;
+
+err_free_connector:
+	drm_connector_cleanup(connector);
+err_free_encoder:
+	drm_encoder_cleanup(encoder);
+	return ret;
+}
+
+static void cdn_dp_unbind(struct device *dev, struct device *master, void *data)
+{
+	struct cdn_dp_device *dp = dev_get_drvdata(dev);
+	struct drm_encoder *encoder = &dp->encoder;
+	struct drm_connector *connector = &dp->connector;
+	int i;
+
+	platform_device_unregister(dp->audio_pdev);
+	cdn_dp_encoder_disable(encoder);
+	encoder->funcs->destroy(encoder);
+	connector->funcs->destroy(connector);
+
+	for (i = 0; i < dp->max_phy; i++) {
+		if (!IS_ERR(dp->extcon[i]))
+			extcon_unregister_notifier(dp->extcon[i], EXTCON_USB,
+						   &dp->event_nb);
+	}
+}
+
+static const struct component_ops cdn_dp_component_ops = {
+	.bind = cdn_dp_bind,
+	.unbind = cdn_dp_unbind,
+};
+
+static int cdn_dp_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct of_device_id *match;
+	struct cdn_dp_data *dp_data;
+	struct cdn_dp_device *dp;
+	u8 count = 0;
+	int i;
+
+	dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
+	if (!dp)
+		return -ENOMEM;
+	dp->dev = dev;
+
+	match = of_match_node(cdn_dp_dt_ids, pdev->dev.of_node);
+	dp_data = (struct cdn_dp_data *)match->data;
+
+	dp->max_phy = dp_data->max_phy;
+
+	for (i = 0; i < dp->max_phy; i++) {
+		dp->extcon[i] = extcon_get_edev_by_phandle(dev, i);
+		dp->phy[i] = devm_of_phy_get_by_index(dev, dev->of_node, i);
+
+		if (!IS_ERR(dp->extcon[i]) && !IS_ERR(dp->phy[i]))
+			count++;
+
+		if (PTR_ERR(dp->extcon[i]) == -EPROBE_DEFER ||
+		    PTR_ERR(dp->phy[i]) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
+	}
+
+	if (!count) {
+		dev_err(dev, "missing extcon or phy\n");
+		return -EINVAL;
+	}
+
+	dev_set_drvdata(dev, dp);
+
+	return component_add(dev, &cdn_dp_component_ops);
+}
+
+static int cdn_dp_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &cdn_dp_component_ops);
+
+	return 0;
+}
+
+static struct platform_driver cdn_dp_driver = {
+	.probe = cdn_dp_probe,
+	.remove = cdn_dp_remove,
+	.driver = {
+		   .name = "cdn-dp",
+		   .owner = THIS_MODULE,
+		   .of_match_table = of_match_ptr(cdn_dp_dt_ids),
+	},
+};
+
+module_platform_driver(cdn_dp_driver);
+
+MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
+MODULE_DESCRIPTION("cdn DP Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h
new file mode 100644
index 0000000..1c577bf
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 Chris Zhong <zyw@rock-chips.com>
+ * Copyright (C) 2016 ROCKCHIP, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _CDN_DP_CORE_H
+#define _CDN_DP_CORE_H
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_panel.h>
+#include "rockchip_drm_drv.h"
+
+#define MAX_PHY		2
+
+enum audio_format {
+	AFMT_I2S = 0,
+	AFMT_SPDIF = 1,
+	AFMT_UNUSED,
+};
+
+struct audio_info {
+	enum audio_format format;
+	int sample_rate;
+	int channels;
+	int sample_width;
+};
+
+enum vic_pxl_encoding_format {
+	PXL_RGB = 0x1,
+	YCBCR_4_4_4 = 0x2,
+	YCBCR_4_2_2 = 0x4,
+	YCBCR_4_2_0 = 0x8,
+	Y_ONLY = 0x10,
+};
+
+struct video_info {
+	bool h_sync_polarity;
+	bool v_sync_polarity;
+	bool interlaced;
+	int color_depth;
+	enum vic_pxl_encoding_format color_fmt;
+};
+
+struct cdn_firmware_header {
+	u32 size_bytes; /* size of the entire header+image(s) in bytes */
+	u32 header_size; /* size of just the header in bytes */
+	u32 iram_size; /* size of iram */
+	u32 dram_size; /* size of dram */
+};
+
+struct cdn_dp_device {
+	struct device *dev;
+	struct drm_device *drm_dev;
+	struct drm_connector connector;
+	struct drm_encoder encoder;
+	struct drm_display_mode mode;
+	struct platform_device *audio_pdev;
+
+	const struct firmware *fw;	/* cdn dp firmware */
+	unsigned int fw_version;	/* cdn fw version */
+	u32 fw_wait;
+	bool fw_loaded;
+	void __iomem *regs;
+	struct regmap *grf;
+	unsigned int irq;
+	struct clk *core_clk;
+	struct clk *pclk;
+	struct clk *spdif_clk;
+	struct reset_control *spdif_rst;
+	struct audio_info audio_info;
+	struct video_info video_info;
+	struct extcon_dev *extcon[MAX_PHY];
+	struct phy *phy[MAX_PHY];
+	struct notifier_block event_nb;
+	struct delayed_work event_wq;
+	struct drm_dp_link link;
+
+	u8 cap_lanes[MAX_PHY];
+	u8 dpcd[DP_RECEIVER_CAP_SIZE];
+	u8 port_id;
+	u8 max_phy;
+	bool phy_status[MAX_PHY];
+	enum drm_connector_status hpd_status;
+
+	int dpms_mode;
+	bool sink_has_audio;
+};
+#endif  /* _CDN_DP_CORE_H */
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.c b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
new file mode 100644
index 0000000..afc8ae9
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
@@ -0,0 +1,898 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/reset.h>
+
+#include "cdn-dp-core.h"
+#include "cdn-dp-reg.h"
+
+#define CDN_DP_SPDIF_CLK		200000000
+#define FW_ALIVE_TIMEOUT_US		1000000
+#define MAILBOX_RETRY_US		1000
+#define MAILBOX_TIMEOUT_US		5000000
+#define LINK_TRAINING_RETRY_MS		20
+#define LINK_TRAINING_TIMEOUT_MS	500
+
+void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, u32 clk)
+{
+	writel(clk / 1000000, dp->regs + SW_CLK_H);
+}
+
+void cdn_dp_clock_reset(struct cdn_dp_device *dp)
+{
+	u32 val;
+
+	val = DPTX_FRMR_DATA_CLK_RSTN_EN |
+	      DPTX_FRMR_DATA_CLK_EN |
+	      DPTX_PHY_DATA_RSTN_EN |
+	      DPTX_PHY_DATA_CLK_EN |
+	      DPTX_PHY_CHAR_RSTN_EN |
+	      DPTX_PHY_CHAR_CLK_EN |
+	      SOURCE_AUX_SYS_CLK_RSTN_EN |
+	      SOURCE_AUX_SYS_CLK_EN |
+	      DPTX_SYS_CLK_RSTN_EN |
+	      DPTX_SYS_CLK_EN |
+	      CFG_DPTX_VIF_CLK_RSTN_EN |
+	      CFG_DPTX_VIF_CLK_EN;
+	writel(val, dp->regs + SOURCE_DPTX_CAR);
+
+	val = SOURCE_PHY_RSTN_EN | SOURCE_PHY_CLK_EN;
+	writel(val, dp->regs + SOURCE_PHY_CAR);
+
+	val = SOURCE_PKT_SYS_RSTN_EN |
+	      SOURCE_PKT_SYS_CLK_EN |
+	      SOURCE_PKT_DATA_RSTN_EN |
+	      SOURCE_PKT_DATA_CLK_EN;
+	writel(val, dp->regs + SOURCE_PKT_CAR);
+
+	val = SPDIF_CDR_CLK_RSTN_EN |
+	      SPDIF_CDR_CLK_EN |
+	      SOURCE_AIF_SYS_RSTN_EN |
+	      SOURCE_AIF_SYS_CLK_EN |
+	      SOURCE_AIF_CLK_RSTN_EN |
+	      SOURCE_AIF_CLK_EN;
+	writel(val, dp->regs + SOURCE_AIF_CAR);
+
+	val = SOURCE_CIPHER_SYSTEM_CLK_RSTN_EN |
+	      SOURCE_CIPHER_SYS_CLK_EN |
+	      SOURCE_CIPHER_CHAR_CLK_RSTN_EN |
+	      SOURCE_CIPHER_CHAR_CLK_EN;
+	writel(val, dp->regs + SOURCE_CIPHER_CAR);
+
+	val = SOURCE_CRYPTO_SYS_CLK_RSTN_EN |
+	      SOURCE_CRYPTO_SYS_CLK_EN;
+	writel(val, dp->regs + SOURCE_CRYPTO_CAR);
+
+	val = ~(MAILBOX_INT_MASK_BIT | PIF_INT_MASK_BIT) & ALL_INT_MASK;
+	writel(val, dp->regs + APB_INT_MASK);
+}
+
+static int cdn_dp_mailbox_read(struct cdn_dp_device *dp)
+{
+	int val, ret;
+
+	if (!dp->fw_loaded)
+		return -EPERM;
+
+	ret = readx_poll_timeout(readl, dp->regs + MAILBOX_EMPTY_ADDR,
+				 val, !val, MAILBOX_RETRY_US,
+				 MAILBOX_TIMEOUT_US);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to read mailbox, keep alive = %x\n",
+			readl(dp->regs + KEEP_ALIVE));
+		return ret;
+	}
+
+	return readl(dp->regs + MAILBOX0_RD_DATA) & 0xff;
+}
+
+static int cdp_dp_mailbox_write(struct cdn_dp_device *dp, u8 val)
+{
+	int ret, full;
+
+	if (!dp->fw_loaded)
+		return -EPERM;
+
+	ret = readx_poll_timeout(readl, dp->regs + MAILBOX_FULL_ADDR,
+				 full, !full, MAILBOX_RETRY_US,
+				 MAILBOX_TIMEOUT_US);
+	if (ret < 0) {
+		dev_err(dp->dev, "mailbox is full, keep alive = %x\n",
+			readl(dp->regs + KEEP_ALIVE));
+		return ret;
+	}
+
+	writel(val, dp->regs + MAILBOX0_WR_DATA);
+
+	return 0;
+}
+
+static int cdn_dp_mailbox_validate_receive(struct cdn_dp_device *dp,
+					   u8 module_id, u8 opcode,
+					   u8 req_size)
+{
+	u32 mbox_size, i;
+	u8 header[4];
+	int ret;
+
+	/* read the header of the message */
+	for (i = 0; i < 4; i++)  {
+		ret = cdn_dp_mailbox_read(dp);
+		if (ret < 0)
+			return ret;
+
+		header[i] = ret;
+	}
+
+	mbox_size = (header[2] << 8) | header[3];
+
+	if (opcode != header[0] || module_id != header[1] ||
+	    req_size != mbox_size) {
+		dump_stack();
+		dev_err(dp->dev, "mailbox validate failed");
+
+		/*
+		 * If the message in mailbox is not what we want, we need to
+		 * clear the mailbox by read.
+		 */
+		for (i = 0; i < mbox_size; i++)
+			if (cdn_dp_mailbox_read(dp) < 0)
+				break;
+
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int cdn_dp_mailbox_read_receive(struct cdn_dp_device *dp,
+				       u8 *buff, u8 buff_size)
+{
+	u32 i;
+	int ret;
+
+	for (i = 0; i < buff_size; i++) {
+		ret = cdn_dp_mailbox_read(dp);
+		if (ret < 0)
+			return ret;
+
+		buff[i] = ret;
+	}
+
+	return 0;
+}
+
+static int cdn_dp_mailbox_send(struct cdn_dp_device *dp, u8 module_id,
+			       u8 opcode, u16 size, u8 *message)
+{
+	u8 header[4];
+	int ret, i;
+
+	header[0] = opcode;
+	header[1] = module_id;
+	header[2] = (size >> 8) & 0xff;
+	header[3] = size & 0xff;
+
+	for (i = 0; i < 4; i++) {
+		ret = cdp_dp_mailbox_write(dp, header[i]);
+		if (ret)
+			return ret;
+	}
+
+	for (i = 0; i < size; i++) {
+		ret = cdp_dp_mailbox_write(dp, message[i]);
+		if (ret) {
+			dev_err(dp->dev, "mailbox send failed: %d", ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int cdn_dp_reg_write(struct cdn_dp_device *dp, u16 addr, u32 val)
+{
+	u8 msg[6];
+
+	msg[0] = (addr >> 8) & 0xff;
+	msg[1] = addr & 0xff;
+	msg[2] = (val >> 24) & 0xff;
+	msg[3] = (val >> 16) & 0xff;
+	msg[4] = (val >> 8) & 0xff;
+	msg[5] = val & 0xff;
+	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_REGISTER,
+				   sizeof(msg), msg);
+}
+
+int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr, u8 *data, u16 len)
+{
+	u8 msg[5], reg[5];
+	int ret;
+
+	msg[0] = (len >> 8) & 0xff;
+	msg[1] = len & 0xff;
+	msg[2] = (addr >> 16) & 0xff;
+	msg[3] = (addr >> 8) & 0xff;
+	msg[4] = addr & 0xff;
+	cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_READ_DPCD,
+			    sizeof(msg), msg);
+
+	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+					      DPTX_READ_DPCD,
+					      sizeof(reg) + len);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
+	if (ret)
+		return ret;
+
+	return cdn_dp_mailbox_read_receive(dp, data, len);
+}
+
+int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value)
+{
+	u8 msg[6], reg[5];
+	int ret;
+
+	msg[0] = 0;
+	msg[1] = 1;
+	msg[2] = (addr >> 16) & 0xff;
+	msg[3] = (addr >> 8) & 0xff;
+	msg[4] = addr & 0xff;
+	msg[5] = value;
+	cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_DPCD,
+			    sizeof(msg), msg);
+
+	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+					      DPTX_WRITE_DPCD, sizeof(reg));
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
+	if (ret)
+		return ret;
+
+	if (addr != (reg[2] << 16 | reg[3] << 8 | reg[4])) {
+		dev_err(dp->dev, "write dpcd failed\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
+			 u32 i_size, const u32 *d_mem, u32 d_size)
+{
+	int i, ret;
+	u32 reg;
+
+	/* reset ucpu before load firmware*/
+	writel(APB_IRAM_PATH | APB_DRAM_PATH | APB_XT_RESET,
+	       dp->regs + APB_CTRL);
+
+	for (i = 0; i < i_size; i += 4)
+		writel(*i_mem++, dp->regs + ADDR_IMEM + i);
+
+	for (i = 0; i < d_size; i += 4)
+		writel(*d_mem++, dp->regs + ADDR_DMEM + i);
+
+	/* un-reset ucpu */
+	writel(0, dp->regs + APB_CTRL);
+
+	/* check the keep alive register to make sure fw working */
+	ret = readx_poll_timeout(readl, dp->regs + KEEP_ALIVE,
+				 reg, reg, 2000, FW_ALIVE_TIMEOUT_US);
+	if (ret < 0) {
+		dev_err(dp->dev, "failed to loaded the FW reg = %x\n", reg);
+		return -EINVAL;
+	}
+
+	reg = readl(dp->regs + VER_L) & 0xff;
+	dp->fw_version = reg;
+	reg = readl(dp->regs + VER_H) & 0xff;
+	dp->fw_version |= reg << 8;
+	reg = readl(dp->regs + VER_LIB_L_ADDR) & 0xff;
+	dp->fw_version |= reg << 16;
+	reg = readl(dp->regs + VER_LIB_H_ADDR) & 0xff;
+	dp->fw_version |= reg << 24;
+
+	dev_dbg(dp->dev, "firmware version: %x\n", dp->fw_version);
+
+	return 0;
+}
+
+int cdn_dp_reg_write_bit(struct cdn_dp_device *dp, u16 addr, u8 start_bit,
+			 u8 bits_no, u32 val)
+{
+	u8 field[8];
+
+	field[0] = (addr >> 8) & 0xff;
+	field[1] = addr & 0xff;
+	field[2] = start_bit;
+	field[3] = bits_no;
+	field[4] = (val >> 24) & 0xff;
+	field[5] = (val >> 16) & 0xff;
+	field[6] = (val >> 8) & 0xff;
+	field[7] = val & 0xff;
+
+	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_FIELD,
+				   sizeof(field), field);
+}
+
+int cdn_dp_active(struct cdn_dp_device *dp, bool enable)
+{
+	u8 active = enable ? 1 : 0;
+	u8 resp;
+	int ret;
+
+	/* set firmware status, 1: active; 0: standby */
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_GENERAL,
+				  GENERAL_MAIN_CONTROL, sizeof(active),
+				  &active);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_GENERAL,
+					      GENERAL_MAIN_CONTROL,
+					      sizeof(resp));
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, &resp, sizeof(resp));
+	if (ret)
+		return ret;
+
+	return resp ? 0 : -EINVAL;
+}
+
+int cdn_dp_set_host_cap(struct cdn_dp_device *dp, u8 lanes)
+{
+	u8 msg[8];
+	int ret;
+
+	msg[0] = CDN_DP_MAX_LINK_RATE;
+	msg[1] = lanes;
+	msg[2] = VOLTAGE_LEVEL_2;
+	msg[3] = PRE_EMPHASIS_LEVEL_3;
+	msg[4] = PTS1 | PTS2 | PTS3 | PTS4;
+	msg[5] = FAST_LT_NOT_SUPPORT;
+	msg[6] = LANE_MAPPING_NORMAL;
+	msg[7] = ENHANCED;
+
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
+				  DPTX_SET_HOST_CAPABILITIES,
+				  sizeof(msg), msg);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_reg_write(dp, DP_AUX_SWAP_INVERSION_CONTROL,
+			       AUX_HOST_INVERT);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+int cdn_dp_event_config(struct cdn_dp_device *dp)
+{
+	u8 msg[5];
+
+	memset(msg, 0, sizeof(msg));
+
+	msg[0] = DPTX_EVENT_ENABLE_HPD | DPTX_EVENT_ENABLE_TRAINING;
+
+	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_ENABLE_EVENT,
+				   sizeof(msg), msg);
+}
+
+u32 cdn_dp_get_event(struct cdn_dp_device *dp)
+{
+	return readl(dp->regs + SW_EVENTS0);
+}
+
+int cdn_dp_get_hpd_status(struct cdn_dp_device *dp)
+{
+	u8 status;
+	int ret;
+
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_HPD_STATE,
+				  0, NULL);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+					      DPTX_HPD_STATE, sizeof(status));
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, &status, sizeof(status));
+	if (ret)
+		return ret;
+
+	return status;
+}
+
+int cdn_dp_get_edid_block(void *data, u8 *edid,
+			  unsigned int block, size_t length)
+{
+	struct cdn_dp_device *dp = data;
+	u8 msg[2], reg[2];
+	int ret;
+
+	msg[0] = block / 2;
+	msg[1] = block % 2;
+
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_GET_EDID,
+				  sizeof(msg), msg);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+					      DPTX_GET_EDID,
+					      sizeof(reg) + length);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, edid, length);
+	if (ret)
+		return ret;
+
+	if (reg[0] != length || reg[1] != block / 2) {
+		dev_err(dp->dev, "edid block size err\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int cdn_dp_training_start(struct cdn_dp_device *dp)
+{
+	u8 msg, event[2];
+	unsigned long timeout;
+	int ret;
+
+	msg = LINK_TRAINING_RUN;
+
+	/* start training */
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_TRAINING_CONTROL,
+				  sizeof(msg), &msg);
+	if (ret)
+		return ret;
+
+	timeout = jiffies + msecs_to_jiffies(LINK_TRAINING_TIMEOUT_MS);
+	while (time_before(jiffies, timeout)) {
+		msleep(LINK_TRAINING_RETRY_MS);
+		ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
+					  DPTX_READ_EVENT, 0, NULL);
+		if (ret)
+			return ret;
+
+		ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+						      DPTX_READ_EVENT,
+						      sizeof(event));
+		if (ret)
+			return ret;
+
+		ret = cdn_dp_mailbox_read_receive(dp, event, sizeof(event));
+		if (ret)
+			return ret;
+
+		if (event[1] & EQ_PHASE_FINISHED)
+			return 0;
+	}
+
+	return -ETIMEDOUT;
+}
+
+int cdn_dp_get_training_status(struct cdn_dp_device *dp)
+{
+	u8 status[10];
+	int ret;
+
+	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_READ_LINK_STAT,
+				  0, NULL);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+					      DPTX_READ_LINK_STAT,
+					      sizeof(status));
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_mailbox_read_receive(dp, status, sizeof(status));
+	if (ret)
+		return ret;
+
+	dp->link.rate = status[0];
+	dp->link.num_lanes = status[1];
+
+	return 0;
+}
+
+int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active)
+{
+	u8 msg;
+
+	msg = !!active;
+
+	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_SET_VIDEO,
+				   sizeof(msg), &msg);
+}
+
+static int cdn_dp_get_msa_misc(struct video_info *video,
+			       struct drm_display_mode *mode)
+{
+	u8 val[2];
+	u32 msa_misc;
+
+	switch (video->color_fmt) {
+	case PXL_RGB:
+	case Y_ONLY:
+		val[0] = 0;
+		break;
+	/* set YUV default color space conversion to BT601 */
+	case YCBCR_4_4_4:
+		val[0] = 6 + BT_601 * 8;
+		break;
+	case YCBCR_4_2_2:
+		val[0] = 5 + BT_601 * 8;
+		break;
+	case YCBCR_4_2_0:
+		val[0] = 5;
+		break;
+	};
+
+	switch (video->color_depth) {
+	case 6:
+		val[1] = 0;
+		break;
+	case 8:
+		val[1] = 1;
+		break;
+	case 10:
+		val[1] = 2;
+		break;
+	case 12:
+		val[1] = 3;
+		break;
+	case 16:
+		val[1] = 4;
+		break;
+	};
+
+	msa_misc = 2 * val[0] + 32 * val[1] +
+		   ((video->color_fmt == Y_ONLY) ? (1 << 14) : 0);
+
+	return msa_misc;
+}
+
+int cdn_dp_config_video(struct cdn_dp_device *dp)
+{
+	struct video_info *video = &dp->video_info;
+	struct drm_display_mode *mode = &dp->mode;
+	u64 symbol;
+	u32 val, link_rate;
+	u8 bit_per_pix;
+	u8 tu_size_reg = TU_SIZE;
+	int ret;
+
+	bit_per_pix = (video->color_fmt == YCBCR_4_2_2) ?
+		      (video->color_depth * 2) : (video->color_depth * 3);
+
+	link_rate = drm_dp_bw_code_to_link_rate(dp->link.rate);
+
+	val = VIF_BYPASS_INTERLACE;
+	ret = cdn_dp_reg_write(dp, BND_HSYNC2VSYNC, val);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_reg_write(dp, HSYNC2VSYNC_POL_CTRL, 0);
+	if (ret)
+		return ret;
+
+	/* get a best tu_size and symbol */
+	do {
+		tu_size_reg += 2;
+		symbol = tu_size_reg * mode->clock * bit_per_pix;
+		symbol /= dp->link.num_lanes * link_rate * 8;
+	} while ((symbol == 1) || (tu_size_reg - symbol < 4));
+
+	val = symbol + (tu_size_reg << 8);
+	ret = cdn_dp_reg_write(dp, DP_FRAMER_TU, val);
+	if (ret)
+		return ret;
+
+	switch (video->color_depth) {
+	case 6:
+		val = BCS_6;
+		break;
+	case 8:
+		val = BCS_8;
+		break;
+	case 10:
+		val = BCS_10;
+		break;
+	case 12:
+		val = BCS_12;
+		break;
+	case 16:
+		val = BCS_16;
+		break;
+	};
+
+	val += video->color_fmt << 8;
+	ret = cdn_dp_reg_write(dp, DP_FRAMER_PXL_REPR, val);
+	if (ret)
+		return ret;
+
+	val = video->h_sync_polarity ? DP_FRAMER_SP_HSP : 0;
+	val |= video->v_sync_polarity ? DP_FRAMER_SP_VSP : 0;
+	ret = cdn_dp_reg_write(dp, DP_FRAMER_SP, val);
+	if (ret)
+		return ret;
+
+	val = (mode->hsync_start - mode->hdisplay) << 16;
+	val |= mode->htotal - mode->hsync_end;
+	ret = cdn_dp_reg_write(dp, DP_FRONT_BACK_PORCH, val);
+	if (ret)
+		return ret;
+
+	val = mode->hdisplay * bit_per_pix / 8;
+	ret = cdn_dp_reg_write(dp, DP_BYTE_COUNT, val);
+	if (ret)
+		return ret;
+
+	val = mode->htotal | ((mode->htotal - mode->hsync_start) << 16);
+	ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_0, val);
+	if (ret)
+		return ret;
+
+	val = mode->hsync_end - mode->hsync_start;
+	val |= (mode->hdisplay << 16) | (video->h_sync_polarity << 15);
+	ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_1, val);
+	if (ret)
+		return ret;
+
+	val = mode->vtotal;
+	val |= ((mode->vtotal - mode->vsync_start) << 16);
+	ret = cdn_dp_reg_write(dp, MSA_VERTICAL_0, val);
+	if (ret)
+		return ret;
+
+	val = mode->vsync_end - mode->vsync_start;
+	val |= mode->vdisplay << 16 | (video->v_sync_polarity << 15);
+	ret = cdn_dp_reg_write(dp, MSA_VERTICAL_1, val);
+	if (ret)
+		return ret;
+
+	val = cdn_dp_get_msa_misc(video, mode);
+	ret = cdn_dp_reg_write(dp, MSA_MISC, val);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_reg_write(dp, STREAM_CONFIG, 1);
+	if (ret)
+		return ret;
+
+	val = mode->hsync_end - mode->hsync_start;
+	val |= (mode->hdisplay << 16);
+	ret = cdn_dp_reg_write(dp, DP_HORIZONTAL, val);
+	if (ret)
+		return ret;
+
+	val = mode->vtotal;
+	val -= (mode->vtotal - mode->vdisplay);
+	val |= (mode->vtotal - mode->vsync_start) << 16;
+	ret = cdn_dp_reg_write(dp, DP_VERTICAL_0, val);
+	if (ret)
+		return ret;
+
+	val = mode->vtotal;
+	ret = cdn_dp_reg_write(dp, DP_VERTICAL_1, val);
+	if (ret)
+		return ret;
+
+	val =  0;
+	return cdn_dp_reg_write_bit(dp, DP_VB_ID, 2, 1, val);
+}
+
+int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio)
+{
+	u32 val;
+	int ret;
+
+	ret = cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, 0);
+	if (ret)
+		return ret;
+
+	val = SPDIF_AVG_SEL | SPDIF_JITTER_BYPASS;
+	val |= SPDIF_FIFO_MID_RANGE(0xe0);
+	val |= SPDIF_JITTER_THRSH(0xe0);
+	val |= SPDIF_JITTER_AVG_WIN(7);
+	writel(val, dp->regs + SPDIF_CTRL_ADDR);
+
+	/* clearn the audio config and reset */
+	writel(0, dp->regs + AUDIO_SRC_CNTL);
+	writel(0, dp->regs + AUDIO_SRC_CNFG);
+	writel(AUDIO_SW_RST, dp->regs + AUDIO_SRC_CNTL);
+	writel(0, dp->regs + AUDIO_SRC_CNTL);
+
+	/* reset smpl2pckt component  */
+	writel(0, dp->regs + SMPL2PKT_CNTL);
+	writel(AUDIO_SW_RST, dp->regs + SMPL2PKT_CNTL);
+	writel(0, dp->regs + SMPL2PKT_CNTL);
+
+	/* reset FIFO */
+	writel(AUDIO_SW_RST, dp->regs + FIFO_CNTL);
+	writel(0, dp->regs + FIFO_CNTL);
+
+	if (audio->format == AFMT_SPDIF)
+		clk_disable_unprepare(dp->spdif_clk);
+
+	return 0;
+}
+
+int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable)
+{
+	return cdn_dp_reg_write_bit(dp, DP_VB_ID, 4, 1, enable);
+}
+
+static void cdn_dp_audio_config_i2s(struct cdn_dp_device *dp,
+				    struct audio_info *audio)
+{
+	int sub_pckt_num = 1, i2s_port_en_val = 0xf, i;
+	u32 val;
+
+	if (audio->channels == 2) {
+		if (dp->link.num_lanes == 1)
+			sub_pckt_num = 2;
+		else
+			sub_pckt_num = 4;
+
+		i2s_port_en_val = 1;
+	} else if (audio->channels == 4) {
+		i2s_port_en_val = 3;
+	}
+
+	writel(0x0, dp->regs + SPDIF_CTRL_ADDR);
+
+	writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
+
+	val = MAX_NUM_CH(audio->channels);
+	val |= NUM_OF_I2S_PORTS(audio->channels);
+	val |= AUDIO_TYPE_LPCM;
+	val |= CFG_SUB_PCKT_NUM(sub_pckt_num);
+	writel(val, dp->regs + SMPL2PKT_CNFG);
+
+	if (audio->sample_width == 16)
+		val = 0;
+	else if (audio->sample_width == 24)
+		val = 1 << 9;
+	else
+		val = 2 << 9;
+
+	val |= AUDIO_CH_NUM(audio->channels);
+	val |= I2S_DEC_PORT_EN(i2s_port_en_val);
+	val |= TRANS_SMPL_WIDTH_32;
+	writel(val, dp->regs + AUDIO_SRC_CNFG);
+
+	for (i = 0; i < (audio->channels + 1) / 2; i++) {
+		if (audio->sample_width == 16)
+			val = (0x08 << 8) | (0x08 << 20);
+		else if (audio->sample_width == 24)
+			val = (0x0b << 8) | (0x0b << 20);
+
+		val |= ((2 * i) << 4) | ((2 * i + 1) << 16);
+		writel(val, dp->regs + STTS_BIT_CH(i));
+	}
+
+	switch (audio->sample_rate) {
+	case 32000:
+		val = SAMPLING_FREQ(3) |
+		      ORIGINAL_SAMP_FREQ(0xc);
+		break;
+	case 44100:
+		val = SAMPLING_FREQ(0) |
+		      ORIGINAL_SAMP_FREQ(0xf);
+		break;
+	case 48000:
+		val = SAMPLING_FREQ(2) |
+		      ORIGINAL_SAMP_FREQ(0xd);
+		break;
+	case 88200:
+		val = SAMPLING_FREQ(8) |
+		      ORIGINAL_SAMP_FREQ(0x7);
+		break;
+	case 96000:
+		val = SAMPLING_FREQ(0xa) |
+		      ORIGINAL_SAMP_FREQ(5);
+		break;
+	case 176400:
+		val = SAMPLING_FREQ(0xc) |
+		      ORIGINAL_SAMP_FREQ(3);
+		break;
+	case 192000:
+		val = SAMPLING_FREQ(0xe) |
+		      ORIGINAL_SAMP_FREQ(1);
+		break;
+	}
+	val |= 4;
+	writel(val, dp->regs + COM_CH_STTS_BITS);
+
+	writel(SMPL2PKT_EN, dp->regs + SMPL2PKT_CNTL);
+	writel(I2S_DEC_START, dp->regs + AUDIO_SRC_CNTL);
+}
+
+static void cdn_dp_audio_config_spdif(struct cdn_dp_device *dp)
+{
+	u32 val;
+
+	val = SPDIF_AVG_SEL | SPDIF_JITTER_BYPASS;
+	val |= SPDIF_FIFO_MID_RANGE(0xe0);
+	val |= SPDIF_JITTER_THRSH(0xe0);
+	val |= SPDIF_JITTER_AVG_WIN(7);
+	writel(val, dp->regs + SPDIF_CTRL_ADDR);
+
+	writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
+
+	val = MAX_NUM_CH(2) | AUDIO_TYPE_LPCM | CFG_SUB_PCKT_NUM(4);
+	writel(val, dp->regs + SMPL2PKT_CNFG);
+	writel(SMPL2PKT_EN, dp->regs + SMPL2PKT_CNTL);
+
+	val = SPDIF_ENABLE | SPDIF_AVG_SEL | SPDIF_JITTER_BYPASS;
+	val |= SPDIF_FIFO_MID_RANGE(0xe0);
+	val |= SPDIF_JITTER_THRSH(0xe0);
+	val |= SPDIF_JITTER_AVG_WIN(7);
+	writel(val, dp->regs + SPDIF_CTRL_ADDR);
+
+	clk_prepare_enable(dp->spdif_clk);
+	clk_set_rate(dp->spdif_clk, CDN_DP_SPDIF_CLK);
+}
+
+int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info *audio)
+{
+	int ret;
+
+	/* reset the spdif clk before config */
+	if (audio->format == AFMT_SPDIF) {
+		reset_control_assert(dp->spdif_rst);
+		reset_control_deassert(dp->spdif_rst);
+	}
+
+	ret = cdn_dp_reg_write(dp, CM_LANE_CTRL, LANE_REF_CYC);
+	if (ret)
+		return ret;
+
+	ret = cdn_dp_reg_write(dp, CM_CTRL, 0);
+	if (ret)
+		return ret;
+
+	if (audio->format == AFMT_I2S)
+		cdn_dp_audio_config_i2s(dp, audio);
+	else
+		cdn_dp_audio_config_spdif(dp);
+
+	return cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, AUDIO_PACK_EN);
+}
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.h b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
new file mode 100644
index 0000000..1affeed
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _CDN_DP_REG_H
+#define _CDN_DP_REG_H
+
+#include <linux/bitops.h>
+
+#define ADDR_IMEM		0x10000
+#define ADDR_DMEM		0x20000
+
+/* APB CFG addr */
+#define APB_CTRL			0
+#define XT_INT_CTRL			0x04
+#define MAILBOX_FULL_ADDR		0x08
+#define MAILBOX_EMPTY_ADDR		0x0c
+#define MAILBOX0_WR_DATA		0x10
+#define MAILBOX0_RD_DATA		0x14
+#define KEEP_ALIVE			0x18
+#define VER_L				0x1c
+#define VER_H				0x20
+#define VER_LIB_L_ADDR			0x24
+#define VER_LIB_H_ADDR			0x28
+#define SW_DEBUG_L			0x2c
+#define SW_DEBUG_H			0x30
+#define MAILBOX_INT_MASK		0x34
+#define MAILBOX_INT_STATUS		0x38
+#define SW_CLK_L			0x3c
+#define SW_CLK_H			0x40
+#define SW_EVENTS0			0x44
+#define SW_EVENTS1			0x48
+#define SW_EVENTS2			0x4c
+#define SW_EVENTS3			0x50
+#define XT_OCD_CTRL			0x60
+#define APB_INT_MASK			0x6c
+#define APB_STATUS_MASK			0x70
+
+/* audio decoder addr */
+#define AUDIO_SRC_CNTL			0x30000
+#define AUDIO_SRC_CNFG			0x30004
+#define COM_CH_STTS_BITS		0x30008
+#define STTS_BIT_CH(x)			(0x3000c + ((x) << 2))
+#define SPDIF_CTRL_ADDR			0x3004c
+#define SPDIF_CH1_CS_3100_ADDR		0x30050
+#define SPDIF_CH1_CS_6332_ADDR		0x30054
+#define SPDIF_CH1_CS_9564_ADDR		0x30058
+#define SPDIF_CH1_CS_12796_ADDR		0x3005c
+#define SPDIF_CH1_CS_159128_ADDR	0x30060
+#define SPDIF_CH1_CS_191160_ADDR	0x30064
+#define SPDIF_CH2_CS_3100_ADDR		0x30068
+#define SPDIF_CH2_CS_6332_ADDR		0x3006c
+#define SPDIF_CH2_CS_9564_ADDR		0x30070
+#define SPDIF_CH2_CS_12796_ADDR		0x30074
+#define SPDIF_CH2_CS_159128_ADDR	0x30078
+#define SPDIF_CH2_CS_191160_ADDR	0x3007c
+#define SMPL2PKT_CNTL			0x30080
+#define SMPL2PKT_CNFG			0x30084
+#define FIFO_CNTL			0x30088
+#define FIFO_STTS			0x3008c
+
+/* source pif addr */
+#define SOURCE_PIF_WR_ADDR		0x30800
+#define SOURCE_PIF_WR_REQ		0x30804
+#define SOURCE_PIF_RD_ADDR		0x30808
+#define SOURCE_PIF_RD_REQ		0x3080c
+#define SOURCE_PIF_DATA_WR		0x30810
+#define SOURCE_PIF_DATA_RD		0x30814
+#define SOURCE_PIF_FIFO1_FLUSH		0x30818
+#define SOURCE_PIF_FIFO2_FLUSH		0x3081c
+#define SOURCE_PIF_STATUS		0x30820
+#define SOURCE_PIF_INTERRUPT_SOURCE	0x30824
+#define SOURCE_PIF_INTERRUPT_MASK	0x30828
+#define SOURCE_PIF_PKT_ALLOC_REG	0x3082c
+#define SOURCE_PIF_PKT_ALLOC_WR_EN	0x30830
+#define SOURCE_PIF_SW_RESET		0x30834
+
+/* bellow registers need access by mailbox */
+/* source car addr */
+#define SOURCE_HDTX_CAR			0x0900
+#define SOURCE_DPTX_CAR			0x0904
+#define SOURCE_PHY_CAR			0x0908
+#define SOURCE_CEC_CAR			0x090c
+#define SOURCE_CBUS_CAR			0x0910
+#define SOURCE_PKT_CAR			0x0918
+#define SOURCE_AIF_CAR			0x091c
+#define SOURCE_CIPHER_CAR		0x0920
+#define SOURCE_CRYPTO_CAR		0x0924
+
+/* clock meters addr */
+#define CM_CTRL				0x0a00
+#define CM_I2S_CTRL			0x0a04
+#define CM_SPDIF_CTRL			0x0a08
+#define CM_VID_CTRL			0x0a0c
+#define CM_LANE_CTRL			0x0a10
+#define I2S_NM_STABLE			0x0a14
+#define I2S_NCTS_STABLE			0x0a18
+#define SPDIF_NM_STABLE			0x0a1c
+#define SPDIF_NCTS_STABLE		0x0a20
+#define NMVID_MEAS_STABLE		0x0a24
+#define I2S_MEAS			0x0a40
+#define SPDIF_MEAS			0x0a80
+#define NMVID_MEAS			0x0ac0
+
+/* source vif addr */
+#define BND_HSYNC2VSYNC			0x0b00
+#define HSYNC2VSYNC_F1_L1		0x0b04
+#define HSYNC2VSYNC_F2_L1		0x0b08
+#define HSYNC2VSYNC_STATUS		0x0b0c
+#define HSYNC2VSYNC_POL_CTRL		0x0b10
+
+/* dptx phy addr */
+#define DP_TX_PHY_CONFIG_REG		0x2000
+#define DP_TX_PHY_STATUS_REG		0x2004
+#define DP_TX_PHY_SW_RESET		0x2008
+#define DP_TX_PHY_SCRAMBLER_SEED	0x200c
+#define DP_TX_PHY_TRAINING_01_04	0x2010
+#define DP_TX_PHY_TRAINING_05_08	0x2014
+#define DP_TX_PHY_TRAINING_09_10	0x2018
+#define TEST_COR			0x23fc
+
+/* dptx hpd addr */
+#define HPD_IRQ_DET_MIN_TIMER		0x2100
+#define HPD_IRQ_DET_MAX_TIMER		0x2104
+#define HPD_UNPLGED_DET_MIN_TIMER	0x2108
+#define HPD_STABLE_TIMER		0x210c
+#define HPD_FILTER_TIMER		0x2110
+#define HPD_EVENT_MASK			0x211c
+#define HPD_EVENT_DET			0x2120
+
+/* dpyx framer addr */
+#define DP_FRAMER_GLOBAL_CONFIG		0x2200
+#define DP_SW_RESET			0x2204
+#define DP_FRAMER_TU			0x2208
+#define DP_FRAMER_PXL_REPR		0x220c
+#define DP_FRAMER_SP			0x2210
+#define AUDIO_PACK_CONTROL		0x2214
+#define DP_VC_TABLE(x)			(0x2218 + ((x) << 2))
+#define DP_VB_ID			0x2258
+#define DP_MTPH_LVP_CONTROL		0x225c
+#define DP_MTPH_SYMBOL_VALUES		0x2260
+#define DP_MTPH_ECF_CONTROL		0x2264
+#define DP_MTPH_ACT_CONTROL		0x2268
+#define DP_MTPH_STATUS			0x226c
+#define DP_INTERRUPT_SOURCE		0x2270
+#define DP_INTERRUPT_MASK		0x2274
+#define DP_FRONT_BACK_PORCH		0x2278
+#define DP_BYTE_COUNT			0x227c
+
+/* dptx stream addr */
+#define MSA_HORIZONTAL_0		0x2280
+#define MSA_HORIZONTAL_1		0x2284
+#define MSA_VERTICAL_0			0x2288
+#define MSA_VERTICAL_1			0x228c
+#define MSA_MISC			0x2290
+#define STREAM_CONFIG			0x2294
+#define AUDIO_PACK_STATUS		0x2298
+#define VIF_STATUS			0x229c
+#define PCK_STUFF_STATUS_0		0x22a0
+#define PCK_STUFF_STATUS_1		0x22a4
+#define INFO_PACK_STATUS		0x22a8
+#define RATE_GOVERNOR_STATUS		0x22ac
+#define DP_HORIZONTAL			0x22b0
+#define DP_VERTICAL_0			0x22b4
+#define DP_VERTICAL_1			0x22b8
+#define DP_BLOCK_SDP			0x22bc
+
+/* dptx glbl addr */
+#define DPTX_LANE_EN			0x2300
+#define DPTX_ENHNCD			0x2304
+#define DPTX_INT_MASK			0x2308
+#define DPTX_INT_STATUS			0x230c
+
+/* dp aux addr */
+#define DP_AUX_HOST_CONTROL		0x2800
+#define DP_AUX_INTERRUPT_SOURCE		0x2804
+#define DP_AUX_INTERRUPT_MASK		0x2808
+#define DP_AUX_SWAP_INVERSION_CONTROL	0x280c
+#define DP_AUX_SEND_NACK_TRANSACTION	0x2810
+#define DP_AUX_CLEAR_RX			0x2814
+#define DP_AUX_CLEAR_TX			0x2818
+#define DP_AUX_TIMER_STOP		0x281c
+#define DP_AUX_TIMER_CLEAR		0x2820
+#define DP_AUX_RESET_SW			0x2824
+#define DP_AUX_DIVIDE_2M		0x2828
+#define DP_AUX_TX_PREACHARGE_LENGTH	0x282c
+#define DP_AUX_FREQUENCY_1M_MAX		0x2830
+#define DP_AUX_FREQUENCY_1M_MIN		0x2834
+#define DP_AUX_RX_PRE_MIN		0x2838
+#define DP_AUX_RX_PRE_MAX		0x283c
+#define DP_AUX_TIMER_PRESET		0x2840
+#define DP_AUX_NACK_FORMAT		0x2844
+#define DP_AUX_TX_DATA			0x2848
+#define DP_AUX_RX_DATA			0x284c
+#define DP_AUX_TX_STATUS		0x2850
+#define DP_AUX_RX_STATUS		0x2854
+#define DP_AUX_RX_CYCLE_COUNTER		0x2858
+#define DP_AUX_MAIN_STATES		0x285c
+#define DP_AUX_MAIN_TIMER		0x2860
+#define DP_AUX_AFE_OUT			0x2864
+
+/* crypto addr */
+#define CRYPTO_HDCP_REVISION		0x5800
+#define HDCP_CRYPTO_CONFIG		0x5804
+#define CRYPTO_INTERRUPT_SOURCE		0x5808
+#define CRYPTO_INTERRUPT_MASK		0x580c
+#define CRYPTO22_CONFIG			0x5818
+#define CRYPTO22_STATUS			0x581c
+#define SHA_256_DATA_IN			0x583c
+#define SHA_256_DATA_OUT_(x)		(0x5850 + ((x) << 2))
+#define AES_32_KEY_(x)			(0x5870 + ((x) << 2))
+#define AES_32_DATA_IN			0x5880
+#define AES_32_DATA_OUT_(x)		(0x5884 + ((x) << 2))
+#define CRYPTO14_CONFIG			0x58a0
+#define CRYPTO14_STATUS			0x58a4
+#define CRYPTO14_PRNM_OUT		0x58a8
+#define CRYPTO14_KM_0			0x58ac
+#define CRYPTO14_KM_1			0x58b0
+#define CRYPTO14_AN_0			0x58b4
+#define CRYPTO14_AN_1			0x58b8
+#define CRYPTO14_YOUR_KSV_0		0x58bc
+#define CRYPTO14_YOUR_KSV_1		0x58c0
+#define CRYPTO14_MI_0			0x58c4
+#define CRYPTO14_MI_1			0x58c8
+#define CRYPTO14_TI_0			0x58cc
+#define CRYPTO14_KI_0			0x58d0
+#define CRYPTO14_KI_1			0x58d4
+#define CRYPTO14_BLOCKS_NUM		0x58d8
+#define CRYPTO14_KEY_MEM_DATA_0		0x58dc
+#define CRYPTO14_KEY_MEM_DATA_1		0x58e0
+#define CRYPTO14_SHA1_MSG_DATA		0x58e4
+#define CRYPTO14_SHA1_V_VALUE_(x)	(0x58e8 + ((x) << 2))
+#define TRNG_CTRL			0x58fc
+#define TRNG_DATA_RDY			0x5900
+#define TRNG_DATA			0x5904
+
+/* cipher addr */
+#define HDCP_REVISION			0x60000
+#define INTERRUPT_SOURCE		0x60004
+#define INTERRUPT_MASK			0x60008
+#define HDCP_CIPHER_CONFIG		0x6000c
+#define AES_128_KEY_0			0x60010
+#define AES_128_KEY_1			0x60014
+#define AES_128_KEY_2			0x60018
+#define AES_128_KEY_3			0x6001c
+#define AES_128_RANDOM_0		0x60020
+#define AES_128_RANDOM_1		0x60024
+#define CIPHER14_KM_0			0x60028
+#define CIPHER14_KM_1			0x6002c
+#define CIPHER14_STATUS			0x60030
+#define CIPHER14_RI_PJ_STATUS		0x60034
+#define CIPHER_MODE			0x60038
+#define CIPHER14_AN_0			0x6003c
+#define CIPHER14_AN_1			0x60040
+#define CIPHER22_AUTH			0x60044
+#define CIPHER14_R0_DP_STATUS		0x60048
+#define CIPHER14_BOOTSTRAP		0x6004c
+
+#define DPTX_FRMR_DATA_CLK_RSTN_EN	BIT(11)
+#define DPTX_FRMR_DATA_CLK_EN		BIT(10)
+#define DPTX_PHY_DATA_RSTN_EN		BIT(9)
+#define DPTX_PHY_DATA_CLK_EN		BIT(8)
+#define DPTX_PHY_CHAR_RSTN_EN		BIT(7)
+#define DPTX_PHY_CHAR_CLK_EN		BIT(6)
+#define SOURCE_AUX_SYS_CLK_RSTN_EN	BIT(5)
+#define SOURCE_AUX_SYS_CLK_EN		BIT(4)
+#define DPTX_SYS_CLK_RSTN_EN		BIT(3)
+#define DPTX_SYS_CLK_EN			BIT(2)
+#define CFG_DPTX_VIF_CLK_RSTN_EN	BIT(1)
+#define CFG_DPTX_VIF_CLK_EN		BIT(0)
+
+#define SOURCE_PHY_RSTN_EN		BIT(1)
+#define SOURCE_PHY_CLK_EN		BIT(0)
+
+#define SOURCE_PKT_SYS_RSTN_EN		BIT(3)
+#define SOURCE_PKT_SYS_CLK_EN		BIT(2)
+#define SOURCE_PKT_DATA_RSTN_EN		BIT(1)
+#define SOURCE_PKT_DATA_CLK_EN		BIT(0)
+
+#define SPDIF_CDR_CLK_RSTN_EN		BIT(5)
+#define SPDIF_CDR_CLK_EN		BIT(4)
+#define SOURCE_AIF_SYS_RSTN_EN		BIT(3)
+#define SOURCE_AIF_SYS_CLK_EN		BIT(2)
+#define SOURCE_AIF_CLK_RSTN_EN		BIT(1)
+#define SOURCE_AIF_CLK_EN		BIT(0)
+
+#define SOURCE_CIPHER_SYSTEM_CLK_RSTN_EN	BIT(3)
+#define SOURCE_CIPHER_SYS_CLK_EN		BIT(2)
+#define SOURCE_CIPHER_CHAR_CLK_RSTN_EN		BIT(1)
+#define SOURCE_CIPHER_CHAR_CLK_EN		BIT(0)
+
+#define SOURCE_CRYPTO_SYS_CLK_RSTN_EN	BIT(1)
+#define SOURCE_CRYPTO_SYS_CLK_EN	BIT(0)
+
+#define APB_IRAM_PATH			BIT(2)
+#define APB_DRAM_PATH			BIT(1)
+#define APB_XT_RESET			BIT(0)
+
+#define MAILBOX_INT_MASK_BIT		BIT(1)
+#define PIF_INT_MASK_BIT		BIT(0)
+#define ALL_INT_MASK			3
+
+/* mailbox */
+#define MB_OPCODE_ID			0
+#define MB_MODULE_ID			1
+#define MB_SIZE_MSB_ID			2
+#define MB_SIZE_LSB_ID			3
+#define MB_DATA_ID			4
+
+#define MB_MODULE_ID_DP_TX		0x01
+#define MB_MODULE_ID_HDCP_TX		0x07
+#define MB_MODULE_ID_HDCP_RX		0x08
+#define MB_MODULE_ID_HDCP_GENERAL	0x09
+#define MB_MODULE_ID_GENERAL		0x0a
+
+/* general opcode */
+#define GENERAL_MAIN_CONTROL            0x01
+#define GENERAL_TEST_ECHO               0x02
+#define GENERAL_BUS_SETTINGS            0x03
+#define GENERAL_TEST_ACCESS             0x04
+
+#define DPTX_SET_POWER_MNG			0x00
+#define DPTX_SET_HOST_CAPABILITIES		0x01
+#define DPTX_GET_EDID				0x02
+#define DPTX_READ_DPCD				0x03
+#define DPTX_WRITE_DPCD				0x04
+#define DPTX_ENABLE_EVENT			0x05
+#define DPTX_WRITE_REGISTER			0x06
+#define DPTX_READ_REGISTER			0x07
+#define DPTX_WRITE_FIELD			0x08
+#define DPTX_TRAINING_CONTROL			0x09
+#define DPTX_READ_EVENT				0x0a
+#define DPTX_READ_LINK_STAT			0x0b
+#define DPTX_SET_VIDEO				0x0c
+#define DPTX_SET_AUDIO				0x0d
+#define DPTX_GET_LAST_AUX_STAUS			0x0e
+#define DPTX_SET_LINK_BREAK_POINT		0x0f
+#define DPTX_FORCE_LANES			0x10
+#define DPTX_HPD_STATE				0x11
+
+#define DPTX_EVENT_ENABLE_HPD			BIT(0)
+#define DPTX_EVENT_ENABLE_TRAINING		BIT(1)
+
+#define LINK_TRAINING_NOT_ACTIVE		0
+#define LINK_TRAINING_RUN			1
+#define LINK_TRAINING_RESTART			2
+
+#define CONTROL_VIDEO_IDLE			0
+#define CONTROL_VIDEO_VALID			1
+
+#define VIF_BYPASS_INTERLACE			BIT(13)
+#define INTERLACE_FMT_DET			BIT(12)
+#define INTERLACE_DTCT_WIN			0x20
+
+#define DP_FRAMER_SP_INTERLACE_EN		BIT(2)
+#define DP_FRAMER_SP_HSP			BIT(1)
+#define DP_FRAMER_SP_VSP			BIT(0)
+
+/* capability */
+#define AUX_HOST_INVERT				3
+#define	FAST_LT_SUPPORT				1
+#define FAST_LT_NOT_SUPPORT			0
+#define LANE_MAPPING_NORMAL			0xe4
+#define LANE_MAPPING_FLIPPED			0x1b
+#define ENHANCED				1
+
+#define	FULL_LT_STARTED				BIT(0)
+#define FASE_LT_STARTED				BIT(1)
+#define CLK_RECOVERY_FINISHED			BIT(2)
+#define EQ_PHASE_FINISHED			BIT(3)
+#define FASE_LT_START_FINISHED			BIT(4)
+#define CLK_RECOVERY_FAILED			BIT(5)
+#define EQ_PHASE_FAILED				BIT(6)
+#define FASE_LT_FAILED				BIT(7)
+
+#define DPTX_HPD_EVENT				BIT(0)
+#define DPTX_TRAINING_EVENT			BIT(1)
+#define HDCP_TX_STATUS_EVENT			BIT(4)
+#define HDCP2_TX_IS_KM_STORED_EVENT		BIT(5)
+#define HDCP2_TX_STORE_KM_EVENT			BIT(6)
+#define HDCP_TX_IS_RECEIVER_ID_VALID_EVENT	BIT(7)
+
+#define TU_SIZE					30
+#define CDN_DP_MAX_LINK_RATE			DP_LINK_BW_5_4
+
+/* audio */
+#define AUDIO_PACK_EN				BIT(8)
+#define SAMPLING_FREQ(x)			(((x) & 0xf) << 16)
+#define ORIGINAL_SAMP_FREQ(x)			(((x) & 0xf) << 24)
+#define SYNC_WR_TO_CH_ZERO			BIT(1)
+#define I2S_DEC_START				BIT(1)
+#define AUDIO_SW_RST				BIT(0)
+#define SMPL2PKT_EN				BIT(1)
+#define MAX_NUM_CH(x)				(((x) & 0x1f) - 1)
+#define NUM_OF_I2S_PORTS(x)			((((x) / 2 - 1) & 0x3) << 5)
+#define AUDIO_TYPE_LPCM				(2 << 7)
+#define CFG_SUB_PCKT_NUM(x)			((((x) - 1) & 0x7) << 11)
+#define AUDIO_CH_NUM(x)				((((x) - 1) & 0x1f) << 2)
+#define TRANS_SMPL_WIDTH_16			0
+#define TRANS_SMPL_WIDTH_24			BIT(11)
+#define TRANS_SMPL_WIDTH_32			(2 << 11)
+#define I2S_DEC_PORT_EN(x)			(((x) & 0xf) << 17)
+#define SPDIF_ENABLE				BIT(21)
+#define SPDIF_AVG_SEL				BIT(20)
+#define SPDIF_JITTER_BYPASS			BIT(19)
+#define SPDIF_FIFO_MID_RANGE(x)			(((x) & 0xff) << 11)
+#define SPDIF_JITTER_THRSH(x)			(((x) & 0xff) << 3)
+#define SPDIF_JITTER_AVG_WIN(x)			((x) & 0x7)
+
+/* Refernce cycles when using lane clock as refernce */
+#define LANE_REF_CYC				0x8000
+
+enum voltage_swing_level {
+	VOLTAGE_LEVEL_0,
+	VOLTAGE_LEVEL_1,
+	VOLTAGE_LEVEL_2,
+	VOLTAGE_LEVEL_3,
+};
+
+enum pre_emphasis_level {
+	PRE_EMPHASIS_LEVEL_0,
+	PRE_EMPHASIS_LEVEL_1,
+	PRE_EMPHASIS_LEVEL_2,
+	PRE_EMPHASIS_LEVEL_3,
+};
+
+enum pattern_set {
+	PTS1		= BIT(0),
+	PTS2		= BIT(1),
+	PTS3		= BIT(2),
+	PTS4		= BIT(3),
+	DP_NONE		= BIT(4)
+};
+
+enum vic_color_depth {
+	BCS_6 = 0x1,
+	BCS_8 = 0x2,
+	BCS_10 = 0x4,
+	BCS_12 = 0x8,
+	BCS_16 = 0x10,
+};
+
+enum vic_bt_type {
+	BT_601 = 0x0,
+	BT_709 = 0x1,
+};
+
+void cdn_dp_clock_reset(struct cdn_dp_device *dp);
+
+void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, u32 clk);
+int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
+			 u32 i_size, const u32 *d_mem, u32 d_size);
+int cdn_dp_active(struct cdn_dp_device *dp, bool enable);
+int cdn_dp_set_host_cap(struct cdn_dp_device *dp, u8 lanes);
+int cdn_dp_event_config(struct cdn_dp_device *dp);
+u32 cdn_dp_get_event(struct cdn_dp_device *dp);
+int cdn_dp_get_hpd_status(struct cdn_dp_device *dp);
+int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value);
+int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr, u8 *data, u16 len);
+int cdn_dp_get_edid_block(void *dp, u8 *edid,
+			  unsigned int block, size_t length);
+int cdn_dp_training_start(struct cdn_dp_device *dp);
+int cdn_dp_get_training_status(struct cdn_dp_device *dp);
+int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active);
+int cdn_dp_config_video(struct cdn_dp_device *dp);
+int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio);
+int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable);
+int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info *audio);
+#endif /* _CDN_DP_REG_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
index edd7ec2..ceb4b86 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -969,9 +969,9 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
 		vop_dsp_hold_valid_irq_disable(vop);
 	}
 
-	pin_pol = 0x8;
-	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : 1;
-	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? 0 : (1 << 1);
+	pin_pol = BIT(DCLK_INVERT);
+	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : BIT(HSYNC_POSITIVE);
+	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? 0 : BIT(VSYNC_POSITIVE);
 	VOP_CTRL_SET(vop, pin_pol, pin_pol);
 
 	switch (s->output_type) {
@@ -991,6 +991,11 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
 		VOP_CTRL_SET(vop, mipi_pin_pol, pin_pol);
 		VOP_CTRL_SET(vop, mipi_en, 1);
 		break;
+	case DRM_MODE_CONNECTOR_DisplayPort:
+		pin_pol &= ~BIT(DCLK_INVERT);
+		VOP_CTRL_SET(vop, dp_pin_pol, pin_pol);
+		VOP_CTRL_SET(vop, dp_en, 1);
+		break;
 	default:
 		DRM_ERROR("unsupport connector_type[%d]\n", s->output_type);
 	}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
index ff4f52e..4820a8b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
@@ -45,6 +45,7 @@ struct vop_ctrl {
 	struct vop_reg edp_en;
 	struct vop_reg hdmi_en;
 	struct vop_reg mipi_en;
+	struct vop_reg dp_en;
 	struct vop_reg out_mode;
 	struct vop_reg dither_down;
 	struct vop_reg dither_up;
@@ -53,6 +54,7 @@ struct vop_ctrl {
 	struct vop_reg hdmi_pin_pol;
 	struct vop_reg edp_pin_pol;
 	struct vop_reg mipi_pin_pol;
+	struct vop_reg dp_pin_pol;
 
 	struct vop_reg htotal_pw;
 	struct vop_reg hact_st_end;
@@ -242,6 +244,13 @@ enum scale_down_mode {
 	SCALE_DOWN_AVG = 0x1
 };
 
+enum vop_pol {
+	HSYNC_POSITIVE = 0,
+	VSYNC_POSITIVE = 1,
+	DEN_NEGATIVE   = 2,
+	DCLK_INVERT    = 3
+};
+
 #define FRAC_16_16(mult, div)    (((mult) << 16) / (div))
 #define SCL_FT_DEFAULT_FIXPOINT_SHIFT	12
 #define SCL_MAX_VSKIPLINES		4
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
index 5b1ae1f..dcf172e 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
@@ -281,6 +281,7 @@ static const struct vop_data rk3288_vop = {
 static const struct vop_ctrl rk3399_ctrl_data = {
 	.standby = VOP_REG(RK3399_SYS_CTRL, 0x1, 22),
 	.gate_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 23),
+	.dp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 11),
 	.rgb_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 12),
 	.hdmi_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 13),
 	.edp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 14),
@@ -290,6 +291,7 @@ static const struct vop_ctrl rk3399_ctrl_data = {
 	.data_blank = VOP_REG(RK3399_DSP_CTRL0, 0x1, 19),
 	.out_mode = VOP_REG(RK3399_DSP_CTRL0, 0xf, 0),
 	.rgb_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 16),
+	.dp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 16),
 	.hdmi_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 20),
 	.edp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 24),
 	.mipi_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 28),
-- 
2.6.3

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

* [v5.2 PATCH 5/5] drm/rockchip: cdn-dp: add cdn DP support for rk3399
  2016-07-15  8:18         ` [v5.2 " Chris Zhong
@ 2016-07-19 16:44           ` Sean Paul
  0 siblings, 0 replies; 11+ messages in thread
From: Sean Paul @ 2016-07-19 16:44 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jul 15, 2016 at 04:18:13PM +0800, Chris Zhong wrote:
> Add support for cdn DP controller which is embedded in the rk3399
> SoCs. The DP is compliant with DisplayPort Specification,
> Version 1.3, This IP is compatible with the rockchip type-c PHY IP.
> There is a uCPU in DP controller, it need a firmware to work,
> please put the firmware file to /lib/firmware/cdn/dptx.bin. The
> uCPU in charge of aux communication and link training, the host use
> mailbox to communicate with the ucpu.
> The dclk pin_pol of vop must not be invert for DP.
> 
> Signed-off-by: Chris Zhong <zyw@rock-chips.com>
> 

I still think there are gremlins lurking in the workqueue code, but I think
it's probably worth merging this initial version and iterating on it. I'm going
to play around with it this week.

Reviewed-by: Sean Paul <seanpaul@chromium.org>


> ---
> 
> Changes in v5.2:
> - fixed the fw_wait always 0
> 
> Changes in v5.1:
> - modify according to Sean Paul's comments
> 
> Changes in v5:
> - alphabetical order
> - do not use long, use u32 or u64
> - return MODE_CLOCK_HIGH when requested > actual
> - Optimized Coding Style
> - add a formula to get better tu size and symbol value.
> 
> Changes in v4:
> - use phy framework to control DP phy
> - support 2 phys
> 
> Changes in v3:
> - use EXTCON_DISP_DP and EXTCON_DISP_DP_ALT cable to get dp port state.
> - reset spdif before config it
> - modify the firmware clk to 100Mhz
> - retry load firmware if fw file is requested too early
> 
> Changes in v2:
> - Alphabetic order
> - remove excess error message
> - use define clk_rate
> - check all return value
> - remove dev_set_name(dp->dev, "cdn-dp");
> - use schedule_delayed_work
> - remove never-called functions
> - remove some unnecessary ()
> 
> Changes in v1:
> - use extcon API
> - use hdmi-codec for the DP Asoc
> - do not initialize the "ret"
> - printk a err log when drm_of_encoder_active_endpoint_id
> - modify the dclk pin_pol to a single line
> 
>  drivers/gpu/drm/rockchip/Kconfig            |   9 +
>  drivers/gpu/drm/rockchip/Makefile           |   1 +
>  drivers/gpu/drm/rockchip/cdn-dp-core.c      | 842 ++++++++++++++++++++++++++
>  drivers/gpu/drm/rockchip/cdn-dp-core.h      |  99 +++
>  drivers/gpu/drm/rockchip/cdn-dp-reg.c       | 898 ++++++++++++++++++++++++++++
>  drivers/gpu/drm/rockchip/cdn-dp-reg.h       | 479 +++++++++++++++
>  drivers/gpu/drm/rockchip/rockchip_drm_vop.c |  11 +-
>  drivers/gpu/drm/rockchip/rockchip_drm_vop.h |   9 +
>  drivers/gpu/drm/rockchip/rockchip_vop_reg.c |   2 +
>  9 files changed, 2347 insertions(+), 3 deletions(-)
>  create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.c
>  create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-core.h
>  create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.c
>  create mode 100644 drivers/gpu/drm/rockchip/cdn-dp-reg.h
> 
> diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
> index d30bdc3..a04446a 100644
> --- a/drivers/gpu/drm/rockchip/Kconfig
> +++ b/drivers/gpu/drm/rockchip/Kconfig
> @@ -25,6 +25,15 @@ config ROCKCHIP_ANALOGIX_DP
>  	  for the Analogix Core DP driver. If you want to enable DP
>  	  on RK3288 based SoC, you should selet this option.
>  
> +config ROCKCHIP_CDN_DP
> +        tristate "Rockchip cdn DP"
> +        depends on DRM_ROCKCHIP
> +        help
> +	  This selects support for Rockchip SoC specific extensions
> +	  for the cdn DP driver. If you want to enable Dp on
> +	  RK3399 based SoC, you should select this
> +	  option.
> +
>  config ROCKCHIP_DW_HDMI
>          tristate "Rockchip specific extensions for Synopsys DW HDMI"
>          depends on DRM_ROCKCHIP
> diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
> index 05d0713..abdecd5 100644
> --- a/drivers/gpu/drm/rockchip/Makefile
> +++ b/drivers/gpu/drm/rockchip/Makefile
> @@ -7,6 +7,7 @@ rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
>  rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
>  
>  obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
> +obj-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o
>  obj-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
>  obj-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi.o
>  obj-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
> new file mode 100644
> index 0000000..ed38dbc
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
> @@ -0,0 +1,842 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author: Chris Zhong <zyw@rock-chips.com>
> + *
> + * This software is licensed under the terms of the GNU General Public
> + * License version 2, as published by the Free Software Foundation, and
> + * may be copied, distributed, and modified under those terms.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_dp_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_of.h>
> +
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/extcon.h>
> +#include <linux/firmware.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/phy/phy.h>
> +
> +#include <sound/hdmi-codec.h>
> +
> +#include "cdn-dp-core.h"
> +#include "cdn-dp-reg.h"
> +#include "rockchip_drm_vop.h"
> +
> +#define connector_to_dp(c) \
> +		container_of(c, struct cdn_dp_device, connector)
> +
> +#define encoder_to_dp(c) \
> +		container_of(c, struct cdn_dp_device, encoder)
> +
> +/* dp grf register offset */
> +#define DP_VOP_SEL		0x6224
> +#define DP_SEL_VOP_LIT		BIT(12)
> +#define MAX_FW_WAIT_SECS	64
> +#define CDN_DP_FIRMWARE		"rockchip/dptx.bin"
> +
> +struct cdn_dp_data {
> +	u8 max_phy;
> +};
> +
> +struct cdn_dp_data rk3399_cdn_dp = {
> +	.max_phy = 2,
> +};
> +
> +static const struct of_device_id cdn_dp_dt_ids[] = {
> +	{ .compatible = "rockchip,rk3399-cdn-dp",
> +		.data = (void *)&rk3399_cdn_dp },
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(of, cdn_dp_dt_ids);
> +
> +static int cdn_dp_clk_enable(struct cdn_dp_device *dp)
> +{
> +	int ret;
> +	u32 rate;
> +
> +	ret = clk_prepare_enable(dp->pclk);
> +	if (ret < 0) {
> +		dev_err(dp->dev, "cannot enable dp pclk %d\n", ret);
> +		goto err_pclk;
> +	}
> +
> +	ret = clk_prepare_enable(dp->core_clk);
> +	if (ret < 0) {
> +		dev_err(dp->dev, "cannot enable core_clk %d\n", ret);
> +		goto err_core_clk;
> +	}
> +
> +	rate = clk_get_rate(dp->core_clk);
> +	if (rate < 0) {
> +		dev_err(dp->dev, "get clk rate failed: %d\n", rate);
> +		goto err_set_rate;
> +	}
> +
> +	cdn_dp_set_fw_clk(dp, rate);
> +
> +	return 0;
> +
> +err_set_rate:
> +	clk_disable_unprepare(dp->core_clk);
> +err_core_clk:
> +	clk_disable_unprepare(dp->pclk);
> +err_pclk:
> +	return ret;
> +}
> +
> +static enum drm_connector_status
> +cdn_dp_connector_detect(struct drm_connector *connector, bool force)
> +{
> +	struct cdn_dp_device *dp = connector_to_dp(connector);
> +
> +	return dp->hpd_status;
> +}
> +
> +static void cdn_dp_connector_destroy(struct drm_connector *connector)
> +{
> +	drm_connector_unregister(connector);
> +	drm_connector_cleanup(connector);
> +}
> +
> +static struct drm_connector_funcs cdn_dp_atomic_connector_funcs = {
> +	.dpms = drm_atomic_helper_connector_dpms,
> +	.detect = cdn_dp_connector_detect,
> +	.destroy = cdn_dp_connector_destroy,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static int cdn_dp_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct cdn_dp_device *dp = connector_to_dp(connector);
> +	struct edid *edid;
> +	int ret = 0;
> +
> +	edid = drm_do_get_edid(connector, cdn_dp_get_edid_block, dp);
> +	if (edid) {
> +		dev_dbg(dp->dev, "got edid: width[%d] x height[%d]\n",
> +			edid->width_cm, edid->height_cm);
> +
> +		dp->sink_has_audio = drm_detect_monitor_audio(edid);
> +		ret = drm_add_edid_modes(connector, edid);
> +		if (ret) {
> +			drm_mode_connector_update_edid_property(connector,
> +								edid);
> +			drm_edid_to_eld(connector, edid);
> +		}
> +		kfree(edid);
> +	} else {
> +		dev_warn(dp->dev, "failed to get edid\n");
> +	}
> +
> +	return ret;
> +}
> +
> +static struct drm_encoder *
> +	cdn_dp_connector_best_encoder(struct drm_connector *connector)
> +{
> +	struct cdn_dp_device *dp = connector_to_dp(connector);
> +
> +	return &dp->encoder;
> +}
> +
> +static int cdn_dp_connector_mode_valid(struct drm_connector *connector,
> +				       struct drm_display_mode *mode)
> +{
> +	struct cdn_dp_device *dp = connector_to_dp(connector);
> +	struct drm_display_info *display_info = &dp->connector.display_info;
> +	u32 requested = mode->clock * display_info->bpc * 3 / 1000;
> +	u32 sink_max, source_max, rate;
> +	u8 lanes, id;
> +	u32 actual;
> +
> +	/* find the running port id */
> +	for (id = 0; id < dp->max_phy; id++) {
> +		if (dp->phy_status[id] == 1)
> +			break;
> +	}
> +
> +	sink_max = drm_dp_max_lane_count(dp->dpcd);
> +	source_max = dp->cap_lanes[id];
> +	lanes = min(sink_max, source_max);
> +
> +	sink_max = drm_dp_max_link_rate(dp->dpcd);
> +	source_max = drm_dp_bw_code_to_link_rate(CDN_DP_MAX_LINK_RATE);
> +	rate = min(sink_max, source_max);
> +
> +	actual = rate * lanes / 100;
> +
> +	/* efficiency is about 0.8 */
> +	actual = actual * 8 / 10;
> +
> +	if (requested > actual) {
> +		dev_dbg(dp->dev, "requested=%d, actual=%d, clock=%d\n",
> +			requested, actual, mode->clock);
> +		return MODE_CLOCK_HIGH;
> +	}
> +
> +	return MODE_OK;
> +}
> +
> +static struct drm_connector_helper_funcs cdn_dp_connector_helper_funcs = {
> +	.get_modes = cdn_dp_connector_get_modes,
> +	.best_encoder = cdn_dp_connector_best_encoder,
> +	.mode_valid = cdn_dp_connector_mode_valid,
> +};
> +
> +static void cdn_dp_commit(struct drm_encoder *encoder)
> +{
> +	struct cdn_dp_device *dp = encoder_to_dp(encoder);
> +	int ret;
> +
> +	ret = cdn_dp_training_start(dp);
> +	if (ret) {
> +		dev_err(dp->dev, "link training failed: %d\n", ret);
> +		return;
> +	}
> +
> +	ret = cdn_dp_get_training_status(dp);
> +	if (ret) {
> +		dev_err(dp->dev, "get link training status failed: %d\n", ret);
> +		return;
> +	}
> +
> +	dev_info(dp->dev, "rate:%d, lanes:%d\n",
> +		 dp->link.rate, dp->link.num_lanes);
> +
> +	if (cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE))
> +		return;
> +
> +	if (cdn_dp_config_video(dp)) {
> +		dev_err(dp->dev, "unable to config video\n");
> +		return;
> +	}
> +
> +	if (cdn_dp_set_video_status(dp, CONTROL_VIDEO_VALID))
> +		return;
> +
> +	dp->dpms_mode = DRM_MODE_DPMS_ON;
> +}
> +
> +static void cdn_dp_encoder_mode_set(struct drm_encoder *encoder,
> +				    struct drm_display_mode *mode,
> +				    struct drm_display_mode *adjusted)
> +{
> +	struct cdn_dp_device *dp = encoder_to_dp(encoder);
> +	struct drm_display_info *display_info = &dp->connector.display_info;
> +	struct rockchip_crtc_state *state;
> +	struct video_info *video = &dp->video_info;
> +	int ret, val;
> +
> +	switch (display_info->bpc) {
> +	case 16:
> +	case 12:
> +	case 10:
> +		video->color_depth = 10;
> +		break;
> +	case 6:
> +		video->color_depth = 6;
> +		break;
> +	default:
> +		video->color_depth = 8;
> +		break;
> +	}
> +
> +	video->color_fmt = PXL_RGB;
> +
> +	video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
> +	video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
> +
> +	ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder);
> +	if (ret < 0) {
> +		dev_err(dp->dev, "Could not get vop id, %d", ret);
> +		return;
> +	}
> +
> +	dev_dbg(dp->dev, "vop %s output to cdn-dp\n", (ret) ? "LIT" : "BIG");
> +	state = to_rockchip_crtc_state(encoder->crtc->state);
> +	if (ret) {
> +		val = DP_SEL_VOP_LIT | (DP_SEL_VOP_LIT << 16);
> +		state->output_mode = ROCKCHIP_OUT_MODE_P888;
> +	} else {
> +		val = DP_SEL_VOP_LIT << 16;
> +		state->output_mode = ROCKCHIP_OUT_MODE_AAAA;
> +	}
> +	ret = regmap_write(dp->grf, DP_VOP_SEL, val);
> +	if (ret != 0)
> +		dev_err(dp->dev, "Could not write to GRF: %d\n", ret);
> +
> +	memcpy(&dp->mode, adjusted, sizeof(*mode));
> +}
> +
> +static void cdn_dp_encoder_enable(struct drm_encoder *encoder)
> +{
> +	struct cdn_dp_device *dp = encoder_to_dp(encoder);
> +
> +	if (dp->dpms_mode != DRM_MODE_DPMS_ON)
> +		cdn_dp_commit(encoder);
> +}
> +
> +static void cdn_dp_encoder_disable(struct drm_encoder *encoder)
> +{
> +	struct cdn_dp_device *dp = encoder_to_dp(encoder);
> +
> +	if (dp->dpms_mode != DRM_MODE_DPMS_OFF) {
> +		cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE);
> +		dp->dpms_mode = DRM_MODE_DPMS_OFF;
> +	}
> +}
> +
> +static int
> +cdn_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);
> +
> +	s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
> +	s->output_type = DRM_MODE_CONNECTOR_DisplayPort;
> +
> +	return 0;
> +}
> +
> +static struct drm_encoder_helper_funcs cdn_dp_encoder_helper_funcs = {
> +	.mode_set = cdn_dp_encoder_mode_set,
> +	.enable = cdn_dp_encoder_enable,
> +	.disable = cdn_dp_encoder_disable,
> +	.atomic_check = cdn_dp_encoder_atomic_check,
> +};
> +
> +static struct drm_encoder_funcs cdn_dp_encoder_funcs = {
> +	.destroy = drm_encoder_cleanup,
> +};
> +
> +static int cdn_dp_firmware_init(struct cdn_dp_device *dp)
> +{
> +	int ret;
> +	const u32 *iram_data, *dram_data;
> +	const struct firmware *fw = dp->fw;
> +	const struct cdn_firmware_header *hdr;
> +
> +	hdr = (struct cdn_firmware_header *)fw->data;
> +	if (fw->size != le32_to_cpu(hdr->size_bytes)) {
> +		dev_err(dp->dev, "firmware is invalid\n");
> +		return -EINVAL;
> +	}
> +
> +	iram_data = (const u32 *)(fw->data + hdr->header_size);
> +	dram_data = (const u32 *)(fw->data + hdr->header_size + hdr->iram_size);
> +
> +	ret = cdn_dp_load_firmware(dp, iram_data, hdr->iram_size,
> +				   dram_data, hdr->dram_size);
> +	if (ret)
> +		return ret;
> +
> +	dp->fw_loaded = 1;
> +
> +	ret = cdn_dp_active(dp, true);
> +	if (ret) {
> +		dev_err(dp->dev, "active ucpu failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return cdn_dp_event_config(dp);
> +}
> +
> +static int cdn_dp_init(struct cdn_dp_device *dp)
> +{
> +	struct device *dev = dp->dev;
> +	struct device_node *np = dev->of_node;
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct resource *res;
> +	int ret;
> +
> +	dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
> +	if (IS_ERR(dp->grf)) {
> +		dev_err(dev, "cdn-dp needs rockchip,grf property\n");
> +		return PTR_ERR(dp->grf);
> +	}
> +
> +	dp->irq = platform_get_irq(pdev, 0);
> +	if (dp->irq < 0) {
> +		dev_err(dev, "cdn-dp can not get irq\n");
> +		return dp->irq;
> +	}
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	dp->regs = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(dp->regs)) {
> +		dev_err(dev, "ioremap reg failed\n");
> +		return PTR_ERR(dp->regs);
> +	}
> +
> +	dp->core_clk = devm_clk_get(dev, "core-clk");
> +	if (IS_ERR(dp->core_clk)) {
> +		dev_err(dev, "cannot get core_clk_dp\n");
> +		return PTR_ERR(dp->core_clk);
> +	}
> +
> +	dp->pclk = devm_clk_get(dev, "pclk");
> +	if (IS_ERR(dp->pclk)) {
> +		dev_err(dev, "cannot get pclk\n");
> +		return PTR_ERR(dp->pclk);
> +	}
> +
> +	dp->spdif_clk = devm_clk_get(dev, "spdif");
> +	if (IS_ERR(dp->spdif_clk)) {
> +		dev_err(dev, "cannot get spdif_clk\n");
> +		return PTR_ERR(dp->spdif_clk);
> +	}
> +
> +	dp->spdif_rst = devm_reset_control_get(dev, "spdif");
> +	if (IS_ERR(dp->spdif_rst)) {
> +		dev_err(dev, "no spdif reset control found\n");
> +		return PTR_ERR(dp->spdif_rst);
> +	}
> +
> +	dp->dpms_mode = DRM_MODE_DPMS_OFF;
> +
> +	ret = cdn_dp_clk_enable(dp);
> +	if (ret < 0)
> +		return ret;
> +
> +	cdn_dp_clock_reset(dp);
> +
> +	return 0;
> +}
> +
> +static int cdn_dp_audio_hw_params(struct device *dev,
> +				  struct hdmi_codec_daifmt *daifmt,
> +				  struct hdmi_codec_params *params)
> +{
> +	struct cdn_dp_device *dp = dev_get_drvdata(dev);
> +	struct audio_info audio = {
> +		.sample_width = params->sample_width,
> +		.sample_rate = params->sample_rate,
> +		.channels = params->channels,
> +	};
> +	int ret;
> +
> +	if (!dp->encoder.crtc)
> +		return -ENODEV;
> +
> +	switch (daifmt->fmt) {
> +	case HDMI_I2S:
> +		audio.format = AFMT_I2S;
> +		break;
> +	case HDMI_SPDIF:
> +		audio.format = AFMT_SPDIF;
> +		break;
> +	default:
> +		dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt);
> +		return -EINVAL;
> +	}
> +
> +	ret = cdn_dp_audio_config_set(dp, &audio);
> +	if (!ret)
> +		dp->audio_info = audio;
> +
> +	return ret;
> +}
> +
> +static void cdn_dp_audio_shutdown(struct device *dev)
> +{
> +	struct cdn_dp_device *dp = dev_get_drvdata(dev);
> +	int ret = cdn_dp_audio_stop(dp, &dp->audio_info);
> +
> +	if (!ret)
> +		dp->audio_info.format = AFMT_UNUSED;
> +}
> +
> +static int cdn_dp_audio_digital_mute(struct device *dev, bool enable)
> +{
> +	struct cdn_dp_device *dp = dev_get_drvdata(dev);
> +
> +	return cdn_dp_audio_mute(dp, enable);
> +}
> +
> +static int cdn_dp_audio_get_eld(struct device *dev, uint8_t *buf, size_t len)
> +{
> +	struct cdn_dp_device *dp = dev_get_drvdata(dev);
> +	struct drm_mode_config *config = &dp->encoder.dev->mode_config;
> +	struct drm_connector *connector;
> +	int ret = -ENODEV;
> +
> +	mutex_lock(&config->mutex);
> +	list_for_each_entry(connector, &config->connector_list, head) {
> +		if (&dp->encoder == connector->encoder) {
> +			memcpy(buf, connector->eld,
> +			       min(sizeof(connector->eld), len));
> +			ret = 0;
> +		}
> +	}
> +	mutex_unlock(&config->mutex);
> +
> +	return ret;
> +}
> +
> +static const struct hdmi_codec_ops audio_codec_ops = {
> +	.hw_params = cdn_dp_audio_hw_params,
> +	.audio_shutdown = cdn_dp_audio_shutdown,
> +	.digital_mute = cdn_dp_audio_digital_mute,
> +	.get_eld = cdn_dp_audio_get_eld,
> +};
> +
> +static int cdn_dp_audio_codec_init(struct cdn_dp_device *dp,
> +				   struct device *dev)
> +{
> +	struct hdmi_codec_pdata codec_data = {
> +		.i2s = 1,
> +		.spdif = 1,
> +		.ops = &audio_codec_ops,
> +		.max_i2s_channels = 8,
> +	};
> +
> +	dp->audio_pdev = platform_device_register_data(
> +			 dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO,
> +			 &codec_data, sizeof(codec_data));
> +
> +	return PTR_ERR_OR_ZERO(dp->audio_pdev);
> +}
> +
> +static int cdn_dp_get_cap_lanes(struct cdn_dp_device *dp,
> +				struct extcon_dev *edev)
> +{
> +	bool dfp, dptx;
> +	u8 lanes;
> +
> +	dfp = extcon_get_cable_state_(edev, EXTCON_USB_HOST);
> +	dptx = extcon_get_cable_state_(edev, EXTCON_DISP_DP);
> +
> +	if (dfp && dptx)
> +		lanes = 2;
> +	else if (dptx)
> +		lanes = 4;
> +	else
> +		lanes = 0;
> +
> +	return lanes;
> +}
> +
> +static int cdn_dp_pd_event(struct notifier_block *nb,
> +			   unsigned long event, void *priv)
> +{
> +	struct cdn_dp_device *dp;
> +	struct extcon_dev *edev = priv;
> +	u8 i;
> +
> +	dp = container_of(nb, struct cdn_dp_device, event_nb);
> +
> +	for (i = 0; i < dp->max_phy; i++) {
> +		if (edev == dp->extcon[i]) {
> +			dp->port_id = i;
> +			break;
> +		}
> +	}
> +
> +	schedule_delayed_work(&dp->event_wq, 0);
> +
> +	return 0;
> +}
> +
> +static void cdn_dp_pd_event_wq(struct work_struct *work)
> +{
> +	struct cdn_dp_device *dp = container_of(work, struct cdn_dp_device,
> +						event_wq.work);
> +	u8 id = dp->port_id, i;
> +	u8 old_cap_lanes = dp->cap_lanes[id];
> +	int ret;
> +
> +	dp->cap_lanes[id] = cdn_dp_get_cap_lanes(dp, dp->extcon[id]);
> +	if (dp->cap_lanes[id] == old_cap_lanes)
> +		return;
> +
> +	/* if phy is on, need reset it when dp lanes count is changed */
> +	if (dp->phy_status[id]) {
> +		ret = phy_power_off(dp->phy[id]);
> +		if (ret) {
> +			dev_err(dp->dev, "phy power off failed: %d", ret);
> +			goto err_phy_power_off;
> +		}
> +		dp->phy_status[id] = 0;
> +	}
> +
> +	/* if other phy is running, do not touch the hpd_status, and return */
> +	for (i = 0; i < dp->max_phy; i++) {
> +		if (dp->phy_status[i] == 1) {
> +			dev_warn(dp->dev, "busy, phy[%d] is running", i);
> +			goto err_phy_power_off;
> +		}
> +	}
> +
> +	if (!dp->cap_lanes[id]) {
> +		dp->hpd_status = connector_status_disconnected;
> +		drm_helper_hpd_irq_event(dp->drm_dev);
> +		return;
> +	}
> +
> +	if (!dp->fw_loaded) {
> +		ret = request_firmware(&dp->fw, CDN_DP_FIRMWARE, dp->dev);
> +		if (ret == -ENOENT && dp->fw_wait <= MAX_FW_WAIT_SECS) {
> +			unsigned long time = msecs_to_jiffies(dp->fw_wait * HZ);
> +
> +			/*
> +			 * If can not find the file, retry to load the firmware
> +			 * in 1 second, if still failed after 1 minute, give up.
> +			 */
> +			schedule_delayed_work(&dp->event_wq, time);
> +			dev_warn(dp->dev, "retry to request firmware in %d\n",
> +				 dp->fw_wait);
> +			dp->fw_wait *= 2;
> +			goto err_phy_power_off;
> +		} else if (ret) {
> +			dev_err(dp->dev, "failed to request firmware: %d\n",
> +				ret);
> +			goto err_phy_power_off;
> +		}
> +	}
> +
> +	ret = phy_power_on(dp->phy[id]);
> +	if (ret) {
> +		dev_err(dp->dev, "phy power on failed: %d", ret);
> +		if (!dp->fw_loaded)
> +			release_firmware(dp->fw);
> +		goto err_phy_power_off;
> +	}
> +
> +	dp->phy_status[id] = 1;
> +
> +	if (!dp->fw_loaded) {
> +		ret = cdn_dp_firmware_init(dp);
> +		release_firmware(dp->fw);
> +		if (ret) {
> +			dev_err(dp->dev, "firmware init failed: %d", ret);
> +			goto err_firmware;
> +		}
> +	}
> +
> +	/* read hpd status failed, or the hpd does not exist. */
> +	ret = cdn_dp_get_hpd_status(dp);
> +	if (ret <= 0) {
> +		dev_err(dp->dev, "get hpd failed: %d", ret);
> +		goto err_firmware;
> +	}
> +
> +	ret = cdn_dp_set_host_cap(dp, dp->cap_lanes[id]);
> +	if (ret) {
> +		dev_err(dp->dev, "set host capabilities failed: %d\n", ret);
> +		goto err_firmware;
> +	}
> +
> +	/*
> +	 * Native read with retry for link status and receiver capability reads
> +	 * for cases where the sink may still be asleep.
> +	 *
> +	 * Sinks are *supposed* to come up within 1ms from an off state, but
> +	 * we're also supposed to retry 3 times per the spec.
> +	 */
> +	for (i = 0; i < 3; i++) {
> +		ret = cdn_dp_dpcd_read(dp, 0x000, dp->dpcd,
> +				       DP_RECEIVER_CAP_SIZE);
> +		if (!ret) {
> +			dp->hpd_status = connector_status_connected;
> +			drm_helper_hpd_irq_event(dp->drm_dev);
> +			return;
> +		}
> +		usleep_range(1000, 1050);
> +	}
> +
> +err_firmware:
> +	ret = phy_power_off(dp->phy[id]);
> +	if (ret)
> +		dev_err(dp->dev, "phy power off failed: %d", ret);
> +	else
> +		dp->phy_status[id] = 0;
> +
> +err_phy_power_off:
> +	dp->cap_lanes[id] = 0;
> +}
> +
> +static int cdn_dp_bind(struct device *dev, struct device *master,
> +		       void *data)
> +{
> +	struct cdn_dp_device *dp = dev_get_drvdata(dev);
> +	struct drm_encoder *encoder;
> +	struct drm_connector *connector;
> +	struct drm_device *drm_dev = data;
> +	int ret, i;
> +
> +	ret = cdn_dp_init(dp);
> +	if (ret < 0)
> +		return ret;
> +
> +	dp->drm_dev = drm_dev;
> +	dp->hpd_status = connector_status_disconnected;
> +	dp->fw_wait = 1;
> +
> +	encoder = &dp->encoder;
> +
> +	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
> +							     dev->of_node);
> +	DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs);
> +
> +	ret = drm_encoder_init(drm_dev, encoder, &cdn_dp_encoder_funcs,
> +			       DRM_MODE_ENCODER_TMDS, NULL);
> +	if (ret) {
> +		DRM_ERROR("failed to initialize encoder with drm\n");
> +		return ret;
> +	} > +
> +	drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs);
> +
> +	connector = &dp->connector;
> +	connector->polled = DRM_CONNECTOR_POLL_HPD;
> +	connector->dpms = DRM_MODE_DPMS_OFF;
> +
> +	ret = drm_connector_init(drm_dev, connector,
> +				 &cdn_dp_atomic_connector_funcs,
> +				 DRM_MODE_CONNECTOR_DisplayPort);
> +	if (ret) {
> +		DRM_ERROR("failed to initialize connector with drm\n");
> +		goto err_free_encoder;
> +	}
> +
> +	drm_connector_helper_add(connector, &cdn_dp_connector_helper_funcs);
> +
> +	ret = drm_mode_connector_attach_encoder(connector, encoder);
> +	if (ret) {
> +		DRM_ERROR("failed to attach connector and encoder\n");
> +		goto err_free_connector;
> +	}
> +
> +	cdn_dp_audio_codec_init(dp, dev);
> +
> +	dp->event_nb.notifier_call = cdn_dp_pd_event;
> +	INIT_DELAYED_WORK(&dp->event_wq, cdn_dp_pd_event_wq);
> +
> +	for (i = 0; i < dp->max_phy; i++) {
> +		if (IS_ERR(dp->extcon[i]))
> +			continue;
> +
> +		ret = extcon_register_notifier(dp->extcon[i], EXTCON_DISP_DP,
> +					       &dp->event_nb);
> +		if (ret) {
> +			dev_err(dev, "regitster EXTCON_DISP_DP notifier err\n");
> +			return ret;
> +		}
> +
> +		dp->port_id = i;
> +		schedule_delayed_work(&dp->event_wq, 0);
> +	}
> +
> +	return 0;
> +
> +err_free_connector:
> +	drm_connector_cleanup(connector);
> +err_free_encoder:
> +	drm_encoder_cleanup(encoder);
> +	return ret;
> +}
> +
> +static void cdn_dp_unbind(struct device *dev, struct device *master, void *data)
> +{
> +	struct cdn_dp_device *dp = dev_get_drvdata(dev);
> +	struct drm_encoder *encoder = &dp->encoder;
> +	struct drm_connector *connector = &dp->connector;
> +	int i;
> +
> +	platform_device_unregister(dp->audio_pdev);
> +	cdn_dp_encoder_disable(encoder);
> +	encoder->funcs->destroy(encoder);
> +	connector->funcs->destroy(connector);
> +
> +	for (i = 0; i < dp->max_phy; i++) {
> +		if (!IS_ERR(dp->extcon[i]))
> +			extcon_unregister_notifier(dp->extcon[i], EXTCON_USB,
> +						   &dp->event_nb);
> +	}
> +}
> +
> +static const struct component_ops cdn_dp_component_ops = {
> +	.bind = cdn_dp_bind,
> +	.unbind = cdn_dp_unbind,
> +};
> +
> +static int cdn_dp_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	const struct of_device_id *match;
> +	struct cdn_dp_data *dp_data;
> +	struct cdn_dp_device *dp;
> +	u8 count = 0;
> +	int i;
> +
> +	dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
> +	if (!dp)
> +		return -ENOMEM;
> +	dp->dev = dev;
> +
> +	match = of_match_node(cdn_dp_dt_ids, pdev->dev.of_node);
> +	dp_data = (struct cdn_dp_data *)match->data;
> +
> +	dp->max_phy = dp_data->max_phy;
> +
> +	for (i = 0; i < dp->max_phy; i++) {
> +		dp->extcon[i] = extcon_get_edev_by_phandle(dev, i);
> +		dp->phy[i] = devm_of_phy_get_by_index(dev, dev->of_node, i);
> +
> +		if (!IS_ERR(dp->extcon[i]) && !IS_ERR(dp->phy[i]))
> +			count++;
> +
> +		if (PTR_ERR(dp->extcon[i]) == -EPROBE_DEFER ||
> +		    PTR_ERR(dp->phy[i]) == -EPROBE_DEFER)
> +			return -EPROBE_DEFER;
> +	}
> +
> +	if (!count) {
> +		dev_err(dev, "missing extcon or phy\n");
> +		return -EINVAL;
> +	}
> +
> +	dev_set_drvdata(dev, dp);
> +
> +	return component_add(dev, &cdn_dp_component_ops);
> +}
> +
> +static int cdn_dp_remove(struct platform_device *pdev)
> +{
> +	component_del(&pdev->dev, &cdn_dp_component_ops);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver cdn_dp_driver = {
> +	.probe = cdn_dp_probe,
> +	.remove = cdn_dp_remove,
> +	.driver = {
> +		   .name = "cdn-dp",
> +		   .owner = THIS_MODULE,
> +		   .of_match_table = of_match_ptr(cdn_dp_dt_ids),
> +	},
> +};
> +
> +module_platform_driver(cdn_dp_driver);
> +
> +MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
> +MODULE_DESCRIPTION("cdn DP Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h
> new file mode 100644
> index 0000000..1c577bf
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h
> @@ -0,0 +1,99 @@
> +/*
> + * Copyright (C) 2016 Chris Zhong <zyw@rock-chips.com>
> + * Copyright (C) 2016 ROCKCHIP, Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#ifndef _CDN_DP_CORE_H
> +#define _CDN_DP_CORE_H
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_dp_helper.h>
> +#include <drm/drm_panel.h>
> +#include "rockchip_drm_drv.h"
> +
> +#define MAX_PHY		2
> +
> +enum audio_format {
> +	AFMT_I2S = 0,
> +	AFMT_SPDIF = 1,
> +	AFMT_UNUSED,
> +};
> +
> +struct audio_info {
> +	enum audio_format format;
> +	int sample_rate;
> +	int channels;
> +	int sample_width;
> +};
> +
> +enum vic_pxl_encoding_format {
> +	PXL_RGB = 0x1,
> +	YCBCR_4_4_4 = 0x2,
> +	YCBCR_4_2_2 = 0x4,
> +	YCBCR_4_2_0 = 0x8,
> +	Y_ONLY = 0x10,
> +};
> +
> +struct video_info {
> +	bool h_sync_polarity;
> +	bool v_sync_polarity;
> +	bool interlaced;
> +	int color_depth;
> +	enum vic_pxl_encoding_format color_fmt;
> +};
> +
> +struct cdn_firmware_header {
> +	u32 size_bytes; /* size of the entire header+image(s) in bytes */
> +	u32 header_size; /* size of just the header in bytes */
> +	u32 iram_size; /* size of iram */
> +	u32 dram_size; /* size of dram */
> +};
> +
> +struct cdn_dp_device {
> +	struct device *dev;
> +	struct drm_device *drm_dev;
> +	struct drm_connector connector;
> +	struct drm_encoder encoder;
> +	struct drm_display_mode mode;
> +	struct platform_device *audio_pdev;
> +
> +	const struct firmware *fw;	/* cdn dp firmware */
> +	unsigned int fw_version;	/* cdn fw version */
> +	u32 fw_wait;
> +	bool fw_loaded;
> +	void __iomem *regs;
> +	struct regmap *grf;
> +	unsigned int irq;
> +	struct clk *core_clk;
> +	struct clk *pclk;
> +	struct clk *spdif_clk;
> +	struct reset_control *spdif_rst;
> +	struct audio_info audio_info;
> +	struct video_info video_info;
> +	struct extcon_dev *extcon[MAX_PHY];
> +	struct phy *phy[MAX_PHY];
> +	struct notifier_block event_nb;
> +	struct delayed_work event_wq;
> +	struct drm_dp_link link;
> +
> +	u8 cap_lanes[MAX_PHY];
> +	u8 dpcd[DP_RECEIVER_CAP_SIZE];
> +	u8 port_id;
> +	u8 max_phy;
> +	bool phy_status[MAX_PHY];
> +	enum drm_connector_status hpd_status;
> +
> +	int dpms_mode;
> +	bool sink_has_audio;
> +};
> +#endif  /* _CDN_DP_CORE_H */
> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.c b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
> new file mode 100644
> index 0000000..afc8ae9
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
> @@ -0,0 +1,898 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author: Chris Zhong <zyw@rock-chips.com>
> + *
> + * This software is licensed under the terms of the GNU General Public
> + * License version 2, as published by the Free Software Foundation, and
> + * may be copied, distributed, and modified under those terms.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/reset.h>
> +
> +#include "cdn-dp-core.h"
> +#include "cdn-dp-reg.h"
> +
> +#define CDN_DP_SPDIF_CLK		200000000
> +#define FW_ALIVE_TIMEOUT_US		1000000
> +#define MAILBOX_RETRY_US		1000
> +#define MAILBOX_TIMEOUT_US		5000000
> +#define LINK_TRAINING_RETRY_MS		20
> +#define LINK_TRAINING_TIMEOUT_MS	500
> +
> +void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, u32 clk)
> +{
> +	writel(clk / 1000000, dp->regs + SW_CLK_H);
> +}
> +
> +void cdn_dp_clock_reset(struct cdn_dp_device *dp)
> +{
> +	u32 val;
> +
> +	val = DPTX_FRMR_DATA_CLK_RSTN_EN |
> +	      DPTX_FRMR_DATA_CLK_EN |
> +	      DPTX_PHY_DATA_RSTN_EN |
> +	      DPTX_PHY_DATA_CLK_EN |
> +	      DPTX_PHY_CHAR_RSTN_EN |
> +	      DPTX_PHY_CHAR_CLK_EN |
> +	      SOURCE_AUX_SYS_CLK_RSTN_EN |
> +	      SOURCE_AUX_SYS_CLK_EN |
> +	      DPTX_SYS_CLK_RSTN_EN |
> +	      DPTX_SYS_CLK_EN |
> +	      CFG_DPTX_VIF_CLK_RSTN_EN |
> +	      CFG_DPTX_VIF_CLK_EN;
> +	writel(val, dp->regs + SOURCE_DPTX_CAR);
> +
> +	val = SOURCE_PHY_RSTN_EN | SOURCE_PHY_CLK_EN;
> +	writel(val, dp->regs + SOURCE_PHY_CAR);
> +
> +	val = SOURCE_PKT_SYS_RSTN_EN |
> +	      SOURCE_PKT_SYS_CLK_EN |
> +	      SOURCE_PKT_DATA_RSTN_EN |
> +	      SOURCE_PKT_DATA_CLK_EN;
> +	writel(val, dp->regs + SOURCE_PKT_CAR);
> +
> +	val = SPDIF_CDR_CLK_RSTN_EN |
> +	      SPDIF_CDR_CLK_EN |
> +	      SOURCE_AIF_SYS_RSTN_EN |
> +	      SOURCE_AIF_SYS_CLK_EN |
> +	      SOURCE_AIF_CLK_RSTN_EN |
> +	      SOURCE_AIF_CLK_EN;
> +	writel(val, dp->regs + SOURCE_AIF_CAR);
> +
> +	val = SOURCE_CIPHER_SYSTEM_CLK_RSTN_EN |
> +	      SOURCE_CIPHER_SYS_CLK_EN |
> +	      SOURCE_CIPHER_CHAR_CLK_RSTN_EN |
> +	      SOURCE_CIPHER_CHAR_CLK_EN;
> +	writel(val, dp->regs + SOURCE_CIPHER_CAR);
> +
> +	val = SOURCE_CRYPTO_SYS_CLK_RSTN_EN |
> +	      SOURCE_CRYPTO_SYS_CLK_EN;
> +	writel(val, dp->regs + SOURCE_CRYPTO_CAR);
> +
> +	val = ~(MAILBOX_INT_MASK_BIT | PIF_INT_MASK_BIT) & ALL_INT_MASK;
> +	writel(val, dp->regs + APB_INT_MASK);
> +}
> +
> +static int cdn_dp_mailbox_read(struct cdn_dp_device *dp)
> +{
> +	int val, ret;
> +
> +	if (!dp->fw_loaded)
> +		return -EPERM;
> +
> +	ret = readx_poll_timeout(readl, dp->regs + MAILBOX_EMPTY_ADDR,
> +				 val, !val, MAILBOX_RETRY_US,
> +				 MAILBOX_TIMEOUT_US);
> +	if (ret < 0) {
> +		dev_err(dp->dev, "failed to read mailbox, keep alive = %x\n",
> +			readl(dp->regs + KEEP_ALIVE));
> +		return ret;
> +	}
> +
> +	return readl(dp->regs + MAILBOX0_RD_DATA) & 0xff;
> +}
> +
> +static int cdp_dp_mailbox_write(struct cdn_dp_device *dp, u8 val)
> +{
> +	int ret, full;
> +
> +	if (!dp->fw_loaded)
> +		return -EPERM;
> +
> +	ret = readx_poll_timeout(readl, dp->regs + MAILBOX_FULL_ADDR,
> +				 full, !full, MAILBOX_RETRY_US,
> +				 MAILBOX_TIMEOUT_US);
> +	if (ret < 0) {
> +		dev_err(dp->dev, "mailbox is full, keep alive = %x\n",
> +			readl(dp->regs + KEEP_ALIVE));
> +		return ret;
> +	}
> +
> +	writel(val, dp->regs + MAILBOX0_WR_DATA);
> +
> +	return 0;
> +}
> +
> +static int cdn_dp_mailbox_validate_receive(struct cdn_dp_device *dp,
> +					   u8 module_id, u8 opcode,
> +					   u8 req_size)
> +{
> +	u32 mbox_size, i;
> +	u8 header[4];
> +	int ret;
> +
> +	/* read the header of the message */
> +	for (i = 0; i < 4; i++)  {
> +		ret = cdn_dp_mailbox_read(dp);
> +		if (ret < 0)
> +			return ret;
> +
> +		header[i] = ret;
> +	}
> +
> +	mbox_size = (header[2] << 8) | header[3];
> +
> +	if (opcode != header[0] || module_id != header[1] ||
> +	    req_size != mbox_size) {
> +		dump_stack();
> +		dev_err(dp->dev, "mailbox validate failed");
> +
> +		/*
> +		 * If the message in mailbox is not what we want, we need to
> +		 * clear the mailbox by read.
> +		 */
> +		for (i = 0; i < mbox_size; i++)
> +			if (cdn_dp_mailbox_read(dp) < 0)
> +				break;
> +
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int cdn_dp_mailbox_read_receive(struct cdn_dp_device *dp,
> +				       u8 *buff, u8 buff_size)
> +{
> +	u32 i;
> +	int ret;
> +
> +	for (i = 0; i < buff_size; i++) {
> +		ret = cdn_dp_mailbox_read(dp);
> +		if (ret < 0)
> +			return ret;
> +
> +		buff[i] = ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int cdn_dp_mailbox_send(struct cdn_dp_device *dp, u8 module_id,
> +			       u8 opcode, u16 size, u8 *message)
> +{
> +	u8 header[4];
> +	int ret, i;
> +
> +	header[0] = opcode;
> +	header[1] = module_id;
> +	header[2] = (size >> 8) & 0xff;
> +	header[3] = size & 0xff;
> +
> +	for (i = 0; i < 4; i++) {
> +		ret = cdp_dp_mailbox_write(dp, header[i]);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	for (i = 0; i < size; i++) {
> +		ret = cdp_dp_mailbox_write(dp, message[i]);
> +		if (ret) {
> +			dev_err(dp->dev, "mailbox send failed: %d", ret);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int cdn_dp_reg_write(struct cdn_dp_device *dp, u16 addr, u32 val)
> +{
> +	u8 msg[6];
> +
> +	msg[0] = (addr >> 8) & 0xff;
> +	msg[1] = addr & 0xff;
> +	msg[2] = (val >> 24) & 0xff;
> +	msg[3] = (val >> 16) & 0xff;
> +	msg[4] = (val >> 8) & 0xff;
> +	msg[5] = val & 0xff;
> +	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_REGISTER,
> +				   sizeof(msg), msg);
> +}
> +
> +int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr, u8 *data, u16 len)
> +{
> +	u8 msg[5], reg[5];
> +	int ret;
> +
> +	msg[0] = (len >> 8) & 0xff;
> +	msg[1] = len & 0xff;
> +	msg[2] = (addr >> 16) & 0xff;
> +	msg[3] = (addr >> 8) & 0xff;
> +	msg[4] = addr & 0xff;
> +	cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_READ_DPCD,
> +			    sizeof(msg), msg);
> +
> +	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
> +					      DPTX_READ_DPCD,
> +					      sizeof(reg) + len);
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
> +	if (ret)
> +		return ret;
> +
> +	return cdn_dp_mailbox_read_receive(dp, data, len);
> +}
> +
> +int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value)
> +{
> +	u8 msg[6], reg[5];
> +	int ret;
> +
> +	msg[0] = 0;
> +	msg[1] = 1;
> +	msg[2] = (addr >> 16) & 0xff;
> +	msg[3] = (addr >> 8) & 0xff;
> +	msg[4] = addr & 0xff;
> +	msg[5] = value;
> +	cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_DPCD,
> +			    sizeof(msg), msg);
> +
> +	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
> +					      DPTX_WRITE_DPCD, sizeof(reg));
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
> +	if (ret)
> +		return ret;
> +
> +	if (addr != (reg[2] << 16 | reg[3] << 8 | reg[4])) {
> +		dev_err(dp->dev, "write dpcd failed\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
> +			 u32 i_size, const u32 *d_mem, u32 d_size)
> +{
> +	int i, ret;
> +	u32 reg;
> +
> +	/* reset ucpu before load firmware*/
> +	writel(APB_IRAM_PATH | APB_DRAM_PATH | APB_XT_RESET,
> +	       dp->regs + APB_CTRL);
> +
> +	for (i = 0; i < i_size; i += 4)
> +		writel(*i_mem++, dp->regs + ADDR_IMEM + i);
> +
> +	for (i = 0; i < d_size; i += 4)
> +		writel(*d_mem++, dp->regs + ADDR_DMEM + i);
> +
> +	/* un-reset ucpu */
> +	writel(0, dp->regs + APB_CTRL);
> +
> +	/* check the keep alive register to make sure fw working */
> +	ret = readx_poll_timeout(readl, dp->regs + KEEP_ALIVE,
> +				 reg, reg, 2000, FW_ALIVE_TIMEOUT_US);
> +	if (ret < 0) {
> +		dev_err(dp->dev, "failed to loaded the FW reg = %x\n", reg);
> +		return -EINVAL;
> +	}
> +
> +	reg = readl(dp->regs + VER_L) & 0xff;
> +	dp->fw_version = reg;
> +	reg = readl(dp->regs + VER_H) & 0xff;
> +	dp->fw_version |= reg << 8;
> +	reg = readl(dp->regs + VER_LIB_L_ADDR) & 0xff;
> +	dp->fw_version |= reg << 16;
> +	reg = readl(dp->regs + VER_LIB_H_ADDR) & 0xff;
> +	dp->fw_version |= reg << 24;
> +
> +	dev_dbg(dp->dev, "firmware version: %x\n", dp->fw_version);
> +
> +	return 0;
> +}
> +
> +int cdn_dp_reg_write_bit(struct cdn_dp_device *dp, u16 addr, u8 start_bit,
> +			 u8 bits_no, u32 val)
> +{
> +	u8 field[8];
> +
> +	field[0] = (addr >> 8) & 0xff;
> +	field[1] = addr & 0xff;
> +	field[2] = start_bit;
> +	field[3] = bits_no;
> +	field[4] = (val >> 24) & 0xff;
> +	field[5] = (val >> 16) & 0xff;
> +	field[6] = (val >> 8) & 0xff;
> +	field[7] = val & 0xff;
> +
> +	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_FIELD,
> +				   sizeof(field), field);
> +}
> +
> +int cdn_dp_active(struct cdn_dp_device *dp, bool enable)
> +{
> +	u8 active = enable ? 1 : 0;
> +	u8 resp;
> +	int ret;
> +
> +	/* set firmware status, 1: active; 0: standby */
> +	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_GENERAL,
> +				  GENERAL_MAIN_CONTROL, sizeof(active),
> +				  &active);
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_GENERAL,
> +					      GENERAL_MAIN_CONTROL,
> +					      sizeof(resp));
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_mailbox_read_receive(dp, &resp, sizeof(resp));
> +	if (ret)
> +		return ret;
> +
> +	return resp ? 0 : -EINVAL;
> +}
> +
> +int cdn_dp_set_host_cap(struct cdn_dp_device *dp, u8 lanes)
> +{
> +	u8 msg[8];
> +	int ret;
> +
> +	msg[0] = CDN_DP_MAX_LINK_RATE;
> +	msg[1] = lanes;
> +	msg[2] = VOLTAGE_LEVEL_2;
> +	msg[3] = PRE_EMPHASIS_LEVEL_3;
> +	msg[4] = PTS1 | PTS2 | PTS3 | PTS4;
> +	msg[5] = FAST_LT_NOT_SUPPORT;
> +	msg[6] = LANE_MAPPING_NORMAL;
> +	msg[7] = ENHANCED;
> +
> +	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
> +				  DPTX_SET_HOST_CAPABILITIES,
> +				  sizeof(msg), msg);
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_reg_write(dp, DP_AUX_SWAP_INVERSION_CONTROL,
> +			       AUX_HOST_INVERT);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +int cdn_dp_event_config(struct cdn_dp_device *dp)
> +{
> +	u8 msg[5];
> +
> +	memset(msg, 0, sizeof(msg));
> +
> +	msg[0] = DPTX_EVENT_ENABLE_HPD | DPTX_EVENT_ENABLE_TRAINING;
> +
> +	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_ENABLE_EVENT,
> +				   sizeof(msg), msg);
> +}
> +
> +u32 cdn_dp_get_event(struct cdn_dp_device *dp)
> +{
> +	return readl(dp->regs + SW_EVENTS0);
> +}
> +
> +int cdn_dp_get_hpd_status(struct cdn_dp_device *dp)
> +{
> +	u8 status;
> +	int ret;
> +
> +	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_HPD_STATE,
> +				  0, NULL);
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
> +					      DPTX_HPD_STATE, sizeof(status));
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_mailbox_read_receive(dp, &status, sizeof(status));
> +	if (ret)
> +		return ret;
> +
> +	return status;
> +}
> +
> +int cdn_dp_get_edid_block(void *data, u8 *edid,
> +			  unsigned int block, size_t length)
> +{
> +	struct cdn_dp_device *dp = data;
> +	u8 msg[2], reg[2];
> +	int ret;
> +
> +	msg[0] = block / 2;
> +	msg[1] = block % 2;
> +
> +	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_GET_EDID,
> +				  sizeof(msg), msg);
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
> +					      DPTX_GET_EDID,
> +					      sizeof(reg) + length);
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_mailbox_read_receive(dp, edid, length);
> +	if (ret)
> +		return ret;
> +
> +	if (reg[0] != length || reg[1] != block / 2) {
> +		dev_err(dp->dev, "edid block size err\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +int cdn_dp_training_start(struct cdn_dp_device *dp)
> +{
> +	u8 msg, event[2];
> +	unsigned long timeout;
> +	int ret;
> +
> +	msg = LINK_TRAINING_RUN;
> +
> +	/* start training */
> +	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_TRAINING_CONTROL,
> +				  sizeof(msg), &msg);
> +	if (ret)
> +		return ret;
> +
> +	timeout = jiffies + msecs_to_jiffies(LINK_TRAINING_TIMEOUT_MS);
> +	while (time_before(jiffies, timeout)) {
> +		msleep(LINK_TRAINING_RETRY_MS);
> +		ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
> +					  DPTX_READ_EVENT, 0, NULL);
> +		if (ret)
> +			return ret;
> +
> +		ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
> +						      DPTX_READ_EVENT,
> +						      sizeof(event));
> +		if (ret)
> +			return ret;
> +
> +		ret = cdn_dp_mailbox_read_receive(dp, event, sizeof(event));
> +		if (ret)
> +			return ret;
> +
> +		if (event[1] & EQ_PHASE_FINISHED)
> +			return 0;
> +	}
> +
> +	return -ETIMEDOUT;
> +}
> +
> +int cdn_dp_get_training_status(struct cdn_dp_device *dp)
> +{
> +	u8 status[10];
> +	int ret;
> +
> +	ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_READ_LINK_STAT,
> +				  0, NULL);
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
> +					      DPTX_READ_LINK_STAT,
> +					      sizeof(status));
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_mailbox_read_receive(dp, status, sizeof(status));
> +	if (ret)
> +		return ret;
> +
> +	dp->link.rate = status[0];
> +	dp->link.num_lanes = status[1];
> +
> +	return 0;
> +}
> +
> +int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active)
> +{
> +	u8 msg;
> +
> +	msg = !!active;
> +
> +	return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_SET_VIDEO,
> +				   sizeof(msg), &msg);
> +}
> +
> +static int cdn_dp_get_msa_misc(struct video_info *video,
> +			       struct drm_display_mode *mode)
> +{
> +	u8 val[2];
> +	u32 msa_misc;
> +
> +	switch (video->color_fmt) {
> +	case PXL_RGB:
> +	case Y_ONLY:
> +		val[0] = 0;
> +		break;
> +	/* set YUV default color space conversion to BT601 */
> +	case YCBCR_4_4_4:
> +		val[0] = 6 + BT_601 * 8;
> +		break;
> +	case YCBCR_4_2_2:
> +		val[0] = 5 + BT_601 * 8;
> +		break;
> +	case YCBCR_4_2_0:
> +		val[0] = 5;
> +		break;
> +	};
> +
> +	switch (video->color_depth) {
> +	case 6:
> +		val[1] = 0;
> +		break;
> +	case 8:
> +		val[1] = 1;
> +		break;
> +	case 10:
> +		val[1] = 2;
> +		break;
> +	case 12:
> +		val[1] = 3;
> +		break;
> +	case 16:
> +		val[1] = 4;
> +		break;
> +	};
> +
> +	msa_misc = 2 * val[0] + 32 * val[1] +
> +		   ((video->color_fmt == Y_ONLY) ? (1 << 14) : 0);
> +
> +	return msa_misc;
> +}
> +
> +int cdn_dp_config_video(struct cdn_dp_device *dp)
> +{
> +	struct video_info *video = &dp->video_info;
> +	struct drm_display_mode *mode = &dp->mode;
> +	u64 symbol;
> +	u32 val, link_rate;
> +	u8 bit_per_pix;
> +	u8 tu_size_reg = TU_SIZE;
> +	int ret;
> +
> +	bit_per_pix = (video->color_fmt == YCBCR_4_2_2) ?
> +		      (video->color_depth * 2) : (video->color_depth * 3);
> +
> +	link_rate = drm_dp_bw_code_to_link_rate(dp->link.rate);
> +
> +	val = VIF_BYPASS_INTERLACE;
> +	ret = cdn_dp_reg_write(dp, BND_HSYNC2VSYNC, val);
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_reg_write(dp, HSYNC2VSYNC_POL_CTRL, 0);
> +	if (ret)
> +		return ret;
> +
> +	/* get a best tu_size and symbol */
> +	do {
> +		tu_size_reg += 2;
> +		symbol = tu_size_reg * mode->clock * bit_per_pix;
> +		symbol /= dp->link.num_lanes * link_rate * 8;
> +	} while ((symbol == 1) || (tu_size_reg - symbol < 4));
> +
> +	val = symbol + (tu_size_reg << 8);
> +	ret = cdn_dp_reg_write(dp, DP_FRAMER_TU, val);
> +	if (ret)
> +		return ret;
> +
> +	switch (video->color_depth) {
> +	case 6:
> +		val = BCS_6;
> +		break;
> +	case 8:
> +		val = BCS_8;
> +		break;
> +	case 10:
> +		val = BCS_10;
> +		break;
> +	case 12:
> +		val = BCS_12;
> +		break;
> +	case 16:
> +		val = BCS_16;
> +		break;
> +	};
> +
> +	val += video->color_fmt << 8;
> +	ret = cdn_dp_reg_write(dp, DP_FRAMER_PXL_REPR, val);
> +	if (ret)
> +		return ret;
> +
> +	val = video->h_sync_polarity ? DP_FRAMER_SP_HSP : 0;
> +	val |= video->v_sync_polarity ? DP_FRAMER_SP_VSP : 0;
> +	ret = cdn_dp_reg_write(dp, DP_FRAMER_SP, val);
> +	if (ret)
> +		return ret;
> +
> +	val = (mode->hsync_start - mode->hdisplay) << 16;
> +	val |= mode->htotal - mode->hsync_end;
> +	ret = cdn_dp_reg_write(dp, DP_FRONT_BACK_PORCH, val);
> +	if (ret)
> +		return ret;
> +
> +	val = mode->hdisplay * bit_per_pix / 8;
> +	ret = cdn_dp_reg_write(dp, DP_BYTE_COUNT, val);
> +	if (ret)
> +		return ret;
> +
> +	val = mode->htotal | ((mode->htotal - mode->hsync_start) << 16);
> +	ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_0, val);
> +	if (ret)
> +		return ret;
> +
> +	val = mode->hsync_end - mode->hsync_start;
> +	val |= (mode->hdisplay << 16) | (video->h_sync_polarity << 15);
> +	ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_1, val);
> +	if (ret)
> +		return ret;
> +
> +	val = mode->vtotal;
> +	val |= ((mode->vtotal - mode->vsync_start) << 16);
> +	ret = cdn_dp_reg_write(dp, MSA_VERTICAL_0, val);
> +	if (ret)
> +		return ret;
> +
> +	val = mode->vsync_end - mode->vsync_start;
> +	val |= mode->vdisplay << 16 | (video->v_sync_polarity << 15);
> +	ret = cdn_dp_reg_write(dp, MSA_VERTICAL_1, val);
> +	if (ret)
> +		return ret;
> +
> +	val = cdn_dp_get_msa_misc(video, mode);
> +	ret = cdn_dp_reg_write(dp, MSA_MISC, val);
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_reg_write(dp, STREAM_CONFIG, 1);
> +	if (ret)
> +		return ret;
> +
> +	val = mode->hsync_end - mode->hsync_start;
> +	val |= (mode->hdisplay << 16);
> +	ret = cdn_dp_reg_write(dp, DP_HORIZONTAL, val);
> +	if (ret)
> +		return ret;
> +
> +	val = mode->vtotal;
> +	val -= (mode->vtotal - mode->vdisplay);
> +	val |= (mode->vtotal - mode->vsync_start) << 16;
> +	ret = cdn_dp_reg_write(dp, DP_VERTICAL_0, val);
> +	if (ret)
> +		return ret;
> +
> +	val = mode->vtotal;
> +	ret = cdn_dp_reg_write(dp, DP_VERTICAL_1, val);
> +	if (ret)
> +		return ret;
> +
> +	val =  0;
> +	return cdn_dp_reg_write_bit(dp, DP_VB_ID, 2, 1, val);
> +}
> +
> +int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio)
> +{
> +	u32 val;
> +	int ret;
> +
> +	ret = cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, 0);
> +	if (ret)
> +		return ret;
> +
> +	val = SPDIF_AVG_SEL | SPDIF_JITTER_BYPASS;
> +	val |= SPDIF_FIFO_MID_RANGE(0xe0);
> +	val |= SPDIF_JITTER_THRSH(0xe0);
> +	val |= SPDIF_JITTER_AVG_WIN(7);
> +	writel(val, dp->regs + SPDIF_CTRL_ADDR);
> +
> +	/* clearn the audio config and reset */
> +	writel(0, dp->regs + AUDIO_SRC_CNTL);
> +	writel(0, dp->regs + AUDIO_SRC_CNFG);
> +	writel(AUDIO_SW_RST, dp->regs + AUDIO_SRC_CNTL);
> +	writel(0, dp->regs + AUDIO_SRC_CNTL);
> +
> +	/* reset smpl2pckt component  */
> +	writel(0, dp->regs + SMPL2PKT_CNTL);
> +	writel(AUDIO_SW_RST, dp->regs + SMPL2PKT_CNTL);
> +	writel(0, dp->regs + SMPL2PKT_CNTL);
> +
> +	/* reset FIFO */
> +	writel(AUDIO_SW_RST, dp->regs + FIFO_CNTL);
> +	writel(0, dp->regs + FIFO_CNTL);
> +
> +	if (audio->format == AFMT_SPDIF)
> +		clk_disable_unprepare(dp->spdif_clk);
> +
> +	return 0;
> +}
> +
> +int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable)
> +{
> +	return cdn_dp_reg_write_bit(dp, DP_VB_ID, 4, 1, enable);
> +}
> +
> +static void cdn_dp_audio_config_i2s(struct cdn_dp_device *dp,
> +				    struct audio_info *audio)
> +{
> +	int sub_pckt_num = 1, i2s_port_en_val = 0xf, i;
> +	u32 val;
> +
> +	if (audio->channels == 2) {
> +		if (dp->link.num_lanes == 1)
> +			sub_pckt_num = 2;
> +		else
> +			sub_pckt_num = 4;
> +
> +		i2s_port_en_val = 1;
> +	} else if (audio->channels == 4) {
> +		i2s_port_en_val = 3;
> +	}
> +
> +	writel(0x0, dp->regs + SPDIF_CTRL_ADDR);
> +
> +	writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
> +
> +	val = MAX_NUM_CH(audio->channels);
> +	val |= NUM_OF_I2S_PORTS(audio->channels);
> +	val |= AUDIO_TYPE_LPCM;
> +	val |= CFG_SUB_PCKT_NUM(sub_pckt_num);
> +	writel(val, dp->regs + SMPL2PKT_CNFG);
> +
> +	if (audio->sample_width == 16)
> +		val = 0;
> +	else if (audio->sample_width == 24)
> +		val = 1 << 9;
> +	else
> +		val = 2 << 9;
> +
> +	val |= AUDIO_CH_NUM(audio->channels);
> +	val |= I2S_DEC_PORT_EN(i2s_port_en_val);
> +	val |= TRANS_SMPL_WIDTH_32;
> +	writel(val, dp->regs + AUDIO_SRC_CNFG);
> +
> +	for (i = 0; i < (audio->channels + 1) / 2; i++) {
> +		if (audio->sample_width == 16)
> +			val = (0x08 << 8) | (0x08 << 20);
> +		else if (audio->sample_width == 24)
> +			val = (0x0b << 8) | (0x0b << 20);
> +
> +		val |= ((2 * i) << 4) | ((2 * i + 1) << 16);
> +		writel(val, dp->regs + STTS_BIT_CH(i));
> +	}
> +
> +	switch (audio->sample_rate) {
> +	case 32000:
> +		val = SAMPLING_FREQ(3) |
> +		      ORIGINAL_SAMP_FREQ(0xc);
> +		break;
> +	case 44100:
> +		val = SAMPLING_FREQ(0) |
> +		      ORIGINAL_SAMP_FREQ(0xf);
> +		break;
> +	case 48000:
> +		val = SAMPLING_FREQ(2) |
> +		      ORIGINAL_SAMP_FREQ(0xd);
> +		break;
> +	case 88200:
> +		val = SAMPLING_FREQ(8) |
> +		      ORIGINAL_SAMP_FREQ(0x7);
> +		break;
> +	case 96000:
> +		val = SAMPLING_FREQ(0xa) |
> +		      ORIGINAL_SAMP_FREQ(5);
> +		break;
> +	case 176400:
> +		val = SAMPLING_FREQ(0xc) |
> +		      ORIGINAL_SAMP_FREQ(3);
> +		break;
> +	case 192000:
> +		val = SAMPLING_FREQ(0xe) |
> +		      ORIGINAL_SAMP_FREQ(1);
> +		break;
> +	}
> +	val |= 4;
> +	writel(val, dp->regs + COM_CH_STTS_BITS);
> +
> +	writel(SMPL2PKT_EN, dp->regs + SMPL2PKT_CNTL);
> +	writel(I2S_DEC_START, dp->regs + AUDIO_SRC_CNTL);
> +}
> +
> +static void cdn_dp_audio_config_spdif(struct cdn_dp_device *dp)
> +{
> +	u32 val;
> +
> +	val = SPDIF_AVG_SEL | SPDIF_JITTER_BYPASS;
> +	val |= SPDIF_FIFO_MID_RANGE(0xe0);
> +	val |= SPDIF_JITTER_THRSH(0xe0);
> +	val |= SPDIF_JITTER_AVG_WIN(7);
> +	writel(val, dp->regs + SPDIF_CTRL_ADDR);
> +
> +	writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
> +
> +	val = MAX_NUM_CH(2) | AUDIO_TYPE_LPCM | CFG_SUB_PCKT_NUM(4);
> +	writel(val, dp->regs + SMPL2PKT_CNFG);
> +	writel(SMPL2PKT_EN, dp->regs + SMPL2PKT_CNTL);
> +
> +	val = SPDIF_ENABLE | SPDIF_AVG_SEL | SPDIF_JITTER_BYPASS;
> +	val |= SPDIF_FIFO_MID_RANGE(0xe0);
> +	val |= SPDIF_JITTER_THRSH(0xe0);
> +	val |= SPDIF_JITTER_AVG_WIN(7);
> +	writel(val, dp->regs + SPDIF_CTRL_ADDR);
> +
> +	clk_prepare_enable(dp->spdif_clk);
> +	clk_set_rate(dp->spdif_clk, CDN_DP_SPDIF_CLK);
> +}
> +
> +int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info *audio)
> +{
> +	int ret;
> +
> +	/* reset the spdif clk before config */
> +	if (audio->format == AFMT_SPDIF) {
> +		reset_control_assert(dp->spdif_rst);
> +		reset_control_deassert(dp->spdif_rst);
> +	}
> +
> +	ret = cdn_dp_reg_write(dp, CM_LANE_CTRL, LANE_REF_CYC);
> +	if (ret)
> +		return ret;
> +
> +	ret = cdn_dp_reg_write(dp, CM_CTRL, 0);
> +	if (ret)
> +		return ret;
> +
> +	if (audio->format == AFMT_I2S)
> +		cdn_dp_audio_config_i2s(dp, audio);
> +	else
> +		cdn_dp_audio_config_spdif(dp);
> +
> +	return cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, AUDIO_PACK_EN);
> +}
> diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.h b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
> new file mode 100644
> index 0000000..1affeed
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
> @@ -0,0 +1,479 @@
> +/*
> + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> + * Author: Chris Zhong <zyw@rock-chips.com>
> + *
> + * This software is licensed under the terms of the GNU General Public
> + * License version 2, as published by the Free Software Foundation, and
> + * may be copied, distributed, and modified under those terms.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#ifndef _CDN_DP_REG_H
> +#define _CDN_DP_REG_H
> +
> +#include <linux/bitops.h>
> +
> +#define ADDR_IMEM		0x10000
> +#define ADDR_DMEM		0x20000
> +
> +/* APB CFG addr */
> +#define APB_CTRL			0
> +#define XT_INT_CTRL			0x04
> +#define MAILBOX_FULL_ADDR		0x08
> +#define MAILBOX_EMPTY_ADDR		0x0c
> +#define MAILBOX0_WR_DATA		0x10
> +#define MAILBOX0_RD_DATA		0x14
> +#define KEEP_ALIVE			0x18
> +#define VER_L				0x1c
> +#define VER_H				0x20
> +#define VER_LIB_L_ADDR			0x24
> +#define VER_LIB_H_ADDR			0x28
> +#define SW_DEBUG_L			0x2c
> +#define SW_DEBUG_H			0x30
> +#define MAILBOX_INT_MASK		0x34
> +#define MAILBOX_INT_STATUS		0x38
> +#define SW_CLK_L			0x3c
> +#define SW_CLK_H			0x40
> +#define SW_EVENTS0			0x44
> +#define SW_EVENTS1			0x48
> +#define SW_EVENTS2			0x4c
> +#define SW_EVENTS3			0x50
> +#define XT_OCD_CTRL			0x60
> +#define APB_INT_MASK			0x6c
> +#define APB_STATUS_MASK			0x70
> +
> +/* audio decoder addr */
> +#define AUDIO_SRC_CNTL			0x30000
> +#define AUDIO_SRC_CNFG			0x30004
> +#define COM_CH_STTS_BITS		0x30008
> +#define STTS_BIT_CH(x)			(0x3000c + ((x) << 2))
> +#define SPDIF_CTRL_ADDR			0x3004c
> +#define SPDIF_CH1_CS_3100_ADDR		0x30050
> +#define SPDIF_CH1_CS_6332_ADDR		0x30054
> +#define SPDIF_CH1_CS_9564_ADDR		0x30058
> +#define SPDIF_CH1_CS_12796_ADDR		0x3005c
> +#define SPDIF_CH1_CS_159128_ADDR	0x30060
> +#define SPDIF_CH1_CS_191160_ADDR	0x30064
> +#define SPDIF_CH2_CS_3100_ADDR		0x30068
> +#define SPDIF_CH2_CS_6332_ADDR		0x3006c
> +#define SPDIF_CH2_CS_9564_ADDR		0x30070
> +#define SPDIF_CH2_CS_12796_ADDR		0x30074
> +#define SPDIF_CH2_CS_159128_ADDR	0x30078
> +#define SPDIF_CH2_CS_191160_ADDR	0x3007c
> +#define SMPL2PKT_CNTL			0x30080
> +#define SMPL2PKT_CNFG			0x30084
> +#define FIFO_CNTL			0x30088
> +#define FIFO_STTS			0x3008c
> +
> +/* source pif addr */
> +#define SOURCE_PIF_WR_ADDR		0x30800
> +#define SOURCE_PIF_WR_REQ		0x30804
> +#define SOURCE_PIF_RD_ADDR		0x30808
> +#define SOURCE_PIF_RD_REQ		0x3080c
> +#define SOURCE_PIF_DATA_WR		0x30810
> +#define SOURCE_PIF_DATA_RD		0x30814
> +#define SOURCE_PIF_FIFO1_FLUSH		0x30818
> +#define SOURCE_PIF_FIFO2_FLUSH		0x3081c
> +#define SOURCE_PIF_STATUS		0x30820
> +#define SOURCE_PIF_INTERRUPT_SOURCE	0x30824
> +#define SOURCE_PIF_INTERRUPT_MASK	0x30828
> +#define SOURCE_PIF_PKT_ALLOC_REG	0x3082c
> +#define SOURCE_PIF_PKT_ALLOC_WR_EN	0x30830
> +#define SOURCE_PIF_SW_RESET		0x30834
> +
> +/* bellow registers need access by mailbox */
> +/* source car addr */
> +#define SOURCE_HDTX_CAR			0x0900
> +#define SOURCE_DPTX_CAR			0x0904
> +#define SOURCE_PHY_CAR			0x0908
> +#define SOURCE_CEC_CAR			0x090c
> +#define SOURCE_CBUS_CAR			0x0910
> +#define SOURCE_PKT_CAR			0x0918
> +#define SOURCE_AIF_CAR			0x091c
> +#define SOURCE_CIPHER_CAR		0x0920
> +#define SOURCE_CRYPTO_CAR		0x0924
> +
> +/* clock meters addr */
> +#define CM_CTRL				0x0a00
> +#define CM_I2S_CTRL			0x0a04
> +#define CM_SPDIF_CTRL			0x0a08
> +#define CM_VID_CTRL			0x0a0c
> +#define CM_LANE_CTRL			0x0a10
> +#define I2S_NM_STABLE			0x0a14
> +#define I2S_NCTS_STABLE			0x0a18
> +#define SPDIF_NM_STABLE			0x0a1c
> +#define SPDIF_NCTS_STABLE		0x0a20
> +#define NMVID_MEAS_STABLE		0x0a24
> +#define I2S_MEAS			0x0a40
> +#define SPDIF_MEAS			0x0a80
> +#define NMVID_MEAS			0x0ac0
> +
> +/* source vif addr */
> +#define BND_HSYNC2VSYNC			0x0b00
> +#define HSYNC2VSYNC_F1_L1		0x0b04
> +#define HSYNC2VSYNC_F2_L1		0x0b08
> +#define HSYNC2VSYNC_STATUS		0x0b0c
> +#define HSYNC2VSYNC_POL_CTRL		0x0b10
> +
> +/* dptx phy addr */
> +#define DP_TX_PHY_CONFIG_REG		0x2000
> +#define DP_TX_PHY_STATUS_REG		0x2004
> +#define DP_TX_PHY_SW_RESET		0x2008
> +#define DP_TX_PHY_SCRAMBLER_SEED	0x200c
> +#define DP_TX_PHY_TRAINING_01_04	0x2010
> +#define DP_TX_PHY_TRAINING_05_08	0x2014
> +#define DP_TX_PHY_TRAINING_09_10	0x2018
> +#define TEST_COR			0x23fc
> +
> +/* dptx hpd addr */
> +#define HPD_IRQ_DET_MIN_TIMER		0x2100
> +#define HPD_IRQ_DET_MAX_TIMER		0x2104
> +#define HPD_UNPLGED_DET_MIN_TIMER	0x2108
> +#define HPD_STABLE_TIMER		0x210c
> +#define HPD_FILTER_TIMER		0x2110
> +#define HPD_EVENT_MASK			0x211c
> +#define HPD_EVENT_DET			0x2120
> +
> +/* dpyx framer addr */
> +#define DP_FRAMER_GLOBAL_CONFIG		0x2200
> +#define DP_SW_RESET			0x2204
> +#define DP_FRAMER_TU			0x2208
> +#define DP_FRAMER_PXL_REPR		0x220c
> +#define DP_FRAMER_SP			0x2210
> +#define AUDIO_PACK_CONTROL		0x2214
> +#define DP_VC_TABLE(x)			(0x2218 + ((x) << 2))
> +#define DP_VB_ID			0x2258
> +#define DP_MTPH_LVP_CONTROL		0x225c
> +#define DP_MTPH_SYMBOL_VALUES		0x2260
> +#define DP_MTPH_ECF_CONTROL		0x2264
> +#define DP_MTPH_ACT_CONTROL		0x2268
> +#define DP_MTPH_STATUS			0x226c
> +#define DP_INTERRUPT_SOURCE		0x2270
> +#define DP_INTERRUPT_MASK		0x2274
> +#define DP_FRONT_BACK_PORCH		0x2278
> +#define DP_BYTE_COUNT			0x227c
> +
> +/* dptx stream addr */
> +#define MSA_HORIZONTAL_0		0x2280
> +#define MSA_HORIZONTAL_1		0x2284
> +#define MSA_VERTICAL_0			0x2288
> +#define MSA_VERTICAL_1			0x228c
> +#define MSA_MISC			0x2290
> +#define STREAM_CONFIG			0x2294
> +#define AUDIO_PACK_STATUS		0x2298
> +#define VIF_STATUS			0x229c
> +#define PCK_STUFF_STATUS_0		0x22a0
> +#define PCK_STUFF_STATUS_1		0x22a4
> +#define INFO_PACK_STATUS		0x22a8
> +#define RATE_GOVERNOR_STATUS		0x22ac
> +#define DP_HORIZONTAL			0x22b0
> +#define DP_VERTICAL_0			0x22b4
> +#define DP_VERTICAL_1			0x22b8
> +#define DP_BLOCK_SDP			0x22bc
> +
> +/* dptx glbl addr */
> +#define DPTX_LANE_EN			0x2300
> +#define DPTX_ENHNCD			0x2304
> +#define DPTX_INT_MASK			0x2308
> +#define DPTX_INT_STATUS			0x230c
> +
> +/* dp aux addr */
> +#define DP_AUX_HOST_CONTROL		0x2800
> +#define DP_AUX_INTERRUPT_SOURCE		0x2804
> +#define DP_AUX_INTERRUPT_MASK		0x2808
> +#define DP_AUX_SWAP_INVERSION_CONTROL	0x280c
> +#define DP_AUX_SEND_NACK_TRANSACTION	0x2810
> +#define DP_AUX_CLEAR_RX			0x2814
> +#define DP_AUX_CLEAR_TX			0x2818
> +#define DP_AUX_TIMER_STOP		0x281c
> +#define DP_AUX_TIMER_CLEAR		0x2820
> +#define DP_AUX_RESET_SW			0x2824
> +#define DP_AUX_DIVIDE_2M		0x2828
> +#define DP_AUX_TX_PREACHARGE_LENGTH	0x282c
> +#define DP_AUX_FREQUENCY_1M_MAX		0x2830
> +#define DP_AUX_FREQUENCY_1M_MIN		0x2834
> +#define DP_AUX_RX_PRE_MIN		0x2838
> +#define DP_AUX_RX_PRE_MAX		0x283c
> +#define DP_AUX_TIMER_PRESET		0x2840
> +#define DP_AUX_NACK_FORMAT		0x2844
> +#define DP_AUX_TX_DATA			0x2848
> +#define DP_AUX_RX_DATA			0x284c
> +#define DP_AUX_TX_STATUS		0x2850
> +#define DP_AUX_RX_STATUS		0x2854
> +#define DP_AUX_RX_CYCLE_COUNTER		0x2858
> +#define DP_AUX_MAIN_STATES		0x285c
> +#define DP_AUX_MAIN_TIMER		0x2860
> +#define DP_AUX_AFE_OUT			0x2864
> +
> +/* crypto addr */
> +#define CRYPTO_HDCP_REVISION		0x5800
> +#define HDCP_CRYPTO_CONFIG		0x5804
> +#define CRYPTO_INTERRUPT_SOURCE		0x5808
> +#define CRYPTO_INTERRUPT_MASK		0x580c
> +#define CRYPTO22_CONFIG			0x5818
> +#define CRYPTO22_STATUS			0x581c
> +#define SHA_256_DATA_IN			0x583c
> +#define SHA_256_DATA_OUT_(x)		(0x5850 + ((x) << 2))
> +#define AES_32_KEY_(x)			(0x5870 + ((x) << 2))
> +#define AES_32_DATA_IN			0x5880
> +#define AES_32_DATA_OUT_(x)		(0x5884 + ((x) << 2))
> +#define CRYPTO14_CONFIG			0x58a0
> +#define CRYPTO14_STATUS			0x58a4
> +#define CRYPTO14_PRNM_OUT		0x58a8
> +#define CRYPTO14_KM_0			0x58ac
> +#define CRYPTO14_KM_1			0x58b0
> +#define CRYPTO14_AN_0			0x58b4
> +#define CRYPTO14_AN_1			0x58b8
> +#define CRYPTO14_YOUR_KSV_0		0x58bc
> +#define CRYPTO14_YOUR_KSV_1		0x58c0
> +#define CRYPTO14_MI_0			0x58c4
> +#define CRYPTO14_MI_1			0x58c8
> +#define CRYPTO14_TI_0			0x58cc
> +#define CRYPTO14_KI_0			0x58d0
> +#define CRYPTO14_KI_1			0x58d4
> +#define CRYPTO14_BLOCKS_NUM		0x58d8
> +#define CRYPTO14_KEY_MEM_DATA_0		0x58dc
> +#define CRYPTO14_KEY_MEM_DATA_1		0x58e0
> +#define CRYPTO14_SHA1_MSG_DATA		0x58e4
> +#define CRYPTO14_SHA1_V_VALUE_(x)	(0x58e8 + ((x) << 2))
> +#define TRNG_CTRL			0x58fc
> +#define TRNG_DATA_RDY			0x5900
> +#define TRNG_DATA			0x5904
> +
> +/* cipher addr */
> +#define HDCP_REVISION			0x60000
> +#define INTERRUPT_SOURCE		0x60004
> +#define INTERRUPT_MASK			0x60008
> +#define HDCP_CIPHER_CONFIG		0x6000c
> +#define AES_128_KEY_0			0x60010
> +#define AES_128_KEY_1			0x60014
> +#define AES_128_KEY_2			0x60018
> +#define AES_128_KEY_3			0x6001c
> +#define AES_128_RANDOM_0		0x60020
> +#define AES_128_RANDOM_1		0x60024
> +#define CIPHER14_KM_0			0x60028
> +#define CIPHER14_KM_1			0x6002c
> +#define CIPHER14_STATUS			0x60030
> +#define CIPHER14_RI_PJ_STATUS		0x60034
> +#define CIPHER_MODE			0x60038
> +#define CIPHER14_AN_0			0x6003c
> +#define CIPHER14_AN_1			0x60040
> +#define CIPHER22_AUTH			0x60044
> +#define CIPHER14_R0_DP_STATUS		0x60048
> +#define CIPHER14_BOOTSTRAP		0x6004c
> +
> +#define DPTX_FRMR_DATA_CLK_RSTN_EN	BIT(11)
> +#define DPTX_FRMR_DATA_CLK_EN		BIT(10)
> +#define DPTX_PHY_DATA_RSTN_EN		BIT(9)
> +#define DPTX_PHY_DATA_CLK_EN		BIT(8)
> +#define DPTX_PHY_CHAR_RSTN_EN		BIT(7)
> +#define DPTX_PHY_CHAR_CLK_EN		BIT(6)
> +#define SOURCE_AUX_SYS_CLK_RSTN_EN	BIT(5)
> +#define SOURCE_AUX_SYS_CLK_EN		BIT(4)
> +#define DPTX_SYS_CLK_RSTN_EN		BIT(3)
> +#define DPTX_SYS_CLK_EN			BIT(2)
> +#define CFG_DPTX_VIF_CLK_RSTN_EN	BIT(1)
> +#define CFG_DPTX_VIF_CLK_EN		BIT(0)
> +
> +#define SOURCE_PHY_RSTN_EN		BIT(1)
> +#define SOURCE_PHY_CLK_EN		BIT(0)
> +
> +#define SOURCE_PKT_SYS_RSTN_EN		BIT(3)
> +#define SOURCE_PKT_SYS_CLK_EN		BIT(2)
> +#define SOURCE_PKT_DATA_RSTN_EN		BIT(1)
> +#define SOURCE_PKT_DATA_CLK_EN		BIT(0)
> +
> +#define SPDIF_CDR_CLK_RSTN_EN		BIT(5)
> +#define SPDIF_CDR_CLK_EN		BIT(4)
> +#define SOURCE_AIF_SYS_RSTN_EN		BIT(3)
> +#define SOURCE_AIF_SYS_CLK_EN		BIT(2)
> +#define SOURCE_AIF_CLK_RSTN_EN		BIT(1)
> +#define SOURCE_AIF_CLK_EN		BIT(0)
> +
> +#define SOURCE_CIPHER_SYSTEM_CLK_RSTN_EN	BIT(3)
> +#define SOURCE_CIPHER_SYS_CLK_EN		BIT(2)
> +#define SOURCE_CIPHER_CHAR_CLK_RSTN_EN		BIT(1)
> +#define SOURCE_CIPHER_CHAR_CLK_EN		BIT(0)
> +
> +#define SOURCE_CRYPTO_SYS_CLK_RSTN_EN	BIT(1)
> +#define SOURCE_CRYPTO_SYS_CLK_EN	BIT(0)
> +
> +#define APB_IRAM_PATH			BIT(2)
> +#define APB_DRAM_PATH			BIT(1)
> +#define APB_XT_RESET			BIT(0)
> +
> +#define MAILBOX_INT_MASK_BIT		BIT(1)
> +#define PIF_INT_MASK_BIT		BIT(0)
> +#define ALL_INT_MASK			3
> +
> +/* mailbox */
> +#define MB_OPCODE_ID			0
> +#define MB_MODULE_ID			1
> +#define MB_SIZE_MSB_ID			2
> +#define MB_SIZE_LSB_ID			3
> +#define MB_DATA_ID			4
> +
> +#define MB_MODULE_ID_DP_TX		0x01
> +#define MB_MODULE_ID_HDCP_TX		0x07
> +#define MB_MODULE_ID_HDCP_RX		0x08
> +#define MB_MODULE_ID_HDCP_GENERAL	0x09
> +#define MB_MODULE_ID_GENERAL		0x0a
> +
> +/* general opcode */
> +#define GENERAL_MAIN_CONTROL            0x01
> +#define GENERAL_TEST_ECHO               0x02
> +#define GENERAL_BUS_SETTINGS            0x03
> +#define GENERAL_TEST_ACCESS             0x04
> +
> +#define DPTX_SET_POWER_MNG			0x00
> +#define DPTX_SET_HOST_CAPABILITIES		0x01
> +#define DPTX_GET_EDID				0x02
> +#define DPTX_READ_DPCD				0x03
> +#define DPTX_WRITE_DPCD				0x04
> +#define DPTX_ENABLE_EVENT			0x05
> +#define DPTX_WRITE_REGISTER			0x06
> +#define DPTX_READ_REGISTER			0x07
> +#define DPTX_WRITE_FIELD			0x08
> +#define DPTX_TRAINING_CONTROL			0x09
> +#define DPTX_READ_EVENT				0x0a
> +#define DPTX_READ_LINK_STAT			0x0b
> +#define DPTX_SET_VIDEO				0x0c
> +#define DPTX_SET_AUDIO				0x0d
> +#define DPTX_GET_LAST_AUX_STAUS			0x0e
> +#define DPTX_SET_LINK_BREAK_POINT		0x0f
> +#define DPTX_FORCE_LANES			0x10
> +#define DPTX_HPD_STATE				0x11
> +
> +#define DPTX_EVENT_ENABLE_HPD			BIT(0)
> +#define DPTX_EVENT_ENABLE_TRAINING		BIT(1)
> +
> +#define LINK_TRAINING_NOT_ACTIVE		0
> +#define LINK_TRAINING_RUN			1
> +#define LINK_TRAINING_RESTART			2
> +
> +#define CONTROL_VIDEO_IDLE			0
> +#define CONTROL_VIDEO_VALID			1
> +
> +#define VIF_BYPASS_INTERLACE			BIT(13)
> +#define INTERLACE_FMT_DET			BIT(12)
> +#define INTERLACE_DTCT_WIN			0x20
> +
> +#define DP_FRAMER_SP_INTERLACE_EN		BIT(2)
> +#define DP_FRAMER_SP_HSP			BIT(1)
> +#define DP_FRAMER_SP_VSP			BIT(0)
> +
> +/* capability */
> +#define AUX_HOST_INVERT				3
> +#define	FAST_LT_SUPPORT				1
> +#define FAST_LT_NOT_SUPPORT			0
> +#define LANE_MAPPING_NORMAL			0xe4
> +#define LANE_MAPPING_FLIPPED			0x1b
> +#define ENHANCED				1
> +
> +#define	FULL_LT_STARTED				BIT(0)
> +#define FASE_LT_STARTED				BIT(1)
> +#define CLK_RECOVERY_FINISHED			BIT(2)
> +#define EQ_PHASE_FINISHED			BIT(3)
> +#define FASE_LT_START_FINISHED			BIT(4)
> +#define CLK_RECOVERY_FAILED			BIT(5)
> +#define EQ_PHASE_FAILED				BIT(6)
> +#define FASE_LT_FAILED				BIT(7)
> +
> +#define DPTX_HPD_EVENT				BIT(0)
> +#define DPTX_TRAINING_EVENT			BIT(1)
> +#define HDCP_TX_STATUS_EVENT			BIT(4)
> +#define HDCP2_TX_IS_KM_STORED_EVENT		BIT(5)
> +#define HDCP2_TX_STORE_KM_EVENT			BIT(6)
> +#define HDCP_TX_IS_RECEIVER_ID_VALID_EVENT	BIT(7)
> +
> +#define TU_SIZE					30
> +#define CDN_DP_MAX_LINK_RATE			DP_LINK_BW_5_4
> +
> +/* audio */
> +#define AUDIO_PACK_EN				BIT(8)
> +#define SAMPLING_FREQ(x)			(((x) & 0xf) << 16)
> +#define ORIGINAL_SAMP_FREQ(x)			(((x) & 0xf) << 24)
> +#define SYNC_WR_TO_CH_ZERO			BIT(1)
> +#define I2S_DEC_START				BIT(1)
> +#define AUDIO_SW_RST				BIT(0)
> +#define SMPL2PKT_EN				BIT(1)
> +#define MAX_NUM_CH(x)				(((x) & 0x1f) - 1)
> +#define NUM_OF_I2S_PORTS(x)			((((x) / 2 - 1) & 0x3) << 5)
> +#define AUDIO_TYPE_LPCM				(2 << 7)
> +#define CFG_SUB_PCKT_NUM(x)			((((x) - 1) & 0x7) << 11)
> +#define AUDIO_CH_NUM(x)				((((x) - 1) & 0x1f) << 2)
> +#define TRANS_SMPL_WIDTH_16			0
> +#define TRANS_SMPL_WIDTH_24			BIT(11)
> +#define TRANS_SMPL_WIDTH_32			(2 << 11)
> +#define I2S_DEC_PORT_EN(x)			(((x) & 0xf) << 17)
> +#define SPDIF_ENABLE				BIT(21)
> +#define SPDIF_AVG_SEL				BIT(20)
> +#define SPDIF_JITTER_BYPASS			BIT(19)
> +#define SPDIF_FIFO_MID_RANGE(x)			(((x) & 0xff) << 11)
> +#define SPDIF_JITTER_THRSH(x)			(((x) & 0xff) << 3)
> +#define SPDIF_JITTER_AVG_WIN(x)			((x) & 0x7)
> +
> +/* Refernce cycles when using lane clock as refernce */
> +#define LANE_REF_CYC				0x8000
> +
> +enum voltage_swing_level {
> +	VOLTAGE_LEVEL_0,
> +	VOLTAGE_LEVEL_1,
> +	VOLTAGE_LEVEL_2,
> +	VOLTAGE_LEVEL_3,
> +};
> +
> +enum pre_emphasis_level {
> +	PRE_EMPHASIS_LEVEL_0,
> +	PRE_EMPHASIS_LEVEL_1,
> +	PRE_EMPHASIS_LEVEL_2,
> +	PRE_EMPHASIS_LEVEL_3,
> +};
> +
> +enum pattern_set {
> +	PTS1		= BIT(0),
> +	PTS2		= BIT(1),
> +	PTS3		= BIT(2),
> +	PTS4		= BIT(3),
> +	DP_NONE		= BIT(4)
> +};
> +
> +enum vic_color_depth {
> +	BCS_6 = 0x1,
> +	BCS_8 = 0x2,
> +	BCS_10 = 0x4,
> +	BCS_12 = 0x8,
> +	BCS_16 = 0x10,
> +};
> +
> +enum vic_bt_type {
> +	BT_601 = 0x0,
> +	BT_709 = 0x1,
> +};
> +
> +void cdn_dp_clock_reset(struct cdn_dp_device *dp);
> +
> +void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, u32 clk);
> +int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
> +			 u32 i_size, const u32 *d_mem, u32 d_size);
> +int cdn_dp_active(struct cdn_dp_device *dp, bool enable);
> +int cdn_dp_set_host_cap(struct cdn_dp_device *dp, u8 lanes);
> +int cdn_dp_event_config(struct cdn_dp_device *dp);
> +u32 cdn_dp_get_event(struct cdn_dp_device *dp);
> +int cdn_dp_get_hpd_status(struct cdn_dp_device *dp);
> +int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value);
> +int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr, u8 *data, u16 len);
> +int cdn_dp_get_edid_block(void *dp, u8 *edid,
> +			  unsigned int block, size_t length);
> +int cdn_dp_training_start(struct cdn_dp_device *dp);
> +int cdn_dp_get_training_status(struct cdn_dp_device *dp);
> +int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active);
> +int cdn_dp_config_video(struct cdn_dp_device *dp);
> +int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio);
> +int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable);
> +int cdn_dp_audio_config_set(struct cdn_dp_device *dp, struct audio_info *audio);
> +#endif /* _CDN_DP_REG_H */
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> index edd7ec2..ceb4b86 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> @@ -969,9 +969,9 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
>  		vop_dsp_hold_valid_irq_disable(vop);
>  	}
>  
> -	pin_pol = 0x8;
> -	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : 1;
> -	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? 0 : (1 << 1);
> +	pin_pol = BIT(DCLK_INVERT);
> +	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : BIT(HSYNC_POSITIVE);
> +	pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? 0 : BIT(VSYNC_POSITIVE);
>  	VOP_CTRL_SET(vop, pin_pol, pin_pol);
>  
>  	switch (s->output_type) {
> @@ -991,6 +991,11 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
>  		VOP_CTRL_SET(vop, mipi_pin_pol, pin_pol);
>  		VOP_CTRL_SET(vop, mipi_en, 1);
>  		break;
> +	case DRM_MODE_CONNECTOR_DisplayPort:
> +		pin_pol &= ~BIT(DCLK_INVERT);
> +		VOP_CTRL_SET(vop, dp_pin_pol, pin_pol);
> +		VOP_CTRL_SET(vop, dp_en, 1);
> +		break;
>  	default:
>  		DRM_ERROR("unsupport connector_type[%d]\n", s->output_type);
>  	}
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
> index ff4f52e..4820a8b 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
> @@ -45,6 +45,7 @@ struct vop_ctrl {
>  	struct vop_reg edp_en;
>  	struct vop_reg hdmi_en;
>  	struct vop_reg mipi_en;
> +	struct vop_reg dp_en;
>  	struct vop_reg out_mode;
>  	struct vop_reg dither_down;
>  	struct vop_reg dither_up;
> @@ -53,6 +54,7 @@ struct vop_ctrl {
>  	struct vop_reg hdmi_pin_pol;
>  	struct vop_reg edp_pin_pol;
>  	struct vop_reg mipi_pin_pol;
> +	struct vop_reg dp_pin_pol;
>  
>  	struct vop_reg htotal_pw;
>  	struct vop_reg hact_st_end;
> @@ -242,6 +244,13 @@ enum scale_down_mode {
>  	SCALE_DOWN_AVG = 0x1
>  };
>  
> +enum vop_pol {
> +	HSYNC_POSITIVE = 0,
> +	VSYNC_POSITIVE = 1,
> +	DEN_NEGATIVE   = 2,
> +	DCLK_INVERT    = 3
> +};
> +
>  #define FRAC_16_16(mult, div)    (((mult) << 16) / (div))
>  #define SCL_FT_DEFAULT_FIXPOINT_SHIFT	12
>  #define SCL_MAX_VSKIPLINES		4
> diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
> index 5b1ae1f..dcf172e 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
> @@ -281,6 +281,7 @@ static const struct vop_data rk3288_vop = {
>  static const struct vop_ctrl rk3399_ctrl_data = {
>  	.standby = VOP_REG(RK3399_SYS_CTRL, 0x1, 22),
>  	.gate_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 23),
> +	.dp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 11),
>  	.rgb_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 12),
>  	.hdmi_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 13),
>  	.edp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 14),
> @@ -290,6 +291,7 @@ static const struct vop_ctrl rk3399_ctrl_data = {
>  	.data_blank = VOP_REG(RK3399_DSP_CTRL0, 0x1, 19),
>  	.out_mode = VOP_REG(RK3399_DSP_CTRL0, 0xf, 0),
>  	.rgb_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 16),
> +	.dp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 16),
>  	.hdmi_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 20),
>  	.edp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 24),
>  	.mipi_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0xf, 28),
> -- 
> 2.6.3
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

end of thread, other threads:[~2016-07-19 16:44 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-07-12 15:09 [v5 PATCH 0/5] Rockchip Type-C and DisplayPort driver Chris Zhong
2016-07-12 15:09 ` [v5 PATCH 2/5] Documentation: bindings: add dt doc for Rockchip USB Type-C PHY Chris Zhong
2016-07-12 15:09 ` [v5 PATCH 3/5] phy: Add USB Type-C PHY driver for rk3399 Chris Zhong
2016-07-12 15:09 ` [v5 PATCH 4/5] Documentation: bindings: add dt documentation for cdn DP controller Chris Zhong
2016-07-12 15:09 ` [v5 PATCH 5/5] drm/rockchip: cdn-dp: add cdn DP support for rk3399 Chris Zhong
2016-07-13 13:59   ` Sean Paul
2016-07-14  3:08     ` Chris Zhong
2016-07-14 14:02       ` Sean Paul
2016-07-15  5:52         ` [v5.1 " Chris Zhong
2016-07-15  8:18         ` [v5.2 " Chris Zhong
2016-07-19 16:44           ` Sean Paul

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).