162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/* Microchip Sparx5 Switch driver
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include "sparx5_main_regs.h"
862306a36Sopenharmony_ci#include "sparx5_main.h"
962306a36Sopenharmony_ci
1062306a36Sopenharmony_cistatic int sparx5_vlant_set_mask(struct sparx5 *sparx5, u16 vid)
1162306a36Sopenharmony_ci{
1262306a36Sopenharmony_ci	u32 mask[3];
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci	/* Divide up mask in 32 bit words */
1562306a36Sopenharmony_ci	bitmap_to_arr32(mask, sparx5->vlan_mask[vid], SPX5_PORTS);
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci	/* Output mask to respective registers */
1862306a36Sopenharmony_ci	spx5_wr(mask[0], sparx5, ANA_L3_VLAN_MASK_CFG(vid));
1962306a36Sopenharmony_ci	spx5_wr(mask[1], sparx5, ANA_L3_VLAN_MASK_CFG1(vid));
2062306a36Sopenharmony_ci	spx5_wr(mask[2], sparx5, ANA_L3_VLAN_MASK_CFG2(vid));
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci	return 0;
2362306a36Sopenharmony_ci}
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_civoid sparx5_vlan_init(struct sparx5 *sparx5)
2662306a36Sopenharmony_ci{
2762306a36Sopenharmony_ci	u16 vid;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	spx5_rmw(ANA_L3_VLAN_CTRL_VLAN_ENA_SET(1),
3062306a36Sopenharmony_ci		 ANA_L3_VLAN_CTRL_VLAN_ENA,
3162306a36Sopenharmony_ci		 sparx5,
3262306a36Sopenharmony_ci		 ANA_L3_VLAN_CTRL);
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	/* Map VLAN = FID */
3562306a36Sopenharmony_ci	for (vid = NULL_VID; vid < VLAN_N_VID; vid++)
3662306a36Sopenharmony_ci		spx5_rmw(ANA_L3_VLAN_CFG_VLAN_FID_SET(vid),
3762306a36Sopenharmony_ci			 ANA_L3_VLAN_CFG_VLAN_FID,
3862306a36Sopenharmony_ci			 sparx5,
3962306a36Sopenharmony_ci			 ANA_L3_VLAN_CFG(vid));
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_civoid sparx5_vlan_port_setup(struct sparx5 *sparx5, int portno)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	struct sparx5_port *port = sparx5->ports[portno];
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	/* Configure PVID */
4762306a36Sopenharmony_ci	spx5_rmw(ANA_CL_VLAN_CTRL_VLAN_AWARE_ENA_SET(0) |
4862306a36Sopenharmony_ci		 ANA_CL_VLAN_CTRL_PORT_VID_SET(port->pvid),
4962306a36Sopenharmony_ci		 ANA_CL_VLAN_CTRL_VLAN_AWARE_ENA |
5062306a36Sopenharmony_ci		 ANA_CL_VLAN_CTRL_PORT_VID,
5162306a36Sopenharmony_ci		 sparx5,
5262306a36Sopenharmony_ci		 ANA_CL_VLAN_CTRL(port->portno));
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ciint sparx5_vlan_vid_add(struct sparx5_port *port, u16 vid, bool pvid,
5662306a36Sopenharmony_ci			bool untagged)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	struct sparx5 *sparx5 = port->sparx5;
5962306a36Sopenharmony_ci	int ret;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	/* Untagged egress vlan classification */
6262306a36Sopenharmony_ci	if (untagged && port->vid != vid) {
6362306a36Sopenharmony_ci		if (port->vid) {
6462306a36Sopenharmony_ci			netdev_err(port->ndev,
6562306a36Sopenharmony_ci				   "Port already has a native VLAN: %d\n",
6662306a36Sopenharmony_ci				   port->vid);
6762306a36Sopenharmony_ci			return -EBUSY;
6862306a36Sopenharmony_ci		}
6962306a36Sopenharmony_ci		port->vid = vid;
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	/* Make the port a member of the VLAN */
7362306a36Sopenharmony_ci	set_bit(port->portno, sparx5->vlan_mask[vid]);
7462306a36Sopenharmony_ci	ret = sparx5_vlant_set_mask(sparx5, vid);
7562306a36Sopenharmony_ci	if (ret)
7662306a36Sopenharmony_ci		return ret;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	/* Default ingress vlan classification */
7962306a36Sopenharmony_ci	if (pvid)
8062306a36Sopenharmony_ci		port->pvid = vid;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	sparx5_vlan_port_apply(sparx5, port);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	return 0;
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ciint sparx5_vlan_vid_del(struct sparx5_port *port, u16 vid)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	struct sparx5 *sparx5 = port->sparx5;
9062306a36Sopenharmony_ci	int ret;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	/* 8021q removes VID 0 on module unload for all interfaces
9362306a36Sopenharmony_ci	 * with VLAN filtering feature. We need to keep it to receive
9462306a36Sopenharmony_ci	 * untagged traffic.
9562306a36Sopenharmony_ci	 */
9662306a36Sopenharmony_ci	if (vid == 0)
9762306a36Sopenharmony_ci		return 0;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	/* Stop the port from being a member of the vlan */
10062306a36Sopenharmony_ci	clear_bit(port->portno, sparx5->vlan_mask[vid]);
10162306a36Sopenharmony_ci	ret = sparx5_vlant_set_mask(sparx5, vid);
10262306a36Sopenharmony_ci	if (ret)
10362306a36Sopenharmony_ci		return ret;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	/* Ingress */
10662306a36Sopenharmony_ci	if (port->pvid == vid)
10762306a36Sopenharmony_ci		port->pvid = 0;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	/* Egress */
11062306a36Sopenharmony_ci	if (port->vid == vid)
11162306a36Sopenharmony_ci		port->vid = 0;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	sparx5_vlan_port_apply(sparx5, port);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	return 0;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_civoid sparx5_pgid_update_mask(struct sparx5_port *port, int pgid, bool enable)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	struct sparx5 *sparx5 = port->sparx5;
12162306a36Sopenharmony_ci	u32 val, mask;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	/* mask is spread across 3 registers x 32 bit */
12462306a36Sopenharmony_ci	if (port->portno < 32) {
12562306a36Sopenharmony_ci		mask = BIT(port->portno);
12662306a36Sopenharmony_ci		val = enable ? mask : 0;
12762306a36Sopenharmony_ci		spx5_rmw(val, mask, sparx5, ANA_AC_PGID_CFG(pgid));
12862306a36Sopenharmony_ci	} else if (port->portno < 64) {
12962306a36Sopenharmony_ci		mask = BIT(port->portno - 32);
13062306a36Sopenharmony_ci		val = enable ? mask : 0;
13162306a36Sopenharmony_ci		spx5_rmw(val, mask, sparx5, ANA_AC_PGID_CFG1(pgid));
13262306a36Sopenharmony_ci	} else if (port->portno < SPX5_PORTS) {
13362306a36Sopenharmony_ci		mask = BIT(port->portno - 64);
13462306a36Sopenharmony_ci		val = enable ? mask : 0;
13562306a36Sopenharmony_ci		spx5_rmw(val, mask, sparx5, ANA_AC_PGID_CFG2(pgid));
13662306a36Sopenharmony_ci	} else {
13762306a36Sopenharmony_ci		netdev_err(port->ndev, "Invalid port no: %d\n", port->portno);
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_civoid sparx5_pgid_clear(struct sparx5 *spx5, int pgid)
14262306a36Sopenharmony_ci{
14362306a36Sopenharmony_ci	spx5_wr(0, spx5, ANA_AC_PGID_CFG(pgid));
14462306a36Sopenharmony_ci	spx5_wr(0, spx5, ANA_AC_PGID_CFG1(pgid));
14562306a36Sopenharmony_ci	spx5_wr(0, spx5, ANA_AC_PGID_CFG2(pgid));
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_civoid sparx5_pgid_read_mask(struct sparx5 *spx5, int pgid, u32 portmask[3])
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	portmask[0] = spx5_rd(spx5, ANA_AC_PGID_CFG(pgid));
15162306a36Sopenharmony_ci	portmask[1] = spx5_rd(spx5, ANA_AC_PGID_CFG1(pgid));
15262306a36Sopenharmony_ci	portmask[2] = spx5_rd(spx5, ANA_AC_PGID_CFG2(pgid));
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_civoid sparx5_update_fwd(struct sparx5 *sparx5)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	DECLARE_BITMAP(workmask, SPX5_PORTS);
15862306a36Sopenharmony_ci	u32 mask[3];
15962306a36Sopenharmony_ci	int port;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	/* Divide up fwd mask in 32 bit words */
16262306a36Sopenharmony_ci	bitmap_to_arr32(mask, sparx5->bridge_fwd_mask, SPX5_PORTS);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	/* Update flood masks */
16562306a36Sopenharmony_ci	for (port = PGID_UC_FLOOD; port <= PGID_BCAST; port++) {
16662306a36Sopenharmony_ci		spx5_wr(mask[0], sparx5, ANA_AC_PGID_CFG(port));
16762306a36Sopenharmony_ci		spx5_wr(mask[1], sparx5, ANA_AC_PGID_CFG1(port));
16862306a36Sopenharmony_ci		spx5_wr(mask[2], sparx5, ANA_AC_PGID_CFG2(port));
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	/* Update SRC masks */
17262306a36Sopenharmony_ci	for (port = 0; port < SPX5_PORTS; port++) {
17362306a36Sopenharmony_ci		if (test_bit(port, sparx5->bridge_fwd_mask)) {
17462306a36Sopenharmony_ci			/* Allow to send to all bridged but self */
17562306a36Sopenharmony_ci			bitmap_copy(workmask, sparx5->bridge_fwd_mask, SPX5_PORTS);
17662306a36Sopenharmony_ci			clear_bit(port, workmask);
17762306a36Sopenharmony_ci			bitmap_to_arr32(mask, workmask, SPX5_PORTS);
17862306a36Sopenharmony_ci			spx5_wr(mask[0], sparx5, ANA_AC_SRC_CFG(port));
17962306a36Sopenharmony_ci			spx5_wr(mask[1], sparx5, ANA_AC_SRC_CFG1(port));
18062306a36Sopenharmony_ci			spx5_wr(mask[2], sparx5, ANA_AC_SRC_CFG2(port));
18162306a36Sopenharmony_ci		} else {
18262306a36Sopenharmony_ci			spx5_wr(0, sparx5, ANA_AC_SRC_CFG(port));
18362306a36Sopenharmony_ci			spx5_wr(0, sparx5, ANA_AC_SRC_CFG1(port));
18462306a36Sopenharmony_ci			spx5_wr(0, sparx5, ANA_AC_SRC_CFG2(port));
18562306a36Sopenharmony_ci		}
18662306a36Sopenharmony_ci	}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	/* Learning enabled only for bridged ports */
18962306a36Sopenharmony_ci	bitmap_and(workmask, sparx5->bridge_fwd_mask,
19062306a36Sopenharmony_ci		   sparx5->bridge_lrn_mask, SPX5_PORTS);
19162306a36Sopenharmony_ci	bitmap_to_arr32(mask, workmask, SPX5_PORTS);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	/* Apply learning mask */
19462306a36Sopenharmony_ci	spx5_wr(mask[0], sparx5, ANA_L2_AUTO_LRN_CFG);
19562306a36Sopenharmony_ci	spx5_wr(mask[1], sparx5, ANA_L2_AUTO_LRN_CFG1);
19662306a36Sopenharmony_ci	spx5_wr(mask[2], sparx5, ANA_L2_AUTO_LRN_CFG2);
19762306a36Sopenharmony_ci}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_civoid sparx5_vlan_port_apply(struct sparx5 *sparx5,
20062306a36Sopenharmony_ci			    struct sparx5_port *port)
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	u32 val;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	/* Configure PVID, vlan aware */
20662306a36Sopenharmony_ci	val = ANA_CL_VLAN_CTRL_VLAN_AWARE_ENA_SET(port->vlan_aware) |
20762306a36Sopenharmony_ci		ANA_CL_VLAN_CTRL_VLAN_POP_CNT_SET(port->vlan_aware) |
20862306a36Sopenharmony_ci		ANA_CL_VLAN_CTRL_PORT_VID_SET(port->pvid);
20962306a36Sopenharmony_ci	spx5_wr(val, sparx5, ANA_CL_VLAN_CTRL(port->portno));
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	val = 0;
21262306a36Sopenharmony_ci	if (port->vlan_aware && !port->pvid)
21362306a36Sopenharmony_ci		/* If port is vlan-aware and tagged, drop untagged and
21462306a36Sopenharmony_ci		 * priority tagged frames.
21562306a36Sopenharmony_ci		 */
21662306a36Sopenharmony_ci		val = ANA_CL_VLAN_FILTER_CTRL_TAG_REQUIRED_ENA_SET(1) |
21762306a36Sopenharmony_ci			ANA_CL_VLAN_FILTER_CTRL_PRIO_CTAG_DIS_SET(1) |
21862306a36Sopenharmony_ci			ANA_CL_VLAN_FILTER_CTRL_PRIO_STAG_DIS_SET(1);
21962306a36Sopenharmony_ci	spx5_wr(val, sparx5,
22062306a36Sopenharmony_ci		ANA_CL_VLAN_FILTER_CTRL(port->portno, 0));
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	/* Egress configuration (REW_TAG_CFG): VLAN tag selected via IFH */
22362306a36Sopenharmony_ci	val = REW_TAG_CTRL_TAG_TPID_CFG_SET(5);
22462306a36Sopenharmony_ci	if (port->vlan_aware) {
22562306a36Sopenharmony_ci		if (port->vid)
22662306a36Sopenharmony_ci			/* Tag all frames except when VID == DEFAULT_VLAN */
22762306a36Sopenharmony_ci			val |= REW_TAG_CTRL_TAG_CFG_SET(1);
22862306a36Sopenharmony_ci		else
22962306a36Sopenharmony_ci			val |= REW_TAG_CTRL_TAG_CFG_SET(3);
23062306a36Sopenharmony_ci	}
23162306a36Sopenharmony_ci	spx5_wr(val, sparx5, REW_TAG_CTRL(port->portno));
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	/* Egress VID */
23462306a36Sopenharmony_ci	spx5_rmw(REW_PORT_VLAN_CFG_PORT_VID_SET(port->vid),
23562306a36Sopenharmony_ci		 REW_PORT_VLAN_CFG_PORT_VID,
23662306a36Sopenharmony_ci		 sparx5,
23762306a36Sopenharmony_ci		 REW_PORT_VLAN_CFG(port->portno));
23862306a36Sopenharmony_ci}
239