summaryrefslogtreecommitdiff
path: root/drivers/net/dsa/realtek
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/dsa/realtek')
-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)1361
-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/rtl8366rb-leds.c8
-rw-r--r--drivers/net/dsa/realtek/rtl8366rb.c270
-rw-r--r--drivers/net/dsa/realtek/rtl83xx.c575
-rw-r--r--drivers/net/dsa/realtek/rtl83xx.h27
13 files changed, 4004 insertions, 231 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 c35cef01ec26..728231d8f94c 100644
--- a/drivers/net/dsa/realtek/rtl8365mb.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -40,7 +40,8 @@
* driver has only been tested with a fixed-link, but in principle it should not
* matter.
*
- * NOTE: Currently, only the RGMII interface is implemented in this driver.
+ * NOTE: Currently, only the RGMII, SGMII and HSGMII interfaces are implemented
+ * in this driver.
*
* The interrupt line is asserted on link UP/DOWN events. The driver creates a
* custom irqchip to handle this interrupt and demultiplex the events by reading
@@ -94,24 +95,32 @@
#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
+#include <linux/mii.h>
#include <linux/mutex.h>
#include <linux/of_irq.h>
#include <linux/regmap.h>
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
+#include <linux/phylink.h>
#include "realtek.h"
#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
@@ -123,6 +132,7 @@
/* Chip reset register */
#define RTL8365MB_CHIP_RESET_REG 0x1322
+#define RTL8365MB_CHIP_RESET_DW8051_MASK 0x0010
#define RTL8365MB_CHIP_RESET_SW_MASK 0x0002
#define RTL8365MB_CHIP_RESET_HW_MASK 0x0001
@@ -232,6 +242,88 @@
#define RTL8365MB_EXT_RGMXF_RXDELAY_MASK 0x0007
#define RTL8365MB_EXT_RGMXF_TXDELAY_MASK 0x0008
+/* External interface line rate bypass register - one bit per external
+ * interface, indexed by the external port number with port 5 (the first
+ * external port) as the base. Other RTL8367 families index this register
+ * differently (e.g. the RTL8367R uses (id + 1) % 2), so this mapping only
+ * holds for the RTL8367C-style parts this driver supports.
+ */
+#define RTL8365MB_BYPASS_LINE_RATE_REG 0x03F7
+#define RTL8365MB_BYPASS_LINE_RATE_MASK(_port) BIT((_port) - 5)
+
+/* Port 6 ingress and egress rate limiter registers. Each limit is a 19-bit
+ * value in units of 8 Kbps, split across a 16-bit LSB register (CTRL0) and a
+ * 3-bit MSB field (CTRL1). The chip resets them to 0x1FFFF; see
+ * rtl8365mb_sds_raise_rate_limits().
+ */
+#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG 0x00CF
+#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG 0x00D0
+#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK 0x0007
+#define RTL8365MB_PORT6_EGRESSBW_CTRL0_REG 0x0398
+#define RTL8365MB_PORT6_EGRESSBW_CTRL1_REG 0x0399
+#define RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK 0x0007
+
+/* SerDes indirect access registers */
+#define RTL8365MB_SDS_INDACS_CMD_REG 0x6600
+#define RTL8365MB_SDS_INDACS_CMD_BUSY_MASK 0x0100
+#define RTL8365MB_SDS_INDACS_CMD_RUN_MASK 0x0080
+#define RTL8365MB_SDS_INDACS_CMD_WR_MASK 0x0040
+#define RTL8365MB_SDS_INDACS_ADR_REG 0x6601
+#define RTL8365MB_SDS_INDACS_DATA_REG 0x6602
+
+/* SerDes miscellaneous configuration register */
+#define RTL8365MB_SDS_MISC_REG 0x1D11
+#define RTL8365MB_SDS_MISC_SGMII_RXFC_MASK 0x4000
+#define RTL8365MB_SDS_MISC_SGMII_TXFC_MASK 0x2000
+#define RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK 0x0800
+#define RTL8365MB_SDS_MISC_SGMII_FDUP_MASK 0x0400
+#define RTL8365MB_SDS_MISC_SGMII_LINK_MASK 0x0200
+#define RTL8365MB_SDS_MISC_SGMII_SPD_MASK 0x0180
+#define RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK 0x0040
+
+/* SerDes internal registers, accessed via the SDS_INDACS registers. The BMCR
+ * data path reset holds BMCR_ANENABLE | BMCR_ISOLATE while toggling the
+ * vendor-specific low bits from phase 1 to phase 2, which triggers a data path
+ * reset and PLL resync.
+ */
+#define RTL8365MB_SDS_REG_BMCR 0x0000
+#define RTL8365MB_SDS_BMCR_DPRST_PHASE1 (BMCR_ANENABLE | BMCR_ISOLATE | 0x1)
+#define RTL8365MB_SDS_BMCR_DPRST_PHASE2 (BMCR_ANENABLE | BMCR_ISOLATE | 0x3)
+#define RTL8365MB_SDS_REG_NWAY 0x0002
+#define RTL8365MB_SDS_NWAY_EN_MASK 0x0200
+#define RTL8365MB_SDS_NWAY_RESTART_MASK 0x0100
+#define RTL8365MB_SDS_REG_RESET 0x0003
+#define RTL8365MB_SDS_RESET_DEASSERT 0x7106
+#define RTL8365MB_SDS_REG_LINK_STATUS 0x003d
+#define RTL8365MB_SDS_LINK_STATUS_LINK_MASK 0x0010
+
+/* The embedded SerDes can only be muxed to external interface 1 (MAC8),
+ * which is port 6.
+ */
+#define RTL8365MB_SDS_EXT_INTERFACE_ID 1
+#define RTL8365MB_SDS_EXT_INTERFACE_PORT 6
+
+/* Line rate bypass bit for the SerDes external interface */
+#define RTL8365MB_SDS_BYPASS_LINE_RATE_MASK \
+ RTL8365MB_BYPASS_LINE_RATE_MASK(RTL8365MB_SDS_EXT_INTERFACE_PORT)
+
+/* SerDes tuning parameter variant selector. The vendor driver picks between
+ * two sets of SerDes tuning parameters based on this chip option. Reading it
+ * requires first arming the read by writing a magic key to the arm register,
+ * then disarming it afterwards.
+ */
+#define RTL8365MB_SDS_OPTION_ARM_REG 0x13C0
+#define RTL8365MB_SDS_OPTION_ARM_KEY 0x0249
+#define RTL8365MB_SDS_OPTION_REG 0x13C1
+
+/* Embedded DW8051 microcontroller control registers. The microcontroller
+ * can run firmware to manage the SerDes link, but this driver keeps it in
+ * reset and disabled: phylink already performs the link management that
+ * the firmware would otherwise do.
+ */
+#define RTL8365MB_MISC_CFG0_REG 0x130C
+#define RTL8365MB_MISC_CFG0_DW8051_EN_MASK 0x0020
+
/* External interface port speed values - used in DIGITAL_INTERFACE_FORCE */
#define RTL8365MB_PORT_SPEED_10M 0
#define RTL8365MB_PORT_SPEED_100M 1
@@ -284,6 +376,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 +393,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))
@@ -470,6 +637,30 @@ static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_common[] = {
{ 0x1D32, 0x0002 },
};
+/* SGMII SerDes tuning parameters, lifted from the vendor driver sources. The
+ * vendor driver keeps two variants of this table and selects between them
+ * based on the chip option register; these are the values for a non-zero
+ * option, which is what RTL8367S parts seen so far report. See
+ * rtl8365mb_sds_probe_option().
+ */
+static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_sgmii[] = {
+ { 0x0480, 0x04D7 }, { 0x0481, 0xF994 }, { 0x0482, 0x2420 },
+ { 0x0483, 0x6960 }, { 0x0484, 0x9728 }, { 0x0423, 0x9D85 },
+ { 0x0424, 0xD810 }, { 0x002E, 0x83F2 },
+};
+
+/* HSGMII SerDes tuning parameters, lifted from the vendor driver sources. As
+ * with the SGMII table, the vendor driver keeps several variants and selects
+ * one based on the chip option register; these are the values for a non-zero
+ * option, which is what RTL8367S parts seen so far report. See
+ * rtl8365mb_sds_probe_option().
+ */
+static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_hsgmii[] = {
+ { 0x0500, 0x82F0 }, { 0x0501, 0xF195 }, { 0x0502, 0x31A2 },
+ { 0x0503, 0x7960 }, { 0x0504, 0x9728 }, { 0x0423, 0x9D85 },
+ { 0x0424, 0xD810 }, { 0x0001, 0x0F80 }, { 0x002E, 0x83F2 },
+};
+
enum rtl8365mb_phy_interface_mode {
RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0,
RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0),
@@ -545,6 +736,20 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = {
.jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),
},
{
+ .name = "RTL8367SB",
+ .chip_id = 0x6367,
+ .chip_ver = 0x0010,
+ .extints = {
+ { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) |
+ PHY_INTF(RMII) | PHY_INTF(RGMII) |
+ PHY_INTF(SGMII) | PHY_INTF(HSGMII) },
+ { 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) |
+ PHY_INTF(RMII) | PHY_INTF(RGMII) },
+ },
+ .jam_table = rtl8365mb_init_jam_8365mb_vc,
+ .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc),
+ },
+ {
.name = "RTL8367RB-VB",
.chip_id = 0x6367,
.chip_ver = 0x0020,
@@ -635,6 +840,9 @@ struct rtl8365mb_port {
* @cpu: CPU tagging and CPU port configuration for this chip
* @mib_lock: prevent concurrent reads of MIB counters
* @ports: per-port data
+ * @pcs: PCS for the SerDes external interface
+ * @sds_supported: SerDes tuning parameters match the chip option, so the
+ * SerDes interface modes can be advertised
*
* Private data for this driver.
*/
@@ -645,8 +853,12 @@ struct rtl8365mb {
struct rtl8365mb_cpu cpu;
struct mutex mib_lock;
struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS];
+ struct phylink_pcs pcs;
+ bool sds_supported;
};
+#define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs)
+
static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv)
{
u32 val;
@@ -789,8 +1001,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;
}
@@ -817,8 +1029,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;
}
@@ -947,6 +1159,391 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port,
return 0;
}
+static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data)
+{
+ int ret;
+
+ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, data);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr);
+ if (ret)
+ return ret;
+
+ /* The SerDes indirect access engine completes the command within the
+ * register write transaction, so there is no need to wait or poll for
+ * completion before the next access, matching the vendor driver.
+ */
+ return regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
+ RTL8365MB_SDS_INDACS_CMD_RUN_MASK |
+ RTL8365MB_SDS_INDACS_CMD_WR_MASK);
+}
+
+static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data)
+{
+ u32 val;
+ int ret;
+
+ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
+ RTL8365MB_SDS_INDACS_CMD_RUN_MASK);
+ if (ret)
+ return ret;
+
+ /* Wait for the indirect read to complete: the engine clears the BUSY
+ * bit once the data register holds the result.
+ */
+ ret = regmap_read_poll_timeout(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
+ val,
+ !(val & RTL8365MB_SDS_INDACS_CMD_BUSY_MASK),
+ 10, 1000);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, &val);
+ if (ret)
+ return ret;
+
+ *data = val;
+
+ return 0;
+}
+
+/* The vendor driver selects between two sets of SerDes tuning parameters based
+ * on the chip option register. Only the variant for a non-zero option has been
+ * tested on real hardware - the RTL8367S parts seen so far all report 1. The
+ * variant for option 0 uses different tuning values that cannot be verified,
+ * so probe the option once at setup and only advertise the SerDes interface
+ * modes when the tuning parameters are known to match, so that an unsupported
+ * variant fails at phylink validation time rather than when configuring the
+ * link.
+ */
+static int rtl8365mb_sds_probe_option(struct realtek_priv *priv)
+{
+ struct rtl8365mb *mb = priv->chip_data;
+ const struct rtl8365mb_extint *extint;
+ u32 option;
+ int ret;
+ int i;
+
+ /* Nothing to probe if no external interface is wired to the SerDes */
+ for (i = 0; i < RTL8365MB_MAX_NUM_EXTINTS; i++) {
+ extint = &mb->chip_info->extints[i];
+
+ if (extint->supported_interfaces &
+ (RTL8365MB_PHY_INTERFACE_MODE_SGMII |
+ RTL8365MB_PHY_INTERFACE_MODE_HSGMII))
+ break;
+ }
+ if (i == RTL8365MB_MAX_NUM_EXTINTS)
+ return 0;
+
+ ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG,
+ RTL8365MB_SDS_OPTION_ARM_KEY);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(priv->map, RTL8365MB_SDS_OPTION_REG, &option);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG, 0);
+ if (ret)
+ return ret;
+
+ if (option == 0) {
+ dev_warn(priv->dev,
+ "unsupported SerDes tuning variant (chip option 0), disabling SerDes interface modes\n");
+ return 0;
+ }
+
+ mb->sds_supported = true;
+
+ return 0;
+}
+
+/* The vendor driver raises the port 6 ingress and egress rate limiters to
+ * their maximum in its switch init, unconditionally for the whole chip
+ * family. The chip reset in rtl8365mb_setup() puts them back to their reset
+ * default of 0x1FFFF, a ~1.048 Gbps limit which caps the aggregate
+ * throughput of an HSGMII CPU port at roughly 1 Gbps. The vendor
+ * documentation describes the reset default as disabling the limiter, but
+ * the cap has been observed on hardware. Raise them likewise, to 0x7FFFF
+ * (~4.19 Gbps, above the HSGMII line rate). The related HSGMII scheduler
+ * line rate register (LINE_RATE_HSG_H, 0x03FA) is already set to its
+ * maximum by the common init jam table.
+ */
+static int rtl8365mb_sds_raise_rate_limits(struct realtek_priv *priv)
+{
+ int ret;
+
+ ret = regmap_write(priv->map, RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG,
+ 0xFFFF);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(priv->map,
+ RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG,
+ RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK,
+ RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->map, RTL8365MB_PORT6_EGRESSBW_CTRL0_REG,
+ 0xFFFF);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(priv->map, RTL8365MB_PORT6_EGRESSBW_CTRL1_REG,
+ RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK,
+ RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK);
+}
+
+static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+{
+ const struct rtl8365mb_jam_tbl_entry *sds_jam;
+ const int id = RTL8365MB_SDS_EXT_INTERFACE_ID;
+ struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
+ struct realtek_priv *priv;
+ size_t sds_jam_size;
+ u32 mode;
+ u16 val;
+ int ret;
+ int i;
+
+ priv = mb->priv;
+
+ if (interface == PHY_INTERFACE_MODE_2500BASEX) {
+ sds_jam = rtl8365mb_sds_jam_hsgmii;
+ sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii);
+ mode = RTL8365MB_EXT_PORT_MODE_HSGMII;
+ } else {
+ sds_jam = rtl8365mb_sds_jam_sgmii;
+ sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii);
+ mode = RTL8365MB_EXT_PORT_MODE_SGMII;
+ }
+
+ /* Hold the embedded DW8051 microcontroller in reset and keep it
+ * disabled. The vendor driver loads firmware into it to manage the
+ * SerDes link, but the firmware only duplicates work that phylink
+ * already does: it polls the port status and forces the external
+ * interface configuration in the very registers this driver manages.
+ * Letting it run would race with phylink.
+ */
+ ret = regmap_update_bits(priv->map, RTL8365MB_CHIP_RESET_REG,
+ RTL8365MB_CHIP_RESET_DW8051_MASK,
+ RTL8365MB_CHIP_RESET_DW8051_MASK);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(priv->map, RTL8365MB_MISC_CFG0_REG,
+ RTL8365MB_MISC_CFG0_DW8051_EN_MASK, 0);
+ if (ret)
+ return ret;
+
+ /* The vendor driver clears the line rate bypass for all interface
+ * modes except TMII.
+ */
+ ret = regmap_update_bits(priv->map, RTL8365MB_BYPASS_LINE_RATE_REG,
+ RTL8365MB_SDS_BYPASS_LINE_RATE_MASK, 0);
+ if (ret)
+ return ret;
+
+ /* Tune the SerDes with vendor-prescribed parameters */
+ for (i = 0; i < sds_jam_size; i++) {
+ ret = rtl8365mb_sds_write(priv, sds_jam[i].reg,
+ sds_jam[i].val);
+ if (ret)
+ return ret;
+ }
+
+ /* Mux the SerDes to MAC8 in the requested mode */
+ ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
+ RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK |
+ RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK,
+ mode == RTL8365MB_EXT_PORT_MODE_SGMII ?
+ RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK :
+ RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK);
+ if (ret)
+ return ret;
+
+ val = mode << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id);
+ ret = regmap_update_bits(priv->map,
+ RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id),
+ RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id),
+ val);
+ if (ret)
+ return ret;
+
+ /* Take the SerDes out of reset. The vendor driver does this only
+ * after the SerDes mux and the interface mode are configured.
+ */
+ ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_RESET,
+ RTL8365MB_SDS_RESET_DEASSERT);
+ if (ret)
+ return ret;
+
+ /* Reset the SerDes data path and resync its PLL, mirroring what the
+ * vendor firmware does right after deasserting the SerDes reset.
+ * This flushes the FIFOs and ensures a clean state for the link,
+ * preventing silent drops and CRC errors.
+ */
+ ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR,
+ RTL8365MB_SDS_BMCR_DPRST_PHASE1);
+ if (ret)
+ return ret;
+
+ ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR,
+ RTL8365MB_SDS_BMCR_DPRST_PHASE2);
+ if (ret)
+ return ret;
+
+ /* Keep SGMII in-band autonegotiation disabled: the link parameters are
+ * forced from rtl8365mb_pcs_link_up() instead.
+ */
+ ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_NWAY, &val);
+ if (ret)
+ return ret;
+
+ val &= ~RTL8365MB_SDS_NWAY_EN_MASK;
+ val |= RTL8365MB_SDS_NWAY_RESTART_MASK;
+
+ return rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_NWAY, val);
+}
+
+static bool rtl8365mb_interface_is_serdes(phy_interface_t interface)
+{
+ return interface == PHY_INTERFACE_MODE_SGMII ||
+ interface == PHY_INTERFACE_MODE_2500BASEX;
+}
+
+static unsigned int rtl8365mb_pcs_inband_caps(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ /* In-band autonegotiation is not implemented; the link is always
+ * forced. Report that to phylink so that it never selects an
+ * in-band-enabled negotiation mode for this PCS.
+ */
+ return LINK_INBAND_DISABLE;
+}
+
+static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ struct phylink_link_state *state)
+{
+ struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
+ struct realtek_priv *priv = mb->priv;
+ u16 status;
+ u32 val;
+ int ret;
+
+ /* In-band autonegotiation is not implemented, so the link parameters are
+ * forced from rtl8365mb_pcs_link_up(). The real link state must still be
+ * read from the SerDes itself: the embedded DW8051 microcontroller that
+ * the vendor firmware uses to poll the SerDes is kept disabled (see
+ * rtl8365mb_pcs_config()), so the link status register can be read
+ * directly through the SDS_INDACS window without racing the auto-poll.
+ */
+ ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_LINK_STATUS, &status);
+ if (ret) {
+ state->link = false;
+ return;
+ }
+
+ state->link = !!(status & RTL8365MB_SDS_LINK_STATUS_LINK_MASK);
+ state->an_complete = state->link;
+ if (!state->link)
+ return;
+
+ /* The speed and duplex are forced; read them back from the values
+ * programmed into the SerDes MISC register.
+ */
+ ret = regmap_read(priv->map, RTL8365MB_SDS_MISC_REG, &val);
+ if (ret) {
+ state->link = false;
+ return;
+ }
+
+ state->duplex = (val & RTL8365MB_SDS_MISC_SGMII_FDUP_MASK) ?
+ DUPLEX_FULL : DUPLEX_HALF;
+
+ switch (FIELD_GET(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, val)) {
+ case RTL8365MB_PORT_SPEED_1000M:
+ state->speed =
+ state->interface == PHY_INTERFACE_MODE_2500BASEX ?
+ SPEED_2500 : SPEED_1000;
+ break;
+ case RTL8365MB_PORT_SPEED_100M:
+ state->speed = SPEED_100;
+ break;
+ case RTL8365MB_PORT_SPEED_10M:
+ state->speed = SPEED_10;
+ break;
+ }
+}
+
+static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ phy_interface_t interface, int speed,
+ int duplex)
+{
+ struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
+ struct realtek_priv *priv = mb->priv;
+ u32 mask = RTL8365MB_SDS_MISC_SGMII_FDUP_MASK |
+ RTL8365MB_SDS_MISC_SGMII_LINK_MASK |
+ RTL8365MB_SDS_MISC_SGMII_SPD_MASK;
+ u32 val = RTL8365MB_SDS_MISC_SGMII_LINK_MASK;
+ u32 r_speed;
+ int ret;
+
+ /* The speed field has no value for 2.5 Gbps: the rate is determined by
+ * the HSGMII SerDes configuration, and the vendor driver programs the
+ * 1 Gbps value here.
+ */
+ if (speed == SPEED_2500 || speed == SPEED_1000) {
+ r_speed = RTL8365MB_PORT_SPEED_1000M;
+ } else if (speed == SPEED_100) {
+ r_speed = RTL8365MB_PORT_SPEED_100M;
+ } else if (speed == SPEED_10) {
+ r_speed = RTL8365MB_PORT_SPEED_10M;
+ } else {
+ dev_err(priv->dev, "unsupported SerDes speed %s\n",
+ phy_speed_to_str(speed));
+ return;
+ }
+
+ val |= FIELD_PREP(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, r_speed);
+
+ if (duplex == DUPLEX_FULL)
+ val |= RTL8365MB_SDS_MISC_SGMII_FDUP_MASK;
+
+ /* pcs_link_up() carries no pause information, so the SerDes flow
+ * control bits are programmed together with the MAC external interface
+ * force from rtl8365mb_phylink_mac_link_up(), where the resolved pause
+ * modes are known.
+ */
+ ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, mask, val);
+ if (ret) {
+ dev_err(priv->dev, "failed to force SerDes link: %pe\n",
+ ERR_PTR(ret));
+ return;
+ }
+}
+
+static const struct phylink_pcs_ops rtl8365mb_pcs_ops = {
+ .pcs_inband_caps = rtl8365mb_pcs_inband_caps,
+ .pcs_config = rtl8365mb_pcs_config,
+ .pcs_get_state = rtl8365mb_pcs_get_state,
+ .pcs_link_up = rtl8365mb_pcs_link_up,
+};
+
static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
bool link, int speed, int duplex,
bool tx_pause, bool rx_pause)
@@ -970,7 +1567,11 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
r_rx_pause = rx_pause ? 1 : 0;
r_tx_pause = tx_pause ? 1 : 0;
- if (speed == SPEED_1000) {
+ /* The speed field has no value for 2.5 Gbps: the rate is
+ * determined by the HSGMII SerDes configuration, and the
+ * vendor driver programs the 1 Gbps value here.
+ */
+ if (speed == SPEED_2500 || speed == SPEED_1000) {
r_speed = RTL8365MB_PORT_SPEED_1000M;
} else if (speed == SPEED_100) {
r_speed = RTL8365MB_PORT_SPEED_100M;
@@ -1023,6 +1624,8 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
{
const struct rtl8365mb_extint *extint =
rtl8365mb_get_port_extint(ds->priv, port);
+ struct realtek_priv *priv = ds->priv;
+ struct rtl8365mb *mb = priv->chip_data;
config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE |
MAC_10 | MAC_100 | MAC_1000FD;
@@ -1046,6 +1649,32 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII)
phy_interface_set_rgmii(config->supported_interfaces);
+
+ if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_SGMII &&
+ mb->sds_supported)
+ __set_bit(PHY_INTERFACE_MODE_SGMII,
+ config->supported_interfaces);
+
+ if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_HSGMII &&
+ mb->sds_supported) {
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+ config->supported_interfaces);
+ config->mac_capabilities |= MAC_2500FD;
+ }
+}
+
+static struct phylink_pcs *
+rtl8365mb_phylink_mac_select_pcs(struct phylink_config *config,
+ phy_interface_t interface)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct realtek_priv *priv = dp->ds->priv;
+ struct rtl8365mb *mb = priv->chip_data;
+
+ if (rtl8365mb_interface_is_serdes(interface))
+ return &mb->pcs;
+
+ return NULL;
}
static void rtl8365mb_phylink_mac_config(struct phylink_config *config,
@@ -1068,11 +1697,17 @@ 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;
}
+ /* SGMII and 2500base-x are handled by the SerDes PCS, configured
+ * through the phylink_pcs ops, so nothing to do here for them.
+ */
+ if (rtl8365mb_interface_is_serdes(state->interface))
+ return;
+
/* TODO: Implement MII and RMII modes, which the RTL8365MB-VC also
* supports
*/
@@ -1093,13 +1728,19 @@ static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config,
p = &mb->ports[port];
cancel_delayed_work_sync(&p->mib_work);
- if (phy_interface_mode_is_rgmii(interface)) {
+ /* phylink has no pcs_link_down callback, so on the SerDes path only the
+ * MAC external interface force is reset here. Clearing the MAC force is
+ * enough to bring the link down; the SerDes keeps presenting its last
+ * forced state until the next pcs_link_up() reprograms it.
+ */
+ if (phy_interface_mode_is_rgmii(interface) ||
+ rtl8365mb_interface_is_serdes(interface)) {
ret = rtl8365mb_ext_config_forcemode(priv, port, false, 0, 0,
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;
}
@@ -1123,14 +1764,51 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,
p = &mb->ports[port];
schedule_delayed_work(&p->mib_work, 0);
- if (phy_interface_mode_is_rgmii(interface)) {
+ /* The SerDes forced link state is programmed by the PCS in
+ * rtl8365mb_pcs_link_up(); here only the MAC external interface force
+ * is configured, for both RGMII and SerDes.
+ */
+ if (phy_interface_mode_is_rgmii(interface) ||
+ rtl8365mb_interface_is_serdes(interface)) {
ret = rtl8365mb_ext_config_forcemode(priv, port, true, speed,
duplex, tx_pause,
rx_pause);
- if (ret)
+ 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;
+ }
+
+ /* The SerDes has its own pause enables; program them from
+ * the resolved pause modes, as the vendor driver does when
+ * forcing the link on a SerDes external interface. These
+ * bits, not the MAC force pause bits, gate pause on the
+ * SerDes external interface: flow control testing shows
+ * that pause frames are only emitted with the SerDes TXFC
+ * bit set, while the MAC force pause bits alone have no
+ * effect on this port. This is done here rather than in
+ * rtl8365mb_pcs_link_up() because pcs_link_up() carries no
+ * pause information.
+ */
+ if (rtl8365mb_interface_is_serdes(interface)) {
+ u32 val = 0;
+
+ if (tx_pause)
+ val |= RTL8365MB_SDS_MISC_SGMII_TXFC_MASK;
+ if (rx_pause)
+ val |= RTL8365MB_SDS_MISC_SGMII_RXFC_MASK;
+
+ ret = regmap_update_bits(priv->map,
+ RTL8365MB_SDS_MISC_REG,
+ RTL8365MB_SDS_MISC_SGMII_TXFC_MASK |
+ RTL8365MB_SDS_MISC_SGMII_RXFC_MASK,
+ val);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to force SerDes pause modes on port %d: %pe\n",
+ port, ERR_PTR(ret));
+ }
return;
}
@@ -1196,6 +1874,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,
+ bool enable)
+{
+ dev_dbg(priv->dev, "%s transparent VLAN from %d to %d\n",
+ enable ? "Enable" : "Disable", igr_port, egr_port);
+
+ /* "Transparent" between the two ports means that packets forwarded by
+ * igr_port and egressed on egr_port will not be filtered by the usual
+ * VLAN membership settings.
+ */
+ return regmap_update_bits(priv->map,
+ RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG(egr_port),
+ BIT(igr_port), enable ? BIT(igr_port) : 0);
+}
+
+static int rtl8365mb_port_set_ingress_filtering(struct realtek_priv *priv,
+ int port, bool enable)
+{
+ /* Ingress filtering enabled: Discard VLAN-tagged frames if the port is
+ * not a member of the VLAN with which the packet is associated.
+ * Untagged packets will also be discarded unless the port has a PVID
+ * programmed. Priority-tagged frames are treated as untagged frames.
+ *
+ * Ingress filtering disabled: Accept all tagged and untagged frames.
+ */
+ return regmap_update_bits(priv->map, RTL8365MB_VLAN_INGRESS_REG,
+ RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(port),
+ enable ?
+ RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(port) :
+ 0);
+}
+
+static int
+rtl8365mb_port_set_vlan_egress_mode(struct realtek_priv *priv, int port,
+ enum rtl8365mb_vlan_egress_mode mode)
+{
+ u32 val;
+
+ val = FIELD_PREP(RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK, mode);
+ return regmap_update_bits(priv->map,
+ RTL8365MB_PORT_MISC_CFG_REG(port),
+ RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK, val);
+}
+
+static int rtl8365mb_port_vlan_filtering(struct dsa_switch *ds, int port,
+ bool vlan_filtering,
+ struct netlink_ext_ack *extack)
+{
+ enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame;
+ enum rtl8365mb_vlan_egress_mode mode;
+ struct realtek_priv *priv = ds->priv;
+ u32 configured_ports = 0;
+ struct dsa_port *dp;
+ u16 pvid_vid;
+ int ret;
+
+ dev_dbg(priv->dev, "port %d: %s VLAN filtering\n", port,
+ vlan_filtering ? "enable" : "disable");
+
+ ret = rtl8365mb_vlan_port_get_framefilter(priv, port,
+ &prev_accepted_frame);
+ if (ret) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Failed to get current framefilter");
+ return ret;
+ }
+
+ /* While filtering, only accepts untagged frames if PVID is enabled */
+ if (vlan_filtering) {
+ ret = rtl8365mb_vlan_port_get_pvid(priv, port, &pvid_vid);
+ if (ret)
+ return ret;
+
+ if (pvid_vid)
+ accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME;
+ else
+ accepted_frame = RTL8365MB_FRAME_TYPE_TAGGED_ONLY;
+ } else {
+ accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME;
+ }
+
+ /* When vlan filter is enable/disabled in a bridge, this function is
+ * called for all member ports. We need to enable/disable ingress
+ * VLAN membership check.
+ */
+ ret = rtl8365mb_port_set_ingress_filtering(priv, port, vlan_filtering);
+ if (ret)
+ return ret;
+
+ /* However, we also enable/disable egress filtering because the switch
+ * still consider the egress interface VLAN membership to forward the
+ * traffic. We enable/disable that check disabling/enabling transparent
+ * VLAN between the ingress port and all other available ports.
+ */
+ dsa_switch_for_each_available_port(dp, ds) {
+ /* port isolation will still keep traffic inside the bridge */
+ ret = rtl8365mb_port_set_transparent(priv, port, dp->index,
+ !vlan_filtering);
+ if (ret)
+ goto undo_transparent;
+
+ configured_ports |= BIT(dp->index);
+ }
+
+ if (accepted_frame != prev_accepted_frame) {
+ ret = rtl8365mb_vlan_port_set_framefilter(priv, port,
+ accepted_frame);
+ if (ret) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Failed to set port framefilter");
+ goto undo_transparent;
+ }
+ }
+
+ /* When VLAN filtering is disabled, preserve frames exactly as received.
+ * Otherwise, the VLAN egress pipeline may still alter tag state
+ * according to VLAN membership and untag configuration.
+ */
+ if (vlan_filtering)
+ mode = RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL;
+ else
+ mode = RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP;
+
+ ret = rtl8365mb_port_set_vlan_egress_mode(priv, port, mode);
+ if (ret)
+ goto undo_set_framefilter;
+
+ return ret;
+
+undo_set_framefilter:
+ if (prev_accepted_frame != accepted_frame)
+ rtl8365mb_vlan_port_set_framefilter(priv, port,
+ prev_accepted_frame);
+undo_transparent:
+ /* The DSA core guarantees this callback is only invoked on an actual
+ * state transition, ensuring the previous hardware state was the
+ * opposite (!vlan_filtering). It is also called during setup but, in
+ * that case, any failure here aborts the entire switch initialization.
+ *
+ * VLAN_INGRESS and VLAN_EGRESS_TRANSPARENT states are directly derived
+ * from vlan_filtering. That way, we can simply undo it without
+ * checking the current HW state as we do with VLAN_EGRESS_MODE.
+ */
+ dsa_switch_for_each_port(dp, ds) {
+ if (configured_ports & BIT(dp->index))
+ rtl8365mb_port_set_transparent(priv, port, dp->index,
+ vlan_filtering);
+ }
+
+ rtl8365mb_port_set_ingress_filtering(priv, port, !vlan_filtering);
+
+ return ret;
+}
+
+static int rtl8365mb_port_vlan_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack)
+{
+ bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
+ bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID);
+ u16 pvid_vid;
+ struct realtek_priv *priv = ds->priv;
+ int ret;
+
+ dev_dbg(priv->dev, "add VLAN %d on port %d, %s, %s\n",
+ vlan->vid, port, untagged ? "untagged" : "tagged",
+ pvid ? "PVID" : "no PVID");
+
+ /* VID == 0 is reserved in this driver */
+ if (vlan->vid == 0) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "VLAN 0 is reserved by this driver");
+ return -EOPNOTSUPP;
+ }
+
+ mutex_lock(&priv->vlan_lock);
+
+ ret = rtl8365mb_vlan_port_get_pvid(priv, port, &pvid_vid);
+ if (ret)
+ goto out_unlock;
+
+ /* Set PVID if needed */
+ if (pvid) {
+ ret = rtl8365mb_vlan_pvid_port_set(ds, port, vlan->vid,
+ extack);
+ if (ret)
+ goto out_unlock;
+ } else {
+ /* or try to unset it if not */
+ ret = rtl8365mb_vlan_pvid_port_clear(ds, port, vlan->vid);
+ if (ret)
+ goto out_unlock;
+ }
+
+ /* add port to vlan4k. It knows nothing about PVID */
+ ret = rtl8365mb_vlan_4k_port_add(ds, port, vlan, extack);
+ if (ret)
+ goto undo_set_pvid;
+
+ ret = 0;
+ goto out_unlock;
+
+undo_set_pvid:
+ /* undo the pvid definition */
+ if (pvid != (pvid_vid == vlan->vid)) {
+ if (pvid_vid)
+ (void)rtl8365mb_vlan_pvid_port_set(ds, port, pvid_vid,
+ NULL);
+ else
+ (void)rtl8365mb_vlan_pvid_port_clear(ds, port,
+ vlan->vid);
+ }
+out_unlock:
+ mutex_unlock(&priv->vlan_lock);
+ return ret;
+}
+
+static int rtl8365mb_port_vlan_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan)
+{
+ bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
+ bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID);
+ struct realtek_priv *priv = ds->priv;
+ int ret;
+
+ dev_dbg(priv->dev, "del VLAN %d on port %d, %s, %s\n",
+ vlan->vid, port, untagged ? "untagged" : "tagged",
+ pvid ? "PVID" : "no PVID");
+
+ /* VID == 0 is reserved in this driver */
+ if (vlan->vid == 0)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&priv->vlan_lock);
+ ret = rtl8365mb_vlan_pvid_port_clear(ds, port, vlan->vid);
+ if (ret)
+ goto out_unlock;
+
+ ret = rtl8365mb_vlan_4k_port_del(ds, port, vlan);
+ /* There is little incentive to try to undo the removal of PVID (if it
+ * was really in use) as an error here might indicate the ASIC stopped
+ * to answer.
+ */
+
+out_unlock:
+ mutex_unlock(&priv->vlan_lock);
+ return ret;
+}
+
+/* VLAN support is always enabled in the switch.
+ *
+ * Standalone forwarding relies on transparent VLAN mode combined with per-port
+ * isolation masks restricting egress to CPU ports only.
+ *
+ */
+static int rtl8365mb_vlan_setup(struct dsa_switch *ds)
+{
+ struct realtek_priv *priv = ds->priv;
+ struct dsa_port *dp;
+ int ret;
+
+ dsa_switch_for_each_available_port(dp, ds) {
+ /* Disable vlan-filtering for all ports */
+ ret = rtl8365mb_port_vlan_filtering(ds, dp->index, false, NULL);
+ if (ret) {
+ dev_err(priv->dev,
+ "Failed to disable vlan filtering on port %d\n",
+ dp->index);
+ return ret;
+ }
+ }
+
+ /* VLAN is always enabled. */
+ ret = regmap_update_bits(priv->map, RTL8365MB_VLAN_CTRL_REG,
+ RTL8365MB_VLAN_CTRL_EN_MASK,
+ FIELD_PREP(RTL8365MB_VLAN_CTRL_EN_MASK, 1));
+ return ret;
+}
+
static int rtl8365mb_port_set_learning(struct realtek_priv *priv, int port,
bool enable)
{
@@ -1208,10 +2166,87 @@ static int rtl8365mb_port_set_learning(struct realtek_priv *priv, int port,
enable ? RTL8365MB_LEARN_LIMIT_MAX : 0);
}
+static int rtl8365mb_port_set_ucast_flood(struct realtek_priv *priv, int port,
+ bool enable)
+{
+ /* Frames with unknown unicast DA will be flooded to a programmable
+ * port mask that by default includes all ports. Add or remove
+ * the specified port from this port mask accordingly.
+ */
+ return regmap_update_bits(priv->map,
+ RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG,
+ BIT(port), enable ? BIT(port) : 0);
+}
+
+static int rtl8365mb_port_set_mcast_flood(struct realtek_priv *priv, int port,
+ bool enable)
+{
+ return regmap_update_bits(priv->map,
+ RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG,
+ BIT(port), enable ? BIT(port) : 0);
+}
+
+static int rtl8365mb_port_set_bcast_flood(struct realtek_priv *priv, int port,
+ bool enable)
+{
+ return regmap_update_bits(priv->map,
+ RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG,
+ BIT(port), enable ? BIT(port) : 0);
+}
+
+static int rtl8365mb_port_pre_bridge_flags(struct dsa_switch *ds, int port,
+ struct switchdev_brport_flags flags,
+ struct netlink_ext_ack *extack)
+{
+ struct realtek_priv *priv = ds->priv;
+
+ dev_dbg(priv->dev, "pre_bridge_flags port:%d flags:%lx supported:%lx\n",
+ port, flags.mask, RTL8365MB_SUPPORTED_BRIDGE_FLAGS);
+
+ if (flags.mask & ~RTL8365MB_SUPPORTED_BRIDGE_FLAGS)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port,
+ u32 efid)
+{
+ return regmap_update_bits(priv->map, RTL8365MB_PORT_EFID_REG(port),
+ RTL8365MB_PORT_EFID_MASK(port),
+ efid << RTL8365MB_PORT_EFID_OFFSET(port));
+}
+
+/* Port isolation manipulation functions.
+ *
+ * The port isolation register controls the forwarding mask of a given
+ * port. The switch will not forward packets ingressed on a given port
+ * to ports which are not enabled in its forwarding mask.
+ *
+ * The port forwarding mask has the highest priority in forwarding
+ * decisions. The only exception to this rule is when the switch
+ * receives a packet on its CPU port with ALLOW=0. In that case the TX
+ * field of the CPU tag will override the forwarding port mask.
+ */
static int rtl8365mb_port_set_isolation(struct realtek_priv *priv, int port,
u32 mask)
{
- return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), mask);
+ return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port),
+ mask);
+}
+
+static int rtl8365mb_port_add_isolation(struct realtek_priv *priv, int port,
+ u32 mask)
+{
+ return regmap_update_bits(priv->map, RTL8365MB_PORT_ISOLATION_REG(port),
+ mask, mask);
+}
+
+static int rtl8365mb_port_remove_isolation(struct realtek_priv *priv, int port,
+ u32 mask)
+{
+ return regmap_update_bits(priv->map, RTL8365MB_PORT_ISOLATION_REG(port),
+ mask, 0);
}
static int rtl8365mb_mib_counter_read(struct realtek_priv *priv, int port,
@@ -1285,8 +2320,8 @@ static void rtl8365mb_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *da
mib->length, &data[i]);
if (ret) {
dev_err(priv->dev,
- "failed to read port %d counters: %d\n", port,
- ret);
+ "failed to read port %d counters: %pe\n", port,
+ ERR_PTR(ret));
break;
}
}
@@ -1536,22 +2571,22 @@ static void rtl8365mb_get_stats64(struct dsa_switch *ds, int port,
spin_unlock(&p->stats_lock);
}
-static void rtl8365mb_stats_setup(struct realtek_priv *priv)
+static int rtl8365mb_stats_setup(struct realtek_priv *priv)
{
struct rtl8365mb *mb = priv->chip_data;
struct dsa_switch *ds = &priv->ds;
- int i;
+ struct dsa_port *dp;
+ int ret;
/* Per-chip global mutex to protect MIB counter access, since doing
* so requires accessing a series of registers in a particular order.
*/
- mutex_init(&mb->mib_lock);
-
- for (i = 0; i < priv->num_ports; i++) {
- struct rtl8365mb_port *p = &mb->ports[i];
+ ret = devm_mutex_init(priv->dev, &mb->mib_lock);
+ if (ret)
+ return ret;
- if (dsa_is_unused_port(ds, i))
- continue;
+ dsa_switch_for_each_available_port(dp, ds) {
+ struct rtl8365mb_port *p = &mb->ports[dp->index];
/* Per-port spinlock to protect the stats64 data */
spin_lock_init(&p->stats_lock);
@@ -1561,19 +2596,18 @@ static void rtl8365mb_stats_setup(struct realtek_priv *priv)
*/
INIT_DELAYED_WORK(&p->mib_work, rtl8365mb_stats_poll);
}
+
+ return 0;
}
static void rtl8365mb_stats_teardown(struct realtek_priv *priv)
{
struct rtl8365mb *mb = priv->chip_data;
struct dsa_switch *ds = &priv->ds;
- int i;
-
- for (i = 0; i < priv->num_ports; i++) {
- struct rtl8365mb_port *p = &mb->ports[i];
+ struct dsa_port *dp;
- if (dsa_is_unused_port(ds, i))
- continue;
+ dsa_switch_for_each_available_port(dp, ds) {
+ struct rtl8365mb_port *p = &mb->ports[dp->index];
cancel_delayed_work_sync(&p->mib_work);
}
@@ -1632,13 +2666,17 @@ static irqreturn_t rtl8365mb_irq(int irq, void *data)
for_each_set_bit(line, &line_changes, priv->num_ports) {
int child_irq = irq_find_mapping(priv->irqdomain, line);
+ if (!child_irq)
+ continue;
+
handle_nested_irq(child_irq);
}
return IRQ_HANDLED;
out_error:
- dev_err(priv->dev, "failed to read interrupt status: %d\n", ret);
+ dev_err(priv->dev, "failed to read interrupt status: %pe\n",
+ ERR_PTR(ret));
out_none:
return IRQ_NONE;
@@ -1652,10 +2690,14 @@ static struct irq_chip rtl8365mb_irq_chip = {
static int rtl8365mb_irq_map(struct irq_domain *domain, unsigned int irq,
irq_hw_number_t hwirq)
{
- irq_set_chip_data(irq, domain->host_data);
+ struct realtek_priv *priv = domain->host_data;
+ struct rtl8365mb *mb = priv->chip_data;
+
+ irq_set_chip_data(irq, priv);
irq_set_chip_and_handler(irq, &rtl8365mb_irq_chip, handle_simple_irq);
irq_set_nested_thread(irq, 1);
irq_set_noprobe(irq);
+ irq_set_parent(irq, mb->irq);
return 0;
}
@@ -1694,13 +2736,14 @@ static int rtl8365mb_irq_disable(struct realtek_priv *priv)
static int rtl8365mb_irq_setup(struct realtek_priv *priv)
{
struct rtl8365mb *mb = priv->chip_data;
+ struct dsa_switch *ds = &priv->ds;
struct device_node *intc;
+ struct dsa_port *dp;
u32 irq_trig;
int virq;
int irq;
u32 val;
int ret;
- int i;
intc = of_get_child_by_name(priv->dev->of_node, "interrupt-controller");
if (!intc) {
@@ -1711,13 +2754,19 @@ static int rtl8365mb_irq_setup(struct realtek_priv *priv)
/* rtl8365mb IRQs cascade off this one */
irq = of_irq_get(intc, 0);
if (irq <= 0) {
- if (irq != -EPROBE_DEFER)
- dev_err(priv->dev, "failed to get parent irq: %d\n",
- irq);
- ret = irq ? irq : -EINVAL;
+ if (!irq) {
+ dev_err(priv->dev, "failed to map IRQ\n");
+ ret = -EINVAL;
+ } else {
+ ret = dev_err_probe(priv->dev, irq,
+ "failed to get parent irq\n");
+ }
goto out_put_node;
}
+ /* Store the irq so that we know to map and free it during teardown */
+ mb->irq = irq;
+
priv->irqdomain = irq_domain_create_linear(of_fwnode_handle(intc), priv->num_ports,
&rtl8365mb_irqdomain_ops, priv);
if (!priv->irqdomain) {
@@ -1726,8 +2775,8 @@ static int rtl8365mb_irq_setup(struct realtek_priv *priv)
goto out_put_node;
}
- for (i = 0; i < priv->num_ports; i++) {
- virq = irq_create_mapping(priv->irqdomain, i);
+ dsa_switch_for_each_available_port(dp, ds) {
+ virq = irq_create_mapping(priv->irqdomain, dp->index);
if (!virq) {
dev_err(priv->dev,
"failed to create irq domain mapping\n");
@@ -1776,13 +2825,11 @@ static int rtl8365mb_irq_setup(struct realtek_priv *priv)
ret = request_threaded_irq(irq, NULL, rtl8365mb_irq, IRQF_ONESHOT,
"rtl8365mb", priv);
if (ret) {
- dev_err(priv->dev, "failed to request irq: %d\n", ret);
+ dev_err(priv->dev, "failed to request irq: %pe\n",
+ ERR_PTR(ret));
goto out_remove_irqdomain;
}
- /* Store the irq so that we know to free it during teardown */
- mb->irq = irq;
-
ret = rtl8365mb_irq_enable(priv);
if (ret)
goto out_free_irq;
@@ -1793,18 +2840,20 @@ static int rtl8365mb_irq_setup(struct realtek_priv *priv)
out_free_irq:
free_irq(mb->irq, priv);
- mb->irq = 0;
out_remove_irqdomain:
- for (i = 0; i < priv->num_ports; i++) {
- virq = irq_find_mapping(priv->irqdomain, i);
- irq_dispose_mapping(virq);
+ dsa_switch_for_each_port(dp, ds) {
+ virq = irq_find_mapping(priv->irqdomain, dp->index);
+
+ if (virq)
+ irq_dispose_mapping(virq);
}
irq_domain_remove(priv->irqdomain);
priv->irqdomain = NULL;
out_put_node:
+ mb->irq = 0;
of_node_put(intc);
return ret;
@@ -1813,8 +2862,9 @@ out_put_node:
static void rtl8365mb_irq_teardown(struct realtek_priv *priv)
{
struct rtl8365mb *mb = priv->chip_data;
+ struct dsa_switch *ds = &priv->ds;
+ struct dsa_port *dp;
int virq;
- int i;
if (mb->irq) {
free_irq(mb->irq, priv);
@@ -1822,9 +2872,15 @@ static void rtl8365mb_irq_teardown(struct realtek_priv *priv)
}
if (priv->irqdomain) {
- for (i = 0; i < priv->num_ports; i++) {
- virq = irq_find_mapping(priv->irqdomain, i);
- irq_dispose_mapping(virq);
+ /* Unused ports with a linked PHY still have an active IRQ
+ * mapping that must be disposed of during teardown. Loop
+ * through all ports.
+ */
+ dsa_switch_for_each_port(dp, ds) {
+ virq = irq_find_mapping(priv->irqdomain, dp->index);
+
+ if (virq)
+ irq_dispose_mapping(virq);
}
irq_domain_remove(priv->irqdomain);
@@ -1942,27 +2998,55 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
{
struct realtek_priv *priv = ds->priv;
struct rtl8365mb_cpu *cpu;
- struct dsa_port *cpu_dp;
+ u32 downports_mask = 0;
+ u32 upports_mask = 0;
struct rtl8365mb *mb;
+ struct dsa_port *dp;
int ret;
- int i;
mb = priv->chip_data;
cpu = &mb->cpu;
+ mb->pcs.ops = &rtl8365mb_pcs_ops;
+
+ /* The SerDes has no link interrupt wired up, so phylink must poll the
+ * PCS for link changes when it tracks the link through pcs_get_state()
+ * (in-band mode with autonegotiation disabled).
+ */
+ mb->pcs.poll = true;
+
ret = rtl8365mb_reset_chip(priv);
if (ret) {
- dev_err(priv->dev, "failed to reset chip: %d\n", ret);
+ dev_err(priv->dev, "failed to reset chip: %pe\n",
+ ERR_PTR(ret));
+ goto out_error;
+ }
+
+ ret = rtl8365mb_sds_probe_option(priv);
+ if (ret) {
+ dev_err(priv->dev, "failed to probe SerDes chip option: %pe\n",
+ ERR_PTR(ret));
goto out_error;
}
/* Configure switch to vendor-defined initial state */
ret = rtl8365mb_switch_init(priv);
if (ret) {
- dev_err(priv->dev, "failed to initialize switch: %d\n", ret);
+ dev_err(priv->dev, "failed to initialize switch: %pe\n",
+ ERR_PTR(ret));
goto out_error;
}
+ if (mb->sds_supported) {
+ ret = rtl8365mb_sds_raise_rate_limits(priv);
+ if (ret) {
+ dev_err(priv->dev,
+ "failed to raise port rate limits: %pe\n",
+ ERR_PTR(ret));
+ goto out_error;
+ }
+ }
+
/* Set up cascading IRQs */
ret = rtl8365mb_irq_setup(priv);
if (ret == -EPROBE_DEFER)
@@ -1970,50 +3054,125 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
else if (ret)
dev_info(priv->dev, "no interrupt support\n");
- /* Configure CPU tagging */
- dsa_switch_for_each_cpu_port(cpu_dp, ds) {
- cpu->mask |= BIT(cpu_dp->index);
-
- if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS)
- cpu->trap_port = cpu_dp->index;
+ dsa_switch_for_each_port(dp, ds) {
+ /* Cascading (DSA links) is not supported yet.
+ * Historically, the driver has always been broken
+ * without a dedicated CPU port because CPU tagging
+ * would be disabled, rendering the switch entirely
+ * non-functional for DSA operations.
+ */
+ if (dsa_port_is_dsa(dp)) {
+ dev_err(priv->dev, "Cascading (DSA link) not supported\n");
+ ret = -EOPNOTSUPP;
+ goto out_teardown_irq;
+ }
}
- cpu->enable = cpu->mask > 0;
- ret = rtl8365mb_cpu_config(priv);
- if (ret)
- goto out_teardown_irq;
- /* Configure ports */
- for (i = 0; i < priv->num_ports; i++) {
- struct rtl8365mb_port *p = &mb->ports[i];
+ /* Start with all ports blocked, including unused ports */
+ dsa_switch_for_each_port(dp, ds) {
+ struct rtl8365mb_port *p = &mb->ports[dp->index];
- if (dsa_is_unused_port(ds, i))
- continue;
+ /* Set the initial STP state of all ports to DISABLED, otherwise
+ * ports will still forward frames to the CPU despite being
+ * administratively down by default.
+ */
+ rtl8365mb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED);
- /* Forward only to the CPU */
- ret = rtl8365mb_port_set_isolation(priv, i, cpu->mask);
+ /* Start with all port completely isolated */
+ ret = rtl8365mb_port_set_isolation(priv, dp->index, 0);
+ if (ret)
+ goto out_teardown_irq;
+
+ /* Set the default EFID 0 for standalone mode */
+ ret = rtl8365mb_port_set_efid(priv, dp->index, 0);
if (ret)
goto out_teardown_irq;
/* Disable learning */
- ret = rtl8365mb_port_set_learning(priv, i, false);
+ ret = rtl8365mb_port_set_learning(priv, dp->index, false);
if (ret)
goto out_teardown_irq;
- /* Set the initial STP state of all ports to DISABLED, otherwise
- * ports will still forward frames to the CPU despite being
- * administratively down by default.
- */
- rtl8365mb_port_stp_state_set(ds, i, BR_STATE_DISABLED);
+ /* Enable all types of flooding */
+ ret = rtl83xx_setup_port_flood_control(priv, dp->index);
+ if (ret)
+ goto out_teardown_irq;
/* Set up per-port private data */
p->priv = priv;
- p->index = i;
+ p->index = dp->index;
+
+ /* Collect CPU ports. If we support cascade switches, it should
+ * also include the upstream DSA ports.
+ */
+ if (!dsa_port_is_cpu(dp))
+ continue;
+
+ upports_mask |= BIT(dp->index);
}
+ /* Configure user ports */
+ dsa_switch_for_each_port(dp, ds) {
+ if (!dsa_port_is_user(dp))
+ continue;
+
+ /* Forward only to the CPU */
+ ret = rtl8365mb_port_set_isolation(priv, dp->index,
+ upports_mask);
+ if (ret)
+ goto out_teardown_irq;
+
+ /* If we support cascade switches, it should also include the
+ * downstream DSA ports.
+ */
+ downports_mask |= BIT(dp->index);
+ }
+
+ /* Configure CPU tagging */
+ /* If we support cascade switches, it should also include the upstream
+ * DSA ports.
+ */
+ dsa_switch_for_each_cpu_port(dp, ds) {
+ /* Use the first CPU port as trap_port */
+ if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS)
+ cpu->trap_port = dp->index;
+
+ /* Forward to all user ports */
+ ret = rtl8365mb_port_set_isolation(priv, dp->index,
+ downports_mask);
+ if (ret)
+ goto out_teardown_irq;
+ }
+
+ cpu->mask = upports_mask;
+ cpu->enable = cpu->mask > 0;
+
+ if (!cpu->enable) {
+ dev_err(priv->dev, "no CPU port defined\n");
+ ret = -EINVAL;
+ goto out_teardown_irq;
+ }
+
+ ret = rtl8365mb_cpu_config(priv);
+ if (ret)
+ goto out_teardown_irq;
+
ret = rtl8365mb_port_change_mtu(ds, cpu->trap_port, ETH_DATA_LEN);
if (ret)
goto out_teardown_irq;
+ ds->assisted_learning_on_cpu_port = true;
+ ds->fdb_isolation = true;
+ /* The EFID is 3 bits, but EFID 0 is reserved for standalone ports */
+ ds->max_num_bridges = FIELD_MAX(RTL8365MB_EFID_MASK);
+
+ ds->configure_vlan_while_not_filtering = true;
+
+ /* Set up VLAN */
+ ret = rtl8365mb_vlan_setup(ds);
+ if (ret)
+ goto out_teardown_irq;
+
ret = rtl83xx_setup_user_mdio(ds);
if (ret) {
dev_err(priv->dev, "could not set up MDIO bus\n");
@@ -2021,7 +3180,12 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
}
/* Start statistics counter polling */
- rtl8365mb_stats_setup(priv);
+ ret = rtl8365mb_stats_setup(priv);
+ if (ret) {
+ dev_err(priv->dev, "failed to setup stats: %pe\n",
+ ERR_PTR(ret));
+ goto out_teardown_irq;
+ }
return 0;
@@ -2077,8 +3241,8 @@ static int rtl8365mb_detect(struct realtek_priv *priv)
ret = rtl8365mb_get_chip_id_and_ver(priv->map, &chip_id, &chip_ver);
if (ret) {
- dev_err(priv->dev, "failed to read chip id and version: %d\n",
- ret);
+ dev_err(priv->dev, "failed to read chip id and version: %pe\n",
+ ERR_PTR(ret));
return ret;
}
@@ -2112,6 +3276,7 @@ static int rtl8365mb_detect(struct realtek_priv *priv)
}
static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops = {
+ .mac_select_pcs = rtl8365mb_phylink_mac_select_pcs,
.mac_config = rtl8365mb_phylink_mac_config,
.mac_link_down = rtl8365mb_phylink_mac_link_down,
.mac_link_up = rtl8365mb_phylink_mac_link_up,
@@ -2123,7 +3288,20 @@ static const struct dsa_switch_ops rtl8365mb_switch_ops = {
.setup = rtl8365mb_setup,
.teardown = rtl8365mb_teardown,
.phylink_get_caps = rtl8365mb_phylink_get_caps,
+ .port_bridge_join = rtl83xx_port_bridge_join,
+ .port_bridge_leave = rtl83xx_port_bridge_leave,
+ .port_pre_bridge_flags = rtl8365mb_port_pre_bridge_flags,
+ .port_bridge_flags = rtl83xx_port_bridge_flags,
.port_stp_state_set = rtl8365mb_port_stp_state_set,
+ .port_fast_age = rtl83xx_port_fast_age,
+ .port_fdb_add = rtl83xx_port_fdb_add,
+ .port_fdb_del = rtl83xx_port_fdb_del,
+ .port_fdb_dump = rtl83xx_port_fdb_dump,
+ .port_mdb_add = rtl83xx_port_mdb_add,
+ .port_mdb_del = rtl83xx_port_mdb_del,
+ .port_vlan_add = rtl8365mb_port_vlan_add,
+ .port_vlan_del = rtl8365mb_port_vlan_del,
+ .port_vlan_filtering = rtl8365mb_port_vlan_filtering,
.get_strings = rtl8365mb_get_strings,
.get_ethtool_stats = rtl8365mb_get_ethtool_stats,
.get_sset_count = rtl8365mb_get_sset_count,
@@ -2139,6 +3317,19 @@ static const struct dsa_switch_ops rtl8365mb_switch_ops = {
static const struct realtek_ops rtl8365mb_ops = {
.detect = rtl8365mb_detect,
+ .port_add_isolation = rtl8365mb_port_add_isolation,
+ .port_remove_isolation = rtl8365mb_port_remove_isolation,
+ .port_set_efid = rtl8365mb_port_set_efid,
+ .port_set_learning = rtl8365mb_port_set_learning,
+ .port_set_ucast_flood = rtl8365mb_port_set_ucast_flood,
+ .port_set_mcast_flood = rtl8365mb_port_set_mcast_flood,
+ .port_set_bcast_flood = rtl8365mb_port_set_bcast_flood,
+ .l2_add_uc = rtl8365mb_l2_add_uc,
+ .l2_del_uc = rtl8365mb_l2_del_uc,
+ .l2_get_next_uc = rtl8365mb_l2_get_next_uc,
+ .l2_add_mc = rtl8365mb_l2_add_mc,
+ .l2_del_mc = rtl8365mb_l2_del_mc,
+ .l2_flush = rtl8365mb_l2_flush,
.phy_read = rtl8365mb_phy_read,
.phy_write = rtl8365mb_phy_write,
};
diff --git a/drivers/net/dsa/realtek/rtl8365mb_table.c b/drivers/net/dsa/realtek/rtl8365mb_table.c
new file mode 100644
index 000000000000..f3c8749a2221
--- /dev/null
+++ b/drivers/net/dsa/realtek/rtl8365mb_table.c
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Look-up table query interface for the rtl8365mb switch family
+ *
+ * Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
+ */
+
+#include "rtl8365mb_table.h"
+#include <linux/regmap.h>
+
+/* Table access control register */
+#define RTL8365MB_TABLE_CTRL_REG 0x0500
+/* Should be one of rtl8365mb_table enum members */
+#define RTL8365MB_TABLE_CTRL_TABLE_MASK GENMASK(2, 0)
+/* Should be one of rtl8365mb_table_op enum members */
+#define RTL8365MB_TABLE_CTRL_OP_MASK GENMASK(3, 3)
+/* Should be one of rtl8365mb_table_l2_method enum members */
+#define RTL8365MB_TABLE_CTRL_METHOD_MASK GENMASK(6, 4)
+#define RTL8365MB_TABLE_CTRL_PORT_MASK GENMASK(11, 8)
+
+/* Table access address register */
+#define RTL8365MB_TABLE_ACCESS_ADDR_REG 0x0501
+#define RTL8365MB_TABLE_ADDR_MASK GENMASK(12, 0)
+
+/* Table status register */
+#define RTL8365MB_TABLE_STATUS_REG 0x0502
+#define RTL8365MB_TABLE_STATUS_ADDRESS_MASK GENMASK(10, 0)
+/* set for L3, unset for L2 */
+#define RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK GENMASK(11, 11)
+#define RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK GENMASK(12, 12)
+#define RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK GENMASK(13, 13)
+#define RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK GENMASK(14, 14)
+
+/* Table read/write registers */
+#define RTL8365MB_TABLE_WRITE_BASE 0x0510
+#define RTL8365MB_TABLE_WRITE_REG(_x) \
+ (RTL8365MB_TABLE_WRITE_BASE + (_x))
+#define RTL8365MB_TABLE_READ_BASE 0x0520
+#define RTL8365MB_TABLE_READ_REG(_x) \
+ (RTL8365MB_TABLE_READ_BASE + (_x))
+#define RTL8365MB_TABLE_10TH_DATA_MASK GENMASK(3, 0)
+#define RTL8365MB_TABLE_WRITE_10TH_REG \
+ RTL8365MB_TABLE_WRITE_REG(RTL8365MB_TABLE_ENTRY_MAX_SIZE - 1)
+
+static int rtl8365mb_table_poll_busy(struct realtek_priv *priv)
+{
+ u32 val;
+
+ return regmap_read_poll_timeout(priv->map_nolock,
+ RTL8365MB_TABLE_STATUS_REG, val,
+ !FIELD_GET(RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK, val),
+ 10, 10000);
+}
+
+int rtl8365mb_table_query(struct realtek_priv *priv,
+ enum rtl8365mb_table table,
+ enum rtl8365mb_table_op op, u16 *addr,
+ enum rtl8365mb_table_l2_method method,
+ u16 port, u16 *data, size_t size)
+{
+ bool addr_as_input = true;
+ bool write_data = false;
+ int ret = 0;
+ u32 cmd;
+ u32 val;
+ u32 hit;
+
+ /* Prepare target table and operation (read or write) */
+ cmd = 0;
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_TABLE_MASK, table);
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_OP_MASK, op);
+ if (op == RTL8365MB_TABLE_OP_READ && table == RTL8365MB_TABLE_L2) {
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_METHOD_MASK, method);
+ switch (method) {
+ case RTL8365MB_TABLE_L2_METHOD_MAC:
+ /*
+ * Method MAC requires as input the same L2 table format
+ * you'll get as result. However, it might only use mac
+ * address and FID/VID fields.
+ */
+ write_data = true;
+
+ /* METHOD_MAC does not use addr as input, but may return
+ * the matched index.
+ */
+ addr_as_input = false;
+
+ break;
+ case RTL8365MB_TABLE_L2_METHOD_ADDR:
+ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT:
+ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC:
+ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC:
+ break;
+ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT:
+ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK, port);
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else if (op == RTL8365MB_TABLE_OP_WRITE) {
+ write_data = true;
+
+ /* Writing to L2 does not use addr as input, as the table index
+ * is derived from key fields.
+ */
+ if (table == RTL8365MB_TABLE_L2)
+ addr_as_input = false;
+ }
+
+ /* To prevent concurrent access to the look-up tables, take the regmap
+ * lock manually and access via the map_nolock regmap.
+ */
+ mutex_lock(&priv->map_lock);
+
+ /* Protect from a busy table access (i.e. previous access timeouts) */
+ ret = rtl8365mb_table_poll_busy(priv);
+ if (ret)
+ goto out;
+
+ /* Write entry data if writing to the table (or L2_METHOD_MAC) */
+ if (write_data) {
+ /* bulk write data up to 9th word */
+ ret = regmap_bulk_write(priv->map_nolock,
+ RTL8365MB_TABLE_WRITE_BASE,
+ data,
+ min_t(size_t, size,
+ RTL8365MB_TABLE_ENTRY_MAX_SIZE -
+ 1));
+ if (ret)
+ goto out;
+
+ /* 10th register uses only 4 least significant bits */
+ if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) {
+ val = FIELD_PREP(RTL8365MB_TABLE_10TH_DATA_MASK,
+ data[size - 1]);
+ ret = regmap_update_bits(priv->map_nolock,
+ RTL8365MB_TABLE_WRITE_10TH_REG,
+ RTL8365MB_TABLE_10TH_DATA_MASK,
+ val);
+ }
+
+ if (ret)
+ goto out;
+ }
+
+ /* Write address (if needed) */
+ if (addr_as_input) {
+ ret = regmap_write(priv->map_nolock,
+ RTL8365MB_TABLE_ACCESS_ADDR_REG,
+ FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK,
+ *addr));
+ if (ret)
+ goto out;
+ }
+
+ /* Execute */
+ ret = regmap_write(priv->map_nolock, RTL8365MB_TABLE_CTRL_REG, cmd);
+ if (ret)
+ goto out;
+
+ /* Poll for completion */
+ ret = rtl8365mb_table_poll_busy(priv);
+ if (ret)
+ goto out;
+
+ /* For both reads and writes to the L2 table, check status */
+ if (table == RTL8365MB_TABLE_L2) {
+ ret = regmap_read(priv->map_nolock, RTL8365MB_TABLE_STATUS_REG,
+ &val);
+ if (ret)
+ goto out;
+
+ /* Did the query find an entry? */
+ hit = FIELD_GET(RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK, val);
+ if (!hit) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ /* If so, extract the address */
+ *addr = 0;
+ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_MASK, val);
+ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK, val)
+ << 11;
+ /* only set if it is a L3 address */
+ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK, val)
+ << 12;
+ }
+
+ /* Finally, get the table entry if we were reading */
+ if (op == RTL8365MB_TABLE_OP_READ) {
+ ret = regmap_bulk_read(priv->map_nolock,
+ RTL8365MB_TABLE_READ_BASE,
+ data, size);
+ if (ret)
+ goto out;
+
+ /* For the biggest table entries, the uppermost table
+ * entry register has space for only one nibble. Mask
+ * out the remainder bits. Empirically I saw nothing
+ * wrong with omitting this mask, but it may prevent
+ * unwanted behaviour. FYI.
+ */
+ if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) {
+ val = FIELD_GET(RTL8365MB_TABLE_10TH_DATA_MASK,
+ data[size - 1]);
+ data[size - 1] = val;
+ }
+ }
+
+out:
+ mutex_unlock(&priv->map_lock);
+
+ return ret;
+}
diff --git a/drivers/net/dsa/realtek/rtl8365mb_table.h b/drivers/net/dsa/realtek/rtl8365mb_table.h
new file mode 100644
index 000000000000..41280eb6f8b0
--- /dev/null
+++ b/drivers/net/dsa/realtek/rtl8365mb_table.h
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Look-up table query interface for the rtl8365mb switch family
+ *
+ * Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
+ */
+
+#ifndef _REALTEK_RTL8365MB_TABLE_H
+#define _REALTEK_RTL8365MB_TABLE_H
+
+#include <linux/if_ether.h>
+#include <linux/types.h>
+
+#include "realtek.h"
+
+#define RTL8365MB_TABLE_ENTRY_MAX_SIZE 10
+
+/*
+ * enum rtl8365mb_table - available switch tables
+ * @RTL8365MB_TABLE_ACL_RULE: ACL rules
+ * @RTL8365MB_TABLE_ACL_ACTION: ACL actions
+ * @RTL8365MB_TABLE_CVLAN: VLAN4k configurations
+ * @RTL8365MB_TABLE_L2: filtering database (2K hash table)
+ * @RTL8365MB_TABLE_IGMP_GROUP: IGMP group database (readonly)
+ *
+ * NOTE: Don't change the enum values. They must concur with the field
+ * described by @RTL8365MB_TABLE_CTRL_TABLE_MASK.
+ */
+enum rtl8365mb_table {
+ RTL8365MB_TABLE_ACL_RULE = 1,
+ RTL8365MB_TABLE_ACL_ACTION = 2,
+ RTL8365MB_TABLE_CVLAN = 3,
+ RTL8365MB_TABLE_L2 = 4,
+ RTL8365MB_TABLE_IGMP_GROUP = 5,
+};
+
+/*
+ * enum rtl8365mb_table_op - table query operation
+ * @RTL8365MB_TABLE_OP_READ: read an entry from the target table
+ * @RTL8365MB_TABLE_OP_WRITE: write an entry to the target table
+ *
+ * NOTE: Don't change the enum values. They must concur with the field
+ * described by @RTL8365MB_TABLE_CTRL_OP_MASK.
+ */
+enum rtl8365mb_table_op {
+ RTL8365MB_TABLE_OP_READ = 0,
+ RTL8365MB_TABLE_OP_WRITE = 1,
+};
+
+/*
+ * enum rtl8365mb_table_l2_method - look-up method for read queries of L2 table
+ * @RTL8365MB_TABLE_L2_METHOD_MAC: look-up by source MAC address and FID (or
+ * VID)
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR: look-up by entry address
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT: look-up next entry starting from the
+ * supplied address
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC: same as ADDR_NEXT but search only
+ * unicast addresses
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC: same as ADDR_NEXT but search only
+ * multicast addresses
+ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT: same as ADDR_NEXT_UC but
+ * search only entries with matching source port
+ *
+ * NOTE: Don't change the enum values. They must concur with the field
+ * described by @RTL8365MB_TABLE_CTRL_METHOD_MASK
+ */
+enum rtl8365mb_table_l2_method {
+ RTL8365MB_TABLE_L2_METHOD_MAC = 0,
+ RTL8365MB_TABLE_L2_METHOD_ADDR = 1,
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT = 2,
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC = 3,
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC = 4,
+ /*
+ * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L3 = 5,
+ * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L2L3 = 6,
+ */
+ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT = 7,
+};
+
+/*
+ * rtl8365mb_table_query() - read from or write to a switch table
+ * @priv: driver context
+ * @table: target table, see &enum rtl8365mb_table
+ * @op: read or write operation, see &enum rtl8365mb_table_op
+ * @addr: table address. For indexed tables, this selects the entry to access.
+ * For L2 read queries, it is ignored as input for MAC-based lookup
+ * methods and used as input for address-based lookup methods. On
+ * successful L2 queries, it is updated with the matched entry address.
+ * @method: L2 table lookup method, see &enum rtl8365mb_table_l2_method.
+ * Ignored for non-L2 tables.
+ * @port: for L2 read queries using method
+ * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, restrict the search
+ * to entries associated with this source port. Ignored otherwise.
+ * @data: data buffer used to read from or write to the table. For L2 MAC
+ * lookups, this buffer provides the lookup key and receives the
+ * matched entry contents on success.
+ * @size: size of @data in 16-bit words. The caller must ensure that @size
+ * matches the target table's entry size and does not exceed
+ * RTL8365MB_TABLE_ENTRY_MAX_SIZE.
+ *
+ * This function provides unified access to the internal tables of the switch.
+ * All tables except the L2 table are simple indexed tables, where @addr
+ * selects the entry and @op determines whether the access is a read or a
+ * write operation.
+ *
+ * The content of @data is used as input when writing to tables or when
+ * specifying the lookup key for L2 MAC searches, and as output for all
+ * successful read operations. It remains unchanged during write operations or
+ * failed read operations that return %-ENOENT. For other errors during read
+ * operations, it is undefined.
+ *
+ * The L2 table is a hash table and supports multiple lookup methods. For
+ * %RTL8365MB_TABLE_L2_METHOD_MAC, an entry is searched based on the MAC
+ * address and FID/VID fields provided in @data, using the same format as
+ * an L2 table entry. Address-based methods either read a specific entry
+ * (%RTL8365MB_TABLE_L2_METHOD_ADDR) or iterate over valid entries starting
+ * from @addr (%RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT and variants). When using
+ * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, only entries associated with
+ * the specified @port are considered.
+ *
+ * On successful L2 operations, @addr is updated with the matched table address
+ * or allocated entry address. If no matching entry is found, or if an L2 write
+ * operation fails (e.g., due to a full table during addition or a missing entry
+ * during deletion), %-ENOENT is returned and @addr remains unchanged. It is the
+ * caller's responsibility to map the returned error to the appropriate
+ * semantic error.
+ *
+ * @size must match the size of the target table entry, expressed in 16-bit
+ * words.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int rtl8365mb_table_query(struct realtek_priv *priv,
+ enum rtl8365mb_table table,
+ enum rtl8365mb_table_op op, u16 *addr,
+ enum rtl8365mb_table_l2_method method,
+ u16 port, u16 *data, size_t size);
+
+#endif /* _REALTEK_RTL8365MB_TABLE_H */
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
new file mode 100644
index 000000000000..64dd8d90a5ee
--- /dev/null
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
@@ -0,0 +1,944 @@
+// SPDX-License-Identifier: GPL-2.0
+/* VLAN configuration interface for the rtl8365mb switch family
+ *
+ * Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
+ *
+ * VLAN configuration takes place in two separate domains of the switch: the
+ * VLAN4k table and the VLAN membership configuration (MC) database. While the
+ * VLAN4k table is exhaustive and can be fully populated with 4096 VLAN
+ * configurations, the same does not hold for the VLAN membership configuration
+ * database, which is limited to 32 entries.
+ *
+ * The switch will normally only use the VLAN4k table when making forwarding
+ * decisions. The VLAN membership configuration database is a vestigial ASIC
+ * design and is only used for a few specific features in the rtl8365mb
+ * family. This means that the limit of 32 entries should not hinder us in
+ * programming a huge number of VLANs into the switch.
+ *
+ * One necessary use of the VLAN membership configuration database is for the
+ * programming of a port-based VLAN ID (PVID). The PVID is programmed on a
+ * per-port basis via register field, which refers to a specific VLAN membership
+ * configuration via an index 0~31. In order to maintain coherent behaviour on a
+ * port with a PVID, it is necessary to keep the VLAN configuration synchronized
+ * between the VLAN4k table and the VLAN membership configuration database.
+ *
+ * Since VLAN membership configs are a scarce resource, it will only be used
+ * when strictly needed (i.e. a VLAN with members using PVID). Otherwise, the
+ * VLAN4k will be enough.
+ *
+ * With some exceptions, the entries in both the VLAN4k table and the VLAN
+ * membership configuration database offer the same configuration options. The
+ * differences are as follows:
+ *
+ * 1. VLAN4k entries can specify whether to use Independent or Shared VLAN
+ * Learning (IVL or SVL respectively). VLAN membership config entries
+ * cannot. This underscores the fact that VLAN membership configs are not
+ * involved in the learning process of the ASIC.
+ *
+ * 2. VLAN membership config entries use an "enhanced VLAN ID" (efid), which has
+ * a range 0~8191 compared with the standard 0~4095 range of the VLAN4k
+ * table. This underscores the fact that VLAN membership configs can be used
+ * to group ports on a layer beyond the standard VLAN configuration, which
+ * may be useful for ACL rules which specify alternative forwarding
+ * decisions.
+ *
+ * VLANMC index 0 is reserved as a neutral PVID, used for standalone ports.
+ *
+ */
+
+#include "rtl8365mb_vlan.h"
+#include "rtl8365mb_table.h"
+#include <linux/if_bridge.h>
+#include <linux/lockdep.h>
+#include <linux/regmap.h>
+
+/* CVLAN (i.e. VLAN4k) table entry layout, u16[3] */
+#define RTL8365MB_CVLAN_ENTRY_SIZE 3 /* 48-bits */
+#define RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK GENMASK(7, 0)
+#define RTL8365MB_CVLAN_MBR_LO_MASK GENMASK(7, 0)
+#define RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK GENMASK(15, 8)
+#define RTL8365MB_CVLAN_UNTAG_LO_MASK GENMASK(7, 0)
+#define RTL8365MB_CVLAN_ENTRY_D1_FID_MASK GENMASK(3, 0)
+#define RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK GENMASK(4, 4)
+#define RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK GENMASK(7, 5)
+#define RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK GENMASK(8, 8)
+#define RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK GENMASK(13, 9)
+#define RTL8365MB_CVLAN_METERIDX_LO_MASK GENMASK(4, 0)
+#define RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK GENMASK(14, 14)
+/* extends RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK */
+#define RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK GENMASK(2, 0)
+#define RTL8365MB_CVLAN_MBR_HI_MASK GENMASK(10, 8)
+/* extends RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK */
+#define RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK GENMASK(5, 3)
+#define RTL8365MB_CVLAN_UNTAG_HI_MASK GENMASK(10, 8)
+/* extends RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK */
+#define RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK GENMASK(6, 6)
+#define RTL8365MB_CVLAN_METERIDX_HI_MASK GENMASK(5, 5)
+
+/* VLAN member configuration registers 0~31, u16[3] */
+#define RTL8365MB_VLAN_MC_BASE 0x0728
+#define RTL8365MB_VLAN_MC_ENTRY_SIZE 4 /* 64-bit */
+#define RTL8365MB_VLAN_MC_REG(index) \
+ (RTL8365MB_VLAN_MC_BASE + \
+ (RTL8365MB_VLAN_MC_ENTRY_SIZE * (index)))
+#define RTL8365MB_VLAN_MC_D0_MBR_MASK GENMASK(10, 0)
+#define RTL8365MB_VLAN_MC_D1_FID_MASK GENMASK(3, 0)
+
+#define RTL8365MB_VLAN_MC_D2_VBPEN_MASK GENMASK(0, 0)
+#define RTL8365MB_VLAN_MC_D2_VBPRI_MASK GENMASK(3, 1)
+#define RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK GENMASK(4, 4)
+#define RTL8365MB_VLAN_MC_D2_METERIDX_MASK GENMASK(10, 5)
+#define RTL8365MB_VLAN_MC_D3_EVID_MASK GENMASK(12, 0)
+
+/* Some limits for VLAN4k/VLAN membership config entries */
+#define RTL8365MB_PRIORITYMAX 7
+#define RTL8365MB_FIDMAX 15
+#define RTL8365MB_METERMAX 63
+#define RTL8365MB_VLAN_MCMAX 31
+
+/* RTL8367S supports 4k vlans (vid<=4095) and 32 enhanced vlans
+ * for VIDs up to 8191
+ */
+#define RTL8365MB_MAX_4K_VID 0x0FFF /* 4095 */
+#define RTL8365MB_MAX_MC_VID 0x1FFF /* 8191 */
+
+ /* Port-based VID registers 0~5 - each one holds an MC index for two ports */
+#define RTL8365MB_VLAN_PVID_CTRL_BASE 0x0700
+#define RTL8365MB_VLAN_PVID_CTRL_REG(_p) \
+ (RTL8365MB_VLAN_PVID_CTRL_BASE + ((_p) >> 1))
+#define RTL8365MB_VLAN_PVID_CTRL_PORT0_MCIDX_MASK 0x001F
+#define RTL8365MB_VLAN_PVID_CTRL_PORT1_MCIDX_MASK 0x1F00
+#define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p) \
+ (((_p) & 1) << 3)
+#define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(_p) \
+ (0x1F << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p))
+
+/* Frame type filtering registers */
+#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE 0x07aa
+#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port) \
+ (RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE + ((port) >> 3))
+/* required as FIELD_PREP cannot use non-constant masks */
+#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port) \
+ (0x3 << RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port))
+#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port) \
+ (((port) & 0x7) << 1)
+
+/*
+ * struct rtl8365mb_vlan4k - VLAN4k table entry
+ * @vid: VLAN ID (0~4095)
+ * @member: port mask of ports in this VLAN
+ * @untag: port mask of ports which untag on egress
+ * @fid: filter ID - only used with SVL (unused)
+ * @priority: priority classification (unused)
+ * @priority_en: enable priority (unused)
+ * @policing_en: enable policing (unused)
+ * @ivl_en: enable IVL instead of default SVL
+ * @meteridx: metering index (unused)
+ *
+ * This structure is used to get/set entries in the VLAN4k table. The
+ * VLAN4k table dictates the VLAN configuration for the switch for the
+ * vast majority of features.
+ */
+struct rtl8365mb_vlan4k {
+ u16 vid;
+ u16 member;
+ u16 untag;
+ u8 fid : 4;
+ u8 priority : 3;
+ u8 priority_en : 1;
+ u8 policing_en : 1;
+ u8 ivl_en : 1;
+ u8 meteridx : 6;
+};
+
+/*
+ * struct rtl8365mb_vlanmc - VLAN membership config
+ * @evid: Enhanced VLAN ID (0~8191)
+ * @member: port mask of ports in this VLAN
+ * @fid: filter ID - only used with SVL (unused)
+ * @priority: priority classification (unused)
+ * @priority_en: enable priority (unused)
+ * @policing_en: enable policing (unused)
+ * @meteridx: metering index (unused)
+ *
+ * This structure is used to get/set entries in the VLAN membership
+ * configuration database. This feature is largely vestigial, but
+ * still needed for at least the following features:
+ * - PVID configuration
+ * - ACL configuration
+ * - selection of VLAN by the CPU tag when VSEL=1, although the switch
+ * can also select VLAN based on the VLAN tag if VSEL=0
+ *
+ * This is a low-level structure and it is recommended to interface with
+ * the VLAN membership config database via &struct rtl8365mb_vlanmc_entry.
+ */
+struct rtl8365mb_vlanmc {
+ u16 evid;
+ u16 member;
+ u8 fid : 4;
+ u8 priority : 3;
+ u8 priority_en : 1;
+ u8 policing_en : 1;
+ u8 meteridx : 6;
+};
+
+static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid,
+ struct rtl8365mb_vlan4k *vlan4k)
+{
+ u16 data[RTL8365MB_CVLAN_ENTRY_SIZE];
+ int val;
+ int ret;
+
+ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN,
+ RTL8365MB_TABLE_OP_READ, &vid, 0, 0,
+ data, ARRAY_SIZE(data));
+ if (ret)
+ return ret;
+
+ /* Unpack table entry */
+ memset(vlan4k, 0, sizeof(*vlan4k));
+ vlan4k->vid = vid;
+
+ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, data[0]);
+ vlan4k->member = FIELD_PREP(RTL8365MB_CVLAN_MBR_LO_MASK, val);
+ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, data[2]);
+ vlan4k->member |= FIELD_PREP(RTL8365MB_CVLAN_MBR_HI_MASK, val);
+
+ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, data[0]);
+ vlan4k->untag = FIELD_PREP(RTL8365MB_CVLAN_UNTAG_LO_MASK, val);
+ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, data[2]);
+ vlan4k->untag |= FIELD_PREP(RTL8365MB_CVLAN_UNTAG_HI_MASK, val);
+
+ vlan4k->fid = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, data[1]);
+ vlan4k->priority_en =
+ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, data[1]);
+ vlan4k->priority =
+ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, data[1]);
+ vlan4k->policing_en =
+ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, data[1]);
+
+ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, data[1]);
+ val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);
+ vlan4k->meteridx = val;
+ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, data[2]);
+ val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);
+ vlan4k->meteridx |= val;
+
+ vlan4k->ivl_en =
+ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, data[1]);
+
+ return 0;
+}
+
+static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv,
+ const struct rtl8365mb_vlan4k *vlan4k)
+{
+ u16 data[RTL8365MB_CVLAN_ENTRY_SIZE] = { 0 };
+ u16 vid;
+ int val;
+
+ /* Pack table entry value */
+ val = FIELD_GET(RTL8365MB_CVLAN_MBR_LO_MASK, vlan4k->member);
+ data[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, val);
+
+ val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_LO_MASK, vlan4k->untag);
+ data[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, val);
+
+ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, vlan4k->fid);
+ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK,
+ vlan4k->priority_en);
+ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK,
+ vlan4k->priority);
+ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK,
+ vlan4k->policing_en);
+
+ /* FIELD_* does not play nice with struct bitfield. */
+ val = vlan4k->meteridx;
+ val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);
+ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, val);
+
+ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK,
+ vlan4k->ivl_en);
+
+ val = FIELD_GET(RTL8365MB_CVLAN_MBR_HI_MASK, vlan4k->member);
+ data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, val);
+
+ val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_HI_MASK, vlan4k->untag);
+ data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, val);
+
+ val = vlan4k->meteridx;
+ val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);
+ data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, val);
+
+ vid = vlan4k->vid;
+ return rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN,
+ RTL8365MB_TABLE_OP_WRITE, &vid, 0, 0,
+ data, ARRAY_SIZE(data));
+}
+
+static int
+rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack,
+ bool include)
+{
+ struct realtek_priv *priv = ds->priv;
+ struct rtl8365mb_vlan4k vlan4k = {0};
+ int ret;
+
+ dev_dbg(priv->dev, "%s VLAN %d 4K on port %d\n",
+ include ? "add" : "del",
+ vlan->vid, port);
+
+ if (vlan->vid > RTL8365MB_MAX_4K_VID) {
+ NL_SET_ERR_MSG_MOD(extack, "VLAN ID greater than "
+ __stringify(RTL8365MB_MAX_4K_VID));
+ return -EINVAL;
+ }
+
+ ret = rtl8365mb_vlan_4k_read(priv, vlan->vid, &vlan4k);
+ if (ret) {
+ dev_err(priv->dev, "Failed to read VLAN 4k table\n");
+ return ret;
+ }
+
+ if (include)
+ vlan4k.member |= BIT(port);
+ else
+ vlan4k.member &= ~BIT(port);
+
+ if (include && (vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED))
+ vlan4k.untag |= BIT(port);
+ else
+ vlan4k.untag &= ~BIT(port);
+ vlan4k.ivl_en = true; /* always use Independent VLAN Learning */
+
+ ret = rtl8365mb_vlan_4k_write(priv, &vlan4k);
+ if (ret) {
+ dev_err(priv->dev, "Failed to write VLAN 4k table\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * rtl8365mb_vlan_4k_port_add() - Add a port to a VLAN 4K table entry
+ * @ds: dsa switch instance
+ * @port: port index
+ * @vlan: switchdev VLAN object containing the target VID and flags
+ * @extack: netlink extended ACK for error reporting
+ *
+ * Adds the specified port to the hardware VLAN 4K membership table.
+ *
+ * Context: Can sleep. Must be called with &priv->vlan_lock held.
+ * Takes and releases &priv->map_lock.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack)
+{
+ struct realtek_priv *priv = ds->priv;
+
+ lockdep_assert_held(&priv->vlan_lock);
+
+ return rtl8365mb_vlan_4k_port_set(ds, port, vlan, extack, true);
+}
+
+/*
+ * rtl8365mb_vlan_4k_port_del() - Remove a port from a VLAN 4K table entry
+ * @ds: dsa switch instance
+ * @port: port index
+ * @vlan: switchdev VLAN object containing the target VID
+ *
+ * Removes the specified port from the hardware VLAN 4K membership table.
+ *
+ * Context: Can sleep. Must be called with &priv->vlan_lock held.
+ * Takes and releases &priv->map_lock.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int rtl8365mb_vlan_4k_port_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan)
+{
+ struct realtek_priv *priv = ds->priv;
+
+ lockdep_assert_held(&priv->vlan_lock);
+
+ return rtl8365mb_vlan_4k_port_set(ds, port, vlan, NULL, false);
+}
+
+static int rtl8365mb_vlan_mc_read(struct realtek_priv *priv, u32 index,
+ struct rtl8365mb_vlanmc *vlanmc)
+{
+ u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE];
+ int ret;
+
+ ret = regmap_bulk_read(priv->map, RTL8365MB_VLAN_MC_REG(index), &data,
+ RTL8365MB_VLAN_MC_ENTRY_SIZE);
+ if (ret)
+ return ret;
+
+ vlanmc->member = FIELD_GET(RTL8365MB_VLAN_MC_D0_MBR_MASK, data[0]);
+ vlanmc->fid = FIELD_GET(RTL8365MB_VLAN_MC_D1_FID_MASK, data[1]);
+ vlanmc->meteridx = FIELD_GET(RTL8365MB_VLAN_MC_D2_METERIDX_MASK,
+ data[2]);
+ vlanmc->policing_en = FIELD_GET(RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK,
+ data[2]);
+ vlanmc->priority = FIELD_GET(RTL8365MB_VLAN_MC_D2_VBPRI_MASK, data[2]);
+ vlanmc->priority_en = FIELD_GET(RTL8365MB_VLAN_MC_D2_VBPEN_MASK,
+ data[2]);
+ vlanmc->evid = FIELD_GET(RTL8365MB_VLAN_MC_D3_EVID_MASK, data[3]);
+
+ return 0;
+}
+
+static int rtl8365mb_vlan_mc_write(struct realtek_priv *priv, u32 index,
+ const struct rtl8365mb_vlanmc *vlanmc)
+{
+ u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE] = { 0 };
+ int ret;
+
+ data[0] |= FIELD_PREP(RTL8365MB_VLAN_MC_D0_MBR_MASK, vlanmc->member);
+ data[1] |= FIELD_PREP(RTL8365MB_VLAN_MC_D1_FID_MASK, vlanmc->fid);
+ data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_METERIDX_MASK,
+ vlanmc->meteridx);
+ data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK,
+ vlanmc->policing_en);
+ data[2] |=
+ FIELD_PREP(RTL8365MB_VLAN_MC_D2_VBPRI_MASK, vlanmc->priority);
+ data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_VBPEN_MASK,
+ vlanmc->priority_en);
+ data[3] |= FIELD_PREP(RTL8365MB_VLAN_MC_D3_EVID_MASK, vlanmc->evid);
+
+ ret = regmap_bulk_write(priv->map, RTL8365MB_VLAN_MC_REG(index), &data,
+ RTL8365MB_VLAN_MC_ENTRY_SIZE);
+
+ return ret;
+}
+
+static int rtl8365mb_vlan_mc_erase(struct realtek_priv *priv, u32 index)
+{
+ u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE] = { 0 };
+ int ret;
+
+ ret = regmap_bulk_write(priv->map, RTL8365MB_VLAN_MC_REG(index), &data,
+ RTL8365MB_VLAN_MC_ENTRY_SIZE);
+
+ return ret;
+}
+
+/*
+ * rtl8365mb_vlan_mc_find() - find VLANMC index by VID or the first free index
+ *
+ * @priv: realtek_priv pointer
+ * @vid: VLAN ID
+ * @index: found index
+ * @first_free: found free index
+ *
+ * If a VLAN MC entry using @vid was found, @index will return the matched index
+ * and @first_free is undefined. If not found, @index will return 0 and
+ * @first_free will return the first found free index in VLAN MC or 0 if the
+ * table is full.
+ *
+ * Although 0 is a valid VLAN MC index, it is reserved for ports without PVID,
+ * including standalone, non-member ports. It uses VID == 0.
+ *
+ * Both @index and @first_free will be in the * 1..@RTL8365MB_VLAN_MCMAX range.
+ *
+ * Return: Returns 0 on success, a negative error on failure.
+ */
+static int rtl8365mb_vlan_mc_find(struct realtek_priv *priv, u16 vid,
+ u8 *index, u8 *first_free)
+{
+ u32 vlan_entry_d3;
+ u8 vlanmc_idx;
+ u16 evid;
+ int ret;
+
+ *index = 0;
+ *first_free = 0;
+
+ /* look for existing entry or an empty one */
+ /* By design, VlanMC[0] is reserved as a neutral PVID value for
+ * standalone ports. It always has EVID == 0. That way, we assume that
+ * all entries after index 0 with VID == 0 are empty.
+ **/
+ for (vlanmc_idx = 1; vlanmc_idx <= RTL8365MB_VLAN_MCMAX; vlanmc_idx++) {
+ /* just read the 4th word, where the evid is */
+ ret = regmap_read(priv->map,
+ RTL8365MB_VLAN_MC_REG(vlanmc_idx) + 3,
+ &vlan_entry_d3);
+ if (ret)
+ return ret;
+
+ evid = FIELD_GET(RTL8365MB_VLAN_MC_D3_EVID_MASK, vlan_entry_d3);
+
+ if (evid == vid) {
+ *index = vlanmc_idx;
+ return 0;
+ }
+
+ if (evid == 0x0 && *first_free < 1)
+ *first_free = vlanmc_idx;
+ }
+ return 0;
+}
+
+static int rtl8365mb_vlan_port_get_pvid_idx(struct realtek_priv *priv,
+ int port, u8 *vlanmc_idx)
+{
+ u32 data;
+ int ret;
+
+ ret = regmap_read(priv->map, RTL8365MB_VLAN_PVID_CTRL_REG(port), &data);
+ if (ret)
+ return ret;
+
+ *vlanmc_idx = (data & RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(port))
+ >> RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(port);
+
+ return 0;
+}
+
+/*
+ * rtl8365mb_vlan_mc_port_set() - include or exclude a port from VlanMC
+ * @ds: dsa switch
+ * @port: the port number
+ * @vid: the vlan VID to include/exclude @port
+ * @pvid: inform if vid is used as pvid in @port
+ * @extack: optional extack to return errors
+ * @include: whether to include or exclude @port
+ *
+ * This function is used to include/exclude ports to the VlanMC table.
+ *
+ * VlanMC stands for VLAN membership config and it is used exclusively for
+ * PVID. If @vlan members are not using PVID, this function will either
+ * remove or not create a new VlanMC entry.
+ *
+ * VlanMC members are used as a reference port map, cleaning the entry once
+ * no port is using it.
+ *
+ * Port PVID and accepted frame type are updated as well.
+ *
+ * Context: Can sleep. Must be called with &priv->vlan_lock held.
+ * Takes and releases &priv->map_lock.
+ * Return: Returns 0 on success, a negative error on failure.
+ */
+static
+int rtl8365mb_vlan_mc_port_set(struct dsa_switch *ds, int port,
+ u16 vid, bool pvid,
+ struct netlink_ext_ack *extack,
+ bool include)
+{
+ struct realtek_priv *priv = ds->priv;
+ struct rtl8365mb_vlanmc vlanmc = {0};
+ u8 first_unused = 0;
+ u8 vlanmc_idx = 0;
+ int ret;
+
+ dev_dbg(priv->dev, "%s VLAN %d MC on port %d\n",
+ include ? "add" : "del",
+ vid, port);
+
+ if (vid > RTL8365MB_MAX_MC_VID) {
+ NL_SET_ERR_MSG_MOD(extack, "VLAN ID greater than "
+ __stringify(RTL8365MB_MAX_MC_VID));
+ return -EINVAL;
+ }
+
+ /* look for existing entry or an empty slot */
+ ret = rtl8365mb_vlan_mc_find(priv, vid, &vlanmc_idx,
+ &first_unused);
+ if (ret) {
+ dev_err(priv->dev, "Failed to find a VLAN MC table index\n");
+ return ret;
+ }
+
+ if (vlanmc_idx) {
+ ret = rtl8365mb_vlan_mc_read(priv, vlanmc_idx, &vlanmc);
+ if (ret) {
+ dev_err(priv->dev, "Failed to read VLAN MC table\n");
+ return ret;
+ }
+ } else if (include) {
+ /* for now, vlan_mc is only required for PVID. Defer allocation
+ * until at least one port uses PVID.
+ */
+ if (!pvid) {
+ dev_dbg(priv->dev,
+ "Not creating VlanMC for vlan %d until a port uses PVID (%d does not)\n",
+ vid, port);
+ return 0;
+ }
+
+ if (!first_unused) {
+ NL_SET_ERR_MSG_MOD(extack, "All VLAN MC entries (0.."
+ __stringify(RTL8365MB_VLAN_MCMAX)
+ ") are in use.");
+ return -ENOSPC;
+ }
+
+ vlanmc_idx = first_unused;
+ vlanmc.evid = vid;
+
+ } else /* excluding and VLANMC not found */ {
+ return 0;
+ }
+
+ dev_dbg(priv->dev,
+ "VLAN %d (idx: %d) PVID curr members: %08x\n",
+ vid, vlanmc_idx, vlanmc.member);
+
+ /* here we either have an existing VLANMC (with PVID members) or the
+ * added port is using this VLAN as PVID
+ */
+ if (include)
+ vlanmc.member |= BIT(port);
+ else
+ vlanmc.member &= ~BIT(port);
+
+ /* just like we don't need to create a VLAN_MC when there is no port
+ * using it as PVID, we can erase it when there is no more port using
+ * it as PVID.
+ */
+ if (!vlanmc.member) {
+ dev_dbg(priv->dev,
+ "Clearing VlanMC index %d previously used by VID %d\n",
+ vlanmc_idx, vid);
+ ret = rtl8365mb_vlan_mc_erase(priv, vlanmc_idx);
+ } else {
+ dev_dbg(priv->dev,
+ "Saving VlanMC index %d with VID %d\n",
+ vlanmc_idx, vid);
+ ret = rtl8365mb_vlan_mc_write(priv, vlanmc_idx, &vlanmc);
+ }
+ if (ret) {
+ dev_err(priv->dev, "Failed to write vlan MC entry\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rtl8365mb_vlan_port_set_pvid(struct realtek_priv *priv,
+ int port, u16 vlanmc_idx)
+{
+ int ret;
+ u32 val;
+
+ dev_dbg(priv->dev, "set PVID IDX %d on port %d\n", vlanmc_idx, port);
+
+ val = vlanmc_idx << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(port);
+ ret = regmap_update_bits(priv->map,
+ RTL8365MB_VLAN_PVID_CTRL_REG(port),
+ RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(port),
+ val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int rtl8365mb_vlan_get_pvid_mc(struct realtek_priv *priv,
+ int port, u8 *vlanmc_idx,
+ struct rtl8365mb_vlanmc *vlanmc)
+{
+ int ret;
+
+ ret = rtl8365mb_vlan_port_get_pvid_idx(priv, port, vlanmc_idx);
+ if (ret)
+ return ret;
+
+ memset(vlanmc, 0, sizeof(*vlanmc));
+
+ if (!*vlanmc_idx)
+ return 0;
+
+ ret = rtl8365mb_vlan_mc_read(priv, *vlanmc_idx, vlanmc);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * rtl8365mb_vlan_port_get_pvid - Retrieve the port PVID
+ * @priv: realtek switch private structure
+ * @port: port index
+ * @pvid: pointer to store the retrieved VLAN ID
+ *
+ * Returns the port PVID if defined or 0 if not.
+ *
+ * Context: Can sleep. Takes and releases &priv->map_lock.
+ * Return: 0 on success or a negative error code on failure.
+ */
+int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, u16 *pvid)
+{
+ struct rtl8365mb_vlanmc vlanmc;
+ u8 vlanmc_idx;
+ int ret;
+
+ ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, &vlanmc);
+ if (ret)
+ return ret;
+
+ *pvid = vlanmc.evid;
+ return 0;
+}
+
+/*
+ * rtl8365mb_vlan_port_get_framefilter() - Get the ingress frame filtering mode
+ * for a port
+ * @priv: realtek switch private structure
+ * @port: port index
+ * @frame_type: pointer to store the retrieved ingress frame filter type
+ *
+ * Context: Can sleep. Takes and releases &priv->map_lock.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int
+rtl8365mb_vlan_port_get_framefilter(struct realtek_priv *priv,
+ int port,
+ enum rtl8365mb_frame_ingress *frame_type)
+{
+ u32 val;
+ int ret;
+
+ /* Even if ACCEPT_FRAME_TYPE_ANY, the switch will still check if the
+ * port is a member of vlan PVID
+ */
+
+ ret = regmap_read(priv->map, RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port),
+ &val);
+ if (ret)
+ return ret;
+
+ *frame_type = field_get(RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port),
+ val);
+
+ return 0;
+}
+
+/*
+ * rtl8365mb_vlan_port_set_framefilter() - Set the ingress frame filtering mode
+ * for a port
+ * @priv: realtek switch private structure
+ * @port: port index
+ * @frame_type: the ingress frame filter type to configure
+ *
+ * Context: Can sleep. Takes and releases &priv->map_lock.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int
+rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv,
+ int port,
+ enum rtl8365mb_frame_ingress frame_type)
+{
+ u32 val;
+
+ /* Even if ACCEPT_FRAME_TYPE_ANY, the switch will still check if the
+ * port is a member of vlan PVID
+ */
+ val = frame_type << RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port);
+
+ return regmap_update_bits(priv->map,
+ RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port),
+ RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port),
+ val);
+}
+
+/*
+ * rtl8365mb_vlan_pvid_port_set() - Configure a port's PVID and associated
+ * VLANMC entry
+ * @ds: dsa switch instance
+ * @port: port index
+ * @vid: target VID
+ * @extack: netlink extended ACK for error reporting
+ *
+ * Allocates or reuses a hardware VLANMC entry to map the given port to its new
+ * PVID. Gracefully unwinds and restores previous configuration if a hardware
+ * write operation fails during execution.
+ *
+ * Context: Can sleep. Must be called with &priv->vlan_lock held.
+ * Takes and releases &priv->map_lock.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,
+ struct netlink_ext_ack *extack)
+{
+ enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame;
+ struct realtek_priv *priv = ds->priv;
+ struct rtl8365mb_vlanmc prev_vlanmc = {0};
+ u8 _unused_first_free_idx;
+ u8 prev_vlanmc_idx;
+ u8 vlanmc_idx;
+ int ret;
+
+ lockdep_assert_held(&priv->vlan_lock);
+
+ /* Read the old PVID exclusively to undo in case of error */
+ ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &prev_vlanmc_idx,
+ &prev_vlanmc);
+ if (ret) {
+ dev_err(priv->dev, "Failed to read current VLAN MC\n");
+ return ret;
+ }
+
+ ret = rtl8365mb_vlan_port_get_framefilter(priv, port,
+ &prev_accepted_frame);
+ if (ret) {
+ dev_err(priv->dev, "Failed to get current framefilter\n");
+ return ret;
+ }
+
+ /* Find or allocate a new vlan MC and add port to members,
+ * although members are not checked by the HW in vlan MC.
+ */
+ ret = rtl8365mb_vlan_mc_port_set(ds, port, vid, true, extack, true);
+ if (ret)
+ return ret;
+
+ /* look for existing entry */
+ ret = rtl8365mb_vlan_mc_find(priv, vid, &vlanmc_idx,
+ &_unused_first_free_idx);
+ if (ret) {
+ dev_err(priv->dev, "Failed to find a VLAN MC table index\n");
+ goto undo_vlan_mc_port_set;
+ }
+
+ if (!vlanmc_idx) {
+ dev_err(priv->dev, "VLAN should already exist in VLAN MC\n");
+ ret = -ENOENT;
+ goto undo_vlan_mc_port_set;
+ }
+
+ ret = rtl8365mb_vlan_port_set_pvid(priv, port, vlanmc_idx);
+ if (ret) {
+ dev_err(priv->dev, "Failed to set port PVID\n");
+ goto undo_vlan_mc_port_set;
+ }
+
+ /* Changing accept frame is what enables PVID (if not enabled before) */
+ accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME;
+ ret = rtl8365mb_vlan_port_set_framefilter(priv, port, accepted_frame);
+ if (ret) {
+ dev_err(priv->dev, "Failed to set port frame filter\n");
+ goto undo_vlan_port_set_pvid;
+ }
+
+ /* A VLAN can be added with PVID without removing from the old
+ * PVID VLAN. Clear PVID from the old VLAN MC (if needed).
+ */
+ if (prev_vlanmc_idx && (prev_vlanmc.evid != vid)) {
+ ret = rtl8365mb_vlan_mc_port_set(ds, port, prev_vlanmc.evid,
+ false, NULL, false);
+ if (ret) {
+ dev_err(priv->dev, "Failed to clear old VLAN MC\n");
+ goto undo_set_framefilter;
+ }
+ }
+
+ return 0;
+
+undo_set_framefilter:
+ (void)rtl8365mb_vlan_port_set_framefilter(priv, port,
+ prev_accepted_frame);
+
+undo_vlan_port_set_pvid:
+ (void)rtl8365mb_vlan_port_set_pvid(priv, port, prev_vlanmc_idx);
+
+undo_vlan_mc_port_set:
+ if (prev_vlanmc.evid != vid)
+ (void)rtl8365mb_vlan_mc_port_set(ds, port, vid, false, NULL,
+ false);
+
+ return ret;
+}
+
+/*
+ * rtl8365mb_vlan_pvid_port_clear() - Remove a port's PVID configuration
+ * @ds: dsa switch instance
+ * @port: port index
+ * @vid: VLAN VID for PVID
+ *
+ * Resets the target port's hardware PVID allocation to 0. Cleans up and frees
+ * the associated VLANMC entry if no other ports are referencing it.
+ *
+ * Context: Can sleep. Must be called with &priv->vlan_lock held.
+ * Takes and releases &priv->map_lock.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid)
+{
+ enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame;
+ struct realtek_priv *priv = ds->priv;
+ struct rtl8365mb_vlanmc vlanmc = {0};
+ u8 vlanmc_idx;
+ int ret;
+
+ lockdep_assert_held(&priv->vlan_lock);
+
+ ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx,
+ &vlanmc);
+ if (ret) {
+ dev_err(priv->dev, "Failed to read current VLAN MC\n");
+ return ret;
+ }
+
+ /* Port is not using PVID. Nothing to remove. */
+ if (!vlanmc_idx)
+ return 0;
+
+ /* We are leaving a non PVID vlan, Nothing to remove. */
+ if (vlanmc.evid != vid)
+ return 0;
+
+ ret = rtl8365mb_vlan_port_get_framefilter(priv, port,
+ &prev_accepted_frame);
+ if (ret) {
+ dev_err(priv->dev, "Failed to get current framefilter\n");
+ return ret;
+ }
+
+ /* Changing accept frame is what really removes PVID. But only do
+ * that if we are filtering vlan
+ */
+ if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) {
+ accepted_frame = RTL8365MB_FRAME_TYPE_TAGGED_ONLY;
+
+ ret = rtl8365mb_vlan_port_set_framefilter(priv, port,
+ accepted_frame);
+ if (ret) {
+ dev_err(priv->dev, "Failed to set port frame filter\n");
+ return ret;
+ }
+ } else {
+ /* skip undo_set_framefilter */
+ accepted_frame = prev_accepted_frame;
+ }
+
+ ret = rtl8365mb_vlan_port_set_pvid(priv, port, 0);
+ if (ret) {
+ dev_err(priv->dev, "Failed to set port PVID to 0\n");
+ goto undo_set_framefilter;
+ }
+
+ /* Clears the VLAN MC membership and maybe VLAN MC entry if empty */
+ ret = rtl8365mb_vlan_mc_port_set(ds, port, vlanmc.evid,
+ false, NULL, false);
+ if (ret)
+ goto undo_port_set_pvid;
+
+ return 0;
+
+undo_port_set_pvid:
+ (void)rtl8365mb_vlan_port_set_pvid(priv, port, vlanmc_idx);
+
+undo_set_framefilter:
+ if (prev_accepted_frame != accepted_frame)
+ (void)rtl8365mb_vlan_port_set_framefilter(priv, port,
+ prev_accepted_frame);
+
+ return ret;
+}
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.h b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
new file mode 100644
index 000000000000..27a526b61873
--- /dev/null
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* VLAN configuration interface for the rtl8365mb switch family
+ *
+ * Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
+ *
+ */
+
+#ifndef _REALTEK_RTL8365MB_VLAN_H
+#define _REALTEK_RTL8365MB_VLAN_H
+
+#include <linux/types.h>
+
+#include "realtek.h"
+
+enum rtl8365mb_frame_ingress {
+ RTL8365MB_FRAME_TYPE_ANY_FRAME = 0,
+ RTL8365MB_FRAME_TYPE_TAGGED_ONLY,
+ RTL8365MB_FRAME_TYPE_UNTAGGED_ONLY,
+};
+
+int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port,
+ u16 *pvid);
+int
+rtl8365mb_vlan_port_get_framefilter(struct realtek_priv *priv,
+ int port,
+ enum rtl8365mb_frame_ingress *frame_type);
+int
+rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv,
+ int port,
+ enum rtl8365mb_frame_ingress frame_type);
+int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack);
+int rtl8365mb_vlan_4k_port_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan);
+int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,
+ struct netlink_ext_ack *extack);
+int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid);
+#endif /* _REALTEK_RTL8365MB_VLAN_H */
diff --git a/drivers/net/dsa/realtek/rtl8366rb-leds.c b/drivers/net/dsa/realtek/rtl8366rb-leds.c
index 509ffd3f8db5..ba50d311cb15 100644
--- a/drivers/net/dsa/realtek/rtl8366rb-leds.c
+++ b/drivers/net/dsa/realtek/rtl8366rb-leds.c
@@ -89,6 +89,7 @@ static int rtl8366rb_setup_led(struct realtek_priv *priv, struct dsa_port *dp,
struct led_init_data init_data = { };
enum led_default_state state;
struct rtl8366rb_led *led;
+ char name[64];
u32 led_group;
int ret;
@@ -129,10 +130,9 @@ static int rtl8366rb_setup_led(struct realtek_priv *priv, struct dsa_port *dp,
init_data.fwnode = led_fwnode;
init_data.devname_mandatory = true;
- init_data.devicename = kasprintf(GFP_KERNEL, "Realtek-%d:0%d:%d",
- dp->ds->index, dp->index, led_group);
- if (!init_data.devicename)
- return -ENOMEM;
+ snprintf(name, sizeof(name), "Realtek-%d:0%d:%d",
+ dp->ds->index, dp->index, led_group);
+ init_data.devicename = name;
ret = devm_led_classdev_register_ext(priv->dev, &led->cdev, &init_data);
if (ret) {
diff --git a/drivers/net/dsa/realtek/rtl8366rb.c b/drivers/net/dsa/realtek/rtl8366rb.c
index d96ae72b0a5c..f11831b66de8 100644
--- a/drivers/net/dsa/realtek/rtl8366rb.c
+++ b/drivers/net/dsa/realtek/rtl8366rb.c
@@ -573,8 +573,7 @@ static int rtl8366rb_set_addr(struct realtek_priv *priv)
eth_random_addr(addr);
- dev_info(priv->dev, "set MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
- addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+ dev_info(priv->dev, "set MAC: %pM\n", addr);
val = addr[0] << 8 | addr[1];
ret = regmap_write(priv->map, RTL8366RB_SMAR0, val);
if (ret)
@@ -792,11 +791,86 @@ static int rtl8366rb_setup_all_leds_off(struct realtek_priv *priv)
return ret;
}
+static int rtl8366rb_port_set_isolation(struct realtek_priv *priv, int port,
+ u32 mask)
+{
+ /* Bit 0 enables isolation, the mask indicates allowed forwarding
+ * ports
+ */
+ mask = RTL8366RB_PORT_ISO_PORTS(mask) | RTL8366RB_PORT_ISO_EN;
+
+ return regmap_write(priv->map, RTL8366RB_PORT_ISO(port),
+ mask);
+}
+
+static int rtl8366rb_port_add_isolation(struct realtek_priv *priv, int port,
+ u32 mask)
+{
+ /* We assume isolation bit is on */
+ return regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port),
+ RTL8366RB_PORT_ISO_PORTS(mask),
+ RTL8366RB_PORT_ISO_PORTS(mask));
+}
+
+static int rtl8366rb_port_remove_isolation(struct realtek_priv *priv, int port,
+ u32 mask)
+{
+ return regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port),
+ RTL8366RB_PORT_ISO_PORTS(mask), 0);
+}
+
+static void
+rtl8366rb_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+{
+ struct realtek_priv *priv = ds->priv;
+ u32 val;
+ int i;
+
+ switch (state) {
+ case BR_STATE_DISABLED:
+ val = RTL8366RB_STP_STATE_DISABLED;
+ break;
+ case BR_STATE_BLOCKING:
+ case BR_STATE_LISTENING:
+ val = RTL8366RB_STP_STATE_BLOCKING;
+ break;
+ case BR_STATE_LEARNING:
+ val = RTL8366RB_STP_STATE_LEARNING;
+ break;
+ case BR_STATE_FORWARDING:
+ val = RTL8366RB_STP_STATE_FORWARDING;
+ break;
+ default:
+ dev_err(priv->dev, "unknown bridge state requested\n");
+ return;
+ }
+
+ /* Set the same status for the port on all the FIDs */
+ for (i = 0; i < RTL8366RB_NUM_FIDS; i++) {
+ regmap_update_bits(priv->map, RTL8366RB_STP_STATE_BASE + i,
+ RTL8366RB_STP_STATE_MASK(port),
+ RTL8366RB_STP_STATE(port, val));
+ }
+}
+
+static int rtl8366rb_port_set_learning(struct realtek_priv *priv, int port,
+ bool enable)
+{
+ /* Notice inverted semantics in this register: setting a bit disables
+ * learning instead of enabling it.
+ */
+ return regmap_update_bits(priv->map, RTL8366RB_PORT_LEARNDIS_CTRL,
+ BIT(port), enable ? 0 : BIT(port));
+}
+
static int rtl8366rb_setup(struct dsa_switch *ds)
{
struct realtek_priv *priv = ds->priv;
const struct rtl8366rb_jam_tbl_entry *jam_table;
+ u32 downports_mask = 0;
struct rtl8366rb *rb;
+ u32 upports_mask = 0;
+ struct dsa_port *dp;
u32 chip_ver = 0;
u32 chip_id = 0;
int jam_size;
@@ -867,20 +941,58 @@ static int rtl8366rb_setup(struct dsa_switch *ds)
if (ret)
return ret;
- /* Isolate all user ports so they can only send packets to itself and the CPU port */
- for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) {
- ret = regmap_write(priv->map, RTL8366RB_PORT_ISO(i),
- RTL8366RB_PORT_ISO_PORTS(BIT(RTL8366RB_PORT_NUM_CPU)) |
- RTL8366RB_PORT_ISO_EN);
+ /* Start with all ports blocked, including unused ports */
+ dsa_switch_for_each_port(dp, ds) {
+ /* Set the initial STP state of all ports to DISABLED, otherwise
+ * ports will still forward frames to the CPU despite being
+ * administratively down by default.
+ */
+ rtl8366rb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED);
+
+ /* Start with all ports completely isolated */
+ ret = rtl8366rb_port_set_isolation(priv, dp->index, 0);
+ if (ret)
+ return ret;
+
+ /* Disable learning */
+ ret = rtl8366rb_port_set_learning(priv, dp->index, false);
+ if (ret)
+ return ret;
+
+ /* Collect CPU ports. If we support cascade switches, it should
+ * also include the upstream DSA ports.
+ */
+ if (!dsa_port_is_cpu(dp))
+ continue;
+
+ upports_mask |= BIT(dp->index);
+ }
+
+ /* Configure user ports */
+ dsa_switch_for_each_port(dp, ds) {
+ if (!dsa_port_is_user(dp))
+ continue;
+
+ /* Forward only to the CPU(s), isolate from all other ports */
+ ret = rtl8366rb_port_set_isolation(priv, dp->index, upports_mask);
+ if (ret)
+ return ret;
+
+ /* If we support cascade switches, it should also include the
+ * downstream DSA ports.
+ */
+ downports_mask |= BIT(dp->index);
+ }
+
+ /* Configure CPU ports. If we support cascade switches, this will also
+ * include DSA ports.
+ */
+ dsa_switch_for_each_cpu_port(dp, ds) {
+ /* Forward to all user ports */
+ ret = rtl8366rb_port_set_isolation(priv, dp->index, downports_mask);
if (ret)
return ret;
}
- /* CPU port can send packets to all ports */
- ret = regmap_write(priv->map, RTL8366RB_PORT_ISO(RTL8366RB_PORT_NUM_CPU),
- RTL8366RB_PORT_ISO_PORTS(dsa_user_ports(ds)) |
- RTL8366RB_PORT_ISO_EN);
- if (ret)
- return ret;
/* Set up the "green ethernet" feature */
ret = rtl8366rb_jam_table(rtl8366rb_green_jam,
@@ -939,12 +1051,6 @@ static int rtl8366rb_setup(struct dsa_switch *ds)
rb->max_mtu[i] = ETH_DATA_LEN;
}
- /* Disable learning for all ports */
- ret = regmap_write(priv->map, RTL8366RB_PORT_LEARNDIS_CTRL,
- RTL8366RB_PORT_ALL);
- if (ret)
- return ret;
-
/* Enable auto ageing for all ports */
ret = regmap_write(priv->map, RTL8366RB_SECURITY_CTRL, 0);
if (ret)
@@ -1185,70 +1291,6 @@ rtl8366rb_port_disable(struct dsa_switch *ds, int port)
return;
}
-static int
-rtl8366rb_port_bridge_join(struct dsa_switch *ds, int port,
- struct dsa_bridge bridge,
- bool *tx_fwd_offload,
- struct netlink_ext_ack *extack)
-{
- struct realtek_priv *priv = ds->priv;
- unsigned int port_bitmap = 0;
- int ret, i;
-
- /* Loop over all other ports than the current one */
- for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) {
- /* Current port handled last */
- if (i == port)
- continue;
- /* Not on this bridge */
- if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge))
- continue;
- /* Join this port to each other port on the bridge */
- ret = regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(i),
- RTL8366RB_PORT_ISO_PORTS(BIT(port)),
- RTL8366RB_PORT_ISO_PORTS(BIT(port)));
- if (ret)
- dev_err(priv->dev, "failed to join port %d\n", port);
-
- port_bitmap |= BIT(i);
- }
-
- /* Set the bits for the ports we can access */
- return regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port),
- RTL8366RB_PORT_ISO_PORTS(port_bitmap),
- RTL8366RB_PORT_ISO_PORTS(port_bitmap));
-}
-
-static void
-rtl8366rb_port_bridge_leave(struct dsa_switch *ds, int port,
- struct dsa_bridge bridge)
-{
- struct realtek_priv *priv = ds->priv;
- unsigned int port_bitmap = 0;
- int ret, i;
-
- /* Loop over all other ports than this one */
- for (i = 0; i < RTL8366RB_PORT_NUM_CPU; i++) {
- /* Current port handled last */
- if (i == port)
- continue;
- /* Not on this bridge */
- if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge))
- continue;
- /* Remove this port from any other port on the bridge */
- ret = regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(i),
- RTL8366RB_PORT_ISO_PORTS(BIT(port)), 0);
- if (ret)
- dev_err(priv->dev, "failed to leave port %d\n", port);
-
- port_bitmap |= BIT(i);
- }
-
- /* Clear the bits for the ports we can not access, leave ourselves */
- regmap_update_bits(priv->map, RTL8366RB_PORT_ISO(port),
- RTL8366RB_PORT_ISO_PORTS(port_bitmap), 0);
-}
-
/**
* rtl8366rb_drop_untagged() - make the switch drop untagged and C-tagged frames
* @priv: SMI state container
@@ -1307,59 +1349,6 @@ rtl8366rb_port_pre_bridge_flags(struct dsa_switch *ds, int port,
return 0;
}
-static int
-rtl8366rb_port_bridge_flags(struct dsa_switch *ds, int port,
- struct switchdev_brport_flags flags,
- struct netlink_ext_ack *extack)
-{
- struct realtek_priv *priv = ds->priv;
- int ret;
-
- if (flags.mask & BR_LEARNING) {
- ret = regmap_update_bits(priv->map, RTL8366RB_PORT_LEARNDIS_CTRL,
- BIT(port),
- (flags.val & BR_LEARNING) ? 0 : BIT(port));
- if (ret)
- return ret;
- }
-
- return 0;
-}
-
-static void
-rtl8366rb_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
-{
- struct realtek_priv *priv = ds->priv;
- u32 val;
- int i;
-
- switch (state) {
- case BR_STATE_DISABLED:
- val = RTL8366RB_STP_STATE_DISABLED;
- break;
- case BR_STATE_BLOCKING:
- case BR_STATE_LISTENING:
- val = RTL8366RB_STP_STATE_BLOCKING;
- break;
- case BR_STATE_LEARNING:
- val = RTL8366RB_STP_STATE_LEARNING;
- break;
- case BR_STATE_FORWARDING:
- val = RTL8366RB_STP_STATE_FORWARDING;
- break;
- default:
- dev_err(priv->dev, "unknown bridge state requested\n");
- return;
- }
-
- /* Set the same status for the port on all the FIDs */
- for (i = 0; i < RTL8366RB_NUM_FIDS; i++) {
- regmap_update_bits(priv->map, RTL8366RB_STP_STATE_BASE + i,
- RTL8366RB_STP_STATE_MASK(port),
- RTL8366RB_STP_STATE(port, val));
- }
-}
-
static void
rtl8366rb_port_fast_age(struct dsa_switch *ds, int port)
{
@@ -1802,15 +1791,15 @@ static const struct dsa_switch_ops rtl8366rb_switch_ops = {
.get_strings = rtl8366_get_strings,
.get_ethtool_stats = rtl8366_get_ethtool_stats,
.get_sset_count = rtl8366_get_sset_count,
- .port_bridge_join = rtl8366rb_port_bridge_join,
- .port_bridge_leave = rtl8366rb_port_bridge_leave,
+ .port_bridge_join = rtl83xx_port_bridge_join,
+ .port_bridge_leave = rtl83xx_port_bridge_leave,
.port_vlan_filtering = rtl8366rb_vlan_filtering,
.port_vlan_add = rtl8366_vlan_add,
.port_vlan_del = rtl8366_vlan_del,
.port_enable = rtl8366rb_port_enable,
.port_disable = rtl8366rb_port_disable,
.port_pre_bridge_flags = rtl8366rb_port_pre_bridge_flags,
- .port_bridge_flags = rtl8366rb_port_bridge_flags,
+ .port_bridge_flags = rtl83xx_port_bridge_flags,
.port_stp_state_set = rtl8366rb_port_stp_state_set,
.port_fast_age = rtl8366rb_port_fast_age,
.port_change_mtu = rtl8366rb_change_mtu,
@@ -1831,6 +1820,9 @@ static const struct realtek_ops rtl8366rb_ops = {
.is_vlan_valid = rtl8366rb_is_vlan_valid,
.enable_vlan = rtl8366rb_enable_vlan,
.enable_vlan4k = rtl8366rb_enable_vlan4k,
+ .port_add_isolation = rtl8366rb_port_add_isolation,
+ .port_remove_isolation = rtl8366rb_port_remove_isolation,
+ .port_set_learning = rtl8366rb_port_set_learning,
.phy_read = rtl8366rb_phy_read,
.phy_write = rtl8366rb_phy_write,
};
diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c
index 2b9bd4462714..a96beea72ef5 100644
--- a/drivers/net/dsa/realtek/rtl83xx.c
+++ b/drivers/net/dsa/realtek/rtl83xx.c
@@ -3,6 +3,8 @@
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/of_mdio.h>
+#include <linux/if_bridge.h>
+#include <linux/etherdevice.h>
#include "realtek.h"
#include "rtl83xx.h"
@@ -154,7 +156,17 @@ rtl83xx_probe(struct device *dev,
if (!priv)
return ERR_PTR(-ENOMEM);
- mutex_init(&priv->map_lock);
+ ret = devm_mutex_init(dev, &priv->map_lock);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = devm_mutex_init(dev, &priv->vlan_lock);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = devm_mutex_init(dev, &priv->l2_lock);
+ if (ret)
+ return ERR_PTR(ret);
rc.lock_arg = priv;
priv->map = devm_regmap_init(dev, NULL, priv, &rc);
@@ -309,7 +321,7 @@ void rtl83xx_reset_assert(struct realtek_priv *priv)
"Failed to assert the switch reset control: %pe\n",
ERR_PTR(ret));
- gpiod_set_value(priv->reset, true);
+ gpiod_set_value_cansleep(priv->reset, true);
}
void rtl83xx_reset_deassert(struct realtek_priv *priv)
@@ -322,8 +334,565 @@ void rtl83xx_reset_deassert(struct realtek_priv *priv)
"Failed to deassert the switch reset control: %pe\n",
ERR_PTR(ret));
- gpiod_set_value(priv->reset, false);
+ gpiod_set_value_cansleep(priv->reset, false);
+}
+
+/**
+ * rtl83xx_port_bridge_join() - join a port to a bridge
+ * @ds: DSA switch instance
+ * @port: port index
+ * @bridge: bridge being joined
+ * @tx_forward_offload: if the switch can offload TX forwarding
+ * @extack: netlink extended ack for reporting errors
+ *
+ * This function handles joining a port to a bridge. It updates the port
+ * isolation masks and EFID.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge,
+ bool *tx_forward_offload,
+ struct netlink_ext_ack *extack)
+{
+ struct realtek_priv *priv = ds->priv;
+ struct dsa_port *dp;
+ u32 mask = 0;
+ int ret;
+
+ if (!priv->ops->port_add_isolation)
+ return -EOPNOTSUPP;
+
+ dev_dbg(priv->dev, "bridge %d join port %d\n", bridge.num, port);
+
+ /* Add this port to the isolation group of every other port
+ * offloading this bridge.
+ */
+ dsa_switch_for_each_user_port(dp, ds) {
+ /* Handle this port after */
+ if (dp->index == port)
+ continue;
+
+ /* Skip ports that are not in this bridge */
+ if (!dsa_port_offloads_bridge(dp, &bridge))
+ continue;
+
+ ret = priv->ops->port_add_isolation(priv, dp->index, BIT(port));
+ if (ret)
+ goto undo_isolation;
+
+ mask |= BIT(dp->index);
+ }
+
+ /* If we support cascade switches, it should also include the
+ * downstream DSA ports to the isolation group.
+ */
+
+ /* Add those ports to the isolation group of this port */
+ ret = priv->ops->port_add_isolation(priv, port, mask);
+ if (ret)
+ goto undo_isolation;
+
+ /* Use the bridge number as the EFID for this port */
+ if (priv->ops->port_set_efid) {
+ ret = priv->ops->port_set_efid(priv, port, bridge.num);
+ if (ret)
+ goto undo_self_isolation;
+ }
+
+ if (priv->ops->port_set_learning) {
+ ret = priv->ops->port_set_learning(priv, port, true);
+ if (ret)
+ goto undo_efid;
+ }
+
+ return 0;
+
+undo_efid:
+ if (priv->ops->port_set_efid)
+ priv->ops->port_set_efid(priv, port, 0);
+
+undo_self_isolation:
+ priv->ops->port_remove_isolation(priv, port, mask);
+
+undo_isolation:
+ dsa_switch_for_each_port(dp, ds) {
+ if (mask & BIT(dp->index))
+ priv->ops->port_remove_isolation(priv, dp->index,
+ BIT(port));
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_join, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_bridge_leave() - leave a bridge
+ * @ds: DSA switch instance
+ * @port: port index
+ * @bridge: bridge being left
+ *
+ * This function handles removing a port from a bridge. It updates the port
+ * isolation masks and EFID.
+ *
+ * Context: Can sleep.
+ * Return: nothing
+ */
+void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge)
+{
+ struct realtek_priv *priv = ds->priv;
+ struct dsa_port *dp;
+ u32 mask = 0;
+ int ret;
+
+ if (!priv->ops->port_remove_isolation)
+ return;
+
+ dev_dbg(priv->dev, "bridge %d leave port %d\n", bridge.num, port);
+
+ /* Remove this port from the isolation group of every other
+ * port offloading this bridge.
+ */
+ dsa_switch_for_each_user_port(dp, ds) {
+ /* Handle this port after */
+ if (dp->index == port)
+ continue;
+
+ /* Skip ports that are not in this bridge */
+ if (!dsa_port_offloads_bridge(dp, &bridge))
+ continue;
+
+ ret = priv->ops->port_remove_isolation(priv, dp->index,
+ BIT(port));
+ if (ret)
+ dev_err(priv->dev,
+ "failed to isolate port %d from port %d: %pe\n",
+ port, dp->index, ERR_PTR(ret));
+
+ mask |= BIT(dp->index);
+ }
+
+ /* If we support cascade switches, it should also exclude the
+ * downstream DSA ports from the isolation group.
+ */
+
+ if (priv->ops->port_set_learning) {
+ ret = priv->ops->port_set_learning(priv, port, false);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to disable learning on port %d: %pe\n",
+ port, ERR_PTR(ret));
+ }
+
+ /* Remove those ports from the isolation group of this port */
+ ret = priv->ops->port_remove_isolation(priv, port, mask);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to remove isolation mask from port %d: %pe\n",
+ port, ERR_PTR(ret));
+
+ /* Revert to the default EFID 0 for standalone mode */
+ if (priv->ops->port_set_efid) {
+ ret = priv->ops->port_set_efid(priv, port, 0);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to clear EFID on port %d: %pe\n",
+ port, ERR_PTR(ret));
+ }
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_leave, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_fast_age() - flush dynamic FDB entries learned on a port
+ * @ds: DSA switch instance
+ * @port: port index
+ *
+ * This function requests the switch to age out dynamic FDB entries learned on
+ * @port.
+ *
+ * Context: Can sleep.
+ * Return: Nothing.
+ */
+void rtl83xx_port_fast_age(struct dsa_switch *ds, int port)
+{
+ struct realtek_priv *priv = ds->priv;
+ int ret;
+
+ if (!priv->ops->l2_flush) {
+ dev_warn_once(priv->dev, "l2_flush op not defined\n");
+ return;
+ }
+
+ dev_dbg(priv->dev, "fast_age port %d\n", port);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_flush(priv, port, 0);
+ mutex_unlock(&priv->l2_lock);
+ if (ret)
+ dev_err(priv->dev, "failed to fast age on port %d: %d\n", port,
+ ret);
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fast_age, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_fdb_add() - add a static FDB entry to a port database
+ * @ds: DSA switch instance
+ * @port: port index
+ * @addr: MAC address to add
+ * @vid: VLAN ID associated with @addr
+ * @db: database where the entry should be added
+ *
+ * This function adds a static unicast FDB entry to the standalone port
+ * database or to a bridge database.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct realtek_priv *priv = ds->priv;
+ int efid;
+ int ret;
+
+ if (is_multicast_ether_addr(addr))
+ return -EOPNOTSUPP;
+
+ if (!priv->ops->l2_add_uc)
+ return -EOPNOTSUPP;
+
+ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
+ return -EOPNOTSUPP;
+
+ /* Bridge ports use bridge.num as EFID, while standalone ports use
+ * EFID 0. FDB entries for the CPU port follow the bridge EFID due
+ * to assisted learning.
+ */
+ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
+
+ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n",
+ __func__, port, addr, efid, vid, db.type);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_add_uc(priv, port, addr, efid, vid);
+
+ mutex_unlock(&priv->l2_lock);
+
+ if (ret)
+ dev_err(priv->dev, "fdb_add ERROR %pe\n", ERR_PTR(ret));
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_add, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_fdb_del() - delete a static FDB entry from a port database
+ * @ds: DSA switch instance
+ * @port: port index
+ * @addr: MAC address to delete
+ * @vid: VLAN ID associated with @addr
+ * @db: database where the entry should be removed
+ *
+ * This function deletes a static unicast FDB entry from the standalone port
+ * database or from a bridge database.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct realtek_priv *priv = ds->priv;
+ int efid;
+ int ret;
+
+ if (is_multicast_ether_addr(addr))
+ return -EOPNOTSUPP;
+
+ if (!priv->ops->l2_del_uc)
+ return -EOPNOTSUPP;
+
+ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
+ return -EOPNOTSUPP;
+
+ /*
+ * DSA_DB_BRIDGE ports use bridge number [1..N] as EFID, while
+ * DSA_DB_PORT use the default EFID (0), not used by any bridge.
+ */
+ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
+
+ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n",
+ __func__, port, addr, efid, vid, db.type);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_del_uc(priv, port, addr, efid, vid);
+ mutex_unlock(&priv->l2_lock);
+
+ if (ret)
+ dev_err(priv->dev, "fdb_del ERROR %pe\n", ERR_PTR(ret));
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_del, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_fdb_dump() - iterate over FDB entries associated with a port
+ * @ds: DSA switch instance
+ * @port: port index
+ * @cb: callback invoked for each entry
+ * @data: opaque pointer passed to @cb
+ *
+ * This function walks the unicast FDB entries associated with @port and calls
+ * @cb for each matching entry.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, or negative value for failure.
+ */
+int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port,
+ dsa_fdb_dump_cb_t *cb, void *data)
+{
+ struct realtek_fdb_entry entry = { 0 };
+ struct realtek_priv *priv = ds->priv;
+ u16 start_addr, addr = 0;
+ int ret = 0;
+
+ if (!priv->ops->l2_get_next_uc)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&priv->l2_lock);
+ while (true) {
+ start_addr = addr;
+
+ dev_dbg(priv->dev, "l2_get_next_uc, addr:%d, port:%d\n",
+ addr, port);
+ ret = priv->ops->l2_get_next_uc(priv, &addr, port, &entry);
+ dev_dbg(priv->dev,
+ "%s addr:%d mac:%pM vid:%d static:%d ret:%pe\n",
+ __func__, addr, entry.mac_addr, entry.vid,
+ entry.is_static, ERR_PTR(ret));
+
+ if (ret == -ENOENT) {
+ /* If the table is empty, returns without errors. Note
+ * that the l2_get_next_uc overflow to the first match
+ * when it reaches the end of the table.
+ */
+ ret = 0;
+ break;
+ }
+
+ if (ret)
+ break;
+
+ /* When the addr returned is before the requested one, it
+ * indicates that we reached the end.
+ */
+ if (addr < start_addr)
+ break;
+
+ ret = cb(entry.mac_addr, entry.vid, entry.is_static, data);
+ if (ret)
+ break;
+
+ addr++;
+ }
+ mutex_unlock(&priv->l2_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_dump, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_mdb_add() - add a multicast database entry to a port database
+ * @ds: DSA switch instance
+ * @port: port index
+ * @mdb: multicast database entry to add
+ * @db: database where the entry should be added
+ *
+ * This function adds a multicast database entry to the standalone port
+ * database or to a bridge database.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ struct realtek_priv *priv = ds->priv;
+ const unsigned char *addr = mdb->addr;
+ u16 vid = mdb->vid;
+ int efid;
+ int ret;
+
+ if (!priv->ops->l2_add_mc)
+ return -EOPNOTSUPP;
+
+ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
+ return -EOPNOTSUPP;
+
+ /* EFID is not used by hardware MDB entries; debugging only */
+ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
+
+ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n",
+ __func__, port, addr, efid, vid, db.type);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_add_mc(priv, port, addr, vid);
+ mutex_unlock(&priv->l2_lock);
+
+ if (ret)
+ dev_err(priv->dev, "mdb_add ERROR %pe\n", ERR_PTR(ret));
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_add, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_mdb_del() - delete a multicast database entry from a port
+ * database
+ * @ds: DSA switch instance
+ * @port: port index
+ * @mdb: multicast database entry to delete
+ * @db: database where the entry should be removed
+ *
+ * This function deletes a multicast database entry from the standalone port
+ * database or from a bridge database.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ struct realtek_priv *priv = ds->priv;
+ const unsigned char *addr = mdb->addr;
+ u16 vid = mdb->vid;
+ int efid;
+ int ret;
+
+ if (!priv->ops->l2_del_mc)
+ return -EOPNOTSUPP;
+
+ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
+ return -EOPNOTSUPP;
+
+ /* EFID is not used by hardware MDB entries; debugging only */
+ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
+
+ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n",
+ __func__, port, addr, efid, vid, db.type);
+
+ mutex_lock(&priv->l2_lock);
+ ret = priv->ops->l2_del_mc(priv, port, addr, vid);
+ mutex_unlock(&priv->l2_lock);
+
+ if (ret)
+ dev_err(priv->dev, "mdb_del ERROR %pe\n", ERR_PTR(ret));
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_del, "REALTEK_DSA");
+
+/**
+ * rtl83xx_port_bridge_flags() - set port bridge flags
+ * @ds: DSA switch instance
+ * @port: port index
+ * @flags: bridge port flags
+ * @extack: netlink extended ack for reporting errors
+ *
+ * This function handles setting bridge port flags like learning and flooding.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port,
+ struct switchdev_brport_flags flags,
+ struct netlink_ext_ack *extack)
+{
+ struct realtek_priv *priv = ds->priv;
+ bool enable;
+ int ret;
+
+ if (flags.mask & BR_LEARNING) {
+ if (!priv->ops->port_set_learning)
+ return -EOPNOTSUPP;
+
+ enable = !!(flags.val & BR_LEARNING);
+ ret = priv->ops->port_set_learning(priv, port, enable);
+ if (ret)
+ return ret;
+ }
+
+ if (flags.mask & BR_FLOOD) {
+ if (!priv->ops->port_set_ucast_flood)
+ return -EOPNOTSUPP;
+
+ enable = !!(flags.val & BR_FLOOD);
+ ret = priv->ops->port_set_ucast_flood(priv, port, enable);
+ if (ret)
+ return ret;
+ }
+
+ if (flags.mask & BR_MCAST_FLOOD) {
+ if (!priv->ops->port_set_mcast_flood)
+ return -EOPNOTSUPP;
+
+ enable = !!(flags.val & BR_MCAST_FLOOD);
+ ret = priv->ops->port_set_mcast_flood(priv, port, enable);
+ if (ret)
+ return ret;
+ }
+
+ if (flags.mask & BR_BCAST_FLOOD) {
+ if (!priv->ops->port_set_bcast_flood)
+ return -EOPNOTSUPP;
+
+ enable = !!(flags.val & BR_BCAST_FLOOD);
+ ret = priv->ops->port_set_bcast_flood(priv, port, enable);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_flags, "REALTEK_DSA");
+
+/**
+ * rtl83xx_setup_port_flood_control() - setup default flood control for a port
+ * @priv: realtek_priv pointer
+ * @port: port index
+ *
+ * This function enables flooding for a given port.
+ *
+ * Context: Can sleep.
+ * Return: 0 on success, negative value for failure.
+ */
+int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port)
+{
+ int ret;
+
+ if (priv->ops->port_set_ucast_flood) {
+ ret = priv->ops->port_set_ucast_flood(priv, port, true);
+ if (ret)
+ return ret;
+ }
+
+ if (priv->ops->port_set_mcast_flood) {
+ ret = priv->ops->port_set_mcast_flood(priv, port, true);
+ if (ret)
+ return ret;
+ }
+
+ if (priv->ops->port_set_bcast_flood) {
+ ret = priv->ops->port_set_bcast_flood(priv, port, true);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_setup_port_flood_control, "REALTEK_DSA");
MODULE_AUTHOR("Luiz Angelo Daros de Luca <luizluca@gmail.com>");
MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
diff --git a/drivers/net/dsa/realtek/rtl83xx.h b/drivers/net/dsa/realtek/rtl83xx.h
index c8a0ff8fd75e..d86447121276 100644
--- a/drivers/net/dsa/realtek/rtl83xx.h
+++ b/drivers/net/dsa/realtek/rtl83xx.h
@@ -21,4 +21,31 @@ void rtl83xx_remove(struct realtek_priv *priv);
void rtl83xx_reset_assert(struct realtek_priv *priv);
void rtl83xx_reset_deassert(struct realtek_priv *priv);
+int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge,
+ bool *tx_forward_offload,
+ struct netlink_ext_ack *extack);
+void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge);
+int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port,
+ struct switchdev_brport_flags flags,
+ struct netlink_ext_ack *extack);
+int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port);
+
+void rtl83xx_port_fast_age(struct dsa_switch *ds, int port);
+int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db);
+int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db);
+int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port,
+ dsa_fdb_dump_cb_t *cb, void *data);
+int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db);
+int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db);
+
#endif /* _RTL83XX_H */