net: ocelot: replay switchdev events when joining bridge
[linux-2.6-microblaze.git] / drivers / net / ethernet / mscc / ocelot_net.c
index 42230f9..36f32a4 100644 (file)
 // SPDX-License-Identifier: (GPL-2.0 OR MIT)
 /* Microsemi Ocelot Switch driver
+ *
+ * This contains glue logic between the switchdev driver operations and the
+ * mscc_ocelot_switch_lib.
  *
  * Copyright (c) 2017, 2019 Microsemi Corporation
+ * Copyright 2020-2021 NXP Semiconductors
  */
 
 #include <linux/if_bridge.h>
+#include <net/pkt_cls.h>
 #include "ocelot.h"
 #include "ocelot_vcap.h"
 
+static struct ocelot *devlink_port_to_ocelot(struct devlink_port *dlp)
+{
+       return devlink_priv(dlp->devlink);
+}
+
+static int devlink_port_to_port(struct devlink_port *dlp)
+{
+       struct ocelot *ocelot = devlink_port_to_ocelot(dlp);
+
+       return dlp - ocelot->devlink_ports;
+}
+
+static int ocelot_devlink_sb_pool_get(struct devlink *dl,
+                                     unsigned int sb_index, u16 pool_index,
+                                     struct devlink_sb_pool_info *pool_info)
+{
+       struct ocelot *ocelot = devlink_priv(dl);
+
+       return ocelot_sb_pool_get(ocelot, sb_index, pool_index, pool_info);
+}
+
+static int ocelot_devlink_sb_pool_set(struct devlink *dl, unsigned int sb_index,
+                                     u16 pool_index, u32 size,
+                                     enum devlink_sb_threshold_type threshold_type,
+                                     struct netlink_ext_ack *extack)
+{
+       struct ocelot *ocelot = devlink_priv(dl);
+
+       return ocelot_sb_pool_set(ocelot, sb_index, pool_index, size,
+                                 threshold_type, extack);
+}
+
+static int ocelot_devlink_sb_port_pool_get(struct devlink_port *dlp,
+                                          unsigned int sb_index, u16 pool_index,
+                                          u32 *p_threshold)
+{
+       struct ocelot *ocelot = devlink_port_to_ocelot(dlp);
+       int port = devlink_port_to_port(dlp);
+
+       return ocelot_sb_port_pool_get(ocelot, port, sb_index, pool_index,
+                                      p_threshold);
+}
+
+static int ocelot_devlink_sb_port_pool_set(struct devlink_port *dlp,
+                                          unsigned int sb_index, u16 pool_index,
+                                          u32 threshold,
+                                          struct netlink_ext_ack *extack)
+{
+       struct ocelot *ocelot = devlink_port_to_ocelot(dlp);
+       int port = devlink_port_to_port(dlp);
+
+       return ocelot_sb_port_pool_set(ocelot, port, sb_index, pool_index,
+                                      threshold, extack);
+}
+
+static int
+ocelot_devlink_sb_tc_pool_bind_get(struct devlink_port *dlp,
+                                  unsigned int sb_index, u16 tc_index,
+                                  enum devlink_sb_pool_type pool_type,
+                                  u16 *p_pool_index, u32 *p_threshold)
+{
+       struct ocelot *ocelot = devlink_port_to_ocelot(dlp);
+       int port = devlink_port_to_port(dlp);
+
+       return ocelot_sb_tc_pool_bind_get(ocelot, port, sb_index, tc_index,
+                                         pool_type, p_pool_index,
+                                         p_threshold);
+}
+
+static int
+ocelot_devlink_sb_tc_pool_bind_set(struct devlink_port *dlp,
+                                  unsigned int sb_index, u16 tc_index,
+                                  enum devlink_sb_pool_type pool_type,
+                                  u16 pool_index, u32 threshold,
+                                  struct netlink_ext_ack *extack)
+{
+       struct ocelot *ocelot = devlink_port_to_ocelot(dlp);
+       int port = devlink_port_to_port(dlp);
+
+       return ocelot_sb_tc_pool_bind_set(ocelot, port, sb_index, tc_index,
+                                         pool_type, pool_index, threshold,
+                                         extack);
+}
+
+static int ocelot_devlink_sb_occ_snapshot(struct devlink *dl,
+                                         unsigned int sb_index)
+{
+       struct ocelot *ocelot = devlink_priv(dl);
+
+       return ocelot_sb_occ_snapshot(ocelot, sb_index);
+}
+
+static int ocelot_devlink_sb_occ_max_clear(struct devlink *dl,
+                                          unsigned int sb_index)
+{
+       struct ocelot *ocelot = devlink_priv(dl);
+
+       return ocelot_sb_occ_max_clear(ocelot, sb_index);
+}
+
+static int ocelot_devlink_sb_occ_port_pool_get(struct devlink_port *dlp,
+                                              unsigned int sb_index,
+                                              u16 pool_index, u32 *p_cur,
+                                              u32 *p_max)
+{
+       struct ocelot *ocelot = devlink_port_to_ocelot(dlp);
+       int port = devlink_port_to_port(dlp);
+
+       return ocelot_sb_occ_port_pool_get(ocelot, port, sb_index, pool_index,
+                                          p_cur, p_max);
+}
+
+static int
+ocelot_devlink_sb_occ_tc_port_bind_get(struct devlink_port *dlp,
+                                      unsigned int sb_index, u16 tc_index,
+                                      enum devlink_sb_pool_type pool_type,
+                                      u32 *p_cur, u32 *p_max)
+{
+       struct ocelot *ocelot = devlink_port_to_ocelot(dlp);
+       int port = devlink_port_to_port(dlp);
+
+       return ocelot_sb_occ_tc_port_bind_get(ocelot, port, sb_index,
+                                             tc_index, pool_type,
+                                             p_cur, p_max);
+}
+
+const struct devlink_ops ocelot_devlink_ops = {
+       .sb_pool_get                    = ocelot_devlink_sb_pool_get,
+       .sb_pool_set                    = ocelot_devlink_sb_pool_set,
+       .sb_port_pool_get               = ocelot_devlink_sb_port_pool_get,
+       .sb_port_pool_set               = ocelot_devlink_sb_port_pool_set,
+       .sb_tc_pool_bind_get            = ocelot_devlink_sb_tc_pool_bind_get,
+       .sb_tc_pool_bind_set            = ocelot_devlink_sb_tc_pool_bind_set,
+       .sb_occ_snapshot                = ocelot_devlink_sb_occ_snapshot,
+       .sb_occ_max_clear               = ocelot_devlink_sb_occ_max_clear,
+       .sb_occ_port_pool_get           = ocelot_devlink_sb_occ_port_pool_get,
+       .sb_occ_tc_port_bind_get        = ocelot_devlink_sb_occ_tc_port_bind_get,
+};
+
+int ocelot_port_devlink_init(struct ocelot *ocelot, int port,
+                            enum devlink_port_flavour flavour)
+{
+       struct devlink_port *dlp = &ocelot->devlink_ports[port];
+       int id_len = sizeof(ocelot->base_mac);
+       struct devlink *dl = ocelot->devlink;
+       struct devlink_port_attrs attrs = {};
+
+       memcpy(attrs.switch_id.id, &ocelot->base_mac, id_len);
+       attrs.switch_id.id_len = id_len;
+       attrs.phys.port_number = port;
+       attrs.flavour = flavour;
+
+       devlink_port_attrs_set(dlp, &attrs);
+
+       return devlink_port_register(dl, dlp, port);
+}
+
+void ocelot_port_devlink_teardown(struct ocelot *ocelot, int port)
+{
+       struct devlink_port *dlp = &ocelot->devlink_ports[port];
+
+       devlink_port_unregister(dlp);
+}
+
+static struct devlink_port *ocelot_get_devlink_port(struct net_device *dev)
+{
+       struct ocelot_port_private *priv = netdev_priv(dev);
+       struct ocelot *ocelot = priv->port.ocelot;
+       int port = priv->chip_port;
+
+       return &ocelot->devlink_ports[port];
+}
+
 int ocelot_setup_tc_cls_flower(struct ocelot_port_private *priv,
                               struct flow_cls_offload *f,
                               bool ingress)
