[rdk-b][common][bsp][Refactor and sync kernel/wifi from Openwrt]

[Description]
Refactor and sync kernel/wifi from Openwrt

[Release-log]
N/A

diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_reset.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_reset.c
index 9327f0c..ebb3940 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_reset.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_reset.c
@@ -200,7 +200,6 @@
 	static u32 err_cnt1 = 0, err_cnt2 = 0, err_cnt3 = 0;
 	static u32 prev_wdidx = 0;
 	unsigned int mib_base = MTK_GDM1_TX_GBCNT;
-	static u32 prev_gdm2rx = 0;
 
 	/*wdma tx path*/
 	u32 cur_wdidx = mtk_r32(eth, MTK_WDMA_DTX_PTR(0));
@@ -226,9 +225,6 @@
 	u32 is_cdm1_busy = (mtk_r32(eth, MTK_FE_CDM1_FSM) & 0xFFFF0000) != 0;
 	u32 is_adma_busy = ((mtk_r32(eth, MTK_ADMA_RX_DBG0) & 0x1F) == 0) &&
 			   ((mtk_r32(eth, MTK_ADMA_RX_DBG0) & 0x40) == 0);
-	/*gmac2 rx path*/
-	u32 gmac2_rx = (mtk_r32(eth, MTK_MAC_FSM(1)) & 0xFF0000 != 0x10000);
-	u32 gdm2_rx_cnt =  mtk_r32(eth, mib_base+0x48);
 
 	if (cur_wdidx == prev_wdidx && is_wtx_busy &&
 	    is_oq_free && is_cdm_full) {
@@ -316,30 +312,13 @@
 				schedule_work(&eth->pending_work);
 			}
 		}
-	}else if ((gdm2_rx_cnt == prev_gdm2rx) && gmac2_rx) {
-		err_cnt3++;
-		if (err_cnt3 >= 3) {
-			pr_info("GMAC Rx Info\n");
-			pr_info("============== Time: %d ================\n",
-				timestamp);
-			pr_info("err_cnt3 = %d", err_cnt3);
-			pr_info("gmac2_rx = %d\n", gmac2_rx);
-			pr_info("gdm2_rx_cnt = %d\n", gdm2_rx_cnt);
-			pr_info("==============================\n");
-			if ((atomic_read(&reset_lock) == 0) &&
-			    (atomic_read(&force) == 0)){
-				atomic_inc(&force);
-				schedule_work(&eth->pending_work);
-			}
-		}
-	} else {
+	}else {
 		err_cnt1 = 0;
 		err_cnt2 = 0;
 		err_cnt3 = 0;
 	}
 
 	prev_wdidx = cur_wdidx;
-	prev_gdm2rx = gdm2_rx_cnt;
 	mod_timer(&eth->mtk_dma_monitor_timer, jiffies + 1 * HZ);
 }
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9991-add-read-poll-timeout-function-for-kernel5.4.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9991-add-read-poll-timeout-function-for-kernel5.4.patch
deleted file mode 100755
index c8221b3..0000000
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9991-add-read-poll-timeout-function-for-kernel5.4.patch
+++ /dev/null
@@ -1,73 +0,0 @@
-From 2be6a2d4eaa4db01d3afbd5e9d6fd15494a87f2f Mon Sep 17 00:00:00 2001
-From: Bo Jiao <Bo.Jiao@mediatek.com>
-Date: Fri, 17 Jun 2022 11:23:57 +0800
-Subject: [PATCH 2/8] 9991-add-read-poll-timeout-function-for-kernel5.4
-
----
- include/linux/iopoll.h | 30 +++++++++++++++++++++++++++---
- 1 file changed, 27 insertions(+), 3 deletions(-)
- mode change 100644 => 100755 include/linux/iopoll.h
-
-diff --git a/include/linux/iopoll.h b/include/linux/iopoll.h
-old mode 100644
-new mode 100755
-index 35e15dfd4..d96087008
---- a/include/linux/iopoll.h
-+++ b/include/linux/iopoll.h
-@@ -31,19 +31,22 @@
-  * When available, you'll probably want to use one of the specialized
-  * macros defined below rather than this macro directly.
-  */
--#define readx_poll_timeout(op, addr, val, cond, sleep_us, timeout_us)	\
-+#define read_poll_timeout(op, val, cond, sleep_us, timeout_us, \
-+				sleep_before_read, args...) \
- ({ \
- 	u64 __timeout_us = (timeout_us); \
- 	unsigned long __sleep_us = (sleep_us); \
- 	ktime_t __timeout = ktime_add_us(ktime_get(), __timeout_us); \
- 	might_sleep_if((__sleep_us) != 0); \
-+	if (sleep_before_read && __sleep_us) \
-+		usleep_range((__sleep_us >> 2) + 1, __sleep_us); \
- 	for (;;) { \
--		(val) = op(addr); \
-+		(val) = op(args); \
- 		if (cond) \
- 			break; \
- 		if (__timeout_us && \
- 		    ktime_compare(ktime_get(), __timeout) > 0) { \
--			(val) = op(addr); \
-+			(val) = op(args); \
- 			break; \
- 		} \
- 		if (__sleep_us) \
-@@ -52,6 +55,27 @@
- 	(cond) ? 0 : -ETIMEDOUT; \
- })
- 
-+/**
-+ * readx_poll_timeout - Periodically poll an address until a condition is met or a timeout occurs
-+ * @op: accessor function (takes @addr as its only argument)
-+ * @addr: Address to poll
-+ * @val: Variable to read the value into
-+ * @cond: Break condition (usually involving @val)
-+ * @sleep_us: Maximum time to sleep between reads in us (0
-+ *            tight-loops).  Should be less than ~20ms since usleep_range
-+ *            is used (see Documentation/timers/timers-howto.rst).
-+ * @timeout_us: Timeout in us, 0 means never timeout
-+ *
-+ * Returns 0 on success and -ETIMEDOUT upon a timeout. In either
-+ * case, the last read value at @addr is stored in @val. Must not
-+ * be called from atomic context if sleep_us or timeout_us are used.
-+ *
-+ * When available, you'll probably want to use one of the specialized
-+ * macros defined below rather than this macro directly.
-+ */
-+#define readx_poll_timeout(op, addr, val, cond, sleep_us, timeout_us)	\
-+	read_poll_timeout(op, val, cond, sleep_us, timeout_us, false, addr)
-+
- /**
-  * readx_poll_timeout_atomic - Periodically poll an address until a condition is met or a timeout occurs
-  * @op: accessor function (takes @addr as its only argument)
--- 
-2.18.0
-
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9997-add-wed-rx-support-for-mt7896.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9997-add-wed-rx-support-for-mt7896.patch
index 7f159a0..47c1ab3 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9997-add-wed-rx-support-for-mt7896.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9997-add-wed-rx-support-for-mt7896.patch
@@ -374,8 +374,8 @@
 +
 +	dev->wlan.release_rx_buf(dev);
 +
-+	/* dma_free_coherent(dev->hw->dev, ring_size * sizeof(*desc),
-+			  desc, dev->buf_ring.desc_phys); */
++	dma_free_coherent(dev->hw->dev, ring_size * sizeof(*desc),
++			  desc, dev->rx_buf_ring.desc_phys);
 +}
 +
  static void
@@ -2527,7 +2527,7 @@
 index 0000000..e101f17
 --- /dev/null
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.c
-@@ -0,0 +1,588 @@
+@@ -0,0 +1,581 @@
 +// SPDX-License-Identifier: GPL-2.0-only
 +
 +#include <linux/kernel.h>
@@ -2615,7 +2615,7 @@
 +
 +	spin_lock_bh(&q->lock);
 +
