lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <675b5abd8b40a71c177e9e4e4c92d2d6b1413b9b.1694527316.git.daniel@makrotopia.org>
Date: Tue, 12 Sep 2023 15:54:40 +0100
From: Daniel Golle <daniel@...rotopia.org>
To: Felix Fietkau <nbd@....name>, John Crispin <john@...ozen.org>,
	Sean Wang <sean.wang@...iatek.com>,
	Mark Lee <Mark-MC.Lee@...iatek.com>,
	Lorenzo Bianconi <lorenzo@...nel.org>,
	"David S. Miller" <davem@...emloft.net>,
	Eric Dumazet <edumazet@...gle.com>,
	Jakub Kicinski <kuba@...nel.org>, Paolo Abeni <pabeni@...hat.com>,
	Matthias Brugger <matthias.bgg@...il.com>,
	AngeloGioacchino Del Regno <angelogioacchino.delregno@...labora.com>,
	Russell King <linux@...linux.org.uk>, linux-kernel@...r.kernel.org,
	netdev@...r.kernel.org, linux-arm-kernel@...ts.infradead.org,
	linux-mediatek@...ts.infradead.org
Cc: llvm@...ts.linux.dev, ndesaulniers@...gle.com, nathan@...nel.org,
	trix@...hat.com
Subject: [PATCH net-next] net: ethernet: mtk_eth_soc: add paths and SerDes
 modes for MT7988

MT7988 comes with a built-in 2.5G PHY as well as SerDes lanes to
connect external PHYs or transceivers in USXGMII, 10GBase-R, 5GBase-R,
2500Base-X, 1000Base-X and Cisco SGMII interface modes.

Implement support for configuring for the new paths to SerDes interfaces
and the internal 2.5G PHY.

Add USXGMII PCS driver for 10GBase-R, 5GBase-R and USXGMII mode, and
setup the new PHYA on MT7988 to access the also still existing old
LynxI PCS for 1000Base-X, 2500Base-X and Cisco SGMII interface modes.

Signed-off-by: Daniel Golle <daniel@...rotopia.org>
---
Changes since RFC v2:
 * read PCS_RX_STATUS0 register to avoid bogus link-up
   (recommended by mtk devs)
 * use parenthese to fix evaluation order
   (Simon Horman reported clang warning)
 * fix allocation size of usxgmii_pcs
   (Simon Horman reported Smatch warning)
 * always set USXGMII_AN_ENABLE bit in USXGMII mode
   (it's what the vendor driver does and USXGMII doesn't seem to work
    at all otherwise, we may need to manually set rate matching registers
    if we don't use AN, but this isn't implemented at this point)

Changes since initial RFC:
 * set missing neg_mode = true for usxgmii pcs
 * use phylink_decode_usxgmii_word instead of open coding

 drivers/net/ethernet/mediatek/Kconfig        |  16 +
 drivers/net/ethernet/mediatek/Makefile       |   1 +
 drivers/net/ethernet/mediatek/mtk_eth_path.c | 123 +++-
 drivers/net/ethernet/mediatek/mtk_eth_soc.c  | 182 ++++-
 drivers/net/ethernet/mediatek/mtk_eth_soc.h  | 232 +++++-
 drivers/net/ethernet/mediatek/mtk_usxgmii.c  | 702 +++++++++++++++++++
 6 files changed, 1225 insertions(+), 31 deletions(-)
 create mode 100644 drivers/net/ethernet/mediatek/mtk_usxgmii.c

diff --git a/drivers/net/ethernet/mediatek/Kconfig b/drivers/net/ethernet/mediatek/Kconfig
index da0db417ab690..b942b4622d146 100644
--- a/drivers/net/ethernet/mediatek/Kconfig
+++ b/drivers/net/ethernet/mediatek/Kconfig
@@ -25,6 +25,22 @@ config NET_MEDIATEK_SOC
 	  This driver supports the gigabit ethernet MACs in the
 	  MediaTek SoC family.
 
+config NET_MEDIATEK_SOC_USXGMII
+	bool "Support USXGMII SerDes on MT7988"
+	depends on (ARCH_MEDIATEK && ARM64) || COMPILE_TEST
+	def_bool NET_MEDIATEK_SOC != n
+	help
+	  Include support for 10GE SerDes which can be found on MT7988.
+	  If this kernel should run on SoCs with 10 GBit/s Ethernet you
+	  will need to select this option to use GMAC2 and GMAC3 with
+	  external PHYs, SFP(+) cages in 10GBase-R, 5GBase-R or USXGMII
+	  interface modes.
+
+	  Note that as the 2500Base-X/1000Base-X/Cisco SGMII SerDes PCS
+	  unit (MediaTek LynxI) in MT7988 is connected via the new 10GE
+	  SerDes, you will also need to select this option in case you
+	  want to use any of those SerDes modes.
+
 config NET_MEDIATEK_STAR_EMAC
 	tristate "MediaTek STAR Ethernet MAC support"
 	select PHYLIB
diff --git a/drivers/net/ethernet/mediatek/Makefile b/drivers/net/ethernet/mediatek/Makefile
index 03e008fbc859b..115ef0faa0e4b 100644
--- a/drivers/net/ethernet/mediatek/Makefile
+++ b/drivers/net/ethernet/mediatek/Makefile
@@ -5,6 +5,7 @@
 
 obj-$(CONFIG_NET_MEDIATEK_SOC) += mtk_eth.o
 mtk_eth-y := mtk_eth_soc.o mtk_eth_path.o mtk_ppe.o mtk_ppe_debugfs.o mtk_ppe_offload.o
+mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_USXGMII) += mtk_usxgmii.o
 mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed.o mtk_wed_mcu.o mtk_wed_wo.o
 ifdef CONFIG_DEBUG_FS
 mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_debugfs.o
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_path.c b/drivers/net/ethernet/mediatek/mtk_eth_path.c
index 7c27a19c4d8f4..0463b0ef4f334 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_path.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_path.c
@@ -31,10 +31,20 @@ static const char *mtk_eth_path_name(u64 path)
 		return "gmac2_rgmii";
 	case MTK_ETH_PATH_GMAC2_SGMII:
 		return "gmac2_sgmii";
+	case MTK_ETH_PATH_GMAC2_2P5GPHY:
+		return "gmac2_2p5gphy";
 	case MTK_ETH_PATH_GMAC2_GEPHY:
 		return "gmac2_gephy";
+	case MTK_ETH_PATH_GMAC3_SGMII:
+		return "gmac3_sgmii";
 	case MTK_ETH_PATH_GDM1_ESW:
 		return "gdm1_esw";
+	case MTK_ETH_PATH_GMAC1_USXGMII:
+		return "gmac1_usxgmii";
+	case MTK_ETH_PATH_GMAC2_USXGMII:
+		return "gmac2_usxgmii";
+	case MTK_ETH_PATH_GMAC3_USXGMII:
+		return "gmac3_usxgmii";
 	default:
 		return "unknown path";
 	}
@@ -127,6 +137,27 @@ static int set_mux_u3_gmac2_to_qphy(struct mtk_eth *eth, u64 path)
 	return 0;
 }
 
+static int set_mux_gmac2_to_2p5gphy(struct mtk_eth *eth, u64 path)
+{
+	int ret;
+
+	if (path == MTK_ETH_PATH_GMAC2_2P5GPHY) {
+		ret = regmap_clear_bits(eth->ethsys, ETHSYS_SYSCFG0, SYSCFG0_SGMII_GMAC2_V2);
+		if (ret)
+			return ret;
+
+		/* Setup mux to 2p5g PHY */
+		ret = regmap_clear_bits(eth->infra, TOP_MISC_NETSYS_PCS_MUX, MUX_G2_USXGMII_SEL);
+		if (ret)
+			return ret;
+
+		dev_dbg(eth->dev, "path %s in %s updated\n",
+			mtk_eth_path_name(path), __func__);
+	}
+
+	return 0;
+}
+
 static int set_mux_gmac1_gmac2_to_sgmii_rgmii(struct mtk_eth *eth, u64 path)
 {
 	unsigned int val = 0;
@@ -165,7 +196,48 @@ static int set_mux_gmac1_gmac2_to_sgmii_rgmii(struct mtk_eth *eth, u64 path)
 	return 0;
 }
 
-static int set_mux_gmac12_to_gephy_sgmii(struct mtk_eth *eth, u64 path)
+static int set_mux_gmac123_to_usxgmii(struct mtk_eth *eth, u64 path)
+{
+	unsigned int val = 0;
+	bool updated = true;
+	int mac_id = 0;
+
+	/* Disable SYSCFG1 SGMII */
+	regmap_read(eth->ethsys, ETHSYS_SYSCFG0, &val);
+
+	switch (path) {
+	case MTK_ETH_PATH_GMAC1_USXGMII:
+		val &= ~(u32)SYSCFG0_SGMII_GMAC1_V2;
+		mac_id = MTK_GMAC1_ID;
+		break;
+	case MTK_ETH_PATH_GMAC2_USXGMII:
+		val &= ~(u32)SYSCFG0_SGMII_GMAC2_V2;
+		mac_id = MTK_GMAC2_ID;
+		break;
+	case MTK_ETH_PATH_GMAC3_USXGMII:
+		val &= ~(u32)SYSCFG0_SGMII_GMAC3_V2;
+		mac_id = MTK_GMAC3_ID;
+		break;
+	default:
+		updated = false;
+	};
+
+	if (updated) {
+		regmap_update_bits(eth->ethsys, ETHSYS_SYSCFG0,
+				   SYSCFG0_SGMII_MASK, val);
+
+		if (mac_id == MTK_GMAC2_ID)
+			regmap_set_bits(eth->infra, TOP_MISC_NETSYS_PCS_MUX,
+					MUX_G2_USXGMII_SEL);
+	}
+
+	dev_dbg(eth->dev, "path %s in %s updated = %d\n",
+		mtk_eth_path_name(path), __func__, updated);
+
+	return 0;
+}
+
+static int set_mux_gmac123_to_gephy_sgmii(struct mtk_eth *eth, u64 path)
 {
 	unsigned int val = 0;
 	bool updated = true;
@@ -182,6 +254,9 @@ static int set_mux_gmac12_to_gephy_sgmii(struct mtk_eth *eth, u64 path)
 	case MTK_ETH_PATH_GMAC2_SGMII:
 		val |= SYSCFG0_SGMII_GMAC2_V2;
 		break;
+	case MTK_ETH_PATH_GMAC3_SGMII:
+		val |= SYSCFG0_SGMII_GMAC3_V2;
+		break;
 	default:
 		updated = false;
 	}
