Kaynağa Gözat

airoha: backport upstream patch for Flow Offload support for AN7581

Backpot upstream patch for Flow Offload support for AN7581 and refresh
all affected patch. To correctly work a dedicated firmware is needed to
use the dedicated Network Coprocessor (NPU).

This also introduce good cleanup and moves the driver in a dedicated
Airoha directory. While currently not totally usable (due to lack of
firmware blob) this is needed to backport support for external PHY/SFP
support.

Refresh all affected patch.

Tested-by: Aleksander Jan Bajkowski <[email protected]> # tested on Quantum W1700k
Tested-by: Andrew LaMarche <[email protected]>
Link: https://github.com/openwrt/openwrt/pull/18166
Signed-off-by: Christian Marangi <[email protected]>
Christian Marangi 10 ay önce
ebeveyn
işleme
ad2077165b
18 değiştirilmiş dosya ile 12118 ekleme ve 24 silme
  1. 2 1
      target/linux/airoha/an7581/config-6.6
  2. 0 5
      target/linux/airoha/patches-6.6/045-v6.14-net-airoha-Fix-wrong-GDM4-register-definition.patch
  3. 3 8
      target/linux/airoha/patches-6.6/046-v6.15-net-airoha-Fix-TSO-support-for-header-cloned-skbs.patch
  4. 2 7
      target/linux/airoha/patches-6.6/047-v6.13-net-airoha-Reset-BQL-stopping-the-netdevice.patch
  5. 6825 0
      target/linux/airoha/patches-6.6/048-01-v6.15-net-airoha-Move-airoha_eth-driver-in-a-dedicated-fol.patch
  6. 538 0
      target/linux/airoha/patches-6.6/048-02-v6.15-net-airoha-Move-definitions-in-airoha_eth.h.patch
  7. 101 0
      target/linux/airoha/patches-6.6/048-03-v6.15-net-airoha-Move-reg-write-utility-routines-in-airoha.patch
  8. 1361 0
      target/linux/airoha/patches-6.6/048-04-v6.15-net-airoha-Move-register-definitions-in-airoha_regs..patch
  9. 287 0
      target/linux/airoha/patches-6.6/048-05-v6.15-net-airoha-Move-DSA-tag-in-DMA-descriptor.patch
  10. 46 0
      target/linux/airoha/patches-6.6/048-06-v6.15-net-dsa-mt7530-Enable-Rx-sptag-for-EN7581-SoC.patch
  11. 144 0
      target/linux/airoha/patches-6.6/048-07-v6.15-net-airoha-Enable-support-for-multiple-net_devices.patch
  12. 77 0
      target/linux/airoha/patches-6.6/048-08-v6.15-net-airoha-Move-REG_GDM_FWD_CFG-initialization-in-ai.patch
  13. 120 0
      target/linux/airoha/patches-6.6/048-09-v6.15-net-airoha-Rename-airoha_set_gdm_port_fwd_cfg-in-air.patch
  14. 627 0
      target/linux/airoha/patches-6.6/048-12-v6.15-net-airoha-Introduce-Airoha-NPU-support.patch
  15. 1481 0
      target/linux/airoha/patches-6.6/048-13-v6.15-net-airoha-Introduce-flowtable-offload-support.patch
  16. 210 0
      target/linux/airoha/patches-6.6/048-14-v6.15-net-airoha-Add-loopback-support-for-GDM2.patch
  17. 291 0
      target/linux/airoha/patches-6.6/048-15-v6.15-net-airoha-Introduce-PPE-debugfs-support.patch
  18. 3 3
      target/linux/airoha/patches-6.6/113-net-airoha-Fix-channel-configuration-for-ETS-Qdisc.patch

+ 2 - 1
target/linux/airoha/an7581/config-6.6

@@ -231,7 +231,8 @@ CONFIG_NET_FLOW_LIMIT=y
 # CONFIG_NET_MEDIATEK_SOC is not set
 CONFIG_NET_SELFTESTS=y
 # CONFIG_NET_VENDOR_3COM is not set
-CONFIG_NET_VENDOR_MEDIATEK=y
+CONFIG_NET_VENDOR_AIROHA=y
+# CONFIG_NET_VENDOR_MEDIATEK is not set
 CONFIG_NLS=y
 CONFIG_NO_HZ_COMMON=y
 CONFIG_NO_HZ_IDLE=y

+ 0 - 5
target/linux/airoha/patches-6.6/045-v6.14-net-airoha-Fix-wrong-GDM4-register-definition.patch

@@ -23,8 +23,6 @@ Signed-off-by: Jakub Kicinski <[email protected]>
  drivers/net/ethernet/mediatek/airoha_eth.c | 4 ++--
  1 file changed, 2 insertions(+), 2 deletions(-)
 
-diff --git a/drivers/net/ethernet/mediatek/airoha_eth.c b/drivers/net/ethernet/mediatek/airoha_eth.c
-index 415d784de741..09f448f29124 100644
 --- a/drivers/net/ethernet/mediatek/airoha_eth.c
 +++ b/drivers/net/ethernet/mediatek/airoha_eth.c
 @@ -266,11 +266,11 @@
@@ -41,6 +39,3 @@ index 415d784de741..09f448f29124 100644
  #define GDM4_SPORT_OFF2_MASK		GENMASK(19, 16)
  #define GDM4_SPORT_OFF1_MASK		GENMASK(15, 12)
  #define GDM4_SPORT_OFF0_MASK		GENMASK(11, 8)
--- 
-2.48.1
-

+ 3 - 8
target/linux/airoha/patches-6.6/046-v6.15-net-airoha-Fix-TSO-support-for-header-cloned-skbs.patch

@@ -22,11 +22,9 @@ Signed-off-by: Jakub Kicinski <[email protected]>
  drivers/net/ethernet/mediatek/airoha_eth.c | 10 +++++-----
  1 file changed, 5 insertions(+), 5 deletions(-)
 
-diff --git a/drivers/net/ethernet/mediatek/airoha_eth.c b/drivers/net/ethernet/mediatek/airoha_eth.c
-index 09f448f29124..aa5f220ddbcf 100644
 --- a/drivers/net/ethernet/mediatek/airoha_eth.c
 +++ b/drivers/net/ethernet/mediatek/airoha_eth.c
-@@ -2556,11 +2556,10 @@ static u16 airoha_dev_select_queue(struct net_device *dev, struct sk_buff *skb,
+@@ -2549,11 +2549,10 @@ static u16 airoha_dev_select_queue(struc
  static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
  				   struct net_device *dev)
  {
@@ -39,7 +37,7 @@ index 09f448f29124..aa5f220ddbcf 100644
  	struct netdev_queue *txq;
  	struct airoha_queue *q;
  	void *data = skb->data;
-@@ -2583,8 +2582,9 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
+@@ -2576,8 +2575,9 @@ static netdev_tx_t airoha_dev_xmit(struc
  		if (skb_cow_head(skb, 0))
  			goto error;
  
@@ -51,7 +49,7 @@ index 09f448f29124..aa5f220ddbcf 100644
  
  			tcp_hdr(skb)->check = (__force __sum16)csum;
  			msg0 |= FIELD_PREP(QDMA_ETH_TXMSG_TSO_MASK, 1);
-@@ -2613,7 +2613,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
+@@ -2606,7 +2606,7 @@ static netdev_tx_t airoha_dev_xmit(struc
  	for (i = 0; i < nr_frags; i++) {
  		struct airoha_qdma_desc *desc = &q->desc[index];
  		struct airoha_queue_entry *e = &q->entry[index];
@@ -60,6 +58,3 @@ index 09f448f29124..aa5f220ddbcf 100644
  		dma_addr_t addr;
  		u32 val;
  
--- 
-2.48.1
-

+ 2 - 7
target/linux/airoha/patches-6.6/047-v6.13-net-airoha-Reset-BQL-stopping-the-netdevice.patch

@@ -14,11 +14,9 @@ Signed-off-by: Andrew Lunn <[email protected]>
  drivers/net/ethernet/mediatek/airoha_eth.c | 10 +++++++++-
  1 file changed, 9 insertions(+), 1 deletion(-)
 
-diff --git a/drivers/net/ethernet/mediatek/airoha_eth.c b/drivers/net/ethernet/mediatek/airoha_eth.c
-index 21d6eed8aece..f463a505f5ba 100644
 --- a/drivers/net/ethernet/mediatek/airoha_eth.c
 +++ b/drivers/net/ethernet/mediatek/airoha_eth.c
-@@ -2342,7 +2342,7 @@ static int airoha_dev_stop(struct net_device *dev)
+@@ -2469,7 +2469,7 @@ static int airoha_dev_stop(struct net_de
  {
  	struct airoha_gdm_port *port = netdev_priv(dev);
  	struct airoha_qdma *qdma = port->qdma;
@@ -27,7 +25,7 @@ index 21d6eed8aece..f463a505f5ba 100644
  
  	netif_tx_disable(dev);
  	err = airoha_set_gdm_ports(qdma->eth, false);
-@@ -2353,6 +2353,14 @@ static int airoha_dev_stop(struct net_device *dev)
+@@ -2480,6 +2480,14 @@ static int airoha_dev_stop(struct net_de
  			  GLOBAL_CFG_TX_DMA_EN_MASK |
  			  GLOBAL_CFG_RX_DMA_EN_MASK);
  
@@ -42,6 +40,3 @@ index 21d6eed8aece..f463a505f5ba 100644
  	return 0;
  }
  
--- 
-2.48.1
-

+ 6825 - 0
target/linux/airoha/patches-6.6/048-01-v6.15-net-airoha-Move-airoha_eth-driver-in-a-dedicated-fol.patch

@@ -0,0 +1,6825 @@
+From fb3dda82fd38ca42140f29b3082324dcdc128293 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <[email protected]>
+Date: Fri, 28 Feb 2025 11:54:09 +0100
+Subject: [PATCH 01/15] net: airoha: Move airoha_eth driver in a dedicated
+ folder
+
+The airoha_eth driver has no codebase shared with mtk_eth_soc one.
+Moreover, the upcoming features (flowtable hw offloading, PCS, ..) will
+not reuse any code from MediaTek driver. Move the Airoha driver in a
+dedicated folder.
+
+Signed-off-by: Lorenzo Bianconi <[email protected]>
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/ethernet/Kconfig                   |  2 ++
+ drivers/net/ethernet/Makefile                  |  1 +
+ drivers/net/ethernet/airoha/Kconfig            | 18 ++++++++++++++++++
+ drivers/net/ethernet/airoha/Makefile           |  6 ++++++
+ .../ethernet/{mediatek => airoha}/airoha_eth.c |  0
+ drivers/net/ethernet/mediatek/Kconfig          |  8 --------
+ drivers/net/ethernet/mediatek/Makefile         |  1 -
+ 7 files changed, 27 insertions(+), 9 deletions(-)
+ create mode 100644 drivers/net/ethernet/airoha/Kconfig
+ create mode 100644 drivers/net/ethernet/airoha/Makefile
+ rename drivers/net/ethernet/{mediatek => airoha}/airoha_eth.c (100%)
+
+--- a/drivers/net/ethernet/Kconfig
++++ b/drivers/net/ethernet/Kconfig
+@@ -23,6 +23,8 @@ source "drivers/net/ethernet/actions/Kco
+ source "drivers/net/ethernet/adaptec/Kconfig"
+ source "drivers/net/ethernet/aeroflex/Kconfig"
+ source "drivers/net/ethernet/agere/Kconfig"
++source "drivers/net/ethernet/airoha/Kconfig"
++source "drivers/net/ethernet/mellanox/Kconfig"
+ source "drivers/net/ethernet/alacritech/Kconfig"
+ source "drivers/net/ethernet/allwinner/Kconfig"
+ source "drivers/net/ethernet/alteon/Kconfig"
+--- a/drivers/net/ethernet/Makefile
++++ b/drivers/net/ethernet/Makefile
+@@ -10,6 +10,7 @@ obj-$(CONFIG_NET_VENDOR_ADAPTEC) += adap
+ obj-$(CONFIG_GRETH) += aeroflex/
+ obj-$(CONFIG_NET_VENDOR_ADI) += adi/
+ obj-$(CONFIG_NET_VENDOR_AGERE) += agere/
++obj-$(CONFIG_NET_VENDOR_AIROHA) += airoha/
+ obj-$(CONFIG_NET_VENDOR_ALACRITECH) += alacritech/
+ obj-$(CONFIG_NET_VENDOR_ALLWINNER) += allwinner/
+ obj-$(CONFIG_NET_VENDOR_ALTEON) += alteon/
+--- /dev/null
++++ b/drivers/net/ethernet/airoha/Kconfig
+@@ -0,0 +1,18 @@
++# SPDX-License-Identifier: GPL-2.0-only
++config NET_VENDOR_AIROHA
++	bool "Airoha devices"
++	depends on ARCH_AIROHA || COMPILE_TEST
++	help
++	  If you have a Airoha SoC with ethernet, say Y.
++
++if NET_VENDOR_AIROHA
++
++config NET_AIROHA
++	tristate "Airoha SoC Gigabit Ethernet support"
++	depends on NET_DSA || !NET_DSA
++	select PAGE_POOL
++	help
++	  This driver supports the gigabit ethernet MACs in the
++	  Airoha SoC family.
++
++endif #NET_VENDOR_AIROHA
+--- /dev/null
++++ b/drivers/net/ethernet/airoha/Makefile
+@@ -0,0 +1,6 @@
++# SPDX-License-Identifier: GPL-2.0-only
++#
++# Airoha for the Mediatek SoCs built-in ethernet macs
++#
++
++obj-$(CONFIG_NET_AIROHA) += airoha_eth.o
+--- a/drivers/net/ethernet/mediatek/Kconfig
++++ b/drivers/net/ethernet/mediatek/Kconfig
+@@ -7,14 +7,6 @@ config NET_VENDOR_MEDIATEK
+ 
+ if NET_VENDOR_MEDIATEK
+ 
+-config NET_AIROHA
+-	tristate "Airoha SoC Gigabit Ethernet support"
+-	depends on NET_DSA || !NET_DSA
+-	select PAGE_POOL
+-	help
+-	  This driver supports the gigabit ethernet MACs in the
+-	  Airoha SoC family.
+-
+ config NET_MEDIATEK_SOC_WED
+ 	depends on ARCH_MEDIATEK || COMPILE_TEST
+ 	def_bool NET_MEDIATEK_SOC != n
+--- a/drivers/net/ethernet/mediatek/Makefile
++++ b/drivers/net/ethernet/mediatek/Makefile
+@@ -11,4 +11,3 @@ mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) +
+ endif
+ obj-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_ops.o
+ obj-$(CONFIG_NET_MEDIATEK_STAR_EMAC) += mtk_star_emac.o
+-obj-$(CONFIG_NET_AIROHA) += airoha_eth.o
+--- /dev/null
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -0,0 +1,3359 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2024 AIROHA Inc
++ * Author: Lorenzo Bianconi <[email protected]>
++ */
++#include <linux/etherdevice.h>
++#include <linux/iopoll.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/of.h>
++#include <linux/of_net.h>
++#include <linux/platform_device.h>
++#include <linux/reset.h>
++#include <linux/tcp.h>
++#include <linux/u64_stats_sync.h>
++#include <net/dsa.h>
++#include <net/page_pool/helpers.h>
++#include <net/pkt_cls.h>
++#include <uapi/linux/ppp_defs.h>
++
++#define AIROHA_MAX_NUM_GDM_PORTS	1
++#define AIROHA_MAX_NUM_QDMA		2
++#define AIROHA_MAX_NUM_RSTS		3
++#define AIROHA_MAX_NUM_XSI_RSTS		5
++#define AIROHA_MAX_MTU			2000
++#define AIROHA_MAX_PACKET_SIZE		2048
++#define AIROHA_NUM_QOS_CHANNELS		4
++#define AIROHA_NUM_QOS_QUEUES		8
++#define AIROHA_NUM_TX_RING		32
++#define AIROHA_NUM_RX_RING		32
++#define AIROHA_NUM_NETDEV_TX_RINGS	(AIROHA_NUM_TX_RING + \
++					 AIROHA_NUM_QOS_CHANNELS)
++#define AIROHA_FE_MC_MAX_VLAN_TABLE	64
++#define AIROHA_FE_MC_MAX_VLAN_PORT	16
++#define AIROHA_NUM_TX_IRQ		2
++#define HW_DSCP_NUM			2048
++#define IRQ_QUEUE_LEN(_n)		((_n) ? 1024 : 2048)
++#define TX_DSCP_NUM			1024
++#define RX_DSCP_NUM(_n)			\
++	((_n) ==  2 ? 128 :		\
++	 (_n) == 11 ? 128 :		\
++	 (_n) == 15 ? 128 :		\
++	 (_n) ==  0 ? 1024 : 16)
++
++#define PSE_RSV_PAGES			128
++#define PSE_QUEUE_RSV_PAGES		64
++
++#define QDMA_METER_IDX(_n)		((_n) & 0xff)
++#define QDMA_METER_GROUP(_n)		(((_n) >> 8) & 0x3)
++
++/* FE */
++#define PSE_BASE			0x0100
++#define CSR_IFC_BASE			0x0200
++#define CDM1_BASE			0x0400
++#define GDM1_BASE			0x0500
++#define PPE1_BASE			0x0c00
++
++#define CDM2_BASE			0x1400
++#define GDM2_BASE			0x1500
++
++#define GDM3_BASE			0x1100
++#define GDM4_BASE			0x2500
++
++#define GDM_BASE(_n)			\
++	((_n) == 4 ? GDM4_BASE :	\
++	 (_n) == 3 ? GDM3_BASE :	\
++	 (_n) == 2 ? GDM2_BASE : GDM1_BASE)
++
++#define REG_FE_DMA_GLO_CFG		0x0000
++#define FE_DMA_GLO_L2_SPACE_MASK	GENMASK(7, 4)
++#define FE_DMA_GLO_PG_SZ_MASK		BIT(3)
++
++#define REG_FE_RST_GLO_CFG		0x0004
++#define FE_RST_GDM4_MBI_ARB_MASK	BIT(3)
++#define FE_RST_GDM3_MBI_ARB_MASK	BIT(2)
++#define FE_RST_CORE_MASK		BIT(0)
++
++#define REG_FE_WAN_MAC_H		0x0030
++#define REG_FE_LAN_MAC_H		0x0040
++
++#define REG_FE_MAC_LMIN(_n)		((_n) + 0x04)
++#define REG_FE_MAC_LMAX(_n)		((_n) + 0x08)
++
++#define REG_FE_CDM1_OQ_MAP0		0x0050
++#define REG_FE_CDM1_OQ_MAP1		0x0054
++#define REG_FE_CDM1_OQ_MAP2		0x0058
++#define REG_FE_CDM1_OQ_MAP3		0x005c
++
++#define REG_FE_PCE_CFG			0x0070
++#define PCE_DPI_EN_MASK			BIT(2)
++#define PCE_KA_EN_MASK			BIT(1)
++#define PCE_MC_EN_MASK			BIT(0)
++
++#define REG_FE_PSE_QUEUE_CFG_WR		0x0080
++#define PSE_CFG_PORT_ID_MASK		GENMASK(27, 24)
++#define PSE_CFG_QUEUE_ID_MASK		GENMASK(20, 16)
++#define PSE_CFG_WR_EN_MASK		BIT(8)
++#define PSE_CFG_OQRSV_SEL_MASK		BIT(0)
++
++#define REG_FE_PSE_QUEUE_CFG_VAL	0x0084
++#define PSE_CFG_OQ_RSV_MASK		GENMASK(13, 0)
++
++#define PSE_FQ_CFG			0x008c
++#define PSE_FQ_LIMIT_MASK		GENMASK(14, 0)
++
++#define REG_FE_PSE_BUF_SET		0x0090
++#define PSE_SHARE_USED_LTHD_MASK	GENMASK(31, 16)
++#define PSE_ALLRSV_MASK			GENMASK(14, 0)
++
++#define REG_PSE_SHARE_USED_THD		0x0094
++#define PSE_SHARE_USED_MTHD_MASK	GENMASK(31, 16)
++#define PSE_SHARE_USED_HTHD_MASK	GENMASK(15, 0)
++
++#define REG_GDM_MISC_CFG		0x0148
++#define GDM2_RDM_ACK_WAIT_PREF_MASK	BIT(9)
++#define GDM2_CHN_VLD_MODE_MASK		BIT(5)
++
++#define REG_FE_CSR_IFC_CFG		CSR_IFC_BASE
++#define FE_IFC_EN_MASK			BIT(0)
++
++#define REG_FE_VIP_PORT_EN		0x01f0
++#define REG_FE_IFC_PORT_EN		0x01f4
++
++#define REG_PSE_IQ_REV1			(PSE_BASE + 0x08)
++#define PSE_IQ_RES1_P2_MASK		GENMASK(23, 16)
++
++#define REG_PSE_IQ_REV2			(PSE_BASE + 0x0c)
++#define PSE_IQ_RES2_P5_MASK		GENMASK(15, 8)
++#define PSE_IQ_RES2_P4_MASK		GENMASK(7, 0)
++
++#define REG_FE_VIP_EN(_n)		(0x0300 + ((_n) << 3))
++#define PATN_FCPU_EN_MASK		BIT(7)
++#define PATN_SWP_EN_MASK		BIT(6)
++#define PATN_DP_EN_MASK			BIT(5)
++#define PATN_SP_EN_MASK			BIT(4)
++#define PATN_TYPE_MASK			GENMASK(3, 1)
++#define PATN_EN_MASK			BIT(0)
++
++#define REG_FE_VIP_PATN(_n)		(0x0304 + ((_n) << 3))
++#define PATN_DP_MASK			GENMASK(31, 16)
++#define PATN_SP_MASK			GENMASK(15, 0)
++
++#define REG_CDM1_VLAN_CTRL		CDM1_BASE
++#define CDM1_VLAN_MASK			GENMASK(31, 16)
++
++#define REG_CDM1_FWD_CFG		(CDM1_BASE + 0x08)
++#define CDM1_VIP_QSEL_MASK		GENMASK(24, 20)
++
++#define REG_CDM1_CRSN_QSEL(_n)		(CDM1_BASE + 0x10 + ((_n) << 2))
++#define CDM1_CRSN_QSEL_REASON_MASK(_n)	\
++	GENMASK(4 + (((_n) % 4) << 3),	(((_n) % 4) << 3))
++
++#define REG_CDM2_FWD_CFG		(CDM2_BASE + 0x08)
++#define CDM2_OAM_QSEL_MASK		GENMASK(31, 27)
++#define CDM2_VIP_QSEL_MASK		GENMASK(24, 20)
++
++#define REG_CDM2_CRSN_QSEL(_n)		(CDM2_BASE + 0x10 + ((_n) << 2))
++#define CDM2_CRSN_QSEL_REASON_MASK(_n)	\
++	GENMASK(4 + (((_n) % 4) << 3),	(((_n) % 4) << 3))
++
++#define REG_GDM_FWD_CFG(_n)		GDM_BASE(_n)
++#define GDM_DROP_CRC_ERR		BIT(23)
++#define GDM_IP4_CKSUM			BIT(22)
++#define GDM_TCP_CKSUM			BIT(21)
++#define GDM_UDP_CKSUM			BIT(20)
++#define GDM_UCFQ_MASK			GENMASK(15, 12)
++#define GDM_BCFQ_MASK			GENMASK(11, 8)
++#define GDM_MCFQ_MASK			GENMASK(7, 4)
++#define GDM_OCFQ_MASK			GENMASK(3, 0)
++
++#define REG_GDM_INGRESS_CFG(_n)		(GDM_BASE(_n) + 0x10)
++#define GDM_INGRESS_FC_EN_MASK		BIT(1)
++#define GDM_STAG_EN_MASK		BIT(0)
++
++#define REG_GDM_LEN_CFG(_n)		(GDM_BASE(_n) + 0x14)
++#define GDM_SHORT_LEN_MASK		GENMASK(13, 0)
++#define GDM_LONG_LEN_MASK		GENMASK(29, 16)
++
++#define REG_FE_CPORT_CFG		(GDM1_BASE + 0x40)
++#define FE_CPORT_PAD			BIT(26)
++#define FE_CPORT_PORT_XFC_MASK		BIT(25)
++#define FE_CPORT_QUEUE_XFC_MASK		BIT(24)
++
++#define REG_FE_GDM_MIB_CLEAR(_n)	(GDM_BASE(_n) + 0xf0)
++#define FE_GDM_MIB_RX_CLEAR_MASK	BIT(1)
++#define FE_GDM_MIB_TX_CLEAR_MASK	BIT(0)
++
++#define REG_FE_GDM1_MIB_CFG		(GDM1_BASE + 0xf4)
++#define FE_STRICT_RFC2819_MODE_MASK	BIT(31)
++#define FE_GDM1_TX_MIB_SPLIT_EN_MASK	BIT(17)
++#define FE_GDM1_RX_MIB_SPLIT_EN_MASK	BIT(16)
++#define FE_TX_MIB_ID_MASK		GENMASK(15, 8)
++#define FE_RX_MIB_ID_MASK		GENMASK(7, 0)
++
++#define REG_FE_GDM_TX_OK_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x104)
++#define REG_FE_GDM_TX_OK_BYTE_CNT_L(_n)		(GDM_BASE(_n) + 0x10c)
++#define REG_FE_GDM_TX_ETH_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x110)
++#define REG_FE_GDM_TX_ETH_BYTE_CNT_L(_n)	(GDM_BASE(_n) + 0x114)
++#define REG_FE_GDM_TX_ETH_DROP_CNT(_n)		(GDM_BASE(_n) + 0x118)
++#define REG_FE_GDM_TX_ETH_BC_CNT(_n)		(GDM_BASE(_n) + 0x11c)
++#define REG_FE_GDM_TX_ETH_MC_CNT(_n)		(GDM_BASE(_n) + 0x120)
++#define REG_FE_GDM_TX_ETH_RUNT_CNT(_n)		(GDM_BASE(_n) + 0x124)
++#define REG_FE_GDM_TX_ETH_LONG_CNT(_n)		(GDM_BASE(_n) + 0x128)
++#define REG_FE_GDM_TX_ETH_E64_CNT_L(_n)		(GDM_BASE(_n) + 0x12c)
++#define REG_FE_GDM_TX_ETH_L64_CNT_L(_n)		(GDM_BASE(_n) + 0x130)
++#define REG_FE_GDM_TX_ETH_L127_CNT_L(_n)	(GDM_BASE(_n) + 0x134)
++#define REG_FE_GDM_TX_ETH_L255_CNT_L(_n)	(GDM_BASE(_n) + 0x138)
++#define REG_FE_GDM_TX_ETH_L511_CNT_L(_n)	(GDM_BASE(_n) + 0x13c)
++#define REG_FE_GDM_TX_ETH_L1023_CNT_L(_n)	(GDM_BASE(_n) + 0x140)
++
++#define REG_FE_GDM_RX_OK_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x148)
++#define REG_FE_GDM_RX_FC_DROP_CNT(_n)		(GDM_BASE(_n) + 0x14c)
++#define REG_FE_GDM_RX_RC_DROP_CNT(_n)		(GDM_BASE(_n) + 0x150)
++#define REG_FE_GDM_RX_OVERFLOW_DROP_CNT(_n)	(GDM_BASE(_n) + 0x154)
++#define REG_FE_GDM_RX_ERROR_DROP_CNT(_n)	(GDM_BASE(_n) + 0x158)
++#define REG_FE_GDM_RX_OK_BYTE_CNT_L(_n)		(GDM_BASE(_n) + 0x15c)
++#define REG_FE_GDM_RX_ETH_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x160)
++#define REG_FE_GDM_RX_ETH_BYTE_CNT_L(_n)	(GDM_BASE(_n) + 0x164)
++#define REG_FE_GDM_RX_ETH_DROP_CNT(_n)		(GDM_BASE(_n) + 0x168)
++#define REG_FE_GDM_RX_ETH_BC_CNT(_n)		(GDM_BASE(_n) + 0x16c)
++#define REG_FE_GDM_RX_ETH_MC_CNT(_n)		(GDM_BASE(_n) + 0x170)
++#define REG_FE_GDM_RX_ETH_CRC_ERR_CNT(_n)	(GDM_BASE(_n) + 0x174)
++#define REG_FE_GDM_RX_ETH_FRAG_CNT(_n)		(GDM_BASE(_n) + 0x178)
++#define REG_FE_GDM_RX_ETH_JABBER_CNT(_n)	(GDM_BASE(_n) + 0x17c)
++#define REG_FE_GDM_RX_ETH_RUNT_CNT(_n)		(GDM_BASE(_n) + 0x180)
++#define REG_FE_GDM_RX_ETH_LONG_CNT(_n)		(GDM_BASE(_n) + 0x184)
++#define REG_FE_GDM_RX_ETH_E64_CNT_L(_n)		(GDM_BASE(_n) + 0x188)
++#define REG_FE_GDM_RX_ETH_L64_CNT_L(_n)		(GDM_BASE(_n) + 0x18c)
++#define REG_FE_GDM_RX_ETH_L127_CNT_L(_n)	(GDM_BASE(_n) + 0x190)
++#define REG_FE_GDM_RX_ETH_L255_CNT_L(_n)	(GDM_BASE(_n) + 0x194)
++#define REG_FE_GDM_RX_ETH_L511_CNT_L(_n)	(GDM_BASE(_n) + 0x198)
++#define REG_FE_GDM_RX_ETH_L1023_CNT_L(_n)	(GDM_BASE(_n) + 0x19c)
++
++#define REG_PPE1_TB_HASH_CFG		(PPE1_BASE + 0x250)
++#define PPE1_SRAM_TABLE_EN_MASK		BIT(0)
++#define PPE1_SRAM_HASH1_EN_MASK		BIT(8)
++#define PPE1_DRAM_TABLE_EN_MASK		BIT(16)
++#define PPE1_DRAM_HASH1_EN_MASK		BIT(24)
++
++#define REG_FE_GDM_TX_OK_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x280)
++#define REG_FE_GDM_TX_OK_BYTE_CNT_H(_n)		(GDM_BASE(_n) + 0x284)
++#define REG_FE_GDM_TX_ETH_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x288)
++#define REG_FE_GDM_TX_ETH_BYTE_CNT_H(_n)	(GDM_BASE(_n) + 0x28c)
++
++#define REG_FE_GDM_RX_OK_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x290)
++#define REG_FE_GDM_RX_OK_BYTE_CNT_H(_n)		(GDM_BASE(_n) + 0x294)
++#define REG_FE_GDM_RX_ETH_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x298)
++#define REG_FE_GDM_RX_ETH_BYTE_CNT_H(_n)	(GDM_BASE(_n) + 0x29c)
++#define REG_FE_GDM_TX_ETH_E64_CNT_H(_n)		(GDM_BASE(_n) + 0x2b8)
++#define REG_FE_GDM_TX_ETH_L64_CNT_H(_n)		(GDM_BASE(_n) + 0x2bc)
++#define REG_FE_GDM_TX_ETH_L127_CNT_H(_n)	(GDM_BASE(_n) + 0x2c0)
++#define REG_FE_GDM_TX_ETH_L255_CNT_H(_n)	(GDM_BASE(_n) + 0x2c4)
++#define REG_FE_GDM_TX_ETH_L511_CNT_H(_n)	(GDM_BASE(_n) + 0x2c8)
++#define REG_FE_GDM_TX_ETH_L1023_CNT_H(_n)	(GDM_BASE(_n) + 0x2cc)
++#define REG_FE_GDM_RX_ETH_E64_CNT_H(_n)		(GDM_BASE(_n) + 0x2e8)
++#define REG_FE_GDM_RX_ETH_L64_CNT_H(_n)		(GDM_BASE(_n) + 0x2ec)
++#define REG_FE_GDM_RX_ETH_L127_CNT_H(_n)	(GDM_BASE(_n) + 0x2f0)
++#define REG_FE_GDM_RX_ETH_L255_CNT_H(_n)	(GDM_BASE(_n) + 0x2f4)
++#define REG_FE_GDM_RX_ETH_L511_CNT_H(_n)	(GDM_BASE(_n) + 0x2f8)
++#define REG_FE_GDM_RX_ETH_L1023_CNT_H(_n)	(GDM_BASE(_n) + 0x2fc)
++
++#define REG_GDM2_CHN_RLS		(GDM2_BASE + 0x20)
++#define MBI_RX_AGE_SEL_MASK		GENMASK(26, 25)
++#define MBI_TX_AGE_SEL_MASK		GENMASK(18, 17)
++
++#define REG_GDM3_FWD_CFG		GDM3_BASE
++#define GDM3_PAD_EN_MASK		BIT(28)
++
++#define REG_GDM4_FWD_CFG		GDM4_BASE
++#define GDM4_PAD_EN_MASK		BIT(28)
++#define GDM4_SPORT_OFFSET0_MASK		GENMASK(11, 8)
++
++#define REG_GDM4_SRC_PORT_SET		(GDM4_BASE + 0x23c)
++#define GDM4_SPORT_OFF2_MASK		GENMASK(19, 16)
++#define GDM4_SPORT_OFF1_MASK		GENMASK(15, 12)
++#define GDM4_SPORT_OFF0_MASK		GENMASK(11, 8)
++
++#define REG_IP_FRAG_FP			0x2010
++#define IP_ASSEMBLE_PORT_MASK		GENMASK(24, 21)
++#define IP_ASSEMBLE_NBQ_MASK		GENMASK(20, 16)
++#define IP_FRAGMENT_PORT_MASK		GENMASK(8, 5)
++#define IP_FRAGMENT_NBQ_MASK		GENMASK(4, 0)
++
++#define REG_MC_VLAN_EN			0x2100
++#define MC_VLAN_EN_MASK			BIT(0)
++
++#define REG_MC_VLAN_CFG			0x2104
++#define MC_VLAN_CFG_CMD_DONE_MASK	BIT(31)
++#define MC_VLAN_CFG_TABLE_ID_MASK	GENMASK(21, 16)
++#define MC_VLAN_CFG_PORT_ID_MASK	GENMASK(11, 8)
++#define MC_VLAN_CFG_TABLE_SEL_MASK	BIT(4)
++#define MC_VLAN_CFG_RW_MASK		BIT(0)
++
++#define REG_MC_VLAN_DATA		0x2108
++
++#define REG_CDM5_RX_OQ1_DROP_CNT	0x29d4
++
++/* QDMA */
++#define REG_QDMA_GLOBAL_CFG			0x0004
++#define GLOBAL_CFG_RX_2B_OFFSET_MASK		BIT(31)
++#define GLOBAL_CFG_DMA_PREFERENCE_MASK		GENMASK(30, 29)
++#define GLOBAL_CFG_CPU_TXR_RR_MASK		BIT(28)
++#define GLOBAL_CFG_DSCP_BYTE_SWAP_MASK		BIT(27)
++#define GLOBAL_CFG_PAYLOAD_BYTE_SWAP_MASK	BIT(26)
++#define GLOBAL_CFG_MULTICAST_MODIFY_FP_MASK	BIT(25)
++#define GLOBAL_CFG_OAM_MODIFY_MASK		BIT(24)
++#define GLOBAL_CFG_RESET_MASK			BIT(23)
++#define GLOBAL_CFG_RESET_DONE_MASK		BIT(22)
++#define GLOBAL_CFG_MULTICAST_EN_MASK		BIT(21)
++#define GLOBAL_CFG_IRQ1_EN_MASK			BIT(20)
++#define GLOBAL_CFG_IRQ0_EN_MASK			BIT(19)
++#define GLOBAL_CFG_LOOPCNT_EN_MASK		BIT(18)
++#define GLOBAL_CFG_RD_BYPASS_WR_MASK		BIT(17)
++#define GLOBAL_CFG_QDMA_LOOPBACK_MASK		BIT(16)
++#define GLOBAL_CFG_LPBK_RXQ_SEL_MASK		GENMASK(13, 8)
++#define GLOBAL_CFG_CHECK_DONE_MASK		BIT(7)
++#define GLOBAL_CFG_TX_WB_DONE_MASK		BIT(6)
++#define GLOBAL_CFG_MAX_ISSUE_NUM_MASK		GENMASK(5, 4)
++#define GLOBAL_CFG_RX_DMA_BUSY_MASK		BIT(3)
++#define GLOBAL_CFG_RX_DMA_EN_MASK		BIT(2)
++#define GLOBAL_CFG_TX_DMA_BUSY_MASK		BIT(1)
++#define GLOBAL_CFG_TX_DMA_EN_MASK		BIT(0)
++
++#define REG_FWD_DSCP_BASE			0x0010
++#define REG_FWD_BUF_BASE			0x0014
++
++#define REG_HW_FWD_DSCP_CFG			0x0018
++#define HW_FWD_DSCP_PAYLOAD_SIZE_MASK		GENMASK(29, 28)
++#define HW_FWD_DSCP_SCATTER_LEN_MASK		GENMASK(17, 16)
++#define HW_FWD_DSCP_MIN_SCATTER_LEN_MASK	GENMASK(15, 0)
++
++#define REG_INT_STATUS(_n)		\
++	(((_n) == 4) ? 0x0730 :		\
++	 ((_n) == 3) ? 0x0724 :		\
++	 ((_n) == 2) ? 0x0720 :		\
++	 ((_n) == 1) ? 0x0024 : 0x0020)
++
++#define REG_INT_ENABLE(_n)		\
++	(((_n) == 4) ? 0x0750 :		\
++	 ((_n) == 3) ? 0x0744 :		\
++	 ((_n) == 2) ? 0x0740 :		\
++	 ((_n) == 1) ? 0x002c : 0x0028)
++
++/* QDMA_CSR_INT_ENABLE1 */
++#define RX15_COHERENT_INT_MASK		BIT(31)
++#define RX14_COHERENT_INT_MASK		BIT(30)
++#define RX13_COHERENT_INT_MASK		BIT(29)
++#define RX12_COHERENT_INT_MASK		BIT(28)
++#define RX11_COHERENT_INT_MASK		BIT(27)
++#define RX10_COHERENT_INT_MASK		BIT(26)
++#define RX9_COHERENT_INT_MASK		BIT(25)
++#define RX8_COHERENT_INT_MASK		BIT(24)
++#define RX7_COHERENT_INT_MASK		BIT(23)
++#define RX6_COHERENT_INT_MASK		BIT(22)
++#define RX5_COHERENT_INT_MASK		BIT(21)
++#define RX4_COHERENT_INT_MASK		BIT(20)
++#define RX3_COHERENT_INT_MASK		BIT(19)
++#define RX2_COHERENT_INT_MASK		BIT(18)
++#define RX1_COHERENT_INT_MASK		BIT(17)
++#define RX0_COHERENT_INT_MASK		BIT(16)
++#define TX7_COHERENT_INT_MASK		BIT(15)
++#define TX6_COHERENT_INT_MASK		BIT(14)
++#define TX5_COHERENT_INT_MASK		BIT(13)
++#define TX4_COHERENT_INT_MASK		BIT(12)
++#define TX3_COHERENT_INT_MASK		BIT(11)
++#define TX2_COHERENT_INT_MASK		BIT(10)
++#define TX1_COHERENT_INT_MASK		BIT(9)
++#define TX0_COHERENT_INT_MASK		BIT(8)
++#define CNT_OVER_FLOW_INT_MASK		BIT(7)
++#define IRQ1_FULL_INT_MASK		BIT(5)
++#define IRQ1_INT_MASK			BIT(4)
++#define HWFWD_DSCP_LOW_INT_MASK		BIT(3)
++#define HWFWD_DSCP_EMPTY_INT_MASK	BIT(2)
++#define IRQ0_FULL_INT_MASK		BIT(1)
++#define IRQ0_INT_MASK			BIT(0)
++
++#define TX_DONE_INT_MASK(_n)					\
++	((_n) ? IRQ1_INT_MASK | IRQ1_FULL_INT_MASK		\
++	      : IRQ0_INT_MASK | IRQ0_FULL_INT_MASK)
++
++#define INT_TX_MASK						\
++	(IRQ1_INT_MASK | IRQ1_FULL_INT_MASK |			\
++	 IRQ0_INT_MASK | IRQ0_FULL_INT_MASK)
++
++#define INT_IDX0_MASK						\
++	(TX0_COHERENT_INT_MASK | TX1_COHERENT_INT_MASK |	\
++	 TX2_COHERENT_INT_MASK | TX3_COHERENT_INT_MASK |	\
++	 TX4_COHERENT_INT_MASK | TX5_COHERENT_INT_MASK |	\
++	 TX6_COHERENT_INT_MASK | TX7_COHERENT_INT_MASK |	\
++	 RX0_COHERENT_INT_MASK | RX1_COHERENT_INT_MASK |	\
++	 RX2_COHERENT_INT_MASK | RX3_COHERENT_INT_MASK |	\
++	 RX4_COHERENT_INT_MASK | RX7_COHERENT_INT_MASK |	\
++	 RX8_COHERENT_INT_MASK | RX9_COHERENT_INT_MASK |	\
++	 RX15_COHERENT_INT_MASK | INT_TX_MASK)
++
++/* QDMA_CSR_INT_ENABLE2 */
++#define RX15_NO_CPU_DSCP_INT_MASK	BIT(31)
++#define RX14_NO_CPU_DSCP_INT_MASK	BIT(30)
++#define RX13_NO_CPU_DSCP_INT_MASK	BIT(29)
++#define RX12_NO_CPU_DSCP_INT_MASK	BIT(28)
++#define RX11_NO_CPU_DSCP_INT_MASK	BIT(27)
++#define RX10_NO_CPU_DSCP_INT_MASK	BIT(26)
++#define RX9_NO_CPU_DSCP_INT_MASK	BIT(25)
++#define RX8_NO_CPU_DSCP_INT_MASK	BIT(24)
++#define RX7_NO_CPU_DSCP_INT_MASK	BIT(23)
++#define RX6_NO_CPU_DSCP_INT_MASK	BIT(22)
++#define RX5_NO_CPU_DSCP_INT_MASK	BIT(21)
++#define RX4_NO_CPU_DSCP_INT_MASK	BIT(20)
++#define RX3_NO_CPU_DSCP_INT_MASK	BIT(19)
++#define RX2_NO_CPU_DSCP_INT_MASK	BIT(18)
++#define RX1_NO_CPU_DSCP_INT_MASK	BIT(17)
++#define RX0_NO_CPU_DSCP_INT_MASK	BIT(16)
++#define RX15_DONE_INT_MASK		BIT(15)
++#define RX14_DONE_INT_MASK		BIT(14)
++#define RX13_DONE_INT_MASK		BIT(13)
++#define RX12_DONE_INT_MASK		BIT(12)
++#define RX11_DONE_INT_MASK		BIT(11)
++#define RX10_DONE_INT_MASK		BIT(10)
++#define RX9_DONE_INT_MASK		BIT(9)
++#define RX8_DONE_INT_MASK		BIT(8)
++#define RX7_DONE_INT_MASK		BIT(7)
++#define RX6_DONE_INT_MASK		BIT(6)
++#define RX5_DONE_INT_MASK		BIT(5)
++#define RX4_DONE_INT_MASK		BIT(4)
++#define RX3_DONE_INT_MASK		BIT(3)
++#define RX2_DONE_INT_MASK		BIT(2)
++#define RX1_DONE_INT_MASK		BIT(1)
++#define RX0_DONE_INT_MASK		BIT(0)
++
++#define RX_DONE_INT_MASK					\
++	(RX0_DONE_INT_MASK | RX1_DONE_INT_MASK |		\
++	 RX2_DONE_INT_MASK | RX3_DONE_INT_MASK |		\
++	 RX4_DONE_INT_MASK | RX7_DONE_INT_MASK |		\
++	 RX8_DONE_INT_MASK | RX9_DONE_INT_MASK |		\
++	 RX15_DONE_INT_MASK)
++#define INT_IDX1_MASK						\
++	(RX_DONE_INT_MASK |					\
++	 RX0_NO_CPU_DSCP_INT_MASK | RX1_NO_CPU_DSCP_INT_MASK |	\
++	 RX2_NO_CPU_DSCP_INT_MASK | RX3_NO_CPU_DSCP_INT_MASK |	\
++	 RX4_NO_CPU_DSCP_INT_MASK | RX7_NO_CPU_DSCP_INT_MASK |	\
++	 RX8_NO_CPU_DSCP_INT_MASK | RX9_NO_CPU_DSCP_INT_MASK |	\
++	 RX15_NO_CPU_DSCP_INT_MASK)
++
++/* QDMA_CSR_INT_ENABLE5 */
++#define TX31_COHERENT_INT_MASK		BIT(31)
++#define TX30_COHERENT_INT_MASK		BIT(30)
++#define TX29_COHERENT_INT_MASK		BIT(29)
++#define TX28_COHERENT_INT_MASK		BIT(28)
++#define TX27_COHERENT_INT_MASK		BIT(27)
++#define TX26_COHERENT_INT_MASK		BIT(26)
++#define TX25_COHERENT_INT_MASK		BIT(25)
++#define TX24_COHERENT_INT_MASK		BIT(24)
++#define TX23_COHERENT_INT_MASK		BIT(23)
++#define TX22_COHERENT_INT_MASK		BIT(22)
++#define TX21_COHERENT_INT_MASK		BIT(21)
++#define TX20_COHERENT_INT_MASK		BIT(20)
++#define TX19_COHERENT_INT_MASK		BIT(19)
++#define TX18_COHERENT_INT_MASK		BIT(18)
++#define TX17_COHERENT_INT_MASK		BIT(17)
++#define TX16_COHERENT_INT_MASK		BIT(16)
++#define TX15_COHERENT_INT_MASK		BIT(15)
++#define TX14_COHERENT_INT_MASK		BIT(14)
++#define TX13_COHERENT_INT_MASK		BIT(13)
++#define TX12_COHERENT_INT_MASK		BIT(12)
++#define TX11_COHERENT_INT_MASK		BIT(11)
++#define TX10_COHERENT_INT_MASK		BIT(10)
++#define TX9_COHERENT_INT_MASK		BIT(9)
++#define TX8_COHERENT_INT_MASK		BIT(8)
++
++#define INT_IDX4_MASK						\
++	(TX8_COHERENT_INT_MASK | TX9_COHERENT_INT_MASK |	\
++	 TX10_COHERENT_INT_MASK | TX11_COHERENT_INT_MASK |	\
++	 TX12_COHERENT_INT_MASK | TX13_COHERENT_INT_MASK |	\
++	 TX14_COHERENT_INT_MASK | TX15_COHERENT_INT_MASK |	\
++	 TX16_COHERENT_INT_MASK | TX17_COHERENT_INT_MASK |	\
++	 TX18_COHERENT_INT_MASK | TX19_COHERENT_INT_MASK |	\
++	 TX20_COHERENT_INT_MASK | TX21_COHERENT_INT_MASK |	\
++	 TX22_COHERENT_INT_MASK | TX23_COHERENT_INT_MASK |	\
++	 TX24_COHERENT_INT_MASK | TX25_COHERENT_INT_MASK |	\
++	 TX26_COHERENT_INT_MASK | TX27_COHERENT_INT_MASK |	\
++	 TX28_COHERENT_INT_MASK | TX29_COHERENT_INT_MASK |	\
++	 TX30_COHERENT_INT_MASK | TX31_COHERENT_INT_MASK)
++
++#define REG_TX_IRQ_BASE(_n)		((_n) ? 0x0048 : 0x0050)
++
++#define REG_TX_IRQ_CFG(_n)		((_n) ? 0x004c : 0x0054)
++#define TX_IRQ_THR_MASK			GENMASK(27, 16)
++#define TX_IRQ_DEPTH_MASK		GENMASK(11, 0)
++
++#define REG_IRQ_CLEAR_LEN(_n)		((_n) ? 0x0064 : 0x0058)
++#define IRQ_CLEAR_LEN_MASK		GENMASK(7, 0)
++
++#define REG_IRQ_STATUS(_n)		((_n) ? 0x0068 : 0x005c)
++#define IRQ_ENTRY_LEN_MASK		GENMASK(27, 16)
++#define IRQ_HEAD_IDX_MASK		GENMASK(11, 0)
++
++#define REG_TX_RING_BASE(_n)	\
++	(((_n) < 8) ? 0x0100 + ((_n) << 5) : 0x0b00 + (((_n) - 8) << 5))
++
++#define REG_TX_RING_BLOCKING(_n)	\
++	(((_n) < 8) ? 0x0104 + ((_n) << 5) : 0x0b04 + (((_n) - 8) << 5))
++
++#define TX_RING_IRQ_BLOCKING_MAP_MASK			BIT(6)
++#define TX_RING_IRQ_BLOCKING_CFG_MASK			BIT(4)
++#define TX_RING_IRQ_BLOCKING_TX_DROP_EN_MASK		BIT(2)
++#define TX_RING_IRQ_BLOCKING_MAX_TH_TXRING_EN_MASK	BIT(1)
++#define TX_RING_IRQ_BLOCKING_MIN_TH_TXRING_EN_MASK	BIT(0)
++
++#define REG_TX_CPU_IDX(_n)	\
++	(((_n) < 8) ? 0x0108 + ((_n) << 5) : 0x0b08 + (((_n) - 8) << 5))
++
++#define TX_RING_CPU_IDX_MASK		GENMASK(15, 0)
++
++#define REG_TX_DMA_IDX(_n)	\
++	(((_n) < 8) ? 0x010c + ((_n) << 5) : 0x0b0c + (((_n) - 8) << 5))
++
++#define TX_RING_DMA_IDX_MASK		GENMASK(15, 0)
++
++#define IRQ_RING_IDX_MASK		GENMASK(20, 16)
++#define IRQ_DESC_IDX_MASK		GENMASK(15, 0)
++
++#define REG_RX_RING_BASE(_n)	\
++	(((_n) < 16) ? 0x0200 + ((_n) << 5) : 0x0e00 + (((_n) - 16) << 5))
++
++#define REG_RX_RING_SIZE(_n)	\
++	(((_n) < 16) ? 0x0204 + ((_n) << 5) : 0x0e04 + (((_n) - 16) << 5))
++
++#define RX_RING_THR_MASK		GENMASK(31, 16)
++#define RX_RING_SIZE_MASK		GENMASK(15, 0)
++
++#define REG_RX_CPU_IDX(_n)	\
++	(((_n) < 16) ? 0x0208 + ((_n) << 5) : 0x0e08 + (((_n) - 16) << 5))
++
++#define RX_RING_CPU_IDX_MASK		GENMASK(15, 0)
++
++#define REG_RX_DMA_IDX(_n)	\
++	(((_n) < 16) ? 0x020c + ((_n) << 5) : 0x0e0c + (((_n) - 16) << 5))
++
++#define REG_RX_DELAY_INT_IDX(_n)	\
++	(((_n) < 16) ? 0x0210 + ((_n) << 5) : 0x0e10 + (((_n) - 16) << 5))
++
++#define RX_DELAY_INT_MASK		GENMASK(15, 0)
++
++#define RX_RING_DMA_IDX_MASK		GENMASK(15, 0)
++
++#define REG_INGRESS_TRTCM_CFG		0x0070
++#define INGRESS_TRTCM_EN_MASK		BIT(31)
++#define INGRESS_TRTCM_MODE_MASK		BIT(30)
++#define INGRESS_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
++#define INGRESS_FAST_TICK_MASK		GENMASK(15, 0)
++
++#define REG_QUEUE_CLOSE_CFG(_n)		(0x00a0 + ((_n) & 0xfc))
++#define TXQ_DISABLE_CHAN_QUEUE_MASK(_n, _m)	BIT((_m) + (((_n) & 0x3) << 3))
++
++#define REG_TXQ_DIS_CFG_BASE(_n)	((_n) ? 0x20a0 : 0x00a0)
++#define REG_TXQ_DIS_CFG(_n, _m)		(REG_TXQ_DIS_CFG_BASE((_n)) + (_m) << 2)
++
++#define REG_CNTR_CFG(_n)		(0x0400 + ((_n) << 3))
++#define CNTR_EN_MASK			BIT(31)
++#define CNTR_ALL_CHAN_EN_MASK		BIT(30)
++#define CNTR_ALL_QUEUE_EN_MASK		BIT(29)
++#define CNTR_ALL_DSCP_RING_EN_MASK	BIT(28)
++#define CNTR_SRC_MASK			GENMASK(27, 24)
++#define CNTR_DSCP_RING_MASK		GENMASK(20, 16)
++#define CNTR_CHAN_MASK			GENMASK(7, 3)
++#define CNTR_QUEUE_MASK			GENMASK(2, 0)
++
++#define REG_CNTR_VAL(_n)		(0x0404 + ((_n) << 3))
++
++#define REG_LMGR_INIT_CFG		0x1000
++#define LMGR_INIT_START			BIT(31)
++#define LMGR_SRAM_MODE_MASK		BIT(30)
++#define HW_FWD_PKTSIZE_OVERHEAD_MASK	GENMASK(27, 20)
++#define HW_FWD_DESC_NUM_MASK		GENMASK(16, 0)
++
++#define REG_FWD_DSCP_LOW_THR		0x1004
++#define FWD_DSCP_LOW_THR_MASK		GENMASK(17, 0)
++
++#define REG_EGRESS_RATE_METER_CFG		0x100c
++#define EGRESS_RATE_METER_EN_MASK		BIT(31)
++#define EGRESS_RATE_METER_EQ_RATE_EN_MASK	BIT(17)
++#define EGRESS_RATE_METER_WINDOW_SZ_MASK	GENMASK(16, 12)
++#define EGRESS_RATE_METER_TIMESLICE_MASK	GENMASK(10, 0)
++
++#define REG_EGRESS_TRTCM_CFG		0x1010
++#define EGRESS_TRTCM_EN_MASK		BIT(31)
++#define EGRESS_TRTCM_MODE_MASK		BIT(30)
++#define EGRESS_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
++#define EGRESS_FAST_TICK_MASK		GENMASK(15, 0)
++
++#define TRTCM_PARAM_RW_MASK		BIT(31)
++#define TRTCM_PARAM_RW_DONE_MASK	BIT(30)
++#define TRTCM_PARAM_TYPE_MASK		GENMASK(29, 28)
++#define TRTCM_METER_GROUP_MASK		GENMASK(27, 26)
++#define TRTCM_PARAM_INDEX_MASK		GENMASK(23, 17)
++#define TRTCM_PARAM_RATE_TYPE_MASK	BIT(16)
++
++#define REG_TRTCM_CFG_PARAM(_n)		((_n) + 0x4)
++#define REG_TRTCM_DATA_LOW(_n)		((_n) + 0x8)
++#define REG_TRTCM_DATA_HIGH(_n)		((_n) + 0xc)
++
++#define REG_TXWRR_MODE_CFG		0x1020
++#define TWRR_WEIGHT_SCALE_MASK		BIT(31)
++#define TWRR_WEIGHT_BASE_MASK		BIT(3)
++
++#define REG_TXWRR_WEIGHT_CFG		0x1024
++#define TWRR_RW_CMD_MASK		BIT(31)
++#define TWRR_RW_CMD_DONE		BIT(30)
++#define TWRR_CHAN_IDX_MASK		GENMASK(23, 19)
++#define TWRR_QUEUE_IDX_MASK		GENMASK(18, 16)
++#define TWRR_VALUE_MASK			GENMASK(15, 0)
++
++#define REG_PSE_BUF_USAGE_CFG		0x1028
++#define PSE_BUF_ESTIMATE_EN_MASK	BIT(29)
++
++#define REG_CHAN_QOS_MODE(_n)		(0x1040 + ((_n) << 2))
++#define CHAN_QOS_MODE_MASK(_n)		GENMASK(2 + ((_n) << 2), (_n) << 2)
++
++#define REG_GLB_TRTCM_CFG		0x1080
++#define GLB_TRTCM_EN_MASK		BIT(31)
++#define GLB_TRTCM_MODE_MASK		BIT(30)
++#define GLB_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
++#define GLB_FAST_TICK_MASK		GENMASK(15, 0)
++
++#define REG_TXQ_CNGST_CFG		0x10a0
++#define TXQ_CNGST_DROP_EN		BIT(31)
++#define TXQ_CNGST_DEI_DROP_EN		BIT(30)
++
++#define REG_SLA_TRTCM_CFG		0x1150
++#define SLA_TRTCM_EN_MASK		BIT(31)
++#define SLA_TRTCM_MODE_MASK		BIT(30)
++#define SLA_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
++#define SLA_FAST_TICK_MASK		GENMASK(15, 0)
++
++/* CTRL */
++#define QDMA_DESC_DONE_MASK		BIT(31)
++#define QDMA_DESC_DROP_MASK		BIT(30) /* tx: drop - rx: overflow */
++#define QDMA_DESC_MORE_MASK		BIT(29) /* more SG elements */
++#define QDMA_DESC_DEI_MASK		BIT(25)
++#define QDMA_DESC_NO_DROP_MASK		BIT(24)
++#define QDMA_DESC_LEN_MASK		GENMASK(15, 0)
++/* DATA */
++#define QDMA_DESC_NEXT_ID_MASK		GENMASK(15, 0)
++/* TX MSG0 */
++#define QDMA_ETH_TXMSG_MIC_IDX_MASK	BIT(30)
++#define QDMA_ETH_TXMSG_SP_TAG_MASK	GENMASK(29, 14)
++#define QDMA_ETH_TXMSG_ICO_MASK		BIT(13)
++#define QDMA_ETH_TXMSG_UCO_MASK		BIT(12)
++#define QDMA_ETH_TXMSG_TCO_MASK		BIT(11)
++#define QDMA_ETH_TXMSG_TSO_MASK		BIT(10)
++#define QDMA_ETH_TXMSG_FAST_MASK	BIT(9)
++#define QDMA_ETH_TXMSG_OAM_MASK		BIT(8)
++#define QDMA_ETH_TXMSG_CHAN_MASK	GENMASK(7, 3)
++#define QDMA_ETH_TXMSG_QUEUE_MASK	GENMASK(2, 0)
++/* TX MSG1 */
++#define QDMA_ETH_TXMSG_NO_DROP		BIT(31)
++#define QDMA_ETH_TXMSG_METER_MASK	GENMASK(30, 24)	/* 0x7f no meters */
++#define QDMA_ETH_TXMSG_FPORT_MASK	GENMASK(23, 20)
++#define QDMA_ETH_TXMSG_NBOQ_MASK	GENMASK(19, 15)
++#define QDMA_ETH_TXMSG_HWF_MASK		BIT(14)
++#define QDMA_ETH_TXMSG_HOP_MASK		BIT(13)
++#define QDMA_ETH_TXMSG_PTP_MASK		BIT(12)
++#define QDMA_ETH_TXMSG_ACNT_G1_MASK	GENMASK(10, 6)	/* 0x1f do not count */
++#define QDMA_ETH_TXMSG_ACNT_G0_MASK	GENMASK(5, 0)	/* 0x3f do not count */
++
++/* RX MSG1 */
++#define QDMA_ETH_RXMSG_DEI_MASK		BIT(31)
++#define QDMA_ETH_RXMSG_IP6_MASK		BIT(30)
++#define QDMA_ETH_RXMSG_IP4_MASK		BIT(29)
++#define QDMA_ETH_RXMSG_IP4F_MASK	BIT(28)
++#define QDMA_ETH_RXMSG_L4_VALID_MASK	BIT(27)
++#define QDMA_ETH_RXMSG_L4F_MASK		BIT(26)
++#define QDMA_ETH_RXMSG_SPORT_MASK	GENMASK(25, 21)
++#define QDMA_ETH_RXMSG_CRSN_MASK	GENMASK(20, 16)
++#define QDMA_ETH_RXMSG_PPE_ENTRY_MASK	GENMASK(15, 0)
++
++struct airoha_qdma_desc {
++	__le32 rsv;
++	__le32 ctrl;
++	__le32 addr;
++	__le32 data;
++	__le32 msg0;
++	__le32 msg1;
++	__le32 msg2;
++	__le32 msg3;
++};
++
++/* CTRL0 */
++#define QDMA_FWD_DESC_CTX_MASK		BIT(31)
++#define QDMA_FWD_DESC_RING_MASK		GENMASK(30, 28)
++#define QDMA_FWD_DESC_IDX_MASK		GENMASK(27, 16)
++#define QDMA_FWD_DESC_LEN_MASK		GENMASK(15, 0)
++/* CTRL1 */
++#define QDMA_FWD_DESC_FIRST_IDX_MASK	GENMASK(15, 0)
++/* CTRL2 */
++#define QDMA_FWD_DESC_MORE_PKT_NUM_MASK	GENMASK(2, 0)
++
++struct airoha_qdma_fwd_desc {
++	__le32 addr;
++	__le32 ctrl0;
++	__le32 ctrl1;
++	__le32 ctrl2;
++	__le32 msg0;
++	__le32 msg1;
++	__le32 rsv0;
++	__le32 rsv1;
++};
++
++enum {
++	QDMA_INT_REG_IDX0,
++	QDMA_INT_REG_IDX1,
++	QDMA_INT_REG_IDX2,
++	QDMA_INT_REG_IDX3,
++	QDMA_INT_REG_IDX4,
++	QDMA_INT_REG_MAX
++};
++
++enum {
++	XSI_PCIE0_PORT,
++	XSI_PCIE1_PORT,
++	XSI_USB_PORT,
++	XSI_AE_PORT,
++	XSI_ETH_PORT,
++};
++
++enum {
++	XSI_PCIE0_VIP_PORT_MASK	= BIT(22),
++	XSI_PCIE1_VIP_PORT_MASK	= BIT(23),
++	XSI_USB_VIP_PORT_MASK	= BIT(25),
++	XSI_ETH_VIP_PORT_MASK	= BIT(24),
++};
++
++enum {
++	DEV_STATE_INITIALIZED,
++};
++
++enum {
++	CDM_CRSN_QSEL_Q1 = 1,
++	CDM_CRSN_QSEL_Q5 = 5,
++	CDM_CRSN_QSEL_Q6 = 6,
++	CDM_CRSN_QSEL_Q15 = 15,
++};
++
++enum {
++	CRSN_08 = 0x8,
++	CRSN_21 = 0x15, /* KA */
++	CRSN_22 = 0x16, /* hit bind and force route to CPU */
++	CRSN_24 = 0x18,
++	CRSN_25 = 0x19,
++};
++
++enum {
++	FE_PSE_PORT_CDM1,
++	FE_PSE_PORT_GDM1,
++	FE_PSE_PORT_GDM2,
++	FE_PSE_PORT_GDM3,
++	FE_PSE_PORT_PPE1,
++	FE_PSE_PORT_CDM2,
++	FE_PSE_PORT_CDM3,
++	FE_PSE_PORT_CDM4,
++	FE_PSE_PORT_PPE2,
++	FE_PSE_PORT_GDM4,
++	FE_PSE_PORT_CDM5,
++	FE_PSE_PORT_DROP = 0xf,
++};
++
++enum tx_sched_mode {
++	TC_SCH_WRR8,
++	TC_SCH_SP,
++	TC_SCH_WRR7,
++	TC_SCH_WRR6,
++	TC_SCH_WRR5,
++	TC_SCH_WRR4,
++	TC_SCH_WRR3,
++	TC_SCH_WRR2,
++};
++
++enum trtcm_param_type {
++	TRTCM_MISC_MODE, /* meter_en, pps_mode, tick_sel */
++	TRTCM_TOKEN_RATE_MODE,
++	TRTCM_BUCKETSIZE_SHIFT_MODE,
++	TRTCM_BUCKET_COUNTER_MODE,
++};
++
++enum trtcm_mode_type {
++	TRTCM_COMMIT_MODE,
++	TRTCM_PEAK_MODE,
++};
++
++enum trtcm_param {
++	TRTCM_TICK_SEL = BIT(0),
++	TRTCM_PKT_MODE = BIT(1),
++	TRTCM_METER_MODE = BIT(2),
++};
++
++#define MIN_TOKEN_SIZE				4096
++#define MAX_TOKEN_SIZE_OFFSET			17
++#define TRTCM_TOKEN_RATE_MASK			GENMASK(23, 6)
++#define TRTCM_TOKEN_RATE_FRACTION_MASK		GENMASK(5, 0)
++
++struct airoha_queue_entry {
++	union {
++		void *buf;
++		struct sk_buff *skb;
++	};
++	dma_addr_t dma_addr;
++	u16 dma_len;
++};
++
++struct airoha_queue {
++	struct airoha_qdma *qdma;
++
++	/* protect concurrent queue accesses */
++	spinlock_t lock;
++	struct airoha_queue_entry *entry;
++	struct airoha_qdma_desc *desc;
++	u16 head;
++	u16 tail;
++
++	int queued;
++	int ndesc;
++	int free_thr;
++	int buf_size;
++
++	struct napi_struct napi;
++	struct page_pool *page_pool;
++};
++
++struct airoha_tx_irq_queue {
++	struct airoha_qdma *qdma;
++
++	struct napi_struct napi;
++
++	int size;
++	u32 *q;
++};
++
++struct airoha_hw_stats {
++	/* protect concurrent hw_stats accesses */
++	spinlock_t lock;
++	struct u64_stats_sync syncp;
++
++	/* get_stats64 */
++	u64 rx_ok_pkts;
++	u64 tx_ok_pkts;
++	u64 rx_ok_bytes;
++	u64 tx_ok_bytes;
++	u64 rx_multicast;
++	u64 rx_errors;
++	u64 rx_drops;
++	u64 tx_drops;
++	u64 rx_crc_error;
++	u64 rx_over_errors;
++	/* ethtool stats */
++	u64 tx_broadcast;
++	u64 tx_multicast;
++	u64 tx_len[7];
++	u64 rx_broadcast;
++	u64 rx_fragment;
++	u64 rx_jabber;
++	u64 rx_len[7];
++};
++
++struct airoha_qdma {
++	struct airoha_eth *eth;
++	void __iomem *regs;
++
++	/* protect concurrent irqmask accesses */
++	spinlock_t irq_lock;
++	u32 irqmask[QDMA_INT_REG_MAX];
++	int irq;
++
++	struct airoha_tx_irq_queue q_tx_irq[AIROHA_NUM_TX_IRQ];
++
++	struct airoha_queue q_tx[AIROHA_NUM_TX_RING];
++	struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
++
++	/* descriptor and packet buffers for qdma hw forward */
++	struct {
++		void *desc;
++		void *q;
++	} hfwd;
++};
++
++struct airoha_gdm_port {
++	struct airoha_qdma *qdma;
++	struct net_device *dev;
++	int id;
++
++	struct airoha_hw_stats stats;
++
++	DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
++
++	/* qos stats counters */
++	u64 cpu_tx_packets;
++	u64 fwd_tx_packets;
++};
++
++struct airoha_eth {
++	struct device *dev;
++
++	unsigned long state;
++	void __iomem *fe_regs;
++
++	struct reset_control_bulk_data rsts[AIROHA_MAX_NUM_RSTS];
++	struct reset_control_bulk_data xsi_rsts[AIROHA_MAX_NUM_XSI_RSTS];
++
++	struct net_device *napi_dev;
++
++	struct airoha_qdma qdma[AIROHA_MAX_NUM_QDMA];
++	struct airoha_gdm_port *ports[AIROHA_MAX_NUM_GDM_PORTS];
++};
++
++static u32 airoha_rr(void __iomem *base, u32 offset)
++{
++	return readl(base + offset);
++}
++
++static void airoha_wr(void __iomem *base, u32 offset, u32 val)
++{
++	writel(val, base + offset);
++}
++
++static u32 airoha_rmw(void __iomem *base, u32 offset, u32 mask, u32 val)
++{
++	val |= (airoha_rr(base, offset) & ~mask);
++	airoha_wr(base, offset, val);
++
++	return val;
++}
++
++#define airoha_fe_rr(eth, offset)				\
++	airoha_rr((eth)->fe_regs, (offset))
++#define airoha_fe_wr(eth, offset, val)				\
++	airoha_wr((eth)->fe_regs, (offset), (val))
++#define airoha_fe_rmw(eth, offset, mask, val)			\
++	airoha_rmw((eth)->fe_regs, (offset), (mask), (val))
++#define airoha_fe_set(eth, offset, val)				\
++	airoha_rmw((eth)->fe_regs, (offset), 0, (val))
++#define airoha_fe_clear(eth, offset, val)			\
++	airoha_rmw((eth)->fe_regs, (offset), (val), 0)
++
++#define airoha_qdma_rr(qdma, offset)				\
++	airoha_rr((qdma)->regs, (offset))
++#define airoha_qdma_wr(qdma, offset, val)			\
++	airoha_wr((qdma)->regs, (offset), (val))
++#define airoha_qdma_rmw(qdma, offset, mask, val)		\
++	airoha_rmw((qdma)->regs, (offset), (mask), (val))
++#define airoha_qdma_set(qdma, offset, val)			\
++	airoha_rmw((qdma)->regs, (offset), 0, (val))
++#define airoha_qdma_clear(qdma, offset, val)			\
++	airoha_rmw((qdma)->regs, (offset), (val), 0)
++
++static void airoha_qdma_set_irqmask(struct airoha_qdma *qdma, int index,
++				    u32 clear, u32 set)
++{
++	unsigned long flags;
++
++	if (WARN_ON_ONCE(index >= ARRAY_SIZE(qdma->irqmask)))
++		return;
++
++	spin_lock_irqsave(&qdma->irq_lock, flags);
++
++	qdma->irqmask[index] &= ~clear;
++	qdma->irqmask[index] |= set;
++	airoha_qdma_wr(qdma, REG_INT_ENABLE(index), qdma->irqmask[index]);
++	/* Read irq_enable register in order to guarantee the update above
++	 * completes in the spinlock critical section.
++	 */
++	airoha_qdma_rr(qdma, REG_INT_ENABLE(index));
++
++	spin_unlock_irqrestore(&qdma->irq_lock, flags);
++}
++
++static void airoha_qdma_irq_enable(struct airoha_qdma *qdma, int index,
++				   u32 mask)
++{
++	airoha_qdma_set_irqmask(qdma, index, 0, mask);
++}
++
++static void airoha_qdma_irq_disable(struct airoha_qdma *qdma, int index,
++				    u32 mask)
++{
++	airoha_qdma_set_irqmask(qdma, index, mask, 0);
++}
++
++static bool airhoa_is_lan_gdm_port(struct airoha_gdm_port *port)
++{
++	/* GDM1 port on EN7581 SoC is connected to the lan dsa switch.
++	 * GDM{2,3,4} can be used as wan port connected to an external
++	 * phy module.
++	 */
++	return port->id == 1;
++}
++
++static void airoha_set_macaddr(struct airoha_gdm_port *port, const u8 *addr)
++{
++	struct airoha_eth *eth = port->qdma->eth;
++	u32 val, reg;
++
++	reg = airhoa_is_lan_gdm_port(port) ? REG_FE_LAN_MAC_H
++					   : REG_FE_WAN_MAC_H;
++	val = (addr[0] << 16) | (addr[1] << 8) | addr[2];
++	airoha_fe_wr(eth, reg, val);
++
++	val = (addr[3] << 16) | (addr[4] << 8) | addr[5];
++	airoha_fe_wr(eth, REG_FE_MAC_LMIN(reg), val);
++	airoha_fe_wr(eth, REG_FE_MAC_LMAX(reg), val);
++}
++
++static void airoha_set_gdm_port_fwd_cfg(struct airoha_eth *eth, u32 addr,
++					u32 val)
++{
++	airoha_fe_rmw(eth, addr, GDM_OCFQ_MASK,
++		      FIELD_PREP(GDM_OCFQ_MASK, val));
++	airoha_fe_rmw(eth, addr, GDM_MCFQ_MASK,
++		      FIELD_PREP(GDM_MCFQ_MASK, val));
++	airoha_fe_rmw(eth, addr, GDM_BCFQ_MASK,
++		      FIELD_PREP(GDM_BCFQ_MASK, val));
++	airoha_fe_rmw(eth, addr, GDM_UCFQ_MASK,
++		      FIELD_PREP(GDM_UCFQ_MASK, val));
++}
++
++static int airoha_set_gdm_port(struct airoha_eth *eth, int port, bool enable)
++{
++	u32 val = enable ? FE_PSE_PORT_PPE1 : FE_PSE_PORT_DROP;
++	u32 vip_port, cfg_addr;
++
++	switch (port) {
++	case XSI_PCIE0_PORT:
++		vip_port = XSI_PCIE0_VIP_PORT_MASK;
++		cfg_addr = REG_GDM_FWD_CFG(3);
++		break;
++	case XSI_PCIE1_PORT:
++		vip_port = XSI_PCIE1_VIP_PORT_MASK;
++		cfg_addr = REG_GDM_FWD_CFG(3);
++		break;
++	case XSI_USB_PORT:
++		vip_port = XSI_USB_VIP_PORT_MASK;
++		cfg_addr = REG_GDM_FWD_CFG(4);
++		break;
++	case XSI_ETH_PORT:
++		vip_port = XSI_ETH_VIP_PORT_MASK;
++		cfg_addr = REG_GDM_FWD_CFG(4);
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	if (enable) {
++		airoha_fe_set(eth, REG_FE_VIP_PORT_EN, vip_port);
++		airoha_fe_set(eth, REG_FE_IFC_PORT_EN, vip_port);
++	} else {
++		airoha_fe_clear(eth, REG_FE_VIP_PORT_EN, vip_port);
++		airoha_fe_clear(eth, REG_FE_IFC_PORT_EN, vip_port);
++	}
++
++	airoha_set_gdm_port_fwd_cfg(eth, cfg_addr, val);
++
++	return 0;
++}
++
++static int airoha_set_gdm_ports(struct airoha_eth *eth, bool enable)
++{
++	const int port_list[] = {
++		XSI_PCIE0_PORT,
++		XSI_PCIE1_PORT,
++		XSI_USB_PORT,
++		XSI_ETH_PORT
++	};
++	int i, err;
++
++	for (i = 0; i < ARRAY_SIZE(port_list); i++) {
++		err = airoha_set_gdm_port(eth, port_list[i], enable);
++		if (err)
++			goto error;
++	}
++
++	return 0;
++
++error:
++	for (i--; i >= 0; i--)
++		airoha_set_gdm_port(eth, port_list[i], false);
++
++	return err;
++}
++
++static void airoha_fe_maccr_init(struct airoha_eth *eth)
++{
++	int p;
++
++	for (p = 1; p <= ARRAY_SIZE(eth->ports); p++) {
++		airoha_fe_set(eth, REG_GDM_FWD_CFG(p),
++			      GDM_TCP_CKSUM | GDM_UDP_CKSUM | GDM_IP4_CKSUM |
++			      GDM_DROP_CRC_ERR);
++		airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(p),
++					    FE_PSE_PORT_CDM1);
++		airoha_fe_rmw(eth, REG_GDM_LEN_CFG(p),
++			      GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
++			      FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
++			      FIELD_PREP(GDM_LONG_LEN_MASK, 4004));
++	}
++
++	airoha_fe_rmw(eth, REG_CDM1_VLAN_CTRL, CDM1_VLAN_MASK,
++		      FIELD_PREP(CDM1_VLAN_MASK, 0x8100));
++
++	airoha_fe_set(eth, REG_FE_CPORT_CFG, FE_CPORT_PAD);
++}
++
++static void airoha_fe_vip_setup(struct airoha_eth *eth)
++{
++	airoha_fe_wr(eth, REG_FE_VIP_PATN(3), ETH_P_PPP_DISC);
++	airoha_fe_wr(eth, REG_FE_VIP_EN(3), PATN_FCPU_EN_MASK | PATN_EN_MASK);
++
++	airoha_fe_wr(eth, REG_FE_VIP_PATN(4), PPP_LCP);
++	airoha_fe_wr(eth, REG_FE_VIP_EN(4),
++		     PATN_FCPU_EN_MASK | FIELD_PREP(PATN_TYPE_MASK, 1) |
++		     PATN_EN_MASK);
++
++	airoha_fe_wr(eth, REG_FE_VIP_PATN(6), PPP_IPCP);
++	airoha_fe_wr(eth, REG_FE_VIP_EN(6),
++		     PATN_FCPU_EN_MASK | FIELD_PREP(PATN_TYPE_MASK, 1) |
++		     PATN_EN_MASK);
++
++	airoha_fe_wr(eth, REG_FE_VIP_PATN(7), PPP_CHAP);
++	airoha_fe_wr(eth, REG_FE_VIP_EN(7),
++		     PATN_FCPU_EN_MASK | FIELD_PREP(PATN_TYPE_MASK, 1) |
++		     PATN_EN_MASK);
++
++	/* BOOTP (0x43) */
++	airoha_fe_wr(eth, REG_FE_VIP_PATN(8), 0x43);
++	airoha_fe_wr(eth, REG_FE_VIP_EN(8),
++		     PATN_FCPU_EN_MASK | PATN_SP_EN_MASK |
++		     FIELD_PREP(PATN_TYPE_MASK, 4) | PATN_EN_MASK);
++
++	/* BOOTP (0x44) */
++	airoha_fe_wr(eth, REG_FE_VIP_PATN(9), 0x44);
++	airoha_fe_wr(eth, REG_FE_VIP_EN(9),
++		     PATN_FCPU_EN_MASK | PATN_SP_EN_MASK |
++		     FIELD_PREP(PATN_TYPE_MASK, 4) | PATN_EN_MASK);
++
++	/* ISAKMP */
++	airoha_fe_wr(eth, REG_FE_VIP_PATN(10), 0x1f401f4);
++	airoha_fe_wr(eth, REG_FE_VIP_EN(10),
++		     PATN_FCPU_EN_MASK | PATN_DP_EN_MASK | PATN_SP_EN_MASK |
++		     FIELD_PREP(PATN_TYPE_MASK, 4) | PATN_EN_MASK);
++
++	airoha_fe_wr(eth, REG_FE_VIP_PATN(11), PPP_IPV6CP);
++	airoha_fe_wr(eth, REG_FE_VIP_EN(11),
++		     PATN_FCPU_EN_MASK | FIELD_PREP(PATN_TYPE_MASK, 1) |
++		     PATN_EN_MASK);
++
++	/* DHCPv6 */
++	airoha_fe_wr(eth, REG_FE_VIP_PATN(12), 0x2220223);
++	airoha_fe_wr(eth, REG_FE_VIP_EN(12),
++		     PATN_FCPU_EN_MASK | PATN_DP_EN_MASK | PATN_SP_EN_MASK |
++		     FIELD_PREP(PATN_TYPE_MASK, 4) | PATN_EN_MASK);
++
++	airoha_fe_wr(eth, REG_FE_VIP_PATN(19), PPP_PAP);
++	airoha_fe_wr(eth, REG_FE_VIP_EN(19),
++		     PATN_FCPU_EN_MASK | FIELD_PREP(PATN_TYPE_MASK, 1) |
++		     PATN_EN_MASK);
++
++	/* ETH->ETH_P_1905 (0x893a) */
++	airoha_fe_wr(eth, REG_FE_VIP_PATN(20), 0x893a);
++	airoha_fe_wr(eth, REG_FE_VIP_EN(20),
++		     PATN_FCPU_EN_MASK | PATN_EN_MASK);
++
++	airoha_fe_wr(eth, REG_FE_VIP_PATN(21), ETH_P_LLDP);
++	airoha_fe_wr(eth, REG_FE_VIP_EN(21),
++		     PATN_FCPU_EN_MASK | PATN_EN_MASK);
++}
++
++static u32 airoha_fe_get_pse_queue_rsv_pages(struct airoha_eth *eth,
++					     u32 port, u32 queue)
++{
++	u32 val;
++
++	airoha_fe_rmw(eth, REG_FE_PSE_QUEUE_CFG_WR,
++		      PSE_CFG_PORT_ID_MASK | PSE_CFG_QUEUE_ID_MASK,
++		      FIELD_PREP(PSE_CFG_PORT_ID_MASK, port) |
++		      FIELD_PREP(PSE_CFG_QUEUE_ID_MASK, queue));
++	val = airoha_fe_rr(eth, REG_FE_PSE_QUEUE_CFG_VAL);
++
++	return FIELD_GET(PSE_CFG_OQ_RSV_MASK, val);
++}
++
++static void airoha_fe_set_pse_queue_rsv_pages(struct airoha_eth *eth,
++					      u32 port, u32 queue, u32 val)
++{
++	airoha_fe_rmw(eth, REG_FE_PSE_QUEUE_CFG_VAL, PSE_CFG_OQ_RSV_MASK,
++		      FIELD_PREP(PSE_CFG_OQ_RSV_MASK, val));
++	airoha_fe_rmw(eth, REG_FE_PSE_QUEUE_CFG_WR,
++		      PSE_CFG_PORT_ID_MASK | PSE_CFG_QUEUE_ID_MASK |
++		      PSE_CFG_WR_EN_MASK | PSE_CFG_OQRSV_SEL_MASK,
++		      FIELD_PREP(PSE_CFG_PORT_ID_MASK, port) |
++		      FIELD_PREP(PSE_CFG_QUEUE_ID_MASK, queue) |
++		      PSE_CFG_WR_EN_MASK | PSE_CFG_OQRSV_SEL_MASK);
++}
++
++static u32 airoha_fe_get_pse_all_rsv(struct airoha_eth *eth)
++{
++	u32 val = airoha_fe_rr(eth, REG_FE_PSE_BUF_SET);
++
++	return FIELD_GET(PSE_ALLRSV_MASK, val);
++}
++
++static int airoha_fe_set_pse_oq_rsv(struct airoha_eth *eth,
++				    u32 port, u32 queue, u32 val)
++{
++	u32 orig_val = airoha_fe_get_pse_queue_rsv_pages(eth, port, queue);
++	u32 tmp, all_rsv, fq_limit;
++
++	airoha_fe_set_pse_queue_rsv_pages(eth, port, queue, val);
++
++	/* modify all rsv */
++	all_rsv = airoha_fe_get_pse_all_rsv(eth);
++	all_rsv += (val - orig_val);
++	airoha_fe_rmw(eth, REG_FE_PSE_BUF_SET, PSE_ALLRSV_MASK,
++		      FIELD_PREP(PSE_ALLRSV_MASK, all_rsv));
++
++	/* modify hthd */
++	tmp = airoha_fe_rr(eth, PSE_FQ_CFG);
++	fq_limit = FIELD_GET(PSE_FQ_LIMIT_MASK, tmp);
++	tmp = fq_limit - all_rsv - 0x20;
++	airoha_fe_rmw(eth, REG_PSE_SHARE_USED_THD,
++		      PSE_SHARE_USED_HTHD_MASK,
++		      FIELD_PREP(PSE_SHARE_USED_HTHD_MASK, tmp));
++
++	tmp = fq_limit - all_rsv - 0x100;
++	airoha_fe_rmw(eth, REG_PSE_SHARE_USED_THD,
++		      PSE_SHARE_USED_MTHD_MASK,
++		      FIELD_PREP(PSE_SHARE_USED_MTHD_MASK, tmp));
++	tmp = (3 * tmp) >> 2;
++	airoha_fe_rmw(eth, REG_FE_PSE_BUF_SET,
++		      PSE_SHARE_USED_LTHD_MASK,
++		      FIELD_PREP(PSE_SHARE_USED_LTHD_MASK, tmp));
++
++	return 0;
++}
++
++static void airoha_fe_pse_ports_init(struct airoha_eth *eth)
++{
++	const u32 pse_port_num_queues[] = {
++		[FE_PSE_PORT_CDM1] = 6,
++		[FE_PSE_PORT_GDM1] = 6,
++		[FE_PSE_PORT_GDM2] = 32,
++		[FE_PSE_PORT_GDM3] = 6,
++		[FE_PSE_PORT_PPE1] = 4,
++		[FE_PSE_PORT_CDM2] = 6,
++		[FE_PSE_PORT_CDM3] = 8,
++		[FE_PSE_PORT_CDM4] = 10,
++		[FE_PSE_PORT_PPE2] = 4,
++		[FE_PSE_PORT_GDM4] = 2,
++		[FE_PSE_PORT_CDM5] = 2,
++	};
++	u32 all_rsv;
++	int q;
++
++	all_rsv = airoha_fe_get_pse_all_rsv(eth);
++	/* hw misses PPE2 oq rsv */
++	all_rsv += PSE_RSV_PAGES * pse_port_num_queues[FE_PSE_PORT_PPE2];
++	airoha_fe_set(eth, REG_FE_PSE_BUF_SET, all_rsv);
++
++	/* CMD1 */
++	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_CDM1]; q++)
++		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_CDM1, q,
++					 PSE_QUEUE_RSV_PAGES);
++	/* GMD1 */
++	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_GDM1]; q++)
++		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_GDM1, q,
++					 PSE_QUEUE_RSV_PAGES);
++	/* GMD2 */
++	for (q = 6; q < pse_port_num_queues[FE_PSE_PORT_GDM2]; q++)
++		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_GDM2, q, 0);
++	/* GMD3 */
++	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_GDM3]; q++)
++		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_GDM3, q,
++					 PSE_QUEUE_RSV_PAGES);
++	/* PPE1 */
++	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_PPE1]; q++) {
++		if (q < pse_port_num_queues[FE_PSE_PORT_PPE1])
++			airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_PPE1, q,
++						 PSE_QUEUE_RSV_PAGES);
++		else
++			airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_PPE1, q, 0);
++	}
++	/* CDM2 */
++	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_CDM2]; q++)
++		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_CDM2, q,
++					 PSE_QUEUE_RSV_PAGES);
++	/* CDM3 */
++	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_CDM3] - 1; q++)
++		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_CDM3, q, 0);
++	/* CDM4 */
++	for (q = 4; q < pse_port_num_queues[FE_PSE_PORT_CDM4]; q++)
++		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_CDM4, q,
++					 PSE_QUEUE_RSV_PAGES);
++	/* PPE2 */
++	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_PPE2]; q++) {
++		if (q < pse_port_num_queues[FE_PSE_PORT_PPE2] / 2)
++			airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_PPE2, q,
++						 PSE_QUEUE_RSV_PAGES);
++		else
++			airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_PPE2, q, 0);
++	}
++	/* GMD4 */
++	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_GDM4]; q++)
++		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_GDM4, q,
++					 PSE_QUEUE_RSV_PAGES);
++	/* CDM5 */
++	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_CDM5]; q++)
++		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_CDM5, q,
++					 PSE_QUEUE_RSV_PAGES);
++}
++
++static int airoha_fe_mc_vlan_clear(struct airoha_eth *eth)
++{
++	int i;
++
++	for (i = 0; i < AIROHA_FE_MC_MAX_VLAN_TABLE; i++) {
++		int err, j;
++		u32 val;
++
++		airoha_fe_wr(eth, REG_MC_VLAN_DATA, 0x0);
++
++		val = FIELD_PREP(MC_VLAN_CFG_TABLE_ID_MASK, i) |
++		      MC_VLAN_CFG_TABLE_SEL_MASK | MC_VLAN_CFG_RW_MASK;
++		airoha_fe_wr(eth, REG_MC_VLAN_CFG, val);
++		err = read_poll_timeout(airoha_fe_rr, val,
++					val & MC_VLAN_CFG_CMD_DONE_MASK,
++					USEC_PER_MSEC, 5 * USEC_PER_MSEC,
++					false, eth, REG_MC_VLAN_CFG);
++		if (err)
++			return err;
++
++		for (j = 0; j < AIROHA_FE_MC_MAX_VLAN_PORT; j++) {
++			airoha_fe_wr(eth, REG_MC_VLAN_DATA, 0x0);
++
++			val = FIELD_PREP(MC_VLAN_CFG_TABLE_ID_MASK, i) |
++			      FIELD_PREP(MC_VLAN_CFG_PORT_ID_MASK, j) |
++			      MC_VLAN_CFG_RW_MASK;
++			airoha_fe_wr(eth, REG_MC_VLAN_CFG, val);
++			err = read_poll_timeout(airoha_fe_rr, val,
++						val & MC_VLAN_CFG_CMD_DONE_MASK,
++						USEC_PER_MSEC,
++						5 * USEC_PER_MSEC, false, eth,
++						REG_MC_VLAN_CFG);
++			if (err)
++				return err;
++		}
++	}
++
++	return 0;
++}
++
++static void airoha_fe_crsn_qsel_init(struct airoha_eth *eth)
++{
++	/* CDM1_CRSN_QSEL */
++	airoha_fe_rmw(eth, REG_CDM1_CRSN_QSEL(CRSN_22 >> 2),
++		      CDM1_CRSN_QSEL_REASON_MASK(CRSN_22),
++		      FIELD_PREP(CDM1_CRSN_QSEL_REASON_MASK(CRSN_22),
++				 CDM_CRSN_QSEL_Q1));
++	airoha_fe_rmw(eth, REG_CDM1_CRSN_QSEL(CRSN_08 >> 2),
++		      CDM1_CRSN_QSEL_REASON_MASK(CRSN_08),
++		      FIELD_PREP(CDM1_CRSN_QSEL_REASON_MASK(CRSN_08),
++				 CDM_CRSN_QSEL_Q1));
++	airoha_fe_rmw(eth, REG_CDM1_CRSN_QSEL(CRSN_21 >> 2),
++		      CDM1_CRSN_QSEL_REASON_MASK(CRSN_21),
++		      FIELD_PREP(CDM1_CRSN_QSEL_REASON_MASK(CRSN_21),
++				 CDM_CRSN_QSEL_Q1));
++	airoha_fe_rmw(eth, REG_CDM1_CRSN_QSEL(CRSN_24 >> 2),
++		      CDM1_CRSN_QSEL_REASON_MASK(CRSN_24),
++		      FIELD_PREP(CDM1_CRSN_QSEL_REASON_MASK(CRSN_24),
++				 CDM_CRSN_QSEL_Q6));
++	airoha_fe_rmw(eth, REG_CDM1_CRSN_QSEL(CRSN_25 >> 2),
++		      CDM1_CRSN_QSEL_REASON_MASK(CRSN_25),
++		      FIELD_PREP(CDM1_CRSN_QSEL_REASON_MASK(CRSN_25),
++				 CDM_CRSN_QSEL_Q1));
++	/* CDM2_CRSN_QSEL */
++	airoha_fe_rmw(eth, REG_CDM2_CRSN_QSEL(CRSN_08 >> 2),
++		      CDM2_CRSN_QSEL_REASON_MASK(CRSN_08),
++		      FIELD_PREP(CDM2_CRSN_QSEL_REASON_MASK(CRSN_08),
++				 CDM_CRSN_QSEL_Q1));
++	airoha_fe_rmw(eth, REG_CDM2_CRSN_QSEL(CRSN_21 >> 2),
++		      CDM2_CRSN_QSEL_REASON_MASK(CRSN_21),
++		      FIELD_PREP(CDM2_CRSN_QSEL_REASON_MASK(CRSN_21),
++				 CDM_CRSN_QSEL_Q1));
++	airoha_fe_rmw(eth, REG_CDM2_CRSN_QSEL(CRSN_22 >> 2),
++		      CDM2_CRSN_QSEL_REASON_MASK(CRSN_22),
++		      FIELD_PREP(CDM2_CRSN_QSEL_REASON_MASK(CRSN_22),
++				 CDM_CRSN_QSEL_Q1));
++	airoha_fe_rmw(eth, REG_CDM2_CRSN_QSEL(CRSN_24 >> 2),
++		      CDM2_CRSN_QSEL_REASON_MASK(CRSN_24),
++		      FIELD_PREP(CDM2_CRSN_QSEL_REASON_MASK(CRSN_24),
++				 CDM_CRSN_QSEL_Q6));
++	airoha_fe_rmw(eth, REG_CDM2_CRSN_QSEL(CRSN_25 >> 2),
++		      CDM2_CRSN_QSEL_REASON_MASK(CRSN_25),
++		      FIELD_PREP(CDM2_CRSN_QSEL_REASON_MASK(CRSN_25),
++				 CDM_CRSN_QSEL_Q1));
++}
++
++static int airoha_fe_init(struct airoha_eth *eth)
++{
++	airoha_fe_maccr_init(eth);
++
++	/* PSE IQ reserve */
++	airoha_fe_rmw(eth, REG_PSE_IQ_REV1, PSE_IQ_RES1_P2_MASK,
++		      FIELD_PREP(PSE_IQ_RES1_P2_MASK, 0x10));
++	airoha_fe_rmw(eth, REG_PSE_IQ_REV2,
++		      PSE_IQ_RES2_P5_MASK | PSE_IQ_RES2_P4_MASK,
++		      FIELD_PREP(PSE_IQ_RES2_P5_MASK, 0x40) |
++		      FIELD_PREP(PSE_IQ_RES2_P4_MASK, 0x34));
++
++	/* enable FE copy engine for MC/KA/DPI */
++	airoha_fe_wr(eth, REG_FE_PCE_CFG,
++		     PCE_DPI_EN_MASK | PCE_KA_EN_MASK | PCE_MC_EN_MASK);
++	/* set vip queue selection to ring 1 */
++	airoha_fe_rmw(eth, REG_CDM1_FWD_CFG, CDM1_VIP_QSEL_MASK,
++		      FIELD_PREP(CDM1_VIP_QSEL_MASK, 0x4));
++	airoha_fe_rmw(eth, REG_CDM2_FWD_CFG, CDM2_VIP_QSEL_MASK,
++		      FIELD_PREP(CDM2_VIP_QSEL_MASK, 0x4));
++	/* set GDM4 source interface offset to 8 */
++	airoha_fe_rmw(eth, REG_GDM4_SRC_PORT_SET,
++		      GDM4_SPORT_OFF2_MASK |
++		      GDM4_SPORT_OFF1_MASK |
++		      GDM4_SPORT_OFF0_MASK,
++		      FIELD_PREP(GDM4_SPORT_OFF2_MASK, 8) |
++		      FIELD_PREP(GDM4_SPORT_OFF1_MASK, 8) |
++		      FIELD_PREP(GDM4_SPORT_OFF0_MASK, 8));
++
++	/* set PSE Page as 128B */
++	airoha_fe_rmw(eth, REG_FE_DMA_GLO_CFG,
++		      FE_DMA_GLO_L2_SPACE_MASK | FE_DMA_GLO_PG_SZ_MASK,
++		      FIELD_PREP(FE_DMA_GLO_L2_SPACE_MASK, 2) |
++		      FE_DMA_GLO_PG_SZ_MASK);
++	airoha_fe_wr(eth, REG_FE_RST_GLO_CFG,
++		     FE_RST_CORE_MASK | FE_RST_GDM3_MBI_ARB_MASK |
++		     FE_RST_GDM4_MBI_ARB_MASK);
++	usleep_range(1000, 2000);
++
++	/* connect RxRing1 and RxRing15 to PSE Port0 OQ-1
++	 * connect other rings to PSE Port0 OQ-0
++	 */
++	airoha_fe_wr(eth, REG_FE_CDM1_OQ_MAP0, BIT(4));
++	airoha_fe_wr(eth, REG_FE_CDM1_OQ_MAP1, BIT(28));
++	airoha_fe_wr(eth, REG_FE_CDM1_OQ_MAP2, BIT(4));
++	airoha_fe_wr(eth, REG_FE_CDM1_OQ_MAP3, BIT(28));
++
++	airoha_fe_vip_setup(eth);
++	airoha_fe_pse_ports_init(eth);
++
++	airoha_fe_set(eth, REG_GDM_MISC_CFG,
++		      GDM2_RDM_ACK_WAIT_PREF_MASK |
++		      GDM2_CHN_VLD_MODE_MASK);
++	airoha_fe_rmw(eth, REG_CDM2_FWD_CFG, CDM2_OAM_QSEL_MASK,
++		      FIELD_PREP(CDM2_OAM_QSEL_MASK, 15));
++
++	/* init fragment and assemble Force Port */
++	/* NPU Core-3, NPU Bridge Channel-3 */
++	airoha_fe_rmw(eth, REG_IP_FRAG_FP,
++		      IP_FRAGMENT_PORT_MASK | IP_FRAGMENT_NBQ_MASK,
++		      FIELD_PREP(IP_FRAGMENT_PORT_MASK, 6) |
++		      FIELD_PREP(IP_FRAGMENT_NBQ_MASK, 3));
++	/* QDMA LAN, RX Ring-22 */
++	airoha_fe_rmw(eth, REG_IP_FRAG_FP,
++		      IP_ASSEMBLE_PORT_MASK | IP_ASSEMBLE_NBQ_MASK,
++		      FIELD_PREP(IP_ASSEMBLE_PORT_MASK, 0) |
++		      FIELD_PREP(IP_ASSEMBLE_NBQ_MASK, 22));
++
++	airoha_fe_set(eth, REG_GDM3_FWD_CFG, GDM3_PAD_EN_MASK);
++	airoha_fe_set(eth, REG_GDM4_FWD_CFG, GDM4_PAD_EN_MASK);
++
++	airoha_fe_crsn_qsel_init(eth);
++
++	airoha_fe_clear(eth, REG_FE_CPORT_CFG, FE_CPORT_QUEUE_XFC_MASK);
++	airoha_fe_set(eth, REG_FE_CPORT_CFG, FE_CPORT_PORT_XFC_MASK);
++
++	/* default aging mode for mbi unlock issue */
++	airoha_fe_rmw(eth, REG_GDM2_CHN_RLS,
++		      MBI_RX_AGE_SEL_MASK | MBI_TX_AGE_SEL_MASK,
++		      FIELD_PREP(MBI_RX_AGE_SEL_MASK, 3) |
++		      FIELD_PREP(MBI_TX_AGE_SEL_MASK, 3));
++
++	/* disable IFC by default */
++	airoha_fe_clear(eth, REG_FE_CSR_IFC_CFG, FE_IFC_EN_MASK);
++
++	/* enable 1:N vlan action, init vlan table */
++	airoha_fe_set(eth, REG_MC_VLAN_EN, MC_VLAN_EN_MASK);
++
++	return airoha_fe_mc_vlan_clear(eth);
++}
++
++static int airoha_qdma_fill_rx_queue(struct airoha_queue *q)
++{
++	enum dma_data_direction dir = page_pool_get_dma_dir(q->page_pool);
++	struct airoha_qdma *qdma = q->qdma;
++	struct airoha_eth *eth = qdma->eth;
++	int qid = q - &qdma->q_rx[0];
++	int nframes = 0;
++
++	while (q->queued < q->ndesc - 1) {
++		struct airoha_queue_entry *e = &q->entry[q->head];
++		struct airoha_qdma_desc *desc = &q->desc[q->head];
++		struct page *page;
++		int offset;
++		u32 val;
++
++		page = page_pool_dev_alloc_frag(q->page_pool, &offset,
++						q->buf_size);
++		if (!page)
++			break;
++
++		q->head = (q->head + 1) % q->ndesc;
++		q->queued++;
++		nframes++;
++
++		e->buf = page_address(page) + offset;
++		e->dma_addr = page_pool_get_dma_addr(page) + offset;
++		e->dma_len = SKB_WITH_OVERHEAD(q->buf_size);
++
++		dma_sync_single_for_device(eth->dev, e->dma_addr, e->dma_len,
++					   dir);
++
++		val = FIELD_PREP(QDMA_DESC_LEN_MASK, e->dma_len);
++		WRITE_ONCE(desc->ctrl, cpu_to_le32(val));
++		WRITE_ONCE(desc->addr, cpu_to_le32(e->dma_addr));
++		val = FIELD_PREP(QDMA_DESC_NEXT_ID_MASK, q->head);
++		WRITE_ONCE(desc->data, cpu_to_le32(val));
++		WRITE_ONCE(desc->msg0, 0);
++		WRITE_ONCE(desc->msg1, 0);
++		WRITE_ONCE(desc->msg2, 0);
++		WRITE_ONCE(desc->msg3, 0);
++
++		airoha_qdma_rmw(qdma, REG_RX_CPU_IDX(qid),
++				RX_RING_CPU_IDX_MASK,
++				FIELD_PREP(RX_RING_CPU_IDX_MASK, q->head));
++	}
++
++	return nframes;
++}
++
++static int airoha_qdma_get_gdm_port(struct airoha_eth *eth,
++				    struct airoha_qdma_desc *desc)
++{
++	u32 port, sport, msg1 = le32_to_cpu(desc->msg1);
++
++	sport = FIELD_GET(QDMA_ETH_RXMSG_SPORT_MASK, msg1);
++	switch (sport) {
++	case 0x10 ... 0x13:
++		port = 0;
++		break;
++	case 0x2 ... 0x4:
++		port = sport - 1;
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	return port >= ARRAY_SIZE(eth->ports) ? -EINVAL : port;
++}
++
++static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
++{
++	enum dma_data_direction dir = page_pool_get_dma_dir(q->page_pool);
++	struct airoha_qdma *qdma = q->qdma;
++	struct airoha_eth *eth = qdma->eth;
++	int qid = q - &qdma->q_rx[0];
++	int done = 0;
++
++	while (done < budget) {
++		struct airoha_queue_entry *e = &q->entry[q->tail];
++		struct airoha_qdma_desc *desc = &q->desc[q->tail];
++		dma_addr_t dma_addr = le32_to_cpu(desc->addr);
++		u32 desc_ctrl = le32_to_cpu(desc->ctrl);
++		struct sk_buff *skb;
++		int len, p;
++
++		if (!(desc_ctrl & QDMA_DESC_DONE_MASK))
++			break;
++
++		if (!dma_addr)
++			break;
++
++		len = FIELD_GET(QDMA_DESC_LEN_MASK, desc_ctrl);
++		if (!len)
++			break;
++
++		q->tail = (q->tail + 1) % q->ndesc;
++		q->queued--;
++
++		dma_sync_single_for_cpu(eth->dev, dma_addr,
++					SKB_WITH_OVERHEAD(q->buf_size), dir);
++
++		p = airoha_qdma_get_gdm_port(eth, desc);
++		if (p < 0 || !eth->ports[p]) {
++			page_pool_put_full_page(q->page_pool,
++						virt_to_head_page(e->buf),
++						true);
++			continue;
++		}
++
++		skb = napi_build_skb(e->buf, q->buf_size);
++		if (!skb) {
++			page_pool_put_full_page(q->page_pool,
++						virt_to_head_page(e->buf),
++						true);
++			break;
++		}
++
++		skb_reserve(skb, 2);
++		__skb_put(skb, len);
++		skb_mark_for_recycle(skb);
++		skb->dev = eth->ports[p]->dev;
++		skb->protocol = eth_type_trans(skb, skb->dev);
++		skb->ip_summed = CHECKSUM_UNNECESSARY;
++		skb_record_rx_queue(skb, qid);
++		napi_gro_receive(&q->napi, skb);
++
++		done++;
++	}
++	airoha_qdma_fill_rx_queue(q);
++
++	return done;
++}
++
++static int airoha_qdma_rx_napi_poll(struct napi_struct *napi, int budget)
++{
++	struct airoha_queue *q = container_of(napi, struct airoha_queue, napi);
++	int cur, done = 0;
++
++	do {
++		cur = airoha_qdma_rx_process(q, budget - done);
++		done += cur;
++	} while (cur && done < budget);
++
++	if (done < budget && napi_complete(napi))
++		airoha_qdma_irq_enable(q->qdma, QDMA_INT_REG_IDX1,
++				       RX_DONE_INT_MASK);
++
++	return done;
++}
++
++static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
++				     struct airoha_qdma *qdma, int ndesc)
++{
++	const struct page_pool_params pp_params = {
++		.order = 0,
++		.pool_size = 256,
++		.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV |
++			 PP_FLAG_PAGE_FRAG,
++		.dma_dir = DMA_FROM_DEVICE,
++		.max_len = PAGE_SIZE,
++		.nid = NUMA_NO_NODE,
++		.dev = qdma->eth->dev,
++		.napi = &q->napi,
++	};
++	struct airoha_eth *eth = qdma->eth;
++	int qid = q - &qdma->q_rx[0], thr;
++	dma_addr_t dma_addr;
++
++	q->buf_size = PAGE_SIZE / 2;
++	q->ndesc = ndesc;
++	q->qdma = qdma;
++
++	q->entry = devm_kzalloc(eth->dev, q->ndesc * sizeof(*q->entry),
++				GFP_KERNEL);
++	if (!q->entry)
++		return -ENOMEM;
++
++	q->page_pool = page_pool_create(&pp_params);
++	if (IS_ERR(q->page_pool)) {
++		int err = PTR_ERR(q->page_pool);
++
++		q->page_pool = NULL;
++		return err;
++	}
++
++	q->desc = dmam_alloc_coherent(eth->dev, q->ndesc * sizeof(*q->desc),
++				      &dma_addr, GFP_KERNEL);
++	if (!q->desc)
++		return -ENOMEM;
++
++	netif_napi_add(eth->napi_dev, &q->napi, airoha_qdma_rx_napi_poll);
++
++	airoha_qdma_wr(qdma, REG_RX_RING_BASE(qid), dma_addr);
++	airoha_qdma_rmw(qdma, REG_RX_RING_SIZE(qid),
++			RX_RING_SIZE_MASK,
++			FIELD_PREP(RX_RING_SIZE_MASK, ndesc));
++
++	thr = clamp(ndesc >> 3, 1, 32);
++	airoha_qdma_rmw(qdma, REG_RX_RING_SIZE(qid), RX_RING_THR_MASK,
++			FIELD_PREP(RX_RING_THR_MASK, thr));
++	airoha_qdma_rmw(qdma, REG_RX_DMA_IDX(qid), RX_RING_DMA_IDX_MASK,
++			FIELD_PREP(RX_RING_DMA_IDX_MASK, q->head));
++
++	airoha_qdma_fill_rx_queue(q);
++
++	return 0;
++}
++
++static void airoha_qdma_cleanup_rx_queue(struct airoha_queue *q)
++{
++	struct airoha_eth *eth = q->qdma->eth;
++
++	while (q->queued) {
++		struct airoha_queue_entry *e = &q->entry[q->tail];
++		struct page *page = virt_to_head_page(e->buf);
++
++		dma_sync_single_for_cpu(eth->dev, e->dma_addr, e->dma_len,
++					page_pool_get_dma_dir(q->page_pool));
++		page_pool_put_full_page(q->page_pool, page, false);
++		q->tail = (q->tail + 1) % q->ndesc;
++		q->queued--;
++	}
++}
++
++static int airoha_qdma_init_rx(struct airoha_qdma *qdma)
++{
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
++		int err;
++
++		if (!(RX_DONE_INT_MASK & BIT(i))) {
++			/* rx-queue not binded to irq */
++			continue;
++		}
++
++		err = airoha_qdma_init_rx_queue(&qdma->q_rx[i], qdma,
++						RX_DSCP_NUM(i));
++		if (err)
++			return err;
++	}
++
++	return 0;
++}
++
++static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget)
++{
++	struct airoha_tx_irq_queue *irq_q;
++	int id, done = 0, irq_queued;
++	struct airoha_qdma *qdma;
++	struct airoha_eth *eth;
++	u32 status, head;
++
++	irq_q = container_of(napi, struct airoha_tx_irq_queue, napi);
++	qdma = irq_q->qdma;
++	id = irq_q - &qdma->q_tx_irq[0];
++	eth = qdma->eth;
++
++	status = airoha_qdma_rr(qdma, REG_IRQ_STATUS(id));
++	head = FIELD_GET(IRQ_HEAD_IDX_MASK, status);
++	head = head % irq_q->size;
++	irq_queued = FIELD_GET(IRQ_ENTRY_LEN_MASK, status);
++
++	while (irq_queued > 0 && done < budget) {
++		u32 qid, val = irq_q->q[head];
++		struct airoha_qdma_desc *desc;
++		struct airoha_queue_entry *e;
++		struct airoha_queue *q;
++		u32 index, desc_ctrl;
++		struct sk_buff *skb;
++
++		if (val == 0xff)
++			break;
++
++		irq_q->q[head] = 0xff; /* mark as done */
++		head = (head + 1) % irq_q->size;
++		irq_queued--;
++		done++;
++
++		qid = FIELD_GET(IRQ_RING_IDX_MASK, val);
++		if (qid >= ARRAY_SIZE(qdma->q_tx))
++			continue;
++
++		q = &qdma->q_tx[qid];
++		if (!q->ndesc)
++			continue;
++
++		index = FIELD_GET(IRQ_DESC_IDX_MASK, val);
++		if (index >= q->ndesc)
++			continue;
++
++		spin_lock_bh(&q->lock);
++
++		if (!q->queued)
++			goto unlock;
++
++		desc = &q->desc[index];
++		desc_ctrl = le32_to_cpu(desc->ctrl);
++
++		if (!(desc_ctrl & QDMA_DESC_DONE_MASK) &&
++		    !(desc_ctrl & QDMA_DESC_DROP_MASK))
++			goto unlock;
++
++		e = &q->entry[index];
++		skb = e->skb;
++
++		dma_unmap_single(eth->dev, e->dma_addr, e->dma_len,
++				 DMA_TO_DEVICE);
++		memset(e, 0, sizeof(*e));
++		WRITE_ONCE(desc->msg0, 0);
++		WRITE_ONCE(desc->msg1, 0);
++		q->queued--;
++
++		/* completion ring can report out-of-order indexes if hw QoS
++		 * is enabled and packets with different priority are queued
++		 * to same DMA ring. Take into account possible out-of-order
++		 * reports incrementing DMA ring tail pointer
++		 */
++		while (q->tail != q->head && !q->entry[q->tail].dma_addr)
++			q->tail = (q->tail + 1) % q->ndesc;
++
++		if (skb) {
++			u16 queue = skb_get_queue_mapping(skb);
++			struct netdev_queue *txq;
++
++			txq = netdev_get_tx_queue(skb->dev, queue);
++			netdev_tx_completed_queue(txq, 1, skb->len);
++			if (netif_tx_queue_stopped(txq) &&
++			    q->ndesc - q->queued >= q->free_thr)
++				netif_tx_wake_queue(txq);
++
++			dev_kfree_skb_any(skb);
++		}
++unlock:
++		spin_unlock_bh(&q->lock);
++	}
++
++	if (done) {
++		int i, len = done >> 7;
++
++		for (i = 0; i < len; i++)
++			airoha_qdma_rmw(qdma, REG_IRQ_CLEAR_LEN(id),
++					IRQ_CLEAR_LEN_MASK, 0x80);
++		airoha_qdma_rmw(qdma, REG_IRQ_CLEAR_LEN(id),
++				IRQ_CLEAR_LEN_MASK, (done & 0x7f));
++	}
++
++	if (done < budget && napi_complete(napi))
++		airoha_qdma_irq_enable(qdma, QDMA_INT_REG_IDX0,
++				       TX_DONE_INT_MASK(id));
++
++	return done;
++}
++
++static int airoha_qdma_init_tx_queue(struct airoha_queue *q,
++				     struct airoha_qdma *qdma, int size)
++{
++	struct airoha_eth *eth = qdma->eth;
++	int i, qid = q - &qdma->q_tx[0];
++	dma_addr_t dma_addr;
++
++	spin_lock_init(&q->lock);
++	q->ndesc = size;
++	q->qdma = qdma;
++	q->free_thr = 1 + MAX_SKB_FRAGS;
++
++	q->entry = devm_kzalloc(eth->dev, q->ndesc * sizeof(*q->entry),
++				GFP_KERNEL);
++	if (!q->entry)
++		return -ENOMEM;
++
++	q->desc = dmam_alloc_coherent(eth->dev, q->ndesc * sizeof(*q->desc),
++				      &dma_addr, GFP_KERNEL);
++	if (!q->desc)
++		return -ENOMEM;
++
++	for (i = 0; i < q->ndesc; i++) {
++		u32 val;
++
++		val = FIELD_PREP(QDMA_DESC_DONE_MASK, 1);
++		WRITE_ONCE(q->desc[i].ctrl, cpu_to_le32(val));
++	}
++
++	/* xmit ring drop default setting */
++	airoha_qdma_set(qdma, REG_TX_RING_BLOCKING(qid),
++			TX_RING_IRQ_BLOCKING_TX_DROP_EN_MASK);
++
++	airoha_qdma_wr(qdma, REG_TX_RING_BASE(qid), dma_addr);
++	airoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid), TX_RING_CPU_IDX_MASK,
++			FIELD_PREP(TX_RING_CPU_IDX_MASK, q->head));
++	airoha_qdma_rmw(qdma, REG_TX_DMA_IDX(qid), TX_RING_DMA_IDX_MASK,
++			FIELD_PREP(TX_RING_DMA_IDX_MASK, q->head));
++
++	return 0;
++}
++
++static int airoha_qdma_tx_irq_init(struct airoha_tx_irq_queue *irq_q,
++				   struct airoha_qdma *qdma, int size)
++{
++	int id = irq_q - &qdma->q_tx_irq[0];
++	struct airoha_eth *eth = qdma->eth;
++	dma_addr_t dma_addr;
++
++	netif_napi_add_tx(eth->napi_dev, &irq_q->napi,
++			  airoha_qdma_tx_napi_poll);
++	irq_q->q = dmam_alloc_coherent(eth->dev, size * sizeof(u32),
++				       &dma_addr, GFP_KERNEL);
++	if (!irq_q->q)
++		return -ENOMEM;
++
++	memset(irq_q->q, 0xff, size * sizeof(u32));
++	irq_q->size = size;
++	irq_q->qdma = qdma;
++
++	airoha_qdma_wr(qdma, REG_TX_IRQ_BASE(id), dma_addr);
++	airoha_qdma_rmw(qdma, REG_TX_IRQ_CFG(id), TX_IRQ_DEPTH_MASK,
++			FIELD_PREP(TX_IRQ_DEPTH_MASK, size));
++	airoha_qdma_rmw(qdma, REG_TX_IRQ_CFG(id), TX_IRQ_THR_MASK,
++			FIELD_PREP(TX_IRQ_THR_MASK, 1));
++
++	return 0;
++}
++
++static int airoha_qdma_init_tx(struct airoha_qdma *qdma)
++{
++	int i, err;
++
++	for (i = 0; i < ARRAY_SIZE(qdma->q_tx_irq); i++) {
++		err = airoha_qdma_tx_irq_init(&qdma->q_tx_irq[i], qdma,
++					      IRQ_QUEUE_LEN(i));
++		if (err)
++			return err;
++	}
++
++	for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
++		err = airoha_qdma_init_tx_queue(&qdma->q_tx[i], qdma,
++						TX_DSCP_NUM);
++		if (err)
++			return err;
++	}
++
++	return 0;
++}
++
++static void airoha_qdma_cleanup_tx_queue(struct airoha_queue *q)
++{
++	struct airoha_eth *eth = q->qdma->eth;
++
++	spin_lock_bh(&q->lock);
++	while (q->queued) {
++		struct airoha_queue_entry *e = &q->entry[q->tail];
++
++		dma_unmap_single(eth->dev, e->dma_addr, e->dma_len,
++				 DMA_TO_DEVICE);
++		dev_kfree_skb_any(e->skb);
++		e->skb = NULL;
++
++		q->tail = (q->tail + 1) % q->ndesc;
++		q->queued--;
++	}
++	spin_unlock_bh(&q->lock);
++}
++
++static int airoha_qdma_init_hfwd_queues(struct airoha_qdma *qdma)
++{
++	struct airoha_eth *eth = qdma->eth;
++	dma_addr_t dma_addr;
++	u32 status;
++	int size;
++
++	size = HW_DSCP_NUM * sizeof(struct airoha_qdma_fwd_desc);
++	qdma->hfwd.desc = dmam_alloc_coherent(eth->dev, size, &dma_addr,
++					      GFP_KERNEL);
++	if (!qdma->hfwd.desc)
++		return -ENOMEM;
++
++	airoha_qdma_wr(qdma, REG_FWD_DSCP_BASE, dma_addr);
++
++	size = AIROHA_MAX_PACKET_SIZE * HW_DSCP_NUM;
++	qdma->hfwd.q = dmam_alloc_coherent(eth->dev, size, &dma_addr,
++					   GFP_KERNEL);
++	if (!qdma->hfwd.q)
++		return -ENOMEM;
++
++	airoha_qdma_wr(qdma, REG_FWD_BUF_BASE, dma_addr);
++
++	airoha_qdma_rmw(qdma, REG_HW_FWD_DSCP_CFG,
++			HW_FWD_DSCP_PAYLOAD_SIZE_MASK,
++			FIELD_PREP(HW_FWD_DSCP_PAYLOAD_SIZE_MASK, 0));
++	airoha_qdma_rmw(qdma, REG_FWD_DSCP_LOW_THR, FWD_DSCP_LOW_THR_MASK,
++			FIELD_PREP(FWD_DSCP_LOW_THR_MASK, 128));
++	airoha_qdma_rmw(qdma, REG_LMGR_INIT_CFG,
++			LMGR_INIT_START | LMGR_SRAM_MODE_MASK |
++			HW_FWD_DESC_NUM_MASK,
++			FIELD_PREP(HW_FWD_DESC_NUM_MASK, HW_DSCP_NUM) |
++			LMGR_INIT_START);
++
++	return read_poll_timeout(airoha_qdma_rr, status,
++				 !(status & LMGR_INIT_START), USEC_PER_MSEC,
++				 30 * USEC_PER_MSEC, true, qdma,
++				 REG_LMGR_INIT_CFG);
++}
++
++static void airoha_qdma_init_qos(struct airoha_qdma *qdma)
++{
++	airoha_qdma_clear(qdma, REG_TXWRR_MODE_CFG, TWRR_WEIGHT_SCALE_MASK);
++	airoha_qdma_set(qdma, REG_TXWRR_MODE_CFG, TWRR_WEIGHT_BASE_MASK);
++
++	airoha_qdma_clear(qdma, REG_PSE_BUF_USAGE_CFG,
++			  PSE_BUF_ESTIMATE_EN_MASK);
++
++	airoha_qdma_set(qdma, REG_EGRESS_RATE_METER_CFG,
++			EGRESS_RATE_METER_EN_MASK |
++			EGRESS_RATE_METER_EQ_RATE_EN_MASK);
++	/* 2047us x 31 = 63.457ms */
++	airoha_qdma_rmw(qdma, REG_EGRESS_RATE_METER_CFG,
++			EGRESS_RATE_METER_WINDOW_SZ_MASK,
++			FIELD_PREP(EGRESS_RATE_METER_WINDOW_SZ_MASK, 0x1f));
++	airoha_qdma_rmw(qdma, REG_EGRESS_RATE_METER_CFG,
++			EGRESS_RATE_METER_TIMESLICE_MASK,
++			FIELD_PREP(EGRESS_RATE_METER_TIMESLICE_MASK, 0x7ff));
++
++	/* ratelimit init */
++	airoha_qdma_set(qdma, REG_GLB_TRTCM_CFG, GLB_TRTCM_EN_MASK);
++	/* fast-tick 25us */
++	airoha_qdma_rmw(qdma, REG_GLB_TRTCM_CFG, GLB_FAST_TICK_MASK,
++			FIELD_PREP(GLB_FAST_TICK_MASK, 25));
++	airoha_qdma_rmw(qdma, REG_GLB_TRTCM_CFG, GLB_SLOW_TICK_RATIO_MASK,
++			FIELD_PREP(GLB_SLOW_TICK_RATIO_MASK, 40));
++
++	airoha_qdma_set(qdma, REG_EGRESS_TRTCM_CFG, EGRESS_TRTCM_EN_MASK);
++	airoha_qdma_rmw(qdma, REG_EGRESS_TRTCM_CFG, EGRESS_FAST_TICK_MASK,
++			FIELD_PREP(EGRESS_FAST_TICK_MASK, 25));
++	airoha_qdma_rmw(qdma, REG_EGRESS_TRTCM_CFG,
++			EGRESS_SLOW_TICK_RATIO_MASK,
++			FIELD_PREP(EGRESS_SLOW_TICK_RATIO_MASK, 40));
++
++	airoha_qdma_set(qdma, REG_INGRESS_TRTCM_CFG, INGRESS_TRTCM_EN_MASK);
++	airoha_qdma_clear(qdma, REG_INGRESS_TRTCM_CFG,
++			  INGRESS_TRTCM_MODE_MASK);
++	airoha_qdma_rmw(qdma, REG_INGRESS_TRTCM_CFG, INGRESS_FAST_TICK_MASK,
++			FIELD_PREP(INGRESS_FAST_TICK_MASK, 125));
++	airoha_qdma_rmw(qdma, REG_INGRESS_TRTCM_CFG,
++			INGRESS_SLOW_TICK_RATIO_MASK,
++			FIELD_PREP(INGRESS_SLOW_TICK_RATIO_MASK, 8));
++
++	airoha_qdma_set(qdma, REG_SLA_TRTCM_CFG, SLA_TRTCM_EN_MASK);
++	airoha_qdma_rmw(qdma, REG_SLA_TRTCM_CFG, SLA_FAST_TICK_MASK,
++			FIELD_PREP(SLA_FAST_TICK_MASK, 25));
++	airoha_qdma_rmw(qdma, REG_SLA_TRTCM_CFG, SLA_SLOW_TICK_RATIO_MASK,
++			FIELD_PREP(SLA_SLOW_TICK_RATIO_MASK, 40));
++}
++
++static void airoha_qdma_init_qos_stats(struct airoha_qdma *qdma)
++{
++	int i;
++
++	for (i = 0; i < AIROHA_NUM_QOS_CHANNELS; i++) {
++		/* Tx-cpu transferred count */
++		airoha_qdma_wr(qdma, REG_CNTR_VAL(i << 1), 0);
++		airoha_qdma_wr(qdma, REG_CNTR_CFG(i << 1),
++			       CNTR_EN_MASK | CNTR_ALL_QUEUE_EN_MASK |
++			       CNTR_ALL_DSCP_RING_EN_MASK |
++			       FIELD_PREP(CNTR_CHAN_MASK, i));
++		/* Tx-fwd transferred count */
++		airoha_qdma_wr(qdma, REG_CNTR_VAL((i << 1) + 1), 0);
++		airoha_qdma_wr(qdma, REG_CNTR_CFG(i << 1),
++			       CNTR_EN_MASK | CNTR_ALL_QUEUE_EN_MASK |
++			       CNTR_ALL_DSCP_RING_EN_MASK |
++			       FIELD_PREP(CNTR_SRC_MASK, 1) |
++			       FIELD_PREP(CNTR_CHAN_MASK, i));
++	}
++}
++
++static int airoha_qdma_hw_init(struct airoha_qdma *qdma)
++{
++	int i;
++
++	/* clear pending irqs */
++	for (i = 0; i < ARRAY_SIZE(qdma->irqmask); i++)
++		airoha_qdma_wr(qdma, REG_INT_STATUS(i), 0xffffffff);
++
++	/* setup irqs */
++	airoha_qdma_irq_enable(qdma, QDMA_INT_REG_IDX0, INT_IDX0_MASK);
++	airoha_qdma_irq_enable(qdma, QDMA_INT_REG_IDX1, INT_IDX1_MASK);
++	airoha_qdma_irq_enable(qdma, QDMA_INT_REG_IDX4, INT_IDX4_MASK);
++
++	/* setup irq binding */
++	for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
++		if (!qdma->q_tx[i].ndesc)
++			continue;
++
++		if (TX_RING_IRQ_BLOCKING_MAP_MASK & BIT(i))
++			airoha_qdma_set(qdma, REG_TX_RING_BLOCKING(i),
++					TX_RING_IRQ_BLOCKING_CFG_MASK);
++		else
++			airoha_qdma_clear(qdma, REG_TX_RING_BLOCKING(i),
++					  TX_RING_IRQ_BLOCKING_CFG_MASK);
++	}
++
++	airoha_qdma_wr(qdma, REG_QDMA_GLOBAL_CFG,
++		       GLOBAL_CFG_RX_2B_OFFSET_MASK |
++		       FIELD_PREP(GLOBAL_CFG_DMA_PREFERENCE_MASK, 3) |
++		       GLOBAL_CFG_CPU_TXR_RR_MASK |
++		       GLOBAL_CFG_PAYLOAD_BYTE_SWAP_MASK |
++		       GLOBAL_CFG_MULTICAST_MODIFY_FP_MASK |
++		       GLOBAL_CFG_MULTICAST_EN_MASK |
++		       GLOBAL_CFG_IRQ0_EN_MASK | GLOBAL_CFG_IRQ1_EN_MASK |
++		       GLOBAL_CFG_TX_WB_DONE_MASK |
++		       FIELD_PREP(GLOBAL_CFG_MAX_ISSUE_NUM_MASK, 2));
++
++	airoha_qdma_init_qos(qdma);
++
++	/* disable qdma rx delay interrupt */
++	for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
++		if (!qdma->q_rx[i].ndesc)
++			continue;
++
++		airoha_qdma_clear(qdma, REG_RX_DELAY_INT_IDX(i),
++				  RX_DELAY_INT_MASK);
++	}
++
++	airoha_qdma_set(qdma, REG_TXQ_CNGST_CFG,
++			TXQ_CNGST_DROP_EN | TXQ_CNGST_DEI_DROP_EN);
++	airoha_qdma_init_qos_stats(qdma);
++
++	return 0;
++}
++
++static irqreturn_t airoha_irq_handler(int irq, void *dev_instance)
++{
++	struct airoha_qdma *qdma = dev_instance;
++	u32 intr[ARRAY_SIZE(qdma->irqmask)];
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(qdma->irqmask); i++) {
++		intr[i] = airoha_qdma_rr(qdma, REG_INT_STATUS(i));
++		intr[i] &= qdma->irqmask[i];
++		airoha_qdma_wr(qdma, REG_INT_STATUS(i), intr[i]);
++	}
++
++	if (!test_bit(DEV_STATE_INITIALIZED, &qdma->eth->state))
++		return IRQ_NONE;
++
++	if (intr[1] & RX_DONE_INT_MASK) {
++		airoha_qdma_irq_disable(qdma, QDMA_INT_REG_IDX1,
++					RX_DONE_INT_MASK);
++
++		for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
++			if (!qdma->q_rx[i].ndesc)
++				continue;
++
++			if (intr[1] & BIT(i))
++				napi_schedule(&qdma->q_rx[i].napi);
++		}
++	}
++
++	if (intr[0] & INT_TX_MASK) {
++		for (i = 0; i < ARRAY_SIZE(qdma->q_tx_irq); i++) {
++			if (!(intr[0] & TX_DONE_INT_MASK(i)))
++				continue;
++
++			airoha_qdma_irq_disable(qdma, QDMA_INT_REG_IDX0,
++						TX_DONE_INT_MASK(i));
++			napi_schedule(&qdma->q_tx_irq[i].napi);
++		}
++	}
++
++	return IRQ_HANDLED;
++}
++
++static int airoha_qdma_init(struct platform_device *pdev,
++			    struct airoha_eth *eth,
++			    struct airoha_qdma *qdma)
++{
++	int err, id = qdma - &eth->qdma[0];
++	const char *res;
++
++	spin_lock_init(&qdma->irq_lock);
++	qdma->eth = eth;
++
++	res = devm_kasprintf(eth->dev, GFP_KERNEL, "qdma%d", id);
++	if (!res)
++		return -ENOMEM;
++
++	qdma->regs = devm_platform_ioremap_resource_byname(pdev, res);
++	if (IS_ERR(qdma->regs))
++		return dev_err_probe(eth->dev, PTR_ERR(qdma->regs),
++				     "failed to iomap qdma%d regs\n", id);
++
++	qdma->irq = platform_get_irq(pdev, 4 * id);
++	if (qdma->irq < 0)
++		return qdma->irq;
++
++	err = devm_request_irq(eth->dev, qdma->irq, airoha_irq_handler,
++			       IRQF_SHARED, KBUILD_MODNAME, qdma);
++	if (err)
++		return err;
++
++	err = airoha_qdma_init_rx(qdma);
++	if (err)
++		return err;
++
++	err = airoha_qdma_init_tx(qdma);
++	if (err)
++		return err;
++
++	err = airoha_qdma_init_hfwd_queues(qdma);
++	if (err)
++		return err;
++
++	return airoha_qdma_hw_init(qdma);
++}
++
++static int airoha_hw_init(struct platform_device *pdev,
++			  struct airoha_eth *eth)
++{
++	int err, i;
++
++	/* disable xsi */
++	err = reset_control_bulk_assert(ARRAY_SIZE(eth->xsi_rsts),
++					eth->xsi_rsts);
++	if (err)
++		return err;
++
++	err = reset_control_bulk_assert(ARRAY_SIZE(eth->rsts), eth->rsts);
++	if (err)
++		return err;
++
++	msleep(20);
++	err = reset_control_bulk_deassert(ARRAY_SIZE(eth->rsts), eth->rsts);
++	if (err)
++		return err;
++
++	msleep(20);
++	err = airoha_fe_init(eth);
++	if (err)
++		return err;
++
++	for (i = 0; i < ARRAY_SIZE(eth->qdma); i++) {
++		err = airoha_qdma_init(pdev, eth, &eth->qdma[i]);
++		if (err)
++			return err;
++	}
++
++	set_bit(DEV_STATE_INITIALIZED, &eth->state);
++
++	return 0;
++}
++
++static void airoha_hw_cleanup(struct airoha_qdma *qdma)
++{
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
++		if (!qdma->q_rx[i].ndesc)
++			continue;
++
++		netif_napi_del(&qdma->q_rx[i].napi);
++		airoha_qdma_cleanup_rx_queue(&qdma->q_rx[i]);
++		if (qdma->q_rx[i].page_pool)
++			page_pool_destroy(qdma->q_rx[i].page_pool);
++	}
++
++	for (i = 0; i < ARRAY_SIZE(qdma->q_tx_irq); i++)
++		netif_napi_del(&qdma->q_tx_irq[i].napi);
++
++	for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
++		if (!qdma->q_tx[i].ndesc)
++			continue;
++
++		airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
++	}
++}
++
++static void airoha_qdma_start_napi(struct airoha_qdma *qdma)
++{
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(qdma->q_tx_irq); i++)
++		napi_enable(&qdma->q_tx_irq[i].napi);
++
++	for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
++		if (!qdma->q_rx[i].ndesc)
++			continue;
++
++		napi_enable(&qdma->q_rx[i].napi);
++	}
++}
++
++static void airoha_qdma_stop_napi(struct airoha_qdma *qdma)
++{
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(qdma->q_tx_irq); i++)
++		napi_disable(&qdma->q_tx_irq[i].napi);
++
++	for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
++		if (!qdma->q_rx[i].ndesc)
++			continue;
++
++		napi_disable(&qdma->q_rx[i].napi);
++	}
++}
++
++static void airoha_update_hw_stats(struct airoha_gdm_port *port)
++{
++	struct airoha_eth *eth = port->qdma->eth;
++	u32 val, i = 0;
++
++	spin_lock(&port->stats.lock);
++	u64_stats_update_begin(&port->stats.syncp);
++
++	/* TX */
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_H(port->id));
++	port->stats.tx_ok_pkts += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
++	port->stats.tx_ok_pkts += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_H(port->id));
++	port->stats.tx_ok_bytes += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id));
++	port->stats.tx_ok_bytes += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_DROP_CNT(port->id));
++	port->stats.tx_drops += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_BC_CNT(port->id));
++	port->stats.tx_broadcast += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_MC_CNT(port->id));
++	port->stats.tx_multicast += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_RUNT_CNT(port->id));
++	port->stats.tx_len[i] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_H(port->id));
++	port->stats.tx_len[i] += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id));
++	port->stats.tx_len[i++] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_H(port->id));
++	port->stats.tx_len[i] += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_L(port->id));
++	port->stats.tx_len[i++] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_H(port->id));
++	port->stats.tx_len[i] += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_L(port->id));
++	port->stats.tx_len[i++] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_H(port->id));
++	port->stats.tx_len[i] += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_L(port->id));
++	port->stats.tx_len[i++] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_H(port->id));
++	port->stats.tx_len[i] += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_L(port->id));
++	port->stats.tx_len[i++] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_H(port->id));
++	port->stats.tx_len[i] += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_L(port->id));
++	port->stats.tx_len[i++] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_LONG_CNT(port->id));
++	port->stats.tx_len[i++] += val;
++
++	/* RX */
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_H(port->id));
++	port->stats.rx_ok_pkts += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_L(port->id));
++	port->stats.rx_ok_pkts += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_H(port->id));
++	port->stats.rx_ok_bytes += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_L(port->id));
++	port->stats.rx_ok_bytes += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_DROP_CNT(port->id));
++	port->stats.rx_drops += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_BC_CNT(port->id));
++	port->stats.rx_broadcast += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_MC_CNT(port->id));
++	port->stats.rx_multicast += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ERROR_DROP_CNT(port->id));
++	port->stats.rx_errors += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_CRC_ERR_CNT(port->id));
++	port->stats.rx_crc_error += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_OVERFLOW_DROP_CNT(port->id));
++	port->stats.rx_over_errors += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_FRAG_CNT(port->id));
++	port->stats.rx_fragment += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_JABBER_CNT(port->id));
++	port->stats.rx_jabber += val;
++
++	i = 0;
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_RUNT_CNT(port->id));
++	port->stats.rx_len[i] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_H(port->id));
++	port->stats.rx_len[i] += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id));
++	port->stats.rx_len[i++] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_H(port->id));
++	port->stats.rx_len[i] += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_L(port->id));
++	port->stats.rx_len[i++] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_H(port->id));
++	port->stats.rx_len[i] += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_L(port->id));
++	port->stats.rx_len[i++] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_H(port->id));
++	port->stats.rx_len[i] += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_L(port->id));
++	port->stats.rx_len[i++] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_H(port->id));
++	port->stats.rx_len[i] += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_L(port->id));
++	port->stats.rx_len[i++] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_H(port->id));
++	port->stats.rx_len[i] += ((u64)val << 32);
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_L(port->id));
++	port->stats.rx_len[i++] += val;
++
++	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_LONG_CNT(port->id));
++	port->stats.rx_len[i++] += val;
++
++	/* reset mib counters */
++	airoha_fe_set(eth, REG_FE_GDM_MIB_CLEAR(port->id),
++		      FE_GDM_MIB_RX_CLEAR_MASK | FE_GDM_MIB_TX_CLEAR_MASK);
++
++	u64_stats_update_end(&port->stats.syncp);
++	spin_unlock(&port->stats.lock);
++}
++
++static int airoha_dev_open(struct net_device *dev)
++{
++	struct airoha_gdm_port *port = netdev_priv(dev);
++	struct airoha_qdma *qdma = port->qdma;
++	int err;
++
++	netif_tx_start_all_queues(dev);
++	err = airoha_set_gdm_ports(qdma->eth, true);
++	if (err)
++		return err;
++
++	if (netdev_uses_dsa(dev))
++		airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
++			      GDM_STAG_EN_MASK);
++	else
++		airoha_fe_clear(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
++				GDM_STAG_EN_MASK);
++
++	airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
++			GLOBAL_CFG_TX_DMA_EN_MASK |
++			GLOBAL_CFG_RX_DMA_EN_MASK);
++
++	return 0;
++}
++
++static int airoha_dev_stop(struct net_device *dev)
++{
++	struct airoha_gdm_port *port = netdev_priv(dev);
++	struct airoha_qdma *qdma = port->qdma;
++	int i, err;
++
++	netif_tx_disable(dev);
++	err = airoha_set_gdm_ports(qdma->eth, false);
++	if (err)
++		return err;
++
++	airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
++			  GLOBAL_CFG_TX_DMA_EN_MASK |
++			  GLOBAL_CFG_RX_DMA_EN_MASK);
++
++	for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
++		if (!qdma->q_tx[i].ndesc)
++			continue;
++
++		airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
++		netdev_tx_reset_subqueue(dev, i);
++	}
++
++	return 0;
++}
++
++static int airoha_dev_set_macaddr(struct net_device *dev, void *p)
++{
++	struct airoha_gdm_port *port = netdev_priv(dev);
++	int err;
++
++	err = eth_mac_addr(dev, p);
++	if (err)
++		return err;
++
++	airoha_set_macaddr(port, dev->dev_addr);
++
++	return 0;
++}
++
++static int airoha_dev_init(struct net_device *dev)
++{
++	struct airoha_gdm_port *port = netdev_priv(dev);
++
++	airoha_set_macaddr(port, dev->dev_addr);
++
++	return 0;
++}
++
++static void airoha_dev_get_stats64(struct net_device *dev,
++				   struct rtnl_link_stats64 *storage)
++{
++	struct airoha_gdm_port *port = netdev_priv(dev);
++	unsigned int start;
++
++	airoha_update_hw_stats(port);
++	do {
++		start = u64_stats_fetch_begin(&port->stats.syncp);
++		storage->rx_packets = port->stats.rx_ok_pkts;
++		storage->tx_packets = port->stats.tx_ok_pkts;
++		storage->rx_bytes = port->stats.rx_ok_bytes;
++		storage->tx_bytes = port->stats.tx_ok_bytes;
++		storage->multicast = port->stats.rx_multicast;
++		storage->rx_errors = port->stats.rx_errors;
++		storage->rx_dropped = port->stats.rx_drops;
++		storage->tx_dropped = port->stats.tx_drops;
++		storage->rx_crc_errors = port->stats.rx_crc_error;
++		storage->rx_over_errors = port->stats.rx_over_errors;
++	} while (u64_stats_fetch_retry(&port->stats.syncp, start));
++}
++
++static u16 airoha_dev_select_queue(struct net_device *dev, struct sk_buff *skb,
++				   struct net_device *sb_dev)
++{
++	struct airoha_gdm_port *port = netdev_priv(dev);
++	int queue, channel;
++
++	/* For dsa device select QoS channel according to the dsa user port
++	 * index, rely on port id otherwise. Select QoS queue based on the
++	 * skb priority.
++	 */
++	channel = netdev_uses_dsa(dev) ? skb_get_queue_mapping(skb) : port->id;
++	channel = channel % AIROHA_NUM_QOS_CHANNELS;
++	queue = (skb->priority - 1) % AIROHA_NUM_QOS_QUEUES; /* QoS queue */
++	queue = channel * AIROHA_NUM_QOS_QUEUES + queue;
++
++	return queue < dev->num_tx_queues ? queue : 0;
++}
++
++static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
++				   struct net_device *dev)
++{
++	struct airoha_gdm_port *port = netdev_priv(dev);
++	u32 nr_frags = 1 + skb_shinfo(skb)->nr_frags;
++	u32 msg0, msg1, len = skb_headlen(skb);
++	struct airoha_qdma *qdma = port->qdma;
++	struct netdev_queue *txq;
++	struct airoha_queue *q;
++	void *data = skb->data;
++	int i, qid;
++	u16 index;
++	u8 fport;
++
++	qid = skb_get_queue_mapping(skb) % ARRAY_SIZE(qdma->q_tx);
++	msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
++			  qid / AIROHA_NUM_QOS_QUEUES) |
++	       FIELD_PREP(QDMA_ETH_TXMSG_QUEUE_MASK,
++			  qid % AIROHA_NUM_QOS_QUEUES);
++	if (skb->ip_summed == CHECKSUM_PARTIAL)
++		msg0 |= FIELD_PREP(QDMA_ETH_TXMSG_TCO_MASK, 1) |
++			FIELD_PREP(QDMA_ETH_TXMSG_UCO_MASK, 1) |
++			FIELD_PREP(QDMA_ETH_TXMSG_ICO_MASK, 1);
++
++	/* TSO: fill MSS info in tcp checksum field */
++	if (skb_is_gso(skb)) {
++		if (skb_cow_head(skb, 0))
++			goto error;
++
++		if (skb_shinfo(skb)->gso_type & (SKB_GSO_TCPV4 |
++						 SKB_GSO_TCPV6)) {
++			__be16 csum = cpu_to_be16(skb_shinfo(skb)->gso_size);
++
++			tcp_hdr(skb)->check = (__force __sum16)csum;
++			msg0 |= FIELD_PREP(QDMA_ETH_TXMSG_TSO_MASK, 1);
++		}
++	}
++
++	fport = port->id == 4 ? FE_PSE_PORT_GDM4 : port->id;
++	msg1 = FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
++	       FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
++
++	q = &qdma->q_tx[qid];
++	if (WARN_ON_ONCE(!q->ndesc))
++		goto error;
++
++	spin_lock_bh(&q->lock);
++
++	txq = netdev_get_tx_queue(dev, qid);
++	if (q->queued + nr_frags > q->ndesc) {
++		/* not enough space in the queue */
++		netif_tx_stop_queue(txq);
++		spin_unlock_bh(&q->lock);
++		return NETDEV_TX_BUSY;
++	}
++
++	index = q->head;
++	for (i = 0; i < nr_frags; i++) {
++		struct airoha_qdma_desc *desc = &q->desc[index];
++		struct airoha_queue_entry *e = &q->entry[index];
++		skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
++		dma_addr_t addr;
++		u32 val;
++
++		addr = dma_map_single(dev->dev.parent, data, len,
++				      DMA_TO_DEVICE);
++		if (unlikely(dma_mapping_error(dev->dev.parent, addr)))
++			goto error_unmap;
++
++		index = (index + 1) % q->ndesc;
++
++		val = FIELD_PREP(QDMA_DESC_LEN_MASK, len);
++		if (i < nr_frags - 1)
++			val |= FIELD_PREP(QDMA_DESC_MORE_MASK, 1);
++		WRITE_ONCE(desc->ctrl, cpu_to_le32(val));
++		WRITE_ONCE(desc->addr, cpu_to_le32(addr));
++		val = FIELD_PREP(QDMA_DESC_NEXT_ID_MASK, index);
++		WRITE_ONCE(desc->data, cpu_to_le32(val));
++		WRITE_ONCE(desc->msg0, cpu_to_le32(msg0));
++		WRITE_ONCE(desc->msg1, cpu_to_le32(msg1));
++		WRITE_ONCE(desc->msg2, cpu_to_le32(0xffff));
++
++		e->skb = i ? NULL : skb;
++		e->dma_addr = addr;
++		e->dma_len = len;
++
++		data = skb_frag_address(frag);
++		len = skb_frag_size(frag);
++	}
++
++	q->head = index;
++	q->queued += i;
++
++	skb_tx_timestamp(skb);
++	netdev_tx_sent_queue(txq, skb->len);
++
++	if (netif_xmit_stopped(txq) || !netdev_xmit_more())
++		airoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid),
++				TX_RING_CPU_IDX_MASK,
++				FIELD_PREP(TX_RING_CPU_IDX_MASK, q->head));
++
++	if (q->ndesc - q->queued < q->free_thr)
++		netif_tx_stop_queue(txq);
++
++	spin_unlock_bh(&q->lock);
++
++	return NETDEV_TX_OK;
++
++error_unmap:
++	for (i--; i >= 0; i--) {
++		index = (q->head + i) % q->ndesc;
++		dma_unmap_single(dev->dev.parent, q->entry[index].dma_addr,
++				 q->entry[index].dma_len, DMA_TO_DEVICE);
++	}
++
++	spin_unlock_bh(&q->lock);
++error:
++	dev_kfree_skb_any(skb);
++	dev->stats.tx_dropped++;
++
++	return NETDEV_TX_OK;
++}
++
++static void airoha_ethtool_get_drvinfo(struct net_device *dev,
++				       struct ethtool_drvinfo *info)
++{
++	struct airoha_gdm_port *port = netdev_priv(dev);
++	struct airoha_eth *eth = port->qdma->eth;
++
++	strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
++	strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
++}
++
++static void airoha_ethtool_get_mac_stats(struct net_device *dev,
++					 struct ethtool_eth_mac_stats *stats)
++{
++	struct airoha_gdm_port *port = netdev_priv(dev);
++	unsigned int start;
++
++	airoha_update_hw_stats(port);
++	do {
++		start = u64_stats_fetch_begin(&port->stats.syncp);
++		stats->MulticastFramesXmittedOK = port->stats.tx_multicast;
++		stats->BroadcastFramesXmittedOK = port->stats.tx_broadcast;
++		stats->BroadcastFramesReceivedOK = port->stats.rx_broadcast;
++	} while (u64_stats_fetch_retry(&port->stats.syncp, start));
++}
++
++static const struct ethtool_rmon_hist_range airoha_ethtool_rmon_ranges[] = {
++	{    0,    64 },
++	{   65,   127 },
++	{  128,   255 },
++	{  256,   511 },
++	{  512,  1023 },
++	{ 1024,  1518 },
++	{ 1519, 10239 },
++	{},
++};
++
++static void
++airoha_ethtool_get_rmon_stats(struct net_device *dev,
++			      struct ethtool_rmon_stats *stats,
++			      const struct ethtool_rmon_hist_range **ranges)
++{
++	struct airoha_gdm_port *port = netdev_priv(dev);
++	struct airoha_hw_stats *hw_stats = &port->stats;
++	unsigned int start;
++
++	BUILD_BUG_ON(ARRAY_SIZE(airoha_ethtool_rmon_ranges) !=
++		     ARRAY_SIZE(hw_stats->tx_len) + 1);
++	BUILD_BUG_ON(ARRAY_SIZE(airoha_ethtool_rmon_ranges) !=
++		     ARRAY_SIZE(hw_stats->rx_len) + 1);
++
++	*ranges = airoha_ethtool_rmon_ranges;
++	airoha_update_hw_stats(port);
++	do {
++		int i;
++
++		start = u64_stats_fetch_begin(&port->stats.syncp);
++		stats->fragments = hw_stats->rx_fragment;
++		stats->jabbers = hw_stats->rx_jabber;
++		for (i = 0; i < ARRAY_SIZE(airoha_ethtool_rmon_ranges) - 1;
++		     i++) {
++			stats->hist[i] = hw_stats->rx_len[i];
++			stats->hist_tx[i] = hw_stats->tx_len[i];
++		}
++	} while (u64_stats_fetch_retry(&port->stats.syncp, start));
++}
++
++static int airoha_qdma_set_chan_tx_sched(struct airoha_gdm_port *port,
++					 int channel, enum tx_sched_mode mode,
++					 const u16 *weights, u8 n_weights)
++{
++	int i;
++
++	for (i = 0; i < AIROHA_NUM_TX_RING; i++)
++		airoha_qdma_clear(port->qdma, REG_QUEUE_CLOSE_CFG(channel),
++				  TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i));
++
++	for (i = 0; i < n_weights; i++) {
++		u32 status;
++		int err;
++
++		airoha_qdma_wr(port->qdma, REG_TXWRR_WEIGHT_CFG,
++			       TWRR_RW_CMD_MASK |
++			       FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) |
++			       FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) |
++			       FIELD_PREP(TWRR_VALUE_MASK, weights[i]));
++		err = read_poll_timeout(airoha_qdma_rr, status,
++					status & TWRR_RW_CMD_DONE,
++					USEC_PER_MSEC, 10 * USEC_PER_MSEC,
++					true, port->qdma,
++					REG_TXWRR_WEIGHT_CFG);
++		if (err)
++			return err;
++	}
++
++	airoha_qdma_rmw(port->qdma, REG_CHAN_QOS_MODE(channel >> 3),
++			CHAN_QOS_MODE_MASK(channel),
++			mode << __ffs(CHAN_QOS_MODE_MASK(channel)));
++
++	return 0;
++}
++
++static int airoha_qdma_set_tx_prio_sched(struct airoha_gdm_port *port,
++					 int channel)
++{
++	static const u16 w[AIROHA_NUM_QOS_QUEUES] = {};
++
++	return airoha_qdma_set_chan_tx_sched(port, channel, TC_SCH_SP, w,
++					     ARRAY_SIZE(w));
++}
++
++static int airoha_qdma_set_tx_ets_sched(struct airoha_gdm_port *port,
++					int channel,
++					struct tc_ets_qopt_offload *opt)
++{
++	struct tc_ets_qopt_offload_replace_params *p = &opt->replace_params;
++	enum tx_sched_mode mode = TC_SCH_SP;
++	u16 w[AIROHA_NUM_QOS_QUEUES] = {};
++	int i, nstrict = 0, nwrr, qidx;
++
++	if (p->bands > AIROHA_NUM_QOS_QUEUES)
++		return -EINVAL;
++
++	for (i = 0; i < p->bands; i++) {
++		if (!p->quanta[i])
++			nstrict++;
++	}
++
++	/* this configuration is not supported by the hw */
++	if (nstrict == AIROHA_NUM_QOS_QUEUES - 1)
++		return -EINVAL;
++
++	/* EN7581 SoC supports fixed QoS band priority where WRR queues have
++	 * lowest priorities with respect to SP ones.
++	 * e.g: WRR0, WRR1, .., WRRm, SP0, SP1, .., SPn
++	 */
++	nwrr = p->bands - nstrict;
++	qidx = nstrict && nwrr ? nstrict : 0;
++	for (i = 1; i <= p->bands; i++) {
++		if (p->priomap[i % AIROHA_NUM_QOS_QUEUES] != qidx)
++			return -EINVAL;
++
++		qidx = i == nwrr ? 0 : qidx + 1;
++	}
++
++	for (i = 0; i < nwrr; i++)
++		w[i] = p->weights[nstrict + i];
++
++	if (!nstrict)
++		mode = TC_SCH_WRR8;
++	else if (nstrict < AIROHA_NUM_QOS_QUEUES - 1)
++		mode = nstrict + 1;
++
++	return airoha_qdma_set_chan_tx_sched(port, channel, mode, w,
++					     ARRAY_SIZE(w));
++}
++
++static int airoha_qdma_get_tx_ets_stats(struct airoha_gdm_port *port,
++					int channel,
++					struct tc_ets_qopt_offload *opt)
++{
++	u64 cpu_tx_packets = airoha_qdma_rr(port->qdma,
++					    REG_CNTR_VAL(channel << 1));
++	u64 fwd_tx_packets = airoha_qdma_rr(port->qdma,
++					    REG_CNTR_VAL((channel << 1) + 1));
++	u64 tx_packets = (cpu_tx_packets - port->cpu_tx_packets) +
++			 (fwd_tx_packets - port->fwd_tx_packets);
++	_bstats_update(opt->stats.bstats, 0, tx_packets);
++
++	port->cpu_tx_packets = cpu_tx_packets;
++	port->fwd_tx_packets = fwd_tx_packets;
++
++	return 0;
++}
++
++static int airoha_tc_setup_qdisc_ets(struct airoha_gdm_port *port,
++				     struct tc_ets_qopt_offload *opt)
++{
++	int channel = TC_H_MAJ(opt->handle) >> 16;
++
++	if (opt->parent == TC_H_ROOT)
++		return -EINVAL;
++
++	switch (opt->command) {
++	case TC_ETS_REPLACE:
++		return airoha_qdma_set_tx_ets_sched(port, channel, opt);
++	case TC_ETS_DESTROY:
++		/* PRIO is default qdisc scheduler */
++		return airoha_qdma_set_tx_prio_sched(port, channel);
++	case TC_ETS_STATS:
++		return airoha_qdma_get_tx_ets_stats(port, channel, opt);
++	default:
++		return -EOPNOTSUPP;
++	}
++}
++
++static int airoha_qdma_get_trtcm_param(struct airoha_qdma *qdma, int channel,
++				       u32 addr, enum trtcm_param_type param,
++				       enum trtcm_mode_type mode,
++				       u32 *val_low, u32 *val_high)
++{
++	u32 idx = QDMA_METER_IDX(channel), group = QDMA_METER_GROUP(channel);
++	u32 val, config = FIELD_PREP(TRTCM_PARAM_TYPE_MASK, param) |
++			  FIELD_PREP(TRTCM_METER_GROUP_MASK, group) |
++			  FIELD_PREP(TRTCM_PARAM_INDEX_MASK, idx) |
++			  FIELD_PREP(TRTCM_PARAM_RATE_TYPE_MASK, mode);
++
++	airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config);
++	if (read_poll_timeout(airoha_qdma_rr, val,
++			      val & TRTCM_PARAM_RW_DONE_MASK,
++			      USEC_PER_MSEC, 10 * USEC_PER_MSEC, true,
++			      qdma, REG_TRTCM_CFG_PARAM(addr)))
++		return -ETIMEDOUT;
++
++	*val_low = airoha_qdma_rr(qdma, REG_TRTCM_DATA_LOW(addr));
++	if (val_high)
++		*val_high = airoha_qdma_rr(qdma, REG_TRTCM_DATA_HIGH(addr));
++
++	return 0;
++}
++
++static int airoha_qdma_set_trtcm_param(struct airoha_qdma *qdma, int channel,
++				       u32 addr, enum trtcm_param_type param,
++				       enum trtcm_mode_type mode, u32 val)
++{
++	u32 idx = QDMA_METER_IDX(channel), group = QDMA_METER_GROUP(channel);
++	u32 config = TRTCM_PARAM_RW_MASK |
++		     FIELD_PREP(TRTCM_PARAM_TYPE_MASK, param) |
++		     FIELD_PREP(TRTCM_METER_GROUP_MASK, group) |
++		     FIELD_PREP(TRTCM_PARAM_INDEX_MASK, idx) |
++		     FIELD_PREP(TRTCM_PARAM_RATE_TYPE_MASK, mode);
++
++	airoha_qdma_wr(qdma, REG_TRTCM_DATA_LOW(addr), val);
++	airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config);
++
++	return read_poll_timeout(airoha_qdma_rr, val,
++				 val & TRTCM_PARAM_RW_DONE_MASK,
++				 USEC_PER_MSEC, 10 * USEC_PER_MSEC, true,
++				 qdma, REG_TRTCM_CFG_PARAM(addr));
++}
++
++static int airoha_qdma_set_trtcm_config(struct airoha_qdma *qdma, int channel,
++					u32 addr, enum trtcm_mode_type mode,
++					bool enable, u32 enable_mask)
++{
++	u32 val;
++
++	if (airoha_qdma_get_trtcm_param(qdma, channel, addr, TRTCM_MISC_MODE,
++					mode, &val, NULL))
++		return -EINVAL;
++
++	val = enable ? val | enable_mask : val & ~enable_mask;
++
++	return airoha_qdma_set_trtcm_param(qdma, channel, addr, TRTCM_MISC_MODE,
++					   mode, val);
++}
++
++static int airoha_qdma_set_trtcm_token_bucket(struct airoha_qdma *qdma,
++					      int channel, u32 addr,
++					      enum trtcm_mode_type mode,
++					      u32 rate_val, u32 bucket_size)
++{
++	u32 val, config, tick, unit, rate, rate_frac;
++	int err;
++
++	if (airoha_qdma_get_trtcm_param(qdma, channel, addr, TRTCM_MISC_MODE,
++					mode, &config, NULL))
++		return -EINVAL;
++
++	val = airoha_qdma_rr(qdma, addr);
++	tick = FIELD_GET(INGRESS_FAST_TICK_MASK, val);
++	if (config & TRTCM_TICK_SEL)
++		tick *= FIELD_GET(INGRESS_SLOW_TICK_RATIO_MASK, val);
++	if (!tick)
++		return -EINVAL;
++
++	unit = (config & TRTCM_PKT_MODE) ? 1000000 / tick : 8000 / tick;
++	if (!unit)
++		return -EINVAL;
++
++	rate = rate_val / unit;
++	rate_frac = rate_val % unit;
++	rate_frac = FIELD_PREP(TRTCM_TOKEN_RATE_MASK, rate_frac) / unit;
++	rate = FIELD_PREP(TRTCM_TOKEN_RATE_MASK, rate) |
++	       FIELD_PREP(TRTCM_TOKEN_RATE_FRACTION_MASK, rate_frac);
++
++	err = airoha_qdma_set_trtcm_param(qdma, channel, addr,
++					  TRTCM_TOKEN_RATE_MODE, mode, rate);
++	if (err)
++		return err;
++
++	val = max_t(u32, bucket_size, MIN_TOKEN_SIZE);
++	val = min_t(u32, __fls(val), MAX_TOKEN_SIZE_OFFSET);
++
++	return airoha_qdma_set_trtcm_param(qdma, channel, addr,
++					   TRTCM_BUCKETSIZE_SHIFT_MODE,
++					   mode, val);
++}
++
++static int airoha_qdma_set_tx_rate_limit(struct airoha_gdm_port *port,
++					 int channel, u32 rate,
++					 u32 bucket_size)
++{
++	int i, err;
++
++	for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
++		err = airoha_qdma_set_trtcm_config(port->qdma, channel,
++						   REG_EGRESS_TRTCM_CFG, i,
++						   !!rate, TRTCM_METER_MODE);
++		if (err)
++			return err;
++
++		err = airoha_qdma_set_trtcm_token_bucket(port->qdma, channel,
++							 REG_EGRESS_TRTCM_CFG,
++							 i, rate, bucket_size);
++		if (err)
++			return err;
++	}
++
++	return 0;
++}
++
++static int airoha_tc_htb_alloc_leaf_queue(struct airoha_gdm_port *port,
++					  struct tc_htb_qopt_offload *opt)
++{
++	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
++	u32 rate = div_u64(opt->rate, 1000) << 3; /* kbps */
++	struct net_device *dev = port->dev;
++	int num_tx_queues = dev->real_num_tx_queues;
++	int err;
++
++	if (opt->parent_classid != TC_HTB_CLASSID_ROOT) {
++		NL_SET_ERR_MSG_MOD(opt->extack, "invalid parent classid");
++		return -EINVAL;
++	}
++
++	err = airoha_qdma_set_tx_rate_limit(port, channel, rate, opt->quantum);
++	if (err) {
++		NL_SET_ERR_MSG_MOD(opt->extack,
++				   "failed configuring htb offload");
++		return err;
++	}
++
++	if (opt->command == TC_HTB_NODE_MODIFY)
++		return 0;
++
++	err = netif_set_real_num_tx_queues(dev, num_tx_queues + 1);
++	if (err) {
++		airoha_qdma_set_tx_rate_limit(port, channel, 0, opt->quantum);
++		NL_SET_ERR_MSG_MOD(opt->extack,
++				   "failed setting real_num_tx_queues");
++		return err;
++	}
++
++	set_bit(channel, port->qos_sq_bmap);
++	opt->qid = AIROHA_NUM_TX_RING + channel;
++
++	return 0;
++}
++
++static void airoha_tc_remove_htb_queue(struct airoha_gdm_port *port, int queue)
++{
++	struct net_device *dev = port->dev;
++
++	netif_set_real_num_tx_queues(dev, dev->real_num_tx_queues - 1);
++	airoha_qdma_set_tx_rate_limit(port, queue + 1, 0, 0);
++	clear_bit(queue, port->qos_sq_bmap);
++}
++
++static int airoha_tc_htb_delete_leaf_queue(struct airoha_gdm_port *port,
++					   struct tc_htb_qopt_offload *opt)
++{
++	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
++
++	if (!test_bit(channel, port->qos_sq_bmap)) {
++		NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
++		return -EINVAL;
++	}
++
++	airoha_tc_remove_htb_queue(port, channel);
++
++	return 0;
++}
++
++static int airoha_tc_htb_destroy(struct airoha_gdm_port *port)
++{
++	int q;
++
++	for_each_set_bit(q, port->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
++		airoha_tc_remove_htb_queue(port, q);
++
++	return 0;
++}
++
++static int airoha_tc_get_htb_get_leaf_queue(struct airoha_gdm_port *port,
++					    struct tc_htb_qopt_offload *opt)
++{
++	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
++
++	if (!test_bit(channel, port->qos_sq_bmap)) {
++		NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
++		return -EINVAL;
++	}
++
++	opt->qid = channel;
++
++	return 0;
++}
++
++static int airoha_tc_setup_qdisc_htb(struct airoha_gdm_port *port,
++				     struct tc_htb_qopt_offload *opt)
++{
++	switch (opt->command) {
++	case TC_HTB_CREATE:
++		break;
++	case TC_HTB_DESTROY:
++		return airoha_tc_htb_destroy(port);
++	case TC_HTB_NODE_MODIFY:
++	case TC_HTB_LEAF_ALLOC_QUEUE:
++		return airoha_tc_htb_alloc_leaf_queue(port, opt);
++	case TC_HTB_LEAF_DEL:
++	case TC_HTB_LEAF_DEL_LAST:
++	case TC_HTB_LEAF_DEL_LAST_FORCE:
++		return airoha_tc_htb_delete_leaf_queue(port, opt);
++	case TC_HTB_LEAF_QUERY_QUEUE:
++		return airoha_tc_get_htb_get_leaf_queue(port, opt);
++	default:
++		return -EOPNOTSUPP;
++	}
++
++	return 0;
++}
++
++static int airoha_dev_tc_setup(struct net_device *dev, enum tc_setup_type type,
++			       void *type_data)
++{
++	struct airoha_gdm_port *port = netdev_priv(dev);
++
++	switch (type) {
++	case TC_SETUP_QDISC_ETS:
++		return airoha_tc_setup_qdisc_ets(port, type_data);
++	case TC_SETUP_QDISC_HTB:
++		return airoha_tc_setup_qdisc_htb(port, type_data);
++	default:
++		return -EOPNOTSUPP;
++	}
++}
++
++static const struct net_device_ops airoha_netdev_ops = {
++	.ndo_init		= airoha_dev_init,
++	.ndo_open		= airoha_dev_open,
++	.ndo_stop		= airoha_dev_stop,
++	.ndo_select_queue	= airoha_dev_select_queue,
++	.ndo_start_xmit		= airoha_dev_xmit,
++	.ndo_get_stats64        = airoha_dev_get_stats64,
++	.ndo_set_mac_address	= airoha_dev_set_macaddr,
++	.ndo_setup_tc		= airoha_dev_tc_setup,
++};
++
++static const struct ethtool_ops airoha_ethtool_ops = {
++	.get_drvinfo		= airoha_ethtool_get_drvinfo,
++	.get_eth_mac_stats      = airoha_ethtool_get_mac_stats,
++	.get_rmon_stats		= airoha_ethtool_get_rmon_stats,
++};
++
++static int airoha_alloc_gdm_port(struct airoha_eth *eth, struct device_node *np)
++{
++	const __be32 *id_ptr = of_get_property(np, "reg", NULL);
++	struct airoha_gdm_port *port;
++	struct airoha_qdma *qdma;
++	struct net_device *dev;
++	int err, index;
++	u32 id;
++
++	if (!id_ptr) {
++		dev_err(eth->dev, "missing gdm port id\n");
++		return -EINVAL;
++	}
++
++	id = be32_to_cpup(id_ptr);
++	index = id - 1;
++
++	if (!id || id > ARRAY_SIZE(eth->ports)) {
++		dev_err(eth->dev, "invalid gdm port id: %d\n", id);
++		return -EINVAL;
++	}
++
++	if (eth->ports[index]) {
++		dev_err(eth->dev, "duplicate gdm port id: %d\n", id);
++		return -EINVAL;
++	}
++
++	dev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*port),
++				      AIROHA_NUM_NETDEV_TX_RINGS,
++				      AIROHA_NUM_RX_RING);
++	if (!dev) {
++		dev_err(eth->dev, "alloc_etherdev failed\n");
++		return -ENOMEM;
++	}
++
++	qdma = &eth->qdma[index % AIROHA_MAX_NUM_QDMA];
++	dev->netdev_ops = &airoha_netdev_ops;
++	dev->ethtool_ops = &airoha_ethtool_ops;
++	dev->max_mtu = AIROHA_MAX_MTU;
++	dev->watchdog_timeo = 5 * HZ;
++	dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM |
++			   NETIF_F_TSO6 | NETIF_F_IPV6_CSUM |
++			   NETIF_F_SG | NETIF_F_TSO |
++			   NETIF_F_HW_TC;
++	dev->features |= dev->hw_features;
++	dev->dev.of_node = np;
++	dev->irq = qdma->irq;
++	SET_NETDEV_DEV(dev, eth->dev);
++
++	/* reserve hw queues for HTB offloading */
++	err = netif_set_real_num_tx_queues(dev, AIROHA_NUM_TX_RING);
++	if (err)
++		return err;
++
++	err = of_get_ethdev_address(np, dev);
++	if (err) {
++		if (err == -EPROBE_DEFER)
++			return err;
++
++		eth_hw_addr_random(dev);
++		dev_info(eth->dev, "generated random MAC address %pM\n",
++			 dev->dev_addr);
++	}
++
++	port = netdev_priv(dev);
++	u64_stats_init(&port->stats.syncp);
++	spin_lock_init(&port->stats.lock);
++	port->qdma = qdma;
++	port->dev = dev;
++	port->id = id;
++	eth->ports[index] = port;
++
++	return register_netdev(dev);
++}
++
++static int airoha_probe(struct platform_device *pdev)
++{
++	struct device_node *np;
++	struct airoha_eth *eth;
++	int i, err;
++
++	eth = devm_kzalloc(&pdev->dev, sizeof(*eth), GFP_KERNEL);
++	if (!eth)
++		return -ENOMEM;
++
++	eth->dev = &pdev->dev;
++
++	err = dma_set_mask_and_coherent(eth->dev, DMA_BIT_MASK(32));
++	if (err) {
++		dev_err(eth->dev, "failed configuring DMA mask\n");
++		return err;
++	}
++
++	eth->fe_regs = devm_platform_ioremap_resource_byname(pdev, "fe");
++	if (IS_ERR(eth->fe_regs))
++		return dev_err_probe(eth->dev, PTR_ERR(eth->fe_regs),
++				     "failed to iomap fe regs\n");
++
++	eth->rsts[0].id = "fe";
++	eth->rsts[1].id = "pdma";
++	eth->rsts[2].id = "qdma";
++	err = devm_reset_control_bulk_get_exclusive(eth->dev,
++						    ARRAY_SIZE(eth->rsts),
++						    eth->rsts);
++	if (err) {
++		dev_err(eth->dev, "failed to get bulk reset lines\n");
++		return err;
++	}
++
++	eth->xsi_rsts[0].id = "xsi-mac";
++	eth->xsi_rsts[1].id = "hsi0-mac";
++	eth->xsi_rsts[2].id = "hsi1-mac";
++	eth->xsi_rsts[3].id = "hsi-mac";
++	eth->xsi_rsts[4].id = "xfp-mac";
++	err = devm_reset_control_bulk_get_exclusive(eth->dev,
++						    ARRAY_SIZE(eth->xsi_rsts),
++						    eth->xsi_rsts);
++	if (err) {
++		dev_err(eth->dev, "failed to get bulk xsi reset lines\n");
++		return err;
++	}
++
++	eth->napi_dev = alloc_netdev_dummy(0);
++	if (!eth->napi_dev)
++		return -ENOMEM;
++
++	/* Enable threaded NAPI by default */
++	eth->napi_dev->threaded = true;
++	strscpy(eth->napi_dev->name, "qdma_eth", sizeof(eth->napi_dev->name));
++	platform_set_drvdata(pdev, eth);
++
++	err = airoha_hw_init(pdev, eth);
++	if (err)
++		goto error_hw_cleanup;
++
++	for (i = 0; i < ARRAY_SIZE(eth->qdma); i++)
++		airoha_qdma_start_napi(&eth->qdma[i]);
++
++	for_each_child_of_node(pdev->dev.of_node, np) {
++		if (!of_device_is_compatible(np, "airoha,eth-mac"))
++			continue;
++
++		if (!of_device_is_available(np))
++			continue;
++
++		err = airoha_alloc_gdm_port(eth, np);
++		if (err) {
++			of_node_put(np);
++			goto error_napi_stop;
++		}
++	}
++
++	return 0;
++
++error_napi_stop:
++	for (i = 0; i < ARRAY_SIZE(eth->qdma); i++)
++		airoha_qdma_stop_napi(&eth->qdma[i]);
++error_hw_cleanup:
++	for (i = 0; i < ARRAY_SIZE(eth->qdma); i++)
++		airoha_hw_cleanup(&eth->qdma[i]);
++
++	for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
++		struct airoha_gdm_port *port = eth->ports[i];
++
++		if (port && port->dev->reg_state == NETREG_REGISTERED)
++			unregister_netdev(port->dev);
++	}
++	free_netdev(eth->napi_dev);
++	platform_set_drvdata(pdev, NULL);
++
++	return err;
++}
++
++static void airoha_remove(struct platform_device *pdev)
++{
++	struct airoha_eth *eth = platform_get_drvdata(pdev);
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(eth->qdma); i++) {
++		airoha_qdma_stop_napi(&eth->qdma[i]);
++		airoha_hw_cleanup(&eth->qdma[i]);
++	}
++
++	for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
++		struct airoha_gdm_port *port = eth->ports[i];
++
++		if (!port)
++			continue;
++
++		airoha_dev_stop(port->dev);
++		unregister_netdev(port->dev);
++	}
++	free_netdev(eth->napi_dev);
++
++	platform_set_drvdata(pdev, NULL);
++}
++
++static const struct of_device_id of_airoha_match[] = {
++	{ .compatible = "airoha,en7581-eth" },
++	{ /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, of_airoha_match);
++
++static struct platform_driver airoha_driver = {
++	.probe = airoha_probe,
++	.remove_new = airoha_remove,
++	.driver = {
++		.name = KBUILD_MODNAME,
++		.of_match_table = of_airoha_match,
++	},
++};
++module_platform_driver(airoha_driver);
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Lorenzo Bianconi <[email protected]>");
++MODULE_DESCRIPTION("Ethernet driver for Airoha SoC");
+--- a/drivers/net/ethernet/mediatek/airoha_eth.c
++++ /dev/null
+@@ -1,3359 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0-only
+-/*
+- * Copyright (c) 2024 AIROHA Inc
+- * Author: Lorenzo Bianconi <[email protected]>
+- */
+-#include <linux/etherdevice.h>
+-#include <linux/iopoll.h>
+-#include <linux/kernel.h>
+-#include <linux/netdevice.h>
+-#include <linux/of.h>
+-#include <linux/of_net.h>
+-#include <linux/platform_device.h>
+-#include <linux/reset.h>
+-#include <linux/tcp.h>
+-#include <linux/u64_stats_sync.h>
+-#include <net/dsa.h>
+-#include <net/page_pool/helpers.h>
+-#include <net/pkt_cls.h>
+-#include <uapi/linux/ppp_defs.h>
+-
+-#define AIROHA_MAX_NUM_GDM_PORTS	1
+-#define AIROHA_MAX_NUM_QDMA		2
+-#define AIROHA_MAX_NUM_RSTS		3
+-#define AIROHA_MAX_NUM_XSI_RSTS		5
+-#define AIROHA_MAX_MTU			2000
+-#define AIROHA_MAX_PACKET_SIZE		2048
+-#define AIROHA_NUM_QOS_CHANNELS		4
+-#define AIROHA_NUM_QOS_QUEUES		8
+-#define AIROHA_NUM_TX_RING		32
+-#define AIROHA_NUM_RX_RING		32
+-#define AIROHA_NUM_NETDEV_TX_RINGS	(AIROHA_NUM_TX_RING + \
+-					 AIROHA_NUM_QOS_CHANNELS)
+-#define AIROHA_FE_MC_MAX_VLAN_TABLE	64
+-#define AIROHA_FE_MC_MAX_VLAN_PORT	16
+-#define AIROHA_NUM_TX_IRQ		2
+-#define HW_DSCP_NUM			2048
+-#define IRQ_QUEUE_LEN(_n)		((_n) ? 1024 : 2048)
+-#define TX_DSCP_NUM			1024
+-#define RX_DSCP_NUM(_n)			\
+-	((_n) ==  2 ? 128 :		\
+-	 (_n) == 11 ? 128 :		\
+-	 (_n) == 15 ? 128 :		\
+-	 (_n) ==  0 ? 1024 : 16)
+-
+-#define PSE_RSV_PAGES			128
+-#define PSE_QUEUE_RSV_PAGES		64
+-
+-#define QDMA_METER_IDX(_n)		((_n) & 0xff)
+-#define QDMA_METER_GROUP(_n)		(((_n) >> 8) & 0x3)
+-
+-/* FE */
+-#define PSE_BASE			0x0100
+-#define CSR_IFC_BASE			0x0200
+-#define CDM1_BASE			0x0400
+-#define GDM1_BASE			0x0500
+-#define PPE1_BASE			0x0c00
+-
+-#define CDM2_BASE			0x1400
+-#define GDM2_BASE			0x1500
+-
+-#define GDM3_BASE			0x1100
+-#define GDM4_BASE			0x2500
+-
+-#define GDM_BASE(_n)			\
+-	((_n) == 4 ? GDM4_BASE :	\
+-	 (_n) == 3 ? GDM3_BASE :	\
+-	 (_n) == 2 ? GDM2_BASE : GDM1_BASE)
+-
+-#define REG_FE_DMA_GLO_CFG		0x0000
+-#define FE_DMA_GLO_L2_SPACE_MASK	GENMASK(7, 4)
+-#define FE_DMA_GLO_PG_SZ_MASK		BIT(3)
+-
+-#define REG_FE_RST_GLO_CFG		0x0004
+-#define FE_RST_GDM4_MBI_ARB_MASK	BIT(3)
+-#define FE_RST_GDM3_MBI_ARB_MASK	BIT(2)
+-#define FE_RST_CORE_MASK		BIT(0)
+-
+-#define REG_FE_WAN_MAC_H		0x0030
+-#define REG_FE_LAN_MAC_H		0x0040
+-
+-#define REG_FE_MAC_LMIN(_n)		((_n) + 0x04)
+-#define REG_FE_MAC_LMAX(_n)		((_n) + 0x08)
+-
+-#define REG_FE_CDM1_OQ_MAP0		0x0050
+-#define REG_FE_CDM1_OQ_MAP1		0x0054
+-#define REG_FE_CDM1_OQ_MAP2		0x0058
+-#define REG_FE_CDM1_OQ_MAP3		0x005c
+-
+-#define REG_FE_PCE_CFG			0x0070
+-#define PCE_DPI_EN_MASK			BIT(2)
+-#define PCE_KA_EN_MASK			BIT(1)
+-#define PCE_MC_EN_MASK			BIT(0)
+-
+-#define REG_FE_PSE_QUEUE_CFG_WR		0x0080
+-#define PSE_CFG_PORT_ID_MASK		GENMASK(27, 24)
+-#define PSE_CFG_QUEUE_ID_MASK		GENMASK(20, 16)
+-#define PSE_CFG_WR_EN_MASK		BIT(8)
+-#define PSE_CFG_OQRSV_SEL_MASK		BIT(0)
+-
+-#define REG_FE_PSE_QUEUE_CFG_VAL	0x0084
+-#define PSE_CFG_OQ_RSV_MASK		GENMASK(13, 0)
+-
+-#define PSE_FQ_CFG			0x008c
+-#define PSE_FQ_LIMIT_MASK		GENMASK(14, 0)
+-
+-#define REG_FE_PSE_BUF_SET		0x0090
+-#define PSE_SHARE_USED_LTHD_MASK	GENMASK(31, 16)
+-#define PSE_ALLRSV_MASK			GENMASK(14, 0)
+-
+-#define REG_PSE_SHARE_USED_THD		0x0094
+-#define PSE_SHARE_USED_MTHD_MASK	GENMASK(31, 16)
+-#define PSE_SHARE_USED_HTHD_MASK	GENMASK(15, 0)
+-
+-#define REG_GDM_MISC_CFG		0x0148
+-#define GDM2_RDM_ACK_WAIT_PREF_MASK	BIT(9)
+-#define GDM2_CHN_VLD_MODE_MASK		BIT(5)
+-
+-#define REG_FE_CSR_IFC_CFG		CSR_IFC_BASE
+-#define FE_IFC_EN_MASK			BIT(0)
+-
+-#define REG_FE_VIP_PORT_EN		0x01f0
+-#define REG_FE_IFC_PORT_EN		0x01f4
+-
+-#define REG_PSE_IQ_REV1			(PSE_BASE + 0x08)
+-#define PSE_IQ_RES1_P2_MASK		GENMASK(23, 16)
+-
+-#define REG_PSE_IQ_REV2			(PSE_BASE + 0x0c)
+-#define PSE_IQ_RES2_P5_MASK		GENMASK(15, 8)
+-#define PSE_IQ_RES2_P4_MASK		GENMASK(7, 0)
+-
+-#define REG_FE_VIP_EN(_n)		(0x0300 + ((_n) << 3))
+-#define PATN_FCPU_EN_MASK		BIT(7)
+-#define PATN_SWP_EN_MASK		BIT(6)
+-#define PATN_DP_EN_MASK			BIT(5)
+-#define PATN_SP_EN_MASK			BIT(4)
+-#define PATN_TYPE_MASK			GENMASK(3, 1)
+-#define PATN_EN_MASK			BIT(0)
+-
+-#define REG_FE_VIP_PATN(_n)		(0x0304 + ((_n) << 3))
+-#define PATN_DP_MASK			GENMASK(31, 16)
+-#define PATN_SP_MASK			GENMASK(15, 0)
+-
+-#define REG_CDM1_VLAN_CTRL		CDM1_BASE
+-#define CDM1_VLAN_MASK			GENMASK(31, 16)
+-
+-#define REG_CDM1_FWD_CFG		(CDM1_BASE + 0x08)
+-#define CDM1_VIP_QSEL_MASK		GENMASK(24, 20)
+-
+-#define REG_CDM1_CRSN_QSEL(_n)		(CDM1_BASE + 0x10 + ((_n) << 2))
+-#define CDM1_CRSN_QSEL_REASON_MASK(_n)	\
+-	GENMASK(4 + (((_n) % 4) << 3),	(((_n) % 4) << 3))
+-
+-#define REG_CDM2_FWD_CFG		(CDM2_BASE + 0x08)
+-#define CDM2_OAM_QSEL_MASK		GENMASK(31, 27)
+-#define CDM2_VIP_QSEL_MASK		GENMASK(24, 20)
+-
+-#define REG_CDM2_CRSN_QSEL(_n)		(CDM2_BASE + 0x10 + ((_n) << 2))
+-#define CDM2_CRSN_QSEL_REASON_MASK(_n)	\
+-	GENMASK(4 + (((_n) % 4) << 3),	(((_n) % 4) << 3))
+-
+-#define REG_GDM_FWD_CFG(_n)		GDM_BASE(_n)
+-#define GDM_DROP_CRC_ERR		BIT(23)
+-#define GDM_IP4_CKSUM			BIT(22)
+-#define GDM_TCP_CKSUM			BIT(21)
+-#define GDM_UDP_CKSUM			BIT(20)
+-#define GDM_UCFQ_MASK			GENMASK(15, 12)
+-#define GDM_BCFQ_MASK			GENMASK(11, 8)
+-#define GDM_MCFQ_MASK			GENMASK(7, 4)
+-#define GDM_OCFQ_MASK			GENMASK(3, 0)
+-
+-#define REG_GDM_INGRESS_CFG(_n)		(GDM_BASE(_n) + 0x10)
+-#define GDM_INGRESS_FC_EN_MASK		BIT(1)
+-#define GDM_STAG_EN_MASK		BIT(0)
+-
+-#define REG_GDM_LEN_CFG(_n)		(GDM_BASE(_n) + 0x14)
+-#define GDM_SHORT_LEN_MASK		GENMASK(13, 0)
+-#define GDM_LONG_LEN_MASK		GENMASK(29, 16)
+-
+-#define REG_FE_CPORT_CFG		(GDM1_BASE + 0x40)
+-#define FE_CPORT_PAD			BIT(26)
+-#define FE_CPORT_PORT_XFC_MASK		BIT(25)
+-#define FE_CPORT_QUEUE_XFC_MASK		BIT(24)
+-
+-#define REG_FE_GDM_MIB_CLEAR(_n)	(GDM_BASE(_n) + 0xf0)
+-#define FE_GDM_MIB_RX_CLEAR_MASK	BIT(1)
+-#define FE_GDM_MIB_TX_CLEAR_MASK	BIT(0)
+-
+-#define REG_FE_GDM1_MIB_CFG		(GDM1_BASE + 0xf4)
+-#define FE_STRICT_RFC2819_MODE_MASK	BIT(31)
+-#define FE_GDM1_TX_MIB_SPLIT_EN_MASK	BIT(17)
+-#define FE_GDM1_RX_MIB_SPLIT_EN_MASK	BIT(16)
+-#define FE_TX_MIB_ID_MASK		GENMASK(15, 8)
+-#define FE_RX_MIB_ID_MASK		GENMASK(7, 0)
+-
+-#define REG_FE_GDM_TX_OK_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x104)
+-#define REG_FE_GDM_TX_OK_BYTE_CNT_L(_n)		(GDM_BASE(_n) + 0x10c)
+-#define REG_FE_GDM_TX_ETH_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x110)
+-#define REG_FE_GDM_TX_ETH_BYTE_CNT_L(_n)	(GDM_BASE(_n) + 0x114)
+-#define REG_FE_GDM_TX_ETH_DROP_CNT(_n)		(GDM_BASE(_n) + 0x118)
+-#define REG_FE_GDM_TX_ETH_BC_CNT(_n)		(GDM_BASE(_n) + 0x11c)
+-#define REG_FE_GDM_TX_ETH_MC_CNT(_n)		(GDM_BASE(_n) + 0x120)
+-#define REG_FE_GDM_TX_ETH_RUNT_CNT(_n)		(GDM_BASE(_n) + 0x124)
+-#define REG_FE_GDM_TX_ETH_LONG_CNT(_n)		(GDM_BASE(_n) + 0x128)
+-#define REG_FE_GDM_TX_ETH_E64_CNT_L(_n)		(GDM_BASE(_n) + 0x12c)
+-#define REG_FE_GDM_TX_ETH_L64_CNT_L(_n)		(GDM_BASE(_n) + 0x130)
+-#define REG_FE_GDM_TX_ETH_L127_CNT_L(_n)	(GDM_BASE(_n) + 0x134)
+-#define REG_FE_GDM_TX_ETH_L255_CNT_L(_n)	(GDM_BASE(_n) + 0x138)
+-#define REG_FE_GDM_TX_ETH_L511_CNT_L(_n)	(GDM_BASE(_n) + 0x13c)
+-#define REG_FE_GDM_TX_ETH_L1023_CNT_L(_n)	(GDM_BASE(_n) + 0x140)
+-
+-#define REG_FE_GDM_RX_OK_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x148)
+-#define REG_FE_GDM_RX_FC_DROP_CNT(_n)		(GDM_BASE(_n) + 0x14c)
+-#define REG_FE_GDM_RX_RC_DROP_CNT(_n)		(GDM_BASE(_n) + 0x150)
+-#define REG_FE_GDM_RX_OVERFLOW_DROP_CNT(_n)	(GDM_BASE(_n) + 0x154)
+-#define REG_FE_GDM_RX_ERROR_DROP_CNT(_n)	(GDM_BASE(_n) + 0x158)
+-#define REG_FE_GDM_RX_OK_BYTE_CNT_L(_n)		(GDM_BASE(_n) + 0x15c)
+-#define REG_FE_GDM_RX_ETH_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x160)
+-#define REG_FE_GDM_RX_ETH_BYTE_CNT_L(_n)	(GDM_BASE(_n) + 0x164)
+-#define REG_FE_GDM_RX_ETH_DROP_CNT(_n)		(GDM_BASE(_n) + 0x168)
+-#define REG_FE_GDM_RX_ETH_BC_CNT(_n)		(GDM_BASE(_n) + 0x16c)
+-#define REG_FE_GDM_RX_ETH_MC_CNT(_n)		(GDM_BASE(_n) + 0x170)
+-#define REG_FE_GDM_RX_ETH_CRC_ERR_CNT(_n)	(GDM_BASE(_n) + 0x174)
+-#define REG_FE_GDM_RX_ETH_FRAG_CNT(_n)		(GDM_BASE(_n) + 0x178)
+-#define REG_FE_GDM_RX_ETH_JABBER_CNT(_n)	(GDM_BASE(_n) + 0x17c)
+-#define REG_FE_GDM_RX_ETH_RUNT_CNT(_n)		(GDM_BASE(_n) + 0x180)
+-#define REG_FE_GDM_RX_ETH_LONG_CNT(_n)		(GDM_BASE(_n) + 0x184)
+-#define REG_FE_GDM_RX_ETH_E64_CNT_L(_n)		(GDM_BASE(_n) + 0x188)
+-#define REG_FE_GDM_RX_ETH_L64_CNT_L(_n)		(GDM_BASE(_n) + 0x18c)
+-#define REG_FE_GDM_RX_ETH_L127_CNT_L(_n)	(GDM_BASE(_n) + 0x190)
+-#define REG_FE_GDM_RX_ETH_L255_CNT_L(_n)	(GDM_BASE(_n) + 0x194)
+-#define REG_FE_GDM_RX_ETH_L511_CNT_L(_n)	(GDM_BASE(_n) + 0x198)
+-#define REG_FE_GDM_RX_ETH_L1023_CNT_L(_n)	(GDM_BASE(_n) + 0x19c)
+-
+-#define REG_PPE1_TB_HASH_CFG		(PPE1_BASE + 0x250)
+-#define PPE1_SRAM_TABLE_EN_MASK		BIT(0)
+-#define PPE1_SRAM_HASH1_EN_MASK		BIT(8)
+-#define PPE1_DRAM_TABLE_EN_MASK		BIT(16)
+-#define PPE1_DRAM_HASH1_EN_MASK		BIT(24)
+-
+-#define REG_FE_GDM_TX_OK_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x280)
+-#define REG_FE_GDM_TX_OK_BYTE_CNT_H(_n)		(GDM_BASE(_n) + 0x284)
+-#define REG_FE_GDM_TX_ETH_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x288)
+-#define REG_FE_GDM_TX_ETH_BYTE_CNT_H(_n)	(GDM_BASE(_n) + 0x28c)
+-
+-#define REG_FE_GDM_RX_OK_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x290)
+-#define REG_FE_GDM_RX_OK_BYTE_CNT_H(_n)		(GDM_BASE(_n) + 0x294)
+-#define REG_FE_GDM_RX_ETH_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x298)
+-#define REG_FE_GDM_RX_ETH_BYTE_CNT_H(_n)	(GDM_BASE(_n) + 0x29c)
+-#define REG_FE_GDM_TX_ETH_E64_CNT_H(_n)		(GDM_BASE(_n) + 0x2b8)
+-#define REG_FE_GDM_TX_ETH_L64_CNT_H(_n)		(GDM_BASE(_n) + 0x2bc)
+-#define REG_FE_GDM_TX_ETH_L127_CNT_H(_n)	(GDM_BASE(_n) + 0x2c0)
+-#define REG_FE_GDM_TX_ETH_L255_CNT_H(_n)	(GDM_BASE(_n) + 0x2c4)
+-#define REG_FE_GDM_TX_ETH_L511_CNT_H(_n)	(GDM_BASE(_n) + 0x2c8)
+-#define REG_FE_GDM_TX_ETH_L1023_CNT_H(_n)	(GDM_BASE(_n) + 0x2cc)
+-#define REG_FE_GDM_RX_ETH_E64_CNT_H(_n)		(GDM_BASE(_n) + 0x2e8)
+-#define REG_FE_GDM_RX_ETH_L64_CNT_H(_n)		(GDM_BASE(_n) + 0x2ec)
+-#define REG_FE_GDM_RX_ETH_L127_CNT_H(_n)	(GDM_BASE(_n) + 0x2f0)
+-#define REG_FE_GDM_RX_ETH_L255_CNT_H(_n)	(GDM_BASE(_n) + 0x2f4)
+-#define REG_FE_GDM_RX_ETH_L511_CNT_H(_n)	(GDM_BASE(_n) + 0x2f8)
+-#define REG_FE_GDM_RX_ETH_L1023_CNT_H(_n)	(GDM_BASE(_n) + 0x2fc)
+-
+-#define REG_GDM2_CHN_RLS		(GDM2_BASE + 0x20)
+-#define MBI_RX_AGE_SEL_MASK		GENMASK(26, 25)
+-#define MBI_TX_AGE_SEL_MASK		GENMASK(18, 17)
+-
+-#define REG_GDM3_FWD_CFG		GDM3_BASE
+-#define GDM3_PAD_EN_MASK		BIT(28)
+-
+-#define REG_GDM4_FWD_CFG		GDM4_BASE
+-#define GDM4_PAD_EN_MASK		BIT(28)
+-#define GDM4_SPORT_OFFSET0_MASK		GENMASK(11, 8)
+-
+-#define REG_GDM4_SRC_PORT_SET		(GDM4_BASE + 0x23c)
+-#define GDM4_SPORT_OFF2_MASK		GENMASK(19, 16)
+-#define GDM4_SPORT_OFF1_MASK		GENMASK(15, 12)
+-#define GDM4_SPORT_OFF0_MASK		GENMASK(11, 8)
+-
+-#define REG_IP_FRAG_FP			0x2010
+-#define IP_ASSEMBLE_PORT_MASK		GENMASK(24, 21)
+-#define IP_ASSEMBLE_NBQ_MASK		GENMASK(20, 16)
+-#define IP_FRAGMENT_PORT_MASK		GENMASK(8, 5)
+-#define IP_FRAGMENT_NBQ_MASK		GENMASK(4, 0)
+-
+-#define REG_MC_VLAN_EN			0x2100
+-#define MC_VLAN_EN_MASK			BIT(0)
+-
+-#define REG_MC_VLAN_CFG			0x2104
+-#define MC_VLAN_CFG_CMD_DONE_MASK	BIT(31)
+-#define MC_VLAN_CFG_TABLE_ID_MASK	GENMASK(21, 16)
+-#define MC_VLAN_CFG_PORT_ID_MASK	GENMASK(11, 8)
+-#define MC_VLAN_CFG_TABLE_SEL_MASK	BIT(4)
+-#define MC_VLAN_CFG_RW_MASK		BIT(0)
+-
+-#define REG_MC_VLAN_DATA		0x2108
+-
+-#define REG_CDM5_RX_OQ1_DROP_CNT	0x29d4
+-
+-/* QDMA */
+-#define REG_QDMA_GLOBAL_CFG			0x0004
+-#define GLOBAL_CFG_RX_2B_OFFSET_MASK		BIT(31)
+-#define GLOBAL_CFG_DMA_PREFERENCE_MASK		GENMASK(30, 29)
+-#define GLOBAL_CFG_CPU_TXR_RR_MASK		BIT(28)
+-#define GLOBAL_CFG_DSCP_BYTE_SWAP_MASK		BIT(27)
+-#define GLOBAL_CFG_PAYLOAD_BYTE_SWAP_MASK	BIT(26)
+-#define GLOBAL_CFG_MULTICAST_MODIFY_FP_MASK	BIT(25)
+-#define GLOBAL_CFG_OAM_MODIFY_MASK		BIT(24)
+-#define GLOBAL_CFG_RESET_MASK			BIT(23)
+-#define GLOBAL_CFG_RESET_DONE_MASK		BIT(22)
+-#define GLOBAL_CFG_MULTICAST_EN_MASK		BIT(21)
+-#define GLOBAL_CFG_IRQ1_EN_MASK			BIT(20)
+-#define GLOBAL_CFG_IRQ0_EN_MASK			BIT(19)
+-#define GLOBAL_CFG_LOOPCNT_EN_MASK		BIT(18)
+-#define GLOBAL_CFG_RD_BYPASS_WR_MASK		BIT(17)
+-#define GLOBAL_CFG_QDMA_LOOPBACK_MASK		BIT(16)
+-#define GLOBAL_CFG_LPBK_RXQ_SEL_MASK		GENMASK(13, 8)
+-#define GLOBAL_CFG_CHECK_DONE_MASK		BIT(7)
+-#define GLOBAL_CFG_TX_WB_DONE_MASK		BIT(6)
+-#define GLOBAL_CFG_MAX_ISSUE_NUM_MASK		GENMASK(5, 4)
+-#define GLOBAL_CFG_RX_DMA_BUSY_MASK		BIT(3)
+-#define GLOBAL_CFG_RX_DMA_EN_MASK		BIT(2)
+-#define GLOBAL_CFG_TX_DMA_BUSY_MASK		BIT(1)
+-#define GLOBAL_CFG_TX_DMA_EN_MASK		BIT(0)
+-
+-#define REG_FWD_DSCP_BASE			0x0010
+-#define REG_FWD_BUF_BASE			0x0014
+-
+-#define REG_HW_FWD_DSCP_CFG			0x0018
+-#define HW_FWD_DSCP_PAYLOAD_SIZE_MASK		GENMASK(29, 28)
+-#define HW_FWD_DSCP_SCATTER_LEN_MASK		GENMASK(17, 16)
+-#define HW_FWD_DSCP_MIN_SCATTER_LEN_MASK	GENMASK(15, 0)
+-
+-#define REG_INT_STATUS(_n)		\
+-	(((_n) == 4) ? 0x0730 :		\
+-	 ((_n) == 3) ? 0x0724 :		\
+-	 ((_n) == 2) ? 0x0720 :		\
+-	 ((_n) == 1) ? 0x0024 : 0x0020)
+-
+-#define REG_INT_ENABLE(_n)		\
+-	(((_n) == 4) ? 0x0750 :		\
+-	 ((_n) == 3) ? 0x0744 :		\
+-	 ((_n) == 2) ? 0x0740 :		\
+-	 ((_n) == 1) ? 0x002c : 0x0028)
+-
+-/* QDMA_CSR_INT_ENABLE1 */
+-#define RX15_COHERENT_INT_MASK		BIT(31)
+-#define RX14_COHERENT_INT_MASK		BIT(30)
+-#define RX13_COHERENT_INT_MASK		BIT(29)
+-#define RX12_COHERENT_INT_MASK		BIT(28)
+-#define RX11_COHERENT_INT_MASK		BIT(27)
+-#define RX10_COHERENT_INT_MASK		BIT(26)
+-#define RX9_COHERENT_INT_MASK		BIT(25)
+-#define RX8_COHERENT_INT_MASK		BIT(24)
+-#define RX7_COHERENT_INT_MASK		BIT(23)
+-#define RX6_COHERENT_INT_MASK		BIT(22)
+-#define RX5_COHERENT_INT_MASK		BIT(21)
+-#define RX4_COHERENT_INT_MASK		BIT(20)
+-#define RX3_COHERENT_INT_MASK		BIT(19)
+-#define RX2_COHERENT_INT_MASK		BIT(18)
+-#define RX1_COHERENT_INT_MASK		BIT(17)
+-#define RX0_COHERENT_INT_MASK		BIT(16)
+-#define TX7_COHERENT_INT_MASK		BIT(15)
+-#define TX6_COHERENT_INT_MASK		BIT(14)
+-#define TX5_COHERENT_INT_MASK		BIT(13)
+-#define TX4_COHERENT_INT_MASK		BIT(12)
+-#define TX3_COHERENT_INT_MASK		BIT(11)
+-#define TX2_COHERENT_INT_MASK		BIT(10)
+-#define TX1_COHERENT_INT_MASK		BIT(9)
+-#define TX0_COHERENT_INT_MASK		BIT(8)
+-#define CNT_OVER_FLOW_INT_MASK		BIT(7)
+-#define IRQ1_FULL_INT_MASK		BIT(5)
+-#define IRQ1_INT_MASK			BIT(4)
+-#define HWFWD_DSCP_LOW_INT_MASK		BIT(3)
+-#define HWFWD_DSCP_EMPTY_INT_MASK	BIT(2)
+-#define IRQ0_FULL_INT_MASK		BIT(1)
+-#define IRQ0_INT_MASK			BIT(0)
+-
+-#define TX_DONE_INT_MASK(_n)					\
+-	((_n) ? IRQ1_INT_MASK | IRQ1_FULL_INT_MASK		\
+-	      : IRQ0_INT_MASK | IRQ0_FULL_INT_MASK)
+-
+-#define INT_TX_MASK						\
+-	(IRQ1_INT_MASK | IRQ1_FULL_INT_MASK |			\
+-	 IRQ0_INT_MASK | IRQ0_FULL_INT_MASK)
+-
+-#define INT_IDX0_MASK						\
+-	(TX0_COHERENT_INT_MASK | TX1_COHERENT_INT_MASK |	\
+-	 TX2_COHERENT_INT_MASK | TX3_COHERENT_INT_MASK |	\
+-	 TX4_COHERENT_INT_MASK | TX5_COHERENT_INT_MASK |	\
+-	 TX6_COHERENT_INT_MASK | TX7_COHERENT_INT_MASK |	\
+-	 RX0_COHERENT_INT_MASK | RX1_COHERENT_INT_MASK |	\
+-	 RX2_COHERENT_INT_MASK | RX3_COHERENT_INT_MASK |	\
+-	 RX4_COHERENT_INT_MASK | RX7_COHERENT_INT_MASK |	\
+-	 RX8_COHERENT_INT_MASK | RX9_COHERENT_INT_MASK |	\
+-	 RX15_COHERENT_INT_MASK | INT_TX_MASK)
+-
+-/* QDMA_CSR_INT_ENABLE2 */
+-#define RX15_NO_CPU_DSCP_INT_MASK	BIT(31)
+-#define RX14_NO_CPU_DSCP_INT_MASK	BIT(30)
+-#define RX13_NO_CPU_DSCP_INT_MASK	BIT(29)
+-#define RX12_NO_CPU_DSCP_INT_MASK	BIT(28)
+-#define RX11_NO_CPU_DSCP_INT_MASK	BIT(27)
+-#define RX10_NO_CPU_DSCP_INT_MASK	BIT(26)
+-#define RX9_NO_CPU_DSCP_INT_MASK	BIT(25)
+-#define RX8_NO_CPU_DSCP_INT_MASK	BIT(24)
+-#define RX7_NO_CPU_DSCP_INT_MASK	BIT(23)
+-#define RX6_NO_CPU_DSCP_INT_MASK	BIT(22)
+-#define RX5_NO_CPU_DSCP_INT_MASK	BIT(21)
+-#define RX4_NO_CPU_DSCP_INT_MASK	BIT(20)
+-#define RX3_NO_CPU_DSCP_INT_MASK	BIT(19)
+-#define RX2_NO_CPU_DSCP_INT_MASK	BIT(18)
+-#define RX1_NO_CPU_DSCP_INT_MASK	BIT(17)
+-#define RX0_NO_CPU_DSCP_INT_MASK	BIT(16)
+-#define RX15_DONE_INT_MASK		BIT(15)
+-#define RX14_DONE_INT_MASK		BIT(14)
+-#define RX13_DONE_INT_MASK		BIT(13)
+-#define RX12_DONE_INT_MASK		BIT(12)
+-#define RX11_DONE_INT_MASK		BIT(11)
+-#define RX10_DONE_INT_MASK		BIT(10)
+-#define RX9_DONE_INT_MASK		BIT(9)
+-#define RX8_DONE_INT_MASK		BIT(8)
+-#define RX7_DONE_INT_MASK		BIT(7)
+-#define RX6_DONE_INT_MASK		BIT(6)
+-#define RX5_DONE_INT_MASK		BIT(5)
+-#define RX4_DONE_INT_MASK		BIT(4)
+-#define RX3_DONE_INT_MASK		BIT(3)
+-#define RX2_DONE_INT_MASK		BIT(2)
+-#define RX1_DONE_INT_MASK		BIT(1)
+-#define RX0_DONE_INT_MASK		BIT(0)
+-
+-#define RX_DONE_INT_MASK					\
+-	(RX0_DONE_INT_MASK | RX1_DONE_INT_MASK |		\
+-	 RX2_DONE_INT_MASK | RX3_DONE_INT_MASK |		\
+-	 RX4_DONE_INT_MASK | RX7_DONE_INT_MASK |		\
+-	 RX8_DONE_INT_MASK | RX9_DONE_INT_MASK |		\
+-	 RX15_DONE_INT_MASK)
+-#define INT_IDX1_MASK						\
+-	(RX_DONE_INT_MASK |					\
+-	 RX0_NO_CPU_DSCP_INT_MASK | RX1_NO_CPU_DSCP_INT_MASK |	\
+-	 RX2_NO_CPU_DSCP_INT_MASK | RX3_NO_CPU_DSCP_INT_MASK |	\
+-	 RX4_NO_CPU_DSCP_INT_MASK | RX7_NO_CPU_DSCP_INT_MASK |	\
+-	 RX8_NO_CPU_DSCP_INT_MASK | RX9_NO_CPU_DSCP_INT_MASK |	\
+-	 RX15_NO_CPU_DSCP_INT_MASK)
+-
+-/* QDMA_CSR_INT_ENABLE5 */
+-#define TX31_COHERENT_INT_MASK		BIT(31)
+-#define TX30_COHERENT_INT_MASK		BIT(30)
+-#define TX29_COHERENT_INT_MASK		BIT(29)
+-#define TX28_COHERENT_INT_MASK		BIT(28)
+-#define TX27_COHERENT_INT_MASK		BIT(27)
+-#define TX26_COHERENT_INT_MASK		BIT(26)
+-#define TX25_COHERENT_INT_MASK		BIT(25)
+-#define TX24_COHERENT_INT_MASK		BIT(24)
+-#define TX23_COHERENT_INT_MASK		BIT(23)
+-#define TX22_COHERENT_INT_MASK		BIT(22)
+-#define TX21_COHERENT_INT_MASK		BIT(21)
+-#define TX20_COHERENT_INT_MASK		BIT(20)
+-#define TX19_COHERENT_INT_MASK		BIT(19)
+-#define TX18_COHERENT_INT_MASK		BIT(18)
+-#define TX17_COHERENT_INT_MASK		BIT(17)
+-#define TX16_COHERENT_INT_MASK		BIT(16)
+-#define TX15_COHERENT_INT_MASK		BIT(15)
+-#define TX14_COHERENT_INT_MASK		BIT(14)
+-#define TX13_COHERENT_INT_MASK		BIT(13)
+-#define TX12_COHERENT_INT_MASK		BIT(12)
+-#define TX11_COHERENT_INT_MASK		BIT(11)
+-#define TX10_COHERENT_INT_MASK		BIT(10)
+-#define TX9_COHERENT_INT_MASK		BIT(9)
+-#define TX8_COHERENT_INT_MASK		BIT(8)
+-
+-#define INT_IDX4_MASK						\
+-	(TX8_COHERENT_INT_MASK | TX9_COHERENT_INT_MASK |	\
+-	 TX10_COHERENT_INT_MASK | TX11_COHERENT_INT_MASK |	\
+-	 TX12_COHERENT_INT_MASK | TX13_COHERENT_INT_MASK |	\
+-	 TX14_COHERENT_INT_MASK | TX15_COHERENT_INT_MASK |	\
+-	 TX16_COHERENT_INT_MASK | TX17_COHERENT_INT_MASK |	\
+-	 TX18_COHERENT_INT_MASK | TX19_COHERENT_INT_MASK |	\
+-	 TX20_COHERENT_INT_MASK | TX21_COHERENT_INT_MASK |	\
+-	 TX22_COHERENT_INT_MASK | TX23_COHERENT_INT_MASK |	\
+-	 TX24_COHERENT_INT_MASK | TX25_COHERENT_INT_MASK |	\
+-	 TX26_COHERENT_INT_MASK | TX27_COHERENT_INT_MASK |	\
+-	 TX28_COHERENT_INT_MASK | TX29_COHERENT_INT_MASK |	\
+-	 TX30_COHERENT_INT_MASK | TX31_COHERENT_INT_MASK)
+-
+-#define REG_TX_IRQ_BASE(_n)		((_n) ? 0x0048 : 0x0050)
+-
+-#define REG_TX_IRQ_CFG(_n)		((_n) ? 0x004c : 0x0054)
+-#define TX_IRQ_THR_MASK			GENMASK(27, 16)
+-#define TX_IRQ_DEPTH_MASK		GENMASK(11, 0)
+-
+-#define REG_IRQ_CLEAR_LEN(_n)		((_n) ? 0x0064 : 0x0058)
+-#define IRQ_CLEAR_LEN_MASK		GENMASK(7, 0)
+-
+-#define REG_IRQ_STATUS(_n)		((_n) ? 0x0068 : 0x005c)
+-#define IRQ_ENTRY_LEN_MASK		GENMASK(27, 16)
+-#define IRQ_HEAD_IDX_MASK		GENMASK(11, 0)
+-
+-#define REG_TX_RING_BASE(_n)	\
+-	(((_n) < 8) ? 0x0100 + ((_n) << 5) : 0x0b00 + (((_n) - 8) << 5))
+-
+-#define REG_TX_RING_BLOCKING(_n)	\
+-	(((_n) < 8) ? 0x0104 + ((_n) << 5) : 0x0b04 + (((_n) - 8) << 5))
+-
+-#define TX_RING_IRQ_BLOCKING_MAP_MASK			BIT(6)
+-#define TX_RING_IRQ_BLOCKING_CFG_MASK			BIT(4)
+-#define TX_RING_IRQ_BLOCKING_TX_DROP_EN_MASK		BIT(2)
+-#define TX_RING_IRQ_BLOCKING_MAX_TH_TXRING_EN_MASK	BIT(1)
+-#define TX_RING_IRQ_BLOCKING_MIN_TH_TXRING_EN_MASK	BIT(0)
+-
+-#define REG_TX_CPU_IDX(_n)	\
+-	(((_n) < 8) ? 0x0108 + ((_n) << 5) : 0x0b08 + (((_n) - 8) << 5))
+-
+-#define TX_RING_CPU_IDX_MASK		GENMASK(15, 0)
+-
+-#define REG_TX_DMA_IDX(_n)	\
+-	(((_n) < 8) ? 0x010c + ((_n) << 5) : 0x0b0c + (((_n) - 8) << 5))
+-
+-#define TX_RING_DMA_IDX_MASK		GENMASK(15, 0)
+-
+-#define IRQ_RING_IDX_MASK		GENMASK(20, 16)
+-#define IRQ_DESC_IDX_MASK		GENMASK(15, 0)
+-
+-#define REG_RX_RING_BASE(_n)	\
+-	(((_n) < 16) ? 0x0200 + ((_n) << 5) : 0x0e00 + (((_n) - 16) << 5))
+-
+-#define REG_RX_RING_SIZE(_n)	\
+-	(((_n) < 16) ? 0x0204 + ((_n) << 5) : 0x0e04 + (((_n) - 16) << 5))
+-
+-#define RX_RING_THR_MASK		GENMASK(31, 16)
+-#define RX_RING_SIZE_MASK		GENMASK(15, 0)
+-
+-#define REG_RX_CPU_IDX(_n)	\
+-	(((_n) < 16) ? 0x0208 + ((_n) << 5) : 0x0e08 + (((_n) - 16) << 5))
+-
+-#define RX_RING_CPU_IDX_MASK		GENMASK(15, 0)
+-
+-#define REG_RX_DMA_IDX(_n)	\
+-	(((_n) < 16) ? 0x020c + ((_n) << 5) : 0x0e0c + (((_n) - 16) << 5))
+-
+-#define REG_RX_DELAY_INT_IDX(_n)	\
+-	(((_n) < 16) ? 0x0210 + ((_n) << 5) : 0x0e10 + (((_n) - 16) << 5))
+-
+-#define RX_DELAY_INT_MASK		GENMASK(15, 0)
+-
+-#define RX_RING_DMA_IDX_MASK		GENMASK(15, 0)
+-
+-#define REG_INGRESS_TRTCM_CFG		0x0070
+-#define INGRESS_TRTCM_EN_MASK		BIT(31)
+-#define INGRESS_TRTCM_MODE_MASK		BIT(30)
+-#define INGRESS_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
+-#define INGRESS_FAST_TICK_MASK		GENMASK(15, 0)
+-
+-#define REG_QUEUE_CLOSE_CFG(_n)		(0x00a0 + ((_n) & 0xfc))
+-#define TXQ_DISABLE_CHAN_QUEUE_MASK(_n, _m)	BIT((_m) + (((_n) & 0x3) << 3))
+-
+-#define REG_TXQ_DIS_CFG_BASE(_n)	((_n) ? 0x20a0 : 0x00a0)
+-#define REG_TXQ_DIS_CFG(_n, _m)		(REG_TXQ_DIS_CFG_BASE((_n)) + (_m) << 2)
+-
+-#define REG_CNTR_CFG(_n)		(0x0400 + ((_n) << 3))
+-#define CNTR_EN_MASK			BIT(31)
+-#define CNTR_ALL_CHAN_EN_MASK		BIT(30)
+-#define CNTR_ALL_QUEUE_EN_MASK		BIT(29)
+-#define CNTR_ALL_DSCP_RING_EN_MASK	BIT(28)
+-#define CNTR_SRC_MASK			GENMASK(27, 24)
+-#define CNTR_DSCP_RING_MASK		GENMASK(20, 16)
+-#define CNTR_CHAN_MASK			GENMASK(7, 3)
+-#define CNTR_QUEUE_MASK			GENMASK(2, 0)
+-
+-#define REG_CNTR_VAL(_n)		(0x0404 + ((_n) << 3))
+-
+-#define REG_LMGR_INIT_CFG		0x1000
+-#define LMGR_INIT_START			BIT(31)
+-#define LMGR_SRAM_MODE_MASK		BIT(30)
+-#define HW_FWD_PKTSIZE_OVERHEAD_MASK	GENMASK(27, 20)
+-#define HW_FWD_DESC_NUM_MASK		GENMASK(16, 0)
+-
+-#define REG_FWD_DSCP_LOW_THR		0x1004
+-#define FWD_DSCP_LOW_THR_MASK		GENMASK(17, 0)
+-
+-#define REG_EGRESS_RATE_METER_CFG		0x100c
+-#define EGRESS_RATE_METER_EN_MASK		BIT(31)
+-#define EGRESS_RATE_METER_EQ_RATE_EN_MASK	BIT(17)
+-#define EGRESS_RATE_METER_WINDOW_SZ_MASK	GENMASK(16, 12)
+-#define EGRESS_RATE_METER_TIMESLICE_MASK	GENMASK(10, 0)
+-
+-#define REG_EGRESS_TRTCM_CFG		0x1010
+-#define EGRESS_TRTCM_EN_MASK		BIT(31)
+-#define EGRESS_TRTCM_MODE_MASK		BIT(30)
+-#define EGRESS_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
+-#define EGRESS_FAST_TICK_MASK		GENMASK(15, 0)
+-
+-#define TRTCM_PARAM_RW_MASK		BIT(31)
+-#define TRTCM_PARAM_RW_DONE_MASK	BIT(30)
+-#define TRTCM_PARAM_TYPE_MASK		GENMASK(29, 28)
+-#define TRTCM_METER_GROUP_MASK		GENMASK(27, 26)
+-#define TRTCM_PARAM_INDEX_MASK		GENMASK(23, 17)
+-#define TRTCM_PARAM_RATE_TYPE_MASK	BIT(16)
+-
+-#define REG_TRTCM_CFG_PARAM(_n)		((_n) + 0x4)
+-#define REG_TRTCM_DATA_LOW(_n)		((_n) + 0x8)
+-#define REG_TRTCM_DATA_HIGH(_n)		((_n) + 0xc)
+-
+-#define REG_TXWRR_MODE_CFG		0x1020
+-#define TWRR_WEIGHT_SCALE_MASK		BIT(31)
+-#define TWRR_WEIGHT_BASE_MASK		BIT(3)
+-
+-#define REG_TXWRR_WEIGHT_CFG		0x1024
+-#define TWRR_RW_CMD_MASK		BIT(31)
+-#define TWRR_RW_CMD_DONE		BIT(30)
+-#define TWRR_CHAN_IDX_MASK		GENMASK(23, 19)
+-#define TWRR_QUEUE_IDX_MASK		GENMASK(18, 16)
+-#define TWRR_VALUE_MASK			GENMASK(15, 0)
+-
+-#define REG_PSE_BUF_USAGE_CFG		0x1028
+-#define PSE_BUF_ESTIMATE_EN_MASK	BIT(29)
+-
+-#define REG_CHAN_QOS_MODE(_n)		(0x1040 + ((_n) << 2))
+-#define CHAN_QOS_MODE_MASK(_n)		GENMASK(2 + ((_n) << 2), (_n) << 2)
+-
+-#define REG_GLB_TRTCM_CFG		0x1080
+-#define GLB_TRTCM_EN_MASK		BIT(31)
+-#define GLB_TRTCM_MODE_MASK		BIT(30)
+-#define GLB_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
+-#define GLB_FAST_TICK_MASK		GENMASK(15, 0)
+-
+-#define REG_TXQ_CNGST_CFG		0x10a0
+-#define TXQ_CNGST_DROP_EN		BIT(31)
+-#define TXQ_CNGST_DEI_DROP_EN		BIT(30)
+-
+-#define REG_SLA_TRTCM_CFG		0x1150
+-#define SLA_TRTCM_EN_MASK		BIT(31)
+-#define SLA_TRTCM_MODE_MASK		BIT(30)
+-#define SLA_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
+-#define SLA_FAST_TICK_MASK		GENMASK(15, 0)
+-
+-/* CTRL */
+-#define QDMA_DESC_DONE_MASK		BIT(31)
+-#define QDMA_DESC_DROP_MASK		BIT(30) /* tx: drop - rx: overflow */
+-#define QDMA_DESC_MORE_MASK		BIT(29) /* more SG elements */
+-#define QDMA_DESC_DEI_MASK		BIT(25)
+-#define QDMA_DESC_NO_DROP_MASK		BIT(24)
+-#define QDMA_DESC_LEN_MASK		GENMASK(15, 0)
+-/* DATA */
+-#define QDMA_DESC_NEXT_ID_MASK		GENMASK(15, 0)
+-/* TX MSG0 */
+-#define QDMA_ETH_TXMSG_MIC_IDX_MASK	BIT(30)
+-#define QDMA_ETH_TXMSG_SP_TAG_MASK	GENMASK(29, 14)
+-#define QDMA_ETH_TXMSG_ICO_MASK		BIT(13)
+-#define QDMA_ETH_TXMSG_UCO_MASK		BIT(12)
+-#define QDMA_ETH_TXMSG_TCO_MASK		BIT(11)
+-#define QDMA_ETH_TXMSG_TSO_MASK		BIT(10)
+-#define QDMA_ETH_TXMSG_FAST_MASK	BIT(9)
+-#define QDMA_ETH_TXMSG_OAM_MASK		BIT(8)
+-#define QDMA_ETH_TXMSG_CHAN_MASK	GENMASK(7, 3)
+-#define QDMA_ETH_TXMSG_QUEUE_MASK	GENMASK(2, 0)
+-/* TX MSG1 */
+-#define QDMA_ETH_TXMSG_NO_DROP		BIT(31)
+-#define QDMA_ETH_TXMSG_METER_MASK	GENMASK(30, 24)	/* 0x7f no meters */
+-#define QDMA_ETH_TXMSG_FPORT_MASK	GENMASK(23, 20)
+-#define QDMA_ETH_TXMSG_NBOQ_MASK	GENMASK(19, 15)
+-#define QDMA_ETH_TXMSG_HWF_MASK		BIT(14)
+-#define QDMA_ETH_TXMSG_HOP_MASK		BIT(13)
+-#define QDMA_ETH_TXMSG_PTP_MASK		BIT(12)
+-#define QDMA_ETH_TXMSG_ACNT_G1_MASK	GENMASK(10, 6)	/* 0x1f do not count */
+-#define QDMA_ETH_TXMSG_ACNT_G0_MASK	GENMASK(5, 0)	/* 0x3f do not count */
+-
+-/* RX MSG1 */
+-#define QDMA_ETH_RXMSG_DEI_MASK		BIT(31)
+-#define QDMA_ETH_RXMSG_IP6_MASK		BIT(30)
+-#define QDMA_ETH_RXMSG_IP4_MASK		BIT(29)
+-#define QDMA_ETH_RXMSG_IP4F_MASK	BIT(28)
+-#define QDMA_ETH_RXMSG_L4_VALID_MASK	BIT(27)
+-#define QDMA_ETH_RXMSG_L4F_MASK		BIT(26)
+-#define QDMA_ETH_RXMSG_SPORT_MASK	GENMASK(25, 21)
+-#define QDMA_ETH_RXMSG_CRSN_MASK	GENMASK(20, 16)
+-#define QDMA_ETH_RXMSG_PPE_ENTRY_MASK	GENMASK(15, 0)
+-
+-struct airoha_qdma_desc {
+-	__le32 rsv;
+-	__le32 ctrl;
+-	__le32 addr;
+-	__le32 data;
+-	__le32 msg0;
+-	__le32 msg1;
+-	__le32 msg2;
+-	__le32 msg3;
+-};
+-
+-/* CTRL0 */
+-#define QDMA_FWD_DESC_CTX_MASK		BIT(31)
+-#define QDMA_FWD_DESC_RING_MASK		GENMASK(30, 28)
+-#define QDMA_FWD_DESC_IDX_MASK		GENMASK(27, 16)
+-#define QDMA_FWD_DESC_LEN_MASK		GENMASK(15, 0)
+-/* CTRL1 */
+-#define QDMA_FWD_DESC_FIRST_IDX_MASK	GENMASK(15, 0)
+-/* CTRL2 */
+-#define QDMA_FWD_DESC_MORE_PKT_NUM_MASK	GENMASK(2, 0)
+-
+-struct airoha_qdma_fwd_desc {
+-	__le32 addr;
+-	__le32 ctrl0;
+-	__le32 ctrl1;
+-	__le32 ctrl2;
+-	__le32 msg0;
+-	__le32 msg1;
+-	__le32 rsv0;
+-	__le32 rsv1;
+-};
+-
+-enum {
+-	QDMA_INT_REG_IDX0,
+-	QDMA_INT_REG_IDX1,
+-	QDMA_INT_REG_IDX2,
+-	QDMA_INT_REG_IDX3,
+-	QDMA_INT_REG_IDX4,
+-	QDMA_INT_REG_MAX
+-};
+-
+-enum {
+-	XSI_PCIE0_PORT,
+-	XSI_PCIE1_PORT,
+-	XSI_USB_PORT,
+-	XSI_AE_PORT,
+-	XSI_ETH_PORT,
+-};
+-
+-enum {
+-	XSI_PCIE0_VIP_PORT_MASK	= BIT(22),
+-	XSI_PCIE1_VIP_PORT_MASK	= BIT(23),
+-	XSI_USB_VIP_PORT_MASK	= BIT(25),
+-	XSI_ETH_VIP_PORT_MASK	= BIT(24),
+-};
+-
+-enum {
+-	DEV_STATE_INITIALIZED,
+-};
+-
+-enum {
+-	CDM_CRSN_QSEL_Q1 = 1,
+-	CDM_CRSN_QSEL_Q5 = 5,
+-	CDM_CRSN_QSEL_Q6 = 6,
+-	CDM_CRSN_QSEL_Q15 = 15,
+-};
+-
+-enum {
+-	CRSN_08 = 0x8,
+-	CRSN_21 = 0x15, /* KA */
+-	CRSN_22 = 0x16, /* hit bind and force route to CPU */
+-	CRSN_24 = 0x18,
+-	CRSN_25 = 0x19,
+-};
+-
+-enum {
+-	FE_PSE_PORT_CDM1,
+-	FE_PSE_PORT_GDM1,
+-	FE_PSE_PORT_GDM2,
+-	FE_PSE_PORT_GDM3,
+-	FE_PSE_PORT_PPE1,
+-	FE_PSE_PORT_CDM2,
+-	FE_PSE_PORT_CDM3,
+-	FE_PSE_PORT_CDM4,
+-	FE_PSE_PORT_PPE2,
+-	FE_PSE_PORT_GDM4,
+-	FE_PSE_PORT_CDM5,
+-	FE_PSE_PORT_DROP = 0xf,
+-};
+-
+-enum tx_sched_mode {
+-	TC_SCH_WRR8,
+-	TC_SCH_SP,
+-	TC_SCH_WRR7,
+-	TC_SCH_WRR6,
+-	TC_SCH_WRR5,
+-	TC_SCH_WRR4,
+-	TC_SCH_WRR3,
+-	TC_SCH_WRR2,
+-};
+-
+-enum trtcm_param_type {
+-	TRTCM_MISC_MODE, /* meter_en, pps_mode, tick_sel */
+-	TRTCM_TOKEN_RATE_MODE,
+-	TRTCM_BUCKETSIZE_SHIFT_MODE,
+-	TRTCM_BUCKET_COUNTER_MODE,
+-};
+-
+-enum trtcm_mode_type {
+-	TRTCM_COMMIT_MODE,
+-	TRTCM_PEAK_MODE,
+-};
+-
+-enum trtcm_param {
+-	TRTCM_TICK_SEL = BIT(0),
+-	TRTCM_PKT_MODE = BIT(1),
+-	TRTCM_METER_MODE = BIT(2),
+-};
+-
+-#define MIN_TOKEN_SIZE				4096
+-#define MAX_TOKEN_SIZE_OFFSET			17
+-#define TRTCM_TOKEN_RATE_MASK			GENMASK(23, 6)
+-#define TRTCM_TOKEN_RATE_FRACTION_MASK		GENMASK(5, 0)
+-
+-struct airoha_queue_entry {
+-	union {
+-		void *buf;
+-		struct sk_buff *skb;
+-	};
+-	dma_addr_t dma_addr;
+-	u16 dma_len;
+-};
+-
+-struct airoha_queue {
+-	struct airoha_qdma *qdma;
+-
+-	/* protect concurrent queue accesses */
+-	spinlock_t lock;
+-	struct airoha_queue_entry *entry;
+-	struct airoha_qdma_desc *desc;
+-	u16 head;
+-	u16 tail;
+-
+-	int queued;
+-	int ndesc;
+-	int free_thr;
+-	int buf_size;
+-
+-	struct napi_struct napi;
+-	struct page_pool *page_pool;
+-};
+-
+-struct airoha_tx_irq_queue {
+-	struct airoha_qdma *qdma;
+-
+-	struct napi_struct napi;
+-
+-	int size;
+-	u32 *q;
+-};
+-
+-struct airoha_hw_stats {
+-	/* protect concurrent hw_stats accesses */
+-	spinlock_t lock;
+-	struct u64_stats_sync syncp;
+-
+-	/* get_stats64 */
+-	u64 rx_ok_pkts;
+-	u64 tx_ok_pkts;
+-	u64 rx_ok_bytes;
+-	u64 tx_ok_bytes;
+-	u64 rx_multicast;
+-	u64 rx_errors;
+-	u64 rx_drops;
+-	u64 tx_drops;
+-	u64 rx_crc_error;
+-	u64 rx_over_errors;
+-	/* ethtool stats */
+-	u64 tx_broadcast;
+-	u64 tx_multicast;
+-	u64 tx_len[7];
+-	u64 rx_broadcast;
+-	u64 rx_fragment;
+-	u64 rx_jabber;
+-	u64 rx_len[7];
+-};
+-
+-struct airoha_qdma {
+-	struct airoha_eth *eth;
+-	void __iomem *regs;
+-
+-	/* protect concurrent irqmask accesses */
+-	spinlock_t irq_lock;
+-	u32 irqmask[QDMA_INT_REG_MAX];
+-	int irq;
+-
+-	struct airoha_tx_irq_queue q_tx_irq[AIROHA_NUM_TX_IRQ];
+-
+-	struct airoha_queue q_tx[AIROHA_NUM_TX_RING];
+-	struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
+-
+-	/* descriptor and packet buffers for qdma hw forward */
+-	struct {
+-		void *desc;
+-		void *q;
+-	} hfwd;
+-};
+-
+-struct airoha_gdm_port {
+-	struct airoha_qdma *qdma;
+-	struct net_device *dev;
+-	int id;
+-
+-	struct airoha_hw_stats stats;
+-
+-	DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
+-
+-	/* qos stats counters */
+-	u64 cpu_tx_packets;
+-	u64 fwd_tx_packets;
+-};
+-
+-struct airoha_eth {
+-	struct device *dev;
+-
+-	unsigned long state;
+-	void __iomem *fe_regs;
+-
+-	struct reset_control_bulk_data rsts[AIROHA_MAX_NUM_RSTS];
+-	struct reset_control_bulk_data xsi_rsts[AIROHA_MAX_NUM_XSI_RSTS];
+-
+-	struct net_device *napi_dev;
+-
+-	struct airoha_qdma qdma[AIROHA_MAX_NUM_QDMA];
+-	struct airoha_gdm_port *ports[AIROHA_MAX_NUM_GDM_PORTS];
+-};
+-
+-static u32 airoha_rr(void __iomem *base, u32 offset)
+-{
+-	return readl(base + offset);
+-}
+-
+-static void airoha_wr(void __iomem *base, u32 offset, u32 val)
+-{
+-	writel(val, base + offset);
+-}
+-
+-static u32 airoha_rmw(void __iomem *base, u32 offset, u32 mask, u32 val)
+-{
+-	val |= (airoha_rr(base, offset) & ~mask);
+-	airoha_wr(base, offset, val);
+-
+-	return val;
+-}
+-
+-#define airoha_fe_rr(eth, offset)				\
+-	airoha_rr((eth)->fe_regs, (offset))
+-#define airoha_fe_wr(eth, offset, val)				\
+-	airoha_wr((eth)->fe_regs, (offset), (val))
+-#define airoha_fe_rmw(eth, offset, mask, val)			\
+-	airoha_rmw((eth)->fe_regs, (offset), (mask), (val))
+-#define airoha_fe_set(eth, offset, val)				\
+-	airoha_rmw((eth)->fe_regs, (offset), 0, (val))
+-#define airoha_fe_clear(eth, offset, val)			\
+-	airoha_rmw((eth)->fe_regs, (offset), (val), 0)
+-
+-#define airoha_qdma_rr(qdma, offset)				\
+-	airoha_rr((qdma)->regs, (offset))
+-#define airoha_qdma_wr(qdma, offset, val)			\
+-	airoha_wr((qdma)->regs, (offset), (val))
+-#define airoha_qdma_rmw(qdma, offset, mask, val)		\
+-	airoha_rmw((qdma)->regs, (offset), (mask), (val))
+-#define airoha_qdma_set(qdma, offset, val)			\
+-	airoha_rmw((qdma)->regs, (offset), 0, (val))
+-#define airoha_qdma_clear(qdma, offset, val)			\
+-	airoha_rmw((qdma)->regs, (offset), (val), 0)
+-
+-static void airoha_qdma_set_irqmask(struct airoha_qdma *qdma, int index,
+-				    u32 clear, u32 set)
+-{
+-	unsigned long flags;
+-
+-	if (WARN_ON_ONCE(index >= ARRAY_SIZE(qdma->irqmask)))
+-		return;
+-
+-	spin_lock_irqsave(&qdma->irq_lock, flags);
+-
+-	qdma->irqmask[index] &= ~clear;
+-	qdma->irqmask[index] |= set;
+-	airoha_qdma_wr(qdma, REG_INT_ENABLE(index), qdma->irqmask[index]);
+-	/* Read irq_enable register in order to guarantee the update above
+-	 * completes in the spinlock critical section.
+-	 */
+-	airoha_qdma_rr(qdma, REG_INT_ENABLE(index));
+-
+-	spin_unlock_irqrestore(&qdma->irq_lock, flags);
+-}
+-
+-static void airoha_qdma_irq_enable(struct airoha_qdma *qdma, int index,
+-				   u32 mask)
+-{
+-	airoha_qdma_set_irqmask(qdma, index, 0, mask);
+-}
+-
+-static void airoha_qdma_irq_disable(struct airoha_qdma *qdma, int index,
+-				    u32 mask)
+-{
+-	airoha_qdma_set_irqmask(qdma, index, mask, 0);
+-}
+-
+-static bool airhoa_is_lan_gdm_port(struct airoha_gdm_port *port)
+-{
+-	/* GDM1 port on EN7581 SoC is connected to the lan dsa switch.
+-	 * GDM{2,3,4} can be used as wan port connected to an external
+-	 * phy module.
+-	 */
+-	return port->id == 1;
+-}
+-
+-static void airoha_set_macaddr(struct airoha_gdm_port *port, const u8 *addr)
+-{
+-	struct airoha_eth *eth = port->qdma->eth;
+-	u32 val, reg;
+-
+-	reg = airhoa_is_lan_gdm_port(port) ? REG_FE_LAN_MAC_H
+-					   : REG_FE_WAN_MAC_H;
+-	val = (addr[0] << 16) | (addr[1] << 8) | addr[2];
+-	airoha_fe_wr(eth, reg, val);
+-
+-	val = (addr[3] << 16) | (addr[4] << 8) | addr[5];
+-	airoha_fe_wr(eth, REG_FE_MAC_LMIN(reg), val);
+-	airoha_fe_wr(eth, REG_FE_MAC_LMAX(reg), val);
+-}
+-
+-static void airoha_set_gdm_port_fwd_cfg(struct airoha_eth *eth, u32 addr,
+-					u32 val)
+-{
+-	airoha_fe_rmw(eth, addr, GDM_OCFQ_MASK,
+-		      FIELD_PREP(GDM_OCFQ_MASK, val));
+-	airoha_fe_rmw(eth, addr, GDM_MCFQ_MASK,
+-		      FIELD_PREP(GDM_MCFQ_MASK, val));
+-	airoha_fe_rmw(eth, addr, GDM_BCFQ_MASK,
+-		      FIELD_PREP(GDM_BCFQ_MASK, val));
+-	airoha_fe_rmw(eth, addr, GDM_UCFQ_MASK,
+-		      FIELD_PREP(GDM_UCFQ_MASK, val));
+-}
+-
+-static int airoha_set_gdm_port(struct airoha_eth *eth, int port, bool enable)
+-{
+-	u32 val = enable ? FE_PSE_PORT_PPE1 : FE_PSE_PORT_DROP;
+-	u32 vip_port, cfg_addr;
+-
+-	switch (port) {
+-	case XSI_PCIE0_PORT:
+-		vip_port = XSI_PCIE0_VIP_PORT_MASK;
+-		cfg_addr = REG_GDM_FWD_CFG(3);
+-		break;
+-	case XSI_PCIE1_PORT:
+-		vip_port = XSI_PCIE1_VIP_PORT_MASK;
+-		cfg_addr = REG_GDM_FWD_CFG(3);
+-		break;
+-	case XSI_USB_PORT:
+-		vip_port = XSI_USB_VIP_PORT_MASK;
+-		cfg_addr = REG_GDM_FWD_CFG(4);
+-		break;
+-	case XSI_ETH_PORT:
+-		vip_port = XSI_ETH_VIP_PORT_MASK;
+-		cfg_addr = REG_GDM_FWD_CFG(4);
+-		break;
+-	default:
+-		return -EINVAL;
+-	}
+-
+-	if (enable) {
+-		airoha_fe_set(eth, REG_FE_VIP_PORT_EN, vip_port);
+-		airoha_fe_set(eth, REG_FE_IFC_PORT_EN, vip_port);
+-	} else {
+-		airoha_fe_clear(eth, REG_FE_VIP_PORT_EN, vip_port);
+-		airoha_fe_clear(eth, REG_FE_IFC_PORT_EN, vip_port);
+-	}
+-
+-	airoha_set_gdm_port_fwd_cfg(eth, cfg_addr, val);
+-
+-	return 0;
+-}
+-
+-static int airoha_set_gdm_ports(struct airoha_eth *eth, bool enable)
+-{
+-	const int port_list[] = {
+-		XSI_PCIE0_PORT,
+-		XSI_PCIE1_PORT,
+-		XSI_USB_PORT,
+-		XSI_ETH_PORT
+-	};
+-	int i, err;
+-
+-	for (i = 0; i < ARRAY_SIZE(port_list); i++) {
+-		err = airoha_set_gdm_port(eth, port_list[i], enable);
+-		if (err)
+-			goto error;
+-	}
+-
+-	return 0;
+-
+-error:
+-	for (i--; i >= 0; i--)
+-		airoha_set_gdm_port(eth, port_list[i], false);
+-
+-	return err;
+-}
+-
+-static void airoha_fe_maccr_init(struct airoha_eth *eth)
+-{
+-	int p;
+-
+-	for (p = 1; p <= ARRAY_SIZE(eth->ports); p++) {
+-		airoha_fe_set(eth, REG_GDM_FWD_CFG(p),
+-			      GDM_TCP_CKSUM | GDM_UDP_CKSUM | GDM_IP4_CKSUM |
+-			      GDM_DROP_CRC_ERR);
+-		airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(p),
+-					    FE_PSE_PORT_CDM1);
+-		airoha_fe_rmw(eth, REG_GDM_LEN_CFG(p),
+-			      GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
+-			      FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
+-			      FIELD_PREP(GDM_LONG_LEN_MASK, 4004));
+-	}
+-
+-	airoha_fe_rmw(eth, REG_CDM1_VLAN_CTRL, CDM1_VLAN_MASK,
+-		      FIELD_PREP(CDM1_VLAN_MASK, 0x8100));
+-
+-	airoha_fe_set(eth, REG_FE_CPORT_CFG, FE_CPORT_PAD);
+-}
+-
+-static void airoha_fe_vip_setup(struct airoha_eth *eth)
+-{
+-	airoha_fe_wr(eth, REG_FE_VIP_PATN(3), ETH_P_PPP_DISC);
+-	airoha_fe_wr(eth, REG_FE_VIP_EN(3), PATN_FCPU_EN_MASK | PATN_EN_MASK);
+-
+-	airoha_fe_wr(eth, REG_FE_VIP_PATN(4), PPP_LCP);
+-	airoha_fe_wr(eth, REG_FE_VIP_EN(4),
+-		     PATN_FCPU_EN_MASK | FIELD_PREP(PATN_TYPE_MASK, 1) |
+-		     PATN_EN_MASK);
+-
+-	airoha_fe_wr(eth, REG_FE_VIP_PATN(6), PPP_IPCP);
+-	airoha_fe_wr(eth, REG_FE_VIP_EN(6),
+-		     PATN_FCPU_EN_MASK | FIELD_PREP(PATN_TYPE_MASK, 1) |
+-		     PATN_EN_MASK);
+-
+-	airoha_fe_wr(eth, REG_FE_VIP_PATN(7), PPP_CHAP);
+-	airoha_fe_wr(eth, REG_FE_VIP_EN(7),
+-		     PATN_FCPU_EN_MASK | FIELD_PREP(PATN_TYPE_MASK, 1) |
+-		     PATN_EN_MASK);
+-
+-	/* BOOTP (0x43) */
+-	airoha_fe_wr(eth, REG_FE_VIP_PATN(8), 0x43);
+-	airoha_fe_wr(eth, REG_FE_VIP_EN(8),
+-		     PATN_FCPU_EN_MASK | PATN_SP_EN_MASK |
+-		     FIELD_PREP(PATN_TYPE_MASK, 4) | PATN_EN_MASK);
+-
+-	/* BOOTP (0x44) */
+-	airoha_fe_wr(eth, REG_FE_VIP_PATN(9), 0x44);
+-	airoha_fe_wr(eth, REG_FE_VIP_EN(9),
+-		     PATN_FCPU_EN_MASK | PATN_SP_EN_MASK |
+-		     FIELD_PREP(PATN_TYPE_MASK, 4) | PATN_EN_MASK);
+-
+-	/* ISAKMP */
+-	airoha_fe_wr(eth, REG_FE_VIP_PATN(10), 0x1f401f4);
+-	airoha_fe_wr(eth, REG_FE_VIP_EN(10),
+-		     PATN_FCPU_EN_MASK | PATN_DP_EN_MASK | PATN_SP_EN_MASK |
+-		     FIELD_PREP(PATN_TYPE_MASK, 4) | PATN_EN_MASK);
+-
+-	airoha_fe_wr(eth, REG_FE_VIP_PATN(11), PPP_IPV6CP);
+-	airoha_fe_wr(eth, REG_FE_VIP_EN(11),
+-		     PATN_FCPU_EN_MASK | FIELD_PREP(PATN_TYPE_MASK, 1) |
+-		     PATN_EN_MASK);
+-
+-	/* DHCPv6 */
+-	airoha_fe_wr(eth, REG_FE_VIP_PATN(12), 0x2220223);
+-	airoha_fe_wr(eth, REG_FE_VIP_EN(12),
+-		     PATN_FCPU_EN_MASK | PATN_DP_EN_MASK | PATN_SP_EN_MASK |
+-		     FIELD_PREP(PATN_TYPE_MASK, 4) | PATN_EN_MASK);
+-
+-	airoha_fe_wr(eth, REG_FE_VIP_PATN(19), PPP_PAP);
+-	airoha_fe_wr(eth, REG_FE_VIP_EN(19),
+-		     PATN_FCPU_EN_MASK | FIELD_PREP(PATN_TYPE_MASK, 1) |
+-		     PATN_EN_MASK);
+-
+-	/* ETH->ETH_P_1905 (0x893a) */
+-	airoha_fe_wr(eth, REG_FE_VIP_PATN(20), 0x893a);
+-	airoha_fe_wr(eth, REG_FE_VIP_EN(20),
+-		     PATN_FCPU_EN_MASK | PATN_EN_MASK);
+-
+-	airoha_fe_wr(eth, REG_FE_VIP_PATN(21), ETH_P_LLDP);
+-	airoha_fe_wr(eth, REG_FE_VIP_EN(21),
+-		     PATN_FCPU_EN_MASK | PATN_EN_MASK);
+-}
+-
+-static u32 airoha_fe_get_pse_queue_rsv_pages(struct airoha_eth *eth,
+-					     u32 port, u32 queue)
+-{
+-	u32 val;
+-
+-	airoha_fe_rmw(eth, REG_FE_PSE_QUEUE_CFG_WR,
+-		      PSE_CFG_PORT_ID_MASK | PSE_CFG_QUEUE_ID_MASK,
+-		      FIELD_PREP(PSE_CFG_PORT_ID_MASK, port) |
+-		      FIELD_PREP(PSE_CFG_QUEUE_ID_MASK, queue));
+-	val = airoha_fe_rr(eth, REG_FE_PSE_QUEUE_CFG_VAL);
+-
+-	return FIELD_GET(PSE_CFG_OQ_RSV_MASK, val);
+-}
+-
+-static void airoha_fe_set_pse_queue_rsv_pages(struct airoha_eth *eth,
+-					      u32 port, u32 queue, u32 val)
+-{
+-	airoha_fe_rmw(eth, REG_FE_PSE_QUEUE_CFG_VAL, PSE_CFG_OQ_RSV_MASK,
+-		      FIELD_PREP(PSE_CFG_OQ_RSV_MASK, val));
+-	airoha_fe_rmw(eth, REG_FE_PSE_QUEUE_CFG_WR,
+-		      PSE_CFG_PORT_ID_MASK | PSE_CFG_QUEUE_ID_MASK |
+-		      PSE_CFG_WR_EN_MASK | PSE_CFG_OQRSV_SEL_MASK,
+-		      FIELD_PREP(PSE_CFG_PORT_ID_MASK, port) |
+-		      FIELD_PREP(PSE_CFG_QUEUE_ID_MASK, queue) |
+-		      PSE_CFG_WR_EN_MASK | PSE_CFG_OQRSV_SEL_MASK);
+-}
+-
+-static u32 airoha_fe_get_pse_all_rsv(struct airoha_eth *eth)
+-{
+-	u32 val = airoha_fe_rr(eth, REG_FE_PSE_BUF_SET);
+-
+-	return FIELD_GET(PSE_ALLRSV_MASK, val);
+-}
+-
+-static int airoha_fe_set_pse_oq_rsv(struct airoha_eth *eth,
+-				    u32 port, u32 queue, u32 val)
+-{
+-	u32 orig_val = airoha_fe_get_pse_queue_rsv_pages(eth, port, queue);
+-	u32 tmp, all_rsv, fq_limit;
+-
+-	airoha_fe_set_pse_queue_rsv_pages(eth, port, queue, val);
+-
+-	/* modify all rsv */
+-	all_rsv = airoha_fe_get_pse_all_rsv(eth);
+-	all_rsv += (val - orig_val);
+-	airoha_fe_rmw(eth, REG_FE_PSE_BUF_SET, PSE_ALLRSV_MASK,
+-		      FIELD_PREP(PSE_ALLRSV_MASK, all_rsv));
+-
+-	/* modify hthd */
+-	tmp = airoha_fe_rr(eth, PSE_FQ_CFG);
+-	fq_limit = FIELD_GET(PSE_FQ_LIMIT_MASK, tmp);
+-	tmp = fq_limit - all_rsv - 0x20;
+-	airoha_fe_rmw(eth, REG_PSE_SHARE_USED_THD,
+-		      PSE_SHARE_USED_HTHD_MASK,
+-		      FIELD_PREP(PSE_SHARE_USED_HTHD_MASK, tmp));
+-
+-	tmp = fq_limit - all_rsv - 0x100;
+-	airoha_fe_rmw(eth, REG_PSE_SHARE_USED_THD,
+-		      PSE_SHARE_USED_MTHD_MASK,
+-		      FIELD_PREP(PSE_SHARE_USED_MTHD_MASK, tmp));
+-	tmp = (3 * tmp) >> 2;
+-	airoha_fe_rmw(eth, REG_FE_PSE_BUF_SET,
+-		      PSE_SHARE_USED_LTHD_MASK,
+-		      FIELD_PREP(PSE_SHARE_USED_LTHD_MASK, tmp));
+-
+-	return 0;
+-}
+-
+-static void airoha_fe_pse_ports_init(struct airoha_eth *eth)
+-{
+-	const u32 pse_port_num_queues[] = {
+-		[FE_PSE_PORT_CDM1] = 6,
+-		[FE_PSE_PORT_GDM1] = 6,
+-		[FE_PSE_PORT_GDM2] = 32,
+-		[FE_PSE_PORT_GDM3] = 6,
+-		[FE_PSE_PORT_PPE1] = 4,
+-		[FE_PSE_PORT_CDM2] = 6,
+-		[FE_PSE_PORT_CDM3] = 8,
+-		[FE_PSE_PORT_CDM4] = 10,
+-		[FE_PSE_PORT_PPE2] = 4,
+-		[FE_PSE_PORT_GDM4] = 2,
+-		[FE_PSE_PORT_CDM5] = 2,
+-	};
+-	u32 all_rsv;
+-	int q;
+-
+-	all_rsv = airoha_fe_get_pse_all_rsv(eth);
+-	/* hw misses PPE2 oq rsv */
+-	all_rsv += PSE_RSV_PAGES * pse_port_num_queues[FE_PSE_PORT_PPE2];
+-	airoha_fe_set(eth, REG_FE_PSE_BUF_SET, all_rsv);
+-
+-	/* CMD1 */
+-	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_CDM1]; q++)
+-		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_CDM1, q,
+-					 PSE_QUEUE_RSV_PAGES);
+-	/* GMD1 */
+-	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_GDM1]; q++)
+-		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_GDM1, q,
+-					 PSE_QUEUE_RSV_PAGES);
+-	/* GMD2 */
+-	for (q = 6; q < pse_port_num_queues[FE_PSE_PORT_GDM2]; q++)
+-		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_GDM2, q, 0);
+-	/* GMD3 */
+-	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_GDM3]; q++)
+-		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_GDM3, q,
+-					 PSE_QUEUE_RSV_PAGES);
+-	/* PPE1 */
+-	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_PPE1]; q++) {
+-		if (q < pse_port_num_queues[FE_PSE_PORT_PPE1])
+-			airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_PPE1, q,
+-						 PSE_QUEUE_RSV_PAGES);
+-		else
+-			airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_PPE1, q, 0);
+-	}
+-	/* CDM2 */
+-	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_CDM2]; q++)
+-		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_CDM2, q,
+-					 PSE_QUEUE_RSV_PAGES);
+-	/* CDM3 */
+-	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_CDM3] - 1; q++)
+-		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_CDM3, q, 0);
+-	/* CDM4 */
+-	for (q = 4; q < pse_port_num_queues[FE_PSE_PORT_CDM4]; q++)
+-		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_CDM4, q,
+-					 PSE_QUEUE_RSV_PAGES);
+-	/* PPE2 */
+-	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_PPE2]; q++) {
+-		if (q < pse_port_num_queues[FE_PSE_PORT_PPE2] / 2)
+-			airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_PPE2, q,
+-						 PSE_QUEUE_RSV_PAGES);
+-		else
+-			airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_PPE2, q, 0);
+-	}
+-	/* GMD4 */
+-	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_GDM4]; q++)
+-		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_GDM4, q,
+-					 PSE_QUEUE_RSV_PAGES);
+-	/* CDM5 */
+-	for (q = 0; q < pse_port_num_queues[FE_PSE_PORT_CDM5]; q++)
+-		airoha_fe_set_pse_oq_rsv(eth, FE_PSE_PORT_CDM5, q,
+-					 PSE_QUEUE_RSV_PAGES);
+-}
+-
+-static int airoha_fe_mc_vlan_clear(struct airoha_eth *eth)
+-{
+-	int i;
+-
+-	for (i = 0; i < AIROHA_FE_MC_MAX_VLAN_TABLE; i++) {
+-		int err, j;
+-		u32 val;
+-
+-		airoha_fe_wr(eth, REG_MC_VLAN_DATA, 0x0);
+-
+-		val = FIELD_PREP(MC_VLAN_CFG_TABLE_ID_MASK, i) |
+-		      MC_VLAN_CFG_TABLE_SEL_MASK | MC_VLAN_CFG_RW_MASK;
+-		airoha_fe_wr(eth, REG_MC_VLAN_CFG, val);
+-		err = read_poll_timeout(airoha_fe_rr, val,
+-					val & MC_VLAN_CFG_CMD_DONE_MASK,
+-					USEC_PER_MSEC, 5 * USEC_PER_MSEC,
+-					false, eth, REG_MC_VLAN_CFG);
+-		if (err)
+-			return err;
+-
+-		for (j = 0; j < AIROHA_FE_MC_MAX_VLAN_PORT; j++) {
+-			airoha_fe_wr(eth, REG_MC_VLAN_DATA, 0x0);
+-
+-			val = FIELD_PREP(MC_VLAN_CFG_TABLE_ID_MASK, i) |
+-			      FIELD_PREP(MC_VLAN_CFG_PORT_ID_MASK, j) |
+-			      MC_VLAN_CFG_RW_MASK;
+-			airoha_fe_wr(eth, REG_MC_VLAN_CFG, val);
+-			err = read_poll_timeout(airoha_fe_rr, val,
+-						val & MC_VLAN_CFG_CMD_DONE_MASK,
+-						USEC_PER_MSEC,
+-						5 * USEC_PER_MSEC, false, eth,
+-						REG_MC_VLAN_CFG);
+-			if (err)
+-				return err;
+-		}
+-	}
+-
+-	return 0;
+-}
+-
+-static void airoha_fe_crsn_qsel_init(struct airoha_eth *eth)
+-{
+-	/* CDM1_CRSN_QSEL */
+-	airoha_fe_rmw(eth, REG_CDM1_CRSN_QSEL(CRSN_22 >> 2),
+-		      CDM1_CRSN_QSEL_REASON_MASK(CRSN_22),
+-		      FIELD_PREP(CDM1_CRSN_QSEL_REASON_MASK(CRSN_22),
+-				 CDM_CRSN_QSEL_Q1));
+-	airoha_fe_rmw(eth, REG_CDM1_CRSN_QSEL(CRSN_08 >> 2),
+-		      CDM1_CRSN_QSEL_REASON_MASK(CRSN_08),
+-		      FIELD_PREP(CDM1_CRSN_QSEL_REASON_MASK(CRSN_08),
+-				 CDM_CRSN_QSEL_Q1));
+-	airoha_fe_rmw(eth, REG_CDM1_CRSN_QSEL(CRSN_21 >> 2),
+-		      CDM1_CRSN_QSEL_REASON_MASK(CRSN_21),
+-		      FIELD_PREP(CDM1_CRSN_QSEL_REASON_MASK(CRSN_21),
+-				 CDM_CRSN_QSEL_Q1));
+-	airoha_fe_rmw(eth, REG_CDM1_CRSN_QSEL(CRSN_24 >> 2),
+-		      CDM1_CRSN_QSEL_REASON_MASK(CRSN_24),
+-		      FIELD_PREP(CDM1_CRSN_QSEL_REASON_MASK(CRSN_24),
+-				 CDM_CRSN_QSEL_Q6));
+-	airoha_fe_rmw(eth, REG_CDM1_CRSN_QSEL(CRSN_25 >> 2),
+-		      CDM1_CRSN_QSEL_REASON_MASK(CRSN_25),
+-		      FIELD_PREP(CDM1_CRSN_QSEL_REASON_MASK(CRSN_25),
+-				 CDM_CRSN_QSEL_Q1));
+-	/* CDM2_CRSN_QSEL */
+-	airoha_fe_rmw(eth, REG_CDM2_CRSN_QSEL(CRSN_08 >> 2),
+-		      CDM2_CRSN_QSEL_REASON_MASK(CRSN_08),
+-		      FIELD_PREP(CDM2_CRSN_QSEL_REASON_MASK(CRSN_08),
+-				 CDM_CRSN_QSEL_Q1));
+-	airoha_fe_rmw(eth, REG_CDM2_CRSN_QSEL(CRSN_21 >> 2),
+-		      CDM2_CRSN_QSEL_REASON_MASK(CRSN_21),
+-		      FIELD_PREP(CDM2_CRSN_QSEL_REASON_MASK(CRSN_21),
+-				 CDM_CRSN_QSEL_Q1));
+-	airoha_fe_rmw(eth, REG_CDM2_CRSN_QSEL(CRSN_22 >> 2),
+-		      CDM2_CRSN_QSEL_REASON_MASK(CRSN_22),
+-		      FIELD_PREP(CDM2_CRSN_QSEL_REASON_MASK(CRSN_22),
+-				 CDM_CRSN_QSEL_Q1));
+-	airoha_fe_rmw(eth, REG_CDM2_CRSN_QSEL(CRSN_24 >> 2),
+-		      CDM2_CRSN_QSEL_REASON_MASK(CRSN_24),
+-		      FIELD_PREP(CDM2_CRSN_QSEL_REASON_MASK(CRSN_24),
+-				 CDM_CRSN_QSEL_Q6));
+-	airoha_fe_rmw(eth, REG_CDM2_CRSN_QSEL(CRSN_25 >> 2),
+-		      CDM2_CRSN_QSEL_REASON_MASK(CRSN_25),
+-		      FIELD_PREP(CDM2_CRSN_QSEL_REASON_MASK(CRSN_25),
+-				 CDM_CRSN_QSEL_Q1));
+-}
+-
+-static int airoha_fe_init(struct airoha_eth *eth)
+-{
+-	airoha_fe_maccr_init(eth);
+-
+-	/* PSE IQ reserve */
+-	airoha_fe_rmw(eth, REG_PSE_IQ_REV1, PSE_IQ_RES1_P2_MASK,
+-		      FIELD_PREP(PSE_IQ_RES1_P2_MASK, 0x10));
+-	airoha_fe_rmw(eth, REG_PSE_IQ_REV2,
+-		      PSE_IQ_RES2_P5_MASK | PSE_IQ_RES2_P4_MASK,
+-		      FIELD_PREP(PSE_IQ_RES2_P5_MASK, 0x40) |
+-		      FIELD_PREP(PSE_IQ_RES2_P4_MASK, 0x34));
+-
+-	/* enable FE copy engine for MC/KA/DPI */
+-	airoha_fe_wr(eth, REG_FE_PCE_CFG,
+-		     PCE_DPI_EN_MASK | PCE_KA_EN_MASK | PCE_MC_EN_MASK);
+-	/* set vip queue selection to ring 1 */
+-	airoha_fe_rmw(eth, REG_CDM1_FWD_CFG, CDM1_VIP_QSEL_MASK,
+-		      FIELD_PREP(CDM1_VIP_QSEL_MASK, 0x4));
+-	airoha_fe_rmw(eth, REG_CDM2_FWD_CFG, CDM2_VIP_QSEL_MASK,
+-		      FIELD_PREP(CDM2_VIP_QSEL_MASK, 0x4));
+-	/* set GDM4 source interface offset to 8 */
+-	airoha_fe_rmw(eth, REG_GDM4_SRC_PORT_SET,
+-		      GDM4_SPORT_OFF2_MASK |
+-		      GDM4_SPORT_OFF1_MASK |
+-		      GDM4_SPORT_OFF0_MASK,
+-		      FIELD_PREP(GDM4_SPORT_OFF2_MASK, 8) |
+-		      FIELD_PREP(GDM4_SPORT_OFF1_MASK, 8) |
+-		      FIELD_PREP(GDM4_SPORT_OFF0_MASK, 8));
+-
+-	/* set PSE Page as 128B */
+-	airoha_fe_rmw(eth, REG_FE_DMA_GLO_CFG,
+-		      FE_DMA_GLO_L2_SPACE_MASK | FE_DMA_GLO_PG_SZ_MASK,
+-		      FIELD_PREP(FE_DMA_GLO_L2_SPACE_MASK, 2) |
+-		      FE_DMA_GLO_PG_SZ_MASK);
+-	airoha_fe_wr(eth, REG_FE_RST_GLO_CFG,
+-		     FE_RST_CORE_MASK | FE_RST_GDM3_MBI_ARB_MASK |
+-		     FE_RST_GDM4_MBI_ARB_MASK);
+-	usleep_range(1000, 2000);
+-
+-	/* connect RxRing1 and RxRing15 to PSE Port0 OQ-1
+-	 * connect other rings to PSE Port0 OQ-0
+-	 */
+-	airoha_fe_wr(eth, REG_FE_CDM1_OQ_MAP0, BIT(4));
+-	airoha_fe_wr(eth, REG_FE_CDM1_OQ_MAP1, BIT(28));
+-	airoha_fe_wr(eth, REG_FE_CDM1_OQ_MAP2, BIT(4));
+-	airoha_fe_wr(eth, REG_FE_CDM1_OQ_MAP3, BIT(28));
+-
+-	airoha_fe_vip_setup(eth);
+-	airoha_fe_pse_ports_init(eth);
+-
+-	airoha_fe_set(eth, REG_GDM_MISC_CFG,
+-		      GDM2_RDM_ACK_WAIT_PREF_MASK |
+-		      GDM2_CHN_VLD_MODE_MASK);
+-	airoha_fe_rmw(eth, REG_CDM2_FWD_CFG, CDM2_OAM_QSEL_MASK,
+-		      FIELD_PREP(CDM2_OAM_QSEL_MASK, 15));
+-
+-	/* init fragment and assemble Force Port */
+-	/* NPU Core-3, NPU Bridge Channel-3 */
+-	airoha_fe_rmw(eth, REG_IP_FRAG_FP,
+-		      IP_FRAGMENT_PORT_MASK | IP_FRAGMENT_NBQ_MASK,
+-		      FIELD_PREP(IP_FRAGMENT_PORT_MASK, 6) |
+-		      FIELD_PREP(IP_FRAGMENT_NBQ_MASK, 3));
+-	/* QDMA LAN, RX Ring-22 */
+-	airoha_fe_rmw(eth, REG_IP_FRAG_FP,
+-		      IP_ASSEMBLE_PORT_MASK | IP_ASSEMBLE_NBQ_MASK,
+-		      FIELD_PREP(IP_ASSEMBLE_PORT_MASK, 0) |
+-		      FIELD_PREP(IP_ASSEMBLE_NBQ_MASK, 22));
+-
+-	airoha_fe_set(eth, REG_GDM3_FWD_CFG, GDM3_PAD_EN_MASK);
+-	airoha_fe_set(eth, REG_GDM4_FWD_CFG, GDM4_PAD_EN_MASK);
+-
+-	airoha_fe_crsn_qsel_init(eth);
+-
+-	airoha_fe_clear(eth, REG_FE_CPORT_CFG, FE_CPORT_QUEUE_XFC_MASK);
+-	airoha_fe_set(eth, REG_FE_CPORT_CFG, FE_CPORT_PORT_XFC_MASK);
+-
+-	/* default aging mode for mbi unlock issue */
+-	airoha_fe_rmw(eth, REG_GDM2_CHN_RLS,
+-		      MBI_RX_AGE_SEL_MASK | MBI_TX_AGE_SEL_MASK,
+-		      FIELD_PREP(MBI_RX_AGE_SEL_MASK, 3) |
+-		      FIELD_PREP(MBI_TX_AGE_SEL_MASK, 3));
+-
+-	/* disable IFC by default */
+-	airoha_fe_clear(eth, REG_FE_CSR_IFC_CFG, FE_IFC_EN_MASK);
+-
+-	/* enable 1:N vlan action, init vlan table */
+-	airoha_fe_set(eth, REG_MC_VLAN_EN, MC_VLAN_EN_MASK);
+-
+-	return airoha_fe_mc_vlan_clear(eth);
+-}
+-
+-static int airoha_qdma_fill_rx_queue(struct airoha_queue *q)
+-{
+-	enum dma_data_direction dir = page_pool_get_dma_dir(q->page_pool);
+-	struct airoha_qdma *qdma = q->qdma;
+-	struct airoha_eth *eth = qdma->eth;
+-	int qid = q - &qdma->q_rx[0];
+-	int nframes = 0;
+-
+-	while (q->queued < q->ndesc - 1) {
+-		struct airoha_queue_entry *e = &q->entry[q->head];
+-		struct airoha_qdma_desc *desc = &q->desc[q->head];
+-		struct page *page;
+-		int offset;
+-		u32 val;
+-
+-		page = page_pool_dev_alloc_frag(q->page_pool, &offset,
+-						q->buf_size);
+-		if (!page)
+-			break;
+-
+-		q->head = (q->head + 1) % q->ndesc;
+-		q->queued++;
+-		nframes++;
+-
+-		e->buf = page_address(page) + offset;
+-		e->dma_addr = page_pool_get_dma_addr(page) + offset;
+-		e->dma_len = SKB_WITH_OVERHEAD(q->buf_size);
+-
+-		dma_sync_single_for_device(eth->dev, e->dma_addr, e->dma_len,
+-					   dir);
+-
+-		val = FIELD_PREP(QDMA_DESC_LEN_MASK, e->dma_len);
+-		WRITE_ONCE(desc->ctrl, cpu_to_le32(val));
+-		WRITE_ONCE(desc->addr, cpu_to_le32(e->dma_addr));
+-		val = FIELD_PREP(QDMA_DESC_NEXT_ID_MASK, q->head);
+-		WRITE_ONCE(desc->data, cpu_to_le32(val));
+-		WRITE_ONCE(desc->msg0, 0);
+-		WRITE_ONCE(desc->msg1, 0);
+-		WRITE_ONCE(desc->msg2, 0);
+-		WRITE_ONCE(desc->msg3, 0);
+-
+-		airoha_qdma_rmw(qdma, REG_RX_CPU_IDX(qid),
+-				RX_RING_CPU_IDX_MASK,
+-				FIELD_PREP(RX_RING_CPU_IDX_MASK, q->head));
+-	}
+-
+-	return nframes;
+-}
+-
+-static int airoha_qdma_get_gdm_port(struct airoha_eth *eth,
+-				    struct airoha_qdma_desc *desc)
+-{
+-	u32 port, sport, msg1 = le32_to_cpu(desc->msg1);
+-
+-	sport = FIELD_GET(QDMA_ETH_RXMSG_SPORT_MASK, msg1);
+-	switch (sport) {
+-	case 0x10 ... 0x13:
+-		port = 0;
+-		break;
+-	case 0x2 ... 0x4:
+-		port = sport - 1;
+-		break;
+-	default:
+-		return -EINVAL;
+-	}
+-
+-	return port >= ARRAY_SIZE(eth->ports) ? -EINVAL : port;
+-}
+-
+-static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
+-{
+-	enum dma_data_direction dir = page_pool_get_dma_dir(q->page_pool);
+-	struct airoha_qdma *qdma = q->qdma;
+-	struct airoha_eth *eth = qdma->eth;
+-	int qid = q - &qdma->q_rx[0];
+-	int done = 0;
+-
+-	while (done < budget) {
+-		struct airoha_queue_entry *e = &q->entry[q->tail];
+-		struct airoha_qdma_desc *desc = &q->desc[q->tail];
+-		dma_addr_t dma_addr = le32_to_cpu(desc->addr);
+-		u32 desc_ctrl = le32_to_cpu(desc->ctrl);
+-		struct sk_buff *skb;
+-		int len, p;
+-
+-		if (!(desc_ctrl & QDMA_DESC_DONE_MASK))
+-			break;
+-
+-		if (!dma_addr)
+-			break;
+-
+-		len = FIELD_GET(QDMA_DESC_LEN_MASK, desc_ctrl);
+-		if (!len)
+-			break;
+-
+-		q->tail = (q->tail + 1) % q->ndesc;
+-		q->queued--;
+-
+-		dma_sync_single_for_cpu(eth->dev, dma_addr,
+-					SKB_WITH_OVERHEAD(q->buf_size), dir);
+-
+-		p = airoha_qdma_get_gdm_port(eth, desc);
+-		if (p < 0 || !eth->ports[p]) {
+-			page_pool_put_full_page(q->page_pool,
+-						virt_to_head_page(e->buf),
+-						true);
+-			continue;
+-		}
+-
+-		skb = napi_build_skb(e->buf, q->buf_size);
+-		if (!skb) {
+-			page_pool_put_full_page(q->page_pool,
+-						virt_to_head_page(e->buf),
+-						true);
+-			break;
+-		}
+-
+-		skb_reserve(skb, 2);
+-		__skb_put(skb, len);
+-		skb_mark_for_recycle(skb);
+-		skb->dev = eth->ports[p]->dev;
+-		skb->protocol = eth_type_trans(skb, skb->dev);
+-		skb->ip_summed = CHECKSUM_UNNECESSARY;
+-		skb_record_rx_queue(skb, qid);
+-		napi_gro_receive(&q->napi, skb);
+-
+-		done++;
+-	}
+-	airoha_qdma_fill_rx_queue(q);
+-
+-	return done;
+-}
+-
+-static int airoha_qdma_rx_napi_poll(struct napi_struct *napi, int budget)
+-{
+-	struct airoha_queue *q = container_of(napi, struct airoha_queue, napi);
+-	int cur, done = 0;
+-
+-	do {
+-		cur = airoha_qdma_rx_process(q, budget - done);
+-		done += cur;
+-	} while (cur && done < budget);
+-
+-	if (done < budget && napi_complete(napi))
+-		airoha_qdma_irq_enable(q->qdma, QDMA_INT_REG_IDX1,
+-				       RX_DONE_INT_MASK);
+-
+-	return done;
+-}
+-
+-static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
+-				     struct airoha_qdma *qdma, int ndesc)
+-{
+-	const struct page_pool_params pp_params = {
+-		.order = 0,
+-		.pool_size = 256,
+-		.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV |
+-			 PP_FLAG_PAGE_FRAG,
+-		.dma_dir = DMA_FROM_DEVICE,
+-		.max_len = PAGE_SIZE,
+-		.nid = NUMA_NO_NODE,
+-		.dev = qdma->eth->dev,
+-		.napi = &q->napi,
+-	};
+-	struct airoha_eth *eth = qdma->eth;
+-	int qid = q - &qdma->q_rx[0], thr;
+-	dma_addr_t dma_addr;
+-
+-	q->buf_size = PAGE_SIZE / 2;
+-	q->ndesc = ndesc;
+-	q->qdma = qdma;
+-
+-	q->entry = devm_kzalloc(eth->dev, q->ndesc * sizeof(*q->entry),
+-				GFP_KERNEL);
+-	if (!q->entry)
+-		return -ENOMEM;
+-
+-	q->page_pool = page_pool_create(&pp_params);
+-	if (IS_ERR(q->page_pool)) {
+-		int err = PTR_ERR(q->page_pool);
+-
+-		q->page_pool = NULL;
+-		return err;
+-	}
+-
+-	q->desc = dmam_alloc_coherent(eth->dev, q->ndesc * sizeof(*q->desc),
+-				      &dma_addr, GFP_KERNEL);
+-	if (!q->desc)
+-		return -ENOMEM;
+-
+-	netif_napi_add(eth->napi_dev, &q->napi, airoha_qdma_rx_napi_poll);
+-
+-	airoha_qdma_wr(qdma, REG_RX_RING_BASE(qid), dma_addr);
+-	airoha_qdma_rmw(qdma, REG_RX_RING_SIZE(qid),
+-			RX_RING_SIZE_MASK,
+-			FIELD_PREP(RX_RING_SIZE_MASK, ndesc));
+-
+-	thr = clamp(ndesc >> 3, 1, 32);
+-	airoha_qdma_rmw(qdma, REG_RX_RING_SIZE(qid), RX_RING_THR_MASK,
+-			FIELD_PREP(RX_RING_THR_MASK, thr));
+-	airoha_qdma_rmw(qdma, REG_RX_DMA_IDX(qid), RX_RING_DMA_IDX_MASK,
+-			FIELD_PREP(RX_RING_DMA_IDX_MASK, q->head));
+-
+-	airoha_qdma_fill_rx_queue(q);
+-
+-	return 0;
+-}
+-
+-static void airoha_qdma_cleanup_rx_queue(struct airoha_queue *q)
+-{
+-	struct airoha_eth *eth = q->qdma->eth;
+-
+-	while (q->queued) {
+-		struct airoha_queue_entry *e = &q->entry[q->tail];
+-		struct page *page = virt_to_head_page(e->buf);
+-
+-		dma_sync_single_for_cpu(eth->dev, e->dma_addr, e->dma_len,
+-					page_pool_get_dma_dir(q->page_pool));
+-		page_pool_put_full_page(q->page_pool, page, false);
+-		q->tail = (q->tail + 1) % q->ndesc;
+-		q->queued--;
+-	}
+-}
+-
+-static int airoha_qdma_init_rx(struct airoha_qdma *qdma)
+-{
+-	int i;
+-
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+-		int err;
+-
+-		if (!(RX_DONE_INT_MASK & BIT(i))) {
+-			/* rx-queue not binded to irq */
+-			continue;
+-		}
+-
+-		err = airoha_qdma_init_rx_queue(&qdma->q_rx[i], qdma,
+-						RX_DSCP_NUM(i));
+-		if (err)
+-			return err;
+-	}
+-
+-	return 0;
+-}
+-
+-static int airoha_qdma_tx_napi_poll(struct napi_struct *napi, int budget)
+-{
+-	struct airoha_tx_irq_queue *irq_q;
+-	int id, done = 0, irq_queued;
+-	struct airoha_qdma *qdma;
+-	struct airoha_eth *eth;
+-	u32 status, head;
+-
+-	irq_q = container_of(napi, struct airoha_tx_irq_queue, napi);
+-	qdma = irq_q->qdma;
+-	id = irq_q - &qdma->q_tx_irq[0];
+-	eth = qdma->eth;
+-
+-	status = airoha_qdma_rr(qdma, REG_IRQ_STATUS(id));
+-	head = FIELD_GET(IRQ_HEAD_IDX_MASK, status);
+-	head = head % irq_q->size;
+-	irq_queued = FIELD_GET(IRQ_ENTRY_LEN_MASK, status);
+-
+-	while (irq_queued > 0 && done < budget) {
+-		u32 qid, val = irq_q->q[head];
+-		struct airoha_qdma_desc *desc;
+-		struct airoha_queue_entry *e;
+-		struct airoha_queue *q;
+-		u32 index, desc_ctrl;
+-		struct sk_buff *skb;
+-
+-		if (val == 0xff)
+-			break;
+-
+-		irq_q->q[head] = 0xff; /* mark as done */
+-		head = (head + 1) % irq_q->size;
+-		irq_queued--;
+-		done++;
+-
+-		qid = FIELD_GET(IRQ_RING_IDX_MASK, val);
+-		if (qid >= ARRAY_SIZE(qdma->q_tx))
+-			continue;
+-
+-		q = &qdma->q_tx[qid];
+-		if (!q->ndesc)
+-			continue;
+-
+-		index = FIELD_GET(IRQ_DESC_IDX_MASK, val);
+-		if (index >= q->ndesc)
+-			continue;
+-
+-		spin_lock_bh(&q->lock);
+-
+-		if (!q->queued)
+-			goto unlock;
+-
+-		desc = &q->desc[index];
+-		desc_ctrl = le32_to_cpu(desc->ctrl);
+-
+-		if (!(desc_ctrl & QDMA_DESC_DONE_MASK) &&
+-		    !(desc_ctrl & QDMA_DESC_DROP_MASK))
+-			goto unlock;
+-
+-		e = &q->entry[index];
+-		skb = e->skb;
+-
+-		dma_unmap_single(eth->dev, e->dma_addr, e->dma_len,
+-				 DMA_TO_DEVICE);
+-		memset(e, 0, sizeof(*e));
+-		WRITE_ONCE(desc->msg0, 0);
+-		WRITE_ONCE(desc->msg1, 0);
+-		q->queued--;
+-
+-		/* completion ring can report out-of-order indexes if hw QoS
+-		 * is enabled and packets with different priority are queued
+-		 * to same DMA ring. Take into account possible out-of-order
+-		 * reports incrementing DMA ring tail pointer
+-		 */
+-		while (q->tail != q->head && !q->entry[q->tail].dma_addr)
+-			q->tail = (q->tail + 1) % q->ndesc;
+-
+-		if (skb) {
+-			u16 queue = skb_get_queue_mapping(skb);
+-			struct netdev_queue *txq;
+-
+-			txq = netdev_get_tx_queue(skb->dev, queue);
+-			netdev_tx_completed_queue(txq, 1, skb->len);
+-			if (netif_tx_queue_stopped(txq) &&
+-			    q->ndesc - q->queued >= q->free_thr)
+-				netif_tx_wake_queue(txq);
+-
+-			dev_kfree_skb_any(skb);
+-		}
+-unlock:
+-		spin_unlock_bh(&q->lock);
+-	}
+-
+-	if (done) {
+-		int i, len = done >> 7;
+-
+-		for (i = 0; i < len; i++)
+-			airoha_qdma_rmw(qdma, REG_IRQ_CLEAR_LEN(id),
+-					IRQ_CLEAR_LEN_MASK, 0x80);
+-		airoha_qdma_rmw(qdma, REG_IRQ_CLEAR_LEN(id),
+-				IRQ_CLEAR_LEN_MASK, (done & 0x7f));
+-	}
+-
+-	if (done < budget && napi_complete(napi))
+-		airoha_qdma_irq_enable(qdma, QDMA_INT_REG_IDX0,
+-				       TX_DONE_INT_MASK(id));
+-
+-	return done;
+-}
+-
+-static int airoha_qdma_init_tx_queue(struct airoha_queue *q,
+-				     struct airoha_qdma *qdma, int size)
+-{
+-	struct airoha_eth *eth = qdma->eth;
+-	int i, qid = q - &qdma->q_tx[0];
+-	dma_addr_t dma_addr;
+-
+-	spin_lock_init(&q->lock);
+-	q->ndesc = size;
+-	q->qdma = qdma;
+-	q->free_thr = 1 + MAX_SKB_FRAGS;
+-
+-	q->entry = devm_kzalloc(eth->dev, q->ndesc * sizeof(*q->entry),
+-				GFP_KERNEL);
+-	if (!q->entry)
+-		return -ENOMEM;
+-
+-	q->desc = dmam_alloc_coherent(eth->dev, q->ndesc * sizeof(*q->desc),
+-				      &dma_addr, GFP_KERNEL);
+-	if (!q->desc)
+-		return -ENOMEM;
+-
+-	for (i = 0; i < q->ndesc; i++) {
+-		u32 val;
+-
+-		val = FIELD_PREP(QDMA_DESC_DONE_MASK, 1);
+-		WRITE_ONCE(q->desc[i].ctrl, cpu_to_le32(val));
+-	}
+-
+-	/* xmit ring drop default setting */
+-	airoha_qdma_set(qdma, REG_TX_RING_BLOCKING(qid),
+-			TX_RING_IRQ_BLOCKING_TX_DROP_EN_MASK);
+-
+-	airoha_qdma_wr(qdma, REG_TX_RING_BASE(qid), dma_addr);
+-	airoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid), TX_RING_CPU_IDX_MASK,
+-			FIELD_PREP(TX_RING_CPU_IDX_MASK, q->head));
+-	airoha_qdma_rmw(qdma, REG_TX_DMA_IDX(qid), TX_RING_DMA_IDX_MASK,
+-			FIELD_PREP(TX_RING_DMA_IDX_MASK, q->head));
+-
+-	return 0;
+-}
+-
+-static int airoha_qdma_tx_irq_init(struct airoha_tx_irq_queue *irq_q,
+-				   struct airoha_qdma *qdma, int size)
+-{
+-	int id = irq_q - &qdma->q_tx_irq[0];
+-	struct airoha_eth *eth = qdma->eth;
+-	dma_addr_t dma_addr;
+-
+-	netif_napi_add_tx(eth->napi_dev, &irq_q->napi,
+-			  airoha_qdma_tx_napi_poll);
+-	irq_q->q = dmam_alloc_coherent(eth->dev, size * sizeof(u32),
+-				       &dma_addr, GFP_KERNEL);
+-	if (!irq_q->q)
+-		return -ENOMEM;
+-
+-	memset(irq_q->q, 0xff, size * sizeof(u32));
+-	irq_q->size = size;
+-	irq_q->qdma = qdma;
+-
+-	airoha_qdma_wr(qdma, REG_TX_IRQ_BASE(id), dma_addr);
+-	airoha_qdma_rmw(qdma, REG_TX_IRQ_CFG(id), TX_IRQ_DEPTH_MASK,
+-			FIELD_PREP(TX_IRQ_DEPTH_MASK, size));
+-	airoha_qdma_rmw(qdma, REG_TX_IRQ_CFG(id), TX_IRQ_THR_MASK,
+-			FIELD_PREP(TX_IRQ_THR_MASK, 1));
+-
+-	return 0;
+-}
+-
+-static int airoha_qdma_init_tx(struct airoha_qdma *qdma)
+-{
+-	int i, err;
+-
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_tx_irq); i++) {
+-		err = airoha_qdma_tx_irq_init(&qdma->q_tx_irq[i], qdma,
+-					      IRQ_QUEUE_LEN(i));
+-		if (err)
+-			return err;
+-	}
+-
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
+-		err = airoha_qdma_init_tx_queue(&qdma->q_tx[i], qdma,
+-						TX_DSCP_NUM);
+-		if (err)
+-			return err;
+-	}
+-
+-	return 0;
+-}
+-
+-static void airoha_qdma_cleanup_tx_queue(struct airoha_queue *q)
+-{
+-	struct airoha_eth *eth = q->qdma->eth;
+-
+-	spin_lock_bh(&q->lock);
+-	while (q->queued) {
+-		struct airoha_queue_entry *e = &q->entry[q->tail];
+-
+-		dma_unmap_single(eth->dev, e->dma_addr, e->dma_len,
+-				 DMA_TO_DEVICE);
+-		dev_kfree_skb_any(e->skb);
+-		e->skb = NULL;
+-
+-		q->tail = (q->tail + 1) % q->ndesc;
+-		q->queued--;
+-	}
+-	spin_unlock_bh(&q->lock);
+-}
+-
+-static int airoha_qdma_init_hfwd_queues(struct airoha_qdma *qdma)
+-{
+-	struct airoha_eth *eth = qdma->eth;
+-	dma_addr_t dma_addr;
+-	u32 status;
+-	int size;
+-
+-	size = HW_DSCP_NUM * sizeof(struct airoha_qdma_fwd_desc);
+-	qdma->hfwd.desc = dmam_alloc_coherent(eth->dev, size, &dma_addr,
+-					      GFP_KERNEL);
+-	if (!qdma->hfwd.desc)
+-		return -ENOMEM;
+-
+-	airoha_qdma_wr(qdma, REG_FWD_DSCP_BASE, dma_addr);
+-
+-	size = AIROHA_MAX_PACKET_SIZE * HW_DSCP_NUM;
+-	qdma->hfwd.q = dmam_alloc_coherent(eth->dev, size, &dma_addr,
+-					   GFP_KERNEL);
+-	if (!qdma->hfwd.q)
+-		return -ENOMEM;
+-
+-	airoha_qdma_wr(qdma, REG_FWD_BUF_BASE, dma_addr);
+-
+-	airoha_qdma_rmw(qdma, REG_HW_FWD_DSCP_CFG,
+-			HW_FWD_DSCP_PAYLOAD_SIZE_MASK,
+-			FIELD_PREP(HW_FWD_DSCP_PAYLOAD_SIZE_MASK, 0));
+-	airoha_qdma_rmw(qdma, REG_FWD_DSCP_LOW_THR, FWD_DSCP_LOW_THR_MASK,
+-			FIELD_PREP(FWD_DSCP_LOW_THR_MASK, 128));
+-	airoha_qdma_rmw(qdma, REG_LMGR_INIT_CFG,
+-			LMGR_INIT_START | LMGR_SRAM_MODE_MASK |
+-			HW_FWD_DESC_NUM_MASK,
+-			FIELD_PREP(HW_FWD_DESC_NUM_MASK, HW_DSCP_NUM) |
+-			LMGR_INIT_START);
+-
+-	return read_poll_timeout(airoha_qdma_rr, status,
+-				 !(status & LMGR_INIT_START), USEC_PER_MSEC,
+-				 30 * USEC_PER_MSEC, true, qdma,
+-				 REG_LMGR_INIT_CFG);
+-}
+-
+-static void airoha_qdma_init_qos(struct airoha_qdma *qdma)
+-{
+-	airoha_qdma_clear(qdma, REG_TXWRR_MODE_CFG, TWRR_WEIGHT_SCALE_MASK);
+-	airoha_qdma_set(qdma, REG_TXWRR_MODE_CFG, TWRR_WEIGHT_BASE_MASK);
+-
+-	airoha_qdma_clear(qdma, REG_PSE_BUF_USAGE_CFG,
+-			  PSE_BUF_ESTIMATE_EN_MASK);
+-
+-	airoha_qdma_set(qdma, REG_EGRESS_RATE_METER_CFG,
+-			EGRESS_RATE_METER_EN_MASK |
+-			EGRESS_RATE_METER_EQ_RATE_EN_MASK);
+-	/* 2047us x 31 = 63.457ms */
+-	airoha_qdma_rmw(qdma, REG_EGRESS_RATE_METER_CFG,
+-			EGRESS_RATE_METER_WINDOW_SZ_MASK,
+-			FIELD_PREP(EGRESS_RATE_METER_WINDOW_SZ_MASK, 0x1f));
+-	airoha_qdma_rmw(qdma, REG_EGRESS_RATE_METER_CFG,
+-			EGRESS_RATE_METER_TIMESLICE_MASK,
+-			FIELD_PREP(EGRESS_RATE_METER_TIMESLICE_MASK, 0x7ff));
+-
+-	/* ratelimit init */
+-	airoha_qdma_set(qdma, REG_GLB_TRTCM_CFG, GLB_TRTCM_EN_MASK);
+-	/* fast-tick 25us */
+-	airoha_qdma_rmw(qdma, REG_GLB_TRTCM_CFG, GLB_FAST_TICK_MASK,
+-			FIELD_PREP(GLB_FAST_TICK_MASK, 25));
+-	airoha_qdma_rmw(qdma, REG_GLB_TRTCM_CFG, GLB_SLOW_TICK_RATIO_MASK,
+-			FIELD_PREP(GLB_SLOW_TICK_RATIO_MASK, 40));
+-
+-	airoha_qdma_set(qdma, REG_EGRESS_TRTCM_CFG, EGRESS_TRTCM_EN_MASK);
+-	airoha_qdma_rmw(qdma, REG_EGRESS_TRTCM_CFG, EGRESS_FAST_TICK_MASK,
+-			FIELD_PREP(EGRESS_FAST_TICK_MASK, 25));
+-	airoha_qdma_rmw(qdma, REG_EGRESS_TRTCM_CFG,
+-			EGRESS_SLOW_TICK_RATIO_MASK,
+-			FIELD_PREP(EGRESS_SLOW_TICK_RATIO_MASK, 40));
+-
+-	airoha_qdma_set(qdma, REG_INGRESS_TRTCM_CFG, INGRESS_TRTCM_EN_MASK);
+-	airoha_qdma_clear(qdma, REG_INGRESS_TRTCM_CFG,
+-			  INGRESS_TRTCM_MODE_MASK);
+-	airoha_qdma_rmw(qdma, REG_INGRESS_TRTCM_CFG, INGRESS_FAST_TICK_MASK,
+-			FIELD_PREP(INGRESS_FAST_TICK_MASK, 125));
+-	airoha_qdma_rmw(qdma, REG_INGRESS_TRTCM_CFG,
+-			INGRESS_SLOW_TICK_RATIO_MASK,
+-			FIELD_PREP(INGRESS_SLOW_TICK_RATIO_MASK, 8));
+-
+-	airoha_qdma_set(qdma, REG_SLA_TRTCM_CFG, SLA_TRTCM_EN_MASK);
+-	airoha_qdma_rmw(qdma, REG_SLA_TRTCM_CFG, SLA_FAST_TICK_MASK,
+-			FIELD_PREP(SLA_FAST_TICK_MASK, 25));
+-	airoha_qdma_rmw(qdma, REG_SLA_TRTCM_CFG, SLA_SLOW_TICK_RATIO_MASK,
+-			FIELD_PREP(SLA_SLOW_TICK_RATIO_MASK, 40));
+-}
+-
+-static void airoha_qdma_init_qos_stats(struct airoha_qdma *qdma)
+-{
+-	int i;
+-
+-	for (i = 0; i < AIROHA_NUM_QOS_CHANNELS; i++) {
+-		/* Tx-cpu transferred count */
+-		airoha_qdma_wr(qdma, REG_CNTR_VAL(i << 1), 0);
+-		airoha_qdma_wr(qdma, REG_CNTR_CFG(i << 1),
+-			       CNTR_EN_MASK | CNTR_ALL_QUEUE_EN_MASK |
+-			       CNTR_ALL_DSCP_RING_EN_MASK |
+-			       FIELD_PREP(CNTR_CHAN_MASK, i));
+-		/* Tx-fwd transferred count */
+-		airoha_qdma_wr(qdma, REG_CNTR_VAL((i << 1) + 1), 0);
+-		airoha_qdma_wr(qdma, REG_CNTR_CFG(i << 1),
+-			       CNTR_EN_MASK | CNTR_ALL_QUEUE_EN_MASK |
+-			       CNTR_ALL_DSCP_RING_EN_MASK |
+-			       FIELD_PREP(CNTR_SRC_MASK, 1) |
+-			       FIELD_PREP(CNTR_CHAN_MASK, i));
+-	}
+-}
+-
+-static int airoha_qdma_hw_init(struct airoha_qdma *qdma)
+-{
+-	int i;
+-
+-	/* clear pending irqs */
+-	for (i = 0; i < ARRAY_SIZE(qdma->irqmask); i++)
+-		airoha_qdma_wr(qdma, REG_INT_STATUS(i), 0xffffffff);
+-
+-	/* setup irqs */
+-	airoha_qdma_irq_enable(qdma, QDMA_INT_REG_IDX0, INT_IDX0_MASK);
+-	airoha_qdma_irq_enable(qdma, QDMA_INT_REG_IDX1, INT_IDX1_MASK);
+-	airoha_qdma_irq_enable(qdma, QDMA_INT_REG_IDX4, INT_IDX4_MASK);
+-
+-	/* setup irq binding */
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
+-		if (!qdma->q_tx[i].ndesc)
+-			continue;
+-
+-		if (TX_RING_IRQ_BLOCKING_MAP_MASK & BIT(i))
+-			airoha_qdma_set(qdma, REG_TX_RING_BLOCKING(i),
+-					TX_RING_IRQ_BLOCKING_CFG_MASK);
+-		else
+-			airoha_qdma_clear(qdma, REG_TX_RING_BLOCKING(i),
+-					  TX_RING_IRQ_BLOCKING_CFG_MASK);
+-	}
+-
+-	airoha_qdma_wr(qdma, REG_QDMA_GLOBAL_CFG,
+-		       GLOBAL_CFG_RX_2B_OFFSET_MASK |
+-		       FIELD_PREP(GLOBAL_CFG_DMA_PREFERENCE_MASK, 3) |
+-		       GLOBAL_CFG_CPU_TXR_RR_MASK |
+-		       GLOBAL_CFG_PAYLOAD_BYTE_SWAP_MASK |
+-		       GLOBAL_CFG_MULTICAST_MODIFY_FP_MASK |
+-		       GLOBAL_CFG_MULTICAST_EN_MASK |
+-		       GLOBAL_CFG_IRQ0_EN_MASK | GLOBAL_CFG_IRQ1_EN_MASK |
+-		       GLOBAL_CFG_TX_WB_DONE_MASK |
+-		       FIELD_PREP(GLOBAL_CFG_MAX_ISSUE_NUM_MASK, 2));
+-
+-	airoha_qdma_init_qos(qdma);
+-
+-	/* disable qdma rx delay interrupt */
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+-		if (!qdma->q_rx[i].ndesc)
+-			continue;
+-
+-		airoha_qdma_clear(qdma, REG_RX_DELAY_INT_IDX(i),
+-				  RX_DELAY_INT_MASK);
+-	}
+-
+-	airoha_qdma_set(qdma, REG_TXQ_CNGST_CFG,
+-			TXQ_CNGST_DROP_EN | TXQ_CNGST_DEI_DROP_EN);
+-	airoha_qdma_init_qos_stats(qdma);
+-
+-	return 0;
+-}
+-
+-static irqreturn_t airoha_irq_handler(int irq, void *dev_instance)
+-{
+-	struct airoha_qdma *qdma = dev_instance;
+-	u32 intr[ARRAY_SIZE(qdma->irqmask)];
+-	int i;
+-
+-	for (i = 0; i < ARRAY_SIZE(qdma->irqmask); i++) {
+-		intr[i] = airoha_qdma_rr(qdma, REG_INT_STATUS(i));
+-		intr[i] &= qdma->irqmask[i];
+-		airoha_qdma_wr(qdma, REG_INT_STATUS(i), intr[i]);
+-	}
+-
+-	if (!test_bit(DEV_STATE_INITIALIZED, &qdma->eth->state))
+-		return IRQ_NONE;
+-
+-	if (intr[1] & RX_DONE_INT_MASK) {
+-		airoha_qdma_irq_disable(qdma, QDMA_INT_REG_IDX1,
+-					RX_DONE_INT_MASK);
+-
+-		for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+-			if (!qdma->q_rx[i].ndesc)
+-				continue;
+-
+-			if (intr[1] & BIT(i))
+-				napi_schedule(&qdma->q_rx[i].napi);
+-		}
+-	}
+-
+-	if (intr[0] & INT_TX_MASK) {
+-		for (i = 0; i < ARRAY_SIZE(qdma->q_tx_irq); i++) {
+-			if (!(intr[0] & TX_DONE_INT_MASK(i)))
+-				continue;
+-
+-			airoha_qdma_irq_disable(qdma, QDMA_INT_REG_IDX0,
+-						TX_DONE_INT_MASK(i));
+-			napi_schedule(&qdma->q_tx_irq[i].napi);
+-		}
+-	}
+-
+-	return IRQ_HANDLED;
+-}
+-
+-static int airoha_qdma_init(struct platform_device *pdev,
+-			    struct airoha_eth *eth,
+-			    struct airoha_qdma *qdma)
+-{
+-	int err, id = qdma - &eth->qdma[0];
+-	const char *res;
+-
+-	spin_lock_init(&qdma->irq_lock);
+-	qdma->eth = eth;
+-
+-	res = devm_kasprintf(eth->dev, GFP_KERNEL, "qdma%d", id);
+-	if (!res)
+-		return -ENOMEM;
+-
+-	qdma->regs = devm_platform_ioremap_resource_byname(pdev, res);
+-	if (IS_ERR(qdma->regs))
+-		return dev_err_probe(eth->dev, PTR_ERR(qdma->regs),
+-				     "failed to iomap qdma%d regs\n", id);
+-
+-	qdma->irq = platform_get_irq(pdev, 4 * id);
+-	if (qdma->irq < 0)
+-		return qdma->irq;
+-
+-	err = devm_request_irq(eth->dev, qdma->irq, airoha_irq_handler,
+-			       IRQF_SHARED, KBUILD_MODNAME, qdma);
+-	if (err)
+-		return err;
+-
+-	err = airoha_qdma_init_rx(qdma);
+-	if (err)
+-		return err;
+-
+-	err = airoha_qdma_init_tx(qdma);
+-	if (err)
+-		return err;
+-
+-	err = airoha_qdma_init_hfwd_queues(qdma);
+-	if (err)
+-		return err;
+-
+-	return airoha_qdma_hw_init(qdma);
+-}
+-
+-static int airoha_hw_init(struct platform_device *pdev,
+-			  struct airoha_eth *eth)
+-{
+-	int err, i;
+-
+-	/* disable xsi */
+-	err = reset_control_bulk_assert(ARRAY_SIZE(eth->xsi_rsts),
+-					eth->xsi_rsts);
+-	if (err)
+-		return err;
+-
+-	err = reset_control_bulk_assert(ARRAY_SIZE(eth->rsts), eth->rsts);
+-	if (err)
+-		return err;
+-
+-	msleep(20);
+-	err = reset_control_bulk_deassert(ARRAY_SIZE(eth->rsts), eth->rsts);
+-	if (err)
+-		return err;
+-
+-	msleep(20);
+-	err = airoha_fe_init(eth);
+-	if (err)
+-		return err;
+-
+-	for (i = 0; i < ARRAY_SIZE(eth->qdma); i++) {
+-		err = airoha_qdma_init(pdev, eth, &eth->qdma[i]);
+-		if (err)
+-			return err;
+-	}
+-
+-	set_bit(DEV_STATE_INITIALIZED, &eth->state);
+-
+-	return 0;
+-}
+-
+-static void airoha_hw_cleanup(struct airoha_qdma *qdma)
+-{
+-	int i;
+-
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+-		if (!qdma->q_rx[i].ndesc)
+-			continue;
+-
+-		netif_napi_del(&qdma->q_rx[i].napi);
+-		airoha_qdma_cleanup_rx_queue(&qdma->q_rx[i]);
+-		if (qdma->q_rx[i].page_pool)
+-			page_pool_destroy(qdma->q_rx[i].page_pool);
+-	}
+-
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_tx_irq); i++)
+-		netif_napi_del(&qdma->q_tx_irq[i].napi);
+-
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
+-		if (!qdma->q_tx[i].ndesc)
+-			continue;
+-
+-		airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
+-	}
+-}
+-
+-static void airoha_qdma_start_napi(struct airoha_qdma *qdma)
+-{
+-	int i;
+-
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_tx_irq); i++)
+-		napi_enable(&qdma->q_tx_irq[i].napi);
+-
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+-		if (!qdma->q_rx[i].ndesc)
+-			continue;
+-
+-		napi_enable(&qdma->q_rx[i].napi);
+-	}
+-}
+-
+-static void airoha_qdma_stop_napi(struct airoha_qdma *qdma)
+-{
+-	int i;
+-
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_tx_irq); i++)
+-		napi_disable(&qdma->q_tx_irq[i].napi);
+-
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+-		if (!qdma->q_rx[i].ndesc)
+-			continue;
+-
+-		napi_disable(&qdma->q_rx[i].napi);
+-	}
+-}
+-
+-static void airoha_update_hw_stats(struct airoha_gdm_port *port)
+-{
+-	struct airoha_eth *eth = port->qdma->eth;
+-	u32 val, i = 0;
+-
+-	spin_lock(&port->stats.lock);
+-	u64_stats_update_begin(&port->stats.syncp);
+-
+-	/* TX */
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_H(port->id));
+-	port->stats.tx_ok_pkts += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
+-	port->stats.tx_ok_pkts += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_H(port->id));
+-	port->stats.tx_ok_bytes += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id));
+-	port->stats.tx_ok_bytes += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_DROP_CNT(port->id));
+-	port->stats.tx_drops += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_BC_CNT(port->id));
+-	port->stats.tx_broadcast += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_MC_CNT(port->id));
+-	port->stats.tx_multicast += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_RUNT_CNT(port->id));
+-	port->stats.tx_len[i] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_H(port->id));
+-	port->stats.tx_len[i] += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id));
+-	port->stats.tx_len[i++] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_H(port->id));
+-	port->stats.tx_len[i] += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_L(port->id));
+-	port->stats.tx_len[i++] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_H(port->id));
+-	port->stats.tx_len[i] += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_L(port->id));
+-	port->stats.tx_len[i++] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_H(port->id));
+-	port->stats.tx_len[i] += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_L(port->id));
+-	port->stats.tx_len[i++] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_H(port->id));
+-	port->stats.tx_len[i] += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_L(port->id));
+-	port->stats.tx_len[i++] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_H(port->id));
+-	port->stats.tx_len[i] += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_L(port->id));
+-	port->stats.tx_len[i++] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_LONG_CNT(port->id));
+-	port->stats.tx_len[i++] += val;
+-
+-	/* RX */
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_H(port->id));
+-	port->stats.rx_ok_pkts += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_L(port->id));
+-	port->stats.rx_ok_pkts += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_H(port->id));
+-	port->stats.rx_ok_bytes += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_L(port->id));
+-	port->stats.rx_ok_bytes += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_DROP_CNT(port->id));
+-	port->stats.rx_drops += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_BC_CNT(port->id));
+-	port->stats.rx_broadcast += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_MC_CNT(port->id));
+-	port->stats.rx_multicast += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ERROR_DROP_CNT(port->id));
+-	port->stats.rx_errors += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_CRC_ERR_CNT(port->id));
+-	port->stats.rx_crc_error += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_OVERFLOW_DROP_CNT(port->id));
+-	port->stats.rx_over_errors += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_FRAG_CNT(port->id));
+-	port->stats.rx_fragment += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_JABBER_CNT(port->id));
+-	port->stats.rx_jabber += val;
+-
+-	i = 0;
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_RUNT_CNT(port->id));
+-	port->stats.rx_len[i] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_H(port->id));
+-	port->stats.rx_len[i] += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id));
+-	port->stats.rx_len[i++] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_H(port->id));
+-	port->stats.rx_len[i] += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_L(port->id));
+-	port->stats.rx_len[i++] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_H(port->id));
+-	port->stats.rx_len[i] += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_L(port->id));
+-	port->stats.rx_len[i++] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_H(port->id));
+-	port->stats.rx_len[i] += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_L(port->id));
+-	port->stats.rx_len[i++] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_H(port->id));
+-	port->stats.rx_len[i] += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_L(port->id));
+-	port->stats.rx_len[i++] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_H(port->id));
+-	port->stats.rx_len[i] += ((u64)val << 32);
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_L(port->id));
+-	port->stats.rx_len[i++] += val;
+-
+-	val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_LONG_CNT(port->id));
+-	port->stats.rx_len[i++] += val;
+-
+-	/* reset mib counters */
+-	airoha_fe_set(eth, REG_FE_GDM_MIB_CLEAR(port->id),
+-		      FE_GDM_MIB_RX_CLEAR_MASK | FE_GDM_MIB_TX_CLEAR_MASK);
+-
+-	u64_stats_update_end(&port->stats.syncp);
+-	spin_unlock(&port->stats.lock);
+-}
+-
+-static int airoha_dev_open(struct net_device *dev)
+-{
+-	struct airoha_gdm_port *port = netdev_priv(dev);
+-	struct airoha_qdma *qdma = port->qdma;
+-	int err;
+-
+-	netif_tx_start_all_queues(dev);
+-	err = airoha_set_gdm_ports(qdma->eth, true);
+-	if (err)
+-		return err;
+-
+-	if (netdev_uses_dsa(dev))
+-		airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
+-			      GDM_STAG_EN_MASK);
+-	else
+-		airoha_fe_clear(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
+-				GDM_STAG_EN_MASK);
+-
+-	airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
+-			GLOBAL_CFG_TX_DMA_EN_MASK |
+-			GLOBAL_CFG_RX_DMA_EN_MASK);
+-
+-	return 0;
+-}
+-
+-static int airoha_dev_stop(struct net_device *dev)
+-{
+-	struct airoha_gdm_port *port = netdev_priv(dev);
+-	struct airoha_qdma *qdma = port->qdma;
+-	int i, err;
+-
+-	netif_tx_disable(dev);
+-	err = airoha_set_gdm_ports(qdma->eth, false);
+-	if (err)
+-		return err;
+-
+-	airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
+-			  GLOBAL_CFG_TX_DMA_EN_MASK |
+-			  GLOBAL_CFG_RX_DMA_EN_MASK);
+-
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
+-		if (!qdma->q_tx[i].ndesc)
+-			continue;
+-
+-		airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
+-		netdev_tx_reset_subqueue(dev, i);
+-	}
+-
+-	return 0;
+-}
+-
+-static int airoha_dev_set_macaddr(struct net_device *dev, void *p)
+-{
+-	struct airoha_gdm_port *port = netdev_priv(dev);
+-	int err;
+-
+-	err = eth_mac_addr(dev, p);
+-	if (err)
+-		return err;
+-
+-	airoha_set_macaddr(port, dev->dev_addr);
+-
+-	return 0;
+-}
+-
+-static int airoha_dev_init(struct net_device *dev)
+-{
+-	struct airoha_gdm_port *port = netdev_priv(dev);
+-
+-	airoha_set_macaddr(port, dev->dev_addr);
+-
+-	return 0;
+-}
+-
+-static void airoha_dev_get_stats64(struct net_device *dev,
+-				   struct rtnl_link_stats64 *storage)
+-{
+-	struct airoha_gdm_port *port = netdev_priv(dev);
+-	unsigned int start;
+-
+-	airoha_update_hw_stats(port);
+-	do {
+-		start = u64_stats_fetch_begin(&port->stats.syncp);
+-		storage->rx_packets = port->stats.rx_ok_pkts;
+-		storage->tx_packets = port->stats.tx_ok_pkts;
+-		storage->rx_bytes = port->stats.rx_ok_bytes;
+-		storage->tx_bytes = port->stats.tx_ok_bytes;
+-		storage->multicast = port->stats.rx_multicast;
+-		storage->rx_errors = port->stats.rx_errors;
+-		storage->rx_dropped = port->stats.rx_drops;
+-		storage->tx_dropped = port->stats.tx_drops;
+-		storage->rx_crc_errors = port->stats.rx_crc_error;
+-		storage->rx_over_errors = port->stats.rx_over_errors;
+-	} while (u64_stats_fetch_retry(&port->stats.syncp, start));
+-}
+-
+-static u16 airoha_dev_select_queue(struct net_device *dev, struct sk_buff *skb,
+-				   struct net_device *sb_dev)
+-{
+-	struct airoha_gdm_port *port = netdev_priv(dev);
+-	int queue, channel;
+-
+-	/* For dsa device select QoS channel according to the dsa user port
+-	 * index, rely on port id otherwise. Select QoS queue based on the
+-	 * skb priority.
+-	 */
+-	channel = netdev_uses_dsa(dev) ? skb_get_queue_mapping(skb) : port->id;
+-	channel = channel % AIROHA_NUM_QOS_CHANNELS;
+-	queue = (skb->priority - 1) % AIROHA_NUM_QOS_QUEUES; /* QoS queue */
+-	queue = channel * AIROHA_NUM_QOS_QUEUES + queue;
+-
+-	return queue < dev->num_tx_queues ? queue : 0;
+-}
+-
+-static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
+-				   struct net_device *dev)
+-{
+-	struct airoha_gdm_port *port = netdev_priv(dev);
+-	u32 nr_frags = 1 + skb_shinfo(skb)->nr_frags;
+-	u32 msg0, msg1, len = skb_headlen(skb);
+-	struct airoha_qdma *qdma = port->qdma;
+-	struct netdev_queue *txq;
+-	struct airoha_queue *q;
+-	void *data = skb->data;
+-	int i, qid;
+-	u16 index;
+-	u8 fport;
+-
+-	qid = skb_get_queue_mapping(skb) % ARRAY_SIZE(qdma->q_tx);
+-	msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
+-			  qid / AIROHA_NUM_QOS_QUEUES) |
+-	       FIELD_PREP(QDMA_ETH_TXMSG_QUEUE_MASK,
+-			  qid % AIROHA_NUM_QOS_QUEUES);
+-	if (skb->ip_summed == CHECKSUM_PARTIAL)
+-		msg0 |= FIELD_PREP(QDMA_ETH_TXMSG_TCO_MASK, 1) |
+-			FIELD_PREP(QDMA_ETH_TXMSG_UCO_MASK, 1) |
+-			FIELD_PREP(QDMA_ETH_TXMSG_ICO_MASK, 1);
+-
+-	/* TSO: fill MSS info in tcp checksum field */
+-	if (skb_is_gso(skb)) {
+-		if (skb_cow_head(skb, 0))
+-			goto error;
+-
+-		if (skb_shinfo(skb)->gso_type & (SKB_GSO_TCPV4 |
+-						 SKB_GSO_TCPV6)) {
+-			__be16 csum = cpu_to_be16(skb_shinfo(skb)->gso_size);
+-
+-			tcp_hdr(skb)->check = (__force __sum16)csum;
+-			msg0 |= FIELD_PREP(QDMA_ETH_TXMSG_TSO_MASK, 1);
+-		}
+-	}
+-
+-	fport = port->id == 4 ? FE_PSE_PORT_GDM4 : port->id;
+-	msg1 = FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
+-	       FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
+-
+-	q = &qdma->q_tx[qid];
+-	if (WARN_ON_ONCE(!q->ndesc))
+-		goto error;
+-
+-	spin_lock_bh(&q->lock);
+-
+-	txq = netdev_get_tx_queue(dev, qid);
+-	if (q->queued + nr_frags > q->ndesc) {
+-		/* not enough space in the queue */
+-		netif_tx_stop_queue(txq);
+-		spin_unlock_bh(&q->lock);
+-		return NETDEV_TX_BUSY;
+-	}
+-
+-	index = q->head;
+-	for (i = 0; i < nr_frags; i++) {
+-		struct airoha_qdma_desc *desc = &q->desc[index];
+-		struct airoha_queue_entry *e = &q->entry[index];
+-		skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+-		dma_addr_t addr;
+-		u32 val;
+-
+-		addr = dma_map_single(dev->dev.parent, data, len,
+-				      DMA_TO_DEVICE);
+-		if (unlikely(dma_mapping_error(dev->dev.parent, addr)))
+-			goto error_unmap;
+-
+-		index = (index + 1) % q->ndesc;
+-
+-		val = FIELD_PREP(QDMA_DESC_LEN_MASK, len);
+-		if (i < nr_frags - 1)
+-			val |= FIELD_PREP(QDMA_DESC_MORE_MASK, 1);
+-		WRITE_ONCE(desc->ctrl, cpu_to_le32(val));
+-		WRITE_ONCE(desc->addr, cpu_to_le32(addr));
+-		val = FIELD_PREP(QDMA_DESC_NEXT_ID_MASK, index);
+-		WRITE_ONCE(desc->data, cpu_to_le32(val));
+-		WRITE_ONCE(desc->msg0, cpu_to_le32(msg0));
+-		WRITE_ONCE(desc->msg1, cpu_to_le32(msg1));
+-		WRITE_ONCE(desc->msg2, cpu_to_le32(0xffff));
+-
+-		e->skb = i ? NULL : skb;
+-		e->dma_addr = addr;
+-		e->dma_len = len;
+-
+-		data = skb_frag_address(frag);
+-		len = skb_frag_size(frag);
+-	}
+-
+-	q->head = index;
+-	q->queued += i;
+-
+-	skb_tx_timestamp(skb);
+-	netdev_tx_sent_queue(txq, skb->len);
+-
+-	if (netif_xmit_stopped(txq) || !netdev_xmit_more())
+-		airoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid),
+-				TX_RING_CPU_IDX_MASK,
+-				FIELD_PREP(TX_RING_CPU_IDX_MASK, q->head));
+-
+-	if (q->ndesc - q->queued < q->free_thr)
+-		netif_tx_stop_queue(txq);
+-
+-	spin_unlock_bh(&q->lock);
+-
+-	return NETDEV_TX_OK;
+-
+-error_unmap:
+-	for (i--; i >= 0; i--) {
+-		index = (q->head + i) % q->ndesc;
+-		dma_unmap_single(dev->dev.parent, q->entry[index].dma_addr,
+-				 q->entry[index].dma_len, DMA_TO_DEVICE);
+-	}
+-
+-	spin_unlock_bh(&q->lock);
+-error:
+-	dev_kfree_skb_any(skb);
+-	dev->stats.tx_dropped++;
+-
+-	return NETDEV_TX_OK;
+-}
+-
+-static void airoha_ethtool_get_drvinfo(struct net_device *dev,
+-				       struct ethtool_drvinfo *info)
+-{
+-	struct airoha_gdm_port *port = netdev_priv(dev);
+-	struct airoha_eth *eth = port->qdma->eth;
+-
+-	strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
+-	strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
+-}
+-
+-static void airoha_ethtool_get_mac_stats(struct net_device *dev,
+-					 struct ethtool_eth_mac_stats *stats)
+-{
+-	struct airoha_gdm_port *port = netdev_priv(dev);
+-	unsigned int start;
+-
+-	airoha_update_hw_stats(port);
+-	do {
+-		start = u64_stats_fetch_begin(&port->stats.syncp);
+-		stats->MulticastFramesXmittedOK = port->stats.tx_multicast;
+-		stats->BroadcastFramesXmittedOK = port->stats.tx_broadcast;
+-		stats->BroadcastFramesReceivedOK = port->stats.rx_broadcast;
+-	} while (u64_stats_fetch_retry(&port->stats.syncp, start));
+-}
+-
+-static const struct ethtool_rmon_hist_range airoha_ethtool_rmon_ranges[] = {
+-	{    0,    64 },
+-	{   65,   127 },
+-	{  128,   255 },
+-	{  256,   511 },
+-	{  512,  1023 },
+-	{ 1024,  1518 },
+-	{ 1519, 10239 },
+-	{},
+-};
+-
+-static void
+-airoha_ethtool_get_rmon_stats(struct net_device *dev,
+-			      struct ethtool_rmon_stats *stats,
+-			      const struct ethtool_rmon_hist_range **ranges)
+-{
+-	struct airoha_gdm_port *port = netdev_priv(dev);
+-	struct airoha_hw_stats *hw_stats = &port->stats;
+-	unsigned int start;
+-
+-	BUILD_BUG_ON(ARRAY_SIZE(airoha_ethtool_rmon_ranges) !=
+-		     ARRAY_SIZE(hw_stats->tx_len) + 1);
+-	BUILD_BUG_ON(ARRAY_SIZE(airoha_ethtool_rmon_ranges) !=
+-		     ARRAY_SIZE(hw_stats->rx_len) + 1);
+-
+-	*ranges = airoha_ethtool_rmon_ranges;
+-	airoha_update_hw_stats(port);
+-	do {
+-		int i;
+-
+-		start = u64_stats_fetch_begin(&port->stats.syncp);
+-		stats->fragments = hw_stats->rx_fragment;
+-		stats->jabbers = hw_stats->rx_jabber;
+-		for (i = 0; i < ARRAY_SIZE(airoha_ethtool_rmon_ranges) - 1;
+-		     i++) {
+-			stats->hist[i] = hw_stats->rx_len[i];
+-			stats->hist_tx[i] = hw_stats->tx_len[i];
+-		}
+-	} while (u64_stats_fetch_retry(&port->stats.syncp, start));
+-}
+-
+-static int airoha_qdma_set_chan_tx_sched(struct airoha_gdm_port *port,
+-					 int channel, enum tx_sched_mode mode,
+-					 const u16 *weights, u8 n_weights)
+-{
+-	int i;
+-
+-	for (i = 0; i < AIROHA_NUM_TX_RING; i++)
+-		airoha_qdma_clear(port->qdma, REG_QUEUE_CLOSE_CFG(channel),
+-				  TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i));
+-
+-	for (i = 0; i < n_weights; i++) {
+-		u32 status;
+-		int err;
+-
+-		airoha_qdma_wr(port->qdma, REG_TXWRR_WEIGHT_CFG,
+-			       TWRR_RW_CMD_MASK |
+-			       FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) |
+-			       FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) |
+-			       FIELD_PREP(TWRR_VALUE_MASK, weights[i]));
+-		err = read_poll_timeout(airoha_qdma_rr, status,
+-					status & TWRR_RW_CMD_DONE,
+-					USEC_PER_MSEC, 10 * USEC_PER_MSEC,
+-					true, port->qdma,
+-					REG_TXWRR_WEIGHT_CFG);
+-		if (err)
+-			return err;
+-	}
+-
+-	airoha_qdma_rmw(port->qdma, REG_CHAN_QOS_MODE(channel >> 3),
+-			CHAN_QOS_MODE_MASK(channel),
+-			mode << __ffs(CHAN_QOS_MODE_MASK(channel)));
+-
+-	return 0;
+-}
+-
+-static int airoha_qdma_set_tx_prio_sched(struct airoha_gdm_port *port,
+-					 int channel)
+-{
+-	static const u16 w[AIROHA_NUM_QOS_QUEUES] = {};
+-
+-	return airoha_qdma_set_chan_tx_sched(port, channel, TC_SCH_SP, w,
+-					     ARRAY_SIZE(w));
+-}
+-
+-static int airoha_qdma_set_tx_ets_sched(struct airoha_gdm_port *port,
+-					int channel,
+-					struct tc_ets_qopt_offload *opt)
+-{
+-	struct tc_ets_qopt_offload_replace_params *p = &opt->replace_params;
+-	enum tx_sched_mode mode = TC_SCH_SP;
+-	u16 w[AIROHA_NUM_QOS_QUEUES] = {};
+-	int i, nstrict = 0, nwrr, qidx;
+-
+-	if (p->bands > AIROHA_NUM_QOS_QUEUES)
+-		return -EINVAL;
+-
+-	for (i = 0; i < p->bands; i++) {
+-		if (!p->quanta[i])
+-			nstrict++;
+-	}
+-
+-	/* this configuration is not supported by the hw */
+-	if (nstrict == AIROHA_NUM_QOS_QUEUES - 1)
+-		return -EINVAL;
+-
+-	/* EN7581 SoC supports fixed QoS band priority where WRR queues have
+-	 * lowest priorities with respect to SP ones.
+-	 * e.g: WRR0, WRR1, .., WRRm, SP0, SP1, .., SPn
+-	 */
+-	nwrr = p->bands - nstrict;
+-	qidx = nstrict && nwrr ? nstrict : 0;
+-	for (i = 1; i <= p->bands; i++) {
+-		if (p->priomap[i % AIROHA_NUM_QOS_QUEUES] != qidx)
+-			return -EINVAL;
+-
+-		qidx = i == nwrr ? 0 : qidx + 1;
+-	}
+-
+-	for (i = 0; i < nwrr; i++)
+-		w[i] = p->weights[nstrict + i];
+-
+-	if (!nstrict)
+-		mode = TC_SCH_WRR8;
+-	else if (nstrict < AIROHA_NUM_QOS_QUEUES - 1)
+-		mode = nstrict + 1;
+-
+-	return airoha_qdma_set_chan_tx_sched(port, channel, mode, w,
+-					     ARRAY_SIZE(w));
+-}
+-
+-static int airoha_qdma_get_tx_ets_stats(struct airoha_gdm_port *port,
+-					int channel,
+-					struct tc_ets_qopt_offload *opt)
+-{
+-	u64 cpu_tx_packets = airoha_qdma_rr(port->qdma,
+-					    REG_CNTR_VAL(channel << 1));
+-	u64 fwd_tx_packets = airoha_qdma_rr(port->qdma,
+-					    REG_CNTR_VAL((channel << 1) + 1));
+-	u64 tx_packets = (cpu_tx_packets - port->cpu_tx_packets) +
+-			 (fwd_tx_packets - port->fwd_tx_packets);
+-	_bstats_update(opt->stats.bstats, 0, tx_packets);
+-
+-	port->cpu_tx_packets = cpu_tx_packets;
+-	port->fwd_tx_packets = fwd_tx_packets;
+-
+-	return 0;
+-}
+-
+-static int airoha_tc_setup_qdisc_ets(struct airoha_gdm_port *port,
+-				     struct tc_ets_qopt_offload *opt)
+-{
+-	int channel = TC_H_MAJ(opt->handle) >> 16;
+-
+-	if (opt->parent == TC_H_ROOT)
+-		return -EINVAL;
+-
+-	switch (opt->command) {
+-	case TC_ETS_REPLACE:
+-		return airoha_qdma_set_tx_ets_sched(port, channel, opt);
+-	case TC_ETS_DESTROY:
+-		/* PRIO is default qdisc scheduler */
+-		return airoha_qdma_set_tx_prio_sched(port, channel);
+-	case TC_ETS_STATS:
+-		return airoha_qdma_get_tx_ets_stats(port, channel, opt);
+-	default:
+-		return -EOPNOTSUPP;
+-	}
+-}
+-
+-static int airoha_qdma_get_trtcm_param(struct airoha_qdma *qdma, int channel,
+-				       u32 addr, enum trtcm_param_type param,
+-				       enum trtcm_mode_type mode,
+-				       u32 *val_low, u32 *val_high)
+-{
+-	u32 idx = QDMA_METER_IDX(channel), group = QDMA_METER_GROUP(channel);
+-	u32 val, config = FIELD_PREP(TRTCM_PARAM_TYPE_MASK, param) |
+-			  FIELD_PREP(TRTCM_METER_GROUP_MASK, group) |
+-			  FIELD_PREP(TRTCM_PARAM_INDEX_MASK, idx) |
+-			  FIELD_PREP(TRTCM_PARAM_RATE_TYPE_MASK, mode);
+-
+-	airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config);
+-	if (read_poll_timeout(airoha_qdma_rr, val,
+-			      val & TRTCM_PARAM_RW_DONE_MASK,
+-			      USEC_PER_MSEC, 10 * USEC_PER_MSEC, true,
+-			      qdma, REG_TRTCM_CFG_PARAM(addr)))
+-		return -ETIMEDOUT;
+-
+-	*val_low = airoha_qdma_rr(qdma, REG_TRTCM_DATA_LOW(addr));
+-	if (val_high)
+-		*val_high = airoha_qdma_rr(qdma, REG_TRTCM_DATA_HIGH(addr));
+-
+-	return 0;
+-}
+-
+-static int airoha_qdma_set_trtcm_param(struct airoha_qdma *qdma, int channel,
+-				       u32 addr, enum trtcm_param_type param,
+-				       enum trtcm_mode_type mode, u32 val)
+-{
+-	u32 idx = QDMA_METER_IDX(channel), group = QDMA_METER_GROUP(channel);
+-	u32 config = TRTCM_PARAM_RW_MASK |
+-		     FIELD_PREP(TRTCM_PARAM_TYPE_MASK, param) |
+-		     FIELD_PREP(TRTCM_METER_GROUP_MASK, group) |
+-		     FIELD_PREP(TRTCM_PARAM_INDEX_MASK, idx) |
+-		     FIELD_PREP(TRTCM_PARAM_RATE_TYPE_MASK, mode);
+-
+-	airoha_qdma_wr(qdma, REG_TRTCM_DATA_LOW(addr), val);
+-	airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config);
+-
+-	return read_poll_timeout(airoha_qdma_rr, val,
+-				 val & TRTCM_PARAM_RW_DONE_MASK,
+-				 USEC_PER_MSEC, 10 * USEC_PER_MSEC, true,
+-				 qdma, REG_TRTCM_CFG_PARAM(addr));
+-}
+-
+-static int airoha_qdma_set_trtcm_config(struct airoha_qdma *qdma, int channel,
+-					u32 addr, enum trtcm_mode_type mode,
+-					bool enable, u32 enable_mask)
+-{
+-	u32 val;
+-
+-	if (airoha_qdma_get_trtcm_param(qdma, channel, addr, TRTCM_MISC_MODE,
+-					mode, &val, NULL))
+-		return -EINVAL;
+-
+-	val = enable ? val | enable_mask : val & ~enable_mask;
+-
+-	return airoha_qdma_set_trtcm_param(qdma, channel, addr, TRTCM_MISC_MODE,
+-					   mode, val);
+-}
+-
+-static int airoha_qdma_set_trtcm_token_bucket(struct airoha_qdma *qdma,
+-					      int channel, u32 addr,
+-					      enum trtcm_mode_type mode,
+-					      u32 rate_val, u32 bucket_size)
+-{
+-	u32 val, config, tick, unit, rate, rate_frac;
+-	int err;
+-
+-	if (airoha_qdma_get_trtcm_param(qdma, channel, addr, TRTCM_MISC_MODE,
+-					mode, &config, NULL))
+-		return -EINVAL;
+-
+-	val = airoha_qdma_rr(qdma, addr);
+-	tick = FIELD_GET(INGRESS_FAST_TICK_MASK, val);
+-	if (config & TRTCM_TICK_SEL)
+-		tick *= FIELD_GET(INGRESS_SLOW_TICK_RATIO_MASK, val);
+-	if (!tick)
+-		return -EINVAL;
+-
+-	unit = (config & TRTCM_PKT_MODE) ? 1000000 / tick : 8000 / tick;
+-	if (!unit)
+-		return -EINVAL;
+-
+-	rate = rate_val / unit;
+-	rate_frac = rate_val % unit;
+-	rate_frac = FIELD_PREP(TRTCM_TOKEN_RATE_MASK, rate_frac) / unit;
+-	rate = FIELD_PREP(TRTCM_TOKEN_RATE_MASK, rate) |
+-	       FIELD_PREP(TRTCM_TOKEN_RATE_FRACTION_MASK, rate_frac);
+-
+-	err = airoha_qdma_set_trtcm_param(qdma, channel, addr,
+-					  TRTCM_TOKEN_RATE_MODE, mode, rate);
+-	if (err)
+-		return err;
+-
+-	val = max_t(u32, bucket_size, MIN_TOKEN_SIZE);
+-	val = min_t(u32, __fls(val), MAX_TOKEN_SIZE_OFFSET);
+-
+-	return airoha_qdma_set_trtcm_param(qdma, channel, addr,
+-					   TRTCM_BUCKETSIZE_SHIFT_MODE,
+-					   mode, val);
+-}
+-
+-static int airoha_qdma_set_tx_rate_limit(struct airoha_gdm_port *port,
+-					 int channel, u32 rate,
+-					 u32 bucket_size)
+-{
+-	int i, err;
+-
+-	for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
+-		err = airoha_qdma_set_trtcm_config(port->qdma, channel,
+-						   REG_EGRESS_TRTCM_CFG, i,
+-						   !!rate, TRTCM_METER_MODE);
+-		if (err)
+-			return err;
+-
+-		err = airoha_qdma_set_trtcm_token_bucket(port->qdma, channel,
+-							 REG_EGRESS_TRTCM_CFG,
+-							 i, rate, bucket_size);
+-		if (err)
+-			return err;
+-	}
+-
+-	return 0;
+-}
+-
+-static int airoha_tc_htb_alloc_leaf_queue(struct airoha_gdm_port *port,
+-					  struct tc_htb_qopt_offload *opt)
+-{
+-	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
+-	u32 rate = div_u64(opt->rate, 1000) << 3; /* kbps */
+-	struct net_device *dev = port->dev;
+-	int num_tx_queues = dev->real_num_tx_queues;
+-	int err;
+-
+-	if (opt->parent_classid != TC_HTB_CLASSID_ROOT) {
+-		NL_SET_ERR_MSG_MOD(opt->extack, "invalid parent classid");
+-		return -EINVAL;
+-	}
+-
+-	err = airoha_qdma_set_tx_rate_limit(port, channel, rate, opt->quantum);
+-	if (err) {
+-		NL_SET_ERR_MSG_MOD(opt->extack,
+-				   "failed configuring htb offload");
+-		return err;
+-	}
+-
+-	if (opt->command == TC_HTB_NODE_MODIFY)
+-		return 0;
+-
+-	err = netif_set_real_num_tx_queues(dev, num_tx_queues + 1);
+-	if (err) {
+-		airoha_qdma_set_tx_rate_limit(port, channel, 0, opt->quantum);
+-		NL_SET_ERR_MSG_MOD(opt->extack,
+-				   "failed setting real_num_tx_queues");
+-		return err;
+-	}
+-
+-	set_bit(channel, port->qos_sq_bmap);
+-	opt->qid = AIROHA_NUM_TX_RING + channel;
+-
+-	return 0;
+-}
+-
+-static void airoha_tc_remove_htb_queue(struct airoha_gdm_port *port, int queue)
+-{
+-	struct net_device *dev = port->dev;
+-
+-	netif_set_real_num_tx_queues(dev, dev->real_num_tx_queues - 1);
+-	airoha_qdma_set_tx_rate_limit(port, queue + 1, 0, 0);
+-	clear_bit(queue, port->qos_sq_bmap);
+-}
+-
+-static int airoha_tc_htb_delete_leaf_queue(struct airoha_gdm_port *port,
+-					   struct tc_htb_qopt_offload *opt)
+-{
+-	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
+-
+-	if (!test_bit(channel, port->qos_sq_bmap)) {
+-		NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
+-		return -EINVAL;
+-	}
+-
+-	airoha_tc_remove_htb_queue(port, channel);
+-
+-	return 0;
+-}
+-
+-static int airoha_tc_htb_destroy(struct airoha_gdm_port *port)
+-{
+-	int q;
+-
+-	for_each_set_bit(q, port->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
+-		airoha_tc_remove_htb_queue(port, q);
+-
+-	return 0;
+-}
+-
+-static int airoha_tc_get_htb_get_leaf_queue(struct airoha_gdm_port *port,
+-					    struct tc_htb_qopt_offload *opt)
+-{
+-	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
+-
+-	if (!test_bit(channel, port->qos_sq_bmap)) {
+-		NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
+-		return -EINVAL;
+-	}
+-
+-	opt->qid = channel;
+-
+-	return 0;
+-}
+-
+-static int airoha_tc_setup_qdisc_htb(struct airoha_gdm_port *port,
+-				     struct tc_htb_qopt_offload *opt)
+-{
+-	switch (opt->command) {
+-	case TC_HTB_CREATE:
+-		break;
+-	case TC_HTB_DESTROY:
+-		return airoha_tc_htb_destroy(port);
+-	case TC_HTB_NODE_MODIFY:
+-	case TC_HTB_LEAF_ALLOC_QUEUE:
+-		return airoha_tc_htb_alloc_leaf_queue(port, opt);
+-	case TC_HTB_LEAF_DEL:
+-	case TC_HTB_LEAF_DEL_LAST:
+-	case TC_HTB_LEAF_DEL_LAST_FORCE:
+-		return airoha_tc_htb_delete_leaf_queue(port, opt);
+-	case TC_HTB_LEAF_QUERY_QUEUE:
+-		return airoha_tc_get_htb_get_leaf_queue(port, opt);
+-	default:
+-		return -EOPNOTSUPP;
+-	}
+-
+-	return 0;
+-}
+-
+-static int airoha_dev_tc_setup(struct net_device *dev, enum tc_setup_type type,
+-			       void *type_data)
+-{
+-	struct airoha_gdm_port *port = netdev_priv(dev);
+-
+-	switch (type) {
+-	case TC_SETUP_QDISC_ETS:
+-		return airoha_tc_setup_qdisc_ets(port, type_data);
+-	case TC_SETUP_QDISC_HTB:
+-		return airoha_tc_setup_qdisc_htb(port, type_data);
+-	default:
+-		return -EOPNOTSUPP;
+-	}
+-}
+-
+-static const struct net_device_ops airoha_netdev_ops = {
+-	.ndo_init		= airoha_dev_init,
+-	.ndo_open		= airoha_dev_open,
+-	.ndo_stop		= airoha_dev_stop,
+-	.ndo_select_queue	= airoha_dev_select_queue,
+-	.ndo_start_xmit		= airoha_dev_xmit,
+-	.ndo_get_stats64        = airoha_dev_get_stats64,
+-	.ndo_set_mac_address	= airoha_dev_set_macaddr,
+-	.ndo_setup_tc		= airoha_dev_tc_setup,
+-};
+-
+-static const struct ethtool_ops airoha_ethtool_ops = {
+-	.get_drvinfo		= airoha_ethtool_get_drvinfo,
+-	.get_eth_mac_stats      = airoha_ethtool_get_mac_stats,
+-	.get_rmon_stats		= airoha_ethtool_get_rmon_stats,
+-};
+-
+-static int airoha_alloc_gdm_port(struct airoha_eth *eth, struct device_node *np)
+-{
+-	const __be32 *id_ptr = of_get_property(np, "reg", NULL);
+-	struct airoha_gdm_port *port;
+-	struct airoha_qdma *qdma;
+-	struct net_device *dev;
+-	int err, index;
+-	u32 id;
+-
+-	if (!id_ptr) {
+-		dev_err(eth->dev, "missing gdm port id\n");
+-		return -EINVAL;
+-	}
+-
+-	id = be32_to_cpup(id_ptr);
+-	index = id - 1;
+-
+-	if (!id || id > ARRAY_SIZE(eth->ports)) {
+-		dev_err(eth->dev, "invalid gdm port id: %d\n", id);
+-		return -EINVAL;
+-	}
+-
+-	if (eth->ports[index]) {
+-		dev_err(eth->dev, "duplicate gdm port id: %d\n", id);
+-		return -EINVAL;
+-	}
+-
+-	dev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*port),
+-				      AIROHA_NUM_NETDEV_TX_RINGS,
+-				      AIROHA_NUM_RX_RING);
+-	if (!dev) {
+-		dev_err(eth->dev, "alloc_etherdev failed\n");
+-		return -ENOMEM;
+-	}
+-
+-	qdma = &eth->qdma[index % AIROHA_MAX_NUM_QDMA];
+-	dev->netdev_ops = &airoha_netdev_ops;
+-	dev->ethtool_ops = &airoha_ethtool_ops;
+-	dev->max_mtu = AIROHA_MAX_MTU;
+-	dev->watchdog_timeo = 5 * HZ;
+-	dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM |
+-			   NETIF_F_TSO6 | NETIF_F_IPV6_CSUM |
+-			   NETIF_F_SG | NETIF_F_TSO |
+-			   NETIF_F_HW_TC;
+-	dev->features |= dev->hw_features;
+-	dev->dev.of_node = np;
+-	dev->irq = qdma->irq;
+-	SET_NETDEV_DEV(dev, eth->dev);
+-
+-	/* reserve hw queues for HTB offloading */
+-	err = netif_set_real_num_tx_queues(dev, AIROHA_NUM_TX_RING);
+-	if (err)
+-		return err;
+-
+-	err = of_get_ethdev_address(np, dev);
+-	if (err) {
+-		if (err == -EPROBE_DEFER)
+-			return err;
+-
+-		eth_hw_addr_random(dev);
+-		dev_info(eth->dev, "generated random MAC address %pM\n",
+-			 dev->dev_addr);
+-	}
+-
+-	port = netdev_priv(dev);
+-	u64_stats_init(&port->stats.syncp);
+-	spin_lock_init(&port->stats.lock);
+-	port->qdma = qdma;
+-	port->dev = dev;
+-	port->id = id;
+-	eth->ports[index] = port;
+-
+-	return register_netdev(dev);
+-}
+-
+-static int airoha_probe(struct platform_device *pdev)
+-{
+-	struct device_node *np;
+-	struct airoha_eth *eth;
+-	int i, err;
+-
+-	eth = devm_kzalloc(&pdev->dev, sizeof(*eth), GFP_KERNEL);
+-	if (!eth)
+-		return -ENOMEM;
+-
+-	eth->dev = &pdev->dev;
+-
+-	err = dma_set_mask_and_coherent(eth->dev, DMA_BIT_MASK(32));
+-	if (err) {
+-		dev_err(eth->dev, "failed configuring DMA mask\n");
+-		return err;
+-	}
+-
+-	eth->fe_regs = devm_platform_ioremap_resource_byname(pdev, "fe");
+-	if (IS_ERR(eth->fe_regs))
+-		return dev_err_probe(eth->dev, PTR_ERR(eth->fe_regs),
+-				     "failed to iomap fe regs\n");
+-
+-	eth->rsts[0].id = "fe";
+-	eth->rsts[1].id = "pdma";
+-	eth->rsts[2].id = "qdma";
+-	err = devm_reset_control_bulk_get_exclusive(eth->dev,
+-						    ARRAY_SIZE(eth->rsts),
+-						    eth->rsts);
+-	if (err) {
+-		dev_err(eth->dev, "failed to get bulk reset lines\n");
+-		return err;
+-	}
+-
+-	eth->xsi_rsts[0].id = "xsi-mac";
+-	eth->xsi_rsts[1].id = "hsi0-mac";
+-	eth->xsi_rsts[2].id = "hsi1-mac";
+-	eth->xsi_rsts[3].id = "hsi-mac";
+-	eth->xsi_rsts[4].id = "xfp-mac";
+-	err = devm_reset_control_bulk_get_exclusive(eth->dev,
+-						    ARRAY_SIZE(eth->xsi_rsts),
+-						    eth->xsi_rsts);
+-	if (err) {
+-		dev_err(eth->dev, "failed to get bulk xsi reset lines\n");
+-		return err;
+-	}
+-
+-	eth->napi_dev = alloc_netdev_dummy(0);
+-	if (!eth->napi_dev)
+-		return -ENOMEM;
+-
+-	/* Enable threaded NAPI by default */
+-	eth->napi_dev->threaded = true;
+-	strscpy(eth->napi_dev->name, "qdma_eth", sizeof(eth->napi_dev->name));
+-	platform_set_drvdata(pdev, eth);
+-
+-	err = airoha_hw_init(pdev, eth);
+-	if (err)
+-		goto error_hw_cleanup;
+-
+-	for (i = 0; i < ARRAY_SIZE(eth->qdma); i++)
+-		airoha_qdma_start_napi(&eth->qdma[i]);
+-
+-	for_each_child_of_node(pdev->dev.of_node, np) {
+-		if (!of_device_is_compatible(np, "airoha,eth-mac"))
+-			continue;
+-
+-		if (!of_device_is_available(np))
+-			continue;
+-
+-		err = airoha_alloc_gdm_port(eth, np);
+-		if (err) {
+-			of_node_put(np);
+-			goto error_napi_stop;
+-		}
+-	}
+-
+-	return 0;
+-
+-error_napi_stop:
+-	for (i = 0; i < ARRAY_SIZE(eth->qdma); i++)
+-		airoha_qdma_stop_napi(&eth->qdma[i]);
+-error_hw_cleanup:
+-	for (i = 0; i < ARRAY_SIZE(eth->qdma); i++)
+-		airoha_hw_cleanup(&eth->qdma[i]);
+-
+-	for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+-		struct airoha_gdm_port *port = eth->ports[i];
+-
+-		if (port && port->dev->reg_state == NETREG_REGISTERED)
+-			unregister_netdev(port->dev);
+-	}
+-	free_netdev(eth->napi_dev);
+-	platform_set_drvdata(pdev, NULL);
+-
+-	return err;
+-}
+-
+-static void airoha_remove(struct platform_device *pdev)
+-{
+-	struct airoha_eth *eth = platform_get_drvdata(pdev);
+-	int i;
+-
+-	for (i = 0; i < ARRAY_SIZE(eth->qdma); i++) {
+-		airoha_qdma_stop_napi(&eth->qdma[i]);
+-		airoha_hw_cleanup(&eth->qdma[i]);
+-	}
+-
+-	for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+-		struct airoha_gdm_port *port = eth->ports[i];
+-
+-		if (!port)
+-			continue;
+-
+-		airoha_dev_stop(port->dev);
+-		unregister_netdev(port->dev);
+-	}
+-	free_netdev(eth->napi_dev);
+-
+-	platform_set_drvdata(pdev, NULL);
+-}
+-
+-static const struct of_device_id of_airoha_match[] = {
+-	{ .compatible = "airoha,en7581-eth" },
+-	{ /* sentinel */ }
+-};
+-MODULE_DEVICE_TABLE(of, of_airoha_match);
+-
+-static struct platform_driver airoha_driver = {
+-	.probe = airoha_probe,
+-	.remove_new = airoha_remove,
+-	.driver = {
+-		.name = KBUILD_MODNAME,
+-		.of_match_table = of_airoha_match,
+-	},
+-};
+-module_platform_driver(airoha_driver);
+-
+-MODULE_LICENSE("GPL");
+-MODULE_AUTHOR("Lorenzo Bianconi <[email protected]>");
+-MODULE_DESCRIPTION("Ethernet driver for Airoha SoC");

+ 538 - 0
target/linux/airoha/patches-6.6/048-02-v6.15-net-airoha-Move-definitions-in-airoha_eth.h.patch

@@ -0,0 +1,538 @@
+From b38f4ff0ceacd6ce8d333a8dc90f405a040968d3 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <[email protected]>
+Date: Fri, 28 Feb 2025 11:54:10 +0100
+Subject: [PATCH 02/15] net: airoha: Move definitions in airoha_eth.h
+
+Move common airoha_eth definitions in airoha_eth.h in order to reuse
+them for Packet Processor Engine (PPE) codebase.
+PPE module is used to enable support for flowtable hw offloading in
+airoha_eth driver.
+
+Signed-off-by: Lorenzo Bianconi <[email protected]>
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 240 +---------------------
+ drivers/net/ethernet/airoha/airoha_eth.h | 251 +++++++++++++++++++++++
+ 2 files changed, 252 insertions(+), 239 deletions(-)
+ create mode 100644 drivers/net/ethernet/airoha/airoha_eth.h
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -3,14 +3,9 @@
+  * Copyright (c) 2024 AIROHA Inc
+  * Author: Lorenzo Bianconi <[email protected]>
+  */
+-#include <linux/etherdevice.h>
+-#include <linux/iopoll.h>
+-#include <linux/kernel.h>
+-#include <linux/netdevice.h>
+ #include <linux/of.h>
+ #include <linux/of_net.h>
+ #include <linux/platform_device.h>
+-#include <linux/reset.h>
+ #include <linux/tcp.h>
+ #include <linux/u64_stats_sync.h>
+ #include <net/dsa.h>
+@@ -18,35 +13,7 @@
+ #include <net/pkt_cls.h>
+ #include <uapi/linux/ppp_defs.h>
+ 
+-#define AIROHA_MAX_NUM_GDM_PORTS	1
+-#define AIROHA_MAX_NUM_QDMA		2
+-#define AIROHA_MAX_NUM_RSTS		3
+-#define AIROHA_MAX_NUM_XSI_RSTS		5
+-#define AIROHA_MAX_MTU			2000
+-#define AIROHA_MAX_PACKET_SIZE		2048
+-#define AIROHA_NUM_QOS_CHANNELS		4
+-#define AIROHA_NUM_QOS_QUEUES		8
+-#define AIROHA_NUM_TX_RING		32
+-#define AIROHA_NUM_RX_RING		32
+-#define AIROHA_NUM_NETDEV_TX_RINGS	(AIROHA_NUM_TX_RING + \
+-					 AIROHA_NUM_QOS_CHANNELS)
+-#define AIROHA_FE_MC_MAX_VLAN_TABLE	64
+-#define AIROHA_FE_MC_MAX_VLAN_PORT	16
+-#define AIROHA_NUM_TX_IRQ		2
+-#define HW_DSCP_NUM			2048
+-#define IRQ_QUEUE_LEN(_n)		((_n) ? 1024 : 2048)
+-#define TX_DSCP_NUM			1024
+-#define RX_DSCP_NUM(_n)			\
+-	((_n) ==  2 ? 128 :		\
+-	 (_n) == 11 ? 128 :		\
+-	 (_n) == 15 ? 128 :		\
+-	 (_n) ==  0 ? 1024 : 16)
+-
+-#define PSE_RSV_PAGES			128
+-#define PSE_QUEUE_RSV_PAGES		64
+-
+-#define QDMA_METER_IDX(_n)		((_n) & 0xff)
+-#define QDMA_METER_GROUP(_n)		(((_n) >> 8) & 0x3)
++#include "airoha_eth.h"
+ 
+ /* FE */
+ #define PSE_BASE			0x0100
+@@ -706,211 +673,6 @@ struct airoha_qdma_fwd_desc {
+ 	__le32 rsv1;
+ };
+ 
+-enum {
+-	QDMA_INT_REG_IDX0,
+-	QDMA_INT_REG_IDX1,
+-	QDMA_INT_REG_IDX2,
+-	QDMA_INT_REG_IDX3,
+-	QDMA_INT_REG_IDX4,
+-	QDMA_INT_REG_MAX
+-};
+-
+-enum {
+-	XSI_PCIE0_PORT,
+-	XSI_PCIE1_PORT,
+-	XSI_USB_PORT,
+-	XSI_AE_PORT,
+-	XSI_ETH_PORT,
+-};
+-
+-enum {
+-	XSI_PCIE0_VIP_PORT_MASK	= BIT(22),
+-	XSI_PCIE1_VIP_PORT_MASK	= BIT(23),
+-	XSI_USB_VIP_PORT_MASK	= BIT(25),
+-	XSI_ETH_VIP_PORT_MASK	= BIT(24),
+-};
+-
+-enum {
+-	DEV_STATE_INITIALIZED,
+-};
+-
+-enum {
+-	CDM_CRSN_QSEL_Q1 = 1,
+-	CDM_CRSN_QSEL_Q5 = 5,
+-	CDM_CRSN_QSEL_Q6 = 6,
+-	CDM_CRSN_QSEL_Q15 = 15,
+-};
+-
+-enum {
+-	CRSN_08 = 0x8,
+-	CRSN_21 = 0x15, /* KA */
+-	CRSN_22 = 0x16, /* hit bind and force route to CPU */
+-	CRSN_24 = 0x18,
+-	CRSN_25 = 0x19,
+-};
+-
+-enum {
+-	FE_PSE_PORT_CDM1,
+-	FE_PSE_PORT_GDM1,
+-	FE_PSE_PORT_GDM2,
+-	FE_PSE_PORT_GDM3,
+-	FE_PSE_PORT_PPE1,
+-	FE_PSE_PORT_CDM2,
+-	FE_PSE_PORT_CDM3,
+-	FE_PSE_PORT_CDM4,
+-	FE_PSE_PORT_PPE2,
+-	FE_PSE_PORT_GDM4,
+-	FE_PSE_PORT_CDM5,
+-	FE_PSE_PORT_DROP = 0xf,
+-};
+-
+-enum tx_sched_mode {
+-	TC_SCH_WRR8,
+-	TC_SCH_SP,
+-	TC_SCH_WRR7,
+-	TC_SCH_WRR6,
+-	TC_SCH_WRR5,
+-	TC_SCH_WRR4,
+-	TC_SCH_WRR3,
+-	TC_SCH_WRR2,
+-};
+-
+-enum trtcm_param_type {
+-	TRTCM_MISC_MODE, /* meter_en, pps_mode, tick_sel */
+-	TRTCM_TOKEN_RATE_MODE,
+-	TRTCM_BUCKETSIZE_SHIFT_MODE,
+-	TRTCM_BUCKET_COUNTER_MODE,
+-};
+-
+-enum trtcm_mode_type {
+-	TRTCM_COMMIT_MODE,
+-	TRTCM_PEAK_MODE,
+-};
+-
+-enum trtcm_param {
+-	TRTCM_TICK_SEL = BIT(0),
+-	TRTCM_PKT_MODE = BIT(1),
+-	TRTCM_METER_MODE = BIT(2),
+-};
+-
+-#define MIN_TOKEN_SIZE				4096
+-#define MAX_TOKEN_SIZE_OFFSET			17
+-#define TRTCM_TOKEN_RATE_MASK			GENMASK(23, 6)
+-#define TRTCM_TOKEN_RATE_FRACTION_MASK		GENMASK(5, 0)
+-
+-struct airoha_queue_entry {
+-	union {
+-		void *buf;
+-		struct sk_buff *skb;
+-	};
+-	dma_addr_t dma_addr;
+-	u16 dma_len;
+-};
+-
+-struct airoha_queue {
+-	struct airoha_qdma *qdma;
+-
+-	/* protect concurrent queue accesses */
+-	spinlock_t lock;
+-	struct airoha_queue_entry *entry;
+-	struct airoha_qdma_desc *desc;
+-	u16 head;
+-	u16 tail;
+-
+-	int queued;
+-	int ndesc;
+-	int free_thr;
+-	int buf_size;
+-
+-	struct napi_struct napi;
+-	struct page_pool *page_pool;
+-};
+-
+-struct airoha_tx_irq_queue {
+-	struct airoha_qdma *qdma;
+-
+-	struct napi_struct napi;
+-
+-	int size;
+-	u32 *q;
+-};
+-
+-struct airoha_hw_stats {
+-	/* protect concurrent hw_stats accesses */
+-	spinlock_t lock;
+-	struct u64_stats_sync syncp;
+-
+-	/* get_stats64 */
+-	u64 rx_ok_pkts;
+-	u64 tx_ok_pkts;
+-	u64 rx_ok_bytes;
+-	u64 tx_ok_bytes;
+-	u64 rx_multicast;
+-	u64 rx_errors;
+-	u64 rx_drops;
+-	u64 tx_drops;
+-	u64 rx_crc_error;
+-	u64 rx_over_errors;
+-	/* ethtool stats */
+-	u64 tx_broadcast;
+-	u64 tx_multicast;
+-	u64 tx_len[7];
+-	u64 rx_broadcast;
+-	u64 rx_fragment;
+-	u64 rx_jabber;
+-	u64 rx_len[7];
+-};
+-
+-struct airoha_qdma {
+-	struct airoha_eth *eth;
+-	void __iomem *regs;
+-
+-	/* protect concurrent irqmask accesses */
+-	spinlock_t irq_lock;
+-	u32 irqmask[QDMA_INT_REG_MAX];
+-	int irq;
+-
+-	struct airoha_tx_irq_queue q_tx_irq[AIROHA_NUM_TX_IRQ];
+-
+-	struct airoha_queue q_tx[AIROHA_NUM_TX_RING];
+-	struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
+-
+-	/* descriptor and packet buffers for qdma hw forward */
+-	struct {
+-		void *desc;
+-		void *q;
+-	} hfwd;
+-};
+-
+-struct airoha_gdm_port {
+-	struct airoha_qdma *qdma;
+-	struct net_device *dev;
+-	int id;
+-
+-	struct airoha_hw_stats stats;
+-
+-	DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
+-
+-	/* qos stats counters */
+-	u64 cpu_tx_packets;
+-	u64 fwd_tx_packets;
+-};
+-
+-struct airoha_eth {
+-	struct device *dev;
+-
+-	unsigned long state;
+-	void __iomem *fe_regs;
+-
+-	struct reset_control_bulk_data rsts[AIROHA_MAX_NUM_RSTS];
+-	struct reset_control_bulk_data xsi_rsts[AIROHA_MAX_NUM_XSI_RSTS];
+-
+-	struct net_device *napi_dev;
+-
+-	struct airoha_qdma qdma[AIROHA_MAX_NUM_QDMA];
+-	struct airoha_gdm_port *ports[AIROHA_MAX_NUM_GDM_PORTS];
+-};
+-
+ static u32 airoha_rr(void __iomem *base, u32 offset)
+ {
+ 	return readl(base + offset);
+--- /dev/null
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -0,0 +1,251 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/*
++ * Copyright (c) 2024 AIROHA Inc
++ * Author: Lorenzo Bianconi <[email protected]>
++ */
++
++#ifndef AIROHA_ETH_H
++#define AIROHA_ETH_H
++
++#include <linux/etherdevice.h>
++#include <linux/iopoll.h>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/reset.h>
++
++#define AIROHA_MAX_NUM_GDM_PORTS	1
++#define AIROHA_MAX_NUM_QDMA		2
++#define AIROHA_MAX_NUM_RSTS		3
++#define AIROHA_MAX_NUM_XSI_RSTS		5
++#define AIROHA_MAX_MTU			2000
++#define AIROHA_MAX_PACKET_SIZE		2048
++#define AIROHA_NUM_QOS_CHANNELS		4
++#define AIROHA_NUM_QOS_QUEUES		8
++#define AIROHA_NUM_TX_RING		32
++#define AIROHA_NUM_RX_RING		32
++#define AIROHA_NUM_NETDEV_TX_RINGS	(AIROHA_NUM_TX_RING + \
++					 AIROHA_NUM_QOS_CHANNELS)
++#define AIROHA_FE_MC_MAX_VLAN_TABLE	64
++#define AIROHA_FE_MC_MAX_VLAN_PORT	16
++#define AIROHA_NUM_TX_IRQ		2
++#define HW_DSCP_NUM			2048
++#define IRQ_QUEUE_LEN(_n)		((_n) ? 1024 : 2048)
++#define TX_DSCP_NUM			1024
++#define RX_DSCP_NUM(_n)			\
++	((_n) ==  2 ? 128 :		\
++	 (_n) == 11 ? 128 :		\
++	 (_n) == 15 ? 128 :		\
++	 (_n) ==  0 ? 1024 : 16)
++
++#define PSE_RSV_PAGES			128
++#define PSE_QUEUE_RSV_PAGES		64
++
++#define QDMA_METER_IDX(_n)		((_n) & 0xff)
++#define QDMA_METER_GROUP(_n)		(((_n) >> 8) & 0x3)
++
++enum {
++	QDMA_INT_REG_IDX0,
++	QDMA_INT_REG_IDX1,
++	QDMA_INT_REG_IDX2,
++	QDMA_INT_REG_IDX3,
++	QDMA_INT_REG_IDX4,
++	QDMA_INT_REG_MAX
++};
++
++enum {
++	XSI_PCIE0_PORT,
++	XSI_PCIE1_PORT,
++	XSI_USB_PORT,
++	XSI_AE_PORT,
++	XSI_ETH_PORT,
++};
++
++enum {
++	XSI_PCIE0_VIP_PORT_MASK	= BIT(22),
++	XSI_PCIE1_VIP_PORT_MASK	= BIT(23),
++	XSI_USB_VIP_PORT_MASK	= BIT(25),
++	XSI_ETH_VIP_PORT_MASK	= BIT(24),
++};
++
++enum {
++	DEV_STATE_INITIALIZED,
++};
++
++enum {
++	CDM_CRSN_QSEL_Q1 = 1,
++	CDM_CRSN_QSEL_Q5 = 5,
++	CDM_CRSN_QSEL_Q6 = 6,
++	CDM_CRSN_QSEL_Q15 = 15,
++};
++
++enum {
++	CRSN_08 = 0x8,
++	CRSN_21 = 0x15, /* KA */
++	CRSN_22 = 0x16, /* hit bind and force route to CPU */
++	CRSN_24 = 0x18,
++	CRSN_25 = 0x19,
++};
++
++enum {
++	FE_PSE_PORT_CDM1,
++	FE_PSE_PORT_GDM1,
++	FE_PSE_PORT_GDM2,
++	FE_PSE_PORT_GDM3,
++	FE_PSE_PORT_PPE1,
++	FE_PSE_PORT_CDM2,
++	FE_PSE_PORT_CDM3,
++	FE_PSE_PORT_CDM4,
++	FE_PSE_PORT_PPE2,
++	FE_PSE_PORT_GDM4,
++	FE_PSE_PORT_CDM5,
++	FE_PSE_PORT_DROP = 0xf,
++};
++
++enum tx_sched_mode {
++	TC_SCH_WRR8,
++	TC_SCH_SP,
++	TC_SCH_WRR7,
++	TC_SCH_WRR6,
++	TC_SCH_WRR5,
++	TC_SCH_WRR4,
++	TC_SCH_WRR3,
++	TC_SCH_WRR2,
++};
++
++enum trtcm_param_type {
++	TRTCM_MISC_MODE, /* meter_en, pps_mode, tick_sel */
++	TRTCM_TOKEN_RATE_MODE,
++	TRTCM_BUCKETSIZE_SHIFT_MODE,
++	TRTCM_BUCKET_COUNTER_MODE,
++};
++
++enum trtcm_mode_type {
++	TRTCM_COMMIT_MODE,
++	TRTCM_PEAK_MODE,
++};
++
++enum trtcm_param {
++	TRTCM_TICK_SEL = BIT(0),
++	TRTCM_PKT_MODE = BIT(1),
++	TRTCM_METER_MODE = BIT(2),
++};
++
++#define MIN_TOKEN_SIZE				4096
++#define MAX_TOKEN_SIZE_OFFSET			17
++#define TRTCM_TOKEN_RATE_MASK			GENMASK(23, 6)
++#define TRTCM_TOKEN_RATE_FRACTION_MASK		GENMASK(5, 0)
++
++struct airoha_queue_entry {
++	union {
++		void *buf;
++		struct sk_buff *skb;
++	};
++	dma_addr_t dma_addr;
++	u16 dma_len;
++};
++
++struct airoha_queue {
++	struct airoha_qdma *qdma;
++
++	/* protect concurrent queue accesses */
++	spinlock_t lock;
++	struct airoha_queue_entry *entry;
++	struct airoha_qdma_desc *desc;
++	u16 head;
++	u16 tail;
++
++	int queued;
++	int ndesc;
++	int free_thr;
++	int buf_size;
++
++	struct napi_struct napi;
++	struct page_pool *page_pool;
++};
++
++struct airoha_tx_irq_queue {
++	struct airoha_qdma *qdma;
++
++	struct napi_struct napi;
++
++	int size;
++	u32 *q;
++};
++
++struct airoha_hw_stats {
++	/* protect concurrent hw_stats accesses */
++	spinlock_t lock;
++	struct u64_stats_sync syncp;
++
++	/* get_stats64 */
++	u64 rx_ok_pkts;
++	u64 tx_ok_pkts;
++	u64 rx_ok_bytes;
++	u64 tx_ok_bytes;
++	u64 rx_multicast;
++	u64 rx_errors;
++	u64 rx_drops;
++	u64 tx_drops;
++	u64 rx_crc_error;
++	u64 rx_over_errors;
++	/* ethtool stats */
++	u64 tx_broadcast;
++	u64 tx_multicast;
++	u64 tx_len[7];
++	u64 rx_broadcast;
++	u64 rx_fragment;
++	u64 rx_jabber;
++	u64 rx_len[7];
++};
++
++struct airoha_qdma {
++	struct airoha_eth *eth;
++	void __iomem *regs;
++
++	/* protect concurrent irqmask accesses */
++	spinlock_t irq_lock;
++	u32 irqmask[QDMA_INT_REG_MAX];
++	int irq;
++
++	struct airoha_tx_irq_queue q_tx_irq[AIROHA_NUM_TX_IRQ];
++
++	struct airoha_queue q_tx[AIROHA_NUM_TX_RING];
++	struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
++
++	/* descriptor and packet buffers for qdma hw forward */
++	struct {
++		void *desc;
++		void *q;
++	} hfwd;
++};
++
++struct airoha_gdm_port {
++	struct airoha_qdma *qdma;
++	struct net_device *dev;
++	int id;
++
++	struct airoha_hw_stats stats;
++
++	DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
++
++	/* qos stats counters */
++	u64 cpu_tx_packets;
++	u64 fwd_tx_packets;
++};
++
++struct airoha_eth {
++	struct device *dev;
++
++	unsigned long state;
++	void __iomem *fe_regs;
++
++	struct reset_control_bulk_data rsts[AIROHA_MAX_NUM_RSTS];
++	struct reset_control_bulk_data xsi_rsts[AIROHA_MAX_NUM_XSI_RSTS];
++
++	struct net_device *napi_dev;
++
++	struct airoha_qdma qdma[AIROHA_MAX_NUM_QDMA];
++	struct airoha_gdm_port *ports[AIROHA_MAX_NUM_GDM_PORTS];
++};
++
++#endif /* AIROHA_ETH_H */

+ 101 - 0
target/linux/airoha/patches-6.6/048-03-v6.15-net-airoha-Move-reg-write-utility-routines-in-airoha.patch

@@ -0,0 +1,101 @@
+From e0758a8694fbaffdc72940774db295585e951119 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <[email protected]>
+Date: Fri, 28 Feb 2025 11:54:11 +0100
+Subject: [PATCH 03/15] net: airoha: Move reg/write utility routines in
+ airoha_eth.h
+
+This is a preliminary patch to introduce flowtable hw offloading
+support for airoha_eth driver.
+
+Signed-off-by: Lorenzo Bianconi <[email protected]>
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 28 +++---------------------
+ drivers/net/ethernet/airoha/airoha_eth.h | 26 ++++++++++++++++++++++
+ 2 files changed, 29 insertions(+), 25 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -673,17 +673,17 @@ struct airoha_qdma_fwd_desc {
+ 	__le32 rsv1;
+ };
+ 
+-static u32 airoha_rr(void __iomem *base, u32 offset)
++u32 airoha_rr(void __iomem *base, u32 offset)
+ {
+ 	return readl(base + offset);
+ }
+ 
+-static void airoha_wr(void __iomem *base, u32 offset, u32 val)
++void airoha_wr(void __iomem *base, u32 offset, u32 val)
+ {
+ 	writel(val, base + offset);
+ }
+ 
+-static u32 airoha_rmw(void __iomem *base, u32 offset, u32 mask, u32 val)
++u32 airoha_rmw(void __iomem *base, u32 offset, u32 mask, u32 val)
+ {
+ 	val |= (airoha_rr(base, offset) & ~mask);
+ 	airoha_wr(base, offset, val);
+@@ -691,28 +691,6 @@ static u32 airoha_rmw(void __iomem *base
+ 	return val;
+ }
+ 
+-#define airoha_fe_rr(eth, offset)				\
+-	airoha_rr((eth)->fe_regs, (offset))
+-#define airoha_fe_wr(eth, offset, val)				\
+-	airoha_wr((eth)->fe_regs, (offset), (val))
+-#define airoha_fe_rmw(eth, offset, mask, val)			\
+-	airoha_rmw((eth)->fe_regs, (offset), (mask), (val))
+-#define airoha_fe_set(eth, offset, val)				\
+-	airoha_rmw((eth)->fe_regs, (offset), 0, (val))
+-#define airoha_fe_clear(eth, offset, val)			\
+-	airoha_rmw((eth)->fe_regs, (offset), (val), 0)
+-
+-#define airoha_qdma_rr(qdma, offset)				\
+-	airoha_rr((qdma)->regs, (offset))
+-#define airoha_qdma_wr(qdma, offset, val)			\
+-	airoha_wr((qdma)->regs, (offset), (val))
+-#define airoha_qdma_rmw(qdma, offset, mask, val)		\
+-	airoha_rmw((qdma)->regs, (offset), (mask), (val))
+-#define airoha_qdma_set(qdma, offset, val)			\
+-	airoha_rmw((qdma)->regs, (offset), 0, (val))
+-#define airoha_qdma_clear(qdma, offset, val)			\
+-	airoha_rmw((qdma)->regs, (offset), (val), 0)
+-
+ static void airoha_qdma_set_irqmask(struct airoha_qdma *qdma, int index,
+ 				    u32 clear, u32 set)
+ {
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -248,4 +248,30 @@ struct airoha_eth {
+ 	struct airoha_gdm_port *ports[AIROHA_MAX_NUM_GDM_PORTS];
+ };
+ 
++u32 airoha_rr(void __iomem *base, u32 offset);
++void airoha_wr(void __iomem *base, u32 offset, u32 val);
++u32 airoha_rmw(void __iomem *base, u32 offset, u32 mask, u32 val);
++
++#define airoha_fe_rr(eth, offset)				\
++	airoha_rr((eth)->fe_regs, (offset))
++#define airoha_fe_wr(eth, offset, val)				\
++	airoha_wr((eth)->fe_regs, (offset), (val))
++#define airoha_fe_rmw(eth, offset, mask, val)			\
++	airoha_rmw((eth)->fe_regs, (offset), (mask), (val))
++#define airoha_fe_set(eth, offset, val)				\
++	airoha_rmw((eth)->fe_regs, (offset), 0, (val))
++#define airoha_fe_clear(eth, offset, val)			\
++	airoha_rmw((eth)->fe_regs, (offset), (val), 0)
++
++#define airoha_qdma_rr(qdma, offset)				\
++	airoha_rr((qdma)->regs, (offset))
++#define airoha_qdma_wr(qdma, offset, val)			\
++	airoha_wr((qdma)->regs, (offset), (val))
++#define airoha_qdma_rmw(qdma, offset, mask, val)		\
++	airoha_rmw((qdma)->regs, (offset), (mask), (val))
++#define airoha_qdma_set(qdma, offset, val)			\
++	airoha_rmw((qdma)->regs, (offset), 0, (val))
++#define airoha_qdma_clear(qdma, offset, val)			\
++	airoha_rmw((qdma)->regs, (offset), (val), 0)
++
+ #endif /* AIROHA_ETH_H */

+ 1361 - 0
target/linux/airoha/patches-6.6/048-04-v6.15-net-airoha-Move-register-definitions-in-airoha_regs..patch

@@ -0,0 +1,1361 @@
+From ec663d9a82bf4d16721f6b1fc29df4892ba6c088 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <[email protected]>
+Date: Fri, 28 Feb 2025 11:54:12 +0100
+Subject: [PATCH 04/15] net: airoha: Move register definitions in airoha_regs.h
+
+Move common airoha_eth register definitions in airoha_regs.h in order
+to reuse them for Packet Processor Engine (PPE) codebase.
+PPE module is used to enable support for flowtable hw offloading in
+airoha_eth driver.
+
+Signed-off-by: Lorenzo Bianconi <[email protected]>
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c  | 659 +--------------------
+ drivers/net/ethernet/airoha/airoha_regs.h | 670 ++++++++++++++++++++++
+ 2 files changed, 671 insertions(+), 658 deletions(-)
+ create mode 100644 drivers/net/ethernet/airoha/airoha_regs.h
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -13,666 +13,9 @@
+ #include <net/pkt_cls.h>
+ #include <uapi/linux/ppp_defs.h>
+ 
++#include "airoha_regs.h"
+ #include "airoha_eth.h"
+ 
+-/* FE */
+-#define PSE_BASE			0x0100
+-#define CSR_IFC_BASE			0x0200
+-#define CDM1_BASE			0x0400
+-#define GDM1_BASE			0x0500
+-#define PPE1_BASE			0x0c00
+-
+-#define CDM2_BASE			0x1400
+-#define GDM2_BASE			0x1500
+-
+-#define GDM3_BASE			0x1100
+-#define GDM4_BASE			0x2500
+-
+-#define GDM_BASE(_n)			\
+-	((_n) == 4 ? GDM4_BASE :	\
+-	 (_n) == 3 ? GDM3_BASE :	\
+-	 (_n) == 2 ? GDM2_BASE : GDM1_BASE)
+-
+-#define REG_FE_DMA_GLO_CFG		0x0000
+-#define FE_DMA_GLO_L2_SPACE_MASK	GENMASK(7, 4)
+-#define FE_DMA_GLO_PG_SZ_MASK		BIT(3)
+-
+-#define REG_FE_RST_GLO_CFG		0x0004
+-#define FE_RST_GDM4_MBI_ARB_MASK	BIT(3)
+-#define FE_RST_GDM3_MBI_ARB_MASK	BIT(2)
+-#define FE_RST_CORE_MASK		BIT(0)
+-
+-#define REG_FE_WAN_MAC_H		0x0030
+-#define REG_FE_LAN_MAC_H		0x0040
+-
+-#define REG_FE_MAC_LMIN(_n)		((_n) + 0x04)
+-#define REG_FE_MAC_LMAX(_n)		((_n) + 0x08)
+-
+-#define REG_FE_CDM1_OQ_MAP0		0x0050
+-#define REG_FE_CDM1_OQ_MAP1		0x0054
+-#define REG_FE_CDM1_OQ_MAP2		0x0058
+-#define REG_FE_CDM1_OQ_MAP3		0x005c
+-
+-#define REG_FE_PCE_CFG			0x0070
+-#define PCE_DPI_EN_MASK			BIT(2)
+-#define PCE_KA_EN_MASK			BIT(1)
+-#define PCE_MC_EN_MASK			BIT(0)
+-
+-#define REG_FE_PSE_QUEUE_CFG_WR		0x0080
+-#define PSE_CFG_PORT_ID_MASK		GENMASK(27, 24)
+-#define PSE_CFG_QUEUE_ID_MASK		GENMASK(20, 16)
+-#define PSE_CFG_WR_EN_MASK		BIT(8)
+-#define PSE_CFG_OQRSV_SEL_MASK		BIT(0)
+-
+-#define REG_FE_PSE_QUEUE_CFG_VAL	0x0084
+-#define PSE_CFG_OQ_RSV_MASK		GENMASK(13, 0)
+-
+-#define PSE_FQ_CFG			0x008c
+-#define PSE_FQ_LIMIT_MASK		GENMASK(14, 0)
+-
+-#define REG_FE_PSE_BUF_SET		0x0090
+-#define PSE_SHARE_USED_LTHD_MASK	GENMASK(31, 16)
+-#define PSE_ALLRSV_MASK			GENMASK(14, 0)
+-
+-#define REG_PSE_SHARE_USED_THD		0x0094
+-#define PSE_SHARE_USED_MTHD_MASK	GENMASK(31, 16)
+-#define PSE_SHARE_USED_HTHD_MASK	GENMASK(15, 0)
+-
+-#define REG_GDM_MISC_CFG		0x0148
+-#define GDM2_RDM_ACK_WAIT_PREF_MASK	BIT(9)
+-#define GDM2_CHN_VLD_MODE_MASK		BIT(5)
+-
+-#define REG_FE_CSR_IFC_CFG		CSR_IFC_BASE
+-#define FE_IFC_EN_MASK			BIT(0)
+-
+-#define REG_FE_VIP_PORT_EN		0x01f0
+-#define REG_FE_IFC_PORT_EN		0x01f4
+-
+-#define REG_PSE_IQ_REV1			(PSE_BASE + 0x08)
+-#define PSE_IQ_RES1_P2_MASK		GENMASK(23, 16)
+-
+-#define REG_PSE_IQ_REV2			(PSE_BASE + 0x0c)
+-#define PSE_IQ_RES2_P5_MASK		GENMASK(15, 8)
+-#define PSE_IQ_RES2_P4_MASK		GENMASK(7, 0)
+-
+-#define REG_FE_VIP_EN(_n)		(0x0300 + ((_n) << 3))
+-#define PATN_FCPU_EN_MASK		BIT(7)
+-#define PATN_SWP_EN_MASK		BIT(6)
+-#define PATN_DP_EN_MASK			BIT(5)
+-#define PATN_SP_EN_MASK			BIT(4)
+-#define PATN_TYPE_MASK			GENMASK(3, 1)
+-#define PATN_EN_MASK			BIT(0)
+-
+-#define REG_FE_VIP_PATN(_n)		(0x0304 + ((_n) << 3))
+-#define PATN_DP_MASK			GENMASK(31, 16)
+-#define PATN_SP_MASK			GENMASK(15, 0)
+-
+-#define REG_CDM1_VLAN_CTRL		CDM1_BASE
+-#define CDM1_VLAN_MASK			GENMASK(31, 16)
+-
+-#define REG_CDM1_FWD_CFG		(CDM1_BASE + 0x08)
+-#define CDM1_VIP_QSEL_MASK		GENMASK(24, 20)
+-
+-#define REG_CDM1_CRSN_QSEL(_n)		(CDM1_BASE + 0x10 + ((_n) << 2))
+-#define CDM1_CRSN_QSEL_REASON_MASK(_n)	\
+-	GENMASK(4 + (((_n) % 4) << 3),	(((_n) % 4) << 3))
+-
+-#define REG_CDM2_FWD_CFG		(CDM2_BASE + 0x08)
+-#define CDM2_OAM_QSEL_MASK		GENMASK(31, 27)
+-#define CDM2_VIP_QSEL_MASK		GENMASK(24, 20)
+-
+-#define REG_CDM2_CRSN_QSEL(_n)		(CDM2_BASE + 0x10 + ((_n) << 2))
+-#define CDM2_CRSN_QSEL_REASON_MASK(_n)	\
+-	GENMASK(4 + (((_n) % 4) << 3),	(((_n) % 4) << 3))
+-
+-#define REG_GDM_FWD_CFG(_n)		GDM_BASE(_n)
+-#define GDM_DROP_CRC_ERR		BIT(23)
+-#define GDM_IP4_CKSUM			BIT(22)
+-#define GDM_TCP_CKSUM			BIT(21)
+-#define GDM_UDP_CKSUM			BIT(20)
+-#define GDM_UCFQ_MASK			GENMASK(15, 12)
+-#define GDM_BCFQ_MASK			GENMASK(11, 8)
+-#define GDM_MCFQ_MASK			GENMASK(7, 4)
+-#define GDM_OCFQ_MASK			GENMASK(3, 0)
+-
+-#define REG_GDM_INGRESS_CFG(_n)		(GDM_BASE(_n) + 0x10)
+-#define GDM_INGRESS_FC_EN_MASK		BIT(1)
+-#define GDM_STAG_EN_MASK		BIT(0)
+-
+-#define REG_GDM_LEN_CFG(_n)		(GDM_BASE(_n) + 0x14)
+-#define GDM_SHORT_LEN_MASK		GENMASK(13, 0)
+-#define GDM_LONG_LEN_MASK		GENMASK(29, 16)
+-
+-#define REG_FE_CPORT_CFG		(GDM1_BASE + 0x40)
+-#define FE_CPORT_PAD			BIT(26)
+-#define FE_CPORT_PORT_XFC_MASK		BIT(25)
+-#define FE_CPORT_QUEUE_XFC_MASK		BIT(24)
+-
+-#define REG_FE_GDM_MIB_CLEAR(_n)	(GDM_BASE(_n) + 0xf0)
+-#define FE_GDM_MIB_RX_CLEAR_MASK	BIT(1)
+-#define FE_GDM_MIB_TX_CLEAR_MASK	BIT(0)
+-
+-#define REG_FE_GDM1_MIB_CFG		(GDM1_BASE + 0xf4)
+-#define FE_STRICT_RFC2819_MODE_MASK	BIT(31)
+-#define FE_GDM1_TX_MIB_SPLIT_EN_MASK	BIT(17)
+-#define FE_GDM1_RX_MIB_SPLIT_EN_MASK	BIT(16)
+-#define FE_TX_MIB_ID_MASK		GENMASK(15, 8)
+-#define FE_RX_MIB_ID_MASK		GENMASK(7, 0)
+-
+-#define REG_FE_GDM_TX_OK_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x104)
+-#define REG_FE_GDM_TX_OK_BYTE_CNT_L(_n)		(GDM_BASE(_n) + 0x10c)
+-#define REG_FE_GDM_TX_ETH_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x110)
+-#define REG_FE_GDM_TX_ETH_BYTE_CNT_L(_n)	(GDM_BASE(_n) + 0x114)
+-#define REG_FE_GDM_TX_ETH_DROP_CNT(_n)		(GDM_BASE(_n) + 0x118)
+-#define REG_FE_GDM_TX_ETH_BC_CNT(_n)		(GDM_BASE(_n) + 0x11c)
+-#define REG_FE_GDM_TX_ETH_MC_CNT(_n)		(GDM_BASE(_n) + 0x120)
+-#define REG_FE_GDM_TX_ETH_RUNT_CNT(_n)		(GDM_BASE(_n) + 0x124)
+-#define REG_FE_GDM_TX_ETH_LONG_CNT(_n)		(GDM_BASE(_n) + 0x128)
+-#define REG_FE_GDM_TX_ETH_E64_CNT_L(_n)		(GDM_BASE(_n) + 0x12c)
+-#define REG_FE_GDM_TX_ETH_L64_CNT_L(_n)		(GDM_BASE(_n) + 0x130)
+-#define REG_FE_GDM_TX_ETH_L127_CNT_L(_n)	(GDM_BASE(_n) + 0x134)
+-#define REG_FE_GDM_TX_ETH_L255_CNT_L(_n)	(GDM_BASE(_n) + 0x138)
+-#define REG_FE_GDM_TX_ETH_L511_CNT_L(_n)	(GDM_BASE(_n) + 0x13c)
+-#define REG_FE_GDM_TX_ETH_L1023_CNT_L(_n)	(GDM_BASE(_n) + 0x140)
+-
+-#define REG_FE_GDM_RX_OK_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x148)
+-#define REG_FE_GDM_RX_FC_DROP_CNT(_n)		(GDM_BASE(_n) + 0x14c)
+-#define REG_FE_GDM_RX_RC_DROP_CNT(_n)		(GDM_BASE(_n) + 0x150)
+-#define REG_FE_GDM_RX_OVERFLOW_DROP_CNT(_n)	(GDM_BASE(_n) + 0x154)
+-#define REG_FE_GDM_RX_ERROR_DROP_CNT(_n)	(GDM_BASE(_n) + 0x158)
+-#define REG_FE_GDM_RX_OK_BYTE_CNT_L(_n)		(GDM_BASE(_n) + 0x15c)
+-#define REG_FE_GDM_RX_ETH_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x160)
+-#define REG_FE_GDM_RX_ETH_BYTE_CNT_L(_n)	(GDM_BASE(_n) + 0x164)
+-#define REG_FE_GDM_RX_ETH_DROP_CNT(_n)		(GDM_BASE(_n) + 0x168)
+-#define REG_FE_GDM_RX_ETH_BC_CNT(_n)		(GDM_BASE(_n) + 0x16c)
+-#define REG_FE_GDM_RX_ETH_MC_CNT(_n)		(GDM_BASE(_n) + 0x170)
+-#define REG_FE_GDM_RX_ETH_CRC_ERR_CNT(_n)	(GDM_BASE(_n) + 0x174)
+-#define REG_FE_GDM_RX_ETH_FRAG_CNT(_n)		(GDM_BASE(_n) + 0x178)
+-#define REG_FE_GDM_RX_ETH_JABBER_CNT(_n)	(GDM_BASE(_n) + 0x17c)
+-#define REG_FE_GDM_RX_ETH_RUNT_CNT(_n)		(GDM_BASE(_n) + 0x180)
+-#define REG_FE_GDM_RX_ETH_LONG_CNT(_n)		(GDM_BASE(_n) + 0x184)
+-#define REG_FE_GDM_RX_ETH_E64_CNT_L(_n)		(GDM_BASE(_n) + 0x188)
+-#define REG_FE_GDM_RX_ETH_L64_CNT_L(_n)		(GDM_BASE(_n) + 0x18c)
+-#define REG_FE_GDM_RX_ETH_L127_CNT_L(_n)	(GDM_BASE(_n) + 0x190)
+-#define REG_FE_GDM_RX_ETH_L255_CNT_L(_n)	(GDM_BASE(_n) + 0x194)
+-#define REG_FE_GDM_RX_ETH_L511_CNT_L(_n)	(GDM_BASE(_n) + 0x198)
+-#define REG_FE_GDM_RX_ETH_L1023_CNT_L(_n)	(GDM_BASE(_n) + 0x19c)
+-
+-#define REG_PPE1_TB_HASH_CFG		(PPE1_BASE + 0x250)
+-#define PPE1_SRAM_TABLE_EN_MASK		BIT(0)
+-#define PPE1_SRAM_HASH1_EN_MASK		BIT(8)
+-#define PPE1_DRAM_TABLE_EN_MASK		BIT(16)
+-#define PPE1_DRAM_HASH1_EN_MASK		BIT(24)
+-
+-#define REG_FE_GDM_TX_OK_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x280)
+-#define REG_FE_GDM_TX_OK_BYTE_CNT_H(_n)		(GDM_BASE(_n) + 0x284)
+-#define REG_FE_GDM_TX_ETH_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x288)
+-#define REG_FE_GDM_TX_ETH_BYTE_CNT_H(_n)	(GDM_BASE(_n) + 0x28c)
+-
+-#define REG_FE_GDM_RX_OK_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x290)
+-#define REG_FE_GDM_RX_OK_BYTE_CNT_H(_n)		(GDM_BASE(_n) + 0x294)
+-#define REG_FE_GDM_RX_ETH_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x298)
+-#define REG_FE_GDM_RX_ETH_BYTE_CNT_H(_n)	(GDM_BASE(_n) + 0x29c)
+-#define REG_FE_GDM_TX_ETH_E64_CNT_H(_n)		(GDM_BASE(_n) + 0x2b8)
+-#define REG_FE_GDM_TX_ETH_L64_CNT_H(_n)		(GDM_BASE(_n) + 0x2bc)
+-#define REG_FE_GDM_TX_ETH_L127_CNT_H(_n)	(GDM_BASE(_n) + 0x2c0)
+-#define REG_FE_GDM_TX_ETH_L255_CNT_H(_n)	(GDM_BASE(_n) + 0x2c4)
+-#define REG_FE_GDM_TX_ETH_L511_CNT_H(_n)	(GDM_BASE(_n) + 0x2c8)
+-#define REG_FE_GDM_TX_ETH_L1023_CNT_H(_n)	(GDM_BASE(_n) + 0x2cc)
+-#define REG_FE_GDM_RX_ETH_E64_CNT_H(_n)		(GDM_BASE(_n) + 0x2e8)
+-#define REG_FE_GDM_RX_ETH_L64_CNT_H(_n)		(GDM_BASE(_n) + 0x2ec)
+-#define REG_FE_GDM_RX_ETH_L127_CNT_H(_n)	(GDM_BASE(_n) + 0x2f0)
+-#define REG_FE_GDM_RX_ETH_L255_CNT_H(_n)	(GDM_BASE(_n) + 0x2f4)
+-#define REG_FE_GDM_RX_ETH_L511_CNT_H(_n)	(GDM_BASE(_n) + 0x2f8)
+-#define REG_FE_GDM_RX_ETH_L1023_CNT_H(_n)	(GDM_BASE(_n) + 0x2fc)
+-
+-#define REG_GDM2_CHN_RLS		(GDM2_BASE + 0x20)
+-#define MBI_RX_AGE_SEL_MASK		GENMASK(26, 25)
+-#define MBI_TX_AGE_SEL_MASK		GENMASK(18, 17)
+-
+-#define REG_GDM3_FWD_CFG		GDM3_BASE
+-#define GDM3_PAD_EN_MASK		BIT(28)
+-
+-#define REG_GDM4_FWD_CFG		GDM4_BASE
+-#define GDM4_PAD_EN_MASK		BIT(28)
+-#define GDM4_SPORT_OFFSET0_MASK		GENMASK(11, 8)
+-
+-#define REG_GDM4_SRC_PORT_SET		(GDM4_BASE + 0x23c)
+-#define GDM4_SPORT_OFF2_MASK		GENMASK(19, 16)
+-#define GDM4_SPORT_OFF1_MASK		GENMASK(15, 12)
+-#define GDM4_SPORT_OFF0_MASK		GENMASK(11, 8)
+-
+-#define REG_IP_FRAG_FP			0x2010
+-#define IP_ASSEMBLE_PORT_MASK		GENMASK(24, 21)
+-#define IP_ASSEMBLE_NBQ_MASK		GENMASK(20, 16)
+-#define IP_FRAGMENT_PORT_MASK		GENMASK(8, 5)
+-#define IP_FRAGMENT_NBQ_MASK		GENMASK(4, 0)
+-
+-#define REG_MC_VLAN_EN			0x2100
+-#define MC_VLAN_EN_MASK			BIT(0)
+-
+-#define REG_MC_VLAN_CFG			0x2104
+-#define MC_VLAN_CFG_CMD_DONE_MASK	BIT(31)
+-#define MC_VLAN_CFG_TABLE_ID_MASK	GENMASK(21, 16)
+-#define MC_VLAN_CFG_PORT_ID_MASK	GENMASK(11, 8)
+-#define MC_VLAN_CFG_TABLE_SEL_MASK	BIT(4)
+-#define MC_VLAN_CFG_RW_MASK		BIT(0)
+-
+-#define REG_MC_VLAN_DATA		0x2108
+-
+-#define REG_CDM5_RX_OQ1_DROP_CNT	0x29d4
+-
+-/* QDMA */
+-#define REG_QDMA_GLOBAL_CFG			0x0004
+-#define GLOBAL_CFG_RX_2B_OFFSET_MASK		BIT(31)
+-#define GLOBAL_CFG_DMA_PREFERENCE_MASK		GENMASK(30, 29)
+-#define GLOBAL_CFG_CPU_TXR_RR_MASK		BIT(28)
+-#define GLOBAL_CFG_DSCP_BYTE_SWAP_MASK		BIT(27)
+-#define GLOBAL_CFG_PAYLOAD_BYTE_SWAP_MASK	BIT(26)
+-#define GLOBAL_CFG_MULTICAST_MODIFY_FP_MASK	BIT(25)
+-#define GLOBAL_CFG_OAM_MODIFY_MASK		BIT(24)
+-#define GLOBAL_CFG_RESET_MASK			BIT(23)
+-#define GLOBAL_CFG_RESET_DONE_MASK		BIT(22)
+-#define GLOBAL_CFG_MULTICAST_EN_MASK		BIT(21)
+-#define GLOBAL_CFG_IRQ1_EN_MASK			BIT(20)
+-#define GLOBAL_CFG_IRQ0_EN_MASK			BIT(19)
+-#define GLOBAL_CFG_LOOPCNT_EN_MASK		BIT(18)
+-#define GLOBAL_CFG_RD_BYPASS_WR_MASK		BIT(17)
+-#define GLOBAL_CFG_QDMA_LOOPBACK_MASK		BIT(16)
+-#define GLOBAL_CFG_LPBK_RXQ_SEL_MASK		GENMASK(13, 8)
+-#define GLOBAL_CFG_CHECK_DONE_MASK		BIT(7)
+-#define GLOBAL_CFG_TX_WB_DONE_MASK		BIT(6)
+-#define GLOBAL_CFG_MAX_ISSUE_NUM_MASK		GENMASK(5, 4)
+-#define GLOBAL_CFG_RX_DMA_BUSY_MASK		BIT(3)
+-#define GLOBAL_CFG_RX_DMA_EN_MASK		BIT(2)
+-#define GLOBAL_CFG_TX_DMA_BUSY_MASK		BIT(1)
+-#define GLOBAL_CFG_TX_DMA_EN_MASK		BIT(0)
+-
+-#define REG_FWD_DSCP_BASE			0x0010
+-#define REG_FWD_BUF_BASE			0x0014
+-
+-#define REG_HW_FWD_DSCP_CFG			0x0018
+-#define HW_FWD_DSCP_PAYLOAD_SIZE_MASK		GENMASK(29, 28)
+-#define HW_FWD_DSCP_SCATTER_LEN_MASK		GENMASK(17, 16)
+-#define HW_FWD_DSCP_MIN_SCATTER_LEN_MASK	GENMASK(15, 0)
+-
+-#define REG_INT_STATUS(_n)		\
+-	(((_n) == 4) ? 0x0730 :		\
+-	 ((_n) == 3) ? 0x0724 :		\
+-	 ((_n) == 2) ? 0x0720 :		\
+-	 ((_n) == 1) ? 0x0024 : 0x0020)
+-
+-#define REG_INT_ENABLE(_n)		\
+-	(((_n) == 4) ? 0x0750 :		\
+-	 ((_n) == 3) ? 0x0744 :		\
+-	 ((_n) == 2) ? 0x0740 :		\
+-	 ((_n) == 1) ? 0x002c : 0x0028)
+-
+-/* QDMA_CSR_INT_ENABLE1 */
+-#define RX15_COHERENT_INT_MASK		BIT(31)
+-#define RX14_COHERENT_INT_MASK		BIT(30)
+-#define RX13_COHERENT_INT_MASK		BIT(29)
+-#define RX12_COHERENT_INT_MASK		BIT(28)
+-#define RX11_COHERENT_INT_MASK		BIT(27)
+-#define RX10_COHERENT_INT_MASK		BIT(26)
+-#define RX9_COHERENT_INT_MASK		BIT(25)
+-#define RX8_COHERENT_INT_MASK		BIT(24)
+-#define RX7_COHERENT_INT_MASK		BIT(23)
+-#define RX6_COHERENT_INT_MASK		BIT(22)
+-#define RX5_COHERENT_INT_MASK		BIT(21)
+-#define RX4_COHERENT_INT_MASK		BIT(20)
+-#define RX3_COHERENT_INT_MASK		BIT(19)
+-#define RX2_COHERENT_INT_MASK		BIT(18)
+-#define RX1_COHERENT_INT_MASK		BIT(17)
+-#define RX0_COHERENT_INT_MASK		BIT(16)
+-#define TX7_COHERENT_INT_MASK		BIT(15)
+-#define TX6_COHERENT_INT_MASK		BIT(14)
+-#define TX5_COHERENT_INT_MASK		BIT(13)
+-#define TX4_COHERENT_INT_MASK		BIT(12)
+-#define TX3_COHERENT_INT_MASK		BIT(11)
+-#define TX2_COHERENT_INT_MASK		BIT(10)
+-#define TX1_COHERENT_INT_MASK		BIT(9)
+-#define TX0_COHERENT_INT_MASK		BIT(8)
+-#define CNT_OVER_FLOW_INT_MASK		BIT(7)
+-#define IRQ1_FULL_INT_MASK		BIT(5)
+-#define IRQ1_INT_MASK			BIT(4)
+-#define HWFWD_DSCP_LOW_INT_MASK		BIT(3)
+-#define HWFWD_DSCP_EMPTY_INT_MASK	BIT(2)
+-#define IRQ0_FULL_INT_MASK		BIT(1)
+-#define IRQ0_INT_MASK			BIT(0)
+-
+-#define TX_DONE_INT_MASK(_n)					\
+-	((_n) ? IRQ1_INT_MASK | IRQ1_FULL_INT_MASK		\
+-	      : IRQ0_INT_MASK | IRQ0_FULL_INT_MASK)
+-
+-#define INT_TX_MASK						\
+-	(IRQ1_INT_MASK | IRQ1_FULL_INT_MASK |			\
+-	 IRQ0_INT_MASK | IRQ0_FULL_INT_MASK)
+-
+-#define INT_IDX0_MASK						\
+-	(TX0_COHERENT_INT_MASK | TX1_COHERENT_INT_MASK |	\
+-	 TX2_COHERENT_INT_MASK | TX3_COHERENT_INT_MASK |	\
+-	 TX4_COHERENT_INT_MASK | TX5_COHERENT_INT_MASK |	\
+-	 TX6_COHERENT_INT_MASK | TX7_COHERENT_INT_MASK |	\
+-	 RX0_COHERENT_INT_MASK | RX1_COHERENT_INT_MASK |	\
+-	 RX2_COHERENT_INT_MASK | RX3_COHERENT_INT_MASK |	\
+-	 RX4_COHERENT_INT_MASK | RX7_COHERENT_INT_MASK |	\
+-	 RX8_COHERENT_INT_MASK | RX9_COHERENT_INT_MASK |	\
+-	 RX15_COHERENT_INT_MASK | INT_TX_MASK)
+-
+-/* QDMA_CSR_INT_ENABLE2 */
+-#define RX15_NO_CPU_DSCP_INT_MASK	BIT(31)
+-#define RX14_NO_CPU_DSCP_INT_MASK	BIT(30)
+-#define RX13_NO_CPU_DSCP_INT_MASK	BIT(29)
+-#define RX12_NO_CPU_DSCP_INT_MASK	BIT(28)
+-#define RX11_NO_CPU_DSCP_INT_MASK	BIT(27)
+-#define RX10_NO_CPU_DSCP_INT_MASK	BIT(26)
+-#define RX9_NO_CPU_DSCP_INT_MASK	BIT(25)
+-#define RX8_NO_CPU_DSCP_INT_MASK	BIT(24)
+-#define RX7_NO_CPU_DSCP_INT_MASK	BIT(23)
+-#define RX6_NO_CPU_DSCP_INT_MASK	BIT(22)
+-#define RX5_NO_CPU_DSCP_INT_MASK	BIT(21)
+-#define RX4_NO_CPU_DSCP_INT_MASK	BIT(20)
+-#define RX3_NO_CPU_DSCP_INT_MASK	BIT(19)
+-#define RX2_NO_CPU_DSCP_INT_MASK	BIT(18)
+-#define RX1_NO_CPU_DSCP_INT_MASK	BIT(17)
+-#define RX0_NO_CPU_DSCP_INT_MASK	BIT(16)
+-#define RX15_DONE_INT_MASK		BIT(15)
+-#define RX14_DONE_INT_MASK		BIT(14)
+-#define RX13_DONE_INT_MASK		BIT(13)
+-#define RX12_DONE_INT_MASK		BIT(12)
+-#define RX11_DONE_INT_MASK		BIT(11)
+-#define RX10_DONE_INT_MASK		BIT(10)
+-#define RX9_DONE_INT_MASK		BIT(9)
+-#define RX8_DONE_INT_MASK		BIT(8)
+-#define RX7_DONE_INT_MASK		BIT(7)
+-#define RX6_DONE_INT_MASK		BIT(6)
+-#define RX5_DONE_INT_MASK		BIT(5)
+-#define RX4_DONE_INT_MASK		BIT(4)
+-#define RX3_DONE_INT_MASK		BIT(3)
+-#define RX2_DONE_INT_MASK		BIT(2)
+-#define RX1_DONE_INT_MASK		BIT(1)
+-#define RX0_DONE_INT_MASK		BIT(0)
+-
+-#define RX_DONE_INT_MASK					\
+-	(RX0_DONE_INT_MASK | RX1_DONE_INT_MASK |		\
+-	 RX2_DONE_INT_MASK | RX3_DONE_INT_MASK |		\
+-	 RX4_DONE_INT_MASK | RX7_DONE_INT_MASK |		\
+-	 RX8_DONE_INT_MASK | RX9_DONE_INT_MASK |		\
+-	 RX15_DONE_INT_MASK)
+-#define INT_IDX1_MASK						\
+-	(RX_DONE_INT_MASK |					\
+-	 RX0_NO_CPU_DSCP_INT_MASK | RX1_NO_CPU_DSCP_INT_MASK |	\
+-	 RX2_NO_CPU_DSCP_INT_MASK | RX3_NO_CPU_DSCP_INT_MASK |	\
+-	 RX4_NO_CPU_DSCP_INT_MASK | RX7_NO_CPU_DSCP_INT_MASK |	\
+-	 RX8_NO_CPU_DSCP_INT_MASK | RX9_NO_CPU_DSCP_INT_MASK |	\
+-	 RX15_NO_CPU_DSCP_INT_MASK)
+-
+-/* QDMA_CSR_INT_ENABLE5 */
+-#define TX31_COHERENT_INT_MASK		BIT(31)
+-#define TX30_COHERENT_INT_MASK		BIT(30)
+-#define TX29_COHERENT_INT_MASK		BIT(29)
+-#define TX28_COHERENT_INT_MASK		BIT(28)
+-#define TX27_COHERENT_INT_MASK		BIT(27)
+-#define TX26_COHERENT_INT_MASK		BIT(26)
+-#define TX25_COHERENT_INT_MASK		BIT(25)
+-#define TX24_COHERENT_INT_MASK		BIT(24)
+-#define TX23_COHERENT_INT_MASK		BIT(23)
+-#define TX22_COHERENT_INT_MASK		BIT(22)
+-#define TX21_COHERENT_INT_MASK		BIT(21)
+-#define TX20_COHERENT_INT_MASK		BIT(20)
+-#define TX19_COHERENT_INT_MASK		BIT(19)
+-#define TX18_COHERENT_INT_MASK		BIT(18)
+-#define TX17_COHERENT_INT_MASK		BIT(17)
+-#define TX16_COHERENT_INT_MASK		BIT(16)
+-#define TX15_COHERENT_INT_MASK		BIT(15)
+-#define TX14_COHERENT_INT_MASK		BIT(14)
+-#define TX13_COHERENT_INT_MASK		BIT(13)
+-#define TX12_COHERENT_INT_MASK		BIT(12)
+-#define TX11_COHERENT_INT_MASK		BIT(11)
+-#define TX10_COHERENT_INT_MASK		BIT(10)
+-#define TX9_COHERENT_INT_MASK		BIT(9)
+-#define TX8_COHERENT_INT_MASK		BIT(8)
+-
+-#define INT_IDX4_MASK						\
+-	(TX8_COHERENT_INT_MASK | TX9_COHERENT_INT_MASK |	\
+-	 TX10_COHERENT_INT_MASK | TX11_COHERENT_INT_MASK |	\
+-	 TX12_COHERENT_INT_MASK | TX13_COHERENT_INT_MASK |	\
+-	 TX14_COHERENT_INT_MASK | TX15_COHERENT_INT_MASK |	\
+-	 TX16_COHERENT_INT_MASK | TX17_COHERENT_INT_MASK |	\
+-	 TX18_COHERENT_INT_MASK | TX19_COHERENT_INT_MASK |	\
+-	 TX20_COHERENT_INT_MASK | TX21_COHERENT_INT_MASK |	\
+-	 TX22_COHERENT_INT_MASK | TX23_COHERENT_INT_MASK |	\
+-	 TX24_COHERENT_INT_MASK | TX25_COHERENT_INT_MASK |	\
+-	 TX26_COHERENT_INT_MASK | TX27_COHERENT_INT_MASK |	\
+-	 TX28_COHERENT_INT_MASK | TX29_COHERENT_INT_MASK |	\
+-	 TX30_COHERENT_INT_MASK | TX31_COHERENT_INT_MASK)
+-
+-#define REG_TX_IRQ_BASE(_n)		((_n) ? 0x0048 : 0x0050)
+-
+-#define REG_TX_IRQ_CFG(_n)		((_n) ? 0x004c : 0x0054)
+-#define TX_IRQ_THR_MASK			GENMASK(27, 16)
+-#define TX_IRQ_DEPTH_MASK		GENMASK(11, 0)
+-
+-#define REG_IRQ_CLEAR_LEN(_n)		((_n) ? 0x0064 : 0x0058)
+-#define IRQ_CLEAR_LEN_MASK		GENMASK(7, 0)
+-
+-#define REG_IRQ_STATUS(_n)		((_n) ? 0x0068 : 0x005c)
+-#define IRQ_ENTRY_LEN_MASK		GENMASK(27, 16)
+-#define IRQ_HEAD_IDX_MASK		GENMASK(11, 0)
+-
+-#define REG_TX_RING_BASE(_n)	\
+-	(((_n) < 8) ? 0x0100 + ((_n) << 5) : 0x0b00 + (((_n) - 8) << 5))
+-
+-#define REG_TX_RING_BLOCKING(_n)	\
+-	(((_n) < 8) ? 0x0104 + ((_n) << 5) : 0x0b04 + (((_n) - 8) << 5))
+-
+-#define TX_RING_IRQ_BLOCKING_MAP_MASK			BIT(6)
+-#define TX_RING_IRQ_BLOCKING_CFG_MASK			BIT(4)
+-#define TX_RING_IRQ_BLOCKING_TX_DROP_EN_MASK		BIT(2)
+-#define TX_RING_IRQ_BLOCKING_MAX_TH_TXRING_EN_MASK	BIT(1)
+-#define TX_RING_IRQ_BLOCKING_MIN_TH_TXRING_EN_MASK	BIT(0)
+-
+-#define REG_TX_CPU_IDX(_n)	\
+-	(((_n) < 8) ? 0x0108 + ((_n) << 5) : 0x0b08 + (((_n) - 8) << 5))
+-
+-#define TX_RING_CPU_IDX_MASK		GENMASK(15, 0)
+-
+-#define REG_TX_DMA_IDX(_n)	\
+-	(((_n) < 8) ? 0x010c + ((_n) << 5) : 0x0b0c + (((_n) - 8) << 5))
+-
+-#define TX_RING_DMA_IDX_MASK		GENMASK(15, 0)
+-
+-#define IRQ_RING_IDX_MASK		GENMASK(20, 16)
+-#define IRQ_DESC_IDX_MASK		GENMASK(15, 0)
+-
+-#define REG_RX_RING_BASE(_n)	\
+-	(((_n) < 16) ? 0x0200 + ((_n) << 5) : 0x0e00 + (((_n) - 16) << 5))
+-
+-#define REG_RX_RING_SIZE(_n)	\
+-	(((_n) < 16) ? 0x0204 + ((_n) << 5) : 0x0e04 + (((_n) - 16) << 5))
+-
+-#define RX_RING_THR_MASK		GENMASK(31, 16)
+-#define RX_RING_SIZE_MASK		GENMASK(15, 0)
+-
+-#define REG_RX_CPU_IDX(_n)	\
+-	(((_n) < 16) ? 0x0208 + ((_n) << 5) : 0x0e08 + (((_n) - 16) << 5))
+-
+-#define RX_RING_CPU_IDX_MASK		GENMASK(15, 0)
+-
+-#define REG_RX_DMA_IDX(_n)	\
+-	(((_n) < 16) ? 0x020c + ((_n) << 5) : 0x0e0c + (((_n) - 16) << 5))
+-
+-#define REG_RX_DELAY_INT_IDX(_n)	\
+-	(((_n) < 16) ? 0x0210 + ((_n) << 5) : 0x0e10 + (((_n) - 16) << 5))
+-
+-#define RX_DELAY_INT_MASK		GENMASK(15, 0)
+-
+-#define RX_RING_DMA_IDX_MASK		GENMASK(15, 0)
+-
+-#define REG_INGRESS_TRTCM_CFG		0x0070
+-#define INGRESS_TRTCM_EN_MASK		BIT(31)
+-#define INGRESS_TRTCM_MODE_MASK		BIT(30)
+-#define INGRESS_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
+-#define INGRESS_FAST_TICK_MASK		GENMASK(15, 0)
+-
+-#define REG_QUEUE_CLOSE_CFG(_n)		(0x00a0 + ((_n) & 0xfc))
+-#define TXQ_DISABLE_CHAN_QUEUE_MASK(_n, _m)	BIT((_m) + (((_n) & 0x3) << 3))
+-
+-#define REG_TXQ_DIS_CFG_BASE(_n)	((_n) ? 0x20a0 : 0x00a0)
+-#define REG_TXQ_DIS_CFG(_n, _m)		(REG_TXQ_DIS_CFG_BASE((_n)) + (_m) << 2)
+-
+-#define REG_CNTR_CFG(_n)		(0x0400 + ((_n) << 3))
+-#define CNTR_EN_MASK			BIT(31)
+-#define CNTR_ALL_CHAN_EN_MASK		BIT(30)
+-#define CNTR_ALL_QUEUE_EN_MASK		BIT(29)
+-#define CNTR_ALL_DSCP_RING_EN_MASK	BIT(28)
+-#define CNTR_SRC_MASK			GENMASK(27, 24)
+-#define CNTR_DSCP_RING_MASK		GENMASK(20, 16)
+-#define CNTR_CHAN_MASK			GENMASK(7, 3)
+-#define CNTR_QUEUE_MASK			GENMASK(2, 0)
+-
+-#define REG_CNTR_VAL(_n)		(0x0404 + ((_n) << 3))
+-
+-#define REG_LMGR_INIT_CFG		0x1000
+-#define LMGR_INIT_START			BIT(31)
+-#define LMGR_SRAM_MODE_MASK		BIT(30)
+-#define HW_FWD_PKTSIZE_OVERHEAD_MASK	GENMASK(27, 20)
+-#define HW_FWD_DESC_NUM_MASK		GENMASK(16, 0)
+-
+-#define REG_FWD_DSCP_LOW_THR		0x1004
+-#define FWD_DSCP_LOW_THR_MASK		GENMASK(17, 0)
+-
+-#define REG_EGRESS_RATE_METER_CFG		0x100c
+-#define EGRESS_RATE_METER_EN_MASK		BIT(31)
+-#define EGRESS_RATE_METER_EQ_RATE_EN_MASK	BIT(17)
+-#define EGRESS_RATE_METER_WINDOW_SZ_MASK	GENMASK(16, 12)
+-#define EGRESS_RATE_METER_TIMESLICE_MASK	GENMASK(10, 0)
+-
+-#define REG_EGRESS_TRTCM_CFG		0x1010
+-#define EGRESS_TRTCM_EN_MASK		BIT(31)
+-#define EGRESS_TRTCM_MODE_MASK		BIT(30)
+-#define EGRESS_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
+-#define EGRESS_FAST_TICK_MASK		GENMASK(15, 0)
+-
+-#define TRTCM_PARAM_RW_MASK		BIT(31)
+-#define TRTCM_PARAM_RW_DONE_MASK	BIT(30)
+-#define TRTCM_PARAM_TYPE_MASK		GENMASK(29, 28)
+-#define TRTCM_METER_GROUP_MASK		GENMASK(27, 26)
+-#define TRTCM_PARAM_INDEX_MASK		GENMASK(23, 17)
+-#define TRTCM_PARAM_RATE_TYPE_MASK	BIT(16)
+-
+-#define REG_TRTCM_CFG_PARAM(_n)		((_n) + 0x4)
+-#define REG_TRTCM_DATA_LOW(_n)		((_n) + 0x8)
+-#define REG_TRTCM_DATA_HIGH(_n)		((_n) + 0xc)
+-
+-#define REG_TXWRR_MODE_CFG		0x1020
+-#define TWRR_WEIGHT_SCALE_MASK		BIT(31)
+-#define TWRR_WEIGHT_BASE_MASK		BIT(3)
+-
+-#define REG_TXWRR_WEIGHT_CFG		0x1024
+-#define TWRR_RW_CMD_MASK		BIT(31)
+-#define TWRR_RW_CMD_DONE		BIT(30)
+-#define TWRR_CHAN_IDX_MASK		GENMASK(23, 19)
+-#define TWRR_QUEUE_IDX_MASK		GENMASK(18, 16)
+-#define TWRR_VALUE_MASK			GENMASK(15, 0)
+-
+-#define REG_PSE_BUF_USAGE_CFG		0x1028
+-#define PSE_BUF_ESTIMATE_EN_MASK	BIT(29)
+-
+-#define REG_CHAN_QOS_MODE(_n)		(0x1040 + ((_n) << 2))
+-#define CHAN_QOS_MODE_MASK(_n)		GENMASK(2 + ((_n) << 2), (_n) << 2)
+-
+-#define REG_GLB_TRTCM_CFG		0x1080
+-#define GLB_TRTCM_EN_MASK		BIT(31)
+-#define GLB_TRTCM_MODE_MASK		BIT(30)
+-#define GLB_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
+-#define GLB_FAST_TICK_MASK		GENMASK(15, 0)
+-
+-#define REG_TXQ_CNGST_CFG		0x10a0
+-#define TXQ_CNGST_DROP_EN		BIT(31)
+-#define TXQ_CNGST_DEI_DROP_EN		BIT(30)
+-
+-#define REG_SLA_TRTCM_CFG		0x1150
+-#define SLA_TRTCM_EN_MASK		BIT(31)
+-#define SLA_TRTCM_MODE_MASK		BIT(30)
+-#define SLA_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
+-#define SLA_FAST_TICK_MASK		GENMASK(15, 0)
+-
+-/* CTRL */
+-#define QDMA_DESC_DONE_MASK		BIT(31)
+-#define QDMA_DESC_DROP_MASK		BIT(30) /* tx: drop - rx: overflow */
+-#define QDMA_DESC_MORE_MASK		BIT(29) /* more SG elements */
+-#define QDMA_DESC_DEI_MASK		BIT(25)
+-#define QDMA_DESC_NO_DROP_MASK		BIT(24)
+-#define QDMA_DESC_LEN_MASK		GENMASK(15, 0)
+-/* DATA */
+-#define QDMA_DESC_NEXT_ID_MASK		GENMASK(15, 0)
+-/* TX MSG0 */
+-#define QDMA_ETH_TXMSG_MIC_IDX_MASK	BIT(30)
+-#define QDMA_ETH_TXMSG_SP_TAG_MASK	GENMASK(29, 14)
+-#define QDMA_ETH_TXMSG_ICO_MASK		BIT(13)
+-#define QDMA_ETH_TXMSG_UCO_MASK		BIT(12)
+-#define QDMA_ETH_TXMSG_TCO_MASK		BIT(11)
+-#define QDMA_ETH_TXMSG_TSO_MASK		BIT(10)
+-#define QDMA_ETH_TXMSG_FAST_MASK	BIT(9)
+-#define QDMA_ETH_TXMSG_OAM_MASK		BIT(8)
+-#define QDMA_ETH_TXMSG_CHAN_MASK	GENMASK(7, 3)
+-#define QDMA_ETH_TXMSG_QUEUE_MASK	GENMASK(2, 0)
+-/* TX MSG1 */
+-#define QDMA_ETH_TXMSG_NO_DROP		BIT(31)
+-#define QDMA_ETH_TXMSG_METER_MASK	GENMASK(30, 24)	/* 0x7f no meters */
+-#define QDMA_ETH_TXMSG_FPORT_MASK	GENMASK(23, 20)
+-#define QDMA_ETH_TXMSG_NBOQ_MASK	GENMASK(19, 15)
+-#define QDMA_ETH_TXMSG_HWF_MASK		BIT(14)
+-#define QDMA_ETH_TXMSG_HOP_MASK		BIT(13)
+-#define QDMA_ETH_TXMSG_PTP_MASK		BIT(12)
+-#define QDMA_ETH_TXMSG_ACNT_G1_MASK	GENMASK(10, 6)	/* 0x1f do not count */
+-#define QDMA_ETH_TXMSG_ACNT_G0_MASK	GENMASK(5, 0)	/* 0x3f do not count */
+-
+-/* RX MSG1 */
+-#define QDMA_ETH_RXMSG_DEI_MASK		BIT(31)
+-#define QDMA_ETH_RXMSG_IP6_MASK		BIT(30)
+-#define QDMA_ETH_RXMSG_IP4_MASK		BIT(29)
+-#define QDMA_ETH_RXMSG_IP4F_MASK	BIT(28)
+-#define QDMA_ETH_RXMSG_L4_VALID_MASK	BIT(27)
+-#define QDMA_ETH_RXMSG_L4F_MASK		BIT(26)
+-#define QDMA_ETH_RXMSG_SPORT_MASK	GENMASK(25, 21)
+-#define QDMA_ETH_RXMSG_CRSN_MASK	GENMASK(20, 16)
+-#define QDMA_ETH_RXMSG_PPE_ENTRY_MASK	GENMASK(15, 0)
+-
+-struct airoha_qdma_desc {
+-	__le32 rsv;
+-	__le32 ctrl;
+-	__le32 addr;
+-	__le32 data;
+-	__le32 msg0;
+-	__le32 msg1;
+-	__le32 msg2;
+-	__le32 msg3;
+-};
+-
+-/* CTRL0 */
+-#define QDMA_FWD_DESC_CTX_MASK		BIT(31)
+-#define QDMA_FWD_DESC_RING_MASK		GENMASK(30, 28)
+-#define QDMA_FWD_DESC_IDX_MASK		GENMASK(27, 16)
+-#define QDMA_FWD_DESC_LEN_MASK		GENMASK(15, 0)
+-/* CTRL1 */
+-#define QDMA_FWD_DESC_FIRST_IDX_MASK	GENMASK(15, 0)
+-/* CTRL2 */
+-#define QDMA_FWD_DESC_MORE_PKT_NUM_MASK	GENMASK(2, 0)
+-
+-struct airoha_qdma_fwd_desc {
+-	__le32 addr;
+-	__le32 ctrl0;
+-	__le32 ctrl1;
+-	__le32 ctrl2;
+-	__le32 msg0;
+-	__le32 msg1;
+-	__le32 rsv0;
+-	__le32 rsv1;
+-};
+-
+ u32 airoha_rr(void __iomem *base, u32 offset)
+ {
+ 	return readl(base + offset);
+--- /dev/null
++++ b/drivers/net/ethernet/airoha/airoha_regs.h
+@@ -0,0 +1,670 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/*
++ * Copyright (c) 2024 AIROHA Inc
++ * Author: Lorenzo Bianconi <[email protected]>
++ */
++
++#ifndef AIROHA_REGS_H
++#define AIROHA_REGS_H
++
++#include <linux/types.h>
++
++/* FE */
++#define PSE_BASE			0x0100
++#define CSR_IFC_BASE			0x0200
++#define CDM1_BASE			0x0400
++#define GDM1_BASE			0x0500
++#define PPE1_BASE			0x0c00
++
++#define CDM2_BASE			0x1400
++#define GDM2_BASE			0x1500
++
++#define GDM3_BASE			0x1100
++#define GDM4_BASE			0x2500
++
++#define GDM_BASE(_n)			\
++	((_n) == 4 ? GDM4_BASE :	\
++	 (_n) == 3 ? GDM3_BASE :	\
++	 (_n) == 2 ? GDM2_BASE : GDM1_BASE)
++
++#define REG_FE_DMA_GLO_CFG		0x0000
++#define FE_DMA_GLO_L2_SPACE_MASK	GENMASK(7, 4)
++#define FE_DMA_GLO_PG_SZ_MASK		BIT(3)
++
++#define REG_FE_RST_GLO_CFG		0x0004
++#define FE_RST_GDM4_MBI_ARB_MASK	BIT(3)
++#define FE_RST_GDM3_MBI_ARB_MASK	BIT(2)
++#define FE_RST_CORE_MASK		BIT(0)
++
++#define REG_FE_WAN_MAC_H		0x0030
++#define REG_FE_LAN_MAC_H		0x0040
++
++#define REG_FE_MAC_LMIN(_n)		((_n) + 0x04)
++#define REG_FE_MAC_LMAX(_n)		((_n) + 0x08)
++
++#define REG_FE_CDM1_OQ_MAP0		0x0050
++#define REG_FE_CDM1_OQ_MAP1		0x0054
++#define REG_FE_CDM1_OQ_MAP2		0x0058
++#define REG_FE_CDM1_OQ_MAP3		0x005c
++
++#define REG_FE_PCE_CFG			0x0070
++#define PCE_DPI_EN_MASK			BIT(2)
++#define PCE_KA_EN_MASK			BIT(1)
++#define PCE_MC_EN_MASK			BIT(0)
++
++#define REG_FE_PSE_QUEUE_CFG_WR		0x0080
++#define PSE_CFG_PORT_ID_MASK		GENMASK(27, 24)
++#define PSE_CFG_QUEUE_ID_MASK		GENMASK(20, 16)
++#define PSE_CFG_WR_EN_MASK		BIT(8)
++#define PSE_CFG_OQRSV_SEL_MASK		BIT(0)
++
++#define REG_FE_PSE_QUEUE_CFG_VAL	0x0084
++#define PSE_CFG_OQ_RSV_MASK		GENMASK(13, 0)
++
++#define PSE_FQ_CFG			0x008c
++#define PSE_FQ_LIMIT_MASK		GENMASK(14, 0)
++
++#define REG_FE_PSE_BUF_SET		0x0090
++#define PSE_SHARE_USED_LTHD_MASK	GENMASK(31, 16)
++#define PSE_ALLRSV_MASK			GENMASK(14, 0)
++
++#define REG_PSE_SHARE_USED_THD		0x0094
++#define PSE_SHARE_USED_MTHD_MASK	GENMASK(31, 16)
++#define PSE_SHARE_USED_HTHD_MASK	GENMASK(15, 0)
++
++#define REG_GDM_MISC_CFG		0x0148
++#define GDM2_RDM_ACK_WAIT_PREF_MASK	BIT(9)
++#define GDM2_CHN_VLD_MODE_MASK		BIT(5)
++
++#define REG_FE_CSR_IFC_CFG		CSR_IFC_BASE
++#define FE_IFC_EN_MASK			BIT(0)
++
++#define REG_FE_VIP_PORT_EN		0x01f0
++#define REG_FE_IFC_PORT_EN		0x01f4
++
++#define REG_PSE_IQ_REV1			(PSE_BASE + 0x08)
++#define PSE_IQ_RES1_P2_MASK		GENMASK(23, 16)
++
++#define REG_PSE_IQ_REV2			(PSE_BASE + 0x0c)
++#define PSE_IQ_RES2_P5_MASK		GENMASK(15, 8)
++#define PSE_IQ_RES2_P4_MASK		GENMASK(7, 0)
++
++#define REG_FE_VIP_EN(_n)		(0x0300 + ((_n) << 3))
++#define PATN_FCPU_EN_MASK		BIT(7)
++#define PATN_SWP_EN_MASK		BIT(6)
++#define PATN_DP_EN_MASK			BIT(5)
++#define PATN_SP_EN_MASK			BIT(4)
++#define PATN_TYPE_MASK			GENMASK(3, 1)
++#define PATN_EN_MASK			BIT(0)
++
++#define REG_FE_VIP_PATN(_n)		(0x0304 + ((_n) << 3))
++#define PATN_DP_MASK			GENMASK(31, 16)
++#define PATN_SP_MASK			GENMASK(15, 0)
++
++#define REG_CDM1_VLAN_CTRL		CDM1_BASE
++#define CDM1_VLAN_MASK			GENMASK(31, 16)
++
++#define REG_CDM1_FWD_CFG		(CDM1_BASE + 0x08)
++#define CDM1_VIP_QSEL_MASK		GENMASK(24, 20)
++
++#define REG_CDM1_CRSN_QSEL(_n)		(CDM1_BASE + 0x10 + ((_n) << 2))
++#define CDM1_CRSN_QSEL_REASON_MASK(_n)	\
++	GENMASK(4 + (((_n) % 4) << 3),	(((_n) % 4) << 3))
++
++#define REG_CDM2_FWD_CFG		(CDM2_BASE + 0x08)
++#define CDM2_OAM_QSEL_MASK		GENMASK(31, 27)
++#define CDM2_VIP_QSEL_MASK		GENMASK(24, 20)
++
++#define REG_CDM2_CRSN_QSEL(_n)		(CDM2_BASE + 0x10 + ((_n) << 2))
++#define CDM2_CRSN_QSEL_REASON_MASK(_n)	\
++	GENMASK(4 + (((_n) % 4) << 3),	(((_n) % 4) << 3))
++
++#define REG_GDM_FWD_CFG(_n)		GDM_BASE(_n)
++#define GDM_DROP_CRC_ERR		BIT(23)
++#define GDM_IP4_CKSUM			BIT(22)
++#define GDM_TCP_CKSUM			BIT(21)
++#define GDM_UDP_CKSUM			BIT(20)
++#define GDM_UCFQ_MASK			GENMASK(15, 12)
++#define GDM_BCFQ_MASK			GENMASK(11, 8)
++#define GDM_MCFQ_MASK			GENMASK(7, 4)
++#define GDM_OCFQ_MASK			GENMASK(3, 0)
++
++#define REG_GDM_INGRESS_CFG(_n)		(GDM_BASE(_n) + 0x10)
++#define GDM_INGRESS_FC_EN_MASK		BIT(1)
++#define GDM_STAG_EN_MASK		BIT(0)
++
++#define REG_GDM_LEN_CFG(_n)		(GDM_BASE(_n) + 0x14)
++#define GDM_SHORT_LEN_MASK		GENMASK(13, 0)
++#define GDM_LONG_LEN_MASK		GENMASK(29, 16)
++
++#define REG_FE_CPORT_CFG		(GDM1_BASE + 0x40)
++#define FE_CPORT_PAD			BIT(26)
++#define FE_CPORT_PORT_XFC_MASK		BIT(25)
++#define FE_CPORT_QUEUE_XFC_MASK		BIT(24)
++
++#define REG_FE_GDM_MIB_CLEAR(_n)	(GDM_BASE(_n) + 0xf0)
++#define FE_GDM_MIB_RX_CLEAR_MASK	BIT(1)
++#define FE_GDM_MIB_TX_CLEAR_MASK	BIT(0)
++
++#define REG_FE_GDM1_MIB_CFG		(GDM1_BASE + 0xf4)
++#define FE_STRICT_RFC2819_MODE_MASK	BIT(31)
++#define FE_GDM1_TX_MIB_SPLIT_EN_MASK	BIT(17)
++#define FE_GDM1_RX_MIB_SPLIT_EN_MASK	BIT(16)
++#define FE_TX_MIB_ID_MASK		GENMASK(15, 8)
++#define FE_RX_MIB_ID_MASK		GENMASK(7, 0)
++
++#define REG_FE_GDM_TX_OK_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x104)
++#define REG_FE_GDM_TX_OK_BYTE_CNT_L(_n)		(GDM_BASE(_n) + 0x10c)
++#define REG_FE_GDM_TX_ETH_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x110)
++#define REG_FE_GDM_TX_ETH_BYTE_CNT_L(_n)	(GDM_BASE(_n) + 0x114)
++#define REG_FE_GDM_TX_ETH_DROP_CNT(_n)		(GDM_BASE(_n) + 0x118)
++#define REG_FE_GDM_TX_ETH_BC_CNT(_n)		(GDM_BASE(_n) + 0x11c)
++#define REG_FE_GDM_TX_ETH_MC_CNT(_n)		(GDM_BASE(_n) + 0x120)
++#define REG_FE_GDM_TX_ETH_RUNT_CNT(_n)		(GDM_BASE(_n) + 0x124)
++#define REG_FE_GDM_TX_ETH_LONG_CNT(_n)		(GDM_BASE(_n) + 0x128)
++#define REG_FE_GDM_TX_ETH_E64_CNT_L(_n)		(GDM_BASE(_n) + 0x12c)
++#define REG_FE_GDM_TX_ETH_L64_CNT_L(_n)		(GDM_BASE(_n) + 0x130)
++#define REG_FE_GDM_TX_ETH_L127_CNT_L(_n)	(GDM_BASE(_n) + 0x134)
++#define REG_FE_GDM_TX_ETH_L255_CNT_L(_n)	(GDM_BASE(_n) + 0x138)
++#define REG_FE_GDM_TX_ETH_L511_CNT_L(_n)	(GDM_BASE(_n) + 0x13c)
++#define REG_FE_GDM_TX_ETH_L1023_CNT_L(_n)	(GDM_BASE(_n) + 0x140)
++
++#define REG_FE_GDM_RX_OK_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x148)
++#define REG_FE_GDM_RX_FC_DROP_CNT(_n)		(GDM_BASE(_n) + 0x14c)
++#define REG_FE_GDM_RX_RC_DROP_CNT(_n)		(GDM_BASE(_n) + 0x150)
++#define REG_FE_GDM_RX_OVERFLOW_DROP_CNT(_n)	(GDM_BASE(_n) + 0x154)
++#define REG_FE_GDM_RX_ERROR_DROP_CNT(_n)	(GDM_BASE(_n) + 0x158)
++#define REG_FE_GDM_RX_OK_BYTE_CNT_L(_n)		(GDM_BASE(_n) + 0x15c)
++#define REG_FE_GDM_RX_ETH_PKT_CNT_L(_n)		(GDM_BASE(_n) + 0x160)
++#define REG_FE_GDM_RX_ETH_BYTE_CNT_L(_n)	(GDM_BASE(_n) + 0x164)
++#define REG_FE_GDM_RX_ETH_DROP_CNT(_n)		(GDM_BASE(_n) + 0x168)
++#define REG_FE_GDM_RX_ETH_BC_CNT(_n)		(GDM_BASE(_n) + 0x16c)
++#define REG_FE_GDM_RX_ETH_MC_CNT(_n)		(GDM_BASE(_n) + 0x170)
++#define REG_FE_GDM_RX_ETH_CRC_ERR_CNT(_n)	(GDM_BASE(_n) + 0x174)
++#define REG_FE_GDM_RX_ETH_FRAG_CNT(_n)		(GDM_BASE(_n) + 0x178)
++#define REG_FE_GDM_RX_ETH_JABBER_CNT(_n)	(GDM_BASE(_n) + 0x17c)
++#define REG_FE_GDM_RX_ETH_RUNT_CNT(_n)		(GDM_BASE(_n) + 0x180)
++#define REG_FE_GDM_RX_ETH_LONG_CNT(_n)		(GDM_BASE(_n) + 0x184)
++#define REG_FE_GDM_RX_ETH_E64_CNT_L(_n)		(GDM_BASE(_n) + 0x188)
++#define REG_FE_GDM_RX_ETH_L64_CNT_L(_n)		(GDM_BASE(_n) + 0x18c)
++#define REG_FE_GDM_RX_ETH_L127_CNT_L(_n)	(GDM_BASE(_n) + 0x190)
++#define REG_FE_GDM_RX_ETH_L255_CNT_L(_n)	(GDM_BASE(_n) + 0x194)
++#define REG_FE_GDM_RX_ETH_L511_CNT_L(_n)	(GDM_BASE(_n) + 0x198)
++#define REG_FE_GDM_RX_ETH_L1023_CNT_L(_n)	(GDM_BASE(_n) + 0x19c)
++
++#define REG_PPE1_TB_HASH_CFG		(PPE1_BASE + 0x250)
++#define PPE1_SRAM_TABLE_EN_MASK		BIT(0)
++#define PPE1_SRAM_HASH1_EN_MASK		BIT(8)
++#define PPE1_DRAM_TABLE_EN_MASK		BIT(16)
++#define PPE1_DRAM_HASH1_EN_MASK		BIT(24)
++
++#define REG_FE_GDM_TX_OK_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x280)
++#define REG_FE_GDM_TX_OK_BYTE_CNT_H(_n)		(GDM_BASE(_n) + 0x284)
++#define REG_FE_GDM_TX_ETH_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x288)
++#define REG_FE_GDM_TX_ETH_BYTE_CNT_H(_n)	(GDM_BASE(_n) + 0x28c)
++
++#define REG_FE_GDM_RX_OK_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x290)
++#define REG_FE_GDM_RX_OK_BYTE_CNT_H(_n)		(GDM_BASE(_n) + 0x294)
++#define REG_FE_GDM_RX_ETH_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x298)
++#define REG_FE_GDM_RX_ETH_BYTE_CNT_H(_n)	(GDM_BASE(_n) + 0x29c)
++#define REG_FE_GDM_TX_ETH_E64_CNT_H(_n)		(GDM_BASE(_n) + 0x2b8)
++#define REG_FE_GDM_TX_ETH_L64_CNT_H(_n)		(GDM_BASE(_n) + 0x2bc)
++#define REG_FE_GDM_TX_ETH_L127_CNT_H(_n)	(GDM_BASE(_n) + 0x2c0)
++#define REG_FE_GDM_TX_ETH_L255_CNT_H(_n)	(GDM_BASE(_n) + 0x2c4)
++#define REG_FE_GDM_TX_ETH_L511_CNT_H(_n)	(GDM_BASE(_n) + 0x2c8)
++#define REG_FE_GDM_TX_ETH_L1023_CNT_H(_n)	(GDM_BASE(_n) + 0x2cc)
++#define REG_FE_GDM_RX_ETH_E64_CNT_H(_n)		(GDM_BASE(_n) + 0x2e8)
++#define REG_FE_GDM_RX_ETH_L64_CNT_H(_n)		(GDM_BASE(_n) + 0x2ec)
++#define REG_FE_GDM_RX_ETH_L127_CNT_H(_n)	(GDM_BASE(_n) + 0x2f0)
++#define REG_FE_GDM_RX_ETH_L255_CNT_H(_n)	(GDM_BASE(_n) + 0x2f4)
++#define REG_FE_GDM_RX_ETH_L511_CNT_H(_n)	(GDM_BASE(_n) + 0x2f8)
++#define REG_FE_GDM_RX_ETH_L1023_CNT_H(_n)	(GDM_BASE(_n) + 0x2fc)
++
++#define REG_GDM2_CHN_RLS		(GDM2_BASE + 0x20)
++#define MBI_RX_AGE_SEL_MASK		GENMASK(26, 25)
++#define MBI_TX_AGE_SEL_MASK		GENMASK(18, 17)
++
++#define REG_GDM3_FWD_CFG		GDM3_BASE
++#define GDM3_PAD_EN_MASK		BIT(28)
++
++#define REG_GDM4_FWD_CFG		GDM4_BASE
++#define GDM4_PAD_EN_MASK		BIT(28)
++#define GDM4_SPORT_OFFSET0_MASK		GENMASK(11, 8)
++
++#define REG_GDM4_SRC_PORT_SET		(GDM4_BASE + 0x23c)
++#define GDM4_SPORT_OFF2_MASK		GENMASK(19, 16)
++#define GDM4_SPORT_OFF1_MASK		GENMASK(15, 12)
++#define GDM4_SPORT_OFF0_MASK		GENMASK(11, 8)
++
++#define REG_IP_FRAG_FP			0x2010
++#define IP_ASSEMBLE_PORT_MASK		GENMASK(24, 21)
++#define IP_ASSEMBLE_NBQ_MASK		GENMASK(20, 16)
++#define IP_FRAGMENT_PORT_MASK		GENMASK(8, 5)
++#define IP_FRAGMENT_NBQ_MASK		GENMASK(4, 0)
++
++#define REG_MC_VLAN_EN			0x2100
++#define MC_VLAN_EN_MASK			BIT(0)
++
++#define REG_MC_VLAN_CFG			0x2104
++#define MC_VLAN_CFG_CMD_DONE_MASK	BIT(31)
++#define MC_VLAN_CFG_TABLE_ID_MASK	GENMASK(21, 16)
++#define MC_VLAN_CFG_PORT_ID_MASK	GENMASK(11, 8)
++#define MC_VLAN_CFG_TABLE_SEL_MASK	BIT(4)
++#define MC_VLAN_CFG_RW_MASK		BIT(0)
++
++#define REG_MC_VLAN_DATA		0x2108
++
++#define REG_CDM5_RX_OQ1_DROP_CNT	0x29d4
++
++/* QDMA */
++#define REG_QDMA_GLOBAL_CFG			0x0004
++#define GLOBAL_CFG_RX_2B_OFFSET_MASK		BIT(31)
++#define GLOBAL_CFG_DMA_PREFERENCE_MASK		GENMASK(30, 29)
++#define GLOBAL_CFG_CPU_TXR_RR_MASK		BIT(28)
++#define GLOBAL_CFG_DSCP_BYTE_SWAP_MASK		BIT(27)
++#define GLOBAL_CFG_PAYLOAD_BYTE_SWAP_MASK	BIT(26)
++#define GLOBAL_CFG_MULTICAST_MODIFY_FP_MASK	BIT(25)
++#define GLOBAL_CFG_OAM_MODIFY_MASK		BIT(24)
++#define GLOBAL_CFG_RESET_MASK			BIT(23)
++#define GLOBAL_CFG_RESET_DONE_MASK		BIT(22)
++#define GLOBAL_CFG_MULTICAST_EN_MASK		BIT(21)
++#define GLOBAL_CFG_IRQ1_EN_MASK			BIT(20)
++#define GLOBAL_CFG_IRQ0_EN_MASK			BIT(19)
++#define GLOBAL_CFG_LOOPCNT_EN_MASK		BIT(18)
++#define GLOBAL_CFG_RD_BYPASS_WR_MASK		BIT(17)
++#define GLOBAL_CFG_QDMA_LOOPBACK_MASK		BIT(16)
++#define GLOBAL_CFG_LPBK_RXQ_SEL_MASK		GENMASK(13, 8)
++#define GLOBAL_CFG_CHECK_DONE_MASK		BIT(7)
++#define GLOBAL_CFG_TX_WB_DONE_MASK		BIT(6)
++#define GLOBAL_CFG_MAX_ISSUE_NUM_MASK		GENMASK(5, 4)
++#define GLOBAL_CFG_RX_DMA_BUSY_MASK		BIT(3)
++#define GLOBAL_CFG_RX_DMA_EN_MASK		BIT(2)
++#define GLOBAL_CFG_TX_DMA_BUSY_MASK		BIT(1)
++#define GLOBAL_CFG_TX_DMA_EN_MASK		BIT(0)
++
++#define REG_FWD_DSCP_BASE			0x0010
++#define REG_FWD_BUF_BASE			0x0014
++
++#define REG_HW_FWD_DSCP_CFG			0x0018
++#define HW_FWD_DSCP_PAYLOAD_SIZE_MASK		GENMASK(29, 28)
++#define HW_FWD_DSCP_SCATTER_LEN_MASK		GENMASK(17, 16)
++#define HW_FWD_DSCP_MIN_SCATTER_LEN_MASK	GENMASK(15, 0)
++
++#define REG_INT_STATUS(_n)		\
++	(((_n) == 4) ? 0x0730 :		\
++	 ((_n) == 3) ? 0x0724 :		\
++	 ((_n) == 2) ? 0x0720 :		\
++	 ((_n) == 1) ? 0x0024 : 0x0020)
++
++#define REG_INT_ENABLE(_n)		\
++	(((_n) == 4) ? 0x0750 :		\
++	 ((_n) == 3) ? 0x0744 :		\
++	 ((_n) == 2) ? 0x0740 :		\
++	 ((_n) == 1) ? 0x002c : 0x0028)
++
++/* QDMA_CSR_INT_ENABLE1 */
++#define RX15_COHERENT_INT_MASK		BIT(31)
++#define RX14_COHERENT_INT_MASK		BIT(30)
++#define RX13_COHERENT_INT_MASK		BIT(29)
++#define RX12_COHERENT_INT_MASK		BIT(28)
++#define RX11_COHERENT_INT_MASK		BIT(27)
++#define RX10_COHERENT_INT_MASK		BIT(26)
++#define RX9_COHERENT_INT_MASK		BIT(25)
++#define RX8_COHERENT_INT_MASK		BIT(24)
++#define RX7_COHERENT_INT_MASK		BIT(23)
++#define RX6_COHERENT_INT_MASK		BIT(22)
++#define RX5_COHERENT_INT_MASK		BIT(21)
++#define RX4_COHERENT_INT_MASK		BIT(20)
++#define RX3_COHERENT_INT_MASK		BIT(19)
++#define RX2_COHERENT_INT_MASK		BIT(18)
++#define RX1_COHERENT_INT_MASK		BIT(17)
++#define RX0_COHERENT_INT_MASK		BIT(16)
++#define TX7_COHERENT_INT_MASK		BIT(15)
++#define TX6_COHERENT_INT_MASK		BIT(14)
++#define TX5_COHERENT_INT_MASK		BIT(13)
++#define TX4_COHERENT_INT_MASK		BIT(12)
++#define TX3_COHERENT_INT_MASK		BIT(11)
++#define TX2_COHERENT_INT_MASK		BIT(10)
++#define TX1_COHERENT_INT_MASK		BIT(9)
++#define TX0_COHERENT_INT_MASK		BIT(8)
++#define CNT_OVER_FLOW_INT_MASK		BIT(7)
++#define IRQ1_FULL_INT_MASK		BIT(5)
++#define IRQ1_INT_MASK			BIT(4)
++#define HWFWD_DSCP_LOW_INT_MASK		BIT(3)
++#define HWFWD_DSCP_EMPTY_INT_MASK	BIT(2)
++#define IRQ0_FULL_INT_MASK		BIT(1)
++#define IRQ0_INT_MASK			BIT(0)
++
++#define TX_DONE_INT_MASK(_n)					\
++	((_n) ? IRQ1_INT_MASK | IRQ1_FULL_INT_MASK		\
++	      : IRQ0_INT_MASK | IRQ0_FULL_INT_MASK)
++
++#define INT_TX_MASK						\
++	(IRQ1_INT_MASK | IRQ1_FULL_INT_MASK |			\
++	 IRQ0_INT_MASK | IRQ0_FULL_INT_MASK)
++
++#define INT_IDX0_MASK						\
++	(TX0_COHERENT_INT_MASK | TX1_COHERENT_INT_MASK |	\
++	 TX2_COHERENT_INT_MASK | TX3_COHERENT_INT_MASK |	\
++	 TX4_COHERENT_INT_MASK | TX5_COHERENT_INT_MASK |	\
++	 TX6_COHERENT_INT_MASK | TX7_COHERENT_INT_MASK |	\
++	 RX0_COHERENT_INT_MASK | RX1_COHERENT_INT_MASK |	\
++	 RX2_COHERENT_INT_MASK | RX3_COHERENT_INT_MASK |	\
++	 RX4_COHERENT_INT_MASK | RX7_COHERENT_INT_MASK |	\
++	 RX8_COHERENT_INT_MASK | RX9_COHERENT_INT_MASK |	\
++	 RX15_COHERENT_INT_MASK | INT_TX_MASK)
++
++/* QDMA_CSR_INT_ENABLE2 */
++#define RX15_NO_CPU_DSCP_INT_MASK	BIT(31)
++#define RX14_NO_CPU_DSCP_INT_MASK	BIT(30)
++#define RX13_NO_CPU_DSCP_INT_MASK	BIT(29)
++#define RX12_NO_CPU_DSCP_INT_MASK	BIT(28)
++#define RX11_NO_CPU_DSCP_INT_MASK	BIT(27)
++#define RX10_NO_CPU_DSCP_INT_MASK	BIT(26)
++#define RX9_NO_CPU_DSCP_INT_MASK	BIT(25)
++#define RX8_NO_CPU_DSCP_INT_MASK	BIT(24)
++#define RX7_NO_CPU_DSCP_INT_MASK	BIT(23)
++#define RX6_NO_CPU_DSCP_INT_MASK	BIT(22)
++#define RX5_NO_CPU_DSCP_INT_MASK	BIT(21)
++#define RX4_NO_CPU_DSCP_INT_MASK	BIT(20)
++#define RX3_NO_CPU_DSCP_INT_MASK	BIT(19)
++#define RX2_NO_CPU_DSCP_INT_MASK	BIT(18)
++#define RX1_NO_CPU_DSCP_INT_MASK	BIT(17)
++#define RX0_NO_CPU_DSCP_INT_MASK	BIT(16)
++#define RX15_DONE_INT_MASK		BIT(15)
++#define RX14_DONE_INT_MASK		BIT(14)
++#define RX13_DONE_INT_MASK		BIT(13)
++#define RX12_DONE_INT_MASK		BIT(12)
++#define RX11_DONE_INT_MASK		BIT(11)
++#define RX10_DONE_INT_MASK		BIT(10)
++#define RX9_DONE_INT_MASK		BIT(9)
++#define RX8_DONE_INT_MASK		BIT(8)
++#define RX7_DONE_INT_MASK		BIT(7)
++#define RX6_DONE_INT_MASK		BIT(6)
++#define RX5_DONE_INT_MASK		BIT(5)
++#define RX4_DONE_INT_MASK		BIT(4)
++#define RX3_DONE_INT_MASK		BIT(3)
++#define RX2_DONE_INT_MASK		BIT(2)
++#define RX1_DONE_INT_MASK		BIT(1)
++#define RX0_DONE_INT_MASK		BIT(0)
++
++#define RX_DONE_INT_MASK					\
++	(RX0_DONE_INT_MASK | RX1_DONE_INT_MASK |		\
++	 RX2_DONE_INT_MASK | RX3_DONE_INT_MASK |		\
++	 RX4_DONE_INT_MASK | RX7_DONE_INT_MASK |		\
++	 RX8_DONE_INT_MASK | RX9_DONE_INT_MASK |		\
++	 RX15_DONE_INT_MASK)
++#define INT_IDX1_MASK						\
++	(RX_DONE_INT_MASK |					\
++	 RX0_NO_CPU_DSCP_INT_MASK | RX1_NO_CPU_DSCP_INT_MASK |	\
++	 RX2_NO_CPU_DSCP_INT_MASK | RX3_NO_CPU_DSCP_INT_MASK |	\
++	 RX4_NO_CPU_DSCP_INT_MASK | RX7_NO_CPU_DSCP_INT_MASK |	\
++	 RX8_NO_CPU_DSCP_INT_MASK | RX9_NO_CPU_DSCP_INT_MASK |	\
++	 RX15_NO_CPU_DSCP_INT_MASK)
++
++/* QDMA_CSR_INT_ENABLE5 */
++#define TX31_COHERENT_INT_MASK		BIT(31)
++#define TX30_COHERENT_INT_MASK		BIT(30)
++#define TX29_COHERENT_INT_MASK		BIT(29)
++#define TX28_COHERENT_INT_MASK		BIT(28)
++#define TX27_COHERENT_INT_MASK		BIT(27)
++#define TX26_COHERENT_INT_MASK		BIT(26)
++#define TX25_COHERENT_INT_MASK		BIT(25)
++#define TX24_COHERENT_INT_MASK		BIT(24)
++#define TX23_COHERENT_INT_MASK		BIT(23)
++#define TX22_COHERENT_INT_MASK		BIT(22)
++#define TX21_COHERENT_INT_MASK		BIT(21)
++#define TX20_COHERENT_INT_MASK		BIT(20)
++#define TX19_COHERENT_INT_MASK		BIT(19)
++#define TX18_COHERENT_INT_MASK		BIT(18)
++#define TX17_COHERENT_INT_MASK		BIT(17)
++#define TX16_COHERENT_INT_MASK		BIT(16)
++#define TX15_COHERENT_INT_MASK		BIT(15)
++#define TX14_COHERENT_INT_MASK		BIT(14)
++#define TX13_COHERENT_INT_MASK		BIT(13)
++#define TX12_COHERENT_INT_MASK		BIT(12)
++#define TX11_COHERENT_INT_MASK		BIT(11)
++#define TX10_COHERENT_INT_MASK		BIT(10)
++#define TX9_COHERENT_INT_MASK		BIT(9)
++#define TX8_COHERENT_INT_MASK		BIT(8)
++
++#define INT_IDX4_MASK						\
++	(TX8_COHERENT_INT_MASK | TX9_COHERENT_INT_MASK |	\
++	 TX10_COHERENT_INT_MASK | TX11_COHERENT_INT_MASK |	\
++	 TX12_COHERENT_INT_MASK | TX13_COHERENT_INT_MASK |	\
++	 TX14_COHERENT_INT_MASK | TX15_COHERENT_INT_MASK |	\
++	 TX16_COHERENT_INT_MASK | TX17_COHERENT_INT_MASK |	\
++	 TX18_COHERENT_INT_MASK | TX19_COHERENT_INT_MASK |	\
++	 TX20_COHERENT_INT_MASK | TX21_COHERENT_INT_MASK |	\
++	 TX22_COHERENT_INT_MASK | TX23_COHERENT_INT_MASK |	\
++	 TX24_COHERENT_INT_MASK | TX25_COHERENT_INT_MASK |	\
++	 TX26_COHERENT_INT_MASK | TX27_COHERENT_INT_MASK |	\
++	 TX28_COHERENT_INT_MASK | TX29_COHERENT_INT_MASK |	\
++	 TX30_COHERENT_INT_MASK | TX31_COHERENT_INT_MASK)
++
++#define REG_TX_IRQ_BASE(_n)		((_n) ? 0x0048 : 0x0050)
++
++#define REG_TX_IRQ_CFG(_n)		((_n) ? 0x004c : 0x0054)
++#define TX_IRQ_THR_MASK			GENMASK(27, 16)
++#define TX_IRQ_DEPTH_MASK		GENMASK(11, 0)
++
++#define REG_IRQ_CLEAR_LEN(_n)		((_n) ? 0x0064 : 0x0058)
++#define IRQ_CLEAR_LEN_MASK		GENMASK(7, 0)
++
++#define REG_IRQ_STATUS(_n)		((_n) ? 0x0068 : 0x005c)
++#define IRQ_ENTRY_LEN_MASK		GENMASK(27, 16)
++#define IRQ_HEAD_IDX_MASK		GENMASK(11, 0)
++
++#define REG_TX_RING_BASE(_n)	\
++	(((_n) < 8) ? 0x0100 + ((_n) << 5) : 0x0b00 + (((_n) - 8) << 5))
++
++#define REG_TX_RING_BLOCKING(_n)	\
++	(((_n) < 8) ? 0x0104 + ((_n) << 5) : 0x0b04 + (((_n) - 8) << 5))
++
++#define TX_RING_IRQ_BLOCKING_MAP_MASK			BIT(6)
++#define TX_RING_IRQ_BLOCKING_CFG_MASK			BIT(4)
++#define TX_RING_IRQ_BLOCKING_TX_DROP_EN_MASK		BIT(2)
++#define TX_RING_IRQ_BLOCKING_MAX_TH_TXRING_EN_MASK	BIT(1)
++#define TX_RING_IRQ_BLOCKING_MIN_TH_TXRING_EN_MASK	BIT(0)
++
++#define REG_TX_CPU_IDX(_n)	\
++	(((_n) < 8) ? 0x0108 + ((_n) << 5) : 0x0b08 + (((_n) - 8) << 5))
++
++#define TX_RING_CPU_IDX_MASK		GENMASK(15, 0)
++
++#define REG_TX_DMA_IDX(_n)	\
++	(((_n) < 8) ? 0x010c + ((_n) << 5) : 0x0b0c + (((_n) - 8) << 5))
++
++#define TX_RING_DMA_IDX_MASK		GENMASK(15, 0)
++
++#define IRQ_RING_IDX_MASK		GENMASK(20, 16)
++#define IRQ_DESC_IDX_MASK		GENMASK(15, 0)
++
++#define REG_RX_RING_BASE(_n)	\
++	(((_n) < 16) ? 0x0200 + ((_n) << 5) : 0x0e00 + (((_n) - 16) << 5))
++
++#define REG_RX_RING_SIZE(_n)	\
++	(((_n) < 16) ? 0x0204 + ((_n) << 5) : 0x0e04 + (((_n) - 16) << 5))
++
++#define RX_RING_THR_MASK		GENMASK(31, 16)
++#define RX_RING_SIZE_MASK		GENMASK(15, 0)
++
++#define REG_RX_CPU_IDX(_n)	\
++	(((_n) < 16) ? 0x0208 + ((_n) << 5) : 0x0e08 + (((_n) - 16) << 5))
++
++#define RX_RING_CPU_IDX_MASK		GENMASK(15, 0)
++
++#define REG_RX_DMA_IDX(_n)	\
++	(((_n) < 16) ? 0x020c + ((_n) << 5) : 0x0e0c + (((_n) - 16) << 5))
++
++#define REG_RX_DELAY_INT_IDX(_n)	\
++	(((_n) < 16) ? 0x0210 + ((_n) << 5) : 0x0e10 + (((_n) - 16) << 5))
++
++#define RX_DELAY_INT_MASK		GENMASK(15, 0)
++
++#define RX_RING_DMA_IDX_MASK		GENMASK(15, 0)
++
++#define REG_INGRESS_TRTCM_CFG		0x0070
++#define INGRESS_TRTCM_EN_MASK		BIT(31)
++#define INGRESS_TRTCM_MODE_MASK		BIT(30)
++#define INGRESS_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
++#define INGRESS_FAST_TICK_MASK		GENMASK(15, 0)
++
++#define REG_QUEUE_CLOSE_CFG(_n)		(0x00a0 + ((_n) & 0xfc))
++#define TXQ_DISABLE_CHAN_QUEUE_MASK(_n, _m)	BIT((_m) + (((_n) & 0x3) << 3))
++
++#define REG_TXQ_DIS_CFG_BASE(_n)	((_n) ? 0x20a0 : 0x00a0)
++#define REG_TXQ_DIS_CFG(_n, _m)		(REG_TXQ_DIS_CFG_BASE((_n)) + (_m) << 2)
++
++#define REG_CNTR_CFG(_n)		(0x0400 + ((_n) << 3))
++#define CNTR_EN_MASK			BIT(31)
++#define CNTR_ALL_CHAN_EN_MASK		BIT(30)
++#define CNTR_ALL_QUEUE_EN_MASK		BIT(29)
++#define CNTR_ALL_DSCP_RING_EN_MASK	BIT(28)
++#define CNTR_SRC_MASK			GENMASK(27, 24)
++#define CNTR_DSCP_RING_MASK		GENMASK(20, 16)
++#define CNTR_CHAN_MASK			GENMASK(7, 3)
++#define CNTR_QUEUE_MASK			GENMASK(2, 0)
++
++#define REG_CNTR_VAL(_n)		(0x0404 + ((_n) << 3))
++
++#define REG_LMGR_INIT_CFG		0x1000
++#define LMGR_INIT_START			BIT(31)
++#define LMGR_SRAM_MODE_MASK		BIT(30)
++#define HW_FWD_PKTSIZE_OVERHEAD_MASK	GENMASK(27, 20)
++#define HW_FWD_DESC_NUM_MASK		GENMASK(16, 0)
++
++#define REG_FWD_DSCP_LOW_THR		0x1004
++#define FWD_DSCP_LOW_THR_MASK		GENMASK(17, 0)
++
++#define REG_EGRESS_RATE_METER_CFG		0x100c
++#define EGRESS_RATE_METER_EN_MASK		BIT(31)
++#define EGRESS_RATE_METER_EQ_RATE_EN_MASK	BIT(17)
++#define EGRESS_RATE_METER_WINDOW_SZ_MASK	GENMASK(16, 12)
++#define EGRESS_RATE_METER_TIMESLICE_MASK	GENMASK(10, 0)
++
++#define REG_EGRESS_TRTCM_CFG		0x1010
++#define EGRESS_TRTCM_EN_MASK		BIT(31)
++#define EGRESS_TRTCM_MODE_MASK		BIT(30)
++#define EGRESS_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
++#define EGRESS_FAST_TICK_MASK		GENMASK(15, 0)
++
++#define TRTCM_PARAM_RW_MASK		BIT(31)
++#define TRTCM_PARAM_RW_DONE_MASK	BIT(30)
++#define TRTCM_PARAM_TYPE_MASK		GENMASK(29, 28)
++#define TRTCM_METER_GROUP_MASK		GENMASK(27, 26)
++#define TRTCM_PARAM_INDEX_MASK		GENMASK(23, 17)
++#define TRTCM_PARAM_RATE_TYPE_MASK	BIT(16)
++
++#define REG_TRTCM_CFG_PARAM(_n)		((_n) + 0x4)
++#define REG_TRTCM_DATA_LOW(_n)		((_n) + 0x8)
++#define REG_TRTCM_DATA_HIGH(_n)		((_n) + 0xc)
++
++#define REG_TXWRR_MODE_CFG		0x1020
++#define TWRR_WEIGHT_SCALE_MASK		BIT(31)
++#define TWRR_WEIGHT_BASE_MASK		BIT(3)
++
++#define REG_TXWRR_WEIGHT_CFG		0x1024
++#define TWRR_RW_CMD_MASK		BIT(31)
++#define TWRR_RW_CMD_DONE		BIT(30)
++#define TWRR_CHAN_IDX_MASK		GENMASK(23, 19)
++#define TWRR_QUEUE_IDX_MASK		GENMASK(18, 16)
++#define TWRR_VALUE_MASK			GENMASK(15, 0)
++
++#define REG_PSE_BUF_USAGE_CFG		0x1028
++#define PSE_BUF_ESTIMATE_EN_MASK	BIT(29)
++
++#define REG_CHAN_QOS_MODE(_n)		(0x1040 + ((_n) << 2))
++#define CHAN_QOS_MODE_MASK(_n)		GENMASK(2 + ((_n) << 2), (_n) << 2)
++
++#define REG_GLB_TRTCM_CFG		0x1080
++#define GLB_TRTCM_EN_MASK		BIT(31)
++#define GLB_TRTCM_MODE_MASK		BIT(30)
++#define GLB_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
++#define GLB_FAST_TICK_MASK		GENMASK(15, 0)
++
++#define REG_TXQ_CNGST_CFG		0x10a0
++#define TXQ_CNGST_DROP_EN		BIT(31)
++#define TXQ_CNGST_DEI_DROP_EN		BIT(30)
++
++#define REG_SLA_TRTCM_CFG		0x1150
++#define SLA_TRTCM_EN_MASK		BIT(31)
++#define SLA_TRTCM_MODE_MASK		BIT(30)
++#define SLA_SLOW_TICK_RATIO_MASK	GENMASK(29, 16)
++#define SLA_FAST_TICK_MASK		GENMASK(15, 0)
++
++/* CTRL */
++#define QDMA_DESC_DONE_MASK		BIT(31)
++#define QDMA_DESC_DROP_MASK		BIT(30) /* tx: drop - rx: overflow */
++#define QDMA_DESC_MORE_MASK		BIT(29) /* more SG elements */
++#define QDMA_DESC_DEI_MASK		BIT(25)
++#define QDMA_DESC_NO_DROP_MASK		BIT(24)
++#define QDMA_DESC_LEN_MASK		GENMASK(15, 0)
++/* DATA */
++#define QDMA_DESC_NEXT_ID_MASK		GENMASK(15, 0)
++/* TX MSG0 */
++#define QDMA_ETH_TXMSG_MIC_IDX_MASK	BIT(30)
++#define QDMA_ETH_TXMSG_SP_TAG_MASK	GENMASK(29, 14)
++#define QDMA_ETH_TXMSG_ICO_MASK		BIT(13)
++#define QDMA_ETH_TXMSG_UCO_MASK		BIT(12)
++#define QDMA_ETH_TXMSG_TCO_MASK		BIT(11)
++#define QDMA_ETH_TXMSG_TSO_MASK		BIT(10)
++#define QDMA_ETH_TXMSG_FAST_MASK	BIT(9)
++#define QDMA_ETH_TXMSG_OAM_MASK		BIT(8)
++#define QDMA_ETH_TXMSG_CHAN_MASK	GENMASK(7, 3)
++#define QDMA_ETH_TXMSG_QUEUE_MASK	GENMASK(2, 0)
++/* TX MSG1 */
++#define QDMA_ETH_TXMSG_NO_DROP		BIT(31)
++#define QDMA_ETH_TXMSG_METER_MASK	GENMASK(30, 24)	/* 0x7f no meters */
++#define QDMA_ETH_TXMSG_FPORT_MASK	GENMASK(23, 20)
++#define QDMA_ETH_TXMSG_NBOQ_MASK	GENMASK(19, 15)
++#define QDMA_ETH_TXMSG_HWF_MASK		BIT(14)
++#define QDMA_ETH_TXMSG_HOP_MASK		BIT(13)
++#define QDMA_ETH_TXMSG_PTP_MASK		BIT(12)
++#define QDMA_ETH_TXMSG_ACNT_G1_MASK	GENMASK(10, 6)	/* 0x1f do not count */
++#define QDMA_ETH_TXMSG_ACNT_G0_MASK	GENMASK(5, 0)	/* 0x3f do not count */
++
++/* RX MSG1 */
++#define QDMA_ETH_RXMSG_DEI_MASK		BIT(31)
++#define QDMA_ETH_RXMSG_IP6_MASK		BIT(30)
++#define QDMA_ETH_RXMSG_IP4_MASK		BIT(29)
++#define QDMA_ETH_RXMSG_IP4F_MASK	BIT(28)
++#define QDMA_ETH_RXMSG_L4_VALID_MASK	BIT(27)
++#define QDMA_ETH_RXMSG_L4F_MASK		BIT(26)
++#define QDMA_ETH_RXMSG_SPORT_MASK	GENMASK(25, 21)
++#define QDMA_ETH_RXMSG_CRSN_MASK	GENMASK(20, 16)
++#define QDMA_ETH_RXMSG_PPE_ENTRY_MASK	GENMASK(15, 0)
++
++struct airoha_qdma_desc {
++	__le32 rsv;
++	__le32 ctrl;
++	__le32 addr;
++	__le32 data;
++	__le32 msg0;
++	__le32 msg1;
++	__le32 msg2;
++	__le32 msg3;
++};
++
++/* CTRL0 */
++#define QDMA_FWD_DESC_CTX_MASK		BIT(31)
++#define QDMA_FWD_DESC_RING_MASK		GENMASK(30, 28)
++#define QDMA_FWD_DESC_IDX_MASK		GENMASK(27, 16)
++#define QDMA_FWD_DESC_LEN_MASK		GENMASK(15, 0)
++/* CTRL1 */
++#define QDMA_FWD_DESC_FIRST_IDX_MASK	GENMASK(15, 0)
++/* CTRL2 */
++#define QDMA_FWD_DESC_MORE_PKT_NUM_MASK	GENMASK(2, 0)
++
++struct airoha_qdma_fwd_desc {
++	__le32 addr;
++	__le32 ctrl0;
++	__le32 ctrl1;
++	__le32 ctrl2;
++	__le32 msg0;
++	__le32 msg1;
++	__le32 rsv0;
++	__le32 rsv1;
++};
++
++#endif /* AIROHA_REGS_H */

+ 287 - 0
target/linux/airoha/patches-6.6/048-05-v6.15-net-airoha-Move-DSA-tag-in-DMA-descriptor.patch

@@ -0,0 +1,287 @@
+From af3cf757d5c99011b9b94ea8d78aeaccc0153fdc Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <[email protected]>
+Date: Fri, 28 Feb 2025 11:54:13 +0100
+Subject: [PATCH 05/15] net: airoha: Move DSA tag in DMA descriptor
+
+Packet Processor Engine (PPE) module reads DSA tags from the DMA descriptor
+and requires untagged DSA packets to properly parse them. Move DSA tag
+in the DMA descriptor on TX side and read DSA tag from DMA descriptor
+on RX side. In order to avoid skb reallocation, store tag in skb_dst on
+RX side.
+This is a preliminary patch to enable netfilter flowtable hw offloading
+on EN7581 SoC.
+
+Tested-by: Sayantan Nandy <[email protected]>
+Signed-off-by: Lorenzo Bianconi <[email protected]>
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c  | 125 ++++++++++++++++++++--
+ drivers/net/ethernet/airoha/airoha_eth.h  |   7 ++
+ drivers/net/ethernet/airoha/airoha_regs.h |   2 +
+ 3 files changed, 128 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -9,6 +9,7 @@
+ #include <linux/tcp.h>
+ #include <linux/u64_stats_sync.h>
+ #include <net/dsa.h>
++#include <net/dst_metadata.h>
+ #include <net/page_pool/helpers.h>
+ #include <net/pkt_cls.h>
+ #include <uapi/linux/ppp_defs.h>
+@@ -656,6 +657,7 @@ static int airoha_qdma_rx_process(struct
+ 		struct airoha_qdma_desc *desc = &q->desc[q->tail];
+ 		dma_addr_t dma_addr = le32_to_cpu(desc->addr);
+ 		u32 desc_ctrl = le32_to_cpu(desc->ctrl);
++		struct airoha_gdm_port *port;
+ 		struct sk_buff *skb;
+ 		int len, p;
+ 
+@@ -683,6 +685,7 @@ static int airoha_qdma_rx_process(struct
+ 			continue;
+ 		}
+ 
++		port = eth->ports[p];
+ 		skb = napi_build_skb(e->buf, q->buf_size);
+ 		if (!skb) {
+ 			page_pool_put_full_page(q->page_pool,
+@@ -694,10 +697,26 @@ static int airoha_qdma_rx_process(struct
+ 		skb_reserve(skb, 2);
+ 		__skb_put(skb, len);
+ 		skb_mark_for_recycle(skb);
+-		skb->dev = eth->ports[p]->dev;
++		skb->dev = port->dev;
+ 		skb->protocol = eth_type_trans(skb, skb->dev);
+ 		skb->ip_summed = CHECKSUM_UNNECESSARY;
+ 		skb_record_rx_queue(skb, qid);
++
++		if (netdev_uses_dsa(port->dev)) {
++			/* PPE module requires untagged packets to work
++			 * properly and it provides DSA port index via the
++			 * DMA descriptor. Report DSA tag to the DSA stack
++			 * via skb dst info.
++			 */
++			u32 sptag = FIELD_GET(QDMA_ETH_RXMSG_SPTAG,
++					      le32_to_cpu(desc->msg0));
++
++			if (sptag < ARRAY_SIZE(port->dsa_meta) &&
++			    port->dsa_meta[sptag])
++				skb_dst_set_noref(skb,
++						  &port->dsa_meta[sptag]->dst);
++		}
++
+ 		napi_gro_receive(&q->napi, skb);
+ 
+ 		done++;
+@@ -1637,25 +1656,76 @@ static u16 airoha_dev_select_queue(struc
+ 	return queue < dev->num_tx_queues ? queue : 0;
+ }
+ 
++static u32 airoha_get_dsa_tag(struct sk_buff *skb, struct net_device *dev)
++{
++#if IS_ENABLED(CONFIG_NET_DSA)
++	struct ethhdr *ehdr;
++	struct dsa_port *dp;
++	u8 xmit_tpid;
++	u16 tag;
++
++	if (!netdev_uses_dsa(dev))
++		return 0;
++
++	dp = dev->dsa_ptr;
++	if (IS_ERR(dp))
++		return 0;
++
++	if (dp->tag_ops->proto != DSA_TAG_PROTO_MTK)
++		return 0;
++
++	if (skb_cow_head(skb, 0))
++		return 0;
++
++	ehdr = (struct ethhdr *)skb->data;
++	tag = be16_to_cpu(ehdr->h_proto);
++	xmit_tpid = tag >> 8;
++
++	switch (xmit_tpid) {
++	case MTK_HDR_XMIT_TAGGED_TPID_8100:
++		ehdr->h_proto = cpu_to_be16(ETH_P_8021Q);
++		tag &= ~(MTK_HDR_XMIT_TAGGED_TPID_8100 << 8);
++		break;
++	case MTK_HDR_XMIT_TAGGED_TPID_88A8:
++		ehdr->h_proto = cpu_to_be16(ETH_P_8021AD);
++		tag &= ~(MTK_HDR_XMIT_TAGGED_TPID_88A8 << 8);
++		break;
++	default:
++		/* PPE module requires untagged DSA packets to work properly,
++		 * so move DSA tag to DMA descriptor.
++		 */
++		memmove(skb->data + MTK_HDR_LEN, skb->data, 2 * ETH_ALEN);
++		__skb_pull(skb, MTK_HDR_LEN);
++		break;
++	}
++
++	return tag;
++#else
++	return 0;
++#endif
++}
++
+ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
+ 				   struct net_device *dev)
+ {
+ 	struct airoha_gdm_port *port = netdev_priv(dev);
+-	u32 nr_frags = 1 + skb_shinfo(skb)->nr_frags;
+-	u32 msg0, msg1, len = skb_headlen(skb);
+ 	struct airoha_qdma *qdma = port->qdma;
++	u32 nr_frags, tag, msg0, msg1, len;
+ 	struct netdev_queue *txq;
+ 	struct airoha_queue *q;
+-	void *data = skb->data;
++	void *data;
+ 	int i, qid;
+ 	u16 index;
+ 	u8 fport;
+ 
+ 	qid = skb_get_queue_mapping(skb) % ARRAY_SIZE(qdma->q_tx);
++	tag = airoha_get_dsa_tag(skb, dev);
++
+ 	msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
+ 			  qid / AIROHA_NUM_QOS_QUEUES) |
+ 	       FIELD_PREP(QDMA_ETH_TXMSG_QUEUE_MASK,
+-			  qid % AIROHA_NUM_QOS_QUEUES);
++			  qid % AIROHA_NUM_QOS_QUEUES) |
++	       FIELD_PREP(QDMA_ETH_TXMSG_SP_TAG_MASK, tag);
+ 	if (skb->ip_summed == CHECKSUM_PARTIAL)
+ 		msg0 |= FIELD_PREP(QDMA_ETH_TXMSG_TCO_MASK, 1) |
+ 			FIELD_PREP(QDMA_ETH_TXMSG_UCO_MASK, 1) |
+@@ -1686,6 +1756,8 @@ static netdev_tx_t airoha_dev_xmit(struc
+ 	spin_lock_bh(&q->lock);
+ 
+ 	txq = netdev_get_tx_queue(dev, qid);
++	nr_frags = 1 + skb_shinfo(skb)->nr_frags;
++
+ 	if (q->queued + nr_frags > q->ndesc) {
+ 		/* not enough space in the queue */
+ 		netif_tx_stop_queue(txq);
+@@ -1693,7 +1765,10 @@ static netdev_tx_t airoha_dev_xmit(struc
+ 		return NETDEV_TX_BUSY;
+ 	}
+ 
++	len = skb_headlen(skb);
++	data = skb->data;
+ 	index = q->head;
++
+ 	for (i = 0; i < nr_frags; i++) {
+ 		struct airoha_qdma_desc *desc = &q->desc[index];
+ 		struct airoha_queue_entry *e = &q->entry[index];
+@@ -2224,6 +2299,37 @@ static const struct ethtool_ops airoha_e
+ 	.get_rmon_stats		= airoha_ethtool_get_rmon_stats,
+ };
+ 
++static int airoha_metadata_dst_alloc(struct airoha_gdm_port *port)
++{
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(port->dsa_meta); i++) {
++		struct metadata_dst *md_dst;
++
++		md_dst = metadata_dst_alloc(0, METADATA_HW_PORT_MUX,
++					    GFP_KERNEL);
++		if (!md_dst)
++			return -ENOMEM;
++
++		md_dst->u.port_info.port_id = i;
++		port->dsa_meta[i] = md_dst;
++	}
++
++	return 0;
++}
++
++static void airoha_metadata_dst_free(struct airoha_gdm_port *port)
++{
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(port->dsa_meta); i++) {
++		if (!port->dsa_meta[i])
++			continue;
++
++		metadata_dst_free(port->dsa_meta[i]);
++	}
++}
++
+ static int airoha_alloc_gdm_port(struct airoha_eth *eth, struct device_node *np)
+ {
+ 	const __be32 *id_ptr = of_get_property(np, "reg", NULL);
+@@ -2296,6 +2402,10 @@ static int airoha_alloc_gdm_port(struct
+ 	port->id = id;
+ 	eth->ports[index] = port;
+ 
++	err = airoha_metadata_dst_alloc(port);
++	if (err)
++		return err;
++
+ 	return register_netdev(dev);
+ }
+ 
+@@ -2388,8 +2498,10 @@ error_hw_cleanup:
+ 	for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ 		struct airoha_gdm_port *port = eth->ports[i];
+ 
+-		if (port && port->dev->reg_state == NETREG_REGISTERED)
++		if (port && port->dev->reg_state == NETREG_REGISTERED) {
+ 			unregister_netdev(port->dev);
++			airoha_metadata_dst_free(port);
++		}
+ 	}
+ 	free_netdev(eth->napi_dev);
+ 	platform_set_drvdata(pdev, NULL);
+@@ -2415,6 +2527,7 @@ static void airoha_remove(struct platfor
+ 
+ 		airoha_dev_stop(port->dev);
+ 		unregister_netdev(port->dev);
++		airoha_metadata_dst_free(port);
+ 	}
+ 	free_netdev(eth->napi_dev);
+ 
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -15,6 +15,7 @@
+ 
+ #define AIROHA_MAX_NUM_GDM_PORTS	1
+ #define AIROHA_MAX_NUM_QDMA		2
++#define AIROHA_MAX_DSA_PORTS		7
+ #define AIROHA_MAX_NUM_RSTS		3
+ #define AIROHA_MAX_NUM_XSI_RSTS		5
+ #define AIROHA_MAX_MTU			2000
+@@ -43,6 +44,10 @@
+ #define QDMA_METER_IDX(_n)		((_n) & 0xff)
+ #define QDMA_METER_GROUP(_n)		(((_n) >> 8) & 0x3)
+ 
++#define MTK_HDR_LEN			4
++#define MTK_HDR_XMIT_TAGGED_TPID_8100	1
++#define MTK_HDR_XMIT_TAGGED_TPID_88A8	2
++
+ enum {
+ 	QDMA_INT_REG_IDX0,
+ 	QDMA_INT_REG_IDX1,
+@@ -231,6 +236,8 @@ struct airoha_gdm_port {
+ 	/* qos stats counters */
+ 	u64 cpu_tx_packets;
+ 	u64 fwd_tx_packets;
++
++	struct metadata_dst *dsa_meta[AIROHA_MAX_DSA_PORTS];
+ };
+ 
+ struct airoha_eth {
+--- a/drivers/net/ethernet/airoha/airoha_regs.h
++++ b/drivers/net/ethernet/airoha/airoha_regs.h
+@@ -624,6 +624,8 @@
+ #define QDMA_ETH_TXMSG_ACNT_G1_MASK	GENMASK(10, 6)	/* 0x1f do not count */
+ #define QDMA_ETH_TXMSG_ACNT_G0_MASK	GENMASK(5, 0)	/* 0x3f do not count */
+ 
++/* RX MSG0 */
++#define QDMA_ETH_RXMSG_SPTAG		GENMASK(21, 14)
+ /* RX MSG1 */
+ #define QDMA_ETH_RXMSG_DEI_MASK		BIT(31)
+ #define QDMA_ETH_RXMSG_IP6_MASK		BIT(30)

+ 46 - 0
target/linux/airoha/patches-6.6/048-06-v6.15-net-dsa-mt7530-Enable-Rx-sptag-for-EN7581-SoC.patch

@@ -0,0 +1,46 @@
+From ab667db1e6014634c6607ebdddc16c1b8394a935 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <[email protected]>
+Date: Fri, 28 Feb 2025 11:54:14 +0100
+Subject: [PATCH 06/15] net: dsa: mt7530: Enable Rx sptag for EN7581 SoC
+
+Packet Processor Engine (PPE) module used for hw acceleration on EN7581
+mac block, in order to properly parse packets, requires DSA untagged
+packets on TX side and read DSA tag from DMA descriptor on RX side.
+For this reason, enable RX Special Tag (SPTAG) for EN7581 SoC.
+This is a preliminary patch to enable netfilter flowtable hw offloading
+on EN7581 SoC.
+
+Signed-off-by: Lorenzo Bianconi <[email protected]>
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/dsa/mt7530.c | 5 +++++
+ drivers/net/dsa/mt7530.h | 4 ++++
+ 2 files changed, 9 insertions(+)
+
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -2585,6 +2585,11 @@ mt7531_setup_common(struct dsa_switch *d
+ 	/* Allow mirroring frames received on the local port (monitor port). */
+ 	mt7530_set(priv, MT753X_AGC, LOCAL_EN);
+ 
++	/* Enable Special Tag for rx frames */
++	if (priv->id == ID_EN7581)
++		mt7530_write(priv, MT753X_CPORT_SPTAG_CFG,
++			     CPORT_SW2FE_STAG_EN | CPORT_FE2SW_STAG_EN);
++
+ 	/* Flush the FDB table */
+ 	ret = mt7530_fdb_cmd(priv, MT7530_FDB_FLUSH, NULL);
+ 	if (ret < 0)
+--- a/drivers/net/dsa/mt7530.h
++++ b/drivers/net/dsa/mt7530.h
+@@ -615,6 +615,10 @@ enum mt7531_xtal_fsel {
+ #define  MT7531_GPIO12_RG_RXD3_MASK	GENMASK(19, 16)
+ #define  MT7531_EXT_P_MDIO_12		(2 << 16)
+ 
++#define MT753X_CPORT_SPTAG_CFG		0x7c10
++#define  CPORT_SW2FE_STAG_EN		BIT(1)
++#define  CPORT_FE2SW_STAG_EN		BIT(0)
++
+ /* Registers for LED GPIO control (MT7530 only)
+  * All registers follow this pattern:
+  * [ 2: 0]  port 0

+ 144 - 0
target/linux/airoha/patches-6.6/048-07-v6.15-net-airoha-Enable-support-for-multiple-net_devices.patch

@@ -0,0 +1,144 @@
+From 80369686737fe07c233a1152da0b84372dabdcd6 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <[email protected]>
+Date: Fri, 28 Feb 2025 11:54:15 +0100
+Subject: [PATCH 07/15] net: airoha: Enable support for multiple net_devices
+
+In the current codebase airoha_eth driver supports just a single
+net_device connected to the Packet Switch Engine (PSE) lan port (GDM1).
+As shown in commit 23020f049327 ("net: airoha: Introduce ethernet
+support for EN7581 SoC"), PSE can switch packets between four GDM ports.
+Enable the capability to create a net_device for each GDM port of the
+PSE module. Moreover, since the QDMA blocks can be shared between
+net_devices, do not stop TX/RX DMA in airoha_dev_stop() if there are
+active net_devices for this QDMA block.
+This is a preliminary patch to enable flowtable hw offloading for EN7581
+SoC.
+
+Co-developed-by: Christian Marangi <[email protected]>
+Signed-off-by: Christian Marangi <[email protected]>
+Signed-off-by: Lorenzo Bianconi <[email protected]>
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 35 ++++++++++++++----------
+ drivers/net/ethernet/airoha/airoha_eth.h |  4 ++-
+ 2 files changed, 24 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1563,6 +1563,7 @@ static int airoha_dev_open(struct net_de
+ 	airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
+ 			GLOBAL_CFG_TX_DMA_EN_MASK |
+ 			GLOBAL_CFG_RX_DMA_EN_MASK);
++	atomic_inc(&qdma->users);
+ 
+ 	return 0;
+ }
+@@ -1578,16 +1579,20 @@ static int airoha_dev_stop(struct net_de
+ 	if (err)
+ 		return err;
+ 
+-	airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
+-			  GLOBAL_CFG_TX_DMA_EN_MASK |
+-			  GLOBAL_CFG_RX_DMA_EN_MASK);
++	for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++)
++		netdev_tx_reset_subqueue(dev, i);
+ 
+-	for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
+-		if (!qdma->q_tx[i].ndesc)
+-			continue;
++	if (atomic_dec_and_test(&qdma->users)) {
++		airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
++				  GLOBAL_CFG_TX_DMA_EN_MASK |
++				  GLOBAL_CFG_RX_DMA_EN_MASK);
++
++		for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
++			if (!qdma->q_tx[i].ndesc)
++				continue;
+ 
+-		airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
+-		netdev_tx_reset_subqueue(dev, i);
++			airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
++		}
+ 	}
+ 
+ 	return 0;
+@@ -2330,13 +2335,14 @@ static void airoha_metadata_dst_free(str
+ 	}
+ }
+ 
+-static int airoha_alloc_gdm_port(struct airoha_eth *eth, struct device_node *np)
++static int airoha_alloc_gdm_port(struct airoha_eth *eth,
++				 struct device_node *np, int index)
+ {
+ 	const __be32 *id_ptr = of_get_property(np, "reg", NULL);
+ 	struct airoha_gdm_port *port;
+ 	struct airoha_qdma *qdma;
+ 	struct net_device *dev;
+-	int err, index;
++	int err, p;
+ 	u32 id;
+ 
+ 	if (!id_ptr) {
+@@ -2345,14 +2351,14 @@ static int airoha_alloc_gdm_port(struct
+ 	}
+ 
+ 	id = be32_to_cpup(id_ptr);
+-	index = id - 1;
++	p = id - 1;
+ 
+ 	if (!id || id > ARRAY_SIZE(eth->ports)) {
+ 		dev_err(eth->dev, "invalid gdm port id: %d\n", id);
+ 		return -EINVAL;
+ 	}
+ 
+-	if (eth->ports[index]) {
++	if (eth->ports[p]) {
+ 		dev_err(eth->dev, "duplicate gdm port id: %d\n", id);
+ 		return -EINVAL;
+ 	}
+@@ -2400,7 +2406,7 @@ static int airoha_alloc_gdm_port(struct
+ 	port->qdma = qdma;
+ 	port->dev = dev;
+ 	port->id = id;
+-	eth->ports[index] = port;
++	eth->ports[p] = port;
+ 
+ 	err = airoha_metadata_dst_alloc(port);
+ 	if (err)
+@@ -2472,6 +2478,7 @@ static int airoha_probe(struct platform_
+ 	for (i = 0; i < ARRAY_SIZE(eth->qdma); i++)
+ 		airoha_qdma_start_napi(&eth->qdma[i]);
+ 
++	i = 0;
+ 	for_each_child_of_node(pdev->dev.of_node, np) {
+ 		if (!of_device_is_compatible(np, "airoha,eth-mac"))
+ 			continue;
+@@ -2479,7 +2486,7 @@ static int airoha_probe(struct platform_
+ 		if (!of_device_is_available(np))
+ 			continue;
+ 
+-		err = airoha_alloc_gdm_port(eth, np);
++		err = airoha_alloc_gdm_port(eth, np, i++);
+ 		if (err) {
+ 			of_node_put(np);
+ 			goto error_napi_stop;
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -13,7 +13,7 @@
+ #include <linux/netdevice.h>
+ #include <linux/reset.h>
+ 
+-#define AIROHA_MAX_NUM_GDM_PORTS	1
++#define AIROHA_MAX_NUM_GDM_PORTS	4
+ #define AIROHA_MAX_NUM_QDMA		2
+ #define AIROHA_MAX_DSA_PORTS		7
+ #define AIROHA_MAX_NUM_RSTS		3
+@@ -212,6 +212,8 @@ struct airoha_qdma {
+ 	u32 irqmask[QDMA_INT_REG_MAX];
+ 	int irq;
+ 
++	atomic_t users;
++
+ 	struct airoha_tx_irq_queue q_tx_irq[AIROHA_NUM_TX_IRQ];
+ 
+ 	struct airoha_queue q_tx[AIROHA_NUM_TX_RING];

+ 77 - 0
target/linux/airoha/patches-6.6/048-08-v6.15-net-airoha-Move-REG_GDM_FWD_CFG-initialization-in-ai.patch

@@ -0,0 +1,77 @@
+From 67fde5d58cd43d129a979e918ec9cd5d2e2fbcfb Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <[email protected]>
+Date: Fri, 28 Feb 2025 11:54:16 +0100
+Subject: [PATCH 08/15] net: airoha: Move REG_GDM_FWD_CFG() initialization in
+ airoha_dev_init()
+
+Move REG_GDM_FWD_CFG() register initialization in airoha_dev_init
+routine. Moreover, always send traffic PPE module in order to be
+processed by hw accelerator.
+This is a preliminary patch to enable netfilter flowtable hw offloading
+on EN7581 SoC.
+
+Signed-off-by: Lorenzo Bianconi <[email protected]>
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 14 ++++----------
+ 1 file changed, 4 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -107,25 +107,20 @@ static void airoha_set_gdm_port_fwd_cfg(
+ 
+ static int airoha_set_gdm_port(struct airoha_eth *eth, int port, bool enable)
+ {
+-	u32 val = enable ? FE_PSE_PORT_PPE1 : FE_PSE_PORT_DROP;
+-	u32 vip_port, cfg_addr;
++	u32 vip_port;
+ 
+ 	switch (port) {
+ 	case XSI_PCIE0_PORT:
+ 		vip_port = XSI_PCIE0_VIP_PORT_MASK;
+-		cfg_addr = REG_GDM_FWD_CFG(3);
+ 		break;
+ 	case XSI_PCIE1_PORT:
+ 		vip_port = XSI_PCIE1_VIP_PORT_MASK;
+-		cfg_addr = REG_GDM_FWD_CFG(3);
+ 		break;
+ 	case XSI_USB_PORT:
+ 		vip_port = XSI_USB_VIP_PORT_MASK;
+-		cfg_addr = REG_GDM_FWD_CFG(4);
+ 		break;
+ 	case XSI_ETH_PORT:
+ 		vip_port = XSI_ETH_VIP_PORT_MASK;
+-		cfg_addr = REG_GDM_FWD_CFG(4);
+ 		break;
+ 	default:
+ 		return -EINVAL;
+@@ -139,8 +134,6 @@ static int airoha_set_gdm_port(struct ai
+ 		airoha_fe_clear(eth, REG_FE_IFC_PORT_EN, vip_port);
+ 	}
+ 
+-	airoha_set_gdm_port_fwd_cfg(eth, cfg_addr, val);
+-
+ 	return 0;
+ }
+ 
+@@ -177,8 +170,6 @@ static void airoha_fe_maccr_init(struct
+ 		airoha_fe_set(eth, REG_GDM_FWD_CFG(p),
+ 			      GDM_TCP_CKSUM | GDM_UDP_CKSUM | GDM_IP4_CKSUM |
+ 			      GDM_DROP_CRC_ERR);
+-		airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(p),
+-					    FE_PSE_PORT_CDM1);
+ 		airoha_fe_rmw(eth, REG_GDM_LEN_CFG(p),
+ 			      GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
+ 			      FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
+@@ -1615,8 +1606,11 @@ static int airoha_dev_set_macaddr(struct
+ static int airoha_dev_init(struct net_device *dev)
+ {
+ 	struct airoha_gdm_port *port = netdev_priv(dev);
++	struct airoha_eth *eth = port->qdma->eth;
+ 
+ 	airoha_set_macaddr(port, dev->dev_addr);
++	airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id),
++				    FE_PSE_PORT_PPE1);
+ 
+ 	return 0;
+ }

+ 120 - 0
target/linux/airoha/patches-6.6/048-09-v6.15-net-airoha-Rename-airoha_set_gdm_port_fwd_cfg-in-air.patch

@@ -0,0 +1,120 @@
+From c28b8375f6d02ef3b5e8c51234cc3f6d47d9fb7f Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <[email protected]>
+Date: Fri, 28 Feb 2025 11:54:17 +0100
+Subject: [PATCH 09/15] net: airoha: Rename airoha_set_gdm_port_fwd_cfg() in
+ airoha_set_vip_for_gdm_port()
+
+Rename airoha_set_gdm_port() in airoha_set_vip_for_gdm_port().
+Get rid of airoha_set_gdm_ports routine.
+
+Signed-off-by: Lorenzo Bianconi <[email protected]>
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 49 ++++++------------------
+ drivers/net/ethernet/airoha/airoha_eth.h |  8 ----
+ 2 files changed, 11 insertions(+), 46 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -105,25 +105,23 @@ static void airoha_set_gdm_port_fwd_cfg(
+ 		      FIELD_PREP(GDM_UCFQ_MASK, val));
+ }
+ 
+-static int airoha_set_gdm_port(struct airoha_eth *eth, int port, bool enable)
++static int airoha_set_vip_for_gdm_port(struct airoha_gdm_port *port,
++				       bool enable)
+ {
++	struct airoha_eth *eth = port->qdma->eth;
+ 	u32 vip_port;
+ 
+-	switch (port) {
+-	case XSI_PCIE0_PORT:
++	switch (port->id) {
++	case 3:
++		/* FIXME: handle XSI_PCIE1_PORT */
+ 		vip_port = XSI_PCIE0_VIP_PORT_MASK;
+ 		break;
+-	case XSI_PCIE1_PORT:
+-		vip_port = XSI_PCIE1_VIP_PORT_MASK;
+-		break;
+-	case XSI_USB_PORT:
+-		vip_port = XSI_USB_VIP_PORT_MASK;
+-		break;
+-	case XSI_ETH_PORT:
++	case 4:
++		/* FIXME: handle XSI_USB_PORT */
+ 		vip_port = XSI_ETH_VIP_PORT_MASK;
+ 		break;
+ 	default:
+-		return -EINVAL;
++		return 0;
+ 	}
+ 
+ 	if (enable) {
+@@ -137,31 +135,6 @@ static int airoha_set_gdm_port(struct ai
+ 	return 0;
+ }
+ 
+-static int airoha_set_gdm_ports(struct airoha_eth *eth, bool enable)
+-{
+-	const int port_list[] = {
+-		XSI_PCIE0_PORT,
+-		XSI_PCIE1_PORT,
+-		XSI_USB_PORT,
+-		XSI_ETH_PORT
+-	};
+-	int i, err;
+-
+-	for (i = 0; i < ARRAY_SIZE(port_list); i++) {
+-		err = airoha_set_gdm_port(eth, port_list[i], enable);
+-		if (err)
+-			goto error;
+-	}
+-
+-	return 0;
+-
+-error:
+-	for (i--; i >= 0; i--)
+-		airoha_set_gdm_port(eth, port_list[i], false);
+-
+-	return err;
+-}
+-
+ static void airoha_fe_maccr_init(struct airoha_eth *eth)
+ {
+ 	int p;
+@@ -1540,7 +1513,7 @@ static int airoha_dev_open(struct net_de
+ 	int err;
+ 
+ 	netif_tx_start_all_queues(dev);
+-	err = airoha_set_gdm_ports(qdma->eth, true);
++	err = airoha_set_vip_for_gdm_port(port, true);
+ 	if (err)
+ 		return err;
+ 
+@@ -1566,7 +1539,7 @@ static int airoha_dev_stop(struct net_de
+ 	int i, err;
+ 
+ 	netif_tx_disable(dev);
+-	err = airoha_set_gdm_ports(qdma->eth, false);
++	err = airoha_set_vip_for_gdm_port(port, false);
+ 	if (err)
+ 		return err;
+ 
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -58,14 +58,6 @@ enum {
+ };
+ 
+ enum {
+-	XSI_PCIE0_PORT,
+-	XSI_PCIE1_PORT,
+-	XSI_USB_PORT,
+-	XSI_AE_PORT,
+-	XSI_ETH_PORT,
+-};
+-
+-enum {
+ 	XSI_PCIE0_VIP_PORT_MASK	= BIT(22),
+ 	XSI_PCIE1_VIP_PORT_MASK	= BIT(23),
+ 	XSI_USB_VIP_PORT_MASK	= BIT(25),

+ 627 - 0
target/linux/airoha/patches-6.6/048-12-v6.15-net-airoha-Introduce-Airoha-NPU-support.patch

@@ -0,0 +1,627 @@
+From 23290c7bc190def4e1ca61610992d9b7c32e33f3 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <[email protected]>
+Date: Fri, 28 Feb 2025 11:54:20 +0100
+Subject: [PATCH 12/15] net: airoha: Introduce Airoha NPU support
+
+Packet Processor Engine (PPE) module available on EN7581 SoC populates
+the PPE table with 5-tuples flower rules learned from traffic forwarded
+between the GDM ports connected to the Packet Switch Engine (PSE) module.
+The airoha_eth driver can enable hw acceleration of learned 5-tuples
+rules if the user configure them in netfilter flowtable (netfilter
+flowtable support will be added with subsequent patches).
+airoha_eth driver configures and collects data from the PPE module via a
+Network Processor Unit (NPU) RISC-V module available on the EN7581 SoC.
+Introduce basic support for Airoha NPU module.
+
+Tested-by: Sayantan Nandy <[email protected]>
+Signed-off-by: Lorenzo Bianconi <[email protected]>
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/ethernet/airoha/Kconfig      |   9 +
+ drivers/net/ethernet/airoha/Makefile     |   1 +
+ drivers/net/ethernet/airoha/airoha_eth.h |   2 +
+ drivers/net/ethernet/airoha/airoha_npu.c | 520 +++++++++++++++++++++++
+ drivers/net/ethernet/airoha/airoha_npu.h |  34 ++
+ 5 files changed, 566 insertions(+)
+ create mode 100644 drivers/net/ethernet/airoha/airoha_npu.c
+ create mode 100644 drivers/net/ethernet/airoha/airoha_npu.h
+
+--- a/drivers/net/ethernet/airoha/Kconfig
++++ b/drivers/net/ethernet/airoha/Kconfig
+@@ -7,9 +7,18 @@ config NET_VENDOR_AIROHA
+ 
+ if NET_VENDOR_AIROHA
+ 
++config NET_AIROHA_NPU
++	tristate "Airoha NPU support"
++	select WANT_DEV_COREDUMP
++	select REGMAP_MMIO
++	help
++	  This driver supports Airoha Network Processor (NPU) available
++	  on the Airoha Soc family.
++
+ config NET_AIROHA
+ 	tristate "Airoha SoC Gigabit Ethernet support"
+ 	depends on NET_DSA || !NET_DSA
++	select NET_AIROHA_NPU
+ 	select PAGE_POOL
+ 	help
+ 	  This driver supports the gigabit ethernet MACs in the
+--- a/drivers/net/ethernet/airoha/Makefile
++++ b/drivers/net/ethernet/airoha/Makefile
+@@ -4,3 +4,4 @@
+ #
+ 
+ obj-$(CONFIG_NET_AIROHA) += airoha_eth.o
++obj-$(CONFIG_NET_AIROHA_NPU) += airoha_npu.o
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -240,6 +240,8 @@ struct airoha_eth {
+ 	unsigned long state;
+ 	void __iomem *fe_regs;
+ 
++	struct airoha_npu __rcu *npu;
++
+ 	struct reset_control_bulk_data rsts[AIROHA_MAX_NUM_RSTS];
+ 	struct reset_control_bulk_data xsi_rsts[AIROHA_MAX_NUM_XSI_RSTS];
+ 
+--- /dev/null
++++ b/drivers/net/ethernet/airoha/airoha_npu.c
+@@ -0,0 +1,520 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2025 AIROHA Inc
++ * Author: Lorenzo Bianconi <[email protected]>
++ */
++
++#include <linux/devcoredump.h>
++#include <linux/firmware.h>
++#include <linux/platform_device.h>
++#include <linux/of_net.h>
++#include <linux/of_platform.h>
++#include <linux/of_reserved_mem.h>
++#include <linux/regmap.h>
++
++#include "airoha_npu.h"
++
++#define NPU_EN7581_FIRMWARE_DATA		"airoha/en7581_npu_data.bin"
++#define NPU_EN7581_FIRMWARE_RV32		"airoha/en7581_npu_rv32.bin"
++#define NPU_EN7581_FIRMWARE_RV32_MAX_SIZE	0x200000
++#define NPU_EN7581_FIRMWARE_DATA_MAX_SIZE	0x10000
++#define NPU_DUMP_SIZE				512
++
++#define REG_NPU_LOCAL_SRAM		0x0
++
++#define NPU_PC_BASE_ADDR		0x305000
++#define REG_PC_DBG(_n)			(0x305000 + ((_n) * 0x100))
++
++#define NPU_CLUSTER_BASE_ADDR		0x306000
++
++#define REG_CR_BOOT_TRIGGER		(NPU_CLUSTER_BASE_ADDR + 0x000)
++#define REG_CR_BOOT_CONFIG		(NPU_CLUSTER_BASE_ADDR + 0x004)
++#define REG_CR_BOOT_BASE(_n)		(NPU_CLUSTER_BASE_ADDR + 0x020 + ((_n) << 2))
++
++#define NPU_MBOX_BASE_ADDR		0x30c000
++
++#define REG_CR_MBOX_INT_STATUS		(NPU_MBOX_BASE_ADDR + 0x000)
++#define MBOX_INT_STATUS_MASK		BIT(8)
++
++#define REG_CR_MBOX_INT_MASK(_n)	(NPU_MBOX_BASE_ADDR + 0x004 + ((_n) << 2))
++#define REG_CR_MBQ0_CTRL(_n)		(NPU_MBOX_BASE_ADDR + 0x030 + ((_n) << 2))
++#define REG_CR_MBQ8_CTRL(_n)		(NPU_MBOX_BASE_ADDR + 0x0b0 + ((_n) << 2))
++#define REG_CR_NPU_MIB(_n)		(NPU_MBOX_BASE_ADDR + 0x140 + ((_n) << 2))
++
++#define NPU_TIMER_BASE_ADDR		0x310100
++#define REG_WDT_TIMER_CTRL(_n)		(NPU_TIMER_BASE_ADDR + ((_n) * 0x100))
++#define WDT_EN_MASK			BIT(25)
++#define WDT_INTR_MASK			BIT(21)
++
++enum {
++	NPU_OP_SET = 1,
++	NPU_OP_SET_NO_WAIT,
++	NPU_OP_GET,
++	NPU_OP_GET_NO_WAIT,
++};
++
++enum {
++	NPU_FUNC_WIFI,
++	NPU_FUNC_TUNNEL,
++	NPU_FUNC_NOTIFY,
++	NPU_FUNC_DBA,
++	NPU_FUNC_TR471,
++	NPU_FUNC_PPE,
++};
++
++enum {
++	NPU_MBOX_ERROR,
++	NPU_MBOX_SUCCESS,
++};
++
++enum {
++	PPE_FUNC_SET_WAIT,
++	PPE_FUNC_SET_WAIT_HWNAT_INIT,
++	PPE_FUNC_SET_WAIT_HWNAT_DEINIT,
++	PPE_FUNC_SET_WAIT_API,
++};
++
++enum {
++	PPE2_SRAM_SET_ENTRY,
++	PPE_SRAM_SET_ENTRY,
++	PPE_SRAM_SET_VAL,
++	PPE_SRAM_RESET_VAL,
++};
++
++enum {
++	QDMA_WAN_ETHER = 1,
++	QDMA_WAN_PON_XDSL,
++};
++
++#define MBOX_MSG_FUNC_ID	GENMASK(14, 11)
++#define MBOX_MSG_STATIC_BUF	BIT(5)
++#define MBOX_MSG_STATUS		GENMASK(4, 2)
++#define MBOX_MSG_DONE		BIT(1)
++#define MBOX_MSG_WAIT_RSP	BIT(0)
++
++#define PPE_TYPE_L2B_IPV4	2
++#define PPE_TYPE_L2B_IPV4_IPV6	3
++
++struct ppe_mbox_data {
++	u32 func_type;
++	u32 func_id;
++	union {
++		struct {
++			u8 cds;
++			u8 xpon_hal_api;
++			u8 wan_xsi;
++			u8 ct_joyme4;
++			int ppe_type;
++			int wan_mode;
++			int wan_sel;
++		} init_info;
++		struct {
++			int func_id;
++			u32 size;
++			u32 data;
++		} set_info;
++	};
++};
++
++static int airoha_npu_send_msg(struct airoha_npu *npu, int func_id,
++			       void *p, int size)
++{
++	u16 core = 0; /* FIXME */
++	u32 val, offset = core << 4;
++	dma_addr_t dma_addr;
++	void *addr;
++	int ret;
++
++	addr = kmemdup(p, size, GFP_ATOMIC);
++	if (!addr)
++		return -ENOMEM;
++
++	dma_addr = dma_map_single(npu->dev, addr, size, DMA_TO_DEVICE);
++	ret = dma_mapping_error(npu->dev, dma_addr);
++	if (ret)
++		goto out;
++
++	spin_lock_bh(&npu->cores[core].lock);
++
++	regmap_write(npu->regmap, REG_CR_MBQ0_CTRL(0) + offset, dma_addr);
++	regmap_write(npu->regmap, REG_CR_MBQ0_CTRL(1) + offset, size);
++	regmap_read(npu->regmap, REG_CR_MBQ0_CTRL(2) + offset, &val);
++	regmap_write(npu->regmap, REG_CR_MBQ0_CTRL(2) + offset, val + 1);
++	val = FIELD_PREP(MBOX_MSG_FUNC_ID, func_id) | MBOX_MSG_WAIT_RSP;
++	regmap_write(npu->regmap, REG_CR_MBQ0_CTRL(3) + offset, val);
++
++	ret = regmap_read_poll_timeout_atomic(npu->regmap,
++					      REG_CR_MBQ0_CTRL(3) + offset,
++					      val, (val & MBOX_MSG_DONE),
++					      100, 100 * MSEC_PER_SEC);
++	if (!ret && FIELD_GET(MBOX_MSG_STATUS, val) != NPU_MBOX_SUCCESS)
++		ret = -EINVAL;
++
++	spin_unlock_bh(&npu->cores[core].lock);
++
++	dma_unmap_single(npu->dev, dma_addr, size, DMA_TO_DEVICE);
++out:
++	kfree(addr);
++
++	return ret;
++}
++
++static int airoha_npu_run_firmware(struct device *dev, void __iomem *base,
++				   struct reserved_mem *rmem)
++{
++	const struct firmware *fw;
++	void __iomem *addr;
++	int ret;
++
++	ret = request_firmware(&fw, NPU_EN7581_FIRMWARE_RV32, dev);
++	if (ret)
++		return ret == -ENOENT ? -EPROBE_DEFER : ret;
++
++	if (fw->size > NPU_EN7581_FIRMWARE_RV32_MAX_SIZE) {
++		dev_err(dev, "%s: fw size too overlimit (%zu)\n",
++			NPU_EN7581_FIRMWARE_RV32, fw->size);
++		ret = -E2BIG;
++		goto out;
++	}
++
++	addr = devm_ioremap(dev, rmem->base, rmem->size);
++	if (!addr) {
++		ret = -ENOMEM;
++		goto out;
++	}
++
++	memcpy_toio(addr, fw->data, fw->size);
++	release_firmware(fw);
++
++	ret = request_firmware(&fw, NPU_EN7581_FIRMWARE_DATA, dev);
++	if (ret)
++		return ret == -ENOENT ? -EPROBE_DEFER : ret;
++
++	if (fw->size > NPU_EN7581_FIRMWARE_DATA_MAX_SIZE) {
++		dev_err(dev, "%s: fw size too overlimit (%zu)\n",
++			NPU_EN7581_FIRMWARE_DATA, fw->size);
++		ret = -E2BIG;
++		goto out;
++	}
++
++	memcpy_toio(base + REG_NPU_LOCAL_SRAM, fw->data, fw->size);
++out:
++	release_firmware(fw);
++
++	return ret;
++}
++
++static irqreturn_t airoha_npu_mbox_handler(int irq, void *npu_instance)
++{
++	struct airoha_npu *npu = npu_instance;
++
++	/* clear mbox interrupt status */
++	regmap_write(npu->regmap, REG_CR_MBOX_INT_STATUS,
++		     MBOX_INT_STATUS_MASK);
++
++	/* acknowledge npu */
++	regmap_update_bits(npu->regmap, REG_CR_MBQ8_CTRL(3),
++			   MBOX_MSG_STATUS | MBOX_MSG_DONE, MBOX_MSG_DONE);
++
++	return IRQ_HANDLED;
++}
++
++static void airoha_npu_wdt_work(struct work_struct *work)
++{
++	struct airoha_npu_core *core;
++	struct airoha_npu *npu;
++	void *dump;
++	u32 val[3];
++	int c;
++
++	core = container_of(work, struct airoha_npu_core, wdt_work);
++	npu = core->npu;
++
++	dump = vzalloc(NPU_DUMP_SIZE);
++	if (!dump)
++		return;
++
++	c = core - &npu->cores[0];
++	regmap_bulk_read(npu->regmap, REG_PC_DBG(c), val, ARRAY_SIZE(val));
++	snprintf(dump, NPU_DUMP_SIZE, "PC: %08x SP: %08x LR: %08x\n",
++		 val[0], val[1], val[2]);
++
++	dev_coredumpv(npu->dev, dump, NPU_DUMP_SIZE, GFP_KERNEL);
++}
++
++static irqreturn_t airoha_npu_wdt_handler(int irq, void *core_instance)
++{
++	struct airoha_npu_core *core = core_instance;
++	struct airoha_npu *npu = core->npu;
++	int c = core - &npu->cores[0];
++	u32 val;
++
++	regmap_set_bits(npu->regmap, REG_WDT_TIMER_CTRL(c), WDT_INTR_MASK);
++	if (!regmap_read(npu->regmap, REG_WDT_TIMER_CTRL(c), &val) &&
++	    FIELD_GET(WDT_EN_MASK, val))
++		schedule_work(&core->wdt_work);
++
++	return IRQ_HANDLED;
++}
++
++static int airoha_npu_ppe_init(struct airoha_npu *npu)
++{
++	struct ppe_mbox_data ppe_data = {
++		.func_type = NPU_OP_SET,
++		.func_id = PPE_FUNC_SET_WAIT_HWNAT_INIT,
++		.init_info = {
++			.ppe_type = PPE_TYPE_L2B_IPV4_IPV6,
++			.wan_mode = QDMA_WAN_ETHER,
++		},
++	};
++
++	return airoha_npu_send_msg(npu, NPU_FUNC_PPE, &ppe_data,
++				   sizeof(struct ppe_mbox_data));
++}
++
++static int airoha_npu_ppe_deinit(struct airoha_npu *npu)
++{
++	struct ppe_mbox_data ppe_data = {
++		.func_type = NPU_OP_SET,
++		.func_id = PPE_FUNC_SET_WAIT_HWNAT_DEINIT,
++	};
++
++	return airoha_npu_send_msg(npu, NPU_FUNC_PPE, &ppe_data,
++				   sizeof(struct ppe_mbox_data));
++}
++
++static int airoha_npu_ppe_flush_sram_entries(struct airoha_npu *npu,
++					     dma_addr_t foe_addr,
++					     int sram_num_entries)
++{
++	struct ppe_mbox_data ppe_data = {
++		.func_type = NPU_OP_SET,
++		.func_id = PPE_FUNC_SET_WAIT_API,
++		.set_info = {
++			.func_id = PPE_SRAM_RESET_VAL,
++			.data = foe_addr,
++			.size = sram_num_entries,
++		},
++	};
++
++	return airoha_npu_send_msg(npu, NPU_FUNC_PPE, &ppe_data,
++				   sizeof(struct ppe_mbox_data));
++}
++
++static int airoha_npu_foe_commit_entry(struct airoha_npu *npu,
++				       dma_addr_t foe_addr,
++				       u32 entry_size, u32 hash, bool ppe2)
++{
++	struct ppe_mbox_data ppe_data = {
++		.func_type = NPU_OP_SET,
++		.func_id = PPE_FUNC_SET_WAIT_API,
++		.set_info = {
++			.data = foe_addr,
++			.size = entry_size,
++		},
++	};
++	int err;
++
++	ppe_data.set_info.func_id = ppe2 ? PPE2_SRAM_SET_ENTRY
++					 : PPE_SRAM_SET_ENTRY;
++
++	err = airoha_npu_send_msg(npu, NPU_FUNC_PPE, &ppe_data,
++				  sizeof(struct ppe_mbox_data));
++	if (err)
++		return err;
++
++	ppe_data.set_info.func_id = PPE_SRAM_SET_VAL;
++	ppe_data.set_info.data = hash;
++	ppe_data.set_info.size = sizeof(u32);
++
++	return airoha_npu_send_msg(npu, NPU_FUNC_PPE, &ppe_data,
++				   sizeof(struct ppe_mbox_data));
++}
++
++struct airoha_npu *airoha_npu_get(struct device *dev)
++{
++	struct platform_device *pdev;
++	struct device_node *np;
++	struct airoha_npu *npu;
++
++	np = of_parse_phandle(dev->of_node, "airoha,npu", 0);
++	if (!np)
++		return ERR_PTR(-ENODEV);
++
++	pdev = of_find_device_by_node(np);
++	of_node_put(np);
++
++	if (!pdev) {
++		dev_err(dev, "cannot find device node %s\n", np->name);
++		return ERR_PTR(-ENODEV);
++	}
++
++	if (!try_module_get(THIS_MODULE)) {
++		dev_err(dev, "failed to get the device driver module\n");
++		npu = ERR_PTR(-ENODEV);
++		goto error_pdev_put;
++	}
++
++	npu = platform_get_drvdata(pdev);
++	if (!npu) {
++		npu = ERR_PTR(-ENODEV);
++		goto error_module_put;
++	}
++
++	if (!device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_SUPPLIER)) {
++		dev_err(&pdev->dev,
++			"failed to create device link to consumer %s\n",
++			dev_name(dev));
++		npu = ERR_PTR(-EINVAL);
++		goto error_module_put;
++	}
++
++	return npu;
++
++error_module_put:
++	module_put(THIS_MODULE);
++error_pdev_put:
++	platform_device_put(pdev);
++
++	return npu;
++}
++EXPORT_SYMBOL_GPL(airoha_npu_get);
++
++void airoha_npu_put(struct airoha_npu *npu)
++{
++	module_put(THIS_MODULE);
++	put_device(npu->dev);
++}
++EXPORT_SYMBOL_GPL(airoha_npu_put);
++
++static const struct of_device_id of_airoha_npu_match[] = {
++	{ .compatible = "airoha,en7581-npu" },
++	{ /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, of_airoha_npu_match);
++
++static const struct regmap_config regmap_config = {
++	.name			= "npu",
++	.reg_bits		= 32,
++	.val_bits		= 32,
++	.reg_stride		= 4,
++	.disable_locking	= true,
++};
++
++static int airoha_npu_probe(struct platform_device *pdev)
++{
++	struct device *dev = &pdev->dev;
++	struct reserved_mem *rmem;
++	struct airoha_npu *npu;
++	struct device_node *np;
++	void __iomem *base;
++	int i, irq, err;
++
++	base = devm_platform_ioremap_resource(pdev, 0);
++	if (IS_ERR(base))
++		return PTR_ERR(base);
++
++	npu = devm_kzalloc(dev, sizeof(*npu), GFP_KERNEL);
++	if (!npu)
++		return -ENOMEM;
++
++	npu->dev = dev;
++	npu->ops.ppe_init = airoha_npu_ppe_init;
++	npu->ops.ppe_deinit = airoha_npu_ppe_deinit;
++	npu->ops.ppe_flush_sram_entries = airoha_npu_ppe_flush_sram_entries;
++	npu->ops.ppe_foe_commit_entry = airoha_npu_foe_commit_entry;
++
++	npu->regmap = devm_regmap_init_mmio(dev, base, &regmap_config);
++	if (IS_ERR(npu->regmap))
++		return PTR_ERR(npu->regmap);
++
++	np = of_parse_phandle(dev->of_node, "memory-region", 0);
++	if (!np)
++		return -ENODEV;
++
++	rmem = of_reserved_mem_lookup(np);
++	of_node_put(np);
++
++	if (!rmem)
++		return -ENODEV;
++
++	irq = platform_get_irq(pdev, 0);
++	if (irq < 0)
++		return irq;
++
++	err = devm_request_irq(dev, irq, airoha_npu_mbox_handler,
++			       IRQF_SHARED, "airoha-npu-mbox", npu);
++	if (err)
++		return err;
++
++	for (i = 0; i < ARRAY_SIZE(npu->cores); i++) {
++		struct airoha_npu_core *core = &npu->cores[i];
++
++		spin_lock_init(&core->lock);
++		core->npu = npu;
++
++		irq = platform_get_irq(pdev, i + 1);
++		if (irq < 0)
++			return irq;
++
++		err = devm_request_irq(dev, irq, airoha_npu_wdt_handler,
++				       IRQF_SHARED, "airoha-npu-wdt", core);
++		if (err)
++			return err;
++
++		INIT_WORK(&core->wdt_work, airoha_npu_wdt_work);
++	}
++
++	err = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
++	if (err)
++		return err;
++
++	err = airoha_npu_run_firmware(dev, base, rmem);
++	if (err)
++		return dev_err_probe(dev, err, "failed to run npu firmware\n");
++
++	regmap_write(npu->regmap, REG_CR_NPU_MIB(10),
++		     rmem->base + NPU_EN7581_FIRMWARE_RV32_MAX_SIZE);
++	regmap_write(npu->regmap, REG_CR_NPU_MIB(11), 0x40000); /* SRAM 256K */
++	regmap_write(npu->regmap, REG_CR_NPU_MIB(12), 0);
++	regmap_write(npu->regmap, REG_CR_NPU_MIB(21), 1);
++	msleep(100);
++
++	/* setting booting address */
++	for (i = 0; i < NPU_NUM_CORES; i++)
++		regmap_write(npu->regmap, REG_CR_BOOT_BASE(i), rmem->base);
++	usleep_range(1000, 2000);
++
++	/* enable NPU cores */
++	/* do not start core3 since it is used for WiFi offloading */
++	regmap_write(npu->regmap, REG_CR_BOOT_CONFIG, 0xf7);
++	regmap_write(npu->regmap, REG_CR_BOOT_TRIGGER, 0x1);
++	msleep(100);
++
++	platform_set_drvdata(pdev, npu);
++
++	return 0;
++}
++
++static void airoha_npu_remove(struct platform_device *pdev)
++{
++	struct airoha_npu *npu = platform_get_drvdata(pdev);
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(npu->cores); i++)
++		cancel_work_sync(&npu->cores[i].wdt_work);
++}
++
++static struct platform_driver airoha_npu_driver = {
++	.probe = airoha_npu_probe,
++	.remove_new = airoha_npu_remove,
++	.driver = {
++		.name = "airoha-npu",
++		.of_match_table = of_airoha_npu_match,
++	},
++};
++module_platform_driver(airoha_npu_driver);
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Lorenzo Bianconi <[email protected]>");
++MODULE_DESCRIPTION("Airoha Network Processor Unit driver");
+--- /dev/null
++++ b/drivers/net/ethernet/airoha/airoha_npu.h
+@@ -0,0 +1,34 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/*
++ * Copyright (c) 2025 AIROHA Inc
++ * Author: Lorenzo Bianconi <[email protected]>
++ */
++
++#define NPU_NUM_CORES		8
++
++struct airoha_npu {
++	struct device *dev;
++	struct regmap *regmap;
++
++	struct airoha_npu_core {
++		struct airoha_npu *npu;
++		/* protect concurrent npu memory accesses */
++		spinlock_t lock;
++		struct work_struct wdt_work;
++	} cores[NPU_NUM_CORES];
++
++	struct {
++		int (*ppe_init)(struct airoha_npu *npu);
++		int (*ppe_deinit)(struct airoha_npu *npu);
++		int (*ppe_flush_sram_entries)(struct airoha_npu *npu,
++					      dma_addr_t foe_addr,
++					      int sram_num_entries);
++		int (*ppe_foe_commit_entry)(struct airoha_npu *npu,
++					    dma_addr_t foe_addr,
++					    u32 entry_size, u32 hash,
++					    bool ppe2);
++	} ops;
++};
++
++struct airoha_npu *airoha_npu_get(struct device *dev);
++void airoha_npu_put(struct airoha_npu *npu);

+ 1481 - 0
target/linux/airoha/patches-6.6/048-13-v6.15-net-airoha-Introduce-flowtable-offload-support.patch

@@ -0,0 +1,1481 @@
+From 00a7678310fe3d3f408513e55d9a0b67f0db380f Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <[email protected]>
+Date: Fri, 28 Feb 2025 11:54:21 +0100
+Subject: [PATCH 13/15] net: airoha: Introduce flowtable offload support
+
+Introduce netfilter flowtable integration in order to allow airoha_eth
+driver to offload 5-tuple flower rules learned by the PPE module if the
+user accelerates them using a nft configuration similar to the one reported
+below:
+
+table inet filter {
+	flowtable ft {
+		hook ingress priority filter
+		devices = { lan1, lan2, lan3, lan4, eth1 }
+		flags offload;
+	}
+	chain forward {
+		type filter hook forward priority filter; policy accept;
+		meta l4proto { tcp, udp } flow add @ft
+	}
+}
+
+Tested-by: Sayantan Nandy <[email protected]>
+Signed-off-by: Lorenzo Bianconi <[email protected]>
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/ethernet/airoha/Makefile      |   3 +-
+ drivers/net/ethernet/airoha/airoha_eth.c  |  60 +-
+ drivers/net/ethernet/airoha/airoha_eth.h  | 250 ++++++
+ drivers/net/ethernet/airoha/airoha_ppe.c  | 901 ++++++++++++++++++++++
+ drivers/net/ethernet/airoha/airoha_regs.h | 107 ++-
+ 5 files changed, 1314 insertions(+), 7 deletions(-)
+ create mode 100644 drivers/net/ethernet/airoha/airoha_ppe.c
+
+--- a/drivers/net/ethernet/airoha/Makefile
++++ b/drivers/net/ethernet/airoha/Makefile
+@@ -3,5 +3,6 @@
+ # Airoha for the Mediatek SoCs built-in ethernet macs
+ #
+ 
+-obj-$(CONFIG_NET_AIROHA) += airoha_eth.o
++obj-$(CONFIG_NET_AIROHA) += airoha-eth.o
++airoha-eth-y := airoha_eth.o airoha_ppe.o
+ obj-$(CONFIG_NET_AIROHA_NPU) += airoha_npu.o
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -8,7 +8,6 @@
+ #include <linux/platform_device.h>
+ #include <linux/tcp.h>
+ #include <linux/u64_stats_sync.h>
+-#include <net/dsa.h>
+ #include <net/dst_metadata.h>
+ #include <net/page_pool/helpers.h>
+ #include <net/pkt_cls.h>
+@@ -619,6 +618,7 @@ static int airoha_qdma_rx_process(struct
+ 	while (done < budget) {
+ 		struct airoha_queue_entry *e = &q->entry[q->tail];
+ 		struct airoha_qdma_desc *desc = &q->desc[q->tail];
++		u32 hash, reason, msg1 = le32_to_cpu(desc->msg1);
+ 		dma_addr_t dma_addr = le32_to_cpu(desc->addr);
+ 		u32 desc_ctrl = le32_to_cpu(desc->ctrl);
+ 		struct airoha_gdm_port *port;
+@@ -681,6 +681,15 @@ static int airoha_qdma_rx_process(struct
+ 						  &port->dsa_meta[sptag]->dst);
+ 		}
+ 
++		hash = FIELD_GET(AIROHA_RXD4_FOE_ENTRY, msg1);
++		if (hash != AIROHA_RXD4_FOE_ENTRY)
++			skb_set_hash(skb, jhash_1word(hash, 0),
++				     PKT_HASH_TYPE_L4);
++
++		reason = FIELD_GET(AIROHA_RXD4_PPE_CPU_REASON, msg1);
++		if (reason == PPE_CPU_REASON_HIT_UNBIND_RATE_REACHED)
++			airoha_ppe_check_skb(eth->ppe, hash);
++
+ 		napi_gro_receive(&q->napi, skb);
+ 
+ 		done++;
+@@ -1302,6 +1311,10 @@ static int airoha_hw_init(struct platfor
+ 			return err;
+ 	}
+ 
++	err = airoha_ppe_init(eth);
++	if (err)
++		return err;
++
+ 	set_bit(DEV_STATE_INITIALIZED, &eth->state);
+ 
+ 	return 0;
+@@ -2166,6 +2179,47 @@ static int airoha_tc_htb_alloc_leaf_queu
+ 	return 0;
+ }
+ 
++static int airoha_dev_setup_tc_block(struct airoha_gdm_port *port,
++				     struct flow_block_offload *f)
++{
++	flow_setup_cb_t *cb = airoha_ppe_setup_tc_block_cb;
++	static LIST_HEAD(block_cb_list);
++	struct flow_block_cb *block_cb;
++
++	if (f->binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
++		return -EOPNOTSUPP;
++
++	f->driver_block_list = &block_cb_list;
++	switch (f->command) {
++	case FLOW_BLOCK_BIND:
++		block_cb = flow_block_cb_lookup(f->block, cb, port->dev);
++		if (block_cb) {
++			flow_block_cb_incref(block_cb);
++			return 0;
++		}
++		block_cb = flow_block_cb_alloc(cb, port->dev, port->dev, NULL);
++		if (IS_ERR(block_cb))
++			return PTR_ERR(block_cb);
++
++		flow_block_cb_incref(block_cb);
++		flow_block_cb_add(block_cb, f);
++		list_add_tail(&block_cb->driver_list, &block_cb_list);
++		return 0;
++	case FLOW_BLOCK_UNBIND:
++		block_cb = flow_block_cb_lookup(f->block, cb, port->dev);
++		if (!block_cb)
++			return -ENOENT;
++
++		if (!flow_block_cb_decref(block_cb)) {
++			flow_block_cb_remove(block_cb, f);
++			list_del(&block_cb->driver_list);
++		}
++		return 0;
++	default:
++		return -EOPNOTSUPP;
++	}
++}
++
+ static void airoha_tc_remove_htb_queue(struct airoha_gdm_port *port, int queue)
+ {
+ 	struct net_device *dev = port->dev;
+@@ -2249,6 +2303,9 @@ static int airoha_dev_tc_setup(struct ne
+ 		return airoha_tc_setup_qdisc_ets(port, type_data);
+ 	case TC_SETUP_QDISC_HTB:
+ 		return airoha_tc_setup_qdisc_htb(port, type_data);
++	case TC_SETUP_BLOCK:
++	case TC_SETUP_FT:
++		return airoha_dev_setup_tc_block(port, type_data);
+ 	default:
+ 		return -EOPNOTSUPP;
+ 	}
+@@ -2505,6 +2562,7 @@ static void airoha_remove(struct platfor
+ 	}
+ 	free_netdev(eth->napi_dev);
+ 
++	airoha_ppe_deinit(eth);
+ 	platform_set_drvdata(pdev, NULL);
+ }
+ 
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -12,6 +12,7 @@
+ #include <linux/kernel.h>
+ #include <linux/netdevice.h>
+ #include <linux/reset.h>
++#include <net/dsa.h>
+ 
+ #define AIROHA_MAX_NUM_GDM_PORTS	4
+ #define AIROHA_MAX_NUM_QDMA		2
+@@ -44,6 +45,15 @@
+ #define QDMA_METER_IDX(_n)		((_n) & 0xff)
+ #define QDMA_METER_GROUP(_n)		(((_n) >> 8) & 0x3)
+ 
++#define PPE_NUM				2
++#define PPE1_SRAM_NUM_ENTRIES		(8 * 1024)
++#define PPE_SRAM_NUM_ENTRIES		(2 * PPE1_SRAM_NUM_ENTRIES)
++#define PPE_DRAM_NUM_ENTRIES		(16 * 1024)
++#define PPE_NUM_ENTRIES			(PPE_SRAM_NUM_ENTRIES + PPE_DRAM_NUM_ENTRIES)
++#define PPE_HASH_MASK			(PPE_NUM_ENTRIES - 1)
++#define PPE_ENTRY_SIZE			80
++#define PPE_RAM_NUM_ENTRIES_SHIFT(_n)	(__ffs((_n) >> 10))
++
+ #define MTK_HDR_LEN			4
+ #define MTK_HDR_XMIT_TAGGED_TPID_8100	1
+ #define MTK_HDR_XMIT_TAGGED_TPID_88A8	2
+@@ -195,6 +205,224 @@ struct airoha_hw_stats {
+ 	u64 rx_len[7];
+ };
+ 
++enum {
++	PPE_CPU_REASON_HIT_UNBIND_RATE_REACHED = 0x0f,
++};
++
++enum {
++	AIROHA_FOE_STATE_INVALID,
++	AIROHA_FOE_STATE_UNBIND,
++	AIROHA_FOE_STATE_BIND,
++	AIROHA_FOE_STATE_FIN
++};
++
++enum {
++	PPE_PKT_TYPE_IPV4_HNAPT = 0,
++	PPE_PKT_TYPE_IPV4_ROUTE = 1,
++	PPE_PKT_TYPE_BRIDGE = 2,
++	PPE_PKT_TYPE_IPV4_DSLITE = 3,
++	PPE_PKT_TYPE_IPV6_ROUTE_3T = 4,
++	PPE_PKT_TYPE_IPV6_ROUTE_5T = 5,
++	PPE_PKT_TYPE_IPV6_6RD = 7,
++};
++
++#define AIROHA_FOE_MAC_SMAC_ID		GENMASK(20, 16)
++#define AIROHA_FOE_MAC_PPPOE_ID		GENMASK(15, 0)
++
++struct airoha_foe_mac_info_common {
++	u16 vlan1;
++	u16 etype;
++
++	u32 dest_mac_hi;
++
++	u16 vlan2;
++	u16 dest_mac_lo;
++
++	u32 src_mac_hi;
++};
++
++struct airoha_foe_mac_info {
++	struct airoha_foe_mac_info_common common;
++
++	u16 pppoe_id;
++	u16 src_mac_lo;
++};
++
++#define AIROHA_FOE_IB1_UNBIND_PREBIND		BIT(24)
++#define AIROHA_FOE_IB1_UNBIND_PACKETS		GENMASK(23, 8)
++#define AIROHA_FOE_IB1_UNBIND_TIMESTAMP		GENMASK(7, 0)
++
++#define AIROHA_FOE_IB1_BIND_STATIC		BIT(31)
++#define AIROHA_FOE_IB1_BIND_UDP			BIT(30)
++#define AIROHA_FOE_IB1_BIND_STATE		GENMASK(29, 28)
++#define AIROHA_FOE_IB1_BIND_PACKET_TYPE		GENMASK(27, 25)
++#define AIROHA_FOE_IB1_BIND_TTL			BIT(24)
++#define AIROHA_FOE_IB1_BIND_TUNNEL_DECAP	BIT(23)
++#define AIROHA_FOE_IB1_BIND_PPPOE		BIT(22)
++#define AIROHA_FOE_IB1_BIND_VPM			GENMASK(21, 20)
++#define AIROHA_FOE_IB1_BIND_VLAN_LAYER		GENMASK(19, 16)
++#define AIROHA_FOE_IB1_BIND_KEEPALIVE		BIT(15)
++#define AIROHA_FOE_IB1_BIND_TIMESTAMP		GENMASK(14, 0)
++
++#define AIROHA_FOE_IB2_DSCP			GENMASK(31, 24)
++#define AIROHA_FOE_IB2_PORT_AG			GENMASK(23, 13)
++#define AIROHA_FOE_IB2_PCP			BIT(12)
++#define AIROHA_FOE_IB2_MULTICAST		BIT(11)
++#define AIROHA_FOE_IB2_FAST_PATH		BIT(10)
++#define AIROHA_FOE_IB2_PSE_QOS			BIT(9)
++#define AIROHA_FOE_IB2_PSE_PORT			GENMASK(8, 5)
++#define AIROHA_FOE_IB2_NBQ			GENMASK(4, 0)
++
++#define AIROHA_FOE_ACTDP			GENMASK(31, 24)
++#define AIROHA_FOE_SHAPER_ID			GENMASK(23, 16)
++#define AIROHA_FOE_CHANNEL			GENMASK(15, 11)
++#define AIROHA_FOE_QID				GENMASK(10, 8)
++#define AIROHA_FOE_DPI				BIT(7)
++#define AIROHA_FOE_TUNNEL			BIT(6)
++#define AIROHA_FOE_TUNNEL_ID			GENMASK(5, 0)
++
++struct airoha_foe_bridge {
++	u32 dest_mac_hi;
++
++	u16 src_mac_hi;
++	u16 dest_mac_lo;
++
++	u32 src_mac_lo;
++
++	u32 ib2;
++
++	u32 rsv[5];
++
++	u32 data;
++
++	struct airoha_foe_mac_info l2;
++};
++
++struct airoha_foe_ipv4_tuple {
++	u32 src_ip;
++	u32 dest_ip;
++	union {
++		struct {
++			u16 dest_port;
++			u16 src_port;
++		};
++		struct {
++			u8 protocol;
++			u8 _pad[3]; /* fill with 0xa5a5a5 */
++		};
++		u32 ports;
++	};
++};
++
++struct airoha_foe_ipv4 {
++	struct airoha_foe_ipv4_tuple orig_tuple;
++
++	u32 ib2;
++
++	struct airoha_foe_ipv4_tuple new_tuple;
++
++	u32 rsv[2];
++
++	u32 data;
++
++	struct airoha_foe_mac_info l2;
++};
++
++struct airoha_foe_ipv4_dslite {
++	struct airoha_foe_ipv4_tuple ip4;
++
++	u32 ib2;
++
++	u8 flow_label[3];
++	u8 priority;
++
++	u32 rsv[4];
++
++	u32 data;
++
++	struct airoha_foe_mac_info l2;
++};
++
++struct airoha_foe_ipv6 {
++	u32 src_ip[4];
++	u32 dest_ip[4];
++
++	union {
++		struct {
++			u16 dest_port;
++			u16 src_port;
++		};
++		struct {
++			u8 protocol;
++			u8 pad[3];
++		};
++		u32 ports;
++	};
++
++	u32 data;
++
++	u32 ib2;
++
++	struct airoha_foe_mac_info_common l2;
++};
++
++struct airoha_foe_entry {
++	union {
++		struct {
++			u32 ib1;
++			union {
++				struct airoha_foe_bridge bridge;
++				struct airoha_foe_ipv4 ipv4;
++				struct airoha_foe_ipv4_dslite dslite;
++				struct airoha_foe_ipv6 ipv6;
++				DECLARE_FLEX_ARRAY(u32, d);
++			};
++		};
++		u8 data[PPE_ENTRY_SIZE];
++	};
++};
++
++struct airoha_flow_data {
++	struct ethhdr eth;
++
++	union {
++		struct {
++			__be32 src_addr;
++			__be32 dst_addr;
++		} v4;
++
++		struct {
++			struct in6_addr src_addr;
++			struct in6_addr dst_addr;
++		} v6;
++	};
++
++	__be16 src_port;
++	__be16 dst_port;
++
++	struct {
++		struct {
++			u16 id;
++			__be16 proto;
++		} hdr[2];
++		u8 num;
++	} vlan;
++	struct {
++		u16 sid;
++		u8 num;
++	} pppoe;
++};
++
++struct airoha_flow_table_entry {
++	struct hlist_node list;
++
++	struct airoha_foe_entry data;
++	u32 hash;
++
++	struct rhash_head node;
++	unsigned long cookie;
++};
++
+ struct airoha_qdma {
+ 	struct airoha_eth *eth;
+ 	void __iomem *regs;
+@@ -234,6 +462,19 @@ struct airoha_gdm_port {
+ 	struct metadata_dst *dsa_meta[AIROHA_MAX_DSA_PORTS];
+ };
+ 
++#define AIROHA_RXD4_PPE_CPU_REASON	GENMASK(20, 16)
++#define AIROHA_RXD4_FOE_ENTRY		GENMASK(15, 0)
++
++struct airoha_ppe {
++	struct airoha_eth *eth;
++
++	void *foe;
++	dma_addr_t foe_dma;
++
++	struct hlist_head *foe_flow;
++	u16 foe_check_time[PPE_NUM_ENTRIES];
++};
++
+ struct airoha_eth {
+ 	struct device *dev;
+ 
+@@ -242,6 +483,9 @@ struct airoha_eth {
+ 
+ 	struct airoha_npu __rcu *npu;
+ 
++	struct airoha_ppe *ppe;
++	struct rhashtable flow_table;
++
+ 	struct reset_control_bulk_data rsts[AIROHA_MAX_NUM_RSTS];
+ 	struct reset_control_bulk_data xsi_rsts[AIROHA_MAX_NUM_XSI_RSTS];
+ 
+@@ -277,4 +521,10 @@ u32 airoha_rmw(void __iomem *base, u32 o
+ #define airoha_qdma_clear(qdma, offset, val)			\
+ 	airoha_rmw((qdma)->regs, (offset), (val), 0)
+ 
++void airoha_ppe_check_skb(struct airoha_ppe *ppe, u16 hash);
++int airoha_ppe_setup_tc_block_cb(enum tc_setup_type type, void *type_data,
++				 void *cb_priv);
++int airoha_ppe_init(struct airoha_eth *eth);
++void airoha_ppe_deinit(struct airoha_eth *eth);
++
+ #endif /* AIROHA_ETH_H */
+--- /dev/null
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -0,0 +1,901 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2025 AIROHA Inc
++ * Author: Lorenzo Bianconi <[email protected]>
++ */
++
++#include <linux/ip.h>
++#include <linux/ipv6.h>
++#include <linux/rhashtable.h>
++#include <net/ipv6.h>
++#include <net/pkt_cls.h>
++
++#include "airoha_npu.h"
++#include "airoha_regs.h"
++#include "airoha_eth.h"
++
++static DEFINE_MUTEX(flow_offload_mutex);
++static DEFINE_SPINLOCK(ppe_lock);
++
++static const struct rhashtable_params airoha_flow_table_params = {
++	.head_offset = offsetof(struct airoha_flow_table_entry, node),
++	.key_offset = offsetof(struct airoha_flow_table_entry, cookie),
++	.key_len = sizeof(unsigned long),
++	.automatic_shrinking = true,
++};
++
++static bool airoha_ppe2_is_enabled(struct airoha_eth *eth)
++{
++	return airoha_fe_rr(eth, REG_PPE_GLO_CFG(1)) & PPE_GLO_CFG_EN_MASK;
++}
++
++static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe)
++{
++	u16 timestamp = airoha_fe_rr(ppe->eth, REG_FE_FOE_TS);
++
++	return FIELD_GET(AIROHA_FOE_IB1_BIND_TIMESTAMP, timestamp);
++}
++
++static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
++{
++	u32 sram_tb_size, sram_num_entries, dram_num_entries;
++	struct airoha_eth *eth = ppe->eth;
++	int i;
++
++	sram_tb_size = PPE_SRAM_NUM_ENTRIES * sizeof(struct airoha_foe_entry);
++	dram_num_entries = PPE_RAM_NUM_ENTRIES_SHIFT(PPE_DRAM_NUM_ENTRIES);
++
++	for (i = 0; i < PPE_NUM; i++) {
++		int p;
++
++		airoha_fe_wr(eth, REG_PPE_TB_BASE(i),
++			     ppe->foe_dma + sram_tb_size);
++
++		airoha_fe_rmw(eth, REG_PPE_BND_AGE0(i),
++			      PPE_BIND_AGE0_DELTA_NON_L4 |
++			      PPE_BIND_AGE0_DELTA_UDP,
++			      FIELD_PREP(PPE_BIND_AGE0_DELTA_NON_L4, 1) |
++			      FIELD_PREP(PPE_BIND_AGE0_DELTA_UDP, 12));
++		airoha_fe_rmw(eth, REG_PPE_BND_AGE1(i),
++			      PPE_BIND_AGE1_DELTA_TCP_FIN |
++			      PPE_BIND_AGE1_DELTA_TCP,
++			      FIELD_PREP(PPE_BIND_AGE1_DELTA_TCP_FIN, 1) |
++			      FIELD_PREP(PPE_BIND_AGE1_DELTA_TCP, 7));
++
++		airoha_fe_rmw(eth, REG_PPE_TB_HASH_CFG(i),
++			      PPE_SRAM_TABLE_EN_MASK |
++			      PPE_SRAM_HASH1_EN_MASK |
++			      PPE_DRAM_TABLE_EN_MASK |
++			      PPE_SRAM_HASH0_MODE_MASK |
++			      PPE_SRAM_HASH1_MODE_MASK |
++			      PPE_DRAM_HASH0_MODE_MASK |
++			      PPE_DRAM_HASH1_MODE_MASK,
++			      FIELD_PREP(PPE_SRAM_TABLE_EN_MASK, 1) |
++			      FIELD_PREP(PPE_SRAM_HASH1_EN_MASK, 1) |
++			      FIELD_PREP(PPE_SRAM_HASH1_MODE_MASK, 1) |
++			      FIELD_PREP(PPE_DRAM_HASH1_MODE_MASK, 3));
++
++		airoha_fe_rmw(eth, REG_PPE_TB_CFG(i),
++			      PPE_TB_CFG_SEARCH_MISS_MASK |
++			      PPE_TB_ENTRY_SIZE_MASK,
++			      FIELD_PREP(PPE_TB_CFG_SEARCH_MISS_MASK, 3) |
++			      FIELD_PREP(PPE_TB_ENTRY_SIZE_MASK, 0));
++
++		airoha_fe_wr(eth, REG_PPE_HASH_SEED(i), PPE_HASH_SEED);
++
++		for (p = 0; p < ARRAY_SIZE(eth->ports); p++)
++			airoha_fe_rmw(eth, REG_PPE_MTU(i, p),
++				      FP0_EGRESS_MTU_MASK |
++				      FP1_EGRESS_MTU_MASK,
++				      FIELD_PREP(FP0_EGRESS_MTU_MASK,
++						 AIROHA_MAX_MTU) |
++				      FIELD_PREP(FP1_EGRESS_MTU_MASK,
++						 AIROHA_MAX_MTU));
++	}
++
++	if (airoha_ppe2_is_enabled(eth)) {
++		sram_num_entries =
++			PPE_RAM_NUM_ENTRIES_SHIFT(PPE1_SRAM_NUM_ENTRIES);
++		airoha_fe_rmw(eth, REG_PPE_TB_CFG(0),
++			      PPE_SRAM_TB_NUM_ENTRY_MASK |
++			      PPE_DRAM_TB_NUM_ENTRY_MASK,
++			      FIELD_PREP(PPE_SRAM_TB_NUM_ENTRY_MASK,
++					 sram_num_entries) |
++			      FIELD_PREP(PPE_DRAM_TB_NUM_ENTRY_MASK,
++					 dram_num_entries));
++		airoha_fe_rmw(eth, REG_PPE_TB_CFG(1),
++			      PPE_SRAM_TB_NUM_ENTRY_MASK |
++			      PPE_DRAM_TB_NUM_ENTRY_MASK,
++			      FIELD_PREP(PPE_SRAM_TB_NUM_ENTRY_MASK,
++					 sram_num_entries) |
++			      FIELD_PREP(PPE_DRAM_TB_NUM_ENTRY_MASK,
++					 dram_num_entries));
++	} else {
++		sram_num_entries =
++			PPE_RAM_NUM_ENTRIES_SHIFT(PPE_SRAM_NUM_ENTRIES);
++		airoha_fe_rmw(eth, REG_PPE_TB_CFG(0),
++			      PPE_SRAM_TB_NUM_ENTRY_MASK |
++			      PPE_DRAM_TB_NUM_ENTRY_MASK,
++			      FIELD_PREP(PPE_SRAM_TB_NUM_ENTRY_MASK,
++					 sram_num_entries) |
++			      FIELD_PREP(PPE_DRAM_TB_NUM_ENTRY_MASK,
++					 dram_num_entries));
++	}
++}
++
++static void airoha_ppe_flow_mangle_eth(const struct flow_action_entry *act, void *eth)
++{
++	void *dest = eth + act->mangle.offset;
++	const void *src = &act->mangle.val;
++
++	if (act->mangle.offset > 8)
++		return;
++
++	if (act->mangle.mask == 0xffff) {
++		src += 2;
++		dest += 2;
++	}
++
++	memcpy(dest, src, act->mangle.mask ? 2 : 4);
++}
++
++static int airoha_ppe_flow_mangle_ports(const struct flow_action_entry *act,
++					struct airoha_flow_data *data)
++{
++	u32 val = be32_to_cpu((__force __be32)act->mangle.val);
++
++	switch (act->mangle.offset) {
++	case 0:
++		if ((__force __be32)act->mangle.mask == ~cpu_to_be32(0xffff))
++			data->dst_port = cpu_to_be16(val);
++		else
++			data->src_port = cpu_to_be16(val >> 16);
++		break;
++	case 2:
++		data->dst_port = cpu_to_be16(val);
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
++static int airoha_ppe_flow_mangle_ipv4(const struct flow_action_entry *act,
++				       struct airoha_flow_data *data)
++{
++	__be32 *dest;
++
++	switch (act->mangle.offset) {
++	case offsetof(struct iphdr, saddr):
++		dest = &data->v4.src_addr;
++		break;
++	case offsetof(struct iphdr, daddr):
++		dest = &data->v4.dst_addr;
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	memcpy(dest, &act->mangle.val, sizeof(u32));
++
++	return 0;
++}
++
++static int airoha_get_dsa_port(struct net_device **dev)
++{
++#if IS_ENABLED(CONFIG_NET_DSA)
++	struct dsa_port *dp = dsa_port_from_netdev(*dev);
++
++	if (IS_ERR(dp))
++		return -ENODEV;
++
++	*dev = dsa_port_to_master(dp);
++	return dp->index;
++#else
++	return -ENODEV;
++#endif
++}
++
++static int airoha_ppe_foe_entry_prepare(struct airoha_foe_entry *hwe,
++					struct net_device *dev, int type,
++					struct airoha_flow_data *data,
++					int l4proto)
++{
++	int dsa_port = airoha_get_dsa_port(&dev);
++	struct airoha_foe_mac_info_common *l2;
++	u32 qdata, ports_pad, val;
++
++	memset(hwe, 0, sizeof(*hwe));
++
++	val = FIELD_PREP(AIROHA_FOE_IB1_BIND_STATE, AIROHA_FOE_STATE_BIND) |
++	      FIELD_PREP(AIROHA_FOE_IB1_BIND_PACKET_TYPE, type) |
++	      FIELD_PREP(AIROHA_FOE_IB1_BIND_UDP, l4proto == IPPROTO_UDP) |
++	      FIELD_PREP(AIROHA_FOE_IB1_BIND_VLAN_LAYER, data->vlan.num) |
++	      FIELD_PREP(AIROHA_FOE_IB1_BIND_VPM, data->vlan.num) |
++	      AIROHA_FOE_IB1_BIND_TTL;
++	hwe->ib1 = val;
++
++	val = FIELD_PREP(AIROHA_FOE_IB2_PORT_AG, 0x1f);
++	if (dsa_port >= 0)
++		val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ, dsa_port);
++
++	if (dev) {
++		struct airoha_gdm_port *port = netdev_priv(dev);
++		u8 pse_port;
++
++		pse_port = port->id == 4 ? FE_PSE_PORT_GDM4 : port->id;
++		val |= FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT, pse_port);
++	}
++
++	/* FIXME: implement QoS support setting pse_port to 2 (loopback)
++	 * for uplink and setting qos bit in ib2
++	 */
++
++	if (is_multicast_ether_addr(data->eth.h_dest))
++		val |= AIROHA_FOE_IB2_MULTICAST;
++
++	ports_pad = 0xa5a5a500 | (l4proto & 0xff);
++	if (type == PPE_PKT_TYPE_IPV4_ROUTE)
++		hwe->ipv4.orig_tuple.ports = ports_pad;
++	if (type == PPE_PKT_TYPE_IPV6_ROUTE_3T)
++		hwe->ipv6.ports = ports_pad;
++
++	qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f);
++	if (type == PPE_PKT_TYPE_BRIDGE) {
++		hwe->bridge.dest_mac_hi = get_unaligned_be32(data->eth.h_dest);
++		hwe->bridge.dest_mac_lo =
++			get_unaligned_be16(data->eth.h_dest + 4);
++		hwe->bridge.src_mac_hi =
++			get_unaligned_be16(data->eth.h_source);
++		hwe->bridge.src_mac_lo =
++			get_unaligned_be32(data->eth.h_source + 2);
++		hwe->bridge.data = qdata;
++		hwe->bridge.ib2 = val;
++		l2 = &hwe->bridge.l2.common;
++	} else if (type >= PPE_PKT_TYPE_IPV6_ROUTE_3T) {
++		hwe->ipv6.data = qdata;
++		hwe->ipv6.ib2 = val;
++		l2 = &hwe->ipv6.l2;
++	} else {
++		hwe->ipv4.data = qdata;
++		hwe->ipv4.ib2 = val;
++		l2 = &hwe->ipv4.l2.common;
++	}
++
++	l2->dest_mac_hi = get_unaligned_be32(data->eth.h_dest);
++	l2->dest_mac_lo = get_unaligned_be16(data->eth.h_dest + 4);
++	if (type <= PPE_PKT_TYPE_IPV4_DSLITE) {
++		l2->src_mac_hi = get_unaligned_be32(data->eth.h_source);
++		hwe->ipv4.l2.src_mac_lo =
++			get_unaligned_be16(data->eth.h_source + 4);
++	} else {
++		l2->src_mac_hi = FIELD_PREP(AIROHA_FOE_MAC_SMAC_ID, 0xf);
++	}
++
++	if (data->vlan.num) {
++		l2->etype = dsa_port >= 0 ? BIT(dsa_port) : 0;
++		l2->vlan1 = data->vlan.hdr[0].id;
++		if (data->vlan.num == 2)
++			l2->vlan2 = data->vlan.hdr[1].id;
++	} else if (dsa_port >= 0) {
++		l2->etype = BIT(15) | BIT(dsa_port);
++	} else if (type >= PPE_PKT_TYPE_IPV6_ROUTE_3T) {
++		l2->etype = ETH_P_IPV6;
++	} else {
++		l2->etype = ETH_P_IP;
++	}
++
++	return 0;
++}
++
++static int airoha_ppe_foe_entry_set_ipv4_tuple(struct airoha_foe_entry *hwe,
++					       struct airoha_flow_data *data,
++					       bool egress)
++{
++	int type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe->ib1);
++	struct airoha_foe_ipv4_tuple *t;
++
++	switch (type) {
++	case PPE_PKT_TYPE_IPV4_HNAPT:
++		if (egress) {
++			t = &hwe->ipv4.new_tuple;
++			break;
++		}
++		fallthrough;
++	case PPE_PKT_TYPE_IPV4_DSLITE:
++	case PPE_PKT_TYPE_IPV4_ROUTE:
++		t = &hwe->ipv4.orig_tuple;
++		break;
++	default:
++		WARN_ON_ONCE(1);
++		return -EINVAL;
++	}
++
++	t->src_ip = be32_to_cpu(data->v4.src_addr);
++	t->dest_ip = be32_to_cpu(data->v4.dst_addr);
++
++	if (type != PPE_PKT_TYPE_IPV4_ROUTE) {
++		t->src_port = be16_to_cpu(data->src_port);
++		t->dest_port = be16_to_cpu(data->dst_port);
++	}
++
++	return 0;
++}
++
++static int airoha_ppe_foe_entry_set_ipv6_tuple(struct airoha_foe_entry *hwe,
++					       struct airoha_flow_data *data)
++
++{
++	int type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe->ib1);
++	u32 *src, *dest;
++
++	switch (type) {
++	case PPE_PKT_TYPE_IPV6_ROUTE_5T:
++	case PPE_PKT_TYPE_IPV6_6RD:
++		hwe->ipv6.src_port = be16_to_cpu(data->src_port);
++		hwe->ipv6.dest_port = be16_to_cpu(data->dst_port);
++		fallthrough;
++	case PPE_PKT_TYPE_IPV6_ROUTE_3T:
++		src = hwe->ipv6.src_ip;
++		dest = hwe->ipv6.dest_ip;
++		break;
++	default:
++		WARN_ON_ONCE(1);
++		return -EINVAL;
++	}
++
++	ipv6_addr_be32_to_cpu(src, data->v6.src_addr.s6_addr32);
++	ipv6_addr_be32_to_cpu(dest, data->v6.dst_addr.s6_addr32);
++
++	return 0;
++}
++
++static u32 airoha_ppe_foe_get_entry_hash(struct airoha_foe_entry *hwe)
++{
++	int type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe->ib1);
++	u32 hash, hv1, hv2, hv3;
++
++	switch (type) {
++	case PPE_PKT_TYPE_IPV4_ROUTE:
++	case PPE_PKT_TYPE_IPV4_HNAPT:
++		hv1 = hwe->ipv4.orig_tuple.ports;
++		hv2 = hwe->ipv4.orig_tuple.dest_ip;
++		hv3 = hwe->ipv4.orig_tuple.src_ip;
++		break;
++	case PPE_PKT_TYPE_IPV6_ROUTE_3T:
++	case PPE_PKT_TYPE_IPV6_ROUTE_5T:
++		hv1 = hwe->ipv6.src_ip[3] ^ hwe->ipv6.dest_ip[3];
++		hv1 ^= hwe->ipv6.ports;
++
++		hv2 = hwe->ipv6.src_ip[2] ^ hwe->ipv6.dest_ip[2];
++		hv2 ^= hwe->ipv6.dest_ip[0];
++
++		hv3 = hwe->ipv6.src_ip[1] ^ hwe->ipv6.dest_ip[1];
++		hv3 ^= hwe->ipv6.src_ip[0];
++		break;
++	case PPE_PKT_TYPE_IPV4_DSLITE:
++	case PPE_PKT_TYPE_IPV6_6RD:
++	default:
++		WARN_ON_ONCE(1);
++		return PPE_HASH_MASK;
++	}
++
++	hash = (hv1 & hv2) | ((~hv1) & hv3);
++	hash = (hash >> 24) | ((hash & 0xffffff) << 8);
++	hash ^= hv1 ^ hv2 ^ hv3;
++	hash ^= hash >> 16;
++	hash &= PPE_NUM_ENTRIES - 1;
++
++	return hash;
++}
++
++static struct airoha_foe_entry *
++airoha_ppe_foe_get_entry(struct airoha_ppe *ppe, u32 hash)
++{
++	if (hash < PPE_SRAM_NUM_ENTRIES) {
++		u32 *hwe = ppe->foe + hash * sizeof(struct airoha_foe_entry);
++		struct airoha_eth *eth = ppe->eth;
++		bool ppe2;
++		u32 val;
++		int i;
++
++		ppe2 = airoha_ppe2_is_enabled(ppe->eth) &&
++		       hash >= PPE1_SRAM_NUM_ENTRIES;
++		airoha_fe_wr(ppe->eth, REG_PPE_RAM_CTRL(ppe2),
++			     FIELD_PREP(PPE_SRAM_CTRL_ENTRY_MASK, hash) |
++			     PPE_SRAM_CTRL_REQ_MASK);
++		if (read_poll_timeout_atomic(airoha_fe_rr, val,
++					     val & PPE_SRAM_CTRL_ACK_MASK,
++					     10, 100, false, eth,
++					     REG_PPE_RAM_CTRL(ppe2)))
++			return NULL;
++
++		for (i = 0; i < sizeof(struct airoha_foe_entry) / 4; i++)
++			hwe[i] = airoha_fe_rr(eth,
++					      REG_PPE_RAM_ENTRY(ppe2, i));
++	}
++
++	return ppe->foe + hash * sizeof(struct airoha_foe_entry);
++}
++
++static bool airoha_ppe_foe_compare_entry(struct airoha_flow_table_entry *e,
++					 struct airoha_foe_entry *hwe)
++{
++	int type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, e->data.ib1);
++	int len;
++
++	if ((hwe->ib1 ^ e->data.ib1) & AIROHA_FOE_IB1_BIND_UDP)
++		return false;
++
++	if (type > PPE_PKT_TYPE_IPV4_DSLITE)
++		len = offsetof(struct airoha_foe_entry, ipv6.data);
++	else
++		len = offsetof(struct airoha_foe_entry, ipv4.ib2);
++
++	return !memcmp(&e->data.d, &hwe->d, len - sizeof(hwe->ib1));
++}
++
++static int airoha_ppe_foe_commit_entry(struct airoha_ppe *ppe,
++				       struct airoha_foe_entry *e,
++				       u32 hash)
++{
++	struct airoha_foe_entry *hwe = ppe->foe + hash * sizeof(*hwe);
++	u32 ts = airoha_ppe_get_timestamp(ppe);
++	struct airoha_eth *eth = ppe->eth;
++
++	memcpy(&hwe->d, &e->d, sizeof(*hwe) - sizeof(hwe->ib1));
++	wmb();
++
++	e->ib1 &= ~AIROHA_FOE_IB1_BIND_TIMESTAMP;
++	e->ib1 |= FIELD_PREP(AIROHA_FOE_IB1_BIND_TIMESTAMP, ts);
++	hwe->ib1 = e->ib1;
++
++	if (hash < PPE_SRAM_NUM_ENTRIES) {
++		dma_addr_t addr = ppe->foe_dma + hash * sizeof(*hwe);
++		bool ppe2 = airoha_ppe2_is_enabled(eth) &&
++			    hash >= PPE1_SRAM_NUM_ENTRIES;
++		struct airoha_npu *npu;
++		int err = -ENODEV;
++
++		rcu_read_lock();
++		npu = rcu_dereference(eth->npu);
++		if (npu)
++			err = npu->ops.ppe_foe_commit_entry(npu, addr,
++							    sizeof(*hwe), hash,
++							    ppe2);
++		rcu_read_unlock();
++
++		return err;
++	}
++
++	return 0;
++}
++
++static void airoha_ppe_foe_insert_entry(struct airoha_ppe *ppe, u32 hash)
++{
++	struct airoha_flow_table_entry *e;
++	struct airoha_foe_entry *hwe;
++	struct hlist_node *n;
++	u32 index, state;
++
++	spin_lock_bh(&ppe_lock);
++
++	hwe = airoha_ppe_foe_get_entry(ppe, hash);
++	if (!hwe)
++		goto unlock;
++
++	state = FIELD_GET(AIROHA_FOE_IB1_BIND_STATE, hwe->ib1);
++	if (state == AIROHA_FOE_STATE_BIND)
++		goto unlock;
++
++	index = airoha_ppe_foe_get_entry_hash(hwe);
++	hlist_for_each_entry_safe(e, n, &ppe->foe_flow[index], list) {
++		if (airoha_ppe_foe_compare_entry(e, hwe)) {
++			airoha_ppe_foe_commit_entry(ppe, &e->data, hash);
++			e->hash = hash;
++			break;
++		}
++	}
++unlock:
++	spin_unlock_bh(&ppe_lock);
++}
++
++static int airoha_ppe_foe_flow_commit_entry(struct airoha_ppe *ppe,
++					    struct airoha_flow_table_entry *e)
++{
++	u32 hash = airoha_ppe_foe_get_entry_hash(&e->data);
++
++	e->hash = 0xffff;
++
++	spin_lock_bh(&ppe_lock);
++	hlist_add_head(&e->list, &ppe->foe_flow[hash]);
++	spin_unlock_bh(&ppe_lock);
++
++	return 0;
++}
++
++static void airoha_ppe_foe_flow_remove_entry(struct airoha_ppe *ppe,
++					     struct airoha_flow_table_entry *e)
++{
++	spin_lock_bh(&ppe_lock);
++
++	hlist_del_init(&e->list);
++	if (e->hash != 0xffff) {
++		e->data.ib1 &= ~AIROHA_FOE_IB1_BIND_STATE;
++		e->data.ib1 |= FIELD_PREP(AIROHA_FOE_IB1_BIND_STATE,
++					  AIROHA_FOE_STATE_INVALID);
++		airoha_ppe_foe_commit_entry(ppe, &e->data, e->hash);
++		e->hash = 0xffff;
++	}
++
++	spin_unlock_bh(&ppe_lock);
++}
++
++static int airoha_ppe_flow_offload_replace(struct airoha_gdm_port *port,
++					   struct flow_cls_offload *f)
++{
++	struct flow_rule *rule = flow_cls_offload_flow_rule(f);
++	struct airoha_eth *eth = port->qdma->eth;
++	struct airoha_flow_table_entry *e;
++	struct airoha_flow_data data = {};
++	struct net_device *odev = NULL;
++	struct flow_action_entry *act;
++	struct airoha_foe_entry hwe;
++	int err, i, offload_type;
++	u16 addr_type = 0;
++	u8 l4proto = 0;
++
++	if (rhashtable_lookup(&eth->flow_table, &f->cookie,
++			      airoha_flow_table_params))
++		return -EEXIST;
++
++	if (!flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_META))
++		return -EOPNOTSUPP;
++
++	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
++		struct flow_match_control match;
++
++		flow_rule_match_control(rule, &match);
++		addr_type = match.key->addr_type;
++		if (flow_rule_has_control_flags(match.mask->flags,
++						f->common.extack))
++			return -EOPNOTSUPP;
++	} else {
++		return -EOPNOTSUPP;
++	}
++
++	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
++		struct flow_match_basic match;
++
++		flow_rule_match_basic(rule, &match);
++		l4proto = match.key->ip_proto;
++	} else {
++		return -EOPNOTSUPP;
++	}
++
++	switch (addr_type) {
++	case 0:
++		offload_type = PPE_PKT_TYPE_BRIDGE;
++		if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
++			struct flow_match_eth_addrs match;
++
++			flow_rule_match_eth_addrs(rule, &match);
++			memcpy(data.eth.h_dest, match.key->dst, ETH_ALEN);
++			memcpy(data.eth.h_source, match.key->src, ETH_ALEN);
++		} else {
++			return -EOPNOTSUPP;
++		}
++		break;
++	case FLOW_DISSECTOR_KEY_IPV4_ADDRS:
++		offload_type = PPE_PKT_TYPE_IPV4_HNAPT;
++		break;
++	case FLOW_DISSECTOR_KEY_IPV6_ADDRS:
++		offload_type = PPE_PKT_TYPE_IPV6_ROUTE_5T;
++		break;
++	default:
++		return -EOPNOTSUPP;
++	}
++
++	flow_action_for_each(i, act, &rule->action) {
++		switch (act->id) {
++		case FLOW_ACTION_MANGLE:
++			if (offload_type == PPE_PKT_TYPE_BRIDGE)
++				return -EOPNOTSUPP;
++
++			if (act->mangle.htype == FLOW_ACT_MANGLE_HDR_TYPE_ETH)
++				airoha_ppe_flow_mangle_eth(act, &data.eth);
++			break;
++		case FLOW_ACTION_REDIRECT:
++			odev = act->dev;
++			break;
++		case FLOW_ACTION_CSUM:
++			break;
++		case FLOW_ACTION_VLAN_PUSH:
++			if (data.vlan.num == 2 ||
++			    act->vlan.proto != htons(ETH_P_8021Q))
++				return -EOPNOTSUPP;
++
++			data.vlan.hdr[data.vlan.num].id = act->vlan.vid;
++			data.vlan.hdr[data.vlan.num].proto = act->vlan.proto;
++			data.vlan.num++;
++			break;
++		case FLOW_ACTION_VLAN_POP:
++			break;
++		case FLOW_ACTION_PPPOE_PUSH:
++			break;
++		default:
++			return -EOPNOTSUPP;
++		}
++	}
++
++	if (!is_valid_ether_addr(data.eth.h_source) ||
++	    !is_valid_ether_addr(data.eth.h_dest))
++		return -EINVAL;
++
++	err = airoha_ppe_foe_entry_prepare(&hwe, odev, offload_type,
++					   &data, l4proto);
++	if (err)
++		return err;
++
++	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
++		struct flow_match_ports ports;
++
++		if (offload_type == PPE_PKT_TYPE_BRIDGE)
++			return -EOPNOTSUPP;
++
++		flow_rule_match_ports(rule, &ports);
++		data.src_port = ports.key->src;
++		data.dst_port = ports.key->dst;
++	} else if (offload_type != PPE_PKT_TYPE_BRIDGE) {
++		return -EOPNOTSUPP;
++	}
++
++	if (addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
++		struct flow_match_ipv4_addrs addrs;
++
++		flow_rule_match_ipv4_addrs(rule, &addrs);
++		data.v4.src_addr = addrs.key->src;
++		data.v4.dst_addr = addrs.key->dst;
++		airoha_ppe_foe_entry_set_ipv4_tuple(&hwe, &data, false);
++	}
++
++	if (addr_type == FLOW_DISSECTOR_KEY_IPV6_ADDRS) {
++		struct flow_match_ipv6_addrs addrs;
++
++		flow_rule_match_ipv6_addrs(rule, &addrs);
++
++		data.v6.src_addr = addrs.key->src;
++		data.v6.dst_addr = addrs.key->dst;
++		airoha_ppe_foe_entry_set_ipv6_tuple(&hwe, &data);
++	}
++
++	flow_action_for_each(i, act, &rule->action) {
++		if (act->id != FLOW_ACTION_MANGLE)
++			continue;
++
++		if (offload_type == PPE_PKT_TYPE_BRIDGE)
++			return -EOPNOTSUPP;
++
++		switch (act->mangle.htype) {
++		case FLOW_ACT_MANGLE_HDR_TYPE_TCP:
++		case FLOW_ACT_MANGLE_HDR_TYPE_UDP:
++			err = airoha_ppe_flow_mangle_ports(act, &data);
++			break;
++		case FLOW_ACT_MANGLE_HDR_TYPE_IP4:
++			err = airoha_ppe_flow_mangle_ipv4(act, &data);
++			break;
++		case FLOW_ACT_MANGLE_HDR_TYPE_ETH:
++			/* handled earlier */
++			break;
++		default:
++			return -EOPNOTSUPP;
++		}
++
++		if (err)
++			return err;
++	}
++
++	if (addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
++		err = airoha_ppe_foe_entry_set_ipv4_tuple(&hwe, &data, true);
++		if (err)
++			return err;
++	}
++
++	e = kzalloc(sizeof(*e), GFP_KERNEL);
++	if (!e)
++		return -ENOMEM;
++
++	e->cookie = f->cookie;
++	memcpy(&e->data, &hwe, sizeof(e->data));
++
++	err = airoha_ppe_foe_flow_commit_entry(eth->ppe, e);
++	if (err)
++		goto free_entry;
++
++	err = rhashtable_insert_fast(&eth->flow_table, &e->node,
++				     airoha_flow_table_params);
++	if (err < 0)
++		goto remove_foe_entry;
++
++	return 0;
++
++remove_foe_entry:
++	airoha_ppe_foe_flow_remove_entry(eth->ppe, e);
++free_entry:
++	kfree(e);
++
++	return err;
++}
++
++static int airoha_ppe_flow_offload_destroy(struct airoha_gdm_port *port,
++					   struct flow_cls_offload *f)
++{
++	struct airoha_eth *eth = port->qdma->eth;
++	struct airoha_flow_table_entry *e;
++
++	e = rhashtable_lookup(&eth->flow_table, &f->cookie,
++			      airoha_flow_table_params);
++	if (!e)
++		return -ENOENT;
++
++	airoha_ppe_foe_flow_remove_entry(eth->ppe, e);
++	rhashtable_remove_fast(&eth->flow_table, &e->node,
++			       airoha_flow_table_params);
++	kfree(e);
++
++	return 0;
++}
++
++static int airoha_ppe_flow_offload_cmd(struct airoha_gdm_port *port,
++				       struct flow_cls_offload *f)
++{
++	switch (f->command) {
++	case FLOW_CLS_REPLACE:
++		return airoha_ppe_flow_offload_replace(port, f);
++	case FLOW_CLS_DESTROY:
++		return airoha_ppe_flow_offload_destroy(port, f);
++	default:
++		break;
++	}
++
++	return -EOPNOTSUPP;
++}
++
++static int airoha_ppe_flush_sram_entries(struct airoha_ppe *ppe,
++					 struct airoha_npu *npu)
++{
++	int i, sram_num_entries = PPE_SRAM_NUM_ENTRIES;
++	struct airoha_foe_entry *hwe = ppe->foe;
++
++	if (airoha_ppe2_is_enabled(ppe->eth))
++		sram_num_entries = sram_num_entries / 2;
++
++	for (i = 0; i < sram_num_entries; i++)
++		memset(&hwe[i], 0, sizeof(*hwe));
++
++	return npu->ops.ppe_flush_sram_entries(npu, ppe->foe_dma,
++					       PPE_SRAM_NUM_ENTRIES);
++}
++
++static struct airoha_npu *airoha_ppe_npu_get(struct airoha_eth *eth)
++{
++	struct airoha_npu *npu = airoha_npu_get(eth->dev);
++
++	if (IS_ERR(npu)) {
++		request_module("airoha-npu");
++		npu = airoha_npu_get(eth->dev);
++	}
++
++	return npu;
++}
++
++static int airoha_ppe_offload_setup(struct airoha_eth *eth)
++{
++	struct airoha_npu *npu = airoha_ppe_npu_get(eth);
++	int err;
++
++	if (IS_ERR(npu))
++		return PTR_ERR(npu);
++
++	err = npu->ops.ppe_init(npu);
++	if (err)
++		goto error_npu_put;
++
++	airoha_ppe_hw_init(eth->ppe);
++	err = airoha_ppe_flush_sram_entries(eth->ppe, npu);
++	if (err)
++		goto error_npu_put;
++
++	rcu_assign_pointer(eth->npu, npu);
++	synchronize_rcu();
++
++	return 0;
++
++error_npu_put:
++	airoha_npu_put(npu);
++
++	return err;
++}
++
++int airoha_ppe_setup_tc_block_cb(enum tc_setup_type type, void *type_data,
++				 void *cb_priv)
++{
++	struct flow_cls_offload *cls = type_data;
++	struct net_device *dev = cb_priv;
++	struct airoha_gdm_port *port = netdev_priv(dev);
++	struct airoha_eth *eth = port->qdma->eth;
++	int err = 0;
++
++	if (!tc_can_offload(dev) || type != TC_SETUP_CLSFLOWER)
++		return -EOPNOTSUPP;
++
++	mutex_lock(&flow_offload_mutex);
++
++	if (!eth->npu)
++		err = airoha_ppe_offload_setup(eth);
++	if (!err)
++		err = airoha_ppe_flow_offload_cmd(port, cls);
++
++	mutex_unlock(&flow_offload_mutex);
++
++	return err;
++}
++
++void airoha_ppe_check_skb(struct airoha_ppe *ppe, u16 hash)
++{
++	u16 now, diff;
++
++	if (hash > PPE_HASH_MASK)
++		return;
++
++	now = (u16)jiffies;
++	diff = now - ppe->foe_check_time[hash];
++	if (diff < HZ / 10)
++		return;
++
++	ppe->foe_check_time[hash] = now;
++	airoha_ppe_foe_insert_entry(ppe, hash);
++}
++
++int airoha_ppe_init(struct airoha_eth *eth)
++{
++	struct airoha_ppe *ppe;
++	int foe_size;
++
++	ppe = devm_kzalloc(eth->dev, sizeof(*ppe), GFP_KERNEL);
++	if (!ppe)
++		return -ENOMEM;
++
++	foe_size = PPE_NUM_ENTRIES * sizeof(struct airoha_foe_entry);
++	ppe->foe = dmam_alloc_coherent(eth->dev, foe_size, &ppe->foe_dma,
++				       GFP_KERNEL);
++	if (!ppe->foe)
++		return -ENOMEM;
++
++	ppe->eth = eth;
++	eth->ppe = ppe;
++
++	ppe->foe_flow = devm_kzalloc(eth->dev,
++				     PPE_NUM_ENTRIES * sizeof(*ppe->foe_flow),
++				     GFP_KERNEL);
++	if (!ppe->foe_flow)
++		return -ENOMEM;
++
++	return rhashtable_init(&eth->flow_table, &airoha_flow_table_params);
++}
++
++void airoha_ppe_deinit(struct airoha_eth *eth)
++{
++	struct airoha_npu *npu;
++
++	rcu_read_lock();
++	npu = rcu_dereference(eth->npu);
++	if (npu) {
++		npu->ops.ppe_deinit(npu);
++		airoha_npu_put(npu);
++	}
++	rcu_read_unlock();
++
++	rhashtable_destroy(&eth->flow_table);
++}
+--- a/drivers/net/ethernet/airoha/airoha_regs.h
++++ b/drivers/net/ethernet/airoha/airoha_regs.h
+@@ -15,6 +15,7 @@
+ #define CDM1_BASE			0x0400
+ #define GDM1_BASE			0x0500
+ #define PPE1_BASE			0x0c00
++#define PPE2_BASE			0x1c00
+ 
+ #define CDM2_BASE			0x1400
+ #define GDM2_BASE			0x1500
+@@ -36,6 +37,7 @@
+ #define FE_RST_GDM3_MBI_ARB_MASK	BIT(2)
+ #define FE_RST_CORE_MASK		BIT(0)
+ 
++#define REG_FE_FOE_TS			0x0010
+ #define REG_FE_WAN_MAC_H		0x0030
+ #define REG_FE_LAN_MAC_H		0x0040
+ 
+@@ -192,11 +194,106 @@
+ #define REG_FE_GDM_RX_ETH_L511_CNT_L(_n)	(GDM_BASE(_n) + 0x198)
+ #define REG_FE_GDM_RX_ETH_L1023_CNT_L(_n)	(GDM_BASE(_n) + 0x19c)
+ 
+-#define REG_PPE1_TB_HASH_CFG		(PPE1_BASE + 0x250)
+-#define PPE1_SRAM_TABLE_EN_MASK		BIT(0)
+-#define PPE1_SRAM_HASH1_EN_MASK		BIT(8)
+-#define PPE1_DRAM_TABLE_EN_MASK		BIT(16)
+-#define PPE1_DRAM_HASH1_EN_MASK		BIT(24)
++#define REG_PPE_GLO_CFG(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x200)
++#define PPE_GLO_CFG_BUSY_MASK			BIT(31)
++#define PPE_GLO_CFG_FLOW_DROP_UPDATE_MASK	BIT(9)
++#define PPE_GLO_CFG_PSE_HASH_OFS_MASK		BIT(6)
++#define PPE_GLO_CFG_PPE_BSWAP_MASK		BIT(5)
++#define PPE_GLO_CFG_TTL_DROP_MASK		BIT(4)
++#define PPE_GLO_CFG_IP4_CS_DROP_MASK		BIT(3)
++#define PPE_GLO_CFG_IP4_L4_CS_DROP_MASK		BIT(2)
++#define PPE_GLO_CFG_EN_MASK			BIT(0)
++
++#define REG_PPE_PPE_FLOW_CFG(_n)		(((_n) ? PPE2_BASE : PPE1_BASE) + 0x204)
++#define PPE_FLOW_CFG_IP6_HASH_GRE_KEY_MASK	BIT(20)
++#define PPE_FLOW_CFG_IP4_HASH_GRE_KEY_MASK	BIT(19)
++#define PPE_FLOW_CFG_IP4_HASH_FLOW_LABEL_MASK	BIT(18)
++#define PPE_FLOW_CFG_IP4_NAT_FRAG_MASK		BIT(17)
++#define PPE_FLOW_CFG_IP_PROTO_BLACKLIST_MASK	BIT(16)
++#define PPE_FLOW_CFG_IP4_DSLITE_MASK		BIT(14)
++#define PPE_FLOW_CFG_IP4_NAPT_MASK		BIT(13)
++#define PPE_FLOW_CFG_IP4_NAT_MASK		BIT(12)
++#define PPE_FLOW_CFG_IP6_6RD_MASK		BIT(10)
++#define PPE_FLOW_CFG_IP6_5T_ROUTE_MASK		BIT(9)
++#define PPE_FLOW_CFG_IP6_3T_ROUTE_MASK		BIT(8)
++#define PPE_FLOW_CFG_IP4_UDP_FRAG_MASK		BIT(7)
++#define PPE_FLOW_CFG_IP4_TCP_FRAG_MASK		BIT(6)
++
++#define REG_PPE_IP_PROTO_CHK(_n)		(((_n) ? PPE2_BASE : PPE1_BASE) + 0x208)
++#define PPE_IP_PROTO_CHK_IPV4_MASK		GENMASK(15, 0)
++#define PPE_IP_PROTO_CHK_IPV6_MASK		GENMASK(31, 16)
++
++#define REG_PPE_TB_CFG(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x21c)
++#define PPE_SRAM_TB_NUM_ENTRY_MASK		GENMASK(26, 24)
++#define PPE_TB_CFG_KEEPALIVE_MASK		GENMASK(13, 12)
++#define PPE_TB_CFG_AGE_TCP_FIN_MASK		BIT(11)
++#define PPE_TB_CFG_AGE_UDP_MASK			BIT(10)
++#define PPE_TB_CFG_AGE_TCP_MASK			BIT(9)
++#define PPE_TB_CFG_AGE_UNBIND_MASK		BIT(8)
++#define PPE_TB_CFG_AGE_NON_L4_MASK		BIT(7)
++#define PPE_TB_CFG_AGE_PREBIND_MASK		BIT(6)
++#define PPE_TB_CFG_SEARCH_MISS_MASK		GENMASK(5, 4)
++#define PPE_TB_ENTRY_SIZE_MASK			BIT(3)
++#define PPE_DRAM_TB_NUM_ENTRY_MASK		GENMASK(2, 0)
++
++#define REG_PPE_TB_BASE(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x220)
++
++#define REG_PPE_BIND_RATE(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x228)
++#define PPE_BIND_RATE_L2B_BIND_MASK		GENMASK(31, 16)
++#define PPE_BIND_RATE_BIND_MASK			GENMASK(15, 0)
++
++#define REG_PPE_BIND_LIMIT0(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x22c)
++#define PPE_BIND_LIMIT0_HALF_MASK		GENMASK(29, 16)
++#define PPE_BIND_LIMIT0_QUARTER_MASK		GENMASK(13, 0)
++
++#define REG_PPE_BIND_LIMIT1(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x230)
++#define PPE_BIND_LIMIT1_NON_L4_MASK		GENMASK(23, 16)
++#define PPE_BIND_LIMIT1_FULL_MASK		GENMASK(13, 0)
++
++#define REG_PPE_BND_AGE0(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x23c)
++#define PPE_BIND_AGE0_DELTA_NON_L4		GENMASK(30, 16)
++#define PPE_BIND_AGE0_DELTA_UDP			GENMASK(14, 0)
++
++#define REG_PPE_UNBIND_AGE(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x238)
++#define PPE_UNBIND_AGE_MIN_PACKETS_MASK		GENMASK(31, 16)
++#define PPE_UNBIND_AGE_DELTA_MASK		GENMASK(7, 0)
++
++#define REG_PPE_BND_AGE1(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x240)
++#define PPE_BIND_AGE1_DELTA_TCP_FIN		GENMASK(30, 16)
++#define PPE_BIND_AGE1_DELTA_TCP			GENMASK(14, 0)
++
++#define REG_PPE_HASH_SEED(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x244)
++#define PPE_HASH_SEED				0x12345678
++
++#define REG_PPE_DFT_CPORT0(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x248)
++
++#define REG_PPE_DFT_CPORT1(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x24c)
++
++#define REG_PPE_TB_HASH_CFG(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x250)
++#define PPE_DRAM_HASH1_MODE_MASK		GENMASK(31, 28)
++#define PPE_DRAM_HASH1_EN_MASK			BIT(24)
++#define PPE_DRAM_HASH0_MODE_MASK		GENMASK(23, 20)
++#define PPE_DRAM_TABLE_EN_MASK			BIT(16)
++#define PPE_SRAM_HASH1_MODE_MASK		GENMASK(15, 12)
++#define PPE_SRAM_HASH1_EN_MASK			BIT(8)
++#define PPE_SRAM_HASH0_MODE_MASK		GENMASK(7, 4)
++#define PPE_SRAM_TABLE_EN_MASK			BIT(0)
++
++#define REG_PPE_MTU_BASE(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x304)
++#define REG_PPE_MTU(_m, _n)			(REG_PPE_MTU_BASE(_m) + ((_n) << 2))
++#define FP1_EGRESS_MTU_MASK			GENMASK(29, 16)
++#define FP0_EGRESS_MTU_MASK			GENMASK(13, 0)
++
++#define REG_PPE_RAM_CTRL(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x31c)
++#define PPE_SRAM_CTRL_ACK_MASK			BIT(31)
++#define PPE_SRAM_CTRL_DUAL_SUCESS_MASK		BIT(30)
++#define PPE_SRAM_CTRL_ENTRY_MASK		GENMASK(23, 8)
++#define PPE_SRAM_WR_DUAL_DIRECTION_MASK		BIT(2)
++#define PPE_SRAM_CTRL_WR_MASK			BIT(1)
++#define PPE_SRAM_CTRL_REQ_MASK			BIT(0)
++
++#define REG_PPE_RAM_BASE(_n)			(((_n) ? PPE2_BASE : PPE1_BASE) + 0x320)
++#define REG_PPE_RAM_ENTRY(_m, _n)		(REG_PPE_RAM_BASE(_m) + ((_n) << 2))
+ 
+ #define REG_FE_GDM_TX_OK_PKT_CNT_H(_n)		(GDM_BASE(_n) + 0x280)
+ #define REG_FE_GDM_TX_OK_BYTE_CNT_H(_n)		(GDM_BASE(_n) + 0x284)

+ 210 - 0
target/linux/airoha/patches-6.6/048-14-v6.15-net-airoha-Add-loopback-support-for-GDM2.patch

@@ -0,0 +1,210 @@
+From 9cd451d414f6e29f507a216fb3b19fa68c011f8c Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <[email protected]>
+Date: Fri, 28 Feb 2025 11:54:22 +0100
+Subject: [PATCH 14/15] net: airoha: Add loopback support for GDM2
+
+Enable hw redirection for traffic received on GDM2 port to GDM{3,4}.
+This is required to apply Qdisc offloading (HTB or ETS) for traffic to
+and from GDM{3,4} port.
+
+Signed-off-by: Lorenzo Bianconi <[email protected]>
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c  | 71 ++++++++++++++++++++++-
+ drivers/net/ethernet/airoha/airoha_eth.h  |  7 +++
+ drivers/net/ethernet/airoha/airoha_ppe.c  | 12 ++--
+ drivers/net/ethernet/airoha/airoha_regs.h | 29 +++++++++
+ 4 files changed, 111 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1589,14 +1589,81 @@ static int airoha_dev_set_macaddr(struct
+ 	return 0;
+ }
+ 
++static void airhoha_set_gdm2_loopback(struct airoha_gdm_port *port)
++{
++	u32 pse_port = port->id == 3 ? FE_PSE_PORT_GDM3 : FE_PSE_PORT_GDM4;
++	struct airoha_eth *eth = port->qdma->eth;
++	u32 chan = port->id == 3 ? 4 : 0;
++
++	/* Forward the traffic to the proper GDM port */
++	airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(2), pse_port);
++	airoha_fe_clear(eth, REG_GDM_FWD_CFG(2), GDM_STRIP_CRC);
++
++	/* Enable GDM2 loopback */
++	airoha_fe_wr(eth, REG_GDM_TXCHN_EN(2), 0xffffffff);
++	airoha_fe_wr(eth, REG_GDM_RXCHN_EN(2), 0xffff);
++	airoha_fe_rmw(eth, REG_GDM_LPBK_CFG(2),
++		      LPBK_CHAN_MASK | LPBK_MODE_MASK | LPBK_EN_MASK,
++		      FIELD_PREP(LPBK_CHAN_MASK, chan) | LPBK_EN_MASK);
++	airoha_fe_rmw(eth, REG_GDM_LEN_CFG(2),
++		      GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
++		      FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
++		      FIELD_PREP(GDM_LONG_LEN_MASK, AIROHA_MAX_MTU));
++
++	/* Disable VIP and IFC for GDM2 */
++	airoha_fe_clear(eth, REG_FE_VIP_PORT_EN, BIT(2));
++	airoha_fe_clear(eth, REG_FE_IFC_PORT_EN, BIT(2));
++
++	if (port->id == 3) {
++		/* FIXME: handle XSI_PCE1_PORT */
++		airoha_fe_wr(eth, REG_PPE_DFT_CPORT0(0),  0x5500);
++		airoha_fe_rmw(eth, REG_FE_WAN_PORT,
++			      WAN1_EN_MASK | WAN1_MASK | WAN0_MASK,
++			      FIELD_PREP(WAN0_MASK, HSGMII_LAN_PCIE0_SRCPORT));
++		airoha_fe_rmw(eth,
++			      REG_SP_DFT_CPORT(HSGMII_LAN_PCIE0_SRCPORT >> 3),
++			      SP_CPORT_PCIE0_MASK,
++			      FIELD_PREP(SP_CPORT_PCIE0_MASK,
++					 FE_PSE_PORT_CDM2));
++	} else {
++		/* FIXME: handle XSI_USB_PORT */
++		airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6,
++			      FC_ID_OF_SRC_PORT24_MASK,
++			      FIELD_PREP(FC_ID_OF_SRC_PORT24_MASK, 2));
++		airoha_fe_rmw(eth, REG_FE_WAN_PORT,
++			      WAN1_EN_MASK | WAN1_MASK | WAN0_MASK,
++			      FIELD_PREP(WAN0_MASK, HSGMII_LAN_ETH_SRCPORT));
++		airoha_fe_rmw(eth,
++			      REG_SP_DFT_CPORT(HSGMII_LAN_ETH_SRCPORT >> 3),
++			      SP_CPORT_ETH_MASK,
++			      FIELD_PREP(SP_CPORT_ETH_MASK, FE_PSE_PORT_CDM2));
++	}
++}
++
+ static int airoha_dev_init(struct net_device *dev)
+ {
+ 	struct airoha_gdm_port *port = netdev_priv(dev);
+ 	struct airoha_eth *eth = port->qdma->eth;
++	u32 pse_port;
+ 
+ 	airoha_set_macaddr(port, dev->dev_addr);
+-	airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id),
+-				    FE_PSE_PORT_PPE1);
++
++	switch (port->id) {
++	case 3:
++	case 4:
++		/* If GDM2 is active we can't enable loopback */
++		if (!eth->ports[1])
++			airhoha_set_gdm2_loopback(port);
++		fallthrough;
++	case 2:
++		pse_port = FE_PSE_PORT_PPE2;
++		break;
++	default:
++		pse_port = FE_PSE_PORT_PPE1;
++		break;
++	}
++
++	airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id), pse_port);
+ 
+ 	return 0;
+ }
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -68,6 +68,13 @@ enum {
+ };
+ 
+ enum {
++	HSGMII_LAN_PCIE0_SRCPORT = 0x16,
++	HSGMII_LAN_PCIE1_SRCPORT,
++	HSGMII_LAN_ETH_SRCPORT,
++	HSGMII_LAN_USB_SRCPORT,
++};
++
++enum {
+ 	XSI_PCIE0_VIP_PORT_MASK	= BIT(22),
+ 	XSI_PCIE1_VIP_PORT_MASK	= BIT(23),
+ 	XSI_USB_VIP_PORT_MASK	= BIT(25),
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -216,7 +216,8 @@ static int airoha_ppe_foe_entry_prepare(
+ 	      AIROHA_FOE_IB1_BIND_TTL;
+ 	hwe->ib1 = val;
+ 
+-	val = FIELD_PREP(AIROHA_FOE_IB2_PORT_AG, 0x1f);
++	val = FIELD_PREP(AIROHA_FOE_IB2_PORT_AG, 0x1f) |
++	      AIROHA_FOE_IB2_PSE_QOS;
+ 	if (dsa_port >= 0)
+ 		val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ, dsa_port);
+ 
+@@ -224,14 +225,13 @@ static int airoha_ppe_foe_entry_prepare(
+ 		struct airoha_gdm_port *port = netdev_priv(dev);
+ 		u8 pse_port;
+ 
+-		pse_port = port->id == 4 ? FE_PSE_PORT_GDM4 : port->id;
++		if (dsa_port >= 0)
++			pse_port = port->id == 4 ? FE_PSE_PORT_GDM4 : port->id;
++		else
++			pse_port = 2; /* uplink relies on GDM2 loopback */
+ 		val |= FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT, pse_port);
+ 	}
+ 
+-	/* FIXME: implement QoS support setting pse_port to 2 (loopback)
+-	 * for uplink and setting qos bit in ib2
+-	 */
+-
+ 	if (is_multicast_ether_addr(data->eth.h_dest))
+ 		val |= AIROHA_FOE_IB2_MULTICAST;
+ 
+--- a/drivers/net/ethernet/airoha/airoha_regs.h
++++ b/drivers/net/ethernet/airoha/airoha_regs.h
+@@ -38,6 +38,12 @@
+ #define FE_RST_CORE_MASK		BIT(0)
+ 
+ #define REG_FE_FOE_TS			0x0010
++
++#define REG_FE_WAN_PORT			0x0024
++#define WAN1_EN_MASK			BIT(16)
++#define WAN1_MASK			GENMASK(12, 8)
++#define WAN0_MASK			GENMASK(4, 0)
++
+ #define REG_FE_WAN_MAC_H		0x0030
+ #define REG_FE_LAN_MAC_H		0x0040
+ 
+@@ -126,6 +132,7 @@
+ #define GDM_IP4_CKSUM			BIT(22)
+ #define GDM_TCP_CKSUM			BIT(21)
+ #define GDM_UDP_CKSUM			BIT(20)
++#define GDM_STRIP_CRC			BIT(16)
+ #define GDM_UCFQ_MASK			GENMASK(15, 12)
+ #define GDM_BCFQ_MASK			GENMASK(11, 8)
+ #define GDM_MCFQ_MASK			GENMASK(7, 4)
+@@ -139,6 +146,16 @@
+ #define GDM_SHORT_LEN_MASK		GENMASK(13, 0)
+ #define GDM_LONG_LEN_MASK		GENMASK(29, 16)
+ 
++#define REG_GDM_LPBK_CFG(_n)		(GDM_BASE(_n) + 0x1c)
++#define LPBK_GAP_MASK			GENMASK(31, 24)
++#define LPBK_LEN_MASK			GENMASK(23, 10)
++#define LPBK_CHAN_MASK			GENMASK(8, 4)
++#define LPBK_MODE_MASK			GENMASK(3, 1)
++#define LPBK_EN_MASK			BIT(0)
++
++#define REG_GDM_TXCHN_EN(_n)		(GDM_BASE(_n) + 0x24)
++#define REG_GDM_RXCHN_EN(_n)		(GDM_BASE(_n) + 0x28)
++
+ #define REG_FE_CPORT_CFG		(GDM1_BASE + 0x40)
+ #define FE_CPORT_PAD			BIT(26)
+ #define FE_CPORT_PORT_XFC_MASK		BIT(25)
+@@ -351,6 +368,18 @@
+ 
+ #define REG_MC_VLAN_DATA		0x2108
+ 
++#define REG_SP_DFT_CPORT(_n)		(0x20e0 + ((_n) << 2))
++#define SP_CPORT_PCIE1_MASK		GENMASK(31, 28)
++#define SP_CPORT_PCIE0_MASK		GENMASK(27, 24)
++#define SP_CPORT_USB_MASK		GENMASK(7, 4)
++#define SP_CPORT_ETH_MASK		GENMASK(7, 4)
++
++#define REG_SRC_PORT_FC_MAP6		0x2298
++#define FC_ID_OF_SRC_PORT27_MASK	GENMASK(28, 24)
++#define FC_ID_OF_SRC_PORT26_MASK	GENMASK(20, 16)
++#define FC_ID_OF_SRC_PORT25_MASK	GENMASK(12, 8)
++#define FC_ID_OF_SRC_PORT24_MASK	GENMASK(4, 0)
++
+ #define REG_CDM5_RX_OQ1_DROP_CNT	0x29d4
+ 
+ /* QDMA */

+ 291 - 0
target/linux/airoha/patches-6.6/048-15-v6.15-net-airoha-Introduce-PPE-debugfs-support.patch

@@ -0,0 +1,291 @@
+From 3fe15c640f3808c3faf235553c67c867d1389e5c Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <[email protected]>
+Date: Fri, 28 Feb 2025 11:54:23 +0100
+Subject: [PATCH 15/15] net: airoha: Introduce PPE debugfs support
+
+Similar to PPE support for Mediatek devices, introduce PPE debugfs
+in order to dump binded and unbinded flows.
+
+Signed-off-by: Lorenzo Bianconi <[email protected]>
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/ethernet/airoha/Makefile          |   1 +
+ drivers/net/ethernet/airoha/airoha_eth.h      |  14 ++
+ drivers/net/ethernet/airoha/airoha_ppe.c      |  17 +-
+ .../net/ethernet/airoha/airoha_ppe_debugfs.c  | 181 ++++++++++++++++++
+ 4 files changed, 209 insertions(+), 4 deletions(-)
+ create mode 100644 drivers/net/ethernet/airoha/airoha_ppe_debugfs.c
+
+--- a/drivers/net/ethernet/airoha/Makefile
++++ b/drivers/net/ethernet/airoha/Makefile
+@@ -5,4 +5,5 @@
+ 
+ obj-$(CONFIG_NET_AIROHA) += airoha-eth.o
+ airoha-eth-y := airoha_eth.o airoha_ppe.o
++airoha-eth-$(CONFIG_DEBUG_FS) += airoha_ppe_debugfs.o
+ obj-$(CONFIG_NET_AIROHA_NPU) += airoha_npu.o
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -7,6 +7,7 @@
+ #ifndef AIROHA_ETH_H
+ #define AIROHA_ETH_H
+ 
++#include <linux/debugfs.h>
+ #include <linux/etherdevice.h>
+ #include <linux/iopoll.h>
+ #include <linux/kernel.h>
+@@ -480,6 +481,8 @@ struct airoha_ppe {
+ 
+ 	struct hlist_head *foe_flow;
+ 	u16 foe_check_time[PPE_NUM_ENTRIES];
++
++	struct dentry *debugfs_dir;
+ };
+ 
+ struct airoha_eth {
+@@ -533,5 +536,16 @@ int airoha_ppe_setup_tc_block_cb(enum tc
+ 				 void *cb_priv);
+ int airoha_ppe_init(struct airoha_eth *eth);
+ void airoha_ppe_deinit(struct airoha_eth *eth);
++struct airoha_foe_entry *airoha_ppe_foe_get_entry(struct airoha_ppe *ppe,
++						  u32 hash);
++
++#if CONFIG_DEBUG_FS
++int airoha_ppe_debugfs_init(struct airoha_ppe *ppe);
++#else
++static inline int airoha_ppe_debugfs_init(struct airoha_ppe *ppe)
++{
++	return 0;
++}
++#endif
+ 
+ #endif /* AIROHA_ETH_H */
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -390,8 +390,8 @@ static u32 airoha_ppe_foe_get_entry_hash
+ 	return hash;
+ }
+ 
+-static struct airoha_foe_entry *
+-airoha_ppe_foe_get_entry(struct airoha_ppe *ppe, u32 hash)
++struct airoha_foe_entry *airoha_ppe_foe_get_entry(struct airoha_ppe *ppe,
++						  u32 hash)
+ {
+ 	if (hash < PPE_SRAM_NUM_ENTRIES) {
+ 		u32 *hwe = ppe->foe + hash * sizeof(struct airoha_foe_entry);
+@@ -861,7 +861,7 @@ void airoha_ppe_check_skb(struct airoha_
+ int airoha_ppe_init(struct airoha_eth *eth)
+ {
+ 	struct airoha_ppe *ppe;
+-	int foe_size;
++	int foe_size, err;
+ 
+ 	ppe = devm_kzalloc(eth->dev, sizeof(*ppe), GFP_KERNEL);
+ 	if (!ppe)
+@@ -882,7 +882,15 @@ int airoha_ppe_init(struct airoha_eth *e
+ 	if (!ppe->foe_flow)
+ 		return -ENOMEM;
+ 
+-	return rhashtable_init(&eth->flow_table, &airoha_flow_table_params);
++	err = rhashtable_init(&eth->flow_table, &airoha_flow_table_params);
++	if (err)
++		return err;
++
++	err = airoha_ppe_debugfs_init(ppe);
++	if (err)
++		rhashtable_destroy(&eth->flow_table);
++
++	return err;
+ }
+ 
+ void airoha_ppe_deinit(struct airoha_eth *eth)
+@@ -898,4 +906,5 @@ void airoha_ppe_deinit(struct airoha_eth
+ 	rcu_read_unlock();
+ 
+ 	rhashtable_destroy(&eth->flow_table);
++	debugfs_remove(eth->ppe->debugfs_dir);
+ }
+--- /dev/null
++++ b/drivers/net/ethernet/airoha/airoha_ppe_debugfs.c
+@@ -0,0 +1,181 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (c) 2025 AIROHA Inc
++ * Author: Lorenzo Bianconi <[email protected]>
++ */
++
++#include "airoha_eth.h"
++
++static void airoha_debugfs_ppe_print_tuple(struct seq_file *m,
++					   void *src_addr, void *dest_addr,
++					   u16 *src_port, u16 *dest_port,
++					   bool ipv6)
++{
++	__be32 n_addr[IPV6_ADDR_WORDS];
++
++	if (ipv6) {
++		ipv6_addr_cpu_to_be32(n_addr, src_addr);
++		seq_printf(m, "%pI6", n_addr);
++	} else {
++		seq_printf(m, "%pI4h", src_addr);
++	}
++	if (src_port)
++		seq_printf(m, ":%d", *src_port);
++
++	seq_puts(m, "->");
++
++	if (ipv6) {
++		ipv6_addr_cpu_to_be32(n_addr, dest_addr);
++		seq_printf(m, "%pI6", n_addr);
++	} else {
++		seq_printf(m, "%pI4h", dest_addr);
++	}
++	if (dest_port)
++		seq_printf(m, ":%d", *dest_port);
++}
++
++static int airoha_ppe_debugfs_foe_show(struct seq_file *m, void *private,
++				       bool bind)
++{
++	static const char *const ppe_type_str[] = {
++		[PPE_PKT_TYPE_IPV4_HNAPT] = "IPv4 5T",
++		[PPE_PKT_TYPE_IPV4_ROUTE] = "IPv4 3T",
++		[PPE_PKT_TYPE_BRIDGE] = "L2B",
++		[PPE_PKT_TYPE_IPV4_DSLITE] = "DS-LITE",
++		[PPE_PKT_TYPE_IPV6_ROUTE_3T] = "IPv6 3T",
++		[PPE_PKT_TYPE_IPV6_ROUTE_5T] = "IPv6 5T",
++		[PPE_PKT_TYPE_IPV6_6RD] = "6RD",
++	};
++	static const char *const ppe_state_str[] = {
++		[AIROHA_FOE_STATE_INVALID] = "INV",
++		[AIROHA_FOE_STATE_UNBIND] = "UNB",
++		[AIROHA_FOE_STATE_BIND] = "BND",
++		[AIROHA_FOE_STATE_FIN] = "FIN",
++	};
++	struct airoha_ppe *ppe = m->private;
++	int i;
++
++	for (i = 0; i < PPE_NUM_ENTRIES; i++) {
++		const char *state_str, *type_str = "UNKNOWN";
++		void *src_addr = NULL, *dest_addr = NULL;
++		u16 *src_port = NULL, *dest_port = NULL;
++		struct airoha_foe_mac_info_common *l2;
++		unsigned char h_source[ETH_ALEN] = {};
++		unsigned char h_dest[ETH_ALEN];
++		struct airoha_foe_entry *hwe;
++		u32 type, state, ib2, data;
++		bool ipv6 = false;
++
++		hwe = airoha_ppe_foe_get_entry(ppe, i);
++		if (!hwe)
++			continue;
++
++		state = FIELD_GET(AIROHA_FOE_IB1_BIND_STATE, hwe->ib1);
++		if (!state)
++			continue;
++
++		if (bind && state != AIROHA_FOE_STATE_BIND)
++			continue;
++
++		state_str = ppe_state_str[state % ARRAY_SIZE(ppe_state_str)];
++		type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe->ib1);
++		if (type < ARRAY_SIZE(ppe_type_str) && ppe_type_str[type])
++			type_str = ppe_type_str[type];
++
++		seq_printf(m, "%05x %s %7s", i, state_str, type_str);
++
++		switch (type) {
++		case PPE_PKT_TYPE_IPV4_HNAPT:
++		case PPE_PKT_TYPE_IPV4_DSLITE:
++			src_port = &hwe->ipv4.orig_tuple.src_port;
++			dest_port = &hwe->ipv4.orig_tuple.dest_port;
++			fallthrough;
++		case PPE_PKT_TYPE_IPV4_ROUTE:
++			src_addr = &hwe->ipv4.orig_tuple.src_ip;
++			dest_addr = &hwe->ipv4.orig_tuple.dest_ip;
++			break;
++		case PPE_PKT_TYPE_IPV6_ROUTE_5T:
++			src_port = &hwe->ipv6.src_port;
++			dest_port = &hwe->ipv6.dest_port;
++			fallthrough;
++		case PPE_PKT_TYPE_IPV6_ROUTE_3T:
++		case PPE_PKT_TYPE_IPV6_6RD:
++			src_addr = &hwe->ipv6.src_ip;
++			dest_addr = &hwe->ipv6.dest_ip;
++			ipv6 = true;
++			break;
++		default:
++			break;
++		}
++
++		if (src_addr && dest_addr) {
++			seq_puts(m, " orig=");
++			airoha_debugfs_ppe_print_tuple(m, src_addr, dest_addr,
++						       src_port, dest_port, ipv6);
++		}
++
++		switch (type) {
++		case PPE_PKT_TYPE_IPV4_HNAPT:
++		case PPE_PKT_TYPE_IPV4_DSLITE:
++			src_port = &hwe->ipv4.new_tuple.src_port;
++			dest_port = &hwe->ipv4.new_tuple.dest_port;
++			fallthrough;
++		case PPE_PKT_TYPE_IPV4_ROUTE:
++			src_addr = &hwe->ipv4.new_tuple.src_ip;
++			dest_addr = &hwe->ipv4.new_tuple.dest_ip;
++			seq_puts(m, " new=");
++			airoha_debugfs_ppe_print_tuple(m, src_addr, dest_addr,
++						       src_port, dest_port,
++						       ipv6);
++			break;
++		default:
++			break;
++		}
++
++		if (type >= PPE_PKT_TYPE_IPV6_ROUTE_3T) {
++			data = hwe->ipv6.data;
++			ib2 = hwe->ipv6.ib2;
++			l2 = &hwe->ipv6.l2;
++		} else {
++			data = hwe->ipv4.data;
++			ib2 = hwe->ipv4.ib2;
++			l2 = &hwe->ipv4.l2.common;
++			*((__be16 *)&h_source[4]) =
++				cpu_to_be16(hwe->ipv4.l2.src_mac_lo);
++		}
++
++		*((__be32 *)h_dest) = cpu_to_be32(l2->dest_mac_hi);
++		*((__be16 *)&h_dest[4]) = cpu_to_be16(l2->dest_mac_lo);
++		*((__be32 *)h_source) = cpu_to_be32(l2->src_mac_hi);
++
++		seq_printf(m, " eth=%pM->%pM etype=%04x data=%08x"
++			      " vlan=%d,%d ib1=%08x ib2=%08x\n",
++			   h_source, h_dest, l2->etype, data,
++			   l2->vlan1, l2->vlan2, hwe->ib1, ib2);
++	}
++
++	return 0;
++}
++
++static int airoha_ppe_debugfs_foe_all_show(struct seq_file *m, void *private)
++{
++	return airoha_ppe_debugfs_foe_show(m, private, false);
++}
++DEFINE_SHOW_ATTRIBUTE(airoha_ppe_debugfs_foe_all);
++
++static int airoha_ppe_debugfs_foe_bind_show(struct seq_file *m, void *private)
++{
++	return airoha_ppe_debugfs_foe_show(m, private, true);
++}
++DEFINE_SHOW_ATTRIBUTE(airoha_ppe_debugfs_foe_bind);
++
++int airoha_ppe_debugfs_init(struct airoha_ppe *ppe)
++{
++	ppe->debugfs_dir = debugfs_create_dir("ppe", NULL);
++	debugfs_create_file("entries", 0444, ppe->debugfs_dir, ppe,
++			    &airoha_ppe_debugfs_foe_all_fops);
++	debugfs_create_file("bind", 0444, ppe->debugfs_dir, ppe,
++			    &airoha_ppe_debugfs_foe_bind_fops);
++
++	return 0;
++}

+ 3 - 3
target/linux/airoha/patches-6.6/113-net-airoha-Fix-channel-configuration-for-ETS-Qdisc.patch

@@ -84,9 +84,9 @@ change-id: 20250107-airoha-ets-fix-chan-e35ccac76d64
 
 Best regards,
 
---- a/drivers/net/ethernet/mediatek/airoha_eth.c
-+++ b/drivers/net/ethernet/mediatek/airoha_eth.c
-@@ -2846,11 +2846,14 @@ static int airoha_qdma_get_tx_ets_stats(
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2064,11 +2064,14 @@ static int airoha_qdma_get_tx_ets_stats(
  static int airoha_tc_setup_qdisc_ets(struct airoha_gdm_port *port,
  				     struct tc_ets_qopt_offload *opt)
  {