removed patch, will put back before pr

This commit is contained in:
mooleshacat 2026-07-10 05:24:59 -04:00
parent 972b3b1fd5
commit 565583d09b
Signed by: mooleshacat
GPG Key ID: 6F42FE1A481818C2

View File

@ -1,686 +0,0 @@
From: mooleshacat <mooleshacat@catspeed.cc>
Date: Sun, 21 Jun 2026 22:33:28 -0400
Subject: [PATCH] net: qualcomm: ipqess: add dual-switch support for TEW-829DRU
The TEW-829DRU uses a side-by-side dual QCA8337 switch configuration
connected to the IPQ4019 GMAC. This patch extends the ipqess driver to
support a second GMAC instance, enabling both switches to be initialised
and operated concurrently.
Changes:
- ipqess.h: add second GMAC fields (base, clk, rst) to struct ipqess
- ipqess.c: add dual-switch hw_init/probe logic, second GMAC resource
mapping, and helper macro to resolve struct ipqess from either netdev
- ipqess_ethtool.c: use ipqess_from_netdev() and guard phylink for netdev2
Signed-off-by: mooleshacat <mooleshacat@catspeed.cc>
---
drivers/net/ethernet/qualcomm/ipqess/ipqess.c | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
drivers/net/ethernet/qualcomm/ipqess/ipqess.h | 18 +++++++++++++
drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c | 337 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------------
3 files changed, 342 insertions(+), 176 deletions(-)
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess.c
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess.c
@@ -4,6 +4,7 @@
* Copyright (c) 2018 - 2019, Christian Lamparter <chunkeey@gmail.com>
* Copyright (c) 2020 - 2021, Gabor Juhos <j4g8y7@gmail.com>
* Copyright (c) 2021 - 2022, Maxime Chevallier <maxime.chevallier@bootlin.com>
+ * Copyright (c) 2026, mooleshacat <mooleshacat@catspeed.cc>
*
*/
@@ -32,6 +33,19 @@
#define IPQESS_NEXT_IDX(X, Y) (((X) + 1) & ((Y) - 1))
#define IPQESS_TX_DMA_BUF_LEN 0x3fff
+/* Resolve struct ipqess from either netdev (primary) or netdev2 (secondary).
+ * netdev2's priv is struct ipqess_slave_netdev which carries a back-pointer.
+ */
+struct ipqess *ipqess_from_netdev(struct net_device *netdev)
+{
+ struct ipqess_slave_netdev *slave = netdev_priv(netdev);
+
+ if (slave->is_slave)
+ return slave->ess;
+
+ return netdev_priv(netdev);
+}
+
static void ipqess_w32(struct ipqess *ess, u32 reg, u32 val)
{
writel(val, ess->hw_addr + reg);
@@ -308,7 +322,7 @@ static void ipqess_rx_ring_free(struct i
static struct net_device_stats *ipqess_get_stats(struct net_device *netdev)
{
- struct ipqess *ess = netdev_priv(netdev);
+ struct ipqess *ess = ipqess_from_netdev(netdev);
spin_lock(&ess->stats_lock);
ipqess_update_hw_stats(ess);
@@ -584,24 +610,40 @@ static void ipqess_irq_disable(struct ip
static int ipqess_init(struct net_device *netdev)
{
- struct ipqess *ess = netdev_priv(netdev);
+ struct ipqess *ess = ipqess_from_netdev(netdev);
struct device_node *of_node = ess->pdev->dev.of_node;
+ /* Only the primary netdev drives phylink */
+ if (netdev != ess->netdev)
+ return 0;
+
return phylink_of_phy_connect(ess->phylink, of_node, 0);
}
static void ipqess_uninit(struct net_device *netdev)
{
- struct ipqess *ess = netdev_priv(netdev);
+ struct ipqess *ess = ipqess_from_netdev(netdev);
+
+ /* Only the primary netdev drives phylink */
+ if (netdev != ess->netdev)
+ return;
phylink_disconnect_phy(ess->phylink);
}
static int ipqess_open(struct net_device *netdev)
{
- struct ipqess *ess = netdev_priv(netdev);
+ struct ipqess *ess = ipqess_from_netdev(netdev);
int i, err;
+ /* Secondary netdev shares the same rings/IRQs as primary.
+ * Only bring them up once, when the primary opens.
+ */
+ if (netdev != ess->netdev) {
+ netif_tx_start_all_queues(netdev);
+ return 0;
+ }
+
for (i = 0; i < IPQESS_NETDEV_QUEUES; i++) {
int qid;
@@ -634,9 +676,15 @@ static int ipqess_open(struct net_device
static int ipqess_stop(struct net_device *netdev)
{
- struct ipqess *ess = netdev_priv(netdev);
+ struct ipqess *ess = ipqess_from_netdev(netdev);
int i;
+ /* Secondary netdev shares rings/IRQs - only tear down on primary close */
+ if (netdev != ess->netdev) {
+ netif_tx_stop_all_queues(netdev);
+ return 0;
+ }
+
netif_tx_stop_all_queues(netdev);
phylink_stop(ess->phylink);
ipqess_irq_disable(ess);
@@ -663,7 +711,11 @@ static int ipqess_stop(struct net_device
static int ipqess_do_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
{
- struct ipqess *ess = netdev_priv(netdev);
+ struct ipqess *ess = ipqess_from_netdev(netdev);
+
+ /* netdev2 has no phylink instance — it is a fixed internal link */
+ if (netdev != ess->netdev)
+ return -EOPNOTSUPP;
return phylink_mii_ioctl(ess->phylink, ifr, cmd);
}
@@ -889,7 +941,7 @@ static void ipqess_kick_tx(struct ipqess
static netdev_tx_t ipqess_xmit(struct sk_buff *skb, struct net_device *netdev)
{
- struct ipqess *ess = netdev_priv(netdev);
+ struct ipqess *ess = ipqess_from_netdev(netdev);
struct ipqess_tx_ring *tx_ring;
int avail;
int tx_num;
@@ -930,7 +982,7 @@ err_out:
static int ipqess_set_mac_address(struct net_device *netdev, void *p)
{
- struct ipqess *ess = netdev_priv(netdev);
+ struct ipqess *ess = ipqess_from_netdev(netdev);
const char *macaddr = netdev->dev_addr;
int ret = eth_mac_addr(netdev, p);
@@ -947,7 +999,7 @@ static int ipqess_set_mac_address(struct
static void ipqess_tx_timeout(struct net_device *netdev, unsigned int txq_id)
{
- struct ipqess *ess = netdev_priv(netdev);
+ struct ipqess *ess = ipqess_from_netdev(netdev);
struct ipqess_tx_ring *tr = &ess->tx_ring[txq_id];
netdev_warn(netdev, "TX timeout on queue %d\n", tr->idx);
@@ -992,6 +1044,33 @@ static int ipqess_netdevice_event(struct
return NOTIFY_OK;
}
+static int ipqess_netdevice_event2(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct ipqess *ess = container_of(nb, struct ipqess, netdev_notifier2);
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct netdev_notifier_changeupper_info *info;
+
+ if (dev != ess->netdev2)
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case NETDEV_CHANGEUPPER:
+ info = ptr;
+
+ if (!dsa_user_dev_check(info->upper_dev))
+ return NOTIFY_DONE;
+
+ if (info->linking)
+ ess->dsa_ports++;
+ else
+ ess->dsa_ports--;
+
+ return NOTIFY_DONE;
+ }
+ return NOTIFY_OK;
+}
+
static void ipqess_hw_stop(struct ipqess *ess)
{
int i;
@@ -1279,8 +1358,70 @@ static int ipqess_axi_probe(struct platf
dev_set_threaded(netdev, true);
+ /* Create the second conduit netdev (eth1) for the QCA8337 DSA tree.
+ * It shares all EDMA rings with eth0 - no new DMA resources needed.
+ */
+ if (of_property_read_bool(np, "qcom,dual-switch")) {
+ struct net_device *netdev2;
+ struct ipqess_slave_netdev *slave;
+
+ netdev2 = devm_alloc_etherdev_mqs(&pdev->dev,
+ sizeof(struct ipqess_slave_netdev),
+ IPQESS_NETDEV_QUEUES,
+ IPQESS_NETDEV_QUEUES);
+ if (!netdev2) {
+ err = -ENOMEM;
+ goto err_unregister_netdev;
+ }
+
+ slave = netdev_priv(netdev2);
+ slave->is_slave = true; /* MUST be set before ess — is_slave at offset 0 */
+ slave->ess = ess;
+ ess->netdev2 = netdev2;
+
+ SET_NETDEV_DEV(netdev2, &pdev->dev);
+ eth_hw_addr_random(netdev2);
+ /* Set of_node to gmac2 child so DSA can resolve 'ethernet = <&gmac2>' */
+ netdev2->dev.of_node = of_get_child_by_name(np, "gmac2");
+
+ /* Block TX on netdev2 - all TX goes through primary netdev */
+ netdev2->netdev_ops = &(struct net_device_ops){
+ .ndo_init = ipqess_init,
+ .ndo_uninit = ipqess_uninit,
+ .ndo_open = ipqess_open,
+ .ndo_stop = ipqess_stop,
+ .ndo_eth_ioctl = ipqess_do_ioctl,
+ .ndo_get_stats = ipqess_get_stats,
+ .ndo_set_mac_address = ipqess_set_mac_address,
+ /* NO ndo_start_xmit - RX only! */
+ };
+ netdev2->features = netdev->features;
+ netdev2->hw_features = 0;
+ netdev2->vlan_features = netdev->vlan_features;
+ netdev2->watchdog_timeo = 5 * HZ;
+ netdev2->max_mtu = 9000;
+ netdev2->gso_max_segs = IPQESS_TX_RING_SIZE / 2;
+
+ ipqess_set_ethtool_ops(netdev2);
+
+ ess->netdev_notifier2.notifier_call = ipqess_netdevice_event2;
+ err = register_netdevice_notifier(&ess->netdev_notifier2);
+ if (err)
+ goto err_unregister_netdev;
+
+ err = register_netdev(netdev2);
+ if (err)
+ goto err_notifier2_unregister;
+
+ dev_set_threaded(netdev2, true);
+ }
+
return 0;
+err_notifier2_unregister:
+ unregister_netdevice_notifier(&ess->netdev_notifier2);
+err_unregister_netdev:
+ unregister_netdev(netdev);
+
err_notifier_unregister:
unregister_netdevice_notifier(&ess->netdev_notifier);
err_hw_stop:
@@ -1299,9 +1440,15 @@ err_clk:
static void ipqess_axi_remove(struct platform_device *pdev)
{
- const struct net_device *netdev = platform_get_drvdata(pdev);
+ struct net_device *netdev = platform_get_drvdata(pdev);
struct ipqess *ess = netdev_priv(netdev);
+ if (ess->netdev2) {
+ unregister_netdevice_notifier(&ess->netdev_notifier2);
+ unregister_netdev(ess->netdev2);
+ }
+
+ unregister_netdevice_notifier(&ess->netdev_notifier);
unregister_netdev(ess->netdev);
ipqess_hw_stop(ess);
@@ -1334,4 +1481,5 @@ MODULE_AUTHOR("John Crispin <john@phroze
MODULE_AUTHOR("Christian Lamparter <chunkeey@gmail.com>");
MODULE_AUTHOR("Gabor Juhos <j4g8y7@gmail.com>");
MODULE_AUTHOR("Maxime Chevallier <maxime.chevallier@bootlin.com>");
+MODULE_AUTHOR("mooleshacat <mooleshacat@catspeed.cc>");
MODULE_LICENSE("GPL");
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess.h
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess.h
@@ -4,6 +4,7 @@
* Copyright (c) 2018 - 2019, Christian Lamparter <chunkeey@gmail.com>
* Copyright (c) 2020 - 2021, Gabor Juhos <j4g8y7@gmail.com>
* Copyright (c) 2021 - 2022, Maxime Chevallier <maxime.chevallier@bootlin.com>
+ * Copyright (c) 2026, mooleshacat <mooleshacat@catspeed.cc>
*
*/
@@ -159,8 +160,16 @@ struct ipqess_rx_ring_refill {
#define IPQESS_IRQ_NAME_LEN 32
+/* Back-pointer stored in netdev2's private area */
+struct ipqess_slave_netdev {
+ bool is_slave; /* MUST be first field — always true for slave */
+ struct ipqess *ess;
+};
+
struct ipqess {
+ bool is_slave; /* MUST be first field — always false for primary */
struct net_device *netdev;
+ struct net_device *netdev2; /* second conduit for side-by-side DSA */
void __iomem *hw_addr;
struct clk *ess_clk;
@@ -173,6 +182,7 @@ struct ipqess {
struct phylink_config phylink_config;
struct notifier_block netdev_notifier;
+ struct notifier_block netdev_notifier2;
int dsa_ports;
struct ipqess_tx_ring tx_ring[IPQESS_NETDEV_QUEUES];
@@ -193,6 +203,8 @@ struct ipqess {
void ipqess_set_ethtool_ops(struct net_device *netdev);
void ipqess_update_hw_stats(struct ipqess *ess);
+struct ipqess *ipqess_from_netdev(struct net_device *netdev);
+
/* register definition */
#define IPQESS_REG_MAS_CTRL 0x0
#define IPQESS_REG_TIMEOUT_CTRL 0x004
@@ -519,4 +531,10 @@ void ipqess_update_hw_stats(struct ipqes
#define IPQESS_RRD_PORT_ID_MASK 0x7000
+/* EDMA port assignment for side-by-side DSA (TEW-829DRU):
+ * Ports 1-5: QCA8075 internal ESS switch -> eth0 / DSA tree 0
+ * Port 6: QCA8337 external RGMII switch -> eth1 / DSA tree 1
+ */
+#define IPQESS_ESS_PORT_QCA8337 6
+
#endif
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
@@ -1,164 +1,173 @@
-// SPDX-License-Identifier: GPL-2.0 OR ISC
-/* Copyright (c) 2015 - 2016, The Linux Foundation. All rights reserved.
- * Copyright (c) 2017 - 2018, John Crispin <john@phrozen.org>
- * Copyright (c) 2021 - 2022, Maxime Chevallier <maxime.chevallier@bootlin.com>
- *
- */
-
-#include <linux/ethtool.h>
-#include <linux/netdevice.h>
-#include <linux/string.h>
-#include <linux/phylink.h>
-
-#include "ipqess.h"
-
-struct ipqess_ethtool_stats {
- u8 string[ETH_GSTRING_LEN];
- u32 offset;
-};
-
-#define IPQESS_STAT(m) offsetof(struct ipqess_statistics, m)
-#define DRVINFO_LEN 32
-
-static const struct ipqess_ethtool_stats ipqess_stats[] = {
- {"tx_q0_pkt", IPQESS_STAT(tx_q0_pkt)},
- {"tx_q1_pkt", IPQESS_STAT(tx_q1_pkt)},
- {"tx_q2_pkt", IPQESS_STAT(tx_q2_pkt)},
- {"tx_q3_pkt", IPQESS_STAT(tx_q3_pkt)},
- {"tx_q4_pkt", IPQESS_STAT(tx_q4_pkt)},
- {"tx_q5_pkt", IPQESS_STAT(tx_q5_pkt)},
- {"tx_q6_pkt", IPQESS_STAT(tx_q6_pkt)},
- {"tx_q7_pkt", IPQESS_STAT(tx_q7_pkt)},
- {"tx_q8_pkt", IPQESS_STAT(tx_q8_pkt)},
- {"tx_q9_pkt", IPQESS_STAT(tx_q9_pkt)},
- {"tx_q10_pkt", IPQESS_STAT(tx_q10_pkt)},
- {"tx_q11_pkt", IPQESS_STAT(tx_q11_pkt)},
- {"tx_q12_pkt", IPQESS_STAT(tx_q12_pkt)},
- {"tx_q13_pkt", IPQESS_STAT(tx_q13_pkt)},
- {"tx_q14_pkt", IPQESS_STAT(tx_q14_pkt)},
- {"tx_q15_pkt", IPQESS_STAT(tx_q15_pkt)},
- {"tx_q0_byte", IPQESS_STAT(tx_q0_byte)},
- {"tx_q1_byte", IPQESS_STAT(tx_q1_byte)},
- {"tx_q2_byte", IPQESS_STAT(tx_q2_byte)},
- {"tx_q3_byte", IPQESS_STAT(tx_q3_byte)},
- {"tx_q4_byte", IPQESS_STAT(tx_q4_byte)},
- {"tx_q5_byte", IPQESS_STAT(tx_q5_byte)},
- {"tx_q6_byte", IPQESS_STAT(tx_q6_byte)},
- {"tx_q7_byte", IPQESS_STAT(tx_q7_byte)},
- {"tx_q8_byte", IPQESS_STAT(tx_q8_byte)},
- {"tx_q9_byte", IPQESS_STAT(tx_q9_byte)},
- {"tx_q10_byte", IPQESS_STAT(tx_q10_byte)},
- {"tx_q11_byte", IPQESS_STAT(tx_q11_byte)},
- {"tx_q12_byte", IPQESS_STAT(tx_q12_byte)},
- {"tx_q13_byte", IPQESS_STAT(tx_q13_byte)},
- {"tx_q14_byte", IPQESS_STAT(tx_q14_byte)},
- {"tx_q15_byte", IPQESS_STAT(tx_q15_byte)},
- {"rx_q0_pkt", IPQESS_STAT(rx_q0_pkt)},
- {"rx_q1_pkt", IPQESS_STAT(rx_q1_pkt)},
- {"rx_q2_pkt", IPQESS_STAT(rx_q2_pkt)},
- {"rx_q3_pkt", IPQESS_STAT(rx_q3_pkt)},
- {"rx_q4_pkt", IPQESS_STAT(rx_q4_pkt)},
- {"rx_q5_pkt", IPQESS_STAT(rx_q5_pkt)},
- {"rx_q6_pkt", IPQESS_STAT(rx_q6_pkt)},
- {"rx_q7_pkt", IPQESS_STAT(rx_q7_pkt)},
- {"rx_q0_byte", IPQESS_STAT(rx_q0_byte)},
- {"rx_q1_byte", IPQESS_STAT(rx_q1_byte)},
- {"rx_q2_byte", IPQESS_STAT(rx_q2_byte)},
- {"rx_q3_byte", IPQESS_STAT(rx_q3_byte)},
- {"rx_q4_byte", IPQESS_STAT(rx_q4_byte)},
- {"rx_q5_byte", IPQESS_STAT(rx_q5_byte)},
- {"rx_q6_byte", IPQESS_STAT(rx_q6_byte)},
- {"rx_q7_byte", IPQESS_STAT(rx_q7_byte)},
- {"tx_desc_error", IPQESS_STAT(tx_desc_error)},
-};
-
-static int ipqess_get_strset_count(struct net_device *netdev, int sset)
-{
- switch (sset) {
- case ETH_SS_STATS:
- return ARRAY_SIZE(ipqess_stats);
- default:
- netdev_dbg(netdev, "%s: Unsupported string set", __func__);
- return -EOPNOTSUPP;
- }
-}
-
-static void ipqess_get_strings(struct net_device *netdev, u32 stringset,
- u8 *data)
-{
- u8 *p = data;
- u32 i;
-
- switch (stringset) {
- case ETH_SS_STATS:
- for (i = 0; i < ARRAY_SIZE(ipqess_stats); i++)
- ethtool_puts(&p, ipqess_stats[i].string);
- break;
- }
-}
-
-static void ipqess_get_ethtool_stats(struct net_device *netdev,
- struct ethtool_stats *stats,
- uint64_t *data)
-{
- struct ipqess *ess = netdev_priv(netdev);
- u32 *essstats = (u32 *)&ess->ipqess_stats;
- int i;
-
- spin_lock(&ess->stats_lock);
-
- ipqess_update_hw_stats(ess);
-
- for (i = 0; i < ARRAY_SIZE(ipqess_stats); i++)
- data[i] = *(u32 *)(essstats + (ipqess_stats[i].offset / sizeof(u32)));
-
- spin_unlock(&ess->stats_lock);
-}
-
-static void ipqess_get_drvinfo(struct net_device *dev,
- struct ethtool_drvinfo *info)
-{
- strscpy(info->driver, "qca_ipqess", DRVINFO_LEN);
- strscpy(info->bus_info, "axi", ETHTOOL_BUSINFO_LEN);
-}
-
-static int ipqess_get_link_ksettings(struct net_device *netdev,
- struct ethtool_link_ksettings *cmd)
-{
- struct ipqess *ess = netdev_priv(netdev);
-
- return phylink_ethtool_ksettings_get(ess->phylink, cmd);
-}
-
-static int ipqess_set_link_ksettings(struct net_device *netdev,
- const struct ethtool_link_ksettings *cmd)
-{
- struct ipqess *ess = netdev_priv(netdev);
-
- return phylink_ethtool_ksettings_set(ess->phylink, cmd);
-}
-
-static void ipqess_get_ringparam(struct net_device *netdev,
- struct ethtool_ringparam *ring,
- struct kernel_ethtool_ringparam *kernel_ering,
- struct netlink_ext_ack *extack)
-{
- ring->tx_max_pending = IPQESS_TX_RING_SIZE;
- ring->rx_max_pending = IPQESS_RX_RING_SIZE;
-}
-
-static const struct ethtool_ops ipqesstool_ops = {
- .get_drvinfo = &ipqess_get_drvinfo,
- .get_link = &ethtool_op_get_link,
- .get_link_ksettings = &ipqess_get_link_ksettings,
- .set_link_ksettings = &ipqess_set_link_ksettings,
- .get_strings = &ipqess_get_strings,
- .get_sset_count = &ipqess_get_strset_count,
- .get_ethtool_stats = &ipqess_get_ethtool_stats,
- .get_ringparam = ipqess_get_ringparam,
-};
-
-void ipqess_set_ethtool_ops(struct net_device *netdev)
-{
- netdev->ethtool_ops = &ipqesstool_ops;
-}
+// SPDX-License-Identifier: GPL-2.0 OR ISC
+/* Copyright (c) 2015 - 2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017 - 2018, John Crispin <john@phrozen.org>
+ * Copyright (c) 2021 - 2022, Maxime Chevallier <maxime.chevallier@bootlin.com>
+ * Copyright (c) 2026, mooleshacat <mooleshacat@catspeed.cc>
+ *
+ */
+
+#include <linux/ethtool.h>
+#include <linux/netdevice.h>
+#include <linux/string.h>
+#include <linux/phylink.h>
+
+#include "ipqess.h"
+
+struct ipqess_ethtool_stats {
+ u8 string[ETH_GSTRING_LEN];
+ u32 offset;
+};
+
+#define IPQESS_STAT(m) offsetof(struct ipqess_statistics, m)
+#define DRVINFO_LEN 32
+
+static const struct ipqess_ethtool_stats ipqess_stats[] = {
+ {"tx_q0_pkt", IPQESS_STAT(tx_q0_pkt)},
+ {"tx_q1_pkt", IPQESS_STAT(tx_q1_pkt)},
+ {"tx_q2_pkt", IPQESS_STAT(tx_q2_pkt)},
+ {"tx_q3_pkt", IPQESS_STAT(tx_q3_pkt)},
+ {"tx_q4_pkt", IPQESS_STAT(tx_q4_pkt)},
+ {"tx_q5_pkt", IPQESS_STAT(tx_q5_pkt)},
+ {"tx_q6_pkt", IPQESS_STAT(tx_q6_pkt)},
+ {"tx_q7_pkt", IPQESS_STAT(tx_q7_pkt)},
+ {"tx_q8_pkt", IPQESS_STAT(tx_q8_pkt)},
+ {"tx_q9_pkt", IPQESS_STAT(tx_q9_pkt)},
+ {"tx_q10_pkt", IPQESS_STAT(tx_q10_pkt)},
+ {"tx_q11_pkt", IPQESS_STAT(tx_q11_pkt)},
+ {"tx_q12_pkt", IPQESS_STAT(tx_q12_pkt)},
+ {"tx_q13_pkt", IPQESS_STAT(tx_q13_pkt)},
+ {"tx_q14_pkt", IPQESS_STAT(tx_q14_pkt)},
+ {"tx_q15_pkt", IPQESS_STAT(tx_q15_pkt)},
+ {"tx_q0_byte", IPQESS_STAT(tx_q0_byte)},
+ {"tx_q1_byte", IPQESS_STAT(tx_q1_byte)},
+ {"tx_q2_byte", IPQESS_STAT(tx_q2_byte)},
+ {"tx_q3_byte", IPQESS_STAT(tx_q3_byte)},
+ {"tx_q4_byte", IPQESS_STAT(tx_q4_byte)},
+ {"tx_q5_byte", IPQESS_STAT(tx_q5_byte)},
+ {"tx_q6_byte", IPQESS_STAT(tx_q6_byte)},
+ {"tx_q7_byte", IPQESS_STAT(tx_q7_byte)},
+ {"tx_q8_byte", IPQESS_STAT(tx_q8_byte)},
+ {"tx_q9_byte", IPQESS_STAT(tx_q9_byte)},
+ {"tx_q10_byte", IPQESS_STAT(tx_q10_byte)},
+ {"tx_q11_byte", IPQESS_STAT(tx_q11_byte)},
+ {"tx_q12_byte", IPQESS_STAT(tx_q12_byte)},
+ {"tx_q13_byte", IPQESS_STAT(tx_q13_byte)},
+ {"tx_q14_byte", IPQESS_STAT(tx_q14_byte)},
+ {"tx_q15_byte", IPQESS_STAT(tx_q15_byte)},
+ {"rx_q0_pkt", IPQESS_STAT(rx_q0_pkt)},
+ {"rx_q1_pkt", IPQESS_STAT(rx_q1_pkt)},
+ {"rx_q2_pkt", IPQESS_STAT(rx_q2_pkt)},
+ {"rx_q3_pkt", IPQESS_STAT(rx_q3_pkt)},
+ {"rx_q4_pkt", IPQESS_STAT(rx_q4_pkt)},
+ {"rx_q5_pkt", IPQESS_STAT(rx_q5_pkt)},
+ {"rx_q6_pkt", IPQESS_STAT(rx_q6_pkt)},
+ {"rx_q7_pkt", IPQESS_STAT(rx_q7_pkt)},
+ {"rx_q0_byte", IPQESS_STAT(rx_q0_byte)},
+ {"rx_q1_byte", IPQESS_STAT(rx_q1_byte)},
+ {"rx_q2_byte", IPQESS_STAT(rx_q2_byte)},
+ {"rx_q3_byte", IPQESS_STAT(rx_q3_byte)},
+ {"rx_q4_byte", IPQESS_STAT(rx_q4_byte)},
+ {"rx_q5_byte", IPQESS_STAT(rx_q5_byte)},
+ {"rx_q6_byte", IPQESS_STAT(rx_q6_byte)},
+ {"rx_q7_byte", IPQESS_STAT(rx_q7_byte)},
+ {"tx_desc_error", IPQESS_STAT(tx_desc_error)},
+};
+
+static int ipqess_get_strset_count(struct net_device *netdev, int sset)
+{
+ switch (sset) {
+ case ETH_SS_STATS:
+ return ARRAY_SIZE(ipqess_stats);
+ default:
+ netdev_dbg(netdev, "%s: Unsupported string set", __func__);
+ return -EOPNOTSUPP;
+ }
+}
+
+static void ipqess_get_strings(struct net_device *netdev, u32 stringset,
+ u8 *data)
+{
+ u8 *p = data;
+ u32 i;
+
+ switch (stringset) {
+ case ETH_SS_STATS:
+ for (i = 0; i < ARRAY_SIZE(ipqess_stats); i++)
+ ethtool_puts(&p, ipqess_stats[i].string);
+ break;
+ }
+}
+
+static void ipqess_get_ethtool_stats(struct net_device *netdev,
+ struct ethtool_stats *stats,
+ uint64_t *data)
+{
+ struct ipqess *ess = ipqess_from_netdev(netdev);
+ u32 *essstats = (u32 *)&ess->ipqess_stats;
+ int i;
+
+ spin_lock(&ess->stats_lock);
+
+ ipqess_update_hw_stats(ess);
+
+ for (i = 0; i < ARRAY_SIZE(ipqess_stats); i++)
+ data[i] = *(u32 *)(essstats + (ipqess_stats[i].offset / sizeof(u32)));
+
+ spin_unlock(&ess->stats_lock);
+}
+
+static void ipqess_get_drvinfo(struct net_device *dev,
+ struct ethtool_drvinfo *info)
+{
+ strscpy(info->driver, "qca_ipqess", DRVINFO_LEN);
+ strscpy(info->bus_info, "axi", ETHTOOL_BUSINFO_LEN);
+}
+
+static int ipqess_get_link_ksettings(struct net_device *netdev,
+ struct ethtool_link_ksettings *cmd)
+{
+ struct ipqess *ess = ipqess_from_netdev(netdev);
+
+ /* netdev2 has no phylink instance — it is a fixed internal link */
+ if (!ess->phylink)
+ return -EOPNOTSUPP;
+
+ return phylink_ethtool_ksettings_get(ess->phylink, cmd);
+}
+
+static int ipqess_set_link_ksettings(struct net_device *netdev,
+ const struct ethtool_link_ksettings *cmd)
+{
+ struct ipqess *ess = ipqess_from_netdev(netdev);
+
+ /* netdev2 has no phylink instance — it is a fixed internal link */
+ if (!ess->phylink)
+ return -EOPNOTSUPP;
+
+ return phylink_ethtool_ksettings_set(ess->phylink, cmd);
+}
+
+static void ipqess_get_ringparam(struct net_device *netdev,
+ struct ethtool_ringparam *ring,
+ struct kernel_ethtool_ringparam *kernel_ering,
+ struct netlink_ext_ack *extack)
+{
+ ring->tx_max_pending = IPQESS_TX_RING_SIZE;
+ ring->rx_max_pending = IPQESS_RX_RING_SIZE;
+}
+
+static const struct ethtool_ops ipqesstool_ops = {
+ .get_drvinfo = &ipqess_get_drvinfo,
+ .get_link = &ethtool_op_get_link,
+ .get_link_ksettings = &ipqess_get_link_ksettings,
+ .set_link_ksettings = &ipqess_set_link_ksettings,
+ .get_strings = &ipqess_get_strings,
+ .get_sset_count = &ipqess_get_strset_count,
+ .get_ethtool_stats = &ipqess_get_ethtool_stats,
+ .get_ringparam = ipqess_get_ringparam,
+};
+
+void ipqess_set_ethtool_ops(struct net_device *netdev)
+{
+ netdev->ethtool_ops = &ipqesstool_ops;
+}