summaryrefslogtreecommitdiffstats
path: root/drivers/net/dsa/netc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/dsa/netc')
-rw-r--r--drivers/net/dsa/netc/Kconfig16
-rw-r--r--drivers/net/dsa/netc/Makefile3
-rw-r--r--drivers/net/dsa/netc/netc_ethtool.c291
-rw-r--r--drivers/net/dsa/netc/netc_main.c2524
-rw-r--r--drivers/net/dsa/netc/netc_platform.c87
-rw-r--r--drivers/net/dsa/netc/netc_switch.h206
-rw-r--r--drivers/net/dsa/netc/netc_switch_hw.h372
7 files changed, 3499 insertions, 0 deletions
diff --git a/drivers/net/dsa/netc/Kconfig b/drivers/net/dsa/netc/Kconfig
new file mode 100644
index 000000000..793f7691a
--- /dev/null
+++ b/drivers/net/dsa/netc/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config NET_DSA_NETC_SWITCH
+ tristate "NXP NETC Ethernet switch support"
+ depends on ARM64 || COMPILE_TEST
+ depends on NET_DSA && PCI
+ depends on NET_VENDOR_FREESCALE
+ select NET_DSA_TAG_NETC
+ select FSL_ENETC_MDIO
+ select NXP_NTMP
+ select NXP_NETC_LIB
+ help
+ This driver supports the NXP NETC Ethernet switch, which is embedded
+ as a PCIe function of the NXP NETC IP. But note that this driver is
+ only available for NETC v4.3 and later versions.
+
+ If compiled as module (M), the module name is nxp-netc-switch.
diff --git a/drivers/net/dsa/netc/Makefile b/drivers/net/dsa/netc/Makefile
new file mode 100644
index 000000000..f40b13c70
--- /dev/null
+++ b/drivers/net/dsa/netc/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_NET_DSA_NETC_SWITCH) += nxp-netc-switch.o
+nxp-netc-switch-objs := netc_main.o netc_platform.o netc_ethtool.o
diff --git a/drivers/net/dsa/netc/netc_ethtool.c b/drivers/net/dsa/netc/netc_ethtool.c
new file mode 100644
index 000000000..e6028851a
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_ethtool.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ * NXP NETC switch driver
+ * Copyright 2025-2026 NXP
+ */
+
+#include <linux/ethtool_netlink.h>
+
+#include "netc_switch.h"
+
+static const struct ethtool_rmon_hist_range netc_rmon_ranges[] = {
+ { 64, 64 },
+ { 65, 127 },
+ { 128, 255 },
+ { 256, 511 },
+ { 512, 1023 },
+ { 1024, 1522 },
+ { 1523, NETC_MAX_FRAME_LEN },
+ { }
+};
+
+static const struct netc_port_stat netc_port_counters[] = {
+ { NETC_PTGSLACR, "port gate late arrival frames" },
+ { NETC_PSDFTCR, "port SDF transmit frames" },
+ { NETC_PSDFDDCR, "port SDF drop duplicate frames" },
+ { NETC_PRXDCR, "port rx discard frames" },
+ { NETC_PRXDCRRR, "port rx discard read-reset" },
+ { NETC_PRXDCRR0, "port rx discard reason 0" },
+ { NETC_PRXDCRR1, "port rx discard reason 1" },
+ { NETC_PTXDCR, "port tx discard frames" },
+ { NETC_BPDCR, "bridge port discard frames" },
+};
+
+static const struct netc_port_stat netc_emac_counters[] = {
+ { NETC_PM_ROCT(0), "eMAC rx octets" },
+ { NETC_PM_RVLAN(0), "eMAC rx VLAN frames" },
+ { NETC_PM_RERR(0), "eMAC rx frame errors" },
+ { NETC_PM_RUCA(0), "eMAC rx unicast frames" },
+ { NETC_PM_RDRP(0), "eMAC rx dropped packets" },
+ { NETC_PM_RPKT(0), "eMAC rx packets" },
+ { NETC_PM_TOCT(0), "eMAC tx octets" },
+ { NETC_PM_TVLAN(0), "eMAC tx VLAN frames" },
+ { NETC_PM_TFCS(0), "eMAC tx FCS errors" },
+ { NETC_PM_TUCA(0), "eMAC tx unicast frames" },
+ { NETC_PM_TPKT(0), "eMAC tx packets" },
+ { NETC_PM_TUND(0), "eMAC tx undersized packets" },
+ { NETC_PM_TIOCT(0), "eMAC tx invalid octets" },
+};
+
+static const struct netc_port_stat netc_pmac_counters[] = {
+ { NETC_PM_ROCT(1), "pMAC rx octets" },
+ { NETC_PM_RVLAN(1), "pMAC rx VLAN frames" },
+ { NETC_PM_RERR(1), "pMAC rx frame errors" },
+ { NETC_PM_RUCA(1), "pMAC rx unicast frames" },
+ { NETC_PM_RDRP(1), "pMAC rx dropped packets" },
+ { NETC_PM_RPKT(1), "pMAC rx packets" },
+ { NETC_PM_TOCT(1), "pMAC tx octets" },
+ { NETC_PM_TVLAN(1), "pMAC tx VLAN frames" },
+ { NETC_PM_TFCS(1), "pMAC tx FCS errors" },
+ { NETC_PM_TUCA(1), "pMAC tx unicast frames" },
+ { NETC_PM_TPKT(1), "pMAC tx packets" },
+ { NETC_PM_TUND(1), "pMAC tx undersized packets" },
+ { NETC_PM_TIOCT(1), "pMAC tx invalid octets" },
+};
+
+static void netc_port_pause_stats(struct netc_port *np, int mac,
+ struct ethtool_pause_stats *stats)
+{
+ if (mac && !np->caps.pmac)
+ return;
+
+ stats->tx_pause_frames = netc_port_rd64(np, NETC_PM_TXPF(mac));
+ stats->rx_pause_frames = netc_port_rd64(np, NETC_PM_RXPF(mac));
+}
+
+void netc_port_get_pause_stats(struct dsa_switch *ds, int port,
+ struct ethtool_pause_stats *pause_stats)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct net_device *ndev;
+
+ switch (pause_stats->src) {
+ case ETHTOOL_MAC_STATS_SRC_EMAC:
+ netc_port_pause_stats(np, 0, pause_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_PMAC:
+ netc_port_pause_stats(np, 1, pause_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_AGGREGATE:
+ ndev = dsa_to_port(ds, port)->user;
+ ethtool_aggregate_pause_stats(ndev, pause_stats);
+ break;
+ }
+}
+
+static void netc_port_rmon_stats(struct netc_port *np, int mac,
+ struct ethtool_rmon_stats *stats)
+{
+ if (mac && !np->caps.pmac)
+ return;
+
+ stats->undersize_pkts = netc_port_rd64(np, NETC_PM_RUND(mac));
+ stats->oversize_pkts = netc_port_rd64(np, NETC_PM_ROVR(mac));
+ stats->fragments = netc_port_rd64(np, NETC_PM_RFRG(mac));
+ stats->jabbers = netc_port_rd64(np, NETC_PM_RJBR(mac));
+
+ stats->hist[0] = netc_port_rd64(np, NETC_PM_R64(mac));
+ stats->hist[1] = netc_port_rd64(np, NETC_PM_R127(mac));
+ stats->hist[2] = netc_port_rd64(np, NETC_PM_R255(mac));
+ stats->hist[3] = netc_port_rd64(np, NETC_PM_R511(mac));
+ stats->hist[4] = netc_port_rd64(np, NETC_PM_R1023(mac));
+ stats->hist[5] = netc_port_rd64(np, NETC_PM_R1522(mac));
+ stats->hist[6] = netc_port_rd64(np, NETC_PM_R1523X(mac));
+
+ stats->hist_tx[0] = netc_port_rd64(np, NETC_PM_T64(mac));
+ stats->hist_tx[1] = netc_port_rd64(np, NETC_PM_T127(mac));
+ stats->hist_tx[2] = netc_port_rd64(np, NETC_PM_T255(mac));
+ stats->hist_tx[3] = netc_port_rd64(np, NETC_PM_T511(mac));
+ stats->hist_tx[4] = netc_port_rd64(np, NETC_PM_T1023(mac));
+ stats->hist_tx[5] = netc_port_rd64(np, NETC_PM_T1522(mac));
+ stats->hist_tx[6] = netc_port_rd64(np, NETC_PM_T1523X(mac));
+}
+
+void netc_port_get_rmon_stats(struct dsa_switch *ds, int port,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct net_device *ndev;
+
+ *ranges = netc_rmon_ranges;
+
+ switch (rmon_stats->src) {
+ case ETHTOOL_MAC_STATS_SRC_EMAC:
+ netc_port_rmon_stats(np, 0, rmon_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_PMAC:
+ netc_port_rmon_stats(np, 1, rmon_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_AGGREGATE:
+ ndev = dsa_to_port(ds, port)->user;
+ ethtool_aggregate_rmon_stats(ndev, rmon_stats);
+ break;
+ }
+}
+
+static void netc_port_ctrl_stats(struct netc_port *np, int mac,
+ struct ethtool_eth_ctrl_stats *stats)
+{
+ if (mac && !np->caps.pmac)
+ return;
+
+ stats->MACControlFramesTransmitted =
+ netc_port_rd64(np, NETC_PM_TCNP(mac));
+ stats->MACControlFramesReceived =
+ netc_port_rd64(np, NETC_PM_RCNP(mac));
+}
+
+void netc_port_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_ctrl_stats *ctrl_stats)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct net_device *ndev;
+
+ switch (ctrl_stats->src) {
+ case ETHTOOL_MAC_STATS_SRC_EMAC:
+ netc_port_ctrl_stats(np, 0, ctrl_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_PMAC:
+ netc_port_ctrl_stats(np, 1, ctrl_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_AGGREGATE:
+ ndev = dsa_to_port(ds, port)->user;
+ ethtool_aggregate_ctrl_stats(ndev, ctrl_stats);
+ break;
+ }
+}
+
+static void netc_port_mac_stats(struct netc_port *np, int mac,
+ struct ethtool_eth_mac_stats *stats)
+{
+ if (mac && !np->caps.pmac)
+ return;
+
+ stats->FramesTransmittedOK = netc_port_rd64(np, NETC_PM_TFRM(mac));
+ stats->SingleCollisionFrames = netc_port_rd64(np, NETC_PM_TSCOL(mac));
+ stats->MultipleCollisionFrames =
+ netc_port_rd64(np, NETC_PM_TMCOL(mac));
+ stats->FramesReceivedOK = netc_port_rd64(np, NETC_PM_RFRM(mac));
+ stats->FrameCheckSequenceErrors =
+ netc_port_rd64(np, NETC_PM_RFCS(mac));
+ stats->AlignmentErrors = netc_port_rd64(np, NETC_PM_RALN(mac));
+ stats->OctetsTransmittedOK = netc_port_rd64(np, NETC_PM_TEOCT(mac));
+ stats->FramesWithDeferredXmissions =
+ netc_port_rd64(np, NETC_PM_TDFR(mac));
+ stats->LateCollisions = netc_port_rd64(np, NETC_PM_TLCOL(mac));
+ stats->FramesAbortedDueToXSColls =
+ netc_port_rd64(np, NETC_PM_TECOL(mac));
+ stats->FramesLostDueToIntMACXmitError =
+ netc_port_rd64(np, NETC_PM_TERR(mac));
+ stats->OctetsReceivedOK = netc_port_rd64(np, NETC_PM_REOCT(mac));
+ stats->FramesLostDueToIntMACRcvError =
+ netc_port_rd64(np, NETC_PM_RDRNTP(mac));
+ stats->MulticastFramesXmittedOK =
+ netc_port_rd64(np, NETC_PM_TMCA(mac));
+ stats->BroadcastFramesXmittedOK =
+ netc_port_rd64(np, NETC_PM_TBCA(mac));
+ stats->MulticastFramesReceivedOK =
+ netc_port_rd64(np, NETC_PM_RMCA(mac));
+ stats->BroadcastFramesReceivedOK =
+ netc_port_rd64(np, NETC_PM_RBCA(mac));
+ stats->FramesWithExcessiveDeferral =
+ netc_port_rd64(np, NETC_PM_TEDFR(mac));
+}
+
+void netc_port_get_eth_mac_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_mac_stats *mac_stats)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct net_device *ndev;
+
+ switch (mac_stats->src) {
+ case ETHTOOL_MAC_STATS_SRC_EMAC:
+ netc_port_mac_stats(np, 0, mac_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_PMAC:
+ netc_port_mac_stats(np, 1, mac_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_AGGREGATE:
+ ndev = dsa_to_port(ds, port)->user;
+ ethtool_aggregate_mac_stats(ndev, mac_stats);
+ break;
+ }
+}
+
+int netc_port_get_sset_count(struct dsa_switch *ds, int port, int sset)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ int size;
+
+ if (sset != ETH_SS_STATS)
+ return -EOPNOTSUPP;
+
+ size = ARRAY_SIZE(netc_port_counters) +
+ ARRAY_SIZE(netc_emac_counters);
+
+ if (np->caps.pmac)
+ size += ARRAY_SIZE(netc_pmac_counters);
+
+ return size;
+}
+
+void netc_port_get_strings(struct dsa_switch *ds, int port,
+ u32 sset, u8 *data)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ int i;
+
+ if (sset != ETH_SS_STATS)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(netc_port_counters); i++)
+ ethtool_cpy(&data, netc_port_counters[i].name);
+
+ for (i = 0; i < ARRAY_SIZE(netc_emac_counters); i++)
+ ethtool_cpy(&data, netc_emac_counters[i].name);
+
+ if (!np->caps.pmac)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(netc_pmac_counters); i++)
+ ethtool_cpy(&data, netc_pmac_counters[i].name);
+}
+
+void netc_port_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(netc_port_counters); i++)
+ *data++ = netc_port_rd(np, netc_port_counters[i].reg);
+
+ for (i = 0; i < ARRAY_SIZE(netc_emac_counters); i++)
+ *data++ = netc_port_rd64(np, netc_emac_counters[i].reg);
+
+ if (!np->caps.pmac)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(netc_pmac_counters); i++)
+ *data++ = netc_port_rd64(np, netc_pmac_counters[i].reg);
+}
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
new file mode 100644
index 000000000..77077352c
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -0,0 +1,2524 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ * NXP NETC switch driver
+ * Copyright 2025-2026 NXP
+ */
+
+#include <linux/clk.h>
+#include <linux/etherdevice.h>
+#include <linux/fsl/enetc_mdio.h>
+#include <linux/if_bridge.h>
+#include <linux/if_vlan.h>
+#include <linux/of_mdio.h>
+
+#include "netc_switch.h"
+
+static struct netc_fdb_entry *
+netc_lookup_fdb_entry(struct netc_switch *priv,
+ const unsigned char *addr,
+ u16 vid)
+{
+ struct netc_fdb_entry *entry;
+
+ hlist_for_each_entry(entry, &priv->fdb_list, node)
+ if (ether_addr_equal(entry->keye.mac_addr, addr) &&
+ le16_to_cpu(entry->keye.fid) == vid)
+ return entry;
+
+ return NULL;
+}
+
+static void netc_destroy_fdb_list(struct netc_switch *priv)
+{
+ struct netc_fdb_entry *entry;
+ struct hlist_node *tmp;
+
+ hlist_for_each_entry_safe(entry, tmp, &priv->fdb_list, node)
+ netc_del_fdb_entry(entry);
+}
+
+static struct netc_vlan_entry *
+netc_lookup_vlan_entry(struct netc_switch *priv, u16 vid)
+{
+ struct netc_vlan_entry *entry;
+
+ hlist_for_each_entry(entry, &priv->vlan_list, node)
+ if (entry->vid == vid)
+ return entry;
+
+ return NULL;
+}
+
+static void netc_destroy_vlan_list(struct netc_switch *priv)
+{
+ struct netc_vlan_entry *entry;
+ struct hlist_node *tmp;
+
+ hlist_for_each_entry_safe(entry, tmp, &priv->vlan_list, node)
+ netc_del_vlan_entry(entry);
+}
+
+static enum dsa_tag_protocol
+netc_get_tag_protocol(struct dsa_switch *ds, int port,
+ enum dsa_tag_protocol mprot)
+{
+ return DSA_TAG_PROTO_NETC;
+}
+
+static void netc_port_rmw(struct netc_port *np, u32 reg,
+ u32 mask, u32 val)
+{
+ u32 old, new;
+
+ WARN_ON((mask | val) != mask);
+
+ old = netc_port_rd(np, reg);
+ new = (old & ~mask) | val;
+ if (new == old)
+ return;
+
+ netc_port_wr(np, reg, new);
+}
+
+static void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val)
+{
+ if (is_netc_pseudo_port(np))
+ return;
+
+ netc_port_wr(np, reg, val);
+ if (np->caps.pmac)
+ netc_port_wr(np, reg + NETC_PMAC_OFFSET, val);
+}
+
+/* netc_mac_port_rmw() is used to synchronize the configurations of eMAC
+ * and pMAC to maintain consistency. This function should not be used if
+ * differentiated settings are required.
+ */
+static void netc_mac_port_rmw(struct netc_port *np, u32 reg,
+ u32 mask, u32 val)
+{
+ u32 old, new;
+
+ if (is_netc_pseudo_port(np))
+ return;
+
+ WARN_ON((mask | val) != mask);
+
+ old = netc_port_rd(np, reg);
+ new = (old & ~mask) | val;
+ if (new == old)
+ return;
+
+ netc_port_wr(np, reg, new);
+ if (np->caps.pmac)
+ netc_port_wr(np, reg + NETC_PMAC_OFFSET, new);
+}
+
+static void netc_port_get_capability(struct netc_port *np)
+{
+ u32 val;
+
+ val = netc_port_rd(np, NETC_PMCAPR);
+ if (val & PMCAPR_HD)
+ np->caps.half_duplex = true;
+
+ if (FIELD_GET(PMCAPR_FP, val) == FP_SUPPORT)
+ np->caps.pmac = true;
+
+ val = netc_port_rd(np, NETC_PCAPR);
+ if (val & PCAPR_LINK_TYPE)
+ np->caps.pseudo_link = true;
+}
+
+static int netc_port_get_info_from_dt(struct netc_port *np,
+ struct device_node *node,
+ struct device *dev)
+{
+ if (of_find_property(node, "clock-names", NULL)) {
+ np->ref_clk = devm_get_clk_from_child(dev, node, "ref");
+ if (IS_ERR(np->ref_clk)) {
+ dev_err(dev, "Port %d cannot get reference clock\n",
+ np->dp->index);
+ return PTR_ERR(np->ref_clk);
+ }
+ }
+
+ return 0;
+}
+
+static int netc_port_create_emdio_bus(struct netc_port *np,
+ struct device_node *node)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct enetc_mdio_priv *mdio_priv;
+ struct device *dev = priv->dev;
+ struct enetc_hw *hw;
+ struct mii_bus *bus;
+ int err;
+
+ hw = enetc_hw_alloc(dev, np->iobase);
+ if (IS_ERR(hw))
+ return dev_err_probe(dev, PTR_ERR(hw),
+ "Failed to allocate enetc_hw\n");
+
+ bus = devm_mdiobus_alloc_size(dev, sizeof(*mdio_priv));
+ if (!bus)
+ return -ENOMEM;
+
+ bus->name = "NXP NETC switch external MDIO Bus";
+ bus->read = enetc_mdio_read_c22;
+ bus->write = enetc_mdio_write_c22;
+ bus->read_c45 = enetc_mdio_read_c45;
+ bus->write_c45 = enetc_mdio_write_c45;
+ bus->parent = dev;
+ mdio_priv = bus->priv;
+ mdio_priv->hw = hw;
+ mdio_priv->mdio_base = NETC_EMDIO_BASE;
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%s-p%d-emdio",
+ dev_name(dev), np->dp->index);
+
+ err = devm_of_mdiobus_register(dev, bus, node);
+ if (err)
+ return dev_err_probe(dev, err,
+ "Cannot register EMDIO bus\n");
+
+ np->emdio = bus;
+
+ return 0;
+}
+
+static int netc_port_create_mdio_bus(struct netc_port *np,
+ struct device_node *node)
+{
+ struct device_node *mdio_node;
+ int err;
+
+ mdio_node = of_get_child_by_name(node, "mdio");
+ if (mdio_node) {
+ err = netc_port_create_emdio_bus(np, mdio_node);
+ of_node_put(mdio_node);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int netc_init_switch_id(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ struct dsa_switch *ds = priv->ds;
+
+ /* The value of 0 is reserved for the VEPA switch and cannot
+ * be used. So 'dsa,member' is a required property for NETC
+ * switch, the member is used to specify the switch ID, which
+ * cannot be zero. This way, the hardware switch ID and the
+ * software switch ID are consistent.
+ */
+ if (ds->index > FIELD_MAX(SWCR_SWID) || !ds->index) {
+ dev_err(priv->dev, "Switch index %d out of range\n",
+ ds->index);
+ return -ERANGE;
+ }
+
+ netc_base_wr(regs, NETC_SWCR, ds->index);
+
+ return 0;
+}
+
+static void netc_get_switch_capabilities(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ u32 val;
+
+ val = netc_base_rd(regs, NETC_HTMCAPR);
+ priv->htmcapr_num_words = FIELD_GET(HTMCAPR_NUM_WORDS, val);
+
+ val = netc_base_rd(regs, NETC_BPCAPR);
+ priv->num_bp = FIELD_GET(BPCAPR_NUM_BP, val);
+}
+
+static int netc_init_all_ports(struct netc_switch *priv)
+{
+ struct device *dev = priv->dev;
+ struct netc_port *np;
+ struct dsa_port *dp;
+ int ett_offset = 0;
+ int err;
+
+ priv->ports = devm_kcalloc(dev, priv->info->num_ports,
+ sizeof(struct netc_port *),
+ GFP_KERNEL);
+ if (!priv->ports)
+ return -ENOMEM;
+
+ /* Some DSA interfaces may set the port even it is disabled, such
+ * as .port_disable(), .port_stp_state_set() and so on. To avoid
+ * crash caused by accessing NULL port pointer, each port is
+ * allocated its own memory. Otherwise, we need to check whether
+ * the port pointer is NULL in these interfaces. The latter is
+ * difficult for us to cover.
+ */
+ for (int i = 0; i < priv->info->num_ports; i++) {
+ np = devm_kzalloc(dev, sizeof(*np), GFP_KERNEL);
+ if (!np)
+ return -ENOMEM;
+
+ np->switch_priv = priv;
+ np->iobase = priv->regs.port + PORT_IOBASE(i);
+ netc_port_get_capability(np);
+ priv->ports[i] = np;
+ }
+
+ dsa_switch_for_each_available_port(dp, priv->ds) {
+ np = priv->ports[dp->index];
+ np->dp = dp;
+ np->ett_offset = ett_offset++;
+ priv->port_bitmap |= BIT(dp->index);
+
+ err = netc_port_get_info_from_dt(np, dp->dn, dev);
+ if (err)
+ return err;
+
+ if (dsa_port_is_user(dp)) {
+ err = netc_port_create_mdio_bus(np, dp->dn);
+ if (err) {
+ dev_err(dev, "Failed to create MDIO bus\n");
+ return err;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void netc_init_ntmp_tbl_versions(struct netc_switch *priv)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+
+ /* All tables default to version 0 */
+ memset(&ntmp->tbl, 0, sizeof(ntmp->tbl));
+}
+
+static int netc_init_all_cbdrs(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ struct ntmp_user *ntmp = &priv->ntmp;
+ int i, err;
+
+ ntmp->cbdr_num = NETC_CBDR_NUM;
+ ntmp->dev = priv->dev;
+ ntmp->ring = devm_kcalloc(ntmp->dev, ntmp->cbdr_num,
+ sizeof(struct netc_cbdr),
+ GFP_KERNEL);
+ if (!ntmp->ring)
+ return -ENOMEM;
+
+ for (i = 0; i < ntmp->cbdr_num; i++) {
+ struct netc_cbdr *cbdr = &ntmp->ring[i];
+ struct netc_cbdr_regs cbdr_regs;
+
+ cbdr_regs.pir = regs->base + NETC_CBDRPIR(i);
+ cbdr_regs.cir = regs->base + NETC_CBDRCIR(i);
+ cbdr_regs.mr = regs->base + NETC_CBDRMR(i);
+ cbdr_regs.bar0 = regs->base + NETC_CBDRBAR0(i);
+ cbdr_regs.bar1 = regs->base + NETC_CBDRBAR1(i);
+ cbdr_regs.lenr = regs->base + NETC_CBDRLENR(i);
+
+ err = ntmp_init_cbdr(cbdr, ntmp->dev, &cbdr_regs);
+ if (err)
+ goto free_cbdrs;
+ }
+
+ return 0;
+
+free_cbdrs:
+ for (i--; i >= 0; i--)
+ ntmp_free_cbdr(&ntmp->ring[i]);
+
+ return err;
+}
+
+static void netc_remove_all_cbdrs(struct netc_switch *priv)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+
+ for (int i = 0; i < NETC_CBDR_NUM; i++)
+ ntmp_free_cbdr(&ntmp->ring[i]);
+}
+
+static u32 netc_num_available_ports(struct netc_switch *priv)
+{
+ struct dsa_port *dp;
+ u32 num_ports = 0;
+
+ dsa_switch_for_each_available_port(dp, priv->ds)
+ num_ports++;
+
+ return num_ports;
+}
+
+static int netc_init_ntmp_bitmap_sizes(struct netc_switch *priv)
+{
+ u32 num_ports = netc_num_available_ports(priv);
+ struct netc_switch_regs *regs = &priv->regs;
+ struct ntmp_user *ntmp = &priv->ntmp;
+ u32 val;
+
+ if (!num_ports)
+ return -EINVAL;
+
+ val = netc_base_rd(regs, NETC_ETTCAPR);
+ ntmp->ett_bitmap_size = NETC_GET_NUM_ENTRIES(val) / num_ports;
+ if (!ntmp->ett_bitmap_size)
+ return -EINVAL;
+
+ val = netc_base_rd(regs, NETC_ECTCAPR);
+ ntmp->ect_bitmap_size = NETC_GET_NUM_ENTRIES(val) / num_ports;
+ if (!ntmp->ect_bitmap_size)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int netc_init_ntmp_bitmaps(struct netc_switch *priv)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+
+ ntmp->ett_gid_bitmap = bitmap_zalloc(ntmp->ett_bitmap_size,
+ GFP_KERNEL);
+ if (!ntmp->ett_gid_bitmap)
+ return -ENOMEM;
+
+ ntmp->ect_gid_bitmap = bitmap_zalloc(ntmp->ect_bitmap_size,
+ GFP_KERNEL);
+ if (!ntmp->ect_gid_bitmap)
+ goto free_ett_gid_bitmap;
+
+ return 0;
+
+free_ett_gid_bitmap:
+ bitmap_free(ntmp->ett_gid_bitmap);
+ ntmp->ett_gid_bitmap = NULL;
+
+ return -ENOMEM;
+}
+
+static void netc_free_ntmp_bitmaps(struct netc_switch *priv)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+
+ bitmap_free(ntmp->ect_gid_bitmap);
+ ntmp->ect_gid_bitmap = NULL;
+
+ bitmap_free(ntmp->ett_gid_bitmap);
+ ntmp->ett_gid_bitmap = NULL;
+}
+
+static int netc_init_ntmp_user(struct netc_switch *priv)
+{
+ int err;
+
+ netc_init_ntmp_tbl_versions(priv);
+
+ err = netc_init_ntmp_bitmap_sizes(priv);
+ if (err)
+ return err;
+
+ err = netc_init_ntmp_bitmaps(priv);
+ if (err)
+ return err;
+
+ err = netc_init_all_cbdrs(priv);
+ if (err)
+ goto free_ntmp_bitmaps;
+
+ return 0;
+
+free_ntmp_bitmaps:
+ netc_free_ntmp_bitmaps(priv);
+
+ return err;
+}
+
+static void netc_free_ntmp_user(struct netc_switch *priv)
+{
+ netc_remove_all_cbdrs(priv);
+ netc_free_ntmp_bitmaps(priv);
+}
+
+static void netc_clean_fdbt_ageing_entries(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct netc_switch *priv;
+
+ priv = container_of(dwork, struct netc_switch, fdbt_ageing_work);
+
+ /* Update the activity element in FDB table */
+ mutex_lock(&priv->fdbt_lock);
+ ntmp_fdbt_update_activity_element(&priv->ntmp);
+ /* Delete the ageing entries after the activity element is updated */
+ ntmp_fdbt_delete_ageing_entries(&priv->ntmp, NETC_FDBT_AGEING_THRESH);
+ mutex_unlock(&priv->fdbt_lock);
+
+ if (atomic_read(&priv->br_cnt))
+ schedule_delayed_work(&priv->fdbt_ageing_work,
+ READ_ONCE(priv->fdbt_ageing_delay));
+}
+
+static void netc_switch_dos_default_config(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ u32 val;
+
+ val = DOSL2CR_SAMEADDR | DOSL2CR_MSAMCC;
+ netc_base_wr(regs, NETC_DOSL2CR, val);
+
+ val = DOSL3CR_SAMEADDR | DOSL3CR_IPSAMCC;
+ netc_base_wr(regs, NETC_DOSL3CR, val);
+}
+
+static void netc_switch_vfht_default_config(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ u32 val;
+
+ val = netc_base_rd(regs, NETC_VFHTDECR2);
+
+ /* If no match is found in the VLAN Filter table, then VFHTDECR2[MLO]
+ * will take effect. VFHTDECR2[MLO] is set to "Software MAC learning
+ * secure" by default. Notice BPCR[MLO] will override VFHTDECR2[MLO]
+ * if its value is not zero.
+ */
+ val = u32_replace_bits(val, MLO_SW_SEC, VFHTDECR2_MLO);
+ val = u32_replace_bits(val, MFO_NO_MATCH_DISCARD, VFHTDECR2_MFO);
+ netc_base_wr(regs, NETC_VFHTDECR2, val);
+}
+
+static void netc_port_set_max_frame_size(struct netc_port *np,
+ u32 max_frame_size)
+{
+ netc_mac_port_wr(np, NETC_PM_MAXFRM(0),
+ max_frame_size & PM_MAXFRAM);
+}
+
+static void netc_switch_fixed_config(struct netc_switch *priv)
+{
+ netc_switch_dos_default_config(priv);
+ netc_switch_vfht_default_config(priv);
+}
+
+static void netc_port_set_tc_max_sdu(struct netc_port *np,
+ int tc, u32 max_sdu)
+{
+ u32 val = FIELD_PREP(PTCTMSDUR_MAXSDU, max_sdu) |
+ FIELD_PREP(PTCTMSDUR_SDU_TYPE, SDU_TYPE_MPDU);
+
+ netc_port_wr(np, NETC_PTCTMSDUR(tc), val);
+}
+
+static void netc_port_set_all_tc_msdu(struct netc_port *np)
+{
+ for (int tc = 0; tc < NETC_TC_NUM; tc++)
+ netc_port_set_tc_max_sdu(np, tc, NETC_MAX_FRAME_LEN);
+}
+
+static void netc_port_set_mlo(struct netc_port *np, enum netc_mlo mlo)
+{
+ netc_port_rmw(np, NETC_BPCR, BPCR_MLO, FIELD_PREP(BPCR_MLO, mlo));
+}
+
+static void netc_port_set_pvid(struct netc_port *np, u16 pvid)
+{
+ netc_port_rmw(np, NETC_BPDVR, BPDVR_VID, pvid);
+}
+
+static void netc_port_set_vlan_aware(struct netc_port *np, bool aware)
+{
+ netc_port_rmw(np, NETC_BPDVR, BPDVR_RXVAM,
+ aware ? 0 : BPDVR_RXVAM);
+}
+
+static void netc_port_fixed_config(struct netc_port *np)
+{
+ /* Default IPV and DR setting */
+ netc_port_rmw(np, NETC_PQOSMR, PQOSMR_VS | PQOSMR_VE,
+ PQOSMR_VS | PQOSMR_VE);
+
+ /* Enable L2 and L3 DOS */
+ netc_port_rmw(np, NETC_PCR, PCR_L2DOSE | PCR_L3DOSE,
+ PCR_L2DOSE | PCR_L3DOSE);
+
+ /* Set the quanta value of TX PAUSE frame */
+ netc_mac_port_wr(np, NETC_PM_PAUSE_QUANTA(0), NETC_PAUSE_QUANTA);
+
+ /* When a quanta timer counts down and reaches this value,
+ * the MAC sends a refresh PAUSE frame with the programmed
+ * full quanta value if a pause condition still exists.
+ */
+ netc_mac_port_wr(np, NETC_PM_PAUSE_THRESH(0), NETC_PAUSE_THRESH);
+}
+
+static void netc_port_default_config(struct netc_port *np)
+{
+ netc_port_fixed_config(np);
+
+ /* Default VLAN unaware */
+ netc_port_set_vlan_aware(np, false);
+
+ if (dsa_port_is_cpu(np->dp))
+ /* For CPU port, source port pruning is disabled */
+ netc_port_rmw(np, NETC_BPCR, BPCR_SRCPRND, BPCR_SRCPRND);
+ else
+ netc_port_set_mlo(np, MLO_DISABLE);
+
+ netc_port_set_max_frame_size(np, NETC_MAX_FRAME_LEN);
+ netc_port_set_all_tc_msdu(np);
+}
+
+static u32 netc_available_port_bitmap(struct netc_switch *priv)
+{
+ struct dsa_port *dp;
+ u32 bitmap = 0;
+
+ dsa_switch_for_each_available_port(dp, priv->ds)
+ bitmap |= BIT(dp->index);
+
+ return bitmap;
+}
+
+static int netc_add_standalone_vlan_entry(struct netc_switch *priv)
+{
+ u32 bitmap_stg = VFT_STG_ID(0) | netc_available_port_bitmap(priv);
+ struct vft_cfge_data *cfge;
+ u16 cfg;
+ int err;
+
+ cfge = kzalloc_obj(*cfge);
+ if (!cfge)
+ return -ENOMEM;
+
+ cfge->bitmap_stg = cpu_to_le32(bitmap_stg);
+ cfge->et_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+ cfge->fid = cpu_to_le16(NETC_STANDALONE_PVID);
+
+ /* For standalone ports, MAC learning needs to be disabled, so frames
+ * from other user ports will not be forwarded to the standalone ports,
+ * because there are no FDB entries on the standalone ports. Also, the
+ * frames received by the standalone ports cannot be flooded to other
+ * ports, so MAC forwarding option needs to be set to
+ * MFO_NO_MATCH_DISCARD, so the frames will be discarded rather than
+ * flooding to other ports.
+ */
+ cfg = FIELD_PREP(VFT_MLO, MLO_DISABLE) |
+ FIELD_PREP(VFT_MFO, MFO_NO_MATCH_DISCARD);
+ cfge->cfg = cpu_to_le16(cfg);
+
+ err = ntmp_vft_add_entry(&priv->ntmp, NETC_STANDALONE_PVID, cfge);
+ if (err)
+ dev_err(priv->dev,
+ "Failed to add standalone VLAN entry\n");
+
+ kfree(cfge);
+
+ return err;
+}
+
+static int netc_port_add_fdb_entry(struct netc_port *np,
+ const unsigned char *addr, u16 vid)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct netc_fdb_entry *entry;
+ struct fdbt_keye_data *keye;
+ struct fdbt_cfge_data *cfge;
+ int port = np->dp->index;
+ u32 cfg = 0;
+ int err;
+
+ entry = kzalloc_obj(*entry);
+ if (!entry)
+ return -ENOMEM;
+
+ keye = &entry->keye;
+ cfge = &entry->cfge;
+ ether_addr_copy(keye->mac_addr, addr);
+ keye->fid = cpu_to_le16(vid);
+
+ cfge->port_bitmap = cpu_to_le32(BIT(port));
+ cfge->cfg = cpu_to_le32(cfg);
+ cfge->et_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+
+ err = ntmp_fdbt_add_entry(&priv->ntmp, &entry->entry_id, keye, cfge);
+ if (err) {
+ kfree(entry);
+
+ return err;
+ }
+
+ netc_add_fdb_entry(priv, entry);
+
+ return 0;
+}
+
+static int netc_port_set_fdb_entry(struct netc_port *np,
+ const unsigned char *addr, u16 vid)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct netc_fdb_entry *entry;
+ struct fdbt_cfge_data *cfge;
+ int port = np->dp->index;
+ __le32 old_port_bitmap;
+ int err = 0;
+
+ mutex_lock(&priv->fdbt_lock);
+
+ entry = netc_lookup_fdb_entry(priv, addr, vid);
+ if (!entry) {
+ err = netc_port_add_fdb_entry(np, addr, vid);
+ if (err)
+ dev_err(priv->dev,
+ "Failed to add FDB entry on port %d\n",
+ port);
+
+ goto unlock_fdbt;
+ }
+
+ cfge = &entry->cfge;
+ /* If the entry already exists on the port, return 0 directly */
+ if (unlikely(cfge->port_bitmap & cpu_to_le32(BIT(port))))
+ goto unlock_fdbt;
+
+ /* If the entry already exists, but not on this port, we need to
+ * update the port bitmap. In general, it should only be valid
+ * for multicast or broadcast address.
+ */
+ old_port_bitmap = cfge->port_bitmap;
+ if (is_multicast_ether_addr(addr))
+ cfge->port_bitmap |= cpu_to_le32(BIT(port));
+ else
+ cfge->port_bitmap = cpu_to_le32(BIT(port));
+
+ err = ntmp_fdbt_update_entry(&priv->ntmp, entry->entry_id, cfge);
+ if (err) {
+ cfge->port_bitmap = old_port_bitmap;
+ dev_err(priv->dev, "Failed to set FDB entry on port %d\n",
+ port);
+ }
+
+unlock_fdbt:
+ mutex_unlock(&priv->fdbt_lock);
+
+ return err;
+}
+
+static int netc_port_del_fdb_entry(struct netc_port *np,
+ const unsigned char *addr, u16 vid)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct ntmp_user *ntmp = &priv->ntmp;
+ struct netc_fdb_entry *entry;
+ struct fdbt_cfge_data *cfge;
+ int port = np->dp->index;
+ int err = 0;
+
+ mutex_lock(&priv->fdbt_lock);
+
+ entry = netc_lookup_fdb_entry(priv, addr, vid);
+ if (unlikely(!entry))
+ /* The hardware-learned dynamic FDB entries cannot be deleted
+ * through .port_fdb_del() interface.
+ * For NTF_MASTER path: Since hardware-learned dynamic FDB
+ * entries are never synchronized back to the bridge software
+ * database. br_fdb_delete() -> br_fdb_find() cannot find the
+ * FDB entry, so .port_fdb_del() will not be called.
+ * For NTF_SELF path: dsa_user_netdev_ops does not implement
+ * ndo_fdb_del(), so rtnl_fdb_del() falls back to
+ * ndo_dflt_fdb_del(), which only supports NUD_PERMANENT static
+ * entries and rejects all others with -EINVAL.
+ */
+ goto unlock_fdbt;
+
+ cfge = &entry->cfge;
+ if (unlikely(!(cfge->port_bitmap & cpu_to_le32(BIT(port)))))
+ goto unlock_fdbt;
+
+ if (cfge->port_bitmap != cpu_to_le32(BIT(port))) {
+ /* If the entry also exists on other ports, we need to
+ * update the entry in the FDB table.
+ */
+ cfge->port_bitmap &= cpu_to_le32(~BIT(port));
+ err = ntmp_fdbt_update_entry(ntmp, entry->entry_id, cfge);
+ if (err) {
+ cfge->port_bitmap |= cpu_to_le32(BIT(port));
+ goto unlock_fdbt;
+ }
+ } else {
+ /* If the entry only exists on this port, just delete
+ * it from the FDB table.
+ */
+ err = ntmp_fdbt_delete_entry(ntmp, entry->entry_id);
+ if (err)
+ goto unlock_fdbt;
+
+ netc_del_fdb_entry(entry);
+ }
+
+unlock_fdbt:
+ mutex_unlock(&priv->fdbt_lock);
+
+ return err;
+}
+
+static int netc_add_standalone_fdb_bcast_entry(struct netc_switch *priv)
+{
+ const u8 bcast[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+ struct dsa_port *dp, *cpu_dp = NULL;
+
+ dsa_switch_for_each_cpu_port(dp, priv->ds) {
+ /* The switch has only one CPU port, so only need to find
+ * the first CPU port to break out of the loop.
+ */
+ cpu_dp = dp;
+ break;
+ }
+
+ if (!cpu_dp)
+ return -ENODEV;
+
+ /* If the user port acts as a standalone port, then its PVID is 0,
+ * MLO is set to "disable MAC learning" and MFO is set to "discard
+ * frames if no matching entry found in FDB table". Therefore, we
+ * need to add a broadcast FDB entry on the CPU port so that the
+ * broadcast frames received on the user port can be forwarded to
+ * the CPU port.
+ */
+ return netc_port_set_fdb_entry(NETC_PORT(priv->ds, cpu_dp->index),
+ bcast, NETC_STANDALONE_PVID);
+}
+
+static void netc_port_set_pbpmcr(struct netc_port *np, u64 mapping)
+{
+ u32 pbpmcr0 = lower_32_bits(mapping);
+ u32 pbpmcr1 = upper_32_bits(mapping);
+
+ netc_port_wr(np, NETC_PBPMCR0, pbpmcr0);
+ netc_port_wr(np, NETC_PBPMCR1, pbpmcr1);
+}
+
+static void netc_ipv_to_buffer_pool_mapping(struct netc_switch *priv)
+{
+ int bp_per_port = priv->num_bp / priv->info->num_ports;
+ int q = NETC_IPV_NUM / bp_per_port;
+ int r = NETC_IPV_NUM % bp_per_port;
+ int num = q + r;
+
+ /* IPV-to-buffer-pool mapping per port:
+ * Each port is allocated 'bp_per_port' buffer pools and supports 8
+ * IPVs, where a higher IPV indicates a higher frame priority. Each
+ * IPV can be mapped to only one buffer pool, from hardware design
+ * perspective, bp_per_port will not be greater than 8. So 'q' will
+ * not be 0.
+ *
+ * The mapping rule is as follows:
+ * - The first 'num' IPVs share the port's first buffer pool (index
+ * 'base_id').
+ * - After that, every 'q' IPVs share one buffer pool, with pool
+ * indices increasing sequentially.
+ */
+ for (int i = 0; i < priv->info->num_ports; i++) {
+ u32 base_id = i * bp_per_port;
+ u32 bp_id = base_id;
+ u64 mapping = 0;
+
+ for (int ipv = 0; ipv < NETC_IPV_NUM; ipv++) {
+ /* Update the buffer pool index */
+ if (ipv >= num)
+ bp_id = base_id + ((ipv - num) / q) + 1;
+
+ mapping |= (u64)bp_id << (ipv * 8);
+ }
+
+ netc_port_set_pbpmcr(priv->ports[i], mapping);
+ }
+}
+
+static int netc_switch_bpt_default_config(struct netc_switch *priv)
+{
+ if (priv->num_bp < priv->info->num_ports)
+ return -EINVAL;
+
+ priv->bpt_list = devm_kcalloc(priv->dev, priv->num_bp,
+ sizeof(struct bpt_cfge_data),
+ GFP_KERNEL);
+ if (!priv->bpt_list)
+ return -ENOMEM;
+
+ /* Initialize the maximum threshold of each buffer pool entry */
+ for (int i = 0; i < priv->num_bp; i++) {
+ struct bpt_cfge_data *cfge = &priv->bpt_list[i];
+ int err;
+
+ cfge->max_thresh = cpu_to_le16(NETC_BP_THRESH);
+ err = ntmp_bpt_update_entry(&priv->ntmp, i, cfge);
+ if (err)
+ return err;
+ }
+
+ netc_ipv_to_buffer_pool_mapping(priv);
+
+ return 0;
+}
+
+static int netc_setup(struct dsa_switch *ds)
+{
+ struct netc_switch *priv = ds->priv;
+ struct dsa_port *dp;
+ int err;
+
+ err = netc_init_switch_id(priv);
+ if (err)
+ return err;
+
+ netc_get_switch_capabilities(priv);
+
+ err = netc_init_all_ports(priv);
+ if (err)
+ return err;
+
+ err = netc_init_ntmp_user(priv);
+ if (err)
+ return err;
+
+ INIT_HLIST_HEAD(&priv->fdb_list);
+ mutex_init(&priv->fdbt_lock);
+ priv->fdbt_ageing_delay = NETC_FDBT_AGEING_DELAY;
+ atomic_set(&priv->br_cnt, 0);
+ INIT_DELAYED_WORK(&priv->fdbt_ageing_work,
+ netc_clean_fdbt_ageing_entries);
+ INIT_HLIST_HEAD(&priv->vlan_list);
+ mutex_init(&priv->vft_lock);
+
+ netc_switch_fixed_config(priv);
+
+ /* default setting for ports */
+ dsa_switch_for_each_available_port(dp, ds)
+ netc_port_default_config(priv->ports[dp->index]);
+
+ err = netc_switch_bpt_default_config(priv);
+ if (err)
+ goto free_lock_and_ntmp_user;
+
+ err = netc_add_standalone_vlan_entry(priv);
+ if (err)
+ goto free_lock_and_ntmp_user;
+
+ err = netc_add_standalone_fdb_bcast_entry(priv);
+ if (err)
+ goto free_lock_and_ntmp_user;
+
+ return 0;
+
+free_lock_and_ntmp_user:
+ /* No need to clear the hardware state, netc_setup() is only called
+ * when the driver is bound, and FLR will be performed to reset the
+ * hardware state.
+ */
+ mutex_destroy(&priv->fdbt_lock);
+ mutex_destroy(&priv->vft_lock);
+ netc_free_ntmp_user(priv);
+
+ return err;
+}
+
+static void netc_destroy_all_lists(struct netc_switch *priv)
+{
+ netc_destroy_fdb_list(priv);
+ mutex_destroy(&priv->fdbt_lock);
+ netc_destroy_vlan_list(priv);
+ mutex_destroy(&priv->vft_lock);
+}
+
+static void netc_free_host_flood_rules(struct netc_switch *priv)
+{
+ struct dsa_port *dp;
+
+ dsa_switch_for_each_user_port(dp, priv->ds) {
+ struct netc_port *np = priv->ports[dp->index];
+
+ /* No need to clear the hardware IPFT entry. Because PCIe
+ * FLR will be performed when the switch is re-registered,
+ * it will reset hardware state. So only need to free the
+ * memory to avoid memory leak.
+ */
+ kfree(np->host_flood);
+ np->host_flood = NULL;
+ }
+}
+
+static void netc_teardown(struct dsa_switch *ds)
+{
+ struct netc_switch *priv = ds->priv;
+
+ disable_delayed_work_sync(&priv->fdbt_ageing_work);
+ netc_destroy_all_lists(priv);
+ netc_free_host_flood_rules(priv);
+ netc_free_ntmp_user(priv);
+}
+
+static bool netc_port_is_emdio_consumer(struct device_node *node)
+{
+ struct device_node *mdio_node;
+
+ /* If the port node has phy-handle property and it does
+ * not contain a mdio child node, then the port is the
+ * EMDIO consumer.
+ */
+ mdio_node = of_get_child_by_name(node, "mdio");
+ if (!mdio_node)
+ return true;
+
+ of_node_put(mdio_node);
+
+ return false;
+}
+
+/* Currently, phylink_of_phy_connect() is called by dsa_user_create(),
+ * so if the switch uses the external MDIO controller (like the EMDIO
+ * function) to manage the external PHYs. The MDIO bus may not be
+ * created when phylink_of_phy_connect() is called, so it will return
+ * an error and cause the switch driver to fail to probe.
+ * This workaround can be removed when DSA phylink_of_phy_connect()
+ * calls are moved from probe() to ndo_open().
+ */
+static int netc_switch_check_emdio_is_ready(struct device *dev)
+{
+ struct device_node *ports, *phy_node;
+ struct phy_device *phydev;
+ int err = 0;
+
+ ports = of_get_child_by_name(dev->of_node, "ethernet-ports");
+ if (!ports) {
+ dev_err(dev, "Cannot find the ethernet-ports node\n");
+ return -EINVAL;
+ }
+
+ for_each_available_child_of_node_scoped(ports, child) {
+ /* If the node does not have phy-handle property, then the
+ * port does not connect to a PHY, so the port is not the
+ * EMDIO consumer.
+ */
+ phy_node = of_parse_phandle(child, "phy-handle", 0);
+ if (!phy_node)
+ continue;
+
+ /* Note that from the hardware perspective, the switch ports
+ * do not support sharing the MDIO bus defined under one port.
+ * Each port can only access its own external PHY through its
+ * port MDIO bus.
+ */
+ if (!netc_port_is_emdio_consumer(child)) {
+ of_node_put(phy_node);
+ continue;
+ }
+
+ phydev = of_phy_find_device(phy_node);
+ of_node_put(phy_node);
+ if (!phydev) {
+ err = -EPROBE_DEFER;
+ goto out;
+ }
+
+ put_device(&phydev->mdio.dev);
+ }
+
+out:
+ of_node_put(ports);
+
+ return err;
+}
+
+static int netc_switch_pci_init(struct pci_dev *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct netc_switch_regs *regs;
+ struct netc_switch *priv;
+ void __iomem *base;
+ int err;
+
+ pcie_flr(pdev);
+ err = pcim_enable_device(pdev);
+ if (err)
+ return dev_err_probe(dev, err, "Failed to enable device\n");
+
+ err = pcim_request_all_regions(pdev, KBUILD_MODNAME);
+ if (err)
+ return dev_err_probe(dev, err, "Failed to request regions\n");
+
+ /* The command BD rings and NTMP tables need DMA. No need to check
+ * the return value, because it never returns fail when the mask is
+ * DMA_BIT_MASK(64), see dma-api-howto.rst.
+ */
+ dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+
+ if (pci_resource_len(pdev, NETC_REGS_BAR) < NETC_REGS_SIZE) {
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid register space size\n");
+ }
+
+ base = pcim_iomap(pdev, NETC_REGS_BAR, 0);
+ if (!base)
+ return dev_err_probe(dev, -ENXIO, "pcim_iomap() failed\n");
+
+ pci_set_master(pdev);
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pdev = pdev;
+ priv->dev = dev;
+
+ regs = &priv->regs;
+ regs->base = base;
+ regs->port = regs->base + NETC_REGS_PORT_BASE;
+ regs->global = regs->base + NETC_REGS_GLOBAL_BASE;
+ pci_set_drvdata(pdev, priv);
+
+ return 0;
+}
+
+static void netc_switch_get_ip_revision(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ u32 val = netc_glb_rd(regs, NETC_IPBRR0);
+
+ priv->revision = FIELD_GET(IPBRR0_IP_REV, val);
+}
+
+static void netc_init_ett_cfge(struct ett_cfge_data *cfge,
+ bool untagged, u32 ect_eid)
+{
+ u32 vuda_sqta = FMTEID_VUDA_SQTA;
+ u16 efm_cfg = 0;
+
+ if (ect_eid != NTMP_NULL_ENTRY_ID) {
+ /* Increase egress frame counter */
+ efm_cfg |= FIELD_PREP(ETT_ECA, ETT_ECA_INC);
+ cfge->ec_eid = cpu_to_le32(ect_eid);
+ }
+
+ /* If egress rule is VLAN untagged */
+ if (untagged) {
+ /* delete outer VLAN tag */
+ vuda_sqta |= FIELD_PREP(FMTEID_VUDA, FMTEID_VUDA_DEL_OTAG);
+ /* length change: twos-complement notation */
+ efm_cfg |= FIELD_PREP(ETT_EFM_LEN_CHANGE,
+ ETT_FRM_LEN_DEL_VLAN);
+ }
+
+ cfge->efm_eid = cpu_to_le32(vuda_sqta);
+ cfge->efm_cfg = cpu_to_le16(efm_cfg);
+}
+
+static int netc_add_ett_entry(struct netc_switch *priv, bool untagged,
+ u32 ett_eid, u32 ect_eid)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+ struct ett_cfge_data cfge = {};
+
+ netc_init_ett_cfge(&cfge, untagged, ect_eid);
+
+ return ntmp_ett_add_entry(ntmp, ett_eid, &cfge);
+}
+
+static int netc_update_ett_entry(struct netc_switch *priv, bool untagged,
+ u32 ett_eid, u32 ect_eid)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+ struct ett_cfge_data cfge = {};
+
+ netc_init_ett_cfge(&cfge, untagged, ect_eid);
+
+ return ntmp_ett_update_entry(ntmp, ett_eid, &cfge);
+}
+
+static int netc_add_ett_group_entries(struct netc_switch *priv,
+ u32 untagged_port_bitmap,
+ u32 ett_base_eid,
+ u32 ect_base_eid)
+{
+ struct netc_port **ports = priv->ports;
+ u32 ett_eid, ect_eid;
+ bool untagged;
+ int i, err;
+
+ for (i = 0; i < priv->info->num_ports; i++) {
+ if (!ports[i]->dp)
+ continue;
+
+ untagged = !!(untagged_port_bitmap & BIT(i));
+ ett_eid = ett_base_eid + ports[i]->ett_offset;
+ ect_eid = NTMP_NULL_ENTRY_ID;
+ if (ect_base_eid != NTMP_NULL_ENTRY_ID)
+ ect_eid = ect_base_eid + ports[i]->ett_offset;
+
+ err = netc_add_ett_entry(priv, untagged, ett_eid, ect_eid);
+ if (err)
+ goto clear_ett_entries;
+ }
+
+ return 0;
+
+clear_ett_entries:
+ while (--i >= 0) {
+ if (!ports[i]->dp)
+ continue;
+
+ ett_eid = ett_base_eid + ports[i]->ett_offset;
+ ntmp_ett_delete_entry(&priv->ntmp, ett_eid);
+ }
+
+ return err;
+}
+
+static int netc_add_vlan_egress_rule(struct netc_switch *priv,
+ struct netc_vlan_entry *entry)
+{
+ u32 num_ports = netc_num_available_ports(priv);
+ struct ntmp_user *ntmp = &priv->ntmp;
+ u32 ect_eid = NTMP_NULL_ENTRY_ID;
+ u32 ett_eid, ett_gid, ect_gid;
+ int err;
+
+ /* Step 1: Find available egress counter table entries and update
+ * these entries.
+ */
+ ect_gid = ntmp_lookup_free_eid(ntmp->ect_gid_bitmap,
+ ntmp->ect_bitmap_size);
+ if (ect_gid == NTMP_NULL_ENTRY_ID) {
+ dev_info(priv->dev,
+ "No egress counter table entries available\n");
+ } else {
+ ect_eid = ect_gid * num_ports;
+ for (int i = 0; i < num_ports; i++)
+ /* There is no need to check the return value, the only
+ * issue is that the entry's counter might be inaccurate,
+ * but it will not affect the functionality, it is only
+ * for future debugging.
+ */
+ ntmp_ect_update_entry(ntmp, ect_eid + i);
+ }
+
+ /* Step 2: Find available egress treatment table entries and add
+ * these entries.
+ */
+ ett_gid = ntmp_lookup_free_eid(ntmp->ett_gid_bitmap,
+ ntmp->ett_bitmap_size);
+ if (ett_gid == NTMP_NULL_ENTRY_ID) {
+ dev_err(priv->dev,
+ "No egress treatment table entries available\n");
+ err = -ENOSPC;
+ goto clear_ect_gid;
+ }
+
+ ett_eid = ett_gid * num_ports;
+ err = netc_add_ett_group_entries(priv, entry->untagged_port_bitmap,
+ ett_eid, ect_eid);
+ if (err)
+ goto clear_ett_gid;
+
+ entry->cfge.et_eid = cpu_to_le32(ett_eid);
+ entry->ect_gid = ect_gid;
+
+ return 0;
+
+clear_ett_gid:
+ ntmp_clear_eid_bitmap(ntmp->ett_gid_bitmap, ett_gid);
+
+clear_ect_gid:
+ if (ect_gid != NTMP_NULL_ENTRY_ID)
+ ntmp_clear_eid_bitmap(ntmp->ect_gid_bitmap, ect_gid);
+
+ return err;
+}
+
+static void netc_delete_vlan_egress_rule(struct netc_switch *priv,
+ struct netc_vlan_entry *entry)
+{
+ u32 num_ports = netc_num_available_ports(priv);
+ struct ntmp_user *ntmp = &priv->ntmp;
+ u32 ett_eid, ett_gid;
+
+ ett_eid = le32_to_cpu(entry->cfge.et_eid);
+ if (ett_eid == NTMP_NULL_ENTRY_ID)
+ return;
+
+ ett_gid = ett_eid / num_ports;
+ ntmp_clear_eid_bitmap(ntmp->ett_gid_bitmap, ett_gid);
+ for (int i = 0; i < num_ports; i++)
+ ntmp_ett_delete_entry(ntmp, ett_eid + i);
+
+ if (entry->ect_gid == NTMP_NULL_ENTRY_ID)
+ return;
+
+ ntmp_clear_eid_bitmap(ntmp->ect_gid_bitmap, entry->ect_gid);
+}
+
+static int netc_port_update_vlan_egress_rule(struct netc_port *np,
+ struct netc_vlan_entry *entry)
+{
+ bool untagged = !!(entry->untagged_port_bitmap & BIT(np->dp->index));
+ u32 num_ports = netc_num_available_ports(np->switch_priv);
+ u32 ett_eid = le32_to_cpu(entry->cfge.et_eid);
+ struct netc_switch *priv = np->switch_priv;
+ u32 ect_eid = NTMP_NULL_ENTRY_ID;
+ int err;
+
+ if (ett_eid == NTMP_NULL_ENTRY_ID)
+ return 0;
+
+ if (entry->ect_gid != NTMP_NULL_ENTRY_ID)
+ /* Each ETT entry maps to an ECT entry if ect_gid is not NULL
+ * entry ID. The offset of the ECT entry corresponding to the
+ * port in the group is equal to ett_offset.
+ */
+ ect_eid = entry->ect_gid * num_ports + np->ett_offset;
+
+ ett_eid += np->ett_offset;
+ err = netc_update_ett_entry(priv, untagged, ett_eid, ect_eid);
+ if (err) {
+ dev_err(priv->dev,
+ "Failed to update VLAN %u egress rule on port %d\n",
+ entry->vid, np->dp->index);
+ return err;
+ }
+
+ if (ect_eid != NTMP_NULL_ENTRY_ID)
+ ntmp_ect_update_entry(&priv->ntmp, ect_eid);
+
+ return 0;
+}
+
+static int netc_port_add_vlan_entry(struct netc_port *np, u16 vid,
+ bool untagged)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct netc_vlan_entry *entry;
+ struct vft_cfge_data *cfge;
+ u32 index = np->dp->index;
+ u32 bitmap_stg;
+ int err;
+ u16 cfg;
+
+ entry = kzalloc_obj(*entry);
+ if (!entry)
+ return -ENOMEM;
+
+ entry->vid = vid;
+ entry->ect_gid = NTMP_NULL_ENTRY_ID;
+
+ bitmap_stg = BIT(index) | VFT_STG_ID(0);
+ /* If the VID is a VLAN-unaware PVID, the CPU port needs to be
+ * a member of this VLAN.
+ */
+ if (dsa_port_is_user(np->dp) &&
+ vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges)) {
+ struct dsa_port *cpu_dp = np->dp->cpu_dp;
+
+ bitmap_stg |= BIT(cpu_dp->index);
+ }
+
+ cfg = FIELD_PREP(VFT_MLO, MLO_HW) |
+ FIELD_PREP(VFT_MFO, MFO_NO_MATCH_FLOOD);
+
+ cfge = &entry->cfge;
+ cfge->et_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+ cfge->bitmap_stg = cpu_to_le32(bitmap_stg);
+ cfge->fid = cpu_to_le16(vid);
+ cfge->cfg = cpu_to_le16(cfg);
+ cfge->eta_port_bitmap = cpu_to_le32(priv->port_bitmap);
+
+ if (untagged)
+ entry->untagged_port_bitmap = BIT(index);
+
+ err = netc_add_vlan_egress_rule(priv, entry);
+ if (err)
+ goto free_vlan_entry;
+
+ err = ntmp_vft_add_entry(&priv->ntmp, vid, cfge);
+ if (err) {
+ dev_err(priv->dev,
+ "Failed to add VLAN %u entry on port %d\n",
+ vid, index);
+ goto delete_vlan_egress_rule;
+ }
+
+ netc_add_vlan_entry(priv, entry);
+
+ return 0;
+
+delete_vlan_egress_rule:
+ netc_delete_vlan_egress_rule(priv, entry);
+free_vlan_entry:
+ kfree(entry);
+
+ return err;
+}
+
+static bool netc_port_vlan_egress_rule_changed(struct netc_switch *priv,
+ struct netc_vlan_entry *entry,
+ int port, bool untagged)
+{
+ bool old_untagged = !!(entry->untagged_port_bitmap & BIT(port));
+
+ /* VLAN-unaware VIDs have no egress rules, so return 'false' */
+ if (entry->vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges))
+ return false;
+
+ return old_untagged != untagged;
+}
+
+static int netc_port_set_vlan_entry(struct netc_port *np, u16 vid,
+ bool untagged)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct netc_vlan_entry *entry;
+ struct vft_cfge_data *cfge;
+ int port = np->dp->index;
+ bool changed;
+ int err = 0;
+
+ mutex_lock(&priv->vft_lock);
+
+ entry = netc_lookup_vlan_entry(priv, vid);
+ if (!entry) {
+ err = netc_port_add_vlan_entry(np, vid, untagged);
+ goto unlock_vft;
+ }
+
+ /* Check whether the egress VLAN rule is changed */
+ changed = netc_port_vlan_egress_rule_changed(priv, entry, port,
+ untagged);
+ if (changed) {
+ entry->untagged_port_bitmap ^= BIT(port);
+ err = netc_port_update_vlan_egress_rule(np, entry);
+ if (err) {
+ entry->untagged_port_bitmap ^= BIT(port);
+ goto unlock_vft;
+ }
+ }
+
+ cfge = &entry->cfge;
+ if (cfge->bitmap_stg & cpu_to_le32(BIT(port)))
+ goto unlock_vft;
+
+ cfge->bitmap_stg |= cpu_to_le32(BIT(port));
+ err = ntmp_vft_update_entry(&priv->ntmp, vid, cfge);
+ if (err) {
+ dev_err(priv->dev,
+ "Failed to update VLAN %u entry on port %d\n",
+ vid, port);
+
+ goto restore_bitmap_stg;
+ }
+
+ mutex_unlock(&priv->vft_lock);
+
+ return 0;
+
+restore_bitmap_stg:
+ cfge->bitmap_stg &= cpu_to_le32(~BIT(port));
+ if (changed) {
+ entry->untagged_port_bitmap ^= BIT(port);
+ /* Recover the corresponding ETT entry. It doesn't matter
+ * if it fails because the bit corresponding to the port
+ * in the port bitmap of the VFT entry is not set. so the
+ * frame will not match that ETT entry.
+ */
+ if (netc_port_update_vlan_egress_rule(np, entry))
+ entry->untagged_port_bitmap ^= BIT(port);
+ }
+unlock_vft:
+ mutex_unlock(&priv->vft_lock);
+
+ return err;
+}
+
+static int netc_port_del_vlan_entry(struct netc_port *np, u16 vid)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct netc_vlan_entry *entry;
+ struct vft_cfge_data *cfge;
+ int port = np->dp->index;
+ u32 vlan_port_bitmap;
+ int err = 0;
+
+ mutex_lock(&priv->vft_lock);
+
+ entry = netc_lookup_vlan_entry(priv, vid);
+ if (!entry)
+ goto unlock_vft;
+
+ cfge = &entry->cfge;
+ vlan_port_bitmap = FIELD_GET(VFT_PORT_MEMBERSHIP,
+ le32_to_cpu(cfge->bitmap_stg));
+ /* If the VID is a VLAN-unaware PVID, we need to clear the CPU
+ * port bit of vlan_port_bitmap, so that the VLAN entry can be
+ * deleted if no user ports use this VLAN.
+ */
+ if (dsa_port_is_user(np->dp) &&
+ vid >= NETC_VLAN_UNAWARE_PVID(priv->ds->max_num_bridges)) {
+ struct dsa_port *cpu_dp = np->dp->cpu_dp;
+
+ vlan_port_bitmap &= ~BIT(cpu_dp->index);
+ }
+
+ /* If the VLAN only belongs to the current port */
+ if (vlan_port_bitmap == BIT(port)) {
+ err = ntmp_vft_delete_entry(&priv->ntmp, vid);
+ if (err)
+ goto unlock_vft;
+
+ netc_delete_vlan_egress_rule(priv, entry);
+ netc_del_vlan_entry(entry);
+
+ goto unlock_vft;
+ }
+
+ if (!(vlan_port_bitmap & BIT(port)))
+ goto unlock_vft;
+
+ cfge->bitmap_stg &= cpu_to_le32(~BIT(port));
+ err = ntmp_vft_update_entry(&priv->ntmp, vid, cfge);
+ if (err) {
+ cfge->bitmap_stg |= cpu_to_le32(BIT(port));
+ goto unlock_vft;
+ }
+
+unlock_vft:
+ mutex_unlock(&priv->vft_lock);
+
+ return err;
+}
+
+static int netc_port_enable(struct dsa_switch *ds, int port,
+ struct phy_device *phy)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ int err;
+
+ if (np->enable)
+ return 0;
+
+ err = clk_prepare_enable(np->ref_clk);
+ if (err) {
+ dev_err(ds->dev,
+ "Failed to enable enet_ref_clk of port %d\n", port);
+ return err;
+ }
+
+ np->enable = true;
+
+ return 0;
+}
+
+static void netc_port_disable(struct dsa_switch *ds, int port)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+
+ /* When .port_disable() is called, .port_enable() may not have been
+ * called. In this case, both the prepare_count and enable_count of
+ * clock are 0. Calling clk_disable_unprepare() at this time will
+ * cause warnings.
+ */
+ if (!np->enable)
+ return;
+
+ clk_disable_unprepare(np->ref_clk);
+ np->enable = false;
+}
+
+static void netc_port_stp_state_set(struct dsa_switch *ds,
+ int port, u8 state)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ u32 val;
+
+ switch (state) {
+ case BR_STATE_DISABLED:
+ case BR_STATE_LISTENING:
+ case BR_STATE_BLOCKING:
+ val = NETC_STG_STATE_DISABLED;
+ break;
+ case BR_STATE_LEARNING:
+ val = NETC_STG_STATE_LEARNING;
+ break;
+ case BR_STATE_FORWARDING:
+ val = NETC_STG_STATE_FORWARDING;
+ break;
+ default:
+ return;
+ }
+
+ netc_port_wr(np, NETC_BPSTGSR, val);
+}
+
+static int netc_port_change_mtu(struct dsa_switch *ds,
+ int port, int mtu)
+{
+ u32 max_frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN;
+
+ netc_port_set_max_frame_size(NETC_PORT(ds, port), max_frame_size);
+
+ return 0;
+}
+
+static int netc_port_max_mtu(struct dsa_switch *ds, int port)
+{
+ return NETC_MAX_FRAME_LEN - VLAN_ETH_HLEN - ETH_FCS_LEN;
+}
+
+static struct net_device *netc_classify_db(struct dsa_db db)
+{
+ switch (db.type) {
+ case DSA_DB_PORT:
+ return NULL;
+ case DSA_DB_BRIDGE:
+ return db.bridge.dev;
+ default:
+ return ERR_PTR(-EOPNOTSUPP);
+ }
+}
+
+static u16 netc_vlan_unaware_pvid(struct dsa_bridge *bridge)
+{
+ u32 br_num;
+
+ if (!bridge)
+ return NETC_STANDALONE_PVID;
+
+ br_num = bridge->num;
+
+ /* The br_num is supposed to be 1 ~ ds->max_num_bridges, see
+ * dsa_bridge_num_get(). Since max_num_bridges is non-zero,
+ * so dsa_port_bridge_create() will return an error if
+ * dsa_bridge_num_get() returns 0.
+ */
+ if (WARN_ON(!br_num))
+ return NETC_STANDALONE_PVID;
+
+ return NETC_VLAN_UNAWARE_PVID(br_num);
+}
+
+static int netc_port_fdb_add(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct net_device *br_ndev = netc_classify_db(db);
+ struct netc_port *np = NETC_PORT(ds, port);
+
+ if (IS_ERR(br_ndev))
+ return PTR_ERR(br_ndev);
+
+ if (!vid)
+ vid = netc_vlan_unaware_pvid(br_ndev ? &db.bridge : NULL);
+
+ return netc_port_set_fdb_entry(np, addr, vid);
+}
+
+static int netc_port_fdb_del(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct net_device *br_ndev = netc_classify_db(db);
+ struct netc_port *np = NETC_PORT(ds, port);
+
+ if (IS_ERR(br_ndev))
+ return PTR_ERR(br_ndev);
+
+ if (!vid)
+ vid = netc_vlan_unaware_pvid(br_ndev ? &db.bridge : NULL);
+
+ return netc_port_del_fdb_entry(np, addr, vid);
+}
+
+static int netc_port_fdb_dump(struct dsa_switch *ds, int port,
+ dsa_fdb_dump_cb_t *cb, void *data)
+{
+ struct netc_switch *priv = ds->priv;
+ u32 resume_eid = NTMP_NULL_ENTRY_ID;
+ struct fdbt_entry_data *entry;
+ struct fdbt_keye_data *keye;
+ struct fdbt_cfge_data *cfge;
+ u32 cfg, cnt = 0;
+ bool is_static;
+ int err;
+ u16 vid;
+
+ entry = kmalloc_obj(*entry);
+ if (!entry)
+ return -ENOMEM;
+
+ keye = &entry->keye;
+ cfge = &entry->cfge;
+ mutex_lock(&priv->fdbt_lock);
+
+ do {
+ memset(entry, 0, sizeof(*entry));
+ err = ntmp_fdbt_search_port_entry(&priv->ntmp, port,
+ &resume_eid, entry);
+ if (err || entry->entry_id == NTMP_NULL_ENTRY_ID)
+ break;
+
+ cfg = le32_to_cpu(cfge->cfg);
+ is_static = (cfg & FDBT_DYNAMIC) ? false : true;
+ vid = le16_to_cpu(keye->fid);
+ if (vid >= NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges))
+ vid = 0;
+
+ err = cb(keye->mac_addr, vid, is_static, data);
+ if (err)
+ break;
+
+ /* To prevent hardware malfunctions from causing an
+ * infinite loop.
+ */
+ if (++cnt >= priv->htmcapr_num_words)
+ break;
+ } while (resume_eid != NTMP_NULL_ENTRY_ID);
+
+ mutex_unlock(&priv->fdbt_lock);
+ kfree(entry);
+
+ return err;
+}
+
+static int netc_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ return netc_port_fdb_add(ds, port, mdb->addr, mdb->vid, db);
+}
+
+static int netc_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ return netc_port_fdb_del(ds, port, mdb->addr, mdb->vid, db);
+}
+
+static int netc_port_add_host_flood_rule(struct netc_port *np,
+ bool uc, bool mc)
+{
+ const u8 dmac_mask[ETH_ALEN] = {0x1, 0, 0, 0, 0, 0};
+ struct netc_switch *priv = np->switch_priv;
+ struct ipft_entry_data *host_flood;
+ struct ipft_keye_data *keye;
+ struct ipft_cfge_data *cfge;
+ u16 src_port;
+ u32 cfg;
+ int err;
+
+ if (!uc && !mc) {
+ /* Disable ingress port filter table lookup */
+ netc_port_wr(np, NETC_PIPFCR, 0);
+ np->uc = false;
+ np->mc = false;
+
+ return 0;
+ }
+
+ host_flood = kzalloc_obj(*host_flood);
+ if (!host_flood)
+ return -ENOMEM;
+
+ keye = &host_flood->keye;
+ cfge = &host_flood->cfge;
+
+ src_port = FIELD_PREP(IPFT_SRC_PORT, np->dp->index);
+ src_port |= IPFT_SRC_PORT_MASK;
+ keye->src_port = cpu_to_le16(src_port);
+
+ /* If either only unicast or only multicast need to be flooded
+ * to the host, we always set the mask that tests the first MAC
+ * DA octet. The value should be 0 for the first bit (if unicast
+ * has to be flooded) or 1 (if multicast). If both unicast and
+ * multicast have to be flooded, we leave the key mask empty, so
+ * it matches everything.
+ */
+ if (uc && !mc)
+ ether_addr_copy(keye->dmac_mask, dmac_mask);
+
+ if (!uc && mc) {
+ ether_addr_copy(keye->dmac, dmac_mask);
+ ether_addr_copy(keye->dmac_mask, dmac_mask);
+ }
+
+ cfg = FIELD_PREP(IPFT_FLTFA, IPFT_FLTFA_REDIRECT);
+ cfg |= FIELD_PREP(IPFT_HR, NETC_HR_HOST_FLOOD);
+ cfge->cfg = cpu_to_le32(cfg);
+
+ err = ntmp_ipft_add_entry(&priv->ntmp, host_flood);
+ if (err) {
+ kfree(host_flood);
+ return err;
+ }
+
+ np->uc = uc;
+ np->mc = mc;
+ np->host_flood = host_flood;
+ /* Enable ingress port filter table lookup */
+ netc_port_wr(np, NETC_PIPFCR, PIPFCR_EN);
+
+ return 0;
+}
+
+static void netc_port_remove_host_flood(struct netc_port *np,
+ struct ipft_entry_data *host_flood)
+{
+ struct netc_switch *priv = np->switch_priv;
+ bool disable_host_flood = false;
+
+ if (!host_flood)
+ return;
+
+ if (np->host_flood == host_flood)
+ disable_host_flood = true;
+
+ ntmp_ipft_delete_entry(&priv->ntmp, host_flood->entry_id);
+ kfree(host_flood);
+
+ if (disable_host_flood) {
+ np->host_flood = NULL;
+ np->uc = false;
+ np->mc = false;
+ netc_port_wr(np, NETC_PIPFCR, 0);
+ }
+}
+
+static void netc_port_set_host_flood(struct dsa_switch *ds, int port,
+ bool uc, bool mc)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct ipft_entry_data *old_host_flood;
+
+ /* Do not add host flood rule to ingress port filter table when
+ * the port has joined a bridge. Otherwise, the ingress frames
+ * will bypass FDB table lookup and MAC learning, so the frames
+ * will be redirected directly to the CPU port.
+ */
+ if (dsa_port_bridge_dev_get(np->dp)) {
+ netc_port_remove_host_flood(np, np->host_flood);
+
+ return;
+ }
+
+ if (np->uc == uc && np->mc == mc)
+ return;
+
+ /* IPFT does not support in-place updates to the KEYE element,
+ * we need to add a new entry and then delete the old one. So
+ * save the old entry first.
+ */
+ old_host_flood = np->host_flood;
+ np->host_flood = NULL;
+
+ if (netc_port_add_host_flood_rule(np, uc, mc)) {
+ np->host_flood = old_host_flood;
+ dev_err(ds->dev, "Failed to add host flood rule on port %d\n",
+ port);
+ return;
+ }
+
+ /* Remove the old host flood entry */
+ netc_port_remove_host_flood(np, old_host_flood);
+}
+
+static int netc_single_vlan_aware_bridge(struct dsa_switch *ds,
+ struct netlink_ext_ack *extack)
+{
+ struct net_device *br_ndev = NULL;
+ struct dsa_port *dp;
+
+ dsa_switch_for_each_available_port(dp, ds) {
+ struct net_device *port_br = dsa_port_bridge_dev_get(dp);
+
+ if (!port_br || !br_vlan_enabled(port_br))
+ continue;
+
+ if (!br_ndev) {
+ br_ndev = port_br;
+ continue;
+ }
+
+ if (br_ndev == port_br)
+ continue;
+
+ NL_SET_ERR_MSG_MOD(extack,
+ "Only one VLAN-aware bridge is supported");
+
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int netc_port_vlan_filtering(struct dsa_switch *ds,
+ int port, bool vlan_aware,
+ struct netlink_ext_ack *extack)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ u16 pvid;
+ int err;
+
+ /* Before calling port_vlan_filtering(), br_vlan_filter_toggle() has
+ * already updated the BROPT_VLAN_ENABLED bit of br->options. So the
+ * VLAN filtering status of the switch ports can be checked by the
+ * br_vlan_enabled() function.
+ */
+ err = netc_single_vlan_aware_bridge(ds, extack);
+ if (err)
+ return err;
+
+ pvid = netc_vlan_unaware_pvid(np->dp->bridge);
+ if (pvid == NETC_STANDALONE_PVID) {
+ vlan_aware = false;
+ goto bpdvr_config;
+ }
+
+ if (vlan_aware) {
+ /* The FDB entries associated with unaware_pvid do not need
+ * to be deleted, so that when switching from VLAN-aware to
+ * VLAN-unaware mode, these FDB entries do not need to be
+ * re-added.
+ */
+ err = netc_port_del_vlan_entry(np, pvid);
+ if (err)
+ return err;
+
+ pvid = np->pvid;
+ } else {
+ err = netc_port_set_vlan_entry(np, pvid, false);
+ if (err)
+ return err;
+ }
+
+bpdvr_config:
+ netc_port_set_vlan_aware(np, vlan_aware);
+ netc_port_set_pvid(np, pvid);
+
+ return 0;
+}
+
+static int netc_port_vlan_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct dsa_port *dp = np->dp;
+ bool untagged;
+ int err;
+
+ /* The 8021q layer may attempt to change NETC_STANDALONE_PVID
+ * (VID 0), so we need to ignore it.
+ */
+ if (vlan->vid == NETC_STANDALONE_PVID)
+ return 0;
+
+ if (vlan->vid >= NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges)) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "VID %d~4095 reserved for VLAN-unaware bridge",
+ NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges));
+ return -EINVAL;
+ }
+
+ untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
+ err = netc_port_set_vlan_entry(np, vlan->vid, untagged);
+ if (err)
+ return err;
+
+ if (vlan->flags & BRIDGE_VLAN_INFO_PVID) {
+ np->pvid = vlan->vid;
+ if (dsa_port_is_vlan_filtering(dp))
+ netc_port_set_pvid(np, vlan->vid);
+
+ return 0;
+ }
+
+ if (np->pvid != vlan->vid)
+ return 0;
+
+ /* Delete PVID */
+ np->pvid = NETC_STANDALONE_PVID;
+ if (dsa_port_is_vlan_filtering(dp))
+ netc_port_set_pvid(np, NETC_STANDALONE_PVID);
+
+ return 0;
+}
+
+static int netc_port_vlan_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ int err;
+
+ if (vlan->vid == NETC_STANDALONE_PVID)
+ return 0;
+
+ if (vlan->vid >= NETC_VLAN_UNAWARE_PVID(ds->max_num_bridges))
+ return -EINVAL;
+
+ err = netc_port_del_vlan_entry(np, vlan->vid);
+ if (err)
+ return err;
+
+ if (np->pvid == vlan->vid) {
+ np->pvid = NETC_STANDALONE_PVID;
+
+ /* Set the port PVID to NETC_STANDALONE_PVID if the VLAN-aware
+ * bridge port has no PVID. The untagged frames will not be
+ * forwarded to other user ports, as NETC_STANDALONE_PVID VLAN
+ * entry has disabled MAC learning and flooding, and other user
+ * ports do not have FDB entries with NETC_STANDALONE_PVID.
+ */
+ if (dsa_port_is_vlan_filtering(np->dp))
+ netc_port_set_pvid(np, NETC_STANDALONE_PVID);
+ }
+
+ return 0;
+}
+
+static int netc_port_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge,
+ bool *tx_fwd_offload,
+ struct netlink_ext_ack *extack)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct netc_switch *priv = ds->priv;
+ u16 vlan_unaware_pvid;
+ int err;
+
+ if (!bridge.num) {
+ NL_SET_ERR_MSG_MOD(extack, "Bridge number 0 is unsupported");
+ return -EINVAL;
+ }
+
+ err = netc_single_vlan_aware_bridge(ds, extack);
+ if (err)
+ return err;
+
+ netc_port_set_mlo(np, MLO_NOT_OVERRIDE);
+
+ if (br_vlan_enabled(bridge.dev))
+ goto out;
+
+ vlan_unaware_pvid = NETC_VLAN_UNAWARE_PVID(bridge.num);
+ err = netc_port_set_vlan_entry(np, vlan_unaware_pvid, false);
+ if (err)
+ goto disable_mlo;
+
+ netc_port_set_pvid(np, vlan_unaware_pvid);
+
+out:
+ netc_port_remove_host_flood(np, np->host_flood);
+
+ if (atomic_inc_return(&priv->br_cnt) == 1)
+ schedule_delayed_work(&priv->fdbt_ageing_work,
+ READ_ONCE(priv->fdbt_ageing_delay));
+
+ return 0;
+
+disable_mlo:
+ netc_port_set_mlo(np, MLO_DISABLE);
+
+ return err;
+}
+
+static void netc_port_remove_dynamic_entries(struct netc_port *np)
+{
+ struct netc_switch *priv = np->switch_priv;
+
+ /* Return if the port is not available */
+ if (!np->dp)
+ return;
+
+ mutex_lock(&priv->fdbt_lock);
+ ntmp_fdbt_delete_port_dynamic_entries(&priv->ntmp, np->dp->index);
+ mutex_unlock(&priv->fdbt_lock);
+}
+
+static void netc_port_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct net_device *ndev = np->dp->user;
+ struct netc_switch *priv = ds->priv;
+ u16 vlan_unaware_pvid;
+ bool mc, uc;
+
+ netc_port_set_mlo(np, MLO_DISABLE);
+ netc_port_set_pvid(np, NETC_STANDALONE_PVID);
+ np->pvid = NETC_STANDALONE_PVID;
+
+ if (atomic_dec_and_test(&priv->br_cnt))
+ cancel_delayed_work_sync(&priv->fdbt_ageing_work);
+
+ netc_port_remove_dynamic_entries(np);
+ uc = ndev->flags & IFF_PROMISC;
+ mc = ndev->flags & (IFF_PROMISC | IFF_ALLMULTI);
+
+ if (netc_port_add_host_flood_rule(np, uc, mc))
+ dev_warn(ds->dev,
+ "Failed to restore host flood rule on port %d\n",
+ port);
+
+ /* When a port leaves a VLAN-aware bridge, dsa_port_bridge_leave()
+ * follows the sequence below:
+ *
+ * 1. dsa_port_bridge_destroy() is called to set dp->bridge to NULL.
+ * 2. dsa_broadcast() is called, which eventually invokes
+ * ds->ops->port_bridge_leave()
+ * 3. dsa_port_switchdev_unsync_attrs() is called, which triggers
+ * dsa_port_reset_vlan_filtering() and ultimately calls
+ * ds->ops->port_vlan_filtering() to transition the port from
+ * VLAN-aware mode to VLAN-unaware mode.
+ *
+ * At step 3, since dp->bridge has already been set to NULL in step 1,
+ * netc_port_vlan_filtering() will detect this and skip the creation
+ * of an unaware PVID entry in the VLAN filter table. Therefore, it is
+ * safe to return directly here.
+ */
+ if (br_vlan_enabled(bridge.dev))
+ return;
+
+ vlan_unaware_pvid = NETC_VLAN_UNAWARE_PVID(bridge.num);
+ /* There is no need to check the return value even if it fails.
+ * Because the PVID has been set to NETC_STANDALONE_PVID, the
+ * frames will not match this VLAN entry.
+ */
+ netc_port_del_vlan_entry(np, vlan_unaware_pvid);
+}
+
+static int netc_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+{
+ struct netc_switch *priv = ds->priv;
+ unsigned long delay_jiffies;
+
+ /* The dynamic FDB entry is deleted when its activity counter reaches
+ * NETC_FDBT_AGEING_THRESH (100). Each delayed_work tick increments
+ * the counter by 1 if the entry is inactive.
+ *
+ * Therefore:
+ * msecs (ms) = NETC_FDBT_AGEING_THRESH * delay_ms (ms)
+ * delay_ms = msecs / NETC_FDBT_AGEING_THRESH
+ * delay_jiffies = (delay_ms / 1000) * HZ
+ * = (msecs * HZ) / (1000 * NETC_FDBT_AGEING_THRESH)
+ *
+ * Use DIV_ROUND_CLOSEST_ULL to perform a single nearest-jiffy
+ * rounding, avoiding the two-step rounding error of the intermediate
+ * delay_ms approach.
+ * Maximum error = +/-0.5 jiffy * 100 = +/-50000/HZ ms.
+ */
+ delay_jiffies = DIV_ROUND_CLOSEST_ULL((u64)msecs * HZ,
+ 1000 * NETC_FDBT_AGEING_THRESH);
+ WRITE_ONCE(priv->fdbt_ageing_delay, delay_jiffies);
+
+ if (atomic_read(&priv->br_cnt))
+ mod_delayed_work(system_percpu_wq, &priv->fdbt_ageing_work,
+ READ_ONCE(priv->fdbt_ageing_delay));
+
+ return 0;
+}
+
+static void netc_port_fast_age(struct dsa_switch *ds, int port)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+
+ netc_port_remove_dynamic_entries(np);
+}
+
+static void netc_phylink_get_caps(struct dsa_switch *ds, int port,
+ struct phylink_config *config)
+{
+ struct netc_switch *priv = ds->priv;
+
+ priv->info->phylink_get_caps(port, config);
+}
+
+static void netc_port_set_mac_mode(struct netc_port *np,
+ unsigned int mode,
+ phy_interface_t phy_mode)
+{
+ u32 mask = PM_IF_MODE_IFMODE | PM_IF_MODE_REVMII;
+ u32 val = 0;
+
+ switch (phy_mode) {
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ val |= IFMODE_RGMII;
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ val |= IFMODE_RMII;
+ break;
+ case PHY_INTERFACE_MODE_REVMII:
+ val |= PM_IF_MODE_REVMII;
+ fallthrough;
+ case PHY_INTERFACE_MODE_MII:
+ val |= IFMODE_MII;
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ val |= IFMODE_SGMII;
+ break;
+ default:
+ break;
+ }
+
+ netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val);
+}
+
+static void netc_mac_config(struct phylink_config *config, unsigned int mode,
+ const struct phylink_link_state *state)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+
+ netc_port_set_mac_mode(NETC_PORT(dp->ds, dp->index), mode,
+ state->interface);
+}
+
+static void netc_port_set_speed(struct netc_port *np, int speed)
+{
+ netc_port_rmw(np, NETC_PCR, PCR_PSPEED, PSPEED_SET_VAL(speed));
+}
+
+static void netc_port_set_rgmii_mac(struct netc_port *np,
+ int speed, int duplex)
+{
+ u32 mask, val;
+
+ mask = PM_IF_MODE_SSP | PM_IF_MODE_HD | PM_IF_MODE_M10;
+
+ switch (speed) {
+ default:
+ case SPEED_1000:
+ val = FIELD_PREP(PM_IF_MODE_SSP, SSP_1G);
+ break;
+ case SPEED_100:
+ val = FIELD_PREP(PM_IF_MODE_SSP, SSP_100M);
+ break;
+ case SPEED_10:
+ val = FIELD_PREP(PM_IF_MODE_SSP, SSP_10M);
+ break;
+ }
+
+ if (duplex != DUPLEX_FULL)
+ val |= PM_IF_MODE_HD;
+
+ netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val);
+}
+
+static void netc_port_set_rmii_mii_mac(struct netc_port *np,
+ int speed, int duplex)
+{
+ u32 mask, val = 0;
+
+ mask = PM_IF_MODE_SSP | PM_IF_MODE_HD | PM_IF_MODE_M10;
+
+ if (speed == SPEED_10)
+ val |= PM_IF_MODE_M10;
+
+ if (duplex != DUPLEX_FULL)
+ val |= PM_IF_MODE_HD;
+
+ netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val);
+}
+
+static void netc_port_set_tx_pause(struct netc_port *np, bool tx_pause)
+{
+ struct netc_switch *priv = np->switch_priv;
+ int port = np->dp->index;
+ int i, j, num_bp;
+
+ num_bp = priv->num_bp / priv->info->num_ports;
+ for (i = 0, j = port * num_bp; i < num_bp; i++, j++) {
+ struct bpt_cfge_data *cfge = &priv->bpt_list[j];
+ struct bpt_cfge_data old_cfge = *cfge;
+
+ if (tx_pause) {
+ cfge->fc_on_thresh = cpu_to_le16(NETC_FC_THRESH_ON);
+ cfge->fc_off_thresh = cpu_to_le16(NETC_FC_THRESH_OFF);
+ cfge->fccfg_sbpen = FIELD_PREP(BPT_FC_CFG,
+ BPT_FC_CFG_EN_BPFC);
+ cfge->fc_ports = cpu_to_le32(BIT(port));
+ } else {
+ cfge->fc_on_thresh = cpu_to_le16(0);
+ cfge->fc_off_thresh = cpu_to_le16(0);
+ cfge->fccfg_sbpen = 0;
+ cfge->fc_ports = cpu_to_le32(0);
+ }
+
+ if (ntmp_bpt_update_entry(&priv->ntmp, j, cfge)) {
+ *cfge = old_cfge;
+ dev_warn(priv->dev,
+ "Failed to %s TX pause of buffer pool %d (swp%d)\n",
+ tx_pause ? "enable" : "disable", j, port);
+ }
+ }
+}
+
+static void netc_port_set_rx_pause(struct netc_port *np, bool rx_pause)
+{
+ netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_PAUSE_IGN,
+ rx_pause ? 0 : PM_CMD_CFG_PAUSE_IGN);
+}
+
+static void netc_port_mac_rx_enable(struct netc_port *np)
+{
+ netc_port_rmw(np, NETC_POR, POR_RXDIS, 0);
+ netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_RX_EN,
+ PM_CMD_CFG_RX_EN);
+}
+
+static void netc_port_wait_rx_empty(struct netc_port *np, int mac)
+{
+ u32 val;
+
+ /* PM_IEVENT_RX_EMPTY is a read-only bit, it is automatically set by
+ * hardware if RX FIFO is empty and no RX packet receive in process.
+ * And it is automatically cleared if RX FIFO is not empty or RX
+ * packet receive in process.
+ */
+ if (read_poll_timeout(netc_port_rd, val, val & PM_IEVENT_RX_EMPTY,
+ 100, 10000, false, np, NETC_PM_IEVENT(mac)))
+ dev_warn(np->switch_priv->dev,
+ "swp%d MAC%d: RX is not idle\n", np->dp->index, mac);
+}
+
+static void netc_port_mac_rx_graceful_stop(struct netc_port *np)
+{
+ u32 val;
+
+ if (is_netc_pseudo_port(np))
+ goto rx_disable;
+
+ if (np->caps.pmac) {
+ netc_port_rmw(np, NETC_PM_CMD_CFG(1), PM_CMD_CFG_RX_EN, 0);
+ netc_port_wait_rx_empty(np, 1);
+ }
+
+ netc_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_RX_EN, 0);
+ netc_port_wait_rx_empty(np, 0);
+
+ if (read_poll_timeout(netc_port_rd, val, !(val & PSR_RX_BUSY),
+ 100, 10000, false, np, NETC_PSR))
+ dev_warn(np->switch_priv->dev, "swp%d RX is busy\n",
+ np->dp->index);
+
+rx_disable:
+ netc_port_rmw(np, NETC_POR, POR_RXDIS, POR_RXDIS);
+}
+
+static void netc_port_mac_tx_enable(struct netc_port *np)
+{
+ netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_TX_EN,
+ PM_CMD_CFG_TX_EN);
+ netc_port_rmw(np, NETC_POR, POR_TXDIS, 0);
+}
+
+static void netc_port_wait_tx_empty(struct netc_port *np, int mac)
+{
+ u32 val;
+
+ /* PM_IEVENT_TX_EMPTY is a read-only bit, it is automatically set by
+ * hardware if TX FIFO is empty. And it is automatically cleared if
+ * TX FIFO is not empty.
+ */
+ if (read_poll_timeout(netc_port_rd, val, val & PM_IEVENT_TX_EMPTY,
+ 100, 10000, false, np, NETC_PM_IEVENT(mac)))
+ dev_warn(np->switch_priv->dev,
+ "swp%d MAC%d: TX FIFO is not empty\n",
+ np->dp->index, mac);
+}
+
+static void netc_port_mac_tx_graceful_stop(struct netc_port *np)
+{
+ netc_port_rmw(np, NETC_POR, POR_TXDIS, POR_TXDIS);
+
+ if (is_netc_pseudo_port(np))
+ return;
+
+ netc_port_wait_tx_empty(np, 0);
+ if (np->caps.pmac)
+ netc_port_wait_tx_empty(np, 1);
+
+ netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_TX_EN, 0);
+}
+
+static void netc_mac_link_up(struct phylink_config *config,
+ struct phy_device *phy, unsigned int mode,
+ phy_interface_t interface, int speed,
+ int duplex, bool tx_pause, bool rx_pause)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct netc_port *np;
+
+ np = NETC_PORT(dp->ds, dp->index);
+ netc_port_set_speed(np, speed);
+
+ if (phy_interface_mode_is_rgmii(interface))
+ netc_port_set_rgmii_mac(np, speed, duplex);
+
+ if (interface == PHY_INTERFACE_MODE_RMII ||
+ interface == PHY_INTERFACE_MODE_REVMII ||
+ interface == PHY_INTERFACE_MODE_MII)
+ netc_port_set_rmii_mii_mac(np, speed, duplex);
+
+ netc_port_set_tx_pause(np, tx_pause);
+ netc_port_set_rx_pause(np, rx_pause);
+ netc_port_mac_tx_enable(np);
+ netc_port_mac_rx_enable(np);
+}
+
+static void netc_mac_link_down(struct phylink_config *config,
+ unsigned int mode,
+ phy_interface_t interface)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct netc_port *np;
+
+ np = NETC_PORT(dp->ds, dp->index);
+ netc_port_mac_rx_graceful_stop(np);
+ netc_port_mac_tx_graceful_stop(np);
+ netc_port_remove_dynamic_entries(np);
+}
+
+static const struct phylink_mac_ops netc_phylink_mac_ops = {
+ .mac_config = netc_mac_config,
+ .mac_link_up = netc_mac_link_up,
+ .mac_link_down = netc_mac_link_down,
+};
+
+static const struct dsa_switch_ops netc_switch_ops = {
+ .get_tag_protocol = netc_get_tag_protocol,
+ .setup = netc_setup,
+ .teardown = netc_teardown,
+ .phylink_get_caps = netc_phylink_get_caps,
+ .port_enable = netc_port_enable,
+ .port_disable = netc_port_disable,
+ .port_stp_state_set = netc_port_stp_state_set,
+ .port_change_mtu = netc_port_change_mtu,
+ .port_max_mtu = netc_port_max_mtu,
+ .port_fdb_add = netc_port_fdb_add,
+ .port_fdb_del = netc_port_fdb_del,
+ .port_fdb_dump = netc_port_fdb_dump,
+ .port_mdb_add = netc_port_mdb_add,
+ .port_mdb_del = netc_port_mdb_del,
+ .port_set_host_flood = netc_port_set_host_flood,
+ .port_vlan_filtering = netc_port_vlan_filtering,
+ .port_vlan_add = netc_port_vlan_add,
+ .port_vlan_del = netc_port_vlan_del,
+ .port_bridge_join = netc_port_bridge_join,
+ .port_bridge_leave = netc_port_bridge_leave,
+ .set_ageing_time = netc_set_ageing_time,
+ .port_fast_age = netc_port_fast_age,
+ .get_pause_stats = netc_port_get_pause_stats,
+ .get_rmon_stats = netc_port_get_rmon_stats,
+ .get_eth_ctrl_stats = netc_port_get_eth_ctrl_stats,
+ .get_eth_mac_stats = netc_port_get_eth_mac_stats,
+ .get_sset_count = netc_port_get_sset_count,
+ .get_strings = netc_port_get_strings,
+ .get_ethtool_stats = netc_port_get_ethtool_stats,
+};
+
+static int netc_switch_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct device_node *node = dev_of_node(&pdev->dev);
+ struct device *dev = &pdev->dev;
+ struct netc_switch *priv;
+ struct dsa_switch *ds;
+ int err;
+
+ if (!node)
+ return dev_err_probe(dev, -ENODEV,
+ "No DT bindings, skipping\n");
+
+ err = netc_switch_check_emdio_is_ready(dev);
+ if (err)
+ return err;
+
+ err = netc_switch_pci_init(pdev);
+ if (err)
+ return err;
+
+ priv = pci_get_drvdata(pdev);
+ netc_switch_get_ip_revision(priv);
+
+ err = netc_switch_platform_probe(priv);
+ if (err)
+ return err;
+
+ ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);
+ if (!ds)
+ return -ENOMEM;
+
+ ds->dev = dev;
+ ds->num_ports = priv->info->num_ports;
+ ds->num_tx_queues = NETC_TC_NUM;
+ ds->ops = &netc_switch_ops;
+ ds->phylink_mac_ops = &netc_phylink_mac_ops;
+ ds->fdb_isolation = true;
+ ds->max_num_bridges = priv->info->num_ports - 1;
+ ds->ageing_time_min = 1000;
+ ds->ageing_time_max = U32_MAX;
+ ds->priv = priv;
+ priv->ds = ds;
+
+ err = dsa_register_switch(ds);
+ if (err)
+ return dev_err_probe(dev, err,
+ "Failed to register DSA switch\n");
+
+ return 0;
+}
+
+static void netc_switch_remove(struct pci_dev *pdev)
+{
+ struct netc_switch *priv = pci_get_drvdata(pdev);
+
+ if (!priv)
+ return;
+
+ dsa_unregister_switch(priv->ds);
+}
+
+static void netc_switch_shutdown(struct pci_dev *pdev)
+{
+ struct netc_switch *priv = pci_get_drvdata(pdev);
+
+ if (!priv)
+ return;
+
+ dsa_switch_shutdown(priv->ds);
+ pci_set_drvdata(pdev, NULL);
+}
+
+static const struct pci_device_id netc_switch_ids[] = {
+ { PCI_DEVICE(NETC_SWITCH_VENDOR_ID, NETC_SWITCH_DEVICE_ID) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, netc_switch_ids);
+
+static struct pci_driver netc_switch_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = netc_switch_ids,
+ .probe = netc_switch_probe,
+ .remove = netc_switch_remove,
+ .shutdown = netc_switch_shutdown,
+};
+module_pci_driver(netc_switch_driver);
+
+MODULE_DESCRIPTION("NXP NETC Switch driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/dsa/netc/netc_platform.c b/drivers/net/dsa/netc/netc_platform.c
new file mode 100644
index 000000000..34aeb6fce
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_platform.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ * NXP NETC switch driver
+ * Copyright 2025-2026 NXP
+ */
+
+#include "netc_switch.h"
+
+struct netc_switch_platform {
+ u16 revision;
+ const struct netc_switch_info *info;
+};
+
+static void imx94_switch_phylink_get_caps(int port,
+ struct phylink_config *config)
+{
+ config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_1000FD;
+
+ switch (port) {
+ case 0 ... 1:
+ __set_bit(PHY_INTERFACE_MODE_SGMII,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+ config->supported_interfaces);
+ config->mac_capabilities |= MAC_2500FD;
+ fallthrough;
+ case 2:
+ config->mac_capabilities |= MAC_10 | MAC_100;
+ __set_bit(PHY_INTERFACE_MODE_MII,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_RMII,
+ config->supported_interfaces);
+ /* Port 0 and 1 do not support REVMII */
+ if (port == 2)
+ __set_bit(PHY_INTERFACE_MODE_REVMII,
+ config->supported_interfaces);
+
+ phy_interface_set_rgmii(config->supported_interfaces);
+ break;
+ case 3: /* CPU port */
+ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+ config->supported_interfaces);
+ config->mac_capabilities |= MAC_10FD | MAC_100FD |
+ MAC_2500FD;
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct netc_switch_info imx94_info = {
+ .num_ports = 4,
+ .phylink_get_caps = imx94_switch_phylink_get_caps,
+};
+
+static const struct netc_switch_platform netc_platforms[] = {
+ { .revision = NETC_SWITCH_REV_4_3, .info = &imx94_info, },
+ { }
+};
+
+static const struct netc_switch_info *
+netc_switch_get_info(struct netc_switch *priv)
+{
+ int i;
+
+ /* Matching based on IP revision */
+ for (i = 0; i < ARRAY_SIZE(netc_platforms); i++) {
+ if (priv->revision == netc_platforms[i].revision)
+ return netc_platforms[i].info;
+ }
+
+ return NULL;
+}
+
+int netc_switch_platform_probe(struct netc_switch *priv)
+{
+ const struct netc_switch_info *info = netc_switch_get_info(priv);
+
+ if (!info) {
+ dev_err(priv->dev, "Cannot find switch platform info\n");
+ return -EINVAL;
+ }
+
+ priv->info = info;
+
+ return 0;
+}
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
new file mode 100644
index 000000000..305f2a92e
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/*
+ * Copyright 2025-2026 NXP
+ */
+
+#ifndef _NETC_SWITCH_H
+#define _NETC_SWITCH_H
+
+#include <linux/dsa/tag_netc.h>
+#include <linux/fsl/netc_global.h>
+#include <linux/fsl/ntmp.h>
+#include <linux/of_device.h>
+#include <linux/of_net.h>
+#include <linux/pci.h>
+
+#include "netc_switch_hw.h"
+
+#define NETC_REGS_BAR 0
+#define NETC_REGS_SIZE 0x80000
+#define NETC_MSIX_TBL_BAR 2
+#define NETC_REGS_PORT_BASE 0x4000
+/* register block size per port */
+#define NETC_REGS_PORT_SIZE 0x4000
+#define PORT_IOBASE(p) (NETC_REGS_PORT_SIZE * (p))
+#define NETC_REGS_GLOBAL_BASE 0x70000
+
+#define NETC_SWITCH_REV_4_3 0x0403
+
+#define NETC_TC_NUM 8
+#define NETC_CBDR_NUM 2
+#define NETC_IPV_NUM 8
+
+#define NETC_MAX_FRAME_LEN 9600
+
+#define NETC_STANDALONE_PVID 0
+#define NETC_VLAN_UNAWARE_PVID(br_id) (4096 - (br_id))
+
+/* Threshold format: MANT (bits 11:4) * 2^EXP (bits 3:0)
+ * Unit: Memory words (average of 20 bytes each)
+ * NETC_BP_THRESH = 0x8c3, MANT = 0x8c, EXP = 3. Threshold: 1120 words
+ * NETC_FC_THRESH_ON = 0x733, MANT = 0x73, EXP = 3. Threshold: 920 words
+ * NETC_FC_THRESH_OFF = 0x263, MANT = 0x26, EXP = 3. Threshold: 304 words
+ */
+#define NETC_BP_THRESH 0x8c3
+#define NETC_FC_THRESH_ON 0x733
+#define NETC_FC_THRESH_OFF 0x263
+
+/* PAUSE quanta: 0xFFFF = 65535 quanta (each quanta = 512 bit times) */
+#define NETC_PAUSE_QUANTA 0xFFFF
+/* PAUSE refresh threshold: send refresh when timer reaches this value */
+#define NETC_PAUSE_THRESH 0x7FFF
+
+#define NETC_FDBT_AGEING_DELAY (3 * HZ)
+#define NETC_FDBT_AGEING_THRESH 100
+
+struct netc_switch;
+
+struct netc_switch_info {
+ u32 num_ports;
+ void (*phylink_get_caps)(int port, struct phylink_config *config);
+};
+
+struct netc_port_caps {
+ u32 half_duplex:1; /* indicates whether the port support half-duplex */
+ u32 pmac:1; /* indicates whether the port has preemption MAC */
+ u32 pseudo_link:1;
+};
+
+enum netc_host_reason {
+ /* Software defined host reasons */
+ NETC_HR_HOST_FLOOD = 8,
+};
+
+struct netc_port {
+ void __iomem *iobase;
+ struct netc_switch *switch_priv;
+ struct netc_port_caps caps;
+ struct dsa_port *dp;
+ struct clk *ref_clk; /* RGMII/RMII reference clock */
+ struct mii_bus *emdio;
+ int ett_offset;
+
+ u16 enable:1;
+ u16 uc:1;
+ u16 mc:1;
+ u16 pvid;
+ struct ipft_entry_data *host_flood;
+};
+
+struct netc_switch_regs {
+ void __iomem *base;
+ void __iomem *port;
+ void __iomem *global;
+};
+
+struct netc_fdb_entry {
+ u32 entry_id;
+ struct fdbt_cfge_data cfge;
+ struct fdbt_keye_data keye;
+ struct hlist_node node;
+};
+
+struct netc_vlan_entry {
+ u16 vid;
+ u32 ect_gid;
+ u32 untagged_port_bitmap;
+ struct vft_cfge_data cfge;
+ struct hlist_node node;
+};
+
+struct netc_port_stat {
+ int reg;
+ char name[ETH_GSTRING_LEN] __nonstring;
+};
+
+struct netc_switch {
+ struct pci_dev *pdev;
+ struct device *dev;
+ struct dsa_switch *ds;
+ u16 revision;
+
+ const struct netc_switch_info *info;
+ struct netc_switch_regs regs;
+ struct netc_port **ports;
+ u32 port_bitmap; /* bitmap of available ports */
+
+ struct ntmp_user ntmp;
+ struct hlist_head fdb_list;
+ struct mutex fdbt_lock; /* FDB table lock */
+ struct delayed_work fdbt_ageing_work;
+ /* (fdbt_ageing_delay * NETC_FDBT_AGEING_THRESH) is ageing time */
+ unsigned long fdbt_ageing_delay;
+ atomic_t br_cnt;
+ struct hlist_head vlan_list;
+ struct mutex vft_lock; /* VLAN filter table lock */
+
+ /* Switch hardware capabilities */
+ u32 htmcapr_num_words;
+ u32 num_bp;
+
+ struct bpt_cfge_data *bpt_list;
+};
+
+#define NETC_PRIV(ds) ((struct netc_switch *)((ds)->priv))
+#define NETC_PORT(ds, port_id) (NETC_PRIV(ds)->ports[(port_id)])
+
+/* Write/Read Switch base registers */
+#define netc_base_rd(r, o) netc_read((r)->base + (o))
+#define netc_base_wr(r, o, v) netc_write((r)->base + (o), v)
+
+/* Write/Read registers of Switch Port (including pseudo MAC port) */
+#define netc_port_rd(p, o) netc_read((p)->iobase + (o))
+#define netc_port_rd64(p, o) netc_read64((p)->iobase + (o))
+#define netc_port_wr(p, o, v) netc_write((p)->iobase + (o), v)
+
+/* Write/Read Switch global registers */
+#define netc_glb_rd(r, o) netc_read((r)->global + (o))
+#define netc_glb_wr(r, o, v) netc_write((r)->global + (o), v)
+
+static inline bool is_netc_pseudo_port(struct netc_port *np)
+{
+ return np->caps.pseudo_link;
+}
+
+static inline void netc_add_fdb_entry(struct netc_switch *priv,
+ struct netc_fdb_entry *entry)
+{
+ hlist_add_head(&entry->node, &priv->fdb_list);
+}
+
+static inline void netc_del_fdb_entry(struct netc_fdb_entry *entry)
+{
+ hlist_del(&entry->node);
+ kfree(entry);
+}
+
+static inline void netc_add_vlan_entry(struct netc_switch *priv,
+ struct netc_vlan_entry *entry)
+{
+ hlist_add_head(&entry->node, &priv->vlan_list);
+}
+
+static inline void netc_del_vlan_entry(struct netc_vlan_entry *entry)
+{
+ hlist_del(&entry->node);
+ kfree(entry);
+}
+
+int netc_switch_platform_probe(struct netc_switch *priv);
+
+/* ethtool APIs */
+void netc_port_get_pause_stats(struct dsa_switch *ds, int port,
+ struct ethtool_pause_stats *pause_stats);
+void netc_port_get_rmon_stats(struct dsa_switch *ds, int port,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges);
+void netc_port_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_ctrl_stats *ctrl_stats);
+void netc_port_get_eth_mac_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_mac_stats *mac_stats);
+int netc_port_get_sset_count(struct dsa_switch *ds, int port, int sset);
+void netc_port_get_strings(struct dsa_switch *ds, int port,
+ u32 sset, u8 *data);
+void netc_port_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data);
+
+#endif
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h
new file mode 100644
index 000000000..1404ae41c
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -0,0 +1,372 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/*
+ * Copyright 2025-2026 NXP
+ */
+
+#ifndef _NETC_SWITCH_HW_H
+#define _NETC_SWITCH_HW_H
+
+#include <linux/bitops.h>
+
+#define NETC_SWITCH_VENDOR_ID 0x1131
+#define NETC_SWITCH_DEVICE_ID 0xeef2
+
+/* Definition of Switch base registers */
+#define NETC_BPCAPR 0x0008
+#define BPCAPR_NUM_BP GENMASK(7, 0)
+
+#define NETC_PBPMCR0 0x0400
+#define NETC_PBPMCR1 0x0404
+
+#define NETC_CBDRMR(a) (0x0800 + (a) * 0x30)
+#define NETC_CBDRBAR0(a) (0x0810 + (a) * 0x30)
+#define NETC_CBDRBAR1(a) (0x0814 + (a) * 0x30)
+#define NETC_CBDRPIR(a) (0x0818 + (a) * 0x30)
+#define NETC_CBDRCIR(a) (0x081c + (a) * 0x30)
+#define NETC_CBDRLENR(a) (0x0820 + (a) * 0x30)
+
+#define NETC_SWCR 0x1018
+#define SWCR_SWID GENMASK(2, 0)
+
+#define NETC_DOSL2CR 0x1220
+#define DOSL2CR_SAMEADDR BIT(0)
+#define DOSL2CR_MSAMCC BIT(1)
+
+#define NETC_DOSL3CR 0x1224
+#define DOSL3CR_SAMEADDR BIT(0)
+#define DOSL3CR_IPSAMCC BIT(1)
+
+#define NETC_ETTCAPR 0x18c4
+#define NETC_ECTCAPR 0x18ec
+/* Index table NUM_ENTRIES mask */
+#define NETC_NUM_ENTRIES GENMASK(15, 0)
+#define NETC_GET_NUM_ENTRIES(v) FIELD_GET(NETC_NUM_ENTRIES, (v))
+
+/* Hash table memory capability register, the memory is shared by
+ * the following tables:
+ *
+ * - Ingress Stream Identification table
+ * - Ingress Stream Filter table
+ * - VLAN Filter table
+ * - FDB table
+ * - L2 IPv4 Multicast Filter table
+ *
+ * Each hash table entry is one word in size.
+ */
+#define NETC_HTMCAPR 0x1900
+#define HTMCAPR_NUM_WORDS GENMASK(15, 0)
+
+#define NETC_VFHTDECR1 0x2014
+#define NETC_VFHTDECR2 0x2018
+#define VFHTDECR2_ET_PORT(a) BIT((a))
+#define VFHTDECR2_MLO GENMASK(26, 24)
+#define VFHTDECR2_MFO GENMASK(28, 27)
+
+/* Definition of Switch port registers */
+#define NETC_PCAPR 0x0000
+#define PCAPR_LINK_TYPE BIT(4)
+#define PCAPR_NUM_TC GENMASK(15, 12)
+#define PCAPR_NUM_Q GENMASK(19, 16)
+#define PCAPR_NUM_CG GENMASK(27, 24)
+#define PCAPR_TGS BIT(28)
+#define PCAPR_CBS BIT(29)
+
+#define NETC_PMCAPR 0x0004
+#define PMCAPR_HD BIT(8)
+#define PMCAPR_FP GENMASK(10, 9)
+#define FP_SUPPORT 2
+
+#define NETC_PCR 0x0010
+#define PCR_HDR_FMT BIT(0)
+#define PCR_NS_TAG_PORT BIT(3)
+#define PCR_L2DOSE BIT(4)
+#define PCR_L3DOSE BIT(5)
+#define PCR_TIMER_CS BIT(8)
+#define PCR_PSPEED GENMASK(29, 16)
+#define PSPEED_SET_VAL(s) FIELD_PREP(PCR_PSPEED, ((s) / 10 - 1))
+
+#define NETC_PQOSMR 0x0054
+#define PQOSMR_VS BIT(0)
+#define PQOSMR_VE BIT(1)
+#define PQOSMR_DDR GENMASK(3, 2)
+#define PQOSMR_DIPV GENMASK(6, 4)
+#define PQOSMR_VQMP GENMASK(19, 16)
+#define PQOSMR_QVMP GENMASK(23, 20)
+
+#define NETC_PIPFCR 0x0084
+#define PIPFCR_EN BIT(0)
+
+#define NETC_POR 0x100
+#define POR_TXDIS BIT(0)
+#define POR_RXDIS BIT(1)
+
+#define NETC_PSR 0x104
+#define PSR_TX_BUSY BIT(0)
+#define PSR_RX_BUSY BIT(1)
+
+#define NETC_PTGSLACR 0x130
+
+#define NETC_PRXDCR 0x1c0
+#define NETC_PRXDCRRR 0x1c4
+#define NETC_PRXDCRR0 0x1c8
+#define NETC_PRXDCRR1 0x1cc
+#define NETC_PTXDCR 0x1e0
+
+#define NETC_PTCTMSDUR(a) (0x208 + (a) * 0x20)
+#define PTCTMSDUR_MAXSDU GENMASK(15, 0)
+#define PTCTMSDUR_SDU_TYPE GENMASK(17, 16)
+#define SDU_TYPE_PPDU 0
+#define SDU_TYPE_MPDU 1
+#define SDU_TYPE_MSDU 2
+
+#define NETC_PSDFTCR 0x4c4
+#define NETC_PSDFDDCR 0x4c8
+
+#define NETC_BPCR 0x500
+#define BPCR_DYN_LIMIT GENMASK(15, 0)
+#define BPCR_MLO GENMASK(22, 20)
+#define BPCR_UUCASTE BIT(24)
+#define BPCR_UMCASTE BIT(25)
+#define BPCR_MCASTE BIT(26)
+#define BPCR_BCASTE BIT(27)
+#define BPCR_STAMVD BIT(28)
+#define BPCR_SRCPRND BIT(29)
+
+/* MAC learning options, see BPCR[MLO], VFHTDECR2[MLO] and
+ * VLAN Filter Table CFGE_DATA[MLO]
+ */
+enum netc_mlo {
+ MLO_NOT_OVERRIDE = 0,
+ MLO_DISABLE,
+ MLO_HW,
+ MLO_SW_SEC,
+ MLO_SW_UNSEC,
+ MLO_DISABLE_SMAC,
+};
+
+/* MAC forwarding options, see VFHTDECR2[MFO] and VLAN
+ * Filter Table CFGE_DATA[MFO]
+ */
+enum netc_mfo {
+ MFO_NO_FDB_LOOKUP = 1,
+ MFO_NO_MATCH_FLOOD,
+ MFO_NO_MATCH_DISCARD,
+};
+
+#define NETC_BPDVR 0x510
+#define BPDVR_VID GENMASK(11, 0)
+#define BPDVR_DEI BIT(12)
+#define BPDVR_PCP GENMASK(15, 13)
+#define BPDVR_TPID BIT(16)
+#define BPDVR_RXTAGA GENMASK(23, 20)
+#define BPDVR_RXVAM BIT(24)
+#define BPDVR_TXTAGA GENMASK(26, 25)
+
+#define NETC_BPSTGSR 0x520
+
+enum netc_stg_stage {
+ NETC_STG_STATE_DISABLED = 0,
+ NETC_STG_STATE_LEARNING,
+ NETC_STG_STATE_FORWARDING,
+};
+
+#define NETC_BPDCR 0x580
+
+/* Definition of Switch ethernet MAC port registers */
+#define NETC_PMAC_OFFSET 0x400
+#define NETC_PM_CMD_CFG(a) (0x1008 + (a) * 0x400)
+#define PM_CMD_CFG_TX_EN BIT(0)
+#define PM_CMD_CFG_RX_EN BIT(1)
+#define PM_CMD_CFG_PAUSE_IGN BIT(8)
+
+#define NETC_PM_MAXFRM(a) (0x1014 + (a) * 0x400)
+#define PM_MAXFRAM GENMASK(15, 0)
+
+#define NETC_PM_IEVENT(a) (0x1040 + (a) * 0x400)
+#define PM_IEVENT_TX_EMPTY BIT(5)
+#define PM_IEVENT_RX_EMPTY BIT(6)
+
+#define NETC_PM_PAUSE_QUANTA(a) (0x1054 + (a) * 0x400)
+#define NETC_PM_PAUSE_THRESH(a) (0x1064 + (a) * 0x400)
+
+#define NETC_PM_IF_MODE(a) (0x1300 + (a) * 0x400)
+#define PM_IF_MODE_IFMODE GENMASK(2, 0)
+#define IFMODE_MII 1
+#define IFMODE_RMII 3
+#define IFMODE_RGMII 4
+#define IFMODE_SGMII 5
+#define PM_IF_MODE_REVMII BIT(3)
+#define PM_IF_MODE_M10 BIT(4)
+#define PM_IF_MODE_HD BIT(6)
+#define PM_IF_MODE_SSP GENMASK(14, 13)
+#define SSP_100M 0
+#define SSP_10M 1
+#define SSP_1G 2
+
+/* Port MAC 0/1 Receive Ethernet Octets Counter */
+#define NETC_PM_REOCT(a) (0x1100 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Octets Counter */
+#define NETC_PM_ROCT(a) (0x1108 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Alignment Error Counter Register */
+#define NETC_PM_RALN(a) (0x1110 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Valid Pause Frame Counter */
+#define NETC_PM_RXPF(a) (0x1118 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Frame Counter */
+#define NETC_PM_RFRM(a) (0x1120 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Frame Check Sequence Error Counter */
+#define NETC_PM_RFCS(a) (0x1128 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive VLAN Frame Counter */
+#define NETC_PM_RVLAN(a) (0x1130 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Frame Error Counter */
+#define NETC_PM_RERR(a) (0x1138 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Unicast Frame Counter */
+#define NETC_PM_RUCA(a) (0x1140 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Multicast Frame Counter */
+#define NETC_PM_RMCA(a) (0x1148 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Broadcast Frame Counter */
+#define NETC_PM_RBCA(a) (0x1150 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Dropped Packets Counter */
+#define NETC_PM_RDRP(a) (0x1158 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Packets Counter */
+#define NETC_PM_RPKT(a) (0x1160 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Undersized Packet Counter */
+#define NETC_PM_RUND(a) (0x1168 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 64-Octet Packet Counter */
+#define NETC_PM_R64(a) (0x1170 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 65 to 127-Octet Packet Counter */
+#define NETC_PM_R127(a) (0x1178 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 128 to 255-Octet Packet Counter */
+#define NETC_PM_R255(a) (0x1180 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 256 to 511-Octet Packet Counter */
+#define NETC_PM_R511(a) (0x1188 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 512 to 1023-Octet Packet Counter */
+#define NETC_PM_R1023(a) (0x1190 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 1024 to 1522-Octet Packet Counter */
+#define NETC_PM_R1522(a) (0x1198 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 1523 to Max-Octet Packet Counter */
+#define NETC_PM_R1523X(a) (0x11a0 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Oversized Packet Counter */
+#define NETC_PM_ROVR(a) (0x11a8 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Jabber Packet Counter */
+#define NETC_PM_RJBR(a) (0x11b0 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Fragment Packet Counter */
+#define NETC_PM_RFRG(a) (0x11b8 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Control Packet Counter */
+#define NETC_PM_RCNP(a) (0x11c0 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Dropped Not Truncated Packets Counter */
+#define NETC_PM_RDRNTP(a) (0x11c8 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Ethernet Octets Counter */
+#define NETC_PM_TEOCT(a) (0x1200 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Octets Counter */
+#define NETC_PM_TOCT(a) (0x1208 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Excessive Deferral Packet Counter */
+#define NETC_PM_TEDFR(a) (0x1210 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Valid Pause Frame Counter */
+#define NETC_PM_TXPF(a) (0x1218 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Frame Counter */
+#define NETC_PM_TFRM(a) (0x1220 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Frame Check Sequence Error Counter */
+#define NETC_PM_TFCS(a) (0x1228 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit VLAN Frame Counter */
+#define NETC_PM_TVLAN(a) (0x1230 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Frame Error Counter */
+#define NETC_PM_TERR(a) (0x1238 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Unicast Frame Counter */
+#define NETC_PM_TUCA(a) (0x1240 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Multicast Frame Counter */
+#define NETC_PM_TMCA(a) (0x1248 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Broadcast Frame Counter */
+#define NETC_PM_TBCA(a) (0x1250 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Packets Counter */
+#define NETC_PM_TPKT(a) (0x1260 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Undersized Packet Counter */
+#define NETC_PM_TUND(a) (0x1268 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 64-Octet Packet Counter */
+#define NETC_PM_T64(a) (0x1270 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 65 to 127-Octet Packet Counter */
+#define NETC_PM_T127(a) (0x1278 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 128 to 255-Octet Packet Counter */
+#define NETC_PM_T255(a) (0x1280 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 256 to 511-Octet Packet Counter */
+#define NETC_PM_T511(a) (0x1288 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 512 to 1023-Octet Packet Counter */
+#define NETC_PM_T1023(a) (0x1290 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 1024 to 1522-Octet Packet Counter */
+#define NETC_PM_T1522(a) (0x1298 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 1523 to TX_MTU-Octet Packet Counter */
+#define NETC_PM_T1523X(a) (0x12a0 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Control Packet Counter */
+#define NETC_PM_TCNP(a) (0x12c0 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Deferred Packet Counter */
+#define NETC_PM_TDFR(a) (0x12d0 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Multiple Collisions Counter */
+#define NETC_PM_TMCOL(a) (0x12d8 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Single Collision */
+#define NETC_PM_TSCOL(a) (0x12e0 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Late Collision Counter */
+#define NETC_PM_TLCOL(a) (0x12e8 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Excessive Collisions Counter */
+#define NETC_PM_TECOL(a) (0x12f0 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Invalid Octets Counter */
+#define NETC_PM_TIOCT(a) (0x12f8 + (a) * 0x400)
+
+#define NETC_PEMDIOCR 0x1c00
+#define NETC_EMDIO_BASE NETC_PEMDIOCR
+
+/* Definition of global registers (read only) */
+#define NETC_IPBRR0 0x0bf8
+#define IPBRR0_IP_REV GENMASK(15, 0)
+
+#endif