| // 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; |
| |
| /* 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; |
| } |
| |
| /* 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; |
| |
| 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_pgid_clear(struct sparx5 *spx5, int pgid) |
| { |
| spx5_wr(0, spx5, ANA_AC_PGID_CFG(pgid)); |
| spx5_wr(0, spx5, ANA_AC_PGID_CFG1(pgid)); |
| spx5_wr(0, spx5, ANA_AC_PGID_CFG2(pgid)); |
| } |
| |
| void sparx5_pgid_read_mask(struct sparx5 *spx5, int pgid, u32 portmask[3]) |
| { |
| portmask[0] = spx5_rd(spx5, ANA_AC_PGID_CFG(pgid)); |
| portmask[1] = spx5_rd(spx5, ANA_AC_PGID_CFG1(pgid)); |
| portmask[2] = spx5_rd(spx5, ANA_AC_PGID_CFG2(pgid)); |
| } |
| |
| 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 selected via IFH */ |
| val = REW_TAG_CTRL_TAG_TPID_CFG_SET(5); |
| 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)); |
| } |