-+	while (q->queued < q->ndesc - 1) {
++	while (q->queued < q->ndesc) {
 +
 +		buf = page_frag_alloc(&q->rx_page, len, GFP_ATOMIC);
 +		if (!buf)
@@ -2955,13 +2955,6 @@
 +		if (!data)
 +			break;
 +
-+		data_len = SKB_WITH_OVERHEAD(q->buf_size);
-+
-+		if (data_len < len) {
-+			skb_free_frag(data);
-+			continue;
-+		}
-+
 +		skb = build_skb(data, q->buf_size);
 +		if (!skb) {
 +			skb_free_frag(data);
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-flow-offload-add-mtkhnat-flow-accounting.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-flow-offload-add-mtkhnat-flow-accounting.patch
new file mode 100644
index 0000000..1a5c255
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-flow-offload-add-mtkhnat-flow-accounting.patch
@@ -0,0 +1,432 @@
+diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+index 4f53794..dc5d050 100644
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+@@ -3944,7 +3944,8 @@ static int mtk_probe(struct platform_device *pdev)
+ 		for (i = 0; i < eth->ppe_num; i++) {
+ 			eth->ppe[i] = mtk_ppe_init(eth,
+ 						   eth->base + MTK_ETH_PPE_BASE + i * 0x400,
+-						   2, eth->soc->hash_way, i);
++						   2, eth->soc->hash_way, i,
++						   eth->soc->has_accounting);
+ 			if (!eth->ppe[i]) {
+ 				err = -ENOMEM;
+ 				goto err_free_dev;
+@@ -4057,6 +4058,7 @@ static const struct mtk_soc_data mt2701_data = {
+ 	.required_clks = MT7623_CLKS_BITMAP,
+ 	.required_pctl = true,
+ 	.has_sram = false,
++	.has_accounting = false,
+ 	.hash_way = 2,
+ 	.offload_version = 2,
+ 	.txrx = {
+@@ -4073,6 +4075,7 @@ static const struct mtk_soc_data mt7621_data = {
+ 	.required_clks = MT7621_CLKS_BITMAP,
+ 	.required_pctl = false,
+ 	.has_sram = false,
++	.has_accounting = false,
+ 	.hash_way = 2,
+ 	.offload_version = 2,
+ 	.txrx = {
+@@ -4090,6 +4093,7 @@ static const struct mtk_soc_data mt7622_data = {
+ 	.required_clks = MT7622_CLKS_BITMAP,
+ 	.required_pctl = false,
+ 	.has_sram = false,
++	.has_accounting = true,
+ 	.hash_way = 2,
+ 	.offload_version = 2,
+ 	.txrx = {
+@@ -4106,6 +4110,7 @@ static const struct mtk_soc_data mt7623_data = {
+ 	.required_clks = MT7623_CLKS_BITMAP,
+ 	.required_pctl = true,
+ 	.has_sram = false,
++	.has_accounting = false,
+ 	.hash_way = 2,
+ 	.offload_version = 2,
+ 	.txrx = {
+@@ -4123,6 +4128,7 @@ static const struct mtk_soc_data mt7629_data = {
+ 	.required_clks = MT7629_CLKS_BITMAP,
+ 	.required_pctl = false,
+ 	.has_sram = false,
++	.has_accounting = true,
+ 	.txrx = {
+ 		.txd_size = sizeof(struct mtk_tx_dma),
+ 		.rxd_size = sizeof(struct mtk_rx_dma),
+@@ -4138,6 +4144,7 @@ static const struct mtk_soc_data mt7986_data = {
+ 	.required_clks = MT7986_CLKS_BITMAP,
+ 	.required_pctl = false,
+ 	.has_sram = true,
++	.has_accounting = true,
+ 	.hash_way = 4,
+ 	.offload_version = 2,
+ 	.txrx = {
+@@ -4155,6 +4162,7 @@ static const struct mtk_soc_data mt7981_data = {
+ 	.required_clks = MT7981_CLKS_BITMAP,
+ 	.required_pctl = false,
+ 	.has_sram = true,
++	.has_accounting = true,
+ 	.hash_way = 4,
+ 	.offload_version = 2,
+ 	.txrx = {
+@@ -4171,6 +4179,7 @@ static const struct mtk_soc_data rt5350_data = {
+ 	.required_clks = MT7628_CLKS_BITMAP,
+ 	.required_pctl = false,
+ 	.has_sram = false,
++	.has_accounting = false,
+ 	.txrx = {
+ 		.txd_size = sizeof(struct mtk_tx_dma),
+ 		.rxd_size = sizeof(struct mtk_rx_dma),
+diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.h b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+index f659633..5e16fa8 100644
+--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+@@ -1213,6 +1213,7 @@ struct mtk_soc_data {
+ 	u8		offload_version;
+ 	netdev_features_t hw_features;
+ 	bool		has_sram;
++	bool		has_accounting;
+ 	struct {
+ 		u32	txd_size;
+ 		u32	rxd_size;
+diff --git a/drivers/net/ethernet/mediatek/mtk_ppe.c b/drivers/net/ethernet/mediatek/mtk_ppe.c
+index 918aa22..8c036cd 100755
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
+@@ -74,6 +74,46 @@ static int mtk_ppe_wait_busy(struct mtk_ppe *ppe)
+ 	return ret;
+ }
+ 
++static int mtk_ppe_mib_wait_busy(struct mtk_ppe *ppe)
++{
++	int ret;
++	u32 val;
++
++	ret = readl_poll_timeout(ppe->base + MTK_PPE_MIB_SER_CR, val,
++				 !(val & MTK_PPE_MIB_SER_CR_ST),
++				 20, MTK_PPE_WAIT_TIMEOUT_US);
++
++	if (ret)
++		dev_err(ppe->dev, "MIB table busy");
++
++	return ret;
++}
++
++int mtk_mib_entry_read(struct mtk_ppe *ppe, u16 index, u64 *bytes, u64 *packets)
++{
++	u32 val, cnt_r0, cnt_r1, cnt_r2;
++	u32 byte_cnt_low, byte_cnt_high, pkt_cnt_low, pkt_cnt_high;
++
++	val = FIELD_PREP(MTK_PPE_MIB_SER_CR_ADDR, index) | MTK_PPE_MIB_SER_CR_ST;
++	ppe_w32(ppe, MTK_PPE_MIB_SER_CR, val);
++
++	if (mtk_ppe_mib_wait_busy(ppe))
++		return -ETIMEDOUT;
++
++	cnt_r0 = readl(ppe->base + MTK_PPE_MIB_SER_R0);
++	cnt_r1 = readl(ppe->base + MTK_PPE_MIB_SER_R1);
++	cnt_r2 = readl(ppe->base + MTK_PPE_MIB_SER_R2);
++
++	byte_cnt_low = FIELD_GET(MTK_PPE_MIB_SER_R0_BYTE_CNT_LOW, cnt_r0);
++	byte_cnt_high = FIELD_GET(MTK_PPE_MIB_SER_R1_BYTE_CNT_HIGH, cnt_r1);
++	pkt_cnt_low = FIELD_GET(MTK_PPE_MIB_SER_R1_PKT_CNT_LOW, cnt_r1);
++	pkt_cnt_high = FIELD_GET(MTK_PPE_MIB_SER_R2_PKT_CNT_HIGH, cnt_r2);
++	*bytes = ((u64)byte_cnt_high << 32) | byte_cnt_low;
++	*packets = (pkt_cnt_high << 16) | pkt_cnt_low;
++
++	return 0;
++}
++
+ static void mtk_ppe_cache_clear(struct mtk_ppe *ppe)
+ {
+ 	ppe_set(ppe, MTK_PPE_CACHE_CTL, MTK_PPE_CACHE_CTL_CLEAR);
+@@ -412,6 +452,14 @@ __mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+ 							      MTK_FOE_STATE_INVALID);
+ 		dma_wmb();
++
++		if (ppe->accounting) {
++			struct mtk_foe_accounting *acct;
++
++			acct = ppe->acct_table + entry->hash * sizeof(*acct);
++			acct->packets = 0;
++			acct->bytes = 0;
++		}
+	}
+ 	entry->hash = 0xffff;
+ 
+ 	if (entry->type != MTK_FLOW_TYPE_L2_SUBFLOW)
+@@ -513,6 +560,16 @@ __mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_foe_entry *entry,
+ 	wmb();
+ 	hwe->ib1 = entry->ib1;
+ 
++	if (ppe->accounting) {
++		int type;
++
++		type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->ib1);
++		if (type >= MTK_PPE_PKT_TYPE_IPV4_DSLITE)
++			hwe->ipv6.ib2 |= MTK_FOE_IB2_MIB_CNT;
++		else
++			hwe->ipv4.ib2 |= MTK_FOE_IB2_MIB_CNT;
++	}
++
+ 	dma_wmb();
+ 
+ 	mtk_ppe_cache_clear(ppe);
+@@ -618,8 +675,6 @@ void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash)
+ 		}
+ 
+ 		if (found || !mtk_flow_entry_match(entry, hwe)) {
+-			if (entry->hash != 0xffff)
+-				entry->hash = 0xffff;
+ 			continue;
+ 		}
+ 
+@@ -676,12 +731,40 @@ int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry)
+ 	return __mtk_foe_entry_idle_time(ppe, entry->data.ib1);
+ }
+ 
+-struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base, int version, int way, int id)
++struct mtk_foe_accounting *mtk_foe_entry_get_mib(struct mtk_ppe *ppe, u32 index, struct mtk_foe_accounting *diff)
++{
++	struct mtk_foe_accounting *acct;
++	int size = sizeof(struct mtk_foe_accounting);
++	u64 bytes, packets;
++
++	if (!ppe->accounting)
++		return NULL;
++
++	if (mtk_mib_entry_read(ppe, index, &bytes, &packets))
++		return NULL;
++
++	acct = ppe->acct_table + index * size;
++
++	acct->bytes += bytes;
++	acct->packets += packets;
++
++	if (diff) {
++		diff->bytes = bytes;
++		diff->packets = packets;
++	}
++
++	return acct;
++}
++
++struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base, int version, int way, int id,
++			     int accounting)
+ {
+ 	struct device *dev = eth->dev;
+ 	struct mtk_foe_entry *foe;
++	struct mtk_mib_entry *mib;
+ 	struct mtk_ppe *ppe;
+ 	struct hlist_head *flow;
++	struct mtk_foe_accounting *acct;
+ 
+ 	ppe = devm_kzalloc(dev, sizeof(*ppe), GFP_KERNEL);
+ 	if (!ppe)
+@@ -698,6 +781,7 @@ struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base, int versio
+ 	ppe->version = version;
+ 	ppe->way = way;
+ 	ppe->id = id;
++	ppe->accounting = accounting;
+ 
+ 	foe = dmam_alloc_coherent(ppe->dev, MTK_PPE_ENTRIES * sizeof(*foe),
+ 				  &ppe->foe_phys, GFP_KERNEL);
+@@ -713,6 +797,24 @@ struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base, int versio
+ 
+ 	ppe->foe_flow = flow;
+ 
++	if (accounting) {
++		mib = dmam_alloc_coherent(ppe->dev, MTK_PPE_ENTRIES * sizeof(*mib),
++				  &ppe->mib_phys, GFP_KERNEL);
++		if (!foe)
++			return NULL;
++
++		memset(mib, 0, MTK_PPE_ENTRIES * sizeof(*mib));
++
++		ppe->mib_table = mib;
++
++		acct = devm_kzalloc(dev, MTK_PPE_ENTRIES * sizeof(*acct),
++				    GFP_KERNEL);
++		if (!acct)
++			return NULL;
++
++		ppe->acct_table = acct;
++	}
++
+ 	return ppe;
+ }
+ 
+@@ -811,6 +949,13 @@ int mtk_ppe_start(struct mtk_ppe *ppe)
+ 	ppe_w32(ppe, MTK_PPE_DEFAULT_CPU_PORT1, 0xcb777);
+ 	ppe_w32(ppe, MTK_PPE_SBW_CTRL, 0x7f);
+ 
++	if (ppe->accounting && ppe->mib_phys) {
++		ppe_w32(ppe, MTK_PPE_MIB_TB_BASE, ppe->mib_phys);
++		ppe_m32(ppe, MTK_PPE_MIB_CFG, MTK_PPE_MIB_CFG_EN, MTK_PPE_MIB_CFG_EN);
++		ppe_m32(ppe, MTK_PPE_MIB_CFG, MTK_PPE_MIB_CFG_RD_CLR, MTK_PPE_MIB_CFG_RD_CLR);
++		ppe_m32(ppe, MTK_PPE_MIB_CACHE_CTL, MTK_PPE_MIB_CACHE_CTL_EN, MTK_PPE_MIB_CFG_RD_CLR);
++	}
++
+ 	return 0;
+ }
+ 
+diff --git a/drivers/net/ethernet/mediatek/mtk_ppe.h b/drivers/net/ethernet/mediatek/mtk_ppe.h
+index 3d6928c..8076e5d 100644
+--- a/drivers/net/ethernet/mediatek/mtk_ppe.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe.h
+@@ -270,6 +270,20 @@ struct mtk_flow_entry {
+ 	unsigned long cookie;
+ };
+ 
++struct mtk_mib_entry {
++	u32	byt_cnt_l;
++	u16	byt_cnt_h;
++	u32	pkt_cnt_l;
++	u8	pkt_cnt_h;
++	u8	_rsv0;
++	u32	_rsv1;
++} __packed;
++
++struct mtk_foe_accounting {
++	u64	bytes;
++	u64	packets;
++};
++
+ struct mtk_ppe {
+ 	struct mtk_eth *eth;
+ 	struct device *dev;
+@@ -277,10 +291,14 @@ struct mtk_ppe {
+ 	int version;
+ 	int id;
+ 	int way;
++	int accounting;
+ 
+ 	struct mtk_foe_entry *foe_table;
+ 	dma_addr_t foe_phys;
+ 
++	struct mtk_mib_entry *mib_table;
++	dma_addr_t mib_phys;
++
+ 	u16 foe_check_time[MTK_PPE_ENTRIES];
+ 	struct hlist_head *foe_flow;
+ 
+@@ -289,7 +307,8 @@ struct mtk_ppe {
+ 	void *acct_table;
+ };
+ 
+-struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base, int version, int way, int id);
++struct mtk_ppe *mtk_ppe_init(struct mtk_eth *eth, void __iomem *base, int version, int way, int id,
++			     int accounting);
+ int mtk_ppe_start(struct mtk_ppe *ppe);
+ int mtk_ppe_stop(struct mtk_ppe *ppe);
+ 
+@@ -340,5 +359,6 @@ int mtk_foe_entry_set_wdma(struct mtk_foe_entry *entry, int wdma_idx, int txq,
+ int mtk_foe_entry_commit(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+ void mtk_foe_entry_clear(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
+ int mtk_foe_entry_idle_time(struct mtk_ppe *ppe, struct mtk_flow_entry *entry);
++struct mtk_foe_accounting *mtk_foe_entry_get_mib(struct mtk_ppe *ppe, u32 index, struct mtk_foe_accounting *diff);
+ 
+ #endif
+diff --git a/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c b/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
+index f4ebe59..d713e2e 100644
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_debugfs.c
+@@ -81,6 +81,7 @@ mtk_ppe_debugfs_foe_show(struct seq_file *m, struct mtk_ppe *ppe, bool bind)
+ 		struct mtk_foe_entry *entry = &ppe->foe_table[i];
+ 		struct mtk_foe_mac_info *l2;
+ 		struct mtk_flow_addr_info ai = {};
++		struct mtk_foe_accounting *acct;
+ 		unsigned char h_source[ETH_ALEN];
+ 		unsigned char h_dest[ETH_ALEN];
+ 		int type, state;
+@@ -94,6 +95,8 @@ mtk_ppe_debugfs_foe_show(struct seq_file *m, struct mtk_ppe *ppe, bool bind)
+ 		if (bind && state != MTK_FOE_STATE_BIND)
+ 			continue;
+ 
++		acct = mtk_foe_entry_get_mib(ppe, i, NULL);
++
+ 		type = FIELD_GET(MTK_FOE_IB1_PACKET_TYPE, entry->ib1);
+ 		seq_printf(m, "%05x %s %7s", i,
+ 			   mtk_foe_entry_state_str(state),
+@@ -154,9 +157,12 @@ mtk_ppe_debugfs_foe_show(struct seq_file *m, struct mtk_ppe *ppe, bool bind)
+ 		*((__be16 *)&h_dest[4]) = htons(l2->dest_mac_lo);
+ 
+ 		seq_printf(m, " eth=%pM->%pM etype=%04x"
+-			      " vlan=%d,%d ib1=%08x ib2=%08x\n",
++			      " vlan=%d,%d ib1=%08x ib2=%08x"
++			      " packets=%lld bytes=%lld\n",
+ 			   h_source, h_dest, ntohs(l2->etype),
+-			   l2->vlan1, l2->vlan2, entry->ib1, ib2);
++			   l2->vlan1, l2->vlan2, entry->ib1, ib2,
++			   acct->packets, acct->bytes
++			   );
+ 	}
+ 
+ 	return 0;
+diff --git a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+index 2f7d76d..f258539 100755
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+@@ -504,6 +504,7 @@ static int
+ mtk_flow_offload_stats(struct mtk_eth *eth, struct flow_cls_offload *f)
+ {
+ 	struct mtk_flow_entry *entry;
++	struct mtk_foe_accounting diff;
+ 	u32 idle;
+ 	int i;
+ 
+@@ -516,6 +517,12 @@ mtk_flow_offload_stats(struct mtk_eth *eth, struct flow_cls_offload *f)
+ 	idle = mtk_foe_entry_idle_time(eth->ppe[i], entry);
+ 	f->stats.lastused = jiffies - idle * HZ;
+ 
++	if (entry->hash != 0xFFFF) {
++		mtk_foe_entry_get_mib(eth->ppe[i], entry->hash, &diff);
++		f->stats.pkts += diff.packets;
++		f->stats.bytes += diff.bytes;
++	}
++
+ 	return 0;
+ }
+ 
+diff --git a/drivers/net/ethernet/mediatek/mtk_ppe_regs.h b/drivers/net/ethernet/mediatek/mtk_ppe_regs.h
+index d319f18..9eb7a0d 100644
+--- a/drivers/net/ethernet/mediatek/mtk_ppe_regs.h
++++ b/drivers/net/ethernet/mediatek/mtk_ppe_regs.h
+@@ -145,6 +146,20 @@ enum {
+ 
+ #define MTK_PPE_MIB_TB_BASE			0x338
+ 
++#define MTK_PPE_MIB_SER_CR			0x33C
++#define MTK_PPE_MIB_SER_CR_ST			BIT(16)
++#define MTK_PPE_MIB_SER_CR_ADDR			GENMASK(13, 0)
++
++#define MTK_PPE_MIB_SER_R0			0x340
++#define MTK_PPE_MIB_SER_R0_BYTE_CNT_LOW		GENMASK(31, 0)
++
++#define MTK_PPE_MIB_SER_R1			0x344
++#define MTK_PPE_MIB_SER_R1_PKT_CNT_LOW		GENMASK(31, 16)
++#define MTK_PPE_MIB_SER_R1_BYTE_CNT_HIGH	GENMASK(15, 0)
++
++#define MTK_PPE_MIB_SER_R2			0x348
++#define MTK_PPE_MIB_SER_R2_PKT_CNT_HIGH		GENMASK(23, 0)
++
+ #define MTK_PPE_MIB_CACHE_CTL			0x350
+ #define MTK_PPE_MIB_CACHE_CTL_EN		BIT(0)
+ #define MTK_PPE_MIB_CACHE_CTL_FLUSH		BIT(2)
+diff --git a/net/netfilter/xt_FLOWOFFLOAD.c b/net/netfilter/xt_FLOWOFFLOAD.c
+index 8547f4a..c175e4d 100644
+--- a/net/netfilter/xt_FLOWOFFLOAD.c
++++ b/net/netfilter/xt_FLOWOFFLOAD.c
+@@ -700,12 +781,12 @@ static int __init xt_flowoffload_tg_init(void)
+ 	if (ret)
+ 		goto cleanup;
+ 
+-	flowtable[1].ft.flags = NF_FLOWTABLE_HW_OFFLOAD;
++	flowtable[1].ft.flags = NF_FLOWTABLE_HW_OFFLOAD | NF_FLOWTABLE_COUNTER;
+ 
+ 	ret = xt_register_target(&offload_tg_reg);
+ 	if (ret)
+ 		goto cleanup2;
+ 
+ 	return 0;
+ 
+ cleanup2:
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/0030-introduce_read_poll_timeout_macro.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/0030-introduce_read_poll_timeout_macro.patch
new file mode 100644
index 0000000..e227037
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/0030-introduce_read_poll_timeout_macro.patch
@@ -0,0 +1,149 @@
+diff --git a/include/linux/iopoll.h b/include/linux/iopoll.h
+index 35e15df..2c8860e 100644
+--- a/include/linux/iopoll.h
++++ b/include/linux/iopoll.h
+@@ -14,36 +14,41 @@
+ #include <linux/io.h>
+ 
+ /**
+- * readx_poll_timeout - Periodically poll an address until a condition is met or a timeout occurs
+- * @op: accessor function (takes @addr as its only argument)
+- * @addr: Address to poll
++ * read_poll_timeout - Periodically poll an address until a condition is
++ *			met or a timeout occurs
++ * @op: accessor function (takes @args as its arguments)
+  * @val: Variable to read the value into
+  * @cond: Break condition (usually involving @val)
+  * @sleep_us: Maximum time to sleep between reads in us (0
+  *            tight-loops).  Should be less than ~20ms since usleep_range
+  *            is used (see Documentation/timers/timers-howto.rst).
+  * @timeout_us: Timeout in us, 0 means never timeout
++ * @sleep_before_read: if it is true, sleep @sleep_us before read.
++ * @args: arguments for @op poll
+  *
+  * Returns 0 on success and -ETIMEDOUT upon a timeout. In either
+- * case, the last read value at @addr is stored in @val. Must not
++ * case, the last read value at @args is stored in @val. Must not
+  * be called from atomic context if sleep_us or timeout_us are used.
+  *
+  * When available, you'll probably want to use one of the specialized
+  * macros defined below rather than this macro directly.
+  */
+-#define readx_poll_timeout(op, addr, val, cond, sleep_us, timeout_us)	\
++#define read_poll_timeout(op, val, cond, sleep_us, timeout_us, \
++				sleep_before_read, args...) \
+ ({ \
+ 	u64 __timeout_us = (timeout_us); \
+ 	unsigned long __sleep_us = (sleep_us); \
+ 	ktime_t __timeout = ktime_add_us(ktime_get(), __timeout_us); \
+ 	might_sleep_if((__sleep_us) != 0); \
++	if (sleep_before_read && __sleep_us) \
++		usleep_range((__sleep_us >> 2) + 1, __sleep_us); \
+ 	for (;;) { \
+-		(val) = op(addr); \
++		(val) = op(args); \
+ 		if (cond) \
+ 			break; \
+ 		if (__timeout_us && \
+ 		    ktime_compare(ktime_get(), __timeout) > 0) { \
+-			(val) = op(addr); \
++			(val) = op(args); \
+ 			break; \
+ 		} \
+ 		if (__sleep_us) \
+@@ -53,42 +58,87 @@
+ })
+ 
+ /**
+- * readx_poll_timeout_atomic - Periodically poll an address until a condition is met or a timeout occurs
+- * @op: accessor function (takes @addr as its only argument)
+- * @addr: Address to poll
++ * read_poll_timeout_atomic - Periodically poll an address until a condition is
++ * 				met or a timeout occurs
++ * @op: accessor function (takes @args as its arguments)
+  * @val: Variable to read the value into
+  * @cond: Break condition (usually involving @val)
+  * @delay_us: Time to udelay between reads in us (0 tight-loops).  Should
+  *            be less than ~10us since udelay is used (see
+  *            Documentation/timers/timers-howto.rst).
+  * @timeout_us: Timeout in us, 0 means never timeout
++ * @delay_before_read: if it is true, delay @delay_us before read.
++ * @args: arguments for @op poll
+  *
+  * Returns 0 on success and -ETIMEDOUT upon a timeout. In either
+- * case, the last read value at @addr is stored in @val.
++ * case, the last read value at @args is stored in @val.
+  *
+  * When available, you'll probably want to use one of the specialized
+  * macros defined below rather than this macro directly.
+  */
+-#define readx_poll_timeout_atomic(op, addr, val, cond, delay_us, timeout_us) \
++#define read_poll_timeout_atomic(op, val, cond, delay_us, timeout_us, \
++					delay_before_read, args...) \
+ ({ \
+ 	u64 __timeout_us = (timeout_us); \
+ 	unsigned long __delay_us = (delay_us); \
+ 	ktime_t __timeout = ktime_add_us(ktime_get(), __timeout_us); \
++	if (delay_before_read && __delay_us) \
++		udelay(__delay_us); \
+ 	for (;;) { \
+-		(val) = op(addr); \
++		(val) = op(args); \
+ 		if (cond) \
+ 			break; \
+ 		if (__timeout_us && \
+ 		    ktime_compare(ktime_get(), __timeout) > 0) { \
+-			(val) = op(addr); \
++			(val) = op(args); \
+ 			break; \
+ 		} \
+ 		if (__delay_us) \
+-			udelay(__delay_us);	\
++			udelay(__delay_us); \
+ 	} \
+ 	(cond) ? 0 : -ETIMEDOUT; \
+ })
+ 
++/**
++ * readx_poll_timeout - Periodically poll an address until a condition is met or a timeout occurs
++ * @op: accessor function (takes @addr as its only argument)
++ * @addr: Address to poll
++ * @val: Variable to read the value into
++ * @cond: Break condition (usually involving @val)
++ * @sleep_us: Maximum time to sleep between reads in us (0
++ *            tight-loops).  Should be less than ~20ms since usleep_range
++ *            is used (see Documentation/timers/timers-howto.rst).
++ * @timeout_us: Timeout in us, 0 means never timeout
++ *
++ * Returns 0 on success and -ETIMEDOUT upon a timeout. In either
++ * case, the last read value at @addr is stored in @val. Must not
++ * be called from atomic context if sleep_us or timeout_us are used.
++ *
++ * When available, you'll probably want to use one of the specialized
++ * macros defined below rather than this macro directly.
++ */
++#define readx_poll_timeout(op, addr, val, cond, sleep_us, timeout_us)	\
++	read_poll_timeout(op, val, cond, sleep_us, timeout_us, false, addr)
++
++/**
++ * readx_poll_timeout_atomic - Periodically poll an address until a condition is met or a timeout occurs
++ * @op: accessor function (takes @addr as its only argument)
++ * @addr: Address to poll
++ * @val: Variable to read the value into
++ * @cond: Break condition (usually involving @val)
++ * @delay_us: Time to udelay between reads in us (0 tight-loops).  Should
++ *            be less than ~10us since udelay is used (see
++ *            Documentation/timers/timers-howto.rst).
++ * @timeout_us: Timeout in us, 0 means never timeout
++ *
++ * Returns 0 on success and -ETIMEDOUT upon a timeout. In either
++ * case, the last read value at @addr is stored in @val.
++ *
++ * When available, you'll probably want to use one of the specialized
++ * macros defined below rather than this macro directly.
++ */
++#define readx_poll_timeout_atomic(op, addr, val, cond, delay_us, timeout_us) \
++	read_poll_timeout_atomic(op, val, cond, delay_us, timeout_us, false, addr)
+ 
+ #define readb_poll_timeout(addr, val, cond, delay_us, timeout_us) \
+ 	readx_poll_timeout(readb, addr, val, cond, delay_us, timeout_us)
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/744-en8801s-gphy-support.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/744-en8801s-gphy-support.patch
index 23a0333..ae9fe7f 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/744-en8801s-gphy-support.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/744-en8801s-gphy-support.patch
@@ -30,9 +30,9 @@
 ===================================================================
 --- /dev/null
 +++ b/drivers/net/phy/en8801s.c
-@@ -0,0 +1,394 @@
+@@ -0,0 +1,434 @@
 +// SPDX-License-Identifier: GPL-2.0
-+/* FILE NAME:  airoha.c
++/* FILE NAME:  en8801s.c
 + * PURPOSE:
 + *      EN8801S phy driver for Linux
 + * NOTES:
@@ -60,23 +60,31 @@
 +#include <linux/phy.h>
 +#include <linux/delay.h>
 +
-+//#include <linux/bitfield.h>
 +#include <linux/uaccess.h>
 +#include <linux/version.h>
 +
 +#include "en8801s.h"
 +
-+/* #define TEST_BOARD */
-+
 +MODULE_DESCRIPTION("Airoha EN8801S PHY drivers");
 +MODULE_AUTHOR("Airoha");
 +MODULE_LICENSE("GPL");
 +
-+static int preSpeed = 0;
++#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0))
++#define phydev_mdio_bus(dev) ((dev)->bus)
++#else
++#define phydev_mdio_bus(dev) ((dev)->mdio.bus)
++#endif
++
++enum {
++    PHY_STATE_DONE = 0,
++    PHY_STATE_INIT = 1,
++    PHY_STATE_PROCESS = 2,
++};
++
 +/************************************************************************
 +*                  F U N C T I O N S
 +************************************************************************/
-+unsigned int mdiobus_write45(struct mii_bus *bus, u32 port, u32 devad, u32 reg, u16 val)
++static unsigned int airoha_cl45_write(struct mii_bus *bus, u32 port, u32 devad, u32 reg, u16 val)
 +{
 +    mdiobus_write(bus, port, MII_MMD_ACC_CTL_REG, devad);
 +    mdiobus_write(bus, port, MII_MMD_ADDR_DATA_REG, reg);
@@ -85,7 +93,7 @@
 +    return 0;
 +}
 +
-+unsigned int mdiobus_read45(struct mii_bus *bus, u32 port, u32 devad, u32 reg, u32 *read_data)
++static unsigned int airoha_cl45_read(struct mii_bus *bus, u32 port, u32 devad, u32 reg, u32 *read_data)
 +{
 +    mdiobus_write(bus, port, MII_MMD_ACC_CTL_REG, devad);
 +    mdiobus_write(bus, port, MII_MMD_ADDR_DATA_REG, reg);
@@ -94,155 +102,148 @@
 +    return 0;
 +}
 +
-+/* Airoha MII read function */
-+unsigned int ecnt_mii_cl22_read(struct mii_bus *ebus, unsigned int phy_addr,unsigned int phy_register,unsigned int *read_data)
++static unsigned int airoha_cl22_read(struct mii_bus *ebus, unsigned int phy_addr, unsigned int phy_register, unsigned int *read_data)
 +{
 +    *read_data = mdiobus_read(ebus, phy_addr, phy_register);
 +    return 0;
 +}
 +
-+/* Airoha MII write function */
-+unsigned int ecnt_mii_cl22_write(struct mii_bus *ebus, unsigned int phy_addr, unsigned int phy_register,unsigned int write_data)
++static unsigned int airoha_cl22_write(struct mii_bus *ebus, unsigned int phy_addr, unsigned int phy_register, unsigned int write_data)
 +{
 +    mdiobus_write(ebus, phy_addr, phy_register, write_data);
 +    return 0;
 +}
 +
-+/* EN8801 PBUS write function */
-+void En8801_PbusRegWr(struct mii_bus *ebus, unsigned long pbus_address, unsigned long pbus_data)
++static void airoha_pbus_write(struct mii_bus *ebus, unsigned long pbus_id, unsigned long pbus_address, unsigned long pbus_data)
 +{
-+    ecnt_mii_cl22_write(ebus, EN8801S_PBUS_PHY_ID, 0x1F, (unsigned int)(pbus_address >> 6));
-+    ecnt_mii_cl22_write(ebus, EN8801S_PBUS_PHY_ID, (unsigned int)((pbus_address >> 2) & 0xf), (unsigned int)(pbus_data & 0xFFFF));
-+    ecnt_mii_cl22_write(ebus, EN8801S_PBUS_PHY_ID, 0x10, (unsigned int)(pbus_data >> 16));
++    airoha_cl22_write(ebus, pbus_id, 0x1F, (unsigned int)(pbus_address >> 6));
++    airoha_cl22_write(ebus, pbus_id, (unsigned int)((pbus_address >> 2) & 0xf), (unsigned int)(pbus_data & 0xFFFF));
++    airoha_cl22_write(ebus, pbus_id, 0x10, (unsigned int)(pbus_data >> 16));
 +    return;
 +}
 +
-+/* EN8801 PBUS read function */
-+unsigned long En8801_PbusRegRd(struct mii_bus *ebus, unsigned long pbus_address)
++static unsigned long airoha_pbus_read(struct mii_bus *ebus, unsigned long pbus_id, unsigned long pbus_address)
 +{
 +    unsigned long pbus_data;
 +    unsigned int pbus_data_low, pbus_data_high;
 +
-+    ecnt_mii_cl22_write(ebus, EN8801S_PBUS_PHY_ID, 0x1F, (unsigned int)(pbus_address >> 6));
-+    ecnt_mii_cl22_read(ebus, EN8801S_PBUS_PHY_ID, (unsigned int)((pbus_address >> 2) & 0xf), &pbus_data_low);
-+    ecnt_mii_cl22_read(ebus, EN8801S_PBUS_PHY_ID, 0x10, &pbus_data_high);
++    airoha_cl22_write(ebus, pbus_id, 0x1F, (unsigned int)(pbus_address >> 6));
++    airoha_cl22_read(ebus, pbus_id, (unsigned int)((pbus_address >> 2) & 0xf), &pbus_data_low);
++    airoha_cl22_read(ebus, pbus_id, 0x10, &pbus_data_high);
 +    pbus_data = (pbus_data_high << 16) + pbus_data_low;
 +    return pbus_data;
 +}
 +
-+/* Use default PBUS_PHY_ID */
-+/* EN8801 PBUS write function */
-+void En8801_varPbusRegWr(struct mii_bus *ebus, unsigned long pbus_id,unsigned long pbus_address, unsigned long pbus_data)
++/* Airoha Token Ring Write function */
++static void airoha_tr_reg_write(struct mii_bus *ebus, unsigned long tr_address, unsigned long tr_data)
 +{
-+    ecnt_mii_cl22_write(ebus, pbus_id, 0x1F, (unsigned int)(pbus_address >> 6));
-+    ecnt_mii_cl22_write(ebus, pbus_id, (unsigned int)((pbus_address >> 2) & 0xf), (unsigned int)(pbus_data & 0xFFFF));
-+    ecnt_mii_cl22_write(ebus, pbus_id, 0x10, (unsigned int)(pbus_data >> 16));
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x1F, 0x52b5);       /* page select */
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x11, (unsigned int)(tr_data & 0xffff));
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x12, (unsigned int)(tr_data >> 16));
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x10, (unsigned int)(tr_address | TrReg_WR));
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x1F, 0x0);          /* page resetore */
 +    return;
 +}
 +
-+/* EN8801 PBUS read function */
-+unsigned long En8801_varPbusRegRd(struct mii_bus *ebus, unsigned long pbus_id, unsigned long pbus_address)
++/* Airoha Token Ring Read function */
++static unsigned long airoha_tr_reg_read(struct mii_bus *ebus, unsigned long tr_address)
 +{
-+    unsigned long pbus_data;
-+    unsigned int pbus_data_low, pbus_data_high;
++    unsigned long tr_data;
++    unsigned int tr_data_low, tr_data_high;
 +
-+    ecnt_mii_cl22_write(ebus, pbus_id, 0x1F, (unsigned int)(pbus_address >> 6));
-+    ecnt_mii_cl22_read(ebus,  pbus_id, (unsigned int)((pbus_address >> 2) & 0xf), &pbus_data_low);
-+    ecnt_mii_cl22_read(ebus,  pbus_id, 0x10, &pbus_data_high);
-+    pbus_data = (pbus_data_high << 16) + pbus_data_low;
-+    return pbus_data;
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x1F, 0x52b5);       /* page select */
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x10, (unsigned int)(tr_address | TrReg_RD));
++    airoha_cl22_read(ebus, EN8801S_MDIO_PHY_ID, 0x11, &tr_data_low);
++    airoha_cl22_read(ebus, EN8801S_MDIO_PHY_ID, 0x12, &tr_data_high);
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x1F, 0x0);          /* page resetore */
++    tr_data = (tr_data_high << 16) + tr_data_low;
++    return tr_data;
 +}
 +
-+/* EN8801 Token Ring Write function */
-+void En8801_TR_RegWr(struct mii_bus *ebus, unsigned long tr_address, unsigned long tr_data)
++static void en8801s_led_init(struct phy_device *phydev)
 +{
-+    ecnt_mii_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x1F, 0x52b5);       /* page select */
-+    ecnt_mii_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x11, (unsigned int)(tr_data & 0xffff));
-+    ecnt_mii_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x12, (unsigned int)(tr_data >> 16));
-+    ecnt_mii_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x10, (unsigned int)(tr_address | TrReg_WR));
-+    ecnt_mii_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x1F, 0x0);          /* page resetore */
-+    return;
++    struct mii_bus *mbus = phydev_mdio_bus(phydev);
++    u32 reg_value;
++
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x186c, 0x3);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0X1870, 0x100);
++    reg_value = (airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1880) & ~(0x3));
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1880, reg_value);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x21, 0x8008);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x22, 0x600);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x23, 0xc00);
++    /* LED0: 10M/100M */
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x24, 0x8006);
++    /* LED0: blink 10M/100M Tx/Rx */
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x25, 0x3c);
++    /* LED1: 1000M */
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x26, 0x8001);
++    /* LED1: blink 1000M Tx/Rx */
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x27, 0x3);
 +}
 +