@@ -209,6 +284,10 @@ static const struct mtk_eth_muxc mtk_eth_muxc[] = {
 		.name = "mux_u3_gmac2_to_qphy",
 		.cap_bit = MTK_ETH_MUX_U3_GMAC2_TO_QPHY,
 		.set_path = set_mux_u3_gmac2_to_qphy,
+	}, {
+		.name = "mux_gmac2_to_2p5gphy",
+		.cap_bit = MTK_ETH_MUX_GMAC2_TO_2P5GPHY,
+		.set_path = set_mux_gmac2_to_2p5gphy,
 	}, {
 		.name = "mux_gmac1_gmac2_to_sgmii_rgmii",
 		.cap_bit = MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII,
@@ -216,7 +295,15 @@ static const struct mtk_eth_muxc mtk_eth_muxc[] = {
 	}, {
 		.name = "mux_gmac12_to_gephy_sgmii",
 		.cap_bit = MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII,
-		.set_path = set_mux_gmac12_to_gephy_sgmii,
+		.set_path = set_mux_gmac123_to_gephy_sgmii,
+	}, {
+		.name = "mux_gmac123_to_gephy_sgmii",
+		.cap_bit = MTK_ETH_MUX_GMAC123_TO_GEPHY_SGMII,
+		.set_path = set_mux_gmac123_to_gephy_sgmii,
+	}, {
+		.name = "mux_gmac123_to_usxgmii",
+		.cap_bit = MTK_ETH_MUX_GMAC123_TO_USXGMII,
+		.set_path = set_mux_gmac123_to_usxgmii,
 	},
 };
 
@@ -249,12 +336,39 @@ static int mtk_eth_mux_setup(struct mtk_eth *eth, u64 path)
 	return err;
 }
 
+int mtk_gmac_usxgmii_path_setup(struct mtk_eth *eth, int mac_id)
+{
+	u64 path;
+
+	path = (mac_id == MTK_GMAC1_ID) ?  MTK_ETH_PATH_GMAC1_USXGMII :
+	       (mac_id == MTK_GMAC2_ID) ?  MTK_ETH_PATH_GMAC2_USXGMII :
+					   MTK_ETH_PATH_GMAC3_USXGMII;
+
+	/* Setup proper MUXes along the path */
+	return mtk_eth_mux_setup(eth, path);
+}
+
 int mtk_gmac_sgmii_path_setup(struct mtk_eth *eth, int mac_id)
 {
 	u64 path;
 
-	path = (mac_id == 0) ?  MTK_ETH_PATH_GMAC1_SGMII :
-				MTK_ETH_PATH_GMAC2_SGMII;
+	path = (mac_id == MTK_GMAC1_ID) ? MTK_ETH_PATH_GMAC1_SGMII :
+	       (mac_id == MTK_GMAC2_ID) ? MTK_ETH_PATH_GMAC2_SGMII :
+					  MTK_ETH_PATH_GMAC3_SGMII;
+
+	/* Setup proper MUXes along the path */
+	return mtk_eth_mux_setup(eth, path);
+}
+
+int mtk_gmac_2p5gphy_path_setup(struct mtk_eth *eth, int mac_id)
+{
+	u64 path = 0;
+
+	if (mac_id == MTK_GMAC2_ID)
+		path = MTK_ETH_PATH_GMAC2_2P5GPHY;
+
+	if (!path)
+		return -EINVAL;
 
 	/* Setup proper MUXes along the path */
 	return mtk_eth_mux_setup(eth, path);
@@ -284,4 +398,3 @@ int mtk_gmac_rgmii_path_setup(struct mtk_eth *eth, int mac_id)
 	/* Setup proper MUXes along the path */
 	return mtk_eth_mux_setup(eth, path);
 }
-
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index 6ad42e3b488f7..02206351322bf 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -507,6 +507,30 @@ static void mtk_setup_bridge_switch(struct mtk_eth *eth)
 		MTK_GSW_CFG);
 }
 
+static bool mtk_check_gmac23_idle(struct mtk_mac *mac)
+{
+	u32 mac_fsm, gdm_fsm;
+
+	mac_fsm = mtk_r32(mac->hw, MTK_MAC_FSM(mac->id));
+
+	switch (mac->id) {
+	case MTK_GMAC2_ID:
+		gdm_fsm = mtk_r32(mac->hw, MTK_FE_GDM2_FSM);
+		break;
+	case MTK_GMAC3_ID:
+		gdm_fsm = mtk_r32(mac->hw, MTK_FE_GDM3_FSM);
+		break;
+	default:
+		return true;
+	};
+
+	if ((mac_fsm & 0xFFFF0000) == 0x01010000 &&
+	    (gdm_fsm & 0xFFFF0000) == 0x00000000)
+		return true;
+
+	return false;
+}
+
 static struct phylink_pcs *mtk_mac_select_pcs(struct phylink_config *config,
 					      phy_interface_t interface)
 {
@@ -515,12 +539,20 @@ static struct phylink_pcs *mtk_mac_select_pcs(struct phylink_config *config,
 	struct mtk_eth *eth = mac->hw;
 	unsigned int sid;
 
-	if (interface == PHY_INTERFACE_MODE_SGMII ||
-	    phy_interface_mode_is_8023z(interface)) {
-		sid = (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_SGMII)) ?
-		       0 : mac->id;
-
-		return eth->sgmii_pcs[sid];
+	if ((interface == PHY_INTERFACE_MODE_SGMII ||
+	     phy_interface_mode_is_8023z(interface)) &&
+	    MTK_HAS_CAPS(eth->soc->caps, MTK_SGMII)) {
+		sid = mtk_mac2xgmii_id(eth, mac->id);
+		if (MTK_HAS_CAPS(eth->soc->caps, MTK_USXGMII))
+			return mtk_sgmii_wrapper_select_pcs(eth, mac->id);
+		else
+			return eth->sgmii_pcs[sid];
+	} else if ((interface == PHY_INTERFACE_MODE_USXGMII ||
+		    interface == PHY_INTERFACE_MODE_10GBASER ||
+		    interface == PHY_INTERFACE_MODE_5GBASER) &&
+		   MTK_HAS_CAPS(eth->soc->caps, MTK_USXGMII) &&
+		   mac->id != MTK_GMAC1_ID) {
+		return mtk_usxgmii_select_pcs(eth, mac->id);
 	}
 
 	return NULL;
@@ -566,7 +598,22 @@ static void mtk_mac_config(struct phylink_config *config, unsigned int mode,
 					goto init_err;
 			}
 			break;
+		case PHY_INTERFACE_MODE_USXGMII:
+		case PHY_INTERFACE_MODE_10GBASER:
+		case PHY_INTERFACE_MODE_5GBASER:
+			if (MTK_HAS_CAPS(eth->soc->caps, MTK_USXGMII)) {
+				err = mtk_gmac_usxgmii_path_setup(eth, mac->id);
+				if (err)
+					goto init_err;
+			}
+			break;
 		case PHY_INTERFACE_MODE_INTERNAL:
+			if (mac->id == MTK_GMAC2_ID &&
+			    MTK_HAS_CAPS(eth->soc->caps, MTK_2P5GPHY)) {
+				err = mtk_gmac_2p5gphy_path_setup(eth, mac->id);
+				if (err)
+					goto init_err;
+			}
 			break;
 		default:
 			goto err_phy;
@@ -613,8 +660,6 @@ static void mtk_mac_config(struct phylink_config *config, unsigned int mode,
 		val &= ~SYSCFG0_GE_MODE(SYSCFG0_GE_MASK, mac->id);
 		val |= SYSCFG0_GE_MODE(ge_mode, mac->id);
 		regmap_write(eth->ethsys, ETHSYS_SYSCFG0, val);
-
-		mac->interface = state->interface;
 	}
 
 	/* SGMII */
@@ -631,21 +676,40 @@ static void mtk_mac_config(struct phylink_config *config, unsigned int mode,
 
 		/* Save the syscfg0 value for mac_finish */
 		mac->syscfg0 = val;
-	} else if (phylink_autoneg_inband(mode)) {
+	} else if (state->interface != PHY_INTERFACE_MODE_USXGMII &&
+		   state->interface != PHY_INTERFACE_MODE_10GBASER &&
+		   state->interface != PHY_INTERFACE_MODE_5GBASER &&
+		   phylink_autoneg_inband(mode)) {
 		dev_err(eth->dev,
-			"In-band mode not supported in non SGMII mode!\n");
+			"In-band mode not supported in non-SerDes modes!\n");
 		return;
 	}
 
 	/* Setup gmac */
-	if (mtk_is_netsys_v3_or_greater(eth) &&
-	    mac->interface == PHY_INTERFACE_MODE_INTERNAL) {
-		mtk_w32(mac->hw, MTK_GDMA_XGDM_SEL, MTK_GDMA_EG_CTRL(mac->id));
-		mtk_w32(mac->hw, MAC_MCR_FORCE_LINK_DOWN, MTK_MAC_MCR(mac->id));
+	if (mtk_is_netsys_v3_or_greater(eth)) {
+		if (mtk_interface_mode_is_xgmii(state->interface)) {
+			mtk_w32(mac->hw, MTK_GDMA_XGDM_SEL, MTK_GDMA_EG_CTRL(mac->id));
+			mtk_w32(mac->hw, MAC_MCR_FORCE_LINK_DOWN, MTK_MAC_MCR(mac->id));
 
-		mtk_setup_bridge_switch(eth);
+			if (mac->id == MTK_GMAC1_ID)
+				mtk_setup_bridge_switch(eth);
+		} else {
+			mtk_w32(eth, 0, MTK_GDMA_EG_CTRL(mac->id));
+
+			/* FIXME: In current hardware design, we have to reset FE
+			 * when swtiching XGDM to GDM. Therefore, here trigger an SER
+			 * to let GDM go back to the initial state.
+			 */
+			if ((mtk_interface_mode_is_xgmii(mac->interface) ||
+			     mac->interface == PHY_INTERFACE_MODE_NA) &&
+			    !mtk_check_gmac23_idle(mac) &&
+			    !test_bit(MTK_RESETTING, &eth->state))
+				schedule_work(&eth->pending_work);
+		}
 	}
 
+	mac->interface = state->interface;
+
 	return;
 
 err_phy:
@@ -691,10 +755,13 @@ static void mtk_mac_link_down(struct phylink_config *config, unsigned int mode,
 {
 	struct mtk_mac *mac = container_of(config, struct mtk_mac,
 					   phylink_config);
-	u32 mcr = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
 
-	mcr &= ~(MAC_MCR_TX_EN | MAC_MCR_RX_EN);
-	mtk_w32(mac->hw, mcr, MTK_MAC_MCR(mac->id));
+	if (!mtk_interface_mode_is_xgmii(interface)) {
+		mtk_m32(mac->hw, MAC_MCR_TX_EN | MAC_MCR_RX_EN, 0, MTK_MAC_MCR(mac->id));
+		mtk_m32(mac->hw, MTK_XGMAC_FORCE_LINK(mac->id), 0, MTK_XGMAC_STS(mac->id));
+	} else if (mac->id != MTK_GMAC1_ID) {
+		mtk_m32(mac->hw, XMAC_MCR_TRX_DISABLE, XMAC_MCR_TRX_DISABLE, MTK_XMAC_MCR(mac->id));
+	}
 }
 
 static void mtk_set_queue_speed(struct mtk_eth *eth, unsigned int idx,
@@ -766,13 +833,11 @@ static void mtk_set_queue_speed(struct mtk_eth *eth, unsigned int idx,
 	mtk_w32(eth, val, soc->reg_map->qdma.qtx_sch + ofs);
 }
 
-static void mtk_mac_link_up(struct phylink_config *config,
-			    struct phy_device *phy,
-			    unsigned int mode, phy_interface_t interface,
-			    int speed, int duplex, bool tx_pause, bool rx_pause)
+static void mtk_gdm_mac_link_up(struct mtk_mac *mac,
+				struct phy_device *phy,
+				unsigned int mode, phy_interface_t interface,
+				int speed, int duplex, bool tx_pause, bool rx_pause)
 {
-	struct mtk_mac *mac = container_of(config, struct mtk_mac,
-					   phylink_config);
 	u32 mcr;
 
 	mcr = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id));
@@ -806,6 +871,55 @@ static void mtk_mac_link_up(struct phylink_config *config,
 	mtk_w32(mac->hw, mcr, MTK_MAC_MCR(mac->id));
 }
 
+static void mtk_xgdm_mac_link_up(struct mtk_mac *mac,
+				 struct phy_device *phy,
+				 unsigned int mode, phy_interface_t interface,
+				 int speed, int duplex, bool tx_pause, bool rx_pause)
+{
+	u32 mcr, force_link = 0;
+
+	if (mac->id == MTK_GMAC1_ID)
+		return;
+
+	/* Eliminate the interference(before link-up) caused by PHY noise */
+	mtk_m32(mac->hw, XMAC_LOGIC_RST, 0, MTK_XMAC_LOGIC_RST(mac->id));
+	mdelay(20);
+	mtk_m32(mac->hw, XMAC_GLB_CNTCLR, XMAC_GLB_CNTCLR, MTK_XMAC_CNT_CTRL(mac->id));
+
+	if (mac->interface == PHY_INTERFACE_MODE_INTERNAL || mac->id == MTK_GMAC3_ID)
+		force_link = MTK_XGMAC_FORCE_LINK(mac->id);
+
+	mtk_m32(mac->hw, MTK_XGMAC_FORCE_LINK(mac->id), force_link, MTK_XGMAC_STS(mac->id));
+
+	mcr = mtk_r32(mac->hw, MTK_XMAC_MCR(mac->id));
+	mcr &= ~(XMAC_MCR_FORCE_TX_FC | XMAC_MCR_FORCE_RX_FC | XMAC_MCR_TRX_DISABLE);
+	/* Configure pause modes -
+	 * phylink will avoid these for half duplex
+	 */
+	if (tx_pause)
+		mcr |= XMAC_MCR_FORCE_TX_FC;
+	if (rx_pause)
+		mcr |= XMAC_MCR_FORCE_RX_FC;
+
+	mtk_w32(mac->hw, mcr, MTK_XMAC_MCR(mac->id));
+}
+
+static void mtk_mac_link_up(struct phylink_config *config,
+			    struct phy_device *phy,
+			    unsigned int mode, phy_interface_t interface,
+			    int speed, int duplex, bool tx_pause, bool rx_pause)
+{
+	struct mtk_mac *mac = container_of(config, struct mtk_mac,
+					   phylink_config);
+
+	if (mtk_interface_mode_is_xgmii(interface))
+		mtk_xgdm_mac_link_up(mac, phy, mode, interface, speed, duplex,
+				     tx_pause, rx_pause);
+	else
+		mtk_gdm_mac_link_up(mac, phy, mode, interface, speed, duplex,
+				    tx_pause, rx_pause);
+}
+
 static const struct phylink_mac_ops mtk_phylink_ops = {
 	.mac_select_pcs = mtk_mac_select_pcs,
 	.mac_config = mtk_mac_config,
@@ -4605,8 +4719,21 @@ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np)
 		phy_interface_zero(mac->phylink_config.supported_interfaces);
 		__set_bit(PHY_INTERFACE_MODE_INTERNAL,
 			  mac->phylink_config.supported_interfaces);
+	} else if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_USXGMII)) {
+		mac->phylink_config.mac_capabilities |= MAC_5000FD | MAC_10000FD;
+		__set_bit(PHY_INTERFACE_MODE_5GBASER,
+			  mac->phylink_config.supported_interfaces);
+		__set_bit(PHY_INTERFACE_MODE_10GBASER,
+			  mac->phylink_config.supported_interfaces);
+		__set_bit(PHY_INTERFACE_MODE_USXGMII,
+			  mac->phylink_config.supported_interfaces);
 	}
 
