switches working, uploaded greatness

This commit is contained in:
mooleshacat 2026-06-21 23:20:48 -04:00
parent 530c603e91
commit 2cfa89061e
Signed by: mooleshacat
GPG Key ID: 6F42FE1A481818C2
3 changed files with 925 additions and 266 deletions

View File

@ -15,7 +15,8 @@
* What is NOT working?
*
* - 128MB nand (Micron MT29F1G08ABADA NAND 128MB // 2x kernel, 2x rootfs, 1x rootfs_data)
* - switches (QCA8075, QCA8335)
* - switches (QCA8075, QCA8337)
* - LEDs (will resolve last :P)
*
* WARNING: ENSURE BOARD.BIN AND CALDATA IS NOT COMMITTED TO GIT REPOSITORY (INTELLECTUAL PROPERTY CONCERNS)
*
@ -47,185 +48,218 @@
};
}; /* end of root block */
/* EDMA — the CPU-facing ethernet, both switches connect here */
/* ============================================================
* EDMA — dual conduit netdev for side-by-side DSA.
* eth0 (netdev) -> QCA8075 DSA tree 0
* eth1 (netdev2) -> QCA8337 DSA tree 1
* gmac2 child node is the of_node anchor for netdev2 so DSA
* can resolve 'ethernet = <&gmac2>' via of_find_net_device_by_node().
* ============================================================ */
&gmac {
status = "okay";
phy-mode = "internal";
/* dual-GMAC: RRD port-id 5 = GMAC1 = QCA8337 switch */
qcom,gmac1-port = <5>;
fixed-link {
speed = <1000>;
full-duplex;
pause;
};
}; /* end of gmac */
status = "okay";
phy-mode = "internal";
qcom,dual-switch;
fixed-link {
speed = <1000>;
full-duplex;
pause;
};
/* we are working on driver patch for second gmac for QCA8075/QCA8337 side-by-side config */
/* Anchor node for netdev2 (eth1).
* No hardware here — driver sets netdev2->dev.of_node to this node. */
gmac2: gmac2 {
/* intentionally empty */
};
};
/* TOP SWITCH: ESS built-in (QCA8075 via PSGMII) at c000000 */
/* compatible MUST be qca,ipq4019-qca8337n — this is what ran on the device */
/* ============================================================
* TOP SWITCH: QCA8075 (internal ESS, PSGMII, at 0xc000000)
* compatible = "qca,ipq4019-qca8337n" — confirmed by extracted DTS.
* CPU port points to &gmac (eth0) — DSA tree 0.
* psgmii-ethphy = ethphy@12 (reg=0x0c) — confirmed by extracted DTS.
* ============================================================ */
&switch {
status = "okay";
compatible = "qca,ipq4019-qca8337n";
status = "okay";
compatible = "qca,ipq4019-qca8337n";
mdio-bus = <&mdio>;
psgmii-ethphy = <&ethphy12>; /* reg=0x0c, the cal PHY at base+4 */
dsa,member = <0 0>;
/delete-node/ ports;
mdio-bus = <&mdio>;
psgmii-ethphy = <&ethphy12>;
ports {
#address-cells = <1>;
#size-cells = <0>;
/delete-node/ ports;
port@0 {
reg = <0>;
label = "cpu";
ethernet = <&gmac>;
phy-mode = "internal";
fixed-link {
speed = <1000>;
full-duplex;
pause;
asym-pause;
};
};
ports {
#address-cells = <1>;
#size-cells = <0>;
port@1 {
reg = <1>;
label = "wan1";
phy-handle = <&ethphy8>;
phy-mode = "psgmii";
status = "okay";
};
port@0 {
reg = <0>;
label = "cpu";
ethernet = <&gmac>;
phy-mode = "internal";
fixed-link {
speed = <1000>;
full-duplex;
pause;
asym-pause;
};
};
port@2 {
reg = <2>;
label = "wan2";
phy-handle = <&ethphy9>;
phy-mode = "psgmii";
status = "okay";
};
port@1 {
reg = <1>;
label = "wan2";
phy-handle = <&ethphy8>;
phy-mode = "psgmii";
};
port@3 {
reg = <3>;
label = "lan1";
phy-handle = <&ethphy10>;
phy-mode = "psgmii";
status = "okay";
};
port@2 {
reg = <2>;
label = "wan1";
phy-handle = <&ethphy9>;
phy-mode = "psgmii";
};
port@4 {
reg = <4>;
label = "lan2";
phy-handle = <&ethphy11>;
phy-mode = "psgmii";
status = "okay";
};
port@3 {
reg = <3>;
label = "lan7";
phy-handle = <&ethphy10>;
phy-mode = "psgmii";
};
port@5 {
reg = <5>;
label = "lan3";
phy-handle = <&ethphy12>; /* same as psgmii-ethphy — cal PHY doubles as port@5 */
phy-mode = "psgmii";
status = "okay";
};
};
}; /* end of switch (QCA8075) */
port@4 {
reg = <4>;
label = "lan8";
phy-handle = <&ethphy11>;
phy-mode = "psgmii";
};
/* MDIO bus — contains QCA8075 package AND QCA8337 bottom switch */
port@5 {
reg = <5>;
label = "lan2";
phy-handle = <&ethphy12>;
phy-mode = "psgmii";
};
/* port@6 REMOVED — qca8k num_ports=6 (indices 0-5 only).
* eth0/eth1 split is handled entirely in ipqess driver
* software via netdev2/gmac2 anchor. No DTS port needed. */
};
}; /* end of &switch */
/* ============================================================
* MDIO bus — QCA8075 PHY package + QCA8337 external switch.
* gpio52 = MDC, gpio53 = MDIO — confirmed by extracted DTS.
* NOTE: gpio53 is also used by NAND (qpic). NAND is disabled.
* Do NOT enable both simultaneously.
* ============================================================ */
&mdio {
status = "okay";
pinctrl-0 = <&mdio_pins>;
pinctrl-names = "default";
status = "okay";
pinctrl-0 = <&mdio_pins>;
pinctrl-names = "default";
/delete-node/ ethernet-phy-package@0;
/delete-node/ ethernet-phy-package@0;
/* QCA8075: 5-port PHY package, MDIO base addr 8 */
ethernet-phy-package@8 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "qcom,qca8075-package";
reg = <8>;
qcom,tx-drive-strength-milliwatt = <300>;
/* QCA8075: 5-port PSGMII PHY package.
* MDIO base address 8 — confirmed by extracted DTS.
* ethphy12 doubles as psgmii-ethphy (calibration PHY). */
ethernet-phy-package@8 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "qcom,qca8075-package";
reg = <8>;
qcom,tx-drive-strength-milliwatt = <300>;
ethphy8: ethernet-phy@8 { reg = <8>; }; /* WAN1 */
ethphy9: ethernet-phy@9 { reg = <9>; }; /* WAN2 */
ethphy10: ethernet-phy@10 { reg = <10>; }; /* LAN1 */
ethphy11: ethernet-phy@11 { reg = <11>; }; /* LAN2 */
ethphy12: ethernet-phy@12 { reg = <12>; }; /* LAN3 / psgmii cal PHY */
};
ethphy8: ethernet-phy@8 { reg = <8>; }; /* WAN1 — switch port@1 */
ethphy9: ethernet-phy@9 { reg = <9>; }; /* WAN2 — switch port@2 */
ethphy10: ethernet-phy@10 { reg = <10>; }; /* LAN1 — switch port@3 */
ethphy11: ethernet-phy@11 { reg = <11>; }; /* LAN2 — switch port@4 */
ethphy12: ethernet-phy@12 { reg = <12>; }; /* LAN3 — switch port@5 / psgmii cal PHY */
};
/* BOTTOM SWITCH: QCA8337 — side by side, own CPU port pointing to same EDMA */
qca8337: switch@10 {
compatible = "qca,qca8337";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x10>;
/* ============================================================
* BOTTOM SWITCH: QCA8337 (external, RGMII, at MDIO addr 0x10)
* compatible = "qca,qca8337" — upstream qca8k.c match string.
* CPU port: phy-mode=rgmii, ethernet=<&gmac2> -> eth1 (netdev2).
* dsa,member = <1 0> — independent DSA tree 1.
* Internal PHY addresses 0-4 confirmed by extracted DTS.
* ============================================================ */
qca8337: switch@10 {
compatible = "qca,qca8337";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x10>;
ports {
#address-cells = <1>;
#size-cells = <0>;
dsa,member = <1 0>;
port@0 {
reg = <0>;
label = "cpu";
ethernet = <&gmac>; /* side-by-side: ALSO points to EDMA, not to swport5 */
phy-mode = "rgmii";
fixed-link {
speed = <1000>;
full-duplex;
};
};
ports {
#address-cells = <1>;
#size-cells = <0>;
port@1 {
reg = <1>;
label = "lan4";
phy-mode = "internal";
phy-handle = <&qca8337_phy0>;
};
port@0 {
reg = <0>;
label = "cpu";
ethernet = <&gmac2>;
phy-mode = "rgmii";
fixed-link {
speed = <1000>;
full-duplex;
};
};
port@2 {
reg = <2>;
label = "lan5";
phy-mode = "internal";
phy-handle = <&qca8337_phy1>;
port@1 {
reg = <1>;
label = "lan1";
phy-mode = "internal";
phy-handle = <&qca8337_phy0>;
};
};
port@3 {
reg = <3>;
label = "lan6";
phy-mode = "internal";
phy-handle = <&qca8337_phy2>;
};
port@2 {
reg = <2>;
label = "lan4";
phy-mode = "internal";
phy-handle = <&qca8337_phy1>;
};
port@4 {
reg = <4>;
label = "lan7";
phy-mode = "internal";
phy-handle = <&qca8337_phy3>;
};
port@3 {
reg = <3>;
label = "lan3";
phy-mode = "internal";
phy-handle = <&qca8337_phy2>;
};
port@5 {
reg = <5>;
label = "lan8";
phy-mode = "internal";
phy-handle = <&qca8337_phy4>;
};
};
port@4 {
reg = <4>;
label = "lan6";
phy-mode = "internal";
phy-handle = <&qca8337_phy3>;
};
/* QCA8337 internal MDIO bus — local PHY addresses 0-4 */
mdio {
#address-cells = <1>;
#size-cells = <0>;
port@5 {
reg = <5>;
label = "lan5";
phy-mode = "internal";
phy-handle = <&qca8337_phy4>;
};
};
/* QCA8337 internal MDIO bus.
* Local PHY addresses 0-4 confirmed by extracted DTS. */
mdio {
#address-cells = <1>;
#size-cells = <0>;
qca8337_phy0: ethernet-phy@0 { reg = <0>; };
qca8337_phy1: ethernet-phy@1 { reg = <1>; };
qca8337_phy2: ethernet-phy@2 { reg = <2>; };
qca8337_phy3: ethernet-phy@3 { reg = <3>; };
qca8337_phy4: ethernet-phy@4 { reg = <4>; };
};
};
}; /* end of &mdio (QCA8075/QCA8337) */
qca8337_phy0: ethernet-phy@0 { reg = <0>; }; /* lan4 */
qca8337_phy1: ethernet-phy@1 { reg = <1>; }; /* lan5 */
qca8337_phy2: ethernet-phy@2 { reg = <2>; }; /* lan6 */
qca8337_phy3: ethernet-phy@3 { reg = <3>; }; /* lan7 */
qca8337_phy4: ethernet-phy@4 { reg = <4>; }; /* lan8 */
};
};
}; /* end of &mdio */
/* 8MB FLASH CHIP (Macronix MX25L6433F SPI NOR 8MB) */
&blsp1_spi1 {
@ -311,40 +345,40 @@
/* start of &nand (Micron MT29F1G08ABADA NAND 128MB) */
&nand {
status = "disabled"; /* DISABLED WHILE FIGURING OUT SWITCH CONFIG */
status = "disabled"; /* DISABLED WHILE FIGURING OUT SWITCH CONFIG */
/delete-property/ pinctrl-0;
/delete-property/ pinctrl-names;
/* REMOVED PINCTRL - NO CONFLICT PLS */
/* REMOVED PINCTRL - NO CONFLICT PLS */
nand@0 {
reg = <0>;
nand@0 {
reg = <0>;
nand-ecc-strength = <4>;
nand-ecc-step-size = <512>;
nand-bus-width = <8>;
nand-ecc-strength = <4>;
nand-ecc-step-size = <512>;
nand-bus-width = <8>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
rootfs_part: partition@0 {
label = "rootfs";
reg = <0x00000000 0x03000000>; /* 48MB */
};
rootfs_part: partition@0 {
label = "rootfs";
reg = <0x00000000 0x03000000>; /* 48MB */
};
rootfs_1_part: partition@3000000 {
label = "rootfs_1";
reg = <0x03000000 0x03000000>; /* 48MB */
};
rootfs_1_part: partition@3000000 {
label = "rootfs_1";
reg = <0x03000000 0x03000000>; /* 48MB */
};
overlay_part: partition@6000000 {
label = "overlay";
reg = <0x06000000 0x02000000>; /* 32MB */
};
};
};
overlay_part: partition@6000000 {
label = "overlay";
reg = <0x06000000 0x02000000>; /* 32MB */
};
};
};
};
/* end of &nand (Micron MT29F1G08ABADA NAND 128MB) */
@ -387,20 +421,20 @@
};
mdio_pins: mdio_pinmux {
pinmux_1 {
pins = "gpio53";
function = "mdio";
};
pinmux_2 {
pins = "gpio52";
function = "mdc";
};
pinconf {
pins = "gpio52", "gpio53";
bias-pull-up;
};
};
mdio_pins: mdio_pinmux {
pinmux_1 {
pins = "gpio53";
function = "mdio";
};
pinmux_2 {
pins = "gpio52";
function = "mdc";
};
pinconf {
pins = "gpio52", "gpio53";
bias-pull-up;
};
};
/* pinmux / gpios */
@ -419,9 +453,6 @@
};
led0_pins: led0_pinmux {
linux,phandle = <0x52>;
phandle = <0x52>;
mux_1 {
pins = "gpio36";
function = "led0";
@ -436,20 +467,14 @@
};
uart1_pins: uart1_pinmux {
linux,phandle = <0x56>;
phandle = <0x56>;
mux {
pins = "gpio8", "gpio9", gpio10", "gpio11";
pins = "gpio8", "gpio9", "gpio10", "gpio11";
function = "blsp_uart1";
bias-disable;
};
};
spi_0_pinmux: spi_0_pinmux {
linux,phandle = <0x4a>;
phandle = <0x4a>;
mux {
pins = "gpio12", "gpio13", "gpio14", "gpio15";
function = "blsp_spi0";
@ -458,9 +483,6 @@
};
i2c_0_pinmux: i2c_0_pinmux {
linux,phandle = <0x4f>;
phandle = <0x4f>;
mux {
pins = "gpio20", "gpio21";
function = "blsp_i2c0";

View File

@ -1,62 +0,0 @@
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess.h
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess.h
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess.h
@@ -162,7 +162,8 @@ struct ipqess_rx_ring {
struct ipqess {
- struct net_device *netdev;
+ struct net_device *netdev;
+ int gmac_id;
void __iomem *hw_addr;
struct clk *ess_clk;
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess.c
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess.c
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess.c
@@ -101,7 +101,7 @@ static int ipqess_tx_ring_alloc(struct ipqess *ess)
tx_ring->ess = ess;
tx_ring->ring_id = i;
tx_ring->idx = i * 4;
tx_ring->count = IPQESS_TX_RING_SIZE;
- tx_ring->nq = netdev_get_tx_queue(ess->netdev, i);
+ tx_ring->nq = netdev_get_tx_queue(ess->netdev[ess->gmac_id], i);
size = sizeof(struct ipqess_buf) * IPQESS_TX_RING_SIZE;
tx_ring->buf = devm_kzalloc(dev, size, GFP_KERNEL);
@@ -474,7 +474,7 @@ static void ipqess_tx_complete(struct ipqess_tx_ring *tx_ring)
if (netif_tx_queue_stopped(tx_ring->nq)) {
- netdev_dbg(tx_ring->ess->netdev, "waking up tx queue %d\n",
+ netdev_dbg(tx_ring->ess->netdev[tx_ring->ess->gmac_id], "waking up tx queue %d\n",
tx_ring->idx);
netif_tx_wake_queue(tx_ring->nq);
}
@@ -968,7 +968,10 @@ static int ipqess_netdevice_event(struct notifier_block *nb,
struct ipqess *ess = container_of(nb, struct ipqess, netdev_notifier);
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct netdev_notifier_changeupper_info *info;
+ int i;
- if (dev != ess->netdev)
+ for (i = 0; i < 2; i++)
+ if (dev == ess->netdev[i])
+ break;
+ if (i == 2)
return NOTIFY_DONE;
switch (event) {
@@ -1180,8 +1180,9 @@ static int ipqess_axi_probe(struct platform_device *pdev)
ess = netdev_priv(netdev);
- ess->netdev = netdev;
+ ess->netdev = netdev;
+ ess->gmac_id = 0;
ess->pdev = pdev;
spin_lock_init(&ess->stats_lock);
SET_NETDEV_DEV(netdev, &pdev->dev);
@@ -1301,7 +1301,7 @@ static void ipqess_axi_remove(struct platform_device *pdev)
const struct net_device *netdev = platform_get_drvdata(pdev);
struct ipqess *ess = netdev_priv(netdev);
- unregister_netdev(ess->netdev);
+ unregister_netdev(ess->netdev[ess->gmac_id]);
ipqess_hw_stop(ess);
ipqess_tx_ring_free(ess);

View File

@ -0,0 +1,699 @@
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);
@@ -393,8 +407,20 @@ static int ipqess_rx_poll(struct ipqess_
skb_put(skb, length);
}
- skb->dev = rx_ring->ess->netdev;
- skb->protocol = eth_type_trans(skb, rx_ring->ess->netdev);
+ {
+ struct net_device *rxdev;
+ u16 port_id = (le16_to_cpu(rd->rrd1) >> IPQESS_RRD_PORT_TYPE_SHIFT)
+ & IPQESS_RRD_PORT_TYPE_MASK;
+
+ if (rx_ring->ess->netdev2 &&
+ port_id == IPQESS_ESS_PORT_QCA8337)
+ rxdev = rx_ring->ess->netdev2;
+ else
+ rxdev = rx_ring->ess->netdev;
+
+ skb->dev = rxdev;
+ skb->protocol = eth_type_trans(skb, rxdev);
+ }
skb_record_rx_queue(skb, rx_ring->ring_id);
if (rd->rrd6 & cpu_to_le16(IPQESS_RRD_CSUM_FAIL_MASK))
@@ -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,61 @@ 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");
+
+ netdev2->netdev_ops = &ipqess_axi_netdev_ops;
+ 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 +1431,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 +1472,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;
+}