[PATCH net-next 2/2] net: econet: add EcoNet EN751221 ethernet driver
Caleb James DeLisle
cjd at cjdns.fr
Wed Sep 9 14:21:46 PDT 2026
The EcoNet ethernet driver is somewhat similar to both the Airoha
and the MediaTek drivers, but still unique enough that attempts at
code reuse did not prove fruitful.
The basic structure is two QDMA (QoS + DMA) modules, two GDM modules
representing the two ports, a PPE (packet processing engine) similar
to that in the MediaTek device, and a "Frame Engine" module which
coordinates the others.
Add EN751221 eth support via relatively loosely coupled files:
gdm_regs.h register layout of the GDM module
qdma_desc.h packet structure for the packet descriptor used in the
DMA ring buffer.
qdma_regs.h register layout of the QDMA module
econet_qdma.c QDMA module controller
econet_port.c GDM module controller and internally facing net_device
econet_eth.c main coordinator
Because the CPU cannot send or receive packets without using a QDMA
subsystem, the econet_port.c object is passed an econet_qdma.c
object on creation and egress traffic is passed to the QDMA. Ingress
traffic arrives at the QDMA which calls en75_rx_before_recv() on
econet_eth.c to set the skb->dev to the correct econet_port.c before
receiving the skb to the kernel.
PPE is not supported at this time.
Signed-off-by: Caleb James DeLisle <cjd at cjdns.fr>
---
MAINTAINERS | 1 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/econet/Kconfig | 20 +
drivers/net/ethernet/econet/Makefile | 7 +
drivers/net/ethernet/econet/econet_eth.c | 334 ++++
drivers/net/ethernet/econet/econet_eth.h | 231 +++
.../net/ethernet/econet/econet_eth_debug.c | 617 +++++++
drivers/net/ethernet/econet/econet_port.c | 473 +++++
drivers/net/ethernet/econet/econet_qdma.c | 1209 +++++++++++++
drivers/net/ethernet/econet/gdm_regs.h | 1514 +++++++++++++++++
drivers/net/ethernet/econet/qdma_desc.h | 677 ++++++++
drivers/net/ethernet/econet/qdma_regs.h | 1243 ++++++++++++++
13 files changed, 6328 insertions(+)
create mode 100644 drivers/net/ethernet/econet/Kconfig
create mode 100644 drivers/net/ethernet/econet/Makefile
create mode 100644 drivers/net/ethernet/econet/econet_eth.c
create mode 100644 drivers/net/ethernet/econet/econet_eth.h
create mode 100644 drivers/net/ethernet/econet/econet_eth_debug.c
create mode 100644 drivers/net/ethernet/econet/econet_port.c
create mode 100644 drivers/net/ethernet/econet/econet_qdma.c
create mode 100644 drivers/net/ethernet/econet/gdm_regs.h
create mode 100644 drivers/net/ethernet/econet/qdma_desc.h
create mode 100644 drivers/net/ethernet/econet/qdma_regs.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 7417ca4ccc79..5758cd83b1f4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9335,6 +9335,7 @@ M: Caleb James DeLisle <cjd at cjdns.fr>
L: netdev at vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/net/econet,en751221-eth.yaml
+F: drivers/net/ethernet/econet/
ECONET PCIE PHY DRIVER
M: Caleb James DeLisle <cjd at cjdns.fr>
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index 8581ccba1505..0c49006c1c0f 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -57,6 +57,7 @@ source "drivers/net/ethernet/cortina/Kconfig"
source "drivers/net/ethernet/davicom/Kconfig"
source "drivers/net/ethernet/dec/Kconfig"
source "drivers/net/ethernet/dlink/Kconfig"
+source "drivers/net/ethernet/econet/Kconfig"
source "drivers/net/ethernet/emulex/Kconfig"
source "drivers/net/ethernet/engleder/Kconfig"
source "drivers/net/ethernet/ezchip/Kconfig"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index 2b1153d35b52..f7b685f0d8b0 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_CX_ECAT) += ec_bhf.o
obj-$(CONFIG_DM9000) += davicom/
obj-$(CONFIG_NET_VENDOR_DEC) += dec/
obj-$(CONFIG_NET_VENDOR_DLINK) += dlink/
+obj-$(CONFIG_NET_VENDOR_ECONET) += econet/
obj-$(CONFIG_NET_VENDOR_EMULEX) += emulex/
obj-$(CONFIG_NET_VENDOR_ENGLEDER) += engleder/
obj-$(CONFIG_NET_VENDOR_EZCHIP) += ezchip/
diff --git a/drivers/net/ethernet/econet/Kconfig b/drivers/net/ethernet/econet/Kconfig
new file mode 100644
index 000000000000..ea1704f12c4e
--- /dev/null
+++ b/drivers/net/ethernet/econet/Kconfig
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config NET_VENDOR_ECONET
+ bool "EcoNet devices"
+ depends on ECONET || COMPILE_TEST
+ help
+ If you have an EcoNet SoC with ethernet, say Y.
+
+if NET_VENDOR_ECONET
+
+config NET_ECONET
+ tristate "EcoNet SoC Gigabit Ethernet support"
+ depends on NET_DSA || !NET_DSA
+ select PAGE_POOL
+ help
+ This driver supports the gigabit ethernet MAC in the
+ EcoNet EN751221 SoC. This device has two ports, two
+ QoS + DMA ("QDMA") subsystems, and a hardware
+ flow/NAT packet processing engine ("PPE").
+
+endif #NET_VENDOR_ECONET
diff --git a/drivers/net/ethernet/econet/Makefile b/drivers/net/ethernet/econet/Makefile
new file mode 100644
index 000000000000..608db8c1d8ce
--- /dev/null
+++ b/drivers/net/ethernet/econet/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# EcoNet SoC Ethernet MACs
+#
+
+obj-$(CONFIG_NET_ECONET) += econet-eth.o
+econet-eth-y := econet_eth.o econet_eth_debug.o econet_qdma.o econet_port.o
diff --git a/drivers/net/ethernet/econet/econet_eth.c b/drivers/net/ethernet/econet/econet_eth.c
new file mode 100644
index 000000000000..3c606c65d813
--- /dev/null
+++ b/drivers/net/ethernet/econet/econet_eth.c
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * EcoNet EN751221 Ethernet Driver
+ *
+ * This Ethernet system consists of 2 QDMA engines and 2 GDM ports.
+ *
+ * The QDMA engine manages DMA and also QoS prioritization, taking advantage of
+ * the fact that the packet is in memory and can therefore be sent at the
+ * appropriate time.
+ *
+ * The GDM port is the outward-facing port of the Ethernet device and it
+ * corrisponds to the net_device that are registered with the kernel.
+ *
+ * The QDMAs are *loosely* coupled with the GDM ports, it is possible to send
+ * a packet to GDM2 by registering it with QDMA1 or vice versa. On the receive
+ * side, the GDM is configured which QDMA to send various types of packets to.
+ * Practically speaking, it is sensible to link GDM1 with QDMA1 and GDM2 with
+ * QDMA2.
+ *
+ * Since QDMA engines are responsible for receiving traffic, the QDMA calls
+ * en75_rx_before_recv() before calling napi_gro_receive(), this gives the main
+ * driver an opportunity to assign the skb a net_device based on which port is
+ * indicated in the DMA packet descriptor. Therefore the QDMA driver
+ * (econet_qdma.c) has no awareness of the GDM driver (econet_port.c).
+ *
+ * When sending, the GDM driver must in fact register the packet with a QDMA
+ * engine. The main driver (this file) passes an opaque QDMA driver struct to
+ * the GDM driver which then calls en75_qdma_xmit() when it has a packet to
+ * send. In order to avoid race conditions during driver teardown, the GDM
+ * calls en75_qdma_use() and only calls en75_qdma_unuse() when the GDM driver
+ * itself is tearing down. The QDMA will not teardown until it no longer has
+ * any users. Any number of GDMs could share a QDMA without problem.
+ */
+#include <linux/skbuff.h>
+#include <linux/etherdevice.h>
+#include <net/dsa.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/of_net.h>
+
+#include "gdm_regs.h"
+#include "linux/dev_printk.h"
+#include "linux/ioport.h"
+#include "linux/mdio.h"
+#include "linux/netdevice.h"
+#include "qdma_desc.h"
+#include "qdma_regs.h"
+#include "econet_eth.h"
+
+struct en75_eth_pvt {
+ struct en75_eth pub;
+ const struct en75_soc_data *soc;
+ struct net_device *ports[EN75_NUM_GDM_PORTS];
+ struct en751221_regs __iomem *regs;
+ struct reset_control *reset;
+ int qdma_irq[EN75_NUM_QDMA * QDMA_NUM_IRQS];
+ struct en75_qdma *qdma[EN75_NUM_QDMA];
+ struct en75_debug *debug;
+};
+
+union gdm_regs {
+ struct gdm regs;
+ u32 words[0x800 / sizeof(u32)];
+};
+
+struct en751221_regs {
+ /* BFB50000 - BFB50400 */
+ u32 fe[0x400 / sizeof(u32)];
+
+ /* BFB50400 - BFB50C00 */
+ union gdm_regs port0;
+
+ /* BFB50C00 - BFB51000 */
+ u32 ppe[0x400 / sizeof(u32)];
+
+ /* BFB51000 - BFB51400 */
+ u32 unknown_deadbeef[0x400 / sizeof(u32)];
+
+ /* BFB51400 - BFB51C00 */
+ union gdm_regs port1;
+
+ /* BFB51C00 - BFB52000 */
+ u32 unknown_deadbeef2[0x400 / sizeof(u32)];
+
+ /* BFB52000 - BFB52400 */
+ u32 ppe_accounting[0x400 / sizeof(u32)];
+
+ /* BFB52400 - BFB54000 */
+ u32 ppe_unused[0x1c00 / sizeof(u32)];
+
+ /* BFB54000 - BFB56000 */
+ struct qregs qdma_regs[EN75_NUM_QDMA];
+
+ /* BFB56000 - BFB58000 */
+ u32 unknown_zeroed[0x2000 / sizeof(u32)];
+};
+
+static struct net_device *en75_get_sport_dev(struct en75_eth_pvt *eth,
+ enum etx_fport sport)
+{
+ if (sport == ETX_FPORT_GDM2)
+ return eth->ports[1];
+
+ if (sport != ETX_FPORT_GDM1)
+ dev_info(eth->pub.dev, "rx: on unexpected fport %d\n", sport);
+
+ return eth->ports[0];
+}
+
+int en75_rx_before_recv(struct en75_eth *eth, struct sk_buff *skb,
+ enum etx_fport sport)
+{
+ struct en75_eth_pvt *ep = (struct en75_eth_pvt *)eth;
+ struct net_device *port;
+
+ port = en75_get_sport_dev(ep, sport);
+
+ skb->dev = port;
+ skb->protocol = eth_type_trans(skb, port);
+
+ return 0;
+}
+
+static int en75_init_port(struct en75_eth_pvt *eth, struct device_node *np)
+{
+ const __be32 *id_ptr = of_get_property(np, "reg", NULL);
+ struct net_device *dev;
+ u32 id;
+
+ if (!id_ptr) {
+ dev_err(eth->pub.dev, "missing gdm port id\n");
+ return -EINVAL;
+ }
+
+ id = be32_to_cpup(id_ptr) + 1;
+
+ if (!id || id > ARRAY_SIZE(eth->ports)) {
+ dev_err(eth->pub.dev, "invalid gdm port id: %d\n", id);
+ return -EINVAL;
+ }
+
+ if (eth->ports[id - 1]) {
+ dev_err(eth->pub.dev, "duplicate gdm port id: %d\n", id);
+ return -EINVAL;
+ }
+
+ if (id == 1)
+ dev = en75_alloc_gdm_port(ð->pub, np,
+ ð->regs->port0.regs,
+ eth->qdma[0],
+ ETX_FPORT_GDM1,
+ false);
+ else if (id == 2)
+ dev = en75_alloc_gdm_port(ð->pub, np,
+ ð->regs->port0.regs,
+ eth->qdma[1],
+ ETX_FPORT_GDM2,
+ true);
+ else
+ return -EINVAL;
+
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+
+ eth->ports[id - 1] = dev;
+ return 0;
+}
+
+static void en75_prepare_qdma_cfg(struct en75_qdma_cfg *cfg,
+ const struct en75_soc_data *soc)
+{
+ memset(cfg, 0, sizeof(*cfg));
+ for (int i = 0; i < QDMA_NUM_CHAINS; i++)
+ cfg->num_rx_descs[i] = 128;
+ for (int i = 0; i < QDMA_NUM_CHAINS; i++)
+ cfg->num_tx_descs[i] = 128;
+ for (int i = 0; i < QDMA_NUM_TX_DONE; i++)
+ cfg->done_list_size[i] = 256;
+ cfg->fwd_max_packet_size = EN75_MAX_PACKET_SIZE;
+ cfg->fwd_low_threshold = 32;
+ cfg->num_fwd_descs = 256;
+ cfg->soc = soc;
+}
+
+static void en75_remove(struct platform_device *pdev)
+{
+ struct en75_eth_pvt *eth = platform_get_drvdata(pdev);
+ int i;
+
+ if (!eth)
+ return;
+
+ en75_debugfs_exit(eth->debug);
+
+ for (i = 0; i < ARRAY_SIZE(eth->qdma); i++)
+ if (eth->qdma[i])
+ en75_qdma_destroy(eth->qdma[i]);
+
+ for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
+ if (!eth->ports[i])
+ continue;
+
+ unregister_netdev(eth->ports[i]);
+ }
+
+ platform_set_drvdata(pdev, NULL);
+}
+
+static int en75_probe(struct platform_device *pdev)
+{
+ struct en75_debug_conf debug_conf = {0};
+ struct en751221_regs __iomem *regs;
+ struct resource *regs_res;
+ struct en75_eth_pvt *eth;
+ struct en75_qdma_cfg cfg;
+ struct device_node *np;
+ int i, err, irq;
+
+ eth = devm_kzalloc(&pdev->dev, sizeof(*eth), GFP_KERNEL);
+ if (!eth)
+ return -ENOMEM;
+
+ eth->soc = of_device_get_match_data(&pdev->dev);
+ if (!eth->soc)
+ return dev_err_probe(&pdev->dev, -EINVAL, "No matching SoC data\n");
+
+ eth->pub.dev = &pdev->dev;
+ platform_set_drvdata(pdev, eth);
+
+ err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+ if (err)
+ return dev_err_probe(&pdev->dev, err,
+ "failed setting DMA mask\n");
+
+ regs = devm_platform_get_and_ioremap_resource(pdev, 0, ®s_res);
+ if (IS_ERR(regs))
+ return dev_err_probe(&pdev->dev, PTR_ERR(regs),
+ "failed to map registers\n");
+
+ if (resource_size(regs_res) < sizeof(struct en751221_regs))
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "insufficient register space\n");
+
+ eth->regs = regs;
+
+ eth->reset = devm_reset_control_array_get_exclusive(&pdev->dev);
+ if (IS_ERR(eth->reset)) {
+ dev_warn(&pdev->dev, "failed to get resets %pe\n", eth->reset);
+ } else {
+ err = reset_control_assert(eth->reset);
+ if (err)
+ return err;
+
+ msleep(20);
+ err = reset_control_deassert(eth->reset);
+ if (err)
+ return err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(eth->qdma_irq); i++) {
+ irq = platform_get_irq(pdev, i);
+ if (irq < 0)
+ return dev_err_probe(&pdev->dev, irq,
+ "failed to get IRQ %d\n", i);
+ eth->qdma_irq[i] = irq;
+ }
+
+ BUILD_BUG_ON(ARRAY_SIZE(eth->qdma) != ARRAY_SIZE(regs->qdma_regs));
+ BUILD_BUG_ON(ARRAY_SIZE(eth->qdma) != ARRAY_SIZE(eth->qdma_irq) * QDMA_NUM_IRQS);
+ for (i = 0; i < ARRAY_SIZE(eth->qdma); i++) {
+ en75_prepare_qdma_cfg(&cfg, eth->soc);
+ eth->qdma[i] = en75_qdma_new(ð->pub, ®s->qdma_regs[i], i,
+ ð->qdma_irq[i * QDMA_NUM_IRQS],
+ QDMA_NUM_IRQS, &cfg,
+ &debug_conf.qdma[i]);
+
+ if (IS_ERR(eth->qdma[i])) {
+ err = PTR_ERR(eth->qdma[i]);
+ eth->qdma[i] = NULL;
+ goto error;
+ }
+ }
+
+ for_each_child_of_node(pdev->dev.of_node, np) {
+ if (!of_device_is_compatible(np, "econet,eth-mac"))
+ continue;
+
+ if (!of_device_is_available(np))
+ continue;
+
+ err = en75_init_port(eth, np);
+ if (err) {
+ of_node_put(np);
+ goto error;
+ }
+ }
+
+ eth->debug = en75_debugfs_init(&debug_conf);
+
+ return 0;
+
+error:
+ en75_remove(pdev);
+ return err;
+}
+
+static const struct en75_soc_data en751221_soc_data = {
+ .dscp_byte_swap = true,
+};
+
+static const struct en75_soc_data en7528_soc_data = {
+ .dscp_byte_swap = false,
+};
+
+static const struct of_device_id of_en75_match[] = {
+ { .compatible = "econet,en751221-eth", .data = &en751221_soc_data },
+ { .compatible = "econet,en7528-eth", .data = &en7528_soc_data },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, of_en75_match);
+
+static struct platform_driver en75_driver = {
+ .probe = en75_probe,
+ .remove = en75_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = of_en75_match,
+ },
+};
+module_platform_driver(en75_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Caleb James DeLisle <cjd at cjdns.fr>");
+MODULE_DESCRIPTION("Ethernet driver for EcoNet SoC");
diff --git a/drivers/net/ethernet/econet/econet_eth.h b/drivers/net/ethernet/econet/econet_eth.h
new file mode 100644
index 000000000000..ecb3f14a06d5
--- /dev/null
+++ b/drivers/net/ethernet/econet/econet_eth.h
@@ -0,0 +1,231 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef ECONET_ETH_H
+#define ECONET_ETH_H
+
+#include <linux/types.h>
+#include <linux/skbuff.h>
+#include <linux/platform_device.h>
+#include <linux/netdevice.h>
+
+#include "asm-generic/int-ll64.h"
+#include "qdma_desc.h"
+#include "qdma_regs.h"
+#include "gdm_regs.h"
+
+struct en75_debug;
+
+struct en75_soc_data {
+ bool dscp_byte_swap;
+};
+
+/* Number of QDMA engines in this ethernet device. */
+#define EN75_NUM_QDMA 2
+
+/* Number of GDM ports. */
+#define EN75_NUM_GDM_PORTS 2
+
+/*
+ * The hardware MTU limit is 16128 (see: rx_len_threshold.oversize_len).
+ * This limit is set to save memory in the page pool. In the future we
+ * may restart the QDMA engines on MTU change which crosses a page size
+ * boundary.
+ */
+#define EN75_MAX_PACKET_SIZE 2048
+
+/*
+ * Internally these are properties of the QDMA engine, but they are important
+ * to the GDM port driver because they define how QoS can be done.
+ */
+#define EN75_NUM_QUEUES 8
+#define EN75_NUM_CHANNELS 32
+
+/*
+ * Number of queues reported to the kernel.
+ * Currently every chan/queue is made available.
+ */
+#define EN75_NUM_SOFT_QUEUES (EN75_NUM_CHANNELS * EN75_NUM_QUEUES)
+
+/* A chain is 1 TX ring + 1 RX ring, each QDMA has 2 chains. */
+#define QDMA_NUM_CHAINS 2
+/* Each QDMA has one queue of TX-complete notifications. */
+#define QDMA_NUM_TX_DONE 1
+/* Each QDMA has only 1 IRQ and 1 mask + 1 status register (on EN751221). */
+#define QDMA_NUM_IRQS 1
+#define QDMA_REGS_PER_IRQ 1
+
+union en75_irq_purpose {
+ struct {
+ enum en75_irq_purpose_type {
+ IPS_INVAL = 0,
+ IPS_DONE,
+ IPS_LOW_DSCP,
+ IPS_NO_DSCP,
+
+ IPS_OVERFLOW,
+ IPS_ERR_COHERENT,
+ IPS_GPON_INT,
+ IPS_EPON_INT,
+ IPS_XPON_INT,
+ } type: 16;
+
+ /* If source is RX, TX, or DONE then chain is the number of the queue */
+ int chain: 8;
+
+ enum en75_irq_purpose_source {
+ IPSC_RX = 1,
+ IPSC_TX,
+ IPSC_DONE,
+ IPSC_FWD,
+ IPSC_UNSPEC,
+ } source: 8;
+ };
+ u32 word;
+};
+
+static inline char *en75_irq_purpose_type_str(enum en75_irq_purpose_type t)
+{
+ switch (t) {
+ case IPS_DONE:
+ return "DONE";
+ case IPS_LOW_DSCP:
+ return "LOW_DSCP";
+ case IPS_NO_DSCP:
+ return "NO_DSCP";
+ case IPS_OVERFLOW:
+ return "OVERFLOW";
+ case IPS_ERR_COHERENT:
+ return "ERR_COHERENT";
+ case IPS_GPON_INT:
+ return "GPON_INT";
+ case IPS_EPON_INT:
+ return "EPON_INT";
+ case IPS_XPON_INT:
+ return "XPON_INT";
+ case IPS_INVAL:
+ default:
+ return "INVAL";
+ }
+}
+
+static inline char *en75_irq_purpose_source_str(enum en75_irq_purpose_source s)
+{
+ switch (s) {
+ case IPSC_RX:
+ return "RX";
+ case IPSC_TX:
+ return "TX";
+ case IPSC_DONE:
+ return "DONE";
+ case IPSC_FWD:
+ return "FWD";
+ case IPSC_UNSPEC:
+ return "UNSPEC";
+ default:
+ return "INVAL";
+ }
+}
+
+/* Set by QDMA for each chain (RX+TX) */
+struct en75_debug_qdma_chain_conf {
+ struct desc *rx_descs;
+ int rx_count;
+ struct desc *tx_descs;
+ int tx_count;
+
+ /* Must be EN75_NUM_QUEUES * EN75_NUM_QUEUES length */
+ u32 *tx_per_ch_q;
+};
+
+/* Set by QDMA for each IRQ (actual interrupt number) */
+struct en75_debug_qdma_irq_conf {
+ union en75_irq_purpose desc[QDMA_REGS_PER_IRQ * 32];
+
+ /* Must match length of desc. */
+ u32 *counters;
+};
+
+struct en75_debug_qdma_conf {
+ struct qregs __iomem *regs;
+ struct fwdesc *hwf_desc;
+ struct en75_debug_qdma_chain_conf chains[QDMA_NUM_CHAINS];
+ struct en75_debug_qdma_irq_conf irqs[QDMA_NUM_IRQS];
+};
+
+struct en75_debug_conf {
+ struct en75_debug_qdma_conf qdma[EN75_NUM_QDMA];
+};
+
+struct en75_debug *en75_debugfs_init(struct en75_debug_conf *config);
+void en75_debugfs_exit(struct en75_debug *debug);
+
+enum en75_fport {
+ DPORT_CPU = 0,
+ DPORT_GDMA1 = 1,
+ DPORT_GDMA2 = 2,
+ DPORT_UNKNOWN_3 = 3,
+ DPORT_PPE = 4,
+ DPORT_QDMA = 5,
+ DPORT_QDMA_HW = 6,
+ DPORT_DISCARD = 7,
+};
+
+struct en75_eth {
+ struct device *dev;
+};
+
+/* Called in softirq context */
+int en75_rx_before_recv(struct en75_eth *eth, struct sk_buff *skb,
+ enum etx_fport sport);
+
+struct en75_qdma_cfg {
+ int num_rx_descs[QDMA_NUM_CHAINS];
+ int num_tx_descs[QDMA_NUM_CHAINS];
+ int done_list_size[QDMA_NUM_TX_DONE];
+ int num_fwd_descs;
+ int fwd_max_packet_size;
+ int fwd_low_threshold;
+ const struct en75_soc_data *soc;
+};
+
+struct en75_qdma;
+
+struct en75_qdma *en75_qdma_new(struct en75_eth *eth,
+ void __iomem *qdma_regs,
+ int id,
+ int *irqs,
+ int num_irqs,
+ struct en75_qdma_cfg *cfg,
+ struct en75_debug_qdma_conf *debug);
+
+int en75_qdma_use(struct en75_qdma *qdma);
+int en75_qdma_unuse(struct en75_qdma *qdma);
+int en75_qdma_destroy(struct en75_qdma *qdma);
+int en75_qdma_xmit(struct en75_qdma *qdma, struct sk_buff *skb,
+ union desc_msg *msg, int qid);
+
+struct net_device *en75_alloc_gdm_port(struct en75_eth *eth,
+ struct device_node *np,
+ struct gdm __iomem *regs,
+ struct en75_qdma *qdma,
+ enum etx_fport fport,
+ bool has_g2_stats);
+
+#define en75_rreg(reg) __extension__({ \
+ BUILD_BUG_ON(sizeof(*(reg)) != sizeof(u32)); \
+ union { typeof(*(reg)) v; u32 w; } __r = { .w = readl(reg) }; \
+ __r.v; \
+ })
+
+#define en75_wreg(val, reg) do { \
+ BUILD_BUG_ON(sizeof(*(reg)) != sizeof(u32)); \
+ BUILD_BUG_ON(!__same_type(*(reg), (val))); \
+ union { typeof(*(reg)) v; u32 w; } __w = { .v = (val) }; \
+ writel(__w.w, (reg)); \
+ } while (0)
+
+#define en75_word(val) __extension__({ \
+ union { typeof(val) v; u32 w; } __r = { .v = (val) }; \
+ BUILD_BUG_ON(sizeof(__r) != sizeof(u32)); \
+ __r.w; \
+ })
+#endif
diff --git a/drivers/net/ethernet/econet/econet_eth_debug.c b/drivers/net/ethernet/econet/econet_eth_debug.c
new file mode 100644
index 000000000000..57f19417263f
--- /dev/null
+++ b/drivers/net/ethernet/econet/econet_eth_debug.c
@@ -0,0 +1,617 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#include "econet_eth.h"
+#include "qdma_desc.h"
+
+struct en75_qdma_chain_debug {
+ struct dentry *descs;
+ struct dentry *chan_ctrs;
+ struct en75_debug_qdma_chain_conf *config;
+ struct en75_qdma_debug *qdma;
+ int chain_n;
+};
+
+struct en75_qdma_debug {
+ struct dentry *dir;
+ struct en75_debug_qdma_conf *config;
+ struct en75_qdma_chain_debug chains[QDMA_NUM_CHAINS];
+ struct dentry *fwdescs;
+ struct dentry *regs;
+ struct dentry *interrupts;
+};
+
+struct en75_debug {
+ struct dentry *dir;
+ struct en75_qdma_debug qdma[EN75_NUM_QDMA];
+ struct en75_debug_conf config;
+};
+
+static void print_erx(struct seq_file *m, struct qdma_desc_erx *erx)
+{
+ seq_printf(m, "crsn=%d sport=%d ppe=%d%s%s%s%s%s%s%s",
+ get_erx_crsn(erx), get_erx_sport(erx), get_erx_ppe_entry(erx),
+
+ is_erx_ip6(erx) ? " IP6" : "",
+ is_erx_ip4(erx) ? " IP4" : "",
+ is_erx_ip4f(erx) ? " IP4F" : "",
+ is_erx_tack(erx) ? " TACK" : "",
+ is_erx_l2vld(erx) ? " L2VLD" : "",
+ is_erx_l4f(erx) ? " L4F" : "",
+ is_erx_untag(erx) ? " UNTAG" : ""
+ );
+
+ if (get_erx_sp_tag(erx))
+ seq_printf(m, " sp_tag=%.4x", get_erx_sp_tag(erx));
+ if (get_erx_tci(erx))
+ seq_printf(m, " tci=%.4x", get_erx_tci(erx));
+ if (erx->unknown0)
+ seq_printf(m, " unknown0=%.8x", erx->unknown0);
+ if (get_erx_unknown1(erx))
+ seq_printf(m, " unknown1=%.2x", get_erx_unknown1(erx));
+ if (get_erx_unknown2(erx))
+ seq_printf(m, " unknown2=%.8x", get_erx_unknown2(erx));
+}
+
+static void print_etx(struct seq_file *m, struct etx *etx)
+{
+ seq_printf(m, "fport=%d%s%s%s%s%s",
+ get_etx_fport(etx),
+
+ is_etx_oam(etx) ? " OAM" : "",
+ is_etx_ico(etx) ? " ICO" : "",
+ is_etx_sco(etx) ? " SCO" : "",
+ is_etx_tco(etx) ? " TCO" : "",
+ is_etx_uco(etx) ? " UCO" : ""
+ );
+
+ if (get_etx_channel(etx))
+ seq_printf(m, " channel=%d", get_etx_channel(etx));
+ if (get_etx_queue(etx))
+ seq_printf(m, " queue=%d", get_etx_queue(etx));
+ if (get_etx_sp_tag(etx))
+ seq_printf(m, " sp_tag=%.4x", get_etx_sp_tag(etx));
+ if (get_etx_udf_pmap(etx))
+ seq_printf(m, " udf_pmap=%.2x", get_etx_udf_pmap(etx));
+ if (is_etx_vlan_en(etx))
+ seq_printf(m, " vlan_type=%.2x", get_etx_vlan_type(etx));
+ if (get_etx_vlan_tag(etx))
+ seq_printf(m, " vlan_tag=%.4x", get_etx_vlan_tag(etx));
+}
+
+static void print_desc(struct seq_file *m, struct desc *desc)
+{
+ seq_printf(m, "len=%d\taddr=%.8x next=%d%s%s%s",
+ get_desc_info_pkt_len(&desc->info),
+ desc->pkt_addr,
+ desc->next_idx,
+ is_desc_info_done(&desc->info) ? " DONE" : "",
+ is_desc_info_dropped(&desc->info) ? " DROPPED" : "",
+ is_desc_info_nls(&desc->info) ? " NLS" : ""
+ );
+ if (desc->unknown0)
+ seq_printf(m, " unknown0=%.8x", desc->unknown0);
+
+ if (get_desc_info_unknown1(&desc->info))
+ seq_printf(m, " unknown1=%.4x", get_desc_info_unknown1(&desc->info));
+}
+
+static void print_fwdesc(struct seq_file *m, struct fwdesc *desc)
+{
+ seq_printf(m, "len=%d\taddr=%.8x%s",
+ get_fwdesc_info_pkt_len(&desc->info),
+ desc->pkt_addr,
+ !is_fwdesc_info_ctx(&desc->info) ? " FWD" : ""
+ );
+ if (is_fwdesc_info_ctx(&desc->info)) {
+ seq_printf(m, " RING=%d IDX=%d",
+ is_fwdesc_info_ctx_ring(&desc->info),
+ get_fwdesc_info_ctx_idx(&desc->info));
+ }
+}
+
+static int en75_qdma_hwf(struct seq_file *m, void *v)
+{
+ struct en75_qdma_debug *debug = m->private;
+ struct qregs __iomem *regs;
+ u32 free_desc_n;
+ int i;
+
+ regs = debug->config->regs;
+
+ struct qregs_hwf_cfg1 cfg1;
+ dma_addr_t hwf_desc_addr;
+
+ cfg1 = en75_rreg(®s->hwf_cfg1);
+ hwf_desc_addr = en75_rreg(®s->hwf_desc_addr);
+ free_desc_n = en75_rreg(®s->debug.hwf_desc_free);
+
+ seq_printf(m, "QDMA FWD Descriptors used=%d total=%d\n",
+ get_qregs_hwf_cfg1_fwd_desc_n(&cfg1) - free_desc_n,
+ get_qregs_hwf_cfg1_fwd_desc_n(&cfg1));
+
+ for (i = 0; i < get_qregs_hwf_cfg1_fwd_desc_n(&cfg1); i++) {
+ struct fwdesc *desc = &debug->config->hwf_desc[i];
+
+ seq_printf(m, " %d ", i);
+ print_fwdesc(m, desc);
+ seq_puts(m, " ");
+ print_etx(m, &desc->msg.etx);
+ seq_puts(m, "\n");
+ }
+
+ return 0;
+}
+
+static int en75_hwf_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, en75_qdma_hwf, inode->i_private);
+}
+
+static const struct file_operations en75_hwf_fops = {
+ .owner = THIS_MODULE,
+ .open = en75_hwf_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void en75_qdma_qchain(struct seq_file *m, struct qchain_regs __iomem *regs)
+{
+ seq_printf(m, "\ttxbase=0x%llx\n", (unsigned long long)regs->txbase);
+ seq_printf(m, "\trxbase=0x%llx\n", (unsigned long long)regs->rxbase);
+ seq_printf(m, "\ttx_cpui=%u\n", regs->tx_cpui);
+ seq_printf(m, "\ttx_hwi=%u\n", regs->tx_hwi);
+ seq_printf(m, "\trx_cpui=%u\n", regs->rx_cpui);
+ seq_printf(m, "\trx_hwi=%u\n", regs->rx_hwi);
+}
+
+static int en75_qdma_regs(struct seq_file *m, void *v)
+{
+ struct en75_qdma_debug *debug = m->private;
+ struct qregs __iomem *regs;
+
+ regs = debug->config->regs;
+
+ seq_printf(m, "version=%.08x\n", en75_rreg(®s->version));
+
+ {
+ struct qregs_qcfg cfg = en75_rreg(®s->qdma_cfg);
+
+ seq_printf(m,
+ "qdma_cfg=%.08x dma_pref=%u burst_size=%u %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
+ en75_word(cfg),
+
+ /* numeric fields */
+ get_qregs_qcfg_dma_pref(&cfg),
+ get_qregs_qcfg_burst_size(&cfg),
+
+ /* flags: printed only if set */
+ is_qregs_qcfg_rx_2b_offset(&cfg) ? " +rx_2b_offset" : "",
+ is_qregs_qcfg_msg_word_swap(&cfg) ? " +msg_word_swap" : "",
+ is_qregs_qcfg_dscp_byte_swap(&cfg) ? " +dscp_byte_swap" : "",
+ is_qregs_qcfg_payload_byte_sw(&cfg) ? " +payload_byte_sw" : "",
+ is_qregs_qcfg_vchnl_map_en(&cfg) ? " +vchnl_map_en" : "",
+ is_qregs_qcfg_vchnl_map_mode(&cfg) ? " +vchnl_map_mode" : "",
+ is_qregs_qcfg_qdma_lpbk_rxq_sel(&cfg) ? " +qdma_lpbk_rxq_sel" : "",
+ is_qregs_qcfg_slm_release_en(&cfg) ? " +slm_release_en" : "",
+ is_qregs_qcfg_tx_immediate_done(&cfg) ? " +tx_immediate_done" : "",
+ is_qregs_qcfg_irq_en(&cfg) ? " +irq_en" : "",
+ is_qregs_qcfg_gdm_loopback(&cfg) ? " +gdm_loopback" : "",
+ is_qregs_qcfg_qdma_loopback(&cfg) ? " +qdma_loopback" : "",
+ is_qregs_qcfg_check_done(&cfg) ? " +check_done" : "",
+ is_qregs_qcfg_tx_wb_done(&cfg) ? " +tx_wb_done" : "",
+ is_qregs_qcfg_rx_dma_busy(&cfg) ? " +rx_dma_busy" : "",
+ is_qregs_qcfg_rx_dma_en(&cfg) ? " +rx_dma_en" : "",
+ is_qregs_qcfg_tx_dma_busy(&cfg) ? " +tx_dma_busy" : "",
+ is_qregs_qcfg_tx_dma_en(&cfg) ? " +tx_dma_en" : ""
+ );
+ }
+
+ seq_puts(m, "qchain0:\n");
+ en75_qdma_qchain(m, ®s->qchain0);
+
+ seq_printf(m, "hwf_desc_addr=%.08x\n", en75_rreg(®s->hwf_desc_addr));
+ seq_printf(m, "hwf_data_addr=%.08x\n", en75_rreg(®s->hwf_data_addr));
+
+ {
+ struct hwf_cfg cfg = en75_rreg(®s->hwf_cfg);
+
+ seq_printf(m,
+ "hwf_cfg=%.08x pkt_sz=%u low_th=%u\n",
+ en75_word(cfg),
+ get_qregs_hwf_cfg_pkt_sz(&cfg),
+ get_qregs_hwf_cfg_low_th(&cfg)
+ );
+ }
+
+ {
+ struct qregs_hwf_cfg1 cfg = en75_rreg(®s->hwf_cfg1);
+
+ seq_printf(m,
+ "hwf_cfg1=%.08x fwd_desc_n=%u overhead=%u%s%s\n",
+ cfg.word,
+ get_qregs_hwf_cfg1_fwd_desc_n(&cfg),
+ get_qregs_hwf_cfg1_overhead(&cfg),
+ is_qregs_hwf_cfg1_start(&cfg) ? " +start" : "",
+ is_qregs_hwf_cfg1_overhead_en(&cfg) ? " +overhead_en" : ""
+ );
+ }
+
+ seq_printf(m, "channel_retire=%.08x\n", en75_rreg(®s->channel_retire));
+ seq_printf(m, "int_status=%.08x\n", en75_rreg(®s->int_status));
+ seq_printf(m, "int_enable=%.08x\n", en75_rreg(®s->int_enable));
+ seq_printf(m, "tx_int_delay=%.08x\n", en75_rreg(®s->tx_int_delay));
+ seq_printf(m, "rx_int_delay=%.08x\n", en75_rreg(®s->rx_int_delay));
+
+ seq_puts(m, "done_queue:\n");
+ seq_printf(m, "\taddress=%.08x\n", en75_rreg(®s->done_queue.address));
+ {
+ struct qregs_doneq_cfg cfg = en75_rreg(®s->done_queue.config);
+
+ seq_printf(m,
+ "\tconfig=%.08x size=%u int_threshold=%u\n",
+ cfg.word,
+ get_qregs_doneq_cfg_size(&cfg),
+ get_qregs_doneq_cfg_int_threshold(&cfg)
+ );
+ }
+ seq_printf(m, "\tpop_back=%.08x\n", en75_rreg(®s->done_queue.pop_back));
+ {
+ struct qregs_doneq_state st = en75_rreg(®s->done_queue.state);
+
+ seq_printf(m,
+ "\tstate=%.08x len=%u head=%u\n",
+ st.word,
+ get_qregs_doneq_state_length(&st),
+ get_qregs_doneq_state_head_index(&st)
+ );
+ }
+ seq_printf(m, "\twait_time=%.08x\n", en75_rreg(®s->done_queue.wait_time));
+
+ {
+ struct wrr_mode cfg = en75_rreg(®s->wrr_mode);
+
+ seq_printf(m,
+ "wrr_mode=%.08x%s%s\n",
+ en75_word(cfg),
+ is_qregs_wrr_mode_use_16b(&cfg) ? " +use_16b" : "",
+ is_qregs_wrr_mode_by_byte(&cfg) ? " +by_byte" : ""
+ );
+ }
+
+ /* wrr_weight needs writing to read as anything meaningful */
+ seq_printf(m, "wrr_weight=%.08x\n", en75_word(en75_rreg(®s->wrr_weight)));
+ seq_printf(m, "buf_usage_cfg=%.08x\n", en75_rreg(®s->buf_usage_cfg));
+ seq_printf(m, "tx_meter_cfg=%.08x\n", en75_rreg(®s->tx_meter_cfg));
+ seq_printf(m, "tx_limit_cfg=%.08x\n", en75_rreg(®s->tx_limit_cfg));
+ seq_printf(m, "tx_limit_param=%.08x\n", en75_rreg(®s->tx_limit_param));
+
+ {
+ struct qregs_tx_congest_cfg cfg = en75_rreg(®s->tx_congest_cfg);
+
+ seq_printf(m,
+ "tx_congest_cfg=%.08x dyncong_tick=%u margin=%u dei_scale=%u\n"
+ "%s%s%s%s%s%s%s%s%s%s\n",
+ en75_word(cfg),
+ cfg.dyncong_tick,
+ get_qregs_tx_congest_cfg_dyncong_margin(&cfg),
+ get_qregs_tx_congest_cfg_dyncong_dei_scale(&cfg),
+
+ /* drop / enable modes */
+ is_qregs_tx_congest_cfg_tail_drop_en(&cfg) ? " +tail_drop" : "",
+ is_qregs_tx_congest_cfg_dei_drop_en(&cfg) ? " +dei_drop" : "",
+ is_qregs_tx_congest_cfg_dyncong_en(&cfg) ? " +dyncong" : "",
+
+ /* threshold blocking */
+ is_qregs_tx_congest_cfg_max_thr_blk_tx1(&cfg) ? " +max_thr_tx1" : "",
+ is_qregs_tx_congest_cfg_min_thr_blk_tx1(&cfg) ? " +min_thr_tx1" : "",
+ is_qregs_tx_congest_cfg_max_thr_blk_tx0(&cfg) ? " +max_thr_tx0" : "",
+ is_qregs_tx_congest_cfg_min_thr_blk_tx0(&cfg) ? " +min_thr_tx0" : "",
+
+ /* dynamic congestion update sources */
+ is_qregs_tx_congest_cfg_dyncong_upd_wrr(&cfg) ? " +upd_wrr" : "",
+ is_qregs_tx_congest_cfg_dyncong_upd_txrx(&cfg) ? " +upd_txrx" : "",
+ is_qregs_tx_congest_cfg_dyncong_upd_tick(&cfg) ? " +upd_tick" : ""
+ );
+ }
+
+ {
+ struct qregs_tx_congest_thr cfg = en75_rreg(®s->tx_congest_thr);
+
+ seq_printf(m,
+ "tx_congest_thr=%.08x min=%u max=%u\n",
+ en75_word(cfg),
+ cfg.min,
+ cfg.max
+ );
+ }
+
+ seq_printf(m, "tx_per_ch_dthr=%.08x\n", en75_rreg(®s->tx_per_ch_dthr));
+ seq_printf(m, "tx_per_q_dthr=%.08x\n", en75_rreg(®s->tx_per_q_dthr));
+
+ seq_puts(m, "debug:\n");
+ {
+ struct mem_ctl cfg = en75_rreg(®s->debug.mem_ctl);
+
+ seq_printf(m,
+ "\tmem_ctl=%.08x%s\n",
+ en75_word(cfg),
+ is_qregs_debug_mem_ctl_done(&cfg) ? " +done" : ""
+ );
+ }
+ seq_printf(m, "\tmem_lo=%.08x\n", en75_rreg(®s->debug.mem_lo));
+ seq_printf(m, "\tmem_hi=%.08x\n", en75_rreg(®s->debug.mem_hi));
+ seq_printf(m, "\tunused_0=%.08x\n", en75_rreg(®s->debug.unused_0));
+ seq_printf(m, "\thwf_desc_free=%.08x\n", en75_rreg(®s->debug.hwf_desc_free));
+ seq_printf(m, "\thwd_buf_used=%.08x\n", en75_rreg(®s->debug.hwd_buf_used));
+ seq_printf(m, "\tprobe_lo=%.08x\n", en75_rreg(®s->debug.probe_lo));
+ seq_printf(m, "\tprobe_hi=%.08x\n", en75_rreg(®s->debug.probe_hi));
+
+ {
+ struct qregs_rxring_size cfg = en75_rreg(®s->rxring_size);
+
+ seq_printf(m,
+ "rxring_size=%.08x rxring0=%u rxring1=%u\n",
+ cfg.word,
+ get_qregs_rxring_size_ring0(&cfg),
+ get_qregs_rxring_size_ring1(&cfg)
+ );
+ }
+
+ {
+ struct qregs_rxring_low cfg = en75_rreg(®s->rxring_low);
+
+ seq_printf(m,
+ "rxring_low=%.08x rxring0_low=%u rxring1_low=%u\n",
+ cfg.word,
+ get_qregs_rxring_low_ring0(&cfg),
+ get_qregs_rxring_low_ring1(&cfg)
+ );
+ }
+
+ seq_puts(m, "qchain1:\n");
+ en75_qdma_qchain(m, ®s->qchain1);
+
+ seq_printf(m, "cpu_rx_limit=%.08x\n", en75_rreg(®s->cpu_rx_limit));
+ seq_printf(m, "cpu_rx_limit_val=%.08x\n", en75_rreg(®s->cpu_rx_limit_val));
+ seq_printf(m, "vch_wrr=%.08x\n", en75_rreg(®s->vch_wrr));
+ seq_printf(m, "vch_qmode=%.08x\n", en75_rreg(®s->vch_qmode));
+ seq_printf(m, "ch_lim_en=%.08x\n", en75_rreg(®s->ch_lim_en));
+
+ return 0;
+}
+
+static int en75_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, en75_qdma_regs, inode->i_private);
+}
+
+static const struct file_operations en75_regs_fops = {
+ .owner = THIS_MODULE,
+ .open = en75_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int en75_qdma_interrupts(struct seq_file *m, void *v)
+{
+ struct en75_qdma_debug *debug = m->private;
+ int i;
+
+ seq_puts(m, "count\tirq\treg\tbit\torigin\tchain\tmeaning\n");
+ for (i = 0; i < ARRAY_SIZE(debug->config->irqs); i++) {
+ struct en75_debug_qdma_irq_conf *irq = &debug->config->irqs[i];
+ int j;
+
+ for (j = 0; j < ARRAY_SIZE(irq->desc); j++) {
+ /* Unused bit */
+ if (!irq->desc[j].word)
+ continue;
+
+ seq_printf(m, "%u\t%d\t%d\t%d\t%s\t%d\t%s\n",
+ irq->counters[j], i, j / 32, j % 32,
+ en75_irq_purpose_source_str(irq->desc[j].source),
+ irq->desc[j].chain,
+ en75_irq_purpose_type_str(irq->desc[j].type));
+ }
+ }
+ return 0;
+}
+
+static int en75_interrupts_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, en75_qdma_interrupts, inode->i_private);
+}
+
+static const struct file_operations en75_interrupts_fops = {
+ .owner = THIS_MODULE,
+ .open = en75_interrupts_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int en75_qdma_descs(struct seq_file *m, void *v)
+{
+ struct en75_qdma_chain_debug *chain = m->private;
+ struct qchain_regs __iomem *qchain_reg;
+ int i;
+
+ qchain_reg = chain->chain_n == 1 ?
+ &chain->qdma->config->regs->qchain1 :
+ &chain->qdma->config->regs->qchain0;
+
+ seq_printf(m, "QDMA RX Descriptors driver_idx=%d hardware_idx=%d\n",
+ readl(&qchain_reg->rx_cpui), readl(&qchain_reg->rx_hwi));
+ for (i = 0; i < chain->config->rx_count; i++) {
+ struct desc *desc = &chain->config->rx_descs[i];
+
+ seq_printf(m, " %d ", i);
+ print_desc(m, desc);
+ seq_puts(m, " ");
+ print_erx(m, &desc->msg.erx);
+ seq_puts(m, "\n");
+ }
+
+ seq_printf(m, "QDMA TX Descriptors driver_idx=%d hardware_idx=%d\n",
+ readl(&qchain_reg->tx_cpui), readl(&qchain_reg->tx_hwi));
+ for (i = 0; i < chain->config->tx_count; i++) {
+ struct desc *desc = &chain->config->tx_descs[i];
+
+ seq_printf(m, " %d ", i);
+ print_desc(m, desc);
+ seq_puts(m, " ");
+ print_etx(m, &desc->msg.etx);
+ seq_puts(m, "\n");
+ }
+
+ return 0;
+}
+
+static int en75_descs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, en75_qdma_descs, inode->i_private);
+}
+
+static const struct file_operations en75_descs_fops = {
+ .owner = THIS_MODULE,
+ .open = en75_descs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int en75_chan_ctrs(struct seq_file *m, void *v)
+{
+ struct en75_qdma_chain_debug *chain = m->private;
+ u32 *ctrs = chain->config->tx_per_ch_q;
+ int i;
+
+ seq_puts(m, "chan\tqueue0\tqueue1\tqueue2\tqueue3\tqueue4\tqueue5\tqueue6\tqueue7\n");
+ for (i = 0; i < EN75_NUM_CHANNELS * EN75_NUM_QUEUES; i += EN75_NUM_QUEUES) {
+ seq_printf(m, "%d\t%u\t%u\t%u\t%u\t%u\t%u\t%u\t%u\n",
+ i / EN75_NUM_QUEUES, ctrs[i], ctrs[i + 1], ctrs[i + 2], ctrs[i + 3],
+ ctrs[i + 4], ctrs[i + 5], ctrs[i + 6], ctrs[i + 7]);
+ }
+
+ return 0;
+}
+
+static int en75_chan_ctrs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, en75_chan_ctrs, inode->i_private);
+}
+
+static const struct file_operations en75_chan_ctrs_fops = {
+ .owner = THIS_MODULE,
+ .open = en75_chan_ctrs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int en75_init_qdma(struct en75_qdma_debug *qdma_debug)
+{
+ struct en75_debug_qdma_conf *config = qdma_debug->config;
+
+ BUILD_BUG_ON(ARRAY_SIZE(qdma_debug->chains) != ARRAY_SIZE(config->chains));
+ for (int i = 0; i < ARRAY_SIZE(qdma_debug->chains); i++) {
+ char filename[16] = {0};
+
+ if (!config->chains[i].rx_descs ||
+ !config->chains[i].tx_descs)
+ continue;
+
+ snprintf(filename, sizeof(filename) - 1, "descs%d", i);
+ qdma_debug->chains[i].descs =
+ debugfs_create_file(filename, 0444,
+ qdma_debug->dir,
+ &qdma_debug->chains[i],
+ &en75_descs_fops);
+
+ snprintf(filename, sizeof(filename) - 1, "chan_ctrs%d", i);
+ qdma_debug->chains[i].chan_ctrs =
+ debugfs_create_file(filename, 0444, qdma_debug->dir,
+ &qdma_debug->chains[i],
+ &en75_chan_ctrs_fops);
+
+ if (!qdma_debug->chains[i].descs)
+ return -ENOMEM;
+
+ qdma_debug->chains[i].config = &config->chains[i];
+ qdma_debug->chains[i].qdma = qdma_debug;
+ qdma_debug->chains[i].chain_n = i;
+ }
+
+ qdma_debug->fwdescs = debugfs_create_file("hwf_descs", 0444,
+ qdma_debug->dir, qdma_debug,
+ &en75_hwf_fops);
+
+ qdma_debug->regs = debugfs_create_file("regs", 0444,
+ qdma_debug->dir, qdma_debug,
+ &en75_regs_fops);
+
+ qdma_debug->interrupts = debugfs_create_file("interrupts", 0444,
+ qdma_debug->dir, qdma_debug,
+ &en75_interrupts_fops);
+ return 0;
+}
+
+struct en75_debug *en75_debugfs_init(struct en75_debug_conf *config)
+{
+ struct en75_debug *debug;
+ int ret = -EINVAL;
+
+ debug = kzalloc_obj(*debug);
+ if (!debug)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(&debug->config, config, sizeof(debug->config));
+ config = &debug->config;
+
+ debug->dir = debugfs_create_dir("econet_eth", NULL);
+ if (!debug->dir) {
+ ret = -ENOMEM;
+ goto err_dir;
+ }
+
+ BUILD_BUG_ON(ARRAY_SIZE(debug->qdma) != ARRAY_SIZE(config->qdma));
+ for (int i = 0; i < ARRAY_SIZE(debug->qdma); i++) {
+ char qdma_dirname[8] = {0};
+
+ if (!config->qdma[i].regs)
+ continue;
+
+ snprintf(qdma_dirname, sizeof(qdma_dirname) - 1, "qdma%d", i);
+ debug->qdma[i].dir =
+ debugfs_create_dir(qdma_dirname, debug->dir);
+ if (!debug->qdma[i].dir) {
+ ret = -ENOMEM;
+ goto err_file;
+ }
+
+ debug->qdma[i].config = &config->qdma[i];
+ ret = en75_init_qdma(&debug->qdma[i]);
+ if (ret)
+ goto err_file;
+ }
+
+ return debug;
+
+err_file:
+ debugfs_remove_recursive(debug->dir);
+err_dir:
+ kfree(debug);
+ return ERR_PTR(ret);
+}
+
+void en75_debugfs_exit(struct en75_debug *debug)
+{
+ if (!debug)
+ return;
+ debugfs_remove_recursive(debug->dir);
+ kfree(debug);
+}
diff --git a/drivers/net/ethernet/econet/econet_port.c b/drivers/net/ethernet/econet/econet_port.c
new file mode 100644
index 000000000000..1434490b5f03
--- /dev/null
+++ b/drivers/net/ethernet/econet/econet_port.c
@@ -0,0 +1,473 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * EcoNet EN751221 has 2 GDM ports, one for LAN and one for WAN.
+ * Each port has it's own registers and MAC address, and it's own
+ * net_device instance.
+ */
+
+#include <linux/skbuff.h>
+#include <linux/etherdevice.h>
+#include <linux/netdevice.h>
+#include <net/dsa.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/of_net.h>
+
+#include "econet_eth.h"
+#include "gdm_regs.h"
+#include "qdma_desc.h"
+
+struct en75_hw_stats {
+ /* protect concurrent hw_stats accesses */
+ spinlock_t lock;
+ struct u64_stats_sync syncp;
+
+ /* EN751221 gdm1 has only limited stats, gdm1 has all */
+ bool g2_stats;
+
+ /* get_stats64 */
+ u64 rx_ok_pkts;
+ u64 tx_ok_pkts;
+ u64 rx_ok_bytes;
+ u64 tx_ok_bytes;
+ u64 rx_errors;
+ u64 rx_drops;
+ u64 tx_drops;
+ u64 rx_over_errors;
+
+ /* get_stats64 (requires gdm2 extended stats) */
+ u64 rx_crc_error;
+ u64 rx_multicast;
+
+ /* ethtool stats (all requires gdm2 extended stats) */
+ u64 tx_broadcast;
+ u64 tx_multicast;
+ u64 tx_len[7];
+ u64 rx_broadcast;
+ u64 rx_fragment;
+ u64 rx_jabber;
+ u64 rx_len[7];
+};
+
+struct en75_gdm_port {
+ struct net_device *dev;
+
+ struct gdm __iomem *regs;
+
+ /* Protects accesses to hardware regs */
+ spinlock_t reg_lock;
+
+ struct en75_hw_stats stats;
+
+ /* qos stats counters */
+ u64 cpu_tx_packets;
+ u64 fwd_tx_packets;
+
+ struct en75_qdma *qdma;
+
+ struct en75_eth *eth;
+
+ enum etx_fport fport;
+
+ int qid;
+};
+
+static void en75_set_macaddr(struct en75_gdm_port *port, const u8 *addr)
+{
+ struct gdm_mymac_msb msb;
+ struct gdm_mymac_lsb lsb;
+
+ scoped_guard(spinlock, &port->reg_lock) {
+ msb = en75_rreg(&port->regs->mymac_msb);
+ lsb = en75_rreg(&port->regs->mymac_lsb);
+ set_gdm_mymac_msb_a(&msb, addr[0]);
+ set_gdm_mymac_msb_b(&msb, addr[1]);
+ set_gdm_mymac_lsb_c(&lsb, addr[2]);
+ set_gdm_mymac_lsb_d(&lsb, addr[3]);
+ set_gdm_mymac_lsb_e(&lsb, addr[4]);
+ set_gdm_mymac_lsb_f(&lsb, addr[5]);
+ en75_wreg(lsb, &port->regs->mymac_lsb);
+ en75_wreg(msb, &port->regs->mymac_msb);
+ }
+}
+
+static int en75_dev_set_macaddr(struct net_device *dev, void *p)
+{
+ struct en75_gdm_port *port = netdev_priv(dev);
+ int err;
+
+ err = eth_mac_addr(dev, p);
+ if (err)
+ return err;
+
+ en75_set_macaddr(port, dev->dev_addr);
+
+ return 0;
+}
+
+static void en75_set_gdm_port_fwd_cfg(struct en75_gdm_port *port,
+ enum etx_fport val)
+{
+ struct fwd_cfg fc;
+
+ guard(spinlock)(&port->reg_lock);
+ fc = en75_rreg(&port->regs->fwd_cfg);
+ set_gdm_fwd_cfg_mymac_fport(&fc, val);
+ set_gdm_fwd_cfg_mcast_fport(&fc, val);
+ set_gdm_fwd_cfg_bcast_fport(&fc, val);
+ set_gdm_fwd_cfg_default_fport(&fc, val);
+ set_gdm_fwd_cfg_drop_oversize(&fc, true);
+ en75_wreg(fc, &port->regs->fwd_cfg);
+}
+
+static int en75_dev_init(struct net_device *dev)
+{
+ struct en75_gdm_port *port = netdev_priv(dev);
+
+ en75_set_macaddr(port, dev->dev_addr);
+ en75_set_gdm_port_fwd_cfg(port, ETX_FPORT_QDMA0_CPU);
+
+ return 0;
+}
+
+static int en75_dev_open(struct net_device *dev)
+{
+ struct en75_gdm_port *port = netdev_priv(dev);
+ struct gdm_len_th rlt;
+
+ netif_tx_start_all_queues(dev);
+
+ guard(spinlock)(&port->reg_lock);
+ rlt = en75_rreg(&port->regs->rx_len_threshold);
+ set_gdm_len_th_runt_len(&rlt, 60);
+ set_gdm_len_th_oversize_len(&rlt, ETH_HLEN + dev->mtu + ETH_FCS_LEN);
+ en75_wreg(rlt, &port->regs->rx_len_threshold);
+
+ return en75_qdma_use(port->qdma);
+}
+
+static int en75_dev_stop(struct net_device *dev)
+{
+ struct en75_gdm_port *port = netdev_priv(dev);
+
+ netif_tx_disable(dev);
+
+ return en75_qdma_unuse(port->qdma);
+}
+
+static int en75_dev_change_mtu(struct net_device *dev, int mtu)
+{
+ struct en75_gdm_port *port = netdev_priv(dev);
+ struct gdm_len_th rlt;
+
+ guard(spinlock)(&port->reg_lock);
+ rlt = en75_rreg(&port->regs->rx_len_threshold);
+ set_gdm_len_th_oversize_len(&rlt, ETH_HLEN + mtu + ETH_FCS_LEN);
+ en75_wreg(rlt, &port->regs->rx_len_threshold);
+
+ WRITE_ONCE(dev->mtu, mtu);
+
+ return 0;
+}
+
+static netdev_tx_t en75_dev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct en75_gdm_port *port = netdev_priv(dev);
+ int qid, ret = 0, len = skb->len;
+ struct netdev_queue *txq;
+ union desc_msg msg = {0};
+
+ qid = skb_get_queue_mapping(skb);
+ set_etx_channel(&msg.etx, qid / EN75_NUM_QUEUES);
+ set_etx_queue(&msg.etx, qid % EN75_NUM_QUEUES);
+ set_etx_fport(&msg.etx, port->fport);
+
+ txq = netdev_get_tx_queue(dev, qid);
+
+ /*
+ * Non-linear skbs are unsupported, we shouldn't get them but
+ * if we do, we'll attempt to linearize.
+ */
+ if (skb_linearize(skb))
+ goto error;
+
+ netdev_tx_sent_queue(txq, len);
+
+ ret = en75_qdma_xmit(port->qdma, skb, &msg, 0);
+
+ if (ret < 0) {
+ netdev_tx_completed_queue(txq, 1, len);
+ goto error;
+ }
+
+ if (ret == EBUSY)
+ netif_tx_stop_queue(txq);
+
+ return NETDEV_TX_OK;
+
+error:
+ dev_kfree_skb_any(skb);
+ dev->stats.tx_dropped++;
+
+ if (ret == -EBUSY) {
+ netif_tx_stop_queue(txq);
+ return NETDEV_TX_BUSY;
+ }
+
+ return NETDEV_TX_OK;
+}
+
+static void en75_update_hw_stats(struct en75_gdm_port *port)
+{
+ struct gdm_counters __iomem *c = &port->regs->counters;
+ struct clear_counters cc = {0};
+ u32 i = 0;
+
+ guard(spinlock)(&port->stats.lock);
+ u64_stats_update_begin(&port->stats.syncp);
+
+ port->stats.tx_ok_pkts += en75_rreg(&c->tx.tx_pkts);
+ port->stats.tx_ok_bytes += en75_rreg(&c->tx.bytes);
+ port->stats.tx_drops += en75_rreg(&c->tx.drops);
+ if (port->stats.g2_stats) {
+ port->stats.tx_broadcast += en75_rreg(&c->tx.g2.tx_bcast);
+ port->stats.tx_multicast += en75_rreg(&c->tx.g2.tx_mcast);
+
+ port->stats.tx_len[i] += en75_rreg(&c->tx.g2.f_less_64);
+ port->stats.tx_len[i++] += en75_rreg(&c->tx.g2.f_64);
+
+ port->stats.tx_len[i++] += en75_rreg(&c->tx.g2.f_65_127);
+ port->stats.tx_len[i++] += en75_rreg(&c->tx.g2.f_128_255);
+ port->stats.tx_len[i++] += en75_rreg(&c->tx.g2.f_256_511);
+ port->stats.tx_len[i++] += en75_rreg(&c->tx.g2.f_512_1023);
+ port->stats.tx_len[i++] += en75_rreg(&c->tx.g2.f_1024_1518);
+ port->stats.tx_len[i++] += en75_rreg(&c->tx.g2.f_more_1518);
+ }
+
+ port->stats.rx_ok_pkts += en75_rreg(&c->rx.pkts);
+ port->stats.rx_ok_bytes += en75_rreg(&c->rx.bytes);
+ port->stats.rx_errors += en75_rreg(&c->rx.drops_err);
+ port->stats.rx_over_errors += en75_rreg(&c->rx.drops_overflow);
+ if (port->stats.g2_stats) {
+ port->stats.rx_drops += en75_rreg(&c->rx.g2.edrops);
+ port->stats.rx_broadcast += en75_rreg(&c->rx.g2.bcast);
+ port->stats.rx_multicast += en75_rreg(&c->rx.g2.mcast);
+ port->stats.rx_crc_error += en75_rreg(&c->rx.g2.ecrc);
+ port->stats.rx_fragment += en75_rreg(&c->rx.g2.efrag);
+ port->stats.rx_jabber += en75_rreg(&c->rx.g2.ejabber);
+
+ i = 0;
+ port->stats.rx_len[i] += en75_rreg(&c->rx.g2.f_less_64);
+ port->stats.rx_len[i++] += en75_rreg(&c->rx.g2.f_64);
+
+ port->stats.rx_len[i++] += en75_rreg(&c->rx.g2.f_65_127);
+ port->stats.rx_len[i++] += en75_rreg(&c->rx.g2.f_128_255);
+ port->stats.rx_len[i++] += en75_rreg(&c->rx.g2.f_256_511);
+ port->stats.rx_len[i++] += en75_rreg(&c->rx.g2.f_512_1023);
+ port->stats.rx_len[i++] += en75_rreg(&c->rx.g2.f_1024_1518);
+ port->stats.rx_len[i++] += en75_rreg(&c->rx.g2.f_more_1518);
+ } else {
+ port->stats.rx_drops += en75_rreg(&c->rx.drops_err);
+ port->stats.rx_drops += en75_rreg(&c->rx.drops_fc);
+ port->stats.rx_drops += en75_rreg(&c->rx.drops_rc);
+ port->stats.rx_drops += en75_rreg(&c->rx.drops_overflow);
+ }
+
+ set_gdm_cl_cnt_rx(&cc, true);
+ set_gdm_cl_cnt_tx(&cc, true);
+ en75_wreg(cc, &port->regs->clear_counters);
+
+ u64_stats_update_end(&port->stats.syncp);
+}
+
+static void en75_dev_get_stats64(struct net_device *dev,
+ struct rtnl_link_stats64 *storage)
+{
+ struct en75_gdm_port *port = netdev_priv(dev);
+ unsigned int start;
+
+ en75_update_hw_stats(port);
+ do {
+ start = u64_stats_fetch_begin(&port->stats.syncp);
+ storage->rx_packets = port->stats.rx_ok_pkts;
+ storage->tx_packets = port->stats.tx_ok_pkts;
+ storage->rx_bytes = port->stats.rx_ok_bytes;
+ storage->tx_bytes = port->stats.tx_ok_bytes;
+ storage->rx_errors = port->stats.rx_errors;
+ storage->rx_dropped = port->stats.rx_drops;
+ storage->tx_dropped = port->stats.tx_drops;
+ storage->rx_over_errors = port->stats.rx_over_errors;
+ if (port->stats.g2_stats) {
+ storage->multicast = port->stats.rx_multicast;
+ storage->rx_crc_errors = port->stats.rx_crc_error;
+ }
+ } while (u64_stats_fetch_retry(&port->stats.syncp, start));
+}
+
+static void en75_ethtool_get_drvinfo(struct net_device *dev,
+ struct ethtool_drvinfo *info)
+{
+ struct device *eth = dev->dev.parent;
+
+ strscpy(info->driver, eth->driver->name, sizeof(info->driver));
+ strscpy(info->bus_info, dev_name(eth), sizeof(info->bus_info));
+}
+
+static void en75_ethtool_get_mac_stats(struct net_device *dev,
+ struct ethtool_eth_mac_stats *stats)
+{
+ struct en75_gdm_port *port = netdev_priv(dev);
+ unsigned int start;
+
+ en75_update_hw_stats(port);
+
+ if (!port->stats.g2_stats)
+ return;
+
+ do {
+ start = u64_stats_fetch_begin(&port->stats.syncp);
+ stats->MulticastFramesXmittedOK = port->stats.tx_multicast;
+ stats->BroadcastFramesXmittedOK = port->stats.tx_broadcast;
+ stats->BroadcastFramesReceivedOK = port->stats.rx_broadcast;
+ } while (u64_stats_fetch_retry(&port->stats.syncp, start));
+}
+
+static const struct ethtool_rmon_hist_range en75_ethtool_rmon_ranges[] = {
+ { 0, 64 },
+ { 65, 127 },
+ { 128, 255 },
+ { 256, 511 },
+ { 512, 1023 },
+ { 1024, 1518 },
+ { 1519, 10239 },
+ {},
+};
+
+static void
+en75_ethtool_get_rmon_stats(struct net_device *dev,
+ struct ethtool_rmon_stats *stats,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ struct en75_gdm_port *port = netdev_priv(dev);
+ struct en75_hw_stats *hw_stats = &port->stats;
+ unsigned int start;
+
+ BUILD_BUG_ON(ARRAY_SIZE(en75_ethtool_rmon_ranges) !=
+ ARRAY_SIZE(hw_stats->tx_len) + 1);
+ BUILD_BUG_ON(ARRAY_SIZE(en75_ethtool_rmon_ranges) !=
+ ARRAY_SIZE(hw_stats->rx_len) + 1);
+
+ *ranges = en75_ethtool_rmon_ranges;
+ en75_update_hw_stats(port);
+
+ if (!port->stats.g2_stats)
+ return;
+
+ do {
+ int i;
+
+ start = u64_stats_fetch_begin(&port->stats.syncp);
+ stats->fragments = hw_stats->rx_fragment;
+ stats->jabbers = hw_stats->rx_jabber;
+ for (i = 0; i < ARRAY_SIZE(en75_ethtool_rmon_ranges) - 1;
+ i++) {
+ stats->hist[i] = hw_stats->rx_len[i];
+ stats->hist_tx[i] = hw_stats->tx_len[i];
+ }
+ } while (u64_stats_fetch_retry(&port->stats.syncp, start));
+}
+
+static const struct net_device_ops en75_netdev_ops = {
+ .ndo_init = en75_dev_init,
+ .ndo_open = en75_dev_open,
+ .ndo_stop = en75_dev_stop,
+ .ndo_change_mtu = en75_dev_change_mtu,
+ .ndo_start_xmit = en75_dev_xmit,
+ .ndo_get_stats64 = en75_dev_get_stats64,
+ .ndo_set_mac_address = en75_dev_set_macaddr,
+};
+
+static const struct ethtool_ops en75_ethtool_ops = {
+ .get_drvinfo = en75_ethtool_get_drvinfo,
+ .get_eth_mac_stats = en75_ethtool_get_mac_stats,
+ .get_rmon_stats = en75_ethtool_get_rmon_stats,
+};
+
+struct net_device *en75_alloc_gdm_port(struct en75_eth *eth,
+ struct device_node *np,
+ struct gdm __iomem *regs,
+ struct en75_qdma *qdma,
+ enum etx_fport fport,
+ bool has_g2_stats)
+{
+ struct en75_gdm_port *port;
+ struct net_device *ndev;
+ int err;
+
+ ndev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*port),
+ EN75_NUM_SOFT_QUEUES,
+ EN75_NUM_SOFT_QUEUES);
+ if (!ndev) {
+ dev_err(eth->dev, "alloc_etherdev failed\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ ndev->netdev_ops = &en75_netdev_ops;
+ ndev->ethtool_ops = &en75_ethtool_ops;
+ ndev->max_mtu = EN75_MAX_PACKET_SIZE;
+ ndev->watchdog_timeo = 5 * HZ;
+
+ /*
+ * This driver is meant to forward packets, not send/receive them.
+ * Therefore most features are not that useful.
+ *
+ * NETIF_F_TSO | NETIF_F_TSO6:
+ * - Supported in EN751627, not in EN751221, not used in forwarding.
+ *
+ * NETIF_F_IP_CSUM | NETIF_F_RXCSUM | NETIF_F_IPV6_CSUM:
+ * - Supported but disabled for simplicity.
+ *
+ * NETIF_F_HW_TC:
+ * - TODO: When we get the PPE working, we will enable this for
+ * TC_SETUP_BLOCK / TC_SETUP_FT for NAT flow-table offloading.
+ */
+ ndev->hw_features = 0;
+
+ ndev->features |= ndev->hw_features;
+ ndev->vlan_features = ndev->hw_features;
+ ndev->dev.of_node = np;
+ SET_NETDEV_DEV(ndev, eth->dev);
+
+ err = of_get_ethdev_address(np, ndev);
+ if (err) {
+ if (err == -EPROBE_DEFER)
+ return ERR_PTR(err);
+
+ eth_hw_addr_random(ndev);
+ dev_info(eth->dev, "generated random MAC address %pM\n",
+ ndev->dev_addr);
+ }
+
+ port = netdev_priv(ndev);
+ u64_stats_init(&port->stats.syncp);
+ spin_lock_init(&port->stats.lock);
+ spin_lock_init(&port->reg_lock);
+ port->stats.g2_stats = has_g2_stats;
+ port->dev = ndev;
+ port->regs = regs;
+ port->fport = fport;
+ port->qdma = qdma;
+ port->eth = eth;
+
+ err = register_netdev(ndev);
+ if (err)
+ return ERR_PTR(err);
+
+ dev_info(eth->dev, "port %d%s registered with %pM\n",
+ fport,
+ (fport == ETX_FPORT_GDM1) ? " (LAN)" :
+ (fport == ETX_FPORT_GDM2) ? " (WAN)" : "",
+ ndev->dev_addr);
+
+ return ndev;
+}
diff --git a/drivers/net/ethernet/econet/econet_qdma.c b/drivers/net/ethernet/econet/econet_qdma.c
new file mode 100644
index 000000000000..12621e2dc6d8
--- /dev/null
+++ b/drivers/net/ethernet/econet/econet_qdma.c
@@ -0,0 +1,1209 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/spinlock.h>
+#include <linux/netdevice.h>
+#include <net/page_pool/helpers.h>
+#include <linux/skbuff.h>
+#include <linux/etherdevice.h>
+#include <net/dsa.h>
+#include <linux/platform_device.h>
+#include <linux/of_reserved_mem.h>
+
+#include "asm-generic/int-ll64.h"
+#include "linux/bitmap.h"
+#include "linux/dev_printk.h"
+#include "linux/types.h"
+#include "qdma_desc.h"
+#include "qdma_regs.h"
+#include "econet_eth.h"
+
+/* The non-dma part of RX packet descriptor */
+struct en75_q_rx_ent {
+ void *buf;
+ dma_addr_t dma_addr;
+ u16 dma_len;
+};
+
+struct en75_q_rx {
+ /*
+ * No lock, access only in NAPI, or else when NAPI is disabled
+ * and qdma->lock is held
+ */
+ struct en75_q_rx_ent *entry;
+ struct desc *desc;
+ u16 cpu_i;
+
+ /* Not modified after init */
+ struct en75_qdma *qdma;
+ struct qchain_regs __iomem *qchain_regs;
+ int ndesc;
+ int buf_size;
+ struct napi_struct napi;
+ struct page_pool *page_pool;
+};
+
+/* The non-dma part of TX packet descriptor */
+struct en75_q_tx_ent {
+ struct sk_buff *skb;
+ dma_addr_t dma_addr;
+ u16 dma_len;
+ u16 freelist_next;
+};
+
+struct en75_q_tx {
+ /*
+ * protect concurrent queue accesses
+ * use _bh unless in napi poll
+ */
+ spinlock_t lock_bh;
+ struct en75_q_tx_ent *entry;
+ struct desc *desc;
+ u32 *debug_tx_cnt;
+
+ /* FIFO of free entries because they complete out of order. */
+ u16 freelist_head;
+ u16 freelist_tail;
+
+ /* Not modified after init */
+ struct en75_qdma *qdma;
+ struct qchain_regs __iomem *qchain_regs;
+ int ndesc;
+ struct napi_struct napi;
+};
+
+struct en75_irq {
+ /*
+ * protect concurrent irqmask accesses
+ * use _irqsave unless in irq handler
+ */
+ spinlock_t lock_irq;
+ u32 irqmask[QDMA_REGS_PER_IRQ];
+ u32 __iomem *mask_reg[QDMA_REGS_PER_IRQ];
+ u32 __iomem *status_reg[QDMA_REGS_PER_IRQ];
+ u32 *debug_int_cnt;
+
+ /* Not modified after init */
+ struct en75_qdma *qdma;
+ int irq;
+};
+
+struct en75_tx_doneq {
+ /* No lock, access only in NAPI */
+ u32 *q;
+ struct qregs_doneq __iomem *regs;
+
+ /* Not modified after init */
+ struct en75_qdma *qdma;
+ int size;
+ struct napi_struct napi;
+};
+
+struct en75_qdma {
+ /* Protects regs, users, destroying */
+ struct mutex lock;
+ struct qregs __iomem *regs;
+ int users;
+ bool destroying;
+
+ /* Synchronized inside of the structure */
+ struct en75_irq irqs[QDMA_NUM_IRQS];
+ struct en75_tx_doneq q_tx_done[QDMA_NUM_TX_DONE];
+ struct en75_q_tx q_tx[QDMA_NUM_CHAINS];
+ struct en75_q_rx q_rx[QDMA_NUM_CHAINS];
+
+ /* Not modified after init */
+ struct en75_eth *eth;
+ struct device *dev;
+ int id;
+ struct fwdesc *hwf_desc;
+ struct net_device *napi_dev;
+ struct en75_qdma_cfg cfg;
+ const struct en75_soc_data *soc;
+};
+
+static void en75_fill_rx_queue(struct en75_q_rx *q, u32 end_i)
+{
+ u32 ndesc = q->ndesc;
+ u32 cpu_i = (q->cpu_i + 1) % ndesc;
+
+ for (; cpu_i != end_i; cpu_i = (cpu_i + 1) % ndesc) {
+ struct en75_q_rx_ent *e = &q->entry[cpu_i];
+ struct desc *pdesc = &q->desc[cpu_i];
+ struct page *page;
+ int offset;
+ int i;
+
+ page = page_pool_dev_alloc_frag(q->page_pool, &offset,
+ q->buf_size);
+
+ if (!page)
+ break;
+
+ WARN_ON_ONCE(e->dma_addr);
+ e->buf = page_address(page) + offset;
+ e->dma_addr = page_pool_get_dma_addr(page) + offset;
+ e->dma_len = SKB_WITH_OVERHEAD(q->buf_size);
+
+ WRITE_ONCE(pdesc->info, ((struct desc_info) {
+ .word = FIELD_PREP(DESC_INFO_PKT_LEN_MASK, e->dma_len)
+ }));
+ WRITE_ONCE(pdesc->pkt_addr, e->dma_addr);
+ for (i = 0; i < ARRAY_SIZE(pdesc->msg.raw); i++)
+ WRITE_ONCE(pdesc->msg.raw[i], 0);
+ }
+
+ cpu_i = (cpu_i - 1) % ndesc;
+
+ q->cpu_i = cpu_i;
+ en75_wreg(cpu_i, &q->qchain_regs->rx_cpui);
+}
+
+static void en75_qdma_rx_process_one(struct en75_q_rx *q, u32 cpu_i,
+ enum dma_data_direction dir)
+{
+ struct en75_q_rx_ent *e = &q->entry[cpu_i];
+ struct sk_buff *skb;
+ struct page *page;
+ struct desc desc;
+ u32 hash;
+ u8 sport;
+ int len;
+
+ memcpy(&desc, &q->desc[cpu_i], sizeof(desc));
+
+ dma_sync_single_for_cpu(q->qdma->dev, e->dma_addr,
+ SKB_WITH_OVERHEAD(q->buf_size), dir);
+
+ /* Not needed but a couple of WARN_ON_ONCE() check this later */
+ e->dma_addr = 0;
+
+ /* Make debug more readable */
+ WRITE_ONCE(q->desc[cpu_i].pkt_addr, 0);
+
+ len = get_desc_info_pkt_len(&desc.info);
+ if (!len || len > SKB_WITH_OVERHEAD(q->buf_size))
+ goto return_page;
+
+ if (WARN_ON_ONCE(is_desc_info_nls(&desc.info)))
+ goto return_page;
+
+ skb = napi_build_skb(e->buf, q->buf_size);
+ if (!skb)
+ goto return_page;
+
+ __skb_put(skb, len);
+ skb_mark_for_recycle(skb);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+
+ hash = get_erx_ppe_entry(&desc.msg.erx);
+ skb_set_hash(skb, jhash_1word(hash, 0),
+ PKT_HASH_TYPE_L4);
+
+ sport = get_erx_sport(&desc.msg.erx);
+ if (en75_rx_before_recv(q->qdma->eth, skb, sport))
+ dev_kfree_skb(skb);
+ else
+ napi_gro_receive(&q->napi, skb);
+
+ return;
+
+return_page:
+ page = virt_to_head_page(e->buf);
+ page_pool_put_full_page(q->page_pool, page, false);
+}
+
+static int en75_qdma_rx_process(struct en75_q_rx *q, int budget)
+{
+ enum dma_data_direction dir = page_pool_get_dma_dir(q->page_pool);
+ u32 hardware_i = en75_rreg(&q->qchain_regs->rx_hwi);
+ int ndesc = q->ndesc;
+ u32 cpu_i;
+ int done;
+
+ /* The stored value of cpu_i is the last entry actually handled.
+ * Whereas hardware_i is the next entry that has not yet been received.
+ */
+ cpu_i = (q->cpu_i + 1) % ndesc;
+
+ for (done = 0; done < budget && cpu_i != hardware_i; done++) {
+ en75_qdma_rx_process_one(q, cpu_i, dir);
+ cpu_i = (cpu_i + 1) % ndesc;
+ }
+
+ en75_fill_rx_queue(q, cpu_i);
+
+ return done;
+}
+
+#define IRQ_PURPOSE(t, s, c) \
+ ((union en75_irq_purpose){ .type = IPS_ ## t, .source = IPSC_ ## s, .chain = (c) })
+
+union irq_bit {
+ struct {
+ int irq_idx : 8;
+ int reg_idx : 8;
+ int bit_idx : 8;
+ int _pad : 8;
+ };
+ u32 word;
+};
+
+static_assert(sizeof(union irq_bit) == 4, "irq_bit size");
+
+/* IRQ functions relevant to the EN751221 IRQ layout */
+
+static const union en75_irq_purpose EN751221_IRQ_MAP[] = {
+ [0] = IRQ_PURPOSE(DONE, TX, 0),
+ [1] = IRQ_PURPOSE(DONE, RX, 0),
+ [2] = IRQ_PURPOSE(NO_DSCP, TX, 0),
+ [3] = IRQ_PURPOSE(NO_DSCP, RX, 0),
+ [4] = IRQ_PURPOSE(DONE, TX, 1),
+ [5] = IRQ_PURPOSE(DONE, RX, 1),
+ [6] = IRQ_PURPOSE(NO_DSCP, TX, 1),
+ [7] = IRQ_PURPOSE(NO_DSCP, RX, 1),
+ [8] = IRQ_PURPOSE(NO_DSCP, FWD, -1),
+ [9] = IRQ_PURPOSE(NO_DSCP, DONE, 0),
+ [10] = IRQ_PURPOSE(LOW_DSCP, FWD, 0),
+ [11] = IRQ_PURPOSE(OVERFLOW, UNSPEC, -1),
+ [12] = IRQ_PURPOSE(ERR_COHERENT, TX, 0),
+ [13] = IRQ_PURPOSE(ERR_COHERENT, RX, 0),
+ [14] = IRQ_PURPOSE(ERR_COHERENT, TX, 1),
+ [15] = IRQ_PURPOSE(ERR_COHERENT, RX, 1),
+ [16] = IRQ_PURPOSE(GPON_INT, UNSPEC, -1),
+ [17] = IRQ_PURPOSE(EPON_INT, UNSPEC, -1),
+ [18] = IRQ_PURPOSE(XPON_INT, UNSPEC, -1),
+};
+
+static bool en751221_valid_irq_bit(union irq_bit bit)
+{
+ return bit.irq_idx == 0 && bit.reg_idx == 0 &&
+ bit.bit_idx < ARRAY_SIZE(EN751221_IRQ_MAP);
+}
+
+static union en75_irq_purpose en751221_irq_purpose(union irq_bit bit)
+{
+ if (WARN_ON_ONCE(!en751221_valid_irq_bit(bit)))
+ return (union en75_irq_purpose){0};
+
+ return EN751221_IRQ_MAP[bit.bit_idx];
+}
+
+static union irq_bit en751221_irq_bit(union en75_irq_purpose purpose)
+{
+ union irq_bit bit = {0};
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(EN751221_IRQ_MAP); i++) {
+ if (EN751221_IRQ_MAP[i].word == purpose.word) {
+ bit.bit_idx = i;
+ return bit;
+ }
+ }
+ return bit;
+}
+
+static u32 __iomem *en751221_irq_status_reg(struct qregs __iomem *regs, int irqn)
+{
+ if (irqn > 0)
+ return ERR_PTR(-EINVAL);
+
+ return ®s->int_status;
+}
+
+static u32 __iomem *en751221_irq_enable_reg(struct qregs __iomem *regs, int irqn)
+{
+ if (irqn > 0)
+ return ERR_PTR(-EINVAL);
+
+ return ®s->int_enable;
+}
+
+/* End EN751221 IRQ functions */
+
+static void en75_qdma_set_irqmask(struct en75_qdma *qdma, union irq_bit b, bool enable)
+{
+ struct en75_irq *irq;
+
+ if (WARN_ON_ONCE(b.irq_idx >= ARRAY_SIZE(qdma->irqs)))
+ return;
+
+ irq = &qdma->irqs[b.irq_idx];
+
+ if (WARN_ON_ONCE(b.reg_idx >= ARRAY_SIZE(irq->irqmask)))
+ return;
+
+ if (WARN_ON_ONCE(b.bit_idx >= 32))
+ return;
+
+ guard(spinlock_irqsave)(&irq->lock_irq);
+
+ if (enable)
+ irq->irqmask[b.reg_idx] |= BIT(b.bit_idx);
+ else
+ irq->irqmask[b.reg_idx] &= ~BIT(b.bit_idx);
+
+ en75_wreg(irq->irqmask[b.reg_idx], irq->mask_reg[b.reg_idx]);
+
+ /* Read irq_enable register in order to guarantee the update above
+ * completes in the spinlock critical section.
+ */
+ en75_rreg(irq->mask_reg[b.reg_idx]);
+}
+
+static int en75_qdma_rx_napi_poll(struct napi_struct *napi, int budget)
+{
+ struct en75_q_rx *q = container_of(napi, struct en75_q_rx, napi);
+ struct en75_qdma *qdma = q->qdma;
+ int cur, done = 0;
+
+ do {
+ cur = en75_qdma_rx_process(q, budget - done);
+ done += cur;
+ } while (cur && done < budget);
+
+ if (done < budget && napi_complete(napi)) {
+ union en75_irq_purpose purpose;
+ union irq_bit b;
+
+ purpose = IRQ_PURPOSE(DONE, RX, q - &qdma->q_rx[0]);
+ b = en751221_irq_bit(purpose);
+ en75_qdma_set_irqmask(qdma, b, true);
+ }
+
+ return done;
+}
+
+static irqreturn_t en75_irq_handler(int irq_num, void *dev_instance)
+{
+ struct en75_irq *irq = dev_instance;
+ struct en75_qdma *qdma = irq->qdma;
+ int i;
+
+ guard(spinlock)(&irq->lock_irq);
+
+ for (i = 0; i < ARRAY_SIZE(irq->irqmask); i++) {
+ unsigned long regval;
+ u32 disable_int = 0;
+ u8 bit;
+
+ regval = en75_rreg(irq->status_reg[i]);
+ regval &= irq->irqmask[i];
+
+ /*
+ * You must write the bits back to the status register
+ * or you will keep receiving the same interrupt.
+ */
+ en75_wreg((u32)regval, irq->status_reg[i]);
+
+ for_each_set_bit(bit, ®val, 32) {
+ union en75_irq_purpose p;
+
+ if (irq->debug_int_cnt)
+ irq->debug_int_cnt[i * 32 + bit]++;
+
+ p = en751221_irq_purpose((union irq_bit){
+ .irq_idx = irq - &qdma->irqs[0],
+ .reg_idx = i,
+ .bit_idx = bit,
+ });
+
+ if (p.type == IPS_DONE && p.source == IPSC_RX) {
+ napi_schedule_irqoff(&qdma->q_rx[p.chain].napi);
+ disable_int |= BIT(bit);
+ continue;
+ }
+ if (p.type == IPS_DONE && p.source == IPSC_TX) {
+ napi_schedule_irqoff(&qdma->q_tx_done[i].napi);
+ disable_int |= BIT(bit);
+ continue;
+ }
+ dev_dbg_ratelimited(qdma->dev,
+ "%s IRQ from %s[%d]\n",
+ en75_irq_purpose_type_str(p.type),
+ en75_irq_purpose_source_str(p.source),
+ p.chain);
+ }
+
+ irq->irqmask[i] &= ~disable_int;
+ en75_wreg(irq->irqmask[i], irq->mask_reg[i]);
+ }
+
+ return IRQ_HANDLED;
+}
+
+#define IRQ_RING_IDX_MASK GENMASK(20, 16)
+#define IRQ_DESC_IDX_MASK GENMASK(15, 0)
+
+static int en75_poll_tx_complete(struct napi_struct *napi, int budget)
+{
+ struct qregs_doneq_state state;
+ struct en75_tx_doneq *done_q;
+ struct en75_qdma *qdma;
+ int id, irq_queued;
+ u32 done = 0, head;
+
+ done_q = container_of(napi, struct en75_tx_doneq, napi);
+ qdma = done_q->qdma;
+ id = done_q - &qdma->q_tx_done[0];
+
+ state = en75_rreg(&done_q->regs->state);
+ head = get_qregs_doneq_state_head_index(&state);
+ head = head % done_q->size;
+ irq_queued = get_qregs_doneq_state_length(&state);
+
+ while (irq_queued > 0 && done < budget) {
+ u32 index, qid, val = done_q->q[head];
+ struct en75_q_tx_ent *e;
+ struct en75_q_tx *q;
+ struct netdev_queue *txq;
+ struct sk_buff *skb;
+ struct desc desc;
+
+ if (val == 0xffffffffU)
+ break;
+
+ done_q->q[head] = 0xffffffffU; /* mark as done */
+ head = (head + 1) % done_q->size;
+ irq_queued--;
+ done++;
+
+ qid = FIELD_GET(IRQ_RING_IDX_MASK, val);
+ if (WARN_ON_ONCE(qid >= ARRAY_SIZE(qdma->q_tx)))
+ continue;
+
+ q = &qdma->q_tx[qid];
+ if (WARN_ON_ONCE(!q->ndesc))
+ continue;
+
+ index = FIELD_GET(IRQ_DESC_IDX_MASK, val);
+ if (WARN_ON_ONCE(index >= q->ndesc))
+ continue;
+
+ guard(spinlock)(&q->lock_bh);
+
+ desc = q->desc[index];
+
+ if (WARN_ON_ONCE(!is_desc_info_done(&desc.info) &&
+ !is_desc_info_dropped(&desc.info)))
+ continue;
+
+ e = &q->entry[index];
+ skb = e->skb;
+
+ dma_unmap_single(qdma->dev, e->dma_addr, e->dma_len,
+ DMA_TO_DEVICE);
+ memset(e, 0, sizeof(*e));
+
+ /* Completion ring can report out of order when hw QoS is
+ * enabled and packets with different priority are queued
+ * to same DMA ring. So we use a linked list to maintain free
+ * entries.
+ */
+ e->freelist_next = 0xffff;
+ q->entry[q->freelist_tail].freelist_next = index;
+ q->freelist_tail = index;
+
+ txq = netdev_get_tx_queue(skb->dev,
+ skb_get_queue_mapping(skb));
+ netdev_tx_completed_queue(txq, 1, skb->len);
+ if (netif_tx_queue_stopped(txq))
+ netif_tx_wake_queue(txq);
+
+ dev_kfree_skb_any(skb);
+ }
+
+ if (done) {
+ int i, len = done >> 7;
+
+ for (i = 0; i < len; i++) {
+ en75_rreg(&qdma->regs->done_queue.pop_back);
+ en75_wreg(0x80U, &qdma->regs->done_queue.pop_back);
+ }
+ en75_rreg(&qdma->regs->done_queue.pop_back);
+ en75_wreg(done & 0x7f, &qdma->regs->done_queue.pop_back);
+ }
+
+ if (done < budget && napi_complete(napi)) {
+ union en75_irq_purpose purpose = IRQ_PURPOSE(DONE, TX, id);
+ union irq_bit b = en751221_irq_bit(purpose);
+
+ en75_qdma_set_irqmask(qdma, b, true);
+ }
+
+ return done;
+}
+
+int en75_qdma_xmit(struct en75_qdma *qdma, struct sk_buff *skb,
+ union desc_msg *msg, int qid)
+{
+ struct en75_q_tx *q = &qdma->q_tx[qid];
+ int len = skb_headlen(skb);
+ struct en75_q_tx_ent *e;
+ u16 index, next_index;
+ struct desc *desc;
+ dma_addr_t addr;
+ int ret;
+
+ guard(spinlock_bh)(&q->lock_bh);
+
+ index = q->freelist_head;
+ if (index == 0xffff)
+ return -EBUSY;
+
+ e = &q->entry[index];
+ next_index = e->freelist_next;
+ if (next_index == 0xffff)
+ return -EBUSY;
+
+ addr = dma_map_single(qdma->dev, skb->data, len, DMA_TO_DEVICE);
+ ret = dma_mapping_error(qdma->dev, addr);
+ if (unlikely(ret))
+ return ret;
+
+ desc = &q->desc[index];
+ WRITE_ONCE(desc->pkt_addr, addr);
+ WRITE_ONCE(desc->info, ((struct desc_info) {
+ .word = FIELD_PREP(DESC_INFO_PKT_LEN_MASK, len)
+ }));
+ WRITE_ONCE(desc->next_idx, next_index);
+ for (int i = 0; i < ARRAY_SIZE(desc->msg.raw); i++)
+ WRITE_ONCE(desc->msg.raw[i], msg->raw[i]);
+
+ e->skb = skb;
+ e->dma_addr = addr;
+ e->dma_len = len;
+ q->freelist_head = next_index;
+
+ skb_tx_timestamp(skb);
+
+ en75_wreg((u32)next_index, &q->qchain_regs->tx_cpui);
+
+ if (q->debug_tx_cnt) {
+ int chq;
+
+ chq = get_etx_channel(&msg->etx) % EN75_NUM_CHANNELS;
+ chq *= EN75_NUM_QUEUES;
+ chq += get_etx_queue(&msg->etx) % EN75_NUM_QUEUES;
+ q->debug_tx_cnt[chq]++;
+ }
+
+ return q->entry[next_index].freelist_next == 0xffff ?
+ EBUSY : 0;
+}
+
+/* Init functions, no locks, assumed non-concurrent */
+
+static int en75_init_rx_queue(struct en75_q_rx *q,
+ struct en75_qdma *qdma, int ndesc)
+{
+ const struct page_pool_params pp_params = {
+ .order = 0,
+ .pool_size = 256,
+ .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
+ .dma_dir = DMA_FROM_DEVICE,
+ .max_len = PAGE_SIZE,
+ .nid = NUMA_NO_NODE,
+ .dev = qdma->dev,
+ .napi = &q->napi,
+ };
+ int threshold = clamp(ndesc >> 3, 1, 32);
+ struct qregs_rxring_size rrs;
+ struct qregs_rxring_low rrl;
+ dma_addr_t dma_addr;
+
+ q->buf_size = EN75_MAX_PACKET_SIZE;
+ q->ndesc = ndesc;
+ q->qdma = qdma;
+ q->qchain_regs = (q == &qdma->q_rx[0]) ?
+ &qdma->regs->qchain0 :
+ &qdma->regs->qchain1;
+
+ q->entry = devm_kzalloc(qdma->dev, q->ndesc * sizeof(*q->entry),
+ GFP_KERNEL);
+ if (!q->entry)
+ return -ENOMEM;
+
+ q->page_pool = page_pool_create(&pp_params);
+ if (IS_ERR(q->page_pool)) {
+ int err = PTR_ERR(q->page_pool);
+
+ q->page_pool = NULL;
+ return err;
+ }
+
+ q->desc = dmam_alloc_coherent(qdma->dev, q->ndesc * sizeof(*q->desc),
+ &dma_addr, GFP_KERNEL);
+ if (!q->desc)
+ return -ENOMEM;
+
+ memset(q->desc, 0, sizeof(*q->desc));
+
+ netif_napi_add(qdma->napi_dev, &q->napi, en75_qdma_rx_napi_poll);
+
+ en75_wreg(dma_addr, &q->qchain_regs->rxbase);
+
+ /*
+ * en75_fill_rx_queue fills everything from current cpu index + 1 up to
+ * but excluding end_i, so to fill every entry we run it with 0 to do
+ * 1,2,3,[...],n, then we run it with 1 to do 0.
+ */
+ en75_fill_rx_queue(q, 0);
+ en75_fill_rx_queue(q, 1);
+
+ /*
+ * The RX hardware side considers that hwi == cpui means the queue is
+ * full, not empty. Since cpui starts at 0, we initialize hwi to 1.
+ */
+ en75_wreg(1U, &q->qchain_regs->rx_hwi);
+
+ rrs = en75_rreg(&qdma->regs->rxring_size);
+ rrl = en75_rreg(&qdma->regs->rxring_low);
+
+ if (q == &qdma->q_rx[0]) {
+ set_qregs_rxring_size_ring0(&rrs, ndesc);
+ set_qregs_rxring_low_ring0(&rrl, threshold);
+ } else {
+ set_qregs_rxring_size_ring1(&rrs, ndesc);
+ set_qregs_rxring_low_ring1(&rrl, threshold);
+ }
+
+ en75_wreg(rrs, &qdma->regs->rxring_size);
+ en75_wreg(rrl, &qdma->regs->rxring_low);
+
+ return 0;
+}
+
+static int en75_init_irqs(struct device *dev, struct en75_qdma *qdma,
+ int *irqs, int num_irqs)
+{
+ int i;
+
+ if (num_irqs < ARRAY_SIZE(qdma->irqs))
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(qdma->irqs); i++) {
+ struct en75_irq *irq = &qdma->irqs[i];
+ const char *name;
+ int err;
+ int j;
+
+ spin_lock_init(&irq->lock_irq);
+ irq->qdma = qdma;
+
+ irq->irq = irqs[i];
+ if (irq->irq < 0)
+ return irq->irq;
+
+ for (j = 0; j < QDMA_REGS_PER_IRQ; j++) {
+ irq->status_reg[j] = en751221_irq_status_reg(qdma->regs, j);
+ irq->mask_reg[j] = en751221_irq_enable_reg(qdma->regs, j);
+ }
+
+ name = devm_kasprintf(dev, GFP_KERNEL,
+ KBUILD_MODNAME "-%d.%d", qdma->id, i);
+ if (!name)
+ return -ENOMEM;
+
+ err = devm_request_irq(dev, irq->irq,
+ en75_irq_handler, IRQF_SHARED, name,
+ irq);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int en75_init_tx_doneq(struct en75_tx_doneq *done_q,
+ struct en75_qdma *qdma, int size)
+{
+ struct qregs_doneq_cfg cfg;
+ dma_addr_t dma_addr;
+
+ netif_napi_add_tx(qdma->napi_dev, &done_q->napi,
+ en75_poll_tx_complete);
+ done_q->q = dmam_alloc_coherent(qdma->dev, size * sizeof(u32),
+ &dma_addr, GFP_KERNEL);
+ if (!done_q->q)
+ return -ENOMEM;
+
+ memset(done_q->q, 0xff, size * sizeof(u32));
+ done_q->size = size;
+ done_q->qdma = qdma;
+ done_q->regs = &qdma->regs->done_queue;
+
+ en75_wreg(dma_addr, &qdma->regs->done_queue.address);
+ cfg = en75_rreg(&qdma->regs->done_queue.config);
+ set_qregs_doneq_cfg_size(&cfg, size);
+ set_qregs_doneq_cfg_int_threshold(&cfg, 1);
+ en75_wreg(cfg, &qdma->regs->done_queue.config);
+
+ return 0;
+}
+
+static int en75_init_tx_queue(struct en75_q_tx *q,
+ struct en75_qdma *qdma, int size)
+{
+ int i, qid = q - &qdma->q_tx[0];
+ dma_addr_t dma_addr;
+
+ spin_lock_init(&q->lock_bh);
+ q->ndesc = size;
+ q->qdma = qdma;
+ q->qchain_regs = (qid == 0) ?
+ &qdma->regs->qchain0 :
+ &qdma->regs->qchain1;
+
+ q->entry = devm_kzalloc(qdma->dev, q->ndesc * sizeof(*q->entry),
+ GFP_KERNEL);
+ if (!q->entry)
+ return -ENOMEM;
+
+ q->desc = dmam_alloc_coherent(qdma->dev, q->ndesc * sizeof(*q->desc),
+ &dma_addr, GFP_KERNEL);
+ if (!q->desc)
+ return -ENOMEM;
+
+ memset(q->desc, 0, sizeof(*q->desc));
+
+ for (i = 0; i < q->ndesc - 1; i++)
+ q->entry[i].freelist_next = i + 1;
+
+ q->entry[q->ndesc - 1].freelist_next = 0xffff;
+ q->freelist_tail = q->ndesc - 1;
+ q->freelist_head = 0;
+
+ en75_wreg(dma_addr, &q->qchain_regs->txbase);
+
+ /* On TX, hardware advances hwi until it is equal to cpui. */
+ en75_wreg(0U, &q->qchain_regs->tx_cpui);
+ en75_wreg(0U, &q->qchain_regs->tx_hwi);
+
+ return 0;
+}
+
+static int en75_init_hw_fwd(struct en75_qdma *qdma)
+{
+ enum qregs_hwf_cfg_pkt_sz buf_size_cfg = QREGS_HWF_CFG_PKT_SZ_2048;
+ int ret, size, index, num_desc = qdma->cfg.num_fwd_descs;
+ struct qregs_hwf_cfg1 cfg1;
+ dma_addr_t dma_addr;
+ u32 buf_size = 2048;
+ struct hwf_cfg cfg;
+ const char *name;
+
+ name = devm_kasprintf(qdma->dev, GFP_KERNEL, "qdma%d-buf", qdma->id);
+ if (!name)
+ return -ENOMEM;
+
+ while (buf_size < qdma->cfg.fwd_max_packet_size) {
+ buf_size_cfg++;
+ buf_size = 2048 << buf_size_cfg;
+ if (buf_size > 16384) {
+ dev_err(qdma->dev, "Unsupported hw forwarding max packet size %d\n",
+ qdma->cfg.fwd_max_packet_size);
+ return -EINVAL;
+ }
+ }
+
+ index = of_property_match_string(qdma->dev->of_node,
+ "memory-region-names", name);
+ if (index >= 0) {
+ struct reserved_mem *rmem;
+ struct device_node *np;
+
+ /* Consume reserved memory for hw forwarding buffers queue if
+ * available in the DTS
+ */
+ np = of_parse_phandle(qdma->dev->of_node, "memory-region",
+ index);
+ if (!np)
+ return -ENODEV;
+
+ rmem = of_reserved_mem_lookup(np);
+ of_node_put(np);
+ dma_addr = rmem->base;
+ /* Compute the number of hw descriptors according to the
+ * reserved memory size and the payload buffer size
+ */
+ num_desc = div_u64(rmem->size, buf_size);
+
+ if (num_desc < qdma->cfg.num_fwd_descs)
+ dev_warn(qdma->dev,
+ "Reserved memory %pa too small for %d hw forwarding descriptors with %d bytes payload, reducing to %d descriptors.\n",
+ &rmem->size, qdma->cfg.num_fwd_descs,
+ buf_size, num_desc);
+ } else {
+ size = buf_size * num_desc;
+ if (!dmam_alloc_coherent(qdma->dev, size, &dma_addr,
+ GFP_KERNEL))
+ return -ENOMEM;
+ }
+
+ en75_wreg(dma_addr, &qdma->regs->hwf_data_addr);
+
+ size = num_desc * sizeof(*qdma->hwf_desc);
+ qdma->hwf_desc = dmam_alloc_coherent(qdma->dev, size, &dma_addr, GFP_KERNEL);
+ if (!qdma->hwf_desc)
+ return -ENOMEM;
+
+ en75_wreg(dma_addr, &qdma->regs->hwf_desc_addr);
+
+ cfg = en75_rreg(&qdma->regs->hwf_cfg);
+ set_qregs_hwf_cfg_pkt_sz(&cfg, buf_size_cfg);
+ set_qregs_hwf_cfg_low_th(&cfg, qdma->cfg.fwd_low_threshold);
+ en75_wreg(cfg, &qdma->regs->hwf_cfg);
+
+ cfg1 = en75_rreg(&qdma->regs->hwf_cfg1);
+ set_qregs_hwf_cfg1_fwd_desc_n(&cfg1, num_desc);
+ set_qregs_hwf_cfg1_overhead(&cfg1, 0x14);
+ set_qregs_hwf_cfg1_start(&cfg1, true);
+ en75_wreg(cfg1, &qdma->regs->hwf_cfg1);
+
+ ret = read_poll_timeout(en75_rreg, cfg1,
+ !is_qregs_hwf_cfg1_start(&cfg1), USEC_PER_MSEC,
+ 30 * USEC_PER_MSEC, true,
+ &qdma->regs->hwf_cfg1);
+ if (ret)
+ dev_err(qdma->dev,
+ "Error %pe waiting for HW forwarding engine to start",
+ ERR_PTR(ret));
+ return ret;
+}
+
+static int en75_init_final(struct en75_qdma *qdma)
+{
+ struct qregs_tx_congest_cfg cngst_cfg = {0};
+ struct qregs_qcfg qcfg = {0};
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(qdma->irqs); i++) {
+ int j;
+
+ /* clear pending irqs */
+ for (j = 0; j < ARRAY_SIZE(qdma->irqs[i].status_reg); j++)
+ en75_wreg(0xffffffffU, qdma->irqs[i].status_reg[j]);
+
+ /* enable IRQs */
+ for (j = 0; j < ARRAY_SIZE(qdma->irqs[i].irqmask); j++) {
+ u32 mask = 0;
+
+ for (int k = 0; k < 32; k++) {
+ union en75_irq_purpose p;
+ union irq_bit bit = {
+ .irq_idx = i,
+ .reg_idx = j,
+ .bit_idx = k,
+ };
+ u32 en = 0;
+
+ if (!en751221_valid_irq_bit(bit))
+ continue;
+
+ p = en751221_irq_purpose(bit);
+
+ /* RX and TX done */
+ en |= (p.type == IPS_DONE);
+
+ /* Running out of resources */
+ en |= (p.type == IPS_NO_DSCP && p.source != IPSC_TX);
+ en |= (p.type == IPS_LOW_DSCP && p.source != IPSC_TX);
+
+ /* Error conditions */
+ en |= (p.type == IPS_ERR_COHERENT);
+ en |= (p.type == IPS_OVERFLOW);
+
+ /* External hardware */
+ en |= (p.type == IPS_GPON_INT);
+ en |= (p.type == IPS_EPON_INT);
+ en |= (p.type == IPS_XPON_INT);
+
+ mask |= en << k;
+ }
+
+ qdma->irqs[i].irqmask[j] = mask;
+ en75_wreg(mask, qdma->irqs[i].mask_reg[j]);
+ }
+ }
+
+ set_qregs_qcfg_msg_word_swap(&qcfg, true);
+ set_qregs_qcfg_dscp_byte_swap(&qcfg, qdma->soc->dscp_byte_swap);
+ set_qregs_qcfg_payload_byte_sw(&qcfg, true);
+ set_qregs_qcfg_irq_en(&qcfg, true);
+ set_qregs_qcfg_check_done(&qcfg, true);
+ set_qregs_qcfg_tx_wb_done(&qcfg, true);
+ set_qregs_qcfg_burst_size(&qcfg, QREGS_QCFG_BURST_SIZE_128_BYTES);
+ en75_wreg(qcfg, &qdma->regs->qdma_cfg);
+
+ en75_wreg(0U, &qdma->regs->rx_int_delay);
+
+ set_qregs_tx_congest_cfg_tail_drop_en(&cngst_cfg, true);
+ set_qregs_tx_congest_cfg_dei_drop_en(&cngst_cfg, true);
+ en75_wreg(cngst_cfg, &qdma->regs->tx_congest_cfg);
+
+ return 0;
+}
+
+static int en75_init(struct device *dev,
+ struct en75_qdma *qdma,
+ int *irqs,
+ int num_irqs)
+{
+ int err;
+ int i;
+
+ /* Make sure RX and TX are shutdown */
+ en75_wreg((struct qregs_qcfg) { 0 }, &qdma->regs->qdma_cfg);
+
+ err = en75_init_irqs(dev, qdma, irqs, num_irqs);
+ if (err)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+ err = en75_init_rx_queue(&qdma->q_rx[i], qdma,
+ qdma->cfg.num_rx_descs[i]);
+ if (err)
+ return err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_tx_done); i++) {
+ err = en75_init_tx_doneq(&qdma->q_tx_done[i], qdma,
+ qdma->cfg.done_list_size[i]);
+ if (err)
+ return err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
+ err = en75_init_tx_queue(&qdma->q_tx[i], qdma,
+ qdma->cfg.num_tx_descs[i]);
+ if (err)
+ return err;
+ }
+
+ err = en75_init_hw_fwd(qdma);
+ if (err)
+ return err;
+
+ return en75_init_final(qdma);
+}
+
+/* End init functions */
+
+static void en75_qdma_destroy_rxq_locked(struct en75_q_rx *q)
+{
+ int i;
+
+ if (!q->page_pool)
+ return;
+
+ for (i = 0; i < q->ndesc; i++) {
+ struct en75_q_rx_ent *e = &q->entry[i];
+ struct page *page = virt_to_head_page(e->buf);
+
+ if (WARN_ON_ONCE(!e->dma_addr))
+ continue;
+
+ dma_sync_single_for_cpu(q->qdma->dev, e->dma_addr, e->dma_len,
+ page_pool_get_dma_dir(q->page_pool));
+ page_pool_put_full_page(q->page_pool, page, false);
+ }
+
+ page_pool_destroy(q->page_pool);
+ q->page_pool = NULL;
+}
+
+static int en75_qdma_destroy_locked(struct en75_qdma *qdma)
+{
+ int i;
+
+ if (qdma->users) {
+ qdma->destroying = true;
+ return 0;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+ struct en75_q_rx *q = &qdma->q_rx[i];
+
+ en75_qdma_destroy_rxq_locked(q);
+ netif_napi_del(&q->napi);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_tx_done); i++) {
+ struct en75_tx_doneq *q = &qdma->q_tx_done[i];
+
+ if (q->napi.dev)
+ netif_napi_del(&q->napi);
+ }
+
+ if (qdma->napi_dev)
+ free_netdev(qdma->napi_dev);
+
+ return 0;
+}
+
+int en75_qdma_destroy(struct en75_qdma *qdma)
+{
+ guard(mutex)(&qdma->lock);
+ return en75_qdma_destroy_locked(qdma);
+}
+
+int en75_qdma_use(struct en75_qdma *qdma)
+{
+ struct qregs_qcfg qcfg;
+ int i;
+
+ guard(mutex)(&qdma->lock);
+
+ if (WARN_ON_ONCE(qdma->destroying))
+ return -EINVAL;
+
+ if (qdma->users++ > 0)
+ return 0;
+
+ qcfg = en75_rreg(&qdma->regs->qdma_cfg);
+ set_qregs_qcfg_rx_dma_en(&qcfg, true);
+ set_qregs_qcfg_tx_dma_en(&qcfg, true);
+ en75_wreg(qcfg, &qdma->regs->qdma_cfg);
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++)
+ napi_enable(&qdma->q_rx[i].napi);
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_tx_done); i++)
+ napi_enable(&qdma->q_tx_done[i].napi);
+
+ return 0;
+}
+
+int en75_qdma_unuse(struct en75_qdma *qdma)
+{
+ struct qregs_qcfg qcfg;
+ int i, j;
+
+ guard(mutex)(&qdma->lock);
+
+ if (--qdma->users > 0)
+ return 0;
+
+ qcfg = en75_rreg(&qdma->regs->qdma_cfg);
+ set_qregs_qcfg_rx_dma_en(&qcfg, false);
+ set_qregs_qcfg_tx_dma_en(&qcfg, false);
+ en75_wreg(qcfg, &qdma->regs->qdma_cfg);
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++)
+ napi_disable(&qdma->q_rx[i].napi);
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_tx_done); i++)
+ napi_disable(&qdma->q_tx_done[i].napi);
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
+ struct en75_q_tx *q = &qdma->q_tx[i];
+
+ guard(spinlock_bh)(&q->lock_bh);
+ for (j = 0; j < q->ndesc; j++) {
+ struct en75_q_tx_ent *e = &q->entry[j];
+
+ /* In the free list already */
+ if (!e->dma_addr)
+ continue;
+
+ dma_unmap_single(qdma->dev, e->dma_addr, e->dma_len,
+ DMA_TO_DEVICE);
+ dev_kfree_skb_any(e->skb);
+ memset(e, 0, sizeof(*e));
+
+ e->freelist_next = 0xffff;
+ q->entry[q->freelist_tail].freelist_next = j;
+ q->freelist_tail = j;
+ }
+ }
+
+ if (qdma->destroying)
+ return en75_qdma_destroy_locked(qdma);
+
+ return 0;
+}
+
+struct en75_qdma *en75_qdma_new(struct en75_eth *eth,
+ void __iomem *qdma_regs,
+ int id,
+ int *irqs,
+ int num_irqs,
+ struct en75_qdma_cfg *cfg,
+ struct en75_debug_qdma_conf *debug)
+{
+ struct en75_qdma *qdma;
+ int err;
+ int i;
+
+ qdma = devm_kzalloc(eth->dev, sizeof(*qdma), GFP_KERNEL);
+ if (!qdma)
+ return ERR_PTR(-ENOMEM);
+
+ qdma->dev = eth->dev;
+ qdma->id = id;
+ mutex_init(&qdma->lock);
+ qdma->regs = qdma_regs;
+ memcpy(&qdma->cfg, cfg, sizeof(*cfg));
+ qdma->soc = cfg->soc;
+ qdma->eth = eth;
+
+ qdma->napi_dev = alloc_netdev_dummy(0);
+ if (!qdma->napi_dev)
+ return ERR_PTR(-ENOMEM);
+
+ qdma->napi_dev->threaded = true;
+ snprintf(qdma->napi_dev->name, sizeof(qdma->napi_dev->name), "qdma%d_eth", id);
+
+ err = en75_init(eth->dev, qdma, irqs, num_irqs);
+ if (err) {
+ en75_qdma_destroy(qdma);
+ return ERR_PTR(err);
+ }
+
+ if (!debug)
+ return qdma;
+
+ debug->regs = qdma_regs;
+ debug->hwf_desc = qdma->hwf_desc;
+ BUILD_BUG_ON(ARRAY_SIZE(qdma->q_rx) != ARRAY_SIZE(debug->chains));
+ BUILD_BUG_ON(ARRAY_SIZE(qdma->q_tx) != ARRAY_SIZE(debug->chains));
+ for (i = 0; i < ARRAY_SIZE(debug->chains); i++) {
+ u32 *cntr;
+
+ cntr = devm_kzalloc(eth->dev, sizeof(*cntr) *
+ EN75_NUM_CHANNELS * EN75_NUM_QUEUES,
+ GFP_KERNEL);
+ debug->chains[i].tx_per_ch_q = cntr;
+ qdma->q_tx[i].debug_tx_cnt = cntr;
+
+ debug->chains[i].rx_descs = qdma->q_rx[i].desc;
+ debug->chains[i].rx_count = qdma->q_rx[i].ndesc;
+ debug->chains[i].tx_descs = qdma->q_tx[i].desc;
+ debug->chains[i].tx_count = qdma->q_tx[i].ndesc;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(debug->irqs); i++) {
+ struct en75_debug_qdma_irq_conf *cnf;
+ u32 *cntr;
+ int j;
+
+ cnf = &debug->irqs[i];
+ cntr = devm_kzalloc(eth->dev, sizeof(*cntr) *
+ ARRAY_SIZE(cnf->desc), GFP_KERNEL);
+ cnf->counters = cntr;
+ qdma->irqs[i].debug_int_cnt = cntr;
+
+ for (j = 0; j < ARRAY_SIZE(cnf->desc); j++) {
+ union irq_bit bit = {
+ .irq_idx = i,
+ .reg_idx = j / 32,
+ .bit_idx = j % 32,
+ };
+
+ if (en751221_valid_irq_bit(bit))
+ cnf->desc[j] = en751221_irq_purpose(bit);
+ }
+ }
+
+ return qdma;
+}
+
diff --git a/drivers/net/ethernet/econet/gdm_regs.h b/drivers/net/ethernet/econet/gdm_regs.h
new file mode 100644
index 000000000000..19e3079a42e4
--- /dev/null
+++ b/drivers/net/ethernet/econet/gdm_regs.h
@@ -0,0 +1,1514 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef GDM_REGS_H
+#define GDM_REGS_H
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/types.h>
+
+#ifndef FIELD_SET
+#define FIELD_SET(current, mask, val) \
+ (((current) & ~(mask)) | FIELD_PREP((mask), (val)))
+#endif
+
+/** gdm: GDM Registers */
+struct gdm {
+ /** gdm_vlan: VLAN tag control */
+ struct gdm_vlan {
+ /** gdm_vlan_tpid: If inserting a MTK Special Tag, this TPID will be used */
+ u16 tpid;
+
+ /**
+ * See accessors:
+ * is_gdm_vlan_urx()
+ * set_gdm_vlan_urx()
+ * is_gdm_vlan_ttx()
+ * set_gdm_vlan_ttx()
+ */
+ u16 bitfield_0;
+
+ } vlan;
+
+ /** gdm_pppoe: PPPoE header control */
+ struct gdm_pppoe {
+ /**
+ * See accessors:
+ * is_gdm_pppoe_ttx()
+ * set_gdm_pppoe_ttx()
+ */
+ u16 bitfield_0;
+
+ /** gdm_pppoe_pppid: PPPoE Session ID to insert on transmitted packets */
+ u16 pppoe_id;
+
+ } pppoe;
+
+ /**
+ * See accessors:
+ * get_gdm_red_cp()
+ * set_gdm_red_cp()
+ * get_gdm_red_lp()
+ * set_gdm_red_lp()
+ * get_gdm_red_hp()
+ * set_gdm_red_hp()
+ * get_gdm_red_dscp_rxm_sz()
+ * set_gdm_red_dscp_rxm_sz()
+ */
+ struct red_drop_criteria { u32 word; } red_drop_criteria;
+
+ /**
+ * gdm_chan_en: Each bit corrisponds to a channel, if one bit is cleared then
+ * this GDM will refuse to forward traffic from that channel number.
+ */
+ u32 channels_enabled;
+
+ /**
+ * gdm_crsn_prio_lo: Priority of a packet based on the CPU_REASON for packet
+ * being sent to CPU. Each CPU_REASON corrisponds to 2 bits, 00 = crit_prio, 01
+ * = low_prio, 10 = high_prio. This word covers CPU_REASONS 0..15
+ */
+ u32 cpu_reason_priority_lo;
+
+ /**
+ * gdm_crsn_prio_hi: Priority of a packet based on the CPU_REASON for packet
+ * being sent to CPU. Each CPU_REASON corrisponds to 2 bits, 00 = crit_prio, 01
+ * = low_prio, 10 = high_prio. This word covers CPU_REASONS 16..31
+ */
+ u32 cpu_reason_priority_hi;
+
+ u8 unused_0[232];
+
+ /**
+ * See accessors:
+ * is_gdm_fwd_cfg_vip()
+ * set_gdm_fwd_cfg_vip()
+ * is_gdm_fwd_cfg_l2lu_dcsp_2cpu()
+ * set_gdm_fwd_cfg_l2lu_dcsp_2cpu()
+ * is_gdm_fwd_cfg_l2lu_ctag_2cpu()
+ * set_gdm_fwd_cfg_l2lu_ctag_2cpu()
+ * is_gdm_fwd_cfg_l2lu_stag_2cpu()
+ * set_gdm_fwd_cfg_l2lu_stag_2cpu()
+ * is_gdm_fwd_cfg_g2_underrun_retry()
+ * set_gdm_fwd_cfg_g2_underrun_retry()
+ * is_gdm_fwd_cfg_g2_drop_256b()
+ * set_gdm_fwd_cfg_g2_drop_256b()
+ * is_gdm_fwd_cfg_drop_oversize()
+ * set_gdm_fwd_cfg_drop_oversize()
+ * is_gdm_fwd_cfg_drop_runt()
+ * set_gdm_fwd_cfg_drop_runt()
+ * is_gdm_fwd_cfg_drop_crc()
+ * set_gdm_fwd_cfg_drop_crc()
+ * is_gdm_fwd_cfg_drop_ip4_csum()
+ * set_gdm_fwd_cfg_drop_ip4_csum()
+ * is_gdm_fwd_cfg_drop_tcp_csum()
+ * set_gdm_fwd_cfg_drop_tcp_csum()
+ * is_gdm_fwd_cfg_drop_ucp_csum()
+ * set_gdm_fwd_cfg_drop_ucp_csum()
+ * is_gdm_fwd_cfg_g2_favor_oam()
+ * set_gdm_fwd_cfg_g2_favor_oam()
+ * is_gdm_fwd_cfg_l2lu_brg_cpu()
+ * set_gdm_fwd_cfg_l2lu_brg_cpu()
+ * is_gdm_fwd_cfg_l2lu_unbrg_cpu()
+ * set_gdm_fwd_cfg_l2lu_unbrg_cpu()
+ * is_gdm_fwd_cfg_strip_crc()
+ * set_gdm_fwd_cfg_strip_crc()
+ * get_gdm_fwd_cfg_mymac_fport()
+ * set_gdm_fwd_cfg_mymac_fport()
+ * get_gdm_fwd_cfg_bcast_fport()
+ * set_gdm_fwd_cfg_bcast_fport()
+ * get_gdm_fwd_cfg_mcast_fport()
+ * set_gdm_fwd_cfg_mcast_fport()
+ * get_gdm_fwd_cfg_default_fport()
+ * set_gdm_fwd_cfg_default_fport()
+ */
+ struct fwd_cfg { u32 word; } fwd_cfg;
+
+ /** gdm_tx_shaper: Traffic shaper for TX */
+ u32 tx_shaper;
+
+ /**
+ * gdm_mymac_lsb: Mac address bytes [c,d,e,f] (bytes 2-5 of MAC)
+ * Register layout (32-bit word):
+ * - Bits 31-24: MAC byte c (index 2)
+ * - Bits 23-16: MAC byte d (index 3)
+ * - Bits 15-8: MAC byte e (index 4)
+ * - Bits 7-0: MAC byte f (index 5)
+ * See accessors:
+ * get_gdm_mymac_lsb_c/d/e/f()
+ * set_gdm_mymac_lsb_c/d/e/f()
+ */
+ struct gdm_mymac_lsb { u32 word; } mymac_lsb;
+
+ /**
+ * gdm_mymac_msb: Mac address bytes [a,b] (bytes 0-1 of MAC) and mask
+ * Register layout (32-bit word):
+ * - Bits 31-24: unused
+ * - Bits 23-16: lsb_mask (LSB matching mask)
+ * - Bits 15-8: MAC byte a (index 0)
+ * - Bits 7-0: MAC byte b (index 1)
+ * See accessors:
+ * get_gdm_mymac_msb_lsb_mask/a/b()
+ * set_gdm_mymac_msb_lsb_mask/a/b()
+ */
+ struct gdm_mymac_msb { u32 word; } mymac_msb;
+
+ /** gdm_stag_en: Add Special Tag on RX frames, 0 or 1 */
+ u32 stag_en;
+
+ /**
+ * gdm_len_th:
+ * Register layout (32-bit word):
+ * - Bits 31-16: OVERSIZE_LEN (packet larger than this is oversize, max 0x3f00)
+ * - Bits 15-0: RUNT_LEN (packet smaller than this is a runt)
+ * See accessors:
+ * get_gdm_len_th_oversize_len()
+ * set_gdm_len_th_oversize_len()
+ * get_gdm_len_th_runt_len()
+ * set_gdm_len_th_runt_len()
+ */
+ struct gdm_len_th { u32 word; } rx_len_threshold;
+
+ /**
+ * See accessors:
+ * get_gdm_pcp_gdm_rx()
+ * set_gdm_pcp_gdm_rx()
+ * get_gdm_pcp_gdm_tx()
+ * set_gdm_pcp_gdm_tx()
+ * get_gdm_pcp_cdm_rx()
+ * set_gdm_pcp_cdm_rx()
+ * get_gdm_pcp_cdm_tx()
+ * set_gdm_pcp_cdm_tx()
+ */
+ struct pcp { u32 word; } pcp;
+
+ /**
+ * See accessors:
+ * get_gdm_lpbk_gap()
+ * set_gdm_lpbk_gap()
+ * get_gdm_lpbk_len()
+ * set_gdm_lpbk_len()
+ * get_gdm_lpbk_chan()
+ * set_gdm_lpbk_chan()
+ * is_gdm_lpbk_gap_mode()
+ * set_gdm_lpbk_gap_mode()
+ * is_gdm_lpbk_len_mode()
+ * set_gdm_lpbk_len_mode()
+ * is_gdm_lpbk_chan_mode()
+ * set_gdm_lpbk_chan_mode()
+ * is_gdm_lpbk_enabled()
+ * set_gdm_lpbk_enabled()
+ */
+ struct loopback { u32 word; } loopback;
+
+ /**
+ * See accessors:
+ * get_gdm_channel_retire_channel()
+ * set_gdm_channel_retire_channel()
+ * is_gdm_channel_retire_done()
+ * set_gdm_channel_retire_done()
+ * is_gdm_channel_retire_release()
+ * set_gdm_channel_retire_release()
+ */
+ struct channel_retire { u32 word; } channel_retire;
+
+ /**
+ * gdm_tx_ch_en: Each bit corripsonds to one of the 32 channels, set bit to
+ * enable channel
+ */
+ u32 enabled_tx_channels;
+
+ /**
+ * gdm_rx_ch_en: Each bit 0..16 corrisonds to one of the 16 RX channels, upper
+ * 16 bits are unused
+ */
+ u32 enabled_rx_channels;
+
+ /**
+ * See accessors:
+ * is_gdm_rx7_en()
+ * set_gdm_rx7_en()
+ * get_gdm_rx7_fport()
+ * set_gdm_rx7_fport()
+ */
+ u32 bitfield_0;
+
+ /** gdm_g2_rx_shapers: Incoming traffic shapers, only present in GDM2 */
+ struct gdm_g2_rx_shapers {
+ /** gdm_g2_rx_shapers_mymac: Shaper for incoming traffic to my MAC */
+ u32 mymac;
+
+ /** gdm_g2_rx_shapers_bcast: Shaper for incoming broadcast traffic */
+ u32 bcast;
+
+ /** gdm_g2_rx_shapers_mcast: Shaper for incoming multicast traffic */
+ u32 mcast;
+
+ /**
+ * gdm_g2_rx_shapers_dflt: Default shaper for incoming traffic not otherwise
+ * categorized
+ */
+ u32 dflt;
+
+ } g2_rx_shapers;
+
+ /**
+ * See accessors:
+ * is_gdm_g1_cport_cfg_add_crc()
+ * set_gdm_g1_cport_cfg_add_crc()
+ * is_gdm_g1_cport_cfg_pad()
+ * set_gdm_g1_cport_cfg_pad()
+ * is_gdm_g1_cport_cfg_port_xfc()
+ * set_gdm_g1_cport_cfg_port_xfc()
+ * is_gdm_g1_cport_cfg_queue_xfc()
+ * set_gdm_g1_cport_cfg_queue_xfc()
+ * get_gdm_g1_cport_cfg_unknown()
+ * set_gdm_g1_cport_cfg_unknown()
+ */
+ struct g1_cport_cfg { u32 word; } g1_cport_cfg;
+
+ /**
+ * gdm_g1_cport_channel_map: Called FE_CPORT_CHN_MAP, never used, observed
+ * value is 0x76543210, GDM1 only
+ */
+ u32 g1_cport_channel_map;
+
+ /** gdm_g1_cport_shaper: Traffic shaper for CPORT */
+ u32 g1_cport_shaper;
+
+ /**
+ * gdm_g1_unknown0: Unused, no symbol, observed value 0x000003c0, GDM1 only.
+ */
+ u32 g1_unknown0;
+
+ /**
+ * gdm_g1_unknown1: Unused, no symbol, observed value 0x00000000, GDM1 only.
+ */
+ u32 g1_unknown1;
+
+ u8 unused_1[28];
+
+ /**
+ * gdm_tx_chan_active: Each of the 32 bits represents one of the 32 channels, 1
+ * means channel is active used with channel_retire to confirm channel is
+ * shutdown. Also GDMA1_TX_CHN_VLD
+ */
+ u32 tx_chan_active;
+
+ /**
+ * gdm_rx_chan_active: Unused in practice except on EN7580, each bit represents
+ * an RX channel, 1 means RX channel is active, used with channel_retire.
+ * Unsure if 16 or 32 RX channels. Also GDMA1_RX_CHN_VLD
+ */
+ u32 rx_chan_active;
+
+ u8 unused_2[8];
+
+ /** gdm_cdm_counters: Various packet counters for CDM1 / CDM2 */
+ struct gdm_cdm_counters {
+ /**
+ * gdm_cdm_counters_tx: Unused in practice, seems to be frames successfully
+ * sent by CDM, also CDMA1_TX_OK_CNT
+ */
+ u32 tx;
+
+ u8 unused_0[12];
+
+ /**
+ * gdm_cdm_counters_rxcpu: Unused in practice, seems to be frames successfully
+ * received to CPU by CDM, also CDMA1_RXCPU_OK_CNT
+ */
+ u32 rxcpu;
+
+ /**
+ * gdm_cdm_counters_rxhwf: Unused in practice, seems to be frames successfully
+ * received to hardware forwarding chain by CDM, also CDMA1_RXHWF_OK_CNT
+ */
+ u32 rxhwf;
+
+ /**
+ * gdm_cdm_counters_ka: Unused in practice, seems to be frames sent to CPU for
+ * NAT keepalive purposes, also CDMA1_RXCPU_KA_CNT
+ */
+ u32 ka;
+
+ u32 unused_1;
+
+ /**
+ * gdm_cdm_counters_rxcpu_drop: Unused in practice, seems to be frames dropped
+ * for congestion on the RX->CPU chain, also CDMA1_RXCPU_DROP_CNT
+ */
+ u32 rxcpu_drop;
+
+ /**
+ * gdm_cdm_counters_rxhwf_drop: Unused in practice, seems to be frames dropped
+ * for congestion on the RX->Hardware Forwarding chain, also
+ * CDMA1_RXHWF_DROP_CNT
+ */
+ u32 rxhwf_drop;
+
+ /**
+ * gdm_cdm_counters_unknown0: Unused, no symbol, observed value matches rxcpu
+ */
+ u32 unknown0;
+
+ /**
+ * gdm_cdm_counters_unknown1: Unused, no symbol, observed value 0x00000000
+ */
+ u32 unknown1;
+
+ /**
+ * gdm_cdm_counters_unknown2: Unused, no symbol, observed value 0x00000000
+ */
+ u32 unknown2;
+
+ /**
+ * gdm_cdm_counters_unknown3: Unused, no symbol, observed value 0x00000000
+ */
+ u32 unknown3;
+
+ /**
+ * gdm_cdm_counters_unknown4: Unused, no symbol, observed value 0x00000000
+ */
+ u32 unknown4;
+
+ /**
+ * gdm_cdm_counters_unknown5: Unused, no symbol, observed value 0x00000000
+ */
+ u32 unknown5;
+
+ } cdm_counters;
+
+ u8 unused_3[48];
+
+ /**
+ * See accessors:
+ * set_gdm_cl_cnt_rx()
+ * set_gdm_cl_cnt_tx()
+ */
+ struct clear_counters { u32 word; } clear_counters;
+
+ u8 unused_4[12];
+
+ /** gdm_counters: Packet and byte counters for GDM port */
+ struct gdm_counters {
+ /** gdm_counters_tx: */
+ struct gdm_counters_tx {
+ /**
+ * gdm_counters_tx_get: Unused in practice, believed to be number of packets
+ * enregistered for TX, also GDMA1_TX_GET_CNT / GDMA2_TX_GETCNT
+ */
+ u32 get;
+
+ /**
+ * gdm_counters_tx_pkts: Packets successfully transmitted, also
+ * GDMA1_TX_OK_CNT / GDMA2_TX_OKCNT
+ */
+ u32 tx_pkts;
+
+ /**
+ * gdm_counters_tx_drops: Packets dropped on transmission chain, also
+ * GDMA1_TX_DROP_CNT / GDMA2_TX_DROPCNT
+ */
+ u32 drops;
+
+ /**
+ * gdm_counters_tx_bytes: Bytes successfully transmitted, also
+ * GDMA1_TX_OK_BYTE_CNT / GDMA2_TX_OKBYTE_CNT
+ */
+ u32 bytes;
+
+ /** gdm_counters_tx_g2: GDM2 only extended TX stats */
+ struct gdm_counters_tx_g2 {
+ /**
+ * gdm_counters_tx_g2_epkts:
+ * Number of eth frames transmitted, should match .pkts
+ * also GDMA2_TX_ETHCNT
+ */
+ u32 tx_epkts;
+
+ /**
+ * gdm_counters_tx_g2_ebytes:
+ * Bytes of eth bytes transmitted
+ * also GDMA2_TX_ETHLENCNT
+ */
+ u32 tx_ebytes;
+
+ /**
+ * gdm_counters_tx_g2_edrops:
+ * Number of eth frames dropped
+ * see GDMA2_TX_ETHDROPCNT
+ */
+ u32 edrops;
+
+ /**
+ * gdm_counters_tx_g2_bcast:
+ * Number of broadcast frames transmitted
+ * see GDMA2_TX_ETHBCDCNT
+ */
+ u32 tx_bcast;
+
+ /**
+ * gdm_counters_tx_g2_mcast:
+ * Number of multicast frames transmitted
+ * see GDMA2_TX_ETHMULTICASTCNT
+ */
+ u32 tx_mcast;
+
+ /**
+ * gdm_counters_tx_g2_f_less_64:
+ * Counter of TX frames of length less than 64
+ * see GDMA2_TX_ETH_LESS64_CNT
+ */
+ u32 f_less_64;
+
+ /**
+ * gdm_counters_tx_g2_f_more_1518:
+ * Counter of TX frames of length more than 1518
+ * see GDMA2_TX_ETH_MORE1518_CNT
+ */
+ u32 f_more_1518;
+
+ /**
+ * gdm_counters_tx_g2_f_64:
+ * Counter of TX frames of length 64
+ * see GDMA2_TX_ETH_64_CNT
+ */
+ u32 f_64;
+
+ /**
+ * gdm_counters_tx_g2_f_65_127:
+ * Counter of TX frames of length 65-127
+ * see GDMA2_TX_ETH_65_TO_127_CNT
+ */
+ u32 f_65_127;
+
+ /**
+ * gdm_counters_tx_g2_f_128_255:
+ * Counter of TX frames of length 128-255
+ * see GDMA2_TX_ETH_128_TO_255_CNT
+ */
+ u32 f_128_255;
+
+ /**
+ * gdm_counters_tx_g2_f_256_511:
+ * Counter of TX frames of length 256-511
+ * see GDMA2_TX_ETH_256_TO_511_CNT
+ */
+ u32 f_256_511;
+
+ /**
+ * gdm_counters_tx_g2_f_512_1023:
+ * Counter of TX frames of length 512-1023,
+ * see GDMA2_TX_ETH_512_TO_1023_CNT
+ */
+ u32 f_512_1023;
+
+ /**
+ * gdm_counters_tx_g2_f_1024_1518:
+ * Counter of TX frames of length 1024-1518
+ * see GDMA2_TX_ETH_1024_TO_1518_CNT
+ */
+ u32 f_1024_1518;
+
+ } g2;
+
+ } tx;
+
+ u32 unused_0;
+
+ /** gdm_counters_rx: */
+ struct gdm_counters_rx {
+ /**
+ * gdm_counters_rx_pkts: Number of packets received, see GDMA1_RX_OK_CNT /
+ * GDMA2_RX_OKCNT
+ */
+ u32 pkts;
+
+ /**
+ * gdm_counters_rx_drops_fc: Packets dropped due to flow control, thought to
+ * be when port sends PAUSE signal. see GDMA1_RX_FC_DROP_CNT
+ */
+ u32 drops_fc;
+
+ /**
+ * gdm_counters_rx_drops_rc: Packets dropped due to rate control, thought to
+ * be shapers. See GDMA1_RX_RC_DROP_CNT / GDMA2_RX_RCDROPCNT
+ */
+ u32 drops_rc;
+
+ /**
+ * gdm_counters_rx_drops_overflow: Packets dropped because we ran out of
+ * queue resources, vendor code logs when this counter increases so it
+ * should not happen. GDMA2_RX_OVDROPCNT / GDMA1_RX_OVER_DROP_CNT
+ */
+ u32 drops_overflow;
+
+ /**
+ * gdm_counters_rx_drops_err: Packets dropped because of any error (crc,
+ * IP/TCP/UDP checksum, oversize, runt, etc)
+ */
+ u32 drops_err;
+
+ /**
+ * gdm_counters_rx_bytes: Received bytes, see GDMA1_RX_BYTECNT /
+ * GDMA2_RX_OKBYTECNT
+ */
+ u32 bytes;
+
+ /** gdm_counters_rx_g2: GDM2 only extended RX stats */
+ struct gdm_counters_rx_g2 {
+ /**
+ * gdm_counters_rx_g2_epkts:
+ * Number of eth frames received, should match .pkts
+ * also GDMA2_RX_ETHERPCNT
+ */
+ u32 epkts;
+
+ /**
+ * gdm_counters_rx_g2_ebytes:
+ * Bytes of eth bytes received
+ * also GDMA2_RX_ETHERPLEN
+ */
+ u32 ebytes;
+
+ /**
+ * gdm_counters_rx_g2_edrops:
+ * Number of eth frames dropped
+ * see GDMA2_RX_ETHDROPCNT
+ */
+ u32 edrops;
+
+ /**
+ * gdm_counters_rx_g2_bcast:
+ * Number of broadcast frames received
+ * see GDMA2_RX_ETHBCCNT
+ */
+ u32 bcast;
+
+ /**
+ * gdm_counters_rx_g2_mcast:
+ * Number of multicast frames received
+ * see GDMA2_RX_ETHMCCNT
+ */
+ u32 mcast;
+
+ /**
+ * gdm_counters_rx_g2_ecrc:
+ * Number of frames with CRC errors
+ * see GDMA2_RX_ETHCRCCNT
+ */
+ u32 ecrc;
+
+ /**
+ * gdm_counters_rx_g2_efrag:
+ * Number of frames with CRC error and which are less than 64 bytes
+ * probably fragments from synchronization issue
+ * see GDMA2_RX_ETHFRACCNT
+ */
+ u32 efrag;
+
+ /**
+ * gdm_counters_rx_g2_ejabber:
+ * Number of frames longer than 1518 with bad CRC
+ * sign of potentially jabbering hardware
+ * see GDMA2_RX_ETHJABCNT
+ */
+ u32 ejabber;
+
+ /**
+ * gdm_counters_rx_g2_f_less_64:
+ * Counter of TX frames of length less than 64
+ * see GDMA2_RX_ETHRUNTCNT
+ */
+ u32 f_less_64;
+
+ /**
+ * gdm_counters_rx_g2_f_more_1518:
+ * Counter of TX frames of length more than 1518
+ * see GDMA2_RX_ETHLONGCNT
+ */
+ u32 f_more_1518;
+
+ /**
+ * gdm_counters_rx_g2_f_64:
+ * Counter of TX frames of length 64
+ * see GDMA2_RX_ETH_64_CNT
+ */
+ u32 f_64;
+
+ /**
+ * gdm_counters_rx_g2_f_65_127:
+ * Counter of TX frames of length 65-127
+ * see GDMA2_RX_ETH_65_TO_127_CNT
+ */
+ u32 f_65_127;
+
+ /**
+ * gdm_counters_rx_g2_f_128_255:
+ * Counter of TX frames of length 128-255
+ * see GDMA2_RX_ETH_128_TO_255_CNT
+ */
+ u32 f_128_255;
+
+ /**
+ * gdm_counters_rx_g2_f_256_511:
+ * Counter of TX frames of length 256-511
+ * see GDMA2_RX_ETH_256_TO_511_CNT
+ */
+ u32 f_256_511;
+
+ /**
+ * gdm_counters_rx_g2_f_512_1023:
+ * Counter of TX frames of length 512-1023,
+ * see GDMA2_RX_ETH_512_TO_1023_CNT
+ */
+ u32 f_512_1023;
+
+ /**
+ * gdm_counters_rx_g2_f_1024_1518:
+ * Counter of TX frames of length 1024-1518
+ * see GDMA2_RX_ETH_1024_TO_1518_CNT
+ */
+ u32 f_1024_1518;
+
+ /**
+ * gdm_counters_rx_g2_unknown0: Observed 0x00000000
+ * possible counter
+ */
+ u32 tx_unknown0;
+
+ /**
+ * gdm_counters_rx_g2_unknown1: Observed 0x00000000
+ * possible counter
+ */
+ u32 unknown1;
+
+ } g2;
+
+ } rx;
+
+ } counters;
+
+};
+
+/**
+ * Bitfield accessors for: gdm_vlan bitfield_0
+ */
+
+#define GDM_VLAN_URX BIT(1)
+#define GDM_VLAN_TTX BIT(0)
+
+/** Untag incoming tagged packets (and put the tag in the DESC) */
+static inline bool is_gdm_vlan_urx(struct gdm_vlan *x)
+{
+ return FIELD_GET(GDM_VLAN_URX, x->bitfield_0);
+}
+
+static inline void set_gdm_vlan_urx(struct gdm_vlan *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, GDM_VLAN_URX, v);
+}
+
+/** Insert MTK Special Tag when sending packets */
+static inline bool is_gdm_vlan_ttx(struct gdm_vlan *x)
+{
+ return FIELD_GET(GDM_VLAN_TTX, x->bitfield_0);
+}
+
+static inline void set_gdm_vlan_ttx(struct gdm_vlan *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, GDM_VLAN_TTX, v);
+}
+
+/**
+ * Bitfield accessors for: gdm_pppoe bitfield_0
+ */
+
+#define GDM_PPPOE_TTX BIT(0)
+
+/** Insert PPPoE header on transmitted packets */
+static inline bool is_gdm_pppoe_ttx(struct gdm_pppoe *x)
+{
+ return FIELD_GET(GDM_PPPOE_TTX, x->bitfield_0);
+}
+
+static inline void set_gdm_pppoe_ttx(struct gdm_pppoe *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, GDM_PPPOE_TTX, v);
+}
+
+/**
+ * Bitfield accessors for: struct red_drop_criteria
+ */
+
+enum gdm_red_cp {
+ GDM_RED_CP_RL_OR_FULL = 0,
+ GDM_RED_CP_RL_OR_TH = 1,
+ GDM_RED_CP_RL_AND_FULL = 2,
+ GDM_RED_CP_RL_AND_TH = 3,
+};
+
+enum gdm_red_dscp_rxm_sz {
+ GDM_RED_DSCP_RXM_SZ_4B = 0,
+ GDM_RED_DSCP_RXM_SZ_8B = 1,
+ GDM_RED_DSCP_RXM_SZ_16B = 2,
+ GDM_RED_DSCP_RXM_SZ_32B = 3,
+};
+
+#define GDM_RED_CP_MASK GENMASK(31, 30)
+#define GDM_RED_LP_MASK GENMASK(29, 28)
+#define GDM_RED_HP_MASK GENMASK(27, 26)
+#define GDM_RED_DSCP_RXM_SZ_MASK GENMASK(25, 24)
+
+/**
+ * Trigger RED packet dropping on 1. Rate-limit-violation OR buffer is full 2.
+ * Rate-limit-violation OR buffer has surpassed threshold 3.
+ * Rate-limit-violation WHEN buffer is also full 4. Rate-limit-violation WHEN
+ * buffer has surpassed threshold For critical priority packets (VIP)
+ */
+static inline enum gdm_red_cp get_gdm_red_cp(struct red_drop_criteria *x)
+{
+ return FIELD_GET(GDM_RED_CP_MASK, x->word);
+}
+
+static inline void set_gdm_red_cp(struct red_drop_criteria *x, enum gdm_red_cp v)
+{
+ x->word = FIELD_SET(x->word, GDM_RED_CP_MASK, v);
+}
+
+/** Same as crit_prio, for packets classed as low priority by CPU Reason */
+static inline u8 get_gdm_red_lp(struct red_drop_criteria *x)
+{
+ return FIELD_GET(GDM_RED_LP_MASK, x->word);
+}
+
+static inline void set_gdm_red_lp(struct red_drop_criteria *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_RED_LP_MASK, v);
+}
+
+/** Same as crit_prio, for packets classed as low priority by CPU Reason */
+static inline u8 get_gdm_red_hp(struct red_drop_criteria *x)
+{
+ return FIELD_GET(GDM_RED_HP_MASK, x->word);
+}
+
+static inline void set_gdm_red_hp(struct red_drop_criteria *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_RED_HP_MASK, v);
+}
+
+/** Size of the msg field within the packet descriptor */
+static inline enum gdm_red_dscp_rxm_sz get_gdm_red_dscp_rxm_sz(struct red_drop_criteria *x)
+{
+ return FIELD_GET(GDM_RED_DSCP_RXM_SZ_MASK, x->word);
+}
+
+static inline void set_gdm_red_dscp_rxm_sz(struct red_drop_criteria *x, enum gdm_red_dscp_rxm_sz v)
+{
+ x->word = FIELD_SET(x->word, GDM_RED_DSCP_RXM_SZ_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: struct fwd_cfg
+ * Configuration for GDM
+ */
+
+#define GDM_FWD_CFG_VIP BIT(31)
+#define GDM_FWD_CFG_L2LU_DCSP_2CPU BIT(30)
+#define GDM_FWD_CFG_L2LU_CTAG_2CPU BIT(29)
+#define GDM_FWD_CFG_L2LU_STAG_2CPU BIT(28)
+#define GDM_FWD_CFG_G2_UNDERRUN_RETRY BIT(27)
+#define GDM_FWD_CFG_G2_DROP_256B BIT(26)
+#define GDM_FWD_CFG_DROP_OVERSIZE BIT(25)
+#define GDM_FWD_CFG_DROP_RUNT BIT(24)
+#define GDM_FWD_CFG_DROP_CRC BIT(23)
+#define GDM_FWD_CFG_DROP_IP4_CSUM BIT(22)
+#define GDM_FWD_CFG_DROP_TCP_CSUM BIT(21)
+#define GDM_FWD_CFG_DROP_UCP_CSUM BIT(20)
+#define GDM_FWD_CFG_G2_FAVOR_OAM BIT(19)
+#define GDM_FWD_CFG_L2LU_BRG_CPU BIT(18)
+#define GDM_FWD_CFG_L2LU_UNBRG_CPU BIT(17)
+#define GDM_FWD_CFG_STRIP_CRC BIT(16)
+#define GDM_FWD_CFG_MYMAC_FPORT_MASK GENMASK(15, 12)
+#define GDM_FWD_CFG_BCAST_FPORT_MASK GENMASK(11, 8)
+#define GDM_FWD_CFG_MCAST_FPORT_MASK GENMASK(7, 4)
+#define GDM_FWD_CFG_DEFAULT_FPORT_MASK GENMASK(3, 0)
+
+/**
+ * GDM2 only, configure whether VIP packets are included in rate control
+ * calculation. They are not actually dropped in any case.
+ */
+static inline bool is_gdm_fwd_cfg_vip(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_VIP, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_vip(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_VIP, v);
+}
+
+/** On L2LU DSCP lookup miss, send packet to CPU */
+static inline bool is_gdm_fwd_cfg_l2lu_dcsp_2cpu(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_L2LU_DCSP_2CPU, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_l2lu_dcsp_2cpu(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_L2LU_DCSP_2CPU, v);
+}
+
+/** On L2LU CTAG lookup miss, send package to CPU */
+static inline bool is_gdm_fwd_cfg_l2lu_ctag_2cpu(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_L2LU_CTAG_2CPU, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_l2lu_ctag_2cpu(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_L2LU_CTAG_2CPU, v);
+}
+
+/** On L2LU STAG lookup miss, send packet to CPU */
+static inline bool is_gdm_fwd_cfg_l2lu_stag_2cpu(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_L2LU_STAG_2CPU, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_l2lu_stag_2cpu(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_L2LU_STAG_2CPU, v);
+}
+
+/** Unknown/Unused, see GDM2_UNDERRUN_RETRY / GDMA1_FWD_CFG_RETRY_OFFSET */
+static inline bool is_gdm_fwd_cfg_g2_underrun_retry(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_G2_UNDERRUN_RETRY, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_g2_underrun_retry(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_G2_UNDERRUN_RETRY, v);
+}
+
+/** Unknown/unused, referred to as GDM2_DROP_256B */
+static inline bool is_gdm_fwd_cfg_g2_drop_256b(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_G2_DROP_256B, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_g2_drop_256b(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_G2_DROP_256B, v);
+}
+
+/**
+ * If frame length (with CRC) is greater than rx_len_threshold.oversize_len,
+ * drop. Referred to as GDM2_DROP_LONG.
+ */
+static inline bool is_gdm_fwd_cfg_drop_oversize(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_DROP_OVERSIZE, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_drop_oversize(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_DROP_OVERSIZE, v);
+}
+
+/**
+ * If frame length (with CRC) is less than rx_len_threshold.runt_len, drop.
+ * Referred to as GDM2_DROP_RUNT.
+ */
+static inline bool is_gdm_fwd_cfg_drop_runt(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_DROP_RUNT, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_drop_runt(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_DROP_RUNT, v);
+}
+
+/** If eth CRC invalid, drop. See GDM2_DROP_CRC_ERR */
+static inline bool is_gdm_fwd_cfg_drop_crc(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_DROP_CRC, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_drop_crc(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_DROP_CRC, v);
+}
+
+/** Drop packet on IPv4 checksum error */
+static inline bool is_gdm_fwd_cfg_drop_ip4_csum(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_DROP_IP4_CSUM, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_drop_ip4_csum(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_DROP_IP4_CSUM, v);
+}
+
+/** Drop packet on TCP checksum error */
+static inline bool is_gdm_fwd_cfg_drop_tcp_csum(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_DROP_TCP_CSUM, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_drop_tcp_csum(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_DROP_TCP_CSUM, v);
+}
+
+/** Drop packet on UDP checksum error */
+static inline bool is_gdm_fwd_cfg_drop_ucp_csum(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_DROP_UCP_CSUM, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_drop_ucp_csum(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_DROP_UCP_CSUM, v);
+}
+
+/**
+ * Favor OAM frames during transmission, this probably means raise their
+ * priority. GDM2 only, EN761221 (EN7521/EN7526) only, see
+ * GDMA2_TX_FAVOR_OAM_OFFSET
+ */
+static inline bool is_gdm_fwd_cfg_g2_favor_oam(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_G2_FAVOR_OAM, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_g2_favor_oam(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_G2_FAVOR_OAM, v);
+}
+
+/**
+ * Packets which are L2LU misses and which match an L2-Bridge to be forwarded to
+ * the CPU. Referred to as GDMA1_FWD_CFG_BRG_OFFSET
+ */
+static inline bool is_gdm_fwd_cfg_l2lu_brg_cpu(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_L2LU_BRG_CPU, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_l2lu_brg_cpu(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_L2LU_BRG_CPU, v);
+}
+
+/**
+ * Packets which are L2LU misses and which are NOT part of a L2-Bridge to be
+ * forwarded to the CPU. Referred to as GDMA1_FWD_CFG_RUT_OFFSET
+ */
+static inline bool is_gdm_fwd_cfg_l2lu_unbrg_cpu(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_L2LU_UNBRG_CPU, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_l2lu_unbrg_cpu(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_L2LU_UNBRG_CPU, v);
+}
+
+/**
+ * Strip eth crc trailer on RX, this is used with g1_cport_cfg.add_crc when xPON
+ * WAN and LAN are bridged.
+ */
+static inline bool is_gdm_fwd_cfg_strip_crc(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_STRIP_CRC, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_strip_crc(struct fwd_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_STRIP_CRC, v);
+}
+
+/** GDM_P_* port for packets with our MAC address */
+static inline u8 get_gdm_fwd_cfg_mymac_fport(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_MYMAC_FPORT_MASK, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_mymac_fport(struct fwd_cfg *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_MYMAC_FPORT_MASK, v);
+}
+
+/** GDM_P_* port for broadcast packets */
+static inline u8 get_gdm_fwd_cfg_bcast_fport(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_BCAST_FPORT_MASK, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_bcast_fport(struct fwd_cfg *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_BCAST_FPORT_MASK, v);
+}
+
+/** GDM_P_* port for multicast packets */
+static inline u8 get_gdm_fwd_cfg_mcast_fport(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_MCAST_FPORT_MASK, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_mcast_fport(struct fwd_cfg *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_MCAST_FPORT_MASK, v);
+}
+
+/** GDM_P_* port for other packets */
+static inline u8 get_gdm_fwd_cfg_default_fport(struct fwd_cfg *x)
+{
+ return FIELD_GET(GDM_FWD_CFG_DEFAULT_FPORT_MASK, x->word);
+}
+
+static inline void set_gdm_fwd_cfg_default_fport(struct fwd_cfg *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_FWD_CFG_DEFAULT_FPORT_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: struct pcp
+ * These codings are defined in IEEE 802.1ad and define how priority and
+ * drop-eligiblity. should be represented in the 3 bit VLAN PCP header. 8p0d
+ * specifies 8 priorities of which none are drop-eligible. 7p1d specifies 7
+ * priorities + p0d which is the same as p0, but marked drop-eligible. 6p2d
+ * specieies 6 priorities + p0d and p1d, same as p0 and p1, but marked
+ * drop-eligible. 5p3d specifies 5 priorities + p0d, p1d, and p2d, same as p0,
+ * p1, p2, but marked drop-eligible.
+ */
+
+enum gdm_pcp_gdm_rx {
+ GDM_PCP_GDM_RX_5P3D = 8,
+ GDM_PCP_GDM_RX_6P2D = 4,
+ GDM_PCP_GDM_RX_7P1D = 2,
+ GDM_PCP_GDM_RX_8P0D = 1,
+};
+
+#define GDM_PCP_GDM_RX_MASK GENMASK(15, 12)
+#define GDM_PCP_GDM_TX_MASK GENMASK(11, 8)
+#define GDM_PCP_CDM_RX_MASK GENMASK(7, 4)
+#define GDM_PCP_CDM_TX_MASK GENMASK(3, 0)
+
+static inline enum gdm_pcp_gdm_rx get_gdm_pcp_gdm_rx(struct pcp *x)
+{
+ return FIELD_GET(GDM_PCP_GDM_RX_MASK, x->word);
+}
+
+static inline void set_gdm_pcp_gdm_rx(struct pcp *x, enum gdm_pcp_gdm_rx v)
+{
+ x->word = FIELD_SET(x->word, GDM_PCP_GDM_RX_MASK, v);
+}
+
+static inline u8 get_gdm_pcp_gdm_tx(struct pcp *x)
+{
+ return FIELD_GET(GDM_PCP_GDM_TX_MASK, x->word);
+}
+
+static inline void set_gdm_pcp_gdm_tx(struct pcp *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_PCP_GDM_TX_MASK, v);
+}
+
+static inline u8 get_gdm_pcp_cdm_rx(struct pcp *x)
+{
+ return FIELD_GET(GDM_PCP_CDM_RX_MASK, x->word);
+}
+
+static inline void set_gdm_pcp_cdm_rx(struct pcp *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_PCP_CDM_RX_MASK, v);
+}
+
+static inline u8 get_gdm_pcp_cdm_tx(struct pcp *x)
+{
+ return FIELD_GET(GDM_PCP_CDM_TX_MASK, x->word);
+}
+
+static inline void set_gdm_pcp_cdm_tx(struct pcp *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_PCP_CDM_TX_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: struct loopback
+ * This is a poorly understood / rarely used register which seems to control GDM
+ * level loopback. It has gap, len, and channel. Channel seems to corrispond to
+ * the QDMA channel but the meaning of gap and len are unknown. Names include
+ * GDMA1_LPBK_CFG, GDMA2_LPBP_CFG, and REG_GDM_LPBK_CFG (Airoha).
+ */
+
+#define GDM_LPBK_GAP_MASK GENMASK(31, 24)
+#define GDM_LPBK_LEN_MASK GENMASK(23, 10)
+#define GDM_LPBK_CHAN_MASK GENMASK(8, 4)
+#define GDM_LPBK_GAP_MODE BIT(3)
+#define GDM_LPBK_LEN_MODE BIT(2)
+#define GDM_LPBK_CHAN_MODE BIT(1)
+#define GDM_LPBK_ENABLED BIT(0)
+
+static inline u8 get_gdm_lpbk_gap(struct loopback *x)
+{
+ return FIELD_GET(GDM_LPBK_GAP_MASK, x->word);
+}
+
+static inline void set_gdm_lpbk_gap(struct loopback *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_LPBK_GAP_MASK, v);
+}
+
+static inline u16 get_gdm_lpbk_len(struct loopback *x)
+{
+ return FIELD_GET(GDM_LPBK_LEN_MASK, x->word);
+}
+
+static inline void set_gdm_lpbk_len(struct loopback *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, GDM_LPBK_LEN_MASK, v);
+}
+
+static inline u8 get_gdm_lpbk_chan(struct loopback *x)
+{
+ return FIELD_GET(GDM_LPBK_CHAN_MASK, x->word);
+}
+
+static inline void set_gdm_lpbk_chan(struct loopback *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_LPBK_CHAN_MASK, v);
+}
+
+static inline bool is_gdm_lpbk_gap_mode(struct loopback *x)
+{
+ return FIELD_GET(GDM_LPBK_GAP_MODE, x->word);
+}
+
+static inline void set_gdm_lpbk_gap_mode(struct loopback *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_LPBK_GAP_MODE, v);
+}
+
+static inline bool is_gdm_lpbk_len_mode(struct loopback *x)
+{
+ return FIELD_GET(GDM_LPBK_LEN_MODE, x->word);
+}
+
+static inline void set_gdm_lpbk_len_mode(struct loopback *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_LPBK_LEN_MODE, v);
+}
+
+static inline bool is_gdm_lpbk_chan_mode(struct loopback *x)
+{
+ return FIELD_GET(GDM_LPBK_CHAN_MODE, x->word);
+}
+
+static inline void set_gdm_lpbk_chan_mode(struct loopback *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_LPBK_CHAN_MODE, v);
+}
+
+static inline bool is_gdm_lpbk_enabled(struct loopback *x)
+{
+ return FIELD_GET(GDM_LPBK_ENABLED, x->word);
+}
+
+static inline void set_gdm_lpbk_enabled(struct loopback *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_LPBK_ENABLED, v);
+}
+
+/**
+ * Bitfield accessors for: struct channel_retire
+ * Shut down a channel and release its resources
+ */
+
+#define GDM_CHANNEL_RETIRE_CHANNEL_MASK GENMASK(8, 4)
+#define GDM_CHANNEL_RETIRE_DONE BIT(1)
+#define GDM_CHANNEL_RETIRE_RELEASE BIT(0)
+
+/** Number of the channel to release */
+static inline u8 get_gdm_channel_retire_channel(struct channel_retire *x)
+{
+ return FIELD_GET(GDM_CHANNEL_RETIRE_CHANNEL_MASK, x->word);
+}
+
+static inline void set_gdm_channel_retire_channel(struct channel_retire *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_CHANNEL_RETIRE_CHANNEL_MASK, v);
+}
+
+/** Read 1 when channel release has completed */
+static inline bool is_gdm_channel_retire_done(struct channel_retire *x)
+{
+ return FIELD_GET(GDM_CHANNEL_RETIRE_DONE, x->word);
+}
+
+static inline void set_gdm_channel_retire_done(struct channel_retire *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_CHANNEL_RETIRE_DONE, v);
+}
+
+/** Write 1 to release the channel */
+static inline bool is_gdm_channel_retire_release(struct channel_retire *x)
+{
+ return FIELD_GET(GDM_CHANNEL_RETIRE_RELEASE, x->word);
+}
+
+static inline void set_gdm_channel_retire_release(struct channel_retire *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_CHANNEL_RETIRE_RELEASE, v);
+}
+
+/**
+ * Bitfield accessors for: gdm bitfield_0
+ * Apparently the lower 8 channels RX channels can be wired to forward to any
+ * fport on receipt. This is not really ever used and the default value is
+ * disabled + all route to PPE.
+ */
+
+#define GDM_RX7_EN BIT(31)
+#define GDM_RX7_FPORT_MASK GENMASK(30, 28)
+
+static inline bool is_gdm_rx7_en(struct gdm *x)
+{
+ return FIELD_GET(GDM_RX7_EN, x->bitfield_0);
+}
+
+static inline void set_gdm_rx7_en(struct gdm *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, GDM_RX7_EN, v);
+}
+
+static inline u8 get_gdm_rx7_fport(struct gdm *x)
+{
+ return FIELD_GET(GDM_RX7_FPORT_MASK, x->bitfield_0);
+}
+
+static inline void set_gdm_rx7_fport(struct gdm *x, u8 v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, GDM_RX7_FPORT_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: struct g1_cport_cfg
+ * Not well known what the CPORT config does, a few bits are understood. Only
+ * used in GDM1, but might still be present in GDM2.
+ */
+
+#define GDM_G1_CPORT_CFG_ADD_CRC BIT(30)
+#define GDM_G1_CPORT_CFG_PAD BIT(26)
+#define GDM_G1_CPORT_CFG_PORT_XFC BIT(25)
+#define GDM_G1_CPORT_CFG_QUEUE_XFC BIT(24)
+#define GDM_G1_CPORT_CFG_UNKNOWN_MASK GENMASK(23, 0)
+
+/**
+ * Add the ethernet trailing CRC to incoming frames (from the switch) this is
+ * used with fwd_cfg.strip_crc when xPON WAN and LAN are bridged because xPON
+ * PHY always sends us a CRC.
+ */
+static inline bool is_gdm_g1_cport_cfg_add_crc(struct g1_cport_cfg *x)
+{
+ return FIELD_GET(GDM_G1_CPORT_CFG_ADD_CRC, x->word);
+}
+
+static inline void set_gdm_g1_cport_cfg_add_crc(struct g1_cport_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_G1_CPORT_CFG_ADD_CRC, v);
+}
+
+/**
+ * Enable padding (fe_api_set_padding, FE_CPORT_PAD), meaning is unknown but
+ * this is always set at startup.
+ */
+static inline bool is_gdm_g1_cport_cfg_pad(struct g1_cport_cfg *x)
+{
+ return FIELD_GET(GDM_G1_CPORT_CFG_PAD, x->word);
+}
+
+static inline void set_gdm_g1_cport_cfg_pad(struct g1_cport_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_G1_CPORT_CFG_PAD, v);
+}
+
+/**
+ * FE_CPORT_PORT_XFC_MASK, always set on startup. Meaning is thought to be frame
+ * engine will pause in case of congestion on port (i.e. switch)
+ */
+static inline bool is_gdm_g1_cport_cfg_port_xfc(struct g1_cport_cfg *x)
+{
+ return FIELD_GET(GDM_G1_CPORT_CFG_PORT_XFC, x->word);
+}
+
+static inline void set_gdm_g1_cport_cfg_port_xfc(struct g1_cport_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_G1_CPORT_CFG_PORT_XFC, v);
+}
+
+/**
+ * FE_CPORT_QUEUE_XFC_MASK, always cleared on startup. Meaning is thought to be
+ * individual queue will pause in case of congestion on port (i.e. switch)
+ */
+static inline bool is_gdm_g1_cport_cfg_queue_xfc(struct g1_cport_cfg *x)
+{
+ return FIELD_GET(GDM_G1_CPORT_CFG_QUEUE_XFC, x->word);
+}
+
+static inline void set_gdm_g1_cport_cfg_queue_xfc(struct g1_cport_cfg *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_G1_CPORT_CFG_QUEUE_XFC, v);
+}
+
+/** Observed value 0x000a02, never updated, unknown meaning */
+static inline u32 get_gdm_g1_cport_cfg_unknown(struct g1_cport_cfg *x)
+{
+ return FIELD_GET(GDM_G1_CPORT_CFG_UNKNOWN_MASK, x->word);
+}
+
+static inline void set_gdm_g1_cport_cfg_unknown(struct g1_cport_cfg *x, u32 v)
+{
+ x->word = FIELD_SET(x->word, GDM_G1_CPORT_CFG_UNKNOWN_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: struct clear_counters
+ */
+
+#define GDM_CL_CNT_RX BIT(1)
+#define GDM_CL_CNT_TX BIT(0)
+
+static inline void set_gdm_cl_cnt_rx(struct clear_counters *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_CL_CNT_RX, v);
+}
+
+static inline void set_gdm_cl_cnt_tx(struct clear_counters *x, bool v)
+{
+ x->word = FIELD_SET(x->word, GDM_CL_CNT_TX, v);
+}
+
+/**
+ * Bitfield accessors for: gdm_mymac_lsb
+ * MAC address bytes c,d,e,f (indices 2-5)
+ */
+#define GDM_MYMAC_LSB_C_MASK GENMASK(31, 24)
+#define GDM_MYMAC_LSB_D_MASK GENMASK(23, 16)
+#define GDM_MYMAC_LSB_E_MASK GENMASK(15, 8)
+#define GDM_MYMAC_LSB_F_MASK GENMASK(7, 0)
+
+static inline u8 get_gdm_mymac_lsb_c(struct gdm_mymac_lsb *x)
+{
+ return FIELD_GET(GDM_MYMAC_LSB_C_MASK, x->word);
+}
+
+static inline void set_gdm_mymac_lsb_c(struct gdm_mymac_lsb *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_MYMAC_LSB_C_MASK, v);
+}
+
+static inline u8 get_gdm_mymac_lsb_d(struct gdm_mymac_lsb *x)
+{
+ return FIELD_GET(GDM_MYMAC_LSB_D_MASK, x->word);
+}
+
+static inline void set_gdm_mymac_lsb_d(struct gdm_mymac_lsb *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_MYMAC_LSB_D_MASK, v);
+}
+
+static inline u8 get_gdm_mymac_lsb_e(struct gdm_mymac_lsb *x)
+{
+ return FIELD_GET(GDM_MYMAC_LSB_E_MASK, x->word);
+}
+
+static inline void set_gdm_mymac_lsb_e(struct gdm_mymac_lsb *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_MYMAC_LSB_E_MASK, v);
+}
+
+static inline u8 get_gdm_mymac_lsb_f(struct gdm_mymac_lsb *x)
+{
+ return FIELD_GET(GDM_MYMAC_LSB_F_MASK, x->word);
+}
+
+static inline void set_gdm_mymac_lsb_f(struct gdm_mymac_lsb *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_MYMAC_LSB_F_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: gdm_mymac_msb
+ * MAC address bytes a,b (indices 0-1) and LSB matching mask
+ */
+#define GDM_MYMAC_MSB_LSB_MASK_MASK GENMASK(23, 16)
+#define GDM_MYMAC_MSB_A_MASK GENMASK(15, 8)
+#define GDM_MYMAC_MSB_B_MASK GENMASK(7, 0)
+
+static inline u8 get_gdm_mymac_msb_lsb_mask(struct gdm_mymac_msb *x)
+{
+ return FIELD_GET(GDM_MYMAC_MSB_LSB_MASK_MASK, x->word);
+}
+
+static inline void set_gdm_mymac_msb_lsb_mask(struct gdm_mymac_msb *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_MYMAC_MSB_LSB_MASK_MASK, v);
+}
+
+static inline u8 get_gdm_mymac_msb_a(struct gdm_mymac_msb *x)
+{
+ return FIELD_GET(GDM_MYMAC_MSB_A_MASK, x->word);
+}
+
+static inline void set_gdm_mymac_msb_a(struct gdm_mymac_msb *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_MYMAC_MSB_A_MASK, v);
+}
+
+static inline u8 get_gdm_mymac_msb_b(struct gdm_mymac_msb *x)
+{
+ return FIELD_GET(GDM_MYMAC_MSB_B_MASK, x->word);
+}
+
+static inline void set_gdm_mymac_msb_b(struct gdm_mymac_msb *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, GDM_MYMAC_MSB_B_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: gdm_len_th
+ * Register layout (32-bit word):
+ * - Bits 31-16: OVERSIZE_LEN (packet larger than this is treated as oversize)
+ * - Bits 15-0: RUNT_LEN (packet smaller than this is treated as a runt)
+ */
+#define GDM_LEN_TH_OVERSIZE_LEN_MASK GENMASK(31, 16)
+#define GDM_LEN_TH_RUNT_LEN_MASK GENMASK(15, 0)
+
+static inline u16 get_gdm_len_th_oversize_len(struct gdm_len_th *x)
+{
+ return FIELD_GET(GDM_LEN_TH_OVERSIZE_LEN_MASK, x->word);
+}
+
+static inline void set_gdm_len_th_oversize_len(struct gdm_len_th *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, GDM_LEN_TH_OVERSIZE_LEN_MASK, v);
+}
+
+static inline u16 get_gdm_len_th_runt_len(struct gdm_len_th *x)
+{
+ return FIELD_GET(GDM_LEN_TH_RUNT_LEN_MASK, x->word);
+}
+
+static inline void set_gdm_len_th_runt_len(struct gdm_len_th *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, GDM_LEN_TH_RUNT_LEN_MASK, v);
+}
+
+#endif
diff --git a/drivers/net/ethernet/econet/qdma_desc.h b/drivers/net/ethernet/econet/qdma_desc.h
new file mode 100644
index 000000000000..173045cc1caf
--- /dev/null
+++ b/drivers/net/ethernet/econet/qdma_desc.h
@@ -0,0 +1,677 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef QDMA_DESC_H
+#define QDMA_DESC_H
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/types.h>
+
+#define FIELD_SET(current, mask, val) \
+ (((current) & ~(mask)) | FIELD_PREP((mask), (val)))
+
+/** etx: */
+struct etx {
+ /**
+ * See accessors:
+ * get_etx_unknown0()
+ * set_etx_unknown0()
+ * get_etx_sp_tag()
+ * set_etx_sp_tag()
+ * is_etx_oam()
+ * set_etx_oam()
+ * get_etx_channel()
+ * set_etx_channel()
+ * get_etx_queue()
+ * set_etx_queue()
+ */
+ u32 bitfield_0;
+
+ /**
+ * See accessors:
+ * is_etx_ico()
+ * set_etx_ico()
+ * is_etx_uco()
+ * set_etx_uco()
+ * is_etx_tco()
+ * set_etx_tco()
+ * is_etx_sco()
+ * set_etx_sco()
+ * get_etx_udf_pmap()
+ * set_etx_udf_pmap()
+ * get_etx_fport()
+ * set_etx_fport()
+ * is_etx_vlan_en()
+ * set_etx_vlan_en()
+ * get_etx_vlan_type()
+ * set_etx_vlan_type()
+ * get_etx_vlan_tag()
+ * set_etx_vlan_tag()
+ */
+ u32 bitfield_1;
+
+};
+
+/**
+ * Bitfield accessors for: etx bitfield_0
+ */
+
+#define ETX_UNKNOWN0_MASK GENMASK(31, 28)
+#define ETX_SP_TAG_MASK GENMASK(27, 12)
+#define ETX_OAM BIT(11)
+#define ETX_CHANNEL_MASK GENMASK(10, 3)
+#define ETX_QUEUE_MASK GENMASK(2, 0)
+
+/** Unused, called "rev" probably short for reserved */
+static inline u8 get_etx_unknown0(struct etx *x)
+{
+ return FIELD_GET(ETX_UNKNOWN0_MASK, x->bitfield_0);
+}
+
+static inline void set_etx_unknown0(struct etx *x, u8 v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ETX_UNKNOWN0_MASK, v);
+}
+
+/**
+ * MediaTek "Special Tag" format which encapsulates both switch port number and
+ * possible VLAN
+ */
+static inline u16 get_etx_sp_tag(struct etx *x)
+{
+ return FIELD_GET(ETX_SP_TAG_MASK, x->bitfield_0);
+}
+
+static inline void set_etx_sp_tag(struct etx *x, u16 v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ETX_SP_TAG_MASK, v);
+}
+
+/** OAM (management) frame, never used with Ethernet transmissions */
+static inline bool is_etx_oam(struct etx *x)
+{
+ return FIELD_GET(ETX_OAM, x->bitfield_0);
+}
+
+static inline void set_etx_oam(struct etx *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ETX_OAM, v);
+}
+
+/** The channel number for QoS prioritization */
+static inline u8 get_etx_channel(struct etx *x)
+{
+ return FIELD_GET(ETX_CHANNEL_MASK, x->bitfield_0);
+}
+
+static inline void set_etx_channel(struct etx *x, u8 v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ETX_CHANNEL_MASK, v);
+}
+
+/** The queue number for QoS prioritization */
+static inline u8 get_etx_queue(struct etx *x)
+{
+ return FIELD_GET(ETX_QUEUE_MASK, x->bitfield_0);
+}
+
+static inline void set_etx_queue(struct etx *x, u8 v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ETX_QUEUE_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: etx bitfield_1
+ */
+
+enum etx_fport {
+ ETX_FPORT_QDMA0_CPU = 0,
+ ETX_FPORT_GDM1 = 1,
+ ETX_FPORT_GDM2 = 2,
+ ETX_FPORT_QDMA0_HWF = 3,
+ ETX_FPORT_PPE = 4,
+ ETX_FPORT_QDMA1_CPU = 5,
+ ETX_FPORT_QDMA1_HWF = 6,
+ ETX_FPORT_DROP = 7,
+};
+
+enum etx_vlan_type {
+ ETX_VLAN_TYPE_8100 = 0,
+ ETX_VLAN_TYPE_9100 = 2,
+ ETX_VLAN_TYPE_88A8 = 1,
+ ETX_VLAN_TYPE_UNKNOWN = 3,
+};
+
+#define ETX_ICO BIT(31)
+#define ETX_UCO BIT(30)
+#define ETX_TCO BIT(29)
+#define ETX_SCO BIT(28)
+#define ETX_UDF_PMAP_MASK GENMASK(27, 22)
+#define ETX_FPORT_MASK GENMASK(21, 19)
+#define ETX_VLAN_EN BIT(18)
+#define ETX_VLAN_TYPE_MASK GENMASK(17, 16)
+#define ETX_VLAN_TAG_MASK GENMASK(15, 0)
+
+/** Checksum offload, probably IP */
+static inline bool is_etx_ico(struct etx *x)
+{
+ return FIELD_GET(ETX_ICO, x->bitfield_1);
+}
+
+static inline void set_etx_ico(struct etx *x, bool v)
+{
+ x->bitfield_1 = FIELD_SET(x->bitfield_1, ETX_ICO, v);
+}
+
+/** Checksum offload, probably UDP */
+static inline bool is_etx_uco(struct etx *x)
+{
+ return FIELD_GET(ETX_UCO, x->bitfield_1);
+}
+
+static inline void set_etx_uco(struct etx *x, bool v)
+{
+ x->bitfield_1 = FIELD_SET(x->bitfield_1, ETX_UCO, v);
+}
+
+/** Checksum offload, probably TCP */
+static inline bool is_etx_tco(struct etx *x)
+{
+ return FIELD_GET(ETX_TCO, x->bitfield_1);
+}
+
+static inline void set_etx_tco(struct etx *x, bool v)
+{
+ x->bitfield_1 = FIELD_SET(x->bitfield_1, ETX_TCO, v);
+}
+
+/** Unknown, maybe SCTP checksum offload */
+static inline bool is_etx_sco(struct etx *x)
+{
+ return FIELD_GET(ETX_SCO, x->bitfield_1);
+}
+
+static inline void set_etx_sco(struct etx *x, bool v)
+{
+ x->bitfield_1 = FIELD_SET(x->bitfield_1, ETX_SCO, v);
+}
+
+/** Unknown / unused */
+static inline u8 get_etx_udf_pmap(struct etx *x)
+{
+ return FIELD_GET(ETX_UDF_PMAP_MASK, x->bitfield_1);
+}
+
+static inline void set_etx_udf_pmap(struct etx *x, u8 v)
+{
+ x->bitfield_1 = FIELD_SET(x->bitfield_1, ETX_UDF_PMAP_MASK, v);
+}
+
+/**
+ * Where in the Frame Engine to send the packet to QDMA0_CPU / QDMA1_CPU sends
+ * to CPU via the relevant QDMA engine. QDMA0_HWF and QDMA1_HWF goes to QDMA
+ * hardware forwarding. GDM1 is the LAN, GDM2 is the WAN, and PPE is the Packet
+ * Processing Engine.
+ */
+static inline enum etx_fport get_etx_fport(struct etx *x)
+{
+ return FIELD_GET(ETX_FPORT_MASK, x->bitfield_1);
+}
+
+static inline void set_etx_fport(struct etx *x, enum etx_fport v)
+{
+ x->bitfield_1 = FIELD_SET(x->bitfield_1, ETX_FPORT_MASK, v);
+}
+
+/** If 1 then add a vlan header to the packet */
+static inline bool is_etx_vlan_en(struct etx *x)
+{
+ return FIELD_GET(ETX_VLAN_EN, x->bitfield_1);
+}
+
+static inline void set_etx_vlan_en(struct etx *x, bool v)
+{
+ x->bitfield_1 = FIELD_SET(x->bitfield_1, ETX_VLAN_EN, v);
+}
+
+/** Which type of vlan to add to the packet header */
+static inline enum etx_vlan_type get_etx_vlan_type(struct etx *x)
+{
+ return FIELD_GET(ETX_VLAN_TYPE_MASK, x->bitfield_1);
+}
+
+static inline void set_etx_vlan_type(struct etx *x, enum etx_vlan_type v)
+{
+ x->bitfield_1 = FIELD_SET(x->bitfield_1, ETX_VLAN_TYPE_MASK, v);
+}
+
+/** The VLAN number, if vlan_en is set */
+static inline u16 get_etx_vlan_tag(struct etx *x)
+{
+ return FIELD_GET(ETX_VLAN_TAG_MASK, x->bitfield_1);
+}
+
+static inline void set_etx_vlan_tag(struct etx *x, u16 v)
+{
+ x->bitfield_1 = FIELD_SET(x->bitfield_1, ETX_VLAN_TAG_MASK, v);
+}
+
+/**
+ * qdma_desc_erx
+ *
+ * 3 2 1 0
+ * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * 0 | unknown0 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * 4 |nknwn|I|P|F|T|L|A| sport | crsn | ppe_entry |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * 8 | unknown2 |N|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * 12 | sp_tag | tci |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * 16
+ *
+ * @unknown0 (32 bit): Unknown / unused field, first word in descriptor
+ * @bitfield_0 (32 bit):
+ * @unknown1 "nknwn" (bits 31..29): Revision number? (unused)
+ * @ip6 "I" (bit 28): IPv6 packet indicator
+ * @ip4 "P" (bit 27): IPv4 packet indicator
+ * @ip4f "F" (bit 26): IPv4 fragment flag
+ * @tack "T" (bit 25): TCP ACK flag
+ * @l2vld "L" (bit 24): Layer 2 valid flag
+ * @l4f "A" (bit 23): Layer 4 flag (e.g., checksum failure)
+ * @sport (bits 22..19): Where the packet came from, mostly unknown / unused,
+ * needs testing
+ * @crsn (bits 18..14): Most likely a MediaTek PPE CPU_REASON
+ * @ppe_entry (bits 13..0): PPE (Packet Processing Engine) entry index
+ * @bitfield_1 (32 bit):
+ * @unknown2 (bits 31..1): Reserved
+ * @untag "N" (bit 0): VLAN untag flag
+ * @bitfield_3 (32 bit):
+ * @sp_tag (bits 31..16): MediaTek "Special Tag" for switch port/VLAN encoding
+ * @tci (bits 15..0): The TCI of any vlan tag that was unpopped beneath the
+ * MTK "Special Tag"
+ */
+struct qdma_desc_erx {
+ u32 unknown0;
+ u32 bitfield_0;
+ u32 bitfield_1;
+ u32 bitfield_3;
+};
+
+/* qdma_desc_erx bitfield_0 */
+
+#define ERX_UNKNOWN1_MASK GENMASK(31, 29)
+#define ERX_IP6 BIT(28)
+#define ERX_IP4 BIT(27)
+#define ERX_IP4F BIT(26)
+#define ERX_TACK BIT(25)
+#define ERX_L2VLD BIT(24)
+#define ERX_L4F BIT(23)
+#define ERX_SPORT_MASK GENMASK(22, 19)
+#define ERX_CRSN_MASK GENMASK(18, 14)
+#define ERX_PPE_ENTRY_MASK GENMASK(13, 0)
+
+static inline u8 get_erx_unknown1(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_UNKNOWN1_MASK, x->bitfield_0);
+}
+
+static inline void set_erx_unknown1(struct qdma_desc_erx *x, u8 v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ERX_UNKNOWN1_MASK, v);
+}
+
+static inline bool is_erx_ip6(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_IP6, x->bitfield_0);
+}
+
+static inline void set_erx_ip6(struct qdma_desc_erx *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ERX_IP6, v);
+}
+
+static inline bool is_erx_ip4(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_IP4, x->bitfield_0);
+}
+
+static inline void set_erx_ip4(struct qdma_desc_erx *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ERX_IP4, v);
+}
+
+static inline bool is_erx_ip4f(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_IP4F, x->bitfield_0);
+}
+
+static inline void set_erx_ip4f(struct qdma_desc_erx *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ERX_IP4F, v);
+}
+
+static inline bool is_erx_tack(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_TACK, x->bitfield_0);
+}
+
+static inline void set_erx_tack(struct qdma_desc_erx *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ERX_TACK, v);
+}
+
+static inline bool is_erx_l2vld(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_L2VLD, x->bitfield_0);
+}
+
+static inline void set_erx_l2vld(struct qdma_desc_erx *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ERX_L2VLD, v);
+}
+
+static inline bool is_erx_l4f(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_L4F, x->bitfield_0);
+}
+
+static inline void set_erx_l4f(struct qdma_desc_erx *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ERX_L4F, v);
+}
+
+static inline u8 get_erx_sport(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_SPORT_MASK, x->bitfield_0);
+}
+
+static inline void set_erx_sport(struct qdma_desc_erx *x, u8 v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ERX_SPORT_MASK, v);
+}
+
+static inline u8 get_erx_crsn(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_CRSN_MASK, x->bitfield_0);
+}
+
+static inline void set_erx_crsn(struct qdma_desc_erx *x, u8 v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ERX_CRSN_MASK, v);
+}
+
+static inline u16 get_erx_ppe_entry(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_PPE_ENTRY_MASK, x->bitfield_0);
+}
+
+static inline void set_erx_ppe_entry(struct qdma_desc_erx *x, u16 v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, ERX_PPE_ENTRY_MASK, v);
+}
+
+/* qdma_desc_erx bitfield_1 */
+
+#define ERX_UNKNOWN2_MASK GENMASK(31, 1)
+#define ERX_UNTAG BIT(0)
+
+static inline u32 get_erx_unknown2(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_UNKNOWN2_MASK, x->bitfield_1);
+}
+
+static inline void set_erx_unknown2(struct qdma_desc_erx *x, u32 v)
+{
+ x->bitfield_1 = FIELD_SET(x->bitfield_1, ERX_UNKNOWN2_MASK, v);
+}
+
+static inline bool is_erx_untag(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_UNTAG, x->bitfield_1);
+}
+
+static inline void set_erx_untag(struct qdma_desc_erx *x, bool v)
+{
+ x->bitfield_1 = FIELD_SET(x->bitfield_1, ERX_UNTAG, v);
+}
+
+/* qdma_desc_erx bitfield_3 */
+
+#define ERX_SP_TAG_MASK GENMASK(31, 16)
+#define ERX_TCI_MASK GENMASK(15, 0)
+
+static inline u16 get_erx_sp_tag(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_SP_TAG_MASK, x->bitfield_3);
+}
+
+static inline void set_erx_sp_tag(struct qdma_desc_erx *x, u16 v)
+{
+ x->bitfield_3 = FIELD_SET(x->bitfield_3, ERX_SP_TAG_MASK, v);
+}
+
+static inline u16 get_erx_tci(struct qdma_desc_erx *x)
+{
+ return FIELD_GET(ERX_TCI_MASK, x->bitfield_3);
+}
+
+static inline void set_erx_tci(struct qdma_desc_erx *x, u16 v)
+{
+ x->bitfield_3 = FIELD_SET(x->bitfield_3, ERX_TCI_MASK, v);
+}
+
+/** desc: QDMA Packet Descriptor */
+struct desc {
+ /**
+ * desc_unknown0: Reserved / unused, we still export the symbol so we can show
+ * it in debugging
+ */
+ u32 unknown0;
+
+ /** desc_info: */
+ struct desc_info {
+ /**
+ * See accessors:
+ * is_desc_info_done()
+ * set_desc_info_done()
+ * is_desc_info_dropped()
+ * set_desc_info_dropped()
+ * is_desc_info_nls()
+ * set_desc_info_nls()
+ * get_desc_info_unknown1()
+ * set_desc_info_unknown1()
+ * get_desc_info_pkt_len()
+ * set_desc_info_pkt_len()
+ */
+ u32 word;
+
+ } info;
+
+ /** desc_pkt_addr: Physical (DMA) address of the packet */
+ u32 pkt_addr;
+
+ /** desc_next_idx: Index of the next descriptor in the ring, max 4096. */
+ u32 next_idx;
+
+ /** desc_msg: This is either Ethernet RX, Ethernet TX, xPON RX, or xPON TX */
+ union desc_msg {
+ struct qdma_desc_erx erx;
+
+ struct etx etx;
+
+ u32 raw[4];
+
+ } msg;
+
+};
+
+/**
+ * Bitfield accessors for: desc_info word
+ */
+
+#define DESC_INFO_DONE BIT(31)
+#define DESC_INFO_DROPPED BIT(30)
+#define DESC_INFO_NLS BIT(29)
+#define DESC_INFO_UNKNOWN1_MASK GENMASK(28, 16)
+#define DESC_INFO_PKT_LEN_MASK GENMASK(15, 0)
+
+/**
+ * Descriptor Done flag, this roughly means that the DSCP "belongs to the
+ * driver", the hardware will set it when it is done receiving or sending and
+ * will check to make sure it's not touching a DSCP that is not meant for it.
+ * This is not strictly necessary, you can determine which packets are yours
+ * only through ring indexes and the TX Done List, and setting and checking of
+ * this flag can be deactivated.
+ */
+static inline bool is_desc_info_done(struct desc_info *x)
+{
+ return FIELD_GET(DESC_INFO_DONE, x->word);
+}
+
+static inline void set_desc_info_done(struct desc_info *x, bool v)
+{
+ x->word = FIELD_SET(x->word, DESC_INFO_DONE, v);
+}
+
+/** Packet has been dropped */
+static inline bool is_desc_info_dropped(struct desc_info *x)
+{
+ return FIELD_GET(DESC_INFO_DROPPED, x->word);
+}
+
+static inline void set_desc_info_dropped(struct desc_info *x, bool v)
+{
+ x->word = FIELD_SET(x->word, DESC_INFO_DROPPED, v);
+}
+
+/** Unknown meaning but used on EN761627 and EN7580 */
+static inline bool is_desc_info_nls(struct desc_info *x)
+{
+ return FIELD_GET(DESC_INFO_NLS, x->word);
+}
+
+static inline void set_desc_info_nls(struct desc_info *x, bool v)
+{
+ x->word = FIELD_SET(x->word, DESC_INFO_NLS, v);
+}
+
+/**
+ * Reserved / unused, we still export the symbol so we can show it in debugging
+ */
+static inline u16 get_desc_info_unknown1(struct desc_info *x)
+{
+ return FIELD_GET(DESC_INFO_UNKNOWN1_MASK, x->word);
+}
+
+static inline void set_desc_info_unknown1(struct desc_info *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, DESC_INFO_UNKNOWN1_MASK, v);
+}
+
+/** Length of the packet in bytes */
+static inline u16 get_desc_info_pkt_len(struct desc_info *x)
+{
+ return FIELD_GET(DESC_INFO_PKT_LEN_MASK, x->word);
+}
+
+static inline void set_desc_info_pkt_len(struct desc_info *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, DESC_INFO_PKT_LEN_MASK, v);
+}
+
+/** fwdesc: QDMA Hardware Forward Packet Descriptor */
+struct fwdesc {
+ /** fwdesc_pkt_addr: Physical (DMA) address of the packet */
+ u32 pkt_addr;
+
+ /** fwdesc_info: */
+ struct fwdesc_info {
+ /**
+ * See accessors:
+ * is_fwdesc_info_ctx()
+ * set_fwdesc_info_ctx()
+ * is_fwdesc_info_ctx_ring()
+ * set_fwdesc_info_ctx_ring()
+ * get_fwdesc_info_ctx_idx()
+ * set_fwdesc_info_ctx_idx()
+ * get_fwdesc_info_pkt_len()
+ * set_fwdesc_info_pkt_len()
+ */
+ u32 word;
+
+ } info;
+
+ /**
+ * fwdesc_msg: This is either Ethernet RX, Ethernet TX, xPON RX, or xPON TX
+ */
+ union fwdesc_msg {
+ struct etx etx;
+
+ u32 raw[2];
+
+ } msg;
+
+};
+
+/**
+ * Bitfield accessors for: fwdesc_info word
+ */
+
+#define FWDESC_INFO_CTX BIT(31)
+#define FWDESC_INFO_CTX_RING BIT(28)
+#define FWDESC_INFO_CTX_IDX_MASK GENMASK(27, 16)
+#define FWDESC_INFO_PKT_LEN_MASK GENMASK(15, 0)
+
+/** True if there is a context descriptor (i.e. it is send, not a forward) */
+static inline bool is_fwdesc_info_ctx(struct fwdesc_info *x)
+{
+ return FIELD_GET(FWDESC_INFO_CTX, x->word);
+}
+
+static inline void set_fwdesc_info_ctx(struct fwdesc_info *x, bool v)
+{
+ x->word = FIELD_SET(x->word, FWDESC_INFO_CTX, v);
+}
+
+/**
+ * If ctx is true then this is the number of the context ring (0 or 1) because
+ * there are 2 transmit rings.
+ */
+static inline bool is_fwdesc_info_ctx_ring(struct fwdesc_info *x)
+{
+ return FIELD_GET(FWDESC_INFO_CTX_RING, x->word);
+}
+
+static inline void set_fwdesc_info_ctx_ring(struct fwdesc_info *x, bool v)
+{
+ x->word = FIELD_SET(x->word, FWDESC_INFO_CTX_RING, v);
+}
+
+/**
+ * If ctx is true then this is the index within the TX ring of the context
+ * packet descriptor.
+ */
+static inline u16 get_fwdesc_info_ctx_idx(struct fwdesc_info *x)
+{
+ return FIELD_GET(FWDESC_INFO_CTX_IDX_MASK, x->word);
+}
+
+static inline void set_fwdesc_info_ctx_idx(struct fwdesc_info *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, FWDESC_INFO_CTX_IDX_MASK, v);
+}
+
+/** Length of the packet in bytes */
+static inline u16 get_fwdesc_info_pkt_len(struct fwdesc_info *x)
+{
+ return FIELD_GET(FWDESC_INFO_PKT_LEN_MASK, x->word);
+}
+
+static inline void set_fwdesc_info_pkt_len(struct fwdesc_info *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, FWDESC_INFO_PKT_LEN_MASK, v);
+}
+
+#endif /* QDMA_DESC_H */
diff --git a/drivers/net/ethernet/econet/qdma_regs.h b/drivers/net/ethernet/econet/qdma_regs.h
new file mode 100644
index 000000000000..abf1d9b8402f
--- /dev/null
+++ b/drivers/net/ethernet/econet/qdma_regs.h
@@ -0,0 +1,1243 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef QDMA_REGS_H
+#define QDMA_REGS_H
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/types.h>
+
+#ifndef FIELD_SET
+#define FIELD_SET(current, mask, val) \
+ (((current) & ~(mask)) | FIELD_PREP((mask), (val)))
+#endif
+
+/** qchain_regs: QDMA Chain Registers */
+struct qchain_regs {
+ /** qchain_regs_txbase: TX descriptor array address */
+ dma_addr_t txbase;
+
+ /** qchain_regs_rxbase: RX descriptor array address */
+ dma_addr_t rxbase;
+
+ /** qchain_regs_tx_cpui: TX ring CPU (driver) index */
+ u32 tx_cpui;
+
+ /** qchain_regs_tx_hwi: TX ring hardware index */
+ u32 tx_hwi;
+
+ /** qchain_regs_rx_cpui: RX ring CPU (driver) index */
+ u32 rx_cpui;
+
+ /** qchain_regs_rx_hwi: TX ring hardware index */
+ u32 rx_hwi;
+
+};
+
+/** qregs: QDMA Registers */
+struct qregs {
+ /** qregs_version: */
+ u32 version;
+
+ /** qregs_qcfg: */
+ struct qregs_qcfg {
+ /**
+ * See accessors:
+ * is_qregs_qcfg_rx_2b_offset()
+ * set_qregs_qcfg_rx_2b_offset()
+ * get_qregs_qcfg_dma_pref()
+ * set_qregs_qcfg_dma_pref()
+ * is_qregs_qcfg_msg_word_swap()
+ * set_qregs_qcfg_msg_word_swap()
+ * is_qregs_qcfg_dscp_byte_swap()
+ * set_qregs_qcfg_dscp_byte_swap()
+ * is_qregs_qcfg_payload_byte_sw()
+ * set_qregs_qcfg_payload_byte_sw()
+ * is_qregs_qcfg_vchnl_map_en()
+ * set_qregs_qcfg_vchnl_map_en()
+ * is_qregs_qcfg_vchnl_map_mode()
+ * set_qregs_qcfg_vchnl_map_mode()
+ * is_qregs_qcfg_qdma_lpbk_rxq_sel()
+ * set_qregs_qcfg_qdma_lpbk_rxq_sel()
+ * is_qregs_qcfg_slm_release_en()
+ * set_qregs_qcfg_slm_release_en()
+ * is_qregs_qcfg_tx_immediate_done()
+ * set_qregs_qcfg_tx_immediate_done()
+ * is_qregs_qcfg_irq_en()
+ * set_qregs_qcfg_irq_en()
+ * is_qregs_qcfg_gdm_loopback()
+ * set_qregs_qcfg_gdm_loopback()
+ * is_qregs_qcfg_qdma_loopback()
+ * set_qregs_qcfg_qdma_loopback()
+ * is_qregs_qcfg_check_done()
+ * set_qregs_qcfg_check_done()
+ * is_qregs_qcfg_tx_wb_done()
+ * set_qregs_qcfg_tx_wb_done()
+ * get_qregs_qcfg_burst_size()
+ * set_qregs_qcfg_burst_size()
+ * is_qregs_qcfg_rx_dma_busy()
+ * set_qregs_qcfg_rx_dma_busy()
+ * is_qregs_qcfg_rx_dma_en()
+ * set_qregs_qcfg_rx_dma_en()
+ * is_qregs_qcfg_tx_dma_busy()
+ * set_qregs_qcfg_tx_dma_busy()
+ * is_qregs_qcfg_tx_dma_en()
+ * set_qregs_qcfg_tx_dma_en()
+ */
+ u32 bitfield_0;
+
+ } qdma_cfg;
+
+ /** qregs_qchain0: */
+ struct qchain_regs qchain0;
+
+ /**
+ * qregs_hwf_desc_addr: Hardware forwarding descriptor table address. This
+ * memory must be forward-descriptor-size (16 bytes) * fwd_desc_n in size.
+ */
+ dma_addr_t hwf_desc_addr;
+
+ /**
+ * qregs_hwf_data_addr: Hardware forwarding packet content address. This memory
+ * must be pkt_sz * fwd_desc_n in size.
+ */
+ dma_addr_t hwf_data_addr;
+
+ /**
+ * See accessors:
+ * get_qregs_hwf_cfg_pkt_sz()
+ * set_qregs_hwf_cfg_pkt_sz()
+ * get_qregs_hwf_cfg_low_th()
+ * set_qregs_hwf_cfg_low_th()
+ */
+ struct hwf_cfg { u32 word; } hwf_cfg;
+
+ u32 unused_0;
+
+ /**
+ * qregs_hwf_cfg1: Hardware forwarding configuration (also called LMGR)
+ * See accessors:
+ * is_qregs_hwf_cfg1_start()
+ * set_qregs_hwf_cfg1_start()
+ * is_qregs_hwf_cfg1_overhead_en()
+ * set_qregs_hwf_cfg1_overhead_en()
+ * get_qregs_hwf_cfg1_overhead()
+ * set_qregs_hwf_cfg1_overhead()
+ * get_qregs_hwf_cfg1_fwd_desc_n()
+ * set_qregs_hwf_cfg1_fwd_desc_n()
+ */
+ struct qregs_hwf_cfg1 { u32 word; } hwf_cfg1;
+
+ u8 unused_1[12];
+
+ /** qregs_channel_retire: Referred to as QDMA_CSR_LMGR_CHNL_RETIRE */
+ u32 channel_retire;
+
+ u8 unused_2[12];
+
+ /**
+ * qregs_int_status: When an interrupt is triggered, these are the pending
+ * events
+ */
+ u32 int_status;
+
+ /** qregs_int_enable: Enabled interrupts */
+ u32 int_enable;
+
+ /** qregs_tx_int_delay: Interrupt delay for reducing interrupt load */
+ u32 tx_int_delay;
+
+ /** qregs_rx_int_delay: Interrupt delay for reducing interrupt load */
+ u32 rx_int_delay;
+
+ /** qregs_doneq: */
+ struct qregs_doneq {
+ /** qregs_doneq_addr: */
+ dma_addr_t address;
+
+ /**
+ * qregs_doneq_cfg:
+ * See accessors:
+ * get_qregs_doneq_cfg_int_threshold()
+ * set_qregs_doneq_cfg_int_threshold()
+ * get_qregs_doneq_cfg_size()
+ * set_qregs_doneq_cfg_size()
+ */
+ struct qregs_doneq_cfg { u32 word; } config;
+
+ /**
+ * qregs_doneq_pop_back: Pop this number of items from the back of the
+ * done queue, max 255
+ */
+ u32 pop_back;
+
+ /**
+ * qregs_doneq_state:
+ * See accessors:
+ * get_qregs_doneq_state_length()
+ * get_qregs_doneq_state_head_index()
+ */
+ struct qregs_doneq_state { u32 word; } state;
+
+ /**
+ * qregs_doneq_wait_time: If there is anything in the queue, fire an interrupt
+ * after this number of units of time, unit is 20 microseconds.
+ */
+ u32 wait_time;
+
+ } done_queue;
+
+ u8 unused_3[12];
+
+ /**
+ * See accessors:
+ * is_qregs_wrr_mode_use_16b()
+ * set_qregs_wrr_mode_use_16b()
+ * is_qregs_wrr_mode_by_byte()
+ * set_qregs_wrr_mode_by_byte()
+ */
+ struct wrr_mode { u32 word; } wrr_mode;
+
+ u32 unused_4;
+
+ /**
+ * qregs_wrr_weight: Read and write a WRR weight for a channel+queue pair.
+ * Called QDMA_CSR_TXWRR_WEIGHT_CFG.
+ */
+ struct qregs_wrr_weight {
+ /**
+ * See accessors:
+ * set_qregs_wrr_weight_write()
+ * is_qregs_wrr_weight_done()
+ * set_qregs_wrr_weight_channel()
+ * set_qregs_wrr_weight_queue()
+ */
+ u16 bitfield_0;
+
+ u8 unused_0;
+
+ /**
+ * qregs_wrr_weight_value: The WRR weight to read or write for the configured
+ * channel+queue.
+ */
+ u8 value;
+
+ } wrr_weight;
+
+ u32 unused_5;
+
+ /**
+ * qregs_buf_usage_cfg: Called QDMA_CSR_PSE_BUF_USAGE_CFG, a bitfield, TODO
+ * document
+ */
+ u32 buf_usage_cfg;
+
+ /**
+ * qregs_tx_meter_cfg: Called QDMA_CSR_EGRESS_RATEMETER_CFG, a bitfield, TODO
+ * document
+ */
+ u32 tx_meter_cfg;
+
+ /**
+ * qregs_tx_limit_cfg: Called QDMA_CSR_EGRESS_RATELIMIT_CFG, a bitfield, TODO
+ * document
+ */
+ u32 tx_limit_cfg;
+
+ /**
+ * qregs_tx_limit_param: Called QDMA_CSR_RATELIMIT_PARAMETER_CFG, a bitfield,
+ * per-channel tx rate limit, TODO document
+ */
+ u32 tx_limit_param;
+
+ /** qregs_tx_congest_cfg: */
+ struct qregs_tx_congest_cfg {
+ /**
+ * See accessors:
+ * is_qregs_tx_congest_cfg_tail_drop_en()
+ * set_qregs_tx_congest_cfg_tail_drop_en()
+ * is_qregs_tx_congest_cfg_dei_drop_en()
+ * set_qregs_tx_congest_cfg_dei_drop_en()
+ * is_qregs_tx_congest_cfg_dyncong_en()
+ * set_qregs_tx_congest_cfg_dyncong_en()
+ * is_qregs_tx_congest_cfg_max_thr_blk_tx1()
+ * set_qregs_tx_congest_cfg_max_thr_blk_tx1()
+ * is_qregs_tx_congest_cfg_min_thr_blk_tx1()
+ * set_qregs_tx_congest_cfg_min_thr_blk_tx1()
+ * is_qregs_tx_congest_cfg_max_thr_blk_tx0()
+ * set_qregs_tx_congest_cfg_max_thr_blk_tx0()
+ * is_qregs_tx_congest_cfg_min_thr_blk_tx0()
+ * set_qregs_tx_congest_cfg_min_thr_blk_tx0()
+ * get_qregs_tx_congest_cfg_dyncong_margin()
+ * set_qregs_tx_congest_cfg_dyncong_margin()
+ * get_qregs_tx_congest_cfg_dyncong_dei_scale()
+ * set_qregs_tx_congest_cfg_dyncong_dei_scale()
+ * is_qregs_tx_congest_cfg_dyncong_upd_wrr()
+ * set_qregs_tx_congest_cfg_dyncong_upd_wrr()
+ * is_qregs_tx_congest_cfg_dyncong_upd_txrx()
+ * set_qregs_tx_congest_cfg_dyncong_upd_txrx()
+ * is_qregs_tx_congest_cfg_dyncong_upd_tick()
+ * set_qregs_tx_congest_cfg_dyncong_upd_tick()
+ */
+ u16 bitfield_0;
+
+ /**
+ * qregs_tx_congest_cfg_dyncong_tick: Dynamic congestion ticker rate in
+ * microseconds
+ */
+ u16 dyncong_tick;
+
+ } tx_congest_cfg;
+
+ /** qregs_tx_congest_thr: */
+ struct qregs_tx_congest_thr {
+ /**
+ * qregs_tx_congest_thr_max: When total buffer usage exceeds this, drop all
+ * packets except VIP
+ */
+ u16 max;
+
+ /**
+ * qregs_tx_congest_thr_min: When total buffer usage exceeds this, dynamic
+ * congestion control will start
+ */
+ u16 min;
+
+ } tx_congest_thr;
+
+ /**
+ * qregs_tx_per_ch_dthr: Called QDMA_CSR_TXQ_DYN_CHNLTHR_CFG, tx per-channel
+ * dynamic threshold max/min threshold, TODO document
+ */
+ u32 tx_per_ch_dthr;
+
+ /**
+ * qregs_tx_per_q_dthr: Called QDMA_CSR_TXQ_DYN_QUEUETHR_CFG, tx per-queue
+ * dynamic threshold max/min threshold, TODO document
+ */
+ u32 tx_per_q_dthr;
+
+ /**
+ * qregs_tx_per_q_sthr: Called QDMA_CSR_STATIC_QUEUE_THR(0..7), tx per-queue
+ * static thresholds, if dynamic thresholds are disabled, TODO document
+ */
+ u8 unused_6[32];
+
+ u8 unused_7[16];
+
+ /** qregs_debug: */
+ struct qregs_debug {
+ /**
+ * See accessors:
+ * set_qregs_debug_mem_ctl_write()
+ * is_qregs_debug_mem_ctl_done()
+ * set_qregs_debug_mem_ctl_dataset()
+ * set_qregs_debug_mem_ctl_word_of_elem()
+ * set_qregs_debug_mem_ctl_elem()
+ */
+ struct mem_ctl { u32 word; } mem_ctl;
+
+ /**
+ * qregs_debug_mem_lo: Read or write the low bits of a memory element (use
+ * with mem_ctl) Called QDMA_CSR_DBG_MEM_XS_DATA_LO.
+ */
+ u32 mem_lo;
+
+ /**
+ * qregs_debug_mem_hi: Read or write the high bits of a memory element (use
+ * with mem_ctl) Called QDMA_CSR_DBG_MEM_XS_DATA_HI. Not used in practice.
+ */
+ u32 mem_hi;
+
+ u32 unused_0;
+
+ /**
+ * qregs_debug_hwf_desc_free: Called QDMA_CSR_DBG_LMGR_STATUS, number of free
+ * hardware forwarding descriptors
+ */
+ u32 hwf_desc_free;
+
+ /**
+ * qregs_debug_hwd_buf_used: Number of bytes of buffer used for hardwre
+ * forwarding
+ */
+ u32 hwd_buf_used;
+
+ /**
+ * qregs_debug_probe_lo: Called QDMA_CSR_DBG_QDMA_PROBE_LO, unknown usage,
+ * TODO document
+ */
+ u32 probe_lo;
+
+ /**
+ * qregs_debug_probe_hi: Called QDMA_CSR_DBG_QDMA_PROBE_HI, unknown usage,
+ * TODO document
+ */
+ u32 probe_hi;
+
+ } debug;
+
+ /**
+ * qregs_rxring_size:
+ * See accessors:
+ * get_qregs_rxring_size_ring0()
+ * set_qregs_rxring_size_ring0()
+ * get_qregs_rxring_size_ring1()
+ * set_qregs_rxring_size_ring1()
+ */
+ struct qregs_rxring_size { u32 word; } rxring_size;
+
+ /**
+ * qregs_rxring_low:
+ * See accessors:
+ * get_qregs_rxring_low_ring0()
+ * set_qregs_rxring_low_ring0()
+ * get_qregs_rxring_low_ring1()
+ * set_qregs_rxring_low_ring1()
+ */
+ struct qregs_rxring_low { u32 word; } rxring_low;
+
+ /** qregs_qchain1: */
+ struct qchain_regs qchain1;
+
+ /** qregs_cpu_rx_limit: CPU protection RX limit, TODO document */
+ u32 cpu_rx_limit;
+
+ /** qregs_cpu_rx_limit_val: CPU protection RX limit, TODO document */
+ u32 cpu_rx_limit_val;
+
+ u8 unused_8[20];
+
+ /** qregs_vch_wrr: Virtual channel WRR weighting, TODO document */
+ u32 vch_wrr;
+
+ /** qregs_vch_qmode: Virtual channel QoS mode (WRR / SP), TODO document */
+ u32 vch_qmode;
+
+ u8 unused_9[28];
+
+ /**
+ * qregs_ch_lim_en: Per-channel rate-limit enable, each bit corriponds to one
+ * channel
+ */
+ u32 ch_lim_en;
+
+ u8 unused_10[28];
+
+ /**
+ * qregs_ch_qmode: Per-channel queue prioritization mode (WRR / SP), TODO
+ * document
+ */
+ u8 unused_11[16];
+
+ u8 unused_12[112];
+
+ /**
+ * qregs_ch_tx_rate: Channel data rate, each word corrisponds to 2 channels,
+ * upper 16 bits is the odd channel number, lower 16 is the even.
+ */
+ u8 unused_13[64];
+
+ u8 unused_14[64];
+
+ /**
+ * qregs_ch_drop: Drop counter for normal packets, each byte corrisponds to one
+ * of the 32 channels.
+ */
+ u8 unused_15[32];
+
+ u8 unused_16[32];
+
+ /**
+ * qregs_ch_dei_drop: Drop counter for Drop-Elligable (DEI) packets, each byte
+ * corrisponds to one of the 32 channels.
+ */
+ u8 unused_17[32];
+
+ u8 unused_18[32];
+
+ /**
+ * qregs_pkt_ctrs: Every other word is a counter config and a counter value,
+ * called QDMA_CSR_DBG_CNTR_CFG / QDMA_CSR_DBG_CNTR_VAR. Definitely 40
+ * counters, possible 64. TODO document
+ */
+ u8 unused_19[512];
+
+ u8 unused_20[2816];
+
+};
+
+/**
+ * Bitfield accessors for: qregs_qcfg bitfield_0
+ */
+
+enum qregs_qcfg_dma_pref {
+ QREGS_QCFG_DMA_PREF_ROUND_ROBIN = 0,
+ QREGS_QCFG_DMA_PREF_FRX_TX1_TX0 = 1,
+ QREGS_QCFG_DMA_PREF_TX1_FRX_TX0 = 2,
+ QREGS_QCFG_DMA_PREF_TX1_TX0_FRX = 3,
+};
+
+enum qregs_qcfg_burst_size {
+ QREGS_QCFG_BURST_SIZE_16_BYTES = 0,
+ QREGS_QCFG_BURST_SIZE_32_BYTES = 1,
+ QREGS_QCFG_BURST_SIZE_64_BYTES = 2,
+ QREGS_QCFG_BURST_SIZE_128_BYTES = 3,
+};
+
+#define QREGS_QCFG_RX_2B_OFFSET BIT(31)
+#define QREGS_QCFG_DMA_PREF_MASK GENMASK(30, 29)
+#define QREGS_QCFG_MSG_WORD_SWAP BIT(28)
+#define QREGS_QCFG_DSCP_BYTE_SWAP BIT(27)
+#define QREGS_QCFG_PAYLOAD_BYTE_SW BIT(26)
+#define QREGS_QCFG_VCHNL_MAP_EN BIT(25)
+#define QREGS_QCFG_VCHNL_MAP_MODE BIT(24)
+#define QREGS_QCFG_QDMA_LPBK_RXQ_SEL BIT(22)
+#define QREGS_QCFG_SLM_RELEASE_EN BIT(21)
+#define QREGS_QCFG_TX_IMMEDIATE_DONE BIT(20)
+#define QREGS_QCFG_IRQ_EN BIT(19)
+#define QREGS_QCFG_GDM_LOOPBACK BIT(17)
+#define QREGS_QCFG_QDMA_LOOPBACK BIT(16)
+#define QREGS_QCFG_CHECK_DONE BIT(7)
+#define QREGS_QCFG_TX_WB_DONE BIT(6)
+#define QREGS_QCFG_BURST_SIZE_MASK GENMASK(5, 4)
+#define QREGS_QCFG_RX_DMA_BUSY BIT(3)
+#define QREGS_QCFG_RX_DMA_EN BIT(2)
+#define QREGS_QCFG_TX_DMA_BUSY BIT(1)
+#define QREGS_QCFG_TX_DMA_EN BIT(0)
+
+/** If enabled, use (dscp_pkt_ptr + 2) as starting address for rx payload */
+static inline bool is_qregs_qcfg_rx_2b_offset(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_RX_2B_OFFSET, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_rx_2b_offset(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_RX_2B_OFFSET, v);
+}
+
+/** DMA channel scheduling preference, FRX means "Forwarding and RX" */
+static inline enum qregs_qcfg_dma_pref get_qregs_qcfg_dma_pref(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_DMA_PREF_MASK, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_dma_pref(struct qregs_qcfg *x, enum qregs_qcfg_dma_pref v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_DMA_PREF_MASK, v);
+}
+
+/**
+ * Enable message word swap, don't know what this does but every implementation
+ * sets it on Big Endian.
+ */
+static inline bool is_qregs_qcfg_msg_word_swap(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_MSG_WORD_SWAP, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_msg_word_swap(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_MSG_WORD_SWAP, v);
+}
+
+/**
+ * Endian-swap packet descriptors (?), drivers always set this on Big Endian
+ * machines.
+ */
+static inline bool is_qregs_qcfg_dscp_byte_swap(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_DSCP_BYTE_SWAP, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_dscp_byte_swap(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_DSCP_BYTE_SWAP, v);
+}
+
+/**
+ * Endian-swap payload bytes, drivers always set this on Big Endian machines.
+ */
+static inline bool is_qregs_qcfg_payload_byte_sw(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_PAYLOAD_BYTE_SW, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_payload_byte_sw(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_PAYLOAD_BYTE_SW, v);
+}
+
+/** Enable virtual mapping to group queues per physical channel */
+static inline bool is_qregs_qcfg_vchnl_map_en(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_VCHNL_MAP_EN, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_vchnl_map_en(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_VCHNL_MAP_EN, v);
+}
+
+/** Map of 4 virtual channels per physical channel, 0 = map 2 */
+static inline bool is_qregs_qcfg_vchnl_map_mode(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_VCHNL_MAP_MODE, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_vchnl_map_mode(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_VCHNL_MAP_MODE, v);
+}
+
+/**
+ * If enabled, qdma loopback goes to queue 1, otherwise it goes to queue zero
+ */
+static inline bool is_qregs_qcfg_qdma_lpbk_rxq_sel(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_QDMA_LPBK_RXQ_SEL, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_qdma_lpbk_rxq_sel(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_QDMA_LPBK_RXQ_SEL, v);
+}
+
+/** Enable qdma fwd path release slm_block */
+static inline bool is_qregs_qcfg_slm_release_en(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_SLM_RELEASE_EN, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_slm_release_en(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_SLM_RELEASE_EN, v);
+}
+
+/** QDMA generate pkt_done itself instead of using pse pkt_done */
+static inline bool is_qregs_qcfg_tx_immediate_done(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_TX_IMMEDIATE_DONE, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_tx_immediate_done(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_TX_IMMEDIATE_DONE, v);
+}
+
+/** Enable "interrupt queue" (i.e. Done List) for tx dma done */
+static inline bool is_qregs_qcfg_irq_en(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_IRQ_EN, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_irq_en(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_IRQ_EN, v);
+}
+
+/** Enable gdm loopback tx packet to rx path */
+static inline bool is_qregs_qcfg_gdm_loopback(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_GDM_LOOPBACK, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_gdm_loopback(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_GDM_LOOPBACK, v);
+}
+
+/** Enable hw qdma loopback tx packet to rx path */
+static inline bool is_qregs_qcfg_qdma_loopback(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_QDMA_LOOPBACK, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_qdma_loopback(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_QDMA_LOOPBACK, v);
+}
+
+/**
+ * Check the done bit of descriptor and don't use descriptors which are marked
+ * done. If disabled, the QDMA engine will determine if a descriptor is usable
+ * based only on the ring pointers.
+ */
+static inline bool is_qregs_qcfg_check_done(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_CHECK_DONE, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_check_done(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_CHECK_DONE, v);
+}
+
+/**
+ * Set the "done" bit in tx descriptor after sending. If disabled then the
+ * engine will skip setting the done bit and rely on the driver to check the
+ * Done List (i.e. `irq_en`).
+ */
+static inline bool is_qregs_qcfg_tx_wb_done(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_TX_WB_DONE, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_tx_wb_done(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_TX_WB_DONE, v);
+}
+
+/** Number of bytes per DMA burst */
+static inline enum qregs_qcfg_burst_size get_qregs_qcfg_burst_size(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_BURST_SIZE_MASK, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_burst_size(struct qregs_qcfg *x, enum qregs_qcfg_burst_size v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_BURST_SIZE_MASK, v);
+}
+
+/** RX DMA engine currently busy */
+static inline bool is_qregs_qcfg_rx_dma_busy(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_RX_DMA_BUSY, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_rx_dma_busy(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_RX_DMA_BUSY, v);
+}
+
+/** Enable RX DMA */
+static inline bool is_qregs_qcfg_rx_dma_en(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_RX_DMA_EN, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_rx_dma_en(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_RX_DMA_EN, v);
+}
+
+/** TX DMA engine currently busy */
+static inline bool is_qregs_qcfg_tx_dma_busy(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_TX_DMA_BUSY, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_tx_dma_busy(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_TX_DMA_BUSY, v);
+}
+
+/** Enable TX DMA */
+static inline bool is_qregs_qcfg_tx_dma_en(struct qregs_qcfg *x)
+{
+ return FIELD_GET(QREGS_QCFG_TX_DMA_EN, x->bitfield_0);
+}
+
+static inline void set_qregs_qcfg_tx_dma_en(struct qregs_qcfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_QCFG_TX_DMA_EN, v);
+}
+
+/**
+ * Bitfield accessors for: struct hwf_cfg
+ * Hardware forwarding configuration
+ */
+
+enum qregs_hwf_cfg_pkt_sz {
+ QREGS_HWF_CFG_PKT_SZ_2048 = 0,
+ QREGS_HWF_CFG_PKT_SZ_4096 = 1,
+ QREGS_HWF_CFG_PKT_SZ_8192 = 2,
+ QREGS_HWF_CFG_PKT_SZ_16384 = 3,
+};
+
+#define QREGS_HWF_CFG_PKT_SZ_MASK GENMASK(29, 28)
+#define QREGS_HWF_CFG_LOW_TH_MASK GENMASK(12, 0)
+
+/**
+ * The size of the packet buffers in hwf_data_addr (and therefore the maximum
+ * effective MTU).
+ */
+static inline enum qregs_hwf_cfg_pkt_sz get_qregs_hwf_cfg_pkt_sz(struct hwf_cfg *x)
+{
+ return FIELD_GET(QREGS_HWF_CFG_PKT_SZ_MASK, x->word);
+}
+
+static inline void set_qregs_hwf_cfg_pkt_sz(struct hwf_cfg *x, enum qregs_hwf_cfg_pkt_sz v)
+{
+ x->word = FIELD_SET(x->word, QREGS_HWF_CFG_PKT_SZ_MASK, v);
+}
+
+/**
+ * When number of available (not busy) hardware descriptors is below this,
+ * generate an interrupt and pause hardware forwarding.
+ */
+static inline u16 get_qregs_hwf_cfg_low_th(struct hwf_cfg *x)
+{
+ return FIELD_GET(QREGS_HWF_CFG_LOW_TH_MASK, x->word);
+}
+
+static inline void set_qregs_hwf_cfg_low_th(struct hwf_cfg *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, QREGS_HWF_CFG_LOW_TH_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: qregs_hwf_cfg1
+ * Register layout (32-bit word):
+ * - Bit 31: START
+ * - Bit 24: OVERHEAD_EN
+ * - Bits 23-16: OVERHEAD (8 bits)
+ * - Bits 15-0: FWD_DESC_N (16 bits)
+ */
+
+#define QREGS_HWF_CFG1_START BIT(31)
+#define QREGS_HWF_CFG1_OVERHEAD_EN BIT(24)
+#define QREGS_HWF_CFG1_OVERHEAD_MASK GENMASK(23, 16)
+#define QREGS_HWF_CFG1_FWD_DESC_N_MASK GENMASK(15, 0)
+
+/** Start up the hardware forwarding subsystem */
+static inline bool is_qregs_hwf_cfg1_start(struct qregs_hwf_cfg1 *x)
+{
+ return FIELD_GET(QREGS_HWF_CFG1_START, x->word);
+}
+
+static inline void set_qregs_hwf_cfg1_start(struct qregs_hwf_cfg1 *x, bool v)
+{
+ x->word = FIELD_SET(x->word, QREGS_HWF_CFG1_START, v);
+}
+
+/** When set, add overhead to packet size for accounting purposes */
+static inline bool is_qregs_hwf_cfg1_overhead_en(struct qregs_hwf_cfg1 *x)
+{
+ return FIELD_GET(QREGS_HWF_CFG1_OVERHEAD_EN, x->word);
+}
+
+static inline void set_qregs_hwf_cfg1_overhead_en(struct qregs_hwf_cfg1 *x, bool v)
+{
+ x->word = FIELD_SET(x->word, QREGS_HWF_CFG1_OVERHEAD_EN, v);
+}
+
+/** Amount of overhead to add to packet size for accounting */
+static inline u8 get_qregs_hwf_cfg1_overhead(struct qregs_hwf_cfg1 *x)
+{
+ return FIELD_GET(QREGS_HWF_CFG1_OVERHEAD_MASK, x->word);
+}
+
+static inline void set_qregs_hwf_cfg1_overhead(struct qregs_hwf_cfg1 *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, QREGS_HWF_CFG1_OVERHEAD_MASK, v);
+}
+
+/** Number of forward descriptors to use */
+static inline u16 get_qregs_hwf_cfg1_fwd_desc_n(struct qregs_hwf_cfg1 *x)
+{
+ return FIELD_GET(QREGS_HWF_CFG1_FWD_DESC_N_MASK, x->word);
+}
+
+static inline void set_qregs_hwf_cfg1_fwd_desc_n(struct qregs_hwf_cfg1 *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, QREGS_HWF_CFG1_FWD_DESC_N_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: struct wrr_mode
+ * WRR control register, called QDMA_CSR_TXWRR_MODE_CFG.
+ */
+
+#define QREGS_WRR_MODE_USE_16B BIT(31)
+#define QREGS_WRR_MODE_BY_BYTE BIT(3)
+
+/** If enabled, weighting is based on 16 byte units, otherwise 64 byte. */
+static inline bool is_qregs_wrr_mode_use_16b(struct wrr_mode *x)
+{
+ return FIELD_GET(QREGS_WRR_MODE_USE_16B, x->word);
+}
+
+static inline void set_qregs_wrr_mode_use_16b(struct wrr_mode *x, bool v)
+{
+ x->word = FIELD_SET(x->word, QREGS_WRR_MODE_USE_16B, v);
+}
+
+/** If enabled, weighting is by byte, otherwise by packet. */
+static inline bool is_qregs_wrr_mode_by_byte(struct wrr_mode *x)
+{
+ return FIELD_GET(QREGS_WRR_MODE_BY_BYTE, x->word);
+}
+
+static inline void set_qregs_wrr_mode_by_byte(struct wrr_mode *x, bool v)
+{
+ x->word = FIELD_SET(x->word, QREGS_WRR_MODE_BY_BYTE, v);
+}
+
+/**
+ * Bitfield accessors for: qregs_wrr_weight bitfield_0
+ */
+
+#define QREGS_WRR_WEIGHT_WRITE BIT(15)
+#define QREGS_WRR_WEIGHT_DONE BIT(14)
+#define QREGS_WRR_WEIGHT_CHANNEL_MASK GENMASK(7, 3)
+#define QREGS_WRR_WEIGHT_QUEUE_MASK GENMASK(2, 0)
+
+static inline void set_qregs_wrr_weight_write(struct qregs_wrr_weight *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_WRR_WEIGHT_WRITE, v);
+}
+
+/**
+ * After performing an operation, this bit asserts from the hardware to indicate
+ * the operation is done.
+ */
+static inline bool is_qregs_wrr_weight_done(struct qregs_wrr_weight *x)
+{
+ return FIELD_GET(QREGS_WRR_WEIGHT_DONE, x->bitfield_0);
+}
+
+static inline void set_qregs_wrr_weight_channel(struct qregs_wrr_weight *x, u8 v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_WRR_WEIGHT_CHANNEL_MASK, v);
+}
+
+static inline void set_qregs_wrr_weight_queue(struct qregs_wrr_weight *x, u8 v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_WRR_WEIGHT_QUEUE_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: qregs_tx_congest_cfg bitfield_0
+ * Configuration for TX congestion dropping
+ */
+
+enum qregs_tx_congest_cfg_dyncong_margin {
+ QREGS_TX_CONGEST_CFG_DYNCONG_MARGIN_0_PCT = 0,
+ QREGS_TX_CONGEST_CFG_DYNCONG_MARGIN_25_PCT = 1,
+ QREGS_TX_CONGEST_CFG_DYNCONG_MARGIN_50_PCT = 2,
+ QREGS_TX_CONGEST_CFG_DYNCONG_MARGIN_100_PCT = 3,
+};
+
+enum qregs_tx_congest_cfg_dyncong_dei_scale {
+ QREGS_TX_CONGEST_CFG_DYNCONG_DEI_SCALE_HALF = 0,
+ QREGS_TX_CONGEST_CFG_DYNCONG_DEI_SCALE_QUARTER = 1,
+ QREGS_TX_CONGEST_CFG_DYNCONG_DEI_SCALE_EIGHTH = 2,
+ QREGS_TX_CONGEST_CFG_DYNCONG_DEI_SCALE_SIXTEENTH = 3,
+};
+
+#define QREGS_TX_CONGEST_CFG_TAIL_DROP_EN BIT(15)
+#define QREGS_TX_CONGEST_CFG_DEI_DROP_EN BIT(14)
+#define QREGS_TX_CONGEST_CFG_DYNCONG_EN BIT(13)
+#define QREGS_TX_CONGEST_CFG_MAX_THR_BLK_TX1 BIT(11)
+#define QREGS_TX_CONGEST_CFG_MIN_THR_BLK_TX1 BIT(10)
+#define QREGS_TX_CONGEST_CFG_MAX_THR_BLK_TX0 BIT(9)
+#define QREGS_TX_CONGEST_CFG_MIN_THR_BLK_TX0 BIT(8)
+#define QREGS_TX_CONGEST_CFG_DYNCONG_MARGIN_MASK GENMASK(7, 6)
+#define QREGS_TX_CONGEST_CFG_DYNCONG_DEI_SCALE_MASK GENMASK(5, 4)
+#define QREGS_TX_CONGEST_CFG_DYNCONG_UPD_WRR BIT(2)
+#define QREGS_TX_CONGEST_CFG_DYNCONG_UPD_TXRX BIT(1)
+#define QREGS_TX_CONGEST_CFG_DYNCONG_UPD_TICK BIT(0)
+
+static inline bool is_qregs_tx_congest_cfg_tail_drop_en(struct qregs_tx_congest_cfg *x)
+{
+ return FIELD_GET(QREGS_TX_CONGEST_CFG_TAIL_DROP_EN, x->bitfield_0);
+}
+
+static inline void set_qregs_tx_congest_cfg_tail_drop_en(struct qregs_tx_congest_cfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_TX_CONGEST_CFG_TAIL_DROP_EN, v);
+}
+
+/** Support 802.1ad DEI packet dropping */
+static inline bool is_qregs_tx_congest_cfg_dei_drop_en(struct qregs_tx_congest_cfg *x)
+{
+ return FIELD_GET(QREGS_TX_CONGEST_CFG_DEI_DROP_EN, x->bitfield_0);
+}
+
+static inline void set_qregs_tx_congest_cfg_dei_drop_en(struct qregs_tx_congest_cfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_TX_CONGEST_CFG_DEI_DROP_EN, v);
+}
+
+/** Enable dynamic congestion algorithm */
+static inline bool is_qregs_tx_congest_cfg_dyncong_en(struct qregs_tx_congest_cfg *x)
+{
+ return FIELD_GET(QREGS_TX_CONGEST_CFG_DYNCONG_EN, x->bitfield_0);
+}
+
+static inline void set_qregs_tx_congest_cfg_dyncong_en(struct qregs_tx_congest_cfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_TX_CONGEST_CFG_DYNCONG_EN, v);
+}
+
+/**
+ * Block TX Ring1 when TX buffer usage exceeds max threshold (see
+ * tx_congest_thr)
+ */
+static inline bool is_qregs_tx_congest_cfg_max_thr_blk_tx1(struct qregs_tx_congest_cfg *x)
+{
+ return FIELD_GET(QREGS_TX_CONGEST_CFG_MAX_THR_BLK_TX1, x->bitfield_0);
+}
+
+static inline void set_qregs_tx_congest_cfg_max_thr_blk_tx1(struct qregs_tx_congest_cfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_TX_CONGEST_CFG_MAX_THR_BLK_TX1, v);
+}
+
+/**
+ * Block TX Ring1 when TX buffer usage exceeds min threshold (see
+ * tx_congest_thr)
+ */
+static inline bool is_qregs_tx_congest_cfg_min_thr_blk_tx1(struct qregs_tx_congest_cfg *x)
+{
+ return FIELD_GET(QREGS_TX_CONGEST_CFG_MIN_THR_BLK_TX1, x->bitfield_0);
+}
+
+static inline void set_qregs_tx_congest_cfg_min_thr_blk_tx1(struct qregs_tx_congest_cfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_TX_CONGEST_CFG_MIN_THR_BLK_TX1, v);
+}
+
+/**
+ * Block TX Ring0 when TX buffer usage exceeds max threshold (see
+ * tx_congest_thr)
+ */
+static inline bool is_qregs_tx_congest_cfg_max_thr_blk_tx0(struct qregs_tx_congest_cfg *x)
+{
+ return FIELD_GET(QREGS_TX_CONGEST_CFG_MAX_THR_BLK_TX0, x->bitfield_0);
+}
+
+static inline void set_qregs_tx_congest_cfg_max_thr_blk_tx0(struct qregs_tx_congest_cfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_TX_CONGEST_CFG_MAX_THR_BLK_TX0, v);
+}
+
+/**
+ * Block TX Ring0 when TX buffer usage exceeds min threshold (see
+ * tx_congest_thr)
+ */
+static inline bool is_qregs_tx_congest_cfg_min_thr_blk_tx0(struct qregs_tx_congest_cfg *x)
+{
+ return FIELD_GET(QREGS_TX_CONGEST_CFG_MIN_THR_BLK_TX0, x->bitfield_0);
+}
+
+static inline void set_qregs_tx_congest_cfg_min_thr_blk_tx0(struct qregs_tx_congest_cfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_TX_CONGEST_CFG_MIN_THR_BLK_TX0, v);
+}
+
+static inline enum qregs_tx_congest_cfg_dyncong_margin
+get_qregs_tx_congest_cfg_dyncong_margin(struct qregs_tx_congest_cfg *x)
+{
+ return FIELD_GET(QREGS_TX_CONGEST_CFG_DYNCONG_MARGIN_MASK, x->bitfield_0);
+}
+
+static inline void
+set_qregs_tx_congest_cfg_dyncong_margin(struct qregs_tx_congest_cfg *x,
+ enum qregs_tx_congest_cfg_dyncong_margin v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_TX_CONGEST_CFG_DYNCONG_MARGIN_MASK, v);
+}
+
+static inline enum qregs_tx_congest_cfg_dyncong_dei_scale
+get_qregs_tx_congest_cfg_dyncong_dei_scale(struct qregs_tx_congest_cfg *x)
+{
+ return FIELD_GET(QREGS_TX_CONGEST_CFG_DYNCONG_DEI_SCALE_MASK, x->bitfield_0);
+}
+
+static inline void
+set_qregs_tx_congest_cfg_dyncong_dei_scale(struct qregs_tx_congest_cfg *x,
+ enum qregs_tx_congest_cfg_dyncong_dei_scale v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_TX_CONGEST_CFG_DYNCONG_DEI_SCALE_MASK, v);
+}
+
+/** Update dynamic congestion after each update to WRR weights. */
+static inline bool is_qregs_tx_congest_cfg_dyncong_upd_wrr(struct qregs_tx_congest_cfg *x)
+{
+ return FIELD_GET(QREGS_TX_CONGEST_CFG_DYNCONG_UPD_WRR, x->bitfield_0);
+}
+
+static inline void set_qregs_tx_congest_cfg_dyncong_upd_wrr(struct qregs_tx_congest_cfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_TX_CONGEST_CFG_DYNCONG_UPD_WRR, v);
+}
+
+/** Update dynamic congestion after each TX or RX */
+static inline bool is_qregs_tx_congest_cfg_dyncong_upd_txrx(struct qregs_tx_congest_cfg *x)
+{
+ return FIELD_GET(QREGS_TX_CONGEST_CFG_DYNCONG_UPD_TXRX, x->bitfield_0);
+}
+
+static inline void set_qregs_tx_congest_cfg_dyncong_upd_txrx(struct qregs_tx_congest_cfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_TX_CONGEST_CFG_DYNCONG_UPD_TXRX, v);
+}
+
+/** Update dynamic congestion after each tick */
+static inline bool is_qregs_tx_congest_cfg_dyncong_upd_tick(struct qregs_tx_congest_cfg *x)
+{
+ return FIELD_GET(QREGS_TX_CONGEST_CFG_DYNCONG_UPD_TICK, x->bitfield_0);
+}
+
+static inline void set_qregs_tx_congest_cfg_dyncong_upd_tick(struct qregs_tx_congest_cfg *x, bool v)
+{
+ x->bitfield_0 = FIELD_SET(x->bitfield_0, QREGS_TX_CONGEST_CFG_DYNCONG_UPD_TICK, v);
+}
+
+/**
+ * Bitfield accessors for: struct mem_ctl
+ * Called QDMA_CSR_DBG_MEM_XS_CFG, used for debugging access to memory spaces.
+ */
+
+enum qregs_debug_mem_ctl_dataset {
+ QREGS_DEBUG_MEM_CTL_DATASET_DESC = 0,
+ QREGS_DEBUG_MEM_CTL_DATASET_QUEUE = 1,
+ QREGS_DEBUG_MEM_CTL_DATASET_QOS_WEIGHT_CTR = 2,
+ QREGS_DEBUG_MEM_CTL_DATASET_DMA_IDX = 3,
+ QREGS_DEBUG_MEM_CTL_DATASET_BUF_MON = 4,
+ QREGS_DEBUG_MEM_CTL_DATASET_RL_PARAM = 5,
+ QREGS_DEBUG_MEM_CTL_DATASET_VCH_WEIGHT = 6,
+};
+
+#define QREGS_DEBUG_MEM_CTL_WRITE BIT(31)
+#define QREGS_DEBUG_MEM_CTL_DONE BIT(30)
+#define QREGS_DEBUG_MEM_CTL_DATASET_MASK GENMASK(26, 24)
+#define QREGS_DEBUG_MEM_CTL_WORD_OF_ELEM_MASK GENMASK(20, 16)
+#define QREGS_DEBUG_MEM_CTL_ELEM_MASK GENMASK(15, 0)
+
+static inline void set_qregs_debug_mem_ctl_write(struct mem_ctl *x, bool v)
+{
+ x->word = FIELD_SET(x->word, QREGS_DEBUG_MEM_CTL_WRITE, v);
+}
+
+/** Becomes set when the command is completed */
+static inline bool is_qregs_debug_mem_ctl_done(struct mem_ctl *x)
+{
+ return FIELD_GET(QREGS_DEBUG_MEM_CTL_DONE, x->word);
+}
+
+static inline void set_qregs_debug_mem_ctl_dataset(struct mem_ctl *x,
+ enum qregs_debug_mem_ctl_dataset v)
+{
+ x->word = FIELD_SET(x->word, QREGS_DEBUG_MEM_CTL_DATASET_MASK, v);
+}
+
+static inline void set_qregs_debug_mem_ctl_word_of_elem(struct mem_ctl *x, u8 v)
+{
+ x->word = FIELD_SET(x->word, QREGS_DEBUG_MEM_CTL_WORD_OF_ELEM_MASK, v);
+}
+
+static inline void set_qregs_debug_mem_ctl_elem(struct mem_ctl *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, QREGS_DEBUG_MEM_CTL_ELEM_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: qregs_doneq_cfg
+ * Register layout (32-bit word):
+ * - Bits 31-16: INT_THRESHOLD (when done queue is this full, fire interrupt)
+ * - Bits 15-0: SIZE (size of done queue buffer in 4-byte units)
+ */
+#define QREGS_DONEQ_CFG_INT_THRESHOLD_MASK GENMASK(31, 16)
+#define QREGS_DONEQ_CFG_SIZE_MASK GENMASK(15, 0)
+
+static inline u16 get_qregs_doneq_cfg_int_threshold(struct qregs_doneq_cfg *x)
+{
+ return FIELD_GET(QREGS_DONEQ_CFG_INT_THRESHOLD_MASK, x->word);
+}
+
+static inline void set_qregs_doneq_cfg_int_threshold(struct qregs_doneq_cfg *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, QREGS_DONEQ_CFG_INT_THRESHOLD_MASK, v);
+}
+
+static inline u16 get_qregs_doneq_cfg_size(struct qregs_doneq_cfg *x)
+{
+ return FIELD_GET(QREGS_DONEQ_CFG_SIZE_MASK, x->word);
+}
+
+static inline void set_qregs_doneq_cfg_size(struct qregs_doneq_cfg *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, QREGS_DONEQ_CFG_SIZE_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: qregs_doneq_state
+ * Register layout (32-bit word):
+ * - Bits 31-16: LENGTH (number of items waiting in the queue)
+ * - Bits 15-0: HEAD_INDEX (index of the first item in the list)
+ */
+#define QREGS_DONEQ_STATE_LENGTH_MASK GENMASK(31, 16)
+#define QREGS_DONEQ_STATE_HEAD_INDEX_MASK GENMASK(15, 0)
+
+static inline u16 get_qregs_doneq_state_length(struct qregs_doneq_state *x)
+{
+ return FIELD_GET(QREGS_DONEQ_STATE_LENGTH_MASK, x->word);
+}
+
+static inline u16 get_qregs_doneq_state_head_index(struct qregs_doneq_state *x)
+{
+ return FIELD_GET(QREGS_DONEQ_STATE_HEAD_INDEX_MASK, x->word);
+}
+
+/**
+ * Bitfield accessors for: qregs_rxring_size
+ * Register layout (32-bit word):
+ * - Bits 31-16: RING0_SIZE (size of RX ring 0, max 4095)
+ * - Bits 15-0: RING1_SIZE (size of RX ring 1, max 4095)
+ */
+#define QREGS_RXRING_SIZE_RING0_MASK GENMASK(31, 16)
+#define QREGS_RXRING_SIZE_RING1_MASK GENMASK(15, 0)
+
+static inline u16 get_qregs_rxring_size_ring0(struct qregs_rxring_size *x)
+{
+ return FIELD_GET(QREGS_RXRING_SIZE_RING0_MASK, x->word);
+}
+
+static inline void set_qregs_rxring_size_ring0(struct qregs_rxring_size *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, QREGS_RXRING_SIZE_RING0_MASK, v);
+}
+
+static inline u16 get_qregs_rxring_size_ring1(struct qregs_rxring_size *x)
+{
+ return FIELD_GET(QREGS_RXRING_SIZE_RING1_MASK, x->word);
+}
+
+static inline void set_qregs_rxring_size_ring1(struct qregs_rxring_size *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, QREGS_RXRING_SIZE_RING1_MASK, v);
+}
+
+/**
+ * Bitfield accessors for: qregs_rxring_low
+ * Register layout (32-bit word):
+ * - Bits 31-16: RING0_LOW (interrupt threshold for ring 0, max 4095)
+ * - Bits 15-0: RING1_LOW (interrupt threshold for ring 1, max 4095)
+ */
+#define QREGS_RXRING_LOW_RING0_MASK GENMASK(31, 16)
+#define QREGS_RXRING_LOW_RING1_MASK GENMASK(15, 0)
+
+static inline u16 get_qregs_rxring_low_ring0(struct qregs_rxring_low *x)
+{
+ return FIELD_GET(QREGS_RXRING_LOW_RING0_MASK, x->word);
+}
+
+static inline void set_qregs_rxring_low_ring0(struct qregs_rxring_low *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, QREGS_RXRING_LOW_RING0_MASK, v);
+}
+
+static inline u16 get_qregs_rxring_low_ring1(struct qregs_rxring_low *x)
+{
+ return FIELD_GET(QREGS_RXRING_LOW_RING1_MASK, x->word);
+}
+
+static inline void set_qregs_rxring_low_ring1(struct qregs_rxring_low *x, u16 v)
+{
+ x->word = FIELD_SET(x->word, QREGS_RXRING_LOW_RING1_MASK, v);
+}
+
+#endif
--
2.39.5
More information about the Linux-mediatek
mailing list