--- /dev/null
+From 09630ab91d840416b0178f3660afa4eebce24286 Mon Sep 17 00:00:00 2001
+From: "Gustavo A. R. Silva" <gustavoars@kernel.org>
+Date: Mon, 22 Sep 2025 16:08:21 +0200
+Subject: [PATCH] net: airoha: Avoid -Wflex-array-member-not-at-end warning
+
+-Wflex-array-member-not-at-end was introduced in GCC-14, and we are
+getting ready to enable it, globally.
+
+Move the conflicting declaration to the end of the corresponding
+structure. Notice that `struct airoha_foe_entry` is a flexible
+structure, this is a structure that contains a flexible-array
+member.
+
+Fix the following warning:
+
+drivers/net/ethernet/airoha/airoha_eth.h:474:33: warning: structure containing a flexible array member is not at the end of another structure [-Wflex-array-member-not-at-end]
+
+Signed-off-by: Gustavo A. R. Silva <gustavoars@kernel.org>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/aNFYVYLXQDqm4yxb@kspp
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.h | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -471,7 +471,6 @@ struct airoha_flow_table_entry {
+ };
+ };
+
+- struct airoha_foe_entry data;
+ struct hlist_node l2_subflow_node; /* PPE L2 subflow entry */
+ u32 hash;
+
+@@ -480,6 +479,9 @@ struct airoha_flow_table_entry {
+
+ struct rhash_head node;
+ unsigned long cookie;
++
++ /* Must be last --ends in a flexible-array member. */
++ struct airoha_foe_entry data;
+ };
+
+ struct airoha_wdma_info {
--- /dev/null
+From e156dd6b856fa462430d875b0d4cd281ecd66c23 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Thu, 18 Sep 2025 08:59:41 +0200
+Subject: [PATCH] net: airoha: Fix PPE_IP_PROTO_CHK register definitions
+
+Fix typo in PPE_IP_PROTO_CHK_IPV4_MASK and PPE_IP_PROTO_CHK_IPV6_MASK
+register mask definitions. This is not a real problem since this
+register is not actually used in the current codebase.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_regs.h | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_regs.h
++++ b/drivers/net/ethernet/airoha/airoha_regs.h
+@@ -237,8 +237,8 @@
+ #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 PPE_IP_PROTO_CHK_IPV4_MASK GENMASK(31, 16)
++#define PPE_IP_PROTO_CHK_IPV6_MASK GENMASK(15, 0)
+
+ #define REG_PPE_TB_CFG(_n) (((_n) ? PPE2_BASE : PPE1_BASE) + 0x21c)
+ #define PPE_SRAM_TB_NUM_ENTRY_MASK GENMASK(26, 24)
--- /dev/null
+From 105ce7ad57e492b75ab40f2dc591db645fadbaa2 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Wed, 24 Sep 2025 23:14:53 +0200
+Subject: [PATCH] net: airoha: npu: Add a NPU callback to initialize flow stats
+
+Introduce a NPU callback to initialize flow stats and remove NPU stats
+initialization from airoha_npu_get routine. Add num_stats_entries to
+airoha_npu_ppe_stats_setup routine.
+This patch makes the code more readable since NPU statistic are now
+initialized on demand by the NPU consumer (at the moment NPU statistic
+are configured just by the airoha_eth driver).
+Moreover this patch allows the NPU consumer (PPE module) to explicitly
+enable/disable NPU flow stats.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/20250924-airoha-npu-init-stats-callback-v1-1-88bdf3c941b2@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_npu.c | 24 ++++++-----------------
+ drivers/net/ethernet/airoha/airoha_ppe.c | 19 ++++++++++++------
+ include/linux/soc/airoha/airoha_offload.h | 7 ++++---
+ 3 files changed, 23 insertions(+), 27 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_npu.c
++++ b/drivers/net/ethernet/airoha/airoha_npu.c
+@@ -379,15 +379,13 @@ out:
+ return err;
+ }
+
+-static int airoha_npu_stats_setup(struct airoha_npu *npu,
+- dma_addr_t foe_stats_addr)
++static int airoha_npu_ppe_stats_setup(struct airoha_npu *npu,
++ dma_addr_t foe_stats_addr,
++ u32 num_stats_entries)
+ {
+- int err, size = PPE_STATS_NUM_ENTRIES * sizeof(*npu->stats);
++ int err, size = num_stats_entries * sizeof(*npu->stats);
+ struct ppe_mbox_data *ppe_data;
+
+- if (!size) /* flow stats are disabled */
+- return 0;
+-
+ ppe_data = kzalloc(sizeof(*ppe_data), GFP_ATOMIC);
+ if (!ppe_data)
+ return -ENOMEM;
+@@ -542,7 +540,7 @@ static void airoha_npu_wlan_irq_disable(
+ regmap_clear_bits(npu->regmap, REG_IRQ_RXDONE(q), NPU_IRQ_RX_MASK(q));
+ }
+
+-struct airoha_npu *airoha_npu_get(struct device *dev, dma_addr_t *stats_addr)
++struct airoha_npu *airoha_npu_get(struct device *dev)
+ {
+ struct platform_device *pdev;
+ struct device_node *np;
+@@ -580,17 +578,6 @@ struct airoha_npu *airoha_npu_get(struct
+ goto error_module_put;
+ }
+
+- if (stats_addr) {
+- int err;
+-
+- err = airoha_npu_stats_setup(npu, *stats_addr);
+- if (err) {
+- dev_err(dev, "failed to allocate npu stats buffer\n");
+- npu = ERR_PTR(err);
+- goto error_module_put;
+- }
+- }
+-
+ return npu;
+
+ error_module_put:
+@@ -643,6 +630,7 @@ static int airoha_npu_probe(struct platf
+ npu->dev = dev;
+ npu->ops.ppe_init = airoha_npu_ppe_init;
+ npu->ops.ppe_deinit = airoha_npu_ppe_deinit;
++ npu->ops.ppe_init_stats = airoha_npu_ppe_stats_setup;
+ npu->ops.ppe_flush_sram_entries = airoha_npu_ppe_flush_sram_entries;
+ npu->ops.ppe_foe_commit_entry = airoha_npu_foe_commit_entry;
+ npu->ops.wlan_init_reserved_memory = airoha_npu_wlan_init_memory;
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -1243,12 +1243,11 @@ static int airoha_ppe_flush_sram_entries
+
+ static struct airoha_npu *airoha_ppe_npu_get(struct airoha_eth *eth)
+ {
+- struct airoha_npu *npu = airoha_npu_get(eth->dev,
+- ð->ppe->foe_stats_dma);
++ struct airoha_npu *npu = airoha_npu_get(eth->dev);
+
+ if (IS_ERR(npu)) {
+ request_module("airoha-npu");
+- npu = airoha_npu_get(eth->dev, ð->ppe->foe_stats_dma);
++ npu = airoha_npu_get(eth->dev);
+ }
+
+ return npu;
+@@ -1257,6 +1256,7 @@ static struct airoha_npu *airoha_ppe_npu
+ static int airoha_ppe_offload_setup(struct airoha_eth *eth)
+ {
+ struct airoha_npu *npu = airoha_ppe_npu_get(eth);
++ struct airoha_ppe *ppe = eth->ppe;
+ int err;
+
+ if (IS_ERR(npu))
+@@ -1266,12 +1266,19 @@ static int airoha_ppe_offload_setup(stru
+ if (err)
+ goto error_npu_put;
+
+- airoha_ppe_hw_init(eth->ppe);
+- err = airoha_ppe_flush_sram_entries(eth->ppe, npu);
++ if (PPE_STATS_NUM_ENTRIES) {
++ err = npu->ops.ppe_init_stats(npu, ppe->foe_stats_dma,
++ PPE_STATS_NUM_ENTRIES);
++ if (err)
++ goto error_npu_put;
++ }
++
++ airoha_ppe_hw_init(ppe);
++ err = airoha_ppe_flush_sram_entries(ppe, npu);
+ if (err)
+ goto error_npu_put;
+
+- airoha_ppe_foe_flow_stats_reset(eth->ppe, npu);
++ airoha_ppe_foe_flow_stats_reset(ppe, npu);
+
+ rcu_assign_pointer(eth->npu, npu);
+ synchronize_rcu();
+--- a/include/linux/soc/airoha/airoha_offload.h
++++ b/include/linux/soc/airoha/airoha_offload.h
+@@ -181,6 +181,8 @@ struct airoha_npu {
+ struct {
+ int (*ppe_init)(struct airoha_npu *npu);
+ int (*ppe_deinit)(struct airoha_npu *npu);
++ int (*ppe_init_stats)(struct airoha_npu *npu,
++ dma_addr_t addr, u32 num_stats_entries);
+ int (*ppe_flush_sram_entries)(struct airoha_npu *npu,
+ dma_addr_t foe_addr,
+ int sram_num_entries);
+@@ -206,7 +208,7 @@ struct airoha_npu {
+ };
+
+ #if (IS_BUILTIN(CONFIG_NET_AIROHA_NPU) || IS_MODULE(CONFIG_NET_AIROHA_NPU))
+-struct airoha_npu *airoha_npu_get(struct device *dev, dma_addr_t *stats_addr);
++struct airoha_npu *airoha_npu_get(struct device *dev);
+ void airoha_npu_put(struct airoha_npu *npu);
+
+ static inline int airoha_npu_wlan_init_reserved_memory(struct airoha_npu *npu)
+@@ -256,8 +258,7 @@ static inline void airoha_npu_wlan_disab
+ npu->ops.wlan_disable_irq(npu, q);
+ }
+ #else
+-static inline struct airoha_npu *airoha_npu_get(struct device *dev,
+- dma_addr_t *foe_stats_addr)
++static inline struct airoha_npu *airoha_npu_get(struct device *dev)
+ {
+ return NULL;
+ }
--- /dev/null
+From fea8cdf6738a8b25fccbb7b109b440795a0892cb Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Wed, 8 Oct 2025 11:27:43 +0200
+Subject: [PATCH] net: airoha: Fix loopback mode configuration for GDM2 port
+
+Add missing configuration for loopback mode in airhoha_set_gdm2_loopback
+routine.
+
+Fixes: 9cd451d414f6e ("net: airoha: Add loopback support for GDM2")
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
+Link: https://patch.msgid.link/20251008-airoha-loopback-mode-fix-v2-1-045694fe7f60@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 4 +++-
+ drivers/net/ethernet/airoha/airoha_regs.h | 3 +++
+ 2 files changed, 6 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1715,7 +1715,9 @@ static void airhoha_set_gdm2_loopback(st
+ 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);
++ FIELD_PREP(LPBK_CHAN_MASK, chan) |
++ LBK_GAP_MODE_MASK | LBK_LEN_MODE_MASK |
++ LBK_CHAN_MODE_MASK | 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) |
+--- a/drivers/net/ethernet/airoha/airoha_regs.h
++++ b/drivers/net/ethernet/airoha/airoha_regs.h
+@@ -151,6 +151,9 @@
+ #define LPBK_LEN_MASK GENMASK(23, 10)
+ #define LPBK_CHAN_MASK GENMASK(8, 4)
+ #define LPBK_MODE_MASK GENMASK(3, 1)
++#define LBK_GAP_MODE_MASK BIT(3)
++#define LBK_LEN_MODE_MASK BIT(2)
++#define LBK_CHAN_MODE_MASK BIT(1)
+ #define LPBK_EN_MASK BIT(0)
+
+ #define REG_GDM_TXCHN_EN(_n) (GDM_BASE(_n) + 0x24)
--- /dev/null
+From 331f8a8bea22aecf99437f3561453a85f40026de Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 13 Oct 2025 16:29:41 +0200
+Subject: [PATCH] net: airoha: Add missing stats to ethtool_eth_mac_stats
+
+Add the following stats to ethtool ethtool_eth_mac_stats stats:
+- FramesTransmittedOK
+- OctetsTransmittedOK
+- FramesReceivedOK
+- OctetsReceivedOK
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20251013-airoha-ethtool-improvements-v1-1-fdd1c6fc9be1@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2027,8 +2027,12 @@ static void airoha_ethtool_get_mac_stats
+ airoha_update_hw_stats(port);
+ do {
+ start = u64_stats_fetch_begin(&port->stats.syncp);
++ stats->FramesTransmittedOK = port->stats.tx_ok_pkts;
++ stats->OctetsTransmittedOK = port->stats.tx_ok_bytes;
+ stats->MulticastFramesXmittedOK = port->stats.tx_multicast;
+ stats->BroadcastFramesXmittedOK = port->stats.tx_broadcast;
++ stats->FramesReceivedOK = port->stats.rx_ok_pkts;
++ stats->OctetsReceivedOK = port->stats.rx_ok_bytes;
+ stats->BroadcastFramesReceivedOK = port->stats.rx_broadcast;
+ } while (u64_stats_fetch_retry(&port->stats.syncp, start));
+ }
--- /dev/null
+From fc4fed9054ef5b5269d4395dd9db36fe98fce9e3 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 13 Oct 2025 16:29:42 +0200
+Subject: [PATCH] net: airoha: Add get_link ethtool callback
+
+Set get_link ethtool callback to ethtool_op_get_link routine.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/20251013-airoha-ethtool-improvements-v1-2-fdd1c6fc9be1@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2775,6 +2775,7 @@ static const struct ethtool_ops airoha_e
+ .get_drvinfo = airoha_ethtool_get_drvinfo,
+ .get_eth_mac_stats = airoha_ethtool_get_mac_stats,
+ .get_rmon_stats = airoha_ethtool_get_rmon_stats,
++ .get_link = ethtool_op_get_link,
+ };
+
+ static int airoha_metadata_dst_alloc(struct airoha_gdm_port *port)
--- /dev/null
+From bd5afca115f181c85f992d42a57cd497bc823ccb Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sun, 12 Oct 2025 11:19:44 +0200
+Subject: [PATCH] net: airoha: Take into account out-of-order tx completions in
+ airoha_dev_xmit()
+
+Completion napi can free out-of-order tx descriptors if hw QoS is
+enabled and packets with different priority are queued to same DMA ring.
+Take into account possible out-of-order reports checking if the tx queue
+is full using circular buffer head/tail pointer instead of the number of
+queued packets.
+
+Fixes: 23020f0493270 ("net: airoha: Introduce ethernet support for EN7581 SoC")
+Suggested-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/20251012-airoha-tx-busy-queue-v2-1-a600b08bab2d@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 16 +++++++++++++++-
+ 1 file changed, 15 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1878,6 +1878,20 @@ static u32 airoha_get_dsa_tag(struct sk_
+ #endif
+ }
+
++static bool airoha_dev_tx_queue_busy(struct airoha_queue *q, u32 nr_frags)
++{
++ u32 tail = q->tail <= q->head ? q->tail + q->ndesc : q->tail;
++ u32 index = q->head + nr_frags;
++
++ /* completion napi can free out-of-order tx descriptors if hw QoS is
++ * enabled and packets with different priorities are queued to the same
++ * DMA ring. Take into account possible out-of-order reports checking
++ * if the tx queue is full using circular buffer head/tail pointers
++ * instead of the number of queued packets.
++ */
++ return index >= tail;
++}
++
+ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
+ struct net_device *dev)
+ {
+@@ -1931,7 +1945,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+ txq = netdev_get_tx_queue(dev, qid);
+ nr_frags = 1 + skb_shinfo(skb)->nr_frags;
+
+- if (q->queued + nr_frags > q->ndesc) {
++ if (airoha_dev_tx_queue_busy(q, nr_frags)) {
+ /* not enough space in the queue */
+ netif_tx_stop_queue(txq);
+ spin_unlock_bh(&q->lock);
--- /dev/null
+From 6d5b601d52a27aafff555b480e538507901c672c Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Oct 2025 11:06:11 +0200
+Subject: [PATCH 01/12] net: airoha: ppe: Dynamically allocate foe_check_time
+ array in airoha_ppe struct
+
+This is a preliminary patch to properly enable PPE support for AN7583
+SoC.
+
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20251017-an7583-eth-support-v3-2-f28319666667@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.h | 2 +-
+ drivers/net/ethernet/airoha/airoha_ppe.c | 5 +++++
+ 2 files changed, 6 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -554,7 +554,7 @@ struct airoha_ppe {
+ struct rhashtable l2_flows;
+
+ struct hlist_head *foe_flow;
+- u16 foe_check_time[PPE_NUM_ENTRIES];
++ u16 *foe_check_time;
+
+ struct airoha_foe_stats *foe_stats;
+ dma_addr_t foe_stats_dma;
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -1440,6 +1440,11 @@ int airoha_ppe_init(struct airoha_eth *e
+ return -ENOMEM;
+ }
+
++ ppe->foe_check_time = devm_kzalloc(eth->dev, PPE_NUM_ENTRIES,
++ GFP_KERNEL);
++ if (!ppe->foe_check_time)
++ return -ENOMEM;
++
+ err = rhashtable_init(ð->flow_table, &airoha_flow_table_params);
+ if (err)
+ return err;
--- /dev/null
+From 15f357cd4581ce6e02e5e97719320600783140ec Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Oct 2025 11:06:12 +0200
+Subject: [PATCH 02/12] net: airoha: Add airoha_ppe_get_num_stats_entries() and
+ airoha_ppe_get_num_total_stats_entries()
+
+Introduce airoha_ppe_get_num_stats_entries and
+airoha_ppe_get_num_total_stats_entries routines in order to make the
+code more readable controlling if CONFIG_NET_AIROHA_FLOW_STATS is
+enabled or disabled.
+Modify airoha_ppe_foe_get_flow_stats_index routine signature relying on
+airoha_ppe_get_num_total_stats_entries().
+
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20251017-an7583-eth-support-v3-3-f28319666667@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.h | 10 +--
+ drivers/net/ethernet/airoha/airoha_ppe.c | 101 ++++++++++++++++++-----
+ 2 files changed, 81 insertions(+), 30 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -50,15 +50,9 @@
+
+ #define PPE_NUM 2
+ #define PPE1_SRAM_NUM_ENTRIES (8 * 1024)
+-#define PPE_SRAM_NUM_ENTRIES (2 * PPE1_SRAM_NUM_ENTRIES)
+-#ifdef CONFIG_NET_AIROHA_FLOW_STATS
++#define PPE_SRAM_NUM_ENTRIES (PPE_NUM * PPE1_SRAM_NUM_ENTRIES)
+ #define PPE1_STATS_NUM_ENTRIES (4 * 1024)
+-#else
+-#define PPE1_STATS_NUM_ENTRIES 0
+-#endif /* CONFIG_NET_AIROHA_FLOW_STATS */
+-#define PPE_STATS_NUM_ENTRIES (2 * PPE1_STATS_NUM_ENTRIES)
+-#define PPE1_SRAM_NUM_DATA_ENTRIES (PPE1_SRAM_NUM_ENTRIES - PPE1_STATS_NUM_ENTRIES)
+-#define PPE_SRAM_NUM_DATA_ENTRIES (2 * PPE1_SRAM_NUM_DATA_ENTRIES)
++#define PPE_STATS_NUM_ENTRIES (PPE_NUM * PPE1_STATS_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)
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -32,6 +32,24 @@ static const struct rhashtable_params ai
+ .automatic_shrinking = true,
+ };
+
++static int airoha_ppe_get_num_stats_entries(struct airoha_ppe *ppe)
++{
++ if (!IS_ENABLED(CONFIG_NET_AIROHA_FLOW_STATS))
++ return -EOPNOTSUPP;
++
++ return PPE1_STATS_NUM_ENTRIES;
++}
++
++static int airoha_ppe_get_total_num_stats_entries(struct airoha_ppe *ppe)
++{
++ int num_stats = airoha_ppe_get_num_stats_entries(ppe);
++
++ if (num_stats > 0)
++ num_stats = num_stats * PPE_NUM;
++
++ return num_stats;
++}
++
+ 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;
+@@ -48,7 +66,7 @@ static void airoha_ppe_hw_init(struct ai
+ {
+ u32 sram_tb_size, sram_num_entries, dram_num_entries;
+ struct airoha_eth *eth = ppe->eth;
+- int i;
++ int i, sram_num_stats_entries;
+
+ sram_tb_size = PPE_SRAM_NUM_ENTRIES * sizeof(struct airoha_foe_entry);
+ dram_num_entries = PPE_RAM_NUM_ENTRIES_SHIFT(PPE_DRAM_NUM_ENTRIES);
+@@ -103,8 +121,13 @@ static void airoha_ppe_hw_init(struct ai
+ }
+
+ if (airoha_ppe2_is_enabled(eth)) {
+- sram_num_entries =
+- PPE_RAM_NUM_ENTRIES_SHIFT(PPE1_SRAM_NUM_DATA_ENTRIES);
++ sram_num_entries = PPE1_SRAM_NUM_ENTRIES;
++ sram_num_stats_entries =
++ airoha_ppe_get_num_stats_entries(ppe);
++ if (sram_num_stats_entries > 0)
++ sram_num_entries -= sram_num_stats_entries;
++ sram_num_entries = PPE_RAM_NUM_ENTRIES_SHIFT(sram_num_entries);
++
+ airoha_fe_rmw(eth, REG_PPE_TB_CFG(0),
+ PPE_SRAM_TB_NUM_ENTRY_MASK |
+ PPE_DRAM_TB_NUM_ENTRY_MASK,
+@@ -120,8 +143,13 @@ static void airoha_ppe_hw_init(struct ai
+ FIELD_PREP(PPE_DRAM_TB_NUM_ENTRY_MASK,
+ dram_num_entries));
+ } else {
+- sram_num_entries =
+- PPE_RAM_NUM_ENTRIES_SHIFT(PPE_SRAM_NUM_DATA_ENTRIES);
++ sram_num_entries = PPE_SRAM_NUM_ENTRIES;
++ sram_num_stats_entries =
++ airoha_ppe_get_total_num_stats_entries(ppe);
++ if (sram_num_stats_entries > 0)
++ sram_num_entries -= sram_num_stats_entries;
++ sram_num_entries = PPE_RAM_NUM_ENTRIES_SHIFT(sram_num_entries);
++
+ airoha_fe_rmw(eth, REG_PPE_TB_CFG(0),
+ PPE_SRAM_TB_NUM_ENTRY_MASK |
+ PPE_DRAM_TB_NUM_ENTRY_MASK,
+@@ -480,13 +508,21 @@ static u32 airoha_ppe_foe_get_entry_hash
+ return hash;
+ }
+
+-static u32 airoha_ppe_foe_get_flow_stats_index(struct airoha_ppe *ppe, u32 hash)
++static int airoha_ppe_foe_get_flow_stats_index(struct airoha_ppe *ppe,
++ u32 hash, u32 *index)
+ {
+- if (!airoha_ppe2_is_enabled(ppe->eth))
+- return hash;
++ int ppe_num_stats_entries;
++
++ ppe_num_stats_entries = airoha_ppe_get_total_num_stats_entries(ppe);
++ if (ppe_num_stats_entries < 0)
++ return ppe_num_stats_entries;
++
++ *index = hash;
++ if (airoha_ppe2_is_enabled(ppe->eth) &&
++ hash >= ppe_num_stats_entries)
++ *index = *index - PPE_STATS_NUM_ENTRIES;
+
+- return hash >= PPE_STATS_NUM_ENTRIES ? hash - PPE1_STATS_NUM_ENTRIES
+- : hash;
++ return 0;
+ }
+
+ static void airoha_ppe_foe_flow_stat_entry_reset(struct airoha_ppe *ppe,
+@@ -500,9 +536,13 @@ static void airoha_ppe_foe_flow_stat_ent
+ static void airoha_ppe_foe_flow_stats_reset(struct airoha_ppe *ppe,
+ struct airoha_npu *npu)
+ {
+- int i;
++ int i, ppe_num_stats_entries;
++
++ ppe_num_stats_entries = airoha_ppe_get_total_num_stats_entries(ppe);
++ if (ppe_num_stats_entries < 0)
++ return;
+
+- for (i = 0; i < PPE_STATS_NUM_ENTRIES; i++)
++ for (i = 0; i < ppe_num_stats_entries; i++)
+ airoha_ppe_foe_flow_stat_entry_reset(ppe, npu, i);
+ }
+
+@@ -513,10 +553,17 @@ static void airoha_ppe_foe_flow_stats_up
+ {
+ int type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe->ib1);
+ u32 index, pse_port, val, *data, *ib2, *meter;
++ int ppe_num_stats_entries;
+ u8 nbq;
+
+- index = airoha_ppe_foe_get_flow_stats_index(ppe, hash);
+- if (index >= PPE_STATS_NUM_ENTRIES)
++ ppe_num_stats_entries = airoha_ppe_get_total_num_stats_entries(ppe);
++ if (ppe_num_stats_entries < 0)
++ return;
++
++ if (airoha_ppe_foe_get_flow_stats_index(ppe, hash, &index))
++ return;
++
++ if (index >= ppe_num_stats_entries)
+ return;
+
+ if (type == PPE_PKT_TYPE_BRIDGE) {
+@@ -1158,11 +1205,19 @@ static int airoha_ppe_flow_offload_destr
+ void airoha_ppe_foe_entry_get_stats(struct airoha_ppe *ppe, u32 hash,
+ struct airoha_foe_stats64 *stats)
+ {
+- u32 index = airoha_ppe_foe_get_flow_stats_index(ppe, hash);
+ struct airoha_eth *eth = ppe->eth;
++ int ppe_num_stats_entries;
+ struct airoha_npu *npu;
++ u32 index;
++
++ ppe_num_stats_entries = airoha_ppe_get_total_num_stats_entries(ppe);
++ if (ppe_num_stats_entries < 0)
++ return;
+
+- if (index >= PPE_STATS_NUM_ENTRIES)
++ if (airoha_ppe_foe_get_flow_stats_index(ppe, hash, &index))
++ return;
++
++ if (index >= ppe_num_stats_entries)
+ return;
+
+ rcu_read_lock();
+@@ -1257,7 +1312,7 @@ static int airoha_ppe_offload_setup(stru
+ {
+ struct airoha_npu *npu = airoha_ppe_npu_get(eth);
+ struct airoha_ppe *ppe = eth->ppe;
+- int err;
++ int err, ppe_num_stats_entries;
+
+ if (IS_ERR(npu))
+ return PTR_ERR(npu);
+@@ -1266,9 +1321,10 @@ static int airoha_ppe_offload_setup(stru
+ if (err)
+ goto error_npu_put;
+
+- if (PPE_STATS_NUM_ENTRIES) {
++ ppe_num_stats_entries = airoha_ppe_get_total_num_stats_entries(ppe);
++ if (ppe_num_stats_entries > 0) {
+ err = npu->ops.ppe_init_stats(npu, ppe->foe_stats_dma,
+- PPE_STATS_NUM_ENTRIES);
++ ppe_num_stats_entries);
+ if (err)
+ goto error_npu_put;
+ }
+@@ -1405,8 +1461,8 @@ EXPORT_SYMBOL_GPL(airoha_ppe_put_dev);
+
+ int airoha_ppe_init(struct airoha_eth *eth)
+ {
++ int foe_size, err, ppe_num_stats_entries;
+ struct airoha_ppe *ppe;
+- int foe_size, err;
+
+ ppe = devm_kzalloc(eth->dev, sizeof(*ppe), GFP_KERNEL);
+ if (!ppe)
+@@ -1431,8 +1487,9 @@ int airoha_ppe_init(struct airoha_eth *e
+ if (!ppe->foe_flow)
+ return -ENOMEM;
+
+- foe_size = PPE_STATS_NUM_ENTRIES * sizeof(*ppe->foe_stats);
+- if (foe_size) {
++ ppe_num_stats_entries = airoha_ppe_get_total_num_stats_entries(ppe);
++ if (ppe_num_stats_entries > 0) {
++ foe_size = ppe_num_stats_entries * sizeof(*ppe->foe_stats);
+ ppe->foe_stats = dmam_alloc_coherent(eth->dev, foe_size,
+ &ppe->foe_stats_dma,
+ GFP_KERNEL);
--- /dev/null
+From 5863b4e065e2253ef05684f728a04e4972046bcb Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Oct 2025 11:06:13 +0200
+Subject: [PATCH 03/12] net: airoha: Add airoha_eth_soc_data struct
+
+Introduce airoha_eth_soc_data struct to contain differences between
+various SoC. Move XSI reset names in airoha_eth_soc_data. This is a
+preliminary patch to enable AN7583 ethernet controller support in
+airoha-eth driver.
+
+Co-developed-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20251017-an7583-eth-support-v3-4-f28319666667@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 42 +++++++++++++++++++-----
+ drivers/net/ethernet/airoha/airoha_eth.h | 17 ++++++++--
+ 2 files changed, 48 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1392,8 +1392,7 @@ static int airoha_hw_init(struct platfor
+ int err, i;
+
+ /* disable xsi */
+- err = reset_control_bulk_assert(ARRAY_SIZE(eth->xsi_rsts),
+- eth->xsi_rsts);
++ err = reset_control_bulk_assert(eth->soc->num_xsi_rsts, eth->xsi_rsts);
+ if (err)
+ return err;
+
+@@ -2927,6 +2926,7 @@ free_metadata_dst:
+
+ static int airoha_probe(struct platform_device *pdev)
+ {
++ struct reset_control_bulk_data *xsi_rsts;
+ struct device_node *np;
+ struct airoha_eth *eth;
+ int i, err;
+@@ -2935,6 +2935,10 @@ static int airoha_probe(struct platform_
+ if (!eth)
+ return -ENOMEM;
+
++ eth->soc = of_device_get_match_data(&pdev->dev);
++ if (!eth->soc)
++ return -EINVAL;
++
+ eth->dev = &pdev->dev;
+
+ err = dma_set_mask_and_coherent(eth->dev, DMA_BIT_MASK(32));
+@@ -2959,13 +2963,18 @@ static int airoha_probe(struct platform_
+ 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";
++ xsi_rsts = devm_kzalloc(eth->dev,
++ eth->soc->num_xsi_rsts * sizeof(*xsi_rsts),
++ GFP_KERNEL);
++ if (err)
++ return err;
++
++ eth->xsi_rsts = xsi_rsts;
++ for (i = 0; i < eth->soc->num_xsi_rsts; i++)
++ eth->xsi_rsts[i].id = eth->soc->xsi_rsts_names[i];
++
+ err = devm_reset_control_bulk_get_exclusive(eth->dev,
+- ARRAY_SIZE(eth->xsi_rsts),
++ eth->soc->num_xsi_rsts,
+ eth->xsi_rsts);
+ if (err) {
+ dev_err(eth->dev, "failed to get bulk xsi reset lines\n");
+@@ -3052,8 +3061,23 @@ static void airoha_remove(struct platfor
+ platform_set_drvdata(pdev, NULL);
+ }
+
++static const char * const en7581_xsi_rsts_names[] = {
++ "xsi-mac",
++ "hsi0-mac",
++ "hsi1-mac",
++ "hsi-mac",
++ "xfp-mac",
++};
++
++static const struct airoha_eth_soc_data en7581_soc_data = {
++ .version = 0x7581,
++ .xsi_rsts_names = en7581_xsi_rsts_names,
++ .num_xsi_rsts = ARRAY_SIZE(en7581_xsi_rsts_names),
++ .num_ppe = 2,
++};
++
+ static const struct of_device_id of_airoha_match[] = {
+- { .compatible = "airoha,en7581-eth" },
++ { .compatible = "airoha,en7581-eth", .data = &en7581_soc_data },
+ { /* sentinel */ }
+ };
+ MODULE_DEVICE_TABLE(of, of_airoha_match);
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -21,7 +21,6 @@
+ #define AIROHA_MAX_NUM_IRQ_BANKS 4
+ #define AIROHA_MAX_DSA_PORTS 7
+ #define AIROHA_MAX_NUM_RSTS 3
+-#define AIROHA_MAX_NUM_XSI_RSTS 5
+ #define AIROHA_MAX_MTU 9216
+ #define AIROHA_MAX_PACKET_SIZE 2048
+ #define AIROHA_NUM_QOS_CHANNELS 4
+@@ -556,9 +555,18 @@ struct airoha_ppe {
+ struct dentry *debugfs_dir;
+ };
+
++struct airoha_eth_soc_data {
++ u16 version;
++ const char * const *xsi_rsts_names;
++ int num_xsi_rsts;
++ int num_ppe;
++};
++
+ struct airoha_eth {
+ struct device *dev;
+
++ const struct airoha_eth_soc_data *soc;
++
+ unsigned long state;
+ void __iomem *fe_regs;
+
+@@ -568,7 +576,7 @@ struct airoha_eth {
+ 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];
++ struct reset_control_bulk_data *xsi_rsts;
+
+ struct net_device *napi_dev;
+
+@@ -611,6 +619,11 @@ static inline bool airhoa_is_lan_gdm_por
+ return port->id == 1;
+ }
+
++static inline bool airoha_is_7581(struct airoha_eth *eth)
++{
++ return eth->soc->version == 0x7581;
++}
++
+ bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
+ struct airoha_gdm_port *port);
+
--- /dev/null
+From ef9449f080b61920cdc3d3106f8ffc2d9ba8b861 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Oct 2025 11:06:14 +0200
+Subject: [PATCH 04/12] net: airoha: Generalize airoha_ppe2_is_enabled routine
+
+Rename airoha_ppe2_is_enabled() in airoha_ppe_is_enabled() and
+generalize it in order to check if each PPE module is enabled.
+Rely on airoha_ppe_is_enabled routine to properly initialize PPE for
+AN7583 SoC since AN7583 does not support PPE2.
+
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20251017-an7583-eth-support-v3-5-f28319666667@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 32 ++++++++++++++++--------
+ drivers/net/ethernet/airoha/airoha_eth.h | 1 +
+ drivers/net/ethernet/airoha/airoha_ppe.c | 17 +++++++------
+ 3 files changed, 32 insertions(+), 18 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -297,8 +297,11 @@ static void airoha_fe_pse_ports_init(str
+ 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];
++ if (airoha_ppe_is_enabled(eth, 1)) {
++ /* 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 */
+@@ -335,13 +338,17 @@ static void airoha_fe_pse_ports_init(str
+ 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);
++ if (airoha_ppe_is_enabled(eth, 1)) {
++ /* 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++)
+@@ -1767,8 +1774,11 @@ static int airoha_dev_init(struct net_de
+ airhoha_set_gdm2_loopback(port);
+ fallthrough;
+ case 2:
+- pse_port = FE_PSE_PORT_PPE2;
+- break;
++ if (airoha_ppe_is_enabled(eth, 1)) {
++ pse_port = FE_PSE_PORT_PPE2;
++ break;
++ }
++ fallthrough;
+ default:
+ pse_port = FE_PSE_PORT_PPE1;
+ break;
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -627,6 +627,7 @@ static inline bool airoha_is_7581(struct
+ bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
+ struct airoha_gdm_port *port);
+
++bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index);
+ void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
+ u16 hash, bool rx_wlan);
+ int airoha_ppe_setup_tc_block_cb(struct airoha_ppe_dev *dev, void *type_data);
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -50,9 +50,12 @@ static int airoha_ppe_get_total_num_stat
+ return num_stats;
+ }
+
+-static bool airoha_ppe2_is_enabled(struct airoha_eth *eth)
++bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index)
+ {
+- return airoha_fe_rr(eth, REG_PPE_GLO_CFG(1)) & PPE_GLO_CFG_EN_MASK;
++ if (index >= eth->soc->num_ppe)
++ return false;
++
++ return airoha_fe_rr(eth, REG_PPE_GLO_CFG(index)) & PPE_GLO_CFG_EN_MASK;
+ }
+
+ static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe)
+@@ -120,7 +123,7 @@ static void airoha_ppe_hw_init(struct ai
+ AIROHA_MAX_MTU));
+ }
+
+- if (airoha_ppe2_is_enabled(eth)) {
++ if (airoha_ppe_is_enabled(eth, 1)) {
+ sram_num_entries = PPE1_SRAM_NUM_ENTRIES;
+ sram_num_stats_entries =
+ airoha_ppe_get_num_stats_entries(ppe);
+@@ -518,7 +521,7 @@ static int airoha_ppe_foe_get_flow_stats
+ return ppe_num_stats_entries;
+
+ *index = hash;
+- if (airoha_ppe2_is_enabled(ppe->eth) &&
++ if (airoha_ppe_is_enabled(ppe->eth, 1) &&
+ hash >= ppe_num_stats_entries)
+ *index = *index - PPE_STATS_NUM_ENTRIES;
+
+@@ -613,7 +616,7 @@ airoha_ppe_foe_get_entry_locked(struct a
+ u32 val;
+ int i;
+
+- ppe2 = airoha_ppe2_is_enabled(ppe->eth) &&
++ ppe2 = airoha_ppe_is_enabled(ppe->eth, 1) &&
+ hash >= PPE1_SRAM_NUM_ENTRIES;
+ airoha_fe_wr(ppe->eth, REG_PPE_RAM_CTRL(ppe2),
+ FIELD_PREP(PPE_SRAM_CTRL_ENTRY_MASK, hash) |
+@@ -691,7 +694,7 @@ static int airoha_ppe_foe_commit_entry(s
+
+ if (hash < PPE_SRAM_NUM_ENTRIES) {
+ dma_addr_t addr = ppe->foe_dma + hash * sizeof(*hwe);
+- bool ppe2 = airoha_ppe2_is_enabled(eth) &&
++ bool ppe2 = airoha_ppe_is_enabled(eth, 1) &&
+ hash >= PPE1_SRAM_NUM_ENTRIES;
+
+ err = npu->ops.ppe_foe_commit_entry(npu, addr, sizeof(*hwe),
+@@ -1286,7 +1289,7 @@ static int airoha_ppe_flush_sram_entries
+ int i, sram_num_entries = PPE_SRAM_NUM_ENTRIES;
+ struct airoha_foe_entry *hwe = ppe->foe;
+
+- if (airoha_ppe2_is_enabled(ppe->eth))
++ if (airoha_ppe_is_enabled(ppe->eth, 1))
+ sram_num_entries = sram_num_entries / 2;
+
+ for (i = 0; i < sram_num_entries; i++)
--- /dev/null
+From 5bd1d1fd48ea9f8300b211540d946899c7f96480 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Oct 2025 11:06:15 +0200
+Subject: [PATCH 05/12] net: airoha: ppe: Move PPE memory info in
+ airoha_eth_soc_data struct
+
+AN7583 SoC runs a single PPE device while EN7581 runs two of them.
+Moreover PPE SRAM in AN7583 SoC is reduced to 8K (while SRAM is 16K on
+EN7581). Take into account PPE memory layout during PPE configuration.
+
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20251017-an7583-eth-support-v3-6-f28319666667@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.h | 10 +-
+ drivers/net/ethernet/airoha/airoha_ppe.c | 133 +++++++++---------
+ .../net/ethernet/airoha/airoha_ppe_debugfs.c | 3 +-
+ 3 files changed, 70 insertions(+), 76 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -47,14 +47,9 @@
+ #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 (PPE_NUM * PPE1_SRAM_NUM_ENTRIES)
+-#define PPE1_STATS_NUM_ENTRIES (4 * 1024)
+-#define PPE_STATS_NUM_ENTRIES (PPE_NUM * PPE1_STATS_NUM_ENTRIES)
++#define PPE_SRAM_NUM_ENTRIES (8 * 1024)
++#define PPE_STATS_NUM_ENTRIES (4 * 1024)
+ #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))
+
+@@ -634,6 +629,7 @@ int airoha_ppe_setup_tc_block_cb(struct
+ int airoha_ppe_init(struct airoha_eth *eth);
+ void airoha_ppe_deinit(struct airoha_eth *eth);
+ void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port);
++u32 airoha_ppe_get_total_num_entries(struct airoha_ppe *ppe);
+ struct airoha_foe_entry *airoha_ppe_foe_get_entry(struct airoha_ppe *ppe,
+ u32 hash);
+ void airoha_ppe_foe_entry_get_stats(struct airoha_ppe *ppe, u32 hash,
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -37,19 +37,36 @@ static int airoha_ppe_get_num_stats_entr
+ if (!IS_ENABLED(CONFIG_NET_AIROHA_FLOW_STATS))
+ return -EOPNOTSUPP;
+
+- return PPE1_STATS_NUM_ENTRIES;
++ return PPE_STATS_NUM_ENTRIES;
+ }
+
+ static int airoha_ppe_get_total_num_stats_entries(struct airoha_ppe *ppe)
+ {
+ int num_stats = airoha_ppe_get_num_stats_entries(ppe);
+
+- if (num_stats > 0)
+- num_stats = num_stats * PPE_NUM;
++ if (num_stats > 0) {
++ struct airoha_eth *eth = ppe->eth;
++
++ num_stats = num_stats * eth->soc->num_ppe;
++ }
+
+ return num_stats;
+ }
+
++static u32 airoha_ppe_get_total_sram_num_entries(struct airoha_ppe *ppe)
++{
++ struct airoha_eth *eth = ppe->eth;
++
++ return PPE_SRAM_NUM_ENTRIES * eth->soc->num_ppe;
++}
++
++u32 airoha_ppe_get_total_num_entries(struct airoha_ppe *ppe)
++{
++ u32 sram_num_entries = airoha_ppe_get_total_sram_num_entries(ppe);
++
++ return sram_num_entries + PPE_DRAM_NUM_ENTRIES;
++}
++
+ bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index)
+ {
+ if (index >= eth->soc->num_ppe)
+@@ -67,14 +84,22 @@ static u32 airoha_ppe_get_timestamp(stru
+
+ static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
+ {
+- u32 sram_tb_size, sram_num_entries, dram_num_entries;
++ u32 sram_ppe_num_data_entries = PPE_SRAM_NUM_ENTRIES, sram_num_entries;
++ u32 sram_tb_size, dram_num_entries;
+ struct airoha_eth *eth = ppe->eth;
+ int i, sram_num_stats_entries;
+
+- sram_tb_size = PPE_SRAM_NUM_ENTRIES * sizeof(struct airoha_foe_entry);
++ sram_num_entries = airoha_ppe_get_total_sram_num_entries(ppe);
++ sram_tb_size = 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++) {
++ sram_num_stats_entries = airoha_ppe_get_num_stats_entries(ppe);
++ if (sram_num_stats_entries > 0)
++ sram_ppe_num_data_entries -= sram_num_stats_entries;
++ sram_ppe_num_data_entries =
++ PPE_RAM_NUM_ENTRIES_SHIFT(sram_ppe_num_data_entries);
++
++ for (i = 0; i < eth->soc->num_ppe; i++) {
+ int p;
+
+ airoha_fe_wr(eth, REG_PPE_TB_BASE(i),
+@@ -106,10 +131,16 @@ static void airoha_ppe_hw_init(struct ai
+
+ airoha_fe_rmw(eth, REG_PPE_TB_CFG(i),
+ PPE_TB_CFG_SEARCH_MISS_MASK |
++ PPE_SRAM_TB_NUM_ENTRY_MASK |
++ PPE_DRAM_TB_NUM_ENTRY_MASK |
+ PPE_TB_CFG_KEEPALIVE_MASK |
+ PPE_TB_ENTRY_SIZE_MASK,
+ FIELD_PREP(PPE_TB_CFG_SEARCH_MISS_MASK, 3) |
+- FIELD_PREP(PPE_TB_ENTRY_SIZE_MASK, 0));
++ FIELD_PREP(PPE_TB_ENTRY_SIZE_MASK, 0) |
++ FIELD_PREP(PPE_SRAM_TB_NUM_ENTRY_MASK,
++ sram_ppe_num_data_entries) |
++ FIELD_PREP(PPE_DRAM_TB_NUM_ENTRY_MASK,
++ dram_num_entries));
+
+ airoha_fe_wr(eth, REG_PPE_HASH_SEED(i), PPE_HASH_SEED);
+
+@@ -122,45 +153,6 @@ static void airoha_ppe_hw_init(struct ai
+ FIELD_PREP(FP1_EGRESS_MTU_MASK,
+ AIROHA_MAX_MTU));
+ }
+-
+- if (airoha_ppe_is_enabled(eth, 1)) {
+- sram_num_entries = PPE1_SRAM_NUM_ENTRIES;
+- sram_num_stats_entries =
+- airoha_ppe_get_num_stats_entries(ppe);
+- if (sram_num_stats_entries > 0)
+- sram_num_entries -= sram_num_stats_entries;
+- sram_num_entries = PPE_RAM_NUM_ENTRIES_SHIFT(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_SRAM_NUM_ENTRIES;
+- sram_num_stats_entries =
+- airoha_ppe_get_total_num_stats_entries(ppe);
+- if (sram_num_stats_entries > 0)
+- sram_num_entries -= sram_num_stats_entries;
+- sram_num_entries = PPE_RAM_NUM_ENTRIES_SHIFT(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)
+@@ -459,9 +451,11 @@ static int airoha_ppe_foe_entry_set_ipv6
+ return 0;
+ }
+
+-static u32 airoha_ppe_foe_get_entry_hash(struct airoha_foe_entry *hwe)
++static u32 airoha_ppe_foe_get_entry_hash(struct airoha_ppe *ppe,
++ struct airoha_foe_entry *hwe)
+ {
+ int type = FIELD_GET(AIROHA_FOE_IB1_BIND_PACKET_TYPE, hwe->ib1);
++ u32 ppe_hash_mask = airoha_ppe_get_total_num_entries(ppe) - 1;
+ u32 hash, hv1, hv2, hv3;
+
+ switch (type) {
+@@ -499,14 +493,14 @@ static u32 airoha_ppe_foe_get_entry_hash
+ case PPE_PKT_TYPE_IPV6_6RD:
+ default:
+ WARN_ON_ONCE(1);
+- return PPE_HASH_MASK;
++ 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;
++ hash &= ppe_hash_mask;
+
+ return hash;
+ }
+@@ -607,9 +601,11 @@ static void airoha_ppe_foe_flow_stats_up
+ static struct airoha_foe_entry *
+ airoha_ppe_foe_get_entry_locked(struct airoha_ppe *ppe, u32 hash)
+ {
++ u32 sram_num_entries = airoha_ppe_get_total_sram_num_entries(ppe);
++
+ lockdep_assert_held(&ppe_lock);
+
+- if (hash < PPE_SRAM_NUM_ENTRIES) {
++ if (hash < sram_num_entries) {
+ u32 *hwe = ppe->foe + hash * sizeof(struct airoha_foe_entry);
+ struct airoha_eth *eth = ppe->eth;
+ bool ppe2;
+@@ -617,7 +613,7 @@ airoha_ppe_foe_get_entry_locked(struct a
+ int i;
+
+ ppe2 = airoha_ppe_is_enabled(ppe->eth, 1) &&
+- hash >= PPE1_SRAM_NUM_ENTRIES;
++ hash >= PPE_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);
+@@ -668,6 +664,7 @@ static int airoha_ppe_foe_commit_entry(s
+ struct airoha_foe_entry *e,
+ u32 hash, bool rx_wlan)
+ {
++ u32 sram_num_entries = airoha_ppe_get_total_sram_num_entries(ppe);
+ struct airoha_foe_entry *hwe = ppe->foe + hash * sizeof(*hwe);
+ u32 ts = airoha_ppe_get_timestamp(ppe);
+ struct airoha_eth *eth = ppe->eth;
+@@ -692,10 +689,10 @@ static int airoha_ppe_foe_commit_entry(s
+ if (!rx_wlan)
+ airoha_ppe_foe_flow_stats_update(ppe, npu, hwe, hash);
+
+- if (hash < PPE_SRAM_NUM_ENTRIES) {
++ if (hash < sram_num_entries) {
+ dma_addr_t addr = ppe->foe_dma + hash * sizeof(*hwe);
+ bool ppe2 = airoha_ppe_is_enabled(eth, 1) &&
+- hash >= PPE1_SRAM_NUM_ENTRIES;
++ hash >= PPE_SRAM_NUM_ENTRIES;
+
+ err = npu->ops.ppe_foe_commit_entry(npu, addr, sizeof(*hwe),
+ hash, ppe2);
+@@ -822,7 +819,7 @@ static void airoha_ppe_foe_insert_entry(
+ if (state == AIROHA_FOE_STATE_BIND)
+ goto unlock;
+
+- index = airoha_ppe_foe_get_entry_hash(hwe);
++ index = airoha_ppe_foe_get_entry_hash(ppe, hwe);
+ hlist_for_each_entry_safe(e, n, &ppe->foe_flow[index], list) {
+ if (e->type == FLOW_TYPE_L2_SUBFLOW) {
+ state = FIELD_GET(AIROHA_FOE_IB1_BIND_STATE, hwe->ib1);
+@@ -882,7 +879,7 @@ static int airoha_ppe_foe_flow_commit_en
+ if (type == PPE_PKT_TYPE_BRIDGE)
+ return airoha_ppe_foe_l2_flow_commit_entry(ppe, e);
+
+- hash = airoha_ppe_foe_get_entry_hash(&e->data);
++ hash = airoha_ppe_foe_get_entry_hash(ppe, &e->data);
+ e->type = FLOW_TYPE_L4;
+ e->hash = 0xffff;
+
+@@ -1286,17 +1283,15 @@ static int airoha_ppe_flow_offload_cmd(s
+ static int airoha_ppe_flush_sram_entries(struct airoha_ppe *ppe,
+ struct airoha_npu *npu)
+ {
+- int i, sram_num_entries = PPE_SRAM_NUM_ENTRIES;
++ u32 sram_num_entries = airoha_ppe_get_total_sram_num_entries(ppe);
+ struct airoha_foe_entry *hwe = ppe->foe;
++ int i;
+
+- if (airoha_ppe_is_enabled(ppe->eth, 1))
+- sram_num_entries = sram_num_entries / 2;
+-
+- for (i = 0; i < sram_num_entries; i++)
++ for (i = 0; i < PPE_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);
++ sram_num_entries);
+ }
+
+ static struct airoha_npu *airoha_ppe_npu_get(struct airoha_eth *eth)
+@@ -1372,9 +1367,10 @@ void airoha_ppe_check_skb(struct airoha_
+ u16 hash, bool rx_wlan)
+ {
+ struct airoha_ppe *ppe = dev->priv;
++ u32 ppe_hash_mask = airoha_ppe_get_total_num_entries(ppe) - 1;
+ u16 now, diff;
+
+- if (hash > PPE_HASH_MASK)
++ if (hash > ppe_hash_mask)
+ return;
+
+ now = (u16)jiffies;
+@@ -1465,6 +1461,7 @@ EXPORT_SYMBOL_GPL(airoha_ppe_put_dev);
+ int airoha_ppe_init(struct airoha_eth *eth)
+ {
+ int foe_size, err, ppe_num_stats_entries;
++ u32 ppe_num_entries;
+ struct airoha_ppe *ppe;
+
+ ppe = devm_kzalloc(eth->dev, sizeof(*ppe), GFP_KERNEL);
+@@ -1474,18 +1471,18 @@ int airoha_ppe_init(struct airoha_eth *e
+ ppe->dev.ops.setup_tc_block_cb = airoha_ppe_setup_tc_block_cb;
+ ppe->dev.ops.check_skb = airoha_ppe_check_skb;
+ ppe->dev.priv = ppe;
++ ppe->eth = eth;
++ eth->ppe = ppe;
+
+- foe_size = PPE_NUM_ENTRIES * sizeof(struct airoha_foe_entry);
++ ppe_num_entries = airoha_ppe_get_total_num_entries(ppe);
++ 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),
++ ppe_num_entries * sizeof(*ppe->foe_flow),
+ GFP_KERNEL);
+ if (!ppe->foe_flow)
+ return -ENOMEM;
+@@ -1500,7 +1497,7 @@ int airoha_ppe_init(struct airoha_eth *e
+ return -ENOMEM;
+ }
+
+- ppe->foe_check_time = devm_kzalloc(eth->dev, PPE_NUM_ENTRIES,
++ ppe->foe_check_time = devm_kzalloc(eth->dev, ppe_num_entries,
+ GFP_KERNEL);
+ if (!ppe->foe_check_time)
+ return -ENOMEM;
+--- a/drivers/net/ethernet/airoha/airoha_ppe_debugfs.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe_debugfs.c
+@@ -53,9 +53,10 @@ static int airoha_ppe_debugfs_foe_show(s
+ [AIROHA_FOE_STATE_FIN] = "FIN",
+ };
+ struct airoha_ppe *ppe = m->private;
++ u32 ppe_num_entries = airoha_ppe_get_total_num_entries(ppe);
+ int i;
+
+- for (i = 0; i < PPE_NUM_ENTRIES; 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;
--- /dev/null
+From 41139125f5c70e0f66f0cc4ac1b3a62f5801ab42 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Oct 2025 11:06:16 +0200
+Subject: [PATCH 06/12] net: airoha: ppe: Remove airoha_ppe_is_enabled() where
+ not necessary
+
+Now each PPE has always PPE_STATS_NUM_ENTRIES entries so we do not need
+to run airoha_ppe_is_enabled routine to check if the hash refers to
+PPE1 or PPE2.
+
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20251017-an7583-eth-support-v3-7-f28319666667@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_ppe.c | 13 ++++---------
+ 1 file changed, 4 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -514,10 +514,8 @@ static int airoha_ppe_foe_get_flow_stats
+ if (ppe_num_stats_entries < 0)
+ return ppe_num_stats_entries;
+
+- *index = hash;
+- if (airoha_ppe_is_enabled(ppe->eth, 1) &&
+- hash >= ppe_num_stats_entries)
+- *index = *index - PPE_STATS_NUM_ENTRIES;
++ *index = hash >= ppe_num_stats_entries ? hash - PPE_STATS_NUM_ENTRIES
++ : hash;
+
+ return 0;
+ }
+@@ -607,13 +605,11 @@ airoha_ppe_foe_get_entry_locked(struct a
+
+ if (hash < sram_num_entries) {
+ u32 *hwe = ppe->foe + hash * sizeof(struct airoha_foe_entry);
++ bool ppe2 = hash >= PPE_SRAM_NUM_ENTRIES;
+ struct airoha_eth *eth = ppe->eth;
+- bool ppe2;
+ u32 val;
+ int i;
+
+- ppe2 = airoha_ppe_is_enabled(ppe->eth, 1) &&
+- hash >= PPE_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);
+@@ -691,8 +687,7 @@ static int airoha_ppe_foe_commit_entry(s
+
+ if (hash < sram_num_entries) {
+ dma_addr_t addr = ppe->foe_dma + hash * sizeof(*hwe);
+- bool ppe2 = airoha_ppe_is_enabled(eth, 1) &&
+- hash >= PPE_SRAM_NUM_ENTRIES;
++ bool ppe2 = hash >= PPE_SRAM_NUM_ENTRIES;
+
+ err = npu->ops.ppe_foe_commit_entry(npu, addr, sizeof(*hwe),
+ hash, ppe2);
--- /dev/null
+From 306b78f5035af4bd011753c5a6b12812515caa6c Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Oct 2025 11:06:17 +0200
+Subject: [PATCH 07/12] net: airoha: ppe: Configure SRAM PPE entries via the
+ cpu
+
+Introduce airoha_ppe_foe_commit_sram_entry routine in order to configure
+the SRAM PPE entries directly via the CPU instead of using the NPU APIs.
+This is a preliminary patch to enable netfilter flowtable hw offload for
+AN7583 SoC.
+
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20251017-an7583-eth-support-v3-8-f28319666667@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_ppe.c | 30 ++++++++++++++++++------
+ 1 file changed, 23 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -656,6 +656,27 @@ static bool airoha_ppe_foe_compare_entry
+ return !memcmp(&e->data.d, &hwe->d, len - sizeof(hwe->ib1));
+ }
+
++static int airoha_ppe_foe_commit_sram_entry(struct airoha_ppe *ppe, u32 hash)
++{
++ struct airoha_foe_entry *hwe = ppe->foe + hash * sizeof(*hwe);
++ bool ppe2 = hash >= PPE_SRAM_NUM_ENTRIES;
++ u32 *ptr = (u32 *)hwe, val;
++ int i;
++
++ for (i = 0; i < sizeof(*hwe) / sizeof(*ptr); i++)
++ airoha_fe_wr(ppe->eth, REG_PPE_RAM_ENTRY(ppe2, i), ptr[i]);
++
++ wmb();
++ airoha_fe_wr(ppe->eth, REG_PPE_RAM_CTRL(ppe2),
++ FIELD_PREP(PPE_SRAM_CTRL_ENTRY_MASK, hash) |
++ PPE_SRAM_CTRL_WR_MASK | PPE_SRAM_CTRL_REQ_MASK);
++
++ return read_poll_timeout_atomic(airoha_fe_rr, val,
++ val & PPE_SRAM_CTRL_ACK_MASK,
++ 10, 100, false, ppe->eth,
++ REG_PPE_RAM_CTRL(ppe2));
++}
++
+ static int airoha_ppe_foe_commit_entry(struct airoha_ppe *ppe,
+ struct airoha_foe_entry *e,
+ u32 hash, bool rx_wlan)
+@@ -685,13 +706,8 @@ static int airoha_ppe_foe_commit_entry(s
+ if (!rx_wlan)
+ airoha_ppe_foe_flow_stats_update(ppe, npu, hwe, hash);
+
+- if (hash < sram_num_entries) {
+- dma_addr_t addr = ppe->foe_dma + hash * sizeof(*hwe);
+- bool ppe2 = hash >= PPE_SRAM_NUM_ENTRIES;
+-
+- err = npu->ops.ppe_foe_commit_entry(npu, addr, sizeof(*hwe),
+- hash, ppe2);
+- }
++ if (hash < sram_num_entries)
++ err = airoha_ppe_foe_commit_sram_entry(ppe, hash);
+ unlock:
+ rcu_read_unlock();
+
--- /dev/null
+From 620d7b91aadbd4eb930895b07e34f0a155a9d3c1 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Oct 2025 11:06:18 +0200
+Subject: [PATCH 08/12] net: airoha: ppe: Flush PPE SRAM table during PPE setup
+
+Rely on airoha_ppe_foe_commit_sram_entry routine to flush SRAM PPE table
+entries. This patch allow moving PPE SRAM flush during PPE setup and
+avoid dumping uninitialized values via the debugfs if no entries are
+offloaded yet.
+
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20251017-an7583-eth-support-v3-9-f28319666667@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_ppe.c | 24 ++++++++++++++----------
+ 1 file changed, 14 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -1291,18 +1291,22 @@ static int airoha_ppe_flow_offload_cmd(s
+ return -EOPNOTSUPP;
+ }
+
+-static int airoha_ppe_flush_sram_entries(struct airoha_ppe *ppe,
+- struct airoha_npu *npu)
++static int airoha_ppe_flush_sram_entries(struct airoha_ppe *ppe)
+ {
+ u32 sram_num_entries = airoha_ppe_get_total_sram_num_entries(ppe);
+ struct airoha_foe_entry *hwe = ppe->foe;
+- int i;
++ int i, err = 0;
++
++ for (i = 0; i < sram_num_entries; i++) {
++ int err;
+
+- for (i = 0; i < PPE_SRAM_NUM_ENTRIES; i++)
+ memset(&hwe[i], 0, sizeof(*hwe));
++ err = airoha_ppe_foe_commit_sram_entry(ppe, i);
++ if (err)
++ break;
++ }
+
+- return npu->ops.ppe_flush_sram_entries(npu, ppe->foe_dma,
+- sram_num_entries);
++ return err;
+ }
+
+ static struct airoha_npu *airoha_ppe_npu_get(struct airoha_eth *eth)
+@@ -1339,10 +1343,6 @@ static int airoha_ppe_offload_setup(stru
+ }
+
+ airoha_ppe_hw_init(ppe);
+- err = airoha_ppe_flush_sram_entries(ppe, npu);
+- if (err)
+- goto error_npu_put;
+-
+ airoha_ppe_foe_flow_stats_reset(ppe, npu);
+
+ rcu_assign_pointer(eth->npu, npu);
+@@ -1513,6 +1513,10 @@ int airoha_ppe_init(struct airoha_eth *e
+ if (!ppe->foe_check_time)
+ return -ENOMEM;
+
++ err = airoha_ppe_flush_sram_entries(ppe);
++ if (err)
++ return err;
++
+ err = rhashtable_init(ð->flow_table, &airoha_flow_table_params);
+ if (err)
+ return err;
--- /dev/null
+From c71a7a861ef02aa2bebb18c2f3385aa3f19094e0 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Oct 2025 11:06:19 +0200
+Subject: [PATCH 09/12] net: airoha: Select default ppe cpu port in
+ airoha_dev_init()
+
+Select the PPE default cpu port in airoha_dev_init routine.
+This patch allows to distribute the load between the two available cpu
+ports (FE_PSE_PORT_CDM1 and FE_PSE_PORT_CDM2) if the device is running a
+single PPE module (e.g. 7583) selecting the cpu port based on the use
+QDMA device. For multi-PPE device (e.g. 7581) assign FE_PSE_PORT_CDM1 to
+PPE1 and FE_PSE_PORT_CDM2 to PPE2.
+
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20251017-an7583-eth-support-v3-10-f28319666667@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 38 ++++++++++--------------
+ 1 file changed, 16 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -531,25 +531,6 @@ static int airoha_fe_init(struct airoha_
+ /* disable IFC by default */
+ airoha_fe_clear(eth, REG_FE_CSR_IFC_CFG, FE_IFC_EN_MASK);
+
+- airoha_fe_wr(eth, REG_PPE_DFT_CPORT0(0),
+- FIELD_PREP(DFT_CPORT_MASK(7), FE_PSE_PORT_CDM1) |
+- FIELD_PREP(DFT_CPORT_MASK(6), FE_PSE_PORT_CDM1) |
+- FIELD_PREP(DFT_CPORT_MASK(5), FE_PSE_PORT_CDM1) |
+- FIELD_PREP(DFT_CPORT_MASK(4), FE_PSE_PORT_CDM1) |
+- FIELD_PREP(DFT_CPORT_MASK(3), FE_PSE_PORT_CDM1) |
+- FIELD_PREP(DFT_CPORT_MASK(2), FE_PSE_PORT_CDM1) |
+- FIELD_PREP(DFT_CPORT_MASK(1), FE_PSE_PORT_CDM1) |
+- FIELD_PREP(DFT_CPORT_MASK(0), FE_PSE_PORT_CDM1));
+- airoha_fe_wr(eth, REG_PPE_DFT_CPORT0(1),
+- FIELD_PREP(DFT_CPORT_MASK(7), FE_PSE_PORT_CDM2) |
+- FIELD_PREP(DFT_CPORT_MASK(6), FE_PSE_PORT_CDM2) |
+- FIELD_PREP(DFT_CPORT_MASK(5), FE_PSE_PORT_CDM2) |
+- FIELD_PREP(DFT_CPORT_MASK(4), FE_PSE_PORT_CDM2) |
+- FIELD_PREP(DFT_CPORT_MASK(3), FE_PSE_PORT_CDM2) |
+- FIELD_PREP(DFT_CPORT_MASK(2), FE_PSE_PORT_CDM2) |
+- FIELD_PREP(DFT_CPORT_MASK(1), FE_PSE_PORT_CDM2) |
+- FIELD_PREP(DFT_CPORT_MASK(0), FE_PSE_PORT_CDM2));
+-
+ /* enable 1:N vlan action, init vlan table */
+ airoha_fe_set(eth, REG_MC_VLAN_EN, MC_VLAN_EN_MASK);
+
+@@ -1761,8 +1742,10 @@ static void airhoha_set_gdm2_loopback(st
+ 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;
++ struct airoha_qdma *qdma = port->qdma;
++ struct airoha_eth *eth = qdma->eth;
++ u32 pse_port, fe_cpu_port;
++ u8 ppe_id;
+
+ airoha_set_macaddr(port, dev->dev_addr);
+
+@@ -1775,16 +1758,27 @@ static int airoha_dev_init(struct net_de
+ fallthrough;
+ case 2:
+ if (airoha_ppe_is_enabled(eth, 1)) {
++ /* For PPE2 always use secondary cpu port. */
++ fe_cpu_port = FE_PSE_PORT_CDM2;
+ pse_port = FE_PSE_PORT_PPE2;
+ break;
+ }
+ fallthrough;
+- default:
++ default: {
++ u8 qdma_id = qdma - ð->qdma[0];
++
++ /* For PPE1 select cpu port according to the running QDMA. */
++ fe_cpu_port = qdma_id ? FE_PSE_PORT_CDM2 : FE_PSE_PORT_CDM1;
+ pse_port = FE_PSE_PORT_PPE1;
+ break;
+ }
++ }
+
+ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id), pse_port);
++ ppe_id = pse_port == FE_PSE_PORT_PPE2 ? 1 : 0;
++ airoha_fe_rmw(eth, REG_PPE_DFT_CPORT0(ppe_id),
++ DFT_CPORT_MASK(port->id),
++ fe_cpu_port << __ffs(DFT_CPORT_MASK(port->id)));
+
+ return 0;
+ }
--- /dev/null
+From 9d5b5219f672c80bed4d4e15f0068e648cdca43b Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Oct 2025 11:06:20 +0200
+Subject: [PATCH 10/12] net: airoha: Refactor src port configuration in
+ airhoha_set_gdm2_loopback
+
+AN7583 chipset relies on different definitions for source-port
+identifier used for hw offloading. In order to support hw offloading
+in AN7583 controller, refactor src port configuration in
+airhoha_set_gdm2_loopback routine and introduce get_src_port_id
+callback.
+
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20251017-an7583-eth-support-v3-11-f28319666667@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 82 ++++++++++++++++-------
+ drivers/net/ethernet/airoha/airoha_eth.h | 18 +++--
+ drivers/net/ethernet/airoha/airoha_regs.h | 6 +-
+ 3 files changed, 73 insertions(+), 33 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1687,13 +1687,17 @@ static int airoha_dev_set_macaddr(struct
+ return 0;
+ }
+
+-static void airhoha_set_gdm2_loopback(struct airoha_gdm_port *port)
++static int airhoha_set_gdm2_loopback(struct airoha_gdm_port *port)
+ {
+- u32 pse_port = port->id == 3 ? FE_PSE_PORT_GDM3 : FE_PSE_PORT_GDM4;
++ u32 val, pse_port, chan = port->id == AIROHA_GDM3_IDX ? 4 : 0;
+ struct airoha_eth *eth = port->qdma->eth;
+- u32 chan = port->id == 3 ? 4 : 0;
++ /* XXX: handle XSI_USB_PORT and XSI_PCE1_PORT */
++ u32 nbq = port->id == AIROHA_GDM3_IDX ? 4 : 0;
++ int src_port;
+
+ /* Forward the traffic to the proper GDM port */
++ pse_port = port->id == AIROHA_GDM3_IDX ? FE_PSE_PORT_GDM3
++ : FE_PSE_PORT_GDM4;
+ 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);
+
+@@ -1714,29 +1718,25 @@ static void airhoha_set_gdm2_loopback(st
+ 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_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 */
++ src_port = eth->soc->ops.get_src_port_id(port, nbq);
++ if (src_port < 0)
++ return src_port;
++
++ airoha_fe_rmw(eth, REG_FE_WAN_PORT,
++ WAN1_EN_MASK | WAN1_MASK | WAN0_MASK,
++ FIELD_PREP(WAN0_MASK, src_port));
++ val = src_port & SP_CPORT_DFT_MASK;
++ airoha_fe_rmw(eth,
++ REG_SP_DFT_CPORT(src_port >> fls(SP_CPORT_DFT_MASK)),
++ SP_CPORT_MASK(val),
++ FE_PSE_PORT_CDM2 << __ffs(SP_CPORT_MASK(val)));
++
++ if (port->id != AIROHA_GDM3_IDX)
+ 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));
+- }
++
++ return 0;
+ }
+
+ static int airoha_dev_init(struct net_device *dev)
+@@ -1753,8 +1753,13 @@ static int airoha_dev_init(struct net_de
+ case 3:
+ case 4:
+ /* If GDM2 is active we can't enable loopback */
+- if (!eth->ports[1])
+- airhoha_set_gdm2_loopback(port);
++ if (!eth->ports[1]) {
++ int err;
++
++ err = airhoha_set_gdm2_loopback(port);
++ if (err)
++ return err;
++ }
+ fallthrough;
+ case 2:
+ if (airoha_ppe_is_enabled(eth, 1)) {
+@@ -3073,11 +3078,38 @@ static const char * const en7581_xsi_rst
+ "xfp-mac",
+ };
+
++static int airoha_en7581_get_src_port_id(struct airoha_gdm_port *port, int nbq)
++{
++ switch (port->id) {
++ case 3:
++ /* 7581 SoC supports PCIe serdes on GDM3 port */
++ if (nbq == 4)
++ return HSGMII_LAN_7581_PCIE0_SRCPORT;
++ if (nbq == 5)
++ return HSGMII_LAN_7581_PCIE1_SRCPORT;
++ break;
++ case 4:
++ /* 7581 SoC supports eth and usb serdes on GDM4 port */
++ if (!nbq)
++ return HSGMII_LAN_7581_ETH_SRCPORT;
++ if (nbq == 1)
++ return HSGMII_LAN_7581_USB_SRCPORT;
++ break;
++ default:
++ break;
++ }
++
++ return -EINVAL;
++}
++
+ static const struct airoha_eth_soc_data en7581_soc_data = {
+ .version = 0x7581,
+ .xsi_rsts_names = en7581_xsi_rsts_names,
+ .num_xsi_rsts = ARRAY_SIZE(en7581_xsi_rsts_names),
+ .num_ppe = 2,
++ .ops = {
++ .get_src_port_id = airoha_en7581_get_src_port_id,
++ },
+ };
+
+ static const struct of_device_id of_airoha_match[] = {
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -67,10 +67,10 @@ enum {
+ };
+
+ enum {
+- HSGMII_LAN_PCIE0_SRCPORT = 0x16,
+- HSGMII_LAN_PCIE1_SRCPORT,
+- HSGMII_LAN_ETH_SRCPORT,
+- HSGMII_LAN_USB_SRCPORT,
++ HSGMII_LAN_7581_PCIE0_SRCPORT = 0x16,
++ HSGMII_LAN_7581_PCIE1_SRCPORT,
++ HSGMII_LAN_7581_ETH_SRCPORT,
++ HSGMII_LAN_7581_USB_SRCPORT,
+ };
+
+ enum {
+@@ -99,6 +99,13 @@ enum {
+ CRSN_25 = 0x19,
+ };
+
++enum airoha_gdm_index {
++ AIROHA_GDM1_IDX = 1,
++ AIROHA_GDM2_IDX = 2,
++ AIROHA_GDM3_IDX = 3,
++ AIROHA_GDM4_IDX = 4,
++};
++
+ enum {
+ FE_PSE_PORT_CDM1,
+ FE_PSE_PORT_GDM1,
+@@ -555,6 +562,9 @@ struct airoha_eth_soc_data {
+ const char * const *xsi_rsts_names;
+ int num_xsi_rsts;
+ int num_ppe;
++ struct {
++ int (*get_src_port_id)(struct airoha_gdm_port *port, int nbq);
++ } ops;
+ };
+
+ struct airoha_eth {
+--- a/drivers/net/ethernet/airoha/airoha_regs.h
++++ b/drivers/net/ethernet/airoha/airoha_regs.h
+@@ -383,10 +383,8 @@
+ #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 SP_CPORT_DFT_MASK GENMASK(2, 0)
++#define SP_CPORT_MASK(_n) GENMASK(3 + ((_n) << 2), ((_n) << 2))
+
+ #define REG_SRC_PORT_FC_MAP6 0x2298
+ #define FC_ID_OF_SRC_PORT27_MASK GENMASK(28, 24)
--- /dev/null
+From 63f283d36b1fb06b55ae609a1f679544f5f66057 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Oct 2025 11:06:21 +0200
+Subject: [PATCH 11/12] net: airoha: ppe: Do not use magic numbers in
+ airoha_ppe_foe_get_entry_locked()
+
+Explicit the size of entries pointed by hwe pointer in
+airoha_ppe_foe_get_entry_locked routine
+
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20251017-an7583-eth-support-v3-12-f28319666667@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_ppe.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -619,7 +619,8 @@ airoha_ppe_foe_get_entry_locked(struct a
+ REG_PPE_RAM_CTRL(ppe2)))
+ return NULL;
+
+- for (i = 0; i < sizeof(struct airoha_foe_entry) / 4; i++)
++ for (i = 0; i < sizeof(struct airoha_foe_entry) / sizeof(*hwe);
++ i++)
+ hwe[i] = airoha_fe_rr(eth,
+ REG_PPE_RAM_ENTRY(ppe2, i));
+ }
--- /dev/null
+From e4e5ce823bdd4601bd75ae7c206ae35e7c2fa60b Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 17 Oct 2025 11:06:22 +0200
+Subject: [PATCH 12/12] net: airoha: Add AN7583 SoC support
+
+Introduce support for AN7583 ethernet controller to airoha-eth dirver.
+
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://patch.msgid.link/20251017-an7583-eth-support-v3-13-f28319666667@kernel.org
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 68 ++++++++++++++++++++++--
+ drivers/net/ethernet/airoha/airoha_eth.h | 11 ++++
+ drivers/net/ethernet/airoha/airoha_ppe.c | 3 ++
+ 3 files changed, 77 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1689,10 +1689,8 @@ static int airoha_dev_set_macaddr(struct
+
+ static int airhoha_set_gdm2_loopback(struct airoha_gdm_port *port)
+ {
+- u32 val, pse_port, chan = port->id == AIROHA_GDM3_IDX ? 4 : 0;
+ struct airoha_eth *eth = port->qdma->eth;
+- /* XXX: handle XSI_USB_PORT and XSI_PCE1_PORT */
+- u32 nbq = port->id == AIROHA_GDM3_IDX ? 4 : 0;
++ u32 val, pse_port, chan, nbq;
+ int src_port;
+
+ /* Forward the traffic to the proper GDM port */
+@@ -1704,6 +1702,8 @@ static int airhoha_set_gdm2_loopback(str
+ /* Enable GDM2 loopback */
+ airoha_fe_wr(eth, REG_GDM_TXCHN_EN(2), 0xffffffff);
+ airoha_fe_wr(eth, REG_GDM_RXCHN_EN(2), 0xffff);
++
++ chan = port->id == AIROHA_GDM3_IDX ? airoha_is_7581(eth) ? 4 : 3 : 0;
+ airoha_fe_rmw(eth, REG_GDM_LPBK_CFG(2),
+ LPBK_CHAN_MASK | LPBK_MODE_MASK | LPBK_EN_MASK,
+ FIELD_PREP(LPBK_CHAN_MASK, chan) |
+@@ -1718,6 +1718,8 @@ static int airhoha_set_gdm2_loopback(str
+ airoha_fe_clear(eth, REG_FE_VIP_PORT_EN, BIT(2));
+ airoha_fe_clear(eth, REG_FE_IFC_PORT_EN, BIT(2));
+
++ /* XXX: handle XSI_USB_PORT and XSI_PCE1_PORT */
++ nbq = port->id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
+ src_port = eth->soc->ops.get_src_port_id(port, nbq);
+ if (src_port < 0)
+ return src_port;
+@@ -1731,7 +1733,7 @@ static int airhoha_set_gdm2_loopback(str
+ SP_CPORT_MASK(val),
+ FE_PSE_PORT_CDM2 << __ffs(SP_CPORT_MASK(val)));
+
+- if (port->id != AIROHA_GDM3_IDX)
++ if (port->id != AIROHA_GDM3_IDX && airoha_is_7581(eth))
+ airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6,
+ FC_ID_OF_SRC_PORT24_MASK,
+ FIELD_PREP(FC_ID_OF_SRC_PORT24_MASK, 2));
+@@ -1900,6 +1902,22 @@ static bool airoha_dev_tx_queue_busy(str
+ return index >= tail;
+ }
+
++static int airoha_get_fe_port(struct airoha_gdm_port *port)
++{
++ struct airoha_qdma *qdma = port->qdma;
++ struct airoha_eth *eth = qdma->eth;
++
++ switch (eth->soc->version) {
++ case 0x7583:
++ return port->id == AIROHA_GDM3_IDX ? FE_PSE_PORT_GDM3
++ : port->id;
++ case 0x7581:
++ default:
++ return port->id == AIROHA_GDM4_IDX ? FE_PSE_PORT_GDM4
++ : port->id;
++ }
++}
++
+ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
+ struct net_device *dev)
+ {
+@@ -1940,7 +1958,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+ }
+ }
+
+- fport = port->id == 4 ? FE_PSE_PORT_GDM4 : port->id;
++ fport = airoha_get_fe_port(port);
+ msg1 = FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
+ FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
+
+@@ -3102,6 +3120,35 @@ static int airoha_en7581_get_src_port_id
+ return -EINVAL;
+ }
+
++static const char * const an7583_xsi_rsts_names[] = {
++ "xsi-mac",
++ "hsi0-mac",
++ "hsi1-mac",
++ "xfp-mac",
++};
++
++static int airoha_an7583_get_src_port_id(struct airoha_gdm_port *port, int nbq)
++{
++ switch (port->id) {
++ case 3:
++ /* 7583 SoC supports eth serdes on GDM3 port */
++ if (!nbq)
++ return HSGMII_LAN_7583_ETH_SRCPORT;
++ break;
++ case 4:
++ /* 7583 SoC supports PCIe and USB serdes on GDM4 port */
++ if (!nbq)
++ return HSGMII_LAN_7583_PCIE_SRCPORT;
++ if (nbq == 1)
++ return HSGMII_LAN_7583_USB_SRCPORT;
++ break;
++ default:
++ break;
++ }
++
++ return -EINVAL;
++}
++
+ static const struct airoha_eth_soc_data en7581_soc_data = {
+ .version = 0x7581,
+ .xsi_rsts_names = en7581_xsi_rsts_names,
+@@ -3112,8 +3159,19 @@ static const struct airoha_eth_soc_data
+ },
+ };
+
++static const struct airoha_eth_soc_data an7583_soc_data = {
++ .version = 0x7583,
++ .xsi_rsts_names = an7583_xsi_rsts_names,
++ .num_xsi_rsts = ARRAY_SIZE(an7583_xsi_rsts_names),
++ .num_ppe = 1,
++ .ops = {
++ .get_src_port_id = airoha_an7583_get_src_port_id,
++ },
++};
++
+ static const struct of_device_id of_airoha_match[] = {
+ { .compatible = "airoha,en7581-eth", .data = &en7581_soc_data },
++ { .compatible = "airoha,an7583-eth", .data = &an7583_soc_data },
+ { /* sentinel */ }
+ };
+ MODULE_DEVICE_TABLE(of, of_airoha_match);
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -74,6 +74,12 @@ enum {
+ };
+
+ enum {
++ HSGMII_LAN_7583_ETH_SRCPORT = 0x16,
++ HSGMII_LAN_7583_PCIE_SRCPORT = 0x18,
++ HSGMII_LAN_7583_USB_SRCPORT,
++};
++
++enum {
+ XSI_PCIE0_VIP_PORT_MASK = BIT(22),
+ XSI_PCIE1_VIP_PORT_MASK = BIT(23),
+ XSI_USB_VIP_PORT_MASK = BIT(25),
+@@ -629,6 +635,11 @@ static inline bool airoha_is_7581(struct
+ return eth->soc->version == 0x7581;
+ }
+
++static inline bool airoha_is_7583(struct airoha_eth *eth)
++{
++ return eth->soc->version == 0x7583;
++}
++
+ bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
+ struct airoha_gdm_port *port);
+
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -37,6 +37,9 @@ static int airoha_ppe_get_num_stats_entr
+ if (!IS_ENABLED(CONFIG_NET_AIROHA_FLOW_STATS))
+ return -EOPNOTSUPP;
+
++ if (airoha_is_7583(ppe->eth))
++ return -EOPNOTSUPP;
++
+ return PPE_STATS_NUM_ENTRIES;
+ }
+
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -1401,6 +1401,10 @@ static int airoha_hw_init(struct platfor
+@@ -1388,6 +1388,10 @@ static int airoha_hw_init(struct platfor
if (err)
return err;
-+ err = reset_control_bulk_deassert(ARRAY_SIZE(eth->xsi_rsts), eth->xsi_rsts);
++ err = reset_control_bulk_deassert(eth->soc->num_xsi_rsts, eth->xsi_rsts);
+ if (err)
+ return err;
+
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -605,8 +605,11 @@ static int airoha_qdma_get_gdm_port(stru
+@@ -593,8 +593,11 @@ static int airoha_qdma_get_gdm_port(stru
sport = FIELD_GET(QDMA_ETH_RXMSG_SPORT_MASK, msg1);
switch (sport) {
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -507,8 +507,8 @@ static int airoha_fe_init(struct airoha_
+@@ -514,8 +514,8 @@ static int airoha_fe_init(struct airoha_
FIELD_PREP(IP_ASSEMBLE_PORT_MASK, 0) |
FIELD_PREP(IP_ASSEMBLE_NBQ_MASK, 22));
#define GDM_DROP_CRC_ERR BIT(23)
#define GDM_IP4_CKSUM BIT(22)
#define GDM_TCP_CKSUM BIT(21)
-@@ -349,13 +350,6 @@
+@@ -352,13 +353,6 @@
#define MBI_RX_AGE_SEL_MASK GENMASK(26, 25)
#define MBI_TX_AGE_SEL_MASK GENMASK(18, 17)
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -507,8 +507,10 @@ static int airoha_fe_init(struct airoha_
+@@ -514,8 +514,10 @@ static int airoha_fe_init(struct airoha_
FIELD_PREP(IP_ASSEMBLE_PORT_MASK, 0) |
FIELD_PREP(IP_ASSEMBLE_NBQ_MASK, 22));
airoha_fe_crsn_qsel_init(eth);
-@@ -1643,7 +1645,8 @@ static int airoha_dev_open(struct net_de
+@@ -1630,7 +1632,8 @@ static int airoha_dev_open(struct net_de
if (err)
return err;
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -2948,11 +2948,10 @@ static int airoha_probe(struct platform_
- return err;
- }
+@@ -3099,7 +3099,6 @@ static void airoha_remove(struct platfor
+ }
-- 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";
-+ eth->xsi_rsts[0].id = "hsi0-mac";
-+ eth->xsi_rsts[1].id = "hsi1-mac";
-+ eth->xsi_rsts[2].id = "hsi-mac";
-+ eth->xsi_rsts[3].id = "xfp-mac";
- err = devm_reset_control_bulk_get_exclusive(eth->dev,
- ARRAY_SIZE(eth->xsi_rsts),
- eth->xsi_rsts);
---- a/drivers/net/ethernet/airoha/airoha_eth.h
-+++ b/drivers/net/ethernet/airoha/airoha_eth.h
-@@ -21,7 +21,7 @@
- #define AIROHA_MAX_NUM_IRQ_BANKS 4
- #define AIROHA_MAX_DSA_PORTS 7
- #define AIROHA_MAX_NUM_RSTS 3
--#define AIROHA_MAX_NUM_XSI_RSTS 5
-+#define AIROHA_MAX_NUM_XSI_RSTS 4
- #define AIROHA_MAX_MTU 9216
- #define AIROHA_MAX_PACKET_SIZE 2048
- #define AIROHA_NUM_QOS_CHANNELS 4
+ static const char * const en7581_xsi_rsts_names[] = {
+- "xsi-mac",
+ "hsi0-mac",
+ "hsi1-mac",
+ "hsi-mac",
+@@ -3131,7 +3130,6 @@ static int airoha_en7581_get_src_port_id
+ }
+
+ static const char * const an7583_xsi_rsts_names[] = {
+- "xsi-mac",
+ "hsi0-mac",
+ "hsi1-mac",
+ "xfp-mac",
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
-@@ -1041,6 +1041,12 @@ static void phylink_pcs_link_up(struct p
- pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex);
+@@ -1088,6 +1088,12 @@ static unsigned int phylink_inband_caps(
+ return phylink_pcs_inband_caps(pcs, interface);
}
+static void phylink_pcs_link_down(struct phylink_pcs *pcs)
static void phylink_pcs_poll_stop(struct phylink *pl)
{
if (pl->cfg_link_an_mode == MLO_AN_INBAND)
-@@ -1454,6 +1460,8 @@ static void phylink_link_down(struct phy
+@@ -1642,6 +1648,8 @@ static void phylink_link_down(struct phy
if (ndev)
netif_carrier_off(ndev);
+ phylink_pcs_link_down(pl->pcs);
+
- pl->mac_ops->mac_link_down(pl->config, pl->cur_link_an_mode,
+ pl->mac_ops->mac_link_down(pl->config, pl->act_link_an_mode,
pl->cur_interface);
phylink_info(pl, "Link is Down\n");
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
-@@ -430,6 +430,7 @@ struct phylink_pcs {
+@@ -431,6 +431,7 @@ struct phylink_pcs {
* (where necessary).
* @pcs_pre_init: configure PCS components necessary for MAC hardware
* initialization e.g. RX clock for stmmac.
*/
struct phylink_pcs_ops {
int (*pcs_validate)(struct phylink_pcs *pcs, unsigned long *supported,
-@@ -450,6 +451,7 @@ struct phylink_pcs_ops {
+@@ -453,6 +454,7 @@ struct phylink_pcs_ops {
void (*pcs_link_up)(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface, int speed, int duplex);
int (*pcs_pre_init)(struct phylink_pcs *pcs);
static void airoha_set_macaddr(struct airoha_gdm_port *port, const u8 *addr)
{
struct airoha_eth *eth = port->qdma->eth;
-@@ -1640,6 +1646,17 @@ static int airoha_dev_open(struct net_de
+@@ -1627,6 +1633,17 @@ static int airoha_dev_open(struct net_de
struct airoha_gdm_port *port = netdev_priv(dev);
struct airoha_qdma *qdma = port->qdma;
netif_tx_start_all_queues(dev);
err = airoha_set_vip_for_gdm_port(port, true);
if (err)
-@@ -1693,6 +1710,11 @@ static int airoha_dev_stop(struct net_de
+@@ -1680,6 +1697,11 @@ static int airoha_dev_stop(struct net_de
}
}
return 0;
}
-@@ -2781,6 +2803,20 @@ static const struct ethtool_ops airoha_e
- .get_rmon_stats = airoha_ethtool_get_rmon_stats,
+@@ -2828,6 +2850,20 @@ static const struct ethtool_ops airoha_e
+ .get_link = ethtool_op_get_link,
};
+static struct phylink_pcs *airoha_phylink_mac_select_pcs(struct phylink_config *config,
static int airoha_metadata_dst_alloc(struct airoha_gdm_port *port)
{
int i;
-@@ -2825,6 +2861,99 @@ bool airoha_is_valid_gdm_port(struct air
+@@ -2872,6 +2908,99 @@ bool airoha_is_valid_gdm_port(struct air
return false;
}
static int airoha_alloc_gdm_port(struct airoha_eth *eth,
struct device_node *np, int index)
{
-@@ -2903,6 +3032,12 @@ static int airoha_alloc_gdm_port(struct
+@@ -2950,6 +3079,12 @@ static int airoha_alloc_gdm_port(struct
if (err)
return err;
err = register_netdev(dev);
if (err)
goto free_metadata_dst;
-@@ -3006,6 +3141,10 @@ error_hw_cleanup:
+@@ -3064,6 +3199,10 @@ error_hw_cleanup:
if (port && port->dev->reg_state == NETREG_REGISTERED) {
unregister_netdev(port->dev);
airoha_metadata_dst_free(port);
}
}
free_netdev(eth->napi_dev);
-@@ -3033,6 +3172,10 @@ static void airoha_remove(struct platfor
+@@ -3091,6 +3230,10 @@ static void airoha_remove(struct platfor
airoha_dev_stop(port->dev);
unregister_netdev(port->dev);
airoha_metadata_dst_free(port);
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
-@@ -528,6 +528,10 @@ struct airoha_gdm_port {
+@@ -531,6 +531,10 @@ struct airoha_gdm_port {
struct net_device *dev;
int id;
DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
--- a/drivers/net/ethernet/airoha/airoha_regs.h
+++ b/drivers/net/ethernet/airoha/airoha_regs.h
-@@ -361,6 +361,18 @@
+@@ -364,6 +364,18 @@
#define IP_FRAGMENT_PORT_MASK GENMASK(8, 5)
#define IP_FRAGMENT_NBQ_MASK GENMASK(4, 0)
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
-@@ -616,6 +616,9 @@ static int airoha_qdma_get_gdm_port(stru
+@@ -604,6 +604,9 @@ static int airoha_qdma_get_gdm_port(stru
case 0x18:
port = 3; /* GDM4 */
break;
static void airoha_set_macaddr(struct airoha_gdm_port *port, const u8 *addr)
{
-@@ -1649,6 +1651,7 @@ static int airoha_dev_open(struct net_de
+@@ -1636,6 +1638,7 @@ static int airoha_dev_open(struct net_de
struct airoha_gdm_port *port = netdev_priv(dev);
struct airoha_qdma *qdma = port->qdma;
if (airhoa_is_phy_external(port)) {
err = phylink_of_phy_connect(port->phylink, dev->dev.of_node, 0);
if (err) {
-@@ -1659,6 +1662,7 @@ static int airoha_dev_open(struct net_de
+@@ -1646,6 +1649,7 @@ static int airoha_dev_open(struct net_de
phylink_start(port->phylink);
}
netif_tx_start_all_queues(dev);
err = airoha_set_vip_for_gdm_port(port, true);
-@@ -1713,10 +1717,12 @@ static int airoha_dev_stop(struct net_de
+@@ -1700,10 +1704,12 @@ static int airoha_dev_stop(struct net_de
}
}
return 0;
}
-@@ -2806,6 +2812,7 @@ static const struct ethtool_ops airoha_e
- .get_rmon_stats = airoha_ethtool_get_rmon_stats,
+@@ -2853,6 +2859,7 @@ static const struct ethtool_ops airoha_e
+ .get_link = ethtool_op_get_link,
};
+#if defined(CONFIG_PCS_AIROHA)
static struct phylink_pcs *airoha_phylink_mac_select_pcs(struct phylink_config *config,
phy_interface_t interface)
{
-@@ -2819,6 +2826,7 @@ static void airoha_mac_config(struct phy
+@@ -2866,6 +2873,7 @@ static void airoha_mac_config(struct phy
const struct phylink_link_state *state)
{
}
static int airoha_metadata_dst_alloc(struct airoha_gdm_port *port)
{
-@@ -2864,6 +2872,7 @@ bool airoha_is_valid_gdm_port(struct air
+@@ -2911,6 +2919,7 @@ bool airoha_is_valid_gdm_port(struct air
return false;
}
static void airoha_mac_link_up(struct phylink_config *config, struct phy_device *phy,
unsigned int mode, phy_interface_t interface,
int speed, int duplex, bool tx_pause, bool rx_pause)
-@@ -2956,6 +2965,7 @@ static int airoha_setup_phylink(struct n
+@@ -3003,6 +3012,7 @@ static int airoha_setup_phylink(struct n
return 0;
}
static int airoha_alloc_gdm_port(struct airoha_eth *eth,
struct device_node *np, int index)
-@@ -3035,11 +3045,13 @@ static int airoha_alloc_gdm_port(struct
+@@ -3082,11 +3092,13 @@ static int airoha_alloc_gdm_port(struct
if (err)
return err;
err = register_netdev(dev);
if (err)
-@@ -3144,10 +3156,12 @@ error_hw_cleanup:
+@@ -3202,10 +3214,12 @@ error_hw_cleanup:
if (port && port->dev->reg_state == NETREG_REGISTERED) {
unregister_netdev(port->dev);
airoha_metadata_dst_free(port);
}
}
free_netdev(eth->napi_dev);
-@@ -3175,10 +3189,12 @@ static void airoha_remove(struct platfor
+@@ -3233,10 +3247,12 @@ static void airoha_remove(struct platfor
airoha_dev_stop(port->dev);
unregister_netdev(port->dev);
airoha_metadata_dst_free(port);
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
-@@ -528,9 +528,11 @@ struct airoha_gdm_port {
+@@ -531,9 +531,11 @@ struct airoha_gdm_port {
struct net_device *dev;
int id;