[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <a0c09443-af32-4440-8439-ad187c21fd13@intel.com>
Date: Tue, 24 Oct 2023 13:44:01 +0200
From: Wojciech Drewek <wojciech.drewek@...el.com>
To: Romain Gantois <romain.gantois@...tlin.com>, <davem@...emloft.net>, "Rob
Herring" <robh+dt@...nel.org>, Krzysztof Kozlowski
<krzysztof.kozlowski+dt@...aro.org>
CC: Jakub Kicinski <kuba@...nel.org>, Eric Dumazet <edumazet@...gle.com>,
Paolo Abeni <pabeni@...hat.com>, <netdev@...r.kernel.org>,
<linux-kernel@...r.kernel.org>, <devicetree@...r.kernel.org>,
<thomas.petazzoni@...tlin.com>, Andrew Lunn <andrew@...n.ch>, "Florian
Fainelli" <f.fainelli@...il.com>, Heiner Kallweit <hkallweit1@...il.com>,
Russell King <linux@...linux.org.uk>, <linux-arm-kernel@...ts.infradead.org>,
Vladimir Oltean <vladimir.oltean@....com>, Luka Perkov
<luka.perkov@...tura.hr>, Robert Marko <robert.marko@...tura.hr>, Andy Gross
<agross@...nel.org>, Bjorn Andersson <andersson@...nel.org>, Konrad Dybcio
<konrad.dybcio@...ainline.org>, Maxime Chevallier
<maxime.chevallier@...tlin.com>
Subject: Re: [PATCH net-next 3/5] net: ipqess: introduce the Qualcomm IPQESS
driver
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@...tlin.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@...tlin.com>
> +L: netdev@...r.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@...tura.hr>
> M: Luka Perkov <luka.perkov@...tura.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, ®);
> + 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@...tlin.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), ®);
> + 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), ®);
> +
> + /* 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@...tlin.org>");
> +MODULE_AUTHOR("Mathieu Olivari, John Crispin <john@...ozen.org>");
> +MODULE_AUTHOR("Gabor Juhos <j4g8y7@...il.com>, Robert Marko <robert.marko@...tura.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
Powered by blists - more mailing lists