-+/* EN8801 Token Ring Read function */
-+unsigned long En8801_TR_RegRd(struct mii_bus *ebus, unsigned long tr_address)
++static int en8801s_phy_process(struct phy_device *phydev)
 +{
-+    unsigned long tr_data;
-+    unsigned int tr_data_low, tr_data_high;
++    struct mii_bus *mbus = phydev_mdio_bus(phydev);
++    u32 reg_value = 0;
 +
-+    ecnt_mii_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x1F, 0x52b5);       /* page select */
-+    ecnt_mii_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x10, (unsigned int)(tr_address | TrReg_RD));
-+    ecnt_mii_cl22_read(ebus, EN8801S_MDIO_PHY_ID, 0x11, &tr_data_low);
-+    ecnt_mii_cl22_read(ebus, EN8801S_MDIO_PHY_ID, 0x12, &tr_data_high);
-+    ecnt_mii_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x1F, 0x0);          /* page resetore */
-+    tr_data = (tr_data_high << 16) + tr_data_low;
-+    return tr_data;
++    reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x19e0);
++    reg_value |= (1 << 0);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x19e0, reg_value);
++    reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x19e0);
++    reg_value &= ~(1 << 0);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x19e0, reg_value);
++    return 0;
 +}
 +
-+static int en8801s_config_init(struct phy_device *phydev)
++static int en8801s_phase1_init(struct phy_device *phydev)
 +{
-+    gephy_all_REG_LpiReg1Ch      GPHY_RG_LPI_1C;
-+    gephy_all_REG_dev1Eh_reg324h GPHY_RG_1E_324;
-+    gephy_all_REG_dev1Eh_reg012h GPHY_RG_1E_012;
-+    gephy_all_REG_dev1Eh_reg017h GPHY_RG_1E_017;
 +    unsigned long pbus_data;
 +    unsigned int pbusAddress;
 +    u32 reg_value;
 +    int retry;
-+    struct mii_bus *mbus;
++    struct mii_bus *mbus = phydev_mdio_bus(phydev);
 +
-+    #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0))
-+    mbus = phydev->bus;
-+    #else
-+    mbus = phydev->mdio.bus;
-+    #endif
++    msleep(1500);
 +
 +    pbusAddress = EN8801S_PBUS_DEFAULT_ID;
 +    retry = MAX_OUI_CHECK;
-+    while(1)
-+    {
-+        pbus_data = En8801_varPbusRegRd(mbus, pbusAddress, EN8801S_RG_ETHER_PHY_OUI);      /* PHY OUI */
-+        if(EN8801S_PBUS_OUI == pbus_data)
-+        {
-+            pbus_data = En8801_varPbusRegRd(mbus, pbusAddress, EN8801S_RG_SMI_ADDR);       /* SMI ADDR */
-+            pbus_data = (pbus_data & 0xffff0000) | (unsigned long)(EN8801S_PBUS_PHY_ID << 8) | (unsigned long)(EN8801S_MDIO_PHY_ID );
-+            printk("[Airoha] EN8801S SMI_ADDR=%lx (renew)\n", pbus_data);
-+            En8801_varPbusRegWr(mbus, pbusAddress, EN8801S_RG_SMI_ADDR, pbus_data);
-+            En8801_varPbusRegWr(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_BUCK_CTL, 0x03);
++    while (1) {
++        pbus_data = airoha_pbus_read(mbus, pbusAddress, EN8801S_RG_ETHER_PHY_OUI);      /* PHY OUI */
++        if (EN8801S_PBUS_OUI == pbus_data) {
++            pbus_data = airoha_pbus_read(mbus, pbusAddress, EN8801S_RG_SMI_ADDR);       /* SMI ADDR */
++            pbus_data = (pbus_data & 0xffff0000) | (unsigned long)(EN8801S_PBUS_PHY_ID << 8) | (unsigned long)(EN8801S_MDIO_PHY_ID);
++            phydev_info(phydev, "SMI_ADDR=%lx (renew)\n", pbus_data);
++            airoha_pbus_write(mbus, pbusAddress, EN8801S_RG_SMI_ADDR, pbus_data);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_BUCK_CTL, 0x03);
 +            mdelay(10);
 +            break;
-+        }
-+        else
-+        {
++        } else {
 +            pbusAddress = EN8801S_PBUS_PHY_ID;
 +        }
 +        retry --;
-+        if (0 == retry)
-+        {
-+            printk("[Airoha] EN8801S probe fail !\n");
++        if (0 == retry) {
++            phydev_err(phydev, "Probe fail !\n");
 +            return 0;
 +        }
 +    }
 +
-+    reg_value = (En8801_PbusRegRd(mbus, EN8801S_RG_LTR_CTL) & 0xfffffffc) | 0x10 | (EN8801S_RX_POLARITY << 1) | EN8801S_TX_POLARITY;
-+    En8801_PbusRegWr(mbus, 0xcf8, reg_value);
++    reg_value = (airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_LTR_CTL) & 0xfffffffc) | 0x10 | (EN8801S_RX_POLARITY << 1) | EN8801S_TX_POLARITY;
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_LTR_CTL, reg_value);
 +    mdelay(10);
 +    reg_value &= 0xffffffef;
-+    En8801_PbusRegWr(mbus, 0xcf8, reg_value);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_LTR_CTL, reg_value);
 +
 +    retry = MAX_RETRY;
