[net-next v16 08/12] net: mtip: Extend the L2 switch driver with management operations

Lukasz Majewski
2025-07-24T23:30:23Z

This patch provides function necessary for manging the L2 switch.

Signed-off-by: Lukasz Majewski <lukma@denx.de>

---
Changes for v13:
- New patch - created by excluding some code from large (i.e. v12 and
earlier) MTIP driver

Changes for v14 - v16:
- None
---
.../net/ethernet/freescale/mtipsw/Makefile | 2 +-
.../net/ethernet/freescale/mtipsw/mtipl2sw.c | 31 ++
.../net/ethernet/freescale/mtipsw/mtipl2sw.h | 23 +
.../ethernet/freescale/mtipsw/mtipl2sw_mgnt.c | 443 ++++++++++++++++++
4 files changed, 498 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/freescale/mtipsw/mtipl2sw_mgnt.c

diff --git a/drivers/net/ethernet/freescale/mtipsw/Makefile b/drivers/net/ethernet/freescale/mtipsw/Makefile
index bd8ffb30939a..a99aaf6ddfb2 100644
--- a/drivers/net/ethernet/freescale/mtipsw/Makefile
+++ b/drivers/net/ethernet/freescale/mtipsw/Makefile
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0

obj-$(CONFIG_FEC_MTIP_L2SW) += nxp-mtipl2sw.o
-nxp-mtipl2sw-objs := mtipl2sw.o
+nxp-mtipl2sw-objs := mtipl2sw.o mtipl2sw_mgnt.o
diff --git a/drivers/net/ethernet/freescale/mtipsw/mtipl2sw.c b/drivers/net/ethernet/freescale/mtipsw/mtipl2sw.c
index 7ccf467ad8ba..6d2e21ccf404 100644
--- a/drivers/net/ethernet/freescale/mtipsw/mtipl2sw.c
+++ b/drivers/net/ethernet/freescale/mtipsw/mtipl2sw.c
@@ -474,8 +474,35 @@ static void mtip_config_switch(struct switch_enet_private *fep)

writel(0, fep->hwp + ESW_BKLR);

+ /* Do NOT disable learning */
+ mtip_port_learning_config(fep, 0, 0, 0);
+ mtip_port_learning_config(fep, 1, 0, 0);
+ mtip_port_learning_config(fep, 2, 0, 0);
+
+ /* Disable blocking */
+ mtip_port_blocking_config(fep, 0, 0);
+ mtip_port_blocking_config(fep, 1, 0);
+ mtip_port_blocking_config(fep, 2, 0);
+
writel(MCF_ESW_IMR_TXF | MCF_ESW_IMR_RXF,
fep->hwp + ESW_IMR);
+
+ mtip_port_enable_config(fep, 0, 1, 1);
+ mtip_port_enable_config(fep, 1, 1, 1);
+ mtip_port_enable_config(fep, 2, 1, 1);
+
+ mtip_port_broadcast_config(fep, 0, 1);
+ mtip_port_broadcast_config(fep, 1, 1);
+ mtip_port_broadcast_config(fep, 2, 1);
+
+ /* Disable multicast receive on port 0 (MGNT) */
+ mtip_port_multicast_config(fep, 0, 0);
+ mtip_port_multicast_config(fep, 1, 1);
+ mtip_port_multicast_config(fep, 2, 1);
+
+ /* Setup VLANs to provide port separation */
+ if (!fep->br_offload)
+ mtip_switch_en_port_separation(fep);
}

