162306a36Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0 */
262306a36Sopenharmony_ci/* Texas Instruments K3 AM65 Ethernet Switchdev Driver
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2020 Texas Instruments Incorporated - https://www.ti.com/
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/etherdevice.h>
962306a36Sopenharmony_ci#include <linux/if_bridge.h>
1062306a36Sopenharmony_ci#include <linux/netdevice.h>
1162306a36Sopenharmony_ci#include <linux/workqueue.h>
1262306a36Sopenharmony_ci#include <net/switchdev.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include "am65-cpsw-nuss.h"
1562306a36Sopenharmony_ci#include "am65-cpsw-switchdev.h"
1662306a36Sopenharmony_ci#include "cpsw_ale.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistruct am65_cpsw_switchdev_event_work {
1962306a36Sopenharmony_ci	struct work_struct work;
2062306a36Sopenharmony_ci	struct switchdev_notifier_fdb_info fdb_info;
2162306a36Sopenharmony_ci	struct am65_cpsw_port *port;
2262306a36Sopenharmony_ci	unsigned long event;
2362306a36Sopenharmony_ci};
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic int am65_cpsw_port_stp_state_set(struct am65_cpsw_port *port, u8 state)
2662306a36Sopenharmony_ci{
2762306a36Sopenharmony_ci	struct am65_cpsw_common *cpsw = port->common;
2862306a36Sopenharmony_ci	u8 cpsw_state;
2962306a36Sopenharmony_ci	int ret = 0;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	switch (state) {
3262306a36Sopenharmony_ci	case BR_STATE_FORWARDING:
3362306a36Sopenharmony_ci		cpsw_state = ALE_PORT_STATE_FORWARD;
3462306a36Sopenharmony_ci		break;
3562306a36Sopenharmony_ci	case BR_STATE_LEARNING:
3662306a36Sopenharmony_ci		cpsw_state = ALE_PORT_STATE_LEARN;
3762306a36Sopenharmony_ci		break;
3862306a36Sopenharmony_ci	case BR_STATE_DISABLED:
3962306a36Sopenharmony_ci		cpsw_state = ALE_PORT_STATE_DISABLE;
4062306a36Sopenharmony_ci		break;
4162306a36Sopenharmony_ci	case BR_STATE_LISTENING:
4262306a36Sopenharmony_ci	case BR_STATE_BLOCKING:
4362306a36Sopenharmony_ci		cpsw_state = ALE_PORT_STATE_BLOCK;
4462306a36Sopenharmony_ci		break;
4562306a36Sopenharmony_ci	default:
4662306a36Sopenharmony_ci		return -EOPNOTSUPP;
4762306a36Sopenharmony_ci	}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	ret = cpsw_ale_control_set(cpsw->ale, port->port_id,
5062306a36Sopenharmony_ci				   ALE_PORT_STATE, cpsw_state);
5162306a36Sopenharmony_ci	netdev_dbg(port->ndev, "ale state: %u\n", cpsw_state);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	return ret;
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic int am65_cpsw_port_attr_br_flags_set(struct am65_cpsw_port *port,
5762306a36Sopenharmony_ci					    struct net_device *orig_dev,
5862306a36Sopenharmony_ci					    struct switchdev_brport_flags flags)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	struct am65_cpsw_common *cpsw = port->common;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if (flags.mask & BR_MCAST_FLOOD) {
6362306a36Sopenharmony_ci		bool unreg_mcast_add = false;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci		if (flags.val & BR_MCAST_FLOOD)
6662306a36Sopenharmony_ci			unreg_mcast_add = true;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci		netdev_dbg(port->ndev, "BR_MCAST_FLOOD: %d port %u\n",
6962306a36Sopenharmony_ci			   unreg_mcast_add, port->port_id);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci		cpsw_ale_set_unreg_mcast(cpsw->ale, BIT(port->port_id),
7262306a36Sopenharmony_ci					 unreg_mcast_add);
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	return 0;
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic int am65_cpsw_port_attr_br_flags_pre_set(struct net_device *netdev,
7962306a36Sopenharmony_ci						struct switchdev_brport_flags flags)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	if (flags.mask & ~(BR_LEARNING | BR_MCAST_FLOOD))
8262306a36Sopenharmony_ci		return -EINVAL;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	return 0;
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic int am65_cpsw_port_attr_set(struct net_device *ndev, const void *ctx,
8862306a36Sopenharmony_ci				   const struct switchdev_attr *attr,
8962306a36Sopenharmony_ci				   struct netlink_ext_ack *extack)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	struct am65_cpsw_port *port = am65_ndev_to_port(ndev);
9262306a36Sopenharmony_ci	int ret;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	netdev_dbg(ndev, "attr: id %u port: %u\n", attr->id, port->port_id);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	switch (attr->id) {
9762306a36Sopenharmony_ci	case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
9862306a36Sopenharmony_ci		ret = am65_cpsw_port_attr_br_flags_pre_set(ndev,
9962306a36Sopenharmony_ci							   attr->u.brport_flags);
10062306a36Sopenharmony_ci		break;
10162306a36Sopenharmony_ci	case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
10262306a36Sopenharmony_ci		ret = am65_cpsw_port_stp_state_set(port, attr->u.stp_state);
10362306a36Sopenharmony_ci		netdev_dbg(ndev, "stp state: %u\n", attr->u.stp_state);
10462306a36Sopenharmony_ci		break;
10562306a36Sopenharmony_ci	case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
10662306a36Sopenharmony_ci		ret = am65_cpsw_port_attr_br_flags_set(port, attr->orig_dev,
10762306a36Sopenharmony_ci						       attr->u.brport_flags);
10862306a36Sopenharmony_ci		break;
10962306a36Sopenharmony_ci	default:
11062306a36Sopenharmony_ci		ret = -EOPNOTSUPP;
11162306a36Sopenharmony_ci		break;
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	return ret;
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic u16 am65_cpsw_get_pvid(struct am65_cpsw_port *port)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	struct am65_cpsw_common *cpsw = port->common;
12062306a36Sopenharmony_ci	struct am65_cpsw_host *host_p = am65_common_get_host(cpsw);
12162306a36Sopenharmony_ci	u32 pvid;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	if (port->port_id)
12462306a36Sopenharmony_ci		pvid = readl(port->port_base + AM65_CPSW_PORT_VLAN_REG_OFFSET);
12562306a36Sopenharmony_ci	else
12662306a36Sopenharmony_ci		pvid = readl(host_p->port_base + AM65_CPSW_PORT_VLAN_REG_OFFSET);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	pvid = pvid & 0xfff;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	return pvid;
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic void am65_cpsw_set_pvid(struct am65_cpsw_port *port, u16 vid, bool cfi, u32 cos)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	struct am65_cpsw_common *cpsw = port->common;
13662306a36Sopenharmony_ci	struct am65_cpsw_host *host_p = am65_common_get_host(cpsw);
13762306a36Sopenharmony_ci	u32 pvid;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	pvid = vid;
14062306a36Sopenharmony_ci	pvid |= cfi ? BIT(12) : 0;
14162306a36Sopenharmony_ci	pvid |= (cos & 0x7) << 13;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	if (port->port_id)
14462306a36Sopenharmony_ci		writel(pvid, port->port_base + AM65_CPSW_PORT_VLAN_REG_OFFSET);
14562306a36Sopenharmony_ci	else
14662306a36Sopenharmony_ci		writel(pvid, host_p->port_base + AM65_CPSW_PORT_VLAN_REG_OFFSET);
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_cistatic int am65_cpsw_port_vlan_add(struct am65_cpsw_port *port, bool untag, bool pvid,
15062306a36Sopenharmony_ci				   u16 vid, struct net_device *orig_dev)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	bool cpu_port = netif_is_bridge_master(orig_dev);
15362306a36Sopenharmony_ci	struct am65_cpsw_common *cpsw = port->common;
15462306a36Sopenharmony_ci	int unreg_mcast_mask = 0;
15562306a36Sopenharmony_ci	int reg_mcast_mask = 0;
15662306a36Sopenharmony_ci	int untag_mask = 0;
15762306a36Sopenharmony_ci	int port_mask;
15862306a36Sopenharmony_ci	int ret = 0;
15962306a36Sopenharmony_ci	u32 flags;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	if (cpu_port) {
16262306a36Sopenharmony_ci		port_mask = BIT(HOST_PORT_NUM);
16362306a36Sopenharmony_ci		flags = orig_dev->flags;
16462306a36Sopenharmony_ci		unreg_mcast_mask = port_mask;
16562306a36Sopenharmony_ci	} else {
16662306a36Sopenharmony_ci		port_mask = BIT(port->port_id);
16762306a36Sopenharmony_ci		flags = port->ndev->flags;
16862306a36Sopenharmony_ci	}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	if (flags & IFF_MULTICAST)
17162306a36Sopenharmony_ci		reg_mcast_mask = port_mask;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	if (untag)
17462306a36Sopenharmony_ci		untag_mask = port_mask;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	ret = cpsw_ale_vlan_add_modify(cpsw->ale, vid, port_mask, untag_mask,
17762306a36Sopenharmony_ci				       reg_mcast_mask, unreg_mcast_mask);
17862306a36Sopenharmony_ci	if (ret) {
17962306a36Sopenharmony_ci		netdev_err(port->ndev, "Unable to add vlan\n");
18062306a36Sopenharmony_ci		return ret;
18162306a36Sopenharmony_ci	}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	if (cpu_port)
18462306a36Sopenharmony_ci		cpsw_ale_add_ucast(cpsw->ale, port->slave.mac_addr,
18562306a36Sopenharmony_ci				   HOST_PORT_NUM, ALE_VLAN | ALE_SECURE, vid);
18662306a36Sopenharmony_ci	if (!pvid)
18762306a36Sopenharmony_ci		return ret;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	am65_cpsw_set_pvid(port, vid, 0, 0);
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	netdev_dbg(port->ndev, "VID add: %s: vid:%u ports:%X\n",
19262306a36Sopenharmony_ci		   port->ndev->name, vid, port_mask);
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	return ret;
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistatic int am65_cpsw_port_vlan_del(struct am65_cpsw_port *port, u16 vid,
19862306a36Sopenharmony_ci				   struct net_device *orig_dev)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	bool cpu_port = netif_is_bridge_master(orig_dev);
20162306a36Sopenharmony_ci	struct am65_cpsw_common *cpsw = port->common;
20262306a36Sopenharmony_ci	int port_mask;
20362306a36Sopenharmony_ci	int ret = 0;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	if (cpu_port)
20662306a36Sopenharmony_ci		port_mask = BIT(HOST_PORT_NUM);
20762306a36Sopenharmony_ci	else
20862306a36Sopenharmony_ci		port_mask = BIT(port->port_id);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	ret = cpsw_ale_del_vlan(cpsw->ale, vid, port_mask);
21162306a36Sopenharmony_ci	if (ret != 0)
21262306a36Sopenharmony_ci		return ret;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	/* We don't care for the return value here, error is returned only if
21562306a36Sopenharmony_ci	 * the unicast entry is not present
21662306a36Sopenharmony_ci	 */
21762306a36Sopenharmony_ci	if (cpu_port)
21862306a36Sopenharmony_ci		cpsw_ale_del_ucast(cpsw->ale, port->slave.mac_addr,
21962306a36Sopenharmony_ci				   HOST_PORT_NUM, ALE_VLAN, vid);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	if (vid == am65_cpsw_get_pvid(port))
22262306a36Sopenharmony_ci		am65_cpsw_set_pvid(port, 0, 0, 0);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	/* We don't care for the return value here, error is returned only if
22562306a36Sopenharmony_ci	 * the multicast entry is not present
22662306a36Sopenharmony_ci	 */
22762306a36Sopenharmony_ci	cpsw_ale_del_mcast(cpsw->ale, port->ndev->broadcast, port_mask,
22862306a36Sopenharmony_ci			   ALE_VLAN, vid);
22962306a36Sopenharmony_ci	netdev_dbg(port->ndev, "VID del: %s: vid:%u ports:%X\n",
23062306a36Sopenharmony_ci		   port->ndev->name, vid, port_mask);
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	return ret;
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic int am65_cpsw_port_vlans_add(struct am65_cpsw_port *port,
23662306a36Sopenharmony_ci				    const struct switchdev_obj_port_vlan *vlan)
23762306a36Sopenharmony_ci{
23862306a36Sopenharmony_ci	bool untag = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
23962306a36Sopenharmony_ci	struct net_device *orig_dev = vlan->obj.orig_dev;
24062306a36Sopenharmony_ci	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	netdev_dbg(port->ndev, "VID add: %s: vid:%u flags:%X\n",
24362306a36Sopenharmony_ci		   port->ndev->name, vlan->vid, vlan->flags);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	return am65_cpsw_port_vlan_add(port, untag, pvid, vlan->vid, orig_dev);
24662306a36Sopenharmony_ci}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_cistatic int am65_cpsw_port_vlans_del(struct am65_cpsw_port *port,
24962306a36Sopenharmony_ci				    const struct switchdev_obj_port_vlan *vlan)
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	return am65_cpsw_port_vlan_del(port, vlan->vid, vlan->obj.orig_dev);
25362306a36Sopenharmony_ci}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_cistatic int am65_cpsw_port_mdb_add(struct am65_cpsw_port *port,
25662306a36Sopenharmony_ci				  struct switchdev_obj_port_mdb *mdb)
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci{
25962306a36Sopenharmony_ci	struct net_device *orig_dev = mdb->obj.orig_dev;
26062306a36Sopenharmony_ci	bool cpu_port = netif_is_bridge_master(orig_dev);
26162306a36Sopenharmony_ci	struct am65_cpsw_common *cpsw = port->common;
26262306a36Sopenharmony_ci	int port_mask;
26362306a36Sopenharmony_ci	int err;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	if (cpu_port)
26662306a36Sopenharmony_ci		port_mask = BIT(HOST_PORT_NUM);
26762306a36Sopenharmony_ci	else
26862306a36Sopenharmony_ci		port_mask = BIT(port->port_id);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	err = cpsw_ale_add_mcast(cpsw->ale, mdb->addr, port_mask,
27162306a36Sopenharmony_ci				 ALE_VLAN, mdb->vid, 0);
27262306a36Sopenharmony_ci	netdev_dbg(port->ndev, "MDB add: %s: vid %u:%pM  ports: %X\n",
27362306a36Sopenharmony_ci		   port->ndev->name, mdb->vid, mdb->addr, port_mask);
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	return err;
27662306a36Sopenharmony_ci}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_cistatic int am65_cpsw_port_mdb_del(struct am65_cpsw_port *port,
27962306a36Sopenharmony_ci				  struct switchdev_obj_port_mdb *mdb)
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci{
28262306a36Sopenharmony_ci	struct net_device *orig_dev = mdb->obj.orig_dev;
28362306a36Sopenharmony_ci	bool cpu_port = netif_is_bridge_master(orig_dev);
28462306a36Sopenharmony_ci	struct am65_cpsw_common *cpsw = port->common;
28562306a36Sopenharmony_ci	int del_mask;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	if (cpu_port)
28862306a36Sopenharmony_ci		del_mask = BIT(HOST_PORT_NUM);
28962306a36Sopenharmony_ci	else
29062306a36Sopenharmony_ci		del_mask = BIT(port->port_id);
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	/* Ignore error as error code is returned only when entry is already removed */
29362306a36Sopenharmony_ci	cpsw_ale_del_mcast(cpsw->ale, mdb->addr, del_mask,
29462306a36Sopenharmony_ci			   ALE_VLAN, mdb->vid);
29562306a36Sopenharmony_ci	netdev_dbg(port->ndev, "MDB del: %s: vid %u:%pM  ports: %X\n",
29662306a36Sopenharmony_ci		   port->ndev->name, mdb->vid, mdb->addr, del_mask);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	return 0;
29962306a36Sopenharmony_ci}
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_cistatic int am65_cpsw_port_obj_add(struct net_device *ndev, const void *ctx,
30262306a36Sopenharmony_ci				  const struct switchdev_obj *obj,
30362306a36Sopenharmony_ci				  struct netlink_ext_ack *extack)
30462306a36Sopenharmony_ci{
30562306a36Sopenharmony_ci	struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
30662306a36Sopenharmony_ci	struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
30762306a36Sopenharmony_ci	struct am65_cpsw_port *port = am65_ndev_to_port(ndev);
30862306a36Sopenharmony_ci	int err = 0;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	netdev_dbg(ndev, "obj_add: id %u port: %u\n", obj->id, port->port_id);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	switch (obj->id) {
31362306a36Sopenharmony_ci	case SWITCHDEV_OBJ_ID_PORT_VLAN:
31462306a36Sopenharmony_ci		err = am65_cpsw_port_vlans_add(port, vlan);
31562306a36Sopenharmony_ci		break;
31662306a36Sopenharmony_ci	case SWITCHDEV_OBJ_ID_PORT_MDB:
31762306a36Sopenharmony_ci	case SWITCHDEV_OBJ_ID_HOST_MDB:
31862306a36Sopenharmony_ci		err = am65_cpsw_port_mdb_add(port, mdb);
31962306a36Sopenharmony_ci		break;
32062306a36Sopenharmony_ci	default:
32162306a36Sopenharmony_ci		err = -EOPNOTSUPP;
32262306a36Sopenharmony_ci		break;
32362306a36Sopenharmony_ci	}
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	return err;
32662306a36Sopenharmony_ci}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_cistatic int am65_cpsw_port_obj_del(struct net_device *ndev, const void *ctx,
32962306a36Sopenharmony_ci				  const struct switchdev_obj *obj)
33062306a36Sopenharmony_ci{
33162306a36Sopenharmony_ci	struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
33262306a36Sopenharmony_ci	struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
33362306a36Sopenharmony_ci	struct am65_cpsw_port *port = am65_ndev_to_port(ndev);
33462306a36Sopenharmony_ci	int err = 0;
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	netdev_dbg(ndev, "obj_del: id %u port: %u\n", obj->id, port->port_id);
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	switch (obj->id) {
33962306a36Sopenharmony_ci	case SWITCHDEV_OBJ_ID_PORT_VLAN:
34062306a36Sopenharmony_ci		err = am65_cpsw_port_vlans_del(port, vlan);
34162306a36Sopenharmony_ci		break;
34262306a36Sopenharmony_ci	case SWITCHDEV_OBJ_ID_PORT_MDB:
34362306a36Sopenharmony_ci	case SWITCHDEV_OBJ_ID_HOST_MDB:
34462306a36Sopenharmony_ci		err = am65_cpsw_port_mdb_del(port, mdb);
34562306a36Sopenharmony_ci		break;
34662306a36Sopenharmony_ci	default:
34762306a36Sopenharmony_ci		err = -EOPNOTSUPP;
34862306a36Sopenharmony_ci		break;
34962306a36Sopenharmony_ci	}
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	return err;
35262306a36Sopenharmony_ci}
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_cistatic void am65_cpsw_fdb_offload_notify(struct net_device *ndev,
35562306a36Sopenharmony_ci					 struct switchdev_notifier_fdb_info *rcv)
35662306a36Sopenharmony_ci{
35762306a36Sopenharmony_ci	struct switchdev_notifier_fdb_info info = {};
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	info.addr = rcv->addr;
36062306a36Sopenharmony_ci	info.vid = rcv->vid;
36162306a36Sopenharmony_ci	info.offloaded = true;
36262306a36Sopenharmony_ci	call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
36362306a36Sopenharmony_ci				 ndev, &info.info, NULL);
36462306a36Sopenharmony_ci}
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_cistatic void am65_cpsw_switchdev_event_work(struct work_struct *work)
36762306a36Sopenharmony_ci{
36862306a36Sopenharmony_ci	struct am65_cpsw_switchdev_event_work *switchdev_work =
36962306a36Sopenharmony_ci		container_of(work, struct am65_cpsw_switchdev_event_work, work);
37062306a36Sopenharmony_ci	struct am65_cpsw_port *port = switchdev_work->port;
37162306a36Sopenharmony_ci	struct switchdev_notifier_fdb_info *fdb;
37262306a36Sopenharmony_ci	struct am65_cpsw_common *cpsw = port->common;
37362306a36Sopenharmony_ci	int port_id = port->port_id;
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	rtnl_lock();
37662306a36Sopenharmony_ci	switch (switchdev_work->event) {
37762306a36Sopenharmony_ci	case SWITCHDEV_FDB_ADD_TO_DEVICE:
37862306a36Sopenharmony_ci		fdb = &switchdev_work->fdb_info;
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci		netdev_dbg(port->ndev, "cpsw_fdb_add: MACID = %pM vid = %u flags = %u %u -- port %d\n",
38162306a36Sopenharmony_ci			   fdb->addr, fdb->vid, fdb->added_by_user,
38262306a36Sopenharmony_ci			   fdb->offloaded, port_id);
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci		if (!fdb->added_by_user || fdb->is_local)
38562306a36Sopenharmony_ci			break;
38662306a36Sopenharmony_ci		if (memcmp(port->slave.mac_addr, (u8 *)fdb->addr, ETH_ALEN) == 0)
38762306a36Sopenharmony_ci			port_id = HOST_PORT_NUM;
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci		cpsw_ale_add_ucast(cpsw->ale, (u8 *)fdb->addr, port_id,
39062306a36Sopenharmony_ci				   fdb->vid ? ALE_VLAN : 0, fdb->vid);
39162306a36Sopenharmony_ci		am65_cpsw_fdb_offload_notify(port->ndev, fdb);
39262306a36Sopenharmony_ci		break;
39362306a36Sopenharmony_ci	case SWITCHDEV_FDB_DEL_TO_DEVICE:
39462306a36Sopenharmony_ci		fdb = &switchdev_work->fdb_info;
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci		netdev_dbg(port->ndev, "cpsw_fdb_del: MACID = %pM vid = %u flags = %u %u -- port %d\n",
39762306a36Sopenharmony_ci			   fdb->addr, fdb->vid, fdb->added_by_user,
39862306a36Sopenharmony_ci			   fdb->offloaded, port_id);
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci		if (!fdb->added_by_user || fdb->is_local)
40162306a36Sopenharmony_ci			break;
40262306a36Sopenharmony_ci		if (memcmp(port->slave.mac_addr, (u8 *)fdb->addr, ETH_ALEN) == 0)
40362306a36Sopenharmony_ci			port_id = HOST_PORT_NUM;
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci		cpsw_ale_del_ucast(cpsw->ale, (u8 *)fdb->addr, port_id,
40662306a36Sopenharmony_ci				   fdb->vid ? ALE_VLAN : 0, fdb->vid);
40762306a36Sopenharmony_ci		break;
40862306a36Sopenharmony_ci	default:
40962306a36Sopenharmony_ci		break;
41062306a36Sopenharmony_ci	}
41162306a36Sopenharmony_ci	rtnl_unlock();
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	kfree(switchdev_work->fdb_info.addr);
41462306a36Sopenharmony_ci	kfree(switchdev_work);
41562306a36Sopenharmony_ci	dev_put(port->ndev);
41662306a36Sopenharmony_ci}
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci/* called under rcu_read_lock() */
41962306a36Sopenharmony_cistatic int am65_cpsw_switchdev_event(struct notifier_block *unused,
42062306a36Sopenharmony_ci				     unsigned long event, void *ptr)
42162306a36Sopenharmony_ci{
42262306a36Sopenharmony_ci	struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
42362306a36Sopenharmony_ci	struct am65_cpsw_switchdev_event_work *switchdev_work;
42462306a36Sopenharmony_ci	struct am65_cpsw_port *port = am65_ndev_to_port(ndev);
42562306a36Sopenharmony_ci	struct switchdev_notifier_fdb_info *fdb_info = ptr;
42662306a36Sopenharmony_ci	int err;
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	if (event == SWITCHDEV_PORT_ATTR_SET) {
42962306a36Sopenharmony_ci		err = switchdev_handle_port_attr_set(ndev, ptr,
43062306a36Sopenharmony_ci						     am65_cpsw_port_dev_check,
43162306a36Sopenharmony_ci						     am65_cpsw_port_attr_set);
43262306a36Sopenharmony_ci		return notifier_from_errno(err);
43362306a36Sopenharmony_ci	}
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	if (!am65_cpsw_port_dev_check(ndev))
43662306a36Sopenharmony_ci		return NOTIFY_DONE;
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
43962306a36Sopenharmony_ci	if (WARN_ON(!switchdev_work))
44062306a36Sopenharmony_ci		return NOTIFY_BAD;
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	INIT_WORK(&switchdev_work->work, am65_cpsw_switchdev_event_work);
44362306a36Sopenharmony_ci	switchdev_work->port = port;
44462306a36Sopenharmony_ci	switchdev_work->event = event;
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci	switch (event) {
44762306a36Sopenharmony_ci	case SWITCHDEV_FDB_ADD_TO_DEVICE:
44862306a36Sopenharmony_ci	case SWITCHDEV_FDB_DEL_TO_DEVICE:
44962306a36Sopenharmony_ci		memcpy(&switchdev_work->fdb_info, ptr,
45062306a36Sopenharmony_ci		       sizeof(switchdev_work->fdb_info));
45162306a36Sopenharmony_ci		switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
45262306a36Sopenharmony_ci		if (!switchdev_work->fdb_info.addr)
45362306a36Sopenharmony_ci			goto err_addr_alloc;
45462306a36Sopenharmony_ci		ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
45562306a36Sopenharmony_ci				fdb_info->addr);
45662306a36Sopenharmony_ci		dev_hold(ndev);
45762306a36Sopenharmony_ci		break;
45862306a36Sopenharmony_ci	default:
45962306a36Sopenharmony_ci		kfree(switchdev_work);
46062306a36Sopenharmony_ci		return NOTIFY_DONE;
46162306a36Sopenharmony_ci	}
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	queue_work(system_long_wq, &switchdev_work->work);
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_ci	return NOTIFY_DONE;
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_cierr_addr_alloc:
46862306a36Sopenharmony_ci	kfree(switchdev_work);
46962306a36Sopenharmony_ci	return NOTIFY_BAD;
47062306a36Sopenharmony_ci}
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_cistatic struct notifier_block cpsw_switchdev_notifier = {
47362306a36Sopenharmony_ci	.notifier_call = am65_cpsw_switchdev_event,
47462306a36Sopenharmony_ci};
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_cistatic int am65_cpsw_switchdev_blocking_event(struct notifier_block *unused,
47762306a36Sopenharmony_ci					      unsigned long event, void *ptr)
47862306a36Sopenharmony_ci{
47962306a36Sopenharmony_ci	struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
48062306a36Sopenharmony_ci	int err;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	switch (event) {
48362306a36Sopenharmony_ci	case SWITCHDEV_PORT_OBJ_ADD:
48462306a36Sopenharmony_ci		err = switchdev_handle_port_obj_add(dev, ptr,
48562306a36Sopenharmony_ci						    am65_cpsw_port_dev_check,
48662306a36Sopenharmony_ci						    am65_cpsw_port_obj_add);
48762306a36Sopenharmony_ci		return notifier_from_errno(err);
48862306a36Sopenharmony_ci	case SWITCHDEV_PORT_OBJ_DEL:
48962306a36Sopenharmony_ci		err = switchdev_handle_port_obj_del(dev, ptr,
49062306a36Sopenharmony_ci						    am65_cpsw_port_dev_check,
49162306a36Sopenharmony_ci						    am65_cpsw_port_obj_del);
49262306a36Sopenharmony_ci		return notifier_from_errno(err);
49362306a36Sopenharmony_ci	case SWITCHDEV_PORT_ATTR_SET:
49462306a36Sopenharmony_ci		err = switchdev_handle_port_attr_set(dev, ptr,
49562306a36Sopenharmony_ci						     am65_cpsw_port_dev_check,
49662306a36Sopenharmony_ci						     am65_cpsw_port_attr_set);
49762306a36Sopenharmony_ci		return notifier_from_errno(err);
49862306a36Sopenharmony_ci	default:
49962306a36Sopenharmony_ci		break;
50062306a36Sopenharmony_ci	}
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	return NOTIFY_DONE;
50362306a36Sopenharmony_ci}
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_cistatic struct notifier_block cpsw_switchdev_bl_notifier = {
50662306a36Sopenharmony_ci	.notifier_call = am65_cpsw_switchdev_blocking_event,
50762306a36Sopenharmony_ci};
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ciint am65_cpsw_switchdev_register_notifiers(struct am65_cpsw_common *cpsw)
51062306a36Sopenharmony_ci{
51162306a36Sopenharmony_ci	int ret = 0;
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	ret = register_switchdev_notifier(&cpsw_switchdev_notifier);
51462306a36Sopenharmony_ci	if (ret) {
51562306a36Sopenharmony_ci		dev_err(cpsw->dev, "register switchdev notifier fail ret:%d\n",
51662306a36Sopenharmony_ci			ret);
51762306a36Sopenharmony_ci		return ret;
51862306a36Sopenharmony_ci	}
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	ret = register_switchdev_blocking_notifier(&cpsw_switchdev_bl_notifier);
52162306a36Sopenharmony_ci	if (ret) {
52262306a36Sopenharmony_ci		dev_err(cpsw->dev, "register switchdev blocking notifier ret:%d\n",
52362306a36Sopenharmony_ci			ret);
52462306a36Sopenharmony_ci		unregister_switchdev_notifier(&cpsw_switchdev_notifier);
52562306a36Sopenharmony_ci	}
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	return ret;
52862306a36Sopenharmony_ci}
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_civoid am65_cpsw_switchdev_unregister_notifiers(struct am65_cpsw_common *cpsw)
53162306a36Sopenharmony_ci{
53262306a36Sopenharmony_ci	unregister_switchdev_blocking_notifier(&cpsw_switchdev_bl_notifier);
53362306a36Sopenharmony_ci	unregister_switchdev_notifier(&cpsw_switchdev_notifier);
53462306a36Sopenharmony_ci}
535