@@ -73,6 +251,12 @@ static int ocelot_setup_tc_cls_matchall(struct ocelot_port_private *priv,
                        return -EEXIST;
                }
 
+               if (action->police.rate_pkt_ps) {
+                       NL_SET_ERR_MSG_MOD(extack,
+                                          "QoS offload not support packets per second");
+                       return -EOPNOTSUPP;
+               }
+
                pol.rate = (u32)div_u64(action->police.rate_bytes_ps, 1000) * 8;
                pol.burst = action->police.burst;
 
@@ -310,53 +494,20 @@ static int ocelot_port_stop(struct net_device *dev)
        return 0;
 }
 
-/* Generate the IFH for frame injection
- *
- * The IFH is a 128bit-value
- * bit 127: bypass the analyzer processing
- * bit 56-67: destination mask
- * bit 28-29: pop_cnt: 3 disables all rewriting of the frame
- * bit 20-27: cpu extraction queue mask
- * bit 16: tag type 0: C-tag, 1: S-tag
- * bit 0-11: VID
- */
-static int ocelot_gen_ifh(u32 *ifh, struct frame_info *info)
-{
-       ifh[0] = IFH_INJ_BYPASS | ((0x1ff & info->rew_op) << 21);
-       ifh[1] = (0xf00 & info->port) >> 8;
-       ifh[2] = (0xff & info->port) << 24;
-       ifh[3] = (info->tag_type << 16) | info->vid;
-
-       return 0;
-}
-
-static int ocelot_port_xmit(struct sk_buff *skb, struct net_device *dev)
+static netdev_tx_t ocelot_port_xmit(struct sk_buff *skb, struct net_device *dev)
 {
        struct ocelot_port_private *priv = netdev_priv(dev);
-       struct skb_shared_info *shinfo = skb_shinfo(skb);
        struct ocelot_port *ocelot_port = &priv->port;
        struct ocelot *ocelot = ocelot_port->ocelot;
-       u32 val, ifh[OCELOT_TAG_LEN / 4];
-       struct frame_info info = {};
-       u8 grp = 0; /* Send everything on CPU group 0 */
-       unsigned int i, count, last;
        int port = priv->chip_port;
+       u32 rew_op = 0;
 
-       val = ocelot_read(ocelot, QS_INJ_STATUS);
-       if (!(val & QS_INJ_STATUS_FIFO_RDY(BIT(grp))) ||
-           (val & QS_INJ_STATUS_WMARK_REACHED(BIT(grp))))
+       if (!ocelot_can_inject(ocelot, 0))
                return NETDEV_TX_BUSY;
 
-       ocelot_write_rix(ocelot, QS_INJ_CTRL_GAP_SIZE(1) |
-                        QS_INJ_CTRL_SOF, QS_INJ_CTRL, grp);
-
-       info.port = BIT(port);
-       info.tag_type = IFH_TAG_TYPE_C;
-       info.vid = skb_vlan_tag_get(skb);
-
        /* Check if timestamping is needed */
-       if (ocelot->ptp && (shinfo->tx_flags & SKBTX_HW_TSTAMP)) {
-               info.rew_op = ocelot_port->ptp_cmd;
+       if (ocelot->ptp && (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) {
+               rew_op = ocelot_port->ptp_cmd;
 
                if (ocelot_port->ptp_cmd == IFH_REW_OP_TWO_STEP_PTP) {
                        struct sk_buff *clone;
@@ -369,45 +520,11 @@ static int ocelot_port_xmit(struct sk_buff *skb, struct net_device *dev)
 
                        ocelot_port_add_txtstamp_skb(ocelot, port, clone);
 
-                       info.rew_op |= clone->cb[0] << 3;
+                       rew_op |= clone->cb[0] << 3;
                }
        }
 
-       if (ocelot->ptp && shinfo->tx_flags & SKBTX_HW_TSTAMP) {
-               info.rew_op = ocelot_port->ptp_cmd;
-               if (ocelot_port->ptp_cmd == IFH_REW_OP_TWO_STEP_PTP)
-                       info.rew_op |= skb->cb[0] << 3;
-       }
-
-       ocelot_gen_ifh(ifh, &info);
-
-       for (i = 0; i < OCELOT_TAG_LEN / 4; i++)
-               ocelot_write_rix(ocelot, (__force u32)cpu_to_be32(ifh[i]),
-                                QS_INJ_WR, grp);
-
-       count = (skb->len + 3) / 4;
-       last = skb->len % 4;
-       for (i = 0; i < count; i++)
-               ocelot_write_rix(ocelot, ((u32 *)skb->data)[i], QS_INJ_WR, grp);
-
-       /* Add padding */
-       while (i < (OCELOT_BUFFER_CELL_SZ / 4)) {
-               ocelot_write_rix(ocelot, 0, QS_INJ_WR, grp);
-               i++;
-       }
-
-       /* Indicate EOF and valid bytes in last word */
-       ocelot_write_rix(ocelot, QS_INJ_CTRL_GAP_SIZE(1) |
-                        QS_INJ_CTRL_VLD_BYTES(skb->len < OCELOT_BUFFER_CELL_SZ ? 0 : last) |
-                        QS_INJ_CTRL_EOF,
-                        QS_INJ_CTRL, grp);
-
-       /* Add dummy CRC */
-       ocelot_write_rix(ocelot, 0, QS_INJ_WR, grp);
-       skb_tx_timestamp(skb);
-
-       dev->stats.tx_packets++;
-       dev->stats.tx_bytes += skb->len;
+       ocelot_port_inject_frame(ocelot, port, 0, rew_op, skb);
 
        kfree_skb(skb);
 
@@ -457,7 +574,7 @@ static void ocelot_mact_work(struct work_struct *work)
                break;
        default:
                break;
-       };
+       }
 
        kfree(w);
 }