+	if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_2P5GPHY) &&
+	    id == MTK_GMAC2_ID)
+		__set_bit(PHY_INTERFACE_MODE_INTERNAL,
+			  mac->phylink_config.supported_interfaces);
+
 	phylink = phylink_create(&mac->phylink_config,
 				 of_fwnode_handle(mac->of_node),
 				 phy_mode, &mtk_phylink_ops);
@@ -4807,6 +4934,13 @@ static int mtk_probe(struct platform_device *pdev)
 			return err;
 	}
 
+	if (MTK_HAS_CAPS(eth->soc->caps, MTK_USXGMII)) {
+		err = mtk_usxgmii_init(eth);
+
+		if (err)
+			return err;
+	}
+
 	if (eth->soc->required_pctl) {
 		eth->pctl = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
 							    "mediatek,pctl");
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.h b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
index 403219d987eff..e8f503af065bf 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
@@ -503,6 +503,21 @@
 #define INTF_MODE_RGMII_1000    (TRGMII_MODE | TRGMII_CENTRAL_ALIGNED)
 #define INTF_MODE_RGMII_10_100  0
 
+/* XFI Mac control registers */
+#define MTK_XMAC_BASE(x)	(0x12000 + (((x) - 1) * 0x1000))
+#define MTK_XMAC_MCR(x)		(MTK_XMAC_BASE(x))
+#define XMAC_MCR_TRX_DISABLE	0xf
+#define XMAC_MCR_FORCE_TX_FC	BIT(5)
+#define XMAC_MCR_FORCE_RX_FC	BIT(4)
+
+/* XFI Mac logic reset registers */
+#define MTK_XMAC_LOGIC_RST(x)	(MTK_XMAC_BASE(x) + 0x10)
+#define XMAC_LOGIC_RST		BIT(0)
+
+/* XFI Mac count global control */
+#define MTK_XMAC_CNT_CTRL(x)	(MTK_XMAC_BASE(x) + 0x100)
+#define XMAC_GLB_CNTCLR		BIT(0)
+
 /* GPIO port control registers for GMAC 2*/
 #define GPIO_OD33_CTRL8		0x4c0
 #define GPIO_BIAS_CTRL		0xed0
@@ -528,6 +543,7 @@
 #define SYSCFG0_SGMII_GMAC2    ((3 << 8) & SYSCFG0_SGMII_MASK)
 #define SYSCFG0_SGMII_GMAC1_V2 BIT(9)
 #define SYSCFG0_SGMII_GMAC2_V2 BIT(8)
+#define SYSCFG0_SGMII_GMAC3_V2 BIT(7)
 
 
 /* ethernet subsystem clock register */
@@ -560,12 +576,74 @@
 #define ETHSYS_DMA_AG_MAP_QDMA	BIT(1)
 #define ETHSYS_DMA_AG_MAP_PPE	BIT(2)
 
+/* USXGMII subsystem config registers */
+/* Register to control speed */
+#define RG_PHY_TOP_SPEED_CTRL1	0x80C
+#define USXGMII_RATE_UPDATE_MODE	BIT(31)
+#define USXGMII_MAC_CK_GATED	BIT(29)
+#define USXGMII_IF_FORCE_EN	BIT(28)
+#define USXGMII_RATE_ADAPT_MODE	GENMASK(10, 8)
+#define USXGMII_RATE_ADAPT_MODE_X1	0
+#define USXGMII_RATE_ADAPT_MODE_X2	1
+#define USXGMII_RATE_ADAPT_MODE_X4	2
+#define USXGMII_RATE_ADAPT_MODE_X10	3
+#define USXGMII_RATE_ADAPT_MODE_X100	4
+#define USXGMII_RATE_ADAPT_MODE_X5	5
+#define USXGMII_RATE_ADAPT_MODE_X50	6
+#define USXGMII_XFI_RX_MODE	GENMASK(6, 4)
+#define USXGMII_XFI_RX_MODE_10G	0
+#define USXGMII_XFI_RX_MODE_5G	1
+#define USXGMII_XFI_TX_MODE	GENMASK(2, 0)
+#define USXGMII_XFI_TX_MODE_10G	0
+#define USXGMII_XFI_TX_MODE_5G	1
+
+/* Register to control PCS AN */
+#define RG_PCS_AN_CTRL0		0x810
+#define USXGMII_AN_RESTART	BIT(31)
+#define USXGMII_AN_SYNC_CNT	GENMASK(30, 11)
+#define USXGMII_AN_ENABLE	BIT(0)
+
+#define RG_PCS_AN_CTRL2		0x818
+#define USXGMII_LINK_TIMER_IDLE_DETECT	GENMASK(29, 20)
+#define USXGMII_LINK_TIMER_COMP_ACK_DETECT	GENMASK(19, 10)
+#define USXGMII_LINK_TIMER_AN_RESTART	GENMASK(9, 0)
+
+/* Register to read PCS AN status */
+#define RG_PCS_AN_STS0		0x81c
+#define USXGMII_PCS_AN_WORD	GENMASK(15, 0)
+#define USXGMII_LPA_LATCH	BIT(31)
+
+/* Register to control USXGMII XFI PLL digital */
+#define XFI_PLL_DIG_GLB8	0x08
+#define RG_XFI_PLL_EN		BIT(31)
+
+/* Register to control USXGMII XFI PLL analog */
+#define XFI_PLL_ANA_GLB8	0x108
+#define RG_XFI_PLL_ANA_SWWA	0x02283248
+
 /* Infrasys subsystem config registers */
 #define INFRA_MISC2            0x70c
 #define CO_QPHY_SEL            BIT(0)
 #define GEPHY_MAC_SEL          BIT(1)
 