-+    while (1)
-+    {
++    while (1) {
 +        mdelay(10);
 +        reg_value = phy_read(phydev, MII_PHYSID2);
-+        if (reg_value == EN8801S_PHY_ID2)
-+        {
++        if (reg_value == EN8801S_PHY_ID2) {
 +            break;    /* wait GPHY ready */
 +        }
 +        retry--;
-+        if (0 == retry)
-+        {
-+            printk("[Airoha] EN8801S initialize fail !\n");
++        if (0 == retry) {
++            phydev_err(phydev, "Initialize fail !\n");
 +            return 0;
 +        }
 +    }
@@ -251,170 +252,210 @@
 +    reg_value |= BMCR_RESET;
 +    phy_write(phydev, MII_BMCR, reg_value);
 +    retry = MAX_RETRY;
-+    do
-+    {
++    do {
 +        mdelay(10);
 +        reg_value = phy_read(phydev, MII_BMCR);
 +        retry--;
-+        if (0 == retry)
-+        {
-+            printk("[Airoha] EN8801S reset fail !\n");
++        if (0 == retry) {
++            phydev_err(phydev, "Reset fail !\n");
 +            return 0;
 +        }
 +    } while (reg_value & BMCR_RESET);
 +
++    phydev->dev_flags = PHY_STATE_INIT;
++
-+    En8801_PbusRegWr(mbus, 0x0600, 0x0c000c00);
-+    En8801_PbusRegWr(mbus, 0x10, 0xD801);
-+    En8801_PbusRegWr(mbus, 0x0,  0x9140);
++    phydev_info(phydev, "Phase1 initialize OK ! (%s)\n", EN8801S_DRIVER_VERSION);
++    return 0;
++}
 +
-+    En8801_PbusRegWr(mbus, 0x0A14, 0x0003);
-+    En8801_PbusRegWr(mbus, 0x0600, 0x0c000c00);
++static int en8801s_phase2_init(struct phy_device *phydev)
++{
++    gephy_all_REG_LpiReg1Ch      GPHY_RG_LPI_1C;
++    gephy_all_REG_dev1Eh_reg324h GPHY_RG_1E_324;
++    gephy_all_REG_dev1Eh_reg012h GPHY_RG_1E_012;
++    gephy_all_REG_dev1Eh_reg017h GPHY_RG_1E_017;
++    unsigned long pbus_data;
++    u32 reg_value;
++    int retry;
++    struct mii_bus *mbus = phydev_mdio_bus(phydev);
++
++    reg_value = (airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_LTR_CTL) & 0xfffffffc) | 0x10 | (EN8801S_RX_POLARITY << 1) | EN8801S_TX_POLARITY;
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_LTR_CTL, reg_value);
++    mdelay(10);
++    reg_value &= 0xffffffef;
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_LTR_CTL, reg_value);
++
++    pbus_data = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1690);
++    pbus_data |= (1 << 31);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1690, pbus_data);
++
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0600, 0x0c000c00);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x10, 0xD801);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0,  0x9140);
++
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0A14, 0x0003);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0600, 0x0c000c00);
 +    /* Set FCM control */
-+    En8801_PbusRegWr(mbus, 0x1404, 0x004b);
-+    En8801_PbusRegWr(mbus, 0x140c, 0x0007);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1404, 0x004b);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x140c, 0x0007);
++
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x142c, 0x05050505);
++    pbus_data = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1440);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1440, pbus_data & ~(1 << 11));
 +    /* Set GPHY Perfomance*/
 +    /* Token Ring */
-+    En8801_TR_RegWr(mbus, RgAddr_PMA_01h,     0x6FB90A);
-+    En8801_TR_RegWr(mbus, RgAddr_PMA_18h,     0x0E2F00);
-+    En8801_TR_RegWr(mbus, RgAddr_DSPF_06h,    0x2EBAEF);
-+    En8801_TR_RegWr(mbus, RgAddr_DSPF_11h,    0x040001);
-+    En8801_TR_RegWr(mbus, RgAddr_DSPF_03h,    0x000004);
-+    En8801_TR_RegWr(mbus, RgAddr_DSPF_1Ch,    0x003210);
-+    En8801_TR_RegWr(mbus, RgAddr_DSPF_14h,    0x00024A);
-+    En8801_TR_RegWr(mbus, RgAddr_DSPF_0Ch,    0x00704D);
-+    En8801_TR_RegWr(mbus, RgAddr_DSPF_0Dh,    0x02314F);
-+    En8801_TR_RegWr(mbus, RgAddr_DSPF_10h,    0x005010);
-+    En8801_TR_RegWr(mbus, RgAddr_DSPF_0Fh,    0x003028);
-+    En8801_TR_RegWr(mbus, RgAddr_TR_26h,      0x444444);
-+    En8801_TR_RegWr(mbus, RgAddr_R1000DEC_15h,0x0055A0);
++    airoha_tr_reg_write(mbus, RgAddr_PMA_01h,     0x6FB90A);
++    airoha_tr_reg_write(mbus, RgAddr_PMA_18h,     0x0E2F00);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_06h,    0x2EBAEF);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_11h,    0x040001);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_03h,    0x000004);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_1Ch,    0x003210);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_14h,    0x00024A);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_0Ch,    0x00704D);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_0Dh,    0x02314F);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_10h,    0x005010);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_0Fh,    0x003028);
++    airoha_tr_reg_write(mbus, RgAddr_TR_26h,      0x444444);
++    airoha_tr_reg_write(mbus, RgAddr_R1000DEC_15h, 0x0055A0);
 +    /* CL22 & CL45 */
 +    phy_write(phydev, 0x1f, 0x03);
 +    GPHY_RG_LPI_1C.DATA = phy_read(phydev, RgAddr_LpiReg1Ch);
 +    GPHY_RG_LPI_1C.DataBitField.smi_deton_th = 0x0C;
 +    phy_write(phydev, RgAddr_LpiReg1Ch, GPHY_RG_LPI_1C.DATA);
 +    phy_write(phydev, 0x1f, 0x0);
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x122, 0xffff);
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x234, 0x0180);
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x238, 0x0120);
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x120, 0x9014);
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x239, 0x0117);
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x14A, 0xEE20);
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x19B, 0x0111);
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1F, 0x268, 0x07F4);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x122, 0xffff);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x234, 0x0180);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x238, 0x0120);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x120, 0x9014);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x239, 0x0117);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x14A, 0xEE20);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x19B, 0x0111);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1F, 0x268, 0x07F4);
 +
-+    mdiobus_read45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x324, &reg_value);
-+    GPHY_RG_1E_324.DATA=(u16)reg_value;
++    airoha_cl45_read(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x324, &reg_value);
++    GPHY_RG_1E_324.DATA = (u16)reg_value;
 +    GPHY_RG_1E_324.DataBitField.smi_det_deglitch_off = 0;
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x324, (u32)GPHY_RG_1E_324.DATA);
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x19E, 0xC2);
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x013, 0x0);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x324, (u32)GPHY_RG_1E_324.DATA);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x19E, 0xC2);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x013, 0x0);
 +
 +    /* EFUSE */
-+    En8801_PbusRegWr(mbus, 0x1C08, 0x40000040);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1C08, 0x40000040);
 +    retry = MAX_RETRY;
-+    while (0 != retry)
-+    {
++    while (0 != retry) {
 +        mdelay(1);
-+        reg_value = En8801_PbusRegRd(mbus, 0x1C08);
-+        if ((reg_value & (1 << 30)) == 0)
-+        {
++        reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1C08);
++        if ((reg_value & (1 << 30)) == 0) {
 +            break;
 +        }
 +        retry--;
 +    }
-+    reg_value = En8801_PbusRegRd(mbus, 0x1C38);          /* RAW#2 */
++    reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1C38);          /* RAW#2 */
 +    GPHY_RG_1E_012.DataBitField.da_tx_i2mpb_a_tbt = reg_value & 0x03f;
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x12, (u32)GPHY_RG_1E_012.DATA);
-+    GPHY_RG_1E_017.DataBitField.da_tx_i2mpb_b_tbt=(reg_value >> 8) & 0x03f;
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x12, (u32)GPHY_RG_1E_017.DATA);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x12, (u32)GPHY_RG_1E_012.DATA);
++    GPHY_RG_1E_017.DataBitField.da_tx_i2mpb_b_tbt = (reg_value >> 8) & 0x03f;
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x12, (u32)GPHY_RG_1E_017.DATA);
 +
-+    En8801_PbusRegWr(mbus, 0x1C08, 0x40400040);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1C08, 0x40400040);
 +    retry = MAX_RETRY;
-+    while (0 != retry)
-+    {
++    while (0 != retry) {
 +        mdelay(1);
-+        reg_value = En8801_PbusRegRd(mbus, 0x1C08);
-+        if ((reg_value & (1 << 30)) == 0)
-+        {
++        reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1C08);
++        if ((reg_value & (1 << 30)) == 0) {
 +            break;
 +        }
 +        retry--;
 +    }
-+    reg_value = En8801_PbusRegRd(mbus, 0x1C30);          /* RAW#16 */
++    reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1C30);          /* RAW#16 */
 +    GPHY_RG_1E_324.DataBitField.smi_det_deglitch_off = (reg_value >> 12) & 0x01;
-+    mdiobus_write45(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x324, (u32)GPHY_RG_1E_324.DATA);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x324, (u32)GPHY_RG_1E_324.DATA);
 +
-+    printk("[Airoha] EN8801S initialize OK ! (1.0.5)\n");
++    en8801s_led_init(phydev);
++
++    phydev_info(phydev, "Phase2 initialize OK !\n");
 +    return 0;
 +}
 +
 +static int en8801s_read_status(struct phy_device *phydev)
 +{
-+    int ret;
-+    struct mii_bus *mbus;
-+
-+    #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0))
-+    mbus = phydev->bus;
-+    #else
-+    mbus = phydev->mdio.bus;
-+    #endif
++    int ret, preSpeed = phydev->speed;
++    struct mii_bus *mbus = phydev_mdio_bus(phydev);
++    u32 reg_value;
 +
 +    ret = genphy_read_status(phydev);
-+    if (LINK_DOWN == phydev->link) preSpeed =0;
++    if (LINK_DOWN == phydev->link) preSpeed = phydev->speed = 0;
 +
-+    if ((preSpeed != phydev->speed) && (LINK_UP == phydev->link))
-+    {
++    if (phydev->dev_flags == PHY_STATE_PROCESS) {
++        en8801s_phy_process(phydev);
++        phydev->dev_flags = PHY_STATE_DONE;
++    }
++
++    if ((preSpeed != phydev->speed) && (LINK_UP == phydev->link)) {
 +        preSpeed = phydev->speed;
-+        En8801_PbusRegWr(mbus, 0x0600, 0x0c000c00);
-+        if (SPEED_1000 == preSpeed)
-+        {
-+            En8801_PbusRegWr(mbus, 0x10, 0xD801);
-+            En8801_PbusRegWr(mbus, 0x0,  0x9140);
 +
-+            En8801_PbusRegWr(mbus, 0x0A14, 0x0003);
-+            En8801_PbusRegWr(mbus, 0x0600, 0x0c000c00);
-+            mdelay(2);      /* delay 2 ms */
-+            En8801_PbusRegWr(mbus, 0x1404, 0x004b);
-+            En8801_PbusRegWr(mbus, 0x140c, 0x0007);
++        if (phydev->dev_flags == PHY_STATE_INIT) {
++            en8801s_phase2_init(phydev);
++            phydev->dev_flags = PHY_STATE_PROCESS;
 +        }
-+        else if (SPEED_100 == preSpeed)
-+        {
-+            En8801_PbusRegWr(mbus, 0x10, 0xD401);
-+            En8801_PbusRegWr(mbus, 0x0,  0x9140);
 +
-+            En8801_PbusRegWr(mbus, 0x0A14, 0x0007);
-+            En8801_PbusRegWr(mbus, 0x0600, 0x0c11);
-+            mdelay(2);      /* delay 2 ms */
-+            En8801_PbusRegWr(mbus, 0x1404, 0x0027);
-+            En8801_PbusRegWr(mbus, 0x140c, 0x0007);
++        if (preSpeed == SPEED_10) {
++            reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1694);
++            reg_value |= (1 << 31);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1694, reg_value);
++            phydev->dev_flags = PHY_STATE_PROCESS;
++        } else {
++            reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1694);
++            reg_value &= ~(1 << 31);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1694, reg_value);
++            phydev->dev_flags = PHY_STATE_PROCESS;
 +        }
-+        else if (SPEED_10 == preSpeed)
-+        {
-+            En8801_PbusRegWr(mbus, 0x10, 0xD001);
-+            En8801_PbusRegWr(mbus, 0x0,  0x9140);
++
++        airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0600, 0x0c000c00);
++        if (SPEED_1000 == preSpeed) {
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x10, 0xD801);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0,  0x9140);
 +
-+            En8801_PbusRegWr(mbus, 0x0A14, 0x000b);
-+            En8801_PbusRegWr(mbus, 0x0600, 0x0c11);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0A14, 0x0003);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0600, 0x0c000c00);
 +            mdelay(2);      /* delay 2 ms */
