[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(ð->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(ð->pending_work);
- }
- }
- } else {
+ }else {
err_cnt1 = 0;
err_cnt2 = 0;
err_cnt3 = 0;
}
prev_wdidx = cur_wdidx;
- prev_gdm2rx = gdm2_rx_cnt;
mod_timer(ð->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, ®_value);
-+ GPHY_RG_1E_324.DATA=(u16)reg_value;
++ airoha_cl45_read(mbus, EN8801S_MDIO_PHY_ID, 0x1E, 0x324, ®_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, ®_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 \