@@ -525,20 +642,6 @@ static void ocelot_set_rx_mode(struct net_device *dev)
        __dev_mc_sync(dev, ocelot_mc_sync, ocelot_mc_unsync);
 }
 
-static int ocelot_port_get_phys_port_name(struct net_device *dev,
-                                         char *buf, size_t len)
-{
-       struct ocelot_port_private *priv = netdev_priv(dev);
-       int port = priv->chip_port;
-       int ret;
-
-       ret = snprintf(buf, len, "p%d", port);
-       if (ret >= len)
-               return -EINVAL;
-
-       return 0;
-}
-
 static int ocelot_port_set_mac_address(struct net_device *dev, void *p)
 {
        struct ocelot_port_private *priv = netdev_priv(dev);
@@ -689,18 +792,6 @@ static int ocelot_set_features(struct net_device *dev,
        return 0;
 }
 
-static int ocelot_get_port_parent_id(struct net_device *dev,
-                                    struct netdev_phys_item_id *ppid)
-{
-       struct ocelot_port_private *priv = netdev_priv(dev);
-       struct ocelot *ocelot = priv->port.ocelot;
-
-       ppid->id_len = sizeof(ocelot->base_mac);
-       memcpy(&ppid->id, &ocelot->base_mac, ppid->id_len);
-
-       return 0;
-}
-
 static int ocelot_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
 {
        struct ocelot_port_private *priv = netdev_priv(dev);
@@ -727,7 +818,6 @@ static const struct net_device_ops ocelot_port_netdev_ops = {
        .ndo_stop                       = ocelot_port_stop,
        .ndo_start_xmit                 = ocelot_port_xmit,
        .ndo_set_rx_mode                = ocelot_set_rx_mode,
-       .ndo_get_phys_port_name         = ocelot_port_get_phys_port_name,
        .ndo_set_mac_address            = ocelot_port_set_mac_address,
        .ndo_get_stats64                = ocelot_get_stats64,
        .ndo_fdb_add                    = ocelot_port_fdb_add,
@@ -736,9 +826,9 @@ static const struct net_device_ops ocelot_port_netdev_ops = {
        .ndo_vlan_rx_add_vid            = ocelot_vlan_rx_add_vid,
        .ndo_vlan_rx_kill_vid           = ocelot_vlan_rx_kill_vid,
        .ndo_set_features               = ocelot_set_features,
-       .ndo_get_port_parent_id         = ocelot_get_port_parent_id,
        .ndo_setup_tc                   = ocelot_setup_tc,
        .ndo_do_ioctl                   = ocelot_ioctl,
+       .ndo_get_devlink_port           = ocelot_get_devlink_port,
 };
 
 struct net_device *ocelot_port_to_netdev(struct ocelot *ocelot, int port)
@@ -825,12 +915,8 @@ static const struct ethtool_ops ocelot_ethtool_ops = {
 };
 
 static void ocelot_port_attr_stp_state_set(struct ocelot *ocelot, int port,
-                                          struct switchdev_trans *trans,
                                           u8 state)
 {
-       if (switchdev_trans_ph_prepare(trans))
-               return;
-
        ocelot_bridge_stp_state_set(ocelot, port, state);
 }
 
@@ -859,7 +945,7 @@ static void ocelot_port_attr_mc_set(struct ocelot *ocelot, int port, bool mc)
 
 static int ocelot_port_attr_set(struct net_device *dev,
                                const struct switchdev_attr *attr,
-                               struct switchdev_trans *trans)
+                               struct netlink_ext_ack *extack)
 {
        struct ocelot_port_private *priv = netdev_priv(dev);
        struct ocelot *ocelot = priv->port.ocelot;
@@ -868,19 +954,24 @@ static int ocelot_port_attr_set(struct net_device *dev,
 
        switch (attr->id) {
        case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
-               ocelot_port_attr_stp_state_set(ocelot, port, trans,
-                                              attr->u.stp_state);
+               ocelot_port_attr_stp_state_set(ocelot, port, attr->u.stp_state);
                break;
        case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
                ocelot_port_attr_ageing_set(ocelot, port, attr->u.ageing_time);
                break;
        case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
-               ocelot_port_vlan_filtering(ocelot, port,
-                                          attr->u.vlan_filtering, trans);
+               ocelot_port_vlan_filtering(ocelot, port, attr->u.vlan_filtering);
                break;
        case SWITCHDEV_ATTR_ID_BRIDGE_MC_DISABLED:
                ocelot_port_attr_mc_set(ocelot, port, !attr->u.mc_disabled);
                break;
+       case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
+               err = ocelot_port_pre_bridge_flags(ocelot, port,
+                                                  attr->u.brport_flags);
+               break;
+       case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
+               ocelot_port_bridge_flags(ocelot, port, attr->u.brport_flags);
+               break;
        default:
                err = -EOPNOTSUPP;
                break;
@@ -890,73 +981,89 @@ static int ocelot_port_attr_set(struct net_device *dev,
 }
 
 static int ocelot_port_obj_add_vlan(struct net_device *dev,
-                                   const struct switchdev_obj_port_vlan *vlan,
-                                   struct switchdev_trans *trans)
+                                   const struct switchdev_obj_port_vlan *vlan)
 {
+       bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+       bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
        int ret;
-       u16 vid;
 
-       for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
-               bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
-               bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+       ret = ocelot_vlan_vid_prepare(dev, vlan->vid, pvid, untagged);
+       if (ret)
+               return ret;
 
-               if (switchdev_trans_ph_prepare(trans))
-                       ret = ocelot_vlan_vid_prepare(dev, vid, pvid,
-                                                     untagged);
-               else
-                       ret = ocelot_vlan_vid_add(dev, vid, pvid, untagged);
-               if (ret)
-                       return ret;
-       }
+       return ocelot_vlan_vid_add(dev, vlan->vid, pvid, untagged);
+}
 
-       return 0;
+static int ocelot_port_obj_add_mdb(struct net_device *dev,
+                                  const struct switchdev_obj_port_mdb *mdb)
+{
+       struct ocelot_port_private *priv = netdev_priv(dev);
+       struct ocelot_port *ocelot_port = &priv->port;
+       struct ocelot *ocelot = ocelot_port->ocelot;
+       int port = priv->chip_port;
+
+       return ocelot_port_mdb_add(ocelot, port, mdb);
 }
 
-static int ocelot_port_vlan_del_vlan(struct net_device *dev,
-                                    const struct switchdev_obj_port_vlan *vlan)
+static int ocelot_port_obj_del_mdb(struct net_device *dev,
+                                  const struct switchdev_obj_port_mdb *mdb)
 {
-       int ret;
-       u16 vid;
+       struct ocelot_port_private *priv = netdev_priv(dev);
+       struct ocelot_port *ocelot_port = &priv->port;
+       struct ocelot *ocelot = ocelot_port->ocelot;
+       int port = priv->chip_port;
 
-       for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
-               ret = ocelot_vlan_vid_del(dev, vid);
+       return ocelot_port_mdb_del(ocelot, port, mdb);
+}
 
-               if (ret)
-                       return ret;
-       }
+static int ocelot_port_obj_mrp_add(struct net_device *dev,
+                                  const struct switchdev_obj_mrp *mrp)
+{
+       struct ocelot_port_private *priv = netdev_priv(dev);
+       struct ocelot_port *ocelot_port = &priv->port;
+       struct ocelot *ocelot = ocelot_port->ocelot;
+       int port = priv->chip_port;
 
-       return 0;
+       return ocelot_mrp_add(ocelot, port, mrp);
 }
 
-static int ocelot_port_obj_add_mdb(struct net_device *dev,
-                                  const struct switchdev_obj_port_mdb *mdb,
-                                  struct switchdev_trans *trans)
+static int ocelot_port_obj_mrp_del(struct net_device *dev,
+                                  const struct switchdev_obj_mrp *mrp)
 {
        struct ocelot_port_private *priv = netdev_priv(dev);
        struct ocelot_port *ocelot_port = &priv->port;
        struct ocelot *ocelot = ocelot_port->ocelot;
        int port = priv->chip_port;
 
-       if (switchdev_trans_ph_prepare(trans))
-               return 0;
+       return ocelot_mrp_del(ocelot, port, mrp);
+}
 
-       return ocelot_port_mdb_add(ocelot, port, mdb);
+static int
+ocelot_port_obj_mrp_add_ring_role(struct net_device *dev,
+                                 const struct switchdev_obj_ring_role_mrp *mrp)
+{
+       struct ocelot_port_private *priv = netdev_priv(dev);
+       struct ocelot_port *ocelot_port = &priv->port;
+       struct ocelot *ocelot = ocelot_port->ocelot;
+       int port = priv->chip_port;
+
+       return ocelot_mrp_add_ring_role(ocelot, port, mrp);
 }
 
-static int ocelot_port_obj_del_mdb(struct net_device *dev,
-                                  const struct switchdev_obj_port_mdb *mdb)
+static int
+ocelot_port_obj_mrp_del_ring_role(struct net_device *dev,
+                                 const struct switchdev_obj_ring_role_mrp *mrp)
 {
        struct ocelot_port_private *priv = netdev_priv(dev);
        struct ocelot_port *ocelot_port = &priv->port;
        struct ocelot *ocelot = ocelot_port->ocelot;
        int port = priv->chip_port;
 
-       return ocelot_port_mdb_del(ocelot, port, mdb);
+       return ocelot_mrp_del_ring_role(ocelot, port, mrp);
 }
 
 static int ocelot_port_obj_add(struct net_device *dev,
                               const struct switchdev_obj *obj,
-                              struct switchdev_trans *trans,
                               struct netlink_ext_ack *extack)
 {
        int ret = 0;
@@ -964,12 +1071,17 @@ static int ocelot_port_obj_add(struct net_device *dev,
        switch (obj->id) {
        case SWITCHDEV_OBJ_ID_PORT_VLAN:
                ret = ocelot_port_obj_add_vlan(dev,
-                                              SWITCHDEV_OBJ_PORT_VLAN(obj),
-                                              trans);
+                                              SWITCHDEV_OBJ_PORT_VLAN(obj));
                break;
        case SWITCHDEV_OBJ_ID_PORT_MDB:
-               ret = ocelot_port_obj_add_mdb(dev, SWITCHDEV_OBJ_PORT_MDB(obj),
-                                             trans);
+               ret = ocelot_port_obj_add_mdb(dev, SWITCHDEV_OBJ_PORT_MDB(obj));
+               break;
+       case SWITCHDEV_OBJ_ID_MRP:
+               ret = ocelot_port_obj_mrp_add(dev, SWITCHDEV_OBJ_MRP(obj));
+               break;
+       case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
+               ret = ocelot_port_obj_mrp_add_ring_role(dev,
+                                                       SWITCHDEV_OBJ_RING_ROLE_MRP(obj));
                break;
        default:
                return -EOPNOTSUPP;
@@ -985,12 +1097,19 @@ static int ocelot_port_obj_del(struct net_device *dev,
 
        switch (obj->id) {
        case SWITCHDEV_OBJ_ID_PORT_VLAN:
-               ret = ocelot_port_vlan_del_vlan(dev,
-                                               SWITCHDEV_OBJ_PORT_VLAN(obj));
+               ret = ocelot_vlan_vid_del(dev,
+                                         SWITCHDEV_OBJ_PORT_VLAN(obj)->vid);
                break;
        case SWITCHDEV_OBJ_ID_PORT_MDB:
                ret = ocelot_port_obj_del_mdb(dev, SWITCHDEV_OBJ_PORT_MDB(obj));
                break;
+       case SWITCHDEV_OBJ_ID_MRP:
+               ret = ocelot_port_obj_mrp_del(dev, SWITCHDEV_OBJ_MRP(obj));
+               break;
+       case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
+               ret = ocelot_port_obj_mrp_del_ring_role(dev,
+                                                       SWITCHDEV_OBJ_RING_ROLE_MRP(obj));
+               break;
        default:
                return -EOPNOTSUPP;
        }
@@ -998,81 +1117,288 @@ static int ocelot_port_obj_del(struct net_device *dev,
        return ret;
 }
 
-static int ocelot_netdevice_port_event(struct net_device *dev,
-                                      unsigned long event,
-                                      struct netdev_notifier_changeupper_info *info)
+static void ocelot_inherit_brport_flags(struct ocelot *ocelot, int port,
+                                       struct net_device *brport_dev)
+{
+       struct switchdev_brport_flags flags = {0};
+       int flag;
+
+       flags.mask = BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD;
+
+       for_each_set_bit(flag, &flags.mask, 32)
+               if (br_port_flag_is_set(brport_dev, BIT(flag)))
+                       flags.val |= BIT(flag);
+
+       ocelot_port_bridge_flags(ocelot, port, flags);
+}
+
+static void ocelot_clear_brport_flags(struct ocelot *ocelot, int port)
+{
+       struct switchdev_brport_flags flags;
+
+       flags.mask = BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD;
+       flags.val = flags.mask & ~BR_LEARNING;
+
+       ocelot_port_bridge_flags(ocelot, port, flags);
+}
+
+static int ocelot_switchdev_sync(struct ocelot *ocelot, int port,
+                                struct net_device *brport_dev,
+                                struct net_device *bridge_dev,
+                                struct netlink_ext_ack *extack)
+{
+       clock_t ageing_time;
+       u8 stp_state;
+       int err;
+
+       ocelot_inherit_brport_flags(ocelot, port, brport_dev);
+
+       stp_state = br_port_get_stp_state(brport_dev);
+       ocelot_bridge_stp_state_set(ocelot, port, stp_state);
+
+       err = ocelot_port_vlan_filtering(ocelot, port,
+                                        br_vlan_enabled(bridge_dev));
+       if (err)
+               return err;
+
+       ageing_time = br_get_ageing_time(bridge_dev);
+       ocelot_port_attr_ageing_set(ocelot, port, ageing_time);
+
+       err = br_mdb_replay(bridge_dev, brport_dev,
+                           &ocelot_switchdev_blocking_nb, extack);
+       if (err && err != -EOPNOTSUPP)
+               return err;
+
+       err = br_fdb_replay(bridge_dev, brport_dev, &ocelot_switchdev_nb);
+       if (err)
+               return err;
+
+       err = br_vlan_replay(bridge_dev, brport_dev,
+                            &ocelot_switchdev_blocking_nb, extack);
+       if (err && err != -EOPNOTSUPP)
+               return err;
+
+       return 0;
+}
+
+static int ocelot_switchdev_unsync(struct ocelot *ocelot, int port)
+{
+       int err;
+
+       err = ocelot_port_vlan_filtering(ocelot, port, false);
+       if (err)
+               return err;
+
+       ocelot_clear_brport_flags(ocelot, port);
+
+       ocelot_bridge_stp_state_set(ocelot, port, BR_STATE_FORWARDING);
+
+       return 0;
+}
+
+static int ocelot_netdevice_bridge_join(struct net_device *dev,
+                                       struct net_device *brport_dev,
+                                       struct net_device *bridge,
+                                       struct netlink_ext_ack *extack)
 {
        struct ocelot_port_private *priv = netdev_priv(dev);
        struct ocelot_port *ocelot_port = &priv->port;
        struct ocelot *ocelot = ocelot_port->ocelot;
        int port = priv->chip_port;
-       int err = 0;
+       int err;
 
-       switch (event) {
-       case NETDEV_CHANGEUPPER:
-               if (netif_is_bridge_master(info->upper_dev)) {
-                       if (info->linking) {
-                               err = ocelot_port_bridge_join(ocelot, port,
-                                                             info->upper_dev);
-                       } else {
-                               err = ocelot_port_bridge_leave(ocelot, port,
-                                                              info->upper_dev);
-                       }
-               }
-               if (netif_is_lag_master(info->upper_dev)) {
-                       if (info->linking)
-                               err = ocelot_port_lag_join(ocelot, port,
-                                                          info->upper_dev);
-                       else
-                               ocelot_port_lag_leave(ocelot, port,
-                                                     info->upper_dev);
-               }
-               break;
-       default:
-               break;
+       ocelot_port_bridge_join(ocelot, port, bridge);
+
+       err = ocelot_switchdev_sync(ocelot, port, brport_dev, bridge, extack);
+       if (err)
+               goto err_switchdev_sync;
+
+       return 0;
+
+err_switchdev_sync:
+       ocelot_port_bridge_leave(ocelot, port, bridge);
+       return err;
+}
+
+static int ocelot_netdevice_bridge_leave(struct net_device *dev,
+                                        struct net_device *brport_dev,
+                                        struct net_device *bridge)
+{
+       struct ocelot_port_private *priv = netdev_priv(dev);
+       struct ocelot_port *ocelot_port = &priv->port;
+       struct ocelot *ocelot = ocelot_port->ocelot;
+       int port = priv->chip_port;
+       int err;
+
+       err = ocelot_switchdev_unsync(ocelot, port);
+       if (err)
+               return err;
+
+       ocelot_port_bridge_leave(ocelot, port, bridge);
+
+       return 0;
+}
+
+static int ocelot_netdevice_lag_join(struct net_device *dev,
+                                    struct net_device *bond,
+                                    struct netdev_lag_upper_info *info,
+                                    struct netlink_ext_ack *extack)
+{
+       struct ocelot_port_private *priv = netdev_priv(dev);
+       struct ocelot_port *ocelot_port = &priv->port;
+       struct ocelot *ocelot = ocelot_port->ocelot;
+       struct net_device *bridge_dev;
+       int port = priv->chip_port;
+       int err;
+
+       err = ocelot_port_lag_join(ocelot, port, bond, info);
+       if (err == -EOPNOTSUPP) {
+               NL_SET_ERR_MSG_MOD(extack, "Offloading not supported");
+               return 0;
        }
 
+       bridge_dev = netdev_master_upper_dev_get(bond);
+       if (!bridge_dev || !netif_is_bridge_master(bridge_dev))
+               return 0;
+
+       err = ocelot_netdevice_bridge_join(dev, bond, bridge_dev, extack);
+       if (err)
+               goto err_bridge_join;
+
+       return 0;
+
+err_bridge_join:
+       ocelot_port_lag_leave(ocelot, port, bond);
        return err;
 }
 
+static int ocelot_netdevice_lag_leave(struct net_device *dev,
+                                     struct net_device *bond)
+{
+       struct ocelot_port_private *priv = netdev_priv(dev);
+       struct ocelot_port *ocelot_port = &priv->port;
+       struct ocelot *ocelot = ocelot_port->ocelot;
+       struct net_device *bridge_dev;
+       int port = priv->chip_port;
+
+       ocelot_port_lag_leave(ocelot, port, bond);
+
+       bridge_dev = netdev_master_upper_dev_get(bond);
+       if (!bridge_dev || !netif_is_bridge_master(bridge_dev))
+               return 0;
+
+       return ocelot_netdevice_bridge_leave(dev, bond, bridge_dev);
+}
+
+static int ocelot_netdevice_changeupper(struct net_device *dev,
+                                       struct netdev_notifier_changeupper_info *info)
+{
+       struct netlink_ext_ack *extack;
+       int err = 0;
+
+       extack = netdev_notifier_info_to_extack(&info->info);
+
+       if (netif_is_bridge_master(info->upper_dev)) {
+               if (info->linking)
+                       err = ocelot_netdevice_bridge_join(dev, dev,
+                                                          info->upper_dev,
+                                                          extack);
+               else
+                       err = ocelot_netdevice_bridge_leave(dev, dev,
+                                                           info->upper_dev);
+       }
+       if (netif_is_lag_master(info->upper_dev)) {
+               if (info->linking)
+                       err = ocelot_netdevice_lag_join(dev, info->upper_dev,
+                                                       info->upper_info, extack);
+               else
+                       ocelot_netdevice_lag_leave(dev, info->upper_dev);
+       }
+
+       return notifier_from_errno(err);
+}
+
+/* Treat CHANGEUPPER events on an offloaded LAG as individual CHANGEUPPER
+ * events for the lower physical ports of the LAG.
+ * If the LAG upper isn't offloaded, ignore its CHANGEUPPER events.
+ * In case the LAG joined a bridge, notify that we are offloading it and can do
+ * forwarding in hardware towards it.
+ */
+static int
+ocelot_netdevice_lag_changeupper(struct net_device *dev,
+                                struct netdev_notifier_changeupper_info *info)
+{
+       struct net_device *lower;
+       struct list_head *iter;
+       int err = NOTIFY_DONE;
+
+       netdev_for_each_lower_dev(dev, lower, iter) {
+               struct ocelot_port_private *priv = netdev_priv(lower);
+               struct ocelot_port *ocelot_port = &priv->port;
+
+               if (ocelot_port->bond != dev)
+                       return NOTIFY_OK;
+
+               err = ocelot_netdevice_changeupper(lower, info);
+               if (err)
+                       return notifier_from_errno(err);
+       }
+
+       return NOTIFY_DONE;
+}
+
+static int
+ocelot_netdevice_changelowerstate(struct net_device *dev,
+                                 struct netdev_lag_lower_state_info *info)
+{
+       struct ocelot_port_private *priv = netdev_priv(dev);
+       bool is_active = info->link_up && info->tx_enabled;
+       struct ocelot_port *ocelot_port = &priv->port;
+       struct ocelot *ocelot = ocelot_port->ocelot;
+       int port = priv->chip_port;
+
+       if (!ocelot_port->bond)
+               return NOTIFY_DONE;
+
+       if (ocelot_port->lag_tx_active == is_active)
+               return NOTIFY_DONE;
+
+       ocelot_port_lag_change(ocelot, port, is_active);
+
+       return NOTIFY_OK;
+}
+
 static int ocelot_netdevice_event(struct notifier_block *unused,
                                  unsigned long event, void *ptr)
 {
-       struct netdev_notifier_changeupper_info *info = ptr;
        struct net_device *dev = netdev_notifier_info_to_dev(ptr);
-       int ret = 0;
 
-       if (event == NETDEV_PRECHANGEUPPER &&
-           ocelot_netdevice_dev_check(dev) &&
-           netif_is_lag_master(info->upper_dev)) {
-               struct netdev_lag_upper_info *lag_upper_info = info->upper_info;
-               struct netlink_ext_ack *extack;
+       switch (event) {
+       case NETDEV_CHANGEUPPER: {
+               struct netdev_notifier_changeupper_info *info = ptr;
 
-               if (lag_upper_info &&
-                   lag_upper_info->tx_type != NETDEV_LAG_TX_TYPE_HASH) {
-                       extack = netdev_notifier_info_to_extack(&info->info);
-                       NL_SET_ERR_MSG_MOD(extack, "LAG device using unsupported Tx type");
+               if (ocelot_netdevice_dev_check(dev))
+                       return ocelot_netdevice_changeupper(dev, info);
 
-                       ret = -EINVAL;
-                       goto notify;
-               }
+               if (netif_is_lag_master(dev))
+                       return ocelot_netdevice_lag_changeupper(dev, info);
+
+               break;
        }
+       case NETDEV_CHANGELOWERSTATE: {
+               struct netdev_notifier_changelowerstate_info *info = ptr;
 
-       if (netif_is_lag_master(dev)) {
-               struct net_device *slave;
-               struct list_head *iter;
+               if (!ocelot_netdevice_dev_check(dev))
+                       break;
 
-               netdev_for_each_lower_dev(dev, slave, iter) {
-                       ret = ocelot_netdevice_port_event(slave, event, info);
-                       if (ret)
-                               goto notify;
-               }
-       } else {
-               ret = ocelot_netdevice_port_event(dev, event, info);
+               return ocelot_netdevice_changelowerstate(dev,
+                                                        info->lower_state_info);
+       }
+       default:
+               break;
        }
 
-notify:
-       return notifier_from_errno(ret);
+       return NOTIFY_DONE;
 }
 
 struct notifier_block ocelot_netdevice_nb __read_mostly = {
@@ -1171,7 +1497,19 @@ int ocelot_probe_port(struct ocelot *ocelot, int port, struct regmap *target,
        if (err) {
                dev_err(ocelot->dev, "register_netdev failed\n");
                free_netdev(dev);
+               ocelot->ports[port] = NULL;
+               return err;
        }
 
-       return err;
+       return 0;
+}
+
+void ocelot_release_port(struct ocelot_port *ocelot_port)
+{
+       struct ocelot_port_private *priv = container_of(ocelot_port,
+                                               struct ocelot_port_private,
+                                               port);
+
+       unregister_netdev(priv->dev);
+       free_netdev(priv->dev);
 }