[PATCH net-next 3/5] net: ipqess: introduce the Qualcomm IPQESS driver

Wojciech Drewek wojciech.drewek at intel.com
Tue Oct 24 04:44:01 PDT 2023



On 23.10.2023 17:50, Romain Gantois wrote:
> The Qualcomm IPQ4019 Ethernet Switch Subsystem for the IPQ4019 chip
> includes an internal Ethernet switch based on the QCA8K IP.
> 
> The CPU-to-switch port data plane depends on the IPQESS EDMA Controller,
> a simple 1G Ethernet controller. It is connected to the switch through an
> internal link, and doesn't expose directly any external interface.
> 
> The EDMA controller has 16 RX and TX queues, with a very basic RSS fanout
> configured at init time.
> 
> Signed-off-by: Romain Gantois <romain.gantois at bootlin.com>
> ---

Hi Romain,
Thanks for the patch.
Whole driver introduced in one commit, hard to review :\
I did as musch as I can but it would be easier if you split it into several patches.

Reagrds,
Wojtek

>  MAINTAINERS                                   |    7 +
>  drivers/net/ethernet/qualcomm/Kconfig         |   14 +
>  drivers/net/ethernet/qualcomm/Makefile        |    2 +
>  drivers/net/ethernet/qualcomm/ipqess/Makefile |    8 +
>  .../ethernet/qualcomm/ipqess/ipqess_edma.c    | 1162 ++++++++++
>  .../ethernet/qualcomm/ipqess/ipqess_edma.h    |  484 ++++
>  .../qualcomm/ipqess/ipqess_notifiers.c        |  306 +++
>  .../qualcomm/ipqess/ipqess_notifiers.h        |   29 +
>  .../ethernet/qualcomm/ipqess/ipqess_port.c    | 2016 +++++++++++++++++
>  .../ethernet/qualcomm/ipqess/ipqess_port.h    |   95 +
>  .../ethernet/qualcomm/ipqess/ipqess_switch.c  |  559 +++++
>  .../ethernet/qualcomm/ipqess/ipqess_switch.h  |   40 +
>  12 files changed, 4722 insertions(+)
>  create mode 100644 drivers/net/ethernet/qualcomm/ipqess/Makefile
>  create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c
>  create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.h
>  create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c
>  create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.h
>  create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
>  create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
>  create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
>  create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 36815d2feb33..df285ef5d36e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17782,6 +17782,13 @@ F:	Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml
>  F:	drivers/mailbox/qcom-ipcc.c
>  F:	include/dt-bindings/mailbox/qcom-ipcc.h
>  
> +QUALCOMM IPQ4019 ESS DRIVER
> +M:	Romain Gantois <romain.gantois at bootlin.com>
> +L:	netdev at vger.kernel.org
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml
> +F:	drivers/net/ethernet/qualcomm/ipqess/
> +
>  QUALCOMM IPQ4019 USB PHY DRIVER
>  M:	Robert Marko <robert.marko at sartura.hr>
>  M:	Luka Perkov <luka.perkov at sartura.hr>
> diff --git a/drivers/net/ethernet/qualcomm/Kconfig b/drivers/net/ethernet/qualcomm/Kconfig
> index 9210ff360fdc..aaae06f93373 100644
> --- a/drivers/net/ethernet/qualcomm/Kconfig
> +++ b/drivers/net/ethernet/qualcomm/Kconfig
> @@ -61,6 +61,20 @@ config QCOM_EMAC
>  	  low power, Receive-Side Scaling (RSS), and IEEE 1588-2008
>  	  Precision Clock Synchronization Protocol.
>  
> +config QCOM_IPQ4019_ESS
> +	tristate "Qualcomm Atheros IPQ4019 Ethernet Switch Subsystem support"
> +	depends on (OF && ARCH_QCOM) || COMPILE_TEST
> +	select PHYLINK
> +	select NET_DSA
> +	select NET_SWITCHDEV
> +	select NET_DSA_QCA8K_LIB
> +	help
> +	  This driver supports the Qualcomm Atheros IPQ40xx built-in
> +	  Ethernet Switch Subsystem.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called ipqess.
> +
>  source "drivers/net/ethernet/qualcomm/rmnet/Kconfig"
>  
>  endif # NET_VENDOR_QUALCOMM
> diff --git a/drivers/net/ethernet/qualcomm/Makefile b/drivers/net/ethernet/qualcomm/Makefile
> index 9250976dd884..63c62704a62d 100644
> --- a/drivers/net/ethernet/qualcomm/Makefile
> +++ b/drivers/net/ethernet/qualcomm/Makefile
> @@ -12,3 +12,5 @@ qcauart-objs := qca_uart.o
>  obj-y += emac/
>  
>  obj-$(CONFIG_RMNET) += rmnet/
> +
> +obj-$(CONFIG_QCOM_IPQ4019_ESS) += ipqess/
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/Makefile b/drivers/net/ethernet/qualcomm/ipqess/Makefile
> new file mode 100644
> index 000000000000..51d7163ef0fc
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/Makefile
> @@ -0,0 +1,8 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Makefile for the IPQ ESS driver
> +#
> +
> +obj-$(CONFIG_QCOM_IPQ4019_ESS) += ipqess.o
> +
> +ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_notifiers.o ipqess_edma.o

<...>

> +
> +static int ipqess_port_vlan_del(struct net_device *netdev,
> +				const struct switchdev_obj *obj)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct net_device *br = ipqess_port_bridge_dev_get(port);
> +	struct qca8k_priv *priv = port->sw->priv;
> +	struct switchdev_obj_port_vlan *vlan;
> +	int ret;
> +
> +	if (br && !br_vlan_enabled(br))
> +		return 0;
> +
> +	vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
> +
> +	ret = qca8k_vlan_del(priv, port->index, vlan->vid);
> +
> +	if (ret)
> +		dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)\n",
> +			port->index, ret);
> +
> +	return ret;
> +}
> +
> +static int ipqess_port_host_vlan_add(struct net_device *netdev,
> +				     const struct switchdev_obj *obj,
> +				     struct netlink_ext_ack *extack)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct net_device *br = ipqess_port_bridge_dev_get(port);
> +	struct switchdev_obj_port_vlan *vlan;

RCT

> +
> +	/* Do nothing is this is a software bridge */
> +	if (!port->bridge)
> +		return -EOPNOTSUPP;
> +
> +	if (br && !br_vlan_enabled(br)) {
> +		NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN");
> +		return 0;
> +	}
> +
> +	vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
> +
> +	vlan->flags &= ~BRIDGE_VLAN_INFO_PVID;
> +
> +	/* Add vid to CPU port */
> +	return ipqess_port_do_vlan_add(port->sw->priv, 0, vlan, extack);
> +}
> +
> +static int ipqess_port_vlan_add(struct net_device *netdev,
> +				const struct switchdev_obj *obj,
> +				struct netlink_ext_ack *extack)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct net_device *br = ipqess_port_bridge_dev_get(port);
> +	struct switchdev_obj_port_vlan *vlan;
> +	int err;
> +
> +	if (br && !br_vlan_enabled(br)) {
> +		NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN");
> +		return 0;
> +	}
> +
> +	vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
> +
> +	/* Deny adding a bridge VLAN when there is already an 802.1Q upper with
> +	 * the same VID.
> +	 */
> +	if (br && br_vlan_enabled(br)) {
> +		rcu_read_lock();
> +		err = ipqess_port_vlan_check_for_8021q_uppers(netdev, vlan);
> +		rcu_read_unlock();
> +		if (err) {
> +			NL_SET_ERR_MSG_MOD(extack,
> +					   "Port already has a VLAN upper with this VID");
> +			return err;
> +		}
> +	}
> +
> +	err = ipqess_port_do_vlan_add(port->sw->priv, port->index, vlan, extack);

nit: blank line