-+            En8801_PbusRegWr(mbus, 0x1404, 0x0027);
-+            En8801_PbusRegWr(mbus, 0x140c, 0x0007);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1404, 0x004b);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x140c, 0x0007);
++        } else if (SPEED_100 == preSpeed) {
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x10, 0xD401);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0,  0x9140);
++
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0A14, 0x0007);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0600, 0x0c11);
++            mdelay(2);      /* delay 2 ms */
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1404, 0x0027);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x140c, 0x0007);
++        } else if (SPEED_10 == preSpeed) {
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x10, 0xD001);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0,  0x9140);
++
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0A14, 0x000b);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0600, 0x0c11);
++            mdelay(2);      /* delay 2 ms */
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1404, 0x0027);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x140c, 0x0007);
 +        }
 +    }
 +    return ret;
 +}
 +
 +static struct phy_driver Airoha_driver[] = {
-+{
-+    .phy_id         = EN8801S_PHY_ID,
-+    .name           = "Airoha EN8801S",
-+    .phy_id_mask    = 0x0ffffff0,
-+    .features       = PHY_GBIT_FEATURES,
-+    .config_init    = en8801s_config_init,
-+    .config_aneg    = genphy_config_aneg,
-+    .read_status    = en8801s_read_status,
-+    .suspend        = genphy_suspend,
-+    .resume         = genphy_resume,
-+} };
++    {
++        .phy_id         = EN8801S_PHY_ID,
++        .name           = "Airoha EN8801S",
++        .phy_id_mask    = 0x0ffffff0,
++        .features       = PHY_GBIT_FEATURES,
++        .config_init    = en8801s_phase1_init,
++        .config_aneg    = genphy_config_aneg,
++        .read_status    = en8801s_read_status,
++        .suspend        = genphy_suspend,
++        .resume         = genphy_resume,
++    }
++};
 +
 +module_phy_driver(Airoha_driver);
 +
@@ -424,14 +465,13 @@
 +};
 +
 +MODULE_DEVICE_TABLE(mdio, Airoha_tbl);
-+MODULE_LICENSE("GPL");
 Index: drivers/net/phy/en8801s.h
 ===================================================================
 --- /dev/null
 +++ b/drivers/net/phy/en8801s.h
-@@ -0,0 +1,153 @@
+@@ -0,0 +1,158 @@
 +// SPDX-License-Identifier: GPL-2.0
-+/* FILE NAME:  airoha.h
++/* FILE NAME:  en8801s.h
 + * PURPOSE:
 + *      Define EN8801S driver function
 + *
@@ -439,11 +479,13 @@
 + *
 + */
 +
-+#ifndef __AIROHA_H
-+#define __AIROHA_H
++#ifndef __EN8801S_H
++#define __EN8801S_H
 +
 +/* NAMING DECLARATIONS
 + */
++#define EN8801S_DRIVER_VERSION  "1.1.0"
++
 +#define PHY_ADDRESS_RANGE       0x18
 +#define EN8801S_PBUS_DEFAULT_ID 0x1e
 +#define EN8801S_MDIO_PHY_ID     0x18       /* Range PHY_ADDRESS_RANGE .. 0x1e */
@@ -468,12 +510,15 @@
 +#define LINK_UP                 1
 +#define LINK_DOWN               0
 +
++//#define TEST_BOARD
 +#if defined(TEST_BOARD)
++/* SFP sample for verification */
 +#define EN8801S_TX_POLARITY     1
 +#define EN8801S_RX_POLARITY     0
 +#else
++/* chip on board */
 +#define EN8801S_TX_POLARITY     0
-+#define EN8801S_RX_POLARITY     1 /* The ping default assignment is set to 1 */
++#define EN8801S_RX_POLARITY     1       /* The pin default assignment is set to 1 */
 +#endif
 +
 +#define MAX_RETRY               5
@@ -582,4 +627,4 @@
 +    u16 DATA;
 +} gephy_all_REG_dev1Eh_reg017h, *Pgephy_all_REG_dev1Eh_reg017h;
 +
