--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/* Microchip Sparx5 Switch driver
+ *
+ * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
+ */
+
+#include "sparx5_main_regs.h"
+#include "sparx5_main.h"
+
+static int sparx5_vlant_set_mask(struct sparx5 *sparx5, u16 vid)
+{
+       u32 mask[3];
+
+       /* Divide up mask in 32 bit words */
+       bitmap_to_arr32(mask, sparx5->vlan_mask[vid], SPX5_PORTS);
+
+       /* Output mask to respective registers */
+       spx5_wr(mask[0], sparx5, ANA_L3_VLAN_MASK_CFG(vid));
+       spx5_wr(mask[1], sparx5, ANA_L3_VLAN_MASK_CFG1(vid));
+       spx5_wr(mask[2], sparx5, ANA_L3_VLAN_MASK_CFG2(vid));
+
+       return 0;
+}
+
+void sparx5_vlan_init(struct sparx5 *sparx5)
+{
+       u16 vid;
+
+       spx5_rmw(ANA_L3_VLAN_CTRL_VLAN_ENA_SET(1),
+                ANA_L3_VLAN_CTRL_VLAN_ENA,
+                sparx5,
+                ANA_L3_VLAN_CTRL);
+
+       /* Map VLAN = FID */
+       for (vid = NULL_VID; vid < VLAN_N_VID; vid++)
+               spx5_rmw(ANA_L3_VLAN_CFG_VLAN_FID_SET(vid),
+                        ANA_L3_VLAN_CFG_VLAN_FID,
+                        sparx5,
+                        ANA_L3_VLAN_CFG(vid));
+}
+
+void sparx5_vlan_port_setup(struct sparx5 *sparx5, int portno)
+{
+       struct sparx5_port *port = sparx5->ports[portno];
+
+       /* Configure PVID */
+       spx5_rmw(ANA_CL_VLAN_CTRL_VLAN_AWARE_ENA_SET(0) |
+                ANA_CL_VLAN_CTRL_PORT_VID_SET(port->pvid),
+                ANA_CL_VLAN_CTRL_VLAN_AWARE_ENA |
+                ANA_CL_VLAN_CTRL_PORT_VID,
+                sparx5,
+                ANA_CL_VLAN_CTRL(port->portno));
+}
+
+int sparx5_vlan_vid_add(struct sparx5_port *port, u16 vid, bool pvid,
+                       bool untagged)
+{
+       struct sparx5 *sparx5 = port->sparx5;
+       int ret;
+
+       /* Make the port a member of the VLAN */
+       set_bit(port->portno, sparx5->vlan_mask[vid]);
+       ret = sparx5_vlant_set_mask(sparx5, vid);
+       if (ret)
+               return ret;
+
+       /* Default ingress vlan classification */
+       if (pvid)
+               port->pvid = vid;
+
+       /* Untagged egress vlan classification */
+       if (untagged && port->vid != vid) {
+               if (port->vid) {
+                       netdev_err(port->ndev,
+                                  "Port already has a native VLAN: %d\n",
+                                  port->vid);
+                       return -EBUSY;
+               }
+               port->vid = vid;
+       }
+
+       sparx5_vlan_port_apply(sparx5, port);
+
+       return 0;
+}
+
+int sparx5_vlan_vid_del(struct sparx5_port *port, u16 vid)
+{
+       struct sparx5 *sparx5 = port->sparx5;
+       int ret;
+
+       /* 8021q removes VID 0 on module unload for all interfaces
+        * with VLAN filtering feature. We need to keep it to receive
+        * untagged traffic.
+        */
+       if (vid == 0)
+               return 0;
+
+       /* Stop the port from being a member of the vlan */
+       clear_bit(port->portno, sparx5->vlan_mask[vid]);
+       ret = sparx5_vlant_set_mask(sparx5, vid);
+       if (ret)
+               return ret;
+
+       /* Ingress */
+       if (port->pvid == vid)
+               port->pvid = 0;
+
+       /* Egress */
+       if (port->vid == vid)
+               port->vid = 0;
+
+       sparx5_vlan_port_apply(sparx5, port);
+
+       return 0;
+}
+
+void sparx5_pgid_update_mask(struct sparx5_port *port, int pgid, bool enable)
+{
+       struct sparx5 *sparx5 = port->sparx5;
+       u32 val, mask;
+
+       /* mask is spread across 3 registers x 32 bit */
+       if (port->portno < 32) {
+               mask = BIT(port->portno);
+               val = enable ? mask : 0;
+               spx5_rmw(val, mask, sparx5, ANA_AC_PGID_CFG(pgid));
+       } else if (port->portno < 64) {
+               mask = BIT(port->portno - 32);
+               val = enable ? mask : 0;
+               spx5_rmw(val, mask, sparx5, ANA_AC_PGID_CFG1(pgid));
+       } else if (port->portno < SPX5_PORTS) {
+               mask = BIT(port->portno - 64);
+               val = enable ? mask : 0;
+               spx5_rmw(val, mask, sparx5, ANA_AC_PGID_CFG2(pgid));
+       } else {
+               netdev_err(port->ndev, "Invalid port no: %d\n", port->portno);
+       }
+}
+
+void sparx5_update_fwd(struct sparx5 *sparx5)
+{
+       DECLARE_BITMAP(workmask, SPX5_PORTS);
+       u32 mask[3];
+       int port;
+
+       /* Divide up fwd mask in 32 bit words */
+       bitmap_to_arr32(mask, sparx5->bridge_fwd_mask, SPX5_PORTS);
+
+       /* Update flood masks */
+       for (port = PGID_UC_FLOOD; port <= PGID_BCAST; port++) {
+               spx5_wr(mask[0], sparx5, ANA_AC_PGID_CFG(port));
+               spx5_wr(mask[1], sparx5, ANA_AC_PGID_CFG1(port));
+               spx5_wr(mask[2], sparx5, ANA_AC_PGID_CFG2(port));
+       }
+
+       /* Update SRC masks */
+       for (port = 0; port < SPX5_PORTS; port++) {
+               if (test_bit(port, sparx5->bridge_fwd_mask)) {
+                       /* Allow to send to all bridged but self */
+                       bitmap_copy(workmask, sparx5->bridge_fwd_mask, SPX5_PORTS);
+                       clear_bit(port, workmask);
+                       bitmap_to_arr32(mask, workmask, SPX5_PORTS);
+                       spx5_wr(mask[0], sparx5, ANA_AC_SRC_CFG(port));
+                       spx5_wr(mask[1], sparx5, ANA_AC_SRC_CFG1(port));
+                       spx5_wr(mask[2], sparx5, ANA_AC_SRC_CFG2(port));
+               } else {
+                       spx5_wr(0, sparx5, ANA_AC_SRC_CFG(port));
+                       spx5_wr(0, sparx5, ANA_AC_SRC_CFG1(port));
+                       spx5_wr(0, sparx5, ANA_AC_SRC_CFG2(port));
+               }
+       }
+
+       /* Learning enabled only for bridged ports */
+       bitmap_and(workmask, sparx5->bridge_fwd_mask,
+                  sparx5->bridge_lrn_mask, SPX5_PORTS);
+       bitmap_to_arr32(mask, workmask, SPX5_PORTS);
+
+       /* Apply learning mask */
+       spx5_wr(mask[0], sparx5, ANA_L2_AUTO_LRN_CFG);
+       spx5_wr(mask[1], sparx5, ANA_L2_AUTO_LRN_CFG1);
+       spx5_wr(mask[2], sparx5, ANA_L2_AUTO_LRN_CFG2);
+}
+
+void sparx5_vlan_port_apply(struct sparx5 *sparx5,
+                           struct sparx5_port *port)
+
+{
+       u32 val;
+
+       /* Configure PVID, vlan aware */
+       val = ANA_CL_VLAN_CTRL_VLAN_AWARE_ENA_SET(port->vlan_aware) |
+               ANA_CL_VLAN_CTRL_VLAN_POP_CNT_SET(port->vlan_aware) |
+               ANA_CL_VLAN_CTRL_PORT_VID_SET(port->pvid);
+       spx5_wr(val, sparx5, ANA_CL_VLAN_CTRL(port->portno));
+
+       val = 0;
+       if (port->vlan_aware && !port->pvid)
+               /* If port is vlan-aware and tagged, drop untagged and
+                * priority tagged frames.
+                */
+               val = ANA_CL_VLAN_FILTER_CTRL_TAG_REQUIRED_ENA_SET(1) |
+                       ANA_CL_VLAN_FILTER_CTRL_PRIO_CTAG_DIS_SET(1) |
+                       ANA_CL_VLAN_FILTER_CTRL_PRIO_STAG_DIS_SET(1);
+       spx5_wr(val, sparx5,
+               ANA_CL_VLAN_FILTER_CTRL(port->portno, 0));
+
+       /* Egress configuration (REW_TAG_CFG): VLAN tag type to 8021Q */
+       val = REW_TAG_CTRL_TAG_TPID_CFG_SET(0);
+       if (port->vlan_aware) {
+               if (port->vid)
+                       /* Tag all frames except when VID == DEFAULT_VLAN */
+                       val |= REW_TAG_CTRL_TAG_CFG_SET(1);
+               else
+                       val |= REW_TAG_CTRL_TAG_CFG_SET(3);
+       }
+       spx5_wr(val, sparx5, REW_TAG_CTRL(port->portno));
+
+       /* Egress VID */
+       spx5_rmw(REW_PORT_VLAN_CFG_PORT_VID_SET(port->vid),
+                REW_PORT_VLAN_CFG_PORT_VID,
+                sparx5,
+                REW_PORT_VLAN_CFG(port->portno));
+}