> +	return err;
> +}
> +
> +static int ipqess_port_host_mdb_del(struct ipqess_port *port,
> +				    const struct switchdev_obj_port_mdb *mdb)
> +{
> +	struct qca8k_priv *priv = port->sw->priv;
> +	const u8 *addr = mdb->addr;
> +	u16 vid = mdb->vid;
> +
> +	return qca8k_fdb_search_and_del(priv, BIT(0), addr, vid);
> +}
> +
> +static int ipqess_port_host_mdb_add(struct ipqess_port *port,
> +				    const struct switchdev_obj_port_mdb *mdb)
> +{
> +	struct qca8k_priv *priv = port->sw->priv;
> +	const u8 *addr = mdb->addr;
> +	u16 vid = mdb->vid;
> +
> +	return qca8k_fdb_search_and_insert(priv, BIT(0), addr, vid,
> +					   QCA8K_ATU_STATUS_STATIC);
> +}
> +
> +static int ipqess_port_mdb_del(struct ipqess_port *port,
> +			       const struct switchdev_obj_port_mdb *mdb)
> +{
> +	struct qca8k_priv *priv = port->sw->priv;
> +	const u8 *addr = mdb->addr;
> +	u16 vid = mdb->vid;
> +
> +	return qca8k_fdb_search_and_del(priv, BIT(port->index), addr, vid);
> +}
> +
> +static int ipqess_port_mdb_add(struct ipqess_port *port,
> +			       const struct switchdev_obj_port_mdb *mdb)
> +{
> +	struct qca8k_priv *priv = port->sw->priv;
> +	const u8 *addr = mdb->addr;
> +	u16 vid = mdb->vid;
> +
> +	return qca8k_fdb_search_and_insert(priv, BIT(port->index), addr, vid,
> +					   QCA8K_ATU_STATUS_STATIC);
> +}
> +
> +int ipqess_port_obj_add(struct net_device *netdev, const void *ctx,
> +			const struct switchdev_obj *obj,
> +			struct netlink_ext_ack *extack)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	int err;
> +
> +	if (ctx && ctx != port)
> +		return 0;
> +
> +	switch (obj->id) {
> +	case SWITCHDEV_OBJ_ID_PORT_MDB:
> +		if (!ipqess_port_offloads_bridge_port(port, obj->orig_dev))
> +			return -EOPNOTSUPP;
> +
> +		err = ipqess_port_mdb_add(port, SWITCHDEV_OBJ_PORT_MDB(obj));
> +		break;
> +	case SWITCHDEV_OBJ_ID_HOST_MDB:
> +		if (!ipqess_port_offloads_bridge_dev(port, obj->orig_dev))
> +			return -EOPNOTSUPP;
> +
> +		err = ipqess_port_host_mdb_add(port, SWITCHDEV_OBJ_PORT_MDB(obj));
> +		break;
> +	case SWITCHDEV_OBJ_ID_PORT_VLAN:
> +		if (ipqess_port_offloads_bridge_port(port, obj->orig_dev))
> +			err = ipqess_port_vlan_add(netdev, obj, extack);
> +		else
> +			err = ipqess_port_host_vlan_add(netdev, obj, extack);
> +		break;
> +	case SWITCHDEV_OBJ_ID_MRP:
> +	case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
> +	default:
> +		err = -EOPNOTSUPP;
> +		break;
> +	}
> +
> +	return err;
> +}
> +
> +int ipqess_port_obj_del(struct net_device *netdev, const void *ctx,
> +			const struct switchdev_obj *obj)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	int err;
> +
> +	if (ctx && ctx != port)
> +		return 0;
> +
> +	switch (obj->id) {
> +	case SWITCHDEV_OBJ_ID_PORT_MDB:
> +		if (!ipqess_port_offloads_bridge_port(port, obj->orig_dev))
> +			return -EOPNOTSUPP;
> +
> +		err = ipqess_port_mdb_del(port, SWITCHDEV_OBJ_PORT_MDB(obj));
> +		break;
> +	case SWITCHDEV_OBJ_ID_HOST_MDB:
> +		if (!ipqess_port_offloads_bridge_dev(port, obj->orig_dev))
> +			return -EOPNOTSUPP;
> +
> +		err = ipqess_port_host_mdb_del(port, SWITCHDEV_OBJ_PORT_MDB(obj));
> +		break;
> +	case SWITCHDEV_OBJ_ID_PORT_VLAN:
> +		if (ipqess_port_offloads_bridge_port(port, obj->orig_dev))
> +			err = ipqess_port_vlan_del(netdev, obj);
> +		else
> +			err = ipqess_port_host_vlan_del(netdev, obj);
> +		break;
> +	case SWITCHDEV_OBJ_ID_MRP:
> +	case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
> +	default:
> +		err = -EOPNOTSUPP;
> +		break;
> +	}
> +
> +	return err;
> +}
> +
> +static int ipqess_cpu_port_fdb_del(struct ipqess_port *port,
> +				   const unsigned char *addr, u16 vid)
> +{
> +	struct ipqess_switch *sw = port->sw;
> +	struct ipqess_mac_addr *a = NULL;
> +	struct ipqess_mac_addr *other_a;
> +	int err = 0;
> +
> +	mutex_lock(&sw->addr_lists_lock);
> +
> +	list_for_each_entry(other_a, &sw->fdbs, list)
> +		if (ether_addr_equal(other_a->addr, addr) && other_a->vid == vid)
> +			a = other_a;
> +
> +	if (!a) {
> +		err = -ENOENT;
> +		goto out;
> +	}
> +
> +	if (!refcount_dec_and_test(&a->refcount))
> +		goto out;
> +
> +	err = qca8k_fdb_del(sw->priv, addr, BIT(IPQESS_SWITCH_CPU_PORT), vid);
> +	if (err) {
> +		refcount_set(&a->refcount, 1);
> +		goto out;
> +	}
> +
> +	list_del(&a->list);
> +	kfree(a);
> +
> +out:
> +	mutex_unlock(&sw->addr_lists_lock);
> +
> +	return err;
> +}
> +
> +static int ipqess_cpu_port_fdb_add(struct ipqess_port *port,
> +				   const unsigned char *addr, u16 vid)
> +{
> +	struct ipqess_switch *sw = port->sw;
> +	struct ipqess_mac_addr *other_a = NULL;
> +	struct ipqess_mac_addr *a = NULL;

more meaningful name than "a" would be nice

> +	int err = 0;

RCT

> +
> +	mutex_lock(&sw->addr_lists_lock);
> +
> +	list_for_each_entry(other_a, &sw->fdbs, list)
> +		if (ether_addr_equal(other_a->addr, addr) && other_a->vid == vid)
> +			a = other_a;
> +
> +	if (a) {
> +		refcount_inc(&a->refcount);
> +		goto out;
> +	}
> +
> +	a = kzalloc(sizeof(*a), GFP_KERNEL);
> +	if (!a) {
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	err = qca8k_port_fdb_insert(port->sw->priv, addr,
> +				    BIT(IPQESS_SWITCH_CPU_PORT), vid);
> +	if (err) {
> +		kfree(a);
> +		goto out;
> +	}
> +
> +	ether_addr_copy(a->addr, addr);
> +	a->vid = vid;
> +	refcount_set(&a->refcount, 1);
> +	list_add_tail(&a->list, &sw->fdbs);
> +
> +out:
> +	mutex_unlock(&sw->addr_lists_lock);
> +
> +	return err;
> +}
> +
> +static void
> +ipqess_fdb_offload_notify(struct ipqess_switchdev_event_work *switchdev_work)
> +{
> +	struct switchdev_notifier_fdb_info info = {};
> +
> +	info.addr = switchdev_work->addr;
> +	info.vid = switchdev_work->vid;
> +	info.offloaded = true;
> +	call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
> +				 switchdev_work->orig_netdev, &info.info, NULL);
> +}
> +
> +void ipqess_port_switchdev_event_work(struct work_struct *work)
> +{
> +	struct ipqess_switchdev_event_work *switchdev_work =
> +		container_of(work, struct ipqess_switchdev_event_work, work);
> +	struct net_device *netdev = switchdev_work->netdev;
> +	const unsigned char *addr = switchdev_work->addr;
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct ipqess_switch *sw = port->sw;
> +	struct qca8k_priv *priv = sw->priv;
> +	u16 vid = switchdev_work->vid;
> +	int err;
> +
> +	if (!vid)
> +		vid = QCA8K_PORT_VID_DEF;
> +
> +	switch (switchdev_work->event) {
> +	case SWITCHDEV_FDB_ADD_TO_DEVICE:
> +		if (switchdev_work->host_addr)
> +			err = ipqess_cpu_port_fdb_add(port, addr, vid);
> +		else
> +			err = qca8k_port_fdb_insert(priv, addr, BIT(port->index), vid);
> +		if (err) {
> +			dev_err(&port->netdev->dev,
> +				"port %d failed to add %pM vid %d to fdb: %d\n",
> +				port->index, addr, vid, err);
> +			break;
> +		}
> +		ipqess_fdb_offload_notify(switchdev_work);
> +		break;
> +
> +	case SWITCHDEV_FDB_DEL_TO_DEVICE:
> +		if (switchdev_work->host_addr)
> +			err = ipqess_cpu_port_fdb_del(port, addr, vid);
> +		else
> +			err = qca8k_fdb_del(priv, addr, BIT(port->index), vid);
> +		if (err) {
> +			dev_err(&port->netdev->dev,
> +				"port %d failed to delete %pM vid %d from fdb: %d\n",
> +				port->index, addr, vid, err);
> +		}
> +
> +		break;
> +	}
> +
> +	kfree(switchdev_work);
> +}
> +
> +int ipqess_port_check_8021q_upper(struct net_device *netdev,
> +				  struct netdev_notifier_changeupper_info *info)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct net_device *br = ipqess_port_bridge_dev_get(port);
> +	struct bridge_vlan_info br_info;
> +	struct netlink_ext_ack *extack;
> +	int err = NOTIFY_DONE;
> +	u16 vid;
> +
> +	if (!br || !br_vlan_enabled(br))
> +		return NOTIFY_DONE;
> +
> +	extack = netdev_notifier_info_to_extack(&info->info);
> +	vid = vlan_dev_vlan_id(info->upper_dev);
> +
> +	/* br_vlan_get_info() returns -EINVAL or -ENOENT if the
> +	 * device, respectively the VID is not found, returning
> +	 * 0 means success, which is a failure for us here.
> +	 */
> +	err = br_vlan_get_info(br, vid, &br_info);
> +	if (err == 0) {
> +		NL_SET_ERR_MSG_MOD(extack,
> +				   "This VLAN is already configured by the bridge");
> +		return notifier_from_errno(-EBUSY);
> +	}
> +
> +	return NOTIFY_DONE;
> +}
> +
> +/* phylink ops */
> +
> +static int
> +ipqess_psgmii_configure(struct qca8k_priv *priv)
> +{
> +	int ret;
> +
> +	if (!atomic_fetch_inc(&priv->psgmii_calibrated)) {
> +		dev_warn(priv->dev, "Unable to calibrate PSGMII, link will be unstable!\n");
> +
> +		ret = regmap_clear_bits(priv->psgmii, PSGMIIPHY_MODE_CONTROL,
> +					PSGMIIPHY_MODE_ATHR_CSCO_MODE_25M);
> +		ret = regmap_write(priv->psgmii, PSGMIIPHY_TX_CONTROL,
> +				   PSGMIIPHY_TX_CONTROL_MAGIC_VALUE);
> +
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void
> +ipqess_phylink_mac_config(struct phylink_config *config,
> +			  unsigned int mode,
> +			  const struct phylink_link_state *state)
> +{
> +	struct ipqess_port *port = ipqess_port_from_pl_state(config, pl_config);
> +	struct qca8k_priv *priv = port->sw->priv;
> +
> +	switch (port->index) {
> +	case 0:
> +		/* CPU port, no configuration needed */
> +		return;
> +	case 1:
> +	case 2:
> +	case 3:
> +		if (state->interface == PHY_INTERFACE_MODE_PSGMII)
> +			if (ipqess_psgmii_configure(priv))
> +				dev_err(priv->dev,
> +					"PSGMII configuration failed!\n");
> +		return;
> +	case 4:
> +	case 5:
> +		if (phy_interface_mode_is_rgmii(state->interface))
> +			regmap_set_bits(priv->regmap,
> +					QCA8K_IPQ4019_REG_RGMII_CTRL,
> +					QCA8K_IPQ4019_RGMII_CTRL_CLK);
> +
> +		if (state->interface == PHY_INTERFACE_MODE_PSGMII)
> +			if (ipqess_psgmii_configure(priv))
> +				dev_err(priv->dev,
> +					"PSGMII configuration failed!\n");
> +		return;
> +	default:
> +		dev_err(priv->dev, "%s: unsupported port: %i\n", __func__,
> +			port->index);
> +		return;
> +	}
> +}
> +
> +static void
> +ipqess_phylink_mac_link_down(struct phylink_config *config,
> +			     unsigned int mode,
> +			     phy_interface_t interface)
> +{
> +	struct ipqess_port *port = ipqess_port_from_pl_state(config, pl_config);
> +	struct qca8k_priv *priv = port->sw->priv;
> +
> +	qca8k_port_set_status(priv, port->index, 0);
> +}
> +
> +static void ipqess_phylink_mac_link_up(struct phylink_config *config,
> +				       struct phy_device *phydev,
> +				       unsigned int mode,
> +				       phy_interface_t interface,
> +				       int speed, int duplex,
> +				       bool tx_pause, bool rx_pause)
> +{
> +	struct ipqess_port *port = ipqess_port_from_pl_state(config, pl_config);
> +	struct qca8k_priv *priv = port->sw->priv;
> +	u32 reg;
> +
> +	if (phylink_autoneg_inband(mode)) {
> +		reg = QCA8K_PORT_STATUS_LINK_AUTO;
> +	} else {
> +		switch (speed) {
> +		case SPEED_10:
> +			reg = QCA8K_PORT_STATUS_SPEED_10;
> +			break;
> +		case SPEED_100:
> +			reg = QCA8K_PORT_STATUS_SPEED_100;
> +			break;
> +		case SPEED_1000:
> +			reg = QCA8K_PORT_STATUS_SPEED_1000;
> +			break;
> +		default:
> +			reg = QCA8K_PORT_STATUS_LINK_AUTO;
> +			break;
> +		}
> +
> +		if (duplex == DUPLEX_FULL)
> +			reg |= QCA8K_PORT_STATUS_DUPLEX;
> +
> +		if (rx_pause || port->index == 0)
> +			reg |= QCA8K_PORT_STATUS_RXFLOW;
> +
> +		if (tx_pause || port->index == 0)
> +			reg |= QCA8K_PORT_STATUS_TXFLOW;
> +	}
> +
> +	reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
> +
> +	qca8k_write(priv, QCA8K_REG_PORT_STATUS(port->index), reg);
> +}
> +
> +static const struct phylink_mac_ops ipqess_phylink_mac_ops = {
> +	.mac_config = ipqess_phylink_mac_config,
> +	.mac_link_down = ipqess_phylink_mac_link_down,
> +	.mac_link_up = ipqess_phylink_mac_link_up,
> +};
> +
> +static int ipqess_phylink_create(struct net_device *netdev)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct phylink_config *pl_config = &port->pl_config;
> +	phy_interface_t mode;
> +	struct phylink *pl;
> +	int err;
> +
> +	err = of_get_phy_mode(port->dn, &mode);
> +	if (err)
> +		mode = PHY_INTERFACE_MODE_NA;
> +
> +	switch (port->index) {
> +	case 1:
> +	case 2:
> +	case 3:
> +		__set_bit(PHY_INTERFACE_MODE_PSGMII,
> +			  pl_config->supported_interfaces);
> +		break;
> +	case 4:
> +	case 5:
> +		phy_interface_set_rgmii(pl_config->supported_interfaces);
> +		__set_bit(PHY_INTERFACE_MODE_PSGMII,
> +			  pl_config->supported_interfaces);
> +		break;
> +	case 0: /* CPU port, this shouldn't happen */
> +	default:
> +		return -EINVAL;
> +	}
> +	/* phylink caps */
> +	pl_config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
> +		MAC_10 | MAC_100 | MAC_1000FD;
> +
> +	pl = phylink_create(pl_config, of_fwnode_handle(port->dn),
> +			    mode, &ipqess_phylink_mac_ops);
> +	if (IS_ERR(pl))
> +		return PTR_ERR(pl);
> +
> +	port->pl = pl;
> +	return 0;
> +}
> +
> +static int ipqess_port_phy_connect(struct net_device *netdev, int addr,
> +				   u32 flags)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +
> +	netdev->phydev = mdiobus_get_phy(port->mii_bus, addr);
> +	if (!netdev->phydev) {
> +		netdev_err(netdev, "no phy at %d\n", addr);
> +		return -ENODEV;
> +	}
> +
> +	netdev->phydev->dev_flags |= flags;
> +
> +	return phylink_connect_phy(port->pl, netdev->phydev);
> +}
> +
> +static int ipqess_port_phy_setup(struct net_device *netdev)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct device_node *port_dn = port->dn;
> +	u32 phy_flags = 0;
> +	int ret;
> +
> +	port->pl_config.dev = &netdev->dev;
> +	port->pl_config.type = PHYLINK_NETDEV;
> +
> +	ret = ipqess_phylink_create(netdev);
> +	if (ret)
> +		return ret;
> +
> +	ret = phylink_of_phy_connect(port->pl, port_dn, phy_flags);
> +	if (ret == -ENODEV && port->mii_bus) {
> +		/* We could not connect to a designated PHY or SFP, so try to
> +		 * use the switch internal MDIO bus instead
> +		 */
> +		ret = ipqess_port_phy_connect(netdev, port->index, phy_flags);
> +	}

