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 <net/switchdev.h>
862306a36Sopenharmony_ci#include <linux/if_bridge.h>
962306a36Sopenharmony_ci#include <linux/iopoll.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include "sparx5_main_regs.h"
1262306a36Sopenharmony_ci#include "sparx5_main.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci/* Commands for Mac Table Command register */
1562306a36Sopenharmony_ci#define MAC_CMD_LEARN         0 /* Insert (Learn) 1 entry */
1662306a36Sopenharmony_ci#define MAC_CMD_UNLEARN       1 /* Unlearn (Forget) 1 entry */
1762306a36Sopenharmony_ci#define MAC_CMD_LOOKUP        2 /* Look up 1 entry */
1862306a36Sopenharmony_ci#define MAC_CMD_READ          3 /* Read entry at Mac Table Index */
1962306a36Sopenharmony_ci#define MAC_CMD_WRITE         4 /* Write entry at Mac Table Index */
2062306a36Sopenharmony_ci#define MAC_CMD_SCAN          5 /* Scan (Age or find next) */
2162306a36Sopenharmony_ci#define MAC_CMD_FIND_SMALLEST 6 /* Get next entry */
2262306a36Sopenharmony_ci#define MAC_CMD_CLEAR_ALL     7 /* Delete all entries in table */
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/* Commands for MAC_ENTRY_ADDR_TYPE */
2562306a36Sopenharmony_ci#define  MAC_ENTRY_ADDR_TYPE_UPSID_PN         0
2662306a36Sopenharmony_ci#define  MAC_ENTRY_ADDR_TYPE_UPSID_CPU_OR_INT 1
2762306a36Sopenharmony_ci#define  MAC_ENTRY_ADDR_TYPE_GLAG             2
2862306a36Sopenharmony_ci#define  MAC_ENTRY_ADDR_TYPE_MC_IDX           3
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define TABLE_UPDATE_SLEEP_US 10
3162306a36Sopenharmony_ci#define TABLE_UPDATE_TIMEOUT_US 100000
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistruct sparx5_mact_entry {
3462306a36Sopenharmony_ci	struct list_head list;
3562306a36Sopenharmony_ci	unsigned char mac[ETH_ALEN];
3662306a36Sopenharmony_ci	u32 flags;
3762306a36Sopenharmony_ci#define MAC_ENT_ALIVE	BIT(0)
3862306a36Sopenharmony_ci#define MAC_ENT_MOVED	BIT(1)
3962306a36Sopenharmony_ci#define MAC_ENT_LOCK	BIT(2)
4062306a36Sopenharmony_ci	u16 vid;
4162306a36Sopenharmony_ci	u16 port;
4262306a36Sopenharmony_ci};
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic int sparx5_mact_get_status(struct sparx5 *sparx5)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	return spx5_rd(sparx5, LRN_COMMON_ACCESS_CTRL);
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic int sparx5_mact_wait_for_completion(struct sparx5 *sparx5)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	u32 val;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	return readx_poll_timeout(sparx5_mact_get_status,
5462306a36Sopenharmony_ci		sparx5, val,
5562306a36Sopenharmony_ci		LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_GET(val) == 0,
5662306a36Sopenharmony_ci		TABLE_UPDATE_SLEEP_US, TABLE_UPDATE_TIMEOUT_US);
5762306a36Sopenharmony_ci}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic void sparx5_mact_select(struct sparx5 *sparx5,
6062306a36Sopenharmony_ci			       const unsigned char mac[ETH_ALEN],
6162306a36Sopenharmony_ci			       u16 vid)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	u32 macl = 0, mach = 0;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	/* Set the MAC address to handle and the vlan associated in a format
6662306a36Sopenharmony_ci	 * understood by the hardware.
6762306a36Sopenharmony_ci	 */
6862306a36Sopenharmony_ci	mach |= vid    << 16;
6962306a36Sopenharmony_ci	mach |= mac[0] << 8;
7062306a36Sopenharmony_ci	mach |= mac[1] << 0;
7162306a36Sopenharmony_ci	macl |= mac[2] << 24;
7262306a36Sopenharmony_ci	macl |= mac[3] << 16;
7362306a36Sopenharmony_ci	macl |= mac[4] << 8;
7462306a36Sopenharmony_ci	macl |= mac[5] << 0;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	spx5_wr(mach, sparx5, LRN_MAC_ACCESS_CFG_0);
7762306a36Sopenharmony_ci	spx5_wr(macl, sparx5, LRN_MAC_ACCESS_CFG_1);
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ciint sparx5_mact_learn(struct sparx5 *sparx5, int pgid,
8162306a36Sopenharmony_ci		      const unsigned char mac[ETH_ALEN], u16 vid)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	int addr, type, ret;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	if (pgid < SPX5_PORTS) {
8662306a36Sopenharmony_ci		type = MAC_ENTRY_ADDR_TYPE_UPSID_PN;
8762306a36Sopenharmony_ci		addr = pgid % 32;
8862306a36Sopenharmony_ci		addr += (pgid / 32) << 5; /* Add upsid */
8962306a36Sopenharmony_ci	} else {
9062306a36Sopenharmony_ci		type = MAC_ENTRY_ADDR_TYPE_MC_IDX;
9162306a36Sopenharmony_ci		addr = pgid - SPX5_PORTS;
9262306a36Sopenharmony_ci	}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	mutex_lock(&sparx5->lock);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	sparx5_mact_select(sparx5, mac, vid);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	/* MAC entry properties */
9962306a36Sopenharmony_ci	spx5_wr(LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_ADDR_SET(addr) |
10062306a36Sopenharmony_ci		LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_ADDR_TYPE_SET(type) |
10162306a36Sopenharmony_ci		LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_VLD_SET(1) |
10262306a36Sopenharmony_ci		LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_LOCKED_SET(1),
10362306a36Sopenharmony_ci		sparx5, LRN_MAC_ACCESS_CFG_2);
10462306a36Sopenharmony_ci	spx5_wr(0, sparx5, LRN_MAC_ACCESS_CFG_3);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	/*  Insert/learn new entry */
10762306a36Sopenharmony_ci	spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_LEARN) |
10862306a36Sopenharmony_ci		LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
10962306a36Sopenharmony_ci		sparx5, LRN_COMMON_ACCESS_CTRL);
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	ret = sparx5_mact_wait_for_completion(sparx5);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	mutex_unlock(&sparx5->lock);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	return ret;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ciint sparx5_mc_unsync(struct net_device *dev, const unsigned char *addr)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	struct sparx5_port *port = netdev_priv(dev);
12162306a36Sopenharmony_ci	struct sparx5 *sparx5 = port->sparx5;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	return sparx5_mact_forget(sparx5, addr, port->pvid);
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ciint sparx5_mc_sync(struct net_device *dev, const unsigned char *addr)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	struct sparx5_port *port = netdev_priv(dev);
12962306a36Sopenharmony_ci	struct sparx5 *sparx5 = port->sparx5;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	return sparx5_mact_learn(sparx5, PGID_CPU, addr, port->pvid);
13262306a36Sopenharmony_ci}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cistatic int sparx5_mact_get(struct sparx5 *sparx5,
13562306a36Sopenharmony_ci			   unsigned char mac[ETH_ALEN],
13662306a36Sopenharmony_ci			   u16 *vid, u32 *pcfg2)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	u32 mach, macl, cfg2;
13962306a36Sopenharmony_ci	int ret = -ENOENT;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	cfg2 = spx5_rd(sparx5, LRN_MAC_ACCESS_CFG_2);
14262306a36Sopenharmony_ci	if (LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_VLD_GET(cfg2)) {
14362306a36Sopenharmony_ci		mach = spx5_rd(sparx5, LRN_MAC_ACCESS_CFG_0);
14462306a36Sopenharmony_ci		macl = spx5_rd(sparx5, LRN_MAC_ACCESS_CFG_1);
14562306a36Sopenharmony_ci		mac[0] = ((mach >> 8)  & 0xff);
14662306a36Sopenharmony_ci		mac[1] = ((mach >> 0)  & 0xff);
14762306a36Sopenharmony_ci		mac[2] = ((macl >> 24) & 0xff);
14862306a36Sopenharmony_ci		mac[3] = ((macl >> 16) & 0xff);
14962306a36Sopenharmony_ci		mac[4] = ((macl >> 8)  & 0xff);
15062306a36Sopenharmony_ci		mac[5] = ((macl >> 0)  & 0xff);
15162306a36Sopenharmony_ci		*vid = mach >> 16;
15262306a36Sopenharmony_ci		*pcfg2 = cfg2;
15362306a36Sopenharmony_ci		ret = 0;
15462306a36Sopenharmony_ci	}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	return ret;
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cibool sparx5_mact_getnext(struct sparx5 *sparx5,
16062306a36Sopenharmony_ci			 unsigned char mac[ETH_ALEN], u16 *vid, u32 *pcfg2)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	u32 cfg2;
16362306a36Sopenharmony_ci	int ret;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	mutex_lock(&sparx5->lock);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	sparx5_mact_select(sparx5, mac, *vid);
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	spx5_wr(LRN_SCAN_NEXT_CFG_SCAN_NEXT_IGNORE_LOCKED_ENA_SET(1) |
17062306a36Sopenharmony_ci		LRN_SCAN_NEXT_CFG_SCAN_NEXT_UNTIL_FOUND_ENA_SET(1),
17162306a36Sopenharmony_ci		sparx5, LRN_SCAN_NEXT_CFG);
17262306a36Sopenharmony_ci	spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET
17362306a36Sopenharmony_ci		(MAC_CMD_FIND_SMALLEST) |
17462306a36Sopenharmony_ci		LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
17562306a36Sopenharmony_ci		sparx5, LRN_COMMON_ACCESS_CTRL);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	ret = sparx5_mact_wait_for_completion(sparx5);
17862306a36Sopenharmony_ci	if (ret == 0) {
17962306a36Sopenharmony_ci		ret = sparx5_mact_get(sparx5, mac, vid, &cfg2);
18062306a36Sopenharmony_ci		if (ret == 0)
18162306a36Sopenharmony_ci			*pcfg2 = cfg2;
18262306a36Sopenharmony_ci	}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	mutex_unlock(&sparx5->lock);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	return ret == 0;
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ciint sparx5_mact_find(struct sparx5 *sparx5,
19062306a36Sopenharmony_ci		     const unsigned char mac[ETH_ALEN], u16 vid, u32 *pcfg2)
19162306a36Sopenharmony_ci{
19262306a36Sopenharmony_ci	int ret;
19362306a36Sopenharmony_ci	u32 cfg2;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	mutex_lock(&sparx5->lock);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	sparx5_mact_select(sparx5, mac, vid);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	/* Issue a lookup command */
20062306a36Sopenharmony_ci	spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_LOOKUP) |
20162306a36Sopenharmony_ci		LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
20262306a36Sopenharmony_ci		sparx5, LRN_COMMON_ACCESS_CTRL);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	ret = sparx5_mact_wait_for_completion(sparx5);
20562306a36Sopenharmony_ci	if (ret == 0) {
20662306a36Sopenharmony_ci		cfg2 = spx5_rd(sparx5, LRN_MAC_ACCESS_CFG_2);
20762306a36Sopenharmony_ci		if (LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_VLD_GET(cfg2))
20862306a36Sopenharmony_ci			*pcfg2 = cfg2;
20962306a36Sopenharmony_ci		else
21062306a36Sopenharmony_ci			ret = -ENOENT;
21162306a36Sopenharmony_ci	}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	mutex_unlock(&sparx5->lock);
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	return ret;
21662306a36Sopenharmony_ci}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ciint sparx5_mact_forget(struct sparx5 *sparx5,
21962306a36Sopenharmony_ci		       const unsigned char mac[ETH_ALEN], u16 vid)
22062306a36Sopenharmony_ci{
22162306a36Sopenharmony_ci	int ret;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	mutex_lock(&sparx5->lock);
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	sparx5_mact_select(sparx5, mac, vid);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	/* Issue an unlearn command */
22862306a36Sopenharmony_ci	spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_UNLEARN) |
22962306a36Sopenharmony_ci		LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
23062306a36Sopenharmony_ci		sparx5, LRN_COMMON_ACCESS_CTRL);
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	ret = sparx5_mact_wait_for_completion(sparx5);
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	mutex_unlock(&sparx5->lock);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	return ret;
23762306a36Sopenharmony_ci}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_cistatic struct sparx5_mact_entry *alloc_mact_entry(struct sparx5 *sparx5,
24062306a36Sopenharmony_ci						  const unsigned char *mac,
24162306a36Sopenharmony_ci						  u16 vid, u16 port_index)
24262306a36Sopenharmony_ci{
24362306a36Sopenharmony_ci	struct sparx5_mact_entry *mact_entry;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	mact_entry = devm_kzalloc(sparx5->dev,
24662306a36Sopenharmony_ci				  sizeof(*mact_entry), GFP_ATOMIC);
24762306a36Sopenharmony_ci	if (!mact_entry)
24862306a36Sopenharmony_ci		return NULL;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	memcpy(mact_entry->mac, mac, ETH_ALEN);
25162306a36Sopenharmony_ci	mact_entry->vid = vid;
25262306a36Sopenharmony_ci	mact_entry->port = port_index;
25362306a36Sopenharmony_ci	return mact_entry;
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cistatic struct sparx5_mact_entry *find_mact_entry(struct sparx5 *sparx5,
25762306a36Sopenharmony_ci						 const unsigned char *mac,
25862306a36Sopenharmony_ci						 u16 vid, u16 port_index)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	struct sparx5_mact_entry *mact_entry;
26162306a36Sopenharmony_ci	struct sparx5_mact_entry *res = NULL;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	mutex_lock(&sparx5->mact_lock);
26462306a36Sopenharmony_ci	list_for_each_entry(mact_entry, &sparx5->mact_entries, list) {
26562306a36Sopenharmony_ci		if (mact_entry->vid == vid &&
26662306a36Sopenharmony_ci		    ether_addr_equal(mac, mact_entry->mac) &&
26762306a36Sopenharmony_ci		    mact_entry->port == port_index) {
26862306a36Sopenharmony_ci			res = mact_entry;
26962306a36Sopenharmony_ci			break;
27062306a36Sopenharmony_ci		}
27162306a36Sopenharmony_ci	}
27262306a36Sopenharmony_ci	mutex_unlock(&sparx5->mact_lock);
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	return res;
27562306a36Sopenharmony_ci}
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_cistatic void sparx5_fdb_call_notifiers(enum switchdev_notifier_type type,
27862306a36Sopenharmony_ci				      const char *mac, u16 vid,
27962306a36Sopenharmony_ci				      struct net_device *dev, bool offloaded)
28062306a36Sopenharmony_ci{
28162306a36Sopenharmony_ci	struct switchdev_notifier_fdb_info info = {};
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	info.addr = mac;
28462306a36Sopenharmony_ci	info.vid = vid;
28562306a36Sopenharmony_ci	info.offloaded = offloaded;
28662306a36Sopenharmony_ci	call_switchdev_notifiers(type, dev, &info.info, NULL);
28762306a36Sopenharmony_ci}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ciint sparx5_add_mact_entry(struct sparx5 *sparx5,
29062306a36Sopenharmony_ci			  struct net_device *dev,
29162306a36Sopenharmony_ci			  u16 portno,
29262306a36Sopenharmony_ci			  const unsigned char *addr, u16 vid)
29362306a36Sopenharmony_ci{
29462306a36Sopenharmony_ci	struct sparx5_mact_entry *mact_entry;
29562306a36Sopenharmony_ci	int ret;
29662306a36Sopenharmony_ci	u32 cfg2;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	ret = sparx5_mact_find(sparx5, addr, vid, &cfg2);
29962306a36Sopenharmony_ci	if (!ret)
30062306a36Sopenharmony_ci		return 0;
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	/* In case the entry already exists, don't add it again to SW,
30362306a36Sopenharmony_ci	 * just update HW, but we need to look in the actual HW because
30462306a36Sopenharmony_ci	 * it is possible for an entry to be learn by HW and before the
30562306a36Sopenharmony_ci	 * mact thread to start the frame will reach CPU and the CPU will
30662306a36Sopenharmony_ci	 * add the entry but without the extern_learn flag.
30762306a36Sopenharmony_ci	 */
30862306a36Sopenharmony_ci	mact_entry = find_mact_entry(sparx5, addr, vid, portno);
30962306a36Sopenharmony_ci	if (mact_entry)
31062306a36Sopenharmony_ci		goto update_hw;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	/* Add the entry in SW MAC table not to get the notification when
31362306a36Sopenharmony_ci	 * SW is pulling again
31462306a36Sopenharmony_ci	 */
31562306a36Sopenharmony_ci	mact_entry = alloc_mact_entry(sparx5, addr, vid, portno);
31662306a36Sopenharmony_ci	if (!mact_entry)
31762306a36Sopenharmony_ci		return -ENOMEM;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	mutex_lock(&sparx5->mact_lock);
32062306a36Sopenharmony_ci	list_add_tail(&mact_entry->list, &sparx5->mact_entries);
32162306a36Sopenharmony_ci	mutex_unlock(&sparx5->mact_lock);
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ciupdate_hw:
32462306a36Sopenharmony_ci	ret = sparx5_mact_learn(sparx5, portno, addr, vid);
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	/* New entry? */
32762306a36Sopenharmony_ci	if (mact_entry->flags == 0) {
32862306a36Sopenharmony_ci		mact_entry->flags |= MAC_ENT_LOCK; /* Don't age this */
32962306a36Sopenharmony_ci		sparx5_fdb_call_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE, addr, vid,
33062306a36Sopenharmony_ci					  dev, true);
33162306a36Sopenharmony_ci	}
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	return ret;
33462306a36Sopenharmony_ci}
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ciint sparx5_del_mact_entry(struct sparx5 *sparx5,
33762306a36Sopenharmony_ci			  const unsigned char *addr,
33862306a36Sopenharmony_ci			  u16 vid)
33962306a36Sopenharmony_ci{
34062306a36Sopenharmony_ci	struct sparx5_mact_entry *mact_entry, *tmp;
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	/* Delete the entry in SW MAC table not to get the notification when
34362306a36Sopenharmony_ci	 * SW is pulling again
34462306a36Sopenharmony_ci	 */
34562306a36Sopenharmony_ci	mutex_lock(&sparx5->mact_lock);
34662306a36Sopenharmony_ci	list_for_each_entry_safe(mact_entry, tmp, &sparx5->mact_entries,
34762306a36Sopenharmony_ci				 list) {
34862306a36Sopenharmony_ci		if ((vid == 0 || mact_entry->vid == vid) &&
34962306a36Sopenharmony_ci		    ether_addr_equal(addr, mact_entry->mac)) {
35062306a36Sopenharmony_ci			sparx5_mact_forget(sparx5, addr, mact_entry->vid);
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci			list_del(&mact_entry->list);
35362306a36Sopenharmony_ci			devm_kfree(sparx5->dev, mact_entry);
35462306a36Sopenharmony_ci		}
35562306a36Sopenharmony_ci	}
35662306a36Sopenharmony_ci	mutex_unlock(&sparx5->mact_lock);
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	return 0;
35962306a36Sopenharmony_ci}
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_cistatic void sparx5_mact_handle_entry(struct sparx5 *sparx5,
36262306a36Sopenharmony_ci				     unsigned char mac[ETH_ALEN],
36362306a36Sopenharmony_ci				     u16 vid, u32 cfg2)
36462306a36Sopenharmony_ci{
36562306a36Sopenharmony_ci	struct sparx5_mact_entry *mact_entry;
36662306a36Sopenharmony_ci	bool found = false;
36762306a36Sopenharmony_ci	u16 port;
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	if (LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_ADDR_TYPE_GET(cfg2) !=
37062306a36Sopenharmony_ci	    MAC_ENTRY_ADDR_TYPE_UPSID_PN)
37162306a36Sopenharmony_ci		return;
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	port = LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_ADDR_GET(cfg2);
37462306a36Sopenharmony_ci	if (port >= SPX5_PORTS)
37562306a36Sopenharmony_ci		return;
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	if (!test_bit(port, sparx5->bridge_mask))
37862306a36Sopenharmony_ci		return;
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	mutex_lock(&sparx5->mact_lock);
38162306a36Sopenharmony_ci	list_for_each_entry(mact_entry, &sparx5->mact_entries, list) {
38262306a36Sopenharmony_ci		if (mact_entry->vid == vid &&
38362306a36Sopenharmony_ci		    ether_addr_equal(mac, mact_entry->mac)) {
38462306a36Sopenharmony_ci			found = true;
38562306a36Sopenharmony_ci			mact_entry->flags |= MAC_ENT_ALIVE;
38662306a36Sopenharmony_ci			if (mact_entry->port != port) {
38762306a36Sopenharmony_ci				dev_warn(sparx5->dev, "Entry move: %d -> %d\n",
38862306a36Sopenharmony_ci					 mact_entry->port, port);
38962306a36Sopenharmony_ci				mact_entry->port = port;
39062306a36Sopenharmony_ci				mact_entry->flags |= MAC_ENT_MOVED;
39162306a36Sopenharmony_ci			}
39262306a36Sopenharmony_ci			/* Entry handled */
39362306a36Sopenharmony_ci			break;
39462306a36Sopenharmony_ci		}
39562306a36Sopenharmony_ci	}
39662306a36Sopenharmony_ci	mutex_unlock(&sparx5->mact_lock);
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	if (found && !(mact_entry->flags & MAC_ENT_MOVED))
39962306a36Sopenharmony_ci		/* Present, not moved */
40062306a36Sopenharmony_ci		return;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	if (!found) {
40362306a36Sopenharmony_ci		/* Entry not found - now add */
40462306a36Sopenharmony_ci		mact_entry = alloc_mact_entry(sparx5, mac, vid, port);
40562306a36Sopenharmony_ci		if (!mact_entry)
40662306a36Sopenharmony_ci			return;
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci		mact_entry->flags |= MAC_ENT_ALIVE;
40962306a36Sopenharmony_ci		mutex_lock(&sparx5->mact_lock);
41062306a36Sopenharmony_ci		list_add_tail(&mact_entry->list, &sparx5->mact_entries);
41162306a36Sopenharmony_ci		mutex_unlock(&sparx5->mact_lock);
41262306a36Sopenharmony_ci	}
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	/* New or moved entry - notify bridge */
41562306a36Sopenharmony_ci	sparx5_fdb_call_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,
41662306a36Sopenharmony_ci				  mac, vid, sparx5->ports[port]->ndev,
41762306a36Sopenharmony_ci				  true);
41862306a36Sopenharmony_ci}
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_civoid sparx5_mact_pull_work(struct work_struct *work)
42162306a36Sopenharmony_ci{
42262306a36Sopenharmony_ci	struct delayed_work *del_work = to_delayed_work(work);
42362306a36Sopenharmony_ci	struct sparx5 *sparx5 = container_of(del_work, struct sparx5,
42462306a36Sopenharmony_ci					     mact_work);
42562306a36Sopenharmony_ci	struct sparx5_mact_entry *mact_entry, *tmp;
42662306a36Sopenharmony_ci	unsigned char mac[ETH_ALEN];
42762306a36Sopenharmony_ci	u32 cfg2;
42862306a36Sopenharmony_ci	u16 vid;
42962306a36Sopenharmony_ci	int ret;
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci	/* Reset MAC entry flags */
43262306a36Sopenharmony_ci	mutex_lock(&sparx5->mact_lock);
43362306a36Sopenharmony_ci	list_for_each_entry(mact_entry, &sparx5->mact_entries, list)
43462306a36Sopenharmony_ci		mact_entry->flags &= MAC_ENT_LOCK;
43562306a36Sopenharmony_ci	mutex_unlock(&sparx5->mact_lock);
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	/* MAIN mac address processing loop */
43862306a36Sopenharmony_ci	vid = 0;
43962306a36Sopenharmony_ci	memset(mac, 0, sizeof(mac));
44062306a36Sopenharmony_ci	do {
44162306a36Sopenharmony_ci		mutex_lock(&sparx5->lock);
44262306a36Sopenharmony_ci		sparx5_mact_select(sparx5, mac, vid);
44362306a36Sopenharmony_ci		spx5_wr(LRN_SCAN_NEXT_CFG_SCAN_NEXT_UNTIL_FOUND_ENA_SET(1),
44462306a36Sopenharmony_ci			sparx5, LRN_SCAN_NEXT_CFG);
44562306a36Sopenharmony_ci		spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET
44662306a36Sopenharmony_ci			(MAC_CMD_FIND_SMALLEST) |
44762306a36Sopenharmony_ci			LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
44862306a36Sopenharmony_ci			sparx5, LRN_COMMON_ACCESS_CTRL);
44962306a36Sopenharmony_ci		ret = sparx5_mact_wait_for_completion(sparx5);
45062306a36Sopenharmony_ci		if (ret == 0)
45162306a36Sopenharmony_ci			ret = sparx5_mact_get(sparx5, mac, &vid, &cfg2);
45262306a36Sopenharmony_ci		mutex_unlock(&sparx5->lock);
45362306a36Sopenharmony_ci		if (ret == 0)
45462306a36Sopenharmony_ci			sparx5_mact_handle_entry(sparx5, mac, vid, cfg2);
45562306a36Sopenharmony_ci	} while (ret == 0);
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	mutex_lock(&sparx5->mact_lock);
45862306a36Sopenharmony_ci	list_for_each_entry_safe(mact_entry, tmp, &sparx5->mact_entries,
45962306a36Sopenharmony_ci				 list) {
46062306a36Sopenharmony_ci		/* If the entry is in HW or permanent, then skip */
46162306a36Sopenharmony_ci		if (mact_entry->flags & (MAC_ENT_ALIVE | MAC_ENT_LOCK))
46262306a36Sopenharmony_ci			continue;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci		sparx5_fdb_call_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,
46562306a36Sopenharmony_ci					  mact_entry->mac, mact_entry->vid,
46662306a36Sopenharmony_ci					  sparx5->ports[mact_entry->port]->ndev,
46762306a36Sopenharmony_ci					  true);
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci		list_del(&mact_entry->list);
47062306a36Sopenharmony_ci		devm_kfree(sparx5->dev, mact_entry);
47162306a36Sopenharmony_ci	}
47262306a36Sopenharmony_ci	mutex_unlock(&sparx5->mact_lock);
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	queue_delayed_work(sparx5->mact_queue, &sparx5->mact_work,
47562306a36Sopenharmony_ci			   SPX5_MACT_PULL_DELAY);
47662306a36Sopenharmony_ci}
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_civoid sparx5_set_ageing(struct sparx5 *sparx5, int msecs)
47962306a36Sopenharmony_ci{
48062306a36Sopenharmony_ci	int value = max(1, msecs / 10); /* unit 10 ms */
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	spx5_rmw(LRN_AUTOAGE_CFG_UNIT_SIZE_SET(2) | /* 10 ms */
48362306a36Sopenharmony_ci		 LRN_AUTOAGE_CFG_PERIOD_VAL_SET(value / 2), /* one bit ageing */
48462306a36Sopenharmony_ci		 LRN_AUTOAGE_CFG_UNIT_SIZE |
48562306a36Sopenharmony_ci		 LRN_AUTOAGE_CFG_PERIOD_VAL,
48662306a36Sopenharmony_ci		 sparx5,
48762306a36Sopenharmony_ci		 LRN_AUTOAGE_CFG(0));
48862306a36Sopenharmony_ci}
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_civoid sparx5_mact_init(struct sparx5 *sparx5)
49162306a36Sopenharmony_ci{
49262306a36Sopenharmony_ci	mutex_init(&sparx5->lock);
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	/*  Flush MAC table */
49562306a36Sopenharmony_ci	spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_CLEAR_ALL) |
49662306a36Sopenharmony_ci		LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
49762306a36Sopenharmony_ci		sparx5, LRN_COMMON_ACCESS_CTRL);
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	if (sparx5_mact_wait_for_completion(sparx5) != 0)
50062306a36Sopenharmony_ci		dev_warn(sparx5->dev, "MAC flush error\n");
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	sparx5_set_ageing(sparx5, BR_DEFAULT_AGEING_TIME / HZ * 1000);
50362306a36Sopenharmony_ci}
504