static netdev_tx_t mtip_start_xmit_port(struct sk_buff *skb,
@@ -564,6 +591,10 @@ static netdev_tx_t mtip_start_xmit_port(struct sk_buff *skb,

skb_tx_timestamp(skb);

+ /* For port separation - force sending via specified port */
+ if (!fep->br_offload && port != 0)
+ mtip_forced_forward(fep, port, 1);
+
/* Trigger transmission start */
writel(MCF_ESW_TDAR_X_DES_ACTIVE, fep->hwp + ESW_TDAR);

diff --git a/drivers/net/ethernet/freescale/mtipsw/mtipl2sw.h b/drivers/net/ethernet/freescale/mtipsw/mtipl2sw.h
index 8a3cce976476..7e5373823d43 100644
--- a/drivers/net/ethernet/freescale/mtipsw/mtipl2sw.h
+++ b/drivers/net/ethernet/freescale/mtipsw/mtipl2sw.h
@@ -621,6 +621,29 @@ static inline int mtip_get_time(void)

#define MTIP_PORT_FORWARDING_INIT 0xFF

+/* Switch Management functions */
+int mtip_vlan_input_process(struct switch_enet_private *fep,
+ int port, int mode, unsigned short port_vlanid,
+ int vlan_verify_en, int vlan_domain_num,
+ int vlan_domain_port);
+int mtip_set_vlan_verification(struct switch_enet_private *fep, int port,
+ int vlan_domain_verify_en,
+ int vlan_discard_unknown_en);
+int mtip_port_multicast_config(struct switch_enet_private *fep, int port,
+ bool enable);
+int mtip_vlan_output_process(struct switch_enet_private *fep, int port,
+ int mode);
+void mtip_switch_en_port_separation(struct switch_enet_private *fep);
+void mtip_switch_dis_port_separation(struct switch_enet_private *fep);
+int mtip_port_broadcast_config(struct switch_enet_private *fep,
+ int port, bool enable);
+int mtip_forced_forward(struct switch_enet_private *fep, int port, bool enable);
+int mtip_port_learning_config(struct switch_enet_private *fep, int port,
+ bool disable, bool irq_adj);
+int mtip_port_blocking_config(struct switch_enet_private *fep, int port,
+ bool enable);
bool mtip_is_switch_netdev_port(const struct net_device *ndev);
+int mtip_port_enable_config(struct switch_enet_private *fep, int port,
+ bool tx_en, bool rx_en);
void mtip_clear_atable(struct switch_enet_private *fep);
#endif /* __MTIP_L2SWITCH_H_ */
diff --git a/drivers/net/ethernet/freescale/mtipsw/mtipl2sw_mgnt.c b/drivers/net/ethernet/freescale/mtipsw/mtipl2sw_mgnt.c
new file mode 100644
index 000000000000..2178b3d02d57
--- /dev/null
+++ b/drivers/net/ethernet/freescale/mtipsw/mtipl2sw_mgnt.c
@@ -0,0 +1,443 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * L2 switch Controller driver for MTIP block - switch MGNT
+ *
+ * Copyright (C) 2025 DENX Software Engineering GmbH
+ * Lukasz Majewski <lukma@denx.de>
+ *
+ * Based on a previous work by:
+ *
+ * Copyright 2010-2012 Freescale Semiconductor, Inc.
+ * Alison Wang (b18965@freescale.com)
+ * Jason Jin (Jason.jin@freescale.com)
+ *
+ * Copyright (C) 2010-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Shrek Wu (B16972@freescale.com)
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+
+#include "mtipl2sw.h"
+
+int mtip_vlan_input_process(struct switch_enet_private *fep,
+ int port, int mode, unsigned short port_vlanid,
+ int vlan_verify_en, int vlan_domain_num,
+ int vlan_domain_port)
+{
+ /* Only modes from 1 to 4 are valid*/
+ if (mode < 0 || mode > 4) {
+ dev_err(&fep->pdev->dev,
+ "%s: VLAN input processing mode (%d) not supported\n",
+ __func__, mode);
+ return -EINVAL;
+ }
+
+ if (port < 0 || port > 2) {
+ dev_err(&fep->pdev->dev, "%s: Port (%d) not supported!\n",
+ __func__, port);
+ return -EINVAL;
+ }
+
+ if (vlan_verify_en == 1 &&
+ (vlan_domain_num < 0 || vlan_domain_num > 32)) {
+ dev_err(&fep->pdev->dev, "%s: Domain out of range\n", __func__);
+ return -EINVAL;
+ }
+
+ writel(FIELD_PREP(MCF_ESW_PID_VLANID_MASK, port_vlanid),
+ fep->hwp + ESW_PID(port));
+ if (port == 0) {
+ if (vlan_verify_en == 1)
+ writel(FIELD_PREP(MCF_ESW_VRES_VLANID_MASK,
+ port_vlanid) | MCF_ESW_VRES_P0,
+ fep->hwp + ESW_VRES(vlan_domain_num));
+
+ writel(readl(fep->hwp + ESW_VIMEN) | MCF_ESW_VIMEN_EN0,
+ fep->hwp + ESW_VIMEN);
+ writel(readl(fep->hwp + ESW_VIMSEL) |
+ FIELD_PREP(MCF_ESW_VIMSEL_IM0_MASK, mode),
+ fep->hwp + ESW_VIMSEL);
+ } else if (port == 1) {
+ if (vlan_verify_en == 1)
+ writel(FIELD_PREP(MCF_ESW_VRES_VLANID_MASK,
+ port_vlanid) | MCF_ESW_VRES_P1,
+ fep->hwp + ESW_VRES(vlan_domain_num));
+
+ writel(readl(fep->hwp + ESW_VIMEN) | MCF_ESW_VIMEN_EN1,
+ fep->hwp + ESW_VIMEN);
+ writel(readl(fep->hwp + ESW_VIMSEL) |
+ FIELD_PREP(MCF_ESW_VIMSEL_IM1_MASK, mode),
+ fep->hwp + ESW_VIMSEL);
+ } else if (port == 2) {
+ if (vlan_verify_en == 1)
+ writel(FIELD_PREP(MCF_ESW_VRES_VLANID_MASK,
+ port_vlanid) | MCF_ESW_VRES_P2,
+ fep->hwp + ESW_VRES(vlan_domain_num));
+
+ writel(readl(fep->hwp + ESW_VIMEN) | MCF_ESW_VIMEN_EN2,
+ fep->hwp + ESW_VIMEN);
+ writel(readl(fep->hwp + ESW_VIMSEL) |
+ FIELD_PREP(MCF_ESW_VIMSEL_IM2_MASK, mode),
+ fep->hwp + ESW_VIMSEL);
+ }
+
+ return 0;
+}
+
+int mtip_vlan_output_process(struct switch_enet_private *fep, int port,
+ int mode)
+{
+ if (port < 0 || port > 2) {
+ dev_err(&fep->pdev->dev, "%s: Port (%d) not supported!\n",
+ __func__, port);
+ return -EINVAL;
+ }
+
+ if (port == 0) {
+ writel(readl(fep->hwp + ESW_VOMSEL) |
+ FIELD_PREP(MCF_ESW_VOMSEL_OM0_MASK, mode),
+ fep->hwp + ESW_VOMSEL);
+ } else if (port == 1) {
+ writel(readl(fep->hwp + ESW_VOMSEL) |
+ FIELD_PREP(MCF_ESW_VOMSEL_OM1_MASK, mode),
+ fep->hwp + ESW_VOMSEL);
+ } else if (port == 2) {
+ writel(readl(fep->hwp + ESW_VOMSEL) |
+ FIELD_PREP(MCF_ESW_VOMSEL_OM2_MASK, mode),
+ fep->hwp + ESW_VOMSEL);
+ }
+
+ return 0;
+}
+
+int mtip_set_vlan_verification(struct switch_enet_private *fep, int port,
+ int vlan_domain_verify_en,
+ int vlan_discard_unknown_en)
+{
+ if (port < 0 || port > 2) {
+ dev_err(&fep->pdev->dev, "%s: Port (%d) not supported!\n",
+ __func__, port);
+ return -EINVAL;
+ }
+
+ if (vlan_domain_verify_en == 1) {
+ if (port == 0)
+ writel(readl(fep->hwp + ESW_VLANV) | MCF_ESW_VLANV_VV0,
+ fep->hwp + ESW_VLANV);
+ else if (port == 1)
+ writel(readl(fep->hwp + ESW_VLANV) | MCF_ESW_VLANV_VV1,
+ fep->hwp + ESW_VLANV);
+ else if (port == 2)
+ writel(readl(fep->hwp + ESW_VLANV) | MCF_ESW_VLANV_VV2,
+ fep->hwp + ESW_VLANV);
+ } else if (vlan_domain_verify_en == 0) {
+ if (port == 0)
+ writel(readl(fep->hwp + ESW_VLANV) & ~MCF_ESW_VLANV_VV0,
+ fep->hwp + ESW_VLANV);
+ else if (port == 1)
+ writel(readl(fep->hwp + ESW_VLANV) & ~MCF_ESW_VLANV_VV1,
+ fep->hwp + ESW_VLANV);
+ else if (port == 2)
+ writel(readl(fep->hwp + ESW_VLANV) & ~MCF_ESW_VLANV_VV2,
+ fep->hwp + ESW_VLANV);
+ }
+
+ if (vlan_discard_unknown_en == 1) {
+ if (port == 0)
+ writel(readl(fep->hwp + ESW_VLANV) | MCF_ESW_VLANV_DU0,
+ fep->hwp + ESW_VLANV);
+ else if (port == 1)
+ writel(readl(fep->hwp + ESW_VLANV) | MCF_ESW_VLANV_DU1,
+ fep->hwp + ESW_VLANV);
+ else if (port == 2)
+ writel(readl(fep->hwp + ESW_VLANV) | MCF_ESW_VLANV_DU2,
+ fep->hwp + ESW_VLANV);
+ } else if (vlan_discard_unknown_en == 0) {
+ if (port == 0)
+ writel(readl(fep->hwp + ESW_VLANV) & ~MCF_ESW_VLANV_DU0,
+ fep->hwp + ESW_VLANV);
+ else if (port == 1)
+ writel(readl(fep->hwp + ESW_VLANV) & ~MCF_ESW_VLANV_DU1,
+ fep->hwp + ESW_VLANV);
+ else if (port == 2)
+ writel(readl(fep->hwp + ESW_VLANV) & ~MCF_ESW_VLANV_DU2,
+ fep->hwp + ESW_VLANV);
+ }
+
+ dev_dbg(&fep->pdev->dev, "%s: ESW_VLANV %#x\n", __func__,
+ readl(fep->hwp + ESW_VLANV));
+
+ return 0;
+}
+
+int mtip_port_multicast_config(struct switch_enet_private *fep,
+ int port, bool enable)
+{
+ u32 reg = 0;
+
+ if (port < 0 || port > 2) {
+ dev_err(&fep->pdev->dev, "%s: Port (%d) not supported\n",
+ __func__, port);
+ return -EINVAL;
+ }
+
+ reg = readl(fep->hwp + ESW_DMCR);
+ if (enable) {
+ if (port == 0)
+ reg |= MCF_ESW_DMCR_P0;
+ else if (port == 1)
+ reg |= MCF_ESW_DMCR_P1;
+ else if (port == 2)
+ reg |= MCF_ESW_DMCR_P2;
+ } else {
+ if (port == 0)
+ reg &= ~MCF_ESW_DMCR_P0;
+ else if (port == 1)
+ reg &= ~MCF_ESW_DMCR_P1;
+ else if (port == 2)
+ reg &= ~MCF_ESW_DMCR_P2;
+ }
+
+ writel(reg, fep->hwp + ESW_DMCR);
+ return 0;
+}
+
+/* enable or disable port n tx or rx
+ * tx_en 0 disable port n tx
+ * tx_en 1 enable port n tx
+ * rx_en 0 disable port n rx
+ * rx_en 1 enable port n rx
+ */
+int mtip_port_enable_config(struct switch_enet_private *fep, int port,
+ bool tx_en, bool rx_en)
+{
+ u32 reg = 0;
+
+ if (port < 0 || port > 2) {
+ dev_err(&fep->pdev->dev, "%s: Port (%d) not supported\n",
+ __func__, port);
+ return -EINVAL;
+ }
+
+ reg = readl(fep->hwp + ESW_PER);
+ if (tx_en) {
+ if (port == 0)
+ reg |= MCF_ESW_PER_TE0;
+ else if (port == 1)
+ reg |= MCF_ESW_PER_TE1;
+ else if (port == 2)
+ reg |= MCF_ESW_PER_TE2;
+ } else {
+ if (port == 0)
+ reg &= (~MCF_ESW_PER_TE0);
+ else if (port == 1)
+ reg &= (~MCF_ESW_PER_TE1);
+ else if (port == 2)
+ reg &= (~MCF_ESW_PER_TE2);
+ }
+
+ if (rx_en) {
+ if (port == 0)
+ reg |= MCF_ESW_PER_RE0;
+ else if (port == 1)
+ reg |= MCF_ESW_PER_RE1;
+ else if (port == 2)
+ reg |= MCF_ESW_PER_RE2;
+ } else {
+ if (port == 0)
+ reg &= (~MCF_ESW_PER_RE0);
+ else if (port == 1)
+ reg &= (~MCF_ESW_PER_RE1);
+ else if (port == 2)
+ reg &= (~MCF_ESW_PER_RE2);
+ }
+
+ writel(reg, fep->hwp + ESW_PER);
+ return 0;
+}
+
+void mtip_switch_en_port_separation(struct switch_enet_private *fep)
+{
+ u32 reg;
+
+ mtip_vlan_input_process(fep, 0, 3, 0x10, 1, 0, 0);
+ mtip_vlan_input_process(fep, 1, 3, 0x11, 1, 1, 0);
+ mtip_vlan_input_process(fep, 2, 3, 0x12, 1, 2, 0);
+
+ reg = readl(fep->hwp + ESW_VRES(0));
+ writel(reg | MCF_ESW_VRES_P1 | MCF_ESW_VRES_P2,
+ fep->hwp + ESW_VRES(0));
+
+ reg = readl(fep->hwp + ESW_VRES(1));
+ writel(reg | MCF_ESW_VRES_P0, fep->hwp + ESW_VRES(1));
+
+ reg = readl(fep->hwp + ESW_VRES(2));
+ writel(reg | MCF_ESW_VRES_P0, fep->hwp + ESW_VRES(2));
+
+ dev_dbg(&fep->pdev->dev, "%s: VRES0: 0x%x\n",
+ __func__, readl(fep->hwp + ESW_VRES(0)));
+ dev_dbg(&fep->pdev->dev, "%s: VRES1: 0x%x\n", __func__,
+ readl(fep->hwp + ESW_VRES(1)));
+ dev_dbg(&fep->pdev->dev, "%s: VRES2: 0x%x\n", __func__,
+ readl(fep->hwp + ESW_VRES(2)));
+
+ mtip_set_vlan_verification(fep, 0, 1, 0);
+ mtip_set_vlan_verification(fep, 1, 1, 0);
+ mtip_set_vlan_verification(fep, 2, 1, 0);
+
+ mtip_vlan_output_process(fep, 0, 2);
+ mtip_vlan_output_process(fep, 1, 2);
+ mtip_vlan_output_process(fep, 2, 2);
+}
+
+void mtip_switch_dis_port_separation(struct switch_enet_private *fep)
+{
+ writel(0, fep->hwp + ESW_PID(0));
+ writel(0, fep->hwp + ESW_PID(1));
+ writel(0, fep->hwp + ESW_PID(2));
+
+ writel(0, fep->hwp + ESW_VRES(0));
+ writel(0, fep->hwp + ESW_VRES(1));
+ writel(0, fep->hwp + ESW_VRES(2));
+
+ writel(0, fep->hwp + ESW_VIMEN);
+ writel(0, fep->hwp + ESW_VIMSEL);
+ writel(0, fep->hwp + ESW_VLANV);
+ writel(0, fep->hwp + ESW_VOMSEL);
+}
+
+int mtip_port_broadcast_config(struct switch_enet_private *fep,
+ int port, bool enable)
+{
+ u32 reg = 0;
+
+ if (port < 0 || port > 2) {
+ dev_err(&fep->pdev->dev, "%s: Port (%d) not supported\n",
+ __func__, port);
+ return -EINVAL;
+ }
+
+ reg = readl(fep->hwp + ESW_DBCR);
+ if (enable) {
+ if (port == 0)
+ reg |= MCF_ESW_DBCR_P0;
+ else if (port == 1)
+ reg |= MCF_ESW_DBCR_P1;
+ else if (port == 2)
+ reg |= MCF_ESW_DBCR_P2;
+ } else {
+ if (port == 0)
+ reg &= ~MCF_ESW_DBCR_P0;
+ else if (port == 1)
+ reg &= ~MCF_ESW_DBCR_P1;
+ else if (port == 2)
+ reg &= ~MCF_ESW_DBCR_P2;
+ }
+
+ writel(reg, fep->hwp + ESW_DBCR);
+ return 0;
+}
+
+/* The frame is forwarded to the forced destination ports.
+ * It only replace the MAC lookup function,
+ * all other filtering(eg.VLAN verification) act as normal
+ */
+int mtip_forced_forward(struct switch_enet_private *fep, int port, bool enable)
+{
+ u32 reg = 0;
+
+ if (port & ~GENMASK(1, 0)) {
+ dev_err(&fep->pdev->dev,
+ "%s: Forced forward for port(s): 0x%x not supported!\n",
+ __func__, port);
+ return -EINVAL;
+ }
+
+ /* Enable Forced forwarding for port(s) */
+ reg |= FIELD_PREP(MCF_ESW_P0FFEN_FD_MASK, port & GENMASK(1, 0));
+
+ if (enable)
+ reg |= MCF_ESW_P0FFEN_FEN;
+ else
+ reg &= ~MCF_ESW_P0FFEN_FEN;
+
+ writel(reg, fep->hwp + ESW_P0FFEN);
+ return 0;
+}
+
+int mtip_port_learning_config(struct switch_enet_private *fep, int port,
+ bool disable, bool irq_adj)
+{
+ u32 reg = 0;
+
+ if (port < 0 || port > 2) {
+ dev_err(&fep->pdev->dev, "%s: Port (%d) not supported\n",
+ __func__, port);
+ return -EINVAL;
+ }
+
+ reg = readl(fep->hwp + ESW_BKLR);
+ if (disable) {
+ if (irq_adj)
+ writel(readl(fep->hwp + ESW_IMR) & ~MCF_ESW_IMR_LRN,
+ fep->hwp + ESW_IMR);
+
+ if (port == 0)
+ reg |= MCF_ESW_BKLR_LD0;
+ else if (port == 1)
+ reg |= MCF_ESW_BKLR_LD1;
+ else if (port == 2)
+ reg |= MCF_ESW_BKLR_LD2;
+ } else {
+ if (irq_adj)
+ writel(readl(fep->hwp + ESW_IMR) | MCF_ESW_IMR_LRN,
+ fep->hwp + ESW_IMR);
+
+ if (port == 0)
+ reg &= ~MCF_ESW_BKLR_LD0;
+ else if (port == 1)
+ reg &= ~MCF_ESW_BKLR_LD1;
+ else if (port == 2)
+ reg &= ~MCF_ESW_BKLR_LD2;
+ }
+
+ writel(reg, fep->hwp + ESW_BKLR);
+ dev_dbg(&fep->pdev->dev, "%s ESW_BKLR %#x, ESW_IMR %#x\n", __func__,
+ readl(fep->hwp + ESW_BKLR), readl(fep->hwp + ESW_IMR));
+
+ return 0;
+}
+
+int mtip_port_blocking_config(struct switch_enet_private *fep, int port,
+ bool enable)
+{
+ u32 reg = 0;
+
+ if (port < 0 || port > 2) {
+ dev_err(&fep->pdev->dev, "%s: Port (%d) not supported\n",
+ __func__, port);
+ return -EINVAL;
+ }
+
+ reg = readl(fep->hwp + ESW_BKLR);
+ if (enable) {
+ if (port == 0)
+ reg |= MCF_ESW_BKLR_BE0;
+ else if (port == 1)
+ reg |= MCF_ESW_BKLR_BE1;
+ else if (port == 2)
+ reg |= MCF_ESW_BKLR_BE2;
+ } else {
+ if (port == 0)
+ reg &= ~MCF_ESW_BKLR_BE0;
+ else if (port == 1)
+ reg &= ~MCF_ESW_BKLR_BE1;
+ else if (port == 2)
+ reg &= ~MCF_ESW_BKLR_BE2;
+ }
+
+ writel(reg, fep->hwp + ESW_BKLR);
+ return 0;
+}
--
2.39.5