nit: blank line

> +	if (ret) {
> +		netdev_err(netdev, "failed to connect to PHY: %pe\n",
> +			   ERR_PTR(ret));
> +		phylink_destroy(port->pl);
> +		port->pl = NULL;
> +	}
> +
> +	dev_info(&netdev->dev, "enabled port's phy: %s",
> +		 phydev_name(netdev->phydev));

nit: blank line

> +	return ret;
> +}
> +
> +/* ethtool ops */
> +
> +static void ipqess_port_get_drvinfo(struct net_device *dev,
> +				    struct ethtool_drvinfo *drvinfo)
> +{
> +	strscpy(drvinfo->driver, "qca8k-ipqess", sizeof(drvinfo->driver));
> +	strscpy(drvinfo->fw_version, "N/A", sizeof(drvinfo->fw_version));
> +	strscpy(drvinfo->bus_info, "platform", sizeof(drvinfo->bus_info));
> +}
> +
> +static int ipqess_port_nway_reset(struct net_device *dev)
> +{
> +	struct ipqess_port *port = netdev_priv(dev);
> +
> +	return phylink_ethtool_nway_reset(port->pl);
> +}
> +
> +static int ipqess_port_get_eeprom_len(struct net_device *dev)
> +{
> +	return 0;
> +}
> +
> +static const char ipqess_gstrings_base_stats[][ETH_GSTRING_LEN] = {
> +	"tx_packets",
> +	"tx_bytes",
> +	"rx_packets",
> +	"rx_bytes",
> +};
> +
> +static void ipqess_port_get_strings(struct net_device *dev,
> +				    u32 stringset, u8 *data)
> +{
> +	struct ipqess_port *port = netdev_priv(dev);
> +	struct qca8k_priv *priv = port->sw->priv;
> +	int i;
> +
> +	if (stringset == ETH_SS_STATS) {
> +		memcpy(data, &ipqess_gstrings_base_stats,
> +		       sizeof(ipqess_gstrings_base_stats));
> +
> +		if (stringset != ETH_SS_STATS)
> +			return;
> +
> +		for (i = 0; i < priv->info->mib_count; i++)
> +			memcpy(data + (4 + i) * ETH_GSTRING_LEN,
> +			       ar8327_mib[i].name,
> +			       ETH_GSTRING_LEN);
> +
> +	} else if (stringset == ETH_SS_TEST) {
> +		net_selftest_get_strings(data);
> +	}
> +}
> +
> +static void ipqess_port_get_ethtool_stats(struct net_device *dev,
> +					  struct ethtool_stats *stats,
> +					  uint64_t *data)
> +{
> +	struct ipqess_port *port = netdev_priv(dev);
> +	struct qca8k_priv *priv = port->sw->priv;
> +	const struct qca8k_mib_desc *mib;
> +	struct pcpu_sw_netstats *s;
> +	unsigned int start;
> +	u32 reg, c, val;
> +	u32 hi = 0;
> +	int ret;
> +	int i;
> +
> +	for_each_possible_cpu(i) {
> +		u64 tx_packets, tx_bytes, rx_packets, rx_bytes;
> +
> +		s = per_cpu_ptr(dev->tstats, i);
> +		do {
> +			start = u64_stats_fetch_begin(&s->syncp);
> +			tx_packets = u64_stats_read(&s->tx_packets);
> +			tx_bytes = u64_stats_read(&s->tx_bytes);
> +			rx_packets = u64_stats_read(&s->rx_packets);
> +			rx_bytes = u64_stats_read(&s->rx_bytes);
> +		} while (u64_stats_fetch_retry(&s->syncp, start));
> +		data[0] += tx_packets;
> +		data[1] += tx_bytes;
> +		data[2] += rx_packets;
> +		data[3] += rx_bytes;
> +	}
> +
> +	for (c = 0; c < priv->info->mib_count; c++) {
> +		mib = &ar8327_mib[c];
> +		reg = QCA8K_PORT_MIB_COUNTER(port->index) + mib->offset;
> +
> +		ret = qca8k_read(priv, reg, &val);
> +		if (ret < 0)
> +			continue;
> +
> +		if (mib->size == 2) {
> +			ret = qca8k_read(priv, reg + 4, &hi);
> +			if (ret < 0)
> +				continue;
> +		}
> +
> +		data[4 + c] = val;
> +		if (mib->size == 2)
> +			data[4 + c] |= (u64)hi << 32;
> +	}
> +}
> +
> +static int ipqess_port_get_sset_count(struct net_device *dev, int sset)
> +{
> +	struct ipqess_port *port = netdev_priv(dev);
> +	struct qca8k_priv *priv = port->sw->priv;
> +
> +	if (sset == ETH_SS_STATS) {
> +		int count = 0;
> +
> +		if (sset != ETH_SS_STATS)
> +			count = 0;
> +		else
> +			count = priv->info->mib_count;
> +
> +		if (count < 0)
> +			return count;
> +
> +		return count + 4;
> +	} else if (sset == ETH_SS_TEST) {
> +		return net_selftest_get_count();
> +	}
> +
> +	return -EOPNOTSUPP;
> +}
> +
> +static int ipqess_port_set_wol(struct net_device *dev,
> +			       struct ethtool_wolinfo *w)
> +{
> +	struct ipqess_port *port = netdev_priv(dev);
> +
> +	return phylink_ethtool_set_wol(port->pl, w);
> +}
> +
> +static void ipqess_port_get_wol(struct net_device *dev,
> +				struct ethtool_wolinfo *w)
> +{
> +	struct ipqess_port *port = netdev_priv(dev);
> +
> +	phylink_ethtool_get_wol(port->pl, w);
> +}
> +
> +static int ipqess_port_set_eee(struct net_device *dev, struct ethtool_eee *eee)
> +{
> +	struct ipqess_port *port = netdev_priv(dev);
> +	int ret;
> +	u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port->index);
> +	struct qca8k_priv *priv = port->sw->priv;
> +	u32 reg;
> +
> +	/* Port's PHY and MAC both need to be EEE capable */
> +	if (!dev->phydev || !port->pl)
> +		return -ENODEV;
> +
> +	mutex_lock(&priv->reg_mutex);
> +	ret = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &reg);
> +	if (ret < 0) {
> +		mutex_unlock(&priv->reg_mutex);
> +		return ret;
> +	}
> +
> +	if (eee->eee_enabled)
> +		reg |= lpi_en;
> +	else
> +		reg &= ~lpi_en;
> +	ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg);
> +	mutex_unlock(&priv->reg_mutex);
> +
> +	if (ret)
> +		return ret;
> +
> +	return phylink_ethtool_set_eee(port->pl, eee);
> +}
> +
> +static int ipqess_port_get_eee(struct net_device *dev, struct ethtool_eee *e)
> +{
> +	struct ipqess_port *port = netdev_priv(dev);
> +
> +	/* Port's PHY and MAC both need to be EEE capable */
> +	if (!dev->phydev || !port->pl)
> +		return -ENODEV;
> +
> +	return phylink_ethtool_get_eee(port->pl, e);
> +}
> +
> +static int ipqess_port_get_link_ksettings(struct net_device *dev,
> +					  struct ethtool_link_ksettings *cmd)
> +{
> +	struct ipqess_port *port = netdev_priv(dev);
> +
> +	return phylink_ethtool_ksettings_get(port->pl, cmd);
> +}
> +
> +static int ipqess_port_set_link_ksettings(struct net_device *dev,
> +					  const struct ethtool_link_ksettings *cmd)
> +{
> +	struct ipqess_port *port = netdev_priv(dev);
> +
> +	return phylink_ethtool_ksettings_set(port->pl, cmd);
> +}
> +
> +static void ipqess_port_get_pauseparam(struct net_device *dev,
> +				       struct ethtool_pauseparam *pause)
> +{
> +	struct ipqess_port *port = netdev_priv(dev);
> +
> +	phylink_ethtool_get_pauseparam(port->pl, pause);
> +}
> +
> +static int ipqess_port_set_pauseparam(struct net_device *dev,
> +				      struct ethtool_pauseparam *pause)
> +{
> +	struct ipqess_port *port = netdev_priv(dev);
> +
> +	return phylink_ethtool_set_pauseparam(port->pl, pause);
> +}
> +

