diff mbox series

[v2,18/19] video: sunxi: Add DW HDMI PHY driver

Message ID 20210306195437.9740-19-jernej.skrabec@siol.net
State Deferred
Delegated to: Tom Rini
Headers show
Series video: sunxi: rework DE2 driver | expand

Commit Message

Jernej Škrabec March 6, 2021, 7:54 p.m. UTC
This commit adds standalone driver for DW HDMI PHY. It deprecates code
which is included in sunxi dw-hdmi platform driver.

Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
 arch/arm/mach-sunxi/Kconfig             |   1 +
 drivers/video/sunxi/Makefile            |   2 +-
 drivers/video/sunxi/sunxi_dw_hdmi_phy.c | 423 ++++++++++++++++++++++++
 drivers/video/sunxi/sunxi_dw_hdmi_phy.h |  24 ++
 4 files changed, 449 insertions(+), 1 deletion(-)
 create mode 100644 drivers/video/sunxi/sunxi_dw_hdmi_phy.c
 create mode 100644 drivers/video/sunxi/sunxi_dw_hdmi_phy.h

Comments

Jagan Teki March 8, 2021, 7:57 a.m. UTC | #1
On Sun, Mar 7, 2021 at 1:25 AM Jernej Skrabec <jernej.skrabec@siol.net> wrote:
>
> This commit adds standalone driver for DW HDMI PHY. It deprecates code
> which is included in sunxi dw-hdmi platform driver.
>
> Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> ---
>  arch/arm/mach-sunxi/Kconfig             |   1 +
>  drivers/video/sunxi/Makefile            |   2 +-
>  drivers/video/sunxi/sunxi_dw_hdmi_phy.c | 423 ++++++++++++++++++++++++
>  drivers/video/sunxi/sunxi_dw_hdmi_phy.h |  24 ++

Would be good if this PHY management code handles via drivers/phy.
Hope this would possible?

Jagan.
Jernej Škrabec March 9, 2021, 5:38 a.m. UTC | #2
Dne ponedeljek, 08. marec 2021 ob 08:57:31 CET je Jagan Teki napisal(a):
> On Sun, Mar 7, 2021 at 1:25 AM Jernej Skrabec <jernej.skrabec@siol.net> 
wrote:
> > This commit adds standalone driver for DW HDMI PHY. It deprecates code
> > which is included in sunxi dw-hdmi platform driver.
> > 
> > Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> > ---
> > 
> >  arch/arm/mach-sunxi/Kconfig             |   1 +
> >  drivers/video/sunxi/Makefile            |   2 +-
> >  drivers/video/sunxi/sunxi_dw_hdmi_phy.c | 423 ++++++++++++++++++++++++
> >  drivers/video/sunxi/sunxi_dw_hdmi_phy.h |  24 ++
> 
> Would be good if this PHY management code handles via drivers/phy.
> Hope this would possible?

You mean move this code there? Yeah, it's possible, but then we have to move 
sunxi_dw_hdmi_phy.h to general or arch includes folder, because it implements 
additional functionality which is not covered by general PHY interface.

Best regards,
Jernej
diff mbox series

Patch

diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig
index 34ef1f4b030f..5f2df7727357 100644
--- a/arch/arm/mach-sunxi/Kconfig
+++ b/arch/arm/mach-sunxi/Kconfig
@@ -973,6 +973,7 @@  config VIDEO_DE2
 	select CLK_SUN8I_DE2
 	select DM_VIDEO
 	select DISPLAY
+	select PHY
 	select VIDEO_DW_HDMI
 	imply VIDEO_DT_SIMPLEFB
 	default y
diff --git a/drivers/video/sunxi/Makefile b/drivers/video/sunxi/Makefile
index 4321673312bf..22ec17fb4fd2 100644
--- a/drivers/video/sunxi/Makefile
+++ b/drivers/video/sunxi/Makefile
@@ -4,4 +4,4 @@ 
 # Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 
 obj-$(CONFIG_VIDEO_SUNXI) += sunxi_display.o simplefb_common.o lcdc.o tve_common.o ../videomodes.o
