]> git.ipfire.org Git - thirdparty/u-boot.git/commitdiff
phy: mediatek: new XS-PHY driver
authorDavid Lechner <dlechner@baylibre.com>
Wed, 1 Apr 2026 21:53:45 +0000 (16:53 -0500)
committerDavid Lechner <dlechner@baylibre.com>
Fri, 17 Apr 2026 22:05:54 +0000 (17:05 -0500)
Add a new driver for the Mediatek XS-PHY. This is found on some newer
Mediatek SoCs.

Upstream devicetree bindings already exist. MAINTAINERS is already
covered by drivers/phy/phy-mtk-*.

Link: https://patch.msgid.link/20260401-mtk-mt8189-usb-v1-1-a4bf951aa8ad@baylibre.com
Signed-off-by: David Lechner <dlechner@baylibre.com>
drivers/phy/Kconfig
drivers/phy/Makefile
drivers/phy/phy-mtk-xsphy.c [new file with mode: 0644]

index 09810b62b512435e07183cfe0f370399b65d5325..fc4daa0066119958907548484685ace41ca1d240 100644 (file)
@@ -292,6 +292,17 @@ config PHY_MTK_UFS
          initialization, power on and power off flow of
          specified M-PHYs.
 
+config PHY_MTK_XSPHY
+       bool "MediaTek XS-PHY Driver"
+       depends on PHY
+       depends on ARCH_MEDIATEK
+       select REGMAP
+       select SYSCON
+       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_NPCM_USB
        bool "Nuvoton NPCM USB PHY support"
        depends on PHY
index 8310234966922b3ddd1a9b54e839ac96833b7c5b..684e9a99af8e2db884d152b5bc64be3eb38ea694 100644 (file)
@@ -38,6 +38,7 @@ obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o
 obj-$(CONFIG_PHY_EXYNOS_USBDRD) += phy-exynos-usbdrd.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
 obj-$(CONFIG_PHY_NPCM_USB) += phy-npcm-usb.o
 obj-$(CONFIG_$(PHASE_)PHY_IMX8MQ_USB) += phy-imx8mq-usb.o
 obj-$(CONFIG_PHY_IMX8M_PCIE) += phy-imx8m-pcie.o