seperate file for ethtool ops?

> +static const struct ethtool_ops ipqess_port_ethtool_ops = {
> +	.get_drvinfo		= ipqess_port_get_drvinfo,
> +	.nway_reset		= ipqess_port_nway_reset,
> +	.get_link		= ethtool_op_get_link,
> +	.get_eeprom_len		= ipqess_port_get_eeprom_len,
> +	.get_strings		= ipqess_port_get_strings,
> +	.get_ethtool_stats	= ipqess_port_get_ethtool_stats,
> +	.get_sset_count		= ipqess_port_get_sset_count,
> +	.self_test		= net_selftest,
> +	.set_wol		= ipqess_port_set_wol,
> +	.get_wol		= ipqess_port_get_wol,
> +	.set_eee		= ipqess_port_set_eee,
> +	.get_eee		= ipqess_port_get_eee,
> +	.get_link_ksettings	= ipqess_port_get_link_ksettings,
> +	.set_link_ksettings	= ipqess_port_set_link_ksettings,
> +	.get_pauseparam		= ipqess_port_get_pauseparam,
> +	.set_pauseparam		= ipqess_port_set_pauseparam,
> +};
> +
> +/* netlink */
> +
> +#define IFLA_IPQESS_UNSPEC 0
> +#define IFLA_IPQESS_MAX 0
> +
> +static const struct nla_policy ipqess_port_policy[IFLA_IPQESS_MAX + 1] = {
> +	[IFLA_IPQESS_MAX] = { .type = NLA_U32 },
> +};
> +
> +static size_t ipqess_port_get_size(const struct net_device *dev)
> +{
> +	return nla_total_size(sizeof(u32));
> +}
> +
> +static int ipqess_port_fill_info(struct sk_buff *skb,
> +				 const struct net_device *dev)
> +{
> +	if (nla_put_u32(skb, IFLA_IPQESS_UNSPEC, dev->ifindex))
> +		return -EMSGSIZE;
> +
> +	return 0;
> +}
> +
> +static struct rtnl_link_ops ipqess_port_link_ops __read_mostly = {
> +	.kind         = "switch",
> +	.priv_size    = sizeof(struct ipqess_port),
> +	.maxtype      = 1,
> +	.policy       = ipqess_port_policy,
> +	.get_size     = ipqess_port_get_size,
> +	.fill_info    = ipqess_port_fill_info,
> +	.netns_refund = true,
> +};
> +
> +/* devlink */
> +
> +static int ipqess_port_devlink_setup(struct ipqess_port *port)
> +{
> +	struct devlink_port *dlp = &port->devlink_port;
> +	struct devlink *dl = port->sw->devlink;
> +	struct devlink_port_attrs attrs = {};
> +	unsigned int index = 0;
> +	const unsigned char *id = (const unsigned char *)&index;
> +	unsigned char len = sizeof(index);
> +	int err;

RCT

> +
> +	memset(dlp, 0, sizeof(*dlp));
> +	devlink_port_init(dl, dlp);

no need to call this, devlink_port_register will init
devlink port (devl_port_register_with_ops to be precise)

> +
> +	attrs.phys.port_number = port->index;
> +	memcpy(attrs.switch_id.id, id, len);
> +	attrs.switch_id.id_len = len;
> +	attrs.flavour = DEVLINK_PORT_FLAVOUR_PHYSICAL;
> +	devlink_port_attrs_set(dlp, &attrs);
> +
> +	err = devlink_port_register(dl, dlp, port->index);
> +	if (err)
> +		return err;
> +
> +	return 0;
> +}
> +
> +/* register */
> +
> +int ipqess_port_register(struct ipqess_switch *sw,
> +			 struct device_node *port_node)
> +{
> +	struct qca8k_priv *priv = sw->priv;
> +	struct net_device *netdev;
> +	struct ipqess_port *port;
> +	const char *name;
> +	int assign_type;
> +	int num_queues;
> +	u32 index;
> +	int err;
> +
> +	err = of_property_read_u32(port_node, "reg", &index);
> +	if (err) {
> +		pr_err("Node without reg property!");
> +		return err;
> +	}
> +
> +	name = of_get_property(port_node, "label", NULL);
> +	if (!name) {
> +		name = "eth%d";
> +		assign_type = NET_NAME_ENUM;
> +	} else {
> +		assign_type = NET_NAME_PREDICTABLE;
> +	}
> +
> +	/* For the NAPI leader, we allocate one queue per MAC queue */
> +	if (!sw->napi_leader)
> +		num_queues = IPQESS_EDMA_NETDEV_QUEUES;
> +	else
> +		num_queues = 1;
> +
> +	netdev = alloc_netdev_mqs(sizeof(struct ipqess_port), name, assign_type,
> +				  ether_setup, num_queues, num_queues);
> +	if (!netdev)
> +		return -ENOMEM;
> +
> +	if (!sw->napi_leader)
> +		sw->napi_leader = netdev;
> +
> +	port = netdev_priv(netdev);
> +	port->index = (int)index;
> +	port->dn = port_node;
> +	port->netdev = netdev;
> +	port->edma = NULL; /* Assigned during edma initialization */
> +	port->qid = port->index - 1;
> +	port->sw = sw;
> +	port->bridge = NULL;
> +
> +	of_get_mac_address(port_node, port->mac);
> +	if (!is_zero_ether_addr(port->mac))
> +		eth_hw_addr_set(netdev, port->mac);
> +	else
> +		eth_hw_addr_random(netdev);
> +
> +	netdev->netdev_ops = &ipqess_port_netdev_ops;
> +	netdev->max_mtu = QCA8K_MAX_MTU;
> +	SET_NETDEV_DEVTYPE(netdev, &ipqess_port_type);
> +	SET_NETDEV_DEV(netdev, priv->dev);
> +	SET_NETDEV_DEVLINK_PORT(netdev, &port->devlink_port);
> +	netdev->dev.of_node = port->dn;
> +
> +	netdev->rtnl_link_ops = &ipqess_port_link_ops;
> +	netdev->ethtool_ops = &ipqess_port_ethtool_ops;
> +
> +	netdev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
> +	if (!netdev->tstats) {
> +		free_netdev(netdev);
> +		return -ENOMEM;
> +	}
> +
> +	err = ipqess_port_devlink_setup(port);
> +	if (err)
> +		goto out_free;
> +
> +	err = gro_cells_init(&port->gcells, netdev);
> +	if (err)
> +		goto out_devlink;
> +
> +	err = ipqess_port_phy_setup(netdev);
> +	if (err) {
> +		pr_err("error setting up PHY: %d\n", err);
> +		goto out_gcells;
> +	}
> +
> +	/* We use the qid and not the index because port 0 isn't registered */
> +	sw->port_list[port->qid] = port;
> +
> +	rtnl_lock();
> +
> +	err = register_netdevice(netdev);

you can use register_netdev which takes care of rtnl locking

> +	if (err) {
> +		pr_err("error %d registering interface %s\n",
> +		       err, netdev->name);
> +		rtnl_unlock();
> +		goto out_phy;
> +	}
> +
> +	rtnl_unlock();
> +
> +	return 0;
> +
> +out_phy:
> +	rtnl_lock();
> +	phylink_disconnect_phy(port->pl);
> +	rtnl_unlock();
> +	phylink_destroy(port->pl);
> +	port->pl = NULL;
> +out_gcells:
> +	gro_cells_destroy(&port->gcells);
> +out_devlink:
> +	devlink_port_unregister(&port->devlink_port);
> +out_free:
> +	free_percpu(netdev->tstats);
> +	free_netdev(netdev);
> +	sw->port_list[port->qid] = NULL;
> +	return err;
> +}
> +
> +void ipqess_port_unregister(struct ipqess_port *port)
> +{
> +	struct net_device *netdev = port->netdev;
> +
> +	unregister_netdev(netdev);
> +
> +	devlink_port_unregister(&port->devlink_port);
> +
> +	rtnl_lock();
> +	phylink_disconnect_phy(port->pl);
> +	rtnl_unlock();
> +	phylink_destroy(port->pl);
> +	port->pl = NULL;
> +
> +	gro_cells_destroy(&port->gcells);
> +
> +	free_percpu(netdev->tstats);
> +	free_netdev(netdev);
> +}
> +
> +/* Utilities */
> +
> +/* Returns true if any port of this switch offloads the given net_device */
> +static bool ipqess_switch_offloads_bridge_port(struct ipqess_switch *sw,
> +					       const struct net_device *netdev)
> +{
> +	struct ipqess_port *port;
> +	int i;
> +
> +	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> +		port = sw->port_list[i];
> +		if (port && ipqess_port_offloads_bridge_port(port, netdev))
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +/* Returns true if any port of this switch offloads the given bridge */
> +static inline bool
> +ipqess_switch_offloads_bridge_dev(struct ipqess_switch *sw,
> +				  const struct net_device *bridge_dev)
> +{
> +	struct ipqess_port *port;
> +	int i;
> +
> +	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> +		port = sw->port_list[i];
> +		if (port && ipqess_port_offloads_bridge_dev(port, bridge_dev))
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +bool ipqess_port_recognize_netdev(const struct net_device *netdev)
> +{
> +	return netdev->netdev_ops == &ipqess_port_netdev_ops;
> +}
> +
> +bool ipqess_port_dev_is_foreign(const struct net_device *netdev,
> +				const struct net_device *foreign_netdev)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct ipqess_switch *sw = port->sw;
> +
> +	if (netif_is_bridge_master(foreign_netdev))
> +		return !ipqess_switch_offloads_bridge_dev(sw, foreign_netdev);
> +
> +	if (netif_is_bridge_port(foreign_netdev))
> +		return !ipqess_switch_offloads_bridge_port(sw, foreign_netdev);
> +
> +	/* Everything else is foreign */
> +	return true;
> +}
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> new file mode 100644
> index 000000000000..a0639933e8bb
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> @@ -0,0 +1,95 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR ISC */
> +
> +#ifndef IPQESS_PORT_H
> +#define IPQESS_PORT_H
> +
> +#include <net/gro_cells.h>
> +#include <net/devlink.h>
> +
> +#include "ipqess_edma.h"
> +#include "ipqess_switch.h"
> +
> +struct ipqess_bridge {
> +	struct net_device *netdev;
> +	refcount_t refcount;
> +};
> +
> +struct ipqess_port {
> +	u16 index;
> +	u16 qid;
> +
> +	struct ipqess_edma *edma;
> +	struct ipqess_switch *sw;
> +	struct phylink *pl;
> +	struct phylink_config pl_config;
> +	struct device_node *dn;
> +	struct mii_bus *mii_bus;
> +	struct net_device *netdev;
> +	struct ipqess_bridge *bridge;
> +	struct devlink_port devlink_port;
> +
> +	u8       stp_state;
> +
> +	u8       mac[ETH_ALEN];
> +
> +	/* Warning: the following bit field is not atomic, and updating it
> +	 * can only be done from code paths where concurrency is not possible
> +	 * (probe time or under rtnl_lock).
> +	 */
> +	u8			vlan_filtering:1;
> +
> +	unsigned int		ageing_time;
> +
> +	struct gro_cells	gcells;
> +
> +#ifdef CONFIG_NET_POLL_CONTROLLER
> +	struct netpoll		*netpoll;
> +#endif
> +};
> +
> +struct ipqess_port_dump_ctx {
> +	struct net_device *dev;
> +	struct sk_buff *skb;
> +	struct netlink_callback *cb;
> +	int idx;
> +};
> +
> +struct ipqess_mac_addr {
> +	unsigned char addr[ETH_ALEN];
> +	u16 vid;
> +	refcount_t refcount;
> +	struct list_head list;
> +};
> +
> +int ipqess_port_register(struct ipqess_switch *sw,
> +			 struct device_node *port_node);
> +void ipqess_port_unregister(struct ipqess_port *port);
> +
> +bool ipqess_port_recognize_netdev(const struct net_device *netdev);
> +bool ipqess_port_dev_is_foreign(const struct net_device *netdev,
> +				const struct net_device *foreign_netdev);
> +
> +int ipqess_port_bridge_join(struct ipqess_port *port, struct net_device *br,
> +			    struct netlink_ext_ack *extack);
> +void ipqess_port_bridge_leave(struct ipqess_port *port, struct net_device *br);
> +
> +int ipqess_port_attr_set(struct net_device *dev, const void *ctx,
> +			 const struct switchdev_attr *attr,
> +			 struct netlink_ext_ack *extack);
> +
> +void ipqess_port_switchdev_event_work(struct work_struct *work);
> +
> +int ipqess_port_check_8021q_upper(struct net_device *netdev,
> +				  struct netdev_notifier_changeupper_info *info);
> +
> +struct net_device *ipqess_port_get_bridged_netdev(const struct ipqess_port *port);
> +
> +int ipqess_port_obj_add(struct net_device *netdev, const void *ctx,
> +			const struct switchdev_obj *obj,
> +			struct netlink_ext_ack *extack);
> +int ipqess_port_obj_del(struct net_device *netdev, const void *ctx,
> +			const struct switchdev_obj *obj);
> +
> +bool ipqess_port_offloads_bridge_port(struct ipqess_port *port,
> +				      const struct net_device *netdev);
> +#endif
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
> new file mode 100644
> index 000000000000..45e83a8965be
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
> @@ -0,0 +1,559 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2023, Romain Gantois <romain.gantois at bootlin.com>
> + * Based on net/dsa
> + */
> +
> +#include <linux/dsa/qca8k.h>
> +#include <linux/of_platform.h>
> +#include <linux/of_mdio.h>
> +#include <linux/reset.h>
> +
> +#include "ipqess_switch.h"
> +#include "ipqess_port.h"
> +#include "ipqess_edma.h"
> +
> +static struct regmap_config qca8k_ipqess_regmap_config = {
> +	.reg_bits = 32,
> +	.val_bits = 32,
> +	.reg_stride = 4,
> +	.max_register = 0x16ac, /* end MIB - Port6 range */
> +	.rd_table = &qca8k_readable_table,
> +};
> +
> +static struct regmap_config qca8k_ipqess_psgmii_phy_regmap_config = {
> +	.name = "psgmii-phy",
> +	.reg_bits = 32,
> +	.val_bits = 32,
> +	.reg_stride = 4,
> +	.max_register = 0x7fc,
> +};
> +
> +static const struct qca8k_match_data ipqess = {
> +	.id = IPQESS_SWITCH_ID,
> +	.mib_count = QCA8K_QCA833X_MIB_COUNT,
> +};
> +
> +/* devlink */
> +
> +static const struct devlink_ops ipqess_devlink_ops = {
> +	/* no ops supported by this driver */
> +};
> +
> +static int ipqess_switch_devlink_alloc(struct ipqess_switch *sw)
> +{
> +	struct ipqess_devlink_priv *dl_priv;

ipqess_devlink_priv is not necessary I think, you can just alloc ipqess_switch using devlink_alloc.

> +	struct devlink *dl;
> +
> +	/* Add the switch to devlink before calling setup, so that setup can
> +	 * add dpipe tables
> +	 */
> +	dl = devlink_alloc(&ipqess_devlink_ops, sizeof(*dl_priv), sw->priv->dev);
> +	if (!dl)
> +		return -ENOMEM;
> +
> +	sw->devlink = dl;
> +
> +	dl_priv = devlink_priv(sw->devlink);
> +	dl_priv->sw = sw;
> +
> +	return 0;
> +}
> +
> +/* setup */
> +
> +unsigned int ipqess_switch_fastest_ageing_time(struct ipqess_switch *sw,
> +					       unsigned int ageing_time)
> +{
> +	struct ipqess_port *port;
> +	int i;
> +
> +	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> +		port = sw->port_list[i];
> +		if (port && port->ageing_time && port->ageing_time < ageing_time)
> +			ageing_time = port->ageing_time;
> +	}
> +
> +	return ageing_time;
> +}
> +
> +int ipqess_set_ageing_time(struct ipqess_switch *sw, unsigned int msecs)
> +{
> +	struct qca8k_priv *priv = sw->priv;
> +	unsigned int secs = msecs / 1000;
> +	u32 val;
> +
> +	/* AGE_TIME reg is set in 7s step */
> +	val = secs / 7;
> +
> +	/* Handle case with 0 as val to NOT disable
> +	 * learning
> +	 */
> +	if (!val)
> +		val = 1;
> +
> +	return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL,
> +				  QCA8K_ATU_AGE_TIME_MASK,
> +				  QCA8K_ATU_AGE_TIME(val));
> +}
> +
> +static struct qca8k_pcs *pcs_to_qca8k_pcs(struct phylink_pcs *pcs)
> +{
> +	return container_of(pcs, struct qca8k_pcs, pcs);
> +}
> +
> +static void ipqess_switch_pcs_get_state(struct phylink_pcs *pcs,
> +					struct phylink_link_state *state)
> +{
> +	struct qca8k_priv *priv = pcs_to_qca8k_pcs(pcs)->priv;
> +	int port = pcs_to_qca8k_pcs(pcs)->port;
> +	u32 reg;
> +	int ret;
> +
> +	ret = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port), &reg);
> +	if (ret < 0) {
> +		state->link = false;
> +		return;
> +	}
> +
> +	state->link = !!(reg & QCA8K_PORT_STATUS_LINK_UP);
> +	state->an_complete = state->link;
> +	state->duplex = (reg & QCA8K_PORT_STATUS_DUPLEX) ? DUPLEX_FULL :
> +							DUPLEX_HALF;
> +
> +	switch (reg & QCA8K_PORT_STATUS_SPEED) {
> +	case QCA8K_PORT_STATUS_SPEED_10:
> +		state->speed = SPEED_10;
> +		break;
> +	case QCA8K_PORT_STATUS_SPEED_100:
> +		state->speed = SPEED_100;
> +		break;
> +	case QCA8K_PORT_STATUS_SPEED_1000:
> +		state->speed = SPEED_1000;
> +		break;
> +	default:
> +		state->speed = SPEED_UNKNOWN;
> +		break;
> +	}
> +
> +	if (reg & QCA8K_PORT_STATUS_RXFLOW)
> +		state->pause |= MLO_PAUSE_RX;
> +	if (reg & QCA8K_PORT_STATUS_TXFLOW)
> +		state->pause |= MLO_PAUSE_TX;
> +}
> +
> +static int ipqess_switch_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
> +				    phy_interface_t interface,
> +				    const unsigned long *advertising,
> +				    bool permit_pause_to_mac)
> +{
> +	return 0;
> +}
> +
> +static void ipqess_switch_pcs_an_restart(struct phylink_pcs *pcs)
> +{
> +}
> +
> +static const struct phylink_pcs_ops qca8k_pcs_ops = {
> +	.pcs_get_state = ipqess_switch_pcs_get_state,
> +	.pcs_config = ipqess_switch_pcs_config,
> +	.pcs_an_restart = ipqess_switch_pcs_an_restart,
> +};
> +
> +static void ipqess_switch_setup_pcs(struct qca8k_priv *priv,
> +				    struct qca8k_pcs *qpcs,
> +				    int port_index)
> +{
> +	qpcs->pcs.ops = &qca8k_pcs_ops;
> +
> +	/* We don't have interrupts for link changes, so we need to poll */
> +	qpcs->pcs.poll = true;
> +	qpcs->priv = priv;
> +	qpcs->port = port_index;
> +}
> +
> +static int ipqess_switch_setup_port(struct ipqess_switch *sw, int port_index)
> +{
> +	struct qca8k_priv *priv = sw->priv;
> +	u32 mask = 0;
> +	int ret, i;
> +	u32 reg;
> +
> +	/* CPU port gets connected to all registered ports of the switch */
> +	if (port_index == IPQESS_SWITCH_CPU_PORT) {
> +		for (i = 1; i < IPQESS_SWITCH_MAX_PORTS; i++) > +			if (sw->port_list[i - 1])
> +				mask |= BIT(i);
> +		}
> +		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_index),
> +				QCA8K_PORT_LOOKUP_MEMBER, mask);
> +		if (ret)
> +			return ret;
> +		qca8k_read(priv, QCA8K_PORT_LOOKUP_CTRL(IPQESS_SWITCH_CPU_PORT), &reg);
> +
> +		/* Disable CPU ARP Auto-learning by default */
> +		ret = regmap_clear_bits(priv->regmap,
> +					QCA8K_PORT_LOOKUP_CTRL(port_index),
> +					QCA8K_PORT_LOOKUP_LEARN);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	/* Individual user ports get connected to CPU port only */
> +	if (port_index > 0 && sw->port_list[port_index - 1]) {
> +		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_index),
> +				QCA8K_PORT_LOOKUP_MEMBER,
> +				BIT(IPQESS_SWITCH_CPU_PORT));
> +		if (ret)
> +			return ret;
> +
> +		/* Enable ARP Auto-learning by default */
> +		ret = regmap_set_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(port_index),
> +				      QCA8K_PORT_LOOKUP_LEARN);
> +		if (ret)
> +			return ret;
> +
> +		/* For port based vlans to work we need to set the
> +		 * default egress vid
> +		 */
> +		ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port_index),
> +				QCA8K_EGREES_VLAN_PORT_MASK(port_index),
> +				QCA8K_EGREES_VLAN_PORT(port_index, QCA8K_PORT_VID_DEF));
> +		if (ret)
> +			return ret;
> +
> +		ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port_index),
> +				  QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) |
> +				  QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +int ipqess_switch_setup(struct ipqess_switch *sw)
> +{
> +	struct qca8k_priv *priv = sw->priv;
> +	int ret, i;
> +	u32 reg;
> +
> +	ipqess_switch_setup_pcs(priv, &priv->pcs_port_0, 0);
> +
> +	/* Enable CPU Port */
> +	ret = regmap_set_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
> +			      QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
> +	if (ret) {
> +		dev_err(priv->dev, "failed enabling CPU port");
> +		return ret;
> +	}
> +
> +	/* Enable MIB counters */
> +	ret = qca8k_mib_init(priv);
> +	if (ret)
> +		dev_warn(priv->dev, "MIB init failed");
> +
> +	/* Disable forwarding by default on all ports */
> +	for (i = 0; i < IPQESS_SWITCH_NUM_PORTS; i++) {
> +		ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
> +				QCA8K_PORT_LOOKUP_MEMBER, 0);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	/* Enable QCA header mode on the CPU port */
> +	ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(IPQESS_SWITCH_CPU_PORT),
> +			  FIELD_PREP(QCA8K_PORT_HDR_CTRL_TX_MASK, QCA8K_PORT_HDR_CTRL_ALL) |
> +			  FIELD_PREP(QCA8K_PORT_HDR_CTRL_RX_MASK, QCA8K_PORT_HDR_CTRL_ALL));
> +	if (ret) {
> +		dev_err(priv->dev, "failed enabling QCA header mode");
> +		return ret;
> +	}
> +
> +	/* Disable MAC by default on all ports */
> +	for (i = 0; i < IPQESS_SWITCH_NUM_PORTS; i++) {
> +		if (i > 0)
> +			qca8k_port_set_status(priv, i, 0);
> +	}
> +
> +	/* Forward all unknown frames to all ports */
> +	ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
> +			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK, 0x3f) |
> +			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK, 0x3f) |
> +			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK, 0x3f) |
> +			  FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK, 0x3f));
> +	if (ret) {
> +		pr_err("Error while disabling MAC and forwarding unknown frames %d\n",
> +		       ret);
> +		return ret;
> +	}
> +
> +	/* Setup connection between CPU port & user ports */
> +	for (i = 0; i < IPQESS_SWITCH_NUM_PORTS; i++) {
> +		ret = ipqess_switch_setup_port(sw, i);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	/* Setup our port MTUs to match power on defaults */
> +	ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN);
> +	if (ret)
> +		dev_warn(priv->dev, "failed setting MTU settings");
> +
> +	/* Flush the FDB table */
> +	qca8k_fdb_flush(priv);
> +
> +	if (ret < 0)
> +		goto devlink_free;
> +
> +	/* set Port0 status */
> +	reg  = QCA8K_PORT_STATUS_LINK_AUTO;
> +	reg |= QCA8K_PORT_STATUS_DUPLEX;
> +	reg |= QCA8K_PORT_STATUS_SPEED_1000;
> +	reg |= QCA8K_PORT_STATUS_RXFLOW;
> +	reg |= QCA8K_PORT_STATUS_TXFLOW;
> +	reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
> +	qca8k_write(priv, QCA8K_REG_PORT_STATUS(0), reg);
> +	sw->port0_enabled = true;
> +
> +	return 0;
> +
> +devlink_free:
> +	pr_err("qca_switch_setup error: %d\n", ret);
> +	return ret;
> +}
> +EXPORT_SYMBOL(ipqess_switch_setup);
> +
> +/* probe */
> +
> +static void ipqess_switch_psgmii_rst(struct ipqess_switch *sw)
> +{
> +	reset_control_assert(sw->psgmii_rst);
> +
> +	mdelay(10);
> +
> +	reset_control_deassert(sw->psgmii_rst);
> +
> +	mdelay(10);
> +}
> +
> +static int ipqess_switch_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct device_node *np = dev->of_node, *mdio_np, *psgmii_ethphy_np;
> +	struct device_node *ports, *port_np;
> +	struct ipqess_port *port = NULL;
> +	void __iomem *base, *psgmii;
> +	struct ipqess_switch *sw;
> +	struct qca8k_priv *priv;
> +	int ret;
> +	int i;

