aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-06-09 19:35:45 -0700
committerJakub Kicinski <kuba@kernel.org>2026-06-09 19:35:45 -0700
commit5855479abc796c3b5d7b2f2ca147d68fc56cae1f (patch)
treeabe840b5480e8f4763ad05c1b319c4c2e3cbd085
parentaa97f11a76e545ed73cc455bcdd1ece4b480b507 (diff)
parent660a9e399ab02c0cb86d277ed6b0c9d10c350fdd (diff)
Merge branch 'net-dsa-realtek-rtl8365mb-bridge-offloading-and-vlan-support'
Luiz Angelo Daros de Luca says: ==================== net: dsa: realtek: rtl8365mb: bridge offloading and VLAN support This series introduces bridge offloading, FDB management, and VLAN support for the Realtek rtl8365mb DSA switch driver. The primary goal is to enable hardware frame forwarding between bridge ports, reducing CPU overhead and providing advanced features like VLAN and FDB isolation. Some of these patches are based on original work by Alvin Šipraga, subsequently adapted and updated for the current net-next state. I attempted to reach Alvin for review of the final version but was unable to establish contact. Any regressions in this version are my responsibility. ==================== Link: https://patch.msgid.link/20260606-realtek_forward-v13-0-b9e409687cbe@gmail.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r--drivers/net/dsa/realtek/Makefile5
-rw-r--r--drivers/net/dsa/realtek/realtek.h46
-rw-r--r--drivers/net/dsa/realtek/rtl8365mb_l2.c576
-rw-r--r--drivers/net/dsa/realtek/rtl8365mb_l2.h32
-rw-r--r--drivers/net/dsa/realtek/rtl8365mb_main.c (renamed from drivers/net/dsa/realtek/rtl8365mb.c)712
-rw-r--r--drivers/net/dsa/realtek/rtl8365mb_table.c214
-rw-r--r--drivers/net/dsa/realtek/rtl8365mb_table.h138
-rw-r--r--drivers/net/dsa/realtek/rtl8365mb_vlan.c944
-rw-r--r--drivers/net/dsa/realtek/rtl8365mb_vlan.h39
-rw-r--r--drivers/net/dsa/realtek/rtl83xx.c563
-rw-r--r--drivers/net/dsa/realtek/rtl83xx.h27
11 files changed, 3219 insertions, 77 deletions
diff --git a/drivers/net/dsa/realtek/Makefile b/drivers/net/dsa/realtek/Makefile
index 17367bcba496..72bb42e80c95 100644
--- a/drivers/net/dsa/realtek/Makefile
+++ b/drivers/net/dsa/realtek/Makefile
@@ -16,3 +16,8 @@ ifdef CONFIG_NET_DSA_REALTEK_RTL8366RB_LEDS
rtl8366-objs += rtl8366rb-leds.o
endif
obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o
+rtl8365mb-objs := rtl8365mb_main.o \
+ rtl8365mb_table.o \
+ rtl8365mb_vlan.o \
+ rtl8365mb_l2.o \
+# end of rtl8365mb-objs
diff --git a/drivers/net/dsa/realtek/realtek.h b/drivers/net/dsa/realtek/realtek.h
index c03485a80d93..6e0148cee8d8 100644
--- a/drivers/net/dsa/realtek/realtek.h
+++ b/drivers/net/dsa/realtek/realtek.h
@@ -45,6 +45,12 @@ struct rtl8366_vlan_4k {
u8 fid;
};
+struct realtek_fdb_entry {
+ u8 mac_addr[ETH_ALEN];
+ u16 vid;
+ bool is_static;
+};
+
struct realtek_priv {
struct device *dev;
struct reset_control *reset_ctl;
@@ -54,6 +60,20 @@ struct realtek_priv {
struct regmap *map;
struct regmap *map_nolock;
struct mutex map_lock;
+ /* vlan_lock protects against concurrent Read-Modify-Write operations
+ * on the global VLAN 4K and VLANMC tables, such as when adding or
+ * deleting port VLAN memberships and PVID configurations.
+ */
+ struct mutex vlan_lock;
+ /* l2_lock is used to prevent concurrent modifications of L2 table
+ * entries while another function is reading it. l2_(add,del)_mc
+ * is an example that first read current table entry and then
+ * create/update it. l2_(add|del)_uc uses a single table op and,
+ * internally, it might not need this lock. However, altering FDB
+ * may still collide, as well as l2_flush, with fdb_dump iterating
+ * over FDB.
+ */
+ struct mutex l2_lock;
struct mii_bus *user_mii_bus;
struct mii_bus *bus;
int mdio_addr;
@@ -107,6 +127,32 @@ struct realtek_ops {
int (*enable_vlan)(struct realtek_priv *priv, bool enable);
int (*enable_vlan4k)(struct realtek_priv *priv, bool enable);
int (*enable_port)(struct realtek_priv *priv, int port, bool enable);
+ int (*port_add_isolation)(struct realtek_priv *priv, int port,
+ u32 mask);
+ int (*port_remove_isolation)(struct realtek_priv *priv, int port,
+ u32 mask);
+ int (*port_set_efid)(struct realtek_priv *priv, int port, u32 efid);
+ int (*port_set_learning)(struct realtek_priv *priv, int port,
+ bool enable);
+ int (*port_set_ucast_flood)(struct realtek_priv *priv, int port,
+ bool enable);
+ int (*port_set_mcast_flood)(struct realtek_priv *priv, int port,
+ bool enable);
+ int (*port_set_bcast_flood)(struct realtek_priv *priv, int port,
+ bool enable);
+ int (*l2_add_uc)(struct realtek_priv *priv, int port,
+ const unsigned char addr[ETH_ALEN],
+ u16 efid, u16 vid);
+ int (*l2_del_uc)(struct realtek_priv *priv, int port,
+ const unsigned char addr[ETH_ALEN],
+ u16 efid, u16 vid);
+ int (*l2_get_next_uc)(struct realtek_priv *priv, u16 *addr,
+ int port, struct realtek_fdb_entry *entry);
+ int (*l2_add_mc)(struct realtek_priv *priv, int port,
+ const unsigned char addr[ETH_ALEN], u16 vid);
+ int (*l2_del_mc)(struct realtek_priv *priv, int port,
+ const unsigned char addr[ETH_ALEN], u16 vid);
+ int (*l2_flush)(struct realtek_priv *priv, int port, u16 vid);
int (*phy_read)(struct realtek_priv *priv, int phy, int regnum);
int (*phy_write)(struct realtek_priv *priv, int phy, int regnum,
u16 val);
diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.c b/drivers/net/dsa/realtek/rtl8365mb_l2.c
new file mode 100644
index 000000000000..0494d8ab2578
--- /dev/null
+++ b/drivers/net/dsa/realtek/rtl8365mb_l2.c
@@ -0,0 +1,576 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Forwarding and multicast database interface for the rtl8365mb switch family
+ *
+ * Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
+ */
+
+#include <linux/etherdevice.h>
+
+#include "rtl8365mb_l2.h"
+#include "rtl8365mb_table.h"
+#include <linux/regmap.h>
+
+#define RTL8365MB_L2_ENTRY_SIZE 6
+
+#define RTL8365MB_L2_UC_D0_MAC5_MSK GENMASK(7, 0)
+#define RTL8365MB_L2_UC_D0_MAC4_MSK GENMASK(15, 8)
+#define RTL8365MB_L2_UC_D1_MAC3_MSK GENMASK(7, 0)
+#define RTL8365MB_L2_UC_D1_MAC2_MSK GENMASK(15, 8)
+#define RTL8365MB_L2_UC_D2_MAC1_MSK GENMASK(7, 0)
+#define RTL8365MB_L2_UC_D2_MAC0_MSK GENMASK(15, 8)
+#define RTL8365MB_L2_UC_D3_VID_MSK GENMASK(11, 0)
+#define RTL8365MB_L2_UC_D3_IVL_MSK GENMASK(13, 13)
+#define RTL8365MB_L2_UC_D3_PORT_EXT_MSK GENMASK(15, 15)
+#define RTL8365MB_L2_UC_PORT_HI_MSK GENMASK(3, 3)
+#define RTL8365MB_L2_UC_D4_EFID_MSK GENMASK(2, 0)
+#define RTL8365MB_L2_UC_D4_FID_MSK GENMASK(6, 3)
+#define RTL8365MB_L2_UC_D4_SA_PRI_MSK GENMASK(7, 7)
+#define RTL8365MB_L2_UC_D4_PORT_MSK GENMASK(10, 8)
+#define RTL8365MB_L2_UC_PORT_LO_MSK GENMASK(2, 0)
+#define RTL8365MB_L2_UC_D4_AGE_MSK GENMASK(13, 11)
+#define RTL8365MB_L2_UC_D4_AUTH_MSK GENMASK(14, 14)
+#define RTL8365MB_L2_UC_D4_SA_BLOCK_MSK GENMASK(15, 15)
+
+#define RTL8365MB_L2_UC_D5_DA_BLOCK_MSK GENMASK(0, 0)
+#define RTL8365MB_L2_UC_D5_PRIORITY_MSK GENMASK(3, 1)
+#define RTL8365MB_L2_UC_D5_FWD_PRI_MSK GENMASK(4, 4)
+#define RTL8365MB_L2_UC_D5_STATIC_MSK GENMASK(5, 5)
+
+#define RTL8365MB_L2_MC_D0_MAC5_MSK GENMASK(7, 0)
+#define RTL8365MB_L2_MC_D0_MAC4_MSK GENMASK(15, 8)
+#define RTL8365MB_L2_MC_D1_MAC3_MSK GENMASK(7, 0)
+#define RTL8365MB_L2_MC_D1_MAC2_MSK GENMASK(15, 8)
+#define RTL8365MB_L2_MC_D2_MAC1_MSK GENMASK(7, 0)
+#define RTL8365MB_L2_MC_D2_MAC0_MSK GENMASK(15, 8)
+#define RTL8365MB_L2_MC_D3_VID_MSK GENMASK(11, 0)
+#define RTL8365MB_L2_MC_D3_IVL_MSK GENMASK(13, 13)
+#define RTL8365MB_L2_MC_D3_MBR_HI1_MSK GENMASK(15, 14)
+#define RTL8365MB_L2_MC_MBR_HI1_MSK GENMASK(9, 8)
+
+#define RTL8365MB_L2_MC_D4_MBR_MSK GENMASK(7, 0)
+#define RTL8365MB_L2_MC_MBR_LO_MSK GENMASK(7, 0)
+#define RTL8365MB_L2_MC_D4_IGMPIDX_MSK GENMASK(15, 8)
+
+#define RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK GENMASK(0, 0)
+#define RTL8365MB_L2_MC_D5_PRIORITY_MSK GENMASK(3, 1)
+#define RTL8365MB_L2_MC_D5_FWD_PRI_MSK GENMASK(4, 4)
+#define RTL8365MB_L2_MC_D5_STATIC_MSK GENMASK(5, 5)
+#define RTL8365MB_L2_MC_D5_MBR_HI2_MSK GENMASK(7, 7)
+#define RTL8365MB_L2_MC_MBR_HI2_MSK GENMASK(10, 10)
+
+/* Port flush command registers - writing a 1 to the port's MASK bit will
+ * initiate the flush procedure. Completion is signalled when the corresponding
+ * BUSY bit is 0.
+ */
+#define RTL8365MB_L2_FLUSH_PORT_REG 0x0A36
+#define RTL8365MB_L2_FLUSH_PORT_MSK_MSK GENMASK(7, 0)
+#define RTL8365MB_L2_FLUSH_PORT_BUSY_MSK GENMASK(15, 8)
+
+#define RTL8365MB_L2_FLUSH_PORT_EXT_REG 0x0A35
+#define RTL8365MB_L2_FLUSH_PORT_EXT_MSK_MSK GENMASK(2, 0)
+#define RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MSK GENMASK(5, 3)
+
+#define RTL8365MB_L2_FLUSH_CTRL1_REG 0x0A37
+#define RTL8365MB_L2_FLUSH_CTRL1_VID_MSK GENMASK(11, 0)
+#define RTL8365MB_L2_FLUSH_CTRL1_FID_MSK GENMASK(15, 12)
+
+#define RTL8365MB_L2_FLUSH_CTRL2_REG 0x0A38
+#define RTL8365MB_L2_FLUSH_CTRL2_MODE_MSK GENMASK(1, 0)
+#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT 0
+#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID 1
+#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_FID 2
+#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_MSK GENMASK(2, 2)
+#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC 0
+#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_BOTH 1
+
+/* This flushes the entire LUT, reading it back it will turn 0 when the
+ * operation is complete
+ */
+#define RTL8365MB_L2_FLUSH_CTRL3_REG 0x0A39
+#define RTL8365MB_L2_FLUSH_CTRL3_MSK GENMASK(0, 0)
+
+struct rtl8365mb_l2_uc_key {
+ u8 mac_addr[ETH_ALEN];
+ u16 vid;
+ u16 fid;
+ bool ivl;
+ u16 efid;
+};
+
+struct rtl8365mb_l2_uc {
+ struct rtl8365mb_l2_uc_key key;
+ u8 port;
+ u8 age;
+ u8 priority;
+
+ bool sa_block;
+ bool da_block;
+ bool auth;
+ bool is_static;
+ bool sa_pri;
+ bool fwd_pri;
+};
+
+struct rtl8365mb_l2_mc_key {
+ u8 mac_addr[ETH_ALEN];
+ union {
+ u16 vid; /* IVL */
+ u16 fid; /* SVL */
+ };
+ bool ivl;
+};
+
+struct rtl8365mb_l2_mc {
+ struct rtl8365mb_l2_mc_key key;
+ u16 member;
+ u8 priority;
+ u8 igmpidx;
+
+ bool is_static;
+ bool fwd_pri;
+ bool igmp_asic;
+};
+
+static void rtl8365mb_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc)
+{
+ u32 val;
+
+ uc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC5_MSK, data[0]);
+ uc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC4_MSK, data[0]);
+ uc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC3_MSK, data[1]);
+ uc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC2_MSK, data[1]);
+ uc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC1_MSK, data[2]);
+ uc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC0_MSK, data[2]);
+ uc->key.efid = FIELD_GET(RTL8365MB_L2_UC_D4_EFID_MSK, data[4]);
+ uc->key.vid = FIELD_GET(RTL8365MB_L2_UC_D3_VID_MSK, data[3]);
+ uc->key.ivl = FIELD_GET(RTL8365MB_L2_UC_D3_IVL_MSK, data[3]);
+ uc->key.fid = FIELD_GET(RTL8365MB_L2_UC_D4_FID_MSK, data[4]);
+ uc->age = FIELD_GET(RTL8365MB_L2_UC_D4_AGE_MSK, data[4]);
+ uc->auth = FIELD_GET(RTL8365MB_L2_UC_D4_AUTH_MSK, data[4]);
+
+ val = FIELD_GET(RTL8365MB_L2_UC_D4_PORT_MSK, data[4]);
+ uc->port = FIELD_PREP(RTL8365MB_L2_UC_PORT_LO_MSK, val);
+ val = FIELD_GET(RTL8365MB_L2_UC_D3_PORT_EXT_MSK, data[3]);
+ uc->port |= FIELD_PREP(RTL8365MB_L2_UC_PORT_HI_MSK, val);
+
+ uc->sa_pri = FIELD_GET(RTL8365MB_L2_UC_D4_SA_PRI_MSK, data[4]);
+ uc->fwd_pri = FIELD_GET(RTL8365MB_L2_UC_D5_FWD_PRI_MSK, data[5]);
+ uc->sa_block = FIELD_GET(RTL8365MB_L2_UC_D4_SA_BLOCK_MSK, data[4]);
+ uc->da_block = FIELD_GET(RTL8365MB_L2_UC_D5_DA_BLOCK_MSK, data[5]);
+ uc->priority = FIELD_GET(RTL8365MB_L2_UC_D5_PRIORITY_MSK, data[5]);
+ uc->is_static = FIELD_GET(RTL8365MB_L2_UC_D5_STATIC_MSK, data[5]);
+}
+
+static void rtl8365mb_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data)
+{
+ u32 val;
+
+ memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2);
+ data[0] |=
+ FIELD_PREP(RTL8365MB_L2_UC_D0_MAC5_MSK, uc->key.mac_addr[5]);
+ data[0] |=
+ FIELD_PREP(RTL8365MB_L2_UC_D0_MAC4_MSK, uc->key.mac_addr[4]);
+ data[1] |=
+ FIELD_PREP(RTL8365MB_L2_UC_D1_MAC3_MSK, uc->key.mac_addr[3]);
+ data[1] |=
+ FIELD_PREP(RTL8365MB_L2_UC_D1_MAC2_MSK, uc->key.mac_addr[2]);
+ data[2] |=
+ FIELD_PREP(RTL8365MB_L2_UC_D2_MAC1_MSK, uc->key.mac_addr[1]);
+ data[2] |=
+ FIELD_PREP(RTL8365MB_L2_UC_D2_MAC0_MSK, uc->key.mac_addr[0]);
+ data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_VID_MSK, uc->key.vid);
+ data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_IVL_MSK, uc->key.ivl);
+
+ val = FIELD_GET(RTL8365MB_L2_UC_PORT_HI_MSK, uc->port);
+ data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_PORT_EXT_MSK, val);
+
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_FID_MSK, uc->key.fid);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_EFID_MSK, uc->key.efid);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_AGE_MSK, uc->age);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_AUTH_MSK, uc->auth);
+
+ val = FIELD_GET(RTL8365MB_L2_UC_PORT_LO_MSK, uc->port);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_PORT_MSK, val);
+
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_SA_PRI_MSK, uc->sa_pri);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_SA_BLOCK_MSK, uc->sa_block);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_FWD_PRI_MSK, uc->fwd_pri);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_DA_BLOCK_MSK, uc->da_block);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_PRIORITY_MSK, uc->priority);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_STATIC_MSK, uc->is_static);
+}
+
+static void rtl8365mb_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc)
+{
+ u32 val;
+
+ mc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_MC_D0_MAC5_MSK, data[0]);
+ mc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_MC_D0_MAC4_MSK, data[0]);
+ mc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_MC_D1_MAC3_MSK, data[1]);
+ mc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_MC_D1_MAC2_MSK, data[1]);
+ mc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_MC_D2_MAC1_MSK, data[2]);
+ mc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_MC_D2_MAC0_MSK, data[2]);
+ /* key.vid,key.fid shares the same memory space */
+ mc->key.vid = FIELD_GET(RTL8365MB_L2_MC_D3_VID_MSK, data[3]);
+ mc->key.ivl = FIELD_GET(RTL8365MB_L2_MC_D3_IVL_MSK, data[3]);
+ mc->priority = FIELD_GET(RTL8365MB_L2_MC_D5_PRIORITY_MSK, data[5]);
+ mc->fwd_pri = FIELD_GET(RTL8365MB_L2_MC_D5_FWD_PRI_MSK, data[5]);
+ mc->is_static = FIELD_GET(RTL8365MB_L2_MC_D5_STATIC_MSK, data[5]);
+
+ val = FIELD_GET(RTL8365MB_L2_MC_D4_MBR_MSK, data[4]);
+ mc->member = FIELD_PREP(RTL8365MB_L2_MC_MBR_LO_MSK, val);
+ val = FIELD_GET(RTL8365MB_L2_MC_D3_MBR_HI1_MSK, data[3]);
+ mc->member |= FIELD_PREP(RTL8365MB_L2_MC_MBR_HI1_MSK, val);
+ val = FIELD_GET(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, data[5]);
+ mc->member |= FIELD_PREP(RTL8365MB_L2_MC_MBR_HI2_MSK, val);
+
+ mc->igmpidx = FIELD_GET(RTL8365MB_L2_MC_D4_IGMPIDX_MSK, data[4]);
+ mc->igmp_asic = FIELD_GET(RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK, data[5]);
+}
+
+static void rtl8365mb_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data)
+{
+ u32 val;
+
+ memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2);
+ data[0] |= FIELD_PREP(RTL8365MB_L2_MC_D0_MAC5_MSK, mc->key.mac_addr[5]);
+ data[0] |= FIELD_PREP(RTL8365MB_L2_MC_D0_MAC4_MSK, mc->key.mac_addr[4]);
+ data[1] |= FIELD_PREP(RTL8365MB_L2_MC_D1_MAC3_MSK, mc->key.mac_addr[3]);
+ data[1] |= FIELD_PREP(RTL8365MB_L2_MC_D1_MAC2_MSK, mc->key.mac_addr[2]);
+ data[2] |= FIELD_PREP(RTL8365MB_L2_MC_D2_MAC1_MSK, mc->key.mac_addr[1]);
+ data[2] |= FIELD_PREP(RTL8365MB_L2_MC_D2_MAC0_MSK, mc->key.mac_addr[0]);
+ data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_VID_MSK, mc->key.vid);
+ data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_IVL_MSK, mc->key.ivl);
+
+ val = FIELD_GET(RTL8365MB_L2_MC_MBR_HI1_MSK, mc->member);
+ data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_MBR_HI1_MSK, val);
+
+ val = FIELD_GET(RTL8365MB_L2_MC_MBR_LO_MSK, mc->member);
+ data[4] |= FIELD_PREP(RTL8365MB_L2_MC_D4_MBR_MSK, val);
+
+ data[4] |= FIELD_PREP(RTL8365MB_L2_MC_D4_IGMPIDX_MSK, mc->igmpidx);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK, mc->igmp_asic);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_PRIORITY_MSK, mc->priority);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_FWD_PRI_MSK, mc->fwd_pri);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_STATIC_MSK, mc->is_static);
+
+ val = FIELD_GET(RTL8365MB_L2_MC_MBR_HI2_MSK, mc->member);
+ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, val);
+}
+
+/*
+ * rtl8365mb_l2_get_next_uc() - get the next Unicast L2 entry
+ * @priv: realtek_priv pointer
+ * @addr: as input, the table index to start the walk
+ * as output, the found table index
+ * @port: restrict the walk on entries related to port
+ * @entry: returned L2 Unicast table entry
+ *
+ * This function gets the next unicast L2 table entry starting from @addr
+ * and checking exclusively entries related to @port.
+ *
+ * On success, it returns 0, updates @addr to the index of the found entry,
+ * and populates @entry. If the search reaches the end of the table and
+ * wraps around and @addr will be strictly lower than the input @addr.
+ * Callers must detect this wrap-around condition to prevent infinite loops.
+ *
+ * If the table contains no matching entries at all, it returns -ENOENT
+ * and leaves @addr and @entry unmodified.
+ *
+ * Return: Returns 0 on success, a negative error on failure.
+ **/
+int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port,
+ struct realtek_fdb_entry *entry)
+{
+ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ struct rtl8365mb_l2_uc uc;
+ int ret;
+
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_READ, addr,
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT,
+ port, data, RTL8365MB_L2_ENTRY_SIZE);
+ if (ret)
+ return ret;
+
+ rtl8365mb_l2_data_to_uc(data, &uc);
+
+ ether_addr_copy(entry->mac_addr, uc.key.mac_addr);
+ entry->vid = uc.key.vid;
+ entry->is_static = uc.is_static;
+
+ return 0;
+}
+
+int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,
+ const unsigned char mac_addr[static ETH_ALEN],
+ u16 efid, u16 vid)
+{
+ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ struct rtl8365mb_l2_uc uc = { 0 };
+ u16 addr;
+ int ret;
+
+ memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN);
+ uc.key.efid = efid;
+ uc.key.fid = 0;
+ uc.key.ivl = true;
+ uc.key.vid = vid;
+
+ uc.port = port;
+ /* Entries programmed by DSA (including those dynamically learned by
+ * the software bridge and injected into the CPU port via assisted
+ * learning) must be static. We do not let HW decrease age behind the
+ * OS's back. As a trade-off, these will show up as permanent to users.
+ */
+ uc.is_static = true;
+ /* age greater than 0 adds/updates entries */
+ uc.age = 1;
+ rtl8365mb_l2_uc_to_data(&uc, data);
+
+ /* add the new entry or update an existing one */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_WRITE, &addr,
+ 0, 0,
+ data, RTL8365MB_L2_ENTRY_SIZE);
+
+ /* Assume the missing new entry as the table is full */
+ if (ret == -ENOENT)
+ return -ENOSPC;
+
+ /* addr will hold the table index, but it is not used here */
+ return ret;
+}
+
+int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port,
+ const unsigned char mac_addr[static ETH_ALEN],
+ u16 efid, u16 vid)
+{
+ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ struct rtl8365mb_l2_uc uc = { 0 };
+ u16 addr;
+ int ret;
+
+ memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN);
+ uc.key.efid = efid;
+ uc.key.fid = 0;
+ uc.key.ivl = true;
+ uc.key.vid = vid;
+ /* age 0 deletes the entry */
+ uc.age = 0;
+ rtl8365mb_l2_uc_to_data(&uc, data);
+
+ /* it looks like the switch will always add/update the entry,
+ * even when age is 0 or uc.key did not match an existing entry,
+ * just to immediately drop it because age is zero. You can still
+ * get the added/updated address from @addr
+ */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_WRITE, &addr,
+ 0, 0,
+ data, RTL8365MB_L2_ENTRY_SIZE);
+
+ if (ret == -ENOENT) {
+ dev_dbg(priv->dev, "%s: %pM vid=%d efid=%d missing\n",
+ __func__, mac_addr, vid, efid);
+ /* Silently return success */
+ return 0;
+ }
+
+ /* addr will hold the table index, but it is not used here */
+ return ret;
+}
+
+int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid)
+{
+ int mode = vid ? RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID :
+ RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT;
+ u32 val, mask;
+ int ret;
+
+ mutex_lock(&priv->map_lock);
+
+ /* Configure flushing mode; only flush dynamic entries */
+ ret = regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL2_REG,
+ FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_MODE_MSK,
+ mode) |
+ FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_TYPE_MSK,
+ RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC));
+ if (ret)
+ goto out;
+
+ ret = regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL1_REG,
+ FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL1_VID_MSK, vid));
+
+ if (ret)
+ goto out;
+ /* Now issue the flush command and wait for its completion. There are
+ * two registers for this purpose, and which one to use depends on the
+ * port number. The _EXT register is for ports 8 or higher.
+ */
+ if (port < 8) {
+ val = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_MSK_MSK,
+ BIT(port) & 0xFF);
+ ret = regmap_write(priv->map_nolock,
+ RTL8365MB_L2_FLUSH_PORT_REG, val);
+ if (ret)
+ goto out;
+
+ mask = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_BUSY_MSK,
+ BIT(port) & 0xFF);
+ ret = regmap_read_poll_timeout(priv->map_nolock,
+ RTL8365MB_L2_FLUSH_PORT_REG,
+ val, !(val & mask), 10, 10000);
+ if (ret)
+ goto out;
+ } else {
+ val = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_MSK_MSK,
+ BIT(port) >> 8);
+ ret = regmap_write(priv->map_nolock,
+ RTL8365MB_L2_FLUSH_PORT_EXT_REG, val);
+ if (ret)
+ goto out;
+
+ mask = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MSK,
+ BIT(port) >> 8);
+ ret = regmap_read_poll_timeout(priv->map_nolock,
+ RTL8365MB_L2_FLUSH_PORT_EXT_REG,
+ val, !(val & mask), 10, 10000);
+ if (ret)
+ goto out;
+ }
+
+out:
+ mutex_unlock(&priv->map_lock);
+
+ return ret;
+}
+
+int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port,
+ const unsigned char mac_addr[static ETH_ALEN],
+ u16 vid)
+{
+ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ struct rtl8365mb_l2_mc mc = { 0 };
+ u16 addr;
+ int ret;
+
+ memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN);
+ mc.key.vid = vid;
+ mc.key.ivl = true;
+ /* Already set the port and is_static, although not used in OP_READ,
+ * data will be ready for OP_WRITE if it is a new entry.
+ */
+ mc.member |= BIT(port);
+ mc.is_static = 1;
+ rtl8365mb_l2_mc_to_data(&mc, data);
+
+ /* First look for an existing entry (to get existing port members) */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_READ, &addr,
+ RTL8365MB_TABLE_L2_METHOD_MAC, 0,
+ data, RTL8365MB_L2_ENTRY_SIZE);
+ if (!ret) {
+ /* There is already an entry... */
+ rtl8365mb_l2_data_to_mc(data, &mc);
+ dev_dbg(priv->dev,
+ "%s: found %pM addr=%d member=0x%x igmpidx=0x%x %s\n",
+ __func__, mac_addr, addr, mc.member, mc.igmpidx,
+ mc.is_static ? "static" : "dynamic");
+ /* the port must be added as a member */
+ mc.member |= BIT(port);
+
+ if (!mc.is_static) {
+ dev_dbg(priv->dev,
+ "%s: promoting addr=%d group to static\n",
+ __func__, addr);
+ mc.is_static = 1;
+ }
+
+ rtl8365mb_l2_mc_to_data(&mc, data);
+ } else if (ret == -ENOENT) {
+ /* New entry, no need to update data again as it already
+ * includes the member.
+ *
+ * Multicast hardware entries do not support EFID (bridge
+ * isolation). However, traffic isolation is still maintained
+ * because the hardware applies the port isolation masks
+ * (pmasks) configured in bridge_join after the L2 lookup.
+ * Entries from different bridges will collide on the same
+ * MAC+VID slot with an OR'ed member mask, but packets will
+ * only exit through ports allowed by the source port's pmask.
+ */
+ } else {
+ return ret;
+ }
+
+ /* add the new entry or update an existing one */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_WRITE, &addr,
+ 0, 0,
+ data, RTL8365MB_L2_ENTRY_SIZE);
+
+ /* Assume the missing new entry as the table is full */
+ if (ret == -ENOENT)
+ return -ENOSPC;
+
+ return ret;
+}
+
+int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port,
+ const unsigned char mac_addr[static ETH_ALEN],
+ u16 vid)
+{
+ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
+ struct rtl8365mb_l2_mc mc = { 0 };
+ u16 addr;
+ int ret;
+
+ memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN);
+ mc.key.vid = vid;
+ mc.key.ivl = true;
+ rtl8365mb_l2_mc_to_data(&mc, data);
+
+ /* First look for an existing entry (to get existing port members) */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_READ, &addr,
+ RTL8365MB_TABLE_L2_METHOD_MAC, 0,
+ data, RTL8365MB_L2_ENTRY_SIZE);
+ if (ret == -ENOENT) {
+ dev_dbg(priv->dev, "%s: %pM vid=%d missing\n",
+ __func__, mac_addr, vid);
+ /* Silently return success */
+ return 0;
+ }
+
+ if (ret)
+ /* Return on any other error */
+ return ret;
+
+ rtl8365mb_l2_data_to_mc(data, &mc);
+ dev_dbg(priv->dev,
+ "%s: found %pM addr=%d member=0x%x igmpidx=0x%x %s\n",
+ __func__, mac_addr, addr, mc.member, mc.igmpidx,
+ mc.is_static ? "static" : "dynamic");
+ /* the port must be removed as a member */
+ mc.member &= ~BIT(port);
+ if (!mc.member) {
+ /* Multicast entries do not have an age field. Clearing both
+ * the member portmask and is_static flags is the hardware
+ * signal to invalidate and reclaim the L2 table slot.
+ */
+ mc.is_static = 0;
+ mc.igmpidx = 0;
+ mc.priority = 0;
+ mc.fwd_pri = 0;
+ mc.igmp_asic = 0;
+ }
+ rtl8365mb_l2_mc_to_data(&mc, data);
+
+ /* update the existing entry. */
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
+ RTL8365MB_TABLE_OP_WRITE, &addr,
+ 0, 0,
+ data, RTL8365MB_L2_ENTRY_SIZE);
+ return ret;
+}
diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.h b/drivers/net/dsa/realtek/rtl8365mb_l2.h
new file mode 100644
index 000000000000..9470cf059ce5
--- /dev/null
+++ b/drivers/net/dsa/realtek/rtl8365mb_l2.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Forwarding and multicast database interface for the rtl8365mb switch family
+ *
+ * Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
+ */
+
+#ifndef _REALTEK_RTL8365MB_L2_H
+#define _REALTEK_RTL8365MB_L2_H
+
+#include <linux/if_ether.h>
+#include <linux/types.h>
+
+#include "realtek.h"
+
+int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port,
+ struct realtek_fdb_entry *entry);
+int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,
+ const unsigned char addr[static ETH_ALEN],
+ u16 efid, u16 vid);
+int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port,
+ const unsigned char addr[static ETH_ALEN],
+ u16 efid, u16 vid);
+int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid);
+
+int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port,
+ const unsigned char mac_addr[static ETH_ALEN],
+ u16 vid);
+int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port,
+ const unsigned char mac_addr[static ETH_ALEN],
+ u16 vid);
+
+#endif /* _REALTEK_RTL8365MB_L2_H */
diff --git a/drivers/net/dsa/realtek/rtl8365mb.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 0da048da533a..5ac091bf93c9 100644
--- a/drivers/net/dsa/realtek/rtl8365mb.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -104,14 +104,20 @@
#include "realtek-smi.h"
#include "realtek-mdio.h"
#include "rtl83xx.h"
+#include "rtl8365mb_l2.h"
+#include "rtl8365mb_vlan.h"
/* Family-specific data and limits */
#define RTL8365MB_PHYADDRMAX 7
#define RTL8365MB_NUM_PHYREGS 32
#define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1)
#define RTL8365MB_MAX_NUM_PORTS 11
-#define RTL8365MB_MAX_NUM_EXTINTS 3
+/* Valid for the whole family except RTL8370B, which has 4160 entries.
+ * RTL8370B is mentioned in vendor code but it might not even belong
+ * to the same RTL8367C family.
+ */
#define RTL8365MB_LEARN_LIMIT_MAX 2112
+#define RTL8365MB_MAX_NUM_EXTINTS 3
/* Chip identification registers */
#define RTL8365MB_CHIP_ID_REG 0x1300
@@ -284,6 +290,15 @@
(RTL8365MB_PORT_ISOLATION_REG_BASE + (_physport))
#define RTL8365MB_PORT_ISOLATION_MASK 0x07FF
+/* Extended filter ID registers - used to key forwarding database with IVL */
+#define RTL8365MB_EFID_MASK GENMASK(2, 0)
+#define RTL8365MB_PORT_EFID_REG_BASE 0x0A32
+#define RTL8365MB_PORT_EFID_REG(_p) \
+ (RTL8365MB_PORT_EFID_REG_BASE + ((_p) >> 2))
+#define RTL8365MB_PORT_EFID_OFFSET(_p) (((_p) & 0x3) << 2)
+#define RTL8365MB_PORT_EFID_MASK(_p) \
+ (RTL8365MB_EFID_MASK << RTL8365MB_PORT_EFID_OFFSET(_p))
+
/* MSTP port state registers - indexed by tree instance */
#define RTL8365MB_MSTI_CTRL_BASE 0x0A00
#define RTL8365MB_MSTI_CTRL_REG(_msti, _physport) \
@@ -292,6 +307,72 @@
#define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \
(0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport)))
+/* Unknown unicast DA flooding port mask */
+#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG 0x0890
+#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_MASK 0x07FF
+
+/* Unknown multicast DA flooding port mask */
+#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG 0x0891
+#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_MASK 0x07FF
+
+/* Broadcast flooding port mask */
+#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG 0x0892
+#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_MASK 0x07FF
+
+#define RTL8365MB_SUPPORTED_BRIDGE_FLAGS \
+ (BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD)
+
+/* Miscellaneous port configuration register, incl. VLAN egress mode */
+#define RTL8365MB_PORT_MISC_CFG_REG_BASE 0x000E
+#define RTL8365MB_PORT_MISC_CFG_REG(_p) \
+ (RTL8365MB_PORT_MISC_CFG_REG_BASE + ((_p) << 5))
+#define RTL8365MB_PORT_MISC_CFG_SMALL_TAG_IPG_MASK 0x8000
+#define RTL8365MB_PORT_MISC_CFG_TX_ITFSP_MODE_MASK 0x4000
+#define RTL8365MB_PORT_MISC_CFG_FLOWCTRL_INDEP_MASK 0x2000
+#define RTL8365MB_PORT_MISC_CFG_DOT1Q_REMARK_ENABLE_MASK 0x1000
+#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_FLOWCTRL_MASK 0x0800
+#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_IFG_MASK 0x0400
+#define RTL8365MB_PORT_MISC_CFG_RX_SPC_MASK 0x0200
+#define RTL8365MB_PORT_MISC_CFG_CRC_SKIP_MASK 0x0100
+#define RTL8365MB_PORT_MISC_CFG_PKTGEN_TX_FIRST_MASK 0x0080
+#define RTL8365MB_PORT_MISC_CFG_MAC_LOOPBACK_MASK 0x0040
+/* See &rtl8365mb_vlan_egress_mode */
+#define RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK 0x0030
+#define RTL8365MB_PORT_MISC_CFG_CONGESTION_SUSTAIN_TIME_MASK 0x000F
+
+/**
+ * enum rtl8365mb_vlan_egress_mode - port VLAN egress mode
+ * @RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL: follow untag mask in VLAN4k table entry
+ * @RTL8365MB_VLAN_EGRESS_MODE_KEEP: the VLAN tag format of egressed packets
+ * will remain the same as their ingressed format, but the priority and VID
+ * fields may be altered
+ * @RTL8365MB_VLAN_EGRESS_MODE_PRI_TAG: always egress with priority tag
+ * @RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP: the VLAN tag format of egressed
+ * packets will remain the same as their ingressed format, and neither the
+ * priority nor VID fields can be altered
+ */
+enum rtl8365mb_vlan_egress_mode {
+ RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL = 0,
+ RTL8365MB_VLAN_EGRESS_MODE_KEEP = 1,
+ RTL8365MB_VLAN_EGRESS_MODE_PRI_TAG = 2,
+ RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP = 3,
+};
+
+/* VLAN control register */
+#define RTL8365MB_VLAN_CTRL_REG 0x07A8
+#define RTL8365MB_VLAN_CTRL_EN_MASK 0x0001
+
+/* VLAN ingress filter register */
+#define RTL8365MB_VLAN_INGRESS_REG 0x07A9
+#define RTL8365MB_VLAN_INGRESS_MASK GENMASK(10, 0)
+#define RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_OFFSET(_p) (_p)
+#define RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(_p) BIT(_p)
+
+/* VLAN "transparent" setting registers */
+#define RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG_BASE 0x09D0
+#define RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG(_p) \
+ (RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG_BASE + (_p))
+
/* MIB counter value registers */
#define RTL8365MB_MIB_COUNTER_BASE 0x1000
#define RTL8365MB_MIB_COUNTER_REG(_x) (RTL8365MB_MIB_COUNTER_BASE + (_x))
@@ -803,8 +884,8 @@ static int rtl8365mb_phy_read(struct realtek_priv *priv, int phy, int regnum)
ret = rtl8365mb_phy_ocp_read(priv, phy, ocp_addr, &val);
if (ret) {
dev_err(priv->dev,
- "failed to read PHY%d reg %02x @ %04x, ret %d\n", phy,
- regnum, ocp_addr, ret);
+ "failed to read PHY%d reg %02x @ %04x, ret %pe\n", phy,
+ regnum, ocp_addr, ERR_PTR(ret));
return ret;
}
@@ -831,8 +912,8 @@ static int rtl8365mb_phy_write(struct realtek_priv *priv, int phy, int regnum,
ret = rtl8365mb_phy_ocp_write(priv, phy, ocp_addr, val);
if (ret) {
dev_err(priv->dev,
- "failed to write PHY%d reg %02x @ %04x, ret %d\n", phy,
- regnum, ocp_addr, ret);
+ "failed to write PHY%d reg %02x @ %04x, ret %pe\n", phy,
+ regnum, ocp_addr, ERR_PTR(ret));
return ret;
}
@@ -1082,8 +1163,8 @@ static void rtl8365mb_phylink_mac_config(struct phylink_config *config,
ret = rtl8365mb_ext_config_rgmii(priv, port, state->interface);
if (ret)
dev_err(priv->dev,
- "failed to configure RGMII mode on port %d: %d\n",
- port, ret);
+ "failed to configure RGMII mode on port %d: %pe\n",
+ port, ERR_PTR(ret));
return;
}
@@ -1112,8 +1193,8 @@ static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config,
false, false);
if (ret)
dev_err(priv->dev,
- "failed to reset forced mode on port %d: %d\n",
- port, ret);
+ "failed to reset forced mode on port %d: %pe\n",
+ port, ERR_PTR(ret));
return;
}
@@ -1143,8 +1224,8 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,
rx_pause);
if (ret)
dev_err(priv->dev,
- "failed to force mode on port %d: %d\n", port,
- ret);
+ "failed to force mode on port %d: %pe\n", port,
+ ERR_PTR(ret));
return;
}
@@ -1210,6 +1291,286 @@ static void rtl8365mb_port_stp_state_set(struct dsa_switch *ds, int port,
val << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(port));
}
+static int rtl8365mb_port_set_transparent(struct realtek_priv *priv,
+ int igr_port, int egr_port,