diff --git a/drivers/phy/phy-mtk-xsphy.c b/drivers/phy/phy-mtk-xsphy.c
new file mode 100644 (file)
index 0000000..d3418ff
--- /dev/null
@@ -0,0 +1,600 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MediaTek USB3.1 gen2 xsphy Driver
+ *
+ * Copyright (c) 2026 MediaTek Inc.
+ * Copyright (c) 2026 BayLibre, SAS
+ *
+ * Based on Linux mtk-xsphy driver:
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ *
+ * And U-Boot mtk-tphy driver:
+ * Copyright (c) 2015 - 2019 MediaTek Inc.
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ *        Ryder Lee <ryder.lee@mediatek.com>
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <generic-phy.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <regmap.h>
+#include <syscon.h>
+
+#include <asm/io.h>
+#include <dm/device_compat.h>
+#include <dm/devres.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+
+#include <dt-bindings/phy/phy.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_MHZ                26
+#define XSP_SLEW_RATE_COEF     17
+#define XSP_SR_COEF_DIVISOR    1000
+#define XSP_FM_DET_CYCLE_CNT   1024
+
+/* PHY switch between pcie/usb3/sgmii */
+#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
+
+struct mtk_xsphy_instance {
+       void __iomem *port_base;
+       struct device_node *np;
+       struct clk ref_clk;     /* reference clock of analog phy */
+       u32 index;
+       u32 type;
+       struct regmap *type_sw;
+       u32 type_sw_reg;
+       u32 type_sw_index;
+       /* only for HQA test */
+       u32 efuse_intr;
+       u32 efuse_tx_imp;
+       u32 efuse_rx_imp;
+       /* u2 eye diagram */
+       u32 eye_src;
+       u32 eye_vrt;
+       u32 eye_term;
+};
+
+struct mtk_xsphy {
+       struct udevice *dev;
+       void __iomem *sif_base;
+       struct mtk_xsphy_instance **phys;
+       u32 nphys;
+       u32 src_ref_clk_mhz; /* reference clock for slew rate calibrate */
+       u32 src_coef; /* coefficient for slew rate calibrate */
+};
+
+static void mtk_xsphy_u2_slew_rate_calibrate(struct mtk_xsphy *xsphy,
+                                            struct mtk_xsphy_instance *instance)
+{
+       void __iomem *pbase = instance->port_base;
+       u32 calib_val;
+       u32 fm_out;
+       u32 tmp;
+
+       /* use force value */
+       if (instance->eye_src)
+               return;
+
+       /* enable USB ring oscillator */
+       setbits_le32(pbase + XSP_USBPHYACR5, P2A5_RG_HSTX_SRCAL_EN);
+       /* wait for clock to become stable */
+       udelay(1);
+
+       /* enable free run clock */
+       setbits_le32(pbase + XSP_U2FREQ_FMMONR1, P2F_RG_FRCK_EN);
+
+       /* set cycle count as 1024 */
+       clrsetbits_le32(pbase + XSP_U2FREQ_FMCR0, P2F_RG_CYCLECNT,
+                       FIELD_PREP(P2F_RG_CYCLECNT, XSP_FM_DET_CYCLE_CNT));
+
+       /* enable frequency meter */
+       setbits_le32(pbase + XSP_U2FREQ_FMCR0, P2F_RG_FREQDET_EN);
+
+       /* ignore return value */
+       readl_poll_sleep_timeout(pbase + XSP_U2FREQ_FMMONR1, tmp,
+                                (tmp & P2F_USB_FM_VALID), 10, 200);
+
+       fm_out = readl(pbase + XSP_U2FREQ_MMONR0);
+
+       /* disable frequency meter */
+       clrbits_le32(pbase + XSP_U2FREQ_FMCR0, P2F_RG_FREQDET_EN);
+
+       /* disable free run clock */
+       clrbits_le32(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_mhz * 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.%u, fm_out:%u, calib:%u (clk:%u, coef:%u)\n",
+               instance->index, fm_out, calib_val, xsphy->src_ref_clk_mhz,
+               xsphy->src_coef);
+
+       /* set HS slew rate */
+       clrsetbits_le32(pbase + XSP_USBPHYACR5, P2A5_RG_HSTX_SRCTRL,
+                       FIELD_PREP(P2A5_RG_HSTX_SRCTRL, calib_val));
+
+       /* disable USB ring oscillator */
+       clrbits_le32(pbase + XSP_USBPHYACR5, P2A5_RG_HSTX_SRCAL_EN);
+}
+
+static void mtk_xsphy_u2_instance_init(struct mtk_xsphy *xsphy,
+                                      struct mtk_xsphy_instance *instance)
+{
+       void __iomem *pbase = instance->port_base;
+
+       /* DP/DM BC1.1 path Disable */
+       clrbits_le32(pbase + XSP_USBPHYACR6, P2A6_RG_BC11_SW_EN);
+
+       setbits_le32(pbase + XSP_USBPHYACR0, P2A0_RG_INTR_EN);
+}
+
+static void mtk_xsphy_u2_instance_power_on(struct mtk_xsphy *xsphy,
+                                          struct mtk_xsphy_instance *instance)
+{
+       void __iomem *pbase = instance->port_base;
+
+       setbits_le32(pbase + XSP_USBPHYACR6, P2A6_RG_OTG_VBUSCMP_EN);
+
+       clrsetbits_le32(pbase + XSP_U2PHYDTM1,
+                       P2D_RG_VBUSVALID | P2D_RG_AVALID | P2D_RG_SESSEND,
+                       P2D_RG_VBUSVALID | P2D_RG_AVALID);
+
+       dev_dbg(xsphy->dev, "%s(%u)\n", __func__, instance->index);
+}
+
+static void mtk_xsphy_u2_instance_power_off(struct mtk_xsphy *xsphy,
+                                           struct mtk_xsphy_instance *instance)
+{
+       void __iomem *pbase = instance->port_base;
+
+       clrbits_le32(pbase + XSP_USBPHYACR6, P2A6_RG_OTG_VBUSCMP_EN);
+
+       clrsetbits_le32(pbase + XSP_U2PHYDTM1,
+                       P2D_RG_VBUSVALID | P2D_RG_AVALID | P2D_RG_SESSEND,
+                       P2D_RG_SESSEND);
+
+       dev_dbg(xsphy->dev, "%s(%u)\n", __func__, instance->index);
+}
+
+static void mtk_xsphy_u2_instance_set_mode(struct mtk_xsphy *xsphy,
+                                          struct mtk_xsphy_instance *instance,
+                                          enum phy_mode mode)
+{
+       u32 tmp;
+
+       tmp = readl(instance->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, instance->port_base + XSP_U2PHYDTM1);
+}
+
+static void mtk_xsphy_parse_property(struct mtk_xsphy *xsphy,
+                                    struct mtk_xsphy_instance *instance)
+{
+       ofnode node = np_to_ofnode(instance->np);
+
+       switch (instance->type) {
+       case PHY_TYPE_USB2:
+               ofnode_read_u32(node, "mediatek,efuse-intr", &instance->efuse_intr);
+               ofnode_read_u32(node, "mediatek,eye-src", &instance->eye_src);
+               ofnode_read_u32(node, "mediatek,eye-vrt", &instance->eye_vrt);
+               ofnode_read_u32(node, "mediatek,eye-term", &instance->eye_term);
+
+               dev_dbg(xsphy->dev, "intr:%u, src:%u, vrt:%u, term:%u\n",
+                       instance->efuse_intr, instance->eye_src,
+                       instance->eye_vrt, instance->eye_term);
+               return;
+       case PHY_TYPE_USB3:
+               ofnode_read_u32(node, "mediatek,efuse-intr", &instance->efuse_intr);
+               ofnode_read_u32(node, "mediatek,efuse-tx-imp", &instance->efuse_tx_imp);
+               ofnode_read_u32(node, "mediatek,efuse-rx-imp", &instance->efuse_rx_imp);
+
+               dev_dbg(xsphy->dev, "intr:%u, tx-imp:%u, rx-imp:%u\n",
+                       instance->efuse_intr, instance->efuse_tx_imp,
+                       instance->efuse_rx_imp);
+               return;
+       case PHY_TYPE_PCIE:
+       case PHY_TYPE_SGMII:
+               /* nothing to do */
+               return;
+       default:
+               dev_err(xsphy->dev, "incompatible PHY type\n");
+               return;
+       }
+}
+
+static void mtk_xsphy_u2_props_set(struct mtk_xsphy *xsphy,
+                                  struct mtk_xsphy_instance *instance)
+{
+       void __iomem *pbase = instance->port_base;
+
+       if (instance->efuse_intr)
+               clrsetbits_le32(pbase + XSP_USBPHYACR1, P2A1_RG_INTR_CAL,
+                               FIELD_PREP(P2A1_RG_INTR_CAL, instance->efuse_intr));
+
+       if (instance->eye_src)
+               clrsetbits_le32(pbase + XSP_USBPHYACR5, P2A5_RG_HSTX_SRCTRL,
+                               FIELD_PREP(P2A5_RG_HSTX_SRCTRL, instance->eye_src));
+
+       if (instance->eye_vrt)
+               clrsetbits_le32(pbase + XSP_USBPHYACR1, P2A1_RG_VRT_SEL,
+                               FIELD_PREP(P2A1_RG_VRT_SEL, instance->eye_vrt));
+
+       if (instance->eye_term)
+               clrsetbits_le32(pbase + XSP_USBPHYACR1, P2A1_RG_TERM_SEL,
+                               FIELD_PREP(P2A1_RG_TERM_SEL, instance->eye_term));
+}
+
+static void mtk_xsphy_u3_props_set(struct mtk_xsphy *xsphy,
+                                  struct mtk_xsphy_instance *instance)
+{
+       void __iomem *pbase = instance->port_base;
+
+       if (instance->efuse_intr)
+               clrsetbits_le32(xsphy->sif_base + SSPXTP_PHYA_GLB_00,
+                               RG_XTP_GLB_BIAS_INTR_CTRL,
+                               FIELD_PREP(RG_XTP_GLB_BIAS_INTR_CTRL, instance->efuse_intr));
+
+       if (instance->efuse_tx_imp)
+               clrsetbits_le32(pbase + SSPXTP_PHYA_LN_04, RG_XTP_LN0_TX_IMPSEL,
+                               FIELD_PREP(RG_XTP_LN0_TX_IMPSEL, instance->efuse_tx_imp));
+
+       if (instance->efuse_rx_imp)
+               clrsetbits_le32(pbase + SSPXTP_PHYA_LN_14, RG_XTP_LN0_RX_IMPSEL,
+                               FIELD_PREP(RG_XTP_LN0_RX_IMPSEL, instance->efuse_rx_imp));
+}
+
+/* type switch for usb3/pcie/sgmii */
+static int mtk_xsphy_type_syscon_get(struct udevice *dev,
+                                    struct mtk_xsphy_instance *instance,
+                                    ofnode dn)
+{
+       struct ofnode_phandle_args args;
+       int ret;
+
+       if (!ofnode_read_bool(dn, "mediatek,syscon-type"))
+               return 0;
+
+       ret = ofnode_parse_phandle_with_args(dn, "mediatek,syscon-type",
+                                            NULL, 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.node);
+       if (IS_ERR(instance->type_sw))
+               return PTR_ERR(instance->type_sw);
+
+       dev_dbg(dev, "phy-%s.%d: type_sw - reg %#x, index %d\n",
+               dev->name, instance->index, instance->type_sw_reg,
+               instance->type_sw_index);
+
+       return 0;
+}
+
+static int mtk_xsphy_type_set(struct mtk_xsphy_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_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 mtk_xsphy_init(struct phy *phy)
+{
+       struct mtk_xsphy *xsphy = dev_get_priv(phy->dev);
+       struct mtk_xsphy_instance *instance = xsphy->phys[phy->id];
+       int ret;
+
+       ret = clk_enable(&instance->ref_clk);
+       if (ret) {
+               dev_err(xsphy->dev, "failed to enable ref_clk\n");
+               return ret;
+       }
+
+       switch (instance->type) {
+       case PHY_TYPE_USB2:
+               mtk_xsphy_u2_instance_init(xsphy, instance);
+               mtk_xsphy_u2_props_set(xsphy, instance);
+               break;
+       case PHY_TYPE_USB3:
+               mtk_xsphy_u3_props_set(xsphy, instance);
+               break;
+       case PHY_TYPE_PCIE:
+       case PHY_TYPE_SGMII:
+               /* nothing to do, only used to set type */
+               break;
+       default:
+               dev_err(xsphy->dev, "incompatible PHY type\n");
+               clk_disable(&instance->ref_clk);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int mtk_xsphy_power_on(struct phy *phy)
+{
+       struct mtk_xsphy *xsphy = dev_get_priv(phy->dev);
+       struct mtk_xsphy_instance *instance = xsphy->phys[phy->id];
+
+       if (instance->type == PHY_TYPE_USB2) {
+               mtk_xsphy_u2_instance_power_on(xsphy, instance);
+               mtk_xsphy_u2_slew_rate_calibrate(xsphy, instance);
+       }
+
+       return 0;
+}
+
+static int mtk_xsphy_power_off(struct phy *phy)
+{
+       struct mtk_xsphy *xsphy = dev_get_priv(phy->dev);
+       struct mtk_xsphy_instance *instance = xsphy->phys[phy->id];
+
+       if (instance->type == PHY_TYPE_USB2)
+               mtk_xsphy_u2_instance_power_off(xsphy, instance);
+
+       return 0;
+}
+
+static int mtk_xsphy_exit(struct phy *phy)
+{
+       struct mtk_xsphy *xsphy = dev_get_priv(phy->dev);
+       struct mtk_xsphy_instance *instance = xsphy->phys[phy->id];
+
+       clk_disable(&instance->ref_clk);
+
+       return 0;
+}
+
+static int mtk_xsphy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+       struct mtk_xsphy *xsphy = dev_get_priv(phy->dev);
+       struct mtk_xsphy_instance *instance = xsphy->phys[phy->id];
+
+       if (instance->type == PHY_TYPE_USB2)
+               mtk_xsphy_u2_instance_set_mode(xsphy, instance, mode);
+
+       return 0;
+}
+
+static int mtk_xsphy_xlate(struct phy *phy, struct ofnode_phandle_args *args)
+{
+       struct mtk_xsphy *xsphy = dev_get_priv(phy->dev);
+       struct mtk_xsphy_instance *instance = NULL;
+       const struct device_node *phy_np = ofnode_to_np(args->node);
+       u32 index;
+
+       if (!phy_np) {
+               dev_err(phy->dev, "null pointer phy node\n");
+               return -EINVAL;
+       }
+
+       if (args->args_count != 2) {
+               dev_err(phy->dev, "invalid number of cells in 'phy' property\n");
+               return -EINVAL;
+       }
+
+       for (index = 0; index < xsphy->nphys; index++)
+               if (phy_np == xsphy->phys[index]->np) {
+                       instance = xsphy->phys[index];
+                       break;
+               }
+
+       if (!instance) {
+               dev_err(phy->dev, "failed to find appropriate phy\n");
+               return -EINVAL;
+       }
+
+       phy->id = index;
+       instance->type = args->args[1];
+       if (!(instance->type == PHY_TYPE_USB2 ||
+             instance->type == PHY_TYPE_USB3 ||
+             instance->type == PHY_TYPE_PCIE ||
+             instance->type == PHY_TYPE_SGMII)) {
+               dev_err(phy->dev, "unsupported PHY type\n");
+               return -EINVAL;
+       }
+
+       mtk_xsphy_parse_property(xsphy, instance);
+       mtk_xsphy_type_set(instance);
+
+       return 0;
+}
+
+static const struct phy_ops mtk_xsphy_ops = {
+       .init           = mtk_xsphy_init,
+       .exit           = mtk_xsphy_exit,
+       .power_on       = mtk_xsphy_power_on,
+       .power_off      = mtk_xsphy_power_off,
+       .set_mode       = mtk_xsphy_set_mode,
+       .of_xlate       = mtk_xsphy_xlate,
+};
+
+static int mtk_xsphy_probe(struct udevice *dev)
+{
+       struct mtk_xsphy *xsphy = dev_get_priv(dev);
+       fdt_addr_t sif_addr;
+       ofnode subnode;
+       int index = 0;
+
+       xsphy->nphys = dev_get_child_count(dev);
+
+       xsphy->phys = devm_kcalloc(dev, xsphy->nphys, sizeof(*xsphy->phys),
+                                  GFP_KERNEL);
+       if (!xsphy->phys)
+               return -ENOMEM;
+
+       xsphy->dev = dev;
+
+       sif_addr = ofnode_get_addr(dev_ofnode(dev));
+       /* optional, may not exist if no u3 phys */
+       if (sif_addr != FDT_ADDR_T_NONE)
+               xsphy->sif_base = map_sysmem(sif_addr, 0);
+
+       xsphy->src_ref_clk_mhz = XSP_REF_CLK_MHZ;
+       xsphy->src_coef = XSP_SLEW_RATE_COEF;
+       /* update parameters of slew rate calibrate if exist */
+       ofnode_read_u32(dev_ofnode(dev), "mediatek,src-ref-clk-mhz",
+                       &xsphy->src_ref_clk_mhz);
+       ofnode_read_u32(dev_ofnode(dev), "mediatek,src-coef", &xsphy->src_coef);
+
+       dev_for_each_subnode(subnode, dev) {
+               struct mtk_xsphy_instance *inst;
+               fdt_addr_t addr;
+               int ret;
+
+               inst = devm_kzalloc(dev, sizeof(*inst), GFP_KERNEL);
+               if (!inst)
+                       return -ENOMEM;
+
+               xsphy->phys[index] = inst;
+
+               addr = ofnode_get_addr(subnode);
+               if (addr == FDT_ADDR_T_NONE)
+                       return -EADDRNOTAVAIL;
+
+               inst->port_base = map_sysmem(addr, 0);
+               inst->index = index;
+               inst->np = ofnode_to_np(subnode);
+
+               ret = clk_get_by_name_nodev(subnode, "ref", &inst->ref_clk);
+               if (ret) {
+                       dev_err(dev, "failed to get ref_clk(id-%d)\n", index);
+                       return ret;
+               }
+
+               ret = mtk_xsphy_type_syscon_get(dev, inst, subnode);
+               if (ret)
+                       return ret;
+
+               index++;
+       }
+
+       return 0;
+}
+
+static const struct udevice_id mtk_xsphy_id_table[] = {
+       { .compatible = "mediatek,xsphy" },
+       { }
+};
+
+U_BOOT_DRIVER(mtk_xsphy) = {
+       .name           = "mtk-xsphy",
+       .id             = UCLASS_PHY,
+       .of_match       = mtk_xsphy_id_table,
+       .ops            = &mtk_xsphy_ops,
+       .probe          = mtk_xsphy_probe,
+       .priv_auto      = sizeof(struct mtk_xsphy),
+};