#include "realtek-smi.h"
#include "realtek-mdio.h"
#include "rtl83xx.h"
+#include "rtl8365mb_vlan.h"
/* Family-specific data and limits */
#define RTL8365MB_PHYADDRMAX 7
#define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \
(0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport)))
+/* 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))
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)
{
if (ret)
goto out_teardown_irq;
+ 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");
.teardown = rtl8365mb_teardown,
.phylink_get_caps = rtl8365mb_phylink_get_caps,
.port_stp_state_set = rtl8365mb_port_stp_state_set,
+ .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,
--- /dev/null
+// 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;
+}