-+#endif /* End of __AIROHA_H */
++#endif /* End of __EN8801S_H */
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/745-en8801sc-gphy-support.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/745-en8801sc-gphy-support.patch
new file mode 100644
index 0000000..38d6d4c
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/745-en8801sc-gphy-support.patch
@@ -0,0 +1,645 @@
+Index: drivers/net/phy/en8801sc.c
+===================================================================
+--- /dev/null
++++ b/drivers/net/phy/en8801sc.c
+@@ -0,0 +1,449 @@
++// SPDX-License-Identifier: GPL-2.0
++/* FILE NAME:  en8801sc.c
++ * PURPOSE:
++ *      EN8801S phy driver for Linux
++ * NOTES:
++ *
++ */
++
++/* INCLUDE FILE DECLARATIONS
++ */
++
++#include <linux/kernel.h>
++#include <linux/string.h>
++#include <linux/errno.h>
++#include <linux/unistd.h>
++#include <linux/interrupt.h>
++#include <linux/init.h>
++#include <linux/delay.h>
++#include <linux/netdevice.h>
++#include <linux/etherdevice.h>
++#include <linux/skbuff.h>
++#include <linux/spinlock.h>
++#include <linux/mm.h>
++#include <linux/module.h>
++#include <linux/mii.h>
++#include <linux/ethtool.h>
++#include <linux/phy.h>
++#include <linux/delay.h>
++
++#include <linux/uaccess.h>
++#include <linux/version.h>
++
++#include "en8801sc.h"
++
++MODULE_DESCRIPTION("Airoha EN8801S PHY drivers for MT7981 SoC");
++MODULE_AUTHOR("Airoha");
++MODULE_LICENSE("GPL");
++
++#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0))
++#define phydev_mdio_bus(dev) ((dev)->bus)
++#else
++#define phydev_mdio_bus(dev) ((dev)->mdio.bus)
++#endif
++
++enum {
++    PHY_STATE_DONE = 0,
++    PHY_STATE_INIT = 1,
++    PHY_STATE_PROCESS = 2,
++};
++
++static int preSpeed = 0;
++/************************************************************************
++*                  F U N C T I O N S
++************************************************************************/
++unsigned int airoha_cl45_write(struct mii_bus *bus, u32 port, u32 devad, u32 reg, u16 val)
++{
++    mdiobus_write(bus, port, MII_MMD_ACC_CTL_REG, devad);
++    mdiobus_write(bus, port, MII_MMD_ADDR_DATA_REG, reg);
++    mdiobus_write(bus, port, MII_MMD_ACC_CTL_REG, MMD_OP_MODE_DATA | devad);
++    mdiobus_write(bus, port, MII_MMD_ADDR_DATA_REG, val);
++    return 0;
++}
++
++unsigned int airoha_cl45_read(struct mii_bus *bus, u32 port, u32 devad, u32 reg, u32 *read_data)
++{
++    mdiobus_write(bus, port, MII_MMD_ACC_CTL_REG, devad);
++    mdiobus_write(bus, port, MII_MMD_ADDR_DATA_REG, reg);
++    mdiobus_write(bus, port, MII_MMD_ACC_CTL_REG, MMD_OP_MODE_DATA | devad);
++    *read_data = mdiobus_read(bus, port, MII_MMD_ADDR_DATA_REG);
++    return 0;
++}
++
++unsigned int airoha_cl22_read(struct mii_bus *ebus, unsigned int phy_addr,unsigned int phy_register,unsigned int *read_data)
++{
++    *read_data = mdiobus_read(ebus, phy_addr, phy_register);
++    return 0;
++}
++
++unsigned int airoha_cl22_write(struct mii_bus *ebus, unsigned int phy_addr, unsigned int phy_register,unsigned int write_data)
++{
++    mdiobus_write(ebus, phy_addr, phy_register, write_data);
++    return 0;
++}
++
++void airoha_pbus_write(struct mii_bus *ebus, unsigned long pbus_id, unsigned long pbus_address, unsigned long pbus_data)
++{
++    airoha_cl22_write(ebus, pbus_id, 0x1F, (unsigned int)(pbus_address >> 6));
++    airoha_cl22_write(ebus, pbus_id, (unsigned int)((pbus_address >> 2) & 0xf), (unsigned int)(pbus_data & 0xFFFF));
++    airoha_cl22_write(ebus, pbus_id, 0x10, (unsigned int)(pbus_data >> 16));
++    return;
++}
++
++unsigned long airoha_pbus_read(struct mii_bus *ebus, unsigned long pbus_id, unsigned long pbus_address)
++{
++    unsigned long pbus_data;
++    unsigned int pbus_data_low, pbus_data_high;
++
++    airoha_cl22_write(ebus, pbus_id, 0x1F, (unsigned int)(pbus_address >> 6));
++    airoha_cl22_read(ebus, pbus_id, (unsigned int)((pbus_address >> 2) & 0xf), &pbus_data_low);
++    airoha_cl22_read(ebus, pbus_id, 0x10, &pbus_data_high);
++    pbus_data = (pbus_data_high << 16) + pbus_data_low;
++    return pbus_data;
++}
++
++/* Airoha Token Ring Write function */
++void airoha_tr_reg_write(struct mii_bus *ebus, unsigned long tr_address, unsigned long tr_data)
++{
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x1F, 0x52b5);       /* page select */
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x11, (unsigned int)(tr_data & 0xffff));
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x12, (unsigned int)(tr_data >> 16));
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x10, (unsigned int)(tr_address | TrReg_WR));
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x1F, 0x0);          /* page resetore */
++    return;
++}
++
++/* Airoha Token Ring Read function */
++unsigned long airoha_tr_reg_read(struct mii_bus *ebus, unsigned long tr_address)
++{
++    unsigned long tr_data;
++    unsigned int tr_data_low, tr_data_high;
++
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x1F, 0x52b5);       /* page select */
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x10, (unsigned int)(tr_address | TrReg_RD));
++    airoha_cl22_read(ebus, EN8801S_MDIO_PHY_ID, 0x11, &tr_data_low);
++    airoha_cl22_read(ebus, EN8801S_MDIO_PHY_ID, 0x12, &tr_data_high);
++    airoha_cl22_write(ebus, EN8801S_MDIO_PHY_ID, 0x1F, 0x0);          /* page resetore */
++    tr_data = (tr_data_high << 16) + tr_data_low;
++    return tr_data;
++}
++
++void en8801s_led_init(struct mii_bus *mbus)
++{
++    u32 reg_value;
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x186c, 0x3);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0X1870, 0x100);
++    reg_value = (airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1880) & ~(0x3));
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1880, reg_value);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x21, 0x8008);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x22, 0x600);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x23, 0xc00);
++    /* LED0: 10M/100M */
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x24, 0x8006);
++    /* LED0: blink 10M/100M Tx/Rx */
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x25, 0x3c);
++    /* LED1: 1000M */
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x26, 0x8001);
++    /* LED1: blink 1000M Tx/Rx */
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1f, 0x27, 0x3);
++}
++
++static int en8801s_phy_process(struct phy_device *phydev)
++{
++    struct mii_bus *mbus = phydev_mdio_bus(phydev);
++    u32 reg_value = 0;
++
++    reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x19e0);
++    reg_value |= (1 << 0);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x19e0, reg_value);
++    reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x19e0);
++    reg_value &= ~(1 << 0);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x19e0, reg_value);
++    return 0;
++}
++
++static int en8801s_phase1_init(struct phy_device *phydev)
++{
++    unsigned long pbus_data;
++    unsigned int pbusAddress;
++    u32 reg_value;
++    int retry;
++    struct mii_bus *mbus = phydev_mdio_bus(phydev);
++
++    msleep(1500);
++
++    pbusAddress = EN8801S_PBUS_DEFAULT_ID;
++    retry = MAX_OUI_CHECK;
++    while(1)
++    {
++        pbus_data = airoha_pbus_read(mbus, pbusAddress, EN8801S_RG_ETHER_PHY_OUI);      /* PHY OUI */
++        if(EN8801S_PBUS_OUI == pbus_data)
++        {
++            pbus_data = airoha_pbus_read(mbus, pbusAddress, EN8801S_RG_SMI_ADDR);       /* SMI ADDR */
++            pbus_data = (pbus_data & 0xffff0000) | (unsigned long)(EN8801S_PBUS_PHY_ID << 8) | (unsigned long)(EN8801S_MDIO_PHY_ID );
++            printk("[Airoha] EN8801S SMI_ADDR=%lx (renew)\n", pbus_data);
++            airoha_pbus_write(mbus, pbusAddress, EN8801S_RG_SMI_ADDR, pbus_data);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_BUCK_CTL, 0x03);
++            mdelay(10);
++            break;
++        }
++        else
++        {
++            pbusAddress = EN8801S_PBUS_PHY_ID;
++        }
++        retry --;
++        if (0 == retry)
++        {
++            printk("[Airoha] EN8801S probe fail !\n");
++            return 0;
++        }
++    }
++
++    reg_value = (airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_LTR_CTL) & 0xfffffffc) | 0x10 | (EN8801S_RX_POLARITY << 1) | EN8801S_TX_POLARITY;
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_LTR_CTL, reg_value);
++    mdelay(10);
++    reg_value &= 0xffffffef;
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_LTR_CTL, reg_value);
++
++    retry = MAX_RETRY;
++    while (1)
++    {
++        mdelay(10);
++        reg_value = phy_read(phydev, MII_PHYSID2);
++        if (reg_value == EN8801S_PHY_ID2)
++        {
++            break;    /* wait GPHY ready */
++        }
++        retry--;
++        if (0 == retry)
++        {
++            printk("[Airoha] EN8801S initialize fail !\n");
++            return 0;
++        }
++    }
++    /* Software Reset PHY */
++    reg_value = phy_read(phydev, MII_BMCR);
++    reg_value |= BMCR_RESET;
++    phy_write(phydev, MII_BMCR, reg_value);
++    retry = MAX_RETRY;
++    do
++    {
++        mdelay(10);
++        reg_value = phy_read(phydev, MII_BMCR);
++        retry--;
++        if (0 == retry)
++        {
++            printk("[Airoha] EN8801S reset fail !\n");
++            return 0;
++        }
++    } while (reg_value & BMCR_RESET);
++
++    printk("[Airoha] EN8801S Phase1 initialize OK ! (%s)\n", EN8801S_DRIVER_VERSION);
++    return 0;
++}
++
++static int en8801s_phase2_init(struct phy_device *phydev)
++{
++    gephy_all_REG_LpiReg1Ch      GPHY_RG_LPI_1C;
++    gephy_all_REG_dev1Eh_reg324h GPHY_RG_1E_324;
++    gephy_all_REG_dev1Eh_reg012h GPHY_RG_1E_012;
++    gephy_all_REG_dev1Eh_reg017h GPHY_RG_1E_017;
++    unsigned long pbus_data;
++    u32 reg_value;
++    int retry;
++    struct mii_bus *mbus = phydev_mdio_bus(phydev);
++
++    reg_value = (airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_LTR_CTL) & 0xfffffffc) | 0x10 | (EN8801S_RX_POLARITY << 1) | EN8801S_TX_POLARITY;
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_LTR_CTL, reg_value);
++    mdelay(10);
++    reg_value &= 0xffffffef;
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, EN8801S_RG_LTR_CTL, reg_value);
++
++    pbus_data = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1690);
++    pbus_data |= (1 << 31);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1690, pbus_data);
++
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0600, 0x0c000c00);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x10, 0xD801);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0,  0x9140);
++
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0A14, 0x0003);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0600, 0x0c000c00);
++    /* Set FCM control */
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1404, 0x004b);
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x140c, 0x0007);
++
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x142c, 0x05050505);
++    /* Set GPHY Perfomance*/
++    /* Token Ring */
++    airoha_tr_reg_write(mbus, RgAddr_PMA_01h,     0x6FB90A);
++    airoha_tr_reg_write(mbus, RgAddr_PMA_18h,     0x0E2F00);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_06h,    0x2EBAEF);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_11h,    0x040001);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_03h,    0x000004);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_1Ch,    0x003210);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_14h,    0x00024A);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_0Ch,    0x00704D);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_0Dh,    0x02314F);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_10h,    0x005010);
++    airoha_tr_reg_write(mbus, RgAddr_DSPF_0Fh,    0x003028);
++    airoha_tr_reg_write(mbus, RgAddr_TR_26h,      0x444444);
++    airoha_tr_reg_write(mbus, RgAddr_R1000DEC_15h,0x0055A0);
++    /* CL22 & CL45 */
++    phy_write(phydev, 0x1f, 0x03);
++    GPHY_RG_LPI_1C.DATA = phy_read(phydev, RgAddr_LpiReg1Ch);
++    GPHY_RG_LPI_1C.DataBitField.smi_deton_th = 0x0C;
++    phy_write(phydev, RgAddr_LpiReg1Ch, GPHY_RG_LPI_1C.DATA);
++    phy_write(phydev, 0x1f, 0x0);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x122, 0xffff);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x234, 0x0180);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x238, 0x0120);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x120, 0x9014);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x239, 0x0117);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x14A, 0xEE20);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x19B, 0x0111);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1F, 0x268, 0x07F4);
++
++    airoha_cl45_read(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x324, &reg_value);
++    GPHY_RG_1E_324.DATA=(u16)reg_value;
++    GPHY_RG_1E_324.DataBitField.smi_det_deglitch_off = 0;
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x324, (u32)GPHY_RG_1E_324.DATA);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x19E, 0xC2);
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x013, 0x0);
++
++    /* EFUSE */
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1C08, 0x40000040);
++    retry = MAX_RETRY;
++    while (0 != retry)
++    {
++        mdelay(1);
++        reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1C08);
++        if ((reg_value & (1 << 30)) == 0)
++        {
++            break;
++        }
++        retry--;
++    }
++    reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1C38);          /* RAW#2 */
++    GPHY_RG_1E_012.DataBitField.da_tx_i2mpb_a_tbt = reg_value & 0x03f;
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x12, (u32)GPHY_RG_1E_012.DATA);
++    GPHY_RG_1E_017.DataBitField.da_tx_i2mpb_b_tbt=(reg_value >> 8) & 0x03f;
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x12, (u32)GPHY_RG_1E_017.DATA);
++
++    airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1C08, 0x40400040);
++    retry = MAX_RETRY;
++    while (0 != retry)
++    {
++        mdelay(1);
++        reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1C08);
++        if ((reg_value & (1 << 30)) == 0)
++        {
++            break;
++        }
++        retry--;
++    }
++    reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1C30);          /* RAW#16 */
++    GPHY_RG_1E_324.DataBitField.smi_det_deglitch_off = (reg_value >> 12) & 0x01;
++    airoha_cl45_write(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x324, (u32)GPHY_RG_1E_324.DATA);
++
++    en8801s_led_init(mbus);
++
++    printk("[Airoha] EN8801S Phase2 initialize OK !\n");
++    return 0;
++}
++
++static int en8801s_read_status(struct phy_device *phydev)
++{
++    int ret;
++    struct mii_bus *mbus = phydev_mdio_bus(phydev);
++    u32 reg_value;
++    static int phystate = PHY_STATE_INIT;
++
++    ret = genphy_read_status(phydev);
++    if (LINK_DOWN == phydev->link) preSpeed =0;
++
++    if (phystate == PHY_STATE_PROCESS) {
++        en8801s_phy_process(phydev);
++        phystate = PHY_STATE_DONE;
++    }
++
++    if ((preSpeed != phydev->speed) && (LINK_UP == phydev->link))
++    {
++        preSpeed = phydev->speed;
++
++        if (phystate == PHY_STATE_INIT) {
++            en8801s_phase2_init(phydev);
++            phystate = PHY_STATE_PROCESS;
++        }
++
++        if (preSpeed == SPEED_10) {
++            reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1694);
++            reg_value |= (1 << 31);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1694, reg_value);
++            phystate = PHY_STATE_PROCESS;
++        } else {
++            reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1694);
++            reg_value &= ~(1 << 31);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1694, reg_value);
++            phystate = PHY_STATE_PROCESS;
++        }
++
++        airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0600, 0x0c000c00);
++        if (SPEED_1000 == preSpeed)
++        {
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x10, 0xD801);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0,  0x9140);
++
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0A14, 0x0003);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0600, 0x0c000c00);
++            mdelay(2);      /* delay 2 ms */
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1404, 0x004b);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x140c, 0x0007);
++        }
++        else if (SPEED_100 == preSpeed)
++        {
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x10, 0xD401);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0,  0x9140);
++
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0A14, 0x0007);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0600, 0x0c11);
++            mdelay(2);      /* delay 2 ms */
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1404, 0x0027);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x140c, 0x0007);
++        }
++        else if (SPEED_10 == preSpeed)
++        {
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x10, 0xD001);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0,  0x9140);
++
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0A14, 0x000b);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x0600, 0x0c11);
++            mdelay(2);      /* delay 2 ms */
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1404, 0x0027);
++            airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x140c, 0x0007);
++        }
++    }
++    return ret;
++}
++
++static struct phy_driver Airoha_driver[] = {
++{
++    .phy_id         = EN8801S_PHY_ID,
++    .name           = "Airoha EN8801S",
++    .phy_id_mask    = 0x0ffffff0,
++    .features       = PHY_GBIT_FEATURES,
++    .config_init    = en8801s_phase1_init,
++    .config_aneg    = genphy_config_aneg,
++    .read_status    = en8801s_read_status,
++    .suspend        = genphy_suspend,
++    .resume         = genphy_resume,
++} };
++
++module_phy_driver(Airoha_driver);
++
++static struct mdio_device_id __maybe_unused Airoha_tbl[] = {
++    { EN8801S_PHY_ID, 0x0ffffff0 },
++    { }
++};
++
++MODULE_DEVICE_TABLE(mdio, Airoha_tbl);
+Index: drivers/net/phy/en8801sc.h
+===================================================================
+--- /dev/null
++++ b/drivers/net/phy/en8801sc.h
+@@ -0,0 +1,158 @@
++// SPDX-License-Identifier: GPL-2.0
++/* FILE NAME:  en8801sc.h
++ * PURPOSE:
++ *      Define EN8801S driver function
++ *
++ * NOTES:
++ *
++ */
++
++#ifndef __AIROHA_H
++#define __AIROHA_H
++
++/* NAMING DECLARATIONS
++ */
++#define EN8801S_DRIVER_VERSION  "1.0.0"
++
++#define PHY_ADDRESS_RANGE       0x18
++#define EN8801S_PBUS_DEFAULT_ID 0x1e
++#define EN8801S_MDIO_PHY_ID     0x18       /* Range PHY_ADDRESS_RANGE .. 0x1e */
++#define EN8801S_PBUS_PHY_ID     (EN8801S_MDIO_PHY_ID + 1)
++
++#define EN8801S_RG_ETHER_PHY_OUI 0x19a4
++#define EN8801S_RG_SMI_ADDR      0x19a8
++#define EN8801S_RG_BUCK_CTL      0x1a20
++#define EN8801S_RG_LTR_CTL      0x0cf8
++
++#define EN8801S_PBUS_OUI        0x17a5
++#define EN8801S_PHY_ID1         0x03a2
++#define EN8801S_PHY_ID2         0x9471
++#define EN8801S_PHY_ID          (unsigned long)((EN8801S_PHY_ID1 << 16) | EN8801S_PHY_ID2)
++
++#define DEV1E_REG013_VALUE      0
++#define DEV1E_REG19E_VALUE      0xC2
++#define DEV1E_REG324_VALUE      0x200
++
++#define TRUE                    1
++#define FALSE                   0
++#define LINK_UP                 1
++#define LINK_DOWN               0
++
++//#define TEST_BOARD
++#if defined(TEST_BOARD)
++/* SFP sample for verification */
++#define EN8801S_TX_POLARITY     1
++#define EN8801S_RX_POLARITY     0
++#else
++/* chip on board */
++#define EN8801S_TX_POLARITY     0
++#define EN8801S_RX_POLARITY     1       /* The pin default assignment is set to 1 */
++#endif
++
++#define MAX_RETRY               5
++#define MAX_OUI_CHECK           2
++/* CL45 MDIO control */
++#define MII_MMD_ACC_CTL_REG     0x0d
++#define MII_MMD_ADDR_DATA_REG   0x0e
++#define MMD_OP_MODE_DATA        BIT(14)
++
++#define MAX_TRG_COUNTER         5
++
++/* CL22 Reg Support Page Select */
++#define RgAddr_Reg1Fh        0x1f
++#define CL22_Page_Reg        0x0000
++#define CL22_Page_ExtReg     0x0001
++#define CL22_Page_MiscReg    0x0002
++#define CL22_Page_LpiReg     0x0003
++#define CL22_Page_tReg       0x02A3
++#define CL22_Page_TrReg      0x52B5
++
++/* CL45 Reg Support DEVID */
++#define DEVID_03             0x03
++#define DEVID_07             0x07
++#define DEVID_1E             0x1E
++#define DEVID_1F             0x1F
++
++/* TokenRing Reg Access */
++#define TrReg_PKT_XMT_STA    0x8000
++#define TrReg_WR             0x8000
++#define TrReg_RD             0xA000
++
++#define RgAddr_LpiReg1Ch     0x1c
++#define RgAddr_PMA_01h       0x0f82
++#define RgAddr_PMA_18h       0x0fb0
++#define RgAddr_DSPF_03h      0x1686
++#define RgAddr_DSPF_06h      0x168c
++#define RgAddr_DSPF_0Ch      0x1698
++#define RgAddr_DSPF_0Dh      0x169a
++#define RgAddr_DSPF_0Fh      0x169e
++#define RgAddr_DSPF_10h      0x16a0
++#define RgAddr_DSPF_11h      0x16a2
++#define RgAddr_DSPF_14h      0x16a8
++#define RgAddr_DSPF_1Ch      0x16b8
++#define RgAddr_TR_26h        0x0ecc
++#define RgAddr_R1000DEC_15h  0x03aa
++
++/* DATA TYPE DECLARATIONS
++ */
++typedef struct
++{
++    u16 DATA_Lo;
++    u16 DATA_Hi;
++}TR_DATA_T;
++
++typedef union
++{
++    struct
++    {
++        /* b[15:00] */
++        u16 smi_deton_wt                             : 3;
++        u16 smi_det_mdi_inv                          : 1;
++        u16 smi_detoff_wt                            : 3;
++        u16 smi_sigdet_debouncing_en                 : 1;
++        u16 smi_deton_th                             : 6;
++        u16 rsv_14                                   : 2;
++    } DataBitField;
++    u16 DATA;
++} gephy_all_REG_LpiReg1Ch, *Pgephy_all_REG_LpiReg1Ch;
++
++typedef union
++{
++    struct
++    {
++        /* b[15:00] */
++        u16 rg_smi_detcnt_max                        : 6;
++        u16 rsv_6                                    : 2;
++        u16 rg_smi_det_max_en                        : 1;
++        u16 smi_det_deglitch_off                     : 1;
++        u16 rsv_10                                   : 6;
++    } DataBitField;
++    u16 DATA;
++} gephy_all_REG_dev1Eh_reg324h, *Pgephy_all_REG_dev1Eh_reg324h;
++
++typedef union
++{
++    struct
++    {
++        /* b[15:00] */
++        u16 da_tx_i2mpb_a_tbt                        : 6;
++        u16 rsv_6                                    : 4;
++        u16 da_tx_i2mpb_a_gbe                        : 6;
++    } DataBitField;
++    u16 DATA;
++} gephy_all_REG_dev1Eh_reg012h, *Pgephy_all_REG_dev1Eh_reg012h;
++
++typedef union
++{
++    struct
++    {
++        /* b[15:00] */
++        u16 da_tx_i2mpb_b_tbt                        : 6;
++        u16 rsv_6                                    : 2;
++        u16 da_tx_i2mpb_b_gbe                        : 6;
++        u16 rsv_14                                   : 2;
++    } DataBitField;
++    u16 DATA;
++} gephy_all_REG_dev1Eh_reg017h, *Pgephy_all_REG_dev1Eh_reg017h;
++
++#endif /* End of __AIROHA_H */
+Index: drivers/net/phy/Kconfig
+===================================================================
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -350,6 +350,11 @@ config AIROHA_EN8801S_PHY
+ 	---help---
+ 	  Currently supports the Airoha EN8801S PHY.
+ 
++config AIROHA_EN8801SC_PHY
++        tristate "Drivers for Airoha EN8801S Gigabit PHYs for MT7981 SoC."
++        ---help---
++          Currently supports the Airoha EN8801S PHY for MT7981 SoC.
++
+ config ADIN_PHY
+ 	tristate "Analog Devices Industrial Ethernet PHYs"
+ 	help
+Index: drivers/net/phy/Makefile
+===================================================================
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -68,6 +68,7 @@ ifdef CONFIG_HWMON
+ aquantia-objs			+= aquantia_hwmon.o
+ endif
+ obj-$(CONFIG_AIROHA_EN8801S_PHY)	+= en8801s.o
++obj-$(CONFIG_AIROHA_EN8801SC_PHY)	+= en8801sc.o
+ obj-$(CONFIG_AQUANTIA_PHY)	+= aquantia.o
+ obj-$(CONFIG_AX88796B_PHY)	+= ax88796b.o
+ obj-$(CONFIG_AT803X_PHY)	+= at803x.o
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/746-mxl-gpy-phy-support.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/746-mxl-gpy-phy-support.patch
new file mode 100644
index 0000000..26bef5f
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/746-mxl-gpy-phy-support.patch
@@ -0,0 +1,804 @@
+diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
+index e0f724a..1f74ff2 100644
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -511,6 +511,12 @@ config MARVELL_10G_PHY
+ 	---help---
+ 	  Support for the Marvell Alaska MV88X3310 and compatible PHYs.
+ 
++config MAXLINEAR_GPHY
++	tristate "Maxlinear Ethernet PHYs"
++	help
++	  Support for the Maxlinear GPY115, GPY211, GPY212, GPY215,
++	  GPY241, GPY245 PHYs.
++
+ config MESON_GXL_PHY
+ 	tristate "Amlogic Meson GXL Internal PHY"
+ 	depends on ARCH_MESON || COMPILE_TEST
+diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
+index e3c411f..7b44a98 100644
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -94,6 +94,7 @@ obj-$(CONFIG_LSI_ET1011C_PHY)	+= et1011c.o
+ obj-$(CONFIG_LXT_PHY)		+= lxt.o
+ obj-$(CONFIG_MARVELL_PHY)	+= marvell.o
+ obj-$(CONFIG_MARVELL_10G_PHY)	+= marvell10g.o
++obj-$(CONFIG_MAXLINEAR_GPHY)	+= mxl-gpy.o
+ obj-$(CONFIG_MEDIATEK_GE_PHY)	+= mediatek-ge.o
+ obj-$(CONFIG_MESON_GXL_PHY)	+= meson-gxl.o
+ obj-$(CONFIG_MICREL_KS8995MA)	+= spi_ks8995.o
+diff --git a/drivers/net/phy/mxl-gpy.c b/drivers/net/phy/mxl-gpy.c
+new file mode 100644
+index 0000000..7304278
+--- /dev/null
++++ b/drivers/net/phy/mxl-gpy.c
+@@ -0,0 +1,738 @@
++// SPDX-License-Identifier: GPL-2.0+
++/* Copyright (C) 2021 Maxlinear Corporation
++ * Copyright (C) 2020 Intel Corporation
++ *
++ * Drivers for Maxlinear Ethernet GPY
++ *
++ */
++
++#include <linux/module.h>
++#include <linux/bitfield.h>
++#include <linux/phy.h>
++#include <linux/netdevice.h>
++
++/* PHY ID */
++#define PHY_ID_GPYx15B_MASK	0xFFFFFFFC
++#define PHY_ID_GPY21xB_MASK	0xFFFFFFF9
++#define PHY_ID_GPY2xx		0x67C9DC00
++#define PHY_ID_GPY115B		0x67C9DF00
++#define PHY_ID_GPY115C		0x67C9DF10
++#define PHY_ID_GPY211B		0x67C9DE08
++#define PHY_ID_GPY211C		0x67C9DE10
++#define PHY_ID_GPY212B		0x67C9DE09
++#define PHY_ID_GPY212C		0x67C9DE20
++#define PHY_ID_GPY215B		0x67C9DF04
++#define PHY_ID_GPY215C		0x67C9DF20
++#define PHY_ID_GPY241B		0x67C9DE40
++#define PHY_ID_GPY241BM		0x67C9DE80
++#define PHY_ID_GPY245B		0x67C9DEC0
++
++#define PHY_MIISTAT		0x18	/* MII state */
++#define PHY_IMASK		0x19	/* interrupt mask */
++#define PHY_ISTAT		0x1A	/* interrupt status */
++#define PHY_FWV			0x1E	/* firmware version */
++
++#define PHY_MIISTAT_SPD_MASK	GENMASK(2, 0)
++#define PHY_MIISTAT_DPX		BIT(3)
++#define PHY_MIISTAT_LS		BIT(10)
++
++#define PHY_MIISTAT_SPD_10	0
++#define PHY_MIISTAT_SPD_100	1
++#define PHY_MIISTAT_SPD_1000	2
++#define PHY_MIISTAT_SPD_2500	4
++
++#define PHY_IMASK_WOL		BIT(15)	/* Wake-on-LAN */
++#define PHY_IMASK_ANC		BIT(10)	/* Auto-Neg complete */
++#define PHY_IMASK_ADSC		BIT(5)	/* Link auto-downspeed detect */
++#define PHY_IMASK_DXMC		BIT(2)	/* Duplex mode change */
++#define PHY_IMASK_LSPC		BIT(1)	/* Link speed change */
++#define PHY_IMASK_LSTC		BIT(0)	/* Link state change */
++#define PHY_IMASK_MASK		(PHY_IMASK_LSTC | \
++				 PHY_IMASK_LSPC | \
++				 PHY_IMASK_DXMC | \
++				 PHY_IMASK_ADSC | \
++				 PHY_IMASK_ANC)
++
++#define PHY_FWV_REL_MASK	BIT(15)
++#define PHY_FWV_TYPE_MASK	GENMASK(11, 8)
++#define PHY_FWV_MINOR_MASK	GENMASK(7, 0)
++
++/* SGMII */
++#define VSPEC1_SGMII_CTRL	0x08
++#define VSPEC1_SGMII_CTRL_ANEN	BIT(12)		/* Aneg enable */
++#define VSPEC1_SGMII_CTRL_ANRS	BIT(9)		/* Restart Aneg */
++#define VSPEC1_SGMII_ANEN_ANRS	(VSPEC1_SGMII_CTRL_ANEN | \
++				 VSPEC1_SGMII_CTRL_ANRS)
++
++/* WoL */
++#define VPSPEC2_WOL_CTL		0x0E06
++#define VPSPEC2_WOL_AD01	0x0E08
++#define VPSPEC2_WOL_AD23	0x0E09
++#define VPSPEC2_WOL_AD45	0x0E0A
++#define WOL_EN			BIT(0)
++
++static const struct {
++	int type;
++	int minor;
++} ver_need_sgmii_reaneg[] = {
++	{7, 0x6D},
++	{8, 0x6D},
++	{9, 0x73},
++};
++
++static int gpy_config_init(struct phy_device *phydev)
++{
++	int ret;
++
++	/* Mask all interrupts */
++	ret = phy_write(phydev, PHY_IMASK, 0);
++	if (ret)
++		return ret;
++
++	/* Clear all pending interrupts */
++	ret = phy_read(phydev, PHY_ISTAT);
++	return ret < 0 ? ret : 0;
++}
++
++static int gpy_probe(struct phy_device *phydev)
++{
++	int ret;
++
++	/* Show GPY PHY FW version in dmesg */
++	ret = phy_read(phydev, PHY_FWV);
++	if (ret < 0)
++		return ret;
++
++	phydev_info(phydev, "Firmware Version: 0x%04X (%s)\n", ret,
++		    (ret & PHY_FWV_REL_MASK) ? "release" : "test");
++
++	return 0;
++}
++
++static bool gpy_sgmii_need_reaneg(struct phy_device *phydev)
++{
++	int fw_ver, fw_type, fw_minor;
++	size_t i;
++
++	fw_ver = phy_read(phydev, PHY_FWV);
++	if (fw_ver < 0)
++		return true;
++
++	fw_type = FIELD_GET(PHY_FWV_TYPE_MASK, fw_ver);
++	fw_minor = FIELD_GET(PHY_FWV_MINOR_MASK, fw_ver);
++
++	for (i = 0; i < ARRAY_SIZE(ver_need_sgmii_reaneg); i++) {
++		if (fw_type != ver_need_sgmii_reaneg[i].type)
++			continue;
++		if (fw_minor < ver_need_sgmii_reaneg[i].minor)
++			return true;
++		break;
++	}
++
++	return false;
++}
++
++static bool gpy_2500basex_chk(struct phy_device *phydev)
++{
++	int ret;
++
++	ret = phy_read(phydev, PHY_MIISTAT);
++	if (ret < 0) {
++		phydev_err(phydev, "Error: MDIO register access failed: %d\n",
++			   ret);
++		return false;
++	}
++
++	if (!(ret & PHY_MIISTAT_LS) ||
++	    FIELD_GET(PHY_MIISTAT_SPD_MASK, ret) != PHY_MIISTAT_SPD_2500)
++		return false;
++
++	phydev->speed = SPEED_2500;
++	phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
++	phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
++		       VSPEC1_SGMII_CTRL_ANEN, 0);
++	return true;
++}
++
++static bool gpy_sgmii_aneg_en(struct phy_device *phydev)
++{
++	int ret;
++
++	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL);
++	if (ret < 0) {
++		phydev_err(phydev, "Error: MMD register access failed: %d\n",
++			   ret);
++		return true;
++	}
++
++	return (ret & VSPEC1_SGMII_CTRL_ANEN) ? true : false;
++}
++
++static int gpy_config_aneg(struct phy_device *phydev)
++{
++	bool changed = false;
++	u32 adv;
++	int ret;
++
++	if (phydev->autoneg == AUTONEG_DISABLE) {
++		/* Configure half duplex with genphy_setup_forced,
++		 * because genphy_c45_pma_setup_forced does not support.
++		 */
++		return phydev->duplex != DUPLEX_FULL
++			? genphy_setup_forced(phydev)
++			: genphy_c45_pma_setup_forced(phydev);
++	}
++
++	ret = genphy_c45_an_config_aneg(phydev);
++	if (ret < 0)
++		return ret;
++	if (ret > 0)
++		changed = true;
++
++	adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
++	ret = phy_modify_changed(phydev, MII_CTRL1000,
++				 ADVERTISE_1000FULL | ADVERTISE_1000HALF,
++				 adv);
++	if (ret < 0)
++		return ret;
++	if (ret > 0)
++		changed = true;
++
++	ret = genphy_c45_check_and_restart_aneg(phydev, changed);
++	if (ret < 0)
++		return ret;
++
++	if (phydev->interface == PHY_INTERFACE_MODE_USXGMII ||
++	    phydev->interface == PHY_INTERFACE_MODE_INTERNAL)
++		return 0;
++
++	/* No need to trigger re-ANEG if link speed is 2.5G or SGMII ANEG is
++	 * disabled.
++	 */
++	if (!gpy_sgmii_need_reaneg(phydev) || gpy_2500basex_chk(phydev) ||
++	    !gpy_sgmii_aneg_en(phydev))
++		return 0;
++
++	/* There is a design constraint in GPY2xx device where SGMII AN is
++	 * only triggered when there is change of speed. If, PHY link
++	 * partner`s speed is still same even after PHY TPI is down and up
++	 * again, SGMII AN is not triggered and hence no new in-band message
++	 * from GPY to MAC side SGMII.
++	 * This could cause an issue during power up, when PHY is up prior to
++	 * MAC. At this condition, once MAC side SGMII is up, MAC side SGMII
++	 * wouldn`t receive new in-band message from GPY with correct link
++	 * status, speed and duplex info.
++	 *
++	 * 1) If PHY is already up and TPI link status is still down (such as
++	 *    hard reboot), TPI link status is polled for 4 seconds before
++	 *    retriggerring SGMII AN.
++	 * 2) If PHY is already up and TPI link status is also up (such as soft
++	 *    reboot), polling of TPI link status is not needed and SGMII AN is
++	 *    immediately retriggered.
++	 * 3) Other conditions such as PHY is down, speed change etc, skip
++	 *    retriggering SGMII AN. Note: in case of speed change, GPY FW will
++	 *    initiate SGMII AN.
++	 */
++
++	if (phydev->state != PHY_UP)
++		return 0;
++
++	ret = phy_read_poll_timeout(phydev, MII_BMSR, ret, ret & BMSR_LSTATUS,
++				    20000, 4000000, false);
++	if (ret == -ETIMEDOUT)
++		return 0;
++	else if (ret < 0)
++		return ret;
++
++	/* Trigger SGMII AN. */
++	return phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
++			      VSPEC1_SGMII_CTRL_ANRS, VSPEC1_SGMII_CTRL_ANRS);
++}
++
++static void gpy_update_interface(struct phy_device *phydev)
++{
++	int ret;
++
++	/* Interface mode is fixed for USXGMII and integrated PHY */
++	if (phydev->interface == PHY_INTERFACE_MODE_USXGMII ||
++	    phydev->interface == PHY_INTERFACE_MODE_INTERNAL)
++		return;
++
++	/* Automatically switch SERDES interface between SGMII and 2500-BaseX
++	 * according to speed. Disable ANEG in 2500-BaseX mode.
++	 */
++	switch (phydev->speed) {
++	case SPEED_2500:
++		phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
++		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
++				     VSPEC1_SGMII_CTRL_ANEN, 0);
++		if (ret < 0)
++			phydev_err(phydev,
++				   "Error: Disable of SGMII ANEG failed: %d\n",
++				   ret);
++		break;
++	case SPEED_1000:
++	case SPEED_100:
++	case SPEED_10:
++		phydev->interface = PHY_INTERFACE_MODE_SGMII;
++		if (gpy_sgmii_aneg_en(phydev))
++			break;
++		/* Enable and restart SGMII ANEG for 10/100/1000Mbps link speed
++		 * if ANEG is disabled (in 2500-BaseX mode).
++		 */
++		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
++				     VSPEC1_SGMII_ANEN_ANRS,
++				     VSPEC1_SGMII_ANEN_ANRS);
++		if (ret < 0)
++			phydev_err(phydev,
++				   "Error: Enable of SGMII ANEG failed: %d\n",
++				   ret);
++		break;
++	}
++}
++
++static int gpy_read_status(struct phy_device *phydev)
++{
++	int ret;
++
++	ret = genphy_update_link(phydev);
++	if (ret)
++		return ret;
++
++	phydev->speed = SPEED_UNKNOWN;
++	phydev->duplex = DUPLEX_UNKNOWN;
++	phydev->pause = 0;
++	phydev->asym_pause = 0;
++
++	if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) {
++		ret = genphy_c45_read_lpa(phydev);
++		if (ret < 0)
++			return ret;
++
++		/* Read the link partner's 1G advertisement */
++		ret = phy_read(phydev, MII_STAT1000);
++		if (ret < 0)
++			return ret;
++		mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, ret);
++	} else if (phydev->autoneg == AUTONEG_DISABLE) {
++		linkmode_zero(phydev->lp_advertising);
++	}
++
++	ret = phy_read(phydev, PHY_MIISTAT);
++	if (ret < 0)
++		return ret;
++
++	phydev->link = (ret & PHY_MIISTAT_LS) ? 1 : 0;
++	phydev->duplex = (ret & PHY_MIISTAT_DPX) ? DUPLEX_FULL : DUPLEX_HALF;
++	switch (FIELD_GET(PHY_MIISTAT_SPD_MASK, ret)) {
++	case PHY_MIISTAT_SPD_10:
++		phydev->speed = SPEED_10;
++		break;
++	case PHY_MIISTAT_SPD_100:
++		phydev->speed = SPEED_100;
++		break;
++	case PHY_MIISTAT_SPD_1000:
++		phydev->speed = SPEED_1000;
++		break;
++	case PHY_MIISTAT_SPD_2500:
++		phydev->speed = SPEED_2500;
++		break;
++	}
++
++	if (phydev->link)
++		gpy_update_interface(phydev);
++
++	return 0;
++}
++
++static int gpy_config_intr(struct phy_device *phydev)
++{
++	u16 mask = 0;
++
++	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
++		mask = PHY_IMASK_MASK;
++
++	return phy_write(phydev, PHY_IMASK, mask);
++}
++
++static int gpy_handle_interrupt(struct phy_device *phydev)
++{
++	int reg;
++
++	reg = phy_read(phydev, PHY_ISTAT);
++	if (reg < 0)
++		return -1;
++
++	if (!(reg & PHY_IMASK_MASK))
++		return -1;
++
++	phy_queue_state_machine(phydev, 0);
++
++	return 0;
++}
++
++static int gpy_set_wol(struct phy_device *phydev,
++		       struct ethtool_wolinfo *wol)
++{
++	struct net_device *attach_dev = phydev->attached_dev;
++	int ret;
++
++	if (wol->wolopts & WAKE_MAGIC) {
++		/* MAC address - Byte0:Byte1:Byte2:Byte3:Byte4:Byte5
++		 * VPSPEC2_WOL_AD45 = Byte0:Byte1
++		 * VPSPEC2_WOL_AD23 = Byte2:Byte3
++		 * VPSPEC2_WOL_AD01 = Byte4:Byte5
++		 */
++		ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
++				       VPSPEC2_WOL_AD45,
++				       ((attach_dev->dev_addr[0] << 8) |
++				       attach_dev->dev_addr[1]));
++		if (ret < 0)
++			return ret;
++
++		ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
++				       VPSPEC2_WOL_AD23,
++				       ((attach_dev->dev_addr[2] << 8) |
++				       attach_dev->dev_addr[3]));
++		if (ret < 0)
++			return ret;
++
++		ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
++				       VPSPEC2_WOL_AD01,
++				       ((attach_dev->dev_addr[4] << 8) |
++				       attach_dev->dev_addr[5]));
++		if (ret < 0)
++			return ret;
++
++		/* Enable the WOL interrupt */
++		ret = phy_write(phydev, PHY_IMASK, PHY_IMASK_WOL);
++		if (ret < 0)
++			return ret;
++
++		/* Enable magic packet matching */
++		ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
++				       VPSPEC2_WOL_CTL,
++				       WOL_EN);
++		if (ret < 0)
++			return ret;
++
++		/* Clear the interrupt status register.
++		 * Only WoL is enabled so clear all.
++		 */
++		ret = phy_read(phydev, PHY_ISTAT);
++		if (ret < 0)
++			return ret;
++	} else {
++		/* Disable magic packet matching */
++		ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2,
++					 VPSPEC2_WOL_CTL,
++					 WOL_EN);
++		if (ret < 0)
++			return ret;
++	}
++
++	if (wol->wolopts & WAKE_PHY) {
++		/* Enable the link state change interrupt */
++		ret = phy_set_bits(phydev, PHY_IMASK, PHY_IMASK_LSTC);
++		if (ret < 0)
++			return ret;
++
++		/* Clear the interrupt status register */
++		ret = phy_read(phydev, PHY_ISTAT);
++		if (ret < 0)
++			return ret;
++
++		if (ret & (PHY_IMASK_MASK & ~PHY_IMASK_LSTC))
++			phy_queue_state_machine(phydev, 0);
++
++		return 0;
++	}
++
++	/* Disable the link state change interrupt */
++	return phy_clear_bits(phydev, PHY_IMASK, PHY_IMASK_LSTC);
++}
++
++static void gpy_get_wol(struct phy_device *phydev,
++			struct ethtool_wolinfo *wol)
++{
++	int ret;
++
++	wol->supported = WAKE_MAGIC | WAKE_PHY;
++	wol->wolopts = 0;
++
++	ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, VPSPEC2_WOL_CTL);
++	if (ret & WOL_EN)
++		wol->wolopts |= WAKE_MAGIC;
++
++	ret = phy_read(phydev, PHY_IMASK);
++	if (ret & PHY_IMASK_LSTC)
++		wol->wolopts |= WAKE_PHY;
++}
++
++static int gpy_loopback(struct phy_device *phydev, bool enable)
++{
++	int ret;
++
++	ret = phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK,
++			 enable ? BMCR_LOOPBACK : 0);
++	if (!ret) {
++		/* It takes some time for PHY device to switch
++		 * into/out-of loopback mode.
++		 */
++		msleep(100);
++	}
++
++	return ret;
++}
++
++static int gpy115_loopback(struct phy_device *phydev, bool enable)
++{
++	int ret;
++	int fw_minor;
++
++	if (enable)
++		return gpy_loopback(phydev, enable);
++
++	ret = phy_read(phydev, PHY_FWV);
++	if (ret < 0)
++		return ret;
++
++	fw_minor = FIELD_GET(PHY_FWV_MINOR_MASK, ret);
++	if (fw_minor > 0x0076)
++		return gpy_loopback(phydev, 0);
++
++	return genphy_soft_reset(phydev);
++}
++
++static struct phy_driver gpy_drivers[] = {
++	{
++		PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx),
++		.name		= "Maxlinear Ethernet GPY2xx",
++		.get_features	= genphy_c45_pma_read_abilities,
++		.config_init	= gpy_config_init,
++		.probe		= gpy_probe,
++		.suspend	= genphy_suspend,
++		.resume		= genphy_resume,
++		.config_aneg	= gpy_config_aneg,
++		.aneg_done	= genphy_c45_aneg_done,
++		.read_status	= gpy_read_status,
++		.config_intr	= gpy_config_intr,
++		.handle_interrupt = gpy_handle_interrupt,
++		.set_wol	= gpy_set_wol,
++		.get_wol	= gpy_get_wol,
++		.set_loopback	= gpy_loopback,
++	},
++	{
++		.phy_id		= PHY_ID_GPY115B,
++		.phy_id_mask	= PHY_ID_GPYx15B_MASK,
++		.name		= "Maxlinear Ethernet GPY115B",
++		.get_features	= genphy_c45_pma_read_abilities,
++		.config_init	= gpy_config_init,
++		.probe		= gpy_probe,
++		.suspend	= genphy_suspend,
++		.resume		= genphy_resume,
++		.config_aneg	= gpy_config_aneg,
++		.aneg_done	= genphy_c45_aneg_done,
++		.read_status	= gpy_read_status,
++		.config_intr	= gpy_config_intr,
++		.handle_interrupt = gpy_handle_interrupt,
++		.set_wol	= gpy_set_wol,
++		.get_wol	= gpy_get_wol,
++		.set_loopback	= gpy115_loopback,
++	},
++	{
++		PHY_ID_MATCH_MODEL(PHY_ID_GPY115C),
++		.name		= "Maxlinear Ethernet GPY115C",
++		.get_features	= genphy_c45_pma_read_abilities,
++		.config_init	= gpy_config_init,
++		.probe		= gpy_probe,
++		.suspend	= genphy_suspend,
++		.resume		= genphy_resume,
++		.config_aneg	= gpy_config_aneg,
++		.aneg_done	= genphy_c45_aneg_done,
++		.read_status	= gpy_read_status,
++		.config_intr	= gpy_config_intr,
++		.handle_interrupt = gpy_handle_interrupt,
++		.set_wol	= gpy_set_wol,
++		.get_wol	= gpy_get_wol,
++		.set_loopback	= gpy115_loopback,
++	},
++	{
++		.phy_id		= PHY_ID_GPY211B,
++		.phy_id_mask	= PHY_ID_GPY21xB_MASK,
++		.name		= "Maxlinear Ethernet GPY211B",
++		.get_features	= genphy_c45_pma_read_abilities,
++		.config_init	= gpy_config_init,
++		.probe		= gpy_probe,
++		.suspend	= genphy_suspend,
++		.resume		= genphy_resume,
++		.config_aneg	= gpy_config_aneg,
++		.aneg_done	= genphy_c45_aneg_done,
++		.read_status	= gpy_read_status,
++		.config_intr	= gpy_config_intr,
++		.handle_interrupt = gpy_handle_interrupt,
++		.set_wol	= gpy_set_wol,
++		.get_wol	= gpy_get_wol,
++		.set_loopback	= gpy_loopback,
++	},
++	{
++		PHY_ID_MATCH_MODEL(PHY_ID_GPY211C),
++		.name		= "Maxlinear Ethernet GPY211C",
++		.get_features	= genphy_c45_pma_read_abilities,
++		.config_init	= gpy_config_init,
++		.probe		= gpy_probe,
++		.suspend	= genphy_suspend,
++		.resume		= genphy_resume,
++		.config_aneg	= gpy_config_aneg,
++		.aneg_done	= genphy_c45_aneg_done,
++		.read_status	= gpy_read_status,
++		.config_intr	= gpy_config_intr,
++		.handle_interrupt = gpy_handle_interrupt,
++		.set_wol	= gpy_set_wol,
++		.get_wol	= gpy_get_wol,
++		.set_loopback	= gpy_loopback,
++	},
++	{
++		.phy_id		= PHY_ID_GPY212B,
++		.phy_id_mask	= PHY_ID_GPY21xB_MASK,
++		.name		= "Maxlinear Ethernet GPY212B",
++		.get_features	= genphy_c45_pma_read_abilities,
++		.config_init	= gpy_config_init,
++		.probe		= gpy_probe,
++		.suspend	= genphy_suspend,
++		.resume		= genphy_resume,
++		.config_aneg	= gpy_config_aneg,
++		.aneg_done	= genphy_c45_aneg_done,
++		.read_status	= gpy_read_status,
++		.config_intr	= gpy_config_intr,
++		.handle_interrupt = gpy_handle_interrupt,
++		.set_wol	= gpy_set_wol,
++		.get_wol	= gpy_get_wol,
++		.set_loopback	= gpy_loopback,
++	},
++	{
++		PHY_ID_MATCH_MODEL(PHY_ID_GPY212C),
++		.name		= "Maxlinear Ethernet GPY212C",
++		.get_features	= genphy_c45_pma_read_abilities,
++		.config_init	= gpy_config_init,
++		.probe		= gpy_probe,
++		.suspend	= genphy_suspend,
++		.resume		= genphy_resume,
++		.config_aneg	= gpy_config_aneg,
++		.aneg_done	= genphy_c45_aneg_done,
++		.read_status	= gpy_read_status,
++		.config_intr	= gpy_config_intr,
++		.handle_interrupt = gpy_handle_interrupt,
++		.set_wol	= gpy_set_wol,
++		.get_wol	= gpy_get_wol,
++		.set_loopback	= gpy_loopback,
++	},
++	{
++		.phy_id		= PHY_ID_GPY215B,
++		.phy_id_mask	= PHY_ID_GPYx15B_MASK,
++		.name		= "Maxlinear Ethernet GPY215B",
++		.get_features	= genphy_c45_pma_read_abilities,
++		.config_init	= gpy_config_init,
++		.probe		= gpy_probe,
++		.suspend	= genphy_suspend,
++		.resume		= genphy_resume,
++		.config_aneg	= gpy_config_aneg,
++		.aneg_done	= genphy_c45_aneg_done,
++		.read_status	= gpy_read_status,
++		.config_intr	= gpy_config_intr,
++		.handle_interrupt = gpy_handle_interrupt,
++		.set_wol	= gpy_set_wol,
++		.get_wol	= gpy_get_wol,
++		.set_loopback	= gpy_loopback,
++	},
++	{
++		PHY_ID_MATCH_MODEL(PHY_ID_GPY215C),
++		.name		= "Maxlinear Ethernet GPY215C",
++		.get_features	= genphy_c45_pma_read_abilities,
++		.config_init	= gpy_config_init,
++		.probe		= gpy_probe,
++		.suspend	= genphy_suspend,
++		.resume		= genphy_resume,
++		.config_aneg	= gpy_config_aneg,
++		.aneg_done	= genphy_c45_aneg_done,
++		.read_status	= gpy_read_status,
++		.config_intr	= gpy_config_intr,
++		.handle_interrupt = gpy_handle_interrupt,
++		.set_wol	= gpy_set_wol,
++		.get_wol	= gpy_get_wol,
++		.set_loopback	= gpy_loopback,
++	},
++	{
++		PHY_ID_MATCH_MODEL(PHY_ID_GPY241B),
++		.name		= "Maxlinear Ethernet GPY241B",
++		.get_features	= genphy_c45_pma_read_abilities,
++		.config_init	= gpy_config_init,
++		.probe		= gpy_probe,
++		.suspend	= genphy_suspend,
++		.resume		= genphy_resume,
++		.config_aneg	= gpy_config_aneg,
++		.aneg_done	= genphy_c45_aneg_done,
++		.read_status	= gpy_read_status,
++		.config_intr	= gpy_config_intr,
++		.handle_interrupt = gpy_handle_interrupt,
++		.set_wol	= gpy_set_wol,
++		.get_wol	= gpy_get_wol,
++		.set_loopback	= gpy_loopback,
++	},
++	{
++		PHY_ID_MATCH_MODEL(PHY_ID_GPY241BM),
++		.name		= "Maxlinear Ethernet GPY241BM",
++		.get_features	= genphy_c45_pma_read_abilities,
++		.config_init	= gpy_config_init,
++		.probe		= gpy_probe,
++		.suspend	= genphy_suspend,
++		.resume		= genphy_resume,
++		.config_aneg	= gpy_config_aneg,
++		.aneg_done	= genphy_c45_aneg_done,
++		.read_status	= gpy_read_status,
++		.config_intr	= gpy_config_intr,
++		.handle_interrupt = gpy_handle_interrupt,
++		.set_wol	= gpy_set_wol,
++		.get_wol	= gpy_get_wol,
++		.set_loopback	= gpy_loopback,
++	},
++	{
++		PHY_ID_MATCH_MODEL(PHY_ID_GPY245B),
++		.name		= "Maxlinear Ethernet GPY245B",
++		.get_features	= genphy_c45_pma_read_abilities,
++		.config_init	= gpy_config_init,
++		.probe		= gpy_probe,
++		.suspend	= genphy_suspend,
++		.resume		= genphy_resume,
++		.config_aneg	= gpy_config_aneg,
++		.aneg_done	= genphy_c45_aneg_done,
++		.read_status	= gpy_read_status,
++		.config_intr	= gpy_config_intr,
++		.handle_interrupt = gpy_handle_interrupt,
++		.set_wol	= gpy_set_wol,
++		.get_wol	= gpy_get_wol,
++		.set_loopback	= gpy_loopback,
++	},
++};
++module_phy_driver(gpy_drivers);
++
++static struct mdio_device_id __maybe_unused gpy_tbl[] = {
++	{PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx)},
++	{PHY_ID_GPY115B, PHY_ID_GPYx15B_MASK},
++	{PHY_ID_MATCH_MODEL(PHY_ID_GPY115C)},
++	{PHY_ID_GPY211B, PHY_ID_GPY21xB_MASK},
++	{PHY_ID_MATCH_MODEL(PHY_ID_GPY211C)},
++	{PHY_ID_GPY212B, PHY_ID_GPY21xB_MASK},
++	{PHY_ID_MATCH_MODEL(PHY_ID_GPY212C)},
++	{PHY_ID_GPY215B, PHY_ID_GPYx15B_MASK},
++	{PHY_ID_MATCH_MODEL(PHY_ID_GPY215C)},
++	{PHY_ID_MATCH_MODEL(PHY_ID_GPY241B)},
++	{PHY_ID_MATCH_MODEL(PHY_ID_GPY241BM)},
++	{PHY_ID_MATCH_MODEL(PHY_ID_GPY245B)},
++	{ }
++};
++MODULE_DEVICE_TABLE(mdio, gpy_tbl);
++
++MODULE_DESCRIPTION("Maxlinear Ethernet GPY Driver");
++MODULE_AUTHOR("Xu Liang");
++MODULE_LICENSE("GPL");
+diff --git a/include/linux/phy.h b/include/linux/phy.h
+index 19444cd..34bdd16 100644
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -21,6 +21,7 @@
+ #include <linux/timer.h>
+ #include <linux/workqueue.h>
+ #include <linux/mod_devicetable.h>
++#include <linux/iopoll.h>
+ 
+ #include <linux/atomic.h>
+ 
+@@ -711,6 +712,18 @@ static inline int phy_read(struct phy_device *phydev, u32 regnum)
+ 	return mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, regnum);
+ }
+ 
++#define phy_read_poll_timeout(phydev, regnum, val, cond, sleep_us, \
++				timeout_us, sleep_before_read) \
++({ \
++	int __ret = read_poll_timeout(phy_read, val, (cond) || val < 0, \
++		sleep_us, timeout_us, sleep_before_read, phydev, regnum); \
++	if (val <  0) \
++		__ret = val; \
++	if (__ret) \
++		phydev_err(phydev, "%s failed: %d\n", __func__, __ret); \
++	__ret; \
++})
++
+ /**
+  * __phy_read - convenience function for reading a given PHY register
+  * @phydev: the phy_device struct
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/747-net-phy-aquantia-add-AQR113C.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/747-net-phy-aquantia-add-AQR113C.patch
new file mode 100644
index 0000000..4842524
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/747-net-phy-aquantia-add-AQR113C.patch
@@ -0,0 +1,45 @@
+diff --git a/drivers/net/phy/aquantia_main.c b/drivers/net/phy/aquantia_main.c
+index 75d8351..ac8dd8e 100644
+--- a/drivers/net/phy/aquantia_main.c
++++ b/drivers/net/phy/aquantia_main.c
+@@ -22,6 +22,7 @@
+ #define PHY_ID_AQR107	0x03a1b4e0
+ #define PHY_ID_AQCS109	0x03a1b5c2
+ #define PHY_ID_AQR405	0x03a1b4b0
++#define PHY_ID_AQR113C	0x31c31c12
+ 
+ #define MDIO_PHYXS_VEND_IF_STATUS		0xe812
+ #define MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK	GENMASK(7, 3)
+@@ -695,6 +696,24 @@ static struct phy_driver aqr_driver[] = {
+ 	.ack_interrupt	= aqr_ack_interrupt,
+ 	.read_status	= aqr_read_status,
+ },
++{
++	PHY_ID_MATCH_MODEL(PHY_ID_AQR113C),
++	.name           = "Aquantia AQR113C",
++	.probe          = aqr107_probe,
++	.config_init    = aqr107_config_init,
++	.config_aneg    = aqr_config_aneg,
++	.config_intr    = aqr_config_intr,
++	.ack_interrupt  = aqr_ack_interrupt,
++	.read_status    = aqr107_read_status,
++	.get_tunable    = aqr107_get_tunable,
++	.set_tunable    = aqr107_set_tunable,
++	.suspend        = aqr107_suspend,
++	.resume         = aqr107_resume,
++	.get_sset_count = aqr107_get_sset_count,
++	.get_strings    = aqr107_get_strings,
++	.get_stats      = aqr107_get_stats,
++	.link_change_notify = aqr107_link_change_notify,
++},
+ };
+ 
+ module_phy_driver(aqr_driver);
+@@ -707,6 +726,7 @@ static struct mdio_device_id __maybe_unused aqr_tbl[] = {
+ 	{ PHY_ID_MATCH_MODEL(PHY_ID_AQR107) },
+ 	{ PHY_ID_MATCH_MODEL(PHY_ID_AQCS109) },
+ 	{ PHY_ID_MATCH_MODEL(PHY_ID_AQR405) },
++	{ PHY_ID_MATCH_MODEL(PHY_ID_AQR113C) },
+ 	{ }
+ };
+ 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/patches-5.4.inc b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/patches-5.4.inc
index b42f13f..2ad7eec 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/patches-5.4.inc
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/patches-5.4.inc
@@ -12,6 +12,7 @@
     file://0010-dts-mt7629-rfb-fix-firmware-partition.patch \
     file://0020-dts-mt7622-enable-new-mtk-snand-for-ubi.patch \
     file://0021-dts-mt7622-remove-cooling-device.patch \
+    file://0030-introduce_read_poll_timeout_macro.patch \
     file://0100-hwnat_Kconfig_Makefile.patch \
     file://0111-mt7986-trng-add-rng-support.patch \
     file://0200-show_model_name_in_cpuinfo_on_arm64.patch \
@@ -88,7 +89,10 @@
     file://742-net-dsa-add-MT7531-Gigabit-Ethernet-PHY-setting.patch \
     file://743-add-mediatek-ge-gphy-support.patch \
     file://744-en8801s-gphy-support.patch \
+    file://745-en8801sc-gphy-support.patch \
     file://745-mdiobus-add-c45.patch \
+    file://746-mxl-gpy-phy-support.patch \
+    file://747-net-phy-aquantia-add-AQR113C.patch \
     file://8000-PATCH-1-4-tphy-support-type-switch-by-pericfg.patch \
     file://8001-PATCH-2-4-dt-bindings-phy-Add-PHY_TYPE_DP-definition.patch \
     file://8002-PATCH-3-4-dt-bindings-phy-Add-PHY_TYPE_XPCS-definition.patch \