-obj-$(CONFIG_VIDEO_DE2) += sunxi_de2.o sunxi_dw_hdmi.o simplefb_common.o lcdc.o sunxi_lcd.o
+obj-$(CONFIG_VIDEO_DE2) += sunxi_de2.o sunxi_dw_hdmi.o sunxi_dw_hdmi_phy.o simplefb_common.o lcdc.o sunxi_lcd.o
diff --git a/drivers/video/sunxi/sunxi_dw_hdmi_phy.c b/drivers/video/sunxi/sunxi_dw_hdmi_phy.c
new file mode 100644
index 000000000000..bed5c2fdfe81
--- /dev/null
+++ b/drivers/video/sunxi/sunxi_dw_hdmi_phy.c
@@ -0,0 +1,423 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Allwinner DW HDMI PHY driver
+ *
+ * (C) Copyright 2021 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#include <clk.h>
+#include <common.h>
+#include <dm.h>
+#include <generic-phy.h>
+#include <reset.h>
+#include <time.h>
+#include <asm/io.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include "sunxi_dw_hdmi_phy.h"
+
+#define SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK		BIT(0)
+#define SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK	GENMASK(15, 8)
+#define SUN8I_HDMI_PHY_DBG_CTRL_POL_NHSYNC	BIT(8)
+#define SUN8I_HDMI_PHY_DBG_CTRL_POL_NVSYNC	BIT(9)
+#define SUN8I_HDMI_PHY_DBG_CTRL_ADDR_MASK	GENMASK(23, 16)
+#define SUN8I_HDMI_PHY_DBG_CTRL_ADDR(addr)	(addr << 16)
+
+#define SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN	BIT(31)
+
+#define SUN8I_HDMI_PHY_READ_EN_MAGIC		0x54524545
+
+#define SUN8I_HDMI_PHY_UNSCRAMBLE_MAGIC		0x42494E47
+
+#define SUN8I_HDMI_PHY_ANA_CFG1_REG_SWI		BIT(31)
+#define SUN8I_HDMI_PHY_ANA_CFG1_REG_PWEND	BIT(30)
+#define SUN8I_HDMI_PHY_ANA_CFG1_REG_PWENC	BIT(29)
+#define SUN8I_HDMI_PHY_ANA_CFG1_REG_CALSW	BIT(28)
+#define SUN8I_HDMI_PHY_ANA_CFG1_REG_SVRCAL(x)	((x) << 26)
+#define SUN8I_HDMI_PHY_ANA_CFG1_REG_SVBH(x)	((x) << 24)
+#define SUN8I_HDMI_PHY_ANA_CFG1_AMP_OPT		BIT(23)
+#define SUN8I_HDMI_PHY_ANA_CFG1_EMP_OPT		BIT(22)
+#define SUN8I_HDMI_PHY_ANA_CFG1_AMPCK_OPT	BIT(21)
+#define SUN8I_HDMI_PHY_ANA_CFG1_EMPCK_OPT	BIT(20)
+#define SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL		BIT(19)
+#define SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG		BIT(18)
+#define SUN8I_HDMI_PHY_ANA_CFG1_REG_SCKTMDS	BIT(17)
+#define SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN	BIT(16)
+#define SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK	GENMASK(15, 12)
+#define SUN8I_HDMI_PHY_ANA_CFG1_TXEN_ALL	(0xf << 12)
+#define SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK	BIT(11)
+#define SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2	BIT(10)
+#define SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1	BIT(9)
+#define SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0	BIT(8)
+#define SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDSCLK	BIT(7)
+#define SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2	BIT(6)
+#define SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1	BIT(5)
+#define SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0	BIT(4)
+#define SUN8I_HDMI_PHY_ANA_CFG1_CKEN		BIT(3)
+#define SUN8I_HDMI_PHY_ANA_CFG1_LDOEN		BIT(2)
+#define SUN8I_HDMI_PHY_ANA_CFG1_ENVBS		BIT(1)
+#define SUN8I_HDMI_PHY_ANA_CFG1_ENBI		BIT(0)
+
+#define SUN8I_HDMI_PHY_ANA_CFG2_M_EN		BIT(31)
+#define SUN8I_HDMI_PHY_ANA_CFG2_PLLDBEN		BIT(30)
+#define SUN8I_HDMI_PHY_ANA_CFG2_SEN		BIT(29)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_HPDPD	BIT(28)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_HPDEN	BIT(27)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_PLRCK	BIT(26)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_PLR(x)	((x) << 23)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_DENCK	BIT(22)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_DEN		BIT(21)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_CD(x)	((x) << 19)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_CKSS(x)	((x) << 17)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSWCK	BIT(16)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSW	BIT(15)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_CSMPS(x)	((x) << 13)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(x)	((x) << 10)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_BOOSTCK(x)	((x) << 8)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_BOOST(x)	((x) << 6)
+#define SUN8I_HDMI_PHY_ANA_CFG2_REG_RESDI(x)	((x) << 0)
+
+#define SUN8I_HDMI_PHY_ANA_CFG3_REG_SLOWCK(x)	((x) << 30)
+#define SUN8I_HDMI_PHY_ANA_CFG3_REG_SLOW(x)	((x) << 28)
+#define SUN8I_HDMI_PHY_ANA_CFG3_REG_WIRE(x)	((x) << 18)
+#define SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(x)	((x) << 14)
+#define SUN8I_HDMI_PHY_ANA_CFG3_REG_EMPCK(x)	((x) << 11)
+#define SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(x)	((x) << 7)
+#define SUN8I_HDMI_PHY_ANA_CFG3_REG_EMP(x)	((x) << 4)
+#define SUN8I_HDMI_PHY_ANA_CFG3_SDAPD		BIT(3)
+#define SUN8I_HDMI_PHY_ANA_CFG3_SDAEN		BIT(2)
+#define SUN8I_HDMI_PHY_ANA_CFG3_SCLPD		BIT(1)
+#define SUN8I_HDMI_PHY_ANA_CFG3_SCLEN		BIT(0)
+
+#define SUN8I_HDMI_PHY_PLL_CFG1_REG_OD1		BIT(31)
+#define SUN8I_HDMI_PHY_PLL_CFG1_REG_OD		BIT(30)
+#define SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN		BIT(29)
+#define SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN		BIT(28)
+#define SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33	BIT(27)
+#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK	BIT(26)
+#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT	26
+#define SUN8I_HDMI_PHY_PLL_CFG1_PLLEN		BIT(25)
+#define SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(x)	((x) << 22)
+#define SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(x)	((x) << 20)
+#define SUN8I_HDMI_PHY_PLL_CFG1_PLLDBEN		BIT(19)
+#define SUN8I_HDMI_PHY_PLL_CFG1_CS		BIT(18)
+#define SUN8I_HDMI_PHY_PLL_CFG1_CP_S(x)		((x) << 13)
+#define SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(x)	((x) << 7)
+#define SUN8I_HDMI_PHY_PLL_CFG1_BWS		BIT(6)
+#define SUN8I_HDMI_PHY_PLL_CFG1_B_IN_MSK	GENMASK(5, 0)
+#define SUN8I_HDMI_PHY_PLL_CFG1_B_IN_SHIFT	0
+
+#define SUN8I_HDMI_PHY_PLL_CFG2_SV_H		BIT(31)
+#define SUN8I_HDMI_PHY_PLL_CFG2_PDCLKSEL(x)	((x) << 29)
+#define SUN8I_HDMI_PHY_PLL_CFG2_CLKSTEP(x)	((x) << 27)
+#define SUN8I_HDMI_PHY_PLL_CFG2_PSET(x)		((x) << 24)
+#define SUN8I_HDMI_PHY_PLL_CFG2_PCLK_SEL	BIT(23)
+#define SUN8I_HDMI_PHY_PLL_CFG2_AUTOSYNC_DIS	BIT(22)
+#define SUN8I_HDMI_PHY_PLL_CFG2_VREG2_OUT_EN	BIT(21)
+#define SUN8I_HDMI_PHY_PLL_CFG2_VREG1_OUT_EN	BIT(20)
+#define SUN8I_HDMI_PHY_PLL_CFG2_VCOGAIN_EN	BIT(19)
+#define SUN8I_HDMI_PHY_PLL_CFG2_VCOGAIN(x)	((x) << 16)
+#define SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(x)	((x) << 12)
+#define SUN8I_HDMI_PHY_PLL_CFG2_VCO_RST_IN	BIT(11)
+#define SUN8I_HDMI_PHY_PLL_CFG2_SINT_FRAC	BIT(10)
+#define SUN8I_HDMI_PHY_PLL_CFG2_SDIV2		BIT(9)
+#define SUN8I_HDMI_PHY_PLL_CFG2_S(x)		((x) << 6)
+#define SUN8I_HDMI_PHY_PLL_CFG2_S6P25_7P5	BIT(5)
+#define SUN8I_HDMI_PHY_PLL_CFG2_S5_7		BIT(4)
+#define SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK	GENMASK(3, 0)
+#define SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_SHIFT	0
+#define SUN8I_HDMI_PHY_PLL_CFG2_PREDIV(x)	(((x) - 1) << 0)
+
+#define SUN8I_HDMI_PHY_PLL_CFG3_SOUT_DIV2	BIT(0)
+
+#define SUN8I_HDMI_PHY_ANA_STS_B_OUT_SHIFT	11
+#define SUN8I_HDMI_PHY_ANA_STS_B_OUT_MSK	GENMASK(16, 11)
+#define SUN8I_HDMI_PHY_ANA_STS_RCALEND2D	BIT(7)
+#define SUN8I_HDMI_PHY_ANA_STS_RCAL_MASK	GENMASK(5, 0)
+
+struct sunxi_hdmi_phy {
+	u32 dbg_ctrl;
+	u32 rext_ctrl;
+	u32 res1[2];
+	u32 read_en;
+	u32 unscramble;
+	u32 res2[2];
+	u32 ana_cfg1;
+	u32 ana_cfg2;
+	u32 ana_cfg3;
+	u32 pll_cfg1;
+	u32 pll_cfg2;
+	u32 pll_cfg3;
+	u32 ana_sts;
+};
+
+struct sunxi_dw_hdmi_phy_priv {
+	void *base;
+	struct clk clk_bus;
+	struct clk clk_mod;
+	uint rcal;
+	struct reset_ctl reset;
+};
+
+void sunxi_dw_hdmi_phy_set(struct phy *_phy, const struct display_timing *edid,
+			   int clk_div)
+{
+	struct sunxi_dw_hdmi_phy_priv *priv = dev_get_priv(_phy->dev);
+	struct sunxi_hdmi_phy *phy = priv->base;
+	u32 pll_cfg1, pll_cfg2, ana_cfg1, ana_cfg2, ana_cfg3;
+	u32 tmp, b_offset = 0;
+
+	/* bandwidth / frequency independent settings */
+
+	pll_cfg1 = SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN |
+		   SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN |
+		   SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(7) |
+		   SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(1) |
+		   SUN8I_HDMI_PHY_PLL_CFG1_PLLDBEN |
+		   SUN8I_HDMI_PHY_PLL_CFG1_CS |
+		   SUN8I_HDMI_PHY_PLL_CFG1_CP_S(2) |
+		   SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(63) |
+		   SUN8I_HDMI_PHY_PLL_CFG1_BWS;
+
+	pll_cfg2 = SUN8I_HDMI_PHY_PLL_CFG2_SV_H |
+		   SUN8I_HDMI_PHY_PLL_CFG2_VCOGAIN_EN |
+		   SUN8I_HDMI_PHY_PLL_CFG2_SDIV2;
+
+	ana_cfg1 = SUN8I_HDMI_PHY_ANA_CFG1_REG_SVBH(1) |
+		   SUN8I_HDMI_PHY_ANA_CFG1_AMP_OPT |
+		   SUN8I_HDMI_PHY_ANA_CFG1_EMP_OPT |
+		   SUN8I_HDMI_PHY_ANA_CFG1_AMPCK_OPT |
+		   SUN8I_HDMI_PHY_ANA_CFG1_EMPCK_OPT |
+		   SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL |
+		   SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG |
+		   SUN8I_HDMI_PHY_ANA_CFG1_REG_SCKTMDS |
+		   SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN |
+		   SUN8I_HDMI_PHY_ANA_CFG1_TXEN_ALL |
+		   SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK |
+		   SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2 |
+		   SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1 |
+		   SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0 |
+		   SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2 |
+		   SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1 |
+		   SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0 |
+		   SUN8I_HDMI_PHY_ANA_CFG1_CKEN |
+		   SUN8I_HDMI_PHY_ANA_CFG1_LDOEN |
+		   SUN8I_HDMI_PHY_ANA_CFG1_ENVBS |
+		   SUN8I_HDMI_PHY_ANA_CFG1_ENBI;
+
+	ana_cfg2 = SUN8I_HDMI_PHY_ANA_CFG2_M_EN |
+		   SUN8I_HDMI_PHY_ANA_CFG2_REG_DENCK |
+		   SUN8I_HDMI_PHY_ANA_CFG2_REG_DEN |
+		   SUN8I_HDMI_PHY_ANA_CFG2_REG_CKSS(1) |
+		   SUN8I_HDMI_PHY_ANA_CFG2_REG_CSMPS(1);
+
+	ana_cfg3 = SUN8I_HDMI_PHY_ANA_CFG3_REG_WIRE(0x3e0) |
+		   SUN8I_HDMI_PHY_ANA_CFG3_SDAEN |
+		   SUN8I_HDMI_PHY_ANA_CFG3_SCLEN;
+
+	/* bandwidth / frequency dependent settings */
+	if (edid->pixelclock.typ <= 27000000) {
+		pll_cfg1 |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 |
+			    SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32);
+		pll_cfg2 |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) |
+			    SUN8I_HDMI_PHY_PLL_CFG2_S(4);
+		ana_cfg1 |= SUN8I_HDMI_PHY_ANA_CFG1_REG_CALSW;
+		ana_cfg2 |= SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4) |
+			    SUN8I_HDMI_PHY_ANA_CFG2_REG_RESDI(priv->rcal);
+		ana_cfg3 |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(3) |
+			    SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(5);
+	} else if (edid->pixelclock.typ <= 74250000) {
+		pll_cfg1 |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 |
+			    SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32);
+		pll_cfg2 |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) |
+			    SUN8I_HDMI_PHY_PLL_CFG2_S(5);
+		ana_cfg1 |= SUN8I_HDMI_PHY_ANA_CFG1_REG_CALSW;
+		ana_cfg2 |= SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4) |
+			    SUN8I_HDMI_PHY_ANA_CFG2_REG_RESDI(priv->rcal);
+		ana_cfg3 |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(5) |
+			    SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(7);
+	} else if (edid->pixelclock.typ <= 148500000) {
+		pll_cfg1 |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 |
+			    SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32);
+		pll_cfg2 |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) |
+			    SUN8I_HDMI_PHY_PLL_CFG2_S(6);
+		ana_cfg2 |= SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSWCK |
+			    SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSW |
+			    SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(2);
+		ana_cfg3 |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(7) |
+			    SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(9);
+	} else {
+		b_offset = 2;
+		pll_cfg1 |= SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(63);
+		pll_cfg2 |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(6) |
+			    SUN8I_HDMI_PHY_PLL_CFG2_S(7);
+		ana_cfg2 |= SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSWCK |
+			    SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSW |
+			    SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4);
+		ana_cfg3 |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(9) |
+			    SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(13) |
+			    SUN8I_HDMI_PHY_ANA_CFG3_REG_EMP(3);
+	}
+
+	writel(pll_cfg1, &phy->pll_cfg1);
+	writel(pll_cfg2 | (clk_div - 1), &phy->pll_cfg2);
+	mdelay(10);
+	writel(SUN8I_HDMI_PHY_PLL_CFG3_SOUT_DIV2, &phy->pll_cfg3);
+	setbits_le32(&phy->pll_cfg1, SUN8I_HDMI_PHY_PLL_CFG1_PLLEN);
+	mdelay(100);
+	tmp = readl(&phy->ana_sts) & SUN8I_HDMI_PHY_ANA_STS_B_OUT_MSK;
+	tmp >>= SUN8I_HDMI_PHY_ANA_STS_B_OUT_SHIFT;
+	tmp = min(tmp + b_offset, (u32)0x3f);
+	setbits_le32(&phy->pll_cfg1,
+		     SUN8I_HDMI_PHY_PLL_CFG1_REG_OD1 |
+		     SUN8I_HDMI_PHY_PLL_CFG1_REG_OD);
+	setbits_le32(&phy->pll_cfg1, tmp);
+	mdelay(100);
+	writel(ana_cfg1, &phy->ana_cfg1);
+	writel(ana_cfg2, &phy->ana_cfg2);
+	writel(ana_cfg3, &phy->ana_cfg3);
+
+	if (edid->flags & DISPLAY_FLAGS_VSYNC_LOW)
+		setbits_le32(&phy->dbg_ctrl,
+			     SUN8I_HDMI_PHY_DBG_CTRL_POL_NVSYNC);
+
+	if (edid->flags & DISPLAY_FLAGS_HSYNC_LOW)
+		setbits_le32(&phy->dbg_ctrl,
+			     SUN8I_HDMI_PHY_DBG_CTRL_POL_NHSYNC);
+
+	setbits_le32(&phy->ana_cfg1, SUN8I_HDMI_PHY_ANA_CFG1_TXEN_ALL);
+}
+
+static int sunxi_dw_hdmi_phy_init(struct phy *phy)
+{
+	struct sunxi_dw_hdmi_phy_priv *priv = dev_get_priv(phy->dev);
+	int ret;
+
+	ret = reset_deassert(&priv->reset);
+	if (ret)
+		return ret;
+
+	ret = clk_enable(&priv->clk_bus);
+	if (ret)
+		return ret;
+
+	ret = clk_enable(&priv->clk_mod);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int sunxi_dw_hdmi_phy_power_on(struct phy *_phy)
+{
+	struct sunxi_dw_hdmi_phy_priv *priv = dev_get_priv(_phy->dev);
+	struct sunxi_hdmi_phy *phy = priv->base;
+	unsigned long tmo;
+
+	/* enable read access to HDMI controller */
+	writel(SUN8I_HDMI_PHY_READ_EN_MAGIC, &phy->read_en);
+	/* descramble register offsets */
+	writel(SUN8I_HDMI_PHY_UNSCRAMBLE_MAGIC, &phy->unscramble);
+
+	writel(0, &phy->ana_cfg1);
+	setbits_le32(&phy->ana_cfg1, SUN8I_HDMI_PHY_ANA_CFG1_ENBI);
+	udelay(5);
+	setbits_le32(&phy->ana_cfg1, SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN);
+	setbits_le32(&phy->ana_cfg1, SUN8I_HDMI_PHY_ANA_CFG1_ENVBS);
+	udelay(10);
+	setbits_le32(&phy->ana_cfg1, SUN8I_HDMI_PHY_ANA_CFG1_LDOEN);
+	udelay(5);
+	setbits_le32(&phy->ana_cfg1, SUN8I_HDMI_PHY_ANA_CFG1_CKEN);
+	udelay(40);
+	setbits_le32(&phy->ana_cfg1, SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL);
+	udelay(100);
+	setbits_le32(&phy->ana_cfg1, SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG);
+	setbits_le32(&phy->ana_cfg1,
+		     SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0 |
+		     SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1 |
+		     SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2);
+
+	/* Note that Allwinner code doesn't fail in case of timeout */
+	tmo = timer_get_us() + 2000;
+	while ((readl(&phy->ana_sts) & SUN8I_HDMI_PHY_ANA_STS_RCALEND2D) == 0) {
+		if (timer_get_us() > tmo) {
+			printf("Warning: HDMI PHY init timeout!\n");
+			break;
+		}
+	}
+
+	setbits_le32(&phy->ana_cfg1,
+		     SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0 |
+		     SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1 |
+		     SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2 |
+		     SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK);
+	setbits_le32(&phy->ana_cfg1, SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDSCLK);
+
+	/* enable DDC communication */
+	writel(SUN8I_HDMI_PHY_ANA_CFG3_SCLEN |
+	       SUN8I_HDMI_PHY_ANA_CFG3_SDAEN, &phy->ana_cfg3);
+
+	priv->rcal = readl(&phy->ana_sts) & SUN8I_HDMI_PHY_ANA_STS_RCAL_MASK;
+	priv->rcal >>= 2;
+
+	return 0;
+}
+
+/*
+ * This callback is abused for executing code after last register in
+ * controller is set.
+ */
+static int sunxi_dw_hdmi_phy_exit(struct phy *_phy)
+{
+	struct sunxi_dw_hdmi_phy_priv *priv = dev_get_priv(_phy->dev);
+	struct sunxi_hdmi_phy *phy = priv->base;
+
+	writel(0, &phy->unscramble);
+
+	return 0;
+}
+
+static struct phy_ops sunxi_dw_hdmi_phy_ops = {
+	.init = sunxi_dw_hdmi_phy_init,
+	.power_on = sunxi_dw_hdmi_phy_power_on,
+	.exit = sunxi_dw_hdmi_phy_exit,
+};
+
+static int sunxi_dw_hdmi_phy_probe(struct udevice *dev)
+{
+	struct sunxi_dw_hdmi_phy_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	priv->base = dev_read_addr_ptr(dev);
+	if (!priv->base)
+		return -EINVAL;
+
+	ret = reset_get_by_index(dev, 0, &priv->reset);
+	if (ret)
+		return ret;
+
+	ret = clk_get_by_name(dev, "bus", &priv->clk_bus);
+	if (ret)
+		return ret;
+
+	ret = clk_get_by_name(dev, "mod", &priv->clk_mod);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct udevice_id sunxi_dw_hdmi_phy_ids[] = {
+	{ .compatible = "allwinner,sun8i-h3-hdmi-phy" },
+	{ .compatible = "allwinner,sun50i-a64-hdmi-phy" },
+	{ }
+};
+
+U_BOOT_DRIVER(sunxi_dw_hdmi_phy) = {
+	.name		= "sunxi_dw_hdmi_phy",
+	.id		= UCLASS_PHY,
+	.of_match	= sunxi_dw_hdmi_phy_ids,
+	.ops		= &sunxi_dw_hdmi_phy_ops,
+	.probe		= sunxi_dw_hdmi_phy_probe,
+	.priv_auto	= sizeof(struct sunxi_dw_hdmi_phy_priv),
+};
diff --git a/drivers/video/sunxi/sunxi_dw_hdmi_phy.h b/drivers/video/sunxi/sunxi_dw_hdmi_phy.h
new file mode 100644
index 000000000000..78f85a413aaf
--- /dev/null
+++ b/drivers/video/sunxi/sunxi_dw_hdmi_phy.h
@@ -0,0 +1,24 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Copyright 2021 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#ifndef __SUNXI_DW_HDMI_PHY_H
+#define __SUNXI_DW_HDMI_PHY_H
+
+#include <edid.h>
+
+/**
+ * sunxi_dw_hdmi_phy_set() - configure sunxi DW HDMI PHY
+ *
+ * Configure Sunxi DW HDMI PHY as found in A64, H3, H5 and other SoCs according
+ * to speficied mode.
+ *
+ * @phy: PHY port
+ * @edid: timing to configure
+ * @clk_div: Clock divider to set
+ */
+void sunxi_dw_hdmi_phy_set(struct phy *phy, const struct display_timing *edid,
+			   int clk_div);
+
+#endif