RFC

> +
> +	sw = devm_kzalloc(dev, sizeof(struct ipqess_switch), GFP_KERNEL);
> +	if (!sw)
> +		return -ENOMEM;
> +
> +	priv = devm_kzalloc(dev, sizeof(struct qca8k_priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	sw->priv = priv;
> +	sw->port0_enabled = false;
> +	priv->dev = dev;
> +	priv->info = &ipqess;
> +	priv->ds = NULL;
> +
> +	ports = of_get_child_by_name(np, "ports");
> +	if (!ports) {
> +		dev_err(dev, "no 'ports' attribute found\n");
> +		return -EINVAL;
> +	}
> +
> +	/* Start by setting up the register mapping */
> +	base = devm_platform_ioremap_resource_byname(pdev, "base");
> +	if (IS_ERR(base)) {
> +		dev_err(dev, "platform ioremap fail %li\n", PTR_ERR(base));
> +		return PTR_ERR(base);
> +	}
> +
> +	priv->regmap = devm_regmap_init_mmio(dev, base,
> +					     &qca8k_ipqess_regmap_config);
> +	if (IS_ERR(priv->regmap)) {
> +		ret = PTR_ERR(priv->regmap);
> +		dev_err(dev, "base regmap initialization failed, %d\n", ret);
> +		return ret;
> +	}
> +
> +	psgmii = devm_platform_ioremap_resource_byname(pdev, "psgmii_phy");
> +	if (IS_ERR(psgmii)) {

you can use @ret var here as well (like above)

> +		dev_err(dev, "platform ioremap psgmii fail %li\n", PTR_ERR(psgmii));
> +		return PTR_ERR(psgmii);
> +	}
> +
> +	priv->psgmii = devm_regmap_init_mmio(dev, psgmii,
> +					     &qca8k_ipqess_psgmii_phy_regmap_config);
> +	if (IS_ERR(priv->psgmii)) {
> +		ret = PTR_ERR(priv->psgmii);
> +		dev_err(dev, "PSGMII regmap initialization failed, %d\n", ret);
> +		return ret;
> +	}
> +
> +	mdio_np = of_parse_phandle(np, "mdio", 0);
> +	if (!mdio_np) {
> +		dev_err(dev, "unable to get MDIO bus phandle\n");
> +		of_node_put(mdio_np);
> +		return -EINVAL;
> +	}
> +
> +	priv->bus = of_mdio_find_bus(mdio_np);
> +	of_node_put(mdio_np);
> +	if (!priv->bus) {
> +		dev_err(dev, "unable to find MDIO bus\n");
> +		return -EPROBE_DEFER;
> +	}
> +
> +	psgmii_ethphy_np = of_parse_phandle(np, "psgmii-ethphy", 0);
> +	if (!psgmii_ethphy_np) {
> +		dev_warn(dev, "unable to get PSGMII eth PHY phandle\n");
> +		of_node_put(psgmii_ethphy_np);
> +	}
> +
> +	if (psgmii_ethphy_np) {
> +		priv->psgmii_ethphy = of_phy_find_device(psgmii_ethphy_np);
> +		of_node_put(psgmii_ethphy_np);
> +		if (!priv->psgmii_ethphy) {
> +			dev_err(dev, "unable to get PSGMII eth PHY\n");
> +			return -ENODEV;
> +		}
> +	}
> +
> +	/* If we don't reset the PSGMII here the switch id check will fail */
> +	sw->psgmii_rst = devm_reset_control_get(&pdev->dev, "psgmii_rst");
> +	if (IS_ERR(sw->psgmii_rst)) {
> +		ret = PTR_ERR(sw->psgmii_rst);
> +		dev_err(dev, "Unable to get PSGMII reset line: err %d\n", ret);
> +		return ret;
> +	}
> +
> +	ipqess_switch_psgmii_rst(sw);
> +
> +	/* Check the detected switch id */
> +	ret = qca8k_read_switch_id(sw->priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to read switch id! error %d\n", ret);
> +		return ret;
> +	}
> +
> +	priv->ds = NULL;
> +
> +	mutex_init(&sw->addr_lists_lock);
> +	INIT_LIST_HEAD(&sw->fdbs);
> +	INIT_LIST_HEAD(&sw->mdbs);
> +
> +	mutex_init(&priv->reg_mutex);
> +	platform_set_drvdata(pdev, sw);
> +
> +	ret = ipqess_switch_devlink_alloc(sw);
> +	if (ret)
> +		goto out_devlink;
> +
> +	devlink_register(sw->devlink);
> +
> +	/* Register switch front-facing ports */
> +	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++)
> +		sw->port_list[i] = NULL;
> +
> +	for_each_available_child_of_node(ports, port_np) {
> +		ret = ipqess_port_register(sw, port_np);
> +		if (ret) {
> +			pr_err("Failed to register ipqess port! error %d\n", ret);
> +			goto out_ports;
> +		}
> +	}
> +	if (!sw->napi_leader) {
> +		pr_err("No switch port registered as napi leader!\n");
> +		ret = -EINVAL;
> +		goto out_ports;
> +	}
> +
> +	ret = ipqess_edma_init(pdev, np);
> +	if (ret) {
> +		dev_err(dev, "Failed to initialize EDMA controller! error %d\n", ret);
> +		goto out_ports;
> +	}
> +
> +	ipqess_switch_setup(sw);
> +
> +	return 0;
> +
> +out_ports:
> +	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> +		port = sw->port_list[i];
> +		if (port)
> +			ipqess_port_unregister(port);
> +	}
> +out_devlink:
> +	devlink_free(sw->devlink);
> +	pr_err("%s failed with error %d\n", __func__, ret);
> +	return ret;
> +}
> +
> +static int
> +ipqess_switch_remove(struct platform_device *pdev)
> +{
> +	struct ipqess_switch *sw = platform_get_drvdata(pdev);
> +	struct qca8k_priv *priv = sw->priv;
> +	struct ipqess_port *port = NULL;
> +	int i;
> +
> +	if (!sw)
> +		return 0;
> +
> +	/* Release EDMA driver */
> +	ipqess_edma_uninit(sw->edma);
> +
> +	/* Disable all user ports */
> +	for (i = 1; i < QCA8K_NUM_PORTS; i++) {
> +		qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
> +			  QCA8K_PORT_LOOKUP_STATE_MASK,
> +			  QCA8K_PORT_LOOKUP_STATE_DISABLED);
> +			  qca8k_port_set_status(priv, i, 0);
> +			  priv->port_enabled_map &= ~BIT(i);

Wrong indentation

> +	}
> +
> +	/* Unregister user ports */
> +	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> +		port = sw->port_list[i];
> +		if (port)
> +			ipqess_port_unregister(port);
> +	}
> +
> +	devlink_unregister(sw->devlink);
> +	devlink_free(sw->devlink);
> +
> +	platform_set_drvdata(pdev, NULL);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id qca8k_ipqess_of_match[] = {
> +	{ .compatible = "qca,ipq4019-qca8337n", },
> +	{ /* sentinel */ },
> +};
> +
> +static struct platform_driver qca8k_ipqess_driver = {
> +	.probe = ipqess_switch_probe,
> +	.remove = ipqess_switch_remove,
> +	.driver = {
> +		.name = "ipqess",
> +		.of_match_table = qca8k_ipqess_of_match,
> +	},
> +};
> +
> +module_platform_driver(qca8k_ipqess_driver);
> +
> +MODULE_AUTHOR("Romain Gantois <romain.gantois at bootlin.org>");
> +MODULE_AUTHOR("Mathieu Olivari, John Crispin <john at phrozen.org>");
> +MODULE_AUTHOR("Gabor Juhos <j4g8y7 at gmail.com>, Robert Marko <robert.marko at sartura.hr>");
> +MODULE_DESCRIPTION("Qualcomm IPQ4019 Ethernet Switch Subsystem driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.h
> new file mode 100644
> index 000000000000..e86674c2947e
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.h
> @@ -0,0 +1,40 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR ISC */
> +
> +#ifndef IPQESS_SWITCH_H
> +#define IPQESS_SWITCH_H
> +
> +#include <linux/dsa/qca8k.h>
> +
> +#define IPQESS_SWITCH_MAX_PORTS       5
> +#define IPQESS_SWITCH_AGEING_TIME_MIN 7000
> +#define IPQESS_SWITCH_AGEING_TIME_MAX 458745000
> +#define IPQESS_SWITCH_CPU_PORT        0
> +#define IPQESS_SWITCH_NUM_PORTS       5
> +#define IPQESS_SWITCH_ID              0x14
> +
> +struct ipqess_switch {
> +	struct net_device *napi_leader;
> +	struct qca8k_priv *priv;
> +	struct ipqess_edma *edma;
> +	struct ipqess_port *port_list[IPQESS_SWITCH_MAX_PORTS];
> +	struct devlink *devlink;
> +	struct reset_control *psgmii_rst;
> +	bool port0_enabled;
> +
> +	/* List of MAC addresses that must be forwarded on the cpu port */
> +	struct mutex		addr_lists_lock;
> +	struct list_head	fdbs;
> +	struct list_head	mdbs;
> +};
> +
> +struct ipqess_devlink_priv {
> +	struct ipqess_switch *sw;
> +};
> +
> +unsigned int ipqess_switch_fastest_ageing_time(struct ipqess_switch *sw,
> +					       unsigned int ageing_time);
> +int ipqess_set_ageing_time(struct ipqess_switch *sw, unsigned int msecs);
> +
> +int ipqess_switch_setup(struct ipqess_switch *sw);
> +
> +#endif



More information about the linux-arm-kernel mailing list