--- /dev/null
+From e15783f7c987e199ecf80b3d858ed5a86d33c508 Mon Sep 17 00:00:00 2001
+Message-ID: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 1 Nov 2025 11:25:52 +0100
+Subject: [PATCH 01/13] net: airoha: Introduce airoha_gdm_dev struct
+
+EN7581 and AN7583 SoCs support connecting multiple external SerDes to GDM3
+or GDM4 ports via a hw arbiter that manages the traffic in a TDM manner.
+As a result multiple net_devices can connect to the same GDM{3,4} port
+and there is a theoretical "1:n" relation between GDM port and
+net_devices.
+Introduce airoha_gdm_dev struct to collect net_device related info (e.g.
+net_device and external phy pointer). Please note this is just a
+preliminary patch and we are still supporting a single net_device for
+each GDM port. Subsequent patches will add support for multiple net_devices
+connected to the same GDM port.
+
+Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 312 ++++++++++++++---------
+ drivers/net/ethernet/airoha/airoha_eth.h | 13 +-
+ drivers/net/ethernet/airoha/airoha_ppe.c | 17 +-
+ 3 files changed, 206 insertions(+), 136 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -727,6 +727,7 @@ static int airoha_qdma_rx_process(struct
+ struct airoha_qdma_desc *desc = &q->desc[q->tail];
+ u32 hash, reason, msg1, desc_ctrl;
+ struct airoha_gdm_port *port;
++ struct net_device *netdev;
+ int data_len, len, p;
+ struct page *page;
+
+@@ -753,6 +754,10 @@ static int airoha_qdma_rx_process(struct
+ goto free_frag;
+
+ port = eth->ports[p];
++ if (!port->dev)
++ goto free_frag;
++
++ netdev = port->dev->dev;
+ if (!q->skb) { /* first buffer */
+ q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
+ q->buf_size);
+@@ -762,15 +767,15 @@ static int airoha_qdma_rx_process(struct
+ skb_reserve(q->skb, AIROHA_RX_HEADROOM);
+ __skb_put(q->skb, len);
+ skb_mark_for_recycle(q->skb);
+- q->skb->dev = port->dev;
++ q->skb->dev = netdev;
+ q->skb->ip_summed = CHECKSUM_UNNECESSARY;
+ skb_record_rx_queue(q->skb, qid);
+
+- if (lro_q && (port->dev->features & NETIF_F_LRO) &&
++ if (lro_q && (netdev->features & NETIF_F_LRO) &&
+ airoha_qdma_lro_rx_process(q, desc) < 0)
+ goto free_frag;
+
+- q->skb->protocol = eth_type_trans(q->skb, port->dev);
++ q->skb->protocol = eth_type_trans(q->skb, netdev);
+ } else { /* scattered frame */
+ struct skb_shared_info *shinfo = skb_shinfo(q->skb);
+ int nr_frags = shinfo->nr_frags;
+@@ -786,7 +791,7 @@ static int airoha_qdma_rx_process(struct
+ if (FIELD_GET(QDMA_DESC_MORE_MASK, desc_ctrl))
+ continue;
+
+- if (netdev_uses_dsa(port->dev)) {
++ if (netdev_uses_dsa(netdev)) {
+ /* 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
+@@ -983,6 +988,7 @@ static void airoha_qdma_wake_netdev_txqs
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *port = eth->ports[i];
++ struct airoha_gdm_dev *dev;
+ int j;
+
+ if (!port)
+@@ -991,11 +997,12 @@ static void airoha_qdma_wake_netdev_txqs
+ if (port->qdma != qdma)
+ continue;
+
+- for (j = 0; j < port->dev->num_tx_queues; j++) {
++ dev = port->dev;
++ for (j = 0; j < dev->dev->num_tx_queues; j++) {
+ if (airoha_qdma_get_txq(qdma, j) != qid)
+ continue;
+
+- netif_wake_subqueue(port->dev, j);
++ netif_wake_subqueue(dev->dev, j);
+ }
+ }
+ q->txq_stopped = false;
+@@ -1839,33 +1846,34 @@ static void airoha_update_hw_stats(struc
+ spin_unlock(&port->stats.lock);
+ }
+
+-static int airoha_dev_open(struct net_device *dev)
++static int airoha_dev_open(struct net_device *netdev)
+ {
+- int err, len = ETH_HLEN + dev->mtu + ETH_FCS_LEN;
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma = port->qdma;
+ u32 pse_port = FE_PSE_PORT_PPE1;
+
+ #if defined(CONFIG_PCS_AIROHA)
+ if (airhoa_is_phy_external(port)) {
+- err = phylink_of_phy_connect(port->phylink, dev->dev.of_node, 0);
++ err = phylink_of_phy_connect(dev->phylink, netdev->dev.of_node, 0);
+ if (err) {
+- netdev_err(dev, "%s: could not attach PHY: %d\n", __func__,
++ netdev_err(netdev, "%s: could not attach PHY: %d\n", __func__,
+ err);
+ return err;
+ }
+
+- phylink_start(port->phylink);
++ phylink_start(dev->phylink);
+ }
+ #endif
+
+- netif_tx_start_all_queues(dev);
++ netif_tx_start_all_queues(netdev);
+ err = airoha_set_vip_for_gdm_port(port, true);
+ if (err)
+ return err;
+
+ /* It seems GDM3 and GDM4 needs SPORT enabled to correctly work */
+- if (netdev_uses_dsa(dev) || port->id > 2)
++ if (netdev_uses_dsa(netdev) || port->id > 2)
+ airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
+ GDM_STAG_EN_MASK);
+ else
+@@ -1893,16 +1901,17 @@ static int airoha_dev_open(struct net_de
+ return 0;
+ }
+
+-static int airoha_dev_stop(struct net_device *dev)
++static int airoha_dev_stop(struct net_device *netdev)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma = port->qdma;
+ int i;
+
+- netif_tx_disable(dev);
++ netif_tx_disable(netdev);
+ airoha_set_vip_for_gdm_port(port, false);
+- for (i = 0; i < dev->num_tx_queues; i++)
+- netdev_tx_reset_subqueue(dev, i);
++ for (i = 0; i < netdev->num_tx_queues; i++)
++ netdev_tx_reset_subqueue(netdev, i);
+
+ airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
+ FE_PSE_PORT_DROP);
+@@ -1922,24 +1931,25 @@ static int airoha_dev_stop(struct net_de
+
+ #if defined(CONFIG_PCS_AIROHA)
+ if (airhoa_is_phy_external(port)) {
+- phylink_stop(port->phylink);
+- phylink_disconnect_phy(port->phylink);
++ phylink_stop(dev->phylink);
++ phylink_disconnect_phy(dev->phylink);
+ }
+ #endif
+
+ return 0;
+ }
+
+-static int airoha_dev_set_macaddr(struct net_device *dev, void *p)
++static int airoha_dev_set_macaddr(struct net_device *netdev, void *p)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ int err;
+
+- err = eth_mac_addr(dev, p);
++ err = eth_mac_addr(netdev, p);
+ if (err)
+ return err;
+
+- airoha_set_macaddr(port, dev->dev_addr);
++ airoha_set_macaddr(port, netdev->dev_addr);
+
+ return 0;
+ }
+@@ -2005,16 +2015,17 @@ static int airoha_set_gdm2_loopback(stru
+ return 0;
+ }
+
+-static int airoha_dev_init(struct net_device *dev)
++static int airoha_dev_init(struct net_device *netdev)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
+- struct airoha_eth *eth = port->eth;
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
++ struct airoha_eth *eth = dev->eth;
+ int i;
+
+ /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
+ port->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
+- port->dev->irq = port->qdma->irq_banks[0].irq;
+- airoha_set_macaddr(port, dev->dev_addr);
++ dev->dev->irq = port->qdma->irq_banks[0].irq;
++ airoha_set_macaddr(port, netdev->dev_addr);
+
+ switch (port->id) {
+ case AIROHA_GDM3_IDX:
+@@ -2039,10 +2050,11 @@ static int airoha_dev_init(struct net_de
+ return 0;
+ }
+
+-static void airoha_dev_get_stats64(struct net_device *dev,
++static void airoha_dev_get_stats64(struct net_device *netdev,
+ struct rtnl_link_stats64 *storage)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ unsigned int start;
+
+ airoha_update_hw_stats(port);
+@@ -2061,36 +2073,39 @@ static void airoha_dev_get_stats64(struc
+ } while (u64_stats_fetch_retry(&port->stats.syncp, start));
+ }
+
+-static int airoha_dev_change_mtu(struct net_device *dev, int mtu)
++static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = port->qdma->eth;
+ u32 len = ETH_HLEN + mtu + ETH_FCS_LEN;
+
+ airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
+ GDM_LONG_LEN_MASK,
+ FIELD_PREP(GDM_LONG_LEN_MASK, len));
+- WRITE_ONCE(dev->mtu, mtu);
++ WRITE_ONCE(netdev->mtu, mtu);
+
+ return 0;
+ }
+
+-static u16 airoha_dev_select_queue(struct net_device *dev, struct sk_buff *skb,
++static u16 airoha_dev_select_queue(struct net_device *netdev,
++ struct sk_buff *skb,
+ struct net_device *sb_dev)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ 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 = netdev_uses_dsa(netdev) ? 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;
++ return queue < netdev->num_tx_queues ? queue : 0;
+ }
+
+ static u32 airoha_get_dsa_tag(struct sk_buff *skb, struct net_device *dev)
+@@ -2153,12 +2168,13 @@ int airoha_get_fe_port(struct airoha_gdm
+ }
+ }
+
+-static int airoha_dev_set_features(struct net_device *dev,
++static int airoha_dev_set_features(struct net_device *netdev,
+ netdev_features_t features)
+ {
+
+- netdev_features_t diff = dev->features ^ features;
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ netdev_features_t diff = netdev->features ^ features;
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma = port->qdma;
+ struct airoha_eth *eth = qdma->eth;
+ int qdma_id = qdma - ð->qdma[0];
+@@ -2192,6 +2208,7 @@ static int airoha_dev_set_features(struc
+ } else {
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *p = eth->ports[i];
++ struct airoha_gdm_dev *d;
+
+ if (!p)
+ continue;
+@@ -2199,10 +2216,11 @@ static int airoha_dev_set_features(struc
+ if (p->qdma != qdma)
+ continue;
+
+- if (p->dev == dev)
++ d = p->dev;
++ if (d->dev == netdev)
+ continue;
+
+- if (p->dev->features & NETIF_F_LRO)
++ if (d->dev->features & NETIF_F_LRO)
+ return 0;
+ }
+ airoha_fe_lro_disable(eth, qdma_id);
+@@ -2212,9 +2230,10 @@ static int airoha_dev_set_features(struc
+ }
+
+ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
+- struct net_device *dev)
++ struct net_device *netdev)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma = port->qdma;
+ u32 nr_frags, tag, msg0, msg1, len;
+ struct airoha_queue_entry *e;
+@@ -2227,7 +2246,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+ u8 fport;
+
+ qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
+- tag = airoha_get_dsa_tag(skb, dev);
++ tag = airoha_get_dsa_tag(skb, netdev);
+
+ msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
+ qid / AIROHA_NUM_QOS_QUEUES) |
+@@ -2263,7 +2282,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+
+ spin_lock_bh(&q->lock);
+
+- txq = skb_get_tx_queue(dev, skb);
++ txq = skb_get_tx_queue(netdev, skb);
+ nr_frags = 1 + skb_shinfo(skb)->nr_frags;
+
+ if (q->queued + nr_frags >= q->ndesc) {
+@@ -2287,9 +2306,9 @@ static netdev_tx_t airoha_dev_xmit(struc
+ dma_addr_t addr;
+ u32 val;
+
+- addr = dma_map_single(dev->dev.parent, data, len,
++ addr = dma_map_single(netdev->dev.parent, data, len,
+ DMA_TO_DEVICE);
+- if (unlikely(dma_mapping_error(dev->dev.parent, addr)))
++ if (unlikely(dma_mapping_error(netdev->dev.parent, addr)))
+ goto error_unmap;
+
+ list_move_tail(&e->list, &tx_list);
+@@ -2338,7 +2357,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+
+ error_unmap:
+ list_for_each_entry(e, &tx_list, list) {
+- dma_unmap_single(dev->dev.parent, e->dma_addr, e->dma_len,
++ dma_unmap_single(netdev->dev.parent, e->dma_addr, e->dma_len,
+ DMA_TO_DEVICE);
+ e->dma_addr = 0;
+ }
+@@ -2347,25 +2366,27 @@ error_unmap:
+ spin_unlock_bh(&q->lock);
+ error:
+ dev_kfree_skb_any(skb);
+- dev->stats.tx_dropped++;
++ netdev->stats.tx_dropped++;
+
+ return NETDEV_TX_OK;
+ }
+
+-static void airoha_ethtool_get_drvinfo(struct net_device *dev,
++static void airoha_ethtool_get_drvinfo(struct net_device *netdev,
+ struct ethtool_drvinfo *info)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ 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,
++static void airoha_ethtool_get_mac_stats(struct net_device *netdev,
+ struct ethtool_eth_mac_stats *stats)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ unsigned int start;
+
+ airoha_update_hw_stats(port);
+@@ -2393,11 +2414,12 @@ static const struct ethtool_rmon_hist_ra
+ };
+
+ static void
+-airoha_ethtool_get_rmon_stats(struct net_device *dev,
++airoha_ethtool_get_rmon_stats(struct net_device *netdev,
+ struct ethtool_rmon_stats *stats,
+ const struct ethtool_rmon_hist_range **ranges)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ struct airoha_hw_stats *hw_stats = &port->stats;
+ unsigned int start;
+
+@@ -2422,11 +2444,12 @@ airoha_ethtool_get_rmon_stats(struct net
+ } while (u64_stats_fetch_retry(&port->stats.syncp, start));
+ }
+
+-static int airoha_qdma_set_chan_tx_sched(struct net_device *dev,
++static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
+ int channel, enum tx_sched_mode mode,
+ const u16 *weights, u8 n_weights)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ int i;
+
+ for (i = 0; i < AIROHA_NUM_TX_RING; i++)
+@@ -2511,10 +2534,12 @@ static int airoha_qdma_set_tx_ets_sched(
+ ARRAY_SIZE(w));
+ }
+
+-static int airoha_qdma_get_tx_ets_stats(struct net_device *dev, int channel,
++static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
+ struct tc_ets_qopt_offload *opt)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
++
+ u64 cpu_tx_packets = airoha_qdma_rr(port->qdma,
+ REG_CNTR_VAL(channel << 1));
+ u64 fwd_tx_packets = airoha_qdma_rr(port->qdma,
+@@ -2776,11 +2801,12 @@ static int airoha_qdma_set_trtcm_token_b
+ mode, val);
+ }
+
+-static int airoha_qdma_set_tx_rate_limit(struct net_device *dev,
++static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev,
+ int channel, u32 rate,
+ u32 bucket_size)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ int i, err;
+
+ for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
+@@ -2800,20 +2826,22 @@ static int airoha_qdma_set_tx_rate_limit
+ return 0;
+ }
+
+-static int airoha_tc_htb_alloc_leaf_queue(struct net_device *dev,
++static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
+ 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 */
+- int err, num_tx_queues = dev->real_num_tx_queues;
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ int err, num_tx_queues = netdev->real_num_tx_queues;
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+
+ 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(dev, channel, rate, opt->quantum);
++ err = airoha_qdma_set_tx_rate_limit(netdev, channel, rate,
++ opt->quantum);
+ if (err) {
+ NL_SET_ERR_MSG_MOD(opt->extack,
+ "failed configuring htb offload");
+@@ -2823,9 +2851,10 @@ static int airoha_tc_htb_alloc_leaf_queu
+ if (opt->command == TC_HTB_NODE_MODIFY)
+ return 0;
+
+- err = netif_set_real_num_tx_queues(dev, num_tx_queues + 1);
++ err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
+ if (err) {
+- airoha_qdma_set_tx_rate_limit(dev, channel, 0, opt->quantum);
++ airoha_qdma_set_tx_rate_limit(netdev, channel, 0,
++ opt->quantum);
+ NL_SET_ERR_MSG_MOD(opt->extack,
+ "failed setting real_num_tx_queues");
+ return err;
+@@ -2915,11 +2944,12 @@ static int airoha_tc_matchall_act_valida
+ return 0;
+ }
+
+-static int airoha_dev_tc_matchall(struct net_device *dev,
++static int airoha_dev_tc_matchall(struct net_device *netdev,
+ struct tc_cls_matchall_offload *f)
+ {
+ enum trtcm_unit_type unit_type = TRTCM_BYTE_UNIT;
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ u32 rate = 0, bucket_size = 0;
+
+ switch (f->command) {
+@@ -2954,18 +2984,19 @@ static int airoha_dev_tc_matchall(struct
+ static int airoha_dev_setup_tc_block_cb(enum tc_setup_type type,
+ void *type_data, void *cb_priv)
+ {
+- struct net_device *dev = cb_priv;
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct net_device *netdev = cb_priv;
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = port->qdma->eth;
+
+- if (!tc_can_offload(dev))
++ if (!tc_can_offload(netdev))
+ return -EOPNOTSUPP;
+
+ switch (type) {
+ case TC_SETUP_CLSFLOWER:
+ return airoha_ppe_setup_tc_block_cb(ð->ppe->dev, type_data);
+ case TC_SETUP_CLSMATCHALL:
+- return airoha_dev_tc_matchall(dev, type_data);
++ return airoha_dev_tc_matchall(netdev, type_data);
+ default:
+ return -EOPNOTSUPP;
+ }
+@@ -3012,47 +3043,51 @@ static int airoha_dev_setup_tc_block(str
+ }
+ }
+
+-static void airoha_tc_remove_htb_queue(struct net_device *dev, int queue)
++static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+
+- netif_set_real_num_tx_queues(dev, dev->real_num_tx_queues - 1);
+- airoha_qdma_set_tx_rate_limit(dev, queue + 1, 0, 0);
++ netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
++ airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0);
+ clear_bit(queue, port->qos_sq_bmap);
+ }
+
+-static int airoha_tc_htb_delete_leaf_queue(struct net_device *dev,
++static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
+ struct tc_htb_qopt_offload *opt)
+ {
+ u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+
+ 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(dev, channel);
++ airoha_tc_remove_htb_queue(netdev, channel);
+
+ return 0;
+ }
+
+-static int airoha_tc_htb_destroy(struct net_device *dev)
++static int airoha_tc_htb_destroy(struct net_device *netdev)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+ int q;
+
+ for_each_set_bit(q, port->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
+- airoha_tc_remove_htb_queue(dev, q);
++ airoha_tc_remove_htb_queue(netdev, q);
+
+ return 0;
+ }
+
+-static int airoha_tc_get_htb_get_leaf_queue(struct net_device *dev,
++static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev,
+ struct tc_htb_qopt_offload *opt)
+ {
+ u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+
+ if (!test_bit(channel, port->qos_sq_bmap)) {
+ NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
+@@ -3088,8 +3123,8 @@ static int airoha_tc_setup_qdisc_htb(str
+ return 0;
+ }
+
+-static int airoha_dev_tc_setup(struct net_device *dev, enum tc_setup_type type,
+- void *type_data)
++static int airoha_dev_tc_setup(struct net_device *dev,
++ enum tc_setup_type type, void *type_data)
+ {
+ switch (type) {
+ case TC_SETUP_QDISC_ETS:
+@@ -3161,13 +3196,18 @@ static void airoha_metadata_dst_free(str
+ }
+ }
+
+-bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
+- struct airoha_gdm_port *port)
++bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
++ struct airoha_gdm_dev *dev)
+ {
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+- if (eth->ports[i] == port)
++ struct airoha_gdm_port *port = eth->ports[i];
++
++ if (!port)
++ continue;
++
++ if (port->dev == dev)
+ return true;
+ }
+
+@@ -3179,8 +3219,9 @@ static void airoha_mac_link_up(struct ph
+ unsigned int mode, phy_interface_t interface,
+ int speed, int duplex, bool tx_pause, bool rx_pause)
+ {
+- struct airoha_gdm_port *port = container_of(config, struct airoha_gdm_port,
+- phylink_config);
++ struct airoha_gdm_dev *dev = container_of(config, struct airoha_gdm_dev,
++ phylink_config);
++ struct airoha_gdm_port *port = dev->port;
+ struct airoha_qdma *qdma = port->qdma;
+ struct airoha_eth *eth = qdma->eth;
+ u32 frag_size_tx, frag_size_rx;
+@@ -3236,65 +3277,122 @@ static int airoha_fill_available_pcs(str
+ &num_available_pcs);
+ }
+
+-static int airoha_setup_phylink(struct net_device *dev)
++static int airoha_setup_phylink(struct net_device *netdev)
+ {
+- struct airoha_gdm_port *port = netdev_priv(dev);
+- struct device_node *np = dev->dev.of_node;
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct device_node *np = netdev->dev.of_node;
+ phy_interface_t phy_mode;
+ struct phylink *phylink;
+ int err;
+
+ err = of_get_phy_mode(np, &phy_mode);
+ if (err) {
+- dev_err(&dev->dev, "incorrect phy-mode\n");
++ dev_err(&netdev->dev, "incorrect phy-mode\n");
+ return err;
+ }
+
+- port->phylink_config.dev = &dev->dev;
+- port->phylink_config.type = PHYLINK_NETDEV;
+- port->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+- MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD |
+- MAC_5000FD | MAC_10000FD;
++ dev->phylink_config.dev = &netdev->dev;
++ dev->phylink_config.type = PHYLINK_NETDEV;
++ dev->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
++ MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD |
++ MAC_5000FD | MAC_10000FD;
+
+- err = fwnode_phylink_pcs_parse(dev_fwnode(&dev->dev), NULL,
+- &port->phylink_config.num_available_pcs);
++ err = fwnode_phylink_pcs_parse(dev_fwnode(&netdev->dev), NULL,
++ &dev->phylink_config.num_available_pcs);
+ if (err)
+ return err;
+
+- port->phylink_config.fill_available_pcs = airoha_fill_available_pcs;
++ dev->phylink_config.fill_available_pcs = airoha_fill_available_pcs;
+
+ __set_bit(PHY_INTERFACE_MODE_SGMII,
+- port->phylink_config.supported_interfaces);
++ dev->phylink_config.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX,
+- port->phylink_config.supported_interfaces);
++ dev->phylink_config.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+- port->phylink_config.supported_interfaces);
++ dev->phylink_config.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER,
+- port->phylink_config.supported_interfaces);
++ dev->phylink_config.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_USXGMII,
+- port->phylink_config.supported_interfaces);
++ dev->phylink_config.supported_interfaces);
+
+- phy_interface_copy(port->phylink_config.pcs_interfaces,
+- port->phylink_config.supported_interfaces);
++ phy_interface_copy(dev->phylink_config.pcs_interfaces,
++ dev->phylink_config.supported_interfaces);
+
+- phylink = phylink_create(&port->phylink_config,
++ phylink = phylink_create(&dev->phylink_config,
+ of_fwnode_handle(np),
+ phy_mode, &airoha_phylink_ops);
+ if (IS_ERR(phylink))
+ return PTR_ERR(phylink);
+
+- port->phylink = phylink;
++ dev->phylink = phylink;
+
+ return err;
+ }
+ #endif
+
++static int airoha_alloc_gdm_device(struct airoha_eth *eth,
++ struct airoha_gdm_port *port,
++ struct device_node *np)
++{
++ struct airoha_gdm_dev *dev;
++ struct net_device *netdev;
++ int err;
++
++ netdev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*dev),
++ AIROHA_NUM_NETDEV_TX_RINGS,
++ AIROHA_NUM_RX_RING);
++ if (!netdev) {
++ dev_err(eth->dev, "alloc_etherdev failed\n");
++ return -ENOMEM;
++ }
++
++ netdev->netdev_ops = &airoha_netdev_ops;
++ netdev->ethtool_ops = &airoha_ethtool_ops;
++ netdev->max_mtu = AIROHA_MAX_MTU;
++ netdev->watchdog_timeo = 5 * HZ;
++ netdev->hw_features = AIROHA_HW_FEATURES | NETIF_F_LRO;
++ netdev->features |= AIROHA_HW_FEATURES;
++ netdev->vlan_features = AIROHA_HW_FEATURES;
++ netdev->dev.of_node = np;
++ SET_NETDEV_DEV(netdev, eth->dev);
++
++ /* reserve hw queues for HTB offloading */
++ err = netif_set_real_num_tx_queues(netdev, AIROHA_NUM_TX_RING);
++ if (err)
++ return err;
++
++ err = of_get_ethdev_address(np, netdev);
++ if (err) {
++ if (err == -EPROBE_DEFER)
++ return err;
++
++ eth_hw_addr_random(netdev);
++ dev_info(eth->dev, "generated random MAC address %pM\n",
++ netdev->dev_addr);
++ }
++
++ dev = netdev_priv(netdev);
++ dev->dev = netdev;
++ dev->port = port;
++ port->dev = dev;
++ dev->eth = eth;
++
++#if defined(CONFIG_PCS_AIROHA)
++ if (airhoa_is_phy_external(port)) {
++ err = airoha_setup_phylink(netdev);
++ if (err)
++ return err;
++ }
++#endif
++
++ return 0;
++}
++
+ 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 net_device *dev;
+ int err, p;
+ u32 id;
+
+@@ -3316,58 +3414,22 @@ static int airoha_alloc_gdm_port(struct
+ 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");
++ port = devm_kzalloc(eth->dev, sizeof(*port), GFP_KERNEL);
++ if (!port)
+ return -ENOMEM;
+- }
+-
+- 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 = AIROHA_HW_FEATURES | NETIF_F_LRO;
+- dev->features |= AIROHA_HW_FEATURES;
+- dev->vlan_features = AIROHA_HW_FEATURES;
+- dev->dev.of_node = np;
+- 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->eth = eth;
+- port->dev = dev;
+ port->id = id;
+ /* XXX: Read nbq from DTS */
+ port->nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
+ eth->ports[p] = port;
+
+-#if defined(CONFIG_PCS_AIROHA)
+- if (airhoa_is_phy_external(port)) {
+- err = airoha_setup_phylink(dev);
+- if (err)
+- return err;
+- }
+-#endif
++ err = airoha_metadata_dst_alloc(port);
++ if (err)
++ return err;
+
+- return airoha_metadata_dst_alloc(port);
++ return airoha_alloc_gdm_device(eth, port, np);
+ }
+
+ static int airoha_register_gdm_devices(struct airoha_eth *eth)
+@@ -3381,7 +3443,7 @@ static int airoha_register_gdm_devices(s
+ if (!port)
+ continue;
+
+- err = register_netdev(port->dev);
++ err = register_netdev(port->dev->dev);
+ if (err)
+ return err;
+ }
+@@ -3490,16 +3552,18 @@ error_napi_stop:
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *port = eth->ports[i];
++ struct airoha_gdm_dev *dev;
+
+ if (!port)
+ continue;
+
+- if (port->dev->reg_state == NETREG_REGISTERED) {
++ dev = port->dev;
++ if (dev && dev->dev->reg_state == NETREG_REGISTERED) {
+ #if defined(CONFIG_PCS_AIROHA)
+ if (airhoa_is_phy_external(port))
+- phylink_destroy(port->phylink);
++ phylink_destroy(dev->phylink);
+ #endif
+- unregister_netdev(port->dev);
++ unregister_netdev(dev->dev);
+ }
+ airoha_metadata_dst_free(port);
+ }
+@@ -3521,15 +3585,19 @@ static void airoha_remove(struct platfor
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *port = eth->ports[i];
++ struct airoha_gdm_dev *dev;
+
+ if (!port)
+ continue;
+
+ #if defined(CONFIG_PCS_AIROHA)
+ if (airhoa_is_phy_external(port))
+- phylink_destroy(port->phylink);
++ phylink_destroy(dev->phylink);
+ #endif
+- unregister_netdev(port->dev);
++
++ dev = port->dev;
++ if (dev)
++ unregister_netdev(dev->dev);
+ airoha_metadata_dst_free(port);
+ }
+ airoha_hw_cleanup(eth);
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -546,17 +546,22 @@ struct airoha_qdma {
+ struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
+ };
+
+-struct airoha_gdm_port {
+- struct airoha_qdma *qdma;
+- struct airoha_eth *eth;
++struct airoha_gdm_dev {
++ struct airoha_gdm_port *port;
+ struct net_device *dev;
+- int id;
+- int nbq;
++ struct airoha_eth *eth;
+
+ #if defined(CONFIG_PCS_AIROHA)
+ struct phylink *phylink;
+ struct phylink_config phylink_config;
+ #endif
++};
++
++struct airoha_gdm_port {
++ struct airoha_qdma *qdma;
++ struct airoha_gdm_dev *dev;
++ int id;
++ int nbq;
+
+ struct airoha_hw_stats stats;
+
+@@ -690,8 +695,8 @@ static inline bool airoha_qdma_is_lro_qu
+ }
+
+ int airoha_get_fe_port(struct airoha_gdm_port *port);
+-bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
+- struct airoha_gdm_port *port);
++bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
++ struct airoha_gdm_dev *dev);
+
+ void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id,
+ u8 fport);
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -297,12 +297,12 @@ static void airoha_ppe_foe_set_bridge_ad
+
+ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
+ struct airoha_foe_entry *hwe,
+- struct net_device *dev, int type,
++ struct net_device *netdev, int type,
+ struct airoha_flow_data *data,
+ int l4proto, u8 dsfield)
+ {
+ u32 qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f), ports_pad, val;
+- int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&dev);
++ int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&netdev);
+ struct airoha_foe_mac_info_common *l2;
+ u8 smac_id = 0xf;
+
+@@ -318,10 +318,11 @@ static int airoha_ppe_foe_entry_prepare(
+ hwe->ib1 = val;
+
+ val = FIELD_PREP(AIROHA_FOE_IB2_PORT_AG, 0x1f);
+- if (dev) {
++ if (netdev) {
+ struct airoha_wdma_info info = {};
+
+- if (!airoha_ppe_get_wdma_info(dev, data->eth.h_dest, &info)) {
++ if (!airoha_ppe_get_wdma_info(netdev, data->eth.h_dest,
++ &info)) {
+ val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ, info.idx) |
+ FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT,
+ FE_PSE_PORT_CDM4);
+@@ -331,12 +332,14 @@ static int airoha_ppe_foe_entry_prepare(
+ FIELD_PREP(AIROHA_FOE_MAC_WDMA_WCID,
+ info.wcid);
+ } else {
+- struct airoha_gdm_port *port = netdev_priv(dev);
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ u8 pse_port, channel, priority;
++ struct airoha_gdm_port *port;
+
+- if (!airoha_is_valid_gdm_port(eth, port))
++ if (!airoha_is_valid_gdm_dev(eth, dev))
+ return -EINVAL;
+
++ port = dev->port;
+ if (dsa_port >= 0 || eth->ports[1])
+ pse_port = port->id == 4 ? FE_PSE_PORT_GDM4
+ : port->id;
+@@ -1483,7 +1486,7 @@ void airoha_ppe_check_skb(struct airoha_
+ void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port)
+ {
+ struct airoha_eth *eth = port->qdma->eth;
+- struct net_device *dev = port->dev;
++ struct net_device *dev = port->dev->dev;
+ const u8 *addr = dev->dev_addr;
+ u32 val;
+
--- /dev/null
+From f62cea6483cc55360863d66300790a5fb9de5f7c Mon Sep 17 00:00:00 2001
+Message-ID: <f62cea6483cc55360863d66300790a5fb9de5f7c.1779348625.git.lorenzo@kernel.org>
+In-Reply-To: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+References: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 24 Feb 2026 18:43:05 +0100
+Subject: [PATCH 02/13] net: airoha: Move airoha_qdma pointer in airoha_gdm_dev
+ struct
+
+Move airoha_qdma pointer from airoha_gdm_port struct to airoha_gdm_dev
+one since the QDMA block used depends on the particular net_device
+WAN/LAN configuration and in the current codebase net_device pointer is
+associated to airoha_gdm_dev struct.
+This is a preliminary patch to support multiple net_devices connected
+to the same GDM{3,4} port via an external hw arbiter.
+
+Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 105 +++++++++++------------
+ drivers/net/ethernet/airoha/airoha_eth.h | 9 +-
+ drivers/net/ethernet/airoha/airoha_ppe.c | 17 ++--
+ 3 files changed, 64 insertions(+), 67 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -80,9 +80,10 @@ static bool airhoa_is_phy_external(struc
+ }
+ #endif
+
+-static void airoha_set_macaddr(struct airoha_gdm_port *port, const u8 *addr)
++static void airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
+ {
+- struct airoha_eth *eth = port->qdma->eth;
++ struct airoha_gdm_port *port = dev->port;
++ struct airoha_eth *eth = dev->eth;
+ u32 val, reg;
+
+ reg = airoha_is_lan_gdm_port(port) ? REG_FE_LAN_MAC_H
+@@ -94,7 +95,7 @@ static void airoha_set_macaddr(struct ai
+ airoha_fe_wr(eth, REG_FE_MAC_LMIN(reg), val);
+ airoha_fe_wr(eth, REG_FE_MAC_LMAX(reg), val);
+
+- airoha_ppe_init_upd_mem(port);
++ airoha_ppe_init_upd_mem(dev);
+ }
+
+ static void airoha_set_gdm_port_fwd_cfg(struct airoha_eth *eth, u32 addr,
+@@ -110,10 +111,10 @@ static void airoha_set_gdm_port_fwd_cfg(
+ FIELD_PREP(GDM_UCFQ_MASK, val));
+ }
+
+-static int airoha_set_vip_for_gdm_port(struct airoha_gdm_port *port,
+- bool enable)
++static int airoha_set_vip_for_gdm_port(struct airoha_gdm_dev *dev, bool enable)
+ {
+- struct airoha_eth *eth = port->qdma->eth;
++ struct airoha_gdm_port *port = dev->port;
++ struct airoha_eth *eth = dev->eth;
+ u32 vip_port;
+
+ vip_port = eth->soc->ops.get_vip_port(port, port->nbq);
+@@ -994,10 +995,13 @@ static void airoha_qdma_wake_netdev_txqs
+ if (!port)
+ continue;
+
+- if (port->qdma != qdma)
++ dev = port->dev;
++ if (!dev)
++ continue;
++
++ if (dev->qdma != qdma)
+ continue;
+
+- dev = port->dev;
+ for (j = 0; j < dev->dev->num_tx_queues; j++) {
+ if (airoha_qdma_get_txq(qdma, j) != qid)
+ continue;
+@@ -1702,9 +1706,10 @@ static void airoha_qdma_stop_napi(struct
+ }
+ }
+
+-static void airoha_update_hw_stats(struct airoha_gdm_port *port)
++static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
+ {
+- struct airoha_eth *eth = port->qdma->eth;
++ struct airoha_gdm_port *port = dev->port;
++ struct airoha_eth *eth = dev->eth;
+ u32 val, i = 0;
+
+ spin_lock(&port->stats.lock);
+@@ -1851,7 +1856,7 @@ static int airoha_dev_open(struct net_de
+ int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+- struct airoha_qdma *qdma = port->qdma;
++ struct airoha_qdma *qdma = dev->qdma;
+ u32 pse_port = FE_PSE_PORT_PPE1;
+
+ #if defined(CONFIG_PCS_AIROHA)
+@@ -1868,7 +1873,7 @@ static int airoha_dev_open(struct net_de
+ #endif
+
+ netif_tx_start_all_queues(netdev);
+- err = airoha_set_vip_for_gdm_port(port, true);
++ err = airoha_set_vip_for_gdm_port(dev, true);
+ if (err)
+ return err;
+
+@@ -1905,11 +1910,11 @@ static int airoha_dev_stop(struct net_de
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+- struct airoha_qdma *qdma = port->qdma;
++ struct airoha_qdma *qdma = dev->qdma;
+ int i;
+
+ netif_tx_disable(netdev);
+- airoha_set_vip_for_gdm_port(port, false);
++ airoha_set_vip_for_gdm_port(dev, false);
+ for (i = 0; i < netdev->num_tx_queues; i++)
+ netdev_tx_reset_subqueue(netdev, i);
+
+@@ -1942,21 +1947,21 @@ static int airoha_dev_stop(struct net_de
+ static int airoha_dev_set_macaddr(struct net_device *netdev, void *p)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+ int err;
+
+ err = eth_mac_addr(netdev, p);
+ if (err)
+ return err;
+
+- airoha_set_macaddr(port, netdev->dev_addr);
++ airoha_set_macaddr(dev, netdev->dev_addr);
+
+ return 0;
+ }
+
+-static int airoha_set_gdm2_loopback(struct airoha_gdm_port *port)
++static int airoha_set_gdm2_loopback(struct airoha_gdm_dev *dev)
+ {
+- struct airoha_eth *eth = port->qdma->eth;
++ struct airoha_gdm_port *port = dev->port;
++ struct airoha_eth *eth = dev->eth;
+ u32 val, pse_port, chan;
+ int i, src_port;
+
+@@ -2003,7 +2008,7 @@ static int airoha_set_gdm2_loopback(stru
+ __field_prep(SP_CPORT_MASK(val), FE_PSE_PORT_CDM2));
+
+ for (i = 0; i < eth->soc->num_ppe; i++)
+- airoha_ppe_set_cpu_port(port, i, AIROHA_GDM2_IDX);
++ airoha_ppe_set_cpu_port(dev, i, AIROHA_GDM2_IDX);
+
+ if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
+ u32 mask = FC_ID_OF_SRC_PORT_MASK(port->nbq);
+@@ -2023,9 +2028,9 @@ static int airoha_dev_init(struct net_de
+ int i;
+
+ /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
+- port->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
+- dev->dev->irq = port->qdma->irq_banks[0].irq;
+- airoha_set_macaddr(port, netdev->dev_addr);
++ dev->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
++ dev->dev->irq = dev->qdma->irq_banks[0].irq;
++ airoha_set_macaddr(dev, netdev->dev_addr);
+
+ switch (port->id) {
+ case AIROHA_GDM3_IDX:
+@@ -2034,7 +2039,7 @@ static int airoha_dev_init(struct net_de
+ if (!eth->ports[1]) {
+ int err;
+
+- err = airoha_set_gdm2_loopback(port);
++ err = airoha_set_gdm2_loopback(dev);
+ if (err)
+ return err;
+ }
+@@ -2044,8 +2049,7 @@ static int airoha_dev_init(struct net_de
+ }
+
+ for (i = 0; i < eth->soc->num_ppe; i++)
+- airoha_ppe_set_cpu_port(port, i,
+- airoha_get_fe_port(port));
++ airoha_ppe_set_cpu_port(dev, i, airoha_get_fe_port(dev));
+
+ return 0;
+ }
+@@ -2057,7 +2061,7 @@ static void airoha_dev_get_stats64(struc
+ struct airoha_gdm_port *port = dev->port;
+ unsigned int start;
+
+- airoha_update_hw_stats(port);
++ airoha_update_hw_stats(dev);
+ do {
+ start = u64_stats_fetch_begin(&port->stats.syncp);
+ storage->rx_packets = port->stats.rx_ok_pkts;
+@@ -2077,8 +2081,8 @@ static int airoha_dev_change_mtu(struct
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+- struct airoha_eth *eth = port->qdma->eth;
+ u32 len = ETH_HLEN + mtu + ETH_FCS_LEN;
++ struct airoha_eth *eth = dev->eth;
+
+ airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
+ GDM_LONG_LEN_MASK,
+@@ -2152,10 +2156,10 @@ static u32 airoha_get_dsa_tag(struct sk_
+ #endif
+ }
+
+-int airoha_get_fe_port(struct airoha_gdm_port *port)
++int airoha_get_fe_port(struct airoha_gdm_dev *dev)
+ {
+- struct airoha_qdma *qdma = port->qdma;
+- struct airoha_eth *eth = qdma->eth;
++ struct airoha_gdm_port *port = dev->port;
++ struct airoha_eth *eth = dev->eth;
+
+ switch (eth->soc->version) {
+ case 0x7583:
+@@ -2174,8 +2178,7 @@ static int airoha_dev_set_features(struc
+
+ netdev_features_t diff = netdev->features ^ features;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+- struct airoha_qdma *qdma = port->qdma;
++ struct airoha_qdma *qdma = dev->qdma;
+ struct airoha_eth *eth = qdma->eth;
+ int qdma_id = qdma - ð->qdma[0];
+ int i;
+@@ -2213,10 +2216,10 @@ static int airoha_dev_set_features(struc
+ if (!p)
+ continue;
+
+- if (p->qdma != qdma)
++ d = p->dev;
++ if (d->qdma != qdma)
+ continue;
+
+- d = p->dev;
+ if (d->dev == netdev)
+ continue;
+
+@@ -2233,8 +2236,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+ struct net_device *netdev)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+- struct airoha_qdma *qdma = port->qdma;
++ struct airoha_qdma *qdma = dev->qdma;
+ u32 nr_frags, tag, msg0, msg1, len;
+ struct airoha_queue_entry *e;
+ struct netdev_queue *txq;
+@@ -2272,7 +2274,7 @@ static netdev_tx_t airoha_dev_xmit(struc
+ }
+ }
+
+- fport = airoha_get_fe_port(port);
++ fport = airoha_get_fe_port(dev);
+ msg1 = FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
+ FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
+
+@@ -2375,8 +2377,7 @@ static void airoha_ethtool_get_drvinfo(s
+ struct ethtool_drvinfo *info)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+- struct airoha_eth *eth = port->qdma->eth;
++ struct airoha_eth *eth = dev->eth;
+
+ strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
+ strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
+@@ -2389,7 +2390,7 @@ static void airoha_ethtool_get_mac_stats
+ struct airoha_gdm_port *port = dev->port;
+ unsigned int start;
+
+- airoha_update_hw_stats(port);
++ airoha_update_hw_stats(dev);
+ do {
+ start = u64_stats_fetch_begin(&port->stats.syncp);
+ stats->FramesTransmittedOK = port->stats.tx_ok_pkts;
+@@ -2429,7 +2430,7 @@ airoha_ethtool_get_rmon_stats(struct net
+ ARRAY_SIZE(hw_stats->rx_len) + 1);
+
+ *ranges = airoha_ethtool_rmon_ranges;
+- airoha_update_hw_stats(port);
++ airoha_update_hw_stats(dev);
+ do {
+ int i;
+
+@@ -2449,18 +2450,17 @@ static int airoha_qdma_set_chan_tx_sched
+ const u16 *weights, u8 n_weights)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+ int i;
+
+ for (i = 0; i < AIROHA_NUM_TX_RING; i++)
+- airoha_qdma_clear(port->qdma, REG_QUEUE_CLOSE_CFG(channel),
++ airoha_qdma_clear(dev->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,
++ airoha_qdma_wr(dev->qdma, REG_TXWRR_WEIGHT_CFG,
+ TWRR_RW_CMD_MASK |
+ FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) |
+ FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) |
+@@ -2468,13 +2468,12 @@ static int airoha_qdma_set_chan_tx_sched
+ 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);
++ true, dev->qdma, REG_TXWRR_WEIGHT_CFG);
+ if (err)
+ return err;
+ }
+
+- airoha_qdma_rmw(port->qdma, REG_CHAN_QOS_MODE(channel >> 3),
++ airoha_qdma_rmw(dev->qdma, REG_CHAN_QOS_MODE(channel >> 3),
+ CHAN_QOS_MODE_MASK(channel),
+ __field_prep(CHAN_QOS_MODE_MASK(channel), mode));
+
+@@ -2540,9 +2539,9 @@ static int airoha_qdma_get_tx_ets_stats(
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+
+- u64 cpu_tx_packets = airoha_qdma_rr(port->qdma,
++ u64 cpu_tx_packets = airoha_qdma_rr(dev->qdma,
+ REG_CNTR_VAL(channel << 1));
+- u64 fwd_tx_packets = airoha_qdma_rr(port->qdma,
++ u64 fwd_tx_packets = airoha_qdma_rr(dev->qdma,
+ REG_CNTR_VAL((channel << 1) + 1));
+ u64 tx_packets = (cpu_tx_packets - port->cpu_tx_packets) +
+ (fwd_tx_packets - port->fwd_tx_packets);
+@@ -2806,17 +2805,16 @@ static int airoha_qdma_set_tx_rate_limit
+ u32 bucket_size)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+ int i, err;
+
+ for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
+- err = airoha_qdma_set_trtcm_config(port->qdma, channel,
++ err = airoha_qdma_set_trtcm_config(dev->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,
++ err = airoha_qdma_set_trtcm_token_bucket(dev->qdma, channel,
+ REG_EGRESS_TRTCM_CFG,
+ i, rate, bucket_size);
+ if (err)
+@@ -2866,11 +2864,11 @@ static int airoha_tc_htb_alloc_leaf_queu
+ return 0;
+ }
+
+-static int airoha_qdma_set_rx_meter(struct airoha_gdm_port *port,
++static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev,
+ u32 rate, u32 bucket_size,
+ enum trtcm_unit_type unit_type)
+ {
+- struct airoha_qdma *qdma = port->qdma;
++ struct airoha_qdma *qdma = dev->qdma;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+@@ -2949,7 +2947,6 @@ static int airoha_dev_tc_matchall(struct
+ {
+ enum trtcm_unit_type unit_type = TRTCM_BYTE_UNIT;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+ u32 rate = 0, bucket_size = 0;
+
+ switch (f->command) {
+@@ -2974,7 +2971,7 @@ static int airoha_dev_tc_matchall(struct
+ fallthrough;
+ }
+ case TC_CLSMATCHALL_DESTROY:
+- return airoha_qdma_set_rx_meter(port, rate, bucket_size,
++ return airoha_qdma_set_rx_meter(dev, rate, bucket_size,
+ unit_type);
+ default:
+ return -EOPNOTSUPP;
+@@ -2986,8 +2983,7 @@ static int airoha_dev_setup_tc_block_cb(
+ {
+ struct net_device *netdev = cb_priv;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+- struct airoha_eth *eth = port->qdma->eth;
++ struct airoha_eth *eth = dev->eth;
+
+ if (!tc_can_offload(netdev))
+ return -EOPNOTSUPP;
+@@ -3222,7 +3218,7 @@ static void airoha_mac_link_up(struct ph
+ struct airoha_gdm_dev *dev = container_of(config, struct airoha_gdm_dev,
+ phylink_config);
+ struct airoha_gdm_port *port = dev->port;
+- struct airoha_qdma *qdma = port->qdma;
++ struct airoha_qdma *qdma = dev->qdma;
+ struct airoha_eth *eth = qdma->eth;
+ u32 frag_size_tx, frag_size_rx;
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -548,6 +548,7 @@ struct airoha_qdma {
+
+ struct airoha_gdm_dev {
+ struct airoha_gdm_port *port;
++ struct airoha_qdma *qdma;
+ struct net_device *dev;
+ struct airoha_eth *eth;
+
+@@ -558,7 +559,6 @@ struct airoha_gdm_dev {
+ };
+
+ struct airoha_gdm_port {
+- struct airoha_qdma *qdma;
+ struct airoha_gdm_dev *dev;
+ int id;
+ int nbq;
+@@ -694,19 +694,18 @@ static inline bool airoha_qdma_is_lro_qu
+ return !!(AIROHA_RXQ_LRO_EN_MASK & BIT(qid));
+ }
+
+-int airoha_get_fe_port(struct airoha_gdm_port *port);
++int airoha_get_fe_port(struct airoha_gdm_dev *dev);
+ bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
+ struct airoha_gdm_dev *dev);
+
+-void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id,
+- u8 fport);
++void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport);
+ 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);
+ 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);
++void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev);
+ 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);
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -85,9 +85,9 @@ static u32 airoha_ppe_get_timestamp(stru
+ AIROHA_FOE_IB1_BIND_TIMESTAMP);
+ }
+
+-void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id, u8 fport)
++void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
+ {
+- struct airoha_qdma *qdma = port->qdma;
++ struct airoha_qdma *qdma = dev->qdma;
+ struct airoha_eth *eth = qdma->eth;
+ u8 qdma_id = qdma - ð->qdma[0];
+ u32 fe_cpu_port;
+@@ -181,8 +181,8 @@ static void airoha_ppe_hw_init(struct ai
+ if (!port)
+ continue;
+
+- airoha_ppe_set_cpu_port(port, i,
+- airoha_get_fe_port(port));
++ airoha_ppe_set_cpu_port(port->dev, i,
++ airoha_get_fe_port(port->dev));
+ }
+ }
+ }
+@@ -1483,11 +1483,12 @@ void airoha_ppe_check_skb(struct airoha_
+ airoha_ppe_foe_insert_entry(ppe, skb, hash, rx_wlan);
+ }
+
+-void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port)
++void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev)
+ {
+- struct airoha_eth *eth = port->qdma->eth;
+- struct net_device *dev = port->dev->dev;
+- const u8 *addr = dev->dev_addr;
++ struct airoha_gdm_port *port = dev->port;
++ struct net_device *netdev = dev->dev;
++ struct airoha_eth *eth = dev->eth;
++ const u8 *addr = netdev->dev_addr;
+ u32 val;
+
+ val = (addr[2] << 24) | (addr[3] << 16) | (addr[4] << 8) | addr[5];
--- /dev/null
+From 32bfd008c19f9ad55514181d8cd02e14bf384475 Mon Sep 17 00:00:00 2001
+Message-ID: <32bfd008c19f9ad55514181d8cd02e14bf384475.1779348625.git.lorenzo@kernel.org>
+In-Reply-To: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+References: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 13 Dec 2025 10:06:45 +0100
+Subject: [PATCH 03/13] net: airoha: Rely on airoha_gdm_dev pointer in
+ airoha_is_lan_gdm_port()
+
+Rename airoha_is_lan_gdm_port in airoha_is_lan_gdm_dev. Moreover, rely
+on airoha_gdm_dev pointer in airoha_is_lan_gdm_dev() instead of
+airoha_gdm_port one.
+This is a preliminary patch to support multiple net_devices connected to
+the same GDM{3,4} port via an external hw arbiter.
+
+Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 6 ++----
+ drivers/net/ethernet/airoha/airoha_eth.h | 4 +++-
+ drivers/net/ethernet/airoha/airoha_ppe.c | 2 +-
+ 3 files changed, 6 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -82,12 +82,10 @@ static bool airhoa_is_phy_external(struc
+
+ static void airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
+ {
+- struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
+ u32 val, reg;
+
+- reg = airoha_is_lan_gdm_port(port) ? REG_FE_LAN_MAC_H
+- : REG_FE_WAN_MAC_H;
++ reg = airoha_is_lan_gdm_dev(dev) ? 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);
+
+@@ -2028,7 +2026,7 @@ static int airoha_dev_init(struct net_de
+ int i;
+
+ /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
+- dev->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
++ dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
+ dev->dev->irq = dev->qdma->irq_banks[0].irq;
+ airoha_set_macaddr(dev, netdev->dev_addr);
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -663,8 +663,10 @@ static inline u16 airoha_qdma_get_txq(st
+ return qid % ARRAY_SIZE(qdma->q_tx);
+ }
+
+-static inline bool airoha_is_lan_gdm_port(struct airoha_gdm_port *port)
++static inline bool airoha_is_lan_gdm_dev(struct airoha_gdm_dev *dev)
+ {
++ struct airoha_gdm_port *port = dev->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.
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -365,7 +365,7 @@ static int airoha_ppe_foe_entry_prepare(
+ /* For downlink traffic consume SRAM memory for hw
+ * forwarding descriptors queue.
+ */
+- if (airoha_is_lan_gdm_port(port))
++ if (airoha_is_lan_gdm_dev(dev))
+ val |= AIROHA_FOE_IB2_FAST_PATH;
+ if (dsa_port >= 0)
+ val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ,
--- /dev/null
+From 634d75285db77f3385aa85a1bf2b185396225100 Mon Sep 17 00:00:00 2001
+Message-ID: <634d75285db77f3385aa85a1bf2b185396225100.1779348625.git.lorenzo@kernel.org>
+In-Reply-To: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+References: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 10 Apr 2026 14:35:32 +0200
+Subject: [PATCH 04/13] net: airoha: Move qos_sq_bmap in airoha_gdm_dev struct
+
+Since now multiple net_devices connected to different QDMA blocks can
+share the same GDM port, qos_sq_bmap field can be overwritten with the
+configuration obtained from a net_device connected to a different QDMA
+block. In order to fix the issue move qos_sq_bmap field from
+airoha_gdm_port struct to airoha_gdm_dev one.
+Add qos_channel_map bitmap in airoha_qdma struct to track if a shared
+QDMA channel is already in use by another net_device.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 58 +++++++++++++++---------
+ drivers/net/ethernet/airoha/airoha_eth.h | 6 ++-
+ 2 files changed, 40 insertions(+), 24 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2822,30 +2822,40 @@ static int airoha_qdma_set_tx_rate_limit
+ return 0;
+ }
+
+-static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
+- struct tc_htb_qopt_offload *opt)
++static int airoha_tc_htb_modify_queue(struct net_device *dev,
++ 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 */
+- int err, num_tx_queues = netdev->real_num_tx_queues;
+- struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+
+ 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(netdev, channel, rate,
+- opt->quantum);
+- if (err) {
++ return airoha_qdma_set_tx_rate_limit(dev, channel, rate, opt->quantum);
++}
++
++static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
++ struct tc_htb_qopt_offload *opt)
++{
++ u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
++ int err, num_tx_queues = netdev->real_num_tx_queues;
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_qdma *qdma = dev->qdma;
++
++ /* Here we need to check the requested QDMA channel is not already
++ * in use by another net_device running on the same QDMA block.
++ */
++ if (test_and_set_bit(channel, qdma->qos_channel_map)) {
+ NL_SET_ERR_MSG_MOD(opt->extack,
+- "failed configuring htb offload");
+- return err;
++ "qdma qos channel already in use");
++ return -EBUSY;
+ }
+
+- if (opt->command == TC_HTB_NODE_MODIFY)
+- return 0;
++ err = airoha_tc_htb_modify_queue(netdev, opt);
++ if (err)
++ goto error;
+
+ err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
+ if (err) {
+@@ -2853,13 +2863,17 @@ static int airoha_tc_htb_alloc_leaf_queu
+ opt->quantum);
+ NL_SET_ERR_MSG_MOD(opt->extack,
+ "failed setting real_num_tx_queues");
+- return err;
++ goto error;
+ }
+
+- set_bit(channel, port->qos_sq_bmap);
++ set_bit(channel, dev->qos_sq_bmap);
+ opt->qid = AIROHA_NUM_TX_RING + channel;
+
+ return 0;
++error:
++ clear_bit(channel, qdma->qos_channel_map);
++
++ return err;
+ }
+
+ static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev,
+@@ -3040,11 +3054,13 @@ static int airoha_dev_setup_tc_block(str
+ static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
++ struct airoha_qdma *qdma = dev->qdma;
+
+ netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
+ airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0);
+- clear_bit(queue, port->qos_sq_bmap);
++
++ clear_bit(queue, qdma->qos_channel_map);
++ clear_bit(queue, dev->qos_sq_bmap);
+ }
+
+ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
+@@ -3052,9 +3068,8 @@ static int airoha_tc_htb_delete_leaf_que
+ {
+ u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+
+- if (!test_bit(channel, port->qos_sq_bmap)) {
++ if (!test_bit(channel, dev->qos_sq_bmap)) {
+ NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
+ return -EINVAL;
+ }
+@@ -3067,10 +3082,9 @@ static int airoha_tc_htb_delete_leaf_que
+ static int airoha_tc_htb_destroy(struct net_device *netdev)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+ int q;
+
+- for_each_set_bit(q, port->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
++ for_each_set_bit(q, dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
+ airoha_tc_remove_htb_queue(netdev, q);
+
+ return 0;
+@@ -3081,9 +3095,8 @@ static int airoha_tc_get_htb_get_leaf_qu
+ {
+ u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+
+- if (!test_bit(channel, port->qos_sq_bmap)) {
++ if (!test_bit(channel, dev->qos_sq_bmap)) {
+ NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
+ return -EINVAL;
+ }
+@@ -3102,6 +3115,7 @@ static int airoha_tc_setup_qdisc_htb(str
+ case TC_HTB_DESTROY:
+ return airoha_tc_htb_destroy(dev);
+ case TC_HTB_NODE_MODIFY:
++ return airoha_tc_htb_modify_queue(dev, opt);
+ case TC_HTB_LEAF_ALLOC_QUEUE:
+ return airoha_tc_htb_alloc_leaf_queue(dev, opt);
+ case TC_HTB_LEAF_DEL:
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -544,6 +544,8 @@ struct airoha_qdma {
+
+ struct airoha_queue q_tx[AIROHA_NUM_TX_RING];
+ struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
++
++ DECLARE_BITMAP(qos_channel_map, AIROHA_NUM_QOS_CHANNELS);
+ };
+
+ struct airoha_gdm_dev {
+@@ -556,6 +558,8 @@ struct airoha_gdm_dev {
+ struct phylink *phylink;
+ struct phylink_config phylink_config;
+ #endif
++
++ DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
+ };
+
+ struct airoha_gdm_port {
+@@ -565,8 +569,6 @@ struct airoha_gdm_port {
+
+ 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;
--- /dev/null
+From 00272dbf6a52241a21145631f22dc5f03891078b Mon Sep 17 00:00:00 2001
+Message-ID: <00272dbf6a52241a21145631f22dc5f03891078b.1779348625.git.lorenzo@kernel.org>
+In-Reply-To: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+References: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 10 Apr 2026 14:47:08 +0200
+Subject: [PATCH 05/13] net: airoha: Move {cpu,fwd}_tx_packets in
+ airoha_gdm_dev struct
+
+Since now multiple net_devices connected to different QDMA blocks can
+share the same GDM port, cpu_tx_packets and fwd_tx_packets fields can
+be overwritten with the value from a different QDMA block. In order to
+fix the issue move cpu_tx_packets and fwd_tx_packets fields from
+airoha_gdm_port struct to airoha_gdm_dev one.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 16 +++++++---------
+ drivers/net/ethernet/airoha/airoha_eth.h | 7 +++----
+ 2 files changed, 10 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2535,19 +2535,17 @@ static int airoha_qdma_get_tx_ets_stats(
+ struct tc_ets_qopt_offload *opt)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
++ struct airoha_qdma *qdma = dev->qdma;
+
+- u64 cpu_tx_packets = airoha_qdma_rr(dev->qdma,
+- REG_CNTR_VAL(channel << 1));
+- u64 fwd_tx_packets = airoha_qdma_rr(dev->qdma,
++ u64 cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1));
++ u64 fwd_tx_packets = airoha_qdma_rr(qdma,
+ REG_CNTR_VAL((channel << 1) + 1));
+- u64 tx_packets = (cpu_tx_packets - port->cpu_tx_packets) +
+- (fwd_tx_packets - port->fwd_tx_packets);
++ u64 tx_packets = (cpu_tx_packets - dev->cpu_tx_packets) +
++ (fwd_tx_packets - dev->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;
++ dev->cpu_tx_packets = cpu_tx_packets;
++ dev->fwd_tx_packets = fwd_tx_packets;
+
+ return 0;
+ }
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -560,6 +560,9 @@ struct airoha_gdm_dev {
+ #endif
+
+ DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
++ /* qos stats counters */
++ u64 cpu_tx_packets;
++ u64 fwd_tx_packets;
+ };
+
+ struct airoha_gdm_port {
+@@ -569,10 +572,6 @@ struct airoha_gdm_port {
+
+ struct airoha_hw_stats stats;
+
+- /* qos stats counters */
+- u64 cpu_tx_packets;
+- u64 fwd_tx_packets;
+-
+ struct metadata_dst *dsa_meta[AIROHA_MAX_DSA_PORTS];
+ };
+
--- /dev/null
+From 8eb0a71bfbe92b6fbc668c5d9ebdcbf6523a89ad Mon Sep 17 00:00:00 2001
+Message-ID: <8eb0a71bfbe92b6fbc668c5d9ebdcbf6523a89ad.1779348625.git.lorenzo@kernel.org>
+In-Reply-To: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+References: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 1 Nov 2025 11:27:48 +0100
+Subject: [PATCH 06/13] net: airoha: Support multiple net_devices for a single
+ FE GDM port
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+EN7581 or AN7583 SoCs support connecting multiple external SerDes (e.g.
+Ethernet or USB SerDes) to GDM3 or GDM4 ports via a hw arbiter that
+manages the traffic in a TDM manner. As a result multiple net_devices can
+connect to the same GDM{3,4} port and there is a theoretical "1:n"
+relation between GDM ports and net_devices.
+
+ ┌─────────────────────────────────┐
+ │ │ ┌──────┐
+ │ P1 GDM1 ├────►MT7530│
+ │ │ └──────┘
+ │ │ ETH0 (DSA conduit)
+ │ │
+ │ PSE/FE │
+ │ │
+ │ │
+ │ │ ┌─────┐
+ │ P0 CDM1 ├────►QDMA0│
+ │ P4 P9 GDM4 │ └─────┘
+ └──┬─────────────────────────┬────┘
+ │ │
+ ┌──▼──┐ ┌────▼────┐
+ │ PPE │ │ ARB │
+ └─────┘ └─┬─────┬─┘
+ │ │
+ ┌──▼──┐┌─▼───┐
+ │ ETH ││ USB │
+ └─────┘└─────┘
+ ETH1 ETH2
+
+Introduce support for multiple net_devices connected to the same Frame
+Engine (FE) GDM port (GDM3 or GDM4) via an external hw arbiter.
+Please note GDM1 or GDM2 does not support the connection with the external
+arbiter.
+Add get_dev_from_sport callback since EN7581 and AN7583 have different
+logics for the net_device type connected to GDM3 or GDM4.
+
+Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 274 +++++++++++++++++------
+ drivers/net/ethernet/airoha/airoha_eth.h | 10 +-
+ drivers/net/ethernet/airoha/airoha_ppe.c | 13 +-
+ 3 files changed, 228 insertions(+), 69 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -115,7 +115,7 @@ static int airoha_set_vip_for_gdm_port(s
+ struct airoha_eth *eth = dev->eth;
+ u32 vip_port;
+
+- vip_port = eth->soc->ops.get_vip_port(port, port->nbq);
++ vip_port = eth->soc->ops.get_vip_port(port, dev->nbq);
+ if (enable) {
+ airoha_fe_set(eth, REG_FE_VIP_PORT_EN, vip_port);
+ airoha_fe_set(eth, REG_FE_IFC_PORT_EN, vip_port);
+@@ -618,30 +618,26 @@ static int airoha_qdma_fill_rx_queue(str
+ return nframes;
+ }
+
+-static int airoha_qdma_get_gdm_port(struct airoha_eth *eth,
+- struct airoha_qdma_desc *desc)
++static struct airoha_gdm_dev *
++airoha_qdma_get_gdm_dev(struct airoha_eth *eth, struct airoha_qdma_desc *desc)
+ {
+- u32 port, sport, msg1 = le32_to_cpu(READ_ONCE(desc->msg1));
++ struct airoha_gdm_port *port;
++ u16 p, d;
+
+- sport = FIELD_GET(QDMA_ETH_RXMSG_SPORT_MASK, msg1);
+- switch (sport) {
+- case 0x18:
+- port = 3; /* GDM4 */
+- break;
+- case 0x16:
+- port = 2; /* GDM3 */
+- break;
+- case 0x10 ... 0x14:
+- port = 0; /* GDM1 */
+- break;
+- case 0x2 ... 0x4:
+- port = sport - 1;
+- break;
+- default:
+- return -EINVAL;
+- }
++ if (eth->soc->ops.get_dev_from_sport(desc, &p, &d))
++ return ERR_PTR(-ENODEV);
++
++ if (p >= ARRAY_SIZE(eth->ports))
++ return ERR_PTR(-ENODEV);
++
++ port = eth->ports[p];
++ if (!port)
++ return ERR_PTR(-ENODEV);
++
++ if (d >= ARRAY_SIZE(port->devs))
++ return ERR_PTR(-ENODEV);
+
+- return port >= ARRAY_SIZE(eth->ports) ? -EINVAL : port;
++ return port->devs[d] ? port->devs[d] : ERR_PTR(-ENODEV);
+ }
+
+ static int airoha_qdma_lro_rx_process(struct airoha_queue *q,
+@@ -725,9 +721,8 @@ static int airoha_qdma_rx_process(struct
+ struct airoha_queue_entry *e = &q->entry[q->tail];
+ struct airoha_qdma_desc *desc = &q->desc[q->tail];
+ u32 hash, reason, msg1, desc_ctrl;
+- struct airoha_gdm_port *port;
+- struct net_device *netdev;
+- int data_len, len, p;
++ struct airoha_gdm_dev *dev;
++ int data_len, len;
+ struct page *page;
+
+ desc_ctrl = le32_to_cpu(READ_ONCE(desc->ctrl));
+@@ -748,15 +743,10 @@ static int airoha_qdma_rx_process(struct
+ if (!len || data_len < len)
+ goto free_frag;
+
+- p = airoha_qdma_get_gdm_port(eth, desc);
+- if (p < 0 || !eth->ports[p])
+- goto free_frag;
+-
+- port = eth->ports[p];
+- if (!port->dev)
++ dev = airoha_qdma_get_gdm_dev(eth, desc);
++ if (IS_ERR(dev))
+ goto free_frag;
+
+- netdev = port->dev->dev;
+ if (!q->skb) { /* first buffer */
+ q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
+ q->buf_size);
+@@ -766,15 +756,15 @@ static int airoha_qdma_rx_process(struct
+ skb_reserve(q->skb, AIROHA_RX_HEADROOM);
+ __skb_put(q->skb, len);
+ skb_mark_for_recycle(q->skb);
+- q->skb->dev = netdev;
++ q->skb->dev = dev->dev;
+ q->skb->ip_summed = CHECKSUM_UNNECESSARY;
+ skb_record_rx_queue(q->skb, qid);
+
+- if (lro_q && (netdev->features & NETIF_F_LRO) &&
++ if (lro_q && (dev->dev->features & NETIF_F_LRO) &&
+ airoha_qdma_lro_rx_process(q, desc) < 0)
+ goto free_frag;
+
+- q->skb->protocol = eth_type_trans(q->skb, netdev);
++ q->skb->protocol = eth_type_trans(q->skb, dev->dev);
+ } else { /* scattered frame */
+ struct skb_shared_info *shinfo = skb_shinfo(q->skb);
+ int nr_frags = shinfo->nr_frags;
+@@ -790,7 +780,9 @@ static int airoha_qdma_rx_process(struct
+ if (FIELD_GET(QDMA_DESC_MORE_MASK, desc_ctrl))
+ continue;
+
+- if (netdev_uses_dsa(netdev)) {
++ if (netdev_uses_dsa(dev->dev)) {
++ struct airoha_gdm_port *port = dev->port;
++
+ /* 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
+@@ -987,24 +979,27 @@ static void airoha_qdma_wake_netdev_txqs
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *port = eth->ports[i];
+- struct airoha_gdm_dev *dev;
+- int j;
++ int d;
+
+ if (!port)
+ continue;
+
+- dev = port->dev;
+- if (!dev)
+- continue;
++ for (d = 0; d < ARRAY_SIZE(port->devs); d++) {
++ struct airoha_gdm_dev *dev = port->devs[d];
++ int j;
+
+- if (dev->qdma != qdma)
+- continue;
++ if (!dev)
++ continue;
+
+- for (j = 0; j < dev->dev->num_tx_queues; j++) {
+- if (airoha_qdma_get_txq(qdma, j) != qid)
++ if (dev->qdma != qdma)
+ continue;
+
+- netif_wake_subqueue(dev->dev, j);
++ for (j = 0; j < dev->dev->num_tx_queues; j++) {
++ if (airoha_qdma_get_txq(qdma, j) != qid)
++ continue;
++
++ netif_wake_subqueue(dev->dev, j);
++ }
+ }
+ }
+ q->txq_stopped = false;
+@@ -1893,11 +1888,9 @@ static int airoha_dev_open(struct net_de
+ GLOBAL_CFG_RX_DMA_EN_MASK);
+ atomic_inc(&qdma->users);
+
+- if (port->id == AIROHA_GDM2_IDX &&
+- airoha_ppe_is_enabled(qdma->eth, 1)) {
+- /* For PPE2 always use secondary cpu port. */
++ if (!airoha_is_lan_gdm_dev(dev) &&
++ airoha_ppe_is_enabled(qdma->eth, 1))
+ pse_port = FE_PSE_PORT_PPE2;
+- }
+ airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
+ pse_port);
+
+@@ -1992,7 +1985,7 @@ static int airoha_set_gdm2_loopback(stru
+ airoha_fe_clear(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX));
+ airoha_fe_clear(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX));
+
+- src_port = eth->soc->ops.get_sport(port, port->nbq);
++ src_port = eth->soc->ops.get_sport(port, dev->nbq);
+ if (src_port < 0)
+ return src_port;
+
+@@ -2009,7 +2002,7 @@ static int airoha_set_gdm2_loopback(stru
+ airoha_ppe_set_cpu_port(dev, i, AIROHA_GDM2_IDX);
+
+ if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
+- u32 mask = FC_ID_OF_SRC_PORT_MASK(port->nbq);
++ u32 mask = FC_ID_OF_SRC_PORT_MASK(dev->nbq);
+
+ airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6, mask,
+ __field_prep(mask, AIROHA_GDM2_IDX));
+@@ -2023,7 +2016,7 @@ static int airoha_dev_init(struct net_de
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
+- int i;
++ int ppe_id;
+
+ /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
+ dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
+@@ -2046,8 +2039,8 @@ static int airoha_dev_init(struct net_de
+ break;
+ }
+
+- for (i = 0; i < eth->soc->num_ppe; i++)
+- airoha_ppe_set_cpu_port(dev, i, airoha_get_fe_port(dev));
++ ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1);
++ airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev));
+
+ return 0;
+ }
+@@ -2209,20 +2202,26 @@ static int airoha_dev_set_features(struc
+ } else {
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *p = eth->ports[i];
+- struct airoha_gdm_dev *d;
++ int j;
+
+ if (!p)
+ continue;
+
+- d = p->dev;
+- if (d->qdma != qdma)
+- continue;
++ for (j = 0; j < ARRAY_SIZE(p->devs); j++) {
++ struct airoha_gdm_dev *d = p->devs[j];
+
+- if (d->dev == netdev)
+- continue;
++ if (!d)
++ continue;
+
+- if (d->dev->features & NETIF_F_LRO)
+- return 0;
++ if (d->qdma != qdma)
++ continue;
++
++ if (d->dev == netdev)
++ continue;
++
++ if (d->dev->features & NETIF_F_LRO)
++ return 0;
++ }
+ }
+ airoha_fe_lro_disable(eth, qdma_id);
+ }
+@@ -2273,7 +2272,8 @@ static netdev_tx_t airoha_dev_xmit(struc
+ }
+
+ fport = airoha_get_fe_port(dev);
+- msg1 = FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
++ msg1 = FIELD_PREP(QDMA_ETH_TXMSG_NBOQ_MASK, dev->nbq) |
++ FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
+ FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
+
+ q = &qdma->q_tx[qid];
+@@ -3209,12 +3209,15 @@ bool airoha_is_valid_gdm_dev(struct airo
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *port = eth->ports[i];
++ int j;
+
+ if (!port)
+ continue;
+
+- if (port->dev == dev)
+- return true;
++ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++ if (port->devs[j] == dev)
++ return true;
++ }
+ }
+
+ return false;
+@@ -3338,10 +3341,11 @@ static int airoha_setup_phylink(struct n
+
+ static int airoha_alloc_gdm_device(struct airoha_eth *eth,
+ struct airoha_gdm_port *port,
+- struct device_node *np)
++ int nbq, struct device_node *np)
+ {
+- struct airoha_gdm_dev *dev;
+ struct net_device *netdev;
++ struct airoha_gdm_dev *dev;
++ u8 index;
+ int err;
+
+ netdev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*dev),
+@@ -3359,7 +3363,6 @@ static int airoha_alloc_gdm_device(struc
+ netdev->hw_features = AIROHA_HW_FEATURES | NETIF_F_LRO;
+ netdev->features |= AIROHA_HW_FEATURES;
+ netdev->vlan_features = AIROHA_HW_FEATURES;
+- netdev->dev.of_node = np;
+ SET_NETDEV_DEV(netdev, eth->dev);
+
+ /* reserve hw queues for HTB offloading */
+@@ -3377,11 +3380,25 @@ static int airoha_alloc_gdm_device(struc
+ netdev->dev_addr);
+ }
+
++ /* Allowed nbq for EN7581 on GDM3 port are 4 and 5 for PCIE0
++ * and PCIE1 respectively.
++ */
++ index = nbq;
++ if (index && airoha_is_7581(eth) && port->id == AIROHA_GDM3_IDX)
++ index -= 4;
++
++ if (index >= ARRAY_SIZE(port->devs) || port->devs[index]) {
++ dev_err(eth->dev, "invalid nbq id: %d\n", nbq);
++ return -EINVAL;
++ }
++
++ netdev->dev.of_node = of_node_get(np);
+ dev = netdev_priv(netdev);
+ dev->dev = netdev;
+ dev->port = port;
+- port->dev = dev;
+ dev->eth = eth;
++ dev->nbq = nbq;
++ port->devs[index] = dev;
+
+ #if defined(CONFIG_PCS_AIROHA)
+ if (airhoa_is_phy_external(port)) {
+@@ -3399,7 +3416,8 @@ static int airoha_alloc_gdm_port(struct
+ {
+ const __be32 *id_ptr = of_get_property(np, "reg", NULL);
+ struct airoha_gdm_port *port;
+- int err, p;
++ struct device_node *node;
++ int err, nbq, p, d = 0;
+ u32 id;
+
+ if (!id_ptr) {
+@@ -3427,15 +3445,51 @@ static int airoha_alloc_gdm_port(struct
+ u64_stats_init(&port->stats.syncp);
+ spin_lock_init(&port->stats.lock);
+ port->id = id;
+- /* XXX: Read nbq from DTS */
+- port->nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
+ eth->ports[p] = port;
+
+ err = airoha_metadata_dst_alloc(port);
+ if (err)
+ return err;
+
+- return airoha_alloc_gdm_device(eth, port, np);
++ /* Default nbq value to ensure backward compatibility */
++ nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
++
++ for_each_child_of_node(np, node) {
++ /* Multiple external serdes connected to the FE GDM port via an
++ * external arbiter.
++ */
++ const __be32 *nbq_ptr;
++
++ if (!of_device_is_compatible(node, "airoha,eth-port"))
++ continue;
++
++ d++;
++ if (!of_device_is_available(node))
++ continue;
++
++ nbq_ptr = of_get_property(node, "reg", NULL);
++ if (!nbq_ptr) {
++ dev_err(eth->dev, "missing nbq id\n");
++ of_node_put(node);
++ return -EINVAL;
++ }
++
++ /* Verify the provided nbq parameter is valid */
++ nbq = be32_to_cpup(nbq_ptr);
++ err = eth->soc->ops.get_sport(port, nbq);
++ if (err < 0) {
++ of_node_put(node);
++ return err;
++ }
++
++ err = airoha_alloc_gdm_device(eth, port, nbq, node);
++ if (err) {
++ of_node_put(node);
++ return err;
++ }
++ }
++
++ return !d ? airoha_alloc_gdm_device(eth, port, nbq, np) : 0;
+ }
+
+ static int airoha_register_gdm_devices(struct airoha_eth *eth)
+@@ -3444,14 +3498,22 @@ static int airoha_register_gdm_devices(s
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *port = eth->ports[i];
+- int err;
++ int j;
+
+ if (!port)
+ continue;
+
+- err = register_netdev(port->dev->dev);
+- if (err)
+- return err;
++ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++ struct airoha_gdm_dev *dev = port->devs[j];
++ int err;
++
++ if (!dev)
++ continue;
++
++ err = register_netdev(dev->dev);
++ if (err)
++ return err;
++ }
+ }
+
+ set_bit(DEV_STATE_REGISTERED, ð->state);
+@@ -3558,18 +3620,27 @@ error_napi_stop:
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *port = eth->ports[i];
+- struct airoha_gdm_dev *dev;
++ int j;
+
+ if (!port)
+ continue;
+
+- dev = port->dev;
+- if (dev && dev->dev->reg_state == NETREG_REGISTERED) {
++ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++ struct airoha_gdm_dev *dev = port->devs[j];
++ struct net_device *netdev;
++
++ if (!dev)
++ continue;
++
++ netdev = dev->dev;
++ if (netdev->reg_state == NETREG_REGISTERED) {
+ #if defined(CONFIG_PCS_AIROHA)
+- if (airhoa_is_phy_external(port))
+- phylink_destroy(dev->phylink);
++ if (airhoa_is_phy_external(port))
++ phylink_destroy(dev->phylink);
+ #endif
+- unregister_netdev(dev->dev);
++ unregister_netdev(netdev);
++ }
++ of_node_put(netdev->dev.of_node);
+ }
+ airoha_metadata_dst_free(port);
+ }
+@@ -3591,19 +3662,26 @@ static void airoha_remove(struct platfor
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ struct airoha_gdm_port *port = eth->ports[i];
+- struct airoha_gdm_dev *dev;
++ int j;
+
+ if (!port)
+ continue;
+
++ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++ struct airoha_gdm_dev *dev = port->devs[j];
++ struct net_device *netdev;
++
++ if (!dev)
++ continue;
++
+ #if defined(CONFIG_PCS_AIROHA)
+ if (airhoa_is_phy_external(port))
+ phylink_destroy(dev->phylink);
+ #endif
+-
+- dev = port->dev;
+- if (dev)
+- unregister_netdev(dev->dev);
++ netdev = dev->dev;
++ unregister_netdev(netdev);
++ of_node_put(netdev->dev.of_node);
++ }
+ airoha_metadata_dst_free(port);
+ }
+ airoha_hw_cleanup(eth);
+@@ -3665,6 +3743,39 @@ static u32 airoha_en7581_get_vip_port(st
+ return 0;
+ }
+
++static int airoha_en7581_get_dev_from_sport(struct airoha_qdma_desc *desc,
++ u16 *port, u16 *dev)
++{
++ u32 sport = FIELD_GET(QDMA_ETH_RXMSG_SPORT_MASK,
++ le32_to_cpu(READ_ONCE(desc->msg1)));
++
++ *dev = 0;
++ switch (sport) {
++ case 0x10 ... 0x14:
++ *port = 0; /* GDM1 */
++ break;
++ case 0x2 ... 0x4:
++ *port = sport - 1;
++ break;
++ case HSGMII_LAN_7581_PCIE1_SRCPORT:
++ *dev = 1;
++ fallthrough;
++ case HSGMII_LAN_7581_PCIE0_SRCPORT:
++ *port = 2; /* GDM3 */
++ break;
++ case HSGMII_LAN_7581_USB_SRCPORT:
++ *dev = 1;
++ fallthrough;
++ case HSGMII_LAN_7581_ETH_SRCPORT:
++ *port = 3; /* GDM4 */
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
+ static const char * const an7583_xsi_rsts_names[] = {
+ "hsi0-mac",
+ "hsi1-mac",
+@@ -3713,6 +3824,36 @@ static u32 airoha_an7583_get_vip_port(st
+ return 0;
+ }
+
++static int airoha_an7583_get_dev_from_sport(struct airoha_qdma_desc *desc,
++ u16 *port, u16 *dev)
++{
++ u32 sport = FIELD_GET(QDMA_ETH_RXMSG_SPORT_MASK,
++ le32_to_cpu(READ_ONCE(desc->msg1)));
++
++ *dev = 0;
++ switch (sport) {
++ case 0x10 ... 0x14:
++ *port = 0; /* GDM1 */
++ break;
++ case 0x2 ... 0x4:
++ *port = sport - 1;
++ break;
++ case HSGMII_LAN_7583_ETH_SRCPORT:
++ *port = 2; /* GDM3 */
++ break;
++ case HSGMII_LAN_7583_USB_SRCPORT:
++ *dev = 1;
++ fallthrough;
++ case HSGMII_LAN_7583_PCIE_SRCPORT:
++ *port = 3; /* GDM4 */
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
+ static const struct airoha_eth_soc_data en7581_soc_data = {
+ .version = 0x7581,
+ .xsi_rsts_names = en7581_xsi_rsts_names,
+@@ -3721,6 +3862,7 @@ static const struct airoha_eth_soc_data
+ .ops = {
+ .get_sport = airoha_en7581_get_sport,
+ .get_vip_port = airoha_en7581_get_vip_port,
++ .get_dev_from_sport = airoha_en7581_get_dev_from_sport,
+ },
+ };
+
+@@ -3732,6 +3874,7 @@ static const struct airoha_eth_soc_data
+ .ops = {
+ .get_sport = airoha_an7583_get_sport,
+ .get_vip_port = airoha_an7583_get_vip_port,
++ .get_dev_from_sport = airoha_an7583_get_dev_from_sport,
+ },
+ };
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -17,6 +17,7 @@
+ #include <net/dsa.h>
+
+ #define AIROHA_MAX_NUM_GDM_PORTS 4
++#define AIROHA_MAX_NUM_GDM_DEVS 2
+ #define AIROHA_MAX_NUM_QDMA 2
+ #define AIROHA_MAX_NUM_IRQ_BANKS 4
+ #define AIROHA_MAX_DSA_PORTS 7
+@@ -551,8 +552,8 @@ struct airoha_qdma {
+ struct airoha_gdm_dev {
+ struct airoha_gdm_port *port;
+ struct airoha_qdma *qdma;
+- struct net_device *dev;
+ struct airoha_eth *eth;
++ struct net_device *dev;
+
+ #if defined(CONFIG_PCS_AIROHA)
+ struct phylink *phylink;
+@@ -563,12 +564,13 @@ struct airoha_gdm_dev {
+ /* qos stats counters */
+ u64 cpu_tx_packets;
+ u64 fwd_tx_packets;
++
++ int nbq;
+ };
+
+ struct airoha_gdm_port {
+- struct airoha_gdm_dev *dev;
++ struct airoha_gdm_dev *devs[AIROHA_MAX_NUM_GDM_DEVS];
+ int id;
+- int nbq;
+
+ struct airoha_hw_stats stats;
+
+@@ -604,6 +606,8 @@ struct airoha_eth_soc_data {
+ struct {
+ int (*get_sport)(struct airoha_gdm_port *port, int nbq);
+ u32 (*get_vip_port)(struct airoha_gdm_port *port, int nbq);
++ int (*get_dev_from_sport)(struct airoha_qdma_desc *desc,
++ u16 *port, u16 *dev);
+ } ops;
+ };
+
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -168,9 +168,7 @@ static void airoha_ppe_hw_init(struct ai
+ airoha_fe_clear(eth, REG_PPE_PPE_FLOW_CFG(i),
+ PPE_FLOW_CFG_IP6_6RD_MASK);
+
+- for (p = 0; p < ARRAY_SIZE(eth->ports); p++) {
+- struct airoha_gdm_port *port = eth->ports[p];
+-
++ 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,
+@@ -178,11 +176,24 @@ static void airoha_ppe_hw_init(struct ai
+ AIROHA_MAX_MTU) |
+ FIELD_PREP(FP1_EGRESS_MTU_MASK,
+ AIROHA_MAX_MTU));
+- if (!port)
++ }
++
++ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
++ struct airoha_gdm_port *port = eth->ports[i];
++ int j;
++
++ if (!port)
++ continue;
++
++ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++ struct airoha_gdm_dev *dev = port->devs[j];
++ u8 fport;
++
++ if (!dev)
+ continue;
+
+- airoha_ppe_set_cpu_port(port->dev, i,
+- airoha_get_fe_port(port->dev));
++ fport = airoha_get_fe_port(dev);
++ airoha_ppe_set_cpu_port(dev, i, fport);
+ }
+ }
+ }
--- /dev/null
+From de856a5b802cf030c8e242e98df3bc88446a4ea1 Mon Sep 17 00:00:00 2001
+Message-ID: <de856a5b802cf030c8e242e98df3bc88446a4ea1.1779348625.git.lorenzo@kernel.org>
+In-Reply-To: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+References: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 20 Mar 2026 11:09:40 +0100
+Subject: [PATCH 07/13] net: airoha: Do not stop GDM port if it is shared
+
+Theoretically, in the current codebase, two independent net_devices can
+be connected to the same GDM port so we need to check the GDM port is not
+used by any other running net_device before setting the forward
+configuration to FE_PSE_PORT_DROP.
+Moreover, always set in GDM_LONG_LEN_MASK field of REG_GDM_LEN_CFG
+register the maximum MTU of all running net_devices connected to the same
+GDM port.
+
+Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 59 +++++++++++++++++++-----
+ drivers/net/ethernet/airoha/airoha_eth.h | 1 +
+ 2 files changed, 48 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1849,8 +1849,8 @@ static int airoha_dev_open(struct net_de
+ int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
++ u32 cur_len, pse_port = FE_PSE_PORT_PPE1;
+ struct airoha_qdma *qdma = dev->qdma;
+- u32 pse_port = FE_PSE_PORT_PPE1;
+
+ #if defined(CONFIG_PCS_AIROHA)
+ if (airhoa_is_phy_external(port)) {
+@@ -1878,10 +1878,20 @@ static int airoha_dev_open(struct net_de
+ airoha_fe_clear(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
+ GDM_STAG_EN_MASK);
+
+- airoha_fe_rmw(qdma->eth, REG_GDM_LEN_CFG(port->id),
+- GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
+- FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
+- FIELD_PREP(GDM_LONG_LEN_MASK, len));
++ cur_len = airoha_fe_get(qdma->eth, REG_GDM_LEN_CFG(port->id),
++ GDM_LONG_LEN_MASK);
++ if (!port->users || len > cur_len) {
++ /* Opening a sibling net_device with a larger MTU updates the
++ * MTU of already running devices. This is required to allow
++ * multiple net_devices with different MTUs to share the same
++ * GDM port.
++ */
++ airoha_fe_rmw(qdma->eth, REG_GDM_LEN_CFG(port->id),
++ GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
++ FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
++ FIELD_PREP(GDM_LONG_LEN_MASK, len));
++ }
++ port->users++;
+
+ airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
+ GLOBAL_CFG_TX_DMA_EN_MASK |
+@@ -1897,6 +1907,30 @@ static int airoha_dev_open(struct net_de
+ return 0;
+ }
+
++static void airoha_set_port_mtu(struct airoha_eth *eth,
++ struct airoha_gdm_port *port)
++{
++ u32 len = 0;
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
++ struct airoha_gdm_dev *dev = port->devs[i];
++ struct net_device *netdev;
++
++ if (!dev)
++ continue;
++
++ netdev = dev->dev;
++ if (netif_running(netdev))
++ len = max_t(u32, len, netdev->mtu);
++ }
++ len += ETH_HLEN + ETH_FCS_LEN;
++
++ airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
++ GDM_LONG_LEN_MASK,
++ FIELD_PREP(GDM_LONG_LEN_MASK, len));
++}
++
+ static int airoha_dev_stop(struct net_device *netdev)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+@@ -1909,8 +1943,12 @@ static int airoha_dev_stop(struct net_de
+ for (i = 0; i < netdev->num_tx_queues; i++)
+ netdev_tx_reset_subqueue(netdev, i);
+
+- airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
+- FE_PSE_PORT_DROP);
++ if (--port->users)
++ airoha_set_port_mtu(dev->eth, port);
++ else
++ airoha_set_gdm_port_fwd_cfg(qdma->eth,
++ REG_GDM_FWD_CFG(port->id),
++ FE_PSE_PORT_DROP);
+
+ if (atomic_dec_and_test(&qdma->users)) {
+ airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
+@@ -2072,13 +2110,10 @@ static int airoha_dev_change_mtu(struct
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+- u32 len = ETH_HLEN + mtu + ETH_FCS_LEN;
+- struct airoha_eth *eth = dev->eth;
+
+- airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
+- GDM_LONG_LEN_MASK,
+- FIELD_PREP(GDM_LONG_LEN_MASK, len));
+ WRITE_ONCE(netdev->mtu, mtu);
++ if (port->users)
++ airoha_set_port_mtu(dev->eth, port);
+
+ return 0;
+ }
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -571,6 +571,7 @@ struct airoha_gdm_dev {
+ struct airoha_gdm_port {
+ struct airoha_gdm_dev *devs[AIROHA_MAX_NUM_GDM_DEVS];
+ int id;
++ int users;
+
+ struct airoha_hw_stats stats;
+
--- /dev/null
+From c4f3077948eda05a6b9d1a11304d82c3e0300151 Mon Sep 17 00:00:00 2001
+Message-ID: <c4f3077948eda05a6b9d1a11304d82c3e0300151.1779348625.git.lorenzo@kernel.org>
+In-Reply-To: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+References: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 3 Apr 2026 12:07:27 +0200
+Subject: [PATCH 08/13] net: airoha: Introduce WAN device flag
+
+Introduce WAN flag to specify if a given device is used to transmit/receive
+WAN or LAN traffic. Current codebase supports specifying LAN/WAN device
+configuration in ndo_init() callback during device bootstrap.
+In order to consider setups where LAN configuration is used even for
+GDM3/GDM4 devices, check airoha_is_lan_gdm_dev() to select pse_port in
+airoha_ppe_foe_entry_prepare().
+Please note after this patch, it will be possible to specify multiple LAN
+devices but just a single WAN one. Please note this change is not visible
+to the user since airoha_eth driver currently supports just the internal
+phy available via the MT7530 DSA switch and there are no WAN interfaces
+officially supported since PCS/external phy is not merged mainline yet
+(it will be posted with following patches).
+
+Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 72 +++++++++++++++++++-----
+ drivers/net/ethernet/airoha/airoha_eth.h | 13 ++---
+ drivers/net/ethernet/airoha/airoha_ppe.c | 2 +-
+ 3 files changed, 65 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2049,36 +2049,80 @@ static int airoha_set_gdm2_loopback(stru
+ return 0;
+ }
+
+-static int airoha_dev_init(struct net_device *netdev)
++static struct airoha_gdm_dev *
++airoha_get_wan_gdm_dev(struct airoha_eth *eth)
++{
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
++ struct airoha_gdm_port *port = eth->ports[i];
++ int j;
++
++ if (!port)
++ continue;
++
++ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++ struct airoha_gdm_dev *dev = port->devs[j];
++
++ if (dev && !airoha_is_lan_gdm_dev(dev))
++ return dev;
++ }
++ }
++
++ return NULL;
++}
++
++static void airoha_dev_set_qdma(struct airoha_gdm_dev *dev)
+ {
+- struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
+ int ppe_id;
+
+ /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
+ dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
+ dev->dev->irq = dev->qdma->irq_banks[0].irq;
+- airoha_set_macaddr(dev, netdev->dev_addr);
++
++ ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1);
++ airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev));
++}
++
++static int airoha_dev_init(struct net_device *netdev)
++{
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
+
+ switch (port->id) {
+ case AIROHA_GDM3_IDX:
+- case AIROHA_GDM4_IDX:
+- /* If GDM2 is active we can't enable loopback */
+- if (!eth->ports[1]) {
+- int err;
+-
+- err = airoha_set_gdm2_loopback(dev);
+- if (err)
+- return err;
+- }
++ case AIROHA_GDM4_IDX: {
++ struct airoha_eth *eth = dev->eth;
++
++ /* GDM2 supports a single net_device */
++ if (eth->ports[1] && eth->ports[1]->devs[0])
++ break;
++
++ if (airoha_get_wan_gdm_dev(eth))
++ break;
++
++ fallthrough;
++ }
++ case AIROHA_GDM2_IDX:
++ /* GDM2 is always used as wan */
++ dev->flags |= AIROHA_PRIV_F_WAN;
+ break;
+ default:
+ break;
+ }
+
+- ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1);
+- airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev));
++ airoha_dev_set_qdma(dev);
++ airoha_set_macaddr(dev, netdev->dev_addr);
++
++ if (!airoha_is_lan_gdm_dev(dev) &&
++ (port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)) {
++ int err;
++
++ err = airoha_set_gdm2_loopback(dev);
++ if (err)
++ return err;
++ }
+
+ return 0;
+ }
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -549,6 +549,10 @@ struct airoha_qdma {
+ DECLARE_BITMAP(qos_channel_map, AIROHA_NUM_QOS_CHANNELS);
+ };
+
++enum airoha_priv_flags {
++ AIROHA_PRIV_F_WAN = BIT(0),
++};
++
+ struct airoha_gdm_dev {
+ struct airoha_gdm_port *port;
+ struct airoha_qdma *qdma;
+@@ -565,6 +569,7 @@ struct airoha_gdm_dev {
+ u64 cpu_tx_packets;
+ u64 fwd_tx_packets;
+
++ u32 flags;
+ int nbq;
+ };
+
+@@ -671,13 +676,7 @@ static inline u16 airoha_qdma_get_txq(st
+
+ static inline bool airoha_is_lan_gdm_dev(struct airoha_gdm_dev *dev)
+ {
+- struct airoha_gdm_port *port = dev->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;
++ return !(dev->flags & AIROHA_PRIV_F_WAN);
+ }
+
+ static inline bool airoha_is_7581(struct airoha_eth *eth)
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -351,7 +351,7 @@ static int airoha_ppe_foe_entry_prepare(
+ return -EINVAL;
+
+ port = dev->port;
+- if (dsa_port >= 0 || eth->ports[1])
++ if (dsa_port >= 0 || airoha_is_lan_gdm_dev(dev))
+ pse_port = port->id == 4 ? FE_PSE_PORT_GDM4
+ : port->id;
+ else
--- /dev/null
+From 144f0e6e55896625e3411aad02399a5ebb48d8f9 Mon Sep 17 00:00:00 2001
+Message-ID: <144f0e6e55896625e3411aad02399a5ebb48d8f9.1779348625.git.lorenzo@kernel.org>
+In-Reply-To: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+References: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Mon, 13 Apr 2026 17:38:51 +0200
+Subject: [PATCH 09/13] net: airoha: Support multiple LAN/WAN interfaces for hw
+ MAC address configuration
+
+The EN7581 and AN7583 SoCs provide registers to configure hardware LAN/WAN
+MAC addresses, used to determine whether received traffic is destined for
+this host or should be forwarded to another device.
+The SoC hardware design assumes all interfaces configured as LAN (or WAN)
+share a common upper MAC address, which is programmed into the
+REG_FE_{LAN,WAN}_MAC_H register. The lower bytes of 'local' addresses can
+be expressed as a range via the REG_FE_MAC_LMIN and REG_FE_MAC_LMAX
+registers.
+Previously, only a single interface was considered when programming these
+registers. Extend the logic to derive the correct minimum and maximum
+values for REG_FE_MAC_LMIN/REG_FE_MAC_LMAX when two or more interfaces are
+configured as LAN or WAN.
+
+Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 77 ++++++++++++++++++++----
+ drivers/net/ethernet/airoha/airoha_eth.h | 2 +-
+ drivers/net/ethernet/airoha/airoha_ppe.c | 4 +-
+ 3 files changed, 68 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -80,20 +80,74 @@ static bool airhoa_is_phy_external(struc
+ }
+ #endif
+
+-static void airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
++static int airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
+ {
++ u8 ref_addr[ETH_ALEN] __aligned(2);
+ struct airoha_eth *eth = dev->eth;
+- u32 val, reg;
++ u32 reg, val, lmin, lmax;
++ int i;
++
++ eth_zero_addr(ref_addr);
++ lmin = (addr[3] << 16) | (addr[4] << 8) | addr[5];
++ lmax = lmin;
++
++ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
++ struct airoha_gdm_port *port = eth->ports[i];
++ int j;
++
++ if (!port)
++ continue;
++
++ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
++ struct airoha_gdm_dev *iter_dev;
++ struct net_device *netdev;
++
++ iter_dev = port->devs[j];
++ if (!iter_dev || iter_dev == dev)
++ continue;
++
++ if (airoha_is_lan_gdm_dev(iter_dev) !=
++ airoha_is_lan_gdm_dev(dev))
++ continue;
++
++ netdev = iter_dev->dev;
++ if (netdev->reg_state != NETREG_REGISTERED)
++ continue;
++
++ ether_addr_copy(ref_addr, netdev->dev_addr);
++ val = (netdev->dev_addr[3] << 16) |
++ (netdev->dev_addr[4] << 8) | netdev->dev_addr[5];
++ if (val < lmin)
++ lmin = val;
++ if (val > lmax)
++ lmax = val;
++ }
++ }
++
++ if (!is_zero_ether_addr(ref_addr) && memcmp(ref_addr, addr, 3)) {
++ /* According to the HW design, hw mac address MSBs must be
++ * the same for each net_device with the same LAN/WAN
++ * configuration.
++ */
++ dev_warn(eth->dev,
++ "%s: wrong mac addr, MSBs must be %02x:%02x:%02x\n",
++ dev->dev->name, ref_addr[0], ref_addr[1],
++ ref_addr[2]);
++ dev_warn(eth->dev, "FE hw forwarding won't work properly\n");
++
++ return -EINVAL;
++ }
+
+ reg = airoha_is_lan_gdm_dev(dev) ? 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);
++ airoha_fe_wr(eth, REG_FE_MAC_LMIN(reg), lmin);
++ airoha_fe_wr(eth, REG_FE_MAC_LMAX(reg), lmax);
++
++ airoha_ppe_init_upd_mem(dev, addr);
+
+- airoha_ppe_init_upd_mem(dev);
++ return 0;
+ }
+
+ static void airoha_set_gdm_port_fwd_cfg(struct airoha_eth *eth, u32 addr,
+@@ -1976,13 +2030,18 @@ static int airoha_dev_stop(struct net_de
+ static int airoha_dev_set_macaddr(struct net_device *netdev, void *p)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct sockaddr *addr = p;
+ int err;
+
+- err = eth_mac_addr(netdev, p);
++ err = eth_prepare_mac_addr_change(netdev, p);
++ if (err)
++ return err;
++
++ err = airoha_set_macaddr(dev, addr->sa_data);
+ if (err)
+ return err;
+
+- airoha_set_macaddr(dev, netdev->dev_addr);
++ eth_commit_mac_addr_change(netdev, p);
+
+ return 0;
+ }
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -712,7 +712,7 @@ void airoha_ppe_check_skb(struct airoha_
+ int airoha_ppe_setup_tc_block_cb(struct airoha_ppe_dev *dev, void *type_data);
+ 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_dev *dev);
++void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev, const u8 *addr);
+ 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);
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -1494,12 +1494,10 @@ void airoha_ppe_check_skb(struct airoha_
+ airoha_ppe_foe_insert_entry(ppe, skb, hash, rx_wlan);
+ }
+
+-void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev)
++void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev, const u8 *addr)
+ {
+ struct airoha_gdm_port *port = dev->port;
+- struct net_device *netdev = dev->dev;
+ struct airoha_eth *eth = dev->eth;
+- const u8 *addr = netdev->dev_addr;
+ u32 val;
+
+ val = (addr[2] << 24) | (addr[3] << 16) | (addr[4] << 8) | addr[5];
--- /dev/null
+From 917f959d54efd09719bd5047aa4b9543615e131e Mon Sep 17 00:00:00 2001
+Message-ID: <917f959d54efd09719bd5047aa4b9543615e131e.1779348625.git.lorenzo@kernel.org>
+In-Reply-To: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+References: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Fri, 19 Dec 2025 23:12:32 +0100
+Subject: [PATCH 10/13] net: airoha: Rename airoha_set_gdm2_loopback in
+ airoha_enable_gdm2_loopback
+
+This is a preliminary patch in order to allow the user to select if the
+configured device will be used as hw lan or wan.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2046,7 +2046,7 @@ static int airoha_dev_set_macaddr(struct
+ return 0;
+ }
+
+-static int airoha_set_gdm2_loopback(struct airoha_gdm_dev *dev)
++static int airoha_enable_gdm2_loopback(struct airoha_gdm_dev *dev)
+ {
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
+@@ -2178,7 +2178,7 @@ static int airoha_dev_init(struct net_de
+ (port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)) {
+ int err;
+
+- err = airoha_set_gdm2_loopback(dev);
++ err = airoha_enable_gdm2_loopback(dev);
+ if (err)
+ return err;
+ }
--- /dev/null
+From e928621a0bbd010b75624c77105ce31f2b8d2faf Mon Sep 17 00:00:00 2001
+Message-ID: <e928621a0bbd010b75624c77105ce31f2b8d2faf.1779348625.git.lorenzo@kernel.org>
+In-Reply-To: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+References: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Sat, 13 Dec 2025 09:45:09 +0100
+Subject: [PATCH 12/13] net: airoha: Add ethtool priv_flags callbacks
+
+Introduce ethtool priv_flags callbacks in order to allow the user to
+select if the configured device will be used as hw lan or wan and enable
+or disable gdm2 loopback (used for hw QoS).
+
+Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 173 ++++++++++++++++++++++
+ drivers/net/ethernet/airoha/airoha_regs.h | 1 +
+ 2 files changed, 174 insertions(+)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -1906,6 +1906,11 @@ static int airoha_dev_open(struct net_de
+ u32 cur_len, pse_port = FE_PSE_PORT_PPE1;
+ struct airoha_qdma *qdma = dev->qdma;
+
++ if (port->id == AIROHA_GDM2_IDX && airoha_is_lan_gdm_dev(dev)) {
++ /* GDM2 can be used just as wan */
++ return -EBUSY;
++ }
++
+ #if defined(CONFIG_PCS_AIROHA)
+ if (airhoa_is_phy_external(port)) {
+ err = phylink_of_phy_connect(dev->phylink, netdev->dev.of_node, 0);
+@@ -2108,6 +2113,45 @@ static int airoha_enable_gdm2_loopback(s
+ return 0;
+ }
+
++static int airoha_disable_gdm2_loopback(struct airoha_gdm_dev *dev)
++{
++ struct airoha_eth *eth = dev->eth;
++ int i, src_port;
++ u32 pse_port;
++
++ src_port = eth->soc->ops.get_sport(dev->port, dev->nbq);
++ if (src_port < 0)
++ return src_port;
++
++ airoha_fe_clear(eth,
++ REG_SP_DFT_CPORT(src_port >> fls(SP_CPORT_DFT_MASK)),
++ SP_CPORT_MASK(src_port & SP_CPORT_DFT_MASK));
++
++ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
++ FE_PSE_PORT_DROP);
++ airoha_fe_clear(eth, REG_GDM_LPBK_CFG(AIROHA_GDM2_IDX),
++ LPBK_CHAN_MASK | LPBK_MODE_MASK | LPBK_EN_MASK);
++ pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2
++ : FE_PSE_PORT_PPE1;
++ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
++ pse_port);
++
++ airoha_fe_rmw(eth, REG_FE_WAN_PORT, WAN0_MASK,
++ FIELD_PREP(WAN0_MASK, AIROHA_GDM2_IDX));
++
++ for (i = 0; i < eth->soc->num_ppe; i++)
++ airoha_fe_clear(eth, REG_PPE_DFT_CPORT(i, AIROHA_GDM2_IDX),
++ DFT_CPORT_MASK(AIROHA_GDM2_IDX));
++
++ /* Enable VIP and IFC for GDM2 */
++ airoha_fe_set(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX));
++ airoha_fe_set(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX));
++
++ airoha_fe_wr(eth, REG_SRC_PORT_FC_MAP6, FC_MAP6_DEF_VALUE);
++
++ return 0;
++}
++
+ static struct airoha_gdm_dev *
+ airoha_get_wan_gdm_dev(struct airoha_eth *eth)
+ {
+@@ -2509,6 +2553,80 @@ error:
+ return NETDEV_TX_OK;
+ }
+
++struct airoha_ethool_priv_flags {
++ char name[ETH_GSTRING_LEN];
++ int (*handler)(struct net_device *netdev, u32 flags);
++};
++
++static int airoha_dev_set_wan_flag(struct net_device *netdev, u32 flags)
++{
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++ struct airoha_gdm_port *port = dev->port;
++ struct airoha_eth *eth = dev->eth;
++
++ if (!((dev->flags ^ flags) & AIROHA_PRIV_F_WAN))
++ return 0;
++
++ if (netif_running(netdev))
++ return -EBUSY;
++
++ if (flags & AIROHA_PRIV_F_WAN) {
++ struct airoha_gdm_dev *wan_dev;
++
++ /* Verify the wan device is not already configured */
++ wan_dev = airoha_get_wan_gdm_dev(eth);
++ if (wan_dev && wan_dev != dev)
++ return -EBUSY;
++
++ switch (port->id) {
++ case AIROHA_GDM2_IDX:
++ dev->flags |= AIROHA_PRIV_F_WAN;
++ airoha_dev_set_qdma(dev);
++ break;
++ case AIROHA_GDM3_IDX:
++ case AIROHA_GDM4_IDX: {
++ int err;
++
++ dev->flags |= AIROHA_PRIV_F_WAN;
++ airoha_dev_set_qdma(dev);
++
++ err = airoha_enable_gdm2_loopback(dev);
++ if (err) {
++ dev->flags &= ~AIROHA_PRIV_F_WAN;
++ return err;
++ }
++ break;
++ }
++ default:
++ return -EOPNOTSUPP;
++ }
++ } else {
++ switch (port->id) {
++ case AIROHA_GDM3_IDX:
++ case AIROHA_GDM4_IDX: {
++ int err;
++
++ err = airoha_disable_gdm2_loopback(dev);
++ if (err)
++ return err;
++ break;
++ }
++ default:
++ break;
++ }
++
++ dev->flags &= ~AIROHA_PRIV_F_WAN;
++ airoha_dev_set_qdma(dev);
++ }
++
++ return airoha_set_macaddr(dev, netdev->dev_addr);
++}
++
++static const struct airoha_ethool_priv_flags airoha_eth_priv_flags[] = {
++ { "wan", airoha_dev_set_wan_flag },
++};
++#define AIROHA_PRIV_FLAGS_STR_LEN ARRAY_SIZE(airoha_eth_priv_flags)
++
+ static void airoha_ethtool_get_drvinfo(struct net_device *netdev,
+ struct ethtool_drvinfo *info)
+ {
+@@ -2517,6 +2635,7 @@ static void airoha_ethtool_get_drvinfo(s
+
+ strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
+ strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
++ info->n_priv_flags = AIROHA_PRIV_FLAGS_STR_LEN;
+ }
+
+ static void airoha_ethtool_get_mac_stats(struct net_device *netdev,
+@@ -2581,6 +2700,56 @@ airoha_ethtool_get_rmon_stats(struct net
+ } while (u64_stats_fetch_retry(&port->stats.syncp, start));
+ }
+
++static int airoha_ethtool_set_priv_flags(struct net_device *netdev, u32 flags)
++{
++ int i;
++
++ for (i = 0; i < AIROHA_PRIV_FLAGS_STR_LEN; i++) {
++ int err;
++
++ if (!airoha_eth_priv_flags[i].handler)
++ continue;
++
++ err = airoha_eth_priv_flags[i].handler(netdev, flags);
++ if (err)
++ return err;
++ }
++
++ return 0;
++}
++
++static u32 airoha_ethtool_get_priv_flags(struct net_device *netdev)
++{
++ struct airoha_gdm_dev *dev = netdev_priv(netdev);
++
++ return dev->flags;
++}
++
++static int airoha_ethtool_get_sset_count(struct net_device *netdev, int sset)
++{
++ switch (sset) {
++ case ETH_SS_PRIV_FLAGS:
++ return AIROHA_PRIV_FLAGS_STR_LEN;
++ default:
++ return -EOPNOTSUPP;
++ }
++}
++
++static void airoha_ethtool_get_strings(struct net_device *netdev,
++ u32 stringset, u8 *data)
++{
++ int i;
++
++ switch (stringset) {
++ case ETH_SS_PRIV_FLAGS:
++ for (i = 0; i < AIROHA_PRIV_FLAGS_STR_LEN; i++)
++ ethtool_puts(&data, airoha_eth_priv_flags[i].name);
++ break;
++ default:
++ break;
++ }
++}
++
+ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
+ int channel, enum tx_sched_mode mode,
+ const u16 *weights, u8 n_weights)
+@@ -3302,6 +3471,10 @@ static const struct ethtool_ops airoha_e
+ .get_rmon_stats = airoha_ethtool_get_rmon_stats,
+ .get_link_ksettings = phy_ethtool_get_link_ksettings,
+ .get_link = ethtool_op_get_link,
++ .set_priv_flags = airoha_ethtool_set_priv_flags,
++ .get_priv_flags = airoha_ethtool_get_priv_flags,
++ .get_sset_count = airoha_ethtool_get_sset_count,
++ .get_strings = airoha_ethtool_get_strings,
+ };
+
+ static void airoha_mac_config(struct phylink_config *config, unsigned int mode,
+--- a/drivers/net/ethernet/airoha/airoha_regs.h
++++ b/drivers/net/ethernet/airoha/airoha_regs.h
+@@ -402,6 +402,7 @@
+
+ #define REG_SRC_PORT_FC_MAP6 0x2298
+ #define FC_ID_OF_SRC_PORT_MASK(_n) GENMASK(4 + ((_n) << 3), ((_n) << 3))
++#define FC_MAP6_DEF_VALUE 0x1b1a1918
+
+ #define REG_CDM5_RX_OQ1_DROP_CNT 0x29d4
+
--- /dev/null
+From 99e204d255c9b47a188dd79762b9406d9c5fed9b Mon Sep 17 00:00:00 2001
+Message-ID: <99e204d255c9b47a188dd79762b9406d9c5fed9b.1779348625.git.lorenzo@kernel.org>
+In-Reply-To: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+References: <e15783f7c987e199ecf80b3d858ed5a86d33c508.1779348625.git.lorenzo@kernel.org>
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Wed, 20 May 2026 17:16:39 +0200
+Subject: [PATCH 13/13] net: airoha: Rework MTU configuration
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 85 +++++++++++------------
+ drivers/net/ethernet/airoha/airoha_eth.h | 1 +
+ drivers/net/ethernet/airoha/airoha_ppe.c | 8 +--
+ drivers/net/ethernet/airoha/airoha_regs.h | 9 ++-
+ 4 files changed, 49 insertions(+), 54 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -185,10 +185,15 @@ static void airoha_fe_maccr_init(struct
+ {
+ int p;
+
+- for (p = 1; p <= ARRAY_SIZE(eth->ports); p++)
++ for (p = 1; p <= ARRAY_SIZE(eth->ports); p++) {
+ airoha_fe_set(eth, REG_GDM_FWD_CFG(p),
+ GDM_TCP_CKSUM_MASK | GDM_UDP_CKSUM_MASK |
+ GDM_IP4_CKSUM_MASK | GDM_DROP_CRC_ERR_MASK);
++ 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, AIROHA_MAX_MTU));
++ }
+
+ airoha_fe_rmw(eth, REG_CDM_VLAN_CTRL(1), CDM_VLAN_MASK,
+ FIELD_PREP(CDM_VLAN_MASK, 0x8100));
+@@ -1898,13 +1903,37 @@ static void airoha_update_hw_stats(struc
+ spin_unlock(&port->stats.lock);
+ }
+
++void airoha_set_port_mtu(struct airoha_eth *eth, struct airoha_gdm_port *port)
++{
++ u32 mtu = 0, index;
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
++ struct airoha_gdm_dev *dev = port->devs[i];
++ struct net_device *netdev;
++
++ if (!dev)
++ continue;
++
++ netdev = dev->dev;
++ if (netif_running(netdev))
++ mtu = max_t(u32, mtu, netdev->mtu);
++ }
++
++ index = port->id == AIROHA_GDM4_IDX ? 7 : port->id;
++ for (i = 0; i < eth->soc->num_ppe; i++)
++ airoha_fe_rmw(eth, REG_PPE_MTU(i, index),
++ FP_EGRESS_MTU_MASK(index),
++ __field_prep(FP_EGRESS_MTU_MASK(index), mtu));
++}
++
+ static int airoha_dev_open(struct net_device *netdev)
+ {
+- int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+- u32 cur_len, pse_port = FE_PSE_PORT_PPE1;
+ struct airoha_qdma *qdma = dev->qdma;
++ u32 pse_port = FE_PSE_PORT_PPE1;
++ int err;
+
+ if (port->id == AIROHA_GDM2_IDX && airoha_is_lan_gdm_dev(dev)) {
+ /* GDM2 can be used just as wan */
+@@ -1937,19 +1966,12 @@ static int airoha_dev_open(struct net_de
+ airoha_fe_clear(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
+ GDM_STAG_EN_MASK);
+
+- cur_len = airoha_fe_get(qdma->eth, REG_GDM_LEN_CFG(port->id),
+- GDM_LONG_LEN_MASK);
+- if (!port->users || len > cur_len) {
+- /* Opening a sibling net_device with a larger MTU updates the
+- * MTU of already running devices. This is required to allow
+- * multiple net_devices with different MTUs to share the same
+- * GDM port.
+- */
+- airoha_fe_rmw(qdma->eth, REG_GDM_LEN_CFG(port->id),
+- GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
+- FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
+- FIELD_PREP(GDM_LONG_LEN_MASK, len));
+- }
++ airoha_set_port_mtu(qdma->eth, port);
++ if (!airoha_is_lan_gdm_dev(dev))
++ airoha_fe_rmw(qdma->eth, REG_WAN_MTU0,
++ WAN_MTU0_MASK,
++ FIELD_PREP(WAN_MTU0_MASK, netdev->mtu));
++
+ port->users++;
+
+ airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
+@@ -1966,30 +1988,6 @@ static int airoha_dev_open(struct net_de
+ return 0;
+ }
+
+-static void airoha_set_port_mtu(struct airoha_eth *eth,
+- struct airoha_gdm_port *port)
+-{
+- u32 len = 0;
+- int i;
+-
+- for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
+- struct airoha_gdm_dev *dev = port->devs[i];
+- struct net_device *netdev;
+-
+- if (!dev)
+- continue;
+-
+- netdev = dev->dev;
+- if (netif_running(netdev))
+- len = max_t(u32, len, netdev->mtu);
+- }
+- len += ETH_HLEN + ETH_FCS_LEN;
+-
+- airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
+- GDM_LONG_LEN_MASK,
+- FIELD_PREP(GDM_LONG_LEN_MASK, len));
+-}
+-
+ static int airoha_dev_stop(struct net_device *netdev)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+@@ -2073,10 +2071,6 @@ static int airoha_enable_gdm2_loopback(s
+ 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(AIROHA_GDM2_IDX),
+- GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK,
+- FIELD_PREP(GDM_SHORT_LEN_MASK, 60) |
+- FIELD_PREP(GDM_LONG_LEN_MASK, AIROHA_MAX_MTU));
+ /* Forward the traffic to the proper GDM port */
+ pse_port = port->id == AIROHA_GDM3_IDX ? FE_PSE_PORT_GDM3
+ : FE_PSE_PORT_GDM4;
+@@ -2600,6 +2594,9 @@ static int airoha_dev_set_wan_flag(struc
+ default:
+ return -EOPNOTSUPP;
+ }
++ airoha_fe_rmw(eth, REG_WAN_MTU0,
++ WAN_MTU0_MASK,
++ FIELD_PREP(WAN_MTU0_MASK, netdev->mtu));
+ } else {
+ switch (port->id) {
+ case AIROHA_GDM3_IDX:
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -705,6 +705,7 @@ int airoha_get_fe_port(struct airoha_gdm
+ bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
+ struct airoha_gdm_dev *dev);
+
++void airoha_set_port_mtu(struct airoha_eth *eth, struct airoha_gdm_port *port);
+ void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport);
+ 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,
+--- a/drivers/net/ethernet/airoha/airoha_ppe.c
++++ b/drivers/net/ethernet/airoha/airoha_ppe.c
+@@ -116,8 +116,6 @@ static void airoha_ppe_hw_init(struct ai
+ 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),
+ ppe->foe_dma + sram_tb_size);
+
+@@ -167,15 +165,6 @@ static void airoha_ppe_hw_init(struct ai
+ airoha_fe_wr(eth, REG_PPE_HASH_SEED(i), PPE_HASH_SEED);
+ airoha_fe_clear(eth, REG_PPE_PPE_FLOW_CFG(i),
+ PPE_FLOW_CFG_IP6_6RD_MASK);
+-
+- 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));
+ }
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+@@ -185,6 +174,7 @@ static void airoha_ppe_hw_init(struct ai
+ if (!port)
+ continue;
+
++ airoha_set_port_mtu(eth, port);
+ for (j = 0; j < ARRAY_SIZE(port->devs); j++) {
+ struct airoha_gdm_dev *dev = port->devs[j];
+ u8 fport;
+--- a/drivers/net/ethernet/airoha/airoha_regs.h
++++ b/drivers/net/ethernet/airoha/airoha_regs.h
+@@ -341,9 +341,8 @@
+ #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_MTU(_m, _n) (REG_PPE_MTU_BASE(_m) + (((_n) / 2) << 2))
++#define FP_EGRESS_MTU_MASK(_n) GENMASK(13 + (((_n) % 2) << 4), ((_n) % 2) << 4)
+
+ #define REG_PPE_RAM_CTRL(_n) (((_n) ? PPE2_BASE : PPE1_BASE) + 0x31c)
+ #define PPE_SRAM_CTRL_ACK_MASK BIT(31)
+@@ -404,6 +403,10 @@
+ #define FC_ID_OF_SRC_PORT_MASK(_n) GENMASK(4 + ((_n) << 3), ((_n) << 3))
+ #define FC_MAP6_DEF_VALUE 0x1b1a1918
+
++#define REG_WAN_MTU0 0x2300
++#define WAN_MTU1_MASK GENMASK(29, 16)
++#define WAN_MTU0_MASK GENMASK(13, 0)
++
+ #define REG_CDM5_RX_OQ1_DROP_CNT 0x29d4
+
+ /* QDMA */
--- /dev/null
+From 9652322e0b47eacfef497828f6476a2a3169fd13 Mon Sep 17 00:00:00 2001
+Message-ID: <9652322e0b47eacfef497828f6476a2a3169fd13.1779351672.git.lorenzo@kernel.org>
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sat, 17 Jan 2026 14:46:12 +0100
+Subject: [PATCH] net: airoha: Better handle MIB for GDM with multiple port
+ attached
+
+In the context of a GDM that can have multiple port attached (GDM3/4)
+the HW counter (MIB) are global for the GDM port. This cause duplicated
+stats reported to the kernel for the related interface.
+
+The SoC supports a split MIB feature where each counter is tracked based
+on the relevant HW channel (NBQ) to account for this scenario and
+provide a way to select the related counter on accessing the MIB
+registers.
+
+Enable this feature for GDM3 and GDM4 and configure the relevant HW
+channel before updating the HW stats to report correct HW counter to the
+kernel for the related interface.
+
+Also move the stats struct from port to dev since HW counter are
+now specific to the network interface instead of the GDM port.
+
+Co-developed-by: Brown Huang <Brown.huang@airoha.com>
+Signed-off-by: Brown Huang <Brown.huang@airoha.com>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 191 +++++++++++++----------
+ drivers/net/ethernet/airoha/airoha_eth.h | 7 +-
+ 2 files changed, 112 insertions(+), 86 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -611,6 +611,14 @@ static int airoha_fe_init(struct airoha_
+ airoha_fe_set(eth, REG_GDM_FWD_CFG(AIROHA_GDM4_IDX),
+ GDM_PAD_EN_MASK | GDM_STRIP_CRC_MASK);
+
++ /* Enable split for MIB counters for GDM3 and GDM4 */
++ airoha_fe_set(eth, REG_FE_GDM_MIB_CFG(AIROHA_GDM3_IDX),
++ FE_GDM_TX_MIB_SPLIT_EN_MASK |
++ FE_GDM_RX_MIB_SPLIT_EN_MASK);
++ airoha_fe_set(eth, REG_FE_GDM_MIB_CFG(AIROHA_GDM4_IDX),
++ FE_GDM_TX_MIB_SPLIT_EN_MASK |
++ FE_GDM_RX_MIB_SPLIT_EN_MASK);
++
+ airoha_fe_crsn_qsel_init(eth);
+
+ airoha_fe_clear(eth, REG_FE_CPORT_CFG, FE_CPORT_QUEUE_XFC_MASK);
+@@ -1758,149 +1766,169 @@ static void airoha_qdma_stop_napi(struct
+ }
+ }
+
+-static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
++static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev)
+ {
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
+ u32 val, i = 0;
+
+- spin_lock(&port->stats.lock);
+- u64_stats_update_begin(&port->stats.syncp);
++ u64_stats_update_begin(&dev->stats.syncp);
++
++ /* Read relevant MIB for GDM with multiple port attached */
++ if (port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)
++ airoha_fe_rmw(eth, REG_FE_GDM_MIB_CFG(port->id),
++ FE_TX_MIB_ID_MASK | FE_RX_MIB_ID_MASK,
++ FIELD_PREP(FE_TX_MIB_ID_MASK, dev->nbq) |
++ FIELD_PREP(FE_RX_MIB_ID_MASK, dev->nbq));
+
+ /* TX */
+ val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_H(port->id));
+- port->stats.tx_ok_pkts += ((u64)val << 32);
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->stats.tx_ok_bytes += val;
+
+ val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_DROP_CNT(port->id));
+- port->stats.tx_drops += val;
++ dev->stats.tx_drops += val;
+
+ val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_BC_CNT(port->id));
+- port->stats.tx_broadcast += val;
++ dev->stats.tx_broadcast += val;
+
+ val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_MC_CNT(port->id));
+- port->stats.tx_multicast += val;
++ dev->stats.tx_multicast += val;
+
+ val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_RUNT_CNT(port->id));
+- port->stats.tx_len[i] += val;
++ dev->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);
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->stats.rx_ok_bytes += val;
+
+ val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_DROP_CNT(port->id));
+- port->stats.rx_drops += val;
++ dev->stats.rx_drops += val;
+
+ val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_BC_CNT(port->id));
+- port->stats.rx_broadcast += val;
++ dev->stats.rx_broadcast += val;
+
+ val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_MC_CNT(port->id));
+- port->stats.rx_multicast += val;
++ dev->stats.rx_multicast += val;
+
+ val = airoha_fe_rr(eth, REG_FE_GDM_RX_ERROR_DROP_CNT(port->id));
+- port->stats.rx_errors += val;
++ dev->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;
++ dev->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;
++ dev->stats.rx_over_errors += val;
+
+ val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_FRAG_CNT(port->id));
+- port->stats.rx_fragment += val;
++ dev->stats.rx_fragment += val;
+
+ val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_JABBER_CNT(port->id));
+- port->stats.rx_jabber += val;
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->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);
++ dev->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;
++ dev->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;
++ dev->stats.rx_len[i++] += val;
++
++ u64_stats_update_end(&dev->stats.syncp);
++}
+
+- /* reset mib counters */
+- airoha_fe_set(eth, REG_FE_GDM_MIB_CLEAR(port->id),
++static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
++{
++ struct airoha_gdm_port *port = dev->port;
++ int i;
++
++ spin_lock(&port->stats_lock);
++
++ for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
++ if (port->devs[i])
++ airoha_dev_get_hw_stats(port->devs[i]);
++ }
++
++ /* Reset MIB counters */
++ airoha_fe_set(dev->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);
++ spin_unlock(&port->stats_lock);
+ }
+
+ void airoha_set_port_mtu(struct airoha_eth *eth, struct airoha_gdm_port *port)
+@@ -2228,23 +2256,22 @@ static void airoha_dev_get_stats64(struc
+ struct rtnl_link_stats64 *storage)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+ unsigned int start;
+
+ airoha_update_hw_stats(dev);
+ 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));
++ start = u64_stats_fetch_begin(&dev->stats.syncp);
++ storage->rx_packets = dev->stats.rx_ok_pkts;
++ storage->tx_packets = dev->stats.tx_ok_pkts;
++ storage->rx_bytes = dev->stats.rx_ok_bytes;
++ storage->tx_bytes = dev->stats.tx_ok_bytes;
++ storage->multicast = dev->stats.rx_multicast;
++ storage->rx_errors = dev->stats.rx_errors;
++ storage->rx_dropped = dev->stats.rx_drops;
++ storage->tx_dropped = dev->stats.tx_drops;
++ storage->rx_crc_errors = dev->stats.rx_crc_error;
++ storage->rx_over_errors = dev->stats.rx_over_errors;
++ } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
+ }
+
+ static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
+@@ -2639,20 +2666,19 @@ static void airoha_ethtool_get_mac_stats
+ struct ethtool_eth_mac_stats *stats)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+ unsigned int start;
+
+ airoha_update_hw_stats(dev);
+ 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));
++ start = u64_stats_fetch_begin(&dev->stats.syncp);
++ stats->FramesTransmittedOK = dev->stats.tx_ok_pkts;
++ stats->OctetsTransmittedOK = dev->stats.tx_ok_bytes;
++ stats->MulticastFramesXmittedOK = dev->stats.tx_multicast;
++ stats->BroadcastFramesXmittedOK = dev->stats.tx_broadcast;
++ stats->FramesReceivedOK = dev->stats.rx_ok_pkts;
++ stats->OctetsReceivedOK = dev->stats.rx_ok_bytes;
++ stats->BroadcastFramesReceivedOK = dev->stats.rx_broadcast;
++ } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
+ }
+
+ static const struct ethtool_rmon_hist_range airoha_ethtool_rmon_ranges[] = {
+@@ -2672,8 +2698,7 @@ airoha_ethtool_get_rmon_stats(struct net
+ const struct ethtool_rmon_hist_range **ranges)
+ {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+- struct airoha_gdm_port *port = dev->port;
+- struct airoha_hw_stats *hw_stats = &port->stats;
++ struct airoha_hw_stats *hw_stats = &dev->stats;
+ unsigned int start;
+
+ BUILD_BUG_ON(ARRAY_SIZE(airoha_ethtool_rmon_ranges) !=
+@@ -2686,7 +2711,7 @@ airoha_ethtool_get_rmon_stats(struct net
+ do {
+ int i;
+
+- start = u64_stats_fetch_begin(&port->stats.syncp);
++ start = u64_stats_fetch_begin(&dev->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;
+@@ -2694,7 +2719,7 @@ airoha_ethtool_get_rmon_stats(struct net
+ 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));
++ } while (u64_stats_fetch_retry(&dev->stats.syncp, start));
+ }
+
+ static int airoha_ethtool_set_priv_flags(struct net_device *netdev, u32 flags)
+@@ -3702,6 +3727,7 @@ static int airoha_alloc_gdm_device(struc
+
+ netdev->dev.of_node = of_node_get(np);
+ dev = netdev_priv(netdev);
++ u64_stats_init(&dev->stats.syncp);
+ dev->dev = netdev;
+ dev->port = port;
+ dev->eth = eth;
+@@ -3750,9 +3776,8 @@ static int airoha_alloc_gdm_port(struct
+ if (!port)
+ return -ENOMEM;
+
+- u64_stats_init(&port->stats.syncp);
+- spin_lock_init(&port->stats.lock);
+ port->id = id;
++ spin_lock_init(&port->stats_lock);
+ eth->ports[p] = port;
+
+ err = airoha_metadata_dst_alloc(port);
+--- a/drivers/net/ethernet/airoha/airoha_eth.h
++++ b/drivers/net/ethernet/airoha/airoha_eth.h
+@@ -226,8 +226,6 @@ struct airoha_tx_irq_queue {
+ };
+
+ struct airoha_hw_stats {
+- /* protect concurrent hw_stats accesses */
+- spinlock_t lock;
+ struct u64_stats_sync syncp;
+
+ /* get_stats64 */
+@@ -571,6 +569,8 @@ struct airoha_gdm_dev {
+
+ u32 flags;
+ int nbq;
++
++ struct airoha_hw_stats stats;
+ };
+
+ struct airoha_gdm_port {
+@@ -578,7 +578,8 @@ struct airoha_gdm_port {
+ int id;
+ int users;
+
+- struct airoha_hw_stats stats;
++ /* protect concurrent hw_stats accesses */
++ spinlock_t stats_lock;
+
+ struct metadata_dst *dsa_meta[AIROHA_MAX_DSA_PORTS];
+ };
--- /dev/null
+From 1e596dac503da44a9fcd3e2654a4963db3e2ee6a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 28 Jan 2026 02:38:24 +0100
+Subject: [PATCH] net: airoha: fix wrong airoha_get_fe_port()
+
+It seems the SDK where the airoha_get_fe_port() logic was taken was
+actually wrong and the AN7583 SoC doesn't have such difference. Instead
+it does follow the same port order of AN7581 SoC.
+
+Drop the switch case and apply the same condition on both AN7581 and
+AN7583 SoC.
+
+Fixes: e4e5ce823bdd ("net: airoha: Add AN7583 SoC support")
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/net/ethernet/airoha/airoha_eth.c | 14 ++------------
+ 1 file changed, 2 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/ethernet/airoha/airoha_eth.c
++++ b/drivers/net/ethernet/airoha/airoha_eth.c
+@@ -2353,17 +2353,9 @@ static u32 airoha_get_dsa_tag(struct sk_
+ int airoha_get_fe_port(struct airoha_gdm_dev *dev)
+ {
+ struct airoha_gdm_port *port = dev->port;
+- struct airoha_eth *eth = dev->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;
+- }
++ return port->id == AIROHA_GDM4_IDX ? FE_PSE_PORT_GDM4
++ : port->id;
+ }
+
+ static int airoha_dev_set_features(struct net_device *netdev,