+/* Toprgu subsystem config registers */
+#define TOPRGU_SWSYSRST		0x18
+#define SWSYSRST_UNLOCK_KEY	GENMASK(31, 24)
+#define SWSYSRST_XFI_PLL_GRST	BIT(16)
+#define SWSYSRST_XFI_PEXPT1_GRST	BIT(15)
+#define SWSYSRST_XFI_PEXPT0_GRST	BIT(14)
+#define SWSYSRST_XFI1_GRST	BIT(13)
+#define SWSYSRST_XFI0_GRST	BIT(12)
+#define SWSYSRST_SGMII1_GRST	BIT(2)
+#define SWSYSRST_SGMII0_GRST	BIT(1)
+#define TOPRGU_SWSYSRST_EN		0xFC
+
 /* Top misc registers */
+#define TOP_MISC_NETSYS_PCS_MUX	0x84
+#define NETSYS_PCS_MUX_MASK	GENMASK(1, 0)
+#define	MUX_G2_USXGMII_SEL	BIT(1)
+#define MUX_HSGMII1_G1_SEL	BIT(0)
+
 #define USB_PHY_SWITCH_REG	0x218
 #define QPHY_SEL_MASK		GENMASK(1, 0)
 #define SGMII_QPHY_SEL		0x2
@@ -590,6 +668,8 @@
 #define MT7628_SDM_RBCNT	(MT7628_SDM_OFFSET + 0x10c)
 #define MT7628_SDM_CS_ERR	(MT7628_SDM_OFFSET + 0x110)
 
+/* Debug Purpose Register */
+#define MTK_PSE_FQFC_CFG	0x100
 #define MTK_FE_CDM1_FSM		0x220
 #define MTK_FE_CDM2_FSM		0x224
 #define MTK_FE_CDM3_FSM		0x238
@@ -598,6 +678,11 @@
 #define MTK_FE_CDM6_FSM		0x328
 #define MTK_FE_GDM1_FSM		0x228
 #define MTK_FE_GDM2_FSM		0x22C
+#define MTK_FE_GDM3_FSM		0x23C
+#define MTK_FE_PSE_FREE		0x240
+#define MTK_FE_DROP_FQ		0x244
+#define MTK_FE_DROP_FC		0x248
+#define MTK_FE_DROP_PPE		0x24C
 
 #define MTK_MAC_FSM(x)		(0x1010C + ((x) * 0x100))
 
