summaryrefslogtreecommitdiffstats
path: root/drivers/phy/mediatek
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--drivers/phy/mediatek/Kconfig46
-rw-r--r--drivers/phy/mediatek/Makefile13
-rw-r--r--drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c249
-rw-r--r--drivers/phy/mediatek/phy-mtk-hdmi-mt8173.c282
-rw-r--r--drivers/phy/mediatek/phy-mtk-hdmi.c215
-rw-r--r--drivers/phy/mediatek/phy-mtk-hdmi.h56
-rw-r--r--drivers/phy/mediatek/phy-mtk-tphy.c1214
-rw-r--r--drivers/phy/mediatek/phy-mtk-ufs.c245
-rw-r--r--drivers/phy/mediatek/phy-mtk-xsphy.c600
9 files changed, 2920 insertions, 0 deletions
diff --git a/drivers/phy/mediatek/Kconfig b/drivers/phy/mediatek/Kconfig
new file mode 100644
index 000000000..43150608d
--- /dev/null
+++ b/drivers/phy/mediatek/Kconfig
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Phy drivers for Mediatek devices
+#
+config PHY_MTK_TPHY
+ tristate "MediaTek T-PHY Driver"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on OF
+ select GENERIC_PHY
+ help
+ Say 'Y' here to add support for MediaTek T-PHY driver,
+ it supports multiple usb2.0, usb3.0 ports, PCIe and
+ SATA, and meanwhile supports two version T-PHY which have
+ different banks layout, the T-PHY with shared banks between
+ multi-ports is first version, otherwise is second version,
+ so you can easily distinguish them by banks layout.
+
+config PHY_MTK_UFS
+ tristate "MediaTek UFS M-PHY driver"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on OF
+ select GENERIC_PHY
+ help
+ Support for UFS M-PHY on MediaTek chipsets.
+ Enable this to provide vendor-specific probing,
+ initialization, power on and power off flow of
+ specified M-PHYs.
+
+config PHY_MTK_XSPHY
+ tristate "MediaTek XS-PHY Driver"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on OF
+ select GENERIC_PHY
+ help
+ Enable this to support the SuperSpeedPlus XS-PHY transceiver for
+ USB3.1 GEN2 controllers on MediaTek chips. The driver supports
+ multiple USB2.0, USB3.1 GEN2 ports.
+
+config PHY_MTK_HDMI
+ tristate "MediaTek HDMI-PHY Driver"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on COMMON_CLK
+ depends on OF
+ select GENERIC_PHY
+ help
+ Support HDMI PHY for Mediatek SoCs.
diff --git a/drivers/phy/mediatek/Makefile b/drivers/phy/mediatek/Makefile
new file mode 100644
index 000000000..6325e3870
--- /dev/null
+++ b/drivers/phy/mediatek/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the phy drivers.
+#
+
+obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
+obj-$(CONFIG_PHY_MTK_UFS) += phy-mtk-ufs.o
+obj-$(CONFIG_PHY_MTK_XSPHY) += phy-mtk-xsphy.o
+
+phy-mtk-hdmi-drv-y := phy-mtk-hdmi.o
+phy-mtk-hdmi-drv-y += phy-mtk-hdmi-mt2701.o
+phy-mtk-hdmi-drv-y += phy-mtk-hdmi-mt8173.o
+obj-$(CONFIG_PHY_MTK_HDMI) += phy-mtk-hdmi-drv.o
diff --git a/drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c b/drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c
new file mode 100644
index 000000000..b74c65a17
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Chunhui Dai <chunhui.dai@mediatek.com>
+ */
+
+#include "phy-mtk-hdmi.h"
+
+#define HDMI_CON0 0x00
+#define RG_HDMITX_DRV_IBIAS 0
+#define RG_HDMITX_DRV_IBIAS_MASK (0x3f << 0)
+#define RG_HDMITX_EN_SER 12
+#define RG_HDMITX_EN_SER_MASK (0x0f << 12)
+#define RG_HDMITX_EN_SLDO 16
+#define RG_HDMITX_EN_SLDO_MASK (0x0f << 16)
+#define RG_HDMITX_EN_PRED 20
+#define RG_HDMITX_EN_PRED_MASK (0x0f << 20)
+#define RG_HDMITX_EN_IMP 24
+#define RG_HDMITX_EN_IMP_MASK (0x0f << 24)
+#define RG_HDMITX_EN_DRV 28
+#define RG_HDMITX_EN_DRV_MASK (0x0f << 28)
+
+#define HDMI_CON1 0x04
+#define RG_HDMITX_PRED_IBIAS 18
+#define RG_HDMITX_PRED_IBIAS_MASK (0x0f << 18)
+#define RG_HDMITX_PRED_IMP (0x01 << 22)
+#define RG_HDMITX_DRV_IMP 26
+#define RG_HDMITX_DRV_IMP_MASK (0x3f << 26)
+
+#define HDMI_CON2 0x08
+#define RG_HDMITX_EN_TX_CKLDO (0x01 << 0)
+#define RG_HDMITX_EN_TX_POSDIV (0x01 << 1)
+#define RG_HDMITX_TX_POSDIV 3
+#define RG_HDMITX_TX_POSDIV_MASK (0x03 << 3)
+#define RG_HDMITX_EN_MBIAS (0x01 << 6)
+#define RG_HDMITX_MBIAS_LPF_EN (0x01 << 7)
+
+#define HDMI_CON4 0x10
+#define RG_HDMITX_RESERVE_MASK (0xffffffff << 0)
+
+#define HDMI_CON6 0x18
+#define RG_HTPLL_BR 0
+#define RG_HTPLL_BR_MASK (0x03 << 0)
+#define RG_HTPLL_BC 2
+#define RG_HTPLL_BC_MASK (0x03 << 2)
+#define RG_HTPLL_BP 4
+#define RG_HTPLL_BP_MASK (0x0f << 4)
+#define RG_HTPLL_IR 8
+#define RG_HTPLL_IR_MASK (0x0f << 8)
+#define RG_HTPLL_IC 12
+#define RG_HTPLL_IC_MASK (0x0f << 12)
+#define RG_HTPLL_POSDIV 16
+#define RG_HTPLL_POSDIV_MASK (0x03 << 16)
+#define RG_HTPLL_PREDIV 18
+#define RG_HTPLL_PREDIV_MASK (0x03 << 18)
+#define RG_HTPLL_FBKSEL 20
+#define RG_HTPLL_FBKSEL_MASK (0x03 << 20)
+#define RG_HTPLL_RLH_EN (0x01 << 22)
+#define RG_HTPLL_FBKDIV 24
+#define RG_HTPLL_FBKDIV_MASK (0x7f << 24)
+#define RG_HTPLL_EN (0x01 << 31)
+
+#define HDMI_CON7 0x1c
+#define RG_HTPLL_AUTOK_EN (0x01 << 23)
+#define RG_HTPLL_DIVEN 28
+#define RG_HTPLL_DIVEN_MASK (0x07 << 28)
+
+static int mtk_hdmi_pll_prepare(struct clk_hw *hw)
+{
+ struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON7, RG_HTPLL_AUTOK_EN);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_RLH_EN);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_POSDIV_MASK);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON2, RG_HDMITX_EN_MBIAS);
+ usleep_range(80, 100);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_EN);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON2, RG_HDMITX_EN_TX_CKLDO);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_SLDO_MASK);
+ usleep_range(80, 100);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON2, RG_HDMITX_MBIAS_LPF_EN);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_SER_MASK);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_PRED_MASK);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_DRV_MASK);
+ usleep_range(80, 100);
+ return 0;
+}
+
+static void mtk_hdmi_pll_unprepare(struct clk_hw *hw)
+{
+ struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_DRV_MASK);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_PRED_MASK);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_SER_MASK);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON2, RG_HDMITX_MBIAS_LPF_EN);
+ usleep_range(80, 100);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_SLDO_MASK);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON2, RG_HDMITX_EN_TX_CKLDO);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_EN);
+ usleep_range(80, 100);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON2, RG_HDMITX_EN_MBIAS);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_POSDIV_MASK);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_RLH_EN);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON7, RG_HTPLL_AUTOK_EN);
+ usleep_range(80, 100);
+}
+
+static long mtk_hdmi_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ return rate;
+}
+
+static int mtk_hdmi_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+ u32 pos_div;
+
+ if (rate <= 64000000)
+ pos_div = 3;
+ else if (rate <= 128000000)
+ pos_div = 2;
+ else
+ pos_div = 1;
+
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_PREDIV_MASK);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_POSDIV_MASK);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON2, RG_HDMITX_EN_TX_POSDIV);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON6, (0x1 << RG_HTPLL_IC),
+ RG_HTPLL_IC_MASK);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON6, (0x1 << RG_HTPLL_IR),
+ RG_HTPLL_IR_MASK);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON2, (pos_div << RG_HDMITX_TX_POSDIV),
+ RG_HDMITX_TX_POSDIV_MASK);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON6, (1 << RG_HTPLL_FBKSEL),
+ RG_HTPLL_FBKSEL_MASK);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON6, (19 << RG_HTPLL_FBKDIV),
+ RG_HTPLL_FBKDIV_MASK);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON7, (0x2 << RG_HTPLL_DIVEN),
+ RG_HTPLL_DIVEN_MASK);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON6, (0xc << RG_HTPLL_BP),
+ RG_HTPLL_BP_MASK);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON6, (0x2 << RG_HTPLL_BC),
+ RG_HTPLL_BC_MASK);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON6, (0x1 << RG_HTPLL_BR),
+ RG_HTPLL_BR_MASK);
+
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PRED_IMP);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, (0x3 << RG_HDMITX_PRED_IBIAS),
+ RG_HDMITX_PRED_IBIAS_MASK);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_IMP_MASK);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, (0x28 << RG_HDMITX_DRV_IMP),
+ RG_HDMITX_DRV_IMP_MASK);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON4, 0x28, RG_HDMITX_RESERVE_MASK);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, (0xa << RG_HDMITX_DRV_IBIAS),
+ RG_HDMITX_DRV_IBIAS_MASK);
+ return 0;
+}
+
+static unsigned long mtk_hdmi_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+ unsigned long out_rate, val;
+
+ val = (readl(hdmi_phy->regs + HDMI_CON6)
+ & RG_HTPLL_PREDIV_MASK) >> RG_HTPLL_PREDIV;
+ switch (val) {
+ case 0x00:
+ out_rate = parent_rate;
+ break;
+ case 0x01:
+ out_rate = parent_rate / 2;
+ break;
+ default:
+ out_rate = parent_rate / 4;
+ break;
+ }
+
+ val = (readl(hdmi_phy->regs + HDMI_CON6)
+ & RG_HTPLL_FBKDIV_MASK) >> RG_HTPLL_FBKDIV;
+ out_rate *= (val + 1) * 2;
+ val = (readl(hdmi_phy->regs + HDMI_CON2)
+ & RG_HDMITX_TX_POSDIV_MASK);
+ out_rate >>= (val >> RG_HDMITX_TX_POSDIV);
+
+ if (readl(hdmi_phy->regs + HDMI_CON2) & RG_HDMITX_EN_TX_POSDIV)
+ out_rate /= 5;
+
+ return out_rate;
+}
+
+static const struct clk_ops mtk_hdmi_phy_pll_ops = {
+ .prepare = mtk_hdmi_pll_prepare,
+ .unprepare = mtk_hdmi_pll_unprepare,
+ .set_rate = mtk_hdmi_pll_set_rate,
+ .round_rate = mtk_hdmi_pll_round_rate,
+ .recalc_rate = mtk_hdmi_pll_recalc_rate,
+};
+
+static void mtk_hdmi_phy_enable_tmds(struct mtk_hdmi_phy *hdmi_phy)
+{
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON7, RG_HTPLL_AUTOK_EN);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_RLH_EN);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_POSDIV_MASK);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON2, RG_HDMITX_EN_MBIAS);
+ usleep_range(80, 100);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_EN);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON2, RG_HDMITX_EN_TX_CKLDO);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_SLDO_MASK);
+ usleep_range(80, 100);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON2, RG_HDMITX_MBIAS_LPF_EN);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_SER_MASK);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_PRED_MASK);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_DRV_MASK);
+ usleep_range(80, 100);
+}
+
+static void mtk_hdmi_phy_disable_tmds(struct mtk_hdmi_phy *hdmi_phy)
+{
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_DRV_MASK);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_PRED_MASK);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_SER_MASK);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON2, RG_HDMITX_MBIAS_LPF_EN);
+ usleep_range(80, 100);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_EN_SLDO_MASK);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON2, RG_HDMITX_EN_TX_CKLDO);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_EN);
+ usleep_range(80, 100);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON2, RG_HDMITX_EN_MBIAS);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_POSDIV_MASK);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON6, RG_HTPLL_RLH_EN);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON7, RG_HTPLL_AUTOK_EN);
+ usleep_range(80, 100);
+}
+
+struct mtk_hdmi_phy_conf mtk_hdmi_phy_2701_conf = {
+ .flags = CLK_SET_RATE_GATE,
+ .pll_default_off = true,
+ .hdmi_phy_clk_ops = &mtk_hdmi_phy_pll_ops,
+ .hdmi_phy_enable_tmds = mtk_hdmi_phy_enable_tmds,
+ .hdmi_phy_disable_tmds = mtk_hdmi_phy_disable_tmds,
+};
+
+MODULE_AUTHOR("Chunhui Dai <chunhui.dai@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek HDMI PHY Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/mediatek/phy-mtk-hdmi-mt8173.c b/drivers/phy/mediatek/phy-mtk-hdmi-mt8173.c
new file mode 100644
index 000000000..6cdfdf5a6
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt8173.c
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ */
+
+#include "phy-mtk-hdmi.h"
+
+#define HDMI_CON0 0x00
+#define RG_HDMITX_PLL_EN BIT(31)
+#define RG_HDMITX_PLL_FBKDIV (0x7f << 24)
+#define PLL_FBKDIV_SHIFT 24
+#define RG_HDMITX_PLL_FBKSEL (0x3 << 22)
+#define PLL_FBKSEL_SHIFT 22
+#define RG_HDMITX_PLL_PREDIV (0x3 << 20)
+#define PREDIV_SHIFT 20
+#define RG_HDMITX_PLL_POSDIV (0x3 << 18)
+#define POSDIV_SHIFT 18
+#define RG_HDMITX_PLL_RST_DLY (0x3 << 16)
+#define RG_HDMITX_PLL_IR (0xf << 12)
+#define PLL_IR_SHIFT 12
+#define RG_HDMITX_PLL_IC (0xf << 8)
+#define PLL_IC_SHIFT 8
+#define RG_HDMITX_PLL_BP (0xf << 4)
+#define PLL_BP_SHIFT 4
+#define RG_HDMITX_PLL_BR (0x3 << 2)
+#define PLL_BR_SHIFT 2
+#define RG_HDMITX_PLL_BC (0x3 << 0)
+#define PLL_BC_SHIFT 0
+#define HDMI_CON1 0x04
+#define RG_HDMITX_PLL_DIVEN (0x7 << 29)
+#define PLL_DIVEN_SHIFT 29
+#define RG_HDMITX_PLL_AUTOK_EN BIT(28)
+#define RG_HDMITX_PLL_AUTOK_KF (0x3 << 26)
+#define RG_HDMITX_PLL_AUTOK_KS (0x3 << 24)
+#define RG_HDMITX_PLL_AUTOK_LOAD BIT(23)
+#define RG_HDMITX_PLL_BAND (0x3f << 16)
+#define RG_HDMITX_PLL_REF_SEL BIT(15)
+#define RG_HDMITX_PLL_BIAS_EN BIT(14)
+#define RG_HDMITX_PLL_BIAS_LPF_EN BIT(13)
+#define RG_HDMITX_PLL_TXDIV_EN BIT(12)
+#define RG_HDMITX_PLL_TXDIV (0x3 << 10)
+#define PLL_TXDIV_SHIFT 10
+#define RG_HDMITX_PLL_LVROD_EN BIT(9)
+#define RG_HDMITX_PLL_MONVC_EN BIT(8)
+#define RG_HDMITX_PLL_MONCK_EN BIT(7)
+#define RG_HDMITX_PLL_MONREF_EN BIT(6)
+#define RG_HDMITX_PLL_TST_EN BIT(5)
+#define RG_HDMITX_PLL_TST_CK_EN BIT(4)
+#define RG_HDMITX_PLL_TST_SEL (0xf << 0)
+#define HDMI_CON2 0x08
+#define RGS_HDMITX_PLL_AUTOK_BAND (0x7f << 8)
+#define RGS_HDMITX_PLL_AUTOK_FAIL BIT(1)
+#define RG_HDMITX_EN_TX_CKLDO BIT(0)
+#define HDMI_CON3 0x0c
+#define RG_HDMITX_SER_EN (0xf << 28)
+#define RG_HDMITX_PRD_EN (0xf << 24)
+#define RG_HDMITX_PRD_IMP_EN (0xf << 20)
+#define RG_HDMITX_DRV_EN (0xf << 16)
+#define RG_HDMITX_DRV_IMP_EN (0xf << 12)
+#define DRV_IMP_EN_SHIFT 12
+#define RG_HDMITX_MHLCK_FORCE BIT(10)
+#define RG_HDMITX_MHLCK_PPIX_EN BIT(9)
+#define RG_HDMITX_MHLCK_EN BIT(8)
+#define RG_HDMITX_SER_DIN_SEL (0xf << 4)
+#define RG_HDMITX_SER_5T1_BIST_EN BIT(3)
+#define RG_HDMITX_SER_BIST_TOG BIT(2)
+#define RG_HDMITX_SER_DIN_TOG BIT(1)
+#define RG_HDMITX_SER_CLKDIG_INV BIT(0)
+#define HDMI_CON4 0x10
+#define RG_HDMITX_PRD_IBIAS_CLK (0xf << 24)
+#define RG_HDMITX_PRD_IBIAS_D2 (0xf << 16)
+#define RG_HDMITX_PRD_IBIAS_D1 (0xf << 8)
+#define RG_HDMITX_PRD_IBIAS_D0 (0xf << 0)
+#define PRD_IBIAS_CLK_SHIFT 24
+#define PRD_IBIAS_D2_SHIFT 16
+#define PRD_IBIAS_D1_SHIFT 8
+#define PRD_IBIAS_D0_SHIFT 0
+#define HDMI_CON5 0x14
+#define RG_HDMITX_DRV_IBIAS_CLK (0x3f << 24)
+#define RG_HDMITX_DRV_IBIAS_D2 (0x3f << 16)
+#define RG_HDMITX_DRV_IBIAS_D1 (0x3f << 8)
+#define RG_HDMITX_DRV_IBIAS_D0 (0x3f << 0)
+#define DRV_IBIAS_CLK_SHIFT 24
+#define DRV_IBIAS_D2_SHIFT 16
+#define DRV_IBIAS_D1_SHIFT 8
+#define DRV_IBIAS_D0_SHIFT 0
+#define HDMI_CON6 0x18
+#define RG_HDMITX_DRV_IMP_CLK (0x3f << 24)
+#define RG_HDMITX_DRV_IMP_D2 (0x3f << 16)
+#define RG_HDMITX_DRV_IMP_D1 (0x3f << 8)
+#define RG_HDMITX_DRV_IMP_D0 (0x3f << 0)
+#define DRV_IMP_CLK_SHIFT 24
+#define DRV_IMP_D2_SHIFT 16
+#define DRV_IMP_D1_SHIFT 8
+#define DRV_IMP_D0_SHIFT 0
+#define HDMI_CON7 0x1c
+#define RG_HDMITX_MHLCK_DRV_IBIAS (0x1f << 27)
+#define RG_HDMITX_SER_DIN (0x3ff << 16)
+#define RG_HDMITX_CHLDC_TST (0xf << 12)
+#define RG_HDMITX_CHLCK_TST (0xf << 8)
+#define RG_HDMITX_RESERVE (0xff << 0)
+#define HDMI_CON8 0x20
+#define RGS_HDMITX_2T1_LEV (0xf << 16)
+#define RGS_HDMITX_2T1_EDG (0xf << 12)
+#define RGS_HDMITX_5T1_LEV (0xf << 8)
+#define RGS_HDMITX_5T1_EDG (0xf << 4)
+#define RGS_HDMITX_PLUG_TST BIT(0)
+
+static int mtk_hdmi_pll_prepare(struct clk_hw *hw)
+{
+ struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_AUTOK_EN);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_POSDIV);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON3, RG_HDMITX_MHLCK_EN);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_BIAS_EN);
+ usleep_range(100, 150);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_EN);
+ usleep_range(100, 150);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_BIAS_LPF_EN);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_TXDIV_EN);
+
+ return 0;
+}
+
+static void mtk_hdmi_pll_unprepare(struct clk_hw *hw)
+{
+ struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_TXDIV_EN);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_BIAS_LPF_EN);
+ usleep_range(100, 150);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_EN);
+ usleep_range(100, 150);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_BIAS_EN);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_POSDIV);
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_AUTOK_EN);
+ usleep_range(100, 150);
+}
+
+static long mtk_hdmi_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+ hdmi_phy->pll_rate = rate;
+ if (rate <= 74250000)
+ *parent_rate = rate;
+ else
+ *parent_rate = rate / 2;
+
+ return rate;
+}
+
+static int mtk_hdmi_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+ unsigned int pre_div;
+ unsigned int div;
+ unsigned int pre_ibias;
+ unsigned int hdmi_ibias;
+ unsigned int imp_en;
+
+ dev_dbg(hdmi_phy->dev, "%s: %lu Hz, parent: %lu Hz\n", __func__,
+ rate, parent_rate);
+
+ if (rate <= 27000000) {
+ pre_div = 0;
+ div = 3;
+ } else if (rate <= 74250000) {
+ pre_div = 1;
+ div = 2;
+ } else {
+ pre_div = 1;
+ div = 1;
+ }
+
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+ (pre_div << PREDIV_SHIFT), RG_HDMITX_PLL_PREDIV);
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_POSDIV);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+ (0x1 << PLL_IC_SHIFT) | (0x1 << PLL_IR_SHIFT),
+ RG_HDMITX_PLL_IC | RG_HDMITX_PLL_IR);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1,
+ (div << PLL_TXDIV_SHIFT), RG_HDMITX_PLL_TXDIV);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+ (0x1 << PLL_FBKSEL_SHIFT) | (19 << PLL_FBKDIV_SHIFT),
+ RG_HDMITX_PLL_FBKSEL | RG_HDMITX_PLL_FBKDIV);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1,
+ (0x2 << PLL_DIVEN_SHIFT), RG_HDMITX_PLL_DIVEN);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0,
+ (0xc << PLL_BP_SHIFT) | (0x2 << PLL_BC_SHIFT) |
+ (0x1 << PLL_BR_SHIFT),
+ RG_HDMITX_PLL_BP | RG_HDMITX_PLL_BC |
+ RG_HDMITX_PLL_BR);
+ if (rate < 165000000) {
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON3,
+ RG_HDMITX_PRD_IMP_EN);
+ pre_ibias = 0x3;
+ imp_en = 0x0;
+ hdmi_ibias = hdmi_phy->ibias;
+ } else {
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON3,
+ RG_HDMITX_PRD_IMP_EN);
+ pre_ibias = 0x6;
+ imp_en = 0xf;
+ hdmi_ibias = hdmi_phy->ibias_up;
+ }
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON4,
+ (pre_ibias << PRD_IBIAS_CLK_SHIFT) |
+ (pre_ibias << PRD_IBIAS_D2_SHIFT) |
+ (pre_ibias << PRD_IBIAS_D1_SHIFT) |
+ (pre_ibias << PRD_IBIAS_D0_SHIFT),
+ RG_HDMITX_PRD_IBIAS_CLK |
+ RG_HDMITX_PRD_IBIAS_D2 |
+ RG_HDMITX_PRD_IBIAS_D1 |
+ RG_HDMITX_PRD_IBIAS_D0);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3,
+ (imp_en << DRV_IMP_EN_SHIFT),
+ RG_HDMITX_DRV_IMP_EN);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON6,
+ (hdmi_phy->drv_imp_clk << DRV_IMP_CLK_SHIFT) |
+ (hdmi_phy->drv_imp_d2 << DRV_IMP_D2_SHIFT) |
+ (hdmi_phy->drv_imp_d1 << DRV_IMP_D1_SHIFT) |
+ (hdmi_phy->drv_imp_d0 << DRV_IMP_D0_SHIFT),
+ RG_HDMITX_DRV_IMP_CLK | RG_HDMITX_DRV_IMP_D2 |
+ RG_HDMITX_DRV_IMP_D1 | RG_HDMITX_DRV_IMP_D0);
+ mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON5,
+ (hdmi_ibias << DRV_IBIAS_CLK_SHIFT) |
+ (hdmi_ibias << DRV_IBIAS_D2_SHIFT) |
+ (hdmi_ibias << DRV_IBIAS_D1_SHIFT) |
+ (hdmi_ibias << DRV_IBIAS_D0_SHIFT),
+ RG_HDMITX_DRV_IBIAS_CLK |
+ RG_HDMITX_DRV_IBIAS_D2 |
+ RG_HDMITX_DRV_IBIAS_D1 |
+ RG_HDMITX_DRV_IBIAS_D0);
+ return 0;
+}
+
+static unsigned long mtk_hdmi_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+
+ return hdmi_phy->pll_rate;
+}
+
+static const struct clk_ops mtk_hdmi_phy_pll_ops = {
+ .prepare = mtk_hdmi_pll_prepare,
+ .unprepare = mtk_hdmi_pll_unprepare,
+ .set_rate = mtk_hdmi_pll_set_rate,
+ .round_rate = mtk_hdmi_pll_round_rate,
+ .recalc_rate = mtk_hdmi_pll_recalc_rate,
+};
+
+static void mtk_hdmi_phy_enable_tmds(struct mtk_hdmi_phy *hdmi_phy)
+{
+ mtk_hdmi_phy_set_bits(hdmi_phy, HDMI_CON3,
+ RG_HDMITX_SER_EN | RG_HDMITX_PRD_EN |
+ RG_HDMITX_DRV_EN);
+ usleep_range(100, 150);
+}
+
+static void mtk_hdmi_phy_disable_tmds(struct mtk_hdmi_phy *hdmi_phy)
+{
+ mtk_hdmi_phy_clear_bits(hdmi_phy, HDMI_CON3,
+ RG_HDMITX_DRV_EN | RG_HDMITX_PRD_EN |
+ RG_HDMITX_SER_EN);
+}
+
+struct mtk_hdmi_phy_conf mtk_hdmi_phy_8173_conf = {
+ .flags = CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE,
+ .hdmi_phy_clk_ops = &mtk_hdmi_phy_pll_ops,
+ .hdmi_phy_enable_tmds = mtk_hdmi_phy_enable_tmds,
+ .hdmi_phy_disable_tmds = mtk_hdmi_phy_disable_tmds,
+};
+
+MODULE_AUTHOR("Jie Qiu <jie.qiu@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek MT8173 HDMI PHY Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/mediatek/phy-mtk-hdmi.c b/drivers/phy/mediatek/phy-mtk-hdmi.c
new file mode 100644
index 000000000..206cc3468
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-hdmi.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ */
+
+#include "phy-mtk-hdmi.h"
+
+static int mtk_hdmi_phy_power_on(struct phy *phy);
+static int mtk_hdmi_phy_power_off(struct phy *phy);
+
+static const struct phy_ops mtk_hdmi_phy_dev_ops = {
+ .power_on = mtk_hdmi_phy_power_on,
+ .power_off = mtk_hdmi_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+void mtk_hdmi_phy_clear_bits(struct mtk_hdmi_phy *hdmi_phy, u32 offset,
+ u32 bits)
+{
+ void __iomem *reg = hdmi_phy->regs + offset;
+ u32 tmp;
+
+ tmp = readl(reg);
+ tmp &= ~bits;
+ writel(tmp, reg);
+}
+
+void mtk_hdmi_phy_set_bits(struct mtk_hdmi_phy *hdmi_phy, u32 offset,
+ u32 bits)
+{
+ void __iomem *reg = hdmi_phy->regs + offset;
+ u32 tmp;
+
+ tmp = readl(reg);
+ tmp |= bits;
+ writel(tmp, reg);
+}
+
+void mtk_hdmi_phy_mask(struct mtk_hdmi_phy *hdmi_phy, u32 offset,
+ u32 val, u32 mask)
+{
+ void __iomem *reg = hdmi_phy->regs + offset;
+ u32 tmp;
+
+ tmp = readl(reg);
+ tmp = (tmp & ~mask) | (val & mask);
+ writel(tmp, reg);
+}
+
+inline struct mtk_hdmi_phy *to_mtk_hdmi_phy(struct clk_hw *hw)
+{
+ return container_of(hw, struct mtk_hdmi_phy, pll_hw);
+}
+
+static int mtk_hdmi_phy_power_on(struct phy *phy)
+{
+ struct mtk_hdmi_phy *hdmi_phy = phy_get_drvdata(phy);
+ int ret;
+
+ ret = clk_prepare_enable(hdmi_phy->pll);
+ if (ret < 0)
+ return ret;
+
+ hdmi_phy->conf->hdmi_phy_enable_tmds(hdmi_phy);
+ return 0;
+}
+
+static int mtk_hdmi_phy_power_off(struct phy *phy)
+{
+ struct mtk_hdmi_phy *hdmi_phy = phy_get_drvdata(phy);
+
+ hdmi_phy->conf->hdmi_phy_disable_tmds(hdmi_phy);
+ clk_disable_unprepare(hdmi_phy->pll);
+
+ return 0;
+}
+
+static const struct phy_ops *
+mtk_hdmi_phy_dev_get_ops(const struct mtk_hdmi_phy *hdmi_phy)
+{
+ if (hdmi_phy && hdmi_phy->conf &&
+ hdmi_phy->conf->hdmi_phy_enable_tmds &&
+ hdmi_phy->conf->hdmi_phy_disable_tmds)
+ return &mtk_hdmi_phy_dev_ops;
+
+ if (hdmi_phy)
+ dev_err(hdmi_phy->dev, "Failed to get dev ops of phy\n");
+ return NULL;
+}
+
+static void mtk_hdmi_phy_clk_get_data(struct mtk_hdmi_phy *hdmi_phy,
+ struct clk_init_data *clk_init)
+{
+ clk_init->flags = hdmi_phy->conf->flags;
+ clk_init->ops = hdmi_phy->conf->hdmi_phy_clk_ops;
+}
+
+static int mtk_hdmi_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mtk_hdmi_phy *hdmi_phy;
+ struct resource *mem;
+ struct clk *ref_clk;
+ const char *ref_clk_name;
+ struct clk_init_data clk_init = {
+ .num_parents = 1,
+ .parent_names = (const char * const *)&ref_clk_name,
+ };
+
+ struct phy *phy;
+ struct phy_provider *phy_provider;
+ int ret;
+
+ hdmi_phy = devm_kzalloc(dev, sizeof(*hdmi_phy), GFP_KERNEL);
+ if (!hdmi_phy)
+ return -ENOMEM;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ hdmi_phy->regs = devm_ioremap_resource(dev, mem);
+ if (IS_ERR(hdmi_phy->regs)) {
+ ret = PTR_ERR(hdmi_phy->regs);
+ dev_err(dev, "Failed to get memory resource: %d\n", ret);
+ return ret;
+ }
+
+ ref_clk = devm_clk_get(dev, "pll_ref");
+ if (IS_ERR(ref_clk)) {
+ ret = PTR_ERR(ref_clk);
+ dev_err(&pdev->dev, "Failed to get PLL reference clock: %d\n",
+ ret);
+ return ret;
+ }
+ ref_clk_name = __clk_get_name(ref_clk);
+
+ ret = of_property_read_string(dev->of_node, "clock-output-names",
+ &clk_init.name);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read clock-output-names: %d\n", ret);
+ return ret;
+ }
+
+ hdmi_phy->dev = dev;
+ hdmi_phy->conf =
+ (struct mtk_hdmi_phy_conf *)of_device_get_match_data(dev);
+ mtk_hdmi_phy_clk_get_data(hdmi_phy, &clk_init);
+ hdmi_phy->pll_hw.init = &clk_init;
+ hdmi_phy->pll = devm_clk_register(dev, &hdmi_phy->pll_hw);
+ if (IS_ERR(hdmi_phy->pll)) {
+ ret = PTR_ERR(hdmi_phy->pll);
+ dev_err(dev, "Failed to register PLL: %d\n", ret);
+ return ret;
+ }
+
+ ret = of_property_read_u32(dev->of_node, "mediatek,ibias",
+ &hdmi_phy->ibias);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to get ibias: %d\n", ret);
+ return ret;
+ }
+
+ ret = of_property_read_u32(dev->of_node, "mediatek,ibias_up",
+ &hdmi_phy->ibias_up);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to get ibias up: %d\n", ret);
+ return ret;
+ }
+
+ dev_info(dev, "Using default TX DRV impedance: 4.2k/36\n");
+ hdmi_phy->drv_imp_clk = 0x30;
+ hdmi_phy->drv_imp_d2 = 0x30;
+ hdmi_phy->drv_imp_d1 = 0x30;
+ hdmi_phy->drv_imp_d0 = 0x30;
+
+ phy = devm_phy_create(dev, NULL, mtk_hdmi_phy_dev_get_ops(hdmi_phy));
+ if (IS_ERR(phy)) {
+ dev_err(dev, "Failed to create HDMI PHY\n");
+ return PTR_ERR(phy);
+ }
+ phy_set_drvdata(phy, hdmi_phy);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider)) {
+ dev_err(dev, "Failed to register HDMI PHY\n");
+ return PTR_ERR(phy_provider);
+ }
+
+ if (hdmi_phy->conf->pll_default_off)
+ hdmi_phy->conf->hdmi_phy_disable_tmds(hdmi_phy);
+
+ return of_clk_add_provider(dev->of_node, of_clk_src_simple_get,
+ hdmi_phy->pll);
+}
+
+static const struct of_device_id mtk_hdmi_phy_match[] = {
+ { .compatible = "mediatek,mt2701-hdmi-phy",
+ .data = &mtk_hdmi_phy_2701_conf,
+ },
+ { .compatible = "mediatek,mt8173-hdmi-phy",
+ .data = &mtk_hdmi_phy_8173_conf,
+ },
+ {},
+};
+
+struct platform_driver mtk_hdmi_phy_driver = {
+ .probe = mtk_hdmi_phy_probe,
+ .driver = {
+ .name = "mediatek-hdmi-phy",
+ .of_match_table = mtk_hdmi_phy_match,
+ },
+};
+module_platform_driver(mtk_hdmi_phy_driver);
+
+MODULE_DESCRIPTION("MediaTek HDMI PHY Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/mediatek/phy-mtk-hdmi.h b/drivers/phy/mediatek/phy-mtk-hdmi.h
new file mode 100644
index 000000000..dcf9bb136
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-hdmi.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Chunhui Dai <chunhui.dai@mediatek.com>
+ */
+
+#ifndef _MTK_HDMI_PHY_H
+#define _MTK_HDMI_PHY_H
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+struct mtk_hdmi_phy;
+
+struct mtk_hdmi_phy_conf {
+ unsigned long flags;
+ bool pll_default_off;
+ const struct clk_ops *hdmi_phy_clk_ops;
+ void (*hdmi_phy_enable_tmds)(struct mtk_hdmi_phy *hdmi_phy);
+ void (*hdmi_phy_disable_tmds)(struct mtk_hdmi_phy *hdmi_phy);
+};
+
+struct mtk_hdmi_phy {
+ void __iomem *regs;
+ struct device *dev;
+ struct mtk_hdmi_phy_conf *conf;
+ struct clk *pll;
+ struct clk_hw pll_hw;
+ unsigned long pll_rate;
+ unsigned char drv_imp_clk;
+ unsigned char drv_imp_d2;
+ unsigned char drv_imp_d1;
+ unsigned char drv_imp_d0;
+ unsigned int ibias;
+ unsigned int ibias_up;
+};
+
+void mtk_hdmi_phy_clear_bits(struct mtk_hdmi_phy *hdmi_phy, u32 offset,
+ u32 bits);
+void mtk_hdmi_phy_set_bits(struct mtk_hdmi_phy *hdmi_phy, u32 offset,
+ u32 bits);
+void mtk_hdmi_phy_mask(struct mtk_hdmi_phy *hdmi_phy, u32 offset,
+ u32 val, u32 mask);
+struct mtk_hdmi_phy *to_mtk_hdmi_phy(struct clk_hw *hw);
+
+extern struct mtk_hdmi_phy_conf mtk_hdmi_phy_8173_conf;
+extern struct mtk_hdmi_phy_conf mtk_hdmi_phy_2701_conf;
+
+#endif /* _MTK_HDMI_PHY_H */
diff --git a/drivers/phy/mediatek/phy-mtk-tphy.c b/drivers/phy/mediatek/phy-mtk-tphy.c
new file mode 100644
index 000000000..731c483a0
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-tphy.c
@@ -0,0 +1,1214 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ *
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+/* version V1 sub-banks offset base address */
+/* banks shared by multiple phys */
+#define SSUSB_SIFSLV_V1_SPLLC 0x000 /* shared by u3 phys */
+#define SSUSB_SIFSLV_V1_U2FREQ 0x100 /* shared by u2 phys */
+#define SSUSB_SIFSLV_V1_CHIP 0x300 /* shared by u3 phys */
+/* u2 phy bank */
+#define SSUSB_SIFSLV_V1_U2PHY_COM 0x000
+/* u3/pcie/sata phy banks */
+#define SSUSB_SIFSLV_V1_U3PHYD 0x000
+#define SSUSB_SIFSLV_V1_U3PHYA 0x200
+
+/* version V2 sub-banks offset base address */
+/* u2 phy banks */
+#define SSUSB_SIFSLV_V2_MISC 0x000
+#define SSUSB_SIFSLV_V2_U2FREQ 0x100
+#define SSUSB_SIFSLV_V2_U2PHY_COM 0x300
+/* u3/pcie/sata phy banks */
+#define SSUSB_SIFSLV_V2_SPLLC 0x000
+#define SSUSB_SIFSLV_V2_CHIP 0x100
+#define SSUSB_SIFSLV_V2_U3PHYD 0x200
+#define SSUSB_SIFSLV_V2_U3PHYA 0x400
+
+#define U3P_USBPHYACR0 0x000
+#define PA0_RG_U2PLL_FORCE_ON BIT(15)
+#define PA0_RG_USB20_INTR_EN BIT(5)
+
+#define U3P_USBPHYACR1 0x004
+#define PA1_RG_INTR_CAL GENMASK(23, 19)
+#define PA1_RG_INTR_CAL_VAL(x) ((0x1f & (x)) << 19)
+#define PA1_RG_VRT_SEL GENMASK(14, 12)
+#define PA1_RG_VRT_SEL_VAL(x) ((0x7 & (x)) << 12)
+#define PA1_RG_TERM_SEL GENMASK(10, 8)
+#define PA1_RG_TERM_SEL_VAL(x) ((0x7 & (x)) << 8)
+
+#define U3P_USBPHYACR2 0x008
+#define PA2_RG_SIF_U2PLL_FORCE_EN BIT(18)
+
+#define U3P_USBPHYACR5 0x014
+#define PA5_RG_U2_HSTX_SRCAL_EN BIT(15)
+#define PA5_RG_U2_HSTX_SRCTRL GENMASK(14, 12)
+#define PA5_RG_U2_HSTX_SRCTRL_VAL(x) ((0x7 & (x)) << 12)
+#define PA5_RG_U2_HS_100U_U3_EN BIT(11)
+
+#define U3P_USBPHYACR6 0x018
+#define PA6_RG_U2_BC11_SW_EN BIT(23)
+#define PA6_RG_U2_OTG_VBUSCMP_EN BIT(20)
+#define PA6_RG_U2_DISCTH GENMASK(7, 4)
+#define PA6_RG_U2_DISCTH_VAL(x) ((0xf & (x)) << 4)
+#define PA6_RG_U2_SQTH GENMASK(3, 0)
+#define PA6_RG_U2_SQTH_VAL(x) (0xf & (x))
+
+#define U3P_U2PHYACR4 0x020
+#define P2C_RG_USB20_GPIO_CTL BIT(9)
+#define P2C_USB20_GPIO_MODE BIT(8)
+#define P2C_U2_GPIO_CTR_MSK (P2C_RG_USB20_GPIO_CTL | P2C_USB20_GPIO_MODE)
+
+#define U3D_U2PHYDCR0 0x060
+#define P2C_RG_SIF_U2PLL_FORCE_ON BIT(24)
+
+#define U3P_U2PHYDTM0 0x068
+#define P2C_FORCE_UART_EN BIT(26)
+#define P2C_FORCE_DATAIN BIT(23)
+#define P2C_FORCE_DM_PULLDOWN BIT(21)
+#define P2C_FORCE_DP_PULLDOWN BIT(20)
+#define P2C_FORCE_XCVRSEL BIT(19)
+#define P2C_FORCE_SUSPENDM BIT(18)
+#define P2C_FORCE_TERMSEL BIT(17)
+#define P2C_RG_DATAIN GENMASK(13, 10)
+#define P2C_RG_DATAIN_VAL(x) ((0xf & (x)) << 10)
+#define P2C_RG_DMPULLDOWN BIT(7)
+#define P2C_RG_DPPULLDOWN BIT(6)
+#define P2C_RG_XCVRSEL GENMASK(5, 4)
+#define P2C_RG_XCVRSEL_VAL(x) ((0x3 & (x)) << 4)
+#define P2C_RG_SUSPENDM BIT(3)
+#define P2C_RG_TERMSEL BIT(2)
+#define P2C_DTM0_PART_MASK \
+ (P2C_FORCE_DATAIN | P2C_FORCE_DM_PULLDOWN | \
+ P2C_FORCE_DP_PULLDOWN | P2C_FORCE_XCVRSEL | \
+ P2C_FORCE_TERMSEL | P2C_RG_DMPULLDOWN | \
+ P2C_RG_DPPULLDOWN | P2C_RG_TERMSEL)
+
+#define U3P_U2PHYDTM1 0x06C
+#define P2C_RG_UART_EN BIT(16)
+#define P2C_FORCE_IDDIG BIT(9)
+#define P2C_RG_VBUSVALID BIT(5)
+#define P2C_RG_SESSEND BIT(4)
+#define P2C_RG_AVALID BIT(2)
+#define P2C_RG_IDDIG BIT(1)
+
+#define U3P_U2PHYBC12C 0x080
+#define P2C_RG_CHGDT_EN BIT(0)
+
+#define U3P_U3_CHIP_GPIO_CTLD 0x0c
+#define P3C_REG_IP_SW_RST BIT(31)
+#define P3C_MCU_BUS_CK_GATE_EN BIT(30)
+#define P3C_FORCE_IP_SW_RST BIT(29)
+
+#define U3P_U3_CHIP_GPIO_CTLE 0x10
+#define P3C_RG_SWRST_U3_PHYD BIT(25)
+#define P3C_RG_SWRST_U3_PHYD_FORCE_EN BIT(24)
+
+#define U3P_U3_PHYA_REG0 0x000
+#define P3A_RG_CLKDRV_OFF GENMASK(3, 2)
+#define P3A_RG_CLKDRV_OFF_VAL(x) ((0x3 & (x)) << 2)
+
+#define U3P_U3_PHYA_REG1 0x004
+#define P3A_RG_CLKDRV_AMP GENMASK(31, 29)
+#define P3A_RG_CLKDRV_AMP_VAL(x) ((0x7 & (x)) << 29)
+
+#define U3P_U3_PHYA_REG6 0x018
+#define P3A_RG_TX_EIDLE_CM GENMASK(31, 28)
+#define P3A_RG_TX_EIDLE_CM_VAL(x) ((0xf & (x)) << 28)
+
+#define U3P_U3_PHYA_REG9 0x024
+#define P3A_RG_RX_DAC_MUX GENMASK(5, 1)
+#define P3A_RG_RX_DAC_MUX_VAL(x) ((0x1f & (x)) << 1)
+
+#define U3P_U3_PHYA_DA_REG0 0x100
+#define P3A_RG_XTAL_EXT_PE2H GENMASK(17, 16)
+#define P3A_RG_XTAL_EXT_PE2H_VAL(x) ((0x3 & (x)) << 16)
+#define P3A_RG_XTAL_EXT_PE1H GENMASK(13, 12)
+#define P3A_RG_XTAL_EXT_PE1H_VAL(x) ((0x3 & (x)) << 12)
+#define P3A_RG_XTAL_EXT_EN_U3 GENMASK(11, 10)
+#define P3A_RG_XTAL_EXT_EN_U3_VAL(x) ((0x3 & (x)) << 10)
+
+#define U3P_U3_PHYA_DA_REG4 0x108
+#define P3A_RG_PLL_DIVEN_PE2H GENMASK(21, 19)
+#define P3A_RG_PLL_BC_PE2H GENMASK(7, 6)
+#define P3A_RG_PLL_BC_PE2H_VAL(x) ((0x3 & (x)) << 6)
+
+#define U3P_U3_PHYA_DA_REG5 0x10c
+#define P3A_RG_PLL_BR_PE2H GENMASK(29, 28)
+#define P3A_RG_PLL_BR_PE2H_VAL(x) ((0x3 & (x)) << 28)
+#define P3A_RG_PLL_IC_PE2H GENMASK(15, 12)
+#define P3A_RG_PLL_IC_PE2H_VAL(x) ((0xf & (x)) << 12)
+
+#define U3P_U3_PHYA_DA_REG6 0x110
+#define P3A_RG_PLL_IR_PE2H GENMASK(19, 16)
+#define P3A_RG_PLL_IR_PE2H_VAL(x) ((0xf & (x)) << 16)
+
+#define U3P_U3_PHYA_DA_REG7 0x114
+#define P3A_RG_PLL_BP_PE2H GENMASK(19, 16)
+#define P3A_RG_PLL_BP_PE2H_VAL(x) ((0xf & (x)) << 16)
+
+#define U3P_U3_PHYA_DA_REG20 0x13c
+#define P3A_RG_PLL_DELTA1_PE2H GENMASK(31, 16)
+#define P3A_RG_PLL_DELTA1_PE2H_VAL(x) ((0xffff & (x)) << 16)
+
+#define U3P_U3_PHYA_DA_REG25 0x148
+#define P3A_RG_PLL_DELTA_PE2H GENMASK(15, 0)
+#define P3A_RG_PLL_DELTA_PE2H_VAL(x) (0xffff & (x))
+
+#define U3P_U3_PHYD_LFPS1 0x00c
+#define P3D_RG_FWAKE_TH GENMASK(21, 16)
+#define P3D_RG_FWAKE_TH_VAL(x) ((0x3f & (x)) << 16)
+
+#define U3P_U3_PHYD_CDR1 0x05c
+#define P3D_RG_CDR_BIR_LTD1 GENMASK(28, 24)
+#define P3D_RG_CDR_BIR_LTD1_VAL(x) ((0x1f & (x)) << 24)
+#define P3D_RG_CDR_BIR_LTD0 GENMASK(12, 8)
+#define P3D_RG_CDR_BIR_LTD0_VAL(x) ((0x1f & (x)) << 8)
+
+#define U3P_U3_PHYD_RXDET1 0x128
+#define P3D_RG_RXDET_STB2_SET GENMASK(17, 9)
+#define P3D_RG_RXDET_STB2_SET_VAL(x) ((0x1ff & (x)) << 9)
+
+#define U3P_U3_PHYD_RXDET2 0x12c
+#define P3D_RG_RXDET_STB2_SET_P3 GENMASK(8, 0)
+#define P3D_RG_RXDET_STB2_SET_P3_VAL(x) (0x1ff & (x))
+
+#define U3P_SPLLC_XTALCTL3 0x018
+#define XC3_RG_U3_XTAL_RX_PWD BIT(9)
+#define XC3_RG_U3_FRC_XTAL_RX_PWD BIT(8)
+
+#define U3P_U2FREQ_FMCR0 0x00
+#define P2F_RG_MONCLK_SEL GENMASK(27, 26)
+#define P2F_RG_MONCLK_SEL_VAL(x) ((0x3 & (x)) << 26)
+#define P2F_RG_FREQDET_EN BIT(24)
+#define P2F_RG_CYCLECNT GENMASK(23, 0)
+#define P2F_RG_CYCLECNT_VAL(x) ((P2F_RG_CYCLECNT) & (x))
+
+#define U3P_U2FREQ_VALUE 0x0c
+
+#define U3P_U2FREQ_FMMONR1 0x10
+#define P2F_USB_FM_VALID BIT(0)
+#define P2F_RG_FRCK_EN BIT(8)
+
+#define U3P_REF_CLK 26 /* MHZ */
+#define U3P_SLEW_RATE_COEF 28
+#define U3P_SR_COEF_DIVISOR 1000
+#define U3P_FM_DET_CYCLE_CNT 1024
+
+/* SATA register setting */
+#define PHYD_CTRL_SIGNAL_MODE4 0x1c
+/* CDR Charge Pump P-path current adjustment */
+#define RG_CDR_BICLTD1_GEN1_MSK GENMASK(23, 20)
+#define RG_CDR_BICLTD1_GEN1_VAL(x) ((0xf & (x)) << 20)
+#define RG_CDR_BICLTD0_GEN1_MSK GENMASK(11, 8)
+#define RG_CDR_BICLTD0_GEN1_VAL(x) ((0xf & (x)) << 8)
+
+#define PHYD_DESIGN_OPTION2 0x24
+/* Symbol lock count selection */
+#define RG_LOCK_CNT_SEL_MSK GENMASK(5, 4)
+#define RG_LOCK_CNT_SEL_VAL(x) ((0x3 & (x)) << 4)
+
+#define PHYD_DESIGN_OPTION9 0x40
+/* COMWAK GAP width window */
+#define RG_TG_MAX_MSK GENMASK(20, 16)
+#define RG_TG_MAX_VAL(x) ((0x1f & (x)) << 16)
+/* COMINIT GAP width window */
+#define RG_T2_MAX_MSK GENMASK(13, 8)
+#define RG_T2_MAX_VAL(x) ((0x3f & (x)) << 8)
+/* COMWAK GAP width window */
+#define RG_TG_MIN_MSK GENMASK(7, 5)
+#define RG_TG_MIN_VAL(x) ((0x7 & (x)) << 5)
+/* COMINIT GAP width window */
+#define RG_T2_MIN_MSK GENMASK(4, 0)
+#define RG_T2_MIN_VAL(x) (0x1f & (x))
+
+#define ANA_RG_CTRL_SIGNAL1 0x4c
+/* TX driver tail current control for 0dB de-empahsis mdoe for Gen1 speed */
+#define RG_IDRV_0DB_GEN1_MSK GENMASK(13, 8)
+#define RG_IDRV_0DB_GEN1_VAL(x) ((0x3f & (x)) << 8)
+
+#define ANA_RG_CTRL_SIGNAL4 0x58
+#define RG_CDR_BICLTR_GEN1_MSK GENMASK(23, 20)
+#define RG_CDR_BICLTR_GEN1_VAL(x) ((0xf & (x)) << 20)
+/* Loop filter R1 resistance adjustment for Gen1 speed */
+#define RG_CDR_BR_GEN2_MSK GENMASK(10, 8)
+#define RG_CDR_BR_GEN2_VAL(x) ((0x7 & (x)) << 8)
+
+#define ANA_RG_CTRL_SIGNAL6 0x60
+/* I-path capacitance adjustment for Gen1 */
+#define RG_CDR_BC_GEN1_MSK GENMASK(28, 24)
+#define RG_CDR_BC_GEN1_VAL(x) ((0x1f & (x)) << 24)
+#define RG_CDR_BIRLTR_GEN1_MSK GENMASK(4, 0)
+#define RG_CDR_BIRLTR_GEN1_VAL(x) (0x1f & (x))
+
+#define ANA_EQ_EYE_CTRL_SIGNAL1 0x6c
+/* RX Gen1 LEQ tuning step */
+#define RG_EQ_DLEQ_LFI_GEN1_MSK GENMASK(11, 8)
+#define RG_EQ_DLEQ_LFI_GEN1_VAL(x) ((0xf & (x)) << 8)
+
+#define ANA_EQ_EYE_CTRL_SIGNAL4 0xd8
+#define RG_CDR_BIRLTD0_GEN1_MSK GENMASK(20, 16)
+#define RG_CDR_BIRLTD0_GEN1_VAL(x) ((0x1f & (x)) << 16)
+
+#define ANA_EQ_EYE_CTRL_SIGNAL5 0xdc
+#define RG_CDR_BIRLTD0_GEN3_MSK GENMASK(4, 0)
+#define RG_CDR_BIRLTD0_GEN3_VAL(x) (0x1f & (x))
+
+enum mtk_phy_version {
+ MTK_PHY_V1 = 1,
+ MTK_PHY_V2,
+};
+
+struct mtk_phy_pdata {
+ /* avoid RX sensitivity level degradation only for mt8173 */
+ bool avoid_rx_sen_degradation;
+ enum mtk_phy_version version;
+};
+
+struct u2phy_banks {
+ void __iomem *misc;
+ void __iomem *fmreg;
+ void __iomem *com;
+};
+
+struct u3phy_banks {
+ void __iomem *spllc;
+ void __iomem *chip;
+ void __iomem *phyd; /* include u3phyd_bank2 */
+ void __iomem *phya; /* include u3phya_da */
+};
+
+struct mtk_phy_instance {
+ struct phy *phy;
+ void __iomem *port_base;
+ union {
+ struct u2phy_banks u2_banks;
+ struct u3phy_banks u3_banks;
+ };
+ struct clk *ref_clk; /* reference clock of (digital) phy */
+ struct clk *da_ref_clk; /* reference clock of analog phy */
+ u32 index;
+ u8 type;
+ int eye_src;
+ int eye_vrt;
+ int eye_term;
+ int intr;
+ int discth;
+ bool bc12_en;
+};
+
+struct mtk_tphy {
+ struct device *dev;
+ void __iomem *sif_base; /* only shared sif */
+ const struct mtk_phy_pdata *pdata;
+ struct mtk_phy_instance **phys;
+ int nphys;
+ int src_ref_clk; /* MHZ, reference clock for slew rate calibrate */
+ int src_coef; /* coefficient for slew rate calibrate */
+};
+
+static void hs_slew_rate_calibrate(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ void __iomem *fmreg = u2_banks->fmreg;
+ void __iomem *com = u2_banks->com;
+ int calibration_val;
+ int fm_out;
+ u32 tmp;
+
+ /* use force value */
+ if (instance->eye_src)
+ return;
+
+ /* enable USB ring oscillator */
+ tmp = readl(com + U3P_USBPHYACR5);
+ tmp |= PA5_RG_U2_HSTX_SRCAL_EN;
+ writel(tmp, com + U3P_USBPHYACR5);
+ udelay(1);
+
+ /*enable free run clock */
+ tmp = readl(fmreg + U3P_U2FREQ_FMMONR1);
+ tmp |= P2F_RG_FRCK_EN;
+ writel(tmp, fmreg + U3P_U2FREQ_FMMONR1);
+
+ /* set cycle count as 1024, and select u2 channel */
+ tmp = readl(fmreg + U3P_U2FREQ_FMCR0);
+ tmp &= ~(P2F_RG_CYCLECNT | P2F_RG_MONCLK_SEL);
+ tmp |= P2F_RG_CYCLECNT_VAL(U3P_FM_DET_CYCLE_CNT);
+ if (tphy->pdata->version == MTK_PHY_V1)
+ tmp |= P2F_RG_MONCLK_SEL_VAL(instance->index >> 1);
+
+ writel(tmp, fmreg + U3P_U2FREQ_FMCR0);
+
+ /* enable frequency meter */
+ tmp = readl(fmreg + U3P_U2FREQ_FMCR0);
+ tmp |= P2F_RG_FREQDET_EN;
+ writel(tmp, fmreg + U3P_U2FREQ_FMCR0);
+
+ /* ignore return value */
+ readl_poll_timeout(fmreg + U3P_U2FREQ_FMMONR1, tmp,
+ (tmp & P2F_USB_FM_VALID), 10, 200);
+
+ fm_out = readl(fmreg + U3P_U2FREQ_VALUE);
+
+ /* disable frequency meter */
+ tmp = readl(fmreg + U3P_U2FREQ_FMCR0);
+ tmp &= ~P2F_RG_FREQDET_EN;
+ writel(tmp, fmreg + U3P_U2FREQ_FMCR0);
+
+ /*disable free run clock */
+ tmp = readl(fmreg + U3P_U2FREQ_FMMONR1);
+ tmp &= ~P2F_RG_FRCK_EN;
+ writel(tmp, fmreg + U3P_U2FREQ_FMMONR1);
+
+ if (fm_out) {
+ /* ( 1024 / FM_OUT ) x reference clock frequency x coef */
+ tmp = tphy->src_ref_clk * tphy->src_coef;
+ tmp = (tmp * U3P_FM_DET_CYCLE_CNT) / fm_out;
+ calibration_val = DIV_ROUND_CLOSEST(tmp, U3P_SR_COEF_DIVISOR);
+ } else {
+ /* if FM detection fail, set default value */
+ calibration_val = 4;
+ }
+ dev_dbg(tphy->dev, "phy:%d, fm_out:%d, calib:%d (clk:%d, coef:%d)\n",
+ instance->index, fm_out, calibration_val,
+ tphy->src_ref_clk, tphy->src_coef);
+
+ /* set HS slew rate */
+ tmp = readl(com + U3P_USBPHYACR5);
+ tmp &= ~PA5_RG_U2_HSTX_SRCTRL;
+ tmp |= PA5_RG_U2_HSTX_SRCTRL_VAL(calibration_val);
+ writel(tmp, com + U3P_USBPHYACR5);
+
+ /* disable USB ring oscillator */
+ tmp = readl(com + U3P_USBPHYACR5);
+ tmp &= ~PA5_RG_U2_HSTX_SRCAL_EN;
+ writel(tmp, com + U3P_USBPHYACR5);
+}
+
+static void u3_phy_instance_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+ u32 tmp;
+
+ /* gating PCIe Analog XTAL clock */
+ tmp = readl(u3_banks->spllc + U3P_SPLLC_XTALCTL3);
+ tmp |= XC3_RG_U3_XTAL_RX_PWD | XC3_RG_U3_FRC_XTAL_RX_PWD;
+ writel(tmp, u3_banks->spllc + U3P_SPLLC_XTALCTL3);
+
+ /* gating XSQ */
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG0);
+ tmp &= ~P3A_RG_XTAL_EXT_EN_U3;
+ tmp |= P3A_RG_XTAL_EXT_EN_U3_VAL(2);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG0);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_REG9);
+ tmp &= ~P3A_RG_RX_DAC_MUX;
+ tmp |= P3A_RG_RX_DAC_MUX_VAL(4);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_REG9);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_REG6);
+ tmp &= ~P3A_RG_TX_EIDLE_CM;
+ tmp |= P3A_RG_TX_EIDLE_CM_VAL(0xe);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_REG6);
+
+ tmp = readl(u3_banks->phyd + U3P_U3_PHYD_CDR1);
+ tmp &= ~(P3D_RG_CDR_BIR_LTD0 | P3D_RG_CDR_BIR_LTD1);
+ tmp |= P3D_RG_CDR_BIR_LTD0_VAL(0xc) | P3D_RG_CDR_BIR_LTD1_VAL(0x3);
+ writel(tmp, u3_banks->phyd + U3P_U3_PHYD_CDR1);
+
+ tmp = readl(u3_banks->phyd + U3P_U3_PHYD_LFPS1);
+ tmp &= ~P3D_RG_FWAKE_TH;
+ tmp |= P3D_RG_FWAKE_TH_VAL(0x34);
+ writel(tmp, u3_banks->phyd + U3P_U3_PHYD_LFPS1);
+
+ tmp = readl(u3_banks->phyd + U3P_U3_PHYD_RXDET1);
+ tmp &= ~P3D_RG_RXDET_STB2_SET;
+ tmp |= P3D_RG_RXDET_STB2_SET_VAL(0x10);
+ writel(tmp, u3_banks->phyd + U3P_U3_PHYD_RXDET1);
+
+ tmp = readl(u3_banks->phyd + U3P_U3_PHYD_RXDET2);
+ tmp &= ~P3D_RG_RXDET_STB2_SET_P3;
+ tmp |= P3D_RG_RXDET_STB2_SET_P3_VAL(0x10);
+ writel(tmp, u3_banks->phyd + U3P_U3_PHYD_RXDET2);
+
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, instance->index);
+}
+
+static void u2_phy_instance_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ void __iomem *com = u2_banks->com;
+ u32 index = instance->index;
+ u32 tmp;
+
+ /* switch to USB function, and enable usb pll */
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp &= ~(P2C_FORCE_UART_EN | P2C_FORCE_SUSPENDM);
+ tmp |= P2C_RG_XCVRSEL_VAL(1) | P2C_RG_DATAIN_VAL(0);
+ writel(tmp, com + U3P_U2PHYDTM0);
+
+ tmp = readl(com + U3P_U2PHYDTM1);
+ tmp &= ~P2C_RG_UART_EN;
+ writel(tmp, com + U3P_U2PHYDTM1);
+
+ tmp = readl(com + U3P_USBPHYACR0);
+ tmp |= PA0_RG_USB20_INTR_EN;
+ writel(tmp, com + U3P_USBPHYACR0);
+
+ /* disable switch 100uA current to SSUSB */
+ tmp = readl(com + U3P_USBPHYACR5);
+ tmp &= ~PA5_RG_U2_HS_100U_U3_EN;
+ writel(tmp, com + U3P_USBPHYACR5);
+
+ if (!index) {
+ tmp = readl(com + U3P_U2PHYACR4);
+ tmp &= ~P2C_U2_GPIO_CTR_MSK;
+ writel(tmp, com + U3P_U2PHYACR4);
+ }
+
+ if (tphy->pdata->avoid_rx_sen_degradation) {
+ if (!index) {
+ tmp = readl(com + U3P_USBPHYACR2);
+ tmp |= PA2_RG_SIF_U2PLL_FORCE_EN;
+ writel(tmp, com + U3P_USBPHYACR2);
+
+ tmp = readl(com + U3D_U2PHYDCR0);
+ tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON;
+ writel(tmp, com + U3D_U2PHYDCR0);
+ } else {
+ tmp = readl(com + U3D_U2PHYDCR0);
+ tmp |= P2C_RG_SIF_U2PLL_FORCE_ON;
+ writel(tmp, com + U3D_U2PHYDCR0);
+
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp |= P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM;
+ writel(tmp, com + U3P_U2PHYDTM0);
+ }
+ }
+
+ tmp = readl(com + U3P_USBPHYACR6);
+ tmp &= ~PA6_RG_U2_BC11_SW_EN; /* DP/DM BC1.1 path Disable */
+ tmp &= ~PA6_RG_U2_SQTH;
+ tmp |= PA6_RG_U2_SQTH_VAL(2);
+ writel(tmp, com + U3P_USBPHYACR6);
+
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, index);
+}
+
+static void u2_phy_instance_power_on(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ void __iomem *com = u2_banks->com;
+ u32 index = instance->index;
+ u32 tmp;
+
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp &= ~(P2C_RG_XCVRSEL | P2C_RG_DATAIN | P2C_DTM0_PART_MASK);
+ writel(tmp, com + U3P_U2PHYDTM0);
+
+ /* OTG Enable */
+ tmp = readl(com + U3P_USBPHYACR6);
+ tmp |= PA6_RG_U2_OTG_VBUSCMP_EN;
+ writel(tmp, com + U3P_USBPHYACR6);
+
+ tmp = readl(com + U3P_U2PHYDTM1);
+ tmp |= P2C_RG_VBUSVALID | P2C_RG_AVALID;
+ tmp &= ~P2C_RG_SESSEND;
+ writel(tmp, com + U3P_U2PHYDTM1);
+
+ if (tphy->pdata->avoid_rx_sen_degradation && index) {
+ tmp = readl(com + U3D_U2PHYDCR0);
+ tmp |= P2C_RG_SIF_U2PLL_FORCE_ON;
+ writel(tmp, com + U3D_U2PHYDCR0);
+
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp |= P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM;
+ writel(tmp, com + U3P_U2PHYDTM0);
+ }
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, index);
+}
+
+static void u2_phy_instance_power_off(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ void __iomem *com = u2_banks->com;
+ u32 index = instance->index;
+ u32 tmp;
+
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp &= ~(P2C_RG_XCVRSEL | P2C_RG_DATAIN);
+ writel(tmp, com + U3P_U2PHYDTM0);
+
+ /* OTG Disable */
+ tmp = readl(com + U3P_USBPHYACR6);
+ tmp &= ~PA6_RG_U2_OTG_VBUSCMP_EN;
+ writel(tmp, com + U3P_USBPHYACR6);
+
+ tmp = readl(com + U3P_U2PHYDTM1);
+ tmp &= ~(P2C_RG_VBUSVALID | P2C_RG_AVALID);
+ tmp |= P2C_RG_SESSEND;
+ writel(tmp, com + U3P_U2PHYDTM1);
+
+ if (tphy->pdata->avoid_rx_sen_degradation && index) {
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp &= ~(P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM);
+ writel(tmp, com + U3P_U2PHYDTM0);
+
+ tmp = readl(com + U3D_U2PHYDCR0);
+ tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON;
+ writel(tmp, com + U3D_U2PHYDCR0);
+ }
+
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, index);
+}
+
+static void u2_phy_instance_exit(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ void __iomem *com = u2_banks->com;
+ u32 index = instance->index;
+ u32 tmp;
+
+ if (tphy->pdata->avoid_rx_sen_degradation && index) {
+ tmp = readl(com + U3D_U2PHYDCR0);
+ tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON;
+ writel(tmp, com + U3D_U2PHYDCR0);
+
+ tmp = readl(com + U3P_U2PHYDTM0);
+ tmp &= ~P2C_FORCE_SUSPENDM;
+ writel(tmp, com + U3P_U2PHYDTM0);
+ }
+}
+
+static void u2_phy_instance_set_mode(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance,
+ enum phy_mode mode)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ u32 tmp;
+
+ tmp = readl(u2_banks->com + U3P_U2PHYDTM1);
+ switch (mode) {
+ case PHY_MODE_USB_DEVICE:
+ tmp |= P2C_FORCE_IDDIG | P2C_RG_IDDIG;
+ break;
+ case PHY_MODE_USB_HOST:
+ tmp |= P2C_FORCE_IDDIG;
+ tmp &= ~P2C_RG_IDDIG;
+ break;
+ case PHY_MODE_USB_OTG:
+ tmp &= ~(P2C_FORCE_IDDIG | P2C_RG_IDDIG);
+ break;
+ default:
+ return;
+ }
+ writel(tmp, u2_banks->com + U3P_U2PHYDTM1);
+}
+
+static void pcie_phy_instance_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+ u32 tmp;
+
+ if (tphy->pdata->version != MTK_PHY_V1)
+ return;
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG0);
+ tmp &= ~(P3A_RG_XTAL_EXT_PE1H | P3A_RG_XTAL_EXT_PE2H);
+ tmp |= P3A_RG_XTAL_EXT_PE1H_VAL(0x2) | P3A_RG_XTAL_EXT_PE2H_VAL(0x2);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG0);
+
+ /* ref clk drive */
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_REG1);
+ tmp &= ~P3A_RG_CLKDRV_AMP;
+ tmp |= P3A_RG_CLKDRV_AMP_VAL(0x4);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_REG1);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_REG0);
+ tmp &= ~P3A_RG_CLKDRV_OFF;
+ tmp |= P3A_RG_CLKDRV_OFF_VAL(0x1);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_REG0);
+
+ /* SSC delta -5000ppm */
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG20);
+ tmp &= ~P3A_RG_PLL_DELTA1_PE2H;
+ tmp |= P3A_RG_PLL_DELTA1_PE2H_VAL(0x3c);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG20);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG25);
+ tmp &= ~P3A_RG_PLL_DELTA_PE2H;
+ tmp |= P3A_RG_PLL_DELTA_PE2H_VAL(0x36);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG25);
+
+ /* change pll BW 0.6M */
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG5);
+ tmp &= ~(P3A_RG_PLL_BR_PE2H | P3A_RG_PLL_IC_PE2H);
+ tmp |= P3A_RG_PLL_BR_PE2H_VAL(0x1) | P3A_RG_PLL_IC_PE2H_VAL(0x1);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG5);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG4);
+ tmp &= ~(P3A_RG_PLL_DIVEN_PE2H | P3A_RG_PLL_BC_PE2H);
+ tmp |= P3A_RG_PLL_BC_PE2H_VAL(0x3);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG4);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG6);
+ tmp &= ~P3A_RG_PLL_IR_PE2H;
+ tmp |= P3A_RG_PLL_IR_PE2H_VAL(0x2);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG6);
+
+ tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG7);
+ tmp &= ~P3A_RG_PLL_BP_PE2H;
+ tmp |= P3A_RG_PLL_BP_PE2H_VAL(0xa);
+ writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG7);
+
+ /* Tx Detect Rx Timing: 10us -> 5us */
+ tmp = readl(u3_banks->phyd + U3P_U3_PHYD_RXDET1);
+ tmp &= ~P3D_RG_RXDET_STB2_SET;
+ tmp |= P3D_RG_RXDET_STB2_SET_VAL(0x10);
+ writel(tmp, u3_banks->phyd + U3P_U3_PHYD_RXDET1);
+
+ tmp = readl(u3_banks->phyd + U3P_U3_PHYD_RXDET2);
+ tmp &= ~P3D_RG_RXDET_STB2_SET_P3;
+ tmp |= P3D_RG_RXDET_STB2_SET_P3_VAL(0x10);
+ writel(tmp, u3_banks->phyd + U3P_U3_PHYD_RXDET2);
+
+ /* wait for PCIe subsys register to active */
+ usleep_range(2500, 3000);
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, instance->index);
+}
+
+static void pcie_phy_instance_power_on(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u3phy_banks *bank = &instance->u3_banks;
+ u32 tmp;
+
+ tmp = readl(bank->chip + U3P_U3_CHIP_GPIO_CTLD);
+ tmp &= ~(P3C_FORCE_IP_SW_RST | P3C_REG_IP_SW_RST);
+ writel(tmp, bank->chip + U3P_U3_CHIP_GPIO_CTLD);
+
+ tmp = readl(bank->chip + U3P_U3_CHIP_GPIO_CTLE);
+ tmp &= ~(P3C_RG_SWRST_U3_PHYD_FORCE_EN | P3C_RG_SWRST_U3_PHYD);
+ writel(tmp, bank->chip + U3P_U3_CHIP_GPIO_CTLE);
+}
+
+static void pcie_phy_instance_power_off(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+
+{
+ struct u3phy_banks *bank = &instance->u3_banks;
+ u32 tmp;
+
+ tmp = readl(bank->chip + U3P_U3_CHIP_GPIO_CTLD);
+ tmp |= P3C_FORCE_IP_SW_RST | P3C_REG_IP_SW_RST;
+ writel(tmp, bank->chip + U3P_U3_CHIP_GPIO_CTLD);
+
+ tmp = readl(bank->chip + U3P_U3_CHIP_GPIO_CTLE);
+ tmp |= P3C_RG_SWRST_U3_PHYD_FORCE_EN | P3C_RG_SWRST_U3_PHYD;
+ writel(tmp, bank->chip + U3P_U3_CHIP_GPIO_CTLE);
+}
+
+static void sata_phy_instance_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+ void __iomem *phyd = u3_banks->phyd;
+ u32 tmp;
+
+ /* charge current adjustment */
+ tmp = readl(phyd + ANA_RG_CTRL_SIGNAL6);
+ tmp &= ~(RG_CDR_BIRLTR_GEN1_MSK | RG_CDR_BC_GEN1_MSK);
+ tmp |= RG_CDR_BIRLTR_GEN1_VAL(0x6) | RG_CDR_BC_GEN1_VAL(0x1a);
+ writel(tmp, phyd + ANA_RG_CTRL_SIGNAL6);
+
+ tmp = readl(phyd + ANA_EQ_EYE_CTRL_SIGNAL4);
+ tmp &= ~RG_CDR_BIRLTD0_GEN1_MSK;
+ tmp |= RG_CDR_BIRLTD0_GEN1_VAL(0x18);
+ writel(tmp, phyd + ANA_EQ_EYE_CTRL_SIGNAL4);
+
+ tmp = readl(phyd + ANA_EQ_EYE_CTRL_SIGNAL5);
+ tmp &= ~RG_CDR_BIRLTD0_GEN3_MSK;
+ tmp |= RG_CDR_BIRLTD0_GEN3_VAL(0x06);
+ writel(tmp, phyd + ANA_EQ_EYE_CTRL_SIGNAL5);
+
+ tmp = readl(phyd + ANA_RG_CTRL_SIGNAL4);
+ tmp &= ~(RG_CDR_BICLTR_GEN1_MSK | RG_CDR_BR_GEN2_MSK);
+ tmp |= RG_CDR_BICLTR_GEN1_VAL(0x0c) | RG_CDR_BR_GEN2_VAL(0x07);
+ writel(tmp, phyd + ANA_RG_CTRL_SIGNAL4);
+
+ tmp = readl(phyd + PHYD_CTRL_SIGNAL_MODE4);
+ tmp &= ~(RG_CDR_BICLTD0_GEN1_MSK | RG_CDR_BICLTD1_GEN1_MSK);
+ tmp |= RG_CDR_BICLTD0_GEN1_VAL(0x08) | RG_CDR_BICLTD1_GEN1_VAL(0x02);
+ writel(tmp, phyd + PHYD_CTRL_SIGNAL_MODE4);
+
+ tmp = readl(phyd + PHYD_DESIGN_OPTION2);
+ tmp &= ~RG_LOCK_CNT_SEL_MSK;
+ tmp |= RG_LOCK_CNT_SEL_VAL(0x02);
+ writel(tmp, phyd + PHYD_DESIGN_OPTION2);
+
+ tmp = readl(phyd + PHYD_DESIGN_OPTION9);
+ tmp &= ~(RG_T2_MIN_MSK | RG_TG_MIN_MSK |
+ RG_T2_MAX_MSK | RG_TG_MAX_MSK);
+ tmp |= RG_T2_MIN_VAL(0x12) | RG_TG_MIN_VAL(0x04) |
+ RG_T2_MAX_VAL(0x31) | RG_TG_MAX_VAL(0x0e);
+ writel(tmp, phyd + PHYD_DESIGN_OPTION9);
+
+ tmp = readl(phyd + ANA_RG_CTRL_SIGNAL1);
+ tmp &= ~RG_IDRV_0DB_GEN1_MSK;
+ tmp |= RG_IDRV_0DB_GEN1_VAL(0x20);
+ writel(tmp, phyd + ANA_RG_CTRL_SIGNAL1);
+
+ tmp = readl(phyd + ANA_EQ_EYE_CTRL_SIGNAL1);
+ tmp &= ~RG_EQ_DLEQ_LFI_GEN1_MSK;
+ tmp |= RG_EQ_DLEQ_LFI_GEN1_VAL(0x03);
+ writel(tmp, phyd + ANA_EQ_EYE_CTRL_SIGNAL1);
+
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, instance->index);
+}
+
+static void phy_v1_banks_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+
+ switch (instance->type) {
+ case PHY_TYPE_USB2:
+ u2_banks->misc = NULL;
+ u2_banks->fmreg = tphy->sif_base + SSUSB_SIFSLV_V1_U2FREQ;
+ u2_banks->com = instance->port_base + SSUSB_SIFSLV_V1_U2PHY_COM;
+ break;
+ case PHY_TYPE_USB3:
+ case PHY_TYPE_PCIE:
+ u3_banks->spllc = tphy->sif_base + SSUSB_SIFSLV_V1_SPLLC;
+ u3_banks->chip = tphy->sif_base + SSUSB_SIFSLV_V1_CHIP;
+ u3_banks->phyd = instance->port_base + SSUSB_SIFSLV_V1_U3PHYD;
+ u3_banks->phya = instance->port_base + SSUSB_SIFSLV_V1_U3PHYA;
+ break;
+ case PHY_TYPE_SATA:
+ u3_banks->phyd = instance->port_base + SSUSB_SIFSLV_V1_U3PHYD;
+ break;
+ default:
+ dev_err(tphy->dev, "incompatible PHY type\n");
+ return;
+ }
+}
+
+static void phy_v2_banks_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+
+ switch (instance->type) {
+ case PHY_TYPE_USB2:
+ u2_banks->misc = instance->port_base + SSUSB_SIFSLV_V2_MISC;
+ u2_banks->fmreg = instance->port_base + SSUSB_SIFSLV_V2_U2FREQ;
+ u2_banks->com = instance->port_base + SSUSB_SIFSLV_V2_U2PHY_COM;
+ break;
+ case PHY_TYPE_USB3:
+ case PHY_TYPE_PCIE:
+ u3_banks->spllc = instance->port_base + SSUSB_SIFSLV_V2_SPLLC;
+ u3_banks->chip = instance->port_base + SSUSB_SIFSLV_V2_CHIP;
+ u3_banks->phyd = instance->port_base + SSUSB_SIFSLV_V2_U3PHYD;
+ u3_banks->phya = instance->port_base + SSUSB_SIFSLV_V2_U3PHYA;
+ break;
+ default:
+ dev_err(tphy->dev, "incompatible PHY type\n");
+ return;
+ }
+}
+
+static void phy_parse_property(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct device *dev = &instance->phy->dev;
+
+ if (instance->type != PHY_TYPE_USB2)
+ return;
+
+ instance->bc12_en = device_property_read_bool(dev, "mediatek,bc12");
+ device_property_read_u32(dev, "mediatek,eye-src",
+ &instance->eye_src);
+ device_property_read_u32(dev, "mediatek,eye-vrt",
+ &instance->eye_vrt);
+ device_property_read_u32(dev, "mediatek,eye-term",
+ &instance->eye_term);
+ device_property_read_u32(dev, "mediatek,intr",
+ &instance->intr);
+ device_property_read_u32(dev, "mediatek,discth",
+ &instance->discth);
+ dev_dbg(dev, "bc12:%d, src:%d, vrt:%d, term:%d, intr:%d, disc:%d\n",
+ instance->bc12_en, instance->eye_src,
+ instance->eye_vrt, instance->eye_term,
+ instance->intr, instance->discth);
+}
+
+static void u2_phy_props_set(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ void __iomem *com = u2_banks->com;
+ u32 tmp;
+
+ if (instance->bc12_en) {
+ tmp = readl(com + U3P_U2PHYBC12C);
+ tmp |= P2C_RG_CHGDT_EN; /* BC1.2 path Enable */
+ writel(tmp, com + U3P_U2PHYBC12C);
+ }
+
+ if (instance->eye_src) {
+ tmp = readl(com + U3P_USBPHYACR5);
+ tmp &= ~PA5_RG_U2_HSTX_SRCTRL;
+ tmp |= PA5_RG_U2_HSTX_SRCTRL_VAL(instance->eye_src);
+ writel(tmp, com + U3P_USBPHYACR5);
+ }
+
+ if (instance->eye_vrt) {
+ tmp = readl(com + U3P_USBPHYACR1);
+ tmp &= ~PA1_RG_VRT_SEL;
+ tmp |= PA1_RG_VRT_SEL_VAL(instance->eye_vrt);
+ writel(tmp, com + U3P_USBPHYACR1);
+ }
+
+ if (instance->eye_term) {
+ tmp = readl(com + U3P_USBPHYACR1);
+ tmp &= ~PA1_RG_TERM_SEL;
+ tmp |= PA1_RG_TERM_SEL_VAL(instance->eye_term);
+ writel(tmp, com + U3P_USBPHYACR1);
+ }
+
+ if (instance->intr) {
+ tmp = readl(com + U3P_USBPHYACR1);
+ tmp &= ~PA1_RG_INTR_CAL;
+ tmp |= PA1_RG_INTR_CAL_VAL(instance->intr);
+ writel(tmp, com + U3P_USBPHYACR1);
+ }
+
+ if (instance->discth) {
+ tmp = readl(com + U3P_USBPHYACR6);
+ tmp &= ~PA6_RG_U2_DISCTH;
+ tmp |= PA6_RG_U2_DISCTH_VAL(instance->discth);
+ writel(tmp, com + U3P_USBPHYACR6);
+ }
+}
+
+static int mtk_phy_init(struct phy *phy)
+{
+ struct mtk_phy_instance *instance = phy_get_drvdata(phy);
+ struct mtk_tphy *tphy = dev_get_drvdata(phy->dev.parent);
+ int ret;
+
+ ret = clk_prepare_enable(instance->ref_clk);
+ if (ret) {
+ dev_err(tphy->dev, "failed to enable ref_clk\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(instance->da_ref_clk);
+ if (ret) {
+ dev_err(tphy->dev, "failed to enable da_ref\n");
+ clk_disable_unprepare(instance->ref_clk);
+ return ret;
+ }
+
+ switch (instance->type) {
+ case PHY_TYPE_USB2:
+ u2_phy_instance_init(tphy, instance);
+ u2_phy_props_set(tphy, instance);
+ break;
+ case PHY_TYPE_USB3:
+ u3_phy_instance_init(tphy, instance);
+ break;
+ case PHY_TYPE_PCIE:
+ pcie_phy_instance_init(tphy, instance);
+ break;
+ case PHY_TYPE_SATA:
+ sata_phy_instance_init(tphy, instance);
+ break;
+ default:
+ dev_err(tphy->dev, "incompatible PHY type\n");
+ clk_disable_unprepare(instance->ref_clk);
+ clk_disable_unprepare(instance->da_ref_clk);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mtk_phy_power_on(struct phy *phy)
+{
+ struct mtk_phy_instance *instance = phy_get_drvdata(phy);
+ struct mtk_tphy *tphy = dev_get_drvdata(phy->dev.parent);
+
+ if (instance->type == PHY_TYPE_USB2) {
+ u2_phy_instance_power_on(tphy, instance);
+ hs_slew_rate_calibrate(tphy, instance);
+ } else if (instance->type == PHY_TYPE_PCIE) {
+ pcie_phy_instance_power_on(tphy, instance);
+ }
+
+ return 0;
+}
+
+static int mtk_phy_power_off(struct phy *phy)
+{
+ struct mtk_phy_instance *instance = phy_get_drvdata(phy);
+ struct mtk_tphy *tphy = dev_get_drvdata(phy->dev.parent);
+
+ if (instance->type == PHY_TYPE_USB2)
+ u2_phy_instance_power_off(tphy, instance);
+ else if (instance->type == PHY_TYPE_PCIE)
+ pcie_phy_instance_power_off(tphy, instance);
+
+ return 0;
+}
+
+static int mtk_phy_exit(struct phy *phy)
+{
+ struct mtk_phy_instance *instance = phy_get_drvdata(phy);
+ struct mtk_tphy *tphy = dev_get_drvdata(phy->dev.parent);
+
+ if (instance->type == PHY_TYPE_USB2)
+ u2_phy_instance_exit(tphy, instance);
+
+ clk_disable_unprepare(instance->ref_clk);
+ clk_disable_unprepare(instance->da_ref_clk);
+ return 0;
+}
+
+static int mtk_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+ struct mtk_phy_instance *instance = phy_get_drvdata(phy);
+ struct mtk_tphy *tphy = dev_get_drvdata(phy->dev.parent);
+
+ if (instance->type == PHY_TYPE_USB2)
+ u2_phy_instance_set_mode(tphy, instance, mode);
+
+ return 0;
+}
+
+static struct phy *mtk_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct mtk_tphy *tphy = dev_get_drvdata(dev);
+ struct mtk_phy_instance *instance = NULL;
+ struct device_node *phy_np = args->np;
+ int index;
+
+ if (args->args_count != 1) {
+ dev_err(dev, "invalid number of cells in 'phy' property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ for (index = 0; index < tphy->nphys; index++)
+ if (phy_np == tphy->phys[index]->phy->dev.of_node) {
+ instance = tphy->phys[index];
+ break;
+ }
+
+ if (!instance) {
+ dev_err(dev, "failed to find appropriate phy\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ instance->type = args->args[0];
+ if (!(instance->type == PHY_TYPE_USB2 ||
+ instance->type == PHY_TYPE_USB3 ||
+ instance->type == PHY_TYPE_PCIE ||
+ instance->type == PHY_TYPE_SATA)) {
+ dev_err(dev, "unsupported device type: %d\n", instance->type);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (tphy->pdata->version == MTK_PHY_V1) {
+ phy_v1_banks_init(tphy, instance);
+ } else if (tphy->pdata->version == MTK_PHY_V2) {
+ phy_v2_banks_init(tphy, instance);
+ } else {
+ dev_err(dev, "phy version is not supported\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ phy_parse_property(tphy, instance);
+
+ return instance->phy;
+}
+
+static const struct phy_ops mtk_tphy_ops = {
+ .init = mtk_phy_init,
+ .exit = mtk_phy_exit,
+ .power_on = mtk_phy_power_on,
+ .power_off = mtk_phy_power_off,
+ .set_mode = mtk_phy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static const struct mtk_phy_pdata tphy_v1_pdata = {
+ .avoid_rx_sen_degradation = false,
+ .version = MTK_PHY_V1,
+};
+
+static const struct mtk_phy_pdata tphy_v2_pdata = {
+ .avoid_rx_sen_degradation = false,
+ .version = MTK_PHY_V2,
+};
+
+static const struct mtk_phy_pdata mt8173_pdata = {
+ .avoid_rx_sen_degradation = true,
+ .version = MTK_PHY_V1,
+};
+
+static const struct of_device_id mtk_tphy_id_table[] = {
+ { .compatible = "mediatek,mt2701-u3phy", .data = &tphy_v1_pdata },
+ { .compatible = "mediatek,mt2712-u3phy", .data = &tphy_v2_pdata },
+ { .compatible = "mediatek,mt8173-u3phy", .data = &mt8173_pdata },
+ { .compatible = "mediatek,generic-tphy-v1", .data = &tphy_v1_pdata },
+ { .compatible = "mediatek,generic-tphy-v2", .data = &tphy_v2_pdata },
+ { },
+};
+MODULE_DEVICE_TABLE(of, mtk_tphy_id_table);
+
+static int mtk_tphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *child_np;
+ struct phy_provider *provider;
+ struct resource *sif_res;
+ struct mtk_tphy *tphy;
+ struct resource res;
+ int port, retval;
+
+ tphy = devm_kzalloc(dev, sizeof(*tphy), GFP_KERNEL);
+ if (!tphy)
+ return -ENOMEM;
+
+ tphy->pdata = of_device_get_match_data(dev);
+ if (!tphy->pdata)
+ return -EINVAL;
+
+ tphy->nphys = of_get_child_count(np);
+ tphy->phys = devm_kcalloc(dev, tphy->nphys,
+ sizeof(*tphy->phys), GFP_KERNEL);
+ if (!tphy->phys)
+ return -ENOMEM;
+
+ tphy->dev = dev;
+ platform_set_drvdata(pdev, tphy);
+
+ sif_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ /* SATA phy of V1 needn't it if not shared with PCIe or USB */
+ if (sif_res && tphy->pdata->version == MTK_PHY_V1) {
+ /* get banks shared by multiple phys */
+ tphy->sif_base = devm_ioremap_resource(dev, sif_res);
+ if (IS_ERR(tphy->sif_base)) {
+ dev_err(dev, "failed to remap sif regs\n");
+ return PTR_ERR(tphy->sif_base);
+ }
+ }
+
+ tphy->src_ref_clk = U3P_REF_CLK;
+ tphy->src_coef = U3P_SLEW_RATE_COEF;
+ /* update parameters of slew rate calibrate if exist */
+ device_property_read_u32(dev, "mediatek,src-ref-clk-mhz",
+ &tphy->src_ref_clk);
+ device_property_read_u32(dev, "mediatek,src-coef", &tphy->src_coef);
+
+ port = 0;
+ for_each_child_of_node(np, child_np) {
+ struct mtk_phy_instance *instance;
+ struct phy *phy;
+
+ instance = devm_kzalloc(dev, sizeof(*instance), GFP_KERNEL);
+ if (!instance) {
+ retval = -ENOMEM;
+ goto put_child;
+ }
+
+ tphy->phys[port] = instance;
+
+ phy = devm_phy_create(dev, child_np, &mtk_tphy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create phy\n");
+ retval = PTR_ERR(phy);
+ goto put_child;
+ }
+
+ retval = of_address_to_resource(child_np, 0, &res);
+ if (retval) {
+ dev_err(dev, "failed to get address resource(id-%d)\n",
+ port);
+ goto put_child;
+ }
+
+ instance->port_base = devm_ioremap_resource(&phy->dev, &res);
+ if (IS_ERR(instance->port_base)) {
+ dev_err(dev, "failed to remap phy regs\n");
+ retval = PTR_ERR(instance->port_base);
+ goto put_child;
+ }
+
+ instance->phy = phy;
+ instance->index = port;
+ phy_set_drvdata(phy, instance);
+ port++;
+
+ instance->ref_clk = devm_clk_get_optional(&phy->dev, "ref");
+ if (IS_ERR(instance->ref_clk)) {
+ dev_err(dev, "failed to get ref_clk(id-%d)\n", port);
+ retval = PTR_ERR(instance->ref_clk);
+ goto put_child;
+ }
+
+ instance->da_ref_clk =
+ devm_clk_get_optional(&phy->dev, "da_ref");
+ if (IS_ERR(instance->da_ref_clk)) {
+ dev_err(dev, "failed to get da_ref_clk(id-%d)\n", port);
+ retval = PTR_ERR(instance->da_ref_clk);
+ goto put_child;
+ }
+ }
+
+ provider = devm_of_phy_provider_register(dev, mtk_phy_xlate);
+
+ return PTR_ERR_OR_ZERO(provider);
+put_child:
+ of_node_put(child_np);
+ return retval;
+}
+
+static struct platform_driver mtk_tphy_driver = {
+ .probe = mtk_tphy_probe,
+ .driver = {
+ .name = "mtk-tphy",
+ .of_match_table = mtk_tphy_id_table,
+ },
+};
+
+module_platform_driver(mtk_tphy_driver);
+
+MODULE_AUTHOR("Chunfeng Yun <chunfeng.yun@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek T-PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/mediatek/phy-mtk-ufs.c b/drivers/phy/mediatek/phy-mtk-ufs.c
new file mode 100644
index 000000000..cf94f5c35
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-ufs.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 MediaTek Inc.
+ * Author: Stanley Chu <stanley.chu@mediatek.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+/* mphy register and offsets */
+#define MP_GLB_DIG_8C 0x008C
+#define FRC_PLL_ISO_EN BIT(8)
+#define PLL_ISO_EN BIT(9)
+#define FRC_FRC_PWR_ON BIT(10)
+#define PLL_PWR_ON BIT(11)
+
+#define MP_LN_DIG_RX_9C 0xA09C
+#define FSM_DIFZ_FRC BIT(18)
+
+#define MP_LN_DIG_RX_AC 0xA0AC
+#define FRC_RX_SQ_EN BIT(0)
+#define RX_SQ_EN BIT(1)
+
+#define MP_LN_RX_44 0xB044
+#define FRC_CDR_PWR_ON BIT(17)
+#define CDR_PWR_ON BIT(18)
+#define FRC_CDR_ISO_EN BIT(19)
+#define CDR_ISO_EN BIT(20)
+
+struct ufs_mtk_phy {
+ struct device *dev;
+ void __iomem *mmio;
+ struct clk *mp_clk;
+ struct clk *unipro_clk;
+};
+
+static inline u32 mphy_readl(struct ufs_mtk_phy *phy, u32 reg)
+{
+ return readl(phy->mmio + reg);
+}
+
+static inline void mphy_writel(struct ufs_mtk_phy *phy, u32 val, u32 reg)
+{
+ writel(val, phy->mmio + reg);
+}
+
+static void mphy_set_bit(struct ufs_mtk_phy *phy, u32 reg, u32 bit)
+{
+ u32 val;
+
+ val = mphy_readl(phy, reg);
+ val |= bit;
+ mphy_writel(phy, val, reg);
+}
+
+static void mphy_clr_bit(struct ufs_mtk_phy *phy, u32 reg, u32 bit)
+{
+ u32 val;
+
+ val = mphy_readl(phy, reg);
+ val &= ~bit;
+ mphy_writel(phy, val, reg);
+}
+
+static struct ufs_mtk_phy *get_ufs_mtk_phy(struct phy *generic_phy)
+{
+ return (struct ufs_mtk_phy *)phy_get_drvdata(generic_phy);
+}
+
+static int ufs_mtk_phy_clk_init(struct ufs_mtk_phy *phy)
+{
+ struct device *dev = phy->dev;
+
+ phy->unipro_clk = devm_clk_get(dev, "unipro");
+ if (IS_ERR(phy->unipro_clk)) {
+ dev_err(dev, "failed to get clock: unipro");
+ return PTR_ERR(phy->unipro_clk);
+ }
+
+ phy->mp_clk = devm_clk_get(dev, "mp");
+ if (IS_ERR(phy->mp_clk)) {
+ dev_err(dev, "failed to get clock: mp");
+ return PTR_ERR(phy->mp_clk);
+ }
+
+ return 0;
+}
+
+static void ufs_mtk_phy_set_active(struct ufs_mtk_phy *phy)
+{
+ /* release DA_MP_PLL_PWR_ON */
+ mphy_set_bit(phy, MP_GLB_DIG_8C, PLL_PWR_ON);
+ mphy_clr_bit(phy, MP_GLB_DIG_8C, FRC_FRC_PWR_ON);
+
+ /* release DA_MP_PLL_ISO_EN */
+ mphy_clr_bit(phy, MP_GLB_DIG_8C, PLL_ISO_EN);
+ mphy_clr_bit(phy, MP_GLB_DIG_8C, FRC_PLL_ISO_EN);
+
+ /* release DA_MP_CDR_PWR_ON */
+ mphy_set_bit(phy, MP_LN_RX_44, CDR_PWR_ON);
+ mphy_clr_bit(phy, MP_LN_RX_44, FRC_CDR_PWR_ON);
+
+ /* release DA_MP_CDR_ISO_EN */
+ mphy_clr_bit(phy, MP_LN_RX_44, CDR_ISO_EN);
+ mphy_clr_bit(phy, MP_LN_RX_44, FRC_CDR_ISO_EN);
+
+ /* release DA_MP_RX0_SQ_EN */
+ mphy_set_bit(phy, MP_LN_DIG_RX_AC, RX_SQ_EN);
+ mphy_clr_bit(phy, MP_LN_DIG_RX_AC, FRC_RX_SQ_EN);
+
+ /* delay 1us to wait DIFZ stable */
+ udelay(1);
+
+ /* release DIFZ */
+ mphy_clr_bit(phy, MP_LN_DIG_RX_9C, FSM_DIFZ_FRC);
+}
+
+static void ufs_mtk_phy_set_deep_hibern(struct ufs_mtk_phy *phy)
+{
+ /* force DIFZ */
+ mphy_set_bit(phy, MP_LN_DIG_RX_9C, FSM_DIFZ_FRC);
+
+ /* force DA_MP_RX0_SQ_EN */
+ mphy_set_bit(phy, MP_LN_DIG_RX_AC, FRC_RX_SQ_EN);
+ mphy_clr_bit(phy, MP_LN_DIG_RX_AC, RX_SQ_EN);
+
+ /* force DA_MP_CDR_ISO_EN */
+ mphy_set_bit(phy, MP_LN_RX_44, FRC_CDR_ISO_EN);
+ mphy_set_bit(phy, MP_LN_RX_44, CDR_ISO_EN);
+
+ /* force DA_MP_CDR_PWR_ON */
+ mphy_set_bit(phy, MP_LN_RX_44, FRC_CDR_PWR_ON);
+ mphy_clr_bit(phy, MP_LN_RX_44, CDR_PWR_ON);
+
+ /* force DA_MP_PLL_ISO_EN */
+ mphy_set_bit(phy, MP_GLB_DIG_8C, FRC_PLL_ISO_EN);
+ mphy_set_bit(phy, MP_GLB_DIG_8C, PLL_ISO_EN);
+
+ /* force DA_MP_PLL_PWR_ON */
+ mphy_set_bit(phy, MP_GLB_DIG_8C, FRC_FRC_PWR_ON);
+ mphy_clr_bit(phy, MP_GLB_DIG_8C, PLL_PWR_ON);
+}
+
+static int ufs_mtk_phy_power_on(struct phy *generic_phy)
+{
+ struct ufs_mtk_phy *phy = get_ufs_mtk_phy(generic_phy);
+ int ret;
+
+ ret = clk_prepare_enable(phy->unipro_clk);
+ if (ret) {
+ dev_err(phy->dev, "unipro_clk enable failed %d\n", ret);
+ goto out;
+ }
+
+ ret = clk_prepare_enable(phy->mp_clk);
+ if (ret) {
+ dev_err(phy->dev, "mp_clk enable failed %d\n", ret);
+ goto out_unprepare_unipro_clk;
+ }
+
+ ufs_mtk_phy_set_active(phy);
+
+ return 0;
+
+out_unprepare_unipro_clk:
+ clk_disable_unprepare(phy->unipro_clk);
+out:
+ return ret;
+}
+
+static int ufs_mtk_phy_power_off(struct phy *generic_phy)
+{
+ struct ufs_mtk_phy *phy = get_ufs_mtk_phy(generic_phy);
+
+ ufs_mtk_phy_set_deep_hibern(phy);
+
+ clk_disable_unprepare(phy->unipro_clk);
+ clk_disable_unprepare(phy->mp_clk);
+
+ return 0;
+}
+
+static const struct phy_ops ufs_mtk_phy_ops = {
+ .power_on = ufs_mtk_phy_power_on,
+ .power_off = ufs_mtk_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int ufs_mtk_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy *generic_phy;
+ struct phy_provider *phy_provider;
+ struct resource *res;
+ struct ufs_mtk_phy *phy;
+ int ret;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ phy->mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(phy->mmio))
+ return PTR_ERR(phy->mmio);
+
+ phy->dev = dev;
+
+ ret = ufs_mtk_phy_clk_init(phy);
+ if (ret)
+ return ret;
+
+ generic_phy = devm_phy_create(dev, NULL, &ufs_mtk_phy_ops);
+ if (IS_ERR(generic_phy))
+ return PTR_ERR(generic_phy);
+
+ phy_set_drvdata(generic_phy, phy);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id ufs_mtk_phy_of_match[] = {
+ {.compatible = "mediatek,mt8183-ufsphy"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, ufs_mtk_phy_of_match);
+
+static struct platform_driver ufs_mtk_phy_driver = {
+ .probe = ufs_mtk_phy_probe,
+ .driver = {
+ .of_match_table = ufs_mtk_phy_of_match,
+ .name = "ufs_mtk_phy",
+ },
+};
+module_platform_driver(ufs_mtk_phy_driver);
+
+MODULE_DESCRIPTION("Universal Flash Storage (UFS) MediaTek MPHY");
+MODULE_AUTHOR("Stanley Chu <stanley.chu@mediatek.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/mediatek/phy-mtk-xsphy.c b/drivers/phy/mediatek/phy-mtk-xsphy.c
new file mode 100644
index 000000000..8c5113194
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-xsphy.c
@@ -0,0 +1,600 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MediaTek USB3.1 gen2 xsphy Driver
+ *
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ *
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+/* u2 phy banks */
+#define SSUSB_SIFSLV_MISC 0x000
+#define SSUSB_SIFSLV_U2FREQ 0x100
+#define SSUSB_SIFSLV_U2PHY_COM 0x300
+
+/* u3 phy shared banks */
+#define SSPXTP_SIFSLV_DIG_GLB 0x000
+#define SSPXTP_SIFSLV_PHYA_GLB 0x100
+
+/* u3 phy banks */
+#define SSPXTP_SIFSLV_DIG_LN_TOP 0x000
+#define SSPXTP_SIFSLV_DIG_LN_TX0 0x100
+#define SSPXTP_SIFSLV_DIG_LN_RX0 0x200
+#define SSPXTP_SIFSLV_DIG_LN_DAIF 0x300
+#define SSPXTP_SIFSLV_PHYA_LN 0x400
+
+#define XSP_U2FREQ_FMCR0 ((SSUSB_SIFSLV_U2FREQ) + 0x00)
+#define P2F_RG_FREQDET_EN BIT(24)
+#define P2F_RG_CYCLECNT GENMASK(23, 0)
+#define P2F_RG_CYCLECNT_VAL(x) ((P2F_RG_CYCLECNT) & (x))
+
+#define XSP_U2FREQ_MMONR0 ((SSUSB_SIFSLV_U2FREQ) + 0x0c)
+
+#define XSP_U2FREQ_FMMONR1 ((SSUSB_SIFSLV_U2FREQ) + 0x10)
+#define P2F_RG_FRCK_EN BIT(8)
+#define P2F_USB_FM_VALID BIT(0)
+
+#define XSP_USBPHYACR0 ((SSUSB_SIFSLV_U2PHY_COM) + 0x00)
+#define P2A0_RG_INTR_EN BIT(5)
+
+#define XSP_USBPHYACR1 ((SSUSB_SIFSLV_U2PHY_COM) + 0x04)
+#define P2A1_RG_INTR_CAL GENMASK(23, 19)
+#define P2A1_RG_INTR_CAL_VAL(x) ((0x1f & (x)) << 19)
+#define P2A1_RG_VRT_SEL GENMASK(14, 12)
+#define P2A1_RG_VRT_SEL_VAL(x) ((0x7 & (x)) << 12)
+#define P2A1_RG_TERM_SEL GENMASK(10, 8)
+#define P2A1_RG_TERM_SEL_VAL(x) ((0x7 & (x)) << 8)
+
+#define XSP_USBPHYACR5 ((SSUSB_SIFSLV_U2PHY_COM) + 0x014)
+#define P2A5_RG_HSTX_SRCAL_EN BIT(15)
+#define P2A5_RG_HSTX_SRCTRL GENMASK(14, 12)
+#define P2A5_RG_HSTX_SRCTRL_VAL(x) ((0x7 & (x)) << 12)
+
+#define XSP_USBPHYACR6 ((SSUSB_SIFSLV_U2PHY_COM) + 0x018)
+#define P2A6_RG_BC11_SW_EN BIT(23)
+#define P2A6_RG_OTG_VBUSCMP_EN BIT(20)
+
+#define XSP_U2PHYDTM1 ((SSUSB_SIFSLV_U2PHY_COM) + 0x06C)
+#define P2D_FORCE_IDDIG BIT(9)
+#define P2D_RG_VBUSVALID BIT(5)
+#define P2D_RG_SESSEND BIT(4)
+#define P2D_RG_AVALID BIT(2)
+#define P2D_RG_IDDIG BIT(1)
+
+#define SSPXTP_PHYA_GLB_00 ((SSPXTP_SIFSLV_PHYA_GLB) + 0x00)
+#define RG_XTP_GLB_BIAS_INTR_CTRL GENMASK(21, 16)
+#define RG_XTP_GLB_BIAS_INTR_CTRL_VAL(x) ((0x3f & (x)) << 16)
+
+#define SSPXTP_PHYA_LN_04 ((SSPXTP_SIFSLV_PHYA_LN) + 0x04)
+#define RG_XTP_LN0_TX_IMPSEL GENMASK(4, 0)
+#define RG_XTP_LN0_TX_IMPSEL_VAL(x) (0x1f & (x))
+
+#define SSPXTP_PHYA_LN_14 ((SSPXTP_SIFSLV_PHYA_LN) + 0x014)
+#define RG_XTP_LN0_RX_IMPSEL GENMASK(4, 0)
+#define RG_XTP_LN0_RX_IMPSEL_VAL(x) (0x1f & (x))
+
+#define XSP_REF_CLK 26 /* MHZ */
+#define XSP_SLEW_RATE_COEF 17
+#define XSP_SR_COEF_DIVISOR 1000
+#define XSP_FM_DET_CYCLE_CNT 1024
+
+struct xsphy_instance {
+ struct phy *phy;
+ void __iomem *port_base;
+ struct clk *ref_clk; /* reference clock of anolog phy */
+ u32 index;
+ u32 type;
+ /* only for HQA test */
+ int efuse_intr;
+ int efuse_tx_imp;
+ int efuse_rx_imp;
+ /* u2 eye diagram */
+ int eye_src;
+ int eye_vrt;
+ int eye_term;
+};
+
+struct mtk_xsphy {
+ struct device *dev;
+ void __iomem *glb_base; /* only shared u3 sif */
+ struct xsphy_instance **phys;
+ int nphys;
+ int src_ref_clk; /* MHZ, reference clock for slew rate calibrate */
+ int src_coef; /* coefficient for slew rate calibrate */
+};
+
+static void u2_phy_slew_rate_calibrate(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+ int calib_val;
+ int fm_out;
+ u32 tmp;
+
+ /* use force value */
+ if (inst->eye_src)
+ return;
+
+ /* enable USB ring oscillator */
+ tmp = readl(pbase + XSP_USBPHYACR5);
+ tmp |= P2A5_RG_HSTX_SRCAL_EN;
+ writel(tmp, pbase + XSP_USBPHYACR5);
+ udelay(1); /* wait clock stable */
+
+ /* enable free run clock */
+ tmp = readl(pbase + XSP_U2FREQ_FMMONR1);
+ tmp |= P2F_RG_FRCK_EN;
+ writel(tmp, pbase + XSP_U2FREQ_FMMONR1);
+
+ /* set cycle count as 1024 */
+ tmp = readl(pbase + XSP_U2FREQ_FMCR0);
+ tmp &= ~(P2F_RG_CYCLECNT);
+ tmp |= P2F_RG_CYCLECNT_VAL(XSP_FM_DET_CYCLE_CNT);
+ writel(tmp, pbase + XSP_U2FREQ_FMCR0);
+
+ /* enable frequency meter */
+ tmp = readl(pbase + XSP_U2FREQ_FMCR0);
+ tmp |= P2F_RG_FREQDET_EN;
+ writel(tmp, pbase + XSP_U2FREQ_FMCR0);
+
+ /* ignore return value */
+ readl_poll_timeout(pbase + XSP_U2FREQ_FMMONR1, tmp,
+ (tmp & P2F_USB_FM_VALID), 10, 200);
+
+ fm_out = readl(pbase + XSP_U2FREQ_MMONR0);
+
+ /* disable frequency meter */
+ tmp = readl(pbase + XSP_U2FREQ_FMCR0);
+ tmp &= ~P2F_RG_FREQDET_EN;
+ writel(tmp, pbase + XSP_U2FREQ_FMCR0);
+
+ /* disable free run clock */
+ tmp = readl(pbase + XSP_U2FREQ_FMMONR1);
+ tmp &= ~P2F_RG_FRCK_EN;
+ writel(tmp, pbase + XSP_U2FREQ_FMMONR1);
+
+ if (fm_out) {
+ /* (1024 / FM_OUT) x reference clock frequency x coefficient */
+ tmp = xsphy->src_ref_clk * xsphy->src_coef;
+ tmp = (tmp * XSP_FM_DET_CYCLE_CNT) / fm_out;
+ calib_val = DIV_ROUND_CLOSEST(tmp, XSP_SR_COEF_DIVISOR);
+ } else {
+ /* if FM detection fail, set default value */
+ calib_val = 3;
+ }
+ dev_dbg(xsphy->dev, "phy.%d, fm_out:%d, calib:%d (clk:%d, coef:%d)\n",
+ inst->index, fm_out, calib_val,
+ xsphy->src_ref_clk, xsphy->src_coef);
+
+ /* set HS slew rate */
+ tmp = readl(pbase + XSP_USBPHYACR5);
+ tmp &= ~P2A5_RG_HSTX_SRCTRL;
+ tmp |= P2A5_RG_HSTX_SRCTRL_VAL(calib_val);
+ writel(tmp, pbase + XSP_USBPHYACR5);
+
+ /* disable USB ring oscillator */
+ tmp = readl(pbase + XSP_USBPHYACR5);
+ tmp &= ~P2A5_RG_HSTX_SRCAL_EN;
+ writel(tmp, pbase + XSP_USBPHYACR5);
+}
+
+static void u2_phy_instance_init(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+ u32 tmp;
+
+ /* DP/DM BC1.1 path Disable */
+ tmp = readl(pbase + XSP_USBPHYACR6);
+ tmp &= ~P2A6_RG_BC11_SW_EN;
+ writel(tmp, pbase + XSP_USBPHYACR6);
+
+ tmp = readl(pbase + XSP_USBPHYACR0);
+ tmp |= P2A0_RG_INTR_EN;
+ writel(tmp, pbase + XSP_USBPHYACR0);
+}
+
+static void u2_phy_instance_power_on(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+ u32 index = inst->index;
+ u32 tmp;
+
+ tmp = readl(pbase + XSP_USBPHYACR6);
+ tmp |= P2A6_RG_OTG_VBUSCMP_EN;
+ writel(tmp, pbase + XSP_USBPHYACR6);
+
+ tmp = readl(pbase + XSP_U2PHYDTM1);
+ tmp |= P2D_RG_VBUSVALID | P2D_RG_AVALID;
+ tmp &= ~P2D_RG_SESSEND;
+ writel(tmp, pbase + XSP_U2PHYDTM1);
+
+ dev_dbg(xsphy->dev, "%s(%d)\n", __func__, index);
+}
+
+static void u2_phy_instance_power_off(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+ u32 index = inst->index;
+ u32 tmp;
+
+ tmp = readl(pbase + XSP_USBPHYACR6);
+ tmp &= ~P2A6_RG_OTG_VBUSCMP_EN;
+ writel(tmp, pbase + XSP_USBPHYACR6);
+
+ tmp = readl(pbase + XSP_U2PHYDTM1);
+ tmp &= ~(P2D_RG_VBUSVALID | P2D_RG_AVALID);
+ tmp |= P2D_RG_SESSEND;
+ writel(tmp, pbase + XSP_U2PHYDTM1);
+
+ dev_dbg(xsphy->dev, "%s(%d)\n", __func__, index);
+}
+
+static void u2_phy_instance_set_mode(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst,
+ enum phy_mode mode)
+{
+ u32 tmp;
+
+ tmp = readl(inst->port_base + XSP_U2PHYDTM1);
+ switch (mode) {
+ case PHY_MODE_USB_DEVICE:
+ tmp |= P2D_FORCE_IDDIG | P2D_RG_IDDIG;
+ break;
+ case PHY_MODE_USB_HOST:
+ tmp |= P2D_FORCE_IDDIG;
+ tmp &= ~P2D_RG_IDDIG;
+ break;
+ case PHY_MODE_USB_OTG:
+ tmp &= ~(P2D_FORCE_IDDIG | P2D_RG_IDDIG);
+ break;
+ default:
+ return;
+ }
+ writel(tmp, inst->port_base + XSP_U2PHYDTM1);
+}
+
+static void phy_parse_property(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ struct device *dev = &inst->phy->dev;
+
+ switch (inst->type) {
+ case PHY_TYPE_USB2:
+ device_property_read_u32(dev, "mediatek,efuse-intr",
+ &inst->efuse_intr);
+ device_property_read_u32(dev, "mediatek,eye-src",
+ &inst->eye_src);
+ device_property_read_u32(dev, "mediatek,eye-vrt",
+ &inst->eye_vrt);
+ device_property_read_u32(dev, "mediatek,eye-term",
+ &inst->eye_term);
+ dev_dbg(dev, "intr:%d, src:%d, vrt:%d, term:%d\n",
+ inst->efuse_intr, inst->eye_src,
+ inst->eye_vrt, inst->eye_term);
+ break;
+ case PHY_TYPE_USB3:
+ device_property_read_u32(dev, "mediatek,efuse-intr",
+ &inst->efuse_intr);
+ device_property_read_u32(dev, "mediatek,efuse-tx-imp",
+ &inst->efuse_tx_imp);
+ device_property_read_u32(dev, "mediatek,efuse-rx-imp",
+ &inst->efuse_rx_imp);
+ dev_dbg(dev, "intr:%d, tx-imp:%d, rx-imp:%d\n",
+ inst->efuse_intr, inst->efuse_tx_imp,
+ inst->efuse_rx_imp);
+ break;
+ default:
+ dev_err(xsphy->dev, "incompatible phy type\n");
+ return;
+ }
+}
+
+static void u2_phy_props_set(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+ u32 tmp;
+
+ if (inst->efuse_intr) {
+ tmp = readl(pbase + XSP_USBPHYACR1);
+ tmp &= ~P2A1_RG_INTR_CAL;
+ tmp |= P2A1_RG_INTR_CAL_VAL(inst->efuse_intr);
+ writel(tmp, pbase + XSP_USBPHYACR1);
+ }
+
+ if (inst->eye_src) {
+ tmp = readl(pbase + XSP_USBPHYACR5);
+ tmp &= ~P2A5_RG_HSTX_SRCTRL;
+ tmp |= P2A5_RG_HSTX_SRCTRL_VAL(inst->eye_src);
+ writel(tmp, pbase + XSP_USBPHYACR5);
+ }
+
+ if (inst->eye_vrt) {
+ tmp = readl(pbase + XSP_USBPHYACR1);
+ tmp &= ~P2A1_RG_VRT_SEL;
+ tmp |= P2A1_RG_VRT_SEL_VAL(inst->eye_vrt);
+ writel(tmp, pbase + XSP_USBPHYACR1);
+ }
+
+ if (inst->eye_term) {
+ tmp = readl(pbase + XSP_USBPHYACR1);
+ tmp &= ~P2A1_RG_TERM_SEL;
+ tmp |= P2A1_RG_TERM_SEL_VAL(inst->eye_term);
+ writel(tmp, pbase + XSP_USBPHYACR1);
+ }
+}
+
+static void u3_phy_props_set(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+ u32 tmp;
+
+ if (inst->efuse_intr) {
+ tmp = readl(xsphy->glb_base + SSPXTP_PHYA_GLB_00);
+ tmp &= ~RG_XTP_GLB_BIAS_INTR_CTRL;
+ tmp |= RG_XTP_GLB_BIAS_INTR_CTRL_VAL(inst->efuse_intr);
+ writel(tmp, xsphy->glb_base + SSPXTP_PHYA_GLB_00);
+ }
+
+ if (inst->efuse_tx_imp) {
+ tmp = readl(pbase + SSPXTP_PHYA_LN_04);
+ tmp &= ~RG_XTP_LN0_TX_IMPSEL;
+ tmp |= RG_XTP_LN0_TX_IMPSEL_VAL(inst->efuse_tx_imp);
+ writel(tmp, pbase + SSPXTP_PHYA_LN_04);
+ }
+
+ if (inst->efuse_rx_imp) {
+ tmp = readl(pbase + SSPXTP_PHYA_LN_14);
+ tmp &= ~RG_XTP_LN0_RX_IMPSEL;
+ tmp |= RG_XTP_LN0_RX_IMPSEL_VAL(inst->efuse_rx_imp);
+ writel(tmp, pbase + SSPXTP_PHYA_LN_14);
+ }
+}
+
+static int mtk_phy_init(struct phy *phy)
+{
+ struct xsphy_instance *inst = phy_get_drvdata(phy);
+ struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent);
+ int ret;
+
+ ret = clk_prepare_enable(inst->ref_clk);
+ if (ret) {
+ dev_err(xsphy->dev, "failed to enable ref_clk\n");
+ return ret;
+ }
+
+ switch (inst->type) {
+ case PHY_TYPE_USB2:
+ u2_phy_instance_init(xsphy, inst);
+ u2_phy_props_set(xsphy, inst);
+ break;
+ case PHY_TYPE_USB3:
+ u3_phy_props_set(xsphy, inst);
+ break;
+ default:
+ dev_err(xsphy->dev, "incompatible phy type\n");
+ clk_disable_unprepare(inst->ref_clk);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mtk_phy_power_on(struct phy *phy)
+{
+ struct xsphy_instance *inst = phy_get_drvdata(phy);
+ struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent);
+
+ if (inst->type == PHY_TYPE_USB2) {
+ u2_phy_instance_power_on(xsphy, inst);
+ u2_phy_slew_rate_calibrate(xsphy, inst);
+ }
+
+ return 0;
+}
+
+static int mtk_phy_power_off(struct phy *phy)
+{
+ struct xsphy_instance *inst = phy_get_drvdata(phy);
+ struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent);
+
+ if (inst->type == PHY_TYPE_USB2)
+ u2_phy_instance_power_off(xsphy, inst);
+
+ return 0;
+}
+
+static int mtk_phy_exit(struct phy *phy)
+{
+ struct xsphy_instance *inst = phy_get_drvdata(phy);
+
+ clk_disable_unprepare(inst->ref_clk);
+ return 0;
+}
+
+static int mtk_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+ struct xsphy_instance *inst = phy_get_drvdata(phy);
+ struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent);
+
+ if (inst->type == PHY_TYPE_USB2)
+ u2_phy_instance_set_mode(xsphy, inst, mode);
+
+ return 0;
+}
+
+static struct phy *mtk_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct mtk_xsphy *xsphy = dev_get_drvdata(dev);
+ struct xsphy_instance *inst = NULL;
+ struct device_node *phy_np = args->np;
+ int index;
+
+ if (args->args_count != 1) {
+ dev_err(dev, "invalid number of cells in 'phy' property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ for (index = 0; index < xsphy->nphys; index++)
+ if (phy_np == xsphy->phys[index]->phy->dev.of_node) {
+ inst = xsphy->phys[index];
+ break;
+ }
+
+ if (!inst) {
+ dev_err(dev, "failed to find appropriate phy\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ inst->type = args->args[0];
+ if (!(inst->type == PHY_TYPE_USB2 ||
+ inst->type == PHY_TYPE_USB3)) {
+ dev_err(dev, "unsupported phy type: %d\n", inst->type);
+ return ERR_PTR(-EINVAL);
+ }
+
+ phy_parse_property(xsphy, inst);
+
+ return inst->phy;
+}
+
+static const struct phy_ops mtk_xsphy_ops = {
+ .init = mtk_phy_init,
+ .exit = mtk_phy_exit,
+ .power_on = mtk_phy_power_on,
+ .power_off = mtk_phy_power_off,
+ .set_mode = mtk_phy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id mtk_xsphy_id_table[] = {
+ { .compatible = "mediatek,xsphy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, mtk_xsphy_id_table);
+
+static int mtk_xsphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *child_np;
+ struct phy_provider *provider;
+ struct resource *glb_res;
+ struct mtk_xsphy *xsphy;
+ struct resource res;
+ int port, retval;
+
+ xsphy = devm_kzalloc(dev, sizeof(*xsphy), GFP_KERNEL);
+ if (!xsphy)
+ return -ENOMEM;
+
+ xsphy->nphys = of_get_child_count(np);
+ xsphy->phys = devm_kcalloc(dev, xsphy->nphys,
+ sizeof(*xsphy->phys), GFP_KERNEL);
+ if (!xsphy->phys)
+ return -ENOMEM;
+
+ xsphy->dev = dev;
+ platform_set_drvdata(pdev, xsphy);
+
+ glb_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ /* optional, may not exist if no u3 phys */
+ if (glb_res) {
+ /* get banks shared by multiple u3 phys */
+ xsphy->glb_base = devm_ioremap_resource(dev, glb_res);
+ if (IS_ERR(xsphy->glb_base)) {
+ dev_err(dev, "failed to remap glb regs\n");
+ return PTR_ERR(xsphy->glb_base);
+ }
+ }
+
+ xsphy->src_ref_clk = XSP_REF_CLK;
+ xsphy->src_coef = XSP_SLEW_RATE_COEF;
+ /* update parameters of slew rate calibrate if exist */
+ device_property_read_u32(dev, "mediatek,src-ref-clk-mhz",
+ &xsphy->src_ref_clk);
+ device_property_read_u32(dev, "mediatek,src-coef", &xsphy->src_coef);
+
+ port = 0;
+ for_each_child_of_node(np, child_np) {
+ struct xsphy_instance *inst;
+ struct phy *phy;
+
+ inst = devm_kzalloc(dev, sizeof(*inst), GFP_KERNEL);
+ if (!inst) {
+ retval = -ENOMEM;
+ goto put_child;
+ }
+
+ xsphy->phys[port] = inst;
+
+ phy = devm_phy_create(dev, child_np, &mtk_xsphy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create phy\n");
+ retval = PTR_ERR(phy);
+ goto put_child;
+ }
+
+ retval = of_address_to_resource(child_np, 0, &res);
+ if (retval) {
+ dev_err(dev, "failed to get address resource(id-%d)\n",
+ port);
+ goto put_child;
+ }
+
+ inst->port_base = devm_ioremap_resource(&phy->dev, &res);
+ if (IS_ERR(inst->port_base)) {
+ dev_err(dev, "failed to remap phy regs\n");
+ retval = PTR_ERR(inst->port_base);
+ goto put_child;
+ }
+
+ inst->phy = phy;
+ inst->index = port;
+ phy_set_drvdata(phy, inst);
+ port++;
+
+ inst->ref_clk = devm_clk_get(&phy->dev, "ref");
+ if (IS_ERR(inst->ref_clk)) {
+ dev_err(dev, "failed to get ref_clk(id-%d)\n", port);
+ retval = PTR_ERR(inst->ref_clk);
+ goto put_child;
+ }
+ }
+
+ provider = devm_of_phy_provider_register(dev, mtk_phy_xlate);
+ return PTR_ERR_OR_ZERO(provider);
+
+put_child:
+ of_node_put(child_np);
+ return retval;
+}
+
+static struct platform_driver mtk_xsphy_driver = {
+ .probe = mtk_xsphy_probe,
+ .driver = {
+ .name = "mtk-xsphy",
+ .of_match_table = mtk_xsphy_id_table,
+ },
+};
+
+module_platform_driver(mtk_xsphy_driver);
+
+MODULE_AUTHOR("Chunfeng Yun <chunfeng.yun@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek USB XS-PHY driver");
+MODULE_LICENSE("GPL v2");