summaryrefslogtreecommitdiffstats
path: root/drivers/phy/mediatek
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/phy/mediatek')
-rw-r--r--drivers/phy/mediatek/Kconfig76
-rw-r--r--drivers/phy/mediatek/Makefile20
-rw-r--r--drivers/phy/mediatek/phy-mtk-dp.c202
-rw-r--r--drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c227
-rw-r--r--drivers/phy/mediatek/phy-mtk-hdmi-mt8173.c254
-rw-r--r--drivers/phy/mediatek/phy-mtk-hdmi.c166
-rw-r--r--drivers/phy/mediatek/phy-mtk-hdmi.h49
-rw-r--r--drivers/phy/mediatek/phy-mtk-io.h46
-rw-r--r--drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8173.c292
-rw-r--r--drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8183.c177
-rw-r--r--drivers/phy/mediatek/phy-mtk-mipi-dsi.c211
-rw-r--r--drivers/phy/mediatek/phy-mtk-mipi-dsi.h48
-rw-r--r--drivers/phy/mediatek/phy-mtk-pcie.c266
-rw-r--r--drivers/phy/mediatek/phy-mtk-tphy.c1317
-rw-r--r--drivers/phy/mediatek/phy-mtk-ufs.c199
-rw-r--r--drivers/phy/mediatek/phy-mtk-xsphy.c540
16 files changed, 4090 insertions, 0 deletions
diff --git a/drivers/phy/mediatek/Kconfig b/drivers/phy/mediatek/Kconfig
new file mode 100644
index 000000000..3125ecb5d
--- /dev/null
+++ b/drivers/phy/mediatek/Kconfig
@@ -0,0 +1,76 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Phy drivers for Mediatek devices
+#
+config PHY_MTK_PCIE
+ tristate "MediaTek PCIe-PHY Driver"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on OF
+ select GENERIC_PHY
+ help
+ Say 'Y' here to add support for MediaTek PCIe PHY driver.
+ This driver create the basic PHY instance and provides initialize
+ callback for PCIe GEN3 port, it supports software efuse
+ initialization.
+
+config PHY_MTK_TPHY
+ tristate "MediaTek T-PHY Driver"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on OF && OF_ADDRESS
+ depends on HAS_IOMEM
+ 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 && OF_ADDRESS
+ depends on HAS_IOMEM
+ 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.
+
+config PHY_MTK_MIPI_DSI
+ tristate "MediaTek MIPI-DSI Driver"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on COMMON_CLK
+ depends on OF
+ select GENERIC_PHY
+ help
+ Support MIPI DSI for Mediatek SoCs.
+
+config PHY_MTK_DP
+ tristate "MediaTek DP-PHY Driver"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on OF
+ select GENERIC_PHY
+ help
+ Support DisplayPort PHY for MediaTek SoCs.
diff --git a/drivers/phy/mediatek/Makefile b/drivers/phy/mediatek/Makefile
new file mode 100644
index 000000000..fb1f8edaf
--- /dev/null
+++ b/drivers/phy/mediatek/Makefile
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the phy drivers.
+#
+
+obj-$(CONFIG_PHY_MTK_DP) += phy-mtk-dp.o
+obj-$(CONFIG_PHY_MTK_PCIE) += phy-mtk-pcie.o
+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
+
+phy-mtk-mipi-dsi-drv-y := phy-mtk-mipi-dsi.o
+phy-mtk-mipi-dsi-drv-y += phy-mtk-mipi-dsi-mt8173.o
+phy-mtk-mipi-dsi-drv-y += phy-mtk-mipi-dsi-mt8183.o
+obj-$(CONFIG_PHY_MTK_MIPI_DSI) += phy-mtk-mipi-dsi-drv.o
diff --git a/drivers/phy/mediatek/phy-mtk-dp.c b/drivers/phy/mediatek/phy-mtk-dp.c
new file mode 100644
index 000000000..d7024a144
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-dp.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MediaTek DisplayPort PHY driver
+ *
+ * Copyright (c) 2022, BayLibre Inc.
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define PHY_OFFSET 0x1000
+
+#define MTK_DP_PHY_DIG_PLL_CTL_1 (PHY_OFFSET + 0x14)
+#define TPLL_SSC_EN BIT(3)
+
+#define MTK_DP_PHY_DIG_BIT_RATE (PHY_OFFSET + 0x3C)
+#define BIT_RATE_RBR 0
+#define BIT_RATE_HBR 1
+#define BIT_RATE_HBR2 2
+#define BIT_RATE_HBR3 3
+
+#define MTK_DP_PHY_DIG_SW_RST (PHY_OFFSET + 0x38)
+#define DP_GLB_SW_RST_PHYD BIT(0)
+
+#define MTK_DP_LANE0_DRIVING_PARAM_3 (PHY_OFFSET + 0x138)
+#define MTK_DP_LANE1_DRIVING_PARAM_3 (PHY_OFFSET + 0x238)
+#define MTK_DP_LANE2_DRIVING_PARAM_3 (PHY_OFFSET + 0x338)
+#define MTK_DP_LANE3_DRIVING_PARAM_3 (PHY_OFFSET + 0x438)
+#define XTP_LN_TX_LCTXC0_SW0_PRE0_DEFAULT BIT(4)
+#define XTP_LN_TX_LCTXC0_SW0_PRE1_DEFAULT (BIT(10) | BIT(12))
+#define XTP_LN_TX_LCTXC0_SW0_PRE2_DEFAULT GENMASK(20, 19)
+#define XTP_LN_TX_LCTXC0_SW0_PRE3_DEFAULT GENMASK(29, 29)
+#define DRIVING_PARAM_3_DEFAULT (XTP_LN_TX_LCTXC0_SW0_PRE0_DEFAULT | \
+ XTP_LN_TX_LCTXC0_SW0_PRE1_DEFAULT | \
+ XTP_LN_TX_LCTXC0_SW0_PRE2_DEFAULT | \
+ XTP_LN_TX_LCTXC0_SW0_PRE3_DEFAULT)
+
+#define XTP_LN_TX_LCTXC0_SW1_PRE0_DEFAULT GENMASK(4, 3)
+#define XTP_LN_TX_LCTXC0_SW1_PRE1_DEFAULT GENMASK(12, 9)
+#define XTP_LN_TX_LCTXC0_SW1_PRE2_DEFAULT (BIT(18) | BIT(21))
+#define XTP_LN_TX_LCTXC0_SW2_PRE0_DEFAULT GENMASK(29, 29)
+#define DRIVING_PARAM_4_DEFAULT (XTP_LN_TX_LCTXC0_SW1_PRE0_DEFAULT | \
+ XTP_LN_TX_LCTXC0_SW1_PRE1_DEFAULT | \
+ XTP_LN_TX_LCTXC0_SW1_PRE2_DEFAULT | \
+ XTP_LN_TX_LCTXC0_SW2_PRE0_DEFAULT)
+
+#define XTP_LN_TX_LCTXC0_SW2_PRE1_DEFAULT (BIT(3) | BIT(5))
+#define XTP_LN_TX_LCTXC0_SW3_PRE0_DEFAULT GENMASK(13, 12)
+#define DRIVING_PARAM_5_DEFAULT (XTP_LN_TX_LCTXC0_SW2_PRE1_DEFAULT | \
+ XTP_LN_TX_LCTXC0_SW3_PRE0_DEFAULT)
+
+#define XTP_LN_TX_LCTXCP1_SW0_PRE0_DEFAULT 0
+#define XTP_LN_TX_LCTXCP1_SW0_PRE1_DEFAULT GENMASK(10, 10)
+#define XTP_LN_TX_LCTXCP1_SW0_PRE2_DEFAULT GENMASK(19, 19)
+#define XTP_LN_TX_LCTXCP1_SW0_PRE3_DEFAULT GENMASK(28, 28)
+#define DRIVING_PARAM_6_DEFAULT (XTP_LN_TX_LCTXCP1_SW0_PRE0_DEFAULT | \
+ XTP_LN_TX_LCTXCP1_SW0_PRE1_DEFAULT | \
+ XTP_LN_TX_LCTXCP1_SW0_PRE2_DEFAULT | \
+ XTP_LN_TX_LCTXCP1_SW0_PRE3_DEFAULT)
+
+#define XTP_LN_TX_LCTXCP1_SW1_PRE0_DEFAULT 0
+#define XTP_LN_TX_LCTXCP1_SW1_PRE1_DEFAULT GENMASK(10, 9)
+#define XTP_LN_TX_LCTXCP1_SW1_PRE2_DEFAULT GENMASK(19, 18)
+#define XTP_LN_TX_LCTXCP1_SW2_PRE0_DEFAULT 0
+#define DRIVING_PARAM_7_DEFAULT (XTP_LN_TX_LCTXCP1_SW1_PRE0_DEFAULT | \
+ XTP_LN_TX_LCTXCP1_SW1_PRE1_DEFAULT | \
+ XTP_LN_TX_LCTXCP1_SW1_PRE2_DEFAULT | \
+ XTP_LN_TX_LCTXCP1_SW2_PRE0_DEFAULT)
+
+#define XTP_LN_TX_LCTXCP1_SW2_PRE1_DEFAULT GENMASK(3, 3)
+#define XTP_LN_TX_LCTXCP1_SW3_PRE0_DEFAULT 0
+#define DRIVING_PARAM_8_DEFAULT (XTP_LN_TX_LCTXCP1_SW2_PRE1_DEFAULT | \
+ XTP_LN_TX_LCTXCP1_SW3_PRE0_DEFAULT)
+
+struct mtk_dp_phy {
+ struct regmap *regs;
+};
+
+static int mtk_dp_phy_init(struct phy *phy)
+{
+ struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy);
+ static const u32 driving_params[] = {
+ DRIVING_PARAM_3_DEFAULT,
+ DRIVING_PARAM_4_DEFAULT,
+ DRIVING_PARAM_5_DEFAULT,
+ DRIVING_PARAM_6_DEFAULT,
+ DRIVING_PARAM_7_DEFAULT,
+ DRIVING_PARAM_8_DEFAULT
+ };
+
+ regmap_bulk_write(dp_phy->regs, MTK_DP_LANE0_DRIVING_PARAM_3,
+ driving_params, ARRAY_SIZE(driving_params));
+ regmap_bulk_write(dp_phy->regs, MTK_DP_LANE1_DRIVING_PARAM_3,
+ driving_params, ARRAY_SIZE(driving_params));
+ regmap_bulk_write(dp_phy->regs, MTK_DP_LANE2_DRIVING_PARAM_3,
+ driving_params, ARRAY_SIZE(driving_params));
+ regmap_bulk_write(dp_phy->regs, MTK_DP_LANE3_DRIVING_PARAM_3,
+ driving_params, ARRAY_SIZE(driving_params));
+
+ return 0;
+}
+
+static int mtk_dp_phy_configure(struct phy *phy, union phy_configure_opts *opts)
+{
+ struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy);
+ u32 val;
+
+ if (opts->dp.set_rate) {
+ switch (opts->dp.link_rate) {
+ default:
+ dev_err(&phy->dev,
+ "Implementation error, unknown linkrate %x\n",
+ opts->dp.link_rate);
+ return -EINVAL;
+ case 1620:
+ val = BIT_RATE_RBR;
+ break;
+ case 2700:
+ val = BIT_RATE_HBR;
+ break;
+ case 5400:
+ val = BIT_RATE_HBR2;
+ break;
+ case 8100:
+ val = BIT_RATE_HBR3;
+ break;
+ }
+ regmap_write(dp_phy->regs, MTK_DP_PHY_DIG_BIT_RATE, val);
+ }
+
+ regmap_update_bits(dp_phy->regs, MTK_DP_PHY_DIG_PLL_CTL_1,
+ TPLL_SSC_EN, opts->dp.ssc ? TPLL_SSC_EN : 0);
+
+ return 0;
+}
+
+static int mtk_dp_phy_reset(struct phy *phy)
+{
+ struct mtk_dp_phy *dp_phy = phy_get_drvdata(phy);
+
+ regmap_update_bits(dp_phy->regs, MTK_DP_PHY_DIG_SW_RST,
+ DP_GLB_SW_RST_PHYD, 0);
+ usleep_range(50, 200);
+ regmap_update_bits(dp_phy->regs, MTK_DP_PHY_DIG_SW_RST,
+ DP_GLB_SW_RST_PHYD, 1);
+
+ return 0;
+}
+
+static const struct phy_ops mtk_dp_phy_dev_ops = {
+ .init = mtk_dp_phy_init,
+ .configure = mtk_dp_phy_configure,
+ .reset = mtk_dp_phy_reset,
+ .owner = THIS_MODULE,
+};
+
+static int mtk_dp_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mtk_dp_phy *dp_phy;
+ struct phy *phy;
+ struct regmap *regs;
+
+ regs = *(struct regmap **)dev->platform_data;
+ if (!regs)
+ return dev_err_probe(dev, -EINVAL,
+ "No data passed, requires struct regmap**\n");
+
+ dp_phy = devm_kzalloc(dev, sizeof(*dp_phy), GFP_KERNEL);
+ if (!dp_phy)
+ return -ENOMEM;
+
+ dp_phy->regs = regs;
+ phy = devm_phy_create(dev, NULL, &mtk_dp_phy_dev_ops);
+ if (IS_ERR(phy))
+ return dev_err_probe(dev, PTR_ERR(phy),
+ "Failed to create DP PHY\n");
+
+ phy_set_drvdata(phy, dp_phy);
+ if (!dev->of_node)
+ phy_create_lookup(phy, "dp", dev_name(dev));
+
+ return 0;
+}
+
+static struct platform_driver mtk_dp_phy_driver = {
+ .probe = mtk_dp_phy_probe,
+ .driver = {
+ .name = "mediatek-dp-phy",
+ },
+};
+module_platform_driver(mtk_dp_phy_driver);
+
+MODULE_AUTHOR("Markus Schneider-Pargmann <msp@baylibre.com>");
+MODULE_DESCRIPTION("MediaTek DP PHY Driver");
+MODULE_LICENSE("GPL");
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..e51b2d13e
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Chunhui Dai <chunhui.dai@mediatek.com>
+ */
+
+#include "phy-mtk-hdmi.h"
+#include "phy-mtk-io.h"
+
+#define HDMI_CON0 0x00
+#define RG_HDMITX_DRV_IBIAS_MASK GENMASK(5, 0)
+#define RG_HDMITX_EN_SER_MASK GENMASK(15, 12)
+#define RG_HDMITX_EN_SLDO_MASK GENMASK(19, 16)
+#define RG_HDMITX_EN_PRED_MASK GENMASK(23, 20)
+#define RG_HDMITX_EN_IMP_MASK GENMASK(27, 24)
+#define RG_HDMITX_EN_DRV_MASK GENMASK(31, 28)
+
+#define HDMI_CON1 0x04
+#define RG_HDMITX_PRED_IBIAS_MASK GENMASK(21, 18)
+#define RG_HDMITX_PRED_IMP BIT(22)
+#define RG_HDMITX_DRV_IMP_MASK GENMASK(31, 26)
+
+#define HDMI_CON2 0x08
+#define RG_HDMITX_EN_TX_CKLDO BIT(0)
+#define RG_HDMITX_EN_TX_POSDIV BIT(1)
+#define RG_HDMITX_TX_POSDIV_MASK GENMASK(4, 3)
+#define RG_HDMITX_EN_MBIAS BIT(6)
+#define RG_HDMITX_MBIAS_LPF_EN BIT(7)
+
+#define HDMI_CON4 0x10
+#define RG_HDMITX_RESERVE_MASK GENMASK(31, 0)
+
+#define HDMI_CON6 0x18
+#define RG_HTPLL_BR_MASK GENMASK(1, 0)
+#define RG_HTPLL_BC_MASK GENMASK(3, 2)
+#define RG_HTPLL_BP_MASK GENMASK(7, 4)
+#define RG_HTPLL_IR_MASK GENMASK(11, 8)
+#define RG_HTPLL_IC_MASK GENMASK(15, 12)
+#define RG_HTPLL_POSDIV_MASK GENMASK(17, 16)
+#define RG_HTPLL_PREDIV_MASK GENMASK(19, 18)
+#define RG_HTPLL_FBKSEL_MASK GENMASK(21, 20)
+#define RG_HTPLL_RLH_EN BIT(22)
+#define RG_HTPLL_FBKDIV_MASK GENMASK(30, 24)
+#define RG_HTPLL_EN BIT(31)
+
+#define HDMI_CON7 0x1c
+#define RG_HTPLL_AUTOK_EN BIT(23)
+#define RG_HTPLL_DIVEN_MASK GENMASK(30, 28)
+
+static int mtk_hdmi_pll_prepare(struct clk_hw *hw)
+{
+ struct mtk_hdmi_phy *hdmi_phy = to_mtk_hdmi_phy(hw);
+ void __iomem *base = hdmi_phy->regs;
+
+ mtk_phy_set_bits(base + HDMI_CON7, RG_HTPLL_AUTOK_EN);
+ mtk_phy_clear_bits(base + HDMI_CON6, RG_HTPLL_RLH_EN);
+ mtk_phy_set_bits(base + HDMI_CON6, RG_HTPLL_POSDIV_MASK);
+ mtk_phy_set_bits(base + HDMI_CON2, RG_HDMITX_EN_MBIAS);
+ usleep_range(80, 100);
+ mtk_phy_set_bits(base + HDMI_CON6, RG_HTPLL_EN);
+ mtk_phy_set_bits(base + HDMI_CON2, RG_HDMITX_EN_TX_CKLDO);
+ mtk_phy_set_bits(base + HDMI_CON0, RG_HDMITX_EN_SLDO_MASK);
+ usleep_range(80, 100);
+ mtk_phy_set_bits(base + HDMI_CON2, RG_HDMITX_MBIAS_LPF_EN);
+ mtk_phy_set_bits(base + HDMI_CON0, RG_HDMITX_EN_SER_MASK);
+ mtk_phy_set_bits(base + HDMI_CON0, RG_HDMITX_EN_PRED_MASK);
+ mtk_phy_set_bits(base + 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);
+ void __iomem *base = hdmi_phy->regs;
+
+ mtk_phy_clear_bits(base + HDMI_CON0, RG_HDMITX_EN_DRV_MASK);
+ mtk_phy_clear_bits(base + HDMI_CON0, RG_HDMITX_EN_PRED_MASK);
+ mtk_phy_clear_bits(base + HDMI_CON0, RG_HDMITX_EN_SER_MASK);
+ mtk_phy_clear_bits(base + HDMI_CON2, RG_HDMITX_MBIAS_LPF_EN);
+ usleep_range(80, 100);
+ mtk_phy_clear_bits(base + HDMI_CON0, RG_HDMITX_EN_SLDO_MASK);
+ mtk_phy_clear_bits(base + HDMI_CON2, RG_HDMITX_EN_TX_CKLDO);
+ mtk_phy_clear_bits(base + HDMI_CON6, RG_HTPLL_EN);
+ usleep_range(80, 100);
+ mtk_phy_clear_bits(base + HDMI_CON2, RG_HDMITX_EN_MBIAS);
+ mtk_phy_clear_bits(base + HDMI_CON6, RG_HTPLL_POSDIV_MASK);
+ mtk_phy_clear_bits(base + HDMI_CON6, RG_HTPLL_RLH_EN);
+ mtk_phy_clear_bits(base + 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);
+ void __iomem *base = hdmi_phy->regs;
+ u32 pos_div;
+
+ if (rate <= 64000000)
+ pos_div = 3;
+ else if (rate <= 128000000)
+ pos_div = 2;
+ else
+ pos_div = 1;
+
+ mtk_phy_set_bits(base + HDMI_CON6, RG_HTPLL_PREDIV_MASK);
+ mtk_phy_set_bits(base + HDMI_CON6, RG_HTPLL_POSDIV_MASK);
+ mtk_phy_set_bits(base + HDMI_CON2, RG_HDMITX_EN_TX_POSDIV);
+ mtk_phy_update_field(base + HDMI_CON6, RG_HTPLL_IC_MASK, 0x1);
+ mtk_phy_update_field(base + HDMI_CON6, RG_HTPLL_IR_MASK, 0x1);
+ mtk_phy_update_field(base + HDMI_CON2, RG_HDMITX_TX_POSDIV_MASK, pos_div);
+ mtk_phy_update_field(base + HDMI_CON6, RG_HTPLL_FBKSEL_MASK, 1);
+ mtk_phy_update_field(base + HDMI_CON6, RG_HTPLL_FBKDIV_MASK, 19);
+ mtk_phy_update_field(base + HDMI_CON7, RG_HTPLL_DIVEN_MASK, 0x2);
+ mtk_phy_update_field(base + HDMI_CON6, RG_HTPLL_BP_MASK, 0xc);
+ mtk_phy_update_field(base + HDMI_CON6, RG_HTPLL_BC_MASK, 0x2);
+ mtk_phy_update_field(base + HDMI_CON6, RG_HTPLL_BR_MASK, 0x1);
+
+ mtk_phy_clear_bits(base + HDMI_CON1, RG_HDMITX_PRED_IMP);
+ mtk_phy_update_field(base + HDMI_CON1, RG_HDMITX_PRED_IBIAS_MASK, 0x3);
+ mtk_phy_clear_bits(base + HDMI_CON0, RG_HDMITX_EN_IMP_MASK);
+ mtk_phy_update_field(base + HDMI_CON1, RG_HDMITX_DRV_IMP_MASK, 0x28);
+ mtk_phy_update_field(base + HDMI_CON4, RG_HDMITX_RESERVE_MASK, 0x28);
+ mtk_phy_update_field(base + HDMI_CON0, RG_HDMITX_DRV_IBIAS_MASK, 0xa);
+ 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;
+ u32 tmp;
+
+ tmp = readl(hdmi_phy->regs + HDMI_CON6);
+ val = FIELD_GET(RG_HTPLL_PREDIV_MASK, tmp);
+ 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 = FIELD_GET(RG_HTPLL_FBKDIV_MASK, tmp);
+ out_rate *= (val + 1) * 2;
+
+ tmp = readl(hdmi_phy->regs + HDMI_CON2);
+ val = FIELD_GET(RG_HDMITX_TX_POSDIV_MASK, tmp);
+ out_rate >>= val;
+
+ if (tmp & 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)
+{
+ void __iomem *base = hdmi_phy->regs;
+
+ mtk_phy_set_bits(base + HDMI_CON7, RG_HTPLL_AUTOK_EN);
+ mtk_phy_clear_bits(base + HDMI_CON6, RG_HTPLL_RLH_EN);
+ mtk_phy_set_bits(base + HDMI_CON6, RG_HTPLL_POSDIV_MASK);
+ mtk_phy_set_bits(base + HDMI_CON2, RG_HDMITX_EN_MBIAS);
+ usleep_range(80, 100);
+ mtk_phy_set_bits(base + HDMI_CON6, RG_HTPLL_EN);
+ mtk_phy_set_bits(base + HDMI_CON2, RG_HDMITX_EN_TX_CKLDO);
+ mtk_phy_set_bits(base + HDMI_CON0, RG_HDMITX_EN_SLDO_MASK);
+ usleep_range(80, 100);
+ mtk_phy_set_bits(base + HDMI_CON2, RG_HDMITX_MBIAS_LPF_EN);
+ mtk_phy_set_bits(base + HDMI_CON0, RG_HDMITX_EN_SER_MASK);
+ mtk_phy_set_bits(base + HDMI_CON0, RG_HDMITX_EN_PRED_MASK);
+ mtk_phy_set_bits(base + HDMI_CON0, RG_HDMITX_EN_DRV_MASK);
+ usleep_range(80, 100);
+}
+
+static void mtk_hdmi_phy_disable_tmds(struct mtk_hdmi_phy *hdmi_phy)
+{
+ void __iomem *base = hdmi_phy->regs;
+
+ mtk_phy_clear_bits(base + HDMI_CON0, RG_HDMITX_EN_DRV_MASK);
+ mtk_phy_clear_bits(base + HDMI_CON0, RG_HDMITX_EN_PRED_MASK);
+ mtk_phy_clear_bits(base + HDMI_CON0, RG_HDMITX_EN_SER_MASK);
+ mtk_phy_clear_bits(base + HDMI_CON2, RG_HDMITX_MBIAS_LPF_EN);
+ usleep_range(80, 100);
+ mtk_phy_clear_bits(base + HDMI_CON0, RG_HDMITX_EN_SLDO_MASK);
+ mtk_phy_clear_bits(base + HDMI_CON2, RG_HDMITX_EN_TX_CKLDO);
+ mtk_phy_clear_bits(base + HDMI_CON6, RG_HTPLL_EN);
+ usleep_range(80, 100);
+ mtk_phy_clear_bits(base + HDMI_CON2, RG_HDMITX_EN_MBIAS);
+ mtk_phy_clear_bits(base + HDMI_CON6, RG_HTPLL_POSDIV_MASK);
+ mtk_phy_clear_bits(base + HDMI_CON6, RG_HTPLL_RLH_EN);
+ mtk_phy_clear_bits(base + 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..d04758396
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt8173.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Jie Qiu <jie.qiu@mediatek.com>
+ */
+
+#include "phy-mtk-hdmi.h"
+#include "phy-mtk-io.h"
+
+#define HDMI_CON0 0x00
+#define RG_HDMITX_PLL_EN BIT(31)
+#define RG_HDMITX_PLL_FBKDIV GENMASK(30, 24)
+#define RG_HDMITX_PLL_FBKSEL GENMASK(23, 22)
+#define RG_HDMITX_PLL_PREDIV GENMASK(21, 20)
+#define RG_HDMITX_PLL_POSDIV GENMASK(19, 18)
+#define RG_HDMITX_PLL_RST_DLY GENMASK(17, 16)
+#define RG_HDMITX_PLL_IR GENMASK(15, 12)
+#define RG_HDMITX_PLL_IC GENMASK(11, 8)
+#define RG_HDMITX_PLL_BP GENMASK(7, 4)
+#define RG_HDMITX_PLL_BR GENMASK(3, 2)
+#define RG_HDMITX_PLL_BC GENMASK(1, 0)
+#define HDMI_CON1 0x04
+#define RG_HDMITX_PLL_DIVEN GENMASK(31, 29)
+#define RG_HDMITX_PLL_AUTOK_EN BIT(28)
+#define RG_HDMITX_PLL_AUTOK_KF GENMASK(27, 26)
+#define RG_HDMITX_PLL_AUTOK_KS GENMASK(25, 24)
+#define RG_HDMITX_PLL_AUTOK_LOAD BIT(23)
+#define RG_HDMITX_PLL_BAND GENMASK(21, 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 GENMASK(11, 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 GENMASK(3, 0)
+#define HDMI_CON2 0x08
+#define RGS_HDMITX_PLL_AUTOK_BAND GENMASK(14, 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 GENMASK(31, 28)
+#define RG_HDMITX_PRD_EN GENMASK(27, 24)
+#define RG_HDMITX_PRD_IMP_EN GENMASK(23, 20)
+#define RG_HDMITX_DRV_EN GENMASK(19, 16)
+#define RG_HDMITX_DRV_IMP_EN GENMASK(15, 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 GENMASK(7, 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 GENMASK(27, 24)
+#define RG_HDMITX_PRD_IBIAS_D2 GENMASK(19, 16)
+#define RG_HDMITX_PRD_IBIAS_D1 GENMASK(11, 8)
+#define RG_HDMITX_PRD_IBIAS_D0 GENMASK(3, 0)
+#define HDMI_CON5 0x14
+#define RG_HDMITX_DRV_IBIAS_CLK GENMASK(29, 24)
+#define RG_HDMITX_DRV_IBIAS_D2 GENMASK(21, 16)
+#define RG_HDMITX_DRV_IBIAS_D1 GENMASK(13, 8)
+#define RG_HDMITX_DRV_IBIAS_D0 GENMASK(5, 0)
+#define HDMI_CON6 0x18
+#define RG_HDMITX_DRV_IMP_CLK GENMASK(29, 24)
+#define RG_HDMITX_DRV_IMP_D2 GENMASK(21, 16)
+#define RG_HDMITX_DRV_IMP_D1 GENMASK(13, 8)
+#define RG_HDMITX_DRV_IMP_D0 GENMASK(5, 0)
+#define HDMI_CON7 0x1c
+#define RG_HDMITX_MHLCK_DRV_IBIAS GENMASK(31, 27)
+#define RG_HDMITX_SER_DIN GENMASK(25, 16)
+#define RG_HDMITX_CHLDC_TST GENMASK(15, 12)
+#define RG_HDMITX_CHLCK_TST GENMASK(11, 8)
+#define RG_HDMITX_RESERVE GENMASK(7, 0)
+#define HDMI_CON8 0x20
+#define RGS_HDMITX_2T1_LEV GENMASK(19, 16)
+#define RGS_HDMITX_2T1_EDG GENMASK(15, 12)
+#define RGS_HDMITX_5T1_LEV GENMASK(11, 8)
+#define RGS_HDMITX_5T1_EDG GENMASK(7, 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);
+ void __iomem *base = hdmi_phy->regs;
+
+ mtk_phy_set_bits(base + HDMI_CON1, RG_HDMITX_PLL_AUTOK_EN);
+ mtk_phy_set_bits(base + HDMI_CON0, RG_HDMITX_PLL_POSDIV);
+ mtk_phy_clear_bits(base + HDMI_CON3, RG_HDMITX_MHLCK_EN);
+ mtk_phy_set_bits(base + HDMI_CON1, RG_HDMITX_PLL_BIAS_EN);
+ usleep_range(100, 150);
+ mtk_phy_set_bits(base + HDMI_CON0, RG_HDMITX_PLL_EN);
+ usleep_range(100, 150);
+ mtk_phy_set_bits(base + HDMI_CON1, RG_HDMITX_PLL_BIAS_LPF_EN);
+ mtk_phy_set_bits(base + 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);
+ void __iomem *base = hdmi_phy->regs;
+
+ mtk_phy_clear_bits(base + HDMI_CON1, RG_HDMITX_PLL_TXDIV_EN);
+ mtk_phy_clear_bits(base + HDMI_CON1, RG_HDMITX_PLL_BIAS_LPF_EN);
+ usleep_range(100, 150);
+ mtk_phy_clear_bits(base + HDMI_CON0, RG_HDMITX_PLL_EN);
+ usleep_range(100, 150);
+ mtk_phy_clear_bits(base + HDMI_CON1, RG_HDMITX_PLL_BIAS_EN);
+ mtk_phy_clear_bits(base + HDMI_CON0, RG_HDMITX_PLL_POSDIV);
+ mtk_phy_clear_bits(base + 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);
+ void __iomem *base = hdmi_phy->regs;
+ 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_phy_update_field(base + HDMI_CON0, RG_HDMITX_PLL_PREDIV, pre_div);
+ mtk_phy_set_bits(base + HDMI_CON0, RG_HDMITX_PLL_POSDIV);
+ mtk_phy_update_bits(base + HDMI_CON0,
+ RG_HDMITX_PLL_IC | RG_HDMITX_PLL_IR,
+ FIELD_PREP(RG_HDMITX_PLL_IC, 0x1) |
+ FIELD_PREP(RG_HDMITX_PLL_IR, 0x1));
+ mtk_phy_update_field(base + HDMI_CON1, RG_HDMITX_PLL_TXDIV, div);
+ mtk_phy_update_bits(base + HDMI_CON0,
+ RG_HDMITX_PLL_FBKSEL | RG_HDMITX_PLL_FBKDIV,
+ FIELD_PREP(RG_HDMITX_PLL_FBKSEL, 0x1) |
+ FIELD_PREP(RG_HDMITX_PLL_FBKDIV, 19));
+ mtk_phy_update_field(base + HDMI_CON1, RG_HDMITX_PLL_DIVEN, 0x2);
+ mtk_phy_update_bits(base + HDMI_CON0,
+ RG_HDMITX_PLL_BP | RG_HDMITX_PLL_BC |
+ RG_HDMITX_PLL_BR,
+ FIELD_PREP(RG_HDMITX_PLL_BP, 0xc) |
+ FIELD_PREP(RG_HDMITX_PLL_BC, 0x2) |
+ FIELD_PREP(RG_HDMITX_PLL_BR, 0x1));
+ if (rate < 165000000) {
+ mtk_phy_clear_bits(base + HDMI_CON3, RG_HDMITX_PRD_IMP_EN);
+ pre_ibias = 0x3;
+ imp_en = 0x0;
+ hdmi_ibias = hdmi_phy->ibias;
+ } else {
+ mtk_phy_set_bits(base + HDMI_CON3, RG_HDMITX_PRD_IMP_EN);
+ pre_ibias = 0x6;
+ imp_en = 0xf;
+ hdmi_ibias = hdmi_phy->ibias_up;
+ }
+ mtk_phy_update_bits(base + HDMI_CON4,
+ RG_HDMITX_PRD_IBIAS_CLK | RG_HDMITX_PRD_IBIAS_D2 |
+ RG_HDMITX_PRD_IBIAS_D1 | RG_HDMITX_PRD_IBIAS_D0,
+ FIELD_PREP(RG_HDMITX_PRD_IBIAS_CLK, pre_ibias) |
+ FIELD_PREP(RG_HDMITX_PRD_IBIAS_D2, pre_ibias) |
+ FIELD_PREP(RG_HDMITX_PRD_IBIAS_D1, pre_ibias) |
+ FIELD_PREP(RG_HDMITX_PRD_IBIAS_D0, pre_ibias));
+ mtk_phy_update_field(base + HDMI_CON3, RG_HDMITX_DRV_IMP_EN, imp_en);
+ mtk_phy_update_bits(base + HDMI_CON6,
+ RG_HDMITX_DRV_IMP_CLK | RG_HDMITX_DRV_IMP_D2 |
+ RG_HDMITX_DRV_IMP_D1 | RG_HDMITX_DRV_IMP_D0,
+ FIELD_PREP(RG_HDMITX_DRV_IMP_CLK, hdmi_phy->drv_imp_clk) |
+ FIELD_PREP(RG_HDMITX_DRV_IMP_D2, hdmi_phy->drv_imp_d2) |
+ FIELD_PREP(RG_HDMITX_DRV_IMP_D1, hdmi_phy->drv_imp_d1) |
+ FIELD_PREP(RG_HDMITX_DRV_IMP_D0, hdmi_phy->drv_imp_d0));
+ mtk_phy_update_bits(base + HDMI_CON5,
+ RG_HDMITX_DRV_IBIAS_CLK | RG_HDMITX_DRV_IBIAS_D2 |
+ RG_HDMITX_DRV_IBIAS_D1 | RG_HDMITX_DRV_IBIAS_D0,
+ FIELD_PREP(RG_HDMITX_DRV_IBIAS_CLK, hdmi_ibias) |
+ FIELD_PREP(RG_HDMITX_DRV_IBIAS_D2, hdmi_ibias) |
+ FIELD_PREP(RG_HDMITX_DRV_IBIAS_D1, hdmi_ibias) |
+ FIELD_PREP(RG_HDMITX_DRV_IBIAS_D0, hdmi_ibias));
+ 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_phy_set_bits(hdmi_phy->regs + 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_phy_clear_bits(hdmi_phy->regs + 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..b16d437d6
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-hdmi.c
@@ -0,0 +1,166 @@
+// 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,
+};
+
+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 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;
+
+ hdmi_phy->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(hdmi_phy->regs))
+ return PTR_ERR(hdmi_phy->regs);
+
+ ref_clk = devm_clk_get(dev, "pll_ref");
+ if (IS_ERR(ref_clk))
+ return dev_err_probe(dev, PTR_ERR(ref_clk),
+ "Failed to get PLL reference clock\n");
+
+ 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)
+ return dev_err_probe(dev, ret, "Failed to read clock-output-names\n");
+
+ 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))
+ return dev_err_probe(dev, PTR_ERR(hdmi_phy->pll),
+ "Failed to register PLL\n");
+
+ ret = of_property_read_u32(dev->of_node, "mediatek,ibias",
+ &hdmi_phy->ibias);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to get ibias\n");
+
+ ret = of_property_read_u32(dev->of_node, "mediatek,ibias_up",
+ &hdmi_phy->ibias_up);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to get ibias_up\n");
+
+ 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))
+ return dev_err_probe(dev, PTR_ERR(phy), "Cannot create HDMI PHY\n");
+
+ phy_set_drvdata(phy, hdmi_phy);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider))
+ return dev_err_probe(dev, PTR_ERR(phy_provider),
+ "Failed to register HDMI PHY\n");
+
+ 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,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mtk_hdmi_phy_match);
+
+static 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..c7fa65cff
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-hdmi.h
@@ -0,0 +1,49 @@
+/* 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/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;
+};
+
+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-io.h b/drivers/phy/mediatek/phy-mtk-io.h
new file mode 100644
index 000000000..58f06db82
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-io.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2021 MediaTek Inc.
+ *
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ */
+
+#ifndef __PHY_MTK_H__
+#define __PHY_MTK_H__
+
+#include <linux/bitfield.h>
+#include <linux/io.h>
+
+static inline void mtk_phy_clear_bits(void __iomem *reg, u32 bits)
+{
+ u32 tmp = readl(reg);
+
+ tmp &= ~bits;
+ writel(tmp, reg);
+}
+
+static inline void mtk_phy_set_bits(void __iomem *reg, u32 bits)
+{
+ u32 tmp = readl(reg);
+
+ tmp |= bits;
+ writel(tmp, reg);
+}
+
+static inline void mtk_phy_update_bits(void __iomem *reg, u32 mask, u32 val)
+{
+ u32 tmp = readl(reg);
+
+ tmp &= ~mask;
+ tmp |= val & mask;
+ writel(tmp, reg);
+}
+
+/* field @mask shall be constant and continuous */
+#define mtk_phy_update_field(reg, mask, val) \
+({ \
+ BUILD_BUG_ON_MSG(!__builtin_constant_p(mask), "mask is not constant"); \
+ mtk_phy_update_bits(reg, mask, FIELD_PREP(mask, val)); \
+})
+
+#endif
diff --git a/drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8173.c b/drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8173.c
new file mode 100644
index 000000000..673cb0f08
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8173.c
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 MediaTek Inc.
+ * Author: jitao.shi <jitao.shi@mediatek.com>
+ */
+
+#include "phy-mtk-io.h"
+#include "phy-mtk-mipi-dsi.h"
+
+#define MIPITX_DSI_CON 0x00
+#define RG_DSI_LDOCORE_EN BIT(0)
+#define RG_DSI_CKG_LDOOUT_EN BIT(1)
+#define RG_DSI_BCLK_SEL GENMASK(3, 2)
+#define RG_DSI_LD_IDX_SEL GENMASK(6, 4)
+#define RG_DSI_PHYCLK_SEL GENMASK(9, 8)
+#define RG_DSI_DSICLK_FREQ_SEL BIT(10)
+#define RG_DSI_LPTX_CLMP_EN BIT(11)
+
+#define MIPITX_DSI_CLOCK_LANE 0x04
+#define MIPITX_DSI_DATA_LANE0 0x08
+#define MIPITX_DSI_DATA_LANE1 0x0c
+#define MIPITX_DSI_DATA_LANE2 0x10
+#define MIPITX_DSI_DATA_LANE3 0x14
+#define RG_DSI_LNTx_LDOOUT_EN BIT(0)
+#define RG_DSI_LNTx_CKLANE_EN BIT(1)
+#define RG_DSI_LNTx_LPTX_IPLUS1 BIT(2)
+#define RG_DSI_LNTx_LPTX_IPLUS2 BIT(3)
+#define RG_DSI_LNTx_LPTX_IMINUS BIT(4)
+#define RG_DSI_LNTx_LPCD_IPLUS BIT(5)
+#define RG_DSI_LNTx_LPCD_IMINUS BIT(6)
+#define RG_DSI_LNTx_RT_CODE GENMASK(11, 8)
+
+#define MIPITX_DSI_TOP_CON 0x40
+#define RG_DSI_LNT_INTR_EN BIT(0)
+#define RG_DSI_LNT_HS_BIAS_EN BIT(1)
+#define RG_DSI_LNT_IMP_CAL_EN BIT(2)
+#define RG_DSI_LNT_TESTMODE_EN BIT(3)
+#define RG_DSI_LNT_IMP_CAL_CODE GENMASK(7, 4)
+#define RG_DSI_LNT_AIO_SEL GENMASK(10, 8)
+#define RG_DSI_PAD_TIE_LOW_EN BIT(11)
+#define RG_DSI_DEBUG_INPUT_EN BIT(12)
+#define RG_DSI_PRESERVE GENMASK(15, 13)
+
+#define MIPITX_DSI_BG_CON 0x44
+#define RG_DSI_BG_CORE_EN BIT(0)
+#define RG_DSI_BG_CKEN BIT(1)
+#define RG_DSI_BG_DIV GENMASK(3, 2)
+#define RG_DSI_BG_FAST_CHARGE BIT(4)
+
+#define RG_DSI_V12_SEL GENMASK(7, 5)
+#define RG_DSI_V10_SEL GENMASK(10, 8)
+#define RG_DSI_V072_SEL GENMASK(13, 11)
+#define RG_DSI_V04_SEL GENMASK(16, 14)
+#define RG_DSI_V032_SEL GENMASK(19, 17)
+#define RG_DSI_V02_SEL GENMASK(22, 20)
+#define RG_DSI_VOUT_MSK \
+ (RG_DSI_V12_SEL | RG_DSI_V10_SEL | RG_DSI_V072_SEL | \
+ RG_DSI_V04_SEL | RG_DSI_V032_SEL | RG_DSI_V02_SEL)
+#define RG_DSI_BG_R1_TRIM GENMASK(27, 24)
+#define RG_DSI_BG_R2_TRIM GENMASK(31, 28)
+
+#define MIPITX_DSI_PLL_CON0 0x50
+#define RG_DSI_MPPLL_PLL_EN BIT(0)
+#define RG_DSI_MPPLL_PREDIV GENMASK(2, 1)
+#define RG_DSI_MPPLL_TXDIV0 GENMASK(4, 3)
+#define RG_DSI_MPPLL_TXDIV1 GENMASK(6, 5)
+#define RG_DSI_MPPLL_POSDIV GENMASK(9, 7)
+#define RG_DSI_MPPLL_DIV_MSK \
+ (RG_DSI_MPPLL_PREDIV | RG_DSI_MPPLL_TXDIV0 | \
+ RG_DSI_MPPLL_TXDIV1 | RG_DSI_MPPLL_POSDIV)
+#define RG_DSI_MPPLL_MONVC_EN BIT(10)
+#define RG_DSI_MPPLL_MONREF_EN BIT(11)
+#define RG_DSI_MPPLL_VOD_EN BIT(12)
+
+#define MIPITX_DSI_PLL_CON1 0x54
+#define RG_DSI_MPPLL_SDM_FRA_EN BIT(0)
+#define RG_DSI_MPPLL_SDM_SSC_PH_INIT BIT(1)
+#define RG_DSI_MPPLL_SDM_SSC_EN BIT(2)
+#define RG_DSI_MPPLL_SDM_SSC_PRD GENMASK(31, 16)
+
+#define MIPITX_DSI_PLL_CON2 0x58
+
+#define MIPITX_DSI_PLL_TOP 0x64
+#define RG_DSI_MPPLL_PRESERVE GENMASK(15, 8)
+
+#define MIPITX_DSI_PLL_PWR 0x68
+#define RG_DSI_MPPLL_SDM_PWR_ON BIT(0)
+#define RG_DSI_MPPLL_SDM_ISO_EN BIT(1)
+#define RG_DSI_MPPLL_SDM_PWR_ACK BIT(8)
+
+#define MIPITX_DSI_SW_CTRL 0x80
+#define SW_CTRL_EN BIT(0)
+
+#define MIPITX_DSI_SW_CTRL_CON0 0x84
+#define SW_LNTC_LPTX_PRE_OE BIT(0)
+#define SW_LNTC_LPTX_OE BIT(1)
+#define SW_LNTC_LPTX_P BIT(2)
+#define SW_LNTC_LPTX_N BIT(3)
+#define SW_LNTC_HSTX_PRE_OE BIT(4)
+#define SW_LNTC_HSTX_OE BIT(5)
+#define SW_LNTC_HSTX_ZEROCLK BIT(6)
+#define SW_LNT0_LPTX_PRE_OE BIT(7)
+#define SW_LNT0_LPTX_OE BIT(8)
+#define SW_LNT0_LPTX_P BIT(9)
+#define SW_LNT0_LPTX_N BIT(10)
+#define SW_LNT0_HSTX_PRE_OE BIT(11)
+#define SW_LNT0_HSTX_OE BIT(12)
+#define SW_LNT0_LPRX_EN BIT(13)
+#define SW_LNT1_LPTX_PRE_OE BIT(14)
+#define SW_LNT1_LPTX_OE BIT(15)
+#define SW_LNT1_LPTX_P BIT(16)
+#define SW_LNT1_LPTX_N BIT(17)
+#define SW_LNT1_HSTX_PRE_OE BIT(18)
+#define SW_LNT1_HSTX_OE BIT(19)
+#define SW_LNT2_LPTX_PRE_OE BIT(20)
+#define SW_LNT2_LPTX_OE BIT(21)
+#define SW_LNT2_LPTX_P BIT(22)
+#define SW_LNT2_LPTX_N BIT(23)
+#define SW_LNT2_HSTX_PRE_OE BIT(24)
+#define SW_LNT2_HSTX_OE BIT(25)
+
+static int mtk_mipi_tx_pll_prepare(struct clk_hw *hw)
+{
+ struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw);
+ void __iomem *base = mipi_tx->regs;
+ u8 txdiv, txdiv0, txdiv1;
+ u64 pcw;
+
+ dev_dbg(mipi_tx->dev, "prepare: %u Hz\n", mipi_tx->data_rate);
+
+ if (mipi_tx->data_rate >= 500000000) {
+ txdiv = 1;
+ txdiv0 = 0;
+ txdiv1 = 0;
+ } else if (mipi_tx->data_rate >= 250000000) {
+ txdiv = 2;
+ txdiv0 = 1;
+ txdiv1 = 0;
+ } else if (mipi_tx->data_rate >= 125000000) {
+ txdiv = 4;
+ txdiv0 = 2;
+ txdiv1 = 0;
+ } else if (mipi_tx->data_rate > 62000000) {
+ txdiv = 8;
+ txdiv0 = 2;
+ txdiv1 = 1;
+ } else if (mipi_tx->data_rate >= 50000000) {
+ txdiv = 16;
+ txdiv0 = 2;
+ txdiv1 = 2;
+ } else {
+ return -EINVAL;
+ }
+
+ mtk_phy_update_bits(base + MIPITX_DSI_BG_CON,
+ RG_DSI_VOUT_MSK | RG_DSI_BG_CKEN |
+ RG_DSI_BG_CORE_EN,
+ FIELD_PREP(RG_DSI_V02_SEL, 4) |
+ FIELD_PREP(RG_DSI_V032_SEL, 4) |
+ FIELD_PREP(RG_DSI_V04_SEL, 4) |
+ FIELD_PREP(RG_DSI_V072_SEL, 4) |
+ FIELD_PREP(RG_DSI_V10_SEL, 4) |
+ FIELD_PREP(RG_DSI_V12_SEL, 4) |
+ RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN);
+
+ usleep_range(30, 100);
+
+ mtk_phy_update_bits(base + MIPITX_DSI_TOP_CON,
+ RG_DSI_LNT_IMP_CAL_CODE | RG_DSI_LNT_HS_BIAS_EN,
+ FIELD_PREP(RG_DSI_LNT_IMP_CAL_CODE, 8) |
+ RG_DSI_LNT_HS_BIAS_EN);
+
+ mtk_phy_set_bits(base + MIPITX_DSI_CON,
+ RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN);
+
+ mtk_phy_update_bits(base + MIPITX_DSI_PLL_PWR,
+ RG_DSI_MPPLL_SDM_PWR_ON | RG_DSI_MPPLL_SDM_ISO_EN,
+ RG_DSI_MPPLL_SDM_PWR_ON);
+
+ mtk_phy_clear_bits(base + MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_PLL_EN);
+
+ mtk_phy_update_bits(base + MIPITX_DSI_PLL_CON0,
+ RG_DSI_MPPLL_TXDIV0 | RG_DSI_MPPLL_TXDIV1 |
+ RG_DSI_MPPLL_PREDIV,
+ FIELD_PREP(RG_DSI_MPPLL_TXDIV0, txdiv0) |
+ FIELD_PREP(RG_DSI_MPPLL_TXDIV1, txdiv1));
+
+ /*
+ * PLL PCW config
+ * PCW bit 24~30 = integer part of pcw
+ * PCW bit 0~23 = fractional part of pcw
+ * pcw = data_Rate*4*txdiv/(Ref_clk*2);
+ * Post DIV =4, so need data_Rate*4
+ * Ref_clk is 26MHz
+ */
+ pcw = div_u64(((u64)mipi_tx->data_rate * 2 * txdiv) << 24, 26000000);
+ writel(pcw, base + MIPITX_DSI_PLL_CON2);
+
+ mtk_phy_set_bits(base + MIPITX_DSI_PLL_CON1, RG_DSI_MPPLL_SDM_FRA_EN);
+
+ mtk_phy_set_bits(base + MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_PLL_EN);
+
+ usleep_range(20, 100);
+
+ mtk_phy_clear_bits(base + MIPITX_DSI_PLL_CON1, RG_DSI_MPPLL_SDM_SSC_EN);
+
+ mtk_phy_update_field(base + MIPITX_DSI_PLL_TOP,
+ RG_DSI_MPPLL_PRESERVE,
+ mipi_tx->driver_data->mppll_preserve);
+
+ return 0;
+}
+
+static void mtk_mipi_tx_pll_unprepare(struct clk_hw *hw)
+{
+ struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw);
+ void __iomem *base = mipi_tx->regs;
+
+ dev_dbg(mipi_tx->dev, "unprepare\n");
+
+ mtk_phy_clear_bits(base + MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_PLL_EN);
+
+ mtk_phy_clear_bits(base + MIPITX_DSI_PLL_TOP, RG_DSI_MPPLL_PRESERVE);
+
+ mtk_phy_update_bits(base + MIPITX_DSI_PLL_PWR,
+ RG_DSI_MPPLL_SDM_ISO_EN | RG_DSI_MPPLL_SDM_PWR_ON,
+ RG_DSI_MPPLL_SDM_ISO_EN);
+
+ mtk_phy_clear_bits(base + MIPITX_DSI_TOP_CON, RG_DSI_LNT_HS_BIAS_EN);
+
+ mtk_phy_clear_bits(base + MIPITX_DSI_CON,
+ RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN);
+
+ mtk_phy_clear_bits(base + MIPITX_DSI_BG_CON,
+ RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN);
+
+ mtk_phy_clear_bits(base + MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_DIV_MSK);
+}
+
+static long mtk_mipi_tx_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ return clamp_val(rate, 50000000, 1250000000);
+}
+
+static const struct clk_ops mtk_mipi_tx_pll_ops = {
+ .prepare = mtk_mipi_tx_pll_prepare,
+ .unprepare = mtk_mipi_tx_pll_unprepare,
+ .round_rate = mtk_mipi_tx_pll_round_rate,
+ .set_rate = mtk_mipi_tx_pll_set_rate,
+ .recalc_rate = mtk_mipi_tx_pll_recalc_rate,
+};
+
+static void mtk_mipi_tx_power_on_signal(struct phy *phy)
+{
+ struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
+ u32 reg;
+
+ for (reg = MIPITX_DSI_CLOCK_LANE;
+ reg <= MIPITX_DSI_DATA_LANE3; reg += 4)
+ mtk_phy_set_bits(mipi_tx->regs + reg, RG_DSI_LNTx_LDOOUT_EN);
+
+ mtk_phy_clear_bits(mipi_tx->regs + MIPITX_DSI_TOP_CON,
+ RG_DSI_PAD_TIE_LOW_EN);
+}
+
+static void mtk_mipi_tx_power_off_signal(struct phy *phy)
+{
+ struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
+ u32 reg;
+
+ mtk_phy_set_bits(mipi_tx->regs + MIPITX_DSI_TOP_CON,
+ RG_DSI_PAD_TIE_LOW_EN);
+
+ for (reg = MIPITX_DSI_CLOCK_LANE;
+ reg <= MIPITX_DSI_DATA_LANE3; reg += 4)
+ mtk_phy_clear_bits(mipi_tx->regs + reg, RG_DSI_LNTx_LDOOUT_EN);
+}
+
+const struct mtk_mipitx_data mt2701_mipitx_data = {
+ .mppll_preserve = 3,
+ .mipi_tx_clk_ops = &mtk_mipi_tx_pll_ops,
+ .mipi_tx_enable_signal = mtk_mipi_tx_power_on_signal,
+ .mipi_tx_disable_signal = mtk_mipi_tx_power_off_signal,
+};
+
+const struct mtk_mipitx_data mt8173_mipitx_data = {
+ .mppll_preserve = 0,
+ .mipi_tx_clk_ops = &mtk_mipi_tx_pll_ops,
+ .mipi_tx_enable_signal = mtk_mipi_tx_power_on_signal,
+ .mipi_tx_disable_signal = mtk_mipi_tx_power_off_signal,
+};
diff --git a/drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8183.c b/drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8183.c
new file mode 100644
index 000000000..f021ec5a7
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8183.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 MediaTek Inc.
+ * Author: jitao.shi <jitao.shi@mediatek.com>
+ */
+
+#include "phy-mtk-io.h"
+#include "phy-mtk-mipi-dsi.h"
+
+#define MIPITX_LANE_CON 0x000c
+#define RG_DSI_CPHY_T1DRV_EN BIT(0)
+#define RG_DSI_ANA_CK_SEL BIT(1)
+#define RG_DSI_PHY_CK_SEL BIT(2)
+#define RG_DSI_CPHY_EN BIT(3)
+#define RG_DSI_PHYCK_INV_EN BIT(4)
+#define RG_DSI_PWR04_EN BIT(5)
+#define RG_DSI_BG_LPF_EN BIT(6)
+#define RG_DSI_BG_CORE_EN BIT(7)
+#define RG_DSI_PAD_TIEL_SEL BIT(8)
+
+#define MIPITX_VOLTAGE_SEL 0x0010
+#define RG_DSI_HSTX_LDO_REF_SEL GENMASK(9, 6)
+
+#define MIPITX_PLL_PWR 0x0028
+#define MIPITX_PLL_CON0 0x002c
+#define MIPITX_PLL_CON1 0x0030
+#define MIPITX_PLL_CON2 0x0034
+#define MIPITX_PLL_CON3 0x0038
+#define MIPITX_PLL_CON4 0x003c
+#define RG_DSI_PLL_IBIAS GENMASK(11, 10)
+
+#define MIPITX_D2P_RTCODE 0x0100
+#define MIPITX_D2_SW_CTL_EN 0x0144
+#define MIPITX_D0_SW_CTL_EN 0x0244
+#define MIPITX_CK_CKMODE_EN 0x0328
+#define DSI_CK_CKMODE_EN BIT(0)
+#define MIPITX_CK_SW_CTL_EN 0x0344
+#define MIPITX_D1_SW_CTL_EN 0x0444
+#define MIPITX_D3_SW_CTL_EN 0x0544
+#define DSI_SW_CTL_EN BIT(0)
+#define AD_DSI_PLL_SDM_PWR_ON BIT(0)
+#define AD_DSI_PLL_SDM_ISO_EN BIT(1)
+
+#define RG_DSI_PLL_EN BIT(4)
+#define RG_DSI_PLL_POSDIV GENMASK(10, 8)
+
+static int mtk_mipi_tx_pll_enable(struct clk_hw *hw)
+{
+ struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw);
+ void __iomem *base = mipi_tx->regs;
+ unsigned int txdiv, txdiv0;
+ u64 pcw;
+
+ dev_dbg(mipi_tx->dev, "enable: %u bps\n", mipi_tx->data_rate);
+
+ if (mipi_tx->data_rate >= 2000000000) {
+ txdiv = 1;
+ txdiv0 = 0;
+ } else if (mipi_tx->data_rate >= 1000000000) {
+ txdiv = 2;
+ txdiv0 = 1;
+ } else if (mipi_tx->data_rate >= 500000000) {
+ txdiv = 4;
+ txdiv0 = 2;
+ } else if (mipi_tx->data_rate > 250000000) {
+ txdiv = 8;
+ txdiv0 = 3;
+ } else if (mipi_tx->data_rate >= 125000000) {
+ txdiv = 16;
+ txdiv0 = 4;
+ } else {
+ return -EINVAL;
+ }
+
+ mtk_phy_clear_bits(base + MIPITX_PLL_CON4, RG_DSI_PLL_IBIAS);
+
+ mtk_phy_set_bits(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_PWR_ON);
+ mtk_phy_clear_bits(base + MIPITX_PLL_CON1, RG_DSI_PLL_EN);
+ udelay(1);
+ mtk_phy_clear_bits(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_ISO_EN);
+ pcw = div_u64(((u64)mipi_tx->data_rate * txdiv) << 24, 26000000);
+ writel(pcw, base + MIPITX_PLL_CON0);
+ mtk_phy_update_field(base + MIPITX_PLL_CON1, RG_DSI_PLL_POSDIV, txdiv0);
+ mtk_phy_set_bits(base + MIPITX_PLL_CON1, RG_DSI_PLL_EN);
+
+ return 0;
+}
+
+static void mtk_mipi_tx_pll_disable(struct clk_hw *hw)
+{
+ struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw);
+ void __iomem *base = mipi_tx->regs;
+
+ mtk_phy_clear_bits(base + MIPITX_PLL_CON1, RG_DSI_PLL_EN);
+
+ mtk_phy_set_bits(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_ISO_EN);
+ mtk_phy_clear_bits(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_PWR_ON);
+}
+
+static long mtk_mipi_tx_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ return clamp_val(rate, 50000000, 1600000000);
+}
+
+static const struct clk_ops mtk_mipi_tx_pll_ops = {
+ .enable = mtk_mipi_tx_pll_enable,
+ .disable = mtk_mipi_tx_pll_disable,
+ .round_rate = mtk_mipi_tx_pll_round_rate,
+ .set_rate = mtk_mipi_tx_pll_set_rate,
+ .recalc_rate = mtk_mipi_tx_pll_recalc_rate,
+};
+
+static void mtk_mipi_tx_config_calibration_data(struct mtk_mipi_tx *mipi_tx)
+{
+ int i, j;
+
+ for (i = 0; i < 5; i++) {
+ if ((mipi_tx->rt_code[i] & 0x1f) == 0)
+ mipi_tx->rt_code[i] |= 0x10;
+
+ if ((mipi_tx->rt_code[i] >> 5 & 0x1f) == 0)
+ mipi_tx->rt_code[i] |= 0x10 << 5;
+
+ for (j = 0; j < 10; j++)
+ mtk_phy_update_bits(mipi_tx->regs +
+ MIPITX_D2P_RTCODE * (i + 1) + j * 4,
+ 1, mipi_tx->rt_code[i] >> j & 1);
+ }
+}
+
+static void mtk_mipi_tx_power_on_signal(struct phy *phy)
+{
+ struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
+ void __iomem *base = mipi_tx->regs;
+
+ /* BG_LPF_EN / BG_CORE_EN */
+ writel(RG_DSI_PAD_TIEL_SEL | RG_DSI_BG_CORE_EN, base + MIPITX_LANE_CON);
+ usleep_range(30, 100);
+ writel(RG_DSI_BG_CORE_EN | RG_DSI_BG_LPF_EN, base + MIPITX_LANE_CON);
+
+ /* Switch OFF each Lane */
+ mtk_phy_clear_bits(base + MIPITX_D0_SW_CTL_EN, DSI_SW_CTL_EN);
+ mtk_phy_clear_bits(base + MIPITX_D1_SW_CTL_EN, DSI_SW_CTL_EN);
+ mtk_phy_clear_bits(base + MIPITX_D2_SW_CTL_EN, DSI_SW_CTL_EN);
+ mtk_phy_clear_bits(base + MIPITX_D3_SW_CTL_EN, DSI_SW_CTL_EN);
+ mtk_phy_clear_bits(base + MIPITX_CK_SW_CTL_EN, DSI_SW_CTL_EN);
+
+ mtk_phy_update_field(base + MIPITX_VOLTAGE_SEL, RG_DSI_HSTX_LDO_REF_SEL,
+ (mipi_tx->mipitx_drive - 3000) / 200);
+
+ mtk_mipi_tx_config_calibration_data(mipi_tx);
+
+ mtk_phy_set_bits(base + MIPITX_CK_CKMODE_EN, DSI_CK_CKMODE_EN);
+}
+
+static void mtk_mipi_tx_power_off_signal(struct phy *phy)
+{
+ struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
+ void __iomem *base = mipi_tx->regs;
+
+ /* Switch ON each Lane */
+ mtk_phy_set_bits(base + MIPITX_D0_SW_CTL_EN, DSI_SW_CTL_EN);
+ mtk_phy_set_bits(base + MIPITX_D1_SW_CTL_EN, DSI_SW_CTL_EN);
+ mtk_phy_set_bits(base + MIPITX_D2_SW_CTL_EN, DSI_SW_CTL_EN);
+ mtk_phy_set_bits(base + MIPITX_D3_SW_CTL_EN, DSI_SW_CTL_EN);
+ mtk_phy_set_bits(base + MIPITX_CK_SW_CTL_EN, DSI_SW_CTL_EN);
+
+ writel(RG_DSI_PAD_TIEL_SEL | RG_DSI_BG_CORE_EN, base + MIPITX_LANE_CON);
+ writel(RG_DSI_PAD_TIEL_SEL, base + MIPITX_LANE_CON);
+}
+
+const struct mtk_mipitx_data mt8183_mipitx_data = {
+ .mipi_tx_clk_ops = &mtk_mipi_tx_pll_ops,
+ .mipi_tx_enable_signal = mtk_mipi_tx_power_on_signal,
+ .mipi_tx_disable_signal = mtk_mipi_tx_power_off_signal,
+};
diff --git a/drivers/phy/mediatek/phy-mtk-mipi-dsi.c b/drivers/phy/mediatek/phy-mtk-mipi-dsi.c
new file mode 100644
index 000000000..cf9c38638
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-mipi-dsi.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ */
+
+#include "phy-mtk-mipi-dsi.h"
+
+inline struct mtk_mipi_tx *mtk_mipi_tx_from_clk_hw(struct clk_hw *hw)
+{
+ return container_of(hw, struct mtk_mipi_tx, pll_hw);
+}
+
+int mtk_mipi_tx_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw);
+
+ dev_dbg(mipi_tx->dev, "set rate: %lu Hz\n", rate);
+
+ mipi_tx->data_rate = rate;
+
+ return 0;
+}
+
+unsigned long mtk_mipi_tx_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw);
+
+ return mipi_tx->data_rate;
+}
+
+static int mtk_mipi_tx_power_on(struct phy *phy)
+{
+ struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
+ int ret;
+
+ /* Power up core and enable PLL */
+ ret = clk_prepare_enable(mipi_tx->pll);
+ if (ret < 0)
+ return ret;
+
+ /* Enable DSI Lane LDO outputs, disable pad tie low */
+ mipi_tx->driver_data->mipi_tx_enable_signal(phy);
+ return 0;
+}
+
+static int mtk_mipi_tx_power_off(struct phy *phy)
+{
+ struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
+
+ /* Enable pad tie low, disable DSI Lane LDO outputs */
+ mipi_tx->driver_data->mipi_tx_disable_signal(phy);
+
+ /* Disable PLL and power down core */
+ clk_disable_unprepare(mipi_tx->pll);
+
+ return 0;
+}
+
+static const struct phy_ops mtk_mipi_tx_ops = {
+ .power_on = mtk_mipi_tx_power_on,
+ .power_off = mtk_mipi_tx_power_off,
+ .owner = THIS_MODULE,
+};
+
+static void mtk_mipi_tx_get_calibration_datal(struct mtk_mipi_tx *mipi_tx)
+{
+ struct nvmem_cell *cell;
+ size_t len;
+ u32 *buf;
+
+ cell = nvmem_cell_get(mipi_tx->dev, "calibration-data");
+ if (IS_ERR(cell)) {
+ dev_info(mipi_tx->dev, "can't get nvmem_cell_get, ignore it\n");
+ return;
+ }
+ buf = (u32 *)nvmem_cell_read(cell, &len);
+ nvmem_cell_put(cell);
+
+ if (IS_ERR(buf)) {
+ dev_info(mipi_tx->dev, "can't get data, ignore it\n");
+ return;
+ }
+
+ if (len < 3 * sizeof(u32)) {
+ dev_info(mipi_tx->dev, "invalid calibration data\n");
+ kfree(buf);
+ return;
+ }
+
+ mipi_tx->rt_code[0] = ((buf[0] >> 6 & 0x1f) << 5) |
+ (buf[0] >> 11 & 0x1f);
+ mipi_tx->rt_code[1] = ((buf[1] >> 27 & 0x1f) << 5) |
+ (buf[0] >> 1 & 0x1f);
+ mipi_tx->rt_code[2] = ((buf[1] >> 17 & 0x1f) << 5) |
+ (buf[1] >> 22 & 0x1f);
+ mipi_tx->rt_code[3] = ((buf[1] >> 7 & 0x1f) << 5) |
+ (buf[1] >> 12 & 0x1f);
+ mipi_tx->rt_code[4] = ((buf[2] >> 27 & 0x1f) << 5) |
+ (buf[1] >> 2 & 0x1f);
+ kfree(buf);
+}
+
+static int mtk_mipi_tx_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mtk_mipi_tx *mipi_tx;
+ const char *ref_clk_name;
+ struct clk *ref_clk;
+ struct clk_init_data clk_init = {
+ .num_parents = 1,
+ .parent_names = (const char * const *)&ref_clk_name,
+ .flags = CLK_SET_RATE_GATE,
+ };
+ struct phy *phy;
+ struct phy_provider *phy_provider;
+ int ret;
+
+ mipi_tx = devm_kzalloc(dev, sizeof(*mipi_tx), GFP_KERNEL);
+ if (!mipi_tx)
+ return -ENOMEM;
+
+ mipi_tx->driver_data = of_device_get_match_data(dev);
+ if (!mipi_tx->driver_data)
+ return -ENODEV;
+
+ mipi_tx->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mipi_tx->regs))
+ return PTR_ERR(mipi_tx->regs);
+
+ ref_clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(ref_clk))
+ return dev_err_probe(dev, PTR_ERR(ref_clk),
+ "Failed to get reference clock\n");
+
+ ret = of_property_read_u32(dev->of_node, "drive-strength-microamp",
+ &mipi_tx->mipitx_drive);
+ /* If can't get the "mipi_tx->mipitx_drive", set it default 0x8 */
+ if (ret < 0)
+ mipi_tx->mipitx_drive = 4600;
+
+ /* check the mipitx_drive valid */
+ if (mipi_tx->mipitx_drive > 6000 || mipi_tx->mipitx_drive < 3000) {
+ dev_warn(dev, "drive-strength-microamp is invalid %d, not in 3000 ~ 6000\n",
+ mipi_tx->mipitx_drive);
+ mipi_tx->mipitx_drive = clamp_val(mipi_tx->mipitx_drive, 3000,
+ 6000);
+ }
+
+ 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)
+ return dev_err_probe(dev, ret, "Failed to read clock-output-names\n");
+
+ clk_init.ops = mipi_tx->driver_data->mipi_tx_clk_ops;
+
+ mipi_tx->pll_hw.init = &clk_init;
+ mipi_tx->pll = devm_clk_register(dev, &mipi_tx->pll_hw);
+ if (IS_ERR(mipi_tx->pll))
+ return dev_err_probe(dev, PTR_ERR(mipi_tx->pll), "Failed to register PLL\n");
+
+ phy = devm_phy_create(dev, NULL, &mtk_mipi_tx_ops);
+ if (IS_ERR(phy))
+ return dev_err_probe(dev, PTR_ERR(phy), "Failed to create MIPI D-PHY\n");
+
+ phy_set_drvdata(phy, mipi_tx);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider))
+ return PTR_ERR(phy_provider);
+
+ mipi_tx->dev = dev;
+
+ mtk_mipi_tx_get_calibration_datal(mipi_tx);
+
+ return of_clk_add_provider(dev->of_node, of_clk_src_simple_get,
+ mipi_tx->pll);
+}
+
+static int mtk_mipi_tx_remove(struct platform_device *pdev)
+{
+ of_clk_del_provider(pdev->dev.of_node);
+ return 0;
+}
+
+static const struct of_device_id mtk_mipi_tx_match[] = {
+ { .compatible = "mediatek,mt2701-mipi-tx",
+ .data = &mt2701_mipitx_data },
+ { .compatible = "mediatek,mt8173-mipi-tx",
+ .data = &mt8173_mipitx_data },
+ { .compatible = "mediatek,mt8183-mipi-tx",
+ .data = &mt8183_mipitx_data },
+ { },
+};
+MODULE_DEVICE_TABLE(of, mtk_mipi_tx_match);
+
+static struct platform_driver mtk_mipi_tx_driver = {
+ .probe = mtk_mipi_tx_probe,
+ .remove = mtk_mipi_tx_remove,
+ .driver = {
+ .name = "mediatek-mipi-tx",
+ .of_match_table = mtk_mipi_tx_match,
+ },
+};
+module_platform_driver(mtk_mipi_tx_driver);
+
+MODULE_DESCRIPTION("MediaTek MIPI TX Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/mediatek/phy-mtk-mipi-dsi.h b/drivers/phy/mediatek/phy-mtk-mipi-dsi.h
new file mode 100644
index 000000000..47b60b1a7
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-mipi-dsi.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2019 MediaTek Inc.
+ * Author: Jitao Shi <jitao.shi@mediatek.com>
+ */
+
+#ifndef _MTK_MIPI_TX_H
+#define _MTK_MIPI_TX_H
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+#include <linux/slab.h>
+
+struct mtk_mipitx_data {
+ const u32 mppll_preserve;
+ const struct clk_ops *mipi_tx_clk_ops;
+ void (*mipi_tx_enable_signal)(struct phy *phy);
+ void (*mipi_tx_disable_signal)(struct phy *phy);
+};
+
+struct mtk_mipi_tx {
+ struct device *dev;
+ void __iomem *regs;
+ u32 data_rate;
+ u32 mipitx_drive;
+ u32 rt_code[5];
+ const struct mtk_mipitx_data *driver_data;
+ struct clk_hw pll_hw;
+ struct clk *pll;
+};
+
+struct mtk_mipi_tx *mtk_mipi_tx_from_clk_hw(struct clk_hw *hw);
+int mtk_mipi_tx_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate);
+unsigned long mtk_mipi_tx_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate);
+
+extern const struct mtk_mipitx_data mt2701_mipitx_data;
+extern const struct mtk_mipitx_data mt8173_mipitx_data;
+extern const struct mtk_mipitx_data mt8183_mipitx_data;
+
+#endif
diff --git a/drivers/phy/mediatek/phy-mtk-pcie.c b/drivers/phy/mediatek/phy-mtk-pcie.c
new file mode 100644
index 000000000..25dbd6e35
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-pcie.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022 MediaTek Inc.
+ * Author: Jianjun Wang <jianjun.wang@mediatek.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "phy-mtk-io.h"
+
+#define PEXTP_ANA_GLB_00_REG 0x9000
+/* Internal Resistor Selection of TX Bias Current */
+#define EFUSE_GLB_INTR_SEL GENMASK(28, 24)
+
+#define PEXTP_ANA_LN0_TRX_REG 0xa000
+
+#define PEXTP_ANA_TX_REG 0x04
+/* TX PMOS impedance selection */
+#define EFUSE_LN_TX_PMOS_SEL GENMASK(5, 2)
+/* TX NMOS impedance selection */
+#define EFUSE_LN_TX_NMOS_SEL GENMASK(11, 8)
+
+#define PEXTP_ANA_RX_REG 0x3c
+/* RX impedance selection */
+#define EFUSE_LN_RX_SEL GENMASK(3, 0)
+
+#define PEXTP_ANA_LANE_OFFSET 0x100
+
+/**
+ * struct mtk_pcie_lane_efuse - eFuse data for each lane
+ * @tx_pmos: TX PMOS impedance selection data
+ * @tx_nmos: TX NMOS impedance selection data
+ * @rx_data: RX impedance selection data
+ * @lane_efuse_supported: software eFuse data is supported for this lane
+ */
+struct mtk_pcie_lane_efuse {
+ u32 tx_pmos;
+ u32 tx_nmos;
+ u32 rx_data;
+ bool lane_efuse_supported;
+};
+
+/**
+ * struct mtk_pcie_phy_data - phy data for each SoC
+ * @num_lanes: supported lane numbers
+ * @sw_efuse_supported: support software to load eFuse data
+ */
+struct mtk_pcie_phy_data {
+ int num_lanes;
+ bool sw_efuse_supported;
+};
+
+/**
+ * struct mtk_pcie_phy - PCIe phy driver main structure
+ * @dev: pointer to device
+ * @phy: pointer to generic phy
+ * @sif_base: IO mapped register base address of system interface
+ * @data: pointer to SoC dependent data
+ * @sw_efuse_en: software eFuse enable status
+ * @efuse_glb_intr: internal resistor selection of TX bias current data
+ * @efuse: pointer to eFuse data for each lane
+ */
+struct mtk_pcie_phy {
+ struct device *dev;
+ struct phy *phy;
+ void __iomem *sif_base;
+ const struct mtk_pcie_phy_data *data;
+
+ bool sw_efuse_en;
+ u32 efuse_glb_intr;
+ struct mtk_pcie_lane_efuse *efuse;
+};
+
+static void mtk_pcie_efuse_set_lane(struct mtk_pcie_phy *pcie_phy,
+ unsigned int lane)
+{
+ struct mtk_pcie_lane_efuse *data = &pcie_phy->efuse[lane];
+ void __iomem *addr;
+
+ if (!data->lane_efuse_supported)
+ return;
+
+ addr = pcie_phy->sif_base + PEXTP_ANA_LN0_TRX_REG +
+ lane * PEXTP_ANA_LANE_OFFSET;
+
+ mtk_phy_update_field(addr + PEXTP_ANA_TX_REG, EFUSE_LN_TX_PMOS_SEL,
+ data->tx_pmos);
+
+ mtk_phy_update_field(addr + PEXTP_ANA_TX_REG, EFUSE_LN_TX_NMOS_SEL,
+ data->tx_nmos);
+
+ mtk_phy_update_field(addr + PEXTP_ANA_RX_REG, EFUSE_LN_RX_SEL,
+ data->rx_data);
+}
+
+/**
+ * mtk_pcie_phy_init() - Initialize the phy
+ * @phy: the phy to be initialized
+ *
+ * Initialize the phy by setting the efuse data.
+ * The hardware settings will be reset during suspend, it should be
+ * reinitialized when the consumer calls phy_init() again on resume.
+ */
+static int mtk_pcie_phy_init(struct phy *phy)
+{
+ struct mtk_pcie_phy *pcie_phy = phy_get_drvdata(phy);
+ int i;
+
+ if (!pcie_phy->sw_efuse_en)
+ return 0;
+
+ /* Set global data */
+ mtk_phy_update_field(pcie_phy->sif_base + PEXTP_ANA_GLB_00_REG,
+ EFUSE_GLB_INTR_SEL, pcie_phy->efuse_glb_intr);
+
+ for (i = 0; i < pcie_phy->data->num_lanes; i++)
+ mtk_pcie_efuse_set_lane(pcie_phy, i);
+
+ return 0;
+}
+
+static const struct phy_ops mtk_pcie_phy_ops = {
+ .init = mtk_pcie_phy_init,
+ .owner = THIS_MODULE,
+};
+
+static int mtk_pcie_efuse_read_for_lane(struct mtk_pcie_phy *pcie_phy,
+ unsigned int lane)
+{
+ struct mtk_pcie_lane_efuse *efuse = &pcie_phy->efuse[lane];
+ struct device *dev = pcie_phy->dev;
+ char efuse_id[16];
+ int ret;
+
+ snprintf(efuse_id, sizeof(efuse_id), "tx_ln%d_pmos", lane);
+ ret = nvmem_cell_read_variable_le_u32(dev, efuse_id, &efuse->tx_pmos);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read %s\n", efuse_id);
+
+ snprintf(efuse_id, sizeof(efuse_id), "tx_ln%d_nmos", lane);
+ ret = nvmem_cell_read_variable_le_u32(dev, efuse_id, &efuse->tx_nmos);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read %s\n", efuse_id);
+
+ snprintf(efuse_id, sizeof(efuse_id), "rx_ln%d", lane);
+ ret = nvmem_cell_read_variable_le_u32(dev, efuse_id, &efuse->rx_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read %s\n", efuse_id);
+
+ if (!(efuse->tx_pmos || efuse->tx_nmos || efuse->rx_data))
+ return dev_err_probe(dev, -EINVAL,
+ "No eFuse data found for lane%d, but dts enable it\n",
+ lane);
+
+ efuse->lane_efuse_supported = true;
+
+ return 0;
+}
+
+static int mtk_pcie_read_efuse(struct mtk_pcie_phy *pcie_phy)
+{
+ struct device *dev = pcie_phy->dev;
+ bool nvmem_enabled;
+ int ret, i;
+
+ /* nvmem data is optional */
+ nvmem_enabled = device_property_present(dev, "nvmem-cells");
+ if (!nvmem_enabled)
+ return 0;
+
+ ret = nvmem_cell_read_variable_le_u32(dev, "glb_intr",
+ &pcie_phy->efuse_glb_intr);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read glb_intr\n");
+
+ pcie_phy->sw_efuse_en = true;
+
+ pcie_phy->efuse = devm_kzalloc(dev, pcie_phy->data->num_lanes *
+ sizeof(*pcie_phy->efuse), GFP_KERNEL);
+ if (!pcie_phy->efuse)
+ return -ENOMEM;
+
+ for (i = 0; i < pcie_phy->data->num_lanes; i++) {
+ ret = mtk_pcie_efuse_read_for_lane(pcie_phy, i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mtk_pcie_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *provider;
+ struct mtk_pcie_phy *pcie_phy;
+ int ret;
+
+ pcie_phy = devm_kzalloc(dev, sizeof(*pcie_phy), GFP_KERNEL);
+ if (!pcie_phy)
+ return -ENOMEM;
+
+ pcie_phy->sif_base = devm_platform_ioremap_resource_byname(pdev, "sif");
+ if (IS_ERR(pcie_phy->sif_base))
+ return dev_err_probe(dev, PTR_ERR(pcie_phy->sif_base),
+ "Failed to map phy-sif base\n");
+
+ pcie_phy->phy = devm_phy_create(dev, dev->of_node, &mtk_pcie_phy_ops);
+ if (IS_ERR(pcie_phy->phy))
+ return dev_err_probe(dev, PTR_ERR(pcie_phy->phy),
+ "Failed to create PCIe phy\n");
+
+ pcie_phy->dev = dev;
+ pcie_phy->data = of_device_get_match_data(dev);
+ if (!pcie_phy->data)
+ return dev_err_probe(dev, -EINVAL, "Failed to get phy data\n");
+
+ if (pcie_phy->data->sw_efuse_supported) {
+ /*
+ * Failed to read the efuse data is not a fatal problem,
+ * ignore the failure and keep going.
+ */
+ ret = mtk_pcie_read_efuse(pcie_phy);
+ if (ret == -EPROBE_DEFER || ret == -ENOMEM)
+ return ret;
+ }
+
+ phy_set_drvdata(pcie_phy->phy, pcie_phy);
+
+ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(provider))
+ return dev_err_probe(dev, PTR_ERR(provider),
+ "PCIe phy probe failed\n");
+
+ return 0;
+}
+
+static const struct mtk_pcie_phy_data mt8195_data = {
+ .num_lanes = 2,
+ .sw_efuse_supported = true,
+};
+
+static const struct of_device_id mtk_pcie_phy_of_match[] = {
+ { .compatible = "mediatek,mt8195-pcie-phy", .data = &mt8195_data },
+ { },
+};
+MODULE_DEVICE_TABLE(of, mtk_pcie_phy_of_match);
+
+static struct platform_driver mtk_pcie_phy_driver = {
+ .probe = mtk_pcie_phy_probe,
+ .driver = {
+ .name = "mtk-pcie-phy",
+ .of_match_table = mtk_pcie_phy_of_match,
+ },
+};
+module_platform_driver(mtk_pcie_phy_driver);
+
+MODULE_DESCRIPTION("MediaTek PCIe PHY driver");
+MODULE_AUTHOR("Jianjun Wang <jianjun.wang@mediatek.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/mediatek/phy-mtk-tphy.c b/drivers/phy/mediatek/phy-mtk-tphy.c
new file mode 100644
index 000000000..e906a8279
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-tphy.c
@@ -0,0 +1,1317 @@
+// 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/iopoll.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "phy-mtk-io.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/V3 sub-banks offset base address */
+/* V3: U2FREQ is not used anymore, but reserved */
+/* 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_MISC_REG1 0x04
+#define MR1_EFUSE_AUTO_LOAD_DIS BIT(6)
+
+#define U3P_USBPHYACR0 0x000
+#define PA0_RG_U2PLL_FORCE_ON BIT(15)
+#define PA0_USB20_PLL_PREDIV GENMASK(7, 6)
+#define PA0_RG_USB20_INTR_EN BIT(5)
+
+#define U3P_USBPHYACR1 0x004
+#define PA1_RG_INTR_CAL GENMASK(23, 19)
+#define PA1_RG_VRT_SEL GENMASK(14, 12)
+#define PA1_RG_TERM_SEL GENMASK(10, 8)
+
+#define U3P_USBPHYACR2 0x008
+#define PA2_RG_U2PLL_BW GENMASK(21, 19)
+#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_HS_100U_U3_EN BIT(11)
+
+#define U3P_USBPHYACR6 0x018
+#define PA6_RG_U2_PRE_EMP GENMASK(31, 30)
+#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_SQTH GENMASK(3, 0)
+
+#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 U3P_U2PHYA_RESV 0x030
+#define P2R_RG_U2PLL_FBDIV_26M 0x1bb13b
+#define P2R_RG_U2PLL_FBDIV_48M 0x3c0000
+
+#define U3P_U2PHYA_RESV1 0x044
+#define P2R_RG_U2PLL_REFCLK_SEL BIT(5)
+#define P2R_RG_U2PLL_FRA_EN BIT(3)
+
+#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_DMPULLDOWN BIT(7)
+#define P2C_RG_DPPULLDOWN BIT(6)
+#define P2C_RG_XCVRSEL GENMASK(5, 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_IEXT_INTR GENMASK(15, 10)
+#define P3A_RG_CLKDRV_OFF GENMASK(3, 2)
+
+#define U3P_U3_PHYA_REG1 0x004
+#define P3A_RG_CLKDRV_AMP GENMASK(31, 29)
+
+#define U3P_U3_PHYA_REG6 0x018
+#define P3A_RG_TX_EIDLE_CM GENMASK(31, 28)
+
+#define U3P_U3_PHYA_REG9 0x024
+#define P3A_RG_RX_DAC_MUX GENMASK(5, 1)
+
+#define U3P_U3_PHYA_DA_REG0 0x100
+#define P3A_RG_XTAL_EXT_PE2H GENMASK(17, 16)
+#define P3A_RG_XTAL_EXT_PE1H GENMASK(13, 12)
+#define P3A_RG_XTAL_EXT_EN_U3 GENMASK(11, 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 U3P_U3_PHYA_DA_REG5 0x10c
+#define P3A_RG_PLL_BR_PE2H GENMASK(29, 28)
+#define P3A_RG_PLL_IC_PE2H GENMASK(15, 12)
+
+#define U3P_U3_PHYA_DA_REG6 0x110
+#define P3A_RG_PLL_IR_PE2H GENMASK(19, 16)
+
+#define U3P_U3_PHYA_DA_REG7 0x114
+#define P3A_RG_PLL_BP_PE2H GENMASK(19, 16)
+
+#define U3P_U3_PHYA_DA_REG20 0x13c
+#define P3A_RG_PLL_DELTA1_PE2H GENMASK(31, 16)
+
+#define U3P_U3_PHYA_DA_REG25 0x148
+#define P3A_RG_PLL_DELTA_PE2H GENMASK(15, 0)
+
+#define U3P_U3_PHYD_LFPS1 0x00c
+#define P3D_RG_FWAKE_TH GENMASK(21, 16)
+
+#define U3P_U3_PHYD_IMPCAL0 0x010
+#define P3D_RG_FORCE_TX_IMPEL BIT(31)
+#define P3D_RG_TX_IMPEL GENMASK(28, 24)
+
+#define U3P_U3_PHYD_IMPCAL1 0x014
+#define P3D_RG_FORCE_RX_IMPEL BIT(31)
+#define P3D_RG_RX_IMPEL GENMASK(28, 24)
+
+#define U3P_U3_PHYD_RSV 0x054
+#define P3D_RG_EFUSE_AUTO_LOAD_DIS BIT(12)
+
+#define U3P_U3_PHYD_CDR1 0x05c
+#define P3D_RG_CDR_BIR_LTD1 GENMASK(28, 24)
+#define P3D_RG_CDR_BIR_LTD0 GENMASK(12, 8)
+
+#define U3P_U3_PHYD_RXDET1 0x128
+#define P3D_RG_RXDET_STB2_SET GENMASK(17, 9)
+
+#define U3P_U3_PHYD_RXDET2 0x12c
+#define P3D_RG_RXDET_STB2_SET_P3 GENMASK(8, 0)
+
+#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_FREQDET_EN BIT(24)
+#define P2F_RG_CYCLECNT GENMASK(23, 0)
+
+#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_BICLTD0_GEN1_MSK GENMASK(11, 8)
+
+#define PHYD_DESIGN_OPTION2 0x24
+/* Symbol lock count selection */
+#define RG_LOCK_CNT_SEL_MSK GENMASK(5, 4)
+
+#define PHYD_DESIGN_OPTION9 0x40
+/* COMWAK GAP width window */
+#define RG_TG_MAX_MSK GENMASK(20, 16)
+/* COMINIT GAP width window */
+#define RG_T2_MAX_MSK GENMASK(13, 8)
+/* COMWAK GAP width window */
+#define RG_TG_MIN_MSK GENMASK(7, 5)
+/* COMINIT GAP width window */
+#define RG_T2_MIN_MSK GENMASK(4, 0)
+
+#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 ANA_RG_CTRL_SIGNAL4 0x58
+#define RG_CDR_BICLTR_GEN1_MSK GENMASK(23, 20)
+/* Loop filter R1 resistance adjustment for Gen1 speed */
+#define RG_CDR_BR_GEN2_MSK GENMASK(10, 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_BIRLTR_GEN1_MSK GENMASK(4, 0)
+
+#define ANA_EQ_EYE_CTRL_SIGNAL1 0x6c
+/* RX Gen1 LEQ tuning step */
+#define RG_EQ_DLEQ_LFI_GEN1_MSK GENMASK(11, 8)
+
+#define ANA_EQ_EYE_CTRL_SIGNAL4 0xd8
+#define RG_CDR_BIRLTD0_GEN1_MSK GENMASK(20, 16)
+
+#define ANA_EQ_EYE_CTRL_SIGNAL5 0xdc
+#define RG_CDR_BIRLTD0_GEN3_MSK GENMASK(4, 0)
+
+/* PHY switch between pcie/usb3/sgmii/sata */
+#define USB_PHY_SWITCH_CTRL 0x0
+#define RG_PHY_SW_TYPE GENMASK(3, 0)
+#define RG_PHY_SW_PCIE 0x0
+#define RG_PHY_SW_USB3 0x1
+#define RG_PHY_SW_SGMII 0x2
+#define RG_PHY_SW_SATA 0x3
+
+#define TPHY_CLKS_CNT 2
+
+enum mtk_phy_version {
+ MTK_PHY_V1 = 1,
+ MTK_PHY_V2,
+ MTK_PHY_V3,
+};
+
+struct mtk_phy_pdata {
+ /* avoid RX sensitivity level degradation only for mt8173 */
+ bool avoid_rx_sen_degradation;
+ /*
+ * workaround only for mt8195, HW fix it for others of V3,
+ * u2phy should use integer mode instead of fractional mode of
+ * 48M PLL, fix it by switching PLL to 26M from default 48M
+ */
+ bool sw_pll_48m_to_26m;
+ /*
+ * Some SoCs (e.g. mt8195) drop a bit when use auto load efuse,
+ * support sw way, also support it for v2/v3 optionally.
+ */
+ bool sw_efuse_supported;
+ 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_bulk_data clks[TPHY_CLKS_CNT];
+ u32 index;
+ u32 type;
+ struct regmap *type_sw;
+ u32 type_sw_reg;
+ u32 type_sw_index;
+ u32 efuse_sw_en;
+ u32 efuse_intr;
+ u32 efuse_tx_imp;
+ u32 efuse_rx_imp;
+ int eye_src;
+ int eye_vrt;
+ int eye_term;
+ int intr;
+ int discth;
+ int pre_emphasis;
+ 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;
+
+ /* HW V3 doesn't support slew rate cal anymore */
+ if (tphy->pdata->version == MTK_PHY_V3)
+ return;
+
+ /* use force value */
+ if (instance->eye_src)
+ return;
+
+ /* enable USB ring oscillator */
+ mtk_phy_set_bits(com + U3P_USBPHYACR5, PA5_RG_U2_HSTX_SRCAL_EN);
+ udelay(1);
+
+ /*enable free run clock */
+ mtk_phy_set_bits(fmreg + U3P_U2FREQ_FMMONR1, P2F_RG_FRCK_EN);
+
+ /* set cycle count as 1024, and select u2 channel */
+ tmp = readl(fmreg + U3P_U2FREQ_FMCR0);
+ tmp &= ~(P2F_RG_CYCLECNT | P2F_RG_MONCLK_SEL);
+ tmp |= FIELD_PREP(P2F_RG_CYCLECNT, U3P_FM_DET_CYCLE_CNT);
+ if (tphy->pdata->version == MTK_PHY_V1)
+ tmp |= FIELD_PREP(P2F_RG_MONCLK_SEL, instance->index >> 1);
+
+ writel(tmp, fmreg + U3P_U2FREQ_FMCR0);
+
+ /* enable frequency meter */
+ mtk_phy_set_bits(fmreg + U3P_U2FREQ_FMCR0, P2F_RG_FREQDET_EN);
+
+ /* 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 */
+ mtk_phy_clear_bits(fmreg + U3P_U2FREQ_FMCR0, P2F_RG_FREQDET_EN);
+
+ /*disable free run clock */
+ mtk_phy_clear_bits(fmreg + U3P_U2FREQ_FMMONR1, P2F_RG_FRCK_EN);
+
+ 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 */
+ mtk_phy_update_field(com + U3P_USBPHYACR5, PA5_RG_U2_HSTX_SRCTRL,
+ calibration_val);
+
+ /* disable USB ring oscillator */
+ mtk_phy_clear_bits(com + U3P_USBPHYACR5, PA5_RG_U2_HSTX_SRCAL_EN);
+}
+
+static void u3_phy_instance_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+ void __iomem *phya = u3_banks->phya;
+ void __iomem *phyd = u3_banks->phyd;
+
+ /* gating PCIe Analog XTAL clock */
+ mtk_phy_set_bits(u3_banks->spllc + U3P_SPLLC_XTALCTL3,
+ XC3_RG_U3_XTAL_RX_PWD | XC3_RG_U3_FRC_XTAL_RX_PWD);
+
+ /* gating XSQ */
+ mtk_phy_update_field(phya + U3P_U3_PHYA_DA_REG0, P3A_RG_XTAL_EXT_EN_U3, 2);
+
+ mtk_phy_update_field(phya + U3P_U3_PHYA_REG9, P3A_RG_RX_DAC_MUX, 4);
+
+ mtk_phy_update_field(phya + U3P_U3_PHYA_REG6, P3A_RG_TX_EIDLE_CM, 0xe);
+
+ mtk_phy_update_bits(u3_banks->phyd + U3P_U3_PHYD_CDR1,
+ P3D_RG_CDR_BIR_LTD0 | P3D_RG_CDR_BIR_LTD1,
+ FIELD_PREP(P3D_RG_CDR_BIR_LTD0, 0xc) |
+ FIELD_PREP(P3D_RG_CDR_BIR_LTD1, 0x3));
+
+ mtk_phy_update_field(phyd + U3P_U3_PHYD_LFPS1, P3D_RG_FWAKE_TH, 0x34);
+
+ mtk_phy_update_field(phyd + U3P_U3_PHYD_RXDET1, P3D_RG_RXDET_STB2_SET, 0x10);
+
+ mtk_phy_update_field(phyd + U3P_U3_PHYD_RXDET2, P3D_RG_RXDET_STB2_SET_P3, 0x10);
+
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, instance->index);
+}
+
+static void u2_phy_pll_26m_set(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ void __iomem *com = u2_banks->com;
+
+ if (!tphy->pdata->sw_pll_48m_to_26m)
+ return;
+
+ mtk_phy_update_field(com + U3P_USBPHYACR0, PA0_USB20_PLL_PREDIV, 0);
+
+ mtk_phy_update_field(com + U3P_USBPHYACR2, PA2_RG_U2PLL_BW, 3);
+
+ writel(P2R_RG_U2PLL_FBDIV_26M, com + U3P_U2PHYA_RESV);
+
+ mtk_phy_set_bits(com + U3P_U2PHYA_RESV1,
+ P2R_RG_U2PLL_FRA_EN | P2R_RG_U2PLL_REFCLK_SEL);
+}
+
+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;
+
+ /* switch to USB function, and enable usb pll */
+ mtk_phy_clear_bits(com + U3P_U2PHYDTM0, P2C_FORCE_UART_EN | P2C_FORCE_SUSPENDM);
+
+ mtk_phy_clear_bits(com + U3P_U2PHYDTM0,
+ P2C_RG_XCVRSEL | P2C_RG_DATAIN | P2C_DTM0_PART_MASK);
+
+ mtk_phy_clear_bits(com + U3P_U2PHYDTM1, P2C_RG_UART_EN);
+
+ mtk_phy_set_bits(com + U3P_USBPHYACR0, PA0_RG_USB20_INTR_EN);
+
+ /* disable switch 100uA current to SSUSB */
+ mtk_phy_clear_bits(com + U3P_USBPHYACR5, PA5_RG_U2_HS_100U_U3_EN);
+
+ mtk_phy_clear_bits(com + U3P_U2PHYACR4, P2C_U2_GPIO_CTR_MSK);
+
+ if (tphy->pdata->avoid_rx_sen_degradation) {
+ if (!index) {
+ mtk_phy_set_bits(com + U3P_USBPHYACR2, PA2_RG_SIF_U2PLL_FORCE_EN);
+
+ mtk_phy_clear_bits(com + U3D_U2PHYDCR0, P2C_RG_SIF_U2PLL_FORCE_ON);
+ } else {
+ mtk_phy_set_bits(com + U3D_U2PHYDCR0, P2C_RG_SIF_U2PLL_FORCE_ON);
+
+ mtk_phy_set_bits(com + U3P_U2PHYDTM0,
+ P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM);
+ }
+ }
+
+ /* DP/DM BC1.1 path Disable */
+ mtk_phy_clear_bits(com + U3P_USBPHYACR6, PA6_RG_U2_BC11_SW_EN);
+
+ mtk_phy_update_field(com + U3P_USBPHYACR6, PA6_RG_U2_SQTH, 2);
+
+ /* Workaround only for mt8195, HW fix it for others (V3) */
+ u2_phy_pll_26m_set(tphy, instance);
+
+ 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;
+
+ /* OTG Enable */
+ mtk_phy_set_bits(com + U3P_USBPHYACR6, PA6_RG_U2_OTG_VBUSCMP_EN);
+
+ mtk_phy_set_bits(com + U3P_U2PHYDTM1, P2C_RG_VBUSVALID | P2C_RG_AVALID);
+
+ mtk_phy_clear_bits(com + U3P_U2PHYDTM1, P2C_RG_SESSEND);
+
+ if (tphy->pdata->avoid_rx_sen_degradation && index) {
+ mtk_phy_set_bits(com + U3D_U2PHYDCR0, P2C_RG_SIF_U2PLL_FORCE_ON);
+
+ mtk_phy_set_bits(com + U3P_U2PHYDTM0, P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM);
+ }
+ 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;
+
+ /* OTG Disable */
+ mtk_phy_clear_bits(com + U3P_USBPHYACR6, PA6_RG_U2_OTG_VBUSCMP_EN);
+
+ mtk_phy_clear_bits(com + U3P_U2PHYDTM1, P2C_RG_VBUSVALID | P2C_RG_AVALID);
+
+ mtk_phy_set_bits(com + U3P_U2PHYDTM1, P2C_RG_SESSEND);
+
+ if (tphy->pdata->avoid_rx_sen_degradation && index) {
+ mtk_phy_clear_bits(com + U3P_U2PHYDTM0, P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM);
+
+ mtk_phy_clear_bits(com + U3D_U2PHYDCR0, P2C_RG_SIF_U2PLL_FORCE_ON);
+ }
+
+ 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;
+
+ if (tphy->pdata->avoid_rx_sen_degradation && index) {
+ mtk_phy_clear_bits(com + U3D_U2PHYDCR0, P2C_RG_SIF_U2PLL_FORCE_ON);
+
+ mtk_phy_clear_bits(com + U3P_U2PHYDTM0, P2C_FORCE_SUSPENDM);
+ }
+}
+
+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;
+ void __iomem *phya = u3_banks->phya;
+
+ if (tphy->pdata->version != MTK_PHY_V1)
+ return;
+
+ mtk_phy_update_bits(phya + U3P_U3_PHYA_DA_REG0,
+ P3A_RG_XTAL_EXT_PE1H | P3A_RG_XTAL_EXT_PE2H,
+ FIELD_PREP(P3A_RG_XTAL_EXT_PE1H, 0x2) |
+ FIELD_PREP(P3A_RG_XTAL_EXT_PE2H, 0x2));
+
+ /* ref clk drive */
+ mtk_phy_update_field(phya + U3P_U3_PHYA_REG1, P3A_RG_CLKDRV_AMP, 0x4);
+
+ mtk_phy_update_field(phya + U3P_U3_PHYA_REG0, P3A_RG_CLKDRV_OFF, 0x1);
+
+ /* SSC delta -5000ppm */
+ mtk_phy_update_field(phya + U3P_U3_PHYA_DA_REG20, P3A_RG_PLL_DELTA1_PE2H, 0x3c);
+
+ mtk_phy_update_field(phya + U3P_U3_PHYA_DA_REG25, P3A_RG_PLL_DELTA_PE2H, 0x36);
+
+ /* change pll BW 0.6M */
+ mtk_phy_update_bits(phya + U3P_U3_PHYA_DA_REG5,
+ P3A_RG_PLL_BR_PE2H | P3A_RG_PLL_IC_PE2H,
+ FIELD_PREP(P3A_RG_PLL_BR_PE2H, 0x1) |
+ FIELD_PREP(P3A_RG_PLL_IC_PE2H, 0x1));
+
+ mtk_phy_update_bits(phya + U3P_U3_PHYA_DA_REG4,
+ P3A_RG_PLL_DIVEN_PE2H | P3A_RG_PLL_BC_PE2H,
+ FIELD_PREP(P3A_RG_PLL_BC_PE2H, 0x3));
+
+ mtk_phy_update_field(phya + U3P_U3_PHYA_DA_REG6, P3A_RG_PLL_IR_PE2H, 0x2);
+
+ mtk_phy_update_field(phya + U3P_U3_PHYA_DA_REG7, P3A_RG_PLL_BP_PE2H, 0xa);
+
+ /* Tx Detect Rx Timing: 10us -> 5us */
+ mtk_phy_update_field(u3_banks->phyd + U3P_U3_PHYD_RXDET1,
+ P3D_RG_RXDET_STB2_SET, 0x10);
+
+ mtk_phy_update_field(u3_banks->phyd + U3P_U3_PHYD_RXDET2,
+ P3D_RG_RXDET_STB2_SET_P3, 0x10);
+
+ /* 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;
+
+ mtk_phy_clear_bits(bank->chip + U3P_U3_CHIP_GPIO_CTLD,
+ P3C_FORCE_IP_SW_RST | P3C_REG_IP_SW_RST);
+
+ mtk_phy_clear_bits(bank->chip + U3P_U3_CHIP_GPIO_CTLE,
+ P3C_RG_SWRST_U3_PHYD_FORCE_EN | P3C_RG_SWRST_U3_PHYD);
+}
+
+static void pcie_phy_instance_power_off(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+
+{
+ struct u3phy_banks *bank = &instance->u3_banks;
+
+ mtk_phy_set_bits(bank->chip + U3P_U3_CHIP_GPIO_CTLD,
+ P3C_FORCE_IP_SW_RST | P3C_REG_IP_SW_RST);
+
+ mtk_phy_set_bits(bank->chip + U3P_U3_CHIP_GPIO_CTLE,
+ P3C_RG_SWRST_U3_PHYD_FORCE_EN | P3C_RG_SWRST_U3_PHYD);
+}
+
+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;
+
+ /* charge current adjustment */
+ mtk_phy_update_bits(phyd + ANA_RG_CTRL_SIGNAL6,
+ RG_CDR_BIRLTR_GEN1_MSK | RG_CDR_BC_GEN1_MSK,
+ FIELD_PREP(RG_CDR_BIRLTR_GEN1_MSK, 0x6) |
+ FIELD_PREP(RG_CDR_BC_GEN1_MSK, 0x1a));
+
+ mtk_phy_update_field(phyd + ANA_EQ_EYE_CTRL_SIGNAL4, RG_CDR_BIRLTD0_GEN1_MSK, 0x18);
+
+ mtk_phy_update_field(phyd + ANA_EQ_EYE_CTRL_SIGNAL5, RG_CDR_BIRLTD0_GEN3_MSK, 0x06);
+
+ mtk_phy_update_bits(phyd + ANA_RG_CTRL_SIGNAL4,
+ RG_CDR_BICLTR_GEN1_MSK | RG_CDR_BR_GEN2_MSK,
+ FIELD_PREP(RG_CDR_BICLTR_GEN1_MSK, 0x0c) |
+ FIELD_PREP(RG_CDR_BR_GEN2_MSK, 0x07));
+
+ mtk_phy_update_bits(phyd + PHYD_CTRL_SIGNAL_MODE4,
+ RG_CDR_BICLTD0_GEN1_MSK | RG_CDR_BICLTD1_GEN1_MSK,
+ FIELD_PREP(RG_CDR_BICLTD0_GEN1_MSK, 0x08) |
+ FIELD_PREP(RG_CDR_BICLTD1_GEN1_MSK, 0x02));
+
+ mtk_phy_update_field(phyd + PHYD_DESIGN_OPTION2, RG_LOCK_CNT_SEL_MSK, 0x02);
+
+ mtk_phy_update_bits(phyd + PHYD_DESIGN_OPTION9,
+ RG_T2_MIN_MSK | RG_TG_MIN_MSK,
+ FIELD_PREP(RG_T2_MIN_MSK, 0x12) |
+ FIELD_PREP(RG_TG_MIN_MSK, 0x04));
+
+ mtk_phy_update_bits(phyd + PHYD_DESIGN_OPTION9,
+ RG_T2_MAX_MSK | RG_TG_MAX_MSK,
+ FIELD_PREP(RG_T2_MAX_MSK, 0x31) |
+ FIELD_PREP(RG_TG_MAX_MSK, 0x0e));
+
+ mtk_phy_update_field(phyd + ANA_RG_CTRL_SIGNAL1, RG_IDRV_0DB_GEN1_MSK, 0x20);
+
+ mtk_phy_update_field(phyd + ANA_EQ_EYE_CTRL_SIGNAL1, RG_EQ_DLEQ_LFI_GEN1_MSK, 0x03);
+
+ 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);
+ device_property_read_u32(dev, "mediatek,pre-emphasis",
+ &instance->pre_emphasis);
+ 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);
+ dev_dbg(dev, "pre-emp:%d\n", instance->pre_emphasis);
+}
+
+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;
+
+ if (instance->bc12_en) /* BC1.2 path Enable */
+ mtk_phy_set_bits(com + U3P_U2PHYBC12C, P2C_RG_CHGDT_EN);
+
+ if (tphy->pdata->version < MTK_PHY_V3 && instance->eye_src)
+ mtk_phy_update_field(com + U3P_USBPHYACR5, PA5_RG_U2_HSTX_SRCTRL,
+ instance->eye_src);
+
+ if (instance->eye_vrt)
+ mtk_phy_update_field(com + U3P_USBPHYACR1, PA1_RG_VRT_SEL,
+ instance->eye_vrt);
+
+ if (instance->eye_term)
+ mtk_phy_update_field(com + U3P_USBPHYACR1, PA1_RG_TERM_SEL,
+ instance->eye_term);
+
+ if (instance->intr) {
+ if (u2_banks->misc)
+ mtk_phy_set_bits(u2_banks->misc + U3P_MISC_REG1,
+ MR1_EFUSE_AUTO_LOAD_DIS);
+
+ mtk_phy_update_field(com + U3P_USBPHYACR1, PA1_RG_INTR_CAL,
+ instance->intr);
+ }
+
+ if (instance->discth)
+ mtk_phy_update_field(com + U3P_USBPHYACR6, PA6_RG_U2_DISCTH,
+ instance->discth);
+
+ if (instance->pre_emphasis)
+ mtk_phy_update_field(com + U3P_USBPHYACR6, PA6_RG_U2_PRE_EMP,
+ instance->pre_emphasis);
+}
+
+/* type switch for usb3/pcie/sgmii/sata */
+static int phy_type_syscon_get(struct mtk_phy_instance *instance,
+ struct device_node *dn)
+{
+ struct of_phandle_args args;
+ int ret;
+
+ /* type switch function is optional */
+ if (!of_property_read_bool(dn, "mediatek,syscon-type"))
+ return 0;
+
+ ret = of_parse_phandle_with_fixed_args(dn, "mediatek,syscon-type",
+ 2, 0, &args);
+ if (ret)
+ return ret;
+
+ instance->type_sw_reg = args.args[0];
+ instance->type_sw_index = args.args[1] & 0x3; /* <=3 */
+ instance->type_sw = syscon_node_to_regmap(args.np);
+ of_node_put(args.np);
+ dev_info(&instance->phy->dev, "type_sw - reg %#x, index %d\n",
+ instance->type_sw_reg, instance->type_sw_index);
+
+ return PTR_ERR_OR_ZERO(instance->type_sw);
+}
+
+static int phy_type_set(struct mtk_phy_instance *instance)
+{
+ int type;
+ u32 offset;
+
+ if (!instance->type_sw)
+ return 0;
+
+ switch (instance->type) {
+ case PHY_TYPE_USB3:
+ type = RG_PHY_SW_USB3;
+ break;
+ case PHY_TYPE_PCIE:
+ type = RG_PHY_SW_PCIE;
+ break;
+ case PHY_TYPE_SGMII:
+ type = RG_PHY_SW_SGMII;
+ break;
+ case PHY_TYPE_SATA:
+ type = RG_PHY_SW_SATA;
+ break;
+ case PHY_TYPE_USB2:
+ default:
+ return 0;
+ }
+
+ offset = instance->type_sw_index * BITS_PER_BYTE;
+ regmap_update_bits(instance->type_sw, instance->type_sw_reg,
+ RG_PHY_SW_TYPE << offset, type << offset);
+
+ return 0;
+}
+
+static int phy_efuse_get(struct mtk_tphy *tphy, struct mtk_phy_instance *instance)
+{
+ struct device *dev = &instance->phy->dev;
+ int ret = 0;
+
+ /* tphy v1 doesn't support sw efuse, skip it */
+ if (!tphy->pdata->sw_efuse_supported) {
+ instance->efuse_sw_en = 0;
+ return 0;
+ }
+
+ /* software efuse is optional */
+ instance->efuse_sw_en = device_property_read_bool(dev, "nvmem-cells");
+ if (!instance->efuse_sw_en)
+ return 0;
+
+ switch (instance->type) {
+ case PHY_TYPE_USB2:
+ ret = nvmem_cell_read_variable_le_u32(dev, "intr", &instance->efuse_intr);
+ if (ret) {
+ dev_err(dev, "fail to get u2 intr efuse, %d\n", ret);
+ break;
+ }
+
+ /* no efuse, ignore it */
+ if (!instance->efuse_intr) {
+ dev_warn(dev, "no u2 intr efuse, but dts enable it\n");
+ instance->efuse_sw_en = 0;
+ break;
+ }
+
+ dev_dbg(dev, "u2 efuse - intr %x\n", instance->efuse_intr);
+ break;
+
+ case PHY_TYPE_USB3:
+ case PHY_TYPE_PCIE:
+ ret = nvmem_cell_read_variable_le_u32(dev, "intr", &instance->efuse_intr);
+ if (ret) {
+ dev_err(dev, "fail to get u3 intr efuse, %d\n", ret);
+ break;
+ }
+
+ ret = nvmem_cell_read_variable_le_u32(dev, "rx_imp", &instance->efuse_rx_imp);
+ if (ret) {
+ dev_err(dev, "fail to get u3 rx_imp efuse, %d\n", ret);
+ break;
+ }
+
+ ret = nvmem_cell_read_variable_le_u32(dev, "tx_imp", &instance->efuse_tx_imp);
+ if (ret) {
+ dev_err(dev, "fail to get u3 tx_imp efuse, %d\n", ret);
+ break;
+ }
+
+ /* no efuse, ignore it */
+ if (!instance->efuse_intr &&
+ !instance->efuse_rx_imp &&
+ !instance->efuse_tx_imp) {
+ dev_warn(dev, "no u3 intr efuse, but dts enable it\n");
+ instance->efuse_sw_en = 0;
+ break;
+ }
+
+ dev_dbg(dev, "u3 efuse - intr %x, rx_imp %x, tx_imp %x\n",
+ instance->efuse_intr, instance->efuse_rx_imp,instance->efuse_tx_imp);
+ break;
+ default:
+ dev_err(dev, "no sw efuse for type %d\n", instance->type);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static void phy_efuse_set(struct mtk_phy_instance *instance)
+{
+ struct device *dev = &instance->phy->dev;
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+
+ if (!instance->efuse_sw_en)
+ return;
+
+ switch (instance->type) {
+ case PHY_TYPE_USB2:
+ mtk_phy_set_bits(u2_banks->misc + U3P_MISC_REG1, MR1_EFUSE_AUTO_LOAD_DIS);
+
+ mtk_phy_update_field(u2_banks->com + U3P_USBPHYACR1, PA1_RG_INTR_CAL,
+ instance->efuse_intr);
+ break;
+ case PHY_TYPE_USB3:
+ case PHY_TYPE_PCIE:
+ mtk_phy_set_bits(u3_banks->phyd + U3P_U3_PHYD_RSV, P3D_RG_EFUSE_AUTO_LOAD_DIS);
+
+ mtk_phy_update_field(u3_banks->phyd + U3P_U3_PHYD_IMPCAL0, P3D_RG_TX_IMPEL,
+ instance->efuse_tx_imp);
+ mtk_phy_set_bits(u3_banks->phyd + U3P_U3_PHYD_IMPCAL0, P3D_RG_FORCE_TX_IMPEL);
+
+ mtk_phy_update_field(u3_banks->phyd + U3P_U3_PHYD_IMPCAL1, P3D_RG_RX_IMPEL,
+ instance->efuse_rx_imp);
+ mtk_phy_set_bits(u3_banks->phyd + U3P_U3_PHYD_IMPCAL1, P3D_RG_FORCE_RX_IMPEL);
+
+ mtk_phy_update_field(u3_banks->phya + U3P_U3_PHYA_REG0, P3A_RG_IEXT_INTR,
+ instance->efuse_intr);
+ break;
+ default:
+ dev_warn(dev, "no sw efuse for type %d\n", instance->type);
+ break;
+ }
+}
+
+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_bulk_prepare_enable(TPHY_CLKS_CNT, instance->clks);
+ if (ret)
+ return ret;
+
+ phy_efuse_set(instance);
+
+ 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;
+ case PHY_TYPE_SGMII:
+ /* nothing to do, only used to set type */
+ break;
+ default:
+ dev_err(tphy->dev, "incompatible PHY type\n");
+ clk_bulk_disable_unprepare(TPHY_CLKS_CNT, instance->clks);
+ 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_bulk_disable_unprepare(TPHY_CLKS_CNT, instance->clks);
+ 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;
+ int ret;
+
+ 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 ||
+ instance->type == PHY_TYPE_SGMII)) {
+ dev_err(dev, "unsupported device type: %d\n", instance->type);
+ return ERR_PTR(-EINVAL);
+ }
+
+ switch (tphy->pdata->version) {
+ case MTK_PHY_V1:
+ phy_v1_banks_init(tphy, instance);
+ break;
+ case MTK_PHY_V2:
+ case MTK_PHY_V3:
+ phy_v2_banks_init(tphy, instance);
+ break;
+ default:
+ dev_err(dev, "phy version is not supported\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ ret = phy_efuse_get(tphy, instance);
+ if (ret)
+ return ERR_PTR(ret);
+
+ phy_parse_property(tphy, instance);
+ phy_type_set(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,
+ .sw_efuse_supported = true,
+ .version = MTK_PHY_V2,
+};
+
+static const struct mtk_phy_pdata tphy_v3_pdata = {
+ .sw_efuse_supported = true,
+ .version = MTK_PHY_V3,
+};
+
+static const struct mtk_phy_pdata mt8173_pdata = {
+ .avoid_rx_sen_degradation = true,
+ .version = MTK_PHY_V1,
+};
+
+static const struct mtk_phy_pdata mt8195_pdata = {
+ .sw_pll_48m_to_26m = true,
+ .sw_efuse_supported = true,
+ .version = MTK_PHY_V3,
+};
+
+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,mt8195-tphy", .data = &mt8195_pdata },
+ { .compatible = "mediatek,generic-tphy-v1", .data = &tphy_v1_pdata },
+ { .compatible = "mediatek,generic-tphy-v2", .data = &tphy_v2_pdata },
+ { .compatible = "mediatek,generic-tphy-v3", .data = &tphy_v3_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);
+ }
+ }
+
+ if (tphy->pdata->version < MTK_PHY_V3) {
+ 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 clk_bulk_data *clks;
+ struct device *subdev;
+ 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;
+ }
+
+ subdev = &phy->dev;
+ retval = of_address_to_resource(child_np, 0, &res);
+ if (retval) {
+ dev_err(subdev, "failed to get address resource(id-%d)\n",
+ port);
+ goto put_child;
+ }
+
+ instance->port_base = devm_ioremap_resource(subdev, &res);
+ if (IS_ERR(instance->port_base)) {
+ retval = PTR_ERR(instance->port_base);
+ goto put_child;
+ }
+
+ instance->phy = phy;
+ instance->index = port;
+ phy_set_drvdata(phy, instance);
+ port++;
+
+ clks = instance->clks;
+ clks[0].id = "ref"; /* digital (& analog) clock */
+ clks[1].id = "da_ref"; /* analog clock */
+ retval = devm_clk_bulk_get_optional(subdev, TPHY_CLKS_CNT, clks);
+ if (retval)
+ goto put_child;
+
+ retval = phy_type_syscon_get(instance, child_np);
+ if (retval)
+ 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..fc19e0fa8
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-ufs.c
@@ -0,0 +1,199 @@
+// 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>
+
+#include "phy-mtk-io.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)
+
+#define UFSPHY_CLKS_CNT 2
+
+struct ufs_mtk_phy {
+ struct device *dev;
+ void __iomem *mmio;
+ struct clk_bulk_data clks[UFSPHY_CLKS_CNT];
+};
+
+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;
+ struct clk_bulk_data *clks = phy->clks;
+
+ clks[0].id = "unipro";
+ clks[1].id = "mp";
+ return devm_clk_bulk_get(dev, UFSPHY_CLKS_CNT, clks);
+}
+
+static void ufs_mtk_phy_set_active(struct ufs_mtk_phy *phy)
+{
+ void __iomem *mmio = phy->mmio;
+
+ /* release DA_MP_PLL_PWR_ON */
+ mtk_phy_set_bits(mmio + MP_GLB_DIG_8C, PLL_PWR_ON);
+ mtk_phy_clear_bits(mmio + MP_GLB_DIG_8C, FRC_FRC_PWR_ON);
+
+ /* release DA_MP_PLL_ISO_EN */
+ mtk_phy_clear_bits(mmio + MP_GLB_DIG_8C, PLL_ISO_EN);
+ mtk_phy_clear_bits(mmio + MP_GLB_DIG_8C, FRC_PLL_ISO_EN);
+
+ /* release DA_MP_CDR_PWR_ON */
+ mtk_phy_set_bits(mmio + MP_LN_RX_44, CDR_PWR_ON);
+ mtk_phy_clear_bits(mmio + MP_LN_RX_44, FRC_CDR_PWR_ON);
+
+ /* release DA_MP_CDR_ISO_EN */
+ mtk_phy_clear_bits(mmio + MP_LN_RX_44, CDR_ISO_EN);
+ mtk_phy_clear_bits(mmio + MP_LN_RX_44, FRC_CDR_ISO_EN);
+
+ /* release DA_MP_RX0_SQ_EN */
+ mtk_phy_set_bits(mmio + MP_LN_DIG_RX_AC, RX_SQ_EN);
+ mtk_phy_clear_bits(mmio + MP_LN_DIG_RX_AC, FRC_RX_SQ_EN);
+
+ /* delay 1us to wait DIFZ stable */
+ udelay(1);
+
+ /* release DIFZ */
+ mtk_phy_clear_bits(mmio + MP_LN_DIG_RX_9C, FSM_DIFZ_FRC);
+}
+
+static void ufs_mtk_phy_set_deep_hibern(struct ufs_mtk_phy *phy)
+{
+ void __iomem *mmio = phy->mmio;
+
+ /* force DIFZ */
+ mtk_phy_set_bits(mmio + MP_LN_DIG_RX_9C, FSM_DIFZ_FRC);
+
+ /* force DA_MP_RX0_SQ_EN */
+ mtk_phy_set_bits(mmio + MP_LN_DIG_RX_AC, FRC_RX_SQ_EN);
+ mtk_phy_clear_bits(mmio + MP_LN_DIG_RX_AC, RX_SQ_EN);
+
+ /* force DA_MP_CDR_ISO_EN */
+ mtk_phy_set_bits(mmio + MP_LN_RX_44, FRC_CDR_ISO_EN);
+ mtk_phy_set_bits(mmio + MP_LN_RX_44, CDR_ISO_EN);
+
+ /* force DA_MP_CDR_PWR_ON */
+ mtk_phy_set_bits(mmio + MP_LN_RX_44, FRC_CDR_PWR_ON);
+ mtk_phy_clear_bits(mmio + MP_LN_RX_44, CDR_PWR_ON);
+
+ /* force DA_MP_PLL_ISO_EN */
+ mtk_phy_set_bits(mmio + MP_GLB_DIG_8C, FRC_PLL_ISO_EN);
+ mtk_phy_set_bits(mmio + MP_GLB_DIG_8C, PLL_ISO_EN);
+
+ /* force DA_MP_PLL_PWR_ON */
+ mtk_phy_set_bits(mmio + MP_GLB_DIG_8C, FRC_FRC_PWR_ON);
+ mtk_phy_clear_bits(mmio + 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_bulk_prepare_enable(UFSPHY_CLKS_CNT, phy->clks);
+ if (ret)
+ return ret;
+
+ ufs_mtk_phy_set_active(phy);
+
+ return 0;
+}
+
+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_bulk_disable_unprepare(UFSPHY_CLKS_CNT, phy->clks);
+
+ 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 ufs_mtk_phy *phy;
+ int ret;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ phy->mmio = devm_platform_ioremap_resource(pdev, 0);
+ 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..b222fbbd7
--- /dev/null
+++ b/drivers/phy/mediatek/phy-mtk-xsphy.c
@@ -0,0 +1,540 @@
+// 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/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+#include "phy-mtk-io.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 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_VRT_SEL GENMASK(14, 12)
+#define P2A1_RG_TERM_SEL GENMASK(10, 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 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 SSPXTP_PHYA_LN_04 ((SSPXTP_SIFSLV_PHYA_LN) + 0x04)
+#define RG_XTP_LN0_TX_IMPSEL GENMASK(4, 0)
+
+#define SSPXTP_PHYA_LN_14 ((SSPXTP_SIFSLV_PHYA_LN) + 0x014)
+#define RG_XTP_LN0_RX_IMPSEL GENMASK(4, 0)
+
+#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 */
+ mtk_phy_set_bits(pbase + XSP_USBPHYACR5, P2A5_RG_HSTX_SRCAL_EN);
+ udelay(1); /* wait clock stable */
+
+ /* enable free run clock */
+ mtk_phy_set_bits(pbase + XSP_U2FREQ_FMMONR1, P2F_RG_FRCK_EN);
+
+ /* set cycle count as 1024 */
+ mtk_phy_update_field(pbase + XSP_U2FREQ_FMCR0, P2F_RG_CYCLECNT,
+ XSP_FM_DET_CYCLE_CNT);
+
+ /* enable frequency meter */
+ mtk_phy_set_bits(pbase + XSP_U2FREQ_FMCR0, P2F_RG_FREQDET_EN);
+
+ /* 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 */
+ mtk_phy_clear_bits(pbase + XSP_U2FREQ_FMCR0, P2F_RG_FREQDET_EN);
+
+ /* disable free run clock */
+ mtk_phy_clear_bits(pbase + XSP_U2FREQ_FMMONR1, P2F_RG_FRCK_EN);
+
+ 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 */
+ mtk_phy_update_field(pbase + XSP_USBPHYACR5, P2A5_RG_HSTX_SRCTRL, calib_val);
+
+ /* disable USB ring oscillator */
+ mtk_phy_clear_bits(pbase + XSP_USBPHYACR5, P2A5_RG_HSTX_SRCAL_EN);
+}
+
+static void u2_phy_instance_init(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+
+ /* DP/DM BC1.1 path Disable */
+ mtk_phy_clear_bits(pbase + XSP_USBPHYACR6, P2A6_RG_BC11_SW_EN);
+
+ mtk_phy_set_bits(pbase + XSP_USBPHYACR0, P2A0_RG_INTR_EN);
+}
+
+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;
+
+ mtk_phy_set_bits(pbase + XSP_USBPHYACR6, P2A6_RG_OTG_VBUSCMP_EN);
+
+ mtk_phy_update_bits(pbase + XSP_U2PHYDTM1,
+ P2D_RG_VBUSVALID | P2D_RG_AVALID | P2D_RG_SESSEND,
+ P2D_RG_VBUSVALID | P2D_RG_AVALID);
+
+ 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;
+
+ mtk_phy_clear_bits(pbase + XSP_USBPHYACR6, P2A6_RG_OTG_VBUSCMP_EN);
+
+ mtk_phy_update_bits(pbase + XSP_U2PHYDTM1,
+ P2D_RG_VBUSVALID | P2D_RG_AVALID | P2D_RG_SESSEND,
+ P2D_RG_SESSEND);
+
+ 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;
+
+ if (inst->efuse_intr)
+ mtk_phy_update_field(pbase + XSP_USBPHYACR1, P2A1_RG_INTR_CAL,
+ inst->efuse_intr);
+
+ if (inst->eye_src)
+ mtk_phy_update_field(pbase + XSP_USBPHYACR5, P2A5_RG_HSTX_SRCTRL,
+ inst->eye_src);
+
+ if (inst->eye_vrt)
+ mtk_phy_update_field(pbase + XSP_USBPHYACR1, P2A1_RG_VRT_SEL,
+ inst->eye_vrt);
+
+ if (inst->eye_term)
+ mtk_phy_update_field(pbase + XSP_USBPHYACR1, P2A1_RG_TERM_SEL,
+ inst->eye_term);
+}
+
+static void u3_phy_props_set(struct mtk_xsphy *xsphy,
+ struct xsphy_instance *inst)
+{
+ void __iomem *pbase = inst->port_base;
+
+ if (inst->efuse_intr)
+ mtk_phy_update_field(xsphy->glb_base + SSPXTP_PHYA_GLB_00,
+ RG_XTP_GLB_BIAS_INTR_CTRL, inst->efuse_intr);
+
+ if (inst->efuse_tx_imp)
+ mtk_phy_update_field(pbase + SSPXTP_PHYA_LN_04,
+ RG_XTP_LN0_TX_IMPSEL, inst->efuse_tx_imp);
+
+ if (inst->efuse_rx_imp)
+ mtk_phy_update_field(pbase + SSPXTP_PHYA_LN_14,
+ RG_XTP_LN0_RX_IMPSEL, inst->efuse_rx_imp);
+}
+
+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");