@@ -944,6 +1029,8 @@ enum mkt_eth_capabilities {
 	MTK_RGMII_BIT = 0,
 	MTK_TRGMII_BIT,
 	MTK_SGMII_BIT,
+	MTK_USXGMII_BIT,
+	MTK_2P5GPHY_BIT,
 	MTK_ESW_BIT,
 	MTK_GEPHY_BIT,
 	MTK_MUX_BIT,
@@ -964,8 +1051,11 @@ enum mkt_eth_capabilities {
 	MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT,
 	MTK_ETH_MUX_GMAC2_GMAC0_TO_GEPHY_BIT,
 	MTK_ETH_MUX_U3_GMAC2_TO_QPHY_BIT,
+	MTK_ETH_MUX_GMAC2_TO_2P5GPHY_BIT,
 	MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII_BIT,
 	MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII_BIT,
+	MTK_ETH_MUX_GMAC123_TO_GEPHY_SGMII_BIT,
+	MTK_ETH_MUX_GMAC123_TO_USXGMII_BIT,
 
 	/* PATH BITS */
 	MTK_ETH_PATH_GMAC1_RGMII_BIT,
@@ -973,14 +1063,21 @@ enum mkt_eth_capabilities {
 	MTK_ETH_PATH_GMAC1_SGMII_BIT,
 	MTK_ETH_PATH_GMAC2_RGMII_BIT,
 	MTK_ETH_PATH_GMAC2_SGMII_BIT,
+	MTK_ETH_PATH_GMAC2_2P5GPHY_BIT,
 	MTK_ETH_PATH_GMAC2_GEPHY_BIT,
+	MTK_ETH_PATH_GMAC3_SGMII_BIT,
 	MTK_ETH_PATH_GDM1_ESW_BIT,
+	MTK_ETH_PATH_GMAC1_USXGMII_BIT,
+	MTK_ETH_PATH_GMAC2_USXGMII_BIT,
+	MTK_ETH_PATH_GMAC3_USXGMII_BIT,
 };
 
 /* Supported hardware group on SoCs */
 #define MTK_RGMII		BIT_ULL(MTK_RGMII_BIT)
 #define MTK_TRGMII		BIT_ULL(MTK_TRGMII_BIT)
 #define MTK_SGMII		BIT_ULL(MTK_SGMII_BIT)
+#define MTK_USXGMII		BIT_ULL(MTK_USXGMII_BIT)
+#define MTK_2P5GPHY		BIT_ULL(MTK_2P5GPHY_BIT)
 #define MTK_ESW			BIT_ULL(MTK_ESW_BIT)
 #define MTK_GEPHY		BIT_ULL(MTK_GEPHY_BIT)
 #define MTK_MUX			BIT_ULL(MTK_MUX_BIT)
@@ -1003,10 +1100,16 @@ enum mkt_eth_capabilities {
 	BIT_ULL(MTK_ETH_MUX_GMAC2_GMAC0_TO_GEPHY_BIT)
 #define MTK_ETH_MUX_U3_GMAC2_TO_QPHY		\
 	BIT_ULL(MTK_ETH_MUX_U3_GMAC2_TO_QPHY_BIT)
+#define MTK_ETH_MUX_GMAC2_TO_2P5GPHY		\
+	BIT_ULL(MTK_ETH_MUX_GMAC2_TO_2P5GPHY_BIT)
 #define MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII	\
 	BIT_ULL(MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII_BIT)
 #define MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII	\
 	BIT_ULL(MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII_BIT)
+#define MTK_ETH_MUX_GMAC123_TO_GEPHY_SGMII	\
+	BIT_ULL(MTK_ETH_MUX_GMAC123_TO_GEPHY_SGMII_BIT)
+#define MTK_ETH_MUX_GMAC123_TO_USXGMII	\
+	BIT_ULL(MTK_ETH_MUX_GMAC123_TO_USXGMII_BIT)
 
 /* Supported path present on SoCs */
 #define MTK_ETH_PATH_GMAC1_RGMII	BIT_ULL(MTK_ETH_PATH_GMAC1_RGMII_BIT)
@@ -1014,8 +1117,13 @@ enum mkt_eth_capabilities {
 #define MTK_ETH_PATH_GMAC1_SGMII	BIT_ULL(MTK_ETH_PATH_GMAC1_SGMII_BIT)
 #define MTK_ETH_PATH_GMAC2_RGMII	BIT_ULL(MTK_ETH_PATH_GMAC2_RGMII_BIT)
 #define MTK_ETH_PATH_GMAC2_SGMII	BIT_ULL(MTK_ETH_PATH_GMAC2_SGMII_BIT)
+#define MTK_ETH_PATH_GMAC2_2P5GPHY	BIT_ULL(MTK_ETH_PATH_GMAC2_2P5GPHY_BIT)
 #define MTK_ETH_PATH_GMAC2_GEPHY	BIT_ULL(MTK_ETH_PATH_GMAC2_GEPHY_BIT)
+#define MTK_ETH_PATH_GMAC3_SGMII	BIT_ULL(MTK_ETH_PATH_GMAC3_SGMII_BIT)
 #define MTK_ETH_PATH_GDM1_ESW		BIT_ULL(MTK_ETH_PATH_GDM1_ESW_BIT)
+#define MTK_ETH_PATH_GMAC1_USXGMII	BIT_ULL(MTK_ETH_PATH_GMAC1_USXGMII_BIT)
+#define MTK_ETH_PATH_GMAC2_USXGMII	BIT_ULL(MTK_ETH_PATH_GMAC2_USXGMII_BIT)
+#define MTK_ETH_PATH_GMAC3_USXGMII	BIT_ULL(MTK_ETH_PATH_GMAC3_USXGMII_BIT)
 
 #define MTK_GMAC1_RGMII		(MTK_ETH_PATH_GMAC1_RGMII | MTK_RGMII)
 #define MTK_GMAC1_TRGMII	(MTK_ETH_PATH_GMAC1_TRGMII | MTK_TRGMII)
@@ -1023,7 +1131,12 @@ enum mkt_eth_capabilities {
 #define MTK_GMAC2_RGMII		(MTK_ETH_PATH_GMAC2_RGMII | MTK_RGMII)
 #define MTK_GMAC2_SGMII		(MTK_ETH_PATH_GMAC2_SGMII | MTK_SGMII)
 #define MTK_GMAC2_GEPHY		(MTK_ETH_PATH_GMAC2_GEPHY | MTK_GEPHY)
+#define MTK_GMAC2_2P5GPHY	(MTK_ETH_PATH_GMAC2_2P5GPHY | MTK_2P5GPHY)
+#define MTK_GMAC3_SGMII		(MTK_ETH_PATH_GMAC3_SGMII | MTK_SGMII)
 #define MTK_GDM1_ESW		(MTK_ETH_PATH_GDM1_ESW | MTK_ESW)
+#define MTK_GMAC1_USXGMII	(MTK_ETH_PATH_GMAC1_USXGMII | MTK_USXGMII)
+#define MTK_GMAC2_USXGMII	(MTK_ETH_PATH_GMAC2_USXGMII | MTK_USXGMII)
+#define MTK_GMAC3_USXGMII	(MTK_ETH_PATH_GMAC3_USXGMII | MTK_USXGMII)
 
 /* MUXes present on SoCs */
 /* 0: GDM1 -> GMAC1, 1: GDM1 -> ESW */
@@ -1042,10 +1155,20 @@ enum mkt_eth_capabilities {
 	(MTK_ETH_MUX_GMAC1_GMAC2_TO_SGMII_RGMII | MTK_MUX | \
 	MTK_SHARED_SGMII)
 
+/* 2: GMAC2 -> XGMII */
+#define MTK_MUX_GMAC2_TO_2P5GPHY      \
+	(MTK_ETH_MUX_GMAC2_TO_2P5GPHY | MTK_MUX | MTK_INFRA)
+
 /* 0: GMACx -> GEPHY, 1: GMACx -> SGMII where x is 1 or 2 */
 #define MTK_MUX_GMAC12_TO_GEPHY_SGMII   \
 	(MTK_ETH_MUX_GMAC12_TO_GEPHY_SGMII | MTK_MUX)
 
+#define MTK_MUX_GMAC123_TO_GEPHY_SGMII   \
+	(MTK_ETH_MUX_GMAC123_TO_GEPHY_SGMII | MTK_MUX)
+
+#define MTK_MUX_GMAC123_TO_USXGMII   \
+	(MTK_ETH_MUX_GMAC123_TO_USXGMII | MTK_MUX | MTK_INFRA)
+
 #define MTK_HAS_CAPS(caps, _x)		(((caps) & (_x)) == (_x))
 
 #define MT7621_CAPS  (MTK_GMAC1_RGMII | MTK_GMAC1_TRGMII | \
@@ -1077,8 +1200,12 @@ enum mkt_eth_capabilities {
 		      MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \
 		      MTK_RSTCTRL_PPE1 | MTK_SRAM)
 
-#define MT7988_CAPS  (MTK_36BIT_DMA | MTK_GDM1_ESW | MTK_QDMA | \
-		      MTK_RSTCTRL_PPE1 | MTK_RSTCTRL_PPE2 | MTK_SRAM)
+#define MT7988_CAPS  (MTK_36BIT_DMA | MTK_GDM1_ESW | MTK_GMAC1_SGMII | \
+		      MTK_GMAC2_2P5GPHY | MTK_GMAC2_SGMII | MTK_GMAC2_USXGMII | \
+		      MTK_GMAC3_SGMII | MTK_GMAC3_USXGMII | \
+		      MTK_MUX_GMAC123_TO_GEPHY_SGMII | \
+		      MTK_MUX_GMAC123_TO_USXGMII | MTK_MUX_GMAC2_TO_2P5GPHY | \
+		      MTK_QDMA | MTK_RSTCTRL_PPE1 | MTK_RSTCTRL_PPE2 | MTK_SRAM)
 
 struct mtk_tx_dma_desc_info {
 	dma_addr_t	addr;
@@ -1188,6 +1315,24 @@ struct mtk_soc_data {
 /* currently no SoC has more than 3 macs */
 #define MTK_MAX_DEVS	3
 
+/* struct mtk_usxgmii_pcs - This structure holds each usxgmii regmap and
+ *			associated data
+ * @regmap:		The register map pointing at the range used to setup
+ *			USXGMII modes
+ * @interface:		Currently selected interface mode
+ * @id:			The element is used to record the index of PCS
+ * @pcs:		Phylink PCS structure
+ */
+struct mtk_usxgmii_pcs {
+	struct mtk_eth		*eth;
+	struct regmap		*regmap;
+	struct phylink_pcs	*wrapped_sgmii_pcs;
+	phy_interface_t		interface;
+	u8			id;
+	unsigned int		neg_mode;
+	struct phylink_pcs	pcs;
+};
+
 /* struct mtk_eth -	This is the main datasructure for holding the state
  *			of the driver
  * @dev:		The device pointer
@@ -1208,6 +1353,12 @@ struct mtk_soc_data {
  * @infra:              The register map pointing at the range used to setup
  *                      SGMII and GePHY path
  * @sgmii_pcs:		Pointers to mtk-pcs-lynxi phylink_pcs instances
+ * @sgmii_wrapped_pcs:	Pointers to NETSYSv3 wrapper PCS instances
+ * @usxgmii_pll:	The register map pointing at the range used to control
+ *			the USXGMII SerDes PLL
+ * @regmap_pextp:	The register map pointing at the range used to setup
+ *			PHYA
+ * @usxgmii_pcs:	Pointer to array of pointers to struct for USXGMII PCS
  * @pctl:		The register map pointing at the range used to setup
  *			GMAC port drive/slew values
  * @dma_refcnt:		track how many netdevs are using the DMA engine
@@ -1251,6 +1402,10 @@ struct mtk_eth {
 	struct regmap			*ethsys;
 	struct regmap			*infra;
 	struct phylink_pcs		*sgmii_pcs[MTK_MAX_DEVS];
+	struct regmap			*toprgu;
+	struct regmap			*usxgmii_pll;
+	struct regmap			*regmap_pextp[MTK_MAX_DEVS];
+	struct mtk_usxgmii_pcs		*usxgmii_pcs[MTK_MAX_DEVS];
 	struct regmap			*pctl;
 	bool				hwlro;
 	refcount_t			dma_refcnt;
@@ -1421,6 +1576,19 @@ static inline u32 mtk_get_ib2_multicast_mask(struct mtk_eth *eth)
 	return MTK_FOE_IB2_MULTICAST;
 }
 
+static inline bool mtk_interface_mode_is_xgmii(phy_interface_t interface)
+{
+	switch (interface) {
+	case PHY_INTERFACE_MODE_INTERNAL:
+	case PHY_INTERFACE_MODE_USXGMII:
+	case PHY_INTERFACE_MODE_10GBASER:
+	case PHY_INTERFACE_MODE_5GBASER:
+		return true;
+	default:
+		return false;
+	}
+}
+
 /* read the hardware status register */
 void mtk_stats_update_mac(struct mtk_mac *mac);
 
@@ -1429,8 +1597,10 @@ u32 mtk_r32(struct mtk_eth *eth, unsigned reg);
 u32 mtk_m32(struct mtk_eth *eth, u32 mask, u32 set, unsigned int reg);
 
 int mtk_gmac_sgmii_path_setup(struct mtk_eth *eth, int mac_id);
+int mtk_gmac_2p5gphy_path_setup(struct mtk_eth *eth, int mac_id);
 int mtk_gmac_gephy_path_setup(struct mtk_eth *eth, int mac_id);
 int mtk_gmac_rgmii_path_setup(struct mtk_eth *eth, int mac_id);
+int mtk_gmac_usxgmii_path_setup(struct mtk_eth *eth, int mac_id);
 
 int mtk_eth_offload_init(struct mtk_eth *eth);
 int mtk_eth_setup_tc(struct net_device *dev, enum tc_setup_type type,
@@ -1440,5 +1610,63 @@ int mtk_flow_offload_cmd(struct mtk_eth *eth, struct flow_cls_offload *cls,
 void mtk_flow_offload_cleanup(struct mtk_eth *eth, struct list_head *list);
 void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev);
 
+static inline int mtk_mac2xgmii_id(struct mtk_eth *eth, int mac_id)
+{
+	int xgmii_id = mac_id;
+
+	if (mtk_is_netsys_v3_or_greater(eth)) {
+		switch (mac_id) {
+		case MTK_GMAC1_ID:
+		case MTK_GMAC2_ID:
+			xgmii_id = 1;
+			break;
+		case MTK_GMAC3_ID:
+			xgmii_id = 0;
+			break;
+		default:
+			xgmii_id = -1;
+		}
+	}
+
+	return MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_SGMII) ? 0 : xgmii_id;
+}
+
+static inline int mtk_xgmii2mac_id(struct mtk_eth *eth, int xgmii_id)
+{
+	int mac_id = xgmii_id;
+
+	if (mtk_is_netsys_v3_or_greater(eth)) {
+		switch (xgmii_id) {
+		case 0:
+			mac_id = 2;
+			break;
+		case 1:
+			mac_id = 1;
+			break;
+		default:
+			mac_id = -1;
+		}
+	}
+
+	return mac_id;
+}
+
+#ifdef CONFIG_NET_MEDIATEK_SOC_USXGMII
+struct phylink_pcs *mtk_sgmii_wrapper_select_pcs(struct mtk_eth *eth, int id);
+struct phylink_pcs *mtk_usxgmii_select_pcs(struct mtk_eth *eth, int id);
+int mtk_usxgmii_init(struct mtk_eth *eth);
+#else
+static inline struct phylink_pcs *mtk_sgmii_wrapper_select_pcs(struct mtk_eth *eth, int id)
+{
+	return NULL;
+}
+
+static inline struct phylink_pcs *mtk_usxgmii_select_pcs(struct mtk_eth *eth, int id)
+{
+	return NULL;
+}
+
+static inline int mtk_usxgmii_init(struct mtk_eth *eth) { return 0; }
+#endif /* NET_MEDIATEK_SOC_USXGMII */
 
 #endif /* MTK_ETH_H */
diff --git a/drivers/net/ethernet/mediatek/mtk_usxgmii.c b/drivers/net/ethernet/mediatek/mtk_usxgmii.c
new file mode 100644
index 0000000000000..e0f0b6b2f5173
--- /dev/null
+++ b/drivers/net/ethernet/mediatek/mtk_usxgmii.c
@@ -0,0 +1,702 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 MediaTek Inc.
+ * Author: Henry Yen <henry.yen@...iatek.com>
+ *         Daniel Golle <daniel@...rotopia.org>
+ */
+
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include "mtk_eth_soc.h"
+
+static struct mtk_usxgmii_pcs *pcs_to_mtk_usxgmii_pcs(struct phylink_pcs *pcs)
+{
+	return container_of(pcs, struct mtk_usxgmii_pcs, pcs);
+}
+
+static int mtk_xfi_pextp_init(struct mtk_eth *eth)
+{
+	struct device *dev = eth->dev;
+	struct device_node *r = dev->of_node;
+	struct device_node *np;
+	int i;
+
+	for (i = 0; i < MTK_MAX_DEVS; i++) {
+		np = of_parse_phandle(r, "mediatek,xfi-pextp", i);
+		if (!np)
+			break;
+
+		eth->regmap_pextp[i] = syscon_node_to_regmap(np);
+		if (IS_ERR(eth->regmap_pextp[i]))
+			return PTR_ERR(eth->regmap_pextp[i]);
+	}
+
+	return 0;
+}
+
+static int mtk_xfi_pll_init(struct mtk_eth *eth)
+{
+	struct device_node *r = eth->dev->of_node;
+	struct device_node *np;
+
+	np = of_parse_phandle(r, "mediatek,xfi-pll", 0);
+	if (!np)
+		return -1;
+
+	eth->usxgmii_pll = syscon_node_to_regmap(np);
+	if (IS_ERR(eth->usxgmii_pll))
+		return PTR_ERR(eth->usxgmii_pll);
+
+	return 0;
+}
+
+static int mtk_toprgu_init(struct mtk_eth *eth)
+{
+	struct device_node *r = eth->dev->of_node;
+	struct device_node *np;
+
+	np = of_parse_phandle(r, "mediatek,toprgu", 0);
+	if (!np)
+		return -1;
+
+	eth->toprgu = syscon_node_to_regmap(np);
+	if (IS_ERR(eth->toprgu))
+		return PTR_ERR(eth->toprgu);
+
+	return 0;
+}
+
+static int mtk_xfi_pll_enable(struct mtk_eth *eth)
+{
+	u32 val = 0;
+
+	if (!eth->usxgmii_pll)
+		return -EINVAL;
+
+	/* Add software workaround for USXGMII PLL TCL issue */
+	regmap_write(eth->usxgmii_pll, XFI_PLL_ANA_GLB8, RG_XFI_PLL_ANA_SWWA);
+
+	regmap_read(eth->usxgmii_pll, XFI_PLL_DIG_GLB8, &val);
+	val |= RG_XFI_PLL_EN;
+	regmap_write(eth->usxgmii_pll, XFI_PLL_DIG_GLB8, val);
+
+	return 0;
+}
+
+static void mtk_usxgmii_setup_phya(struct regmap *pextp, phy_interface_t interface, int id)
+{
+	bool is_10g = (interface == PHY_INTERFACE_MODE_10GBASER ||
+		       interface == PHY_INTERFACE_MODE_USXGMII);
+	bool is_2p5g = (interface == PHY_INTERFACE_MODE_2500BASEX);
+	bool is_5g = (interface == PHY_INTERFACE_MODE_5GBASER);
+
+	/* Setup operation mode */
+	if (is_10g)
+		regmap_write(pextp, 0x9024, 0x00C9071C);
+	else
+		regmap_write(pextp, 0x9024, 0x00D9071C);
+
+	if (is_5g)
+		regmap_write(pextp, 0x2020, 0xAAA5A5AA);
+	else
+		regmap_write(pextp, 0x2020, 0xAA8585AA);
+
+	if (is_2p5g || is_5g || is_10g) {
+		regmap_write(pextp, 0x2030, 0x0C020707);
+		regmap_write(pextp, 0x2034, 0x0E050F0F);
+		regmap_write(pextp, 0x2040, 0x00140032);
+	} else {
+		regmap_write(pextp, 0x2030, 0x0C020207);
+		regmap_write(pextp, 0x2034, 0x0E05050F);
+		regmap_write(pextp, 0x2040, 0x00200032);
+	}
+
+	if (is_2p5g || is_10g)
+		regmap_write(pextp, 0x50F0, 0x00C014AA);
+	else if (is_5g)
+		regmap_write(pextp, 0x50F0, 0x00C018AA);
+	else
+		regmap_write(pextp, 0x50F0, 0x00C014BA);
+
+	if (is_5g) {
+		regmap_write(pextp, 0x50E0, 0x3777812B);
+		regmap_write(pextp, 0x506C, 0x005C9CFF);
+		regmap_write(pextp, 0x5070, 0x9DFAFAFA);
+		regmap_write(pextp, 0x5074, 0x273F3F3F);
+		regmap_write(pextp, 0x5078, 0xA8883868);
+		regmap_write(pextp, 0x507C, 0x14661466);
+	} else {
+		regmap_write(pextp, 0x50E0, 0x3777C12B);
+		regmap_write(pextp, 0x506C, 0x005F9CFF);
+		regmap_write(pextp, 0x5070, 0x9D9DFAFA);
+		regmap_write(pextp, 0x5074, 0x27273F3F);
+		regmap_write(pextp, 0x5078, 0xA7883C68);
+		regmap_write(pextp, 0x507C, 0x11661166);
+	}
+
+	if (is_2p5g || is_10g) {
+		regmap_write(pextp, 0x5080, 0x0E000AAF);
+		regmap_write(pextp, 0x5084, 0x08080D0D);
+		regmap_write(pextp, 0x5088, 0x02030909);
+	} else if (is_5g) {
+		regmap_write(pextp, 0x5080, 0x0E001ABF);
+		regmap_write(pextp, 0x5084, 0x080B0D0D);
+		regmap_write(pextp, 0x5088, 0x02050909);
+	} else {
+		regmap_write(pextp, 0x5080, 0x0E000EAF);
+		regmap_write(pextp, 0x5084, 0x08080E0D);
+		regmap_write(pextp, 0x5088, 0x02030B09);
+	}
+
+	if (is_5g) {
+		regmap_write(pextp, 0x50E4, 0x0C000000);
+		regmap_write(pextp, 0x50E8, 0x04000000);
+	} else {
+		regmap_write(pextp, 0x50E4, 0x0C0C0000);
+		regmap_write(pextp, 0x50E8, 0x04040000);
+	}
+
+	if (is_2p5g || mtk_interface_mode_is_xgmii(interface))
+		regmap_write(pextp, 0x50EC, 0x0F0F0C06);
+	else
+		regmap_write(pextp, 0x50EC, 0x0F0F0606);
+
+	if (is_5g) {
+		regmap_write(pextp, 0x50A8, 0x50808C8C);
+		regmap_write(pextp, 0x6004, 0x18000000);
+	} else {
+		regmap_write(pextp, 0x50A8, 0x506E8C8C);
+		regmap_write(pextp, 0x6004, 0x18190000);
+	}
+
+	if (is_10g)
+		regmap_write(pextp, 0x00F8, 0x01423342);
+	else if (is_5g)
+		regmap_write(pextp, 0x00F8, 0x00A132A1);
+	else if (is_2p5g)
+		regmap_write(pextp, 0x00F8, 0x009C329C);
+	else
+		regmap_write(pextp, 0x00F8, 0x00FA32FA);
+
+	/* Force SGDT_OUT off and select PCS */
+	if (mtk_interface_mode_is_xgmii(interface))
+		regmap_write(pextp, 0x00F4, 0x80201F20);
+	else
+		regmap_write(pextp, 0x00F4, 0x80201F21);
+
+	/* Force GLB_CKDET_OUT */
+	regmap_write(pextp, 0x0030, 0x00050C00);
+
+	/* Force AEQ on */
+	regmap_write(pextp, 0x0070, 0x02002800);
+	ndelay(1020);
+
+	/* Setup DA default value */
+	regmap_write(pextp, 0x30B0, 0x00000020);
+	regmap_write(pextp, 0x3028, 0x00008A01);
+	regmap_write(pextp, 0x302C, 0x0000A884);
+	regmap_write(pextp, 0x3024, 0x00083002);
+	if (mtk_interface_mode_is_xgmii(interface)) {
+		regmap_write(pextp, 0x3010, 0x00022220);
+		regmap_write(pextp, 0x5064, 0x0F020A01);
+		regmap_write(pextp, 0x50B4, 0x06100600);
+		if (interface == PHY_INTERFACE_MODE_USXGMII)
+			regmap_write(pextp, 0x3048, 0x40704000);
+		else
+			regmap_write(pextp, 0x3048, 0x47684100);
+	} else {
+		regmap_write(pextp, 0x3010, 0x00011110);
+		regmap_write(pextp, 0x3048, 0x40704000);
+	}
+
+	if (!mtk_interface_mode_is_xgmii(interface) && !is_2p5g)
+		regmap_write(pextp, 0x3064, 0x0000C000);
+
+	if (interface == PHY_INTERFACE_MODE_USXGMII) {
+		regmap_write(pextp, 0x3050, 0xA8000000);
+		regmap_write(pextp, 0x3054, 0x000000AA);
+	} else if (mtk_interface_mode_is_xgmii(interface)) {
+		regmap_write(pextp, 0x3050, 0x00000000);
+		regmap_write(pextp, 0x3054, 0x00000000);
+	} else {
+		regmap_write(pextp, 0x3050, 0xA8000000);
+		regmap_write(pextp, 0x3054, 0x000000AA);
+	}
+
+	if (mtk_interface_mode_is_xgmii(interface))
+		regmap_write(pextp, 0x306C, 0x00000F00);
+	else if (is_2p5g)
+		regmap_write(pextp, 0x306C, 0x22000F00);
+	else
+		regmap_write(pextp, 0x306C, 0x20200F00);
+
+	if (interface == PHY_INTERFACE_MODE_10GBASER && id == 0)
+		regmap_write(pextp, 0xA008, 0x0007B400);
+
+	if (mtk_interface_mode_is_xgmii(interface))
+		regmap_write(pextp, 0xA060, 0x00040000);
+	else
+		regmap_write(pextp, 0xA060, 0x00050000);
+
+	if (is_10g)
+		regmap_write(pextp, 0x90D0, 0x00000001);
+	else if (is_5g)
+		regmap_write(pextp, 0x90D0, 0x00000003);
+	else if (is_2p5g)
+		regmap_write(pextp, 0x90D0, 0x00000005);
+	else
+		regmap_write(pextp, 0x90D0, 0x00000007);
+
+	/* Release reset */
+	regmap_write(pextp, 0x0070, 0x0200E800);
+	usleep_range(150, 500);
+
+	/* Switch to P0 */
+	regmap_write(pextp, 0x0070, 0x0200C111);
+	ndelay(1020);
+	regmap_write(pextp, 0x0070, 0x0200C101);
+	usleep_range(15, 50);
+
+	if (mtk_interface_mode_is_xgmii(interface)) {
+		/* Switch to Gen3 */
+		regmap_write(pextp, 0x0070, 0x0202C111);
+	} else {
+		/* Switch to Gen2 */
+		regmap_write(pextp, 0x0070, 0x0201C111);
+	}
+	ndelay(1020);
+	if (mtk_interface_mode_is_xgmii(interface))
+		regmap_write(pextp, 0x0070, 0x0202C101);
+	else
+		regmap_write(pextp, 0x0070, 0x0201C101);
+	usleep_range(100, 500);
+	regmap_write(pextp, 0x30B0, 0x00000030);
+	if (mtk_interface_mode_is_xgmii(interface))
+		regmap_write(pextp, 0x00F4, 0x80201F00);
+	else
+		regmap_write(pextp, 0x00F4, 0x80201F01);
+
+	regmap_write(pextp, 0x3040, 0x30000000);
+	usleep_range(400, 1000);
+}
+
+static void mtk_usxgmii_reset(struct mtk_eth *eth, int id)
+{
+	u32 toggle, val;
+
+	if (id >= MTK_MAX_DEVS || !eth->toprgu)
+		return;
+
+	switch (id) {
+	case 0:
+		toggle = SWSYSRST_XFI_PEXPT0_GRST | SWSYSRST_XFI0_GRST |
+			 SWSYSRST_SGMII0_GRST;
+		break;
+	case 1:
+		toggle = SWSYSRST_XFI_PEXPT1_GRST | SWSYSRST_XFI1_GRST |
+			 SWSYSRST_SGMII1_GRST;
+		break;
+	default:
+		return;
+	}
+
+	/* Enable software reset */
+	regmap_set_bits(eth->toprgu, TOPRGU_SWSYSRST_EN, toggle);
+
+	/* Assert USXGMII reset */
+	regmap_set_bits(eth->toprgu, TOPRGU_SWSYSRST,
+			FIELD_PREP(SWSYSRST_UNLOCK_KEY, 0x88) | toggle);
+
+	usleep_range(100, 500);
+
+	/* De-assert USXGMII reset */
+	regmap_read(eth->toprgu, TOPRGU_SWSYSRST, &val);
+	val |= FIELD_PREP(SWSYSRST_UNLOCK_KEY, 0x88);
+	val &= ~toggle;
+	regmap_write(eth->toprgu, TOPRGU_SWSYSRST, val);
+
+	/* Disable software reset */
+	regmap_clear_bits(eth->toprgu, TOPRGU_SWSYSRST_EN, toggle);
+
+	mdelay(10);
+}
+
+/* As the USXGMII PHYA is shared with the 1000Base-X/2500Base-X/Cisco SGMII unit
+ * the psc-mtk-lynxi instance needs to be wrapped, so that calls to .pcs_config
+ * also trigger an initial reset and subsequent configuration of the PHYA.
+ */
+struct mtk_sgmii_wrapper_pcs {
+	struct mtk_eth		*eth;
+	struct phylink_pcs	*wrapped_pcs;
+	u8			id;
+	struct phylink_pcs	pcs;
+};
+
+static int mtk_sgmii_wrapped_pcs_config(struct phylink_pcs *pcs,
+					unsigned int neg_mode,
+					phy_interface_t interface,
+					const unsigned long *advertising,
+					bool permit_pause_to_mac)
+{
+	struct mtk_sgmii_wrapper_pcs *wp = container_of(pcs, struct mtk_sgmii_wrapper_pcs, pcs);
+	bool full_reconf;
+	int ret;
+
+	full_reconf = interface != wp->eth->usxgmii_pcs[wp->id]->interface;
+	if (full_reconf) {
+		mtk_xfi_pll_enable(wp->eth);
+		mtk_usxgmii_reset(wp->eth, wp->id);
+	}
+
+	ret = wp->wrapped_pcs->ops->pcs_config(wp->wrapped_pcs, neg_mode, interface,
+					       advertising, permit_pause_to_mac);
+
+	if (full_reconf)
+		mtk_usxgmii_setup_phya(wp->eth->regmap_pextp[wp->id], interface, wp->id);
+
+	wp->eth->usxgmii_pcs[wp->id]->interface = interface;
+
+	return ret;
+}
+
+static void mtk_sgmii_wrapped_pcs_get_state(struct phylink_pcs *pcs,
+					    struct phylink_link_state *state)
+{
+	struct mtk_sgmii_wrapper_pcs *wp = container_of(pcs, struct mtk_sgmii_wrapper_pcs, pcs);
+
+	return wp->wrapped_pcs->ops->pcs_get_state(wp->wrapped_pcs, state);
+}
+
+static void mtk_sgmii_wrapped_pcs_an_restart(struct phylink_pcs *pcs)
+{
+	struct mtk_sgmii_wrapper_pcs *wp = container_of(pcs, struct mtk_sgmii_wrapper_pcs, pcs);
+
+	wp->wrapped_pcs->ops->pcs_an_restart(wp->wrapped_pcs);
+}
+
+static void mtk_sgmii_wrapped_pcs_link_up(struct phylink_pcs *pcs,
+					  unsigned int neg_mode,
+					  phy_interface_t interface, int speed,
+					  int duplex)
+{
+	struct mtk_sgmii_wrapper_pcs *wp = container_of(pcs, struct mtk_sgmii_wrapper_pcs, pcs);
+
+	wp->wrapped_pcs->ops->pcs_link_up(wp->wrapped_pcs, neg_mode, interface, speed, duplex);
+}
+
+static void mtk_sgmii_wrapped_pcs_disable(struct phylink_pcs *pcs)
+{
+	struct mtk_sgmii_wrapper_pcs *wp = container_of(pcs, struct mtk_sgmii_wrapper_pcs, pcs);
+
+	wp->wrapped_pcs->ops->pcs_disable(wp->wrapped_pcs);
+
+	wp->eth->usxgmii_pcs[wp->id]->interface = PHY_INTERFACE_MODE_NA;
+}
+
+static const struct phylink_pcs_ops mtk_sgmii_wrapped_pcs_ops = {
+	.pcs_get_state = mtk_sgmii_wrapped_pcs_get_state,
+	.pcs_config = mtk_sgmii_wrapped_pcs_config,
+	.pcs_an_restart = mtk_sgmii_wrapped_pcs_an_restart,
+	.pcs_link_up = mtk_sgmii_wrapped_pcs_link_up,
+	.pcs_disable = mtk_sgmii_wrapped_pcs_disable,
+};
+
+static int mtk_sgmii_wrapper_init(struct mtk_eth *eth)
+{
+	struct mtk_sgmii_wrapper_pcs *wp;
+	int i;
+
+	for (i = 0; i < MTK_MAX_DEVS; i++) {
+		if (!eth->sgmii_pcs[i])
+			continue;
+
+		if (!eth->usxgmii_pcs[i])
+			continue;
+
+		/* Make sure all PCS ops are supported by wrapped PCS */
+		if (!eth->sgmii_pcs[i]->ops->pcs_get_state ||
+		    !eth->sgmii_pcs[i]->ops->pcs_config ||
+		    !eth->sgmii_pcs[i]->ops->pcs_an_restart ||
+		    !eth->sgmii_pcs[i]->ops->pcs_link_up ||
+		    !eth->sgmii_pcs[i]->ops->pcs_disable)
+			return -EOPNOTSUPP;
+
+		wp = devm_kzalloc(eth->dev, sizeof(*wp), GFP_KERNEL);
+		if (!wp)
+			return -ENOMEM;
+
+		wp->wrapped_pcs = eth->sgmii_pcs[i];
+		wp->id = i;
+		wp->pcs.neg_mode = true;
+		wp->pcs.poll = true;
+		wp->pcs.ops = &mtk_sgmii_wrapped_pcs_ops;
+		wp->eth = eth;
+
+		eth->usxgmii_pcs[i]->wrapped_sgmii_pcs = &wp->pcs;
+	}
+
+	return 0;
+}
+
+struct phylink_pcs *mtk_sgmii_wrapper_select_pcs(struct mtk_eth *eth, int mac_id)
+{
+	u32 xgmii_id = mtk_mac2xgmii_id(eth, mac_id);
+
+	if (!eth->usxgmii_pcs[xgmii_id])
+		return NULL;
+
+	return eth->usxgmii_pcs[xgmii_id]->wrapped_sgmii_pcs;
+}
+
+static int mtk_usxgmii_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+				  phy_interface_t interface,
+				  const unsigned long *advertising,
+				  bool permit_pause_to_mac)
+{
+	struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
+	struct mtk_eth *eth = mpcs->eth;
+	struct regmap *pextp = eth->regmap_pextp[mpcs->id];
+	unsigned int an_ctrl = 0, link_timer = 0, xfi_mode = 0, adapt_mode = 0;
+	bool mode_changed = false;
+
+	if (!pextp)
+		return -ENODEV;
+
+	if (interface == PHY_INTERFACE_MODE_USXGMII) {
+		an_ctrl = FIELD_PREP(USXGMII_AN_SYNC_CNT, 0x1FF) | USXGMII_AN_ENABLE;
+		link_timer = FIELD_PREP(USXGMII_LINK_TIMER_IDLE_DETECT, 0x7B) |
+			     FIELD_PREP(USXGMII_LINK_TIMER_COMP_ACK_DETECT, 0x7B) |
+			     FIELD_PREP(USXGMII_LINK_TIMER_AN_RESTART, 0x7B);
+		xfi_mode = FIELD_PREP(USXGMII_XFI_RX_MODE, USXGMII_XFI_RX_MODE_10G) |
+			   FIELD_PREP(USXGMII_XFI_TX_MODE, USXGMII_XFI_TX_MODE_10G);
+	} else if (interface == PHY_INTERFACE_MODE_10GBASER) {
+		an_ctrl = FIELD_PREP(USXGMII_AN_SYNC_CNT, 0x1FF);
+		link_timer = FIELD_PREP(USXGMII_LINK_TIMER_IDLE_DETECT, 0x7B) |
+			     FIELD_PREP(USXGMII_LINK_TIMER_COMP_ACK_DETECT, 0x7B) |
+			     FIELD_PREP(USXGMII_LINK_TIMER_AN_RESTART, 0x7B);
+		xfi_mode = FIELD_PREP(USXGMII_XFI_RX_MODE, USXGMII_XFI_RX_MODE_10G) |
+			   FIELD_PREP(USXGMII_XFI_TX_MODE, USXGMII_XFI_TX_MODE_10G);
+		adapt_mode = USXGMII_RATE_UPDATE_MODE;
+	} else if (interface == PHY_INTERFACE_MODE_5GBASER) {
+		an_ctrl = FIELD_PREP(USXGMII_AN_SYNC_CNT, 0xFF);
+		link_timer = FIELD_PREP(USXGMII_LINK_TIMER_IDLE_DETECT, 0x3D) |
+			     FIELD_PREP(USXGMII_LINK_TIMER_COMP_ACK_DETECT, 0x3D) |
+			     FIELD_PREP(USXGMII_LINK_TIMER_AN_RESTART, 0x3D);
+		xfi_mode = FIELD_PREP(USXGMII_XFI_RX_MODE, USXGMII_XFI_RX_MODE_5G) |
+			   FIELD_PREP(USXGMII_XFI_TX_MODE, USXGMII_XFI_TX_MODE_5G);
+		adapt_mode = USXGMII_RATE_UPDATE_MODE;
+	} else {
+		return -EINVAL;
+	}
+
+	adapt_mode |= FIELD_PREP(USXGMII_RATE_ADAPT_MODE, USXGMII_RATE_ADAPT_MODE_X1);
+
+	if (mpcs->interface != interface) {
+		mpcs->interface = interface;
+		mode_changed = true;
+	}
+
+	mtk_xfi_pll_enable(eth);
+	mtk_usxgmii_reset(eth, mpcs->id);
+
+	/* Setup USXGMII AN ctrl */
+	regmap_update_bits(mpcs->regmap, RG_PCS_AN_CTRL0,
+			   USXGMII_AN_SYNC_CNT | USXGMII_AN_ENABLE,
+			   an_ctrl);
+
+	regmap_update_bits(mpcs->regmap, RG_PCS_AN_CTRL2,
+			   USXGMII_LINK_TIMER_IDLE_DETECT |
+			   USXGMII_LINK_TIMER_COMP_ACK_DETECT |
+			   USXGMII_LINK_TIMER_AN_RESTART,
+			   link_timer);
+
+	mpcs->neg_mode = neg_mode;
+
+	/* Gated MAC CK */
+	regmap_update_bits(mpcs->regmap, RG_PHY_TOP_SPEED_CTRL1,
+			   USXGMII_MAC_CK_GATED, USXGMII_MAC_CK_GATED);
+
+	/* Enable interface force mode */
+	regmap_update_bits(mpcs->regmap, RG_PHY_TOP_SPEED_CTRL1,
+			   USXGMII_IF_FORCE_EN, USXGMII_IF_FORCE_EN);
+
+	/* Setup USXGMII adapt mode */
+	regmap_update_bits(mpcs->regmap, RG_PHY_TOP_SPEED_CTRL1,
+			   USXGMII_RATE_UPDATE_MODE | USXGMII_RATE_ADAPT_MODE,
+			   adapt_mode);
+
+	/* Setup USXGMII speed */
+	regmap_update_bits(mpcs->regmap, RG_PHY_TOP_SPEED_CTRL1,
+			   USXGMII_XFI_RX_MODE | USXGMII_XFI_TX_MODE,
+			   xfi_mode);
+
+	usleep_range(1, 10);
+
+	/* Un-gated MAC CK */
+	regmap_update_bits(mpcs->regmap, RG_PHY_TOP_SPEED_CTRL1,
+			   USXGMII_MAC_CK_GATED, 0);
+
+	usleep_range(1, 10);
+
+	/* Disable interface force mode for the AN mode */
+	if (an_ctrl & USXGMII_AN_ENABLE)
+		regmap_update_bits(mpcs->regmap, RG_PHY_TOP_SPEED_CTRL1,
+				   USXGMII_IF_FORCE_EN, 0);
+
+	/* Setup USXGMIISYS with the determined property */
+	mtk_usxgmii_setup_phya(pextp, interface, mpcs->id);
+
+	return mode_changed;
+}
+
+static void mtk_usxgmii_pcs_get_state(struct phylink_pcs *pcs,
+				      struct phylink_link_state *state)
+{
+	struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
+	struct mtk_eth *eth = mpcs->eth;
+	struct mtk_mac *mac = eth->mac[mtk_xgmii2mac_id(eth, mpcs->id)];
+	u32 val = 0;
+
+	regmap_read(mpcs->regmap, RG_PCS_AN_CTRL0, &val);
+	if (FIELD_GET(USXGMII_AN_ENABLE, val)) {
+		/* Refresh LPA by inverting LPA_LATCH */
+		regmap_read(mpcs->regmap, RG_PCS_AN_STS0, &val);
+		regmap_update_bits(mpcs->regmap, RG_PCS_AN_STS0,
+				   USXGMII_LPA_LATCH,
+				   !(val & USXGMII_LPA_LATCH));
+
+		regmap_read(mpcs->regmap, RG_PCS_AN_STS0, &val);
+
+		phylink_decode_usxgmii_word(state, FIELD_GET(USXGMII_PCS_AN_WORD,
+							     val));
+
+		state->interface = mpcs->interface;
+	} else {
+		val = mtk_r32(mac->hw, MTK_XGMAC_STS(mac->id));
+
+		if (mac->id == MTK_GMAC2_ID)
+			val >>= 16;
+
+		switch (FIELD_GET(MTK_USXGMII_PCS_MODE, val)) {
+		case 0:
+			state->speed = SPEED_10000;
+			break;
+		case 1:
+			state->speed = SPEED_5000;
+			break;
+		case 2:
+			state->speed = SPEED_2500;
+			break;
+		case 3:
+			state->speed = SPEED_1000;
+			break;
+		}
+
+		state->interface = mpcs->interface;
+		state->link = FIELD_GET(MTK_USXGMII_PCS_LINK, val);
+		state->duplex = DUPLEX_FULL;
+	}
+
+	/* Refresh USXGMII link status by toggling RG_PCS_RX_STATUS_UPDATE */
+	regmap_set_bits(mpcs->regmap, RG_PCS_RX_STATUS0, RG_PCS_RX_STATUS_UPDATE);
+	ndelay(1020);
+	regmap_clear_bits(mpcs->regmap, RG_PCS_RX_STATUS0, RG_PCS_RX_STATUS_UPDATE);
+	ndelay(1020);
+
+	/* Read USXGMII link status */
+	regmap_read(mpcs->regmap, RG_PCS_RX_STATUS0, &val);
+	state->link = state->link && FIELD_GET(RG_PCS_RX_LINK_STATUS, val);
+
+	/* Continuously repeat re-configuration sequence until link comes up */
+	if (!state->link)
+		mtk_usxgmii_pcs_config(pcs, mpcs->neg_mode,
+				       state->interface, NULL, false);
+}
+
+static void mtk_usxgmii_pcs_restart_an(struct phylink_pcs *pcs)
+{
+	struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
+	unsigned int val = 0;
+
+	if (!mpcs->regmap)
+		return;
+
+	regmap_read(mpcs->regmap, RG_PCS_AN_CTRL0, &val);
+	val |= USXGMII_AN_RESTART;
+	regmap_write(mpcs->regmap, RG_PCS_AN_CTRL0, val);
+}
+
+static void mtk_usxgmii_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
+				    phy_interface_t interface,
+				    int speed, int duplex)
+{
+	/* Reconfiguring USXGMII to ensure the quality of the RX signal
+	 * after the line side link up.
+	 */
+	mtk_usxgmii_pcs_config(pcs, neg_mode,
+			       interface, NULL, false);
+}
+
+static const struct phylink_pcs_ops mtk_usxgmii_pcs_ops = {
+	.pcs_config = mtk_usxgmii_pcs_config,
+	.pcs_get_state = mtk_usxgmii_pcs_get_state,
+	.pcs_an_restart = mtk_usxgmii_pcs_restart_an,
+	.pcs_link_up = mtk_usxgmii_pcs_link_up,
+};
+
+int mtk_usxgmii_init(struct mtk_eth *eth)
+{
+	struct device_node *r = eth->dev->of_node;
+	struct device *dev = eth->dev;
+	struct device_node *np;
+	int i, ret;
+
+	for (i = 0; i < MTK_MAX_DEVS; i++) {
+		np = of_parse_phandle(r, "mediatek,usxgmiisys", i);
+		if (!np)
+			break;
+
+		eth->usxgmii_pcs[i] = devm_kzalloc(dev, sizeof(*eth->usxgmii_pcs[i]), GFP_KERNEL);
+		if (!eth->usxgmii_pcs[i])
+			return -ENOMEM;
+
+		eth->usxgmii_pcs[i]->id = i;
+		eth->usxgmii_pcs[i]->eth = eth;
+		eth->usxgmii_pcs[i]->regmap = syscon_node_to_regmap(np);
+		if (IS_ERR(eth->usxgmii_pcs[i]->regmap))
+			return PTR_ERR(eth->usxgmii_pcs[i]->regmap);
+
+		eth->usxgmii_pcs[i]->pcs.ops = &mtk_usxgmii_pcs_ops;
+		eth->usxgmii_pcs[i]->pcs.poll = true;
+		eth->usxgmii_pcs[i]->pcs.neg_mode = true;
+		eth->usxgmii_pcs[i]->interface = PHY_INTERFACE_MODE_NA;
+		eth->usxgmii_pcs[i]->neg_mode = -1;
+
+		of_node_put(np);
+	}
+
+	ret = mtk_xfi_pextp_init(eth);
+	if (ret)
+		return ret;
+
+	ret = mtk_xfi_pll_init(eth);
+	if (ret)
+		return ret;
+
+	ret = mtk_toprgu_init(eth);
+	if (ret)
+		return ret;
+
+	return mtk_sgmii_wrapper_init(eth);
+}
+
+struct phylink_pcs *mtk_usxgmii_select_pcs(struct mtk_eth *eth, int mac_id)
+{
+	u32 xgmii_id = mtk_mac2xgmii_id(eth, mac_id);
+
+	if (!eth->usxgmii_pcs[xgmii_id]->regmap)
+		return NULL;
+
+	return &eth->usxgmii_pcs[xgmii_id]->pcs;
+}